Skip to content

Commit 4b7cd4b

Browse files
committed
trace_events: add trace category enabled tracking
Track state of async_hooks trace event category enablement. Enable/disable the async_hooks trace event dynamically. PR-URL: #22128 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
1 parent 2ce0380 commit 4b7cd4b

File tree

8 files changed

+159
-37
lines changed

8 files changed

+159
-37
lines changed

lib/internal/bootstrap/node.js

+30-13
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
// bootstrapper properties... destructured to
1717
// avoid retaining a reference to the bootstrap
1818
// object.
19-
{ _setupProcessObject, _setupNextTick,
19+
{ _setupTraceCategoryState,
20+
_setupProcessObject, _setupNextTick,
2021
_setupPromises, _chdir, _cpuUsage,
2122
_hrtime, _hrtimeBigInt,
2223
_memoryUsage, _rawDebug,
@@ -35,6 +36,8 @@
3536

3637
EventEmitter.call(process);
3738

39+
setupTraceCategoryState();
40+
3841
setupProcessObject();
3942

4043
// Do this good and early, since it handles errors.
@@ -98,18 +101,6 @@
98101
if (global.__coverage__)
99102
NativeModule.require('internal/process/write-coverage').setup();
100103

101-
102-
{
103-
const traceEvents = internalBinding('trace_events');
104-
const traceEventCategory = 'node,node.async_hooks';
105-
106-
if (traceEvents.isTraceCategoryEnabled(traceEventCategory)) {
107-
NativeModule.require('internal/trace_events_async_hooks')
108-
.setup(traceEvents, traceEventCategory);
109-
}
110-
}
111-
112-
113104
if (process.config.variables.v8_enable_inspector) {
114105
NativeModule.require('internal/inspector_async_hook').setup();
115106
}
@@ -311,6 +302,32 @@
311302
}
312303
}
313304

305+
function setupTraceCategoryState() {
306+
const { traceCategoryState } = internalBinding('trace_events');
307+
const kCategoryAsyncHooks = 0;
308+
let traceEventsAsyncHook;
309+
310+
function toggleTraceCategoryState() {
311+
// Dynamically enable/disable the traceEventsAsyncHook
312+
const asyncHooksEnabled = !!traceCategoryState[kCategoryAsyncHooks];
313+
314+
if (asyncHooksEnabled) {
315+
// Lazy load internal/trace_events_async_hooks only if the async_hooks
316+
// trace event category is enabled.
317+
if (!traceEventsAsyncHook) {
318+
traceEventsAsyncHook =
319+
NativeModule.require('internal/trace_events_async_hooks');
320+
}
321+
traceEventsAsyncHook.enable();
322+
} else if (traceEventsAsyncHook) {
323+
traceEventsAsyncHook.disable();
324+
}
325+
}
326+
327+
toggleTraceCategoryState();
328+
_setupTraceCategoryState(toggleTraceCategoryState);
329+
}
330+
314331
function setupProcessObject() {
315332
_setupProcessObject(pushValueToArray);
316333

+48-24
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
'use strict';
22

3-
exports.setup = function(traceEvents, traceEventCategory) {
4-
const { trace } = traceEvents;
5-
const async_wrap = process.binding('async_wrap');
6-
const async_hooks = require('async_hooks');
3+
const { internalBinding } = require('internal/bootstrap/loaders');
4+
const { trace } = internalBinding('trace_events');
5+
const async_wrap = process.binding('async_wrap');
6+
const async_hooks = require('async_hooks');
7+
const { SafeMap, SafeSet } = require('internal/safe_globals');
78

8-
// Use small letters such that chrome://tracing groups by the name.
9-
// The behavior is not only useful but the same as the events emitted using
10-
// the specific C++ macros.
11-
const BEFORE_EVENT = 'b'.charCodeAt(0);
12-
const END_EVENT = 'e'.charCodeAt(0);
9+
// Use small letters such that chrome://tracing groups by the name.
10+
// The behavior is not only useful but the same as the events emitted using
11+
// the specific C++ macros.
12+
const kBeforeEvent = 'b'.charCodeAt(0);
13+
const kEndEvent = 'e'.charCodeAt(0);
14+
const kTraceEventCategory = 'node,node.async_hooks';
1315

16+
const kEnabled = Symbol('enabled');
17+
18+
// It is faster to emit traceEvents directly from C++. Thus, this happens
19+
// in async_wrap.cc. However, events emitted from the JavaScript API or the
20+
// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are
21+
// emitted using the JavaScript API. To prevent emitting the same event
22+
// twice the async_wrap.Providers list is used to filter the events.
23+
const nativeProviders = new SafeSet(Object.keys(async_wrap.Providers));
24+
const typeMemory = new SafeMap();
25+
26+
function createHook() {
1427
// In traceEvents it is not only the id but also the name that needs to be
1528
// repeated. Since async_hooks doesn't expose the provider type in the
1629
// non-init events, use a map to manually map the asyncId to the type name.
17-
const typeMemory = new Map();
1830

19-
// It is faster to emit traceEvents directly from C++. Thus, this happens
20-
// in async_wrap.cc. However, events emitted from the JavaScript API or the
21-
// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are
22-
// emitted using the JavaScript API. To prevent emitting the same event
23-
// twice the async_wrap.Providers list is used to filter the events.
24-
const nativeProviders = new Set(Object.keys(async_wrap.Providers));
25-
26-
async_hooks.createHook({
31+
const hook = async_hooks.createHook({
2732
init(asyncId, type, triggerAsyncId, resource) {
2833
if (nativeProviders.has(type)) return;
2934

3035
typeMemory.set(asyncId, type);
31-
trace(BEFORE_EVENT, traceEventCategory,
36+
trace(kBeforeEvent, kTraceEventCategory,
3237
type, asyncId,
3338
{
3439
triggerAsyncId,
@@ -40,24 +45,43 @@ exports.setup = function(traceEvents, traceEventCategory) {
4045
const type = typeMemory.get(asyncId);
4146
if (type === undefined) return;
4247

43-
trace(BEFORE_EVENT, traceEventCategory, `${type}_CALLBACK`, asyncId);
48+
trace(kBeforeEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId);
4449
},
4550

4651
after(asyncId) {
4752
const type = typeMemory.get(asyncId);
4853
if (type === undefined) return;
4954

50-
trace(END_EVENT, traceEventCategory, `${type}_CALLBACK`, asyncId);
55+
trace(kEndEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId);
5156
},
5257

5358
destroy(asyncId) {
5459
const type = typeMemory.get(asyncId);
5560
if (type === undefined) return;
5661

57-
trace(END_EVENT, traceEventCategory, type, asyncId);
62+
trace(kEndEvent, kTraceEventCategory, type, asyncId);
5863

5964
// cleanup asyncId to type map
6065
typeMemory.delete(asyncId);
6166
}
62-
}).enable();
63-
};
67+
});
68+
69+
return {
70+
enable() {
71+
if (this[kEnabled])
72+
return;
73+
this[kEnabled] = true;
74+
hook.enable();
75+
},
76+
77+
disable() {
78+
if (!this[kEnabled])
79+
return;
80+
this[kEnabled] = false;
81+
hook.disable();
82+
typeMemory.clear();
83+
}
84+
};
85+
}
86+
87+
module.exports = createHook();

src/bootstrapper.cc

+7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
3131
args.GetIsolate()->RunMicrotasks();
3232
}
3333

34+
void SetupTraceCategoryState(const FunctionCallbackInfo<Value>& args) {
35+
Environment* env = Environment::GetCurrent(args);
36+
CHECK(args[0]->IsFunction());
37+
env->set_trace_category_state_function(args[0].As<Function>());
38+
}
39+
3440
void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
3541
Environment* env = Environment::GetCurrent(args);
3642
Isolate* isolate = env->isolate();
@@ -117,6 +123,7 @@ void SetupPromises(const FunctionCallbackInfo<Value>& args) {
117123
// completes so that it can be gc'd as soon as possible.
118124
void SetupBootstrapObject(Environment* env,
119125
Local<Object> bootstrapper) {
126+
BOOTSTRAP_METHOD(_setupTraceCategoryState, SetupTraceCategoryState);
120127
BOOTSTRAP_METHOD(_setupProcessObject, SetupProcessObject);
121128
BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick);
122129
BOOTSTRAP_METHOD(_setupPromises, SetupPromises);

src/env-inl.h

+5
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,11 @@ Environment::should_abort_on_uncaught_toggle() {
432432
return should_abort_on_uncaught_toggle_;
433433
}
434434

435+
inline AliasedBuffer<uint8_t, v8::Uint8Array>&
436+
Environment::trace_category_state() {
437+
return trace_category_state_;
438+
}
439+
435440
Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
436441
Environment* env)
437442
: env_(env) {

src/env.cc

+33
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ using v8::TryCatch;
3131
using v8::Value;
3232
using worker::Worker;
3333

34+
#define kTraceCategoryCount 1
35+
3436
int const Environment::kNodeContextTag = 0x6e6f64;
3537
void* Environment::kNodeContextTagPtr = const_cast<void*>(
3638
static_cast<const void*>(&Environment::kNodeContextTag));
@@ -103,6 +105,21 @@ void InitThreadLocalOnce() {
103105
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
104106
}
105107

108+
void Environment::TrackingTraceStateObserver::UpdateTraceCategoryState() {
109+
env_->trace_category_state()[0] =
110+
*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
111+
TRACING_CATEGORY_NODE1(async_hooks));
112+
113+
Isolate* isolate = env_->isolate();
114+
Local<Function> cb = env_->trace_category_state_function();
115+
if (cb.IsEmpty())
116+
return;
117+
TryCatch try_catch(isolate);
118+
try_catch.SetVerbose(true);
119+
cb->Call(env_->context(), v8::Undefined(isolate),
120+
0, nullptr).ToLocalChecked();
121+
}
122+
106123
Environment::Environment(IsolateData* isolate_data,
107124
Local<Context> context,
108125
tracing::AgentWriterHandle* tracing_agent_writer)
@@ -118,6 +135,7 @@ Environment::Environment(IsolateData* isolate_data,
118135
emit_env_nonstring_warning_(true),
119136
makecallback_cntr_(0),
120137
should_abort_on_uncaught_toggle_(isolate_, 1),
138+
trace_category_state_(isolate_, kTraceCategoryCount),
121139
#if HAVE_INSPECTOR
122140
inspector_agent_(new inspector::Agent(this)),
123141
#endif
@@ -132,6 +150,14 @@ Environment::Environment(IsolateData* isolate_data,
132150

133151
AssignToContext(context, ContextInfo(""));
134152

153+
if (tracing_agent_writer_ != nullptr) {
154+
trace_state_observer_.reset(new TrackingTraceStateObserver(this));
155+
v8::TracingController* tracing_controller =
156+
tracing_agent_writer_->GetTracingController();
157+
if (tracing_controller != nullptr)
158+
tracing_controller->AddTraceStateObserver(trace_state_observer_.get());
159+
}
160+
135161
destroy_async_id_list_.reserve(512);
136162
performance_state_.reset(new performance::performance_state(isolate()));
137163
performance_state_->Mark(
@@ -173,6 +199,13 @@ Environment::~Environment() {
173199
context()->SetAlignedPointerInEmbedderData(
174200
ContextEmbedderIndex::kEnvironment, nullptr);
175201

202+
if (tracing_agent_writer_ != nullptr) {
203+
v8::TracingController* tracing_controller =
204+
tracing_agent_writer_->GetTracingController();
205+
if (tracing_controller != nullptr)
206+
tracing_controller->RemoveTraceStateObserver(trace_state_observer_.get());
207+
}
208+
176209
delete[] heap_statistics_buffer_;
177210
delete[] heap_space_statistics_buffer_;
178211
delete[] http_parser_buffer_;

src/env.h

+26
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ struct PackageConfig {
344344
V(tick_callback_function, v8::Function) \
345345
V(timers_callback_function, v8::Function) \
346346
V(tls_wrap_constructor_function, v8::Function) \
347+
V(trace_category_state_function, v8::Function) \
347348
V(tty_constructor_template, v8::FunctionTemplate) \
348349
V(udp_constructor_function, v8::Function) \
349350
V(url_constructor_function, v8::Function) \
@@ -649,6 +650,8 @@ class Environment {
649650
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
650651
should_abort_on_uncaught_toggle();
651652

653+
inline AliasedBuffer<uint8_t, v8::Uint8Array>& trace_category_state();
654+
652655
// The necessary API for async_hooks.
653656
inline double new_async_id();
654657
inline double execution_async_id();
@@ -830,6 +833,25 @@ class Environment {
830833
// This needs to be available for the JS-land setImmediate().
831834
void ToggleImmediateRef(bool ref);
832835

836+
class TrackingTraceStateObserver :
837+
public v8::TracingController::TraceStateObserver {
838+
public:
839+
explicit TrackingTraceStateObserver(Environment* env) : env_(env) {}
840+
841+
void OnTraceEnabled() override {
842+
UpdateTraceCategoryState();
843+
}
844+
845+
void OnTraceDisabled() override {
846+
UpdateTraceCategoryState();
847+
}
848+
849+
private:
850+
void UpdateTraceCategoryState();
851+
852+
Environment* env_;
853+
};
854+
833855
class ShouldNotAbortOnUncaughtScope {
834856
public:
835857
explicit inline ShouldNotAbortOnUncaughtScope(Environment* env);
@@ -889,6 +911,10 @@ class Environment {
889911
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
890912
int should_not_abort_scope_counter_ = 0;
891913

914+
// Attached to a Uint8Array that tracks the state of trace category
915+
AliasedBuffer<uint8_t, v8::Uint8Array> trace_category_state_;
916+
std::unique_ptr<TrackingTraceStateObserver> trace_state_observer_;
917+
892918
std::unique_ptr<performance::performance_state> performance_state_;
893919
std::unordered_map<std::string, uint64_t> performance_marks_;
894920

src/node_trace_events.cc

+4
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ void Initialize(Local<Object> target,
127127
.FromJust();
128128
target->Set(context, trace,
129129
binding->Get(context, trace).ToLocalChecked()).FromJust();
130+
131+
target->Set(context,
132+
FIXED_ONE_BYTE_STRING(env->isolate(), "traceCategoryState"),
133+
env->trace_category_state().GetJSArray()).FromJust();
130134
}
131135

132136
} // namespace node

src/tracing/agent.h

+6
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class AgentWriterHandle {
5151

5252
inline Agent* agent() { return agent_; }
5353

54+
inline v8::TracingController* GetTracingController();
55+
5456
private:
5557
inline AgentWriterHandle(Agent* agent, int id) : agent_(agent), id_(id) {}
5658

@@ -155,6 +157,10 @@ void AgentWriterHandle::Disable(const std::set<std::string>& categories) {
155157
if (agent_ != nullptr) agent_->Disable(id_, categories);
156158
}
157159

160+
inline v8::TracingController* AgentWriterHandle::GetTracingController() {
161+
return agent_ != nullptr ? agent_->GetTracingController() : nullptr;
162+
}
163+
158164
} // namespace tracing
159165
} // namespace node
160166

0 commit comments

Comments
 (0)