diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 08daeb191551e5..26ab0c3198cd56 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -16,7 +16,8 @@ // bootstrapper properties... destructured to // avoid retaining a reference to the bootstrap // object. - { _setupProcessObject, _setupNextTick, + { _setupTraceCategoryState, + _setupProcessObject, _setupNextTick, _setupPromises, _chdir, _cpuUsage, _hrtime, _hrtimeBigInt, _memoryUsage, _rawDebug, @@ -35,6 +36,8 @@ EventEmitter.call(process); + setupTraceCategoryState(); + setupProcessObject(); // Do this good and early, since it handles errors. @@ -98,18 +101,6 @@ if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); - - { - const traceEvents = internalBinding('trace_events'); - const traceEventCategory = 'node,node.async_hooks'; - - if (traceEvents.isTraceCategoryEnabled(traceEventCategory)) { - NativeModule.require('internal/trace_events_async_hooks') - .setup(traceEvents, traceEventCategory); - } - } - - if (process.config.variables.v8_enable_inspector) { NativeModule.require('internal/inspector_async_hook').setup(); } @@ -311,6 +302,32 @@ } } + function setupTraceCategoryState() { + const { traceCategoryState } = internalBinding('trace_events'); + const kCategoryAsyncHooks = 0; + let traceEventsAsyncHook; + + function toggleTraceCategoryState() { + // Dynamically enable/disable the traceEventsAsyncHook + const asyncHooksEnabled = !!traceCategoryState[kCategoryAsyncHooks]; + + if (asyncHooksEnabled) { + // Lazy load internal/trace_events_async_hooks only if the async_hooks + // trace event category is enabled. + if (!traceEventsAsyncHook) { + traceEventsAsyncHook = + NativeModule.require('internal/trace_events_async_hooks'); + } + traceEventsAsyncHook.enable(); + } else if (traceEventsAsyncHook) { + traceEventsAsyncHook.disable(); + } + } + + toggleTraceCategoryState(); + _setupTraceCategoryState(toggleTraceCategoryState); + } + function setupProcessObject() { _setupProcessObject(pushValueToArray); diff --git a/lib/internal/trace_events_async_hooks.js b/lib/internal/trace_events_async_hooks.js index f687f1ced8e155..a983cb2fa2e0bd 100644 --- a/lib/internal/trace_events_async_hooks.js +++ b/lib/internal/trace_events_async_hooks.js @@ -1,34 +1,39 @@ 'use strict'; -exports.setup = function(traceEvents, traceEventCategory) { - const { trace } = traceEvents; - const async_wrap = process.binding('async_wrap'); - const async_hooks = require('async_hooks'); +const { internalBinding } = require('internal/bootstrap/loaders'); +const { trace } = internalBinding('trace_events'); +const async_wrap = process.binding('async_wrap'); +const async_hooks = require('async_hooks'); +const { SafeMap, SafeSet } = require('internal/safe_globals'); - // Use small letters such that chrome://tracing groups by the name. - // The behavior is not only useful but the same as the events emitted using - // the specific C++ macros. - const BEFORE_EVENT = 'b'.charCodeAt(0); - const END_EVENT = 'e'.charCodeAt(0); +// Use small letters such that chrome://tracing groups by the name. +// The behavior is not only useful but the same as the events emitted using +// the specific C++ macros. +const kBeforeEvent = 'b'.charCodeAt(0); +const kEndEvent = 'e'.charCodeAt(0); +const kTraceEventCategory = 'node,node.async_hooks'; +const kEnabled = Symbol('enabled'); + +// It is faster to emit traceEvents directly from C++. Thus, this happens +// in async_wrap.cc. However, events emitted from the JavaScript API or the +// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are +// emitted using the JavaScript API. To prevent emitting the same event +// twice the async_wrap.Providers list is used to filter the events. +const nativeProviders = new SafeSet(Object.keys(async_wrap.Providers)); +const typeMemory = new SafeMap(); + +function createHook() { // In traceEvents it is not only the id but also the name that needs to be // repeated. Since async_hooks doesn't expose the provider type in the // non-init events, use a map to manually map the asyncId to the type name. - const typeMemory = new Map(); - // It is faster to emit traceEvents directly from C++. Thus, this happens - // in async_wrap.cc. However, events emitted from the JavaScript API or the - // Embedder C++ API can't be emitted from async_wrap.cc. Thus they are - // emitted using the JavaScript API. To prevent emitting the same event - // twice the async_wrap.Providers list is used to filter the events. - const nativeProviders = new Set(Object.keys(async_wrap.Providers)); - - async_hooks.createHook({ + const hook = async_hooks.createHook({ init(asyncId, type, triggerAsyncId, resource) { if (nativeProviders.has(type)) return; typeMemory.set(asyncId, type); - trace(BEFORE_EVENT, traceEventCategory, + trace(kBeforeEvent, kTraceEventCategory, type, asyncId, { triggerAsyncId, @@ -40,24 +45,43 @@ exports.setup = function(traceEvents, traceEventCategory) { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(BEFORE_EVENT, traceEventCategory, `${type}_CALLBACK`, asyncId); + trace(kBeforeEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); }, after(asyncId) { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(END_EVENT, traceEventCategory, `${type}_CALLBACK`, asyncId); + trace(kEndEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); }, destroy(asyncId) { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(END_EVENT, traceEventCategory, type, asyncId); + trace(kEndEvent, kTraceEventCategory, type, asyncId); // cleanup asyncId to type map typeMemory.delete(asyncId); } - }).enable(); -}; + }); + + return { + enable() { + if (this[kEnabled]) + return; + this[kEnabled] = true; + hook.enable(); + }, + + disable() { + if (!this[kEnabled]) + return; + this[kEnabled] = false; + hook.disable(); + typeMemory.clear(); + } + }; +} + +module.exports = createHook(); diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 94bb9413772dd0..720f89c011801e 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -31,6 +31,12 @@ void RunMicrotasks(const FunctionCallbackInfo& args) { args.GetIsolate()->RunMicrotasks(); } +void SetupTraceCategoryState(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsFunction()); + env->set_trace_category_state_function(args[0].As()); +} + void SetupNextTick(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); @@ -117,6 +123,7 @@ void SetupPromises(const FunctionCallbackInfo& args) { // completes so that it can be gc'd as soon as possible. void SetupBootstrapObject(Environment* env, Local bootstrapper) { + BOOTSTRAP_METHOD(_setupTraceCategoryState, SetupTraceCategoryState); BOOTSTRAP_METHOD(_setupProcessObject, SetupProcessObject); BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick); BOOTSTRAP_METHOD(_setupPromises, SetupPromises); diff --git a/src/env-inl.h b/src/env-inl.h index d65d7e7562f286..3bca20c81ce4ce 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -432,6 +432,11 @@ Environment::should_abort_on_uncaught_toggle() { return should_abort_on_uncaught_toggle_; } +inline AliasedBuffer& +Environment::trace_category_state() { + return trace_category_state_; +} + Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope( Environment* env) : env_(env) { diff --git a/src/env.cc b/src/env.cc index f691803039e28d..97405fe4b1006f 100644 --- a/src/env.cc +++ b/src/env.cc @@ -31,6 +31,8 @@ using v8::TryCatch; using v8::Value; using worker::Worker; +#define kTraceCategoryCount 1 + int const Environment::kNodeContextTag = 0x6e6f64; void* Environment::kNodeContextTagPtr = const_cast( static_cast(&Environment::kNodeContextTag)); @@ -103,6 +105,21 @@ void InitThreadLocalOnce() { CHECK_EQ(0, uv_key_create(&Environment::thread_local_env)); } +void Environment::TrackingTraceStateObserver::UpdateTraceCategoryState() { + env_->trace_category_state()[0] = + *TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( + TRACING_CATEGORY_NODE1(async_hooks)); + + Isolate* isolate = env_->isolate(); + Local cb = env_->trace_category_state_function(); + if (cb.IsEmpty()) + return; + TryCatch try_catch(isolate); + try_catch.SetVerbose(true); + cb->Call(env_->context(), v8::Undefined(isolate), + 0, nullptr).ToLocalChecked(); +} + Environment::Environment(IsolateData* isolate_data, Local context, tracing::AgentWriterHandle* tracing_agent_writer) @@ -118,6 +135,7 @@ Environment::Environment(IsolateData* isolate_data, emit_env_nonstring_warning_(true), makecallback_cntr_(0), should_abort_on_uncaught_toggle_(isolate_, 1), + trace_category_state_(isolate_, kTraceCategoryCount), #if HAVE_INSPECTOR inspector_agent_(new inspector::Agent(this)), #endif @@ -132,6 +150,14 @@ Environment::Environment(IsolateData* isolate_data, AssignToContext(context, ContextInfo("")); + if (tracing_agent_writer_ != nullptr) { + trace_state_observer_.reset(new TrackingTraceStateObserver(this)); + v8::TracingController* tracing_controller = + tracing_agent_writer_->GetTracingController(); + if (tracing_controller != nullptr) + tracing_controller->AddTraceStateObserver(trace_state_observer_.get()); + } + destroy_async_id_list_.reserve(512); performance_state_.reset(new performance::performance_state(isolate())); performance_state_->Mark( @@ -173,6 +199,13 @@ Environment::~Environment() { context()->SetAlignedPointerInEmbedderData( ContextEmbedderIndex::kEnvironment, nullptr); + if (tracing_agent_writer_ != nullptr) { + v8::TracingController* tracing_controller = + tracing_agent_writer_->GetTracingController(); + if (tracing_controller != nullptr) + tracing_controller->RemoveTraceStateObserver(trace_state_observer_.get()); + } + delete[] heap_statistics_buffer_; delete[] heap_space_statistics_buffer_; delete[] http_parser_buffer_; diff --git a/src/env.h b/src/env.h index 7a4804cdd45481..28aa936c246d25 100644 --- a/src/env.h +++ b/src/env.h @@ -344,6 +344,7 @@ struct PackageConfig { V(tick_callback_function, v8::Function) \ V(timers_callback_function, v8::Function) \ V(tls_wrap_constructor_function, v8::Function) \ + V(trace_category_state_function, v8::Function) \ V(tty_constructor_template, v8::FunctionTemplate) \ V(udp_constructor_function, v8::Function) \ V(url_constructor_function, v8::Function) \ @@ -649,6 +650,8 @@ class Environment { inline AliasedBuffer& should_abort_on_uncaught_toggle(); + inline AliasedBuffer& trace_category_state(); + // The necessary API for async_hooks. inline double new_async_id(); inline double execution_async_id(); @@ -830,6 +833,25 @@ class Environment { // This needs to be available for the JS-land setImmediate(). void ToggleImmediateRef(bool ref); + class TrackingTraceStateObserver : + public v8::TracingController::TraceStateObserver { + public: + explicit TrackingTraceStateObserver(Environment* env) : env_(env) {} + + void OnTraceEnabled() override { + UpdateTraceCategoryState(); + } + + void OnTraceDisabled() override { + UpdateTraceCategoryState(); + } + + private: + void UpdateTraceCategoryState(); + + Environment* env_; + }; + class ShouldNotAbortOnUncaughtScope { public: explicit inline ShouldNotAbortOnUncaughtScope(Environment* env); @@ -889,6 +911,10 @@ class Environment { AliasedBuffer should_abort_on_uncaught_toggle_; int should_not_abort_scope_counter_ = 0; + // Attached to a Uint8Array that tracks the state of trace category + AliasedBuffer trace_category_state_; + std::unique_ptr trace_state_observer_; + std::unique_ptr performance_state_; std::unordered_map performance_marks_; diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 219e142a319556..6e70cfce7bed2e 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -127,6 +127,10 @@ void Initialize(Local target, .FromJust(); target->Set(context, trace, binding->Get(context, trace).ToLocalChecked()).FromJust(); + + target->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "traceCategoryState"), + env->trace_category_state().GetJSArray()).FromJust(); } } // namespace node diff --git a/src/tracing/agent.h b/src/tracing/agent.h index 045aaef85e8ea6..e79480a0369ff7 100644 --- a/src/tracing/agent.h +++ b/src/tracing/agent.h @@ -51,6 +51,8 @@ class AgentWriterHandle { inline Agent* agent() { return agent_; } + inline v8::TracingController* GetTracingController(); + private: inline AgentWriterHandle(Agent* agent, int id) : agent_(agent), id_(id) {} @@ -155,6 +157,10 @@ void AgentWriterHandle::Disable(const std::set& categories) { if (agent_ != nullptr) agent_->Disable(id_, categories); } +inline v8::TracingController* AgentWriterHandle::GetTracingController() { + return agent_ != nullptr ? agent_->GetTracingController() : nullptr; +} + } // namespace tracing } // namespace node