I love great product and build them.
I'm developing my dream with code.
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
It's super easy to write a Node.js application.
But it's hard to hunt the Ghost in the shell.
and recover after few minutes
(health check failed)
built-in tool to profile the app
node --prof app.js
ab -k -c 20 -n 250 "http://localhost:3000"
20 concurrent connections with 250 requests per each
Concurrency Level: 20
Time taken for tests: 46.932 seconds
Complete requests: 250
Failed requests: 0
Keep-Alive requests: 250
Total transferred: 50250 bytes
HTML transferred: 500 bytes
Requests per second: 5.33 [#/sec] (mean)
Time per request: 3754.556 [ms] (mean)
Time per request: 187.728 [ms] (mean, across all concurrent requests)
Transfer rate: 1.05 [Kbytes/sec] received
...
Percentage of the requests served within a certain time (ms)
50% 3755
66% 3804
75% 3818
80% 3825
90% 3845
95% 3858
98% 3874
99% 3875
100% 4225 (longest request)
named like:
isolate-0xnnnnnnnnnnnn-v8.log
wait for a while to collect more data
process the log file the get the statistics by this command
node --prof-process isolate-0xnnnnnnnnnnnn-v8.log > processed.txt
The file is broken up into sections which are again broken up by language
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 1.0% are not shown.
ticks parent name
98984 37.6% UNKNOWN
83688 31.8% /lib/x86_64-linux-gnu/libc-2.19.so
2162 2.6% /usr/local/bin/node
583 27.0% Builtin: StringPrototypeReplace
148 25.4% LazyCompile: *InlineLexer.output /hackmd/node_modules/marked/lib/marked.js:585:40
66 44.6% LazyCompile: *Parser.tok /hackmd/node_modules/marked/lib/marked.js:1043:32
22 33.3% LazyCompile: ~Parser.parse /hackmd/node_modules/marked/lib/marked.js:992:34
21 31.8% LazyCompile: *Parser.parse /hackmd/node_modules/marked/lib/marked.js:992:34
19 28.8% LazyCompile: *Parser.tok /hackmd/node_modules/marked/lib/marked.js:1043:32
4 6.1% RegExp: \\bXbox *\\d+[.\\w_]* {3}
37 25.0% LazyCompile: *InlineLexer.output /hackmd/node_modules/marked/lib/marked.js:585:40
19 51.4% LazyCompile: *Parser.tok /hackmd/node_modules/marked/lib/marked.js:1043:32
9 24.3% LazyCompile: ~Parser.tok /hackmd/node_modules/marked/lib/marked.js:1043:32
6 16.2% LazyCompile: *InlineLexer.output /hackmd/node_modules/marked/lib/marked.js:585:40
1 2.7% RegExp: \\bChrome\\b
...
We found the marked
library that used for parsing markdown cause the problem.
and been killed by the OS scheduler
again
use the built-in inspector to understand your app
node --inspect app.js
ab -k -c 20 -n 250 "http://localhost:3000"
20 concurrent connections with 250 requests per each
Let's make the server generates the memory dump
triggers events when the memory usage still growing after 5 consecutive GCs
const Memwatch = require('memwatch-next'); /** * Check for memory leaks */ let hd = null; Memwatch.on('leak', (info) => { console.log('memwatch::leak'); console.error(info); if (!hd) { hd = new Memwatch.HeapDiff(); } else { const diff = hd.end(); console.error(Util.inspect(diff, true, null)); trace.report('memwatch::leak', { HeapDiff: hd }); hd = null; } }); Memwatch.on('stats', (stats) => { console.log('memwatch::stats'); console.error(Util.inspect(stats, true, null)); trace.report('memwatch::stats', { Stats: stats }); });
memwatch::stats // <-- GC stats
{ num_full_gc: 227,
num_inc_gc: 991,
heap_compactions: 227,
usage_trend: 1.3, // <-- the usage trending
estimated_base: 49219293,
current_base: 47057208,
min: 41628720,
max: 50299360 }
memwatch::leak // <-- the leak event
growth=1362440, reason=heap growth over 5 consecutive GCs (1m 56s) - 40.32 mb/hr
// show the heap diff
{ before: { nodes: 401400, size_bytes: 44743648, size: '42.67 mb' },
after: { nodes: 407650, size_bytes: 46306635, size: '44.16 mb' },
change:
{ size_bytes: 1562987,
size: '1.49 mb',
freed_nodes: 24397,
allocated_nodes: 30647,
details:
[ { what: 'Array',
size_bytes: 557728,
size: '544.66 kb',
'+': 5438,
'-': 2962 },
{ what: 'ArrayBuffer',
size_bytes: 560,
size: '560 bytes',
'+': 21,
'-': 14 },
{ what: 'Buffer',
size_bytes: 400,
size: '400 bytes',
'+': 12,
'-': 7 },
{ what: 'BufferList',
size_bytes: 1200,
size: '1.17 kb',
'+': 48,
'-': 23 },
{ what: 'Client', size_bytes: 0, size: '0 bytes', '+': 5, '-': 5 },
{ what: 'Closure',
size_bytes: 28080,
size: '27.42 kb',
'+': 1525,
'-': 1021 },
{ what: 'Code',
size_bytes: 405144,
size: '395.65 kb',
'+': 315,
'-': 61 },
...
It's better if we can have the whole heap to inspect
dumping the memory stack of node.js app to a file to inspect later
const http = require('http'); const heapdump = require('heapdump'); const requestLogs = []; const server = http.createServer((req, res) => { if (req.url === '/heapdump') { heapdump.writeSnapshot((err, filename) => { console.log('Heap dump written to', filename) }); } requestLogs.push({ url: req.url, date: new Date() }); res.end(JSON.stringify(requestLogs)); }); server.listen(3000); console.log('Server listening to port 3000. Press Ctrl+C to stop it.'); console.log(`Heapdump enabled. Run "kill -USR2 ${process.pid}" or send a request to "/heapdump" to generate a heapdump.`);
$ node index.js Server listening to port 3000. Press Ctrl+C to stop it. Heapdump enabled. Run "kill -USR2 29431" or send a request to "/heapdump" to generate a heapdump. $ kill -USR2 29431 $ curl http://localhost:3000/heapdump $ ls heapdump-31208326.300922.heapsnapshot heapdump-31216569.978846.heapsnapshot
We found the gray-matter
library that used for generating the page title cause the problem.
the best debbuger of Node.js!
ndb is an improved debugging experience for Node.js, enabled by Chrome DevTools
npm install -g ndb
ndb app.js
profiling
inspecting
memory dumping
ndb
do you have any questions?