Wieso ist meine Anwendung eigentlich so langsam?

snail emoji

BED-Con 2023 · 28.09.2023


tgbyte bk
Thilo-Alexander Ginkel · Stefan Reuter · TG Byte Software GmbH
kontakt@tgbyte.de · www.tgbyte.de

Über uns

  • Beratung von Entwicklungsteams bei

    • Softwareentwicklung

    • DevOps

    • Performanceanalyse und -optimierung

  • Schulungen

    • Spring Boot

    • Monitoring

    • Java-Performance

Wie merke ich, dass meine Anwendung langsam ist?

 
 
 
 
 
 
 
 
 
 
 

Lasttest

  • Erzeugt synthetisch eine reproduzierbare Last auf der Anwendung

  • Lastprofil, das durch Lasttest erzeugt wird, sollte repräsentativ für tatsächliche Nutzung der Anwendung sein

  • Sollte regelmäßig, idealerweise automatisiert im Rahmen der CI/CD-Pipeline ausgeführt werden

Gatling

  • Lasttest mit Fokus auf HTTP-basierten Anwendungen

  • Erzeugen von Szenarios über Capture/Replay ("Recorder") oder über DSL (Java, Kotlin oder Scala)

  • Apache License, Version 2.0

  • gatling.io

Demo

Demo

Woran liegt es, dass meine Anwendung langsam ist?

Ursachen

  • Verwendung ineffizienter Algorithmen

  • Verwendung inperformanter Bibliotheken

  • Behinderung der parallelen Verarbeitung durch ungünstiges Locking

  • Erzeugen unnötig vieler Objekte

  • Erzeugen von Objekten, die nicht wieder freigegeben werden ("Memory Leak")

Profiling

Profiling

  • Analyse des Laufzeitverhaltens von Software

    • Messen von Geschwindigkeit

    • Nebenläufigkeit

    • Speichernutzung

  • umgesetzt über Instrumentierung oder statistische Auswertung (Sampling)

async-profiler

  • Open-Source-Profiler für Java mit geringem Overhead

  • Verfügbar für Linux, MacOS, Windows (nur via IntelliJ IDEA Ultimate)

  • Visualisierung typischerweise als sogenannter Flame-Graph

  • Apache License, Version 2.0

  • GitHub Mark 120px plus jvm-profiling-tools/async-profiler

CPU-Profiling

CPU-Profiling

  • Erfasst, wieviel CPU-Zeit die JVM in welcher Methode verbringt

  • Sampled Stacktraces, die Java-Methoden, Native-Calls, JVM-Code bis zum Aufruf von Kernelfunktionen umfassen

  • Nutzt perf_events aus dem Linux-Kernel und AsyncGetCallTrace aus der JVM

  • Tipp: JVM mit Debug-Symbolen verwenden, damit Aufrufe innerhalb von libjvm aufgelöst werden können

  • Kernel-Optionen, um als Nicht-Root-Nutzer perf_events zu nutzen (z.B. für Entwicklung):

sysctl kernel.kptr_restrict=0
sysctl kernel.perf_event_paranoid=1

CPU-Profiling

async-profiler -f profile-cpu.html -d 30 -e cpu PetClinicApplication
  • Optionen

    • -d: Dauer des Profilings

    • -e: Event-Typ

    • -i: erlaubt, das Profiling-Intervall zu setzen (Default: 10ms)

Lock-Profiling

Lock-Profiling

  • Erfasst, wie lange Code auf das Betreten eines Monitors/Locks gewartet hat

  • Zähler ist die kumulierte Wartezeit auf das Lock

  • Top Frame: Klasse des Monitors

async-profiler -f profile-lock.html -d 30 -e lock PetClinicApplication

Allocation-Profiling

Allocation-Profiling

  • Sampled die Allokation von Speicher über HotSpot-Callbacks:

    • Allokation eines Objects in einem neu erzeugten TLAB

    • Allokation eines (großen) Objekts außerhalb des TLAB

  • Eher grobgranular (alle n KB, mit n = durchschnittliche TLAB Größe)

  • Darstellung als Flame-Graph, Top Frame: Klasse des allokierten Objekts

  • --live Option seit Version 2.9 zur Erkennung von Memory-Leaks

async-profiler -f profile-alloc.html -d 30 -e alloc --live PetClinicApplication

Memory-Leaks finden

  • Heap-Dump erlaubt Ermittlung von Objekten, die nicht mehr existieren sollten, aber nicht unbedingt deren Herkunft

  • Profiler zeigen auch wo Objekte allokiert wurden, sind aber durch Instrumentierung oft langsam und ungeeignet für Produktion

  • Async-Profiler hat geringen Overhead durch Sampling und ist für Produktion geeignet, bekommt aber nicht jede Allokation mit

async-profiler -e alloc --live --total start PetClinicApplication

# Lasttest laufen lassen

jcmd PetClinicApplication GC.run # Garbage collection auslösen

async-profiler -f profile-alloc.html stop PetClinicApplication

Java Flight Recorder

  • Gleichzeitiges Profiling von CPU, Locks und Allocation

  • Profiling-Ergebnisse werden als JFR-Events gespeichert

  • Mit --jfrsync CONFIG kann Java Flight Recording synchron zum Profiling aktiviert werden

async-profiler -f profile.jfr -d 30 -i 500u -e cpu,lock,alloc \
  --jfrsync default PetClinicApplication
  • converter.jar enthält Konverter für JFR-Dumps, u.a. um Flame Graphs zu erzeugen

java -cp converter.jar jfr2flame profile.jfr profile-cpu.html
java -cp converter.jar jfr2flame --lock --total profile.jfr profile-lock.html
java -cp converter.jar jfr2flame --alloc --total profile.jfr profile-alloc.html

Weitere Profiling-Events

  • Basic Events:

    • wall, itimer

  • Aufruf von Java-Methoden:

    • ClassName.methodName

  • Perf Events:

    • page-faults, context-switches

    • cycles, instructions

    • cache-references, cache-misses

    • branch-instructions, branch-misses

    • …​

Laden des Profilers

Laden des Profilers

  • Attachen zur Laufzeit über profiler.sh

    • Beim Start der JVM sollten dann folgende Flags gesetzt werden:

-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
  • Beim Starten der JVM als Java-Agent

java -agentpath:/path/to/libasyncProfiler.so

Profiling in Containern

  • Ausführen des Async-Profilers im Container

    • Container mit erhöhten Privilegien:

      • --security-opt seccomp=unconfined

      • --cap-add SYS_ADMIN

    • Nutzung des Profiling-Mode -e itimer statt cpu über Perf-Events

  • Ausführen des Profilers "von außen" mit Super-User-Rechten

    • --fdtransfer

  • Hinweis: Profiling-Ergebnisse müssen aus dem Container kopiert oder auf ein Volume geschrieben werden

Pyroscope

pyroscope logo

Pyroscope

  • Continuous Profiling

  • Unterstützt viele Technologien (Go, Python, Ruby, PHP, NodeJS, Rust, Java)

  • Java-Agent basiert auf async-profiler

  • Apache License, Version 2.0

  • pyroscope.io

Java Integration (1/2)

  • Dependency hinzufügen:

<dependency>
    <groupId>io.pyroscope</groupId>
    <artifactId>agent</artifactId>
    <version>0.11.1</version>
</dependency>
  • Aus Java-Code heraus starten (z.B. in der main-Methode):

PyroscopeAgent.start(
    new Config.Builder()
        .setApplicationName("petclinic-rest")
        .setProfilingEvent(EventType.CPU)
        .setProfilingLock("0")
        .setProfilingInterval(Duration.of(500, MICROS))
        .setServerAddress("http://localhost:4040")
        .build()
);

Java Integration (2/2)

  • Alternativ beim Starten als Java-Agent angeben:

java -javaagent:pyroscope.jar -jar app.jar
  • Konfiguration über Umgebungsvariablen, u.a.

    • PYROSCOPE_APPLICATION_NAME

    • PYROSCOPE_SERVER_ADDRESS

    • PYROSCOPE_PROFILING_INTERVAL

    • PYROSCOPE_PROFILER_EVENT

    • PYROSCOPE_PROFILER_LOCK

    • PYROSCOPE_PROFILER_ALLOC

Demo

Q&A – Fragen?

 
 
 
 
 

tgbyte bk
www.tgbyte.de

Thilo-Alexander Ginkel · Stefan Reuter
@thiloginkel · @StefanReuter
thilo.ginkel@tgbyte.de · stefan.reuter@tgbyte.de

Bildnachweis (1/2)

Bildnachweis (2/2)