Motto: "Creșterea performanței unui sistem e ușoară, trebuie doar să știi totul." Sergey Kuksenko, Inginer Performanță Oracle
Scopul acestui articol este de a oferi informații despre cele mai folosite unelte în măsurarea performanței aplicațiilor scrise în Java. Pentru a ne atinge scopul am folosit exemplul de la Pivotal, Spring Pet clinic application. Am optat pentru acest exemplu deoarece Spring MVC și Spring Boot au fost web frameworkurile cele mai folosite în 2016, conform studiului realizat de ZeroTurnaround.
HPROF este un tool distribuit gratuit de Oracle odată cu JDK și apare în format .dll. Acesta folosește API JVM TI pentru a măsura performanța heapului și a CPU-ul. Brendan Gregg, care a lucrat pentru Sun și Oracle ca Inginer Performance și Kernel, a raportat câteva probleme:
Nu poate fi pornit/oprit - pornește și se oprește o dată cu aplicația monitorizată.
CPU profiling overhead este prea mare, rezultatele nu au acuratețea așteptată, uneori valorile măsurătorilor sunt de 10 ori mai mari.
Pentru a monitoriza aplicația Spring Pet Clinic a trebuit să fie adăugat un http endpoint care să forțeze aplicația să se închidă, se constată lipsa posibilității de a porni/pauza/opri măsurătorile. Rezultatele pentru Pet Clinic se pot observa mai jos:
$ mvn package
$ java -Xrunhprof:cpu=samples -jar target/spring-petclinic-1.5.1.jar
...
2017-03-20 23:00:49.565 INFO 11640 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/close]}" o nto public void org.springframework.samples.petclinic.vet.VetController.close()
...
Dumping CPU usage by sampling running threads ... done.
$ cat java.hprof.txt
JAVA PROFILE 1.0.1, created Mon Mar 20 23:00:22 2017
...
CPU SAMPLES BEGIN (total = 22941) Mon Mar 20 23:04:20 2017
rank self accum count trace method
1 56.37% 56.37% 5784 301590 sun.nio.ch.WindowsSelectorImpl$ SubSelector.poll0
2 16.73% 73.10% 1716 301591 sun.nio.ch.ServerSocketChannelImpl.accept0
3 6.67% 79.77% 684 300352 org.springframework.boot.loader.jar.JarURLConnection.getInputStream
4 5.47% 85.23% 561 300356 sun.misc.URLClassPath$Loader. getResource
5 2.16% 87.40% 222 300231 java.io.RandomAccessFile.open0
6 1.39% 88.79% 143 300131 java.lang.ClassLoader.defineClass1
7 1.02% 89.81% 105 300502 sun.misc.URLClassPath$Loader. findResource
...
CPU SAMPLES END
NetBeans profiler este un tool gratuit de profiling integrat în NetBeans IDE. Oferă suport pentru următoarele: CPU profiling (overhead mare) /sampling (overhead mic) Heap profiling
Analiza threaduri și lockuri * Monitorizare SQL queries și JVM.
Adăugarea de profiling points (pointuri în cod unde se pot face snapshoturi, se resetează rezultatele sau se obțin timestampuri) .
Se pot filtra evenimentele.
NetBeans profiler ne-a făcut o impresie foarte bună și este un tool care oferă tot ceea ce este nevoie pentru a analiza problemele de performanță.
VisualVM este un profiler Java distribuit cu Oracle JDK dar care poate fi instalat și separat de pe pagina de download. Acesta este alternativă bună pentru JConsole, oferind aceleași funcții dar și posibilitatea de a le extinde prin pluginuri.
VisualVM include următoarele: Se atașează la procesele locale și remote Monitorizează performanța CPU și memoria Analizează threadurile Obține și analizează snapshoturi de thread/heap Extensibil prin pluginuri*, incluzând MBeans (această opțiune din JConsole sau din Java Mission Control fiind binecunoscută)
VisualVM se poate porni din terminal cu următoarea comandă:
$ jvisualvm
VisualVM este un tool care merită luat în considerare, conform unui studiu ZeroTurnaround din noiembrie 2015 este surprinzător de utilizat, 46% dintre respondenți au folosit VisualVM pentru măsurători de performanță.
Java Mission Control și Java Flight Recorder (cunoscute ca JRockit Mission Control și JRockit Flight Recorder - JFR) sunt profiling tooluri adăugate la suita JDK începând cu 7u40 și promovate ca având profiling overhead apropiat de zero.
Java Mision Control este gratuit, spre deosebire de Java Flight Recorder. Pentru a folosi JFR este nevoie de semnarea acordului comercial Oracle.
JMC & JFR oferă toate opțiunile de bază gen monitorizare CPU, heap, threaduri incluzând detecția deadlockuri. Dintre funcțiile avansate merită menționate suportul pentru triggere, monitorizarea I/O a excepțiilor și a evenimentelor, suport pentru MBeans, statisticile despre hot dar și restrângerea monitorizării la o sub -perioadă de timp.
Mission Control se pornește rulând următoarea comandă (din UI se poate folosi wizzardul Java Flight Recording dar și rula metodele JFR prin JMX):
$ jmc
Pentru a pregăti aplicația pentru Flight Recorder, trebuie adăugate următoarele argumente:
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder
Pentru a programa o înregistrare cu Flight Recorder, întârziată cu 20 secunde și salvată în fișierul c:/TEMP/myrecording.jfr se folosesc următorii parametri:
-XX:StartFlightRecording=delay=20s,
duration=60s,
name=MyRecordings,
filename=c:/TEMP/myrecording.jfr,
settings=profile
-XX:FlightRecorderOptions=loglevel=info
Monitorizarea cu JFR poate fi pornită din terminal folosind jcmd și JMX rulând comanda de mai jos:
$ jcmd 9200 JFR.start delay=20s duration=60s name=MyRecordings
filename=c:/TEMP/myrecording.jfr,settings=profile
9200:
Recording 4 scheduled to start in 20 s. The result will be written to:
C:\temp\myrecording.jfr,settings=profile
Verificarea stării unei înregistrări se realizează rulând următoarea comandă:
$ jcmd 9200 JFR.check
9200:
Recording: recording=4 name="MyRecordings" duration=1m filename="c:/TEMP/myrecording.jfr,settings=profile" compress=false (running)
Pentru crearea unui JFR dump folosim următoarea comandă:
$ jcmd 9200 JFR.dump name=MyRecordings filename=c:/TEMP/dump.jfr
9200:
Dumped recording "MyRecordings", 265.2 kB written to: C:\temp\dump.jfr
Java Mission Control și Java Flight Recorder sunt cu adevărat niște profilere de excepție și dacă dețineți una dintre licențele: "Oracle Java SE Advanced", "Oracle Java SE Advanced Desktop" sau "Oracle Java SE Suite" nu există nici un motiv să folosiți un alt profiler.
Numele JMH vine de la Java Microbenchmark Harness și este un micro benchmarking framework, distribuit cu OpenJDK începând din 2013. Funcționalitatea pe care o oferă și anume obținerea de statistici de performanță este motivul pentru care este menționat acest framework printre uneltele de profiling. Pentru a obține rezultate cât mai precise, JMH trebuie configurat cu un warmup time generos; acest pas este folosit și de unele profilere și este numit calibrare.
Analiza JMH are ca rezultate posibile timpul de rulare al unei metode (incluzând percentila), media timpului sau throughputului, în funcție de configurație.
Modul cel mai ușor și recomandat de folosire al JMH este de a crea un proiect separat doar pentru JMH folosind Maven:
$ mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test-pet-clinic \
-Dversion=1.0
Rezultatul comenzii de mai sus este crearea directorului test-pet-clinic care conține proiectul de testare JMH și care poate fi rulat în modul următor (mod throughput în exemplul de mai jos):
$ cd test-pet-clinic/
$ mvn clean install
$ java -jar target/benchmarks.jar
# JMH 1.9.3 (released 667 days ago, please consider updating!)
# VM invoker: C:\Program Files\Java\jre1.8.0_111\bin\java.exe
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.test.spring.RestImplementationsBenchmark.owners
# Parameters: (path = /owners, port = 8080, server = localhost)
# Run progress: 0.00% complete, ETA 00:06:40
# Fork: 1 of 10
# Warmup Iteration 1: 7.829 ops/s
...
# Warmup Iteration 19: 91.628 ops/s
# Warmup Iteration 20: 93.504 ops/s
Iteration 1: 92.267 ops/s
Iteration 2: 34.738 ops/s
Iteration 3: 92.347 ops/s
Result " owners ":
98.147 ▒(99.9%) 3.851 ops/s [Average]
(min, avg, max) = (34.738, 98.147, 113.290), stdev = 15.129
CI (99.9%): [94.296, 101.998] (assumes normal distribution)
# Run complete. Total time: 00:06:08
Benchmark (path) (port) (server) Mode Cnt Score Error Units
RestImpl /owners 8080 localhost thrpt 173 98.147 ▒ 3.851 ops/s
Dintre utilizările existente ale JMH merită amintite benchmark-urile de Java 8 făcute de TubeMogul sau măsurătorile lui Pavel Samolysov conform cărora: "EJB este cu 15% mai rapid decât Spring Framework în timp ce CDI este cu 19% mai încet".
Daniel Mitterdofer a identificat în prezentarea sa despre benchmark-ul elastic search-ului 7 probleme ale benchmarking-ului:
A nu lua in considerare sistemul folosit (hardware, OS, alte aplicații)
Nu se folosește warmup
Nu se face analiza bootleneck-urilor
A considera scriptul de benchmarking ca fiind infailibil
A nu înțelege și folosi analizele statistice - distribuție, t-test, percentila
Metrici vagi
XRebel Trial este împachetat ca arhivă zip conținând xrebel.jar java agent, licența și acordul de licențv. Se utilizează ușor prin adăugarea unui parametru: -javaagent:xrebel.jar - acesta va activa XRebel inclusiv toolbar-ul XRebel. Odată ce aplicația web a pornit cu XRebel activat, toolbarul este afișat stânga jos a oricărei pagini web a aplicației. Toolbarul are următoarele meniuri:
Find exceptions - conține toate excepțiile logate
Access application profiling - durata fiecărui request
IO Queries - query-urile la baza de date indiferent de implementarea accesului la baze de date - bazat pe JDBC sau NoSQL
Logs - monitorizează logurile sesiunii.
Urmărește eveniment din următoarele surse:
HTTP
Quartz
JMS
Periodic Task
RabbitMQ
XRebel oferă suport pentru următoarele frameworkuri: Spark, Spring MVC, Spring Boot, JSF, Vaadin, Spark Framework, Grails, Struts, Jersey și permite agregarea datelor de la alte servicii web (pornite cu XRebel activat și folosite de care aplicația curenta). Printre application servers suportate amintim: GlassFish, JBoss, Jetty, Tomcat, TomEE, WebLogic, WebSphere, WebSphere Liberty Profile, WildFly. Dintre bazele de date NoSQL există suport pentru: Cassandra, Couchbase Server, MongoDB, Redis. În ce privește bazele de date relaționale există suport pentru următoarele: Apache Derby, H2, HSQLDB, Microsoft SQL Server, MySQL, Oracle, PostgreSQL, SAP MaxDB și SQLite.
XRebel oferă o experiența plăcută, interfața este intuitivă și ușor de folosit. XRebel ar putea fi mult mai util dacă s-ar implementa vizualizare timpului luat de threadul/urile pornite de către anumite requesturi.
JProfiler este un produs comercial implementat de către EJ Technologies. Oferă suport pentru următoarele:
Profiling pentru procese locale și remote
Triggere
Analiza memoriei
Profiling de CPU și threaduri incluzând statistici pentru metode
Măsurători specifice VM
Logare de evenimente.
Funcțiile care ne-au atras atenția au fost următoarele:
Bookmarkul pe timeline
Exportarea raportului ca html
Detecția de leakuri de conexiuni la baza de date
Logare evenimente RMI, web service, remote EJB calls
Afișare evenimente de baze de date incluzând conexiuni bazate pe JDBC și query-uri JPA/Hibernate
Interfața este ușor de folosit permițându-ne ca în scurt timp să folosim JProfiler la parametri maximi.
JMeter poate fi configurat pentru a obține statistici începând de la web service endpoint throughput până la timpul necesar execuției unei metode. Terminologia JMeter conține următoarele elemente:
Test plan - script folosit de JMeter pentru testare, exista unul singur și conține unul sau mai multe thread groupuri
Thread Group - simulează acțiunea unui utilizator - toate celelalte acțiuni îi sunt adăugate lui
Sampler - responsabil de acțiune - poate să fie o metodă JUnit de executat, o metodă a unei clase speciale Java (implementează interfața org.apache.jmeter.protocol.java.sampler.JavaSamplerClient), un script Groovy, execuția unui proces local sau un request în rețea (HTTP, TCP, JDBC, SMTP, RPC etc.)
Timer - adaugă întârzieri
Exista posibilitatea de a adăuga manual dar și de a înregistra requesturi HTTP folosind proxy serverul oferit de JMeter. De obicei, Firefox este folosit pentru înregistrări și trebuie amintit faptul că în caz în care aplicația web monitorizată folosește headere x-csrf-token pentru detecția falsificării, este posibil să se extragă tokenul din callul dinainte folosind post procesare gen regex și creând variabile referențiabile (de exemplu ${my_csrf}). JMeter conține următorii extractori:
RegexExtractor
XPath Extractor
CSS/JQuery Extractor
Măsurătorile conțin următoarele elemente relevante din punct de vedere al performanței sistemului:
Elapsed time - măsurat dinainte de a face acțiunea până când a fost primit răspunsul
Latency - include toate procesările făcute pentru a crea requestul cât și timpul până se obține prima parte a requestului
Răspunsul pentru un request http realizat cu JMeter se prezintă ca și mai jos:
Thread Name: Thread Group 1-1
Sample Start: 2017-03-26 13:33:01 EET
Load time: 1559
Connect Time: 1
Latency: 1559
…
Sample Count: 1
Error Count: 0
…
Response headers:
HTTP/1.1 200 OK
…
Set-Cookie: JSESSIONID=11D10081A56BB9FA911E7350E57429A1; Path=/owners
Content-Type: application/json;charset=utf-8
…
JMeter este o surpriză plăcută, fiind foarte personalizabil iar integrarea cu unele dintre cele mai importante Continuous Integration tools (Bamboo JMeter Aggregator, JMeter plugin for TeamCity, CircleCI JMeter package or JMeter Jenkins Plugin) îl face un profiler de luat în considerare.
YourKit este un profiler comercial care are următoarele funcții:
Monitorizare CPU Analiza threaduri și deadlockuri*
Vizualizare informații despre memoria folosită
Evenimente legate de excepții, baze de date și sistem de fișiere.
Dintre funcțiile avansate YourKit suportă:
Exportarea rapoartelor ca și CSV, HTML, TXT, ZIP
În general, interacțiunea a fost plăcută, interfața nu este atât de intuitivă pe cât ne-am dori, iar pentru a obține anumite informații avansate este nevoie ca profilerul să fie folosit în mod CPU tracing sau CPU sampling.
Notele reprezintă perspectiva noastră parțial subiectivă, iar intervalul folosit pentru notare este de la 0 la 5. Nota finală a fost calculată ca media notelor, unde Y și N corespund notelor 0 respectiv 5.
Tabel 1 - Evaluarea profiler-elor Java
Cunoașterea avansată a JVM-ului, propriei aplicații și a sistemului în care rulează ajută în a înțelege rezultatele monitorizării și a aplica schimbările necesare pentru a obține o aplicație performantă care va satisface așteptările clienților. Pentru a afla mai multe despre Hotspot și optimizările pe care le face, recomandăm prezentarea JVM Mechanics a lui Doug Hawkins. Pentru cei interesați există toolurile cu ajutorul cărora se poate dezasambla o aplicație Java până la nivel de Java byte code sau cod asamblare nativ cum ar fi: javap JITWatch cu hsdis.
Una dintre problemele cele mai amintite legat de downtime-ul aplicațiilor sunt GC hiccups. Dacă se dovedește că GC-ul cauzează probleme, există opțiunea de a schimba implementarea folosită cu o versiune mai apropiată de aplicația cu probleme. Există câteva implementări disponibile de GC atât pentru Oracle JVM cât și pentru Open JDK unde noul GC Shenandoah merită menționat. Tuningul de GC este explicat în detaliu pe pagina Oracle de GC tuning 6.
Instrumentarea aplicațiilor poate altera valorile măsurătorilor uneori și de 10 ori. Acest lucru se întâmplă deoarece codul este modificat de profiler în timpul monitorizării, ceea ce schimbă modul în care JVM-ul se comportă (optimizează alte zone de cod, schimbă safepoints, schimba code layoutul și adaugă overhead). Acesta nu este un caz izolat, deoarece majoritatea profiler-or folosesc API-ul Java Virtual Machine Tool Interface. Diferența între acuratețea măsurătorilor unui profiler și a altuia poate constă în calibrarea făcută.
Samplingul funcționează prin observarea din exterior a sistemului, crearea de thread dumps la interval regulat de timp și analizarea dumpului. Acest mod de profiling consumă mult CPU, ceea ce îl face inexact. Există profilere mai inteligente cum ar fi cel al lui Richard Warburton numit Honest Profiler, care funcționează ca un hibrid între instrumentare și sampling, verificând la interval regulat de timp stackul metodelor care rulează la un moment dat, în mod asincron, folosindu-se de un API intern OpenJDK.
Măsurarea unei aplicații Java poate fi făcută și în alte moduri. De exemplu, prin examinarea logurilor, monitorizare JMX, Spring actuators sau prin folosirea performance counterilor de sistem.
Îmbunătățirea performanței unei aplicații este o sarcină dificilă și de multe ori consumatoare de timp, cu rezultate concrete doar după eforturi consecvente. Profilerele cu acuratețe ridicată și care oferă setul necesar de funcții sunt un ajutor real și binevenit.