Badanie wydajności repozytorium plików JackRabbit

post_img

Apache JackRabbit jest znaną i uznaną biblioteką implementującą standard JSR-170 czyli repozytorium treści. Potrafi przechowywać w hierarchiczny sposób z obsługą ustrukturyzowanych oraz nieustrukturyzowanych danych, z możliwością ich tekstowego przeszukiwania, zarządzania wersjami itp. Jednym z interfejsów dostępu do JackRabbit jest WebDAV. Celem tego artykułu jest pokazanie jak można przetestować wydajność podstawowych operacji zapisu i odczytu plików komunikując się z repozytorium poprzez interfejs WebDAV. Wykorzystam do tego niezastąpiony Apache JMeter.

Metodologia testu

Interfejs WebDAV oferuje szeroki zakres funkcjonalności. Na potrzeby testu wybrałem tylko kilku podstawowych metod: PUT (wysłanie pliku), GET (pobranie pliku), LIST (lista wszystkich plików w węźle) oraz DELETE (usunięcie pliku). Niestety, interfejs WebDAV jest rozszerzeniem standardu dlatego standardowe komponenty JMetera nie są w stanie wygenerować prawidłowych żądań. Dlatego utworzymy bibliotekę dostępu do wybranych metod WebDAV, a następnie będziemy wywoływać metody przy pomocy komponentu JMetera „Java Request”.

Uruchomienie JackRabbit

Zacznijmy od uruchomienia repozytorium. JackRabbit można uruchomić w różnych środowiskach i konfiguracjach. Wszystkie możliwości zaprezentowane są tutaj. Na potrzeby naszego testu uruchomimy JackRabbit jako moduł WAR na serwerze JBoss 5.1.  W tym celu ściągamy stabilną wersję aplikacji jackrabbit-webapp-X.X.X.war ze strony  http://jackrabbit.apache.org/downloads.html. Następnie za radą FAQ Jackrabbita musimy usunąć z niej biblioteki: WEB-INF\lib\xercesImpl-2.9.0.jar oraz WEB-INF\lib\xml-apis-1.3.04.jar. Wynika to z tego, że te biblioteki istnieją już w dystrybucji Jboss-a. Umieszczenie ich dodatkowo w aplikacji WAR prowadzi do konfliktu. Innym rozwiązaniem jest zmiana konfiguracji classloaderów w JBoss.

Po uruchomieniu jboss-a aplikacja JackRabbit jest dostępna na przykład pod adresem http:\\localhost:8080\jackrabbit. Domyślnie pod tym adresem dostępna jest strona z dokumentacją, opisem standardów, FAQ, tutorialami itp. Z innych ciekawych rzeczy mamy do dyspozycji browser, wyszukiwarkę treści oraz tzw: „Zasilacz treści” (ang. Populate).  Zasilenie polega na losowym wygenerowaniu i zapisaniu do repozytorium różnego rodzaju plików. Opcja przydatna przy przeprowadzaniu testów. My nie będziemy leniwi i sami zasilimy repozytorium własnymi danymi.

Projekt biblioteki dostępu do JackRabbit za pomocą WebDav

Api JackRabbita zawiera cały zestaw metod służących do wykonywania wszystkich dostępnych metod interfejsu WebDAV. Metody te są zawarte w pakiecie: “org.apache.jackrabbit.webdav.client.methods”. Przykładowy kod uruchomienia metody PUT może wyglądać następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package pl.atena.webdav;

import java.io.IOException;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.jackrabbit.webdav.client.methods.PutMethod;
import org.apache.log4j.Logger;

public class PutWebDav extends AbstractWebDav {
    private static Logger log = Logger.getLogger(PutWebDav.class);
   
    public PutWebDav() {}
   
    public PutWebDav(String webDavUrl) {
        super(webDavUrl);
    }
   
    public void putFile(final String fileName, final String destName) {
        byte[] bytes = readFile(fileName);
        putFile(bytes, destName);
    }
   
    public void putFile(final byte[] bytes, final String destName) {
        HttpClient client = initHttpClient();
        PutMethod method = new PutMethod(getWebDavUrl() + destName);
        RequestEntity requestEntity = new ByteArrayRequestEntity(bytes);
        method.setRequestEntity(requestEntity);
        HostConfiguration conf = new HostConfiguration();
        try {
            do {
                responseCode = client.executeMethod(conf, method);
                errorCounter--;
            } while (responseCode >= 400 && errorCounter > 0);
        } catch (IOException e) {
            log.error(e);
        }
    }
}

Warto zwrócić uwagę na pętlę do{… }while. Służy ona do zmniejszenia ryzyka wystąpienia błędu jednoczesnej modyfikacji węzła. Sytuacja taka wystąpi, gdy równolegle dwóch lub więcej klientów wykonuje operację dodawania/usuwanie/modyfikacji pliku w tym samym węźle. Jest to spowodowane tym, że podczas tych operacji wykonywane są również modyfikacje na węźle, co przy równoległej pracy prowadzi do błędu HTTP 409 (Konflikt). W celu rozwiązania tego problemu operację wykonuję w pętli. Jeśli za pierwszym razem wystąpił błąd, próbuję wykonać ją ponownie. Liczba prób nie może przekroczyć dziesięciu, po tym metoda kończy działanie i zwraca kod błędu.
Kod metody List:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package pl.atena.webdav;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.client.methods.DavMethod;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
import org.apache.log4j.Logger;

import pl.atena.webdav.utils.Utils;

/**
* Lista wszystkich plików w podanym węźle
*/

public class ListWebDav extends AbstractWebDav {

    private static Logger log = Logger.getLogger(ListWebDav.class);
   
    public ListWebDav() {}
   
    public ListWebDav(String webDavUrl) {
        super(webDavUrl);
    }
   
    public List<String> listFiles() {

        List<String> list = new ArrayList<String>();

        try {
            DavMethod pFind = new PropFindMethod(getWebDavUrl(),DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_1);
            HttpClient client = initHttpClient();

            responseCode = client.executeMethod(pFind);
            MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus();
            MultiStatusResponse[] responses = multiStatus.getResponses();
            MultiStatusResponse currResponse;

            for (int i = 0; i < responses.length; i++) {
                currResponse = responses[i];
                if ((currResponse.getHref().endsWith(".txt"))) {
                    list.add(Utils.getFileName(currResponse.getHref()));
                }
            }
        } catch (Exception e) {
            log.error(e);
        }
        Collections.sort(list);
        return list;
    }
}

W podobny sposób zaimplementowane są pozostałe metody Get i Delete.

Projekt biblioteki JMeter

Aby wykonywać żądania WebDAV, wykorzystamy komponent Java Request. Pozwala on na wykonywanie metod napisanych w języku Java. Będziemy wykonywać metody powyższego Api. Aby było to możliwe, należy utworzyć dodatkowe klasy testu JMeter, które rozszerzają interfejs „org.apache.jmeter.protocol.java.sampler.JavaSamplerClient”. Dla każdej z metod tworzymy klasę JMetera. Zawiera ona następujące metody:

  • getDefaultParameters – lista parametrów metody
  • runTest – ciało metody testującej
  • setupTest – przygotowanie testu (metoda wykonywana jako pierwsza)
  • teardownTest – zakończenie testu (metoda wykonywana jako ostatnia)

W metodzie runTest implementujemy uruchomienie akcji WebDAV.

Przykładowy kod klasy testującej metodę PUT w JMeter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package pl.atena.webdav.jmeter;

import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;

import pl.atena.webdav.PutWebDav;

/**
* Klasa JMeter-a potrzebna do uruchomienia metod interfejsu WebDAV
*/

public class PutWebDavJMeter extends AbstractJavaSamplerClient implements JMeterStatics {

    @Override
    public Arguments getDefaultParameters() {
        Arguments arg = new Arguments();
        arg.addArgument(FILE_NAME, "");
        arg.addArgument(DEST_NAME, "");
        arg.addArgument(WEB_DAV_URL, STATIC_WEB_DAV_URL);
        return arg;
    }

    @Override
    public SampleResult runTest(JavaSamplerContext arg0) {
       
        SampleResult result = new SampleResult();
        result.sampleStart();
        PutWebDav putWebDav = new PutWebDav(arg0.getParameter(WEB_DAV_URL));
        putWebDav.putFile(arg0.getParameter(FILE_NAME), arg0.getParameter(DEST_NAME));
        result.sampleEnd();
       
        result.setSuccessful(true);
        result.setResponseCodeOK();
       
        result.setResponseMessage("<response>" + putWebDav.getResponseCode() + "</response><fileName>" + arg0.getParameter(DEST_NAME) + "</fileName>");
        result.setSamplerData("Put source " + arg0.getParameter(FILE_NAME) + " as " + arg0.getParameter(DEST_NAME));
        result.setSampleLabel("PutWebDav");
        return result;
    }
}

W metodzie getDefaultParameters umieszczamy listę parametrów, jakie mają być dostępne do ustawienia z poziomu JMetera. Widok na listę parametrów zdefiniowanych w powyższym teście:

Analogicznie tworzymy klasy testowe dla pozostałych metody WebDAV.
Po skompilowaniu należy umieścić klasy java w pliku jar i skopiować go to katalogu JMetera: “<jmeter.home>/lib/ext”. Po zrestartowaniu JMetera oraz wybraniu komponentu “Java Request” pojawią się nowe opcje:

Skrypt testowy

Widok na hierarchie skryptu testowego:

Opis konfiguracji:
Zmienne globalne – zmienne wykorzystywane w różnych komponentach:

VAR_LICZBA_UZYTKOWNIKOW – liczba symulowanych równolegle pracujących użytkowników
VAR_LICZBA_POWTORZEN – liczba powtórzeń testu
VAR_LICZBA_KATALOGOW – liczba węzłów na których pracują użytkownicy
VAR_WEB_DAV_URL – adres WebDav

Java Request Put

sourceFileName – lokalizacja pliku źródłowego (w tym przypadku plik tekstowy o wielkości 256Kb)
destName – nazwa docelowa pliku w JackRabbit, funkcja ${__counter(false)} zwraca kolejną wartość, zaczynając od 1, parametr false oznacza, że licznik jest wspólny dla wszystkich użytkowników
webDavURL – adres serwera WebDav
Jako dziecko do tego komponentu dodany jest komponent „Regular Expression Extractor”. Wykorzystujemy go do odczytania nazwy, pod którą został zapisanego pliku w JackRabbit. Nazwę tę umieszczamy w odpowiedzi żądania w tagach <fileName>…</fileName> (kod do obejrzenia w klasie PutWebDavJMeter).

Java Request – List
Wylistowanie wszystkich plików z węzła podanego w parametrze webDavURL. Plik testowy został tak napisany, że zwraca nazwy plików w znacznikach <name>…</name>. Wyrażenie regularne wyciąga listę nazw plików z odpowiedzi Java Request i umieszcza w zmiennej VAR_CONTENT_NAME_IN.

ForEach Controller – Get
Odczytuje listę nazw plików ze zmiennej VAR_CONTENT_NAME_IN i iteruje po niej umieszczając konkretne nazwy plików w zmiennej VAR_CONTENT_NAME_OUT.

Java Request Get
Wywołuję metodę GET na pliku podanym w zmiennej destName:

Wykonanie testu JMeter

Tak przygotowany test umożliwia nam manipulowanie liczbą użytkowników, liczbą węzłów, do których zapisujemy pliki oraz rozmiarem pliku.

Skrypt uruchamiamy, wybierając opcję z menu Uruchom->Start, a następnie pozostaje nam już tylko cieszyć się napływającymi wynikami:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *