Построение графика памяти процесса PHP с помощью Gnuplot 📈

В последнее время я занимался устранением проблем с памятью в процессе, которым владеет моя команда.
Я начал с просмотра этого видео об утечках памяти от Бенуа Жакмона:

Я многому научился в процессе, но также заметил красивые графики памяти в видео и решил, что будет трудно устранить неполадки, если у меня их не будет.
При использовании php-расширений, таких как у Бенуа или Арно Ле Блана, для получения снимка памяти, очень важно продумать наиболее подходящий момент для получения снимка, чтобы поймать утечку памяти, за которой вы, возможно, охотитесь.
Конечно, для этого можно использовать MemoryUsageProcessor от Monolog, но я подумал, что было бы полезнее получить что-то более ✨ наглядное✨.

В наших средах мы используем Datadog, но в моей системе разработки его нет.

Есть несколько показателей, которые можно отследить для устранения проблем с памятью, некоторые из них предоставляются ОС и обычно сообщаются Datadog, другие — PHP через memory_get_usage() (в настоящее время у меня нет возможности отслеживать их в производстве).

Измерения из PHP

PHP предоставляет несколько методов для понимания того, что происходит с памятью. Во-первых, у вас есть memory_get_usage(), который принимает аргумент boolean. В зависимости от этого аргумента, метод возвращает память, используемую PHP, или память, выделенную PHP. При освобождении памяти, как правило, первый показатель уменьшается, а второй остается стабильным.

Далее, есть memory_get_peak_usage(), который сообщает наибольшее значение использованной или выделенной памяти с начала работы скрипта. Это полезно, поскольку может помочь разработчику понять, что он вызывает memory_get_usage() не там, где использование памяти максимально.

Получение метрик

В случае со сценарием, который я проверял, у меня был главный цикл, который выполнялся часто (но не равномерно). Это все еще хороший кандидат для сбора метрик, как мы увидим.

Вот что я поместил в этот цикл:

<?php
file_put_contents(
    'memory.tsv',
    time() . "t" .
    memory_get_usage(true) / 1024 / 1024 . "t" .
    memory_get_usage(false) / 1024 / 1024 . "t".
    memory_get_peak_usage(true) / 1024 / 1024 . "t" .
    memory_get_peak_usage(false) / 1024 / 1024 . "n",
    FILE_APPEND
);
Войти в полноэкранный режим Выйти из полноэкранного режима

В результате получается TSV-файл, который выглядит следующим образом:


1660831187  20  8.1252593994141 20.359375   19.608978271484
1660831187  20  8.1281814575195 20.359375   19.608978271484
1660831187  20  8.131103515625  20.359375   19.608978271484
1660831187  20  8.134033203125  20.359375   19.608978271484
1660831187  20  8.1369552612305 20.359375   19.608978271484
1660831187  20  8.1398773193359 20.359375   19.608978271484
1660831190  22  8.2328033447266 24.42578125 22.869613647461
1660831190  22  8.2357330322266 24.42578125 22.869613647461
1660831190  22  8.2386627197266 24.42578125 22.869613647461
1660831190  22  8.2415924072266 24.42578125 22.869613647461
Вход в полноэкранный режим Выход из полноэкранного режима

Если посмотреть на первый столбец, то можно заметить, что там есть группы строк, которые могут находиться на расстоянии нескольких секунд друг от друга, так что производство метрик действительно, действительно не является регулярным.

Построение графиков

Затем, чтобы создать график, я обратился к gnuplot, который кажется целой вселенной, а также очень надежным программным обеспечением. Я начал с создания следующего конфигурационного файла:

# config.plt
set term png small size 800,600
set output "/tmp/memory_get_usage-graph.png"

set ylabel "memory in MB"

set yrange [0:*]

set xdata time # x is not just a random number
set timefmt "%s" # we use UNIX timestamps

plot "memory.tsv" using 1:2 with lines axes x1y1 title "memory_get_usage(true) in MB", 
     "memory.tsv" using 1:3 with lines axes x1y1 title "memory_get_usage(false) in MB", 
     "memory.tsv" using 1:4 with lines axes x1y1 title "memory_get_peak_usage(true) in MB", 
     "memory.tsv" using 1:5 with lines axes x1y1 title "memory_get_peak_usage(false) in MB"
Войти в полноэкранный режим Выйти из полноэкранного режима

Как вы видите, можно сообщить gnuplot, что ось x представляет время, что гарантирует, что у вас будет хорошо отформатированная ось X.

График создается путем запуска gnuplot config.plt.

Рендеринг графика

Было бы удобно иметь график, который обновляется с течением времени. Для этого вам понадобятся 2 маленькие программы: watch и feh.

Запустите watch gnuplot config.plt, чтобы убедиться, что png создается каждые 2 секунды (это значение по умолчанию в watch).
Параллельно с этим вы запускаете feh /tmp/memory_get_usage-graph.png для отображения png-файла. Что замечательно в feh, так это то, что он обновляется автоматически, поэтому вам не нужно делать ничего особенного, чтобы получить живой график. 🤯 feh делает очень мало, но делает это хорошо.

Здесь следует отметить 2 интересных момента:

  • Только график для memory_get_usage(false) падает, но он действительно падает, так что утечки памяти нет.
  • Умолчания gnuplot немного уродливы, а я не разработчик фронтенда, так что они так и останутся уродливыми.

Измерения в Linux

Производство метрик

Здесь для получения метрик можно использовать ps.

while true; do
    ps --pid $(pgrep -f some_string_that_identifies_your_process) 
    -o pid=,%mem=,vsz= >> /tmp/mem.log
    gnuplot config.plt
    sleep 1
done
Вход в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, что вы можете использовать это для любого процесса, а не только для PHP.

Построение графиков

На этот раз все немного сложнее, я говорю gnuplot построить 2 метрики, которые
имеют разные единицы измерения на одном графике.

Левая ось Y будет иметь шкалу для первой метрики, а правая ось Y
будет иметь шкалу для второй метрики.

На этот раз я не настраиваю ось X, так как я создаю метрики в
регулярном темпе.

Все это бессовестно украдено из Stack Overflow.

set term png small size 800,600
set output "/tmp/mem-graph.png"

set ylabel "VSZ"
set y2label "%MEM"

set ytics nomirror
set y2tics nomirror in

set yrange [0:*]
set y2range [0:*]

plot "/tmp/mem.log" using 3 with lines axes x1y1 title "VSZ", 
     "/tmp/mem.log" using 2 with lines axes x1y2 title "%MEM"
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь вы можете видеть, что показатели отличаются от показателей из PHP. Я не буду углубляться в эту тему, потому что это не по теме, но при устранении проблем с памятью может быть важно сравнить оба аспекта.

Вынос

Эти графики помогли мне понять разницу между memory_get_usage(true) и memory_get_usage(false), и дали мне лучшее понимание моего приложения. В частности, я понял, что пакетная обработка, которую я выполнял, полагалась на партии объектов, которые не все были одинакового размера, и что обеспечение того, что все они были примерно одинакового размера, поможет избежать ситуаций, когда серия больших объектов вызывает ошибку нехватки памяти.

Оцените статью
devanswers.ru
Добавить комментарий