Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Write your own Java Profiler in 240 lines of pure Java

Write your own Java Profiler in 240 lines of pure Java

Profilers help to analyze performance bottlenecks of your application if you know how to use them. Getting to grips with profilers helps to understand how they work: Profilers aren't rocket science. A usable Java profiler can be written in 240 lines of pure Java code, allowing you to fix performance issues and add custom features quickly.

This talk will give the fundamentals of Java profiling and how Java profilers typically work, followed by a detailed explanation of how to develop a functioning profiler in a few lines of Java code. This talk will also explain how you can use it in production to analyze performance issues and show briefly how to work with a widely used open-source profiler based on the same principles.

Recording from Devoxx Belgium 2023: https://www.youtube.com/watch?v=Mxcp2khJ4fw

Johannes Bechberger

October 05, 2023
Tweet

More Decks by Johannes Bechberger

Other Decks in Programming

Transcript

  1. Public Write your own Java Profiler in 240 lines of

    pure Java Johannes Bechberger The best JDKTM
  2. Public Premature Optimization We should forget about small efficiencies, say

    about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. — Donald Knuth “ Source: Jacob Appelbaum, Wikipedia
  3. Public Premature Optimization We should forget about small efficiencies, say

    about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. — Donald Knuth “ Source: Jacob Appelbaum, Wikipedia
  4. Public main serverLoop handleQuestionRequest currentQuestion isQuestionEnabled fun main() { ...

    serverLoop() } fun serverLoop() { while (true) { req = ... if (req.isQuestionRequest) handleQuestionRequest(req) ... } } fun handleQuestionRequest(req) { if (isQuestionEnabled()) { emit(currentQuestion().json) } else { emit({}) } } parseJSON parseJSON
  5. Public main serverLoop handleQuestionRequest fun main() { ... serverLoop() }

    fun serverLoop() { while (true) { req = ... if (req.isQuestionRequest) handleQuestionRequest(req) ... } } fun handleQuestionRequest(req) { if (isQuestionEnabled()) { emit(currentQuestion().json) } else { emit({}) } }
  6. Public What is profiling? A report on the amounts of

    time spent in each routine of a program, used to find and tune away the hot spots in it. — The Jargon File “
  7. Public Instrumenting Profilers Inserting instructions into the code automatically fun

    serverLoop() { logEntry(“serverLoop”) while (true) { req = ... if (req.isQuestionRequest) handleQuestionRequest(req) ... } logExit(“serverLoop”) } Profiled Code ≠ Production Code long time = time(); println(“serverLoop: “ + (time() – start)) on class load
  8. Public Sampling Profilers fun serverLoop() { while (true) { req

    = ... if (req.isQuestionRequest) handleQuestionRequest(req) ... } } Profiled Code = Production Code
  9. Public Main Class java -javaagent:./target/tiny_profiler.jar=agentArgs ... public static void agentmain(String

    agentArgs) { premain(agentArgs); } JVM start later attach attach public static void premain(String agentArgs) { Main main = new Main(); main.run(new Options(agentArgs)); }
  10. Public Main class private void run(Options options) { Thread t

    = new Thread( Profiler.newInstance(options)); t.setDaemon(true); t.setName("Profiler"); t.start(); } public class Options { Duration interval; Optional<Path> flamePath; boolean printMethodTable; }
  11. Public Profiler public static Profiler newInstance(Options options) { Profiler profiler

    = new Profiler(options); Runtime.getRuntime() .addShutdownHook( new Thread(profiler::onEnd)); return profiler; }
  12. Public Profiler private void sample() { Thread.getAllStackTraces() .forEach( (thread, st)

    -> { if (thread.isDaemon()) { return; } store.addSample(st); } }); }
  13. Public StackTraceElement class StackTraceElement { Class<?> declaringClassObject; String classLoaderName; String

    moduleName; String moduleVersion; String declaringClass; String methodName; String fileName; int lineNumber; }
  14. Public Turning traces into flamegraphs parseJSON currentQuestion serverLoop main main

    serverLoop handleQR currentQuestion parseJSON 1 1 1 1 1 handleQuestionRequest
  15. Public Turning traces into flamegraphs parseJSON currentQuestion serverLoop main main

    serverLoop handleQR currentQuestion parseJSON 2 2 2 2 2 handleQuestionRequest
  16. Public Turning traces into flamegraphs currentQuestion serverLoop main main serverLoop

    handleQR currentQuestion parseJSON 3 3 3 3 2 handleQuestionRequest
  17. Public Turning traces into flamegraphs isQuestionEnabled serverLoop main main serverLoop

    handleQR currentQuestion parseJSON 4 4 4 3 2 parseJSON isQuestionEnabled parseJSON 1 1 handleQuestionRequest
  18. Public Turning traces into flamegraphs main serverLoop handleQR currentQuestion parseJSON

    4 4 4 3 2 isQuestionEnabled parseJSON 1 1 main serverLoop handleQuestionRequest currentQuestion isQEnabled parseJSON parseJSON
  19. Public Write your own Java Profiler in 240 lines of

    pure Java Johannes Bechberger The best JDKTM 251
  20. Public Java/JDK Mission Control Application Performance Monitors, ... Sampling Profiler

    External VisualVM Netbeans async-profiler Sync Async Forte Analyzer Built-In Java/JDK Flight Recorder 2016 1991 ASGCT 2002 2012 2018 Open Source 2010
  21. Public JDK Flight Recorder (JFR) java \ -XX:+UnlockDiagnosticVMOptions \ -XX:+DebugNonSafepoints

    \ -XX:+FlightRecorder \ -XX:StartFlightRecording=filename=file.jfr \ arguments jcmd PID JFR.start jcmd PID JFR.dump filename=file.jfr jcmd PID JFR.stop Already included in your JDK 8+
  22. Public Custom JFR Events class SessionEvent extends jdk.jfr.Event { int

    sessionId; int n; // ... // constructor } var event = new SessionEvent(sessionId, n); event.begin(); ctx.result("fibonacci: " + fib(n)); event.commit();
  23. Public Impact on performance in some benchmark, 48s, 8 cores,

    4% standard deviation Overhead JFR (reduced setting) 0 - 5%, typically < 2% JFR (profiling setting) 1 - 8%, typically < 5% async-profiler 3 - 6%, typically < 2% async-profiler with jfrsync 3 - 10%