Skip to content

Commit b00b827

Browse files
committed
diagnostics_channel: add USDT probes
Fire a `dc__publish` USDT probe for every diagnostics_channel publish, passing the channel name, so tracers such as bpftrace, perf, or SystemTap can observe publish traffic with near-zero cost when nothing is attached. Probes are enabled by default on Linux; --without-dtrace disables them. The probe semaphore is exposed to JS as a Uint16Array over the native semaphore so the publish hot path can gate on one indexed load instead of a binding call. The array is resolved lazily rather than captured at module load: this module is baked into the startup snapshot, and a view captured while building the snapshot is detached when the snapshot is deserialized. Builds without USDT support keep a branch-only path that never calls the binding. Signed-off-by: Bryan English <bryan@bryanenglish.com> Assisted-by: Pi using GLM-5.3
1 parent 4bf4c00 commit b00b827

11 files changed

Lines changed: 709 additions & 6 deletions

configure.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,12 @@
10661066
default=None,
10671067
help='do not install the bundled Amaro (TypeScript utils)')
10681068

1069+
parser.add_argument('--without-dtrace',
1070+
action='store_true',
1071+
dest='without_dtrace',
1072+
default=None,
1073+
help='build without DTrace/USDT probe support')
1074+
10691075
parser.add_argument('--without-lief',
10701076
action='store_true',
10711077
dest='without_lief',
@@ -1360,6 +1366,31 @@ def B(value):
13601366
def to_utf8(s):
13611367
return s if isinstance(s, str) else s.decode("utf-8")
13621368

1369+
def has_working_dtrace_h():
1370+
"""Check whether a dtrace tool that supports -h is available.
1371+
1372+
Supported on Linux (SystemTap dtrace wrapper), macOS, FreeBSD, and
1373+
illumos/SmartOS (native DTrace). Non-Linux platforms require -xnolibs
1374+
to avoid loading standard D libraries during header generation."""
1375+
dtrace = shutil.which('dtrace')
1376+
if dtrace is None:
1377+
return False
1378+
# -xnolibs is required on macOS/FreeBSD/illumos (native DTrace) to avoid
1379+
# loading standard D libraries. Linux (SystemTap wrapper) does not
1380+
# recognise this flag, so only pass it on non-Linux platforms.
1381+
cmd = [dtrace, '-h', '-s', '/dev/stdin', '-o', '/dev/null']
1382+
if sys.platform != 'linux':
1383+
cmd.insert(2, '-xnolibs')
1384+
try:
1385+
proc = subprocess.run(
1386+
cmd,
1387+
input=b'provider _test { probe _test(); };',
1388+
capture_output=True, timeout=10)
1389+
return proc.returncode == 0
1390+
except (OSError, subprocess.TimeoutExpired) as e:
1391+
warn('dtrace probe check failed: %s' % e)
1392+
return False
1393+
13631394
def pkg_config(pkg):
13641395
"""Run pkg-config on the specified package
13651396
Returns ("-l flags", "-I flags", "-L flags", "version")
@@ -2180,6 +2211,16 @@ def configure_node(o):
21802211
print('Warning! Loading builtin modules from disk is for development')
21812212
o['variables']['node_builtin_modules_path'] = options.node_builtin_modules_path
21822213

2214+
o['variables']['node_no_usdt'] = b(options.without_dtrace)
2215+
use_dtrace = not options.without_dtrace and has_working_dtrace_h()
2216+
o['variables']['node_use_dtrace'] = b(use_dtrace)
2217+
if options.without_dtrace:
2218+
print('USDT probes: disabled (--without-dtrace)')
2219+
elif use_dtrace:
2220+
print('USDT probes: enabled (dtrace -h, semaphore support)')
2221+
else:
2222+
print('USDT probes: fallback (sys/sdt.h) or disabled')
2223+
21832224
def configure_napi(output):
21842225
version = getnapibuildversion.get_napi_version()
21852226
output['variables']['napi_build_version'] = version

doc/api/diagnostics_channel.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,78 @@ another async task is triggered internally which fails and then the sync part
15291529
of the function then throws and error two `error` events will be emitted, one
15301530
for the sync error and one for the async error.
15311531

1532+
### USDT probes
1533+
1534+
<!-- YAML
1535+
added: REPLACEME
1536+
-->
1537+
1538+
> Stability: 1 - Experimental
1539+
1540+
Node.js exposes a USDT (User-Level Statically Defined Tracing) probe for
1541+
diagnostics channel publish events, enabling external observability tools
1542+
such as `bpftrace`, DTrace, and `perf` to trace channel activity without
1543+
modifying application code or adding JavaScript subscribers.
1544+
1545+
#### Probe: `node:dc__publish`
1546+
1547+
Fired when a message is published to a string-named diagnostics channel.
1548+
When published from native (C++) code and a tracer is attached, the probe
1549+
fires regardless of subscriber state. When published from JavaScript, the
1550+
probe fires only if the channel has active subscribers.
1551+
1552+
* `arg0` {const char\*} The channel name (UTF-8).
1553+
* `arg1` {const void\*} An opaque pointer to the V8 message object, or `NULL`
1554+
if the published message is not a JavaScript object (e.g., a string, number,
1555+
or `null`). **Warning:** This pointer is unstable and must NOT be
1556+
dereferenced by tracing scripts. V8's garbage collector may move the
1557+
underlying object at any time. The pointer is valid only for the
1558+
duration of the probe callback and must not be stored or compared
1559+
across separate probe firings.
1560+
1561+
#### Platform support
1562+
1563+
At `./configure` time, Node.js checks for a working `dtrace` tool and
1564+
uses `dtrace -h` to generate a probe header. Pass `--without-dtrace` to
1565+
`./configure` to disable probe support entirely.
1566+
1567+
* **Linux**: Install the `systemtap-sdt-dev` package (Debian/Ubuntu) or
1568+
`systemtap-sdt-devel` (Fedora/RHEL) before building Node.js. The
1569+
SystemTap `dtrace` wrapper generates a header with semaphore support,
1570+
giving the probe zero overhead when no tracer is attached.
1571+
* **macOS**: Supported natively via DTrace. The probe instruction is
1572+
patched to a no-op by the kernel when no tracer is attached, but the
1573+
JS-to-C++ call for `emitPublishProbe` is still incurred on every
1574+
publish to a string-named channel with subscribers.
1575+
* **FreeBSD**: Supported natively via DTrace, with the same
1576+
characteristics as macOS.
1577+
* **illumos/SmartOS**: Supported natively via DTrace, with the same
1578+
characteristics as macOS.
1579+
1580+
If `dtrace` is not found but `<sys/sdt.h>` is available, the probe falls
1581+
back to always-enabled mode. On platforms where neither is available,
1582+
the probe compiles to a no-op with zero runtime overhead.
1583+
1584+
#### Example: bpftrace (Linux)
1585+
1586+
```bash
1587+
sudo bpftrace -e '
1588+
usdt:./out/Release/node:node:dc__publish {
1589+
printf("channel: %s\n", str(arg0));
1590+
}
1591+
' -c './out/Release/node app.js'
1592+
```
1593+
1594+
#### Example: DTrace (macOS/FreeBSD)
1595+
1596+
```bash
1597+
sudo dtrace -n '
1598+
node*:::dc__publish {
1599+
printf("channel: %s\n", copyinstr(arg0));
1600+
}
1601+
' -c './out/Release/node app.js'
1602+
```
1603+
15321604
### Built-in Channels
15331605

15341606
#### Console

lib/diagnostics_channel.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ const { triggerUncaughtException } = internalBinding('errors');
3333
// The subscriber buffer is replaced when native channel storage grows, so it
3434
// must always be accessed through the binding instead of cached.
3535
const dc_binding = internalBinding('diagnostics_channel');
36+
// The USDT probe semaphore is exposed by the binding as a Uint16Array
37+
// view over static native memory. It must be resolved lazily rather than
38+
// captured at module load: this module is included in the startup
39+
// snapshot, and the view's native backing store cannot be serialized:
40+
// a view captured while building the snapshot is detached when the
41+
// snapshot is deserialized. `null` marks a USDT-less build after the
42+
// first resolution so that the hot path stays branch-only in that case.
43+
let probeSemaphore;
3644

3745
const { WeakReference, kEmptyObject } = require('internal/util');
3846
const { isPromise } = require('internal/util/types');
@@ -188,6 +196,14 @@ class ActiveChannel {
188196
}
189197

190198
publish(data) {
199+
if (probeSemaphore === undefined) {
200+
probeSemaphore = dc_binding.probeSemaphore ?? null;
201+
}
202+
if (probeSemaphore !== null &&
203+
probeSemaphore[0] > 0 &&
204+
typeof this.name === 'string') {
205+
dc_binding.emitPublishProbe(this.name, data);
206+
}
191207
const subscribers = this._subscribers;
192208
for (let i = 0; i < (subscribers?.length || 0); i++) {
193209
try {

node.gyp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
'node_use_dtls%': 'false',
4747
'node_use_sqlite%': 'true',
4848
'node_use_ffi%': 'false',
49+
'node_use_dtrace%': 'false',
50+
'node_no_usdt%': 'false',
4951
'node_use_v8_platform%': 'true',
5052
'node_enable_v8_vtunejit%': 'false',
5153
'node_v8_options%': '',
@@ -276,6 +278,8 @@
276278
'src/node_metadata.h',
277279
'src/node_mutex.h',
278280
'src/node_diagnostics_channel.h',
281+
'src/node_usdt.h',
282+
'src/node_provider.d',
279283
'src/node_modules.h',
280284
'src/node_object_wrap.h',
281285
'src/node_options.h',
@@ -901,6 +905,43 @@
901905
'WARNING_CFLAGS': [ '-Werror' ],
902906
},
903907
}],
908+
[ 'node_no_usdt=="true"', {
909+
'defines': [ 'NODE_NO_USDT=1' ],
910+
}],
911+
[ 'node_use_dtrace=="true"', {
912+
'defines': [ 'NODE_HAVE_DTRACE=1' ],
913+
'conditions': [
914+
[ 'OS=="linux"', {
915+
'actions': [
916+
{
917+
'action_name': 'node_dtrace_header',
918+
'inputs': [ 'src/node_provider.d' ],
919+
'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/node_provider.h' ],
920+
'action': [
921+
'dtrace', '-h',
922+
'-s', 'src/node_provider.d',
923+
'-o', '<(SHARED_INTERMEDIATE_DIR)/node_provider.h',
924+
],
925+
},
926+
],
927+
}, {
928+
# macOS, FreeBSD, illumos: native DTrace requires -xnolibs
929+
# to avoid loading kernel D libraries during header generation.
930+
'actions': [
931+
{
932+
'action_name': 'node_dtrace_header',
933+
'inputs': [ 'src/node_provider.d' ],
934+
'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/node_provider.h' ],
935+
'action': [
936+
'dtrace', '-h', '-xnolibs',
937+
'-s', 'src/node_provider.d',
938+
'-o', '<(SHARED_INTERMEDIATE_DIR)/node_provider.h',
939+
],
940+
},
941+
],
942+
}],
943+
],
944+
}],
904945
[ 'node_builtin_modules_path!=""', {
905946
'defines': [ 'NODE_BUILTIN_MODULES_PATH="<(node_builtin_modules_path)"' ],
906947
}],

0 commit comments

Comments
 (0)