Profiler w Netbeans
10.02.2012 | aktual.: 11.02.2012 15:14
Jest wolna chwila, jest i wpis na blogu – tym razem o wydajności programów, a raczej o sprawdzaniu wydajności programów w środowisku Netbeans. Służy do tego Profiler, który dostarcza nam wielu cennych informacji o tworzonej przez nas aplikacji.
Ale co mamy sprawdzić? W tym przypadku możemy sprawdzić głównie wydajność programów pod względem zużycia pamięci, monitorowanie stanu wątków, wydajność procesora oraz możemy znaleźć „wąskie gardła”. Ja zwracam bardziej uwagę na możliwość monitorowania wątków i zużycie pamięci niż na wydajność procesora, ponieważ w tym przypadku wyniki są często (zawsze) bardzo przekłamane. Ale nie ma co się dziwić, ponieważ środowisko też potrzebuje czasu procesora na zebranie informacji o stanie pamięci, wątkach, załadowanych obiektach i klasach.
Do czego służy Profiler? Do poprawy naszej aplikacji, aby była wydajniejsza, doskonalsza, lepsza – tworząc programy/systemy należy dążyć do doskonałości chociaż już na starcie wiadomo, że jej nie osiągniemy. Dzięki temu narzędziu możemy się dowiedzieć jak wykonują się nasze wątki, ile czasu się wykonują, spędzają w trybie monitora lub są uśpione. Otrzymamy również informacje o ilości instancji obiektów poszczególnych klas oraz o tym ile pamięci zużywa nasza aplikacja. Większość informacji otrzymamy w formie wykresu lub tabelki. Wszystko ładnie, przejrzyście i zrozumiale.
Niestety nie poprawi za nas aplikacji. Może i to lepiej... więcej pracy dla programistów i projektantów(Ci też powinni być przy „profilowaniu” aplikacji).
Hmm co jeszcze... Może to, że tworząc program w Netbeans`ie możemy wyznaczać miejsca, w których chcemy uzyskać szczegółowych informacji o stanie pamięci i programie tj. sprawdzić ilość obiektów poszczególnych instancji itp. . Uruchamiając Profiler możemy określić co nas interesuje i jakie informacje mają być zbierane podczas wykonania tego procesu.
Dla zaawansowanych użytkowników Netbeansa istnieje możliwość uruchamiania aplikacji na innym komputerze co skutkuje szybszym wykonywanie procesu i zalecane jest dla dużych systemów z wiadomych przyczyn.
Tradycyjnie już skromny przykład
Zaczniemy od stworzenia wielowątkowej aplikacji- w tym wypadku aplikacja serwera. W tym celu otwieramy Netbensa, tworzymy nowy projekt. Nasza aplikacja będzie się składała z trzech klas: główna klasa z metodą "main", klasa będąca wątkiem serwera oraz klasa wątków obsługujących klientów.
Mój projekt wygląda mniej więcej tak, jak na obrazku poniżej.
Na początek kod wątku obsługi klienta ”ClientThread.java”.
package profilertest; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author michal */ public class ClientThread extends Thread{ final private Vector clients; private Socket s; private static int id=0; public ClientThread(Socket s, Vector c){ super("ClientThread"+id); id++; this.s=s; this.clients=c; } @Override public void run() { BufferedReader reader = null; BufferedWriter writer = null; try { reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line = reader.readLine(); writer.write(line + "\r\n"); writer.flush(); synchronized (clients) { clients.remove(this); } } catch (IOException ex) { Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex); } finally { try { reader.close(); writer.close(); this.finalize(); } catch (IOException ex) { Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex); } catch (Throwable ex) { Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex); } } } }
Jak widać nic skomplikowanego klasa ta nie robi, ot po prostu odbiera napis od klienta i odsyła go, a na końcu usuwa obiekt z listy.
Poniżej przedstawiony został kod klasy ServerThread. Również za dużo się nie dzieje w tej klasie. Zwyczajnie akceptowane jest połączenie od klienta, tworzony wątek obsługi klienta i dodanie go do listy.
package profilertest; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author michal */ public class ServerThread extends Thread { ServerSocket s; final int port = 16969; final Vector<ClientThread> clients = new Vector<ClientThread>(); public ServerThread() { super("ServerThread"); } @Override public void run() { try { System.out.println("Serwer wystartował"); s = new ServerSocket(port); // tworzymy nowe gniazdo serwera while (true) { Socket s1 = s.accept(); //akceptujemy nowe połączenie ClientThread ct1 = new ClientThread(s1, clients); //tworzymy wątek klienta synchronized(clients){ clients.add(ct1); //dodajemy klienta do wektora } ct1.start(); //uruchamiamy wątek obsługi klienta System.out.println("Dodałem klietna"); } } catch (IOException ex) { Logger.getLogger(ServerThread.class.getName()).log(Level.SEVERE, null, ex); System.exit(-1); } } }
Główna klasa z metodą „main” wygląda następująco:
package profilertest; public class ProfilerTest { public static void main(String[] args) { ServerThread server = new ServerThread(); server.start(); } }
Chyba najdłuższa klasa w moim życiu :P Jak już mamy stworzony projekt serwera, to trzeba coś zrobić, aby przetestować nasz serwer i pokazać jak się sprawdza wydajność aplikacji w Netbeansie. Do tego tworzymy nowy projekt, z jedną klasa z metodą main. Kod został przedstawiony poniżej.
package profilerclient; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.UnknownHostException; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author michal */ public class ProfilerClient { /** * @param args the command line arguments */ public static void main(String[] args) { Vector<Thread> v = new Vector<Thread>(); for(int i=0; i<100; i++){ Thread t1 = new Thread("Client: "+i){//tworzymy nowy wątek public void run(){ BufferedReader reader = null; BufferedWriter writer = null; try { Socket s = new Socket("localhost",16969);//łączymy się z serwerem writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.write(getName()+"\r\n");//wysyłamy nazwę wątku writer.flush(); System.out.println(reader.readLine());//wypisujemy odebrany napis reader.close(); writer.close(); } catch (UnknownHostException ex) { Logger.getLogger(ProfilerClient.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(ProfilerClient.class.getName()).log(Level.SEVERE, null, ex); } }//koniec metody run }; v.add(t1); }//koniec pętli for for(int i=0; i<100; i++){ v.elementAt(i).start();//startujemy wątek i-ty } } }
Po skończeniu pracy z kodem powinniśmy otrzymać coś takiego lub podobnego jak na zrzucie poniżej.
Ok. Więc kod programu mamy już gotowy. Co teraz należy zrobić? Rozpocząć proces profilowania. Warto przed tym dokonać kalibracji środowiska JDK. W tym celu należy wybrać z menu „Profile” ->”Advanced Commands”-> „Run Profiler Calibration”, wybrać wersję JDK, którą używamy.
Następnie zaznaczamy nasz projekt jako główny(PPM, z menu kontekstowego wybieramy „Set as Main Project”) i z menu wybieramy „Profile” -> „Profile Main Project”. Powinno otworzyć się okno konfiguracyjne, w którym zaznaczamy interesujące opcje. Ja zrobiłem to tak, jak na zrzutach poniżej.
Przyszedł czas na uruchomienie przyciskiem „Run”. Po tym kroku po prawej stronie powinna pokazać się zakładka „Profiler”. Jeżeli z jakiegoś powodu nie pokazała się, można ją włączyć w menu „Windows”-> „Profiling”->”Profiler Control Panel”.
W panelu tym możemy zarządzać procesem sprawdzania aplikacji serwera, włączyć monitorowanie wątków, zużycie pamięci, pobierać „zrzuty pamięci” etc. . Zostało to przedstawione na rysunkach poniżej.
Trzeba pamiętać, że sprawdzając aplikację serwera trzeba uruchomić aplikację klienta. Na wykresie przebiegu wątków widać jak tworzone i uruchamiane były wątki obsługi klienta.
Jak zostało to pokazane można wiele się dowiedzieć z wykresów i tabel o wykonywaniu naszego programu. Ale to nie wszystko...
Podczas wykonywania programu tym sposobem możemy pobierać zrzuty pamięci przyciskiem „Take Snapshot” lub obserwować wyniki na żywo (jak ktoś potrafi szybko analizować :P). Dodatkowo możemy w programie ustawić miejsca w których ma się wykonać „snapshot”.
Jak to zrobić? Przechodzimy do kodu programu, klikamy prawym klawiszem myszy i z menu kontekstowego wybieramy „Profiling”->„Insert Profiling Point”. Pokaże się okno dodawania, w którym ustawiamy wszystkie parametry i kończymy dodając miejsce zrzutu. W jednej linijce możemy dodać 2 miejsca – przed wykonaniem instrukcji/metody i po wykonaniu. Wykorzystuję to do sprawdzania dużych metod, które długo się wykonują.
Następnie podczas wykonania procesu profilowania w oknie „Profiling Points” (jeżeli nie jest ono widoczne, należy je włączyć w menu: „Windows” -> „Profiling” -> „Profiling Points”), zostanie pokazany nasz punkt i przechwycony zrzut. Można go otworzyć klikająć PPM i wybierając opcję „Show Report”.
Trochę długi wpis wyszedł ze względu na zamieszczony kod programów oraz zrzuty ekranów. Dla osób szerzej zainteresowanych polecam strony: http://netbeans.org/features/java/profiler.html http://profiler.netbeans.org/ http://netbeans.org/kb/docs/java/profiler-intro.html Dziękuję za uwagę ;)