poniedziałek, 15 grudnia 2014

Java OQL

Nie będę się rozwodził czy i po co warto oglądać co znajduje się na stercie Java. W praktyce korzystam z 2 narzędzi


Daruje sobie instrukcję jak korzystać z MAT - tego narzędzia da się używać bez specjalnej znajomości i pozwala w wielu wypadkach pokazać szybciej problem z pamięcią. Właściwie na rzecz tego bajeru zarzuciłem korzystanie jvisualvm dostępnego w Java SE od wersji 1.6. Spotkałem się jednak z kilkoma przypadkami kiedy nie potrafiłem dokładnie zbadać zawartości kolekcji lub zbioru obiektów przy pomocy zapytania OQL z MAT (oczywiście też posiada) Na wstępie dodam że całość publikacji jest właśnie poświęcam właśnie takiemu sposobowi exploracji Heap-a.

Czego używać do przeglądania i analizy?

  • Zasadnicza zasada OQL dla MAT to nie ten sam co dla jvisualvm. 
  • MAT pod wieloma względami jest lepszy i szybszy niż jvisualvm
  • jvisualvm ma OQL, który działa wolno jednak posiada porządne wsparcie dla wyrażeń javascript i tego właśnie mi brakuje (albo nie znam jak to użyć) w MAT
Przejrzałem sporo postów na temat tego jak ludzie sobie radzą z OQL. Nie ma jakiejś rewelacji temat jest niszowy w niewielu miejscach można znaleźć pasjonatów, którzy wiedzą co się dzieje w środku. Ci właśnie używają OQL.

Dokumentacja OQL jvisualvm

Należy dokumentację traktować jak reference. W moim odczuciu pokazane przypadki użycia nie pokazuja tego że to jest JavaScript i co można tu zrobić. Przykłady koncentrują się na zapytaniu. Projekt można znaleźć w http://visualvm.java.net/ Aplikacja jednak jest bardzo podobna do standardowej dołączanej do java sun jdk1.7\bin\jvisualvm.exe http://visualvm.java.net/oqlhelp.html Podczas przeglądania znalazłem bardziej użyteczną dokumentację https://blogs.oracle.com/poonam/resource/OQL.htm

Przykłady użycia

Trywialne przypadki opisane w dokumentacji nie oddają użyteczności narzędzia. Lista wszystkich obiektów to za mało aby coś wiedzieć. Ale od tego się wszystko zaczyna trzeba mieć jakąś kolekcje którą chcemy explorować i filtrować.

Listowanie obiektów jakiejś klasy

select x from java.util.HashMap$Entry x
Zamiast tego można zrobić
select można się posłużyć heap.objects
heap.objects("java.util.HashMap$Entry")

Listowanie String do pliku

Dla mnie osobiście to najbardziej użyteczny kawałek.
Podam klika przypadków kiedy dostałem to co chciałem zobaczyć.
Przejrzeć to w MAT nie sposób, z poziomu opcji jest dostępna opcja copy i ona pozwala też exportować dane do pliku, ale nie przypadła mi do gustu. Dane nie są zapisywane w takim formacie który mnie interesuje.
Właśnie w jvisualvm znalazłem to co szukałem!
map(heap.objects("java.lang.String"),function(it) {
 return null != it ? it.toString() :"null";
});
var m = map(heap.objects("java.lang.String"),function(it) {
 return null != it ? it.toString() :"null";
});
var out = new java.io.BufferedWriter(
new java.io.FileWriter("c:/temp/result14.txt", true)
);
var i=0;
out.write("id|wartosc\n");
while(m.hasMoreElements()) {
 var e=m.nextElement();
 out.write(i+"|" + e.replace("\n"," ") + "\n");
 out.flush();
 i++;
}
out.close();

W ten sposob do pliku mogę zapisać nawet gigabajty a potem w zewnętrznym sofcie sobie to policzyć (zagregować jak chce). OQL jest specyficzny i na razie te narzędzia są średnio wydajne.

Przeglądanie MapEntry

Kolejny przykład w którym dane z kolekcji lądują do pliku.
var m = map(filter(heap.objects("java.util.HashMap$Entry"), 
 'null != it.key'),function(it) {
 return {
  k:it.key.toString(), 
  vc:(it.value == null? "null": classof(it.value).name), 
  v:(it.value != null?it.value.toString():"null")
 };
});
var out = new java.io.BufferedWriter(
 new java.io.FileWriter("c:/temp/result13.txt", true)
 );
var i=0;
 out.write("id|klucz|klasa|wartosc\n");
while(m.hasMoreElements()) {
 var e=m.nextElement();
 out.write(i+"|" + e.k + "|" + e.vc + "|" + e.v + "\n");
 out.flush();
 i++;
}
out.close();
Można wyeksportować elementy mapy do postacji id|klucz|klasa|wartosc

Obiekt po ID

