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

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

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

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

PHP предоставляет несколько методов для понимания того, что происходит с памятью. Во-первых, у вас есть memory_get_usage(), который принимает логический аргумент. В зависимости от этого аргумента метод возвращает память, используемую 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
Войти в полноэкранный режим

Выйти из полноэкранного режима

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


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

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

# 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 секунды (это значение по умолчанию для часов).
Параллельно с этим вы запускаете feh /tmp/memory_get_usage-graph.png для отображения файла png. Что хорошего с feh заключается в том, что он обновляется автоматически, поэтому вам не нужно делать ничего особенного, чтобы получить график в реальном времени. 🤯 feh делает очень мало, но делает это хорошо.

Описание изображения

2 интересные вещи, на которые стоит обратить внимание:

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


Создание показателей

Здесь для создания метрик вы можете использовать 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, так как произвожу метрики в
регулярный темп.

Это все беззастенчиво украдено у Переполнение стека

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), и дал мне лучшее понимание моего приложения. В частности, я понял, что пакетная обработка, которую я выполнял, основывалась на пакетах объектов разного размера, и что уверенность в том, что все они примерно одинакового размера, поможет избежать ситуаций, когда серия больших объектов вызывает перегрузку. ошибка памяти.