Node.js/V8 Javascript stack unwinder in eBPF
a7e51403 — Keyhan Vakil a month ago
Rename README to md file
29e1ecef — Keyhan Vakil a month ago
Update README, minor improvements for release
5b4dd5d9 — Keyhan Vakil a month ago
Split into separate ustackjs.c file & change to perf output


browse  log 



You can also use your local clone with git send-email.

#ustackjs.py: unwind Javascript stacks in eBPF

Unwinds a Javascript stack in eBPF.


Run this in one terminal:

$ node --jitless example.js

and then in another terminal:

$ sudo python3 ustackjs.py --node `which node` _ZN2v87Isolate37AdjustAmountOfExternalAllocatedMemoryEl

The output looks like this:

5845 5845 5845 12888.364267112: 1 event:
         561614bf9f40 v8::internal::Builtin_ArrayBufferConstructor(int, unsigned long*, v8::internal::Isolate*)+0x120 ([node])
         5616155ecd79 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit+0x39 ([node])
         56161556e7ec Builtins_JSBuiltinsConstructStub+0xec ([node])
         561615660652 Builtins_CreateTypedArray+0x892 ([node])
         5616155de2c7 Builtins_TypedArrayConstructor+0x87 ([node])
         56161556e7ec Uint8Array ([js])
         561595706add [unknown]
         561595706bb6 foo ([js])
         561595706cb6 bar ([js])
         56159570b5b7 baz ([js])
         5616155a49bf quux ([js])
         56161563d271 Builtins_PromiseFulfillReactionJob+0x31 ([node])
         56161559613b Builtins_RunMicrotasks+0x27b ([node])
         56161556f203 Builtins_JSRunMicrotasksEntry+0x83 ([node])
         561614cdede9 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&)+0x649 ([node])
         561614ce01fa v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*, v8::internal::MaybeHandle<v8::internal::Object>*)+0xaa ([node])
         561614d157e7 v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*)+0xc7 ([node])
         561614d15bed v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*)+0x3d ([node])
         561614895add node::InternalCallbackScope::Close()+0x12d ([node])
         561614895cc5 node::InternalCallbackScope::~InternalCallbackScope()+0x15 ([node])
         5616148d3128 node::Environment::RunTimers(uv_timer_s*)+0x368 ([node])
         56161554a8c2 uv__run_timers+0x22 ([node])
         56161554eab2 uv_run+0x92 ([node])
         561614896a56 node::SpinEventLoop(node::Environment*)+0x196 ([node])
         5616149b9f9f node::NodeMainInstance::Run()+0x16f ([node])
         5616149212a1 node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*)+0xe1 ([node])
         561614924a9c node::Start(int, char**)+0x47c ([node])
         7efc1657dd90 __libc_start_call_main+0x80 ([libc.so.6])

The first line of each stack trace contains the process id (twice), followed by the thread id and a timestamp in seconds. The following lines are the functions leading up to the function that was traced. Stack frames which are from Javascript are displayed with [js] as the library name.

(We use --jitless above to avoid inlining, but everything works with the JIT as well.)


  • get v8dbg symbols from the binary, rather than hardcoding them
  • cache code symbols until a GC occurs
  • quantify performance impact
  • maybe other type of strings (two-byte? cons strings?)
  • don't probe 4 slots for the right string