Repository navigation
Conversation
|
@nodejs/diagnostics wdyt? Should we do this or should we have one event for each operation? |
| if (!path.empty()) { | ||
| obj->Set(context, | ||
| env->path_string(), | ||
| String::NewFromUtf8(isolate, |
|
SGTM |
|
Does this impact perf. much when the channels are not subscribed to? Depending on the impact with a subscriber it might be worth having a channel per operation so that we can be selective over which ops we want to impact? |
|
I think having granularity via multiple channels is better because it allows APMs to choose what to subscribe to without adding overhead to NO-OP events. |
657db86 to
508ffc0
Compare
Add built-in node:diagnostics_channel channels for file system operations performed through node:fs and node:fs/promises. Each operation gets its own TracingChannel family named fs.<operation>, with channels tracing:fs.<operation>:start, :end, :asyncStart, :asyncEnd, and :error. The event payload carries the API (sync/callback/promise), path/dest/fd fields when applicable, plus result/error following TracingChannel conventions. Events are published from the internal shared file system layer rather than the JS wrappers, so captured function references still emit events. Signed-off-by: Matteo Collina <hello@matteocollina.com>
508ffc0 to
6e1aa61
Compare
Define the inline channel helpers in node_file-inl.h so that including node_file.h alone builds under -Werror=undefined-inline. Hold the cached channels weakly, as the permission code does, and skip the instrumentation while building a snapshot: linking a native channel creates JS channel objects that cannot be serialized. Signed-off-by: Matteo Collina <hello@matteocollina.com>
The libuv request's file field is private and a union on Windows, so reading it does not compile there. Every descriptor-based libuv fs operation takes the descriptor as its first argument, so pick it up from the call arguments instead. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65370 +/- ##
==========================================
+ Coverage 90.19% 90.26% +0.06%
==========================================
Files 770 789 +19
Lines 264410 271799 +7389
Branches 50243 51879 +1636
==========================================
+ Hits 238479 245333 +6854
+ Misses 16926 16919 -7
- Partials 9005 9547 +542
🚀 New features to boost your workflow:
|
| * `fd` {number|undefined} The file descriptor for operations that operate on | ||
| an existing file descriptor, such as `read`, `write`, `fsync`, or `close`. | ||
|
|
||
| Large read/write buffers are not copied into the event payload. The `start` |
There was a problem hiding this comment.
Why not include them? Not copy, but pass the reference.
| Large read/write buffers are not copied into the event payload. The `start` | ||
| and `asyncStart` events carry no `result` or `error`; the `end` and `asyncEnd` | ||
| events carry the `result` of the operation, and the `error` event carries the | ||
| `error`, following the [TracingChannel Channels][] conventions. |
There was a problem hiding this comment.
The convention includes result fields in asyncStart https://nodejs.org/docs/latest/api/diagnostics_channel.html#asyncstartevent
In fact this is necessary for many kinds of instrumentation.
There was a problem hiding this comment.
This one is the only blocker, the rest can be handled in future PRs if desired.
| `error`, following the [TracingChannel Channels][] conventions. | ||
|
|
||
| Operations performed through streams (`fs.createReadStream` and | ||
| `fs.createWriteStream`), most `FileHandle` methods, and the `fs.readFile` |
There was a problem hiding this comment.
Why not FileHandle?
The streams should be doable with diagnostics channels, but maybe just not TracingChannels.
| function reference was captured before subscribing or whether the operation | ||
| uses the callback, promise, or synchronous API. | ||
|
|
||
| Each event carries an object with the following common fields: |
There was a problem hiding this comment.
Other args may be necessary for o11y, for example the args to chmod/chown other than the path.
logaretm
left a comment
There was a problem hiding this comment.
A few comments on top of what @bengl brought up, so doing the channels from C++ as it is now does a few things:
- Skips the single context object correlation, I think this is a major blocker. Otherwise APMs cannot use this to create spans and will mostly rely on external maps to guess the correlation.
- It also means the context propagation isn't there, because it publishes without
runStores, sobindStore()on these channels does nothing. Technically there isn't any nested operations here so it may not matter. So this is more of a nit. - The
errorevent issue that I brought below, theerrorevent is not a terminal event, there must be always anendorasyncEndafter it.
I think switching to the JS API here would solve all of these issues and make sure the publishing semantics are consistent/correct.
| Isolate* isolate = env->isolate(); | ||
| HandleScope scope(isolate); | ||
| Local<Context> context = env->context(); | ||
| Local<Object> obj = Object::New(isolate); |
There was a problem hiding this comment.
Each event gets its own payload object here. Subscribers usually store per-operation state (like a span) on the context object in start and read it back in asyncEnd or error.
With separate objects there's no way to corrolate those events, especially for concurrent calls on the same path.
Could one object be created per operation and reused for every event? If not then it might be better to use the JS API here for those channels.
| Operations performed through streams (`fs.createReadStream` and | ||
| `fs.createWriteStream`), most `FileHandle` methods, and the `fs.readFile` | ||
| fast path (which batches open/stat/read/close into a single background job) | ||
| are not covered by these channel families, and may not emit the full set of | ||
| events. |
There was a problem hiding this comment.
This says the readFile fast path isn't covered, but the paragraph above says every public fs operation is.
Also, writeFile and fs.promises.readFile skip the channels, and so do readFileSync with an encoding and existsSync. Those are the fs calls we instrument the most.
I think the PR should cover them too, so all the public fs APIs emit.
| req_wrap->dest_p); | ||
| PublishFSOperationEvent(env, | ||
| *channels, | ||
| FSOperationChannel::kError, |
There was a problem hiding this comment.
On failure this emits error and then stops, with no end after it. traceSync always emits end after error, for APMs end is important because that's when they end the span or flush any telemetry.
| } | ||
|
|
||
| void FSReqCallback::Reject(Local<Value> reject) { | ||
| PublishFSOpCompletionEvent(this, FSOperationChannel::kError, "error", reject); |
There was a problem hiding this comment.
Same for callback/promise calls: error is emitted but asyncEnd never follows it, while traceCallback/tracePromise always emit asyncEnd after error.
Qard
left a comment
There was a problem hiding this comment.
As has already been stated, this needs a shared context object between tracing channel events, and we'd also need a proper runStores for this to be complete TracingChannel behaviour.
However, it's also worth pointing out it'd likely actually be faster to just do all this in JS as also already suggested as the entire data and subscription model is already in JS. Putting it in native code adds an extra native barrier cross for not really any reason. We should generally avoid native-side publishing except for cases where there is no viable hook point from JS.
Adds built-in
node:diagnostics_channelchannels for file system operations performed throughnode:fsandnode:fs/promises, addressing #65330.Each operation gets its own
TracingChannelfamily namedfs.<operation>(e.g.fs.open,fs.read,fs.stat), with channelstracing:fs.<operation>:start,:end,:asyncStart,:asyncEnd, and:error. The event payload carries the API (sync/callback/promise),path/dest/fdfields when applicable, plusresult/errorfollowing TracingChannel conventions. Subscribers can usediagnostics_channel.tracingChannel('fs.open')to subscribe to all events of one operation at once, or subscribe to individual channels by name.Events are published from the internal shared file system layer rather than the JS wrappers, so captured function references still emit events. Adds documentation and a new
test/parallel/test-diagnostics-channel-fs.js.AI generated, humanly reviewed.