Pobranie identyfikator obiektu
select objectid(o) from java.lang.Object o
Odnalezienie obiektu po id
heap.findObject(32402378344)
Dlaczego to może być użyteczne? Dlatego że gdy już mamy element z kolekcji zawsze możemy się do niego ponownie odwołać.

Dostęp do atrybutów obiektu

Nic prostszego. Wszystko to widać na bieżąco podczas oglądania obiektów.
heap.findObject(32402378344).attributes.table

Listowanie elementów mapy

Normalnie pokazują się tylko wybrane pozycje z kolekcji. Gdy chcemy coś zobaczyć trzeba dodać do zapytania coś co pozwoli aby pokazało się te co chcemy... ale wtedy zapytanie musi wszystko przemielić a to trawa strasznie długo. Ja potrzebowałem przejrzeć mapę która miała 570000 pozycji. Nie udało mi się doczekać na to z OQL-a bezpośrednio. Tym sposobem no problem (oczywiście widać na bieżąco co leci do pliku)
var t = heap.findObject(32402378344).attributes.table;
var size1 = heap.findObject(32402378344).attributes.size;

var out = new java.io.BufferedWriter(
 new java.io.FileWriter(
  "c:\temp\file_03.txt", true));
 out.write("nr|object");
for (var i=0; i<size1; i++) 
{
  var e = t[i];
  if (e==null) continue;
  var k=e.key;
  var v=e.value;
  if (k==null) {k="";} 
   else {k = k.toString();};
  if (v==null) {v="";}
   else {v = v.toString();};
  out.write(i+"|"+ k.toString().replace("\n"," ") 
    + "|" + v.toString().replace("\n"," ") +"\\n");
  out.flush();
}
out.close();

// lista contextow aplikacji
map(heap.objects("org.apache.catalina.loader.WebappClassLoader"),function(it) {
 return null != it ? it.canonicalLoaderDir.toString() :"null";
});




W Przypadku MAT
Zwracane obiekty z zapytania są określonego typu i można na ich rzecz również wywoływać metody
http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Ftasks%2Fqueryingheapobjects.html

SELECT s.getClazz() FROM INSTANCEOF pl.kamsoft.common.monitor.KamMonitors s

SELECT s.resolveValue("timeMonitors") FROM INSTANCEOF pl.kamsoft.common.monitor.KamMonitors s

piątek, 11 kwietnia 2014

Zabbix - Openfire monitoring

Po dłuższym okresie użytkowania aplikacji Openfire, okazało się że serwer na którym jest uruchomiona miewa mocne zadyszki procesora i na maszynie wirtualnej powoduje zamieszanie. Aplikacji sam nie pisałem. Dostałem do obejrzenia tzw. "Czarną skrzynkę".

Od jakiegoś czasu próbuje zbierać takie historyjki. Nawiązując więc do wcześniejszego http://kczerw.blogspot.com/2014/01/zabbix-daje-rade.html postaram się pokazać nieco szczegółów tylko pod kątem Openfire i Zabbix.

System aktualnie był monitorowany tylko na poziomie Linux. Nieregularnie, ale zazwyczaj w godzinach porannych, można było obserwować duże obciążenie procesora. Przypadkiem po zalogowaniu do konsoli ssh udało się złapać że to jest proces openfire.

Po zalogowaniu do konsoli administracyjnej Openfire nie zastałem zbyt wiele. Wersja prehistoryczna 3.6.4. w oryginale nie miała statystyk. Jedyne co było widać do objawy na postawie systemu Linux.
Pierwsze co zrobiłem to rozszerzyłem statystyki o monitorowanie socketów.
Z tych gniazdek wyszła pierwsza relacja która pokazała, że jest wyjątkowo dużo połączeń ESTABILISHED. Zaskoczyło mnie to, że jest ich znacznie więcej niż samych userów.

Openfire Linux socket


Postanowiłem poszukać źródeł do Openfire. W przypadku tej wersji nie było łatwo... w końcu to prawie zabytek. Udało się! Nawet takie starocie mają na serwerze. 

http://download.igniterealtime.org/openfire/openfire_3_6_4.tar.gz
http://download.igniterealtime.org/openfire/openfire_src_3_6_4.zip

Zabrałem się za oglądanie źródeł dla openfire.

W pierwszej kolejności dla tej wersji skompilowałem wszystko co się dało. Teraz już miałem to co ważne - pluginy do monitorowania.
Nie pozostało nic jak przymierzyć się do uruchomienia dodatkowych elementów które pokazują statystyki tego co się dzieje w środku. Po krótkich poszukiwaniach okazało się, że dodatkowe informacje można uzyskać na podstawie pluginów. Całkiem ciekawy okazał się plugin "Monitoring Service".

