Руководство по отладке узлов — Память

Node.js

Основные выдержки из: -Руководство по отладке 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:

GitHub.com/node будет /LL no…

# 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
  • print
  • 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>Активировать дамп памяти вручную.

Ссылка на ссылку