Profilowanie

20 marzec, 2012

 Gdy ostatnio z czystej ciekawości przeprowadziłem prosty test wydajności na pewnej aplikacji webowej, okazało się, że "coś się dzieje". Okazało się, że po kilku naprawdę bystrych enhancementach, słowo "bystry" nabrało zupełnie nowego znaczenia. Skoro "ab" daje wyniki rzędu 150 req/sek na stronie która nie robi wiele więcej niż wyrenderowanie w miarę prostej strony z kilku zagnieżdżonych szablonów Velocity to coś nie gra. Wynik zadowalający to 400req/sek. Do dzieła...

Niektórzy używają Netbeans czy też JMeter, ja skoro na codzień preferuję Eclipse to postanowiłem poszukać czegoś all-in-one. Wybór padł na TPTP. Do Eclipse Indigo musiałem doinstalować go ręcznie. Nie pamiętam na czym polegały problemy z podłączeniem się do embeded Agent Controller-a więc pobrałem pakiet TPTP All-in-one i uruchamiałem Agent Controller ręcznie. Gdy ten już działa możemy uruchomić aplikację którą chcemy profilować. Ważne są zmienne środowiskowe, czytanie dokumentacji (to mnie zgubiło) oraz (w moim przypadku) parametr do Mavena...

SET TPTP_AC_HOME=G:\tools\agentcontroller
SET JAVA_PROFILER_HOME=%TPTP_AC_HOME%\plugins\org.eclipse.tptp.javaprofiler
SET PATH=%JAVA_PROFILER_HOME%;%PATH%;%TPTP_AC_HOME%\bin
SET MAVEN_OPTS=-agentlib:JPIBootLoader=JPIAgent:server=enabled;CGProf
mvn jetty:run


Teraz można utworzyć nową konfigurację profilera, podpiąć się pod port Agent Controllera, wybrać wątek, który udostepnia interfejs profilera...

undefined

 ... i ponownie uruchomić "ab -n 1000 -c 10 xxxx" by po paru chwilach otrzymać statystykę najczęściej wykonywanych i najdłużej trwających metod...

undefined

Tak więc mamy narzędzie. Warto nauczyć się nie tyle co z niego korzystać a interpretować wyniki które daje.

Dwie aplikacje w jednym projekcie Qooxdoo

25 listopad, 2011

Przy odrobinie samozaparcia udało mi się stworzyć szablon projektu Qooxdoo, który wspiera dwie aplikacje w jednym projekcie. Chodziło o to by mieć dwie strony html z rożnymi aplikacjami (logowanie i strona aplikacji), więc żaden tam widzimiś a potrzeba chwili... ważne było by aplikacje korzystały ze wspólnych zasobów oraz jedna korzystała z kodu drugiej, nie wspomnę o tym by zachować ład i porządek w repozytorium.

Moje wskazówki:

1) skryptu generate.py nie modyfikujemy (nie ma takiej potrzeby)

2) tworzymy odrębne katalogi dla żródeł kodu, plików językowych i konfiguracji builda tj:

-- build
-- cfg-login
-- cfg-main
-- source
   `-- class-main
   `-- class-login
      |-- resource
      |-- login
      |-- main
      |-- script
   `-- translation-main
   `-- translation-login


3) plik manifestu (Manifest.json) MUSI być zawsze w tym samym katalogu co plik konfiguracji (config.json). Nie udało mi się zmusić qooxdoo do zmiany nazwy pliku manifestu co wymusiło niestety kolejne zmiany w konfiguracji. Umieszczamy je odpowiednio w folderach cfg-login i cfg-main

4) Manifest każdej z aplikacji musi deklarować odrębną nazwę aplikacji, a sekcja 'provides' deklarować odpowiednie ścieżki poprzedzone prefixem '..'

"provides" :
{
"namespace" : "openfileresa",
"encoding" : "utf-8",
"class" : "../source/class-login",
"resource" : "../source/resource",
"translation" : "../source/translation-login",
"type" : "application"
}


5) W pliku konfiguracyjnym należy predefiniować zadane 'build-files' by nadpisać property 'source' i ewentualnie wskazać właściwy plik html do skopiowania...

"build-files" :
{
  "copy-files" :
  {
    "files" :
    [
      "login.html"
    ],
    "source" : "../source"
  }
}

6) pozostaje tylko pamiętać by używając generatora podać ścieżkę do właściwego pliku konfiguracji; na przykład w zadaniu Ant-a:

<target name="compile-login-page">
<exec dir="." executable="${PYTHON_PATH}" logerror="true">
<arg value="${SOURCE_DIR}/generate.py" />
<arg value="-c" />
<arg value="cfg-login/config.json" />
<arg value="build" />
</exec>
</target>

Camel

20 czerwiec, 2011

Nie chodzi bynajmniej o moją druga połówkę lecz Camela, projekt typu lightweight ESB. W Camelu możemy zdefiniować ścieżki określające przepływ danych. Mało tego, możemy je po drodze agregować, skierować do różnych źródeł, skonwertować, procesować w dowolny sposób etc. Danymi mogą być pliki, encje, informacje z wielu źródeł typu kolejki JMS, bazy danych itd. Dokumentacja do Camela jest wyjątkowo rozbudowana i przejrzysta więc poprzestanę na tym prostym opisie a weźmiemy się za konkretny sample...


Załaduje proste dane z pliku CSV do bazy MySQL. Od początku wiedziałem, że Camel nic specjalnego tu nie pokaże, ale chodziło by sprawdzić że działa. Działa, ale z bólami. Wiem, że obecnie są problemy z alokacją pamięci przy ładowaniu naprawdę dużych plików, konfiguracja i przechwytywanie błedów nie jest tak elastyczne jak np. w MS Integration Services (ale IS to zupełnie inna bajka choćby ze względu na bulk inserty w Sql Server), ale nie takich błędów oczekiwałem.


Zdefiniowałem kontekst Camela wraz z ścieżką jak poniżej...

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" />
 <xsl:template match="@* | node()">
   <xsl:copy>
     <xsl:apply-templates select="@* | node()"/>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

Flow ścieżki jest prosty, mam źródło w postaci folderu w którym znajdują się pliki csv, skanowanie folderu odbywa się co 5 sekund. Każdy plik będzie po przetworzeniu usunięty (tak naprawdę przeniesiony). W trakcie przetwarzania każda linia będzie czytana i komponentem 'bindy' konwertowana do obiektu CSV, a konkretniej encji oznaczonej odpowiednimi annotacjami w moim wypadku jest to klasa tomekkup.camelcsv.model.FooCsvBean. Komponent zwróci nam obiekt typu List<Map<FooCsvBean,FooCsvBean>>. Magiczne i to w tym miejscu jest właśnie issue. Kolejne komponenty nie rozumieją takiego formatu danych i nie mają żadnego konwertera który by sobie z tym stanem poradził. Zatem komponentem 'split' będę iterował elementy i procesował procesorem entryResolvingProcessor, który jest zarejestrowany w kontekście jako obiekt klasy jak poniżej...

import org.apache.camel.Exchange;
import org.apache.camel.Processor;

public class CsvProcessor implements Processor {
@Override
public void process(Exchange exchange) throws Exception {
Map<Object, Object> body = (Map<Object, Object>) exchange.getIn().getBody();
exchange.getIn().setBody(body.values().iterator().next());
}
}

Procesor wyciąga z mapy elementy i podmienia jest we flow. W ten sposób kolejny komponent otrzyma obiekt który rozumie... kolejny czyli komponent JPA, który zapisze dane do bazy. No tak, brakuje jeszcze definicji klasy FooCsvBean ze wszystkimi mapowaniami !

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
import org.apache.camel.dataformat.bindy.annotation.DataField;

@CsvRecord(skipFirstLine = true, separator = ";", crlf="UNIX")
@Entity(name = "csvSourceBean")
public class CsvSourceBean {

@Id()
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

@Column(name = "code")
@DataField(pos = 1)
private String code;

@Column(name = "account")
@DataField(pos = 2)
private String account;

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

public String getAccount() {
return account;
}

public void setAccount(String account) {
this.account = account;
}

}
Home ← Starsze wpisy