Czysta instalacja Openfire (bez dodatkowych pluginów) nie pokazuje zbyt wiele co się daje w aplikacji.
Wykonane modyfikacje dotyczyły pluginów do autoryzacji i grup. W największym skrócie powodowały, że struktura firmy w postaci wydziałów (zapisana w bazie) jest odwzorowana na grupy w Openfire. Po rewizji kodu pluginu okazuje się, że również zmienia się sposób informowania o widoczności. W ramach firmy przyjęto, że każda osoba widzi nawzajem zmiany statusu w komunikatorze.

Po wykonaniu wywiadu i przejrzeniu bazy dowiedziałem się że mamy około 400 userów, ale w praktyce korzysta około 200 osób.

I na tym skończyła się moja wiedza, reszty mogłem się domyślać, ale nie potrafiłem tego potwierdzić.
Moja hipoteza już na wstępie zakłada, że przyczyna dużego obciążenia procesora jest związana z podłączaniem (logowaniem) nowych operatorów. W dużej mierze można to było potwierdzić. Po wykonaniu restartu openfire zjawisko się również mocno nasilało.

Linux CPU Usage

Linux CPU Usage no iddle

Linux network traffic

Linux CPU Load


Kontakt z autorem pluginów niewiele dał. Ogólnie aplikacja jest w porządku działa - trzeba dać mocniejszą maszynę. Taka argumentacja mnie specjalnie nie przekonała, a co jak dodamy 100 userów?

Postanowiłem podłączyć do aplikacji monitor Java. Generalnie do aplikacji Java gdzie mogę to pcham zapcat-a. Jest znacznie prostszy we współpracy z zabbix niż natywny connector JMX w zabbix.
Openfire korzysta z Tanuki wrapper. Nie mam prostego sposobu na wpięcie się do tego wrapera aby wystartować proces zapcat-a. Tu można to prościej załatwić. Zapcat ma plugin który można wgrać do openfire.

Obserwowałem obrazki z monitora Linuxa i ze smutkiem spowodowany bezsilnością. Prawie pewne że kolejny dzień rano znów przypali się procesor w wirtualce.

Co do monitorowania Java nie można mieć zastrzeżeń. Ilość zjadanej pamięci jest spora ale nie dramatyczna. Nie widać też jakiś jawnych wycieków.





Spróbowałem podglądać wątki przy pomocy jconsole oraz jvisualvm. Niestety rezultaty nie były zbyt odkrywcze. Nawet załączenie monitorowania wywoływanych metod nie rzuciło światła na to co jest przyczyną obciążenia. Próbowałem wykorzystać sampler aby sprawdzić która z metod jest najbardziej kosztowana.

jvisualm openfire sampler snapshot

jvisualm openfire sampler
Ciągle rosła wartość czasu przetwarzania NamePreservingRunnable.run(). Po sprawdzeniu kodu tej klasy okazuje się, że to tylko wraper do uruchomienia wątku (na podstawie tego czegoś nie wiadomo co ten wątek robi).

Znaczna aktywność bazy danych w tym momencie skłoniła mnie do sprawdzenia ilości wywołań metod związanych z operacjami na bazie danych autoryzacji. Zrobiłem to w prosty sposób grepując loga.
Moje podejrzenia się potwierdziły. Ilość operacji była ogromna. Najbardziej dawał popalić plugin dostarczający informację o userach. Rekordowa była metoda

public class XXXUserProvider implements  UserProvider... {
 public Collection<String> getUsernames() {
 ...

Bardzo mnie to zastanawiało dlaczego operacje na bazie obciążają procesor. Normalnie to nie do pomyślenia, przecież wtedy procesor czeka na dostarczenie danych, to baza co najwyżej mogła być obciążona (ale to zupełnie inny system w tym wypadku iSeries), jednak baza dawała radę. Zatem to niekoniecznie jest przyczyną całego problemu.

Trochę światła się pojawiło po zainstalowaniu pluginu "Monitoring Service"
Pokazuje on 3 ważne parametry:

  • liczba userów, 
  • aktywne konwersacje, 
  • liczba pakietów na minutę


Liczba pakietów które się pojawiają po restarcie Openfire sporo daje do myślenia. Z dużym prawdopodobieństwem można powiedzieć, że są to pakiety z informacją o widoczności. 
Przy założeniu że 1 klient jest źródłem powiadomienia do wszystkich innych mamy 1 -> 200 a klientów jest 200 - wiec mamy 200 * 200 co daje 40 000 pakietów. A ponieważ nie jest to tylko ten pakiet wiec rachunek w miarę się zgadza. Model który został przyjęty przy definiowaniu widoczności jest na pewno kosztowny przy masowej zmianie widoczności operatorów. 

Oglądając wykresy statystyk openfire na bierząco nie miałem jakiś specjalnych zastrzeżeń. Gdy chciałem sprawdzić stan który był wczoraj, albo kilka dni wcześnie było kiepsko. Dane zostały uśrednione. 
Plugin "Monitoring Service" w tej wersji trochę mnie zawiódł. Niestety nie podłącza danych które zbiera do JMX. Pozostaje na początek zatem tylko konsola admina do oglądania tego co się dzieje.

Obejrzałem również wartości dla cache Openfire. Tu po chwili obserwacji okazało się że wartość cache dla "Roster" zdecydowanie była zbyt mała. Zwiększyłem ją. I tu pojawił się skok. Liczba wywołań metody  getUsernames() dla UserProvider-a kolosalnie spadła. To mnie przekonało o znaczeniu cache. Niemniej jednak nie spowodowało to zmniejszenia zużycia procesora w jakiś znaczący sposób. Podobnie miała się sprawa w przypadku cache "VCard"

Ponieważ chciałem mieć te elementy w jednym słusznym miejscu zabrałem się do przeróbki pluginu zabbix dla Openfire. Postanowiłem dodać do JMX elementy które są widoczne w konsoli administracyjnej Openfire po dodaniu pluginu "Monitoring".

Przyjąłem sobie założenie aby nie zepsuć aktualnego pluginu. Czyli jeżeli nie ma zainstalowanego pluginu "Monitoring" to po prostu nie mam drzewka ze statystyka w JMX.

Do w/w elementów można się dostać w wykorzystując wymienione poniżej klasy:

XMPPServer xmppServer = XMPPServer.getInstance();
org.jivesoftware.openfire.plugin.MonitoringPlugin
org.jivesoftware.openfire.archive.ConversationManager
org.jivesoftware.openfire.reporting.stats.StatsViewer
org.jivesoftware.openfire.reporting.graph.GraphEngine
org.jivesoftware.openfire.reporting.stats.StatsAction

Postaram się udostępnić zmodyfikowane źródła pluginu zapcat-openfire oraz template dla Zabbix.

Rezultat jest wystarczający. W drzewku JMX pojawiają się pozycje openfire/session/cache oraz openfire/session/stats
openfire-zabbix plugin JMX tree (cache)

openfire-zabbix plugin JMX tree (stats)

Teraz już wystarczy przygotować template dla Zabbix.

openfire-zapcat zabbix cache roster

openfire-zapcat zabbix cache routing-user-session

openfire-zapcat zabbix cache vcard

openfire-zapcat zabbix conversation

openfire-zapcat zabbix pocket-count

openfire-zapcat zabbix server-bytes

openfire-zapcat zabbix session
Teraz może pora pochwalić się czy udało się zmienić tą maszynę z piekarnika z powrotem w normalnie pracujący system. Okazało się że przyczyną jest trywialny problem. Na dysku znajdował się katalog, który zawierał 500 000 plików. Dostęp do tego katalogu (inicjowany przez zupełnie inną aplikację) pochłaniał takie zasoby procesora. Ale skutek uboczny również przymulał openfire.
W takim modelu powiadamiania jaki mamy wybrany nie da się pewnie zrealizować tego bez obciążenia procesora. Można wykonać dokładniejszą eksploracje mechanizmu komunikacji Openfire.

W tym jednak przypadku ja postawiłem sobie za cel tylko zrobienie statystyk w Zabbix dla Openfire.

środa, 5 marca 2014

Zabbix - Interface WEB

Modyfikacje kodu interface WEB

Już od dawna przymierzałem się do drobnych modyfikacji interface webowego Zabbix. Za każdym razem odstraszało mnie przygotowanie środowiska do pracy z PHP. 

Z tego co widzę projekt się nadal rozwija, jednak przy każdych zmianach mam wątpliwości czy aby na pewno słuszny kierunek to obiera.

Na początek powiem, że ogólna koncepcja nie jest zła i dość daleko zaszła. Brakuje zdecydowanie estetycznego dopracowania i stabilności (szczególnie w interface od strony użytkownika). Nie będę sobie zawracał teraz głowy gdzie i kiedy się sypie. W wielu miejscach brakuje prostych filtrów. Przy dużej ilości elementów typu Template, Screen bieda się odnaleźć - właśnie tam nie ma filtrów. Mam wątpliwości co do użyteczności takiego czegoś jak makro.

Takie właśnie drobne rzeczy chciałem sprawdzić czy da się poprawić.

Da się!

Ale o zgrozo zajrzałem w jaki sposób jest budowana formatka i po prostu wolna ameryka!

Wziąłem sobie na pulpit screenconf.php i wzorując się na hosts.php próbowałem go wzbogacić o filtr.

Postaram się opisać jak to zrobiłem i opisać ogólną koncepcję, która jakoś tam się rysuje.

Gdzie sesja

Nie jest to dziwne... w java - Servlet itp takie coś jest jako obiekt. Tu na próżno szukać. Z tego co widzę to do przechowywania takich rzeczy jest CProfile
// aktualizacja wartości w profilu
CProfile::update('web.template.filter_template', $_REQUEST['filter_template'], PROFILE_TYPE_STR);

// pobranie wartości z profilu
CProfile::get('web.template.filter_template')

Filtrowanie parametrów wejściowych

Podejście które autorzy zastosowali polega na przepuszczeniu przez $_REQUEST tylko zadeklarowanych parametrów. Już na wstępie jest robiona walidacja dopuszczalnych wartości w oparciu o tablicę $fields.
Bardzo niewygodne podejście bo już na samym wstępie trzeba wiedzieć jakie będą dopuszczalne parametry i ich typy. Wymaga to wielokrotnej i rozproszonej deklaracji. W pierwszej kolejności na $fields, potem w przepisywaniu parametrów do określonych kolekcji takich jak CProfile reprezentujących profil, czy pola.
Trzeba to mieć na uwadze. Ja się przejechałem nie mogłem dotrzeć dlaczego pomimo tego, że parametry się przesyłają nie widać ich w $_REQUEST. Załatwiała mnie check_fields($fields). Właśnie ta funkcja waliduje co może być w $_REQUEST.

Template

Są ... ale nie jakieś tam cudo. Po prostu jest to taki sam plik php jak główny powiedzmy kontroler. Może różnica jest umowna że w tym template powinniśmy korzystać z $data do dekorowania zawartości.

// render view
$screenView = new CView('configuration.screen.list', $data);
$screenView->render();
$screenView->show();

Jak widać żadnych sensacji. Pliki są czytane z include/views w tym wypadku będzie to

include/views/configuration.screen.list.php

Renderowanie filtrów i elementów

Takie podejście jak tu już się spotkałem w wielu projektach. Klasy odpowiedzialne za generowanie konkretnych elementów HTML. Jednym z trochę już archaicznych rozwiązań z Java to ECS http://jakarta.apache.org/ecs/

Filtrowanie danych

Dane do list są dostarczane przez API. Można powiedzieć że na tym poziomie w ogóle odcinamy się od bazy. Nie posługujemy się ani jak w Java obiektami Query, Connection. Warstwa abstrakcji na tym poziomie wydaje się wystarczająca. 
// pobranie listy temlate
API::Template()->get($options);
Warunki są przekazywane w $options mogą być dość złożone.
Na pierwszy rzut może wydawać się to skomplikowane, ale na szczęście API jest dość dobrze opisane. Można znaleźć na https://www.zabbix.com/documentation/2.0/manual/appendix/api/api
Jeżeli mam się odnieść do pobierania listy template będzie to https://www.zabbix.com/documentation/2.0/manual/appendix/api/template/get, Niestety trzeba się tu zapoznać z pozycjami, które możemy używać w options bo są one różne dla różnych rodzajów danych. Ogólna zasada jest taka: filtrowanie z like wykorzystuje search
// przygotowanie options do wywołania API
$options = array(
 'editable' => 1,
 'sortfield' => $sortfield,
 'sortorder' => $sortorder,
 'limit' => ($config['search_limit']+1),
 'search' => array()
);
// inicjowanie pozycji po których szukamy z like
// tu gdy jest ustawione filter_template to dodane jest szukanie po host
$local_template_name = $_REQUEST['filter_template'];
if (!zbx_empty($local_template_name)) {
 $options['search']['host'] = $local_template_name;
}
W moim odczuciu z opisu API nie do końca wynika co podać jako warunki szukania. Wydaje się jednak dość sensowna metoda polegająca na sprawdzeniu jakie zostanie przygotowane zapytanie.
Można to zrobić wykonując breakpoint-a w klasie wykonującej zapytanie. Ponieważ aktualnie korzystam z mysql-a zrobiłem to w include/db.inc.php w funkcjach DBexecute oraz DBselect niezawodnie można tam sprawdzić query które idzie. 

Standardy

W całym projekcje słabo widać nową technologię - ale nie to jest przecież najważniejsze. Czasem działa dlatego, że nie zostało przeenżynierowane. Ale jednak raczej są to stare techniki MVC i to własnej konstrukcji. Nie mówię że traktuje to jako wadę. Ale patrząc na ilość pracy przy napisaniu większej ilości formatek już może być problem aby to ogarnąć. Jest ogólna separacja, kontroler-a, dostarczenia danych, renderowania rezultatów ale bardzo umowna i raczej słabo przestrzegana.

Wyświetlanie na screen wartości typu plain text

W moim odczuciu w przypadku tego typu informacji powinna być wykonana konwersja podobnie jak w przypadku "Last value" z Latest data (latest.php). Ogólnie wydaje mi się że jest to niespójność dlatego że na wykresie item który ma zdefiniowane unit jest prezentowany z uwzględnieniem konwersji do podanych jednostek (wybierany jest adekwatny dekorator - jeżeli da się to zrobić) W tym miejscu po wybraniu jednostek jako sekundy (wszędzie jest wyświetlane jako czas) ale na screen ze zdefiniowanym "plain text" wyświetla się bez dekoratora (jawny timestamp).

// latest.php
$change=convert_units($db_item['lastvalue']-$db_item['prevvalue'],$db_item['units']);
// include\func.inc.php
// gdy s 
convertUnitsS
 
// include\views\monitoring.screen.php

// include\screens.inc.php
$resourcetype = $screenItem['resourcetype'];
$resourcetype == SCREEN_RESOURCE_PLAIN_TEXT
get_screen_plaintext
$element = get_screen($screen, 0, $effectiveperiod);


get_screen_plaintext
get_item_by_itemid
... oczywiście będzie ciąg dalszy
Zamierzam dodać w tym przykładzie filtry do miejsc które wspomniałem wcześniej.



środa, 26 lutego 2014

Ant - Selektory plików dla fileset

Na wstępie powiem tak - to nie jest tutorial. Znacznie lepsze określenie do tego co jest niżej to rodzaj pamiętnika. Wiele rzeczy można zrobić na wiele sposobów. Niektóre są dobrze opracowane i łatwo je znaleźć. Niektóre nie wydają się zbyt popularne i drążenie zaczyna się od suchej dokumentacji. To co próbuje zrobić niżej można zrobić na wiele innych sposobów (pewnie to prawda). Ja akurat się uwziąłem aby zrobić to przy pomocy ANT-a. Chodzi o wybieranie elementów do fileset-a. fileset jest przekazywany jako argument do innych task-ów i dlatego jest popularny.

W przypadku prostych definicji nie trzeba sobie zawracać głowy, wystarczy tylko wskazać katalog i się robi. To jest dobre. Co zrobić gdy trzeba w jakiś złożony sposób spreparować listę do skopiowania?
Zamiast zmagać się z jakimś kosmicznym wyrażeniem regularnym pisanym w xml-u można spróbować dodać do fileset selektor który będzie wybierał pliki przy pomocy kodu. Wydaje się że to będzie prostsze. Czy to prawda? Z tego co piszą można to zrobić korzystając z języków skryptowych. Można też napisać klasę która to realizuje wykorzystując Custom Selectors.

Może najpierw spróbuje sprawdzić selektory skryptowe. Wybrałem jako język JavaScript.
Przykład selektora który wybiera każdy plik
<scriptselector language="javascript">
    self.setSelected(true);
</scriptselector>
Przykład selektora który ma nazwę o długości parzystej
<scriptselector language="javascript">
    self.setSelected((filename.length%2)==0);
</scriptselector>
Jak to wykorzystać w taskach które akceptują fileset? Poniżej przykład kopiowania z takim selektorem
<copy todir="dir-to">
    <fileset dir="..">
        <scriptselector language="javascript">
            self.setSelected((filename.length%2)==0);
        </scriptselector>
    </fileset>
</copy>
Po wykonaniu widać że kopiowana jest cała hierarchia. Wcale nie jest to takie proste. Na pierwszy rzut oka nie wiadomo dlaczego i skąd bierze się self i co to w ogóle jest. No na szczęście w tym temacie wyjaśnia się sprawa po lekturze manual-a Script Selector (patrz Selektory w ANT i radzę dokładnie przeczytać pisze co i jak).

Mam już jakieś narzędzie. Postawiłem więc sobie takie zadanie. Chcę wybrać tylko katalogi (nie pliki), które są w katalogu poniżej i zaczynają się od liczby.
Moja struktura może to wyglądać tak:
[all]
    build.xml
    test.xml
[00]
    [properties]
    ...
    build.xml
[00_auth]
    [properties]
    ...
    build.xml
[02]
    [properties]
    ...
    build.xml
...
[17]
    [properties]
    ...
    build.xml
[17_auth]
    [properties]
    ...
    build.xml
Jest to typowe zadanie gdy buduje wersję dla różnych oddziałów. W każdym katalogu który wybiorę jest skrypt, który buduje aplikację. Nie jest ważne jak to się buduje. Ważne jest to że chce mieć jakiś customizowalny sposób na wybieranie tych katalogów. Jakby się zastanowić to wcale może nie trzeba korzystać z selektora skryptowego! Może skorzystać z Filename Selector oraz Depth Selector. Mój testowy skrypt Ant-a to test.xml. Przyjąłem założenie, że w każdym katalogu jest plik o nazwie build.xml. Jako częściowa próba zrealizowania tego może to wyglądać tak:
<copy todir="dir-to" verbose="true">
    <fileset dir="..">
        <and>
            <depth max="1" />
            <filename regex="^\d+" />
            <filename regex="build.xml" />
        </and>
    </fileset>
</copy>
Wydaje się to działać. Ale czy to koniec? Normalne copy kopiuje wszystko to kopiuje tylko build.xml. Działa ale tylko dlatego że jest jeden build.xml. Co by było gdyby  build.xml był w kolejnym podkatalogu [00]/xx/build.xml? Skopiuje się! Nie okazuje się że właśnie depth przed tym zabezpieczył!

Selectory filename

W selektorze przekazywana jest nazwa pliku. W dokumentacji bezpośrednio nie ma informacji że to jest pełna ścieżka od miejsca podanego jako atrybut dir dla fileset. To dość ważne. Pisząc warunek który wybiera po nazwie chcemy np zdefiniować tak aby brało 17/ ale już nie 17_auth/. W zależności od tego czy ant jest odpalony na Windows czy Linux warunek jest interpretowany inaczej i wartość filename przekazana do badania warunkiem może mieć ścieżkę z separatorem / lub \.
Stosując wyrażenie regularne można to opędzić definiując np. wyważenie
<filename regex="^17[\\/]" />
Taki patent działa na obu systemach.

Eclipse

Uruchomienie takiego skryptu w Eclipse wymaga ustawienia ANT_HOME w Eclipse na właściwą wersję ANT. Wymagana też jest wskazanie odpowiedniej wersji JAVA. W moim przypadku korzystałem ze starej wersji Eclipse dlatego wskazałem moją własną - zewnętrzną wersję ANT (Window / Preferences / Ant / Runtime / Ant Home). Uruchomnienie przy pomocy Eclipse wymagało wskazania JAVA 1.6 (prawoklik - menu - Debug As / Ant Build / JRE / minumum 1.6)
W przypadku JAVA 1.5 dostałem komunikat że nie można załadować BSF
Nie udało mi się na razie debugować wyrażenia w JavaScript. Debuguje się jedynie skrypt ANT-a.
W sumie mało użyteczne odpalenie tego z Eclipse!

Selektory w ANT http://ant.apache.org/manual/Types/selectors.html
Opis języka skryptowego http://ant.apache.org/manual/Tasks/script.html

poniedziałek, 24 lutego 2014

Windows - batch

Manager urządzeń

Można odpalić z cmd przez komenę devmgmt.msc

Pobranie do zmiennej bieżącego katalogu

Przypadkowo zdarzyło mi się że musiałem napisać prosty plik bat. Nie przepadam za skryptami w batch-ach... ale gdy trzeba trudno.
Chciałem sprawdzić bieżący katalog.
Postąpiłem w sposób analogiczny jak dla Linux.
Pewnie jest to jakaś zmienna systemowa.
Okazało się że to nie tak samo...

W przypadku Linux można to sprawdzić przeglądając zawartość zmiennych komendą set
Wartość mamy w PWD
set |less
set |grep PWD=
Chciałem to sprawdzić dla Windows. I co? Okazuje się że szukam a tu nie ma!
set |more
set >c:\test
Można sobie taką zmienną spreparować. Komenda cd zwraca tę wartość.
set PWD="%cd%"
echo %PWD%

... i gotowe

Źródło http://stackoverflow.com/questions/6789491/batch-script-to-set-a-variable-with-the-current-path-location

Szukanie pliku który się uruchomił

I znów pracuję w Windows.
Odpalam konsole cmd. Wydaje polecenie np: java i wykonuje. 
Działa... ale czemu?
Może na początku zaznaczę że polecenia wydaje bez podania pełnej ścieżki. Wygląda tak jak komenda systemu. Niektóre polecenia się wywołują - ot tak - bez podania pełnej ścieżki.
Gdyby się zastanowić, to tak do końca nie wiem co się uruchomiło.
Sprawdzając managera zadań, pewnie zobaczę pełną ścieżkę, ale już po uruchomieniu.
A jak zobaczyć co system wybierze zanim to wykonam?

Wracając do teori w systemie Windows programy są odnajdywane na podstawie ścieżki PATH. Skąd tak na prawdę ten program jest uruchamiany? Jak znasz scieżkę PATH możesz spróbować to prześledzić, ale czy to proste i komu by się to chciało robić.

Okazuje się że można zobaczyć co właściwie system znajduje gdy się poda polecenie - służy do tego where.
where java
 C:\Windows\System32\java.exe
 D:\java\jdk\jdk1.6\bin\java.exe
Teraz już wiem.... wykona C:\Windows\System32\java.exe bo jest pierwsze na liście

W przypadku Linux jest whereis. Wydaje się że where z Windows to właśnie jego odpowiednik.

Rozszerzone polecenia cmd

Chciałem wykonać prosty skrypt do odpalania programu java. Jestem w katalogu X, wywołuje skrypt program.bat z podaniem bezwzględnej ścieżki katalogu Y. W ścieżce Y znajdują się wymagane jary. Trzeba je dodać do wywołania jar-a. Okazuje się że zrobiłem sobie zagadkę.
c:\X> c:\Y\program.bat
Kod program.bat jest prosty:
java -cp .\lib1.jar Class1
Program przy takim wywołaniu się wykłada. Wina to podanie bezwzględnej ścieżki. Ścieżka reprezentowana przez . to c:\X a tu nie ma lib1.jar.

Jak zatem to zrobić aby działało również dla takiego przypadku?
Nie chce przecież w skrypcie mieć zapisanych ścieżek na sztywno!
Czytam więc reference dla cmd. Zabrałem się do tego po lekturze linka poniżej. Należy zwrócić uwagę że I to numer od 0 - n
%~fI        - expands %I to a fully qualified path name
%~dI        - expands %I to a drive letter only
%~pI        - expands %I to a path only
Co ciekawe podobny rezultat dostałem gdy sprawdziłem zawartość %0 podobnie jak dla %~f0 

Idąc tym tropem spróbowałem ustawić zmienną. Wydaje się działać:
set BATH_PATH=%~d0%~p0
Po tych zmianach kod program.bat wygląda tak
set BATH_PATH=%~d0%~p0
java -cp %BATH_PATH%\lib1.jar Class1
Teraz działa tak jak chciałem!

piątek, 31 stycznia 2014

Tomcat - RequestFilterValve

Zabezpieczenie po IP

Czasem trzeba się zabezpieczyć przed dostępem do niektórych zasobów Tomcat-a. Wprawdzie ta metoda nie jest zbyt skuteczna, ale na początek można ją spróbować.

Przykładowa definicja:
<Context>
  ...
  <Valve className="org.apache.catalina.valves.RemoteHostValve"
         allow=".*\.mycompany\.com|www\.yourcompany\.com"/>
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         deny="192\.168\.1\.\d+"/>
  ...
</Context>
Można powiedzieć że jest w tym przykładzie zdefiniowany łańcuch 2 obiektów Value.
Jak tylko dojdzie do RemoteHostValve to na pewno go wykona jak, nie wywali w tym czasie błędu 403 to wykona również RemoteAddrValve. Wiec to oznacza że do RemoteAddrValve może nie wejść.

Podczas badania jak działa RequestFilterValve wyszły mi następujące wnioski:

Dane do sprawdzania
  • request.getRemoteAddr() - adres IP z requesta
  • request.getRemoteHost() - nazwa hosta


Kolejność sprawdzania
  • denies jak przejdzie idzie dalej
  • allows jak przejdzie idzie dalej
  • jak doszło do tego miejsca i jest ustawione denies i nie ustawione allows to idzie dalej
  • wywala 403

Scenariusz testów

Dodaje definicję w/w value do tomcat/conf/server.xml w sekcji Host tak jak access log
Moja maszyna ma IP 192.168.1.10
Mój Tomcat ma connector https na porcie 443.
Odpalam Tomcat-a 
Z przeglądarki pytam https://192.168.1.10/manager/html/ - powinno mnie wpuścić
Z przeglądarki pytam https://127.0.0.1/manager/html/ - powinno dać komunikat 403
Definicję value przenoszę do tomcat/conf/context.xml - spawdzam działanie
Definicję value przenoszę do tomcat/conf/Catalina/localhost/ap-xxxx.xml - spawdzam działanie

Wnioski:

  • przy ustawionych denies i allows - definicja denies powinna być bardziej szczegółowa niż allows, dlatego że inaczej nie wejdzie do sprawdzenia allow
  • prawdopodobnie lepiej jest zdefiniować kilka value z pojedynczymi definicjami denies albo allows
  • można dla definicji denies i allows podać dowolne wyrażenie regularne przyklady z ip oczywiście są sensowne, ale może to być cokolwiek. Warto zwrócić uwagę na znaki \ 
  • dla definicji denies i allows można podać kilka wartości oddzielonych znakiem "," Zostaną one zamienione na tablicę Pattern[]

Źródła:

http://tomcat.apache.org/tomcat-6.0-doc/config/context.html#Request_Filters
http://fossies.org/dox/apache-tomcat-6.0.37-src/RequestFilterValve_8java_source.html

Tomcat - zdalne debugowanie

Łatwo znaleźć - ale lepiej mieć pod ręką

Niektóre rzeczy łatwo znaleźć u wujka google, trzeba jednak wiedzieć jak go zapytać.

Wiadomo można odpalić Tomcat-a w środowisku IDE Eclipse i podłączyć źródła.
Ja to robię jeszcze prehistoryczną metodą przy pomocy pluginu http://www.eclipsetotale.com/tomcatPlugin.html ale jak, ktoś wie co i jak to znajdzie lepsze patenty.

Czasem jednak skórka nie warta wyprawki i lepiej się podłączyć do tego co już jest.

Akurat pracuję pod Windows więc cytuje zaklęcia:

  set JPDA_ADDRESS=8000
  set JPDA_TRANSPORT=dt_socket
  bin/catalina.bat jpda start

W IDE Eclipse wystarczy zdefiniować Run / Debug Configuration / Remote Java Application, ustawić port 8000, podłączyć źródła np Tomcat-a i można sobie oglądać na żywo.