-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
trace_events: enable trace events implicitly, add module #19233
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test that enable/disable the trace_events and process them when the target process is still running? Ideally from the same process.
const path = require('path'); | ||
const fs = require('fs'); | ||
const tmpdir = require('../common/tmpdir'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a small comment explaining what this test does? are we validating trace_events with perf_hooks? I would prefer to have an independent test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's using the fact that perf_hooks emits a very small number of very predictable trace events. The v8 and node.async_hooks trace events tend to be more unpredictable.
@@ -23,15 +20,17 @@ The available categories are: | |||
measurements. | |||
* `v8` | |||
|
|||
By default the `node`, `node.async_hooks`, and `v8` categories are enabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since --trace-events-enabled
does not enable all categories we should still document this default list for that option.
doc/api/tracing.md
Outdated
|
||
### trace_events.setTracingCategories(categories) | ||
|
||
* `categories` [string] A comma-separated list of category names. If |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[string]
-> {string}
?
doc/api/tracing.md
Outdated
|
||
### trace_events.getTracingCategories() | ||
|
||
* Return: [string] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return:
-> Returns:
?
[string]
-> {string}
?
doc/api/tracing.md
Outdated
|
||
It is accessible using `require('trace_events')`. | ||
|
||
### trace_events.setTracingCategories(categories) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if categories
can be undefined
, should this be ([categories])
?
As I have argued before, I think it would be better to just completely remove |
@@ -5,12 +5,9 @@ | |||
Trace Event provides a mechanism to centralize tracing information generated by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't have to be in this PR, but we really need to rename this file to trace_events.md
and give it a correct title :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, definitely :-) ... I can mark that as a todo for another PR.
doc/api/tracing.md
Outdated
|
||
It is accessible using `require('trace_events')`. | ||
|
||
### trace_events.setTracingCategories(categories) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still in favor of an API that allows one to enable and disable categories individually, as that will make it easier to have different modules enable categories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was going back and forth on that. It wouldn't be too difficult to do I just wasn't sure just how far to go down that road so opted for the easiest thing initially.
What I had in mind was a simple variation on this such as:
// trace.events.enableTracingCategories(...categories)
// trace.events.disableTracingCategories(...categories)
trace_events.enableTracingCategories('node', 'v8', 'foo')
trace_events.disableTracingCategories('node')
trace_events.getTracingCategories() // Returns an array, e,g, ['v8', 'foo']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was something like that I had in mind.
@AndreasMadsen ... re: |
src/node.cc
Outdated
static bool trace_enabled = false; | ||
static std::string trace_enabled_categories; // NOLINT(runtime/string) | ||
|
||
std::string trace_enabled_categories; // NOLINT(runtime/string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the lint exception really needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently, yes. Not entirely sure why but it complains without it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C++ part lgtm
@jasnell In |
lib/trace_events.js
Outdated
getTracingCategories | ||
} = process.binding('trace_events'); | ||
|
||
const { trace_events_enabled } = process.binding('config'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: If we can rename trace_events_enabled
to something which is not in snake case here, it would be better.
doc/api/tracing.md
Outdated
* Return: [string] | ||
|
||
Returns the current list of category names as a comma-separated list or | ||
`undefined`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return undefined
? Wouldn't an empty array be better here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would prefer not to do a useless allocation.
9024393
to
bab4b0c
Compare
@AndreasMadsen ... ok, reworked to use the @thefourtheye ... @fhinkel ... C++ part changed quite a bit :-) |
doc/api/tracing.md
Outdated
|
||
### trace_events.disableTracingCategories(...categories) | ||
|
||
* `...categories` [string] One or more category names. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[string]
-> {string}
(otherwise this will not be linkified by the type parser).
doc/api/tracing.md
Outdated
|
||
### trace_events.enableTracingCategories(...categories) | ||
|
||
* `...categories` [string] One or more category names. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[string]
-> {string}
doc/api/tracing.md
Outdated
|
||
### trace_events.getTracingCategories() | ||
|
||
* Return: {string[]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return:
-> Returns:
Ping @addaleax, @AndreasMadsen, @thefourtheye, @fhinkel :-) |
<a id="ERR_TRACE_EVENTS_UNAVAILABLE"></a> | ||
### ERR_TRACE_EVENTS_UNAVAILABLE | ||
|
||
The trace events mechanism is not available for use in the Node.js binary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding:
This is the case when Node.js was compiled with the `--without-v8-platform` flag.
src/node_internals.h
Outdated
extern void DisableTracingCategories( | ||
const std::vector<std::string>& categories); | ||
extern const std::set<std::string>& GetEnabledTracingCategories(); | ||
extern void StopTracing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can drop extern
, it doesn’t make a difference for function declarations + we don’t use it elsewhere
src/node.cc
Outdated
tracing_agent_->EnableCategories(categories); | ||
} | ||
|
||
void DisableTracingCategories(const std::vector<std::string> categories) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these should use const references
src/node_trace_events.cc
Outdated
static void GetTracingCategories(const FunctionCallbackInfo<Value>& args) { | ||
#if NODE_USE_V8_PLATFORM | ||
Environment* env = Environment::GetCurrent(args); | ||
auto categories = GetEnabledTracingCategories(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would create a copy of the set, so const auto&
would be better.
src/node_trace_events.cc
Outdated
auto categories = GetEnabledTracingCategories(); | ||
Local<Array> ret = Array::New(env->isolate(), categories.size()); | ||
size_t n = 0; | ||
for (auto it = categories.begin(); it != categories.end(); it++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (const std::string& category : categories)
? If it fits into one line you can even replace categories
with GetEnabledTracingCategories()
in that scenario
src/tracing/agent.cc
Outdated
started_ = true; | ||
} | ||
TraceConfig* trace_config = new TraceConfig(); | ||
for (auto it = categories_.begin(); it != categories_.end(); it++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto – for (const std::string& category : categories)
would read better, imo.
src/tracing/agent.cc
Outdated
category != categories.end(); | ||
category++) { | ||
categories_.insert(*category); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can avoid the loop and just write categories_.insert(categories.begin(), categories.end());
src/tracing/agent.cc
Outdated
category++) { | ||
categories_.erase(*category); | ||
} | ||
StartTracing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be if (categories_.empty()) StopTracing();
somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because StartTracing()
already handles it.
src/tracing/agent.cc
Outdated
if (!categories.empty()) { | ||
for (auto category = categories.begin(); | ||
category != categories.end(); | ||
category++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, for (const std::string& category : categories)
src/tracing/agent.h
Outdated
void StartTracing(); | ||
|
||
const std::string& log_file_pattern_; | ||
std::set<std::string> categories_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this doesn’t make a huge difference, but why not std::unordered_set
?
Enable trace events to be turned on if `--trace-event-categories` is set even if `--trace-events-enabled` is not used. Using `--trace-events-enabled` will still work and will still set the default categories to `v8,node,node.async_hooks`, but the boolean flag is not required to enable the tracing agent. Also, add trace_events module and API Allows trace event collection to be turned on/off at runtime via a new top level `trace_events` module.
`trace_events.enableTracingCategories('v8', 'node')` `trace_events.disableTracingCategories('v8')` `trace_events.getTracingCategories()` // return an array
049be5a
to
d366cc6
Compare
@jasnell One use case I have been thinking about is having the ability to enable/disable tracing on a specific operations (say an individual http request). I think this is mostly compatible with the API being proposed here with some extra features:
Of course, the latter assumes that there is only one actor in the system that wants to control tracing. This is a different use-case than ambient, always-on, tracing. It is not clear to me how users would actually use the tracing system in Node, but I think we should support both use-cases. WDYT? |
Controlling the output destination would require a deeper internal change to the agent to essentially support multiple simultaneous targets unless we're going to limit the API to one Target at a time. That could run into issues if dependencies attempt to enable/disable their own tracing targets in conflict with one another. Not out of the question but also not trivial. Disabling all categories using this api is trivial. Enabling all assumes we know what all can be enabled. For now we can do that by enabling the |
I was thinking a single target at a time. It seems that there are two distinct use-cases: ambient mode (multiple actors may enable / disable categories; traces are written and consumed at end), or online mode (where an actor is directly controlling when tracing is enabled, where the output is written). If we deem the use-cases to be exclusive, the single target option would work. IOW, one cannot do ambient tracing and online tracing at the same time. |
@ofrobots ... yeah, I guess the part I'm struggling with are the API ergonomics on that. Do you have any rough sketch ideas on what the API would look like? @AndreasMadsen ... what do you think? |
@jasnell I was thinking of something like: const tracer = trace_events.enableCategories('node.http', 'node.dns')
tracer.disableCategories(); Then, if at least one agent has the category enabled it is enabled. The API also prevents an actor from disabling another actors category. The next question is then how to deal with multiple listeners. I think this should be separated into two:
const tracer = new trace_events.Tracer('node.http', 'node.dns')
tracer.on('event', console.log);
tracer.disable(); Properly that API could be the same as the API for dynamically enabling and disabling a category. If an in-process actor enables a category that category will leak to the out-of-process actor. It is the out-of-process actors responsibility to filter the data. I think the in-process use case should have a low priority. But we should consider making the category API such that it can be extended in the future. |
An API that somewhat echoes async_hooks would be good here... const tracer = trace_events.createTracer('node.http', 'node.dns')
trace.enable()
trace.disable() I'm not as convinced about the in-process consumer, however, as it could easily end up being a significant performance foot-gun. If we do end up with the in-process model, however, let's avoid the EventEmitter approach and go with a simple callback... something like... const tracer = trace_events.createTracer('node.http', 'node.dns')
tracer.enable((event) => { /* ... */ });
tracer.disable(); My key concern with the in-process model is the conversion of the trace event into a javascript object and the cost of the boundary cross that would be required. If someone is also using async_hooks, there could certainly be a significant performance penalty. Do we care? |
I would be opposed to an in process consumer. The performance overhead would defeat the benefit of the extremely efficient low-level implementation. I do want to be able to control the output filename being controllable from an in process actor. |
I agree. |
Ok, then I think we're all aligned on that then. The remaining question is then how do we allow for in-process control of the trace filename without running into conflicts with multiple tracers. One thought is to only allow changing of the file name while trace events are not being actively captured... that is, while there are no active categories. Any attempt to set the filename while tracers are active would result in an error. trace_events.setTraceFile(filenameOrFD) // ok because there are no active tracers yet.
const tracer = trace_events.createTracer('node.http', 'node.dns')
trace_events.setTraceFile(filenameOrFD) // still ok, because tracer is not enabled yet.
tracer.enable()
trace_events.setTraceFile(filenameOrFD) // throws! tracing is active
tracer.disable()
trace_events.setTraceFile(filenameOrFD) // ok, no tracer running A default tracer can be created when the trace_events.getDefaultTracer().disable() // turn off the tracing categories set using the command line flag
trace_events.setTraceFile(filenameOrFD)
trace_events.getDefaultTracer().enable() // re-enable the default tracer @AndreasMadsen ... Another question: with regards to the trace file pattern work you recently landed (specifically e.g. trace_events.setTraceFile('node_trace_${rotation}.log')
const tracer = trace_events.createTracer('node.http2')
tracer.enable()
// do stuff
tracer.disable() // causes node_trace_1.log to be closed because all tracing is off
tracer.enable() // causes node_trace_2.log to be opened and started because tracing is back on
// do stuff |
@jasnell This doesn't really allow for multiple independent agents. I have no idea how it should be implemented, but I think we need something like: const tracer = trace_events.createTracer({
categories: ['node.http', 'node.dns'],
destination: filenameOrFD
})
tracer.enable()
tracer.disable()
const tracer = trace_events.createTracer({
categories: categories.split(','),
destination: filePattern
})
tracer.enable()
I think it would be better to look into a more streaming friendly file format. |
Hmm... that increases the complexity of the agent fairly significantly in that we would end up having to output to multiple writers for each destination... or spin up a separate agent thread for each destination and refactor the queue. Will think about that a bit more.
Yeah, I've started some investigations into that with @ofrobots' help was able to determine that there's a little bit of flexibility in the v8 json format but what would be ideal is a true line-delimited json approach that could write to a file or to a pipe or any arbitrary fd. I'll be working up an experimental alternative writer that does exactly that in the coming couple of weeks once I clear a few higher priority items off my list. |
Yeah, I know
This could be streaming parsed by splitting at new-lines and skipping the first character. I don't like it, but at least it is possible. |
After talking it over with @ofrobots a bit more today, it would be good to split this into three distinct efforts
On the JS side, the API I have in mind for 2 is a slight variation of what was last discussed above: const tracer = trace_events.createTracer({
categories: ['node.http', 'node.dns']
})
tracer.enable()
tracer.disable() With this pattern, if someone wanted to support multiple destinations, a new option can be added to the Some details on the proposed implementation:
|
Closing this in favor of a new fresh take...#19803 |
Ping @AndreasMadsen, @addaleax, @mcollina
This is a replacement for #18826 that takes a different approach.
Enable trace events to be turned on if
--trace-event-categories
is set even if
--trace-events-enabled
is not used. Using--trace-events-enabled
will still work and will still set thedefault categories to
v8,node,node.async_hooks
, but the booleanflag is not required to enable the tracing agent.
Also, add trace_events module and API
Allows trace event collection to be turned on/off at runtime
via a new top level
trace_events
module.Semver-major because of the addition of the new top level module.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes