A core dump is generated when a Node.js process encounters a critical error that it cannot recover from, such as segmentation faults, accessing invalid memory, or other fatal errors.
- By default, Node.js does not generate core dumps automatically. Instead, you typically need to enable core dumps on the operating system level.
- On Linux, for example, you can use
ulimit -c unlimitedto enable core dumps of unlimited size. - Once enabled, if a Node.js process crashes, a core dump file (
core.<pid>) is generated in the current working directory or the directory specified by thecore_patternsetting.
const memwatch = require('memwatch-next');
memwatch.on('leak', function(info) {
console.error('Memory leak detected:', info);
memwatch.gc();
});
Linux
sudo gcore <pid>
gdb /path/to/node /path/to/core.<pid>
strings heapdump-394083985.931455.heapsnapshot | grep "twilio" → will print all the char contain twilio
kill -USR2 PID → by default nodejs will listen and create a heapdump
Event loop monitor
import { createHook } from "node:async_hooks";
import { tracer } from "/blog/event-loop-lag/v3/tracer.server";
const THRESHOLD_NS = 1e8; // 100ms
const cache = new Map<number, { type: string; start?: [number, number] }>();
function init(
asyncId: number,
type: string,
triggerAsyncId: number,
resource: any
) {
cache.set(asyncId, {
type,
});
}
function destroy(asyncId: number) {
cache.delete(asyncId);
}
function before(asyncId: number) {
const cached = cache.get(asyncId);
if (!cached) {
return;
}
cache.set(asyncId, {
...cached,
start: process.hrtime(),
});
}
function after(asyncId: number) {
const cached = cache.get(asyncId);
if (!cached) {
return;
}
cache.delete(asyncId);
if (!cached.start) {
return;
}
const diff = process.hrtime(cached.start);
const diffNs = diff[0] * 1e9 + diff[1];
if (diffNs > THRESHOLD_NS) {
const time = diffNs / 1e6; // in ms
const newSpan = tracer.startSpan("event-loop-blocked", {
startTime: new Date(new Date().getTime() - time),
attributes: {
asyncType: cached.type,
label: "EventLoopMonitor",
},
});
newSpan.end();
}
}
export const eventLoopMonitor = singleton("eventLoopMonitor", () => {
const hook = createHook({ init, before, after, destroy });
return {
enable: () => {
console.log("🥸 Initializing event loop monitor");
hook.enable();
},
disable: () => {
console.log("🥸 Disabling event loop monitor");
hook.disable();
},
};
});I used the basic “collectDefaultMetrics” of the prom-client package. We use Prometheus to gather metrics from instances, and Grafana to display Graphs.
About the ELU, I’m not aware of any “standard” prometheus ways to compute and report it. We do it manually via a small snippet of code like this
let lastELU = performance.eventLoopUtilization();
this._intervalRef = setInterval(() => {
// Store the current ELU so it can be assigned later.
const tmpELU = performance.eventLoopUtilization();
// Calculate the diff between the current and last before sending.
const report = performance.eventLoopUtilization(tmpELU, lastELU);
this._idleGauge.set(report.idle);
this._activeGauge.set(report.active);
this._utilizationGauge.set(report.utilization);
// Assign over the last value to report the next interval.
lastELU = tmpELU;
}, this._interval);
https://www.npmjs.com/package/prom-client
https://easyperf.net/blog/2024/02/12/Memory-Profiling-Part1?utm_source=tldrwebdev
https://blog.jiayihu.net/comprenhensive-guide-chrome-performance/
-
https://medium.com/yandex/yandexs-high-performance-profiler-is-now-open-source-95e291df9d18
-
https://blogs.halodoc.io/fix-node-js-memory-leaks/ (kubernetes)
https://github.com/alibaba/JS-Memory-Analysor
A deepagent to witnessing slowdowns in your test runs.
https://github.com/christian-bromann/ZeitZeuge
node trace-sync-io server
trace-sync-io detects calls to the Node sync APIs, like fs.readFileSync. So these warnings are what you are trying to catch
—kernel-tracing —expreimental-report → provide report
node —trace_gc app.js print when ever the garbage collected run and memory cleaned up
—perf-basic-prof-only-functions
Node.js, you need to use the —abort-on-uncaught-exception flag to tell the operating system to create a core dump when the program crashes
- Basic monitoring tools like the top command or task manager
- Modules like “memory usage” to track memory consumption
- Heap snapshots, which show all the objects in the JavaScript heap, and who is holding on to them. Heap snapshots can be used to compare different states of the heap and identify what objects are leaking. However, heap snapshots can have a performance impact and can contain sensitive data.
- Allocation timelines, which are similar to heap snapshots, but focus on a small time period to better understand what is being allocated.
- Sampling profilers, which are low-overhead tools that sample the heap and show which functions are allocating memory. The speaker notes that sampling profilers are suitable for use in production.
- Tools for debugging native leaks, such as Valgrind and the sampling profiler, which can help to pinpoint leaks in native code
Garbage Collection Basics: GC deals with the heap, where dynamically allocated objects like arrays, functions, and objects are stored. The heap is divided into pages of 512 kilobytes. The garbage collector’s job is to find live objects (those still in use), reclaim the memory of dead objects, and optionally defragment the heap.
Marking Algorithm: The process of finding live objects starts with known root pointers (stack and global object) and follows all references to other objects, marking each one as reachable. Everything not marked is considered garbage. The cost is relative to surviving objects, not all allocated objects.
Generational Heap Layout: The V8 heap is divided into a young generation and an old generation, with three ages for objects: nursery, intermediate, and old. - Nursery: New objects are allocated here. - Intermediate: Objects that survive the first GC are moved here. - Old Generation: Objects that survive another GC are moved here.
Two Garbage Collectors: V8 uses two independent garbage collectors: - Minor GC (Scavenger): Deals with the young generation. It uses an evacuation process where live objects are copied from “from space” to “to space,” and then the spaces are switched. - Major GC (Mark-Sweep-Compact): Deals with the entire heap. It finds gaps where objects are unreachable and puts the space on a free list. It compacts fragmented pages to save space.
Generational Hypothesis: Most objects die young, so the generational layout optimizes for this by only copying objects that survive garbage collection.
Orinoco Project: This project aims to transform the V8 GC from a purely sequential process to a concurrent and parallel one. The goals are to free the main thread of garbage collection work, improve main thread latency, and ensure JavaScript keeps running smoothly.
Key Concepts in Orinoco:
- Parallel GC: The main thread and helper threads split the work and do it at the same time, reducing pause times, though still stopping the world.
- Incremental GC: GC work is divided into small pieces and interleaved with JavaScript execution, but requires fix-up steps.
- Concurrent GC: JavaScript continues to run on the main thread while helper threads do garbage collection work, requiring more synchronization.
- Modern V8 GC: The V8 GC uses parallel scavenging (up to 7 helper threads) with interleaved marking, evacuation, and pointer updating for the minor GC (resulting in ~5ms pause times). For the major GC, it uses concurrent marking, parallel compaction and updating, and concurrent sweeping with background tasks to reduce main thread pauses (resulting in ~9.8ms pauses).
GC Triggers:
- Minor GC: Triggered when the new space runs out of space.
- Major GC: Triggered by a combination of factors, including allocation rate, object size, and survival rate, with the system trying to dynamically compute an optimal limit.
V8
V8 heap statistics
Tools
- https://github.com/lloyd/node-memwatch A NodeJS library to keep an eye on your memory usage, and discover and isolate leaks.it can emit leak events if it sees the heap grow over 5 consecutive garbage collections.
- https://github.com/bretcope/node-gc-profiler Allows you to profile when the garbage collector runs, and how long it takes.
- An lldb plugin for Node.js and V8, which enables inspection of JavaScript states for insights into Node.js processes and their core dumps. https://github.com/nodejs/llnode
- https://github.com/TritonDataCenter/mdb_v8 postmortem debugging for Node.js and other V8-based programs
- https://www.npmjs.com/package/0x profile and generate an interactive flamegraph for a Node process with a single command
- https://www.speedscope.app/ speedscope, an interactive flamegraph visualizer. Use it to help you make your software faster.
- pprof is a tool for visualization and analysis of profiling data https://github.com/google/pprof (google)
ssh -L 8080:localhost:8080 admin@example.com Port forwarding to remote machin we can access remote machine 8080 port form local
CPU profileing
-
sudo perf record -F 99 -p 3870 -g
-
sudo perf script > perfs.out
-
https://www.cloudbees.com/blog/understanding-garbage-collection-in-node-js
-
https://www.tritondatacenter.com/blog/walmart-node-js-memory-leak
-
https://www.brendangregg.com/blog/2016-07-13/llnode-nodejs-memory-leak-analysis.html
-
https://developer.chrome.com/docs/devtools/memory-problems/get-started
-
https://blog.platformatic.dev/optimizing-nodejs-performance-v8-memory-management-and-gc-tuning
V8 file structure
{
nodes: [ /* array of Node objects */ ],
nodesById: { /* mapping of Node ID to Node object */ },
edges: [ /* array of Edge objects */ ]
}
Each Node represents a JavaScript object in memory:
{
id: 1210027, // Unique ID of the object
name: "system / Context", // Name of the object (from V8 internals)
type: "object", // Type (object, array, string, etc.)
self_size: 6176, // Memory occupied by this object (bytes)
trace_node_id: 0, // Unused in most cases
references: [ /* Array of Edge objects */ ], // Objects this node references
referrers: [ /* Array of Edge objects */ ] // Objects that reference this node
}Edges (References Between Objects)
Each Edge represents a reference between objects:
{
type: "property", // Type of reference (e.g., property, element, internal)
name_or_index: "next", // Name of the reference (e.g., property name, array index)
fromNode: { ... }, // Object that holds the reference
toNode: { ... } // Object being referenced
}
[<process_id>:<thread_id>]