feat(profiler): add custom profiling labels API#7879
Conversation
Add tracer.profiling.runWithLabels() and tracer.profiling.setCustomLabelKeys() APIs that allow users to attach custom labels to wall profiler samples, enabling flame graph filtering by business logic dimensions (e.g. customer, region) in the Datadog UI. This brings Node.js profiler to parity with Java and Go. The implementation stores custom labels alongside span profiling context in pprof's CPED AsyncLocalStorage as a 2-element array [profilingContext, customLabels]. A monotonic flag avoids getContext() overhead when custom labels have never been used. Labels propagate correctly through async continuations. Internal labels (span id, thread name, etc.) always take precedence over custom labels with the same key. Requires AsyncContextFrame (ACF) to be enabled; gracefully falls back to a passthrough (just calls fn()) when ACF is off or profiling is disabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Overall package sizeSelf size: 5.48 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.0.1 | 82.56 kB | 817.39 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #7879 +/- ##
==========================================
- Coverage 74.62% 74.22% -0.40%
==========================================
Files 768 769 +1
Lines 35673 36120 +447
==========================================
+ Hits 26620 26810 +190
- Misses 9053 9310 +257
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
✅ Tests 🎉 All green!❄️ No new flaky tests detected 🎯 Code Coverage (details) 🔗 Commit SHA: 36082b5 | Docs | Datadog PR Page | Was this helpful? React with 👍/👎 or give us feedback! |
BenchmarksBenchmark execution time: 2026-04-08 09:33:09 Comparing candidate commit 36082b5 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 229 metrics, 31 unstable metrics. |
IlyasShabi
left a comment
There was a problem hiding this comment.
LGTM with one NIT comment
There was a problem hiding this comment.
Can you add test for the API part in docs/test.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(profiler): add custom profiling labels API Add tracer.profiling.runWithLabels() and tracer.profiling.setCustomLabelKeys() APIs that allow users to attach custom labels to wall profiler samples, enabling flame graph filtering by business logic dimensions (e.g. customer, region) in the Datadog UI. This brings Node.js profiler to parity with Java and Go. The implementation stores custom labels alongside span profiling context in pprof's CPED AsyncLocalStorage as a 2-element array [profilingContext, customLabels]. A monotonic flag avoids getContext() overhead when custom labels have never been used. Labels propagate correctly through async continuations. Internal labels (span id, thread name, etc.) always take precedence over custom labels with the same key. Requires AsyncContextFrame (ACF) to be enabled; gracefully falls back to a passthrough (just calls fn()) when ACF is off or profiling is disabled.
* feat(profiler): add custom profiling labels API Add tracer.profiling.runWithLabels() and tracer.profiling.setCustomLabelKeys() APIs that allow users to attach custom labels to wall profiler samples, enabling flame graph filtering by business logic dimensions (e.g. customer, region) in the Datadog UI. This brings Node.js profiler to parity with Java and Go. The implementation stores custom labels alongside span profiling context in pprof's CPED AsyncLocalStorage as a 2-element array [profilingContext, customLabels]. A monotonic flag avoids getContext() overhead when custom labels have never been used. Labels propagate correctly through async continuations. Internal labels (span id, thread name, etc.) always take precedence over custom labels with the same key. Requires AsyncContextFrame (ACF) to be enabled; gracefully falls back to a passthrough (just calls fn()) when ACF is off or profiling is disabled.
Summary
tracer.profiling.runWithLabels(labels, fn)API that attaches custom key-value labels to all wall profiler samples taken duringfn's execution (including async continuations)tracer.profiling.setCustomLabelKeys(keys)to declare label keys for UI indexing and pprof serialization optimizationcustom_attributesin profile upload event metadata so the Datadog backend knows which keys to indexUsage
Implementation details
Custom labels are stored alongside the span profiling context in pprof's CPED
AsyncLocalStorageas a 2-element array[profilingContext, customLabels]. When no custom labels are active, the context remains a plain object; justprofilingContextfrom the array – unchanged behavior, zero cost. A monotonic flag (#customLabelsActive) avoids thegetContext()native call overhead when custom labels have never been used, and when they're used then the code usesArray.isArray()to distinguish between the two forms of value in the ALS.Internal labels (
span id,thread name, etc.) are set later than custom labels when constructing samples, so they always take precedence over custom labels with the same key.setCustomLabelKeyswould not even be strictly necessary, but this way we can avoid dynamically constructing a set of labels, and in any case the Java and Go APIs both have this method for explicit declaration of the set of custom label keys.The feature requires AsyncContextFrame to be enabled; it gracefully falls back to a no-op when ACF is off or profiling is disabled.
Test plan
_generateLabelswith array contexts (ACF), empty profiling context, non-ACF unchangedrunWithLabels: basic operation, nesting/merging, key override, ACF-off passthrough, contexts-off passthrough, internal label precedence#enteroptimizations: custom labels preservation, skip-when-unchanged, async continuation correctness🤖 Generated with Claude Code