Профилирование JVM в Kubernetes : три больших шага

Смирнов Вячеслав, 2021

Исследую и создаю результаты нагрузки в ВТБ, ДБО: vtbbo.ru

И развиваю чат @qa_load

100 JVM работающих друг с другом и базой

На тестовом стенде

Особенности профилирования JVM в Kubernetes

Особенности Kubernetes

Выделение ресурсов для нужд профилирования

Особенности Kubernetes

Как выполнять анализ: от потоков к коду

Анализ

Анализ взаимодействия микросервисов

Анализ

Cтандартизация процесса в большой команде

Масштабирование

Обмен знаниями, передача опыта, автоматизация

Масштабирование

Особенности профилирования JVM в Kubernetes

Особенности Kubernetes

Бизнес-отчет по тестированию производительности

Особенности Kubernetes

Бизнес-отчет по тестированию производительности

Особенности Kubernetes

Увеличиваем количество потоков или реплик?

Особенности Kubernetes

Когда JVM профайлер не нужен и чем его заменить

Особенности Kubernetes

Профилирование в цикле тестирования

  1. Регрессионный тест производительности (JMeter/Gatling)
  2. Статистика по логам, детали по ERROR (Kibana/Grafana, grep)
  3. Бизнесс-метрики производительности (Яндекс.Метрика, ...)
  4. Статистика по запросам (Zipkin, Jaeger)
  5. Мониторинг системных метрик (CPU, Memory, IO)
  6. Прикладной мониторинг (JVM MBean, SQL stat)
  7. Профилирование JVM
  8. perf, strace, lsof, ...

Влияние количества потоков сервиса на профилирование

Особенности профайлеров

Если нужно посчитать процент активности

Инструмент Примечание
JFR (Java Fligth Recorder)
SJK (Swiss Java Knife)
AsyncProfiler
JVisualVM в режиме семплирования
JProfiler в режиме семплирования
YourKit Java Profiler в режиме семплирования

Stack Trace

Метод Семплы
HashMap.putVal 4
HashMap.put 5
HttpContext.setAttribute:75 7
CoreContext.setAttribute:105 7
Impl.setupClient:1050 8
Impl.sample:613 8
Proxy.sample:613 8
... 9
Метод Семплы
HashMap.putVal 49%
HashMap.put 57%
HttpContext.setAttribute:75 77%
CoreContext.setAttribute:105 79%
Impl.setupClient:1050 80%
Impl.sample:613 81%
Proxy.sample:613 81%
... 90%

Соберем тестовый стенд

Результаты замеров длительности

Средняя длительность для мсек
HTTP-запрос 100
SQL-запрос 50
Java-метод 10

Профилирование надо проводить под нагрузкой

1000 Java-методов * 3 потока * 10 мс / 11 мс = 2 700 семплов

Средняя длительность для мсек частота (в сек)
HTTP-запрос 100
SQL-запрос 50
Java-метод 10
SJK (3 активных потока + 120 спящих) 11 90

Профилирование надо проводить под нагрузкой

1000 HTTP запросов * 3 потока * 100 мс / 11 мс = 27 000 семплов

Средняя длительность для мсек частота (в сек)
HTTP-запрос 100
SQL-запрос 50
Java-метод 10
SJK (3 активных потока + 120 спящих) 11 90

Меньше спящих потоков - выше точность

1000 HTTP запросов * 3 потока * 100 мс / 3.5 мс = 85 000 семплов

Средняя длительность для мсек частота (в сек)
HTTP-запрос 100
SQL-запрос 50
Java-метод 10
SJK (3 активных потока + 120 спящих) 11 90
SJK (3 активных потока + 20 спящих) 3.5 290

Меньше спящих потоков - выше точность

Снижаем server.tomcat.max-threads с 100 до 5

Снижаем server.tomcat.max-threads с 100 до 5

Повышается точность профилирования в 3 раза

Снижаем server.tomcat.max-threads с 200 до 5

Повышается точность профилирования в 5 раз

Частота семплирования в SJK (по факту)

# Запуск профилирования с максимальной интенсивностью
$ java -jar ./sjk-0.17.jar stcap -s localhost:9010 \ 
  -o "result.sjk"

# Отображение фактической интенсивности
$ java -jar ./sjk-0.17.jar ssa -f "result.sjk" \
  --thread-info --numeric -co -si FREQ  
Freq.
59.5
59.5
59.5
59.5
59.5
59.5
...

Влияние CPU Limit на профилирование

Особенности Kubernetes

Соберем тестовый стенд с малым CPU Limit

Точность профилирования снилизась в 10-13 раз

Средняя длительность для мсек частота (в сек)
SJK (когда было достаточно CPU) 11 90
SJK (нехватка CPU, малый CPU Limit) 143 7

Сам SJK потребляет до 0,4 ядра при 200 потоках

Задавая лимит CPU помни о накладных расходах

Комфортная интенсивность: раз в 100 мсек

Для большей точности можно собирать метрики дольше

# Профилирование с заданной интенсивностью
$ java -jar ./sjk-0.17.jar stcap \ 
  -s localhost:9010 \ 
  -o "result.sjk" \ 
  --sampler-interval 100ms \
  --timeout 10m

Влияние Memory Limit на профилирование

Особенности Kubernetes

Процент активности, длительность и количество

Их позволяет оценить инструментирующее профилирование

Инструмент Примечание
JVisualVM в режиме Startup Profiler
JProfiler в режиме инструментации
YourKit Java Profiler в режиме инструментации

Инструментация на лету расходует HEAP

Инструментация новых объектов расходует CPU

При добавлении JvmAgent для инструментации

Стоит увеличить HEAP Xmx, CPU и Memory Limit

БылоСтало +1 на всё
limits:
    memory: 5Gi
    cpu: 3
requests:
    memory: 1Gi
    cpu: 0.5
# JAVA_MAX_MEM_RATIO=50
# Xmx = 2.5Gi
limits:
    memory: 7Gi
    cpu: 4
requests:
    memory: 2Gi
    cpu: 1.5
# JAVA_MAX_MEM_RATIO=50
# limits.memory += 2Gi

При добавлении JvmAgent для инструментации

Не стоит подавать большую нагрузку, достаточно ручных запросов

Добавление ресурсов при профилировании

Особенности Kubernetes

Может понадобиться +1 CPU, +1 GiB Memory

Limit не задан у профилируемых сервисов

Limit задан не у всех профилируемых сервисов

Limit задан у всех профилируемых сервисов

Может понадобиться +1 CPU, +1 GiB Memory

Подключение профайлера к JVM в Kubernetes

Особенности профайлеров

Удаленное подключение

  • Доступ до Pod-ы на время:
    • PortForward
  • Доступ до Service (1 Pod):
    • NodePort
    • LoadBalancer
  • Доступ до Service (2+ Pod):
    • Не получится

Опции JMX, RMI для удаленного подключения

Опции JVM задаются в Deployment

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.rmi.port=9010
-Dcom.sun.management.jmxremote.local.only=true
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=127.0.0.1

Проброс локального порта в Pod

kubectl port-forward "<имя поды>" 9010:9010

Две Pod одного Service так не подключить

Опции JVM задаются в Deployment — общие для Pod-ов

-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.rmi.port=9010

Проброс локального порта в Pod дважды не сделать

$ kubectl port-forward "<имя поды 1>" 9010:9010
Forwarding from 127.0.0.1:9010 -> 9010
Forwarding from [::1]:9010 -> 9010

$ kubectl port-forward "<имя поды 2>" 9010:9010
unable to create listener: Error listen tcp4 127.0.0.1:9010: bind:
Only one usage of each socket address (protocol/network address/port)
is normally permitted.

Профилирование другого Service — другой порт

Опции JVM на другой порт 9011

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9011
-Dcom.sun.management.jmxremote.rmi.port=9011
-Dcom.sun.management.jmxremote.local.only=true
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=127.0.0.1

Проброс другого порта для другого сервиса

kubectl port-forward "<имя поды>" 9011

Опции JMX, RMI для NodePort

Открыть NodePort в Service

  ports:
    - name: JMX
      protocol: TCP
      port: 31111
      targetPort: 31111
      nodePort: 31111

Если порт 31111 свободен и открылся, то задать его в Deployment

-Dcom.sun.management.jmxremote.port=31111
-Dcom.sun.management.jmxremote.rmi.port=31111
-Djava.rmi.server.hostname=<адрес NodeHost>

JVM in Linux containers, surviving the isolation

Алексей Рагозин

SJK удаленное (локальное) профилирование

С ограниченной интенсивностью

java -jar ./sjk-0.17.jar stcap \
  -s localhost:9010 \
  -o "result.sjk" \
  --sampler-interval 100ms \
  --timeout 5m

С максимальной интенсивностью

java -jar ./sjk-0.17.jar stcap \
  -s localhost:9010 \
  -o "result.sjk" \
  --timeout 5m

JMC для JFR удаленное профилирование

Для OpenJDK 8u272 и старше, OpenJDK 11, OpenJDK 12, ...

  • Настройки JVM, кроме опций JMX/RMI, не требуются

Для OpenJDK 8u271 и младше

  • Монтировать OpenJDK 8u272 и старше в контейнер

Для Oracle JDK 8

  • -XX:+UnlockCommercialFeatures -XX:+FlightRecorder

JMC: в меню File / Connect ...

JMC: указать JMX/RMI порт

JMC: запустить JMX Console, для проверки

JMC: запустить Flight Recording

VisualVM: File / Add JMX Connection ...

VisualVM: указать JMX-порт и имя подключения

VisualVM: в Applications/Local открыть

VisualVM: на вкладке Sampler нажать CPU

Удаленное подключение к JavaAgent

  1. Создать Persistent Storage или каталог на NodeHost (hostpath)
  2. Deployments: смонтировать каталог в поду
  3. Kubectl (cp): загрузить в каталог профайлер для Linux
  4. Deployments: указать путь к javaagent и свободный порт
  5. Service: открыть NodePort или сделать Port Forward (Kubectl)
  6. Запустить профайлер локально (JProfiler, YourKit)
  7. Создать удаленное подключение и профилировать
  • Всего один сетевой порт
  • Можно подключиться к 2+ подам одного сервиса

Для Alpine Linux: linux_musl-x64

Для CentOS, RHEL и других: linux-x64

В Pods / Exec посмотреть на версию ОС

Для Alpine получится так

$ cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
...

Для других ОС получится другое имя

$ cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
...

В Deployment смонтировать и подключить агент

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jprofiler
          hostPath:
            path: /opt/data/jprofiler12.0.2/ # на диске    
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS                                             # свободный порт
              value: -agentpath:/tmp/jp/bin/linux_musl-x64/libjprofilerti.so=port=8849,nowait
          volumeMounts:
            - name: jprofiler
              mountPath: /tmp/jp             # в поде 

Для CentOS: linux-x64 вместо linux_musl-x64

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jprofiler
          hostPath:
            path: /opt/data/jprofiler12.0.2 # просто linux-x64   
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: -agentpath:/tmp/jp/bin/linux-x64/libjprofilerti.so=port=8849,nowait
          volumeMounts:
            - name: jprofiler
              mountPath: /tmp/jp

Для другого сервиса можно оставить все также

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jprofiler
          hostPath:
            path: /opt/data/jprofiler12.0.2/ # или linux_musl-x64  
      containers:
        - name: test-webserver-other
          env:
            - name: JAVA_OPTIONS
              value: -agentpath:/tmp/jp/bin/linux-x64/libjprofilerti.so=port=8849,nowait
          volumeMounts:
            - name: jprofiler
              mountPath: /tmp/jp

Swiss Java Knife (SJK) (репозиторий проекта), простой

Алексей Рагозин

Упрощенная настройка JMX для SJK

Опции JVM

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
#-Dcom.sun.management.jmxremote.rmi.port=9010
#-Dcom.sun.management.jmxremote.local.only=true
#-Djava.rmi.server.hostname=127.0.0.1

Упрощенная настройка JMX для SJK

kind: Deployment
spec:
  template:
    spec:  
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-
                -Dcom.sun.management.jmxremote
                -Dcom.sun.management.jmxremote.port=9010
                -Dcom.sun.management.jmxremote.authenticate=false
                -Dcom.sun.management.jmxremote.ssl=false

Запуск SJK осуществляется из POD-ы

Найти имя поды <podname>

kubectl get pods | grep test-webserver
pod="<podname>"

Проброс портов не нужен, нужно копирование файла

kubectl cp "sjk-0.17.jar" $pod:/tmp/sjk.jar

Запуск профайлера

kubectl exec $pod -- java -jar /tmp/sjk.jar stcap 
--socket localhost:9010 --timeout 5m --sampler-interval 100ms
--output /tmp/result.sdt &

Запуск SJK из git bash для Windows

#!/bin/sh
duration=5m
pod="<podname>"
kubectl exec $pod -- sh -x -c \
"java -jar   /tmp/sjk.jar         
    -X stcap 
    --socket localhost:9010 
    --timeout $duration 
    --sampler-interval 100ms
    --output /tmp/result.sdt 
            >/tmp/result.out.txt 
           2>/tmp/result.error.txt ; 
echo Complete" &

Скачивание результатов через kubectl exec

#!/bin/sh
pod="<podname>"
currdir=$(pwd)
currdate=$(date '+%Y-%m-%d_%H-%M-%S')
# Каталог с результатами
result="$currdir/$currdate/$pod/"
mkdir -p "$result"
cd "$result"
# Упаковать файлы /tmp/result.* POD-ы и распаковать локально
kubectl exec $pod -- sh -c 'cd /tmp ; tar cf - result.*' \
    | tar xf - -C "$result"
explorer .
cd "$currdir"

Управление Java Flight Recorder (блог НПО Криста на habr)

Виктор Вербицкий

Основные опции

-XX:StartFlightRecording=disk=true,maxsize=1g,maxage=24h,filename=/tmp/recording.jfr \ -XX:FlightRecorderOptions=repository=/tmp/ diagnostics/,maxchunksize=1m,stackdepth=1024

  • disk=true запись на диск, а не в память
  • maxsize=1g,maxage=24h чтобы диск не переполнился
  • filename=/tmp/recording.jfr параметры JFR
  • repository=/tmp/diagnostics/ каталог результатов
  • maxchunksize=1m размер одного файла будет 2-3 МБайт
  • stackdepth=1024 глубина стека увеличена

Параметры JFR: filename=Путь-к-файлу

  • Параметры по умолчанию в файле jre\lib\jfr\default.jfc
  • Файл в формате XML
  • Можно редактировать в JMC

Параметры JFR можно редактировать в JMC

Упрощенная настройка Java Flight Recorder

kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-
                -XX:StartFlightRecording=disk=true,maxsize=1g
                -XX:FlightRecorderOptions=repository=/tmp/results,maxchunksize=1m,stackdepth=1024

Тонкая настройка Java Flight Recorder

С файлом настроек /tmp/jfr/prof.jfc

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jfr
          hostPath:
            path: /opt/data/jfr 
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-
                -XX:StartFlightRecording=disk=true,maxsize=1g,filename=/tmp/jfr/prof.jfc
                -XX:FlightRecorderOptions=repository=/tmp/results,maxchunksize=1m,stackdepth=1024
          volumeMounts:
            - name: jfr
              mountPath: /tmp/jfr

Тонкая настройка Java Flight Recorder

Файл настроек prof.jfc сохраняется во внешний каталог

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jfr
          hostPath:
            path: /opt/data/jfr     # Каталог /opt/data/jfr с файлом prof.jfc
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-
                -XX:StartFlightRecording=disk=true,maxsize=1g,filename=/tmp/jfr/prof.jfc
                -XX:FlightRecorderOptions=repository=/tmp/results,maxchunksize=1m,stackdepth=1024
          volumeMounts:
            - name: jfr
              mountPath: /tmp/jfr

Тонкая настройка Java Flight Recorder

Внешний каталог с файлом монтируется в Pod

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jfr
          hostPath:
            path: /opt/data/jfr    
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-
                -XX:StartFlightRecording=disk=true,maxsize=1g,filename=/tmp/jfr/prof.jfc
                -XX:FlightRecorderOptions=repository=/tmp/results,maxchunksize=1m,stackdepth=1024
          volumeMounts:
            - name: jfr
              mountPath: /tmp/jfr   # Монтируем /opt/data/jfr в /tmp/jfr

Тонкая настройка Java Flight Recorder

Файл настроек prof.jfc передается в JAVA_OPTIONS

kind: Deployment
spec:
  template:
    spec:
      volumes:
        - name: jfr
          hostPath:
            path: /opt/data/jfr    
      containers:
        - name: test-webserver
          env:
            - name: JAVA_OPTIONS
              value: >-             # Передаем путь /tmp/jfr/prof.jfc в параметр filename
                -XX:StartFlightRecording=disk=true,maxsize=1g,filename=/tmp/jfr/prof.jfc
                -XX:FlightRecorderOptions=repository=/tmp/results,maxchunksize=1m,stackdepth=1024
          volumeMounts:
            - name: jfr
              mountPath: /tmp/jfr  

Async Profiler, поддержка Alpine Linux и не только

Андрей Паньгин

Kubernetes, Docker-контейнеры, Alpine Linux, JDK DevTools

Linux (musl) и Async Profiler

Факторы выбора контейнеров

Популярные образы с OpenJDK

Name OS Ver Dev? Hit
s2i-java CentOS 8/11 JRE 10M
java-centos-openjdk8-jre CentOS 8 JRE 100k
java-centos-openjdk8-jdk CentOS 8 JDK 100k
java-alpine-openjdk8-jre Alpine 8 JRE 100k
java-alpine-openjdk8-jdk Alpine 8 JDK 100k
java-alpine-openjdk11-jre Alpine 11 JRE 100k

Популярные образы с OpenJDK

Первое место Второе место
Операционная систеа CentOS Alpine
Версия Java в OpenJDK 8 11
Cредства разработки JRE (нет dev tools) JDK (есть dev tools)
Маркировка для профайлеров linux-x64 linux-musl-x64

Популярные образы с OpenJDK

Второе место
Операционная систеа Alpine
Версия Java в OpenJDK 11
Cредства разработки JDK (есть dev tools)
Маркировка для профайлеров linux-musl-x64
  • DevTools есть, но их как бы нет, не работают утилиты jcmd, ...
  • К счастью в Async Profiler есть утилита jattach
  • jattach — аналог jcmd и она работает в Alpine внутри Docker

Async Profiler, поддержка Alpine Linux

Нужны права ROOT

Подключение профайлера к JVM в Kubernetes

Особенности профайлеров

Выбор количества реплик сервиса и инструмента

Особенности Kubernetes

Выберу инструмент для детального профилирования

Выберу инструмент с наименьшим замедлением

Выберу путь наименьшего сопротивления

От задачи к инструменту

Как анализировать результаты профилирования

Анализ

Как анализировать результаты профилирования

Анализ результатов семплирования

  1. Визуально оценить работу потоков
  2. Собрать статистику по работе потоков
  3. Выбрать проблемные потоки, исключить несущественные
  4. Собрать статистику и отчет только по выбранным потокам
  5. Выбрать проблемные методы, выделить их в статистике
  6. Выделить ожидание внешних сервисов и систем
  7. Наложить статистику по программный код сервиса

Как визуально оценить работу потоков

Анализ

Потоков много и они работают одновременно

Анализ активной работы потоков

Потоков много, но нет паралельности работы

Потоков много, но нет паралельности работы

Блокировки, анализ блокировок

Ключевые потоки JVM для Spring Boot сервиса

Анализ

Что можно исключить из детального анализа

Достаточно статистику посмотреть

  • RMI и JMX - это само профилирование и мониторинг
    • RMI Scheduler
    • RMI TCP Accept
    • RMI TCP Connection
    • JMX server connection timeout
  • kafka-coordinator-heartbeat-thread
  • Reference Handler
  • Finalizer
  • GC Daemon

Ключевые потоки

Посмотреть на статистику и заглянуть внутрь

  • http-nio-8080-exec-номер
    • http-nio-8080-exec-1
    • http-nio-8080-exec-2
  • Thread-pool-номер
  • Thread-номер
  • OkHttp
  • WebSocket
  • SockJS

Статистика выполнения кода в одном потоке

Анализ

Статистика выполнения кода в нескольких потоках

Анализ

Задаем настройки и потоки для анализа

#!/bin/bash -x 

reportFolder=$(pwd)
java="java"
sjk="$java -jar ../sjk-0.17.jar"

declare -A threads
threads=(\
  ["_all"]='.+' \
  ["http-nio-8080-exec"]='http-nio-8080-exec.+' \
  ["pool-3-thread"]='pool-3-thread-.+' \
  ["OkHttp https"]='OkHttp https.+' \
  ["Thread-2"]='Thread-2' \
  ["WebSocket"]='WebSocket.+' \
)

Выделяем статистику по потокам в файл

И строим гистограмму по выделенной статистике

for thread in "${!threads[@]}"
do
  mkdir "${reportFolder}/${thread}"

  $sjk \
  stcpy \
  --input "${reportFolder}/sjk.sdt"  \
  --thread-name "${threads[$thread]}" \
  --trace-filter "!sun.misc.Unsafe.park" \
  --output "${reportFolder}/${thread}.sdt"
  
  $sjk \
  ssa \
  --file "${reportFolder}/${thread}.sdt" \
  --histo \
  > "${reportFolder}/${thread}/${thread}.histo.txt"
done                                                                                 

Выделяем статистику по потокам в файл

Или flame-диаграмму по выделенной статистике

for thread in "${!threads[@]}"
do
  mkdir "${reportFolder}/${thread}"

  $sjk \
  stcpy \
  --input "${reportFolder}/sjk.sdt"  \
  --thread-name "${threads[$thread]}" \
  --trace-filter "!sun.misc.Unsafe.park" \
  --output "${reportFolder}/${thread}.sdt"

  $sjk \
  ssa \
  --file  "$reportFolder/${thread}.sdt" \
  --flame \
  --width 2000 \
  > "${reportFolder}/${thread}/${thread}.flame.svg"
done                                                                                 

Статистика выполнения отдельного метода

Анализ

Запросы разделяются на doPost, doGet, ...

Запросы разделяются на doPost, doGet, ...

По префиксу: ru.vtb.dbo...

Запросы разделяются на doPost, doGet, ...

По префиксу: ru.vtb.dbo...

По имени: organizationsSearch

В SJK фильтры методов можно объединять по +

org.springframework.web.servlet.FrameworkServlet.doPost+

ru.vtb.dbo.statement.resource.client+

organizationsSearch

Выполнять фильтрацию по методам

thread="http-nio-8080-exec"
for method in "${!methods[@]}"
do
    $sjk \
    stcpy \
    --input "${reportFolder}/${thread}.sdt"  \
    --trace-filter "${methods[$method]}" \
    --output "${reportFolder}/${thread}.${method}.sdt"
    mkdir "${reportFolder}/${thread}/${method}"
    
    $sjk \
    ssa \
    --file "${reportFolder}/${thread}.${method}.sdt" \
    --histo \
    > "${reportFolder}/${thread}/${method}/${method}.histo.txt"
done

Ожидание ответа от SQL-сервера

Анализ

С дополнительной фильтрацией

От Postgre SQL Server, например

$sjk \
ssa \
--file "${reportFolder}/${thread}.${method}.sdt" \
--trace-filter "org.postgresql.jdbc" \
--histo \
> "${reportFolder}/${thread}/${method}/${method}.jdbc.only.histo.txt"

С дополнительной фильтрацией

Без Postgre SQL Server, например

$sjk \
ssa \
--file "${reportFolder}/${thread}.${method}.sdt" \
--trace-filter "!org.postgresql.jdbc" \
--histo \
> "${reportFolder}/${thread}/${method}/${method}.wo.jdbc.histo.txt"

Автоматизация формирования отчета

Масштабирование профилирования на всю команду

Масштабирование

Пишем подробный отчет по профилированию

Масштабирование

Пишем инструкцию по профилированию в ручном режиме

Масштабирование

Записываем видео с демонстрацией профилирования

Масштабирование

Парное профилирование и анализ результатов

Масштабирование

Пишем скрипты автоматизации профилирования

Масштабирование

Пишем скрипты автоматизации профилирования

Масштабирование

Примонтировать/загрузить профайлер

Настроить JAVA_OPTIONS на профилирование

Запустить профилирование

Скачать результаты профилирования

kubectl

kubectl, Web UI (Dashboard)

kubectl, Web UI (Dashboard), Lens, ...

Помещаем скрипты в CI/CD окружение: добавляем Web UI

Масштабирование

Для микросервисов задач не стало меньше

Особенности Kubernetes

Подключение профайлера к JVM в k8s

Добавь ресурсов +1 CPU и если JavaAgent, то и +1 GiByte HEAP

При большой нагрузке профилируй локально, семплированием

Для Alpine Linux выбирай musl реализации инструментов

Как выполнять анализ: от потоков к коду

Анализ

Анализ результатов профилирования

Посмотреть на потоки визуально

Собрать статистику по потокам

Детализировать работу потоков

JProfiler и YourKit также перехватывают SQL и HTTP-запросы

С SJK несложно автоматизировать формирование отчета

JDK Flight Recorder собирает огромное количество метрик

Обмен знаниями, передача опыта, автоматизация

Масштабирование

Обмен знаниями, передача опыта, скрипты

Документировать результат

Доброжелательность и терпение

Стремиться к автоматизации и регрессионному профилированию

Профилирование JVM в Kubernetes : три больших шага

Вопросы и ответы

# Масштабирование сервисов и их профилирования Микросервисная архитектура является популярной. Kubernetes позволяет запускать множество микросервисов и предоставляет API доступа к ним. Сотни сервисов. С каждым из которых работают разные инженеры. Часто нужно узнать детали работы приложения в контейнере. Нужна трассировка или профилирование. А также последующий анализ результатов. И этим также будут заниматься разные инженеры. Расскажу, как можно сделать процесс профилирования проще. Мастабировать его на все сервисы и всю команду. # Этапы профилирования и стадии масштабирования Для трассировки и профилирования нужно добавить внутрь контейнера специальные инструменты, которых в легковесных контейнерах нет. Часто в контейнерах нет ни JDK, ни утилит профилирования, ни утилит трассировки, ни прав админа, ни доступа к репозиториям пакетов и сети Интернет, чтобы установить недостающие пакеты. Но выполнить профилирование можно: - примонтировать в контейнер каталоги с файлами - изменить параметры запуска JVM - запустить внутри контейнера команды - скачать из контейнера файлы с результами Это повторяемый однообразный процесс, особенно, когда на тестовом стенде работает 100 микросервисов. При этом, это непростой процесс. А его должна уметь выполнять вся команда. Чтобы масштабировать запуск профилирования на всю команду можно: - написать подробную инструкцию - выполнять профилирование по Skype/Zoom/... совместно - автоматизировать процесс с помощью скриптов - организовать запуск профилирования из CI/CD Но потом понадобится анализ результатов профилирования. Анализ становится повторяемым однообразным процессом, особенно, когда на тестовом стенде работает 100 микросервисов, которые нужно регулярно профилировать. Анализ - получение фактов, сравнимых чисел, из результатов профилирования. При этом, это непростой процесс. А его должна уметь выполнять вся команда. Чтобы мастабировать анализ результатов профилирования на всю команду можно: - написать подробные отчеты - выполнять анализ профилирования по Skype/Zoom/... совместно - автоматизировать процесс анализа результатов с помощью скриптов - организовать анализ результатов профилирования из CI/CD А в завершении анализа, бывает нужно сравнить текущие результаты с предыдущими. Чтобы сделать вывод - ускорился метод или нет. И этот этап также нуждается в масштабировании и автоматизации. Таким образом выделяется четыре этапа: 1. Подготовка микросервиса к профилированию 2. Запуск профилирования 3. Анализ результатов 4. Сравнение Каждый из которых проходит четыре стадии: 1. Описание в виде инструкции 2. Совместное выполнение с коллегой 3. Автоматизация процесса с помощью скриптов 4. Возможность запуска скриптов с помощью CI/CD # Технологии автоматизации этапов профилироваия Благодаря Kubernetes и инструменту kubectl, есть единый для всех сервисов способ выполнения первых двух этапов: - Подготовка микросервиса к профилированию - Запуск профилирования Благодаря возможностям инструментов профилирования по запуску в консольном режиме, есть автоматизируемый способ выполнения второго и третьего этапа. - Запуск профилирования - Анализ результатов И если профилирование проводилось для сервисов, работающих при схожей нагрузке, и проводилось схожим образом, то сравнить результаты профилирования можно и скриптом и в Excel и в Grafana: - Сравнение тоже автоматизируется Интересно, как это можно сделать? # Kubernetes, JVM в Docker и kubectl Популярные образы с OpenJDK от fabric8 | Name | OS | Ver | Dev? | Hit | |---|---|---|---|---|---| | [s2i-java](https://hub.docker.com/r/fabric8/s2i-java) | CentOS | 8/11 | JRE | 10M | | [java-centos-openjdk8-jre](https://hub.docker.com/r/fabric8/java-centos-openjdk8-jre) | CentOS | 8 | JRE | 100k | | [java-centos-openjdk8-jdk](https://hub.docker.com/r/fabric8/java-centos-openjdk8-jdk) | CentOS | 8 | JDK | 100k | | [java-alpine-openjdk8-jre](https://hub.docker.com/r/fabric8/java-alpine-openjdk8-jre) | Alpine | 8 | JRE | 100k | | [java-alpine-openjdk8-jdk](https://hub.docker.com/r/fabric8/java-alpine-openjdk8-jdk) | Alpine | 8 | JDK | 100k | | [java-alpine-openjdk11-jre](https://hub.docker.com/r/fabric8/java-alpine-openjdk11-jre) | Alpine | 11 | JRE | 100k | Небольшое разнообразие. Кроме таких JVM также применяется IBM OpenJ9. С помощью kubectl можно: - менять Deployment-файл - монтировать каталоги в контейнер - менять JAVA_OPTIONS - добавлять javaAgent - открывать JMX/RMI-порты - менять другие параметры запуска JVM - копировать файлы в контейнер - получать доступ до сетевых портов контейнера - запускать команды внутри контейнера - копировать файлы из контейнера Это все, что нужно для запуска и получения результатов профилирования. # JVM профайлеры У популярных инструментов и технологий профилирования есть консольный режим запуска: - JDK Flight Recorder c утилитой jcmd или параметрами запуска JVM - SJK с подключением по JMX/RMI или по PID-процесса - AsyncProfiler с утилитой jattach или JVM агентом - JProfiler с утилитой jpenable или JVM агентом - YourKit с утилитой yjp-controller-api-redist.jar и JVM агентом У профайлеров есть разные варианты сбора событий: - JDK Flight Recorder собирает внутренние события JVM - SJK использует семплирование - AsyncProfiler использует семплирование - JProfiler использует и семплирование и инструментацию - YourKit использует и семплирование и инструментацию Выделяется три варианта сбора событий по длительности работы кода: - внутренние события - семплирование - инструментация Инструментация требует значительных накладных расходов, большое приложение может проходить инструментацию, предшествующую началу профилирования от 20 до 60 минут. И приложение значительно замедляется при выполнении профилирования с инструментацией. Таким образом для повседневного профилирования рекомендется использовать подход JDK Flight Recorder со сбором внутренних событий JVM, почти без накладных расходов. И подход с семплированием потоков с небольшими накладными расходами. # Визуальный анализ результатов - Java Mission Control для JFR-файлов - Flame-диаграммы в SJK - Flame-диаграммы в AsyncProfiler - JProfiler GUI и экспорт в HTML в JProfiler - YourKit GUI и экспорт в HTML в YourKit # Анализ и сравнение результатов Также есть программный и консольный режим анализа результатов профилирования, превращения файла с результатами профилирования в числа, записанные в текстовом формате: - Для jfr-файлов от JDK Flight Recorder и AsyncProfiler: - OpenJDK Mission Control Java API для работы с jfr - SJK jfr2json с сохранением в JSON - SJK ssa для allocation и exception с сохранением в TXT и CSV - Для sdt-файлов от SJK: - SJK ssa с сохранением в TXT и CSV - SJK dexp c сохранением в CSV - Для JProfiler: - jpexport с сохранением в XML и CSV - Для YourKit: - консольный экспорт в XML, CSV, TXT Для анализа нужны как визуальные так и числовые результаты профилирования. Чтобы такие результаты получить надо будет запустить консольные команды и скрипты с различными параметрами. Разработкой таких скриптов и предлагаю заняться. # Сравнение результатов Инструменты профилирования позволяют сохранять результат в CSV-формат. А данные в формате CSV удобно сравнивать. Для сравнения результатов достаточно данных: - по активности потоков - по активности прикладных методов То есть сравнение не ищет узкие места, оно показывает измениласть ли длительность работы потоков и методов, и если изменилась, то они стали работать меньше или больше. # Хранение результатов Кроме сбора и анализа результатов возникает задача удобного хранения. Так, чтобы место хранения было общедоступным. Чтобы результаты можно бы было прикрепить к дефекту и отчету. Чтобы структура хранения была удобной. Удобно хранить файловые результаты профилирования в nexus / artifactory / ... - в хранилище артефактов с веб-интерфейсом, которое обычно есть инфраструктуре разработки для JVM. А числовые результаты профилирования в PostgreSQL, InfluxDB и отображать их в Grafana - веб-интерфейс для чистовых данных, которое обучно есть в инфраструктуре тестирования производительности. # Тестовый стенд Соберем тестовый стенд в котором будут: - JVM-приложение SpringBoot в Kubernetes Не в Kubernetes будут работать: - TeamCity Server - TeamCity Agent, с которого будет запускаться профилирование - TeamCity Agent, с которого будет подаваться нагрузка - Nexus для хранения результатов - influxdb - timeseries база данных, используется для хранения клиентских метрик - prometheus - система мониторинга, используется для сбора и хранения метрик cadvisor - grafana - система визуализации, используется для визуализации метрик/логов - loki - система аггрегации логов, используется для хранения логов - vector - коллектор логов, используется для отправки логов в loki - cadvisor - коллектор метрик docker, используется для сбора метрик всего окружения - github.com в качестве git-репозитория # Настройка локального Kubernetes https://kubernetes.io/docs/tasks/tools/ - kubectl - minikube git clone https://github.com/polarnik/JVM-profiling-in-Kubernetes cd ./JVM-profiling-in-Kubernetes/services/ ./setup.sh !! Скрипт при работе удаляет кластер с именем minicube и создает его снова

- Отлдельно заострить внимание на тестовом стенде - Подход к мониторингу на продуктиве несколько другой - особенность работы именно в Kubernetes - как запустить профилирование на 1 поде из 12 - как собрать результаты при падении - почему нужно увеличить память и CPU limit - особенность работы с Alpine - другой способ запуска - На каком этапе мы подключаем профилировщик - реплики - Java должна быть поверх Kubernetes а не наоборот - Как мы профилируем - Масштабирование - часть звучала обще, тут нужна конкретика про Kubernetes, показать примеры, где это нужна почему это нужно на примере облаком, нужны примеры и картинки. - Сколько потоков нужно на два ядра - 2 или 22 - Мы управляем только потоками или ядрами - Вместо состояний потоков - Более явно разобрать пазл - Картинку показывать один раз - глибс, масл - Бизнес-отчет - Какой Scale нужен - Какие Request/Limit нужны - Какой коэффициент масштабирования - Выводы, более явно что людям делать - 3-4 совета - простые и не длинные - что сделать дальше - к чему стремиться - чего бояться - где подкладывать соломку - что будет при переходе от VM к Kubernetes - Как сравнить Zipkin/Jaeger и JVM профайлер - Как собрать цифры с этой системы, 0-й уровень, пусть система сама все скажет

_footer: Изображение <a href="https://pixabay.com/ru/users/nicolaticola-2681567/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=1736209">Nicola Redfern</a> с сайта <a href="https://pixabay.com/ru/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=1736209">Pixabay</a>