Основные выдержки из: -Руководство по отладке Node.js Раскрыто место преступления Узла
Node.js развился до сегодняшнего дня и все шире используется вBFF 前后端分离
,全栈开发
,客户端工具
и другие поля. Однако, по сравнению с активной разработкой прикладного уровня, его Runtime находится в состоянии черного ящика для большинства фронтенд-разработчиков, которое не было хорошо улучшено, что препятствует применению Node.js в бизнес-приложении и продвижении.
проблема с утечкой памяти
-
Для типа утечки памяти, которая медленно увеличивается и, в конечном итоге, OOM, у нас достаточно времени, чтобы поймать Heapsnapshot, а затем проанализировать снимок кучи, чтобы найти точку утечки. (См. предыдущую статью «Секреты места преступления Node — быстрое обнаружение утечек памяти в Интернете»)
-
для таких вещей, как
while
Часто бывает слишком поздно уловить сбой условия выхода из цикла, приостановленный процесс, вызванный длительным регулярным выполнением, и OOM приложения за короткое время из-за аномальных запросов.Heapsnapshot
, не было особенно хорошего способа справиться с этим.
Существует два способа создания файла Coredump.
- ОС будет автоматически регистрировать, когда наше приложение аварийно завершает работу и неожиданно завершает работу. Этот метод обычно используется для"Посмертный", который используется для анализа OOM, вызванного Avalanche, для выполнения автоматического дампа ядра также для неперехваченных исключений.
Здесь следует отметить, что эта операция не так уж безопасна: pm2 и другие инструменты-демоны с функцией автоматического перезапуска обычно используются в режиме онлайн для демонизации процесса, а это означает, что если наша программа в некоторых случаях часто дает сбой и перезапускается, то будет сгенерировано большое количество файлов Coredump, и это может даже заполнить диск сервера. Поэтому после включения этой опции обязательно помните о мониторинге и предупреждении диска сервера.
- вызов вручную
gcore <pid>
способ генерировать вручную. Этот метод обычно используется для«Тест на зрение»., который используется для обнаружения проблемы в приостановленном состоянии процесса Node.js.
В этой статье будут представлены несколько рекомендаций по отладке памяти Node.
1 gcore + llnode
1.1 Core & Core Dump
Прежде чем мы начнем, давайте сначала разберемся, что такое Core и Core Dump.
Что такое ядро?
Прежде чем использовать полупроводник в качестве материала памяти, люди используют катушку в качестве памяти, катушка называется Core, которая называется Core Memory с памятью, созданной катушкой. Сегодня полупроводниковый промышленный уклон, никто не использовал Core Memory, но во многих случаях люди по-прежнему называют память Core.
Что такое дамп ядра?
Когда программа работает ненормально или дает сбой, операционная система записывает текущее состояние памяти программы и сохраняет его в файле.Это поведение называется Core Dump (некоторые китайцы переводятся как «дамп ядра»). Можно подумать, что Core Dump — это «моментальный снимок памяти», но на самом деле, помимо информации о памяти, одновременно сбрасываются и некоторые ключевые состояния работы программы, например информация о регистрах (включая указатель программы, указатель стека и т. ), информация об управлении памятью, другое состояние процессора и операционной системы и информация. Core Dump очень полезен для программистов при диагностике и отладке программ, потому что некоторые программные ошибки трудно воспроизвести, например, исключения указателя, а файлы Core Dump могут воспроизвести ситуацию, когда программа неверна.
1.2 Тестовая среда
$ uname -a
Darwin xiaopinguodeMBP 16.7.0 Darwin Kernel Version 16.7.0: Wed Oct 10 20:06:00 PDT 2018; root:xnu-3789.73.24~1/RELEASE_X86_64 x86_64
1.3 Включить основной дамп
Введите в терминале:
$ ulimit -c
Проверьте размер файлов, которые разрешено генерировать Core Dump. Если он равен 0, это означает, что Core Dump отключен. Используйте следующую команду, чтобы включить Core Dump без ограничения размера файла, создаваемого Core Dump:
$ ulimit -c unlimited
Приведенные выше команды действительны только для текущей терминальной среды.Если вы хотите, чтобы они вступали в силу постоянно, вам необходимо изменить/etc/security/limits.conf
файл следующим образом:
1.4 gcore
Используйте gcore для создания дампа основного файла определенного процесса без перезапуска программы. Как использовать gcore заключается в следующем:
$ gcore [-o filename] pid
# 用法如下
$gcore
gcore: no pid specified
usage:
gcore [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] pid
существуетCore Dump
По умолчанию он будет сгенерирован в каталоге, где выполняется команда gcore.core.pid
документ.
1.5 llnode
Что такое llnode?
Node.js v4.x+ C++ plugin for LLDB - a next generation, high-performance debugger.
Что такое ЛЛДБ?
LLDB is a next generation, high-performance debugger. It is built as a set of reusable components which highly leverage existing libraries in the larger LLVM Project, such as the Clang expression parser and LLVM disassembler.
Установите llnode + lldb:
# Prerequisites: Install LLDB and its Library
brew update && brew install --with-lldb --with-toolchain llvm
# instal
npm install -g llnode
1.6 Тестовый экземпляр памяти
Давайте проверим использование llnode на типичном примере утечки памяти, вызванной кешем глобальных переменных. код показывает, как показано ниже:
const leaks = []
function LeakingClass() {
this.name = Math.random().toString(36)
this.age = Math.floor(Math.random() * 100)
}
setInterval(() => {
for (let i = 0; i < 100; i++) {
leaks.push(new LeakingClass)
}
console.warn('Leaks: %d', leaks.length)
}, 1000)
Запустите программу:
$ node app.js
Подождите несколько секунд, откройте другой терминал и запустите gcore:
$ ulimit -c unlimited
$ pgrep -n node
$ 33833
$ sudo gcore -c core.33833 33833
генерироватьcore.33833
документ.
1.7 Анализ основных файлов
Используйте lldb для загрузки только что созданного файла Core:
llnode -c ./core.33833
(lldb) target create --core "./core.33833"
Core file '/Users/xiaopingguo/repos/my_repos/node_repos/node-in-debugging/./core.33833' (x86_64) was loaded.
(lldb) plugin load '/usr/local/lib/node_modules/llnode/llnode.dylib'
Введите v8, чтобы просмотреть документацию по использованию, есть следующие команды:
v8
The following subcommands are supported:
bt -- Show a backtrace with node.js JavaScript functions and their args. An optional argument is accepted; if that argument is a number, it
specifies the number of frames to display. Otherwise all frames will be dumped.
Syntax: v8 bt [number]
findjsinstances -- List every object with the specified type name.
Flags:
* -v, --verbose - display detailed `v8 inspect` output for each object.
* -n <num> --output-limit <num> - limit the number of entries displayed to `num` (use 0 to show all). To get next page repeat
command or press [ENTER].
Accepts the same options as `v8 inspect`
findjsobjects -- List all object types and instance counts grouped by type name and sorted by instance count. Use -d or --detailed to get an output
grouped by type name, properties, and array length, as well as more information regarding each type.
findrefs -- Finds all the object properties which meet the search criteria.
The default is to list all the object properties that reference the specified value.
Flags:
* -v, --value expr - all properties that refer to the specified JavaScript object (default)
* -n, --name name - all properties with the specified name
* -s, --string string - all properties that refer to the specified JavaScript string value
getactivehandles -- Print all pending handles in the queue. Equivalent to running process._getActiveHandles() on the living process.
getactiverequests -- Print all pending requests in the queue. Equivalent to running process._getActiveRequests() on the living process.
inspect -- Print detailed description and contents of the JavaScript value.
Possible flags (all optional):
* -F, --full-string - print whole string without adding ellipsis
* -m, --print-map - print object's map address
* -s, --print-source - print source code for function objects
* -l num, --length num - print maximum of `num` elements from string/array
Syntax: v8 inspect [flags] expr
nodeinfo -- Print information about Node.js
print -- Print short description of the JavaScript value.
Syntax: v8 print expr
settings -- Interpreter settings
source -- Source code information
For more help on any particular subcommand, type 'help <command> <subcommand>'.
- bt
- findjsinstances
- findjsobjects
- findrefs
- inspect
- nodeinfo
- source
бегатьv8 findjsobjects
Просмотр всех экземпляров объектов и их общего объема памяти
(llnode) v8 findjsobjects
Instances Total Size Name
---------- ---------- ----
...
356 11392 (Array)
632 35776 Object
8300 332000 LeakingClass
14953 53360 (String)
---------- ----------
24399 442680
Как можно заметить:LeakingClass
Есть 8300 экземпляров, занимающих память332000 byte
. использоватьv8 findjsinstances
посмотреть всеLeakingClass
Пример:
(lldb) v8 findjsinstances LeakingClass
...
0x221fb297fbb9:<Object: LeakingClass>
0x221fb297fc29:<Object: LeakingClass>
0x221fb297fc99:<Object: LeakingClass>
0x221fb297fd09:<Object: LeakingClass>
0x221fb297fd79:<Object: LeakingClass>
0x221fb297fde9:<Object: LeakingClass>
0x221fb297fe59:<Object: LeakingClass>
0x221fb297fec9:<Object: LeakingClass>
0x221fb297ff39:<Object: LeakingClass>
0x221fb297ffa9:<Object: LeakingClass>
(Showing 1 to 8300 of 8300 instances)
использовать v8 i
Получить конкретное содержимое экземпляра
(llnode) v8 i 0x221fb297ffa9
0x221fb297ffa9:<Object: LeakingClass properties {
.name=0x221f9bc82201:<String: "0.s3psjp4ctzj">,
.age=<Smi: 95>}>
(llnode) v8 i 0x221fb297ff39
0x221fb297ff39:<Object: LeakingClass properties {
.name=0x221fb297ff71:<String: "0.q1t4gikp9a">,
.age=<Smi: 6>}>
(llnode) v8 i 0x221fb297fec9
0x221fb297fec9:<Object: LeakingClass properties {
.name=0x221fb297ff01:<String: "0.zzomfpcmgn">,
.age=<Smi: 52>}>
Вы можете увидеть значение полей имени и возраста каждого экземпляра LeakingClass.
использовать v8 findrefs
Посмотреть цитаты
(llnode) v8 findrefs 0x221fb297ffa9
0x221fd136cb51: (Array)[7041]=0x221fb297ffa9
(llnode) v8 i 0x221fd136cb51
0x221fd136cb51:<Array: length=10018 {
[0]=0x221f9b627171:<Object: LeakingClass>,
[1]=0x221f9b627199:<Object: LeakingClass>,
[2]=0x221f9b6271c1:<Object: LeakingClass>,
[3]=0x221f9b6271e9:<Object: LeakingClass>,
[4]=0x221f9b627211:<Object: LeakingClass>,
[5]=0x221f9b627239:<Object: LeakingClass>,
[6]=0x221f9b627261:<Object: LeakingClass>,
[7]=0x221f9b627289:<Object: LeakingClass>,
[8]=0x221f9b6272b1:<Object: LeakingClass>,
[9]=0x221f9b6272d9:<Object: LeakingClass>,
[10]=0x221f9b627301:<Object: LeakingClass>,
[11]=0x221f9b627329:<Object: LeakingClass>,
[12]=0x221f9b627351:<Object: LeakingClass>,
[13]=0x221f9b627379:<Object: LeakingClass>,
[14]=0x221f9b6273a1:<Object: LeakingClass>,
[15]=0x221f9b6273c9:<Object: LeakingClass>}>
Видно, что черезLeakingClass
Адрес памяти экземпляра, который мы используемv8 findrefs
Найдите адрес памяти массива, который на него ссылается, а затем извлеките массив по этому адресу, и получите длину массива 10018, каждый элемент представляет собойLeakingClass
Например, разве это не массив утечек в нашем коде?
намекать: v8 i
Да v8 inspect
аббревиатура,v8 p
Да v8 print
аббревиатура от.
1.8 --abort-on-uncaught-exception
Добавляется при запуске программы Node.js—-abort-on-uncaught-exception
Параметры, при сбое программы она автоматически сделает Core Dump, что удобно при "посмертном вскрытии".
Добавить к--abort-on-uncaught-exception
параметры для запуска тестовой программы:
$ ulimit -c unlimited
$ node --abort-on-uncaught-exception app.js
Запустите другой терминал и выполните:
$ kill -BUS `pgrep -n node`
Терминал 1 отображает:
Leaks: 100
Leaks: 200
Leaks: 300
Leaks: 400
Leaks: 500
Leaks: 600
Leaks: 700
Leaks: 800
Bus error (core dumped)
Шаги отладки такие же, как описано выше:
(llnode) v8 findjsobjects
Instances Total Size Name
---------- ---------- ----
...
356 11392 (Array)
632 35776 Object
8300 332000 LeakingClass
14953 53360 (String)
---------- ----------
24399 442680
1.9 Резюме
Наш тестовый код очень прост и не ссылается на какие-либо сторонние модули.Если проект большой и есть много модулей, на которые ссылаются, результаты v8 findjsobjects будет трудно различить.В настоящее время вы можете использовать gcore для выполнения Core Сделайте дамп несколько раз, чтобы сравнить и найти растущие объекты, а затем поставить диагноз.
2 Использование дампа памяти
heapdump — это инструмент для вывода информации о куче V8. v8-profiler также включает эту функцию.Принципы этих двух инструментов одинаковы, оба v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(title, control), но использование heapdump простое немного. Давайте возьмем heapdump в качестве примера, чтобы объяснить, как анализировать утечки памяти в Node.js.
Вот классический код утечки памяти в качестве тестового кода:
const heapdump = require('heapdump')
let leakObject = null
let count = 0
setInterval(function testMemoryLeak() {
const originLeakObject = leakObject
const unused = function () {
if (originLeakObject) {
console.log('originLeakObject')
}
}
leakObject = {
count: String(count++),
leakStr: new Array(1e7).join(''),
leakMethod: function () {
console.log('leakMessage')
}
}
}, 1000)
Почему эта программа имеет утечку памяти? Прежде всего, нам нужно понять принцип замыкания: внутри одной и той же функции есть только одна область замыкания, и все замыкания разделяют ее. При выполнении функции, если встречается замыкание, будет создано пространство памяти области замыкания, к нему будут добавлены локальные переменные, используемые замыканием, а затем, когда встречается замыкание, будет создана ранее созданная область . Пространство для добавления переменных, которые используются этим замыканием, но не используются предыдущим замыканием. Когда функция завершается, очищайте переменные, на которые не ссылается область закрытия.
Причина утечки памяти этого кода: в функции testMemoryLeak есть два замыкания: неиспользуемое и утечкаМетод. Неиспользуемое замыкание ссылается на переменную originLeakObject в родительской области видимости.Если нет последующего LeakMethod, он будет очищен после завершения функции, и область замыкания также будет очищена. Поскольку последнийleakObjectявляетсяглобальнойпеременной,т.е.leaksMethodявляетсяглобальнойпеременной, и область замыкания, на которую она ссылается (включая originLeakObject, на которую ссылается unused), не будет освобождена. При непрерывном вызове testMemoryLeak originLeakObject указывает на предыдущий объектleakObject, а следующий объектleakObject.leakMethod будет ссылаться на предыдущий originLeakObject, таким образом формируя цепочку ссылок закрытия, тогда как leakStr представляет собой большую строку, которую нельзя освободить, что вызвало утечку памяти. .
Обходной путь: добавить в конце внутри функции testMemoryLeakoriginLeakObject = null
Вот и все.
Запустите тестовый код:
$ node app
Затем выполните его дважды:
$ kill -USR2 `pgrep -n node`
В текущем каталоге создаются два файла heapsnapshot:
heapdump-100427359.61348.heapsnapshot
heapdump-100438986.797085.heapsnapshot
2.1 Chrome DevTools
Мы использовали Chrome DevTools для анализа созданного ранее файла heapsnapshot. вызыватьChrome DevTools -> Memory -> Load
, загрузите ранее сгенерированные файлы heapsnapshot по порядку. Нажмите на второй снимок кучи, в левом верхнем углу появится раскрывающееся меню со следующими 4 вариантами:
- Сводка: отображается по имени конструктора.
- Сравнение: Сравните различия между несколькими снимками.
- Сдерживание: просмотр всего пути GC.
- Статистика: отображает информацию об использовании памяти в виде круговой диаграммы. Обычно мы используем только первые два варианта, третий вариант вообще не используется, потому что при раскрытии каждого из Сводка и Сравнение можно увидеть путь от корней GC до объекта, только четвертый вариант Можно увидеть использование памяти отношение, как показано на следующем рисунке:
Переключитесь на страницу «Сводка», вы увидите следующие пять свойств:
- Contructor: Имя конструктора, например, Object, Module, Socket, (массив), (string), (regexp) и т. д. В скобках представлены встроенные Array, String и Regexp соответственно.
- Distance: Расстояние до корней GC (корневой объект GC). Корневой объект GC обычно является объектом окна в браузере и глобальным объектом в Node.js. Чем больше расстояние, тем глубже ссылка, необходимо сосредоточиться на объекте, который, скорее всего, является утечкой памяти.
- Количество объектов: количество объектов.
- Неглубокий размер: размер самого объекта, за исключением объектов, на которые он ссылается.
- Retained Size: размер самого объекта и размер объекта, на который он ссылается, т. е. размер памяти, которая может быть освобождена после того, как объект будет GC.
намекать:
- Сохраненный размер объекта = МАЛЕНЬКИЙ РАЗМЕР + на этот объект можно ссылаться прямо или косвенно.
- Shallow Size == Retained Size имеет (логическое значение), (число), (строку), которые не могут ссылаться на другие значения и всегда являются листовыми узлами.
Мы нажимаем Retained Size и выбираем отображение в порядке убывания, мы видим, что содержимое этой ссылки (закрытие) достигает 99% и продолжает расширяться следующим образом:
Как можно заметить: LeakStr занимает 5 % памяти, тогда как утечкаMethod относится к 88 % памяти. Дерево хранения объектов (Retainers, старая версия Chrome называется деревом хранения объектов) показывает путь GC объекта, щелкните по текуStr (расстояние равно 13) на приведенном выше рисунке, Retainers будут автоматически развернуты, а расстояние уменьшится с 13 к 1.
Мы продолжаем расширять утечкуMethod следующим образом:
Как можно заметить: Eстьcount=”18”
изoriginLeakObject
Контекст (то есть контекст) функцииleakMethod ссылается наcount=”17”
изoriginLeakObject
объект, и контекст функцииleakMethod этой ссылки на объект originLeakObjectcount=”16”
изoriginLeakObject
объект и так далее. и каждыйoriginLeakObject
На объекте большая веревкаleakStr
(занимает 8% памяти), что приводит к утечке памяти в соответствии с нашим предыдущим выводом.
Подсказка: Если цвет фона желтый, это означает, что в JavaScript все еще есть ссылка на этот объект, поэтому ее нельзя очистить. Если цвет фона красный, это означает, что объект не упоминается в JavaScript, но все еще существует в памяти, что обычно наблюдается в объектах DOM. Их места хранения отличаются от объектов в JavaScript. В Node.js встречается редко.
2.2 Сравнение снимков
Переключитесь на представление «Сравнение», вы увидите некоторые свойства, такие как #New, #Deleted, #Delta, + и -, представляющие моментальный снимок кучи относительно сравнения. Мы сравниваем 2-й снимок с 1-м снимком следующим образом:
Видно: (строка) увеличилась на 5, размер каждой строки 10000024 байта.
3 Использование memwatch-next
memwatch-next (далее memwatch) — это модуль для мониторинга утечек памяти Node.js и сравнения информации о куче. Давайте возьмем фрагмент кода, вызывающий утечку памяти из прослушивателя событий, в качестве примера, чтобы объяснить, как использовать memwatch.
Код теста выглядит следующим образом:
let count = 1
const memwatch = require('memwatch-next')
memwatch.on('stats', (stats) => {
console.log(count++, stats)
})
memwatch.on('leak', (info) => {
console.log('---')
console.log(info)
console.log('---')
})
const http = require('http')
const server = http.createServer((req, res) => {
for (let i = 0; i < 10000; i++) {
server.on('request', function leakEventCallback() {})
}
res.end('Hello World')
global.gc()
}).listen(3000)
При поступлении каждого запроса регистрируйте для сервера 10 000 функций прослушивания событий запроса (большое количество функций прослушивания событий хранится в памяти, вызывая утечки памяти), а затем вручную запускайте сборщик мусора.
Запустите программу:
$ node --expose-gc app.js
Примечание. Параметр --expose-gc добавлен здесь для запуска программы, чтобы мы могли вручную запускать GC в программе.
memwatch может прослушивать два события:
- статистика:Событие GC, каждый раз, когда выполняется GC, эта функция будет запускаться для печати информации, связанной с кучей. следующим образом:
{
num_full_gc: 1,// 完整的垃圾回收次数
num_inc_gc: 1,// 增长的垃圾回收次数
heap_compactions: 1,// 内存压缩次数
usage_trend: 0,// 使用趋势
estimated_base: 5350136,// 预期基数
current_base: 5350136,// 当前基数
min: 0,// 最小值
max: 0// 最大值
}
- утечка:Событие утечки памяти, условие для запуска этого события: объем памяти увеличивается после 5 последовательных GC. Он печатается следующим образом:
{
growth: 3616040,
reason: 'heap growth over 5 consecutive GCs (0s) - -2147483648 bytes/hr'
}
бегать:
$ ab -c 1 -n 5 http://localhost:3000/
выход:
(node:35513) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 request listeners added. Use emitter.setMaxListeners() to increase limit
1 { num_full_gc: 1,
num_inc_gc: 2,
heap_compactions: 1,
usage_trend: 0,
estimated_base: 5674608,
current_base: 5674608,
min: 0,
max: 0 }
2 { num_full_gc: 2,
num_inc_gc: 4,
heap_compactions: 2,
usage_trend: 0,
estimated_base: 6668760,
current_base: 6668760,
min: 0,
max: 0 }
3 { num_full_gc: 3,
num_inc_gc: 5,
heap_compactions: 3,
usage_trend: 0,
estimated_base: 7570424,
current_base: 7570424,
min: 7570424,
max: 7570424 }
4 { num_full_gc: 4,
num_inc_gc: 7,
heap_compactions: 4,
usage_trend: 0,
estimated_base: 8488368,
current_base: 8488368,
min: 7570424,
max: 8488368 }
--------------
{ growth: 3616040,
reason: 'heap growth over 5 consecutive GCs (0s) - -2147483648 bytes/hr' }
--------------
5 { num_full_gc: 5,
num_inc_gc: 9,
heap_compactions: 5,
usage_trend: 0,
estimated_base: 9290648,
current_base: 9290648,
min: 7570424,
max: 9290648 }
Видно: Node.js предупредил нас, что прослушивателей событий больше 11, что может привести к утечке памяти. 5 последовательных событий роста памяти запускают события утечки, чтобы распечатать, сколько памяти (байтов) увеличилось и сколько байтов в час, по оценкам, будет увеличиваться.
3.1 Heap Diffing
В memwatch есть функция HeapDiff для сравнения и вычисления разницы между двумя снимками кучи. Измените тестовый код следующим образом:
const memwatch = require('memwatch-next')
const http = require('http')
const server = http.createServer((req, res) => {
for (let i = 0; i < 10000; i++) {
server.on('request', function leakEventCallback() {})
}
res.end('Hello World')
global.gc()
}).listen(3000)
const hd = new memwatch.HeapDiff()
memwatch.on('leak', (info) => {
const diff = hd.end()
console.dir(diff, { depth: 10 })
})
运行这段代码并执行同样的 ab 命令,打印如下:
(node:35690) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 request listeners added. Use emitter.setMaxListeners() to increase limit
{ before: { nodes: 35864, size_bytes: 4737664, size: '4.52 mb' },
after: { nodes: 87476, size_bytes: 8946784, size: '8.53 mb' },
change:
{ size_bytes: 4209120,
size: '4.01 mb',
freed_nodes: 894,
allocated_nodes: 52506,
details:
[ ...
{ what: 'Array',
size_bytes: 533008,
size: '520.52 kb',
'+': 1038,
'-': 517 },
{ what: 'Closure',
size_bytes: 3599856,
size: '3.43 mb',
'+': 50001,
'-': 3 }
...
]
}
}
It can be seen: the memory of 4.52mb rose 8.53mb, which Closure and Array up the majority, but we know that registered the event listener is a function of the nature of the event function (Closure) push to the corresponding array (Array ) годы.
3.2 Объединение динамического дампа
memwatch лучше работает в сочетании с heapdump. Обычно используют memwatch для обнаружения утечек памяти, используют heapdump для экспорта нескольких снимков кучи, а затем используют Chrome DevTools для анализа и сравнения, чтобы найти виновника утечки памяти.
Измените код следующим образом:
const memwatch = require('memwatch-next')
const heapdump = require('heapdump')
const http = require('http')
const server = http.createServer((req, res) => {
for (let i = 0; i < 10000; i++) {
server.on('request', function leakEventCallback() {})
}
res.end('Hello World')
global.gc()
}).listen(3000)
dump()
memwatch.on('leak', () => {
dump()
})
function dump() {
const filename = `${__dirname}/heapdump-${process.pid}-${Date.now()}.heapsnapshot`
heapdump.writeSnapshot(filename, () => {
console.log(`${filename} dump completed.`)
})
}
Приведенная выше программа сначала выполняет дамп кучи после запуска и снова выполняет дамп кучи при возникновении события утечки. Запуск этого кода и выполнение той же команды ab создает два файла heapsnapshot:
heapdump-21126-1519545957879.heapsnapshot
heapdump-21126-1519545975702.heapsnapshot
Загрузите два файла heapsnapshot с помощью Chrome DevTools и выберите представление сравнения, как показано ниже:
Видно: рост на 50 000leakEventCallback
функции, щелкните любую из них, вы можете увидеть более подробную информацию от Retainers, такую как путь GC и файл, в котором он находится.
ранее представленныйheapdump
а такжеmemwatch-next
Однако в реальной эксплуатации это не так удобно, мы не можем постоянно смотреть на состояние сервера, когда мы обнаруживаем, что память продолжает расти и превышает порог в нашем сознании, можем ли мы запустить Core Dump вручную? К моменту обнаружения проблемы в большинстве случаев место происшествия уже пропущено. Итак, нам может понадобитьсяcpu-memory-monitor
. Как следует из названия, этот модуль можно использовать для мониторинга использования ЦП и памяти, а также для автоматического создания дампа использования ЦП (cpuprofile) и моментальных снимков памяти (heapsnapshot) в соответствии с политиками конфигурации.
4 Использование монитора памяти процессора
Давайте сначала посмотрим, как использоватьcpu-memory-monitor
, это на самом деле очень просто, достаточно ввести в файл записи запуска процесса следующий код:
require('cpu-memory-monitor')({
cpu: {
interval: 1000,
duration: 30000,
threshold: 60,
profileDir: '/tmp',
counter: 3,
limiter: [5, 'hour']
}
})
Функция приведенного выше кода заключается в проверке загрузки ЦП каждые 1000 мс (интервал), если обнаружено, что загрузка ЦП превышает 60% (порог) в течение 3 (счетчик) последовательныхdump 30000ms(duration) CPU
использование, генерироватьcpu-${process.pid}-${Date.now()}.cpuprofile
прибыть/tmp(profileDir)
Под содержанием,1(limiter[1])
часов максимумdump 5(limiter[0])
Второсортный.
Выше приведена стратегия автоматического сброса загрузки ЦП. Стратегия сброса использования памяти такая же:
require('cpu-memory-monitor')({
memory: {
interval: 1000,
threshold: '1.2gb',
profileDir: '/tmp',
counter: 3,
limiter: [3, 'hour']
}
})
Функция приведенного выше кода: каждый1000ms(interval)
Проверьте использование памяти один раз, если вы обнаружите непрерывное3(counter)
Вторичная память больше, чем1.2gb(threshold)
, затем выгрузить память один раз, сгенерироватьmemory-${process.pid}-${Date.now()}.heapsnapshot
прибыть /tmp(profileDir)
Под содержанием,1(limiter[1])
часов максимумdump 3(limiter[0])
Второсортный.
Примечание. В конфигурации памяти нет параметра продолжительности, поскольку дамп Memroy создается только за определенное время, а не за период времени.
Умный должен спросить: можно ли сконфигурировать процессор и память вместе для использования? Например:
require('cpu-memory-monitor')({
cpu: {
interval: 1000,
duration: 30000,
threshold: 60,
...
},
memory: {
interval: 10000,
threshold: '1.2gb',
...
}
})
Ответ: да, но не делайте этого. Потому что это может привести к следующему:
Память высока и достигает установленного порога -> запускает дамп памяти/GC -> вызывает высокую загрузку ЦП и достигает установленного порога -> запускает дамп ЦП -> вызывает все больше и больше накопленных запросов (таких как накопление в памяти) Много SQL-запросов ) -> запускает дамп памяти -> вызывает лавину.
Обычно достаточно просто использовать один или другой.
4.1 Интерпретация исходного кода
cpu-memory-monitor
Исходный код всего более 100 строк, общая логика такова:
const processing = {
cpu: false,
memory: false
}
const counter = {
cpu: 0,
memory: 0
}
function dumpCpu(cpuProfileDir, cpuDuration) { ... }
function dumpMemory(memProfileDir) { ... }
module.exports = function cpuMemoryMonitor(options = {}) {
...
if (options.cpu) {
const cpuTimer = setInterval(() => {
if (processing.cpu) {
return
}
pusage.stat(process.pid, (err, stat) => {
if (err) {
clearInterval(cpuTimer)
return
}
if (stat.cpu > cpuThreshold) {
counter.cpu += 1
if (counter.cpu >= cpuCounter) {
memLimiter.removeTokens(1, (limiterErr, remaining) => {
if (limiterErr) {
return
}
if (remaining > -1) {
dumpCpu(cpuProfileDir, cpuDuration)
counter.cpu = 0
}
})
} else {
counter.cpu = 0
}
}
})
}, cpuInterval)
}
if (options.memory) {
...
memwatch.on('leak', () => {
dumpMemory(...)
})
}
}
Как можно заметить:cpu-memory-monitor
Ничего нового не было использовано, это было объяснено ранееv8-profiler
,heapdump
,memwatch-next
используется только комбинация.
Есть несколько замечаний:
Только при передаче конфигурации ЦП или памяти будет осуществляться мониторинг соответствующего ЦП или памяти.
При передаче конфигурации памяти используйтеmemwatch-next
Событие утечки дополнительно отслеживается, а также производится дамп памяти в форматеleak-memory-${process.pid}-${Date.now()}.heapsnapshot
.
Сверху введен heapdump, так что даже без настройки памяти можно пройтиkill -USR2 <PID>
Активировать дамп памяти вручную.