diff --git a/Makefile b/Makefile index 985ed3dd84834d..bc9dd7a144db5e 100644 --- a/Makefile +++ b/Makefile @@ -1572,7 +1572,7 @@ LINT_CPP_EXCLUDE ?= LINT_CPP_EXCLUDE += src/node_root_certs.h LINT_CPP_EXCLUDE += $(LINT_CPP_ADDON_DOC_FILES) # These files were copied more or less verbatim from V8. -LINT_CPP_EXCLUDE += src/tracing/trace_event.h src/tracing/trace_event_common.h +LINT_CPP_EXCLUDE += src/tracing/trace_event_legacy.h src/tracing/trace_event_legacy_inl.h # deps/ncrypto is included in this list, as it is maintained in # this repository, and should be linted. Eventually it should move diff --git a/common.gypi b/common.gypi index 8cf118455ec827..c08208e9894837 100644 --- a/common.gypi +++ b/common.gypi @@ -87,7 +87,7 @@ 'v8_enable_external_code_space%': 0, 'v8_enable_sandbox%': 0, 'v8_enable_v8_checks%': 0, - 'v8_use_perfetto': 0, + 'v8_use_perfetto%': 0, 'tsan%': 0, ##### end V8 defaults ##### diff --git a/configure.py b/configure.py index 04ffff0db81982..def52c6fb22f73 100755 --- a/configure.py +++ b/configure.py @@ -1123,6 +1123,12 @@ default=None, help='disable the V8 inspector protocol') +parser.add_argument('--with-perfetto', + action='store_true', + dest='with_perfetto', + default=None, + help='enable perfetto support') + parser.add_argument('--shared', action='store_true', dest='shared', @@ -2223,6 +2229,7 @@ def configure_v8(o, configs): options.v8_disable_temporal_support = True o['variables']['v8_enable_temporal_support'] = 0 if options.v8_disable_temporal_support else 1 o['variables']['v8_trace_maps'] = 1 if options.trace_maps else 0 + o['variables']['v8_use_perfetto'] = 1 if options.with_perfetto else 0 o['variables']['node_use_v8_platform'] = b(not options.without_v8_platform) o['variables']['node_use_bundled_v8'] = b(not options.without_bundled_v8) o['variables']['force_dynamic_crt'] = 1 if options.shared else 0 diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index 0b71abc98f700f..9d653793f133db 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -35,7 +35,7 @@ const { SymbolToStringTag, } = primordials; -const { trace } = internalBinding('trace_events'); +const { trace, nodeTraceEventCategory, kTraceCount } = require('internal/trace_events'); const { codes: { ERR_CONSOLE_WRITABLE_STREAM, @@ -59,9 +59,6 @@ const { const { isTypedArray, isSet, isMap, isSetIterator, isMapIterator, } = require('internal/util/types'); -const { - CHAR_UPPERCASE_C: kTraceCount, -} = require('internal/constants'); const kCounts = Symbol('counts'); const { time, timeLog, timeEnd, kNone } = require('internal/util/debuglog'); const { channel } = require('diagnostics_channel'); @@ -72,7 +69,7 @@ const onError = channel('console.error'); const onInfo = channel('console.info'); const onDebug = channel('console.debug'); -const kTraceConsoleCategory = 'node,node.console'; +const kTraceConsoleCategory = nodeTraceEventCategory('node.console'); const kMaxGroupIndentation = 1000; diff --git a/lib/internal/constants.js b/lib/internal/constants.js index 8d7204f6cb48f7..c635272dc6cf6a 100644 --- a/lib/internal/constants.js +++ b/lib/internal/constants.js @@ -9,7 +9,9 @@ module.exports = { CHAR_UPPERCASE_Z: 90, /* Z */ CHAR_LOWERCASE_Z: 122, /* z */ CHAR_UPPERCASE_C: 67, /* C */ + CHAR_UPPERCASE_B: 66, /* B */ CHAR_LOWERCASE_B: 98, /* b */ + CHAR_UPPERCASE_E: 69, /* E */ CHAR_LOWERCASE_E: 101, /* e */ CHAR_LOWERCASE_N: 110, /* n */ diff --git a/lib/internal/http.js b/lib/internal/http.js index 116d699f505a4f..304ef04d663826 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -9,11 +9,13 @@ const { } = primordials; const { setUnrefTimeout } = require('internal/timers'); -const { getCategoryEnabledBuffer, trace } = internalBinding('trace_events'); const { - CHAR_LOWERCASE_B, - CHAR_LOWERCASE_E, -} = require('internal/constants'); + getCategoryEnabledBuffer, + trace, + nodeTraceEventCategory, + kAsyncBegin, + kAsyncEnd, +} = require('internal/trace_events'); const { URL } = require('internal/url'); const { Buffer } = require('buffer'); @@ -48,14 +50,14 @@ function isTraceHTTPEnabled() { return httpEnabled[0] > 0; } -const traceEventCategory = 'node,node.http'; +const traceEventCategory = nodeTraceEventCategory('node.http'); function traceBegin(...args) { - trace(CHAR_LOWERCASE_B, traceEventCategory, ...args); + trace(kAsyncBegin, traceEventCategory, ...args); } function traceEnd(...args) { - trace(CHAR_LOWERCASE_E, traceEventCategory, ...args); + trace(kAsyncEnd, traceEventCategory, ...args); } function ipToInt(ip) { diff --git a/lib/internal/trace_events.js b/lib/internal/trace_events.js new file mode 100644 index 00000000000000..d240f4ad589e74 --- /dev/null +++ b/lib/internal/trace_events.js @@ -0,0 +1,56 @@ +'use strict'; + +const { getCategoryEnabledBuffer, trace, usePerfetto } = internalBinding('trace_events'); +const { + CHAR_UPPERCASE_B, + CHAR_LOWERCASE_B, + CHAR_UPPERCASE_C, + CHAR_LOWERCASE_E, + CHAR_UPPERCASE_E, + CHAR_LOWERCASE_N, +} = require('internal/constants'); + +let nodeTraceEventCategory; +if (usePerfetto) { + nodeTraceEventCategory = (category) => `${category}`; +} else { + nodeTraceEventCategory = (category) => `node,${category}`; +} + +// The async events describe the execution of a single asynchronous operation, and are +// used to measure the time spent in a single asynchronous operation. +// Async events may overlap with each other. Different events do not have +// to be nested, or FILO (first in last out). +// TODO(legendecas): V8 `trace` API does not support async marks in perfetto yet. +const kAsyncBegin = usePerfetto ? CHAR_UPPERCASE_B : CHAR_LOWERCASE_B; +const kAsyncEnd = usePerfetto ? CHAR_UPPERCASE_E : CHAR_LOWERCASE_E; + +// The sync events describe the execution of a single thread, and are +// used to measure the time spent in a function. +// Sync events must be nested, and are FILO (first in last out), in a stack +// manner. +const kSyncBegin = CHAR_UPPERCASE_B; +const kSyncEnd = CHAR_UPPERCASE_E; + +// Counter events track a named numeric value as it changes over time. Each +// event records the value at a point in time, and the trace viewer renders the +// series as a graph. +// TODO(legendecas): V8 `trace` API does not support count marks in perfetto yet. +const kTraceCount = usePerfetto ? CHAR_LOWERCASE_N : CHAR_UPPERCASE_C; + +// Instant events mark a single moment in time. They have no duration and do +// not need to be paired or nested. +const kTraceInstant = CHAR_LOWERCASE_N; + +module.exports = { + usePerfetto, + getCategoryEnabledBuffer, + trace, + nodeTraceEventCategory, + kAsyncBegin, + kAsyncEnd, + kSyncBegin, + kSyncEnd, + kTraceCount, + kTraceInstant, +}; diff --git a/lib/internal/trace_events_async_hooks.js b/lib/internal/trace_events_async_hooks.js index a9f517ffc9e4ee..86de9e7964743e 100644 --- a/lib/internal/trace_events_async_hooks.js +++ b/lib/internal/trace_events_async_hooks.js @@ -7,20 +7,18 @@ const { Symbol, } = primordials; -const { trace } = internalBinding('trace_events'); +const { + trace, + nodeTraceEventCategory, + kAsyncBegin, + kAsyncEnd, + kSyncBegin, + kSyncEnd, +} = require('internal/trace_events'); const async_wrap = internalBinding('async_wrap'); const async_hooks = require('async_hooks'); -const { - CHAR_LOWERCASE_B, - CHAR_LOWERCASE_E, -} = require('internal/constants'); -// 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 = CHAR_LOWERCASE_B; -const kEndEvent = CHAR_LOWERCASE_E; -const kTraceEventCategory = 'node,node.async_hooks'; +const kTraceEventCategory = nodeTraceEventCategory('node.async_hooks'); const kEnabled = Symbol('enabled'); @@ -45,7 +43,7 @@ function createHook() { if (nativeProviders.has(type)) return; typeMemory.set(asyncId, type); - trace(kBeforeEvent, kTraceEventCategory, + trace(kAsyncBegin, kTraceEventCategory, type, asyncId, { triggerAsyncId, @@ -57,21 +55,21 @@ function createHook() { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(kBeforeEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); + trace(kSyncBegin, kTraceEventCategory, `${type}_CALLBACK`, asyncId); }, after(asyncId) { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(kEndEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); + trace(kSyncEnd, kTraceEventCategory, `${type}_CALLBACK`, asyncId); }, destroy(asyncId) { const type = typeMemory.get(asyncId); if (type === undefined) return; - trace(kEndEvent, kTraceEventCategory, type, asyncId); + trace(kAsyncEnd, kTraceEventCategory, type, asyncId); // Cleanup asyncId to type map typeMemory.delete(asyncId); diff --git a/lib/internal/util/debuglog.js b/lib/internal/util/debuglog.js index 06a4f8a2398555..1cb3ba4df520e7 100644 --- a/lib/internal/util/debuglog.js +++ b/lib/internal/util/debuglog.js @@ -14,13 +14,15 @@ const { StringPrototypeToLowerCase, StringPrototypeToUpperCase, } = primordials; -const { - CHAR_LOWERCASE_B: kTraceBegin, - CHAR_LOWERCASE_E: kTraceEnd, - CHAR_LOWERCASE_N: kTraceInstant, -} = require('internal/constants'); const { inspect, format, formatWithOptions } = require('internal/util/inspect'); -const { getCategoryEnabledBuffer, trace } = internalBinding('trace_events'); +const { + getCategoryEnabledBuffer, + trace, + nodeTraceEventCategory, + kAsyncBegin, + kAsyncEnd, + kTraceInstant, +} = require('internal/trace_events'); // `debugImpls` and `testEnabled` are deliberately not initialized so any call // to `debuglog()` before `initializeDebugEnv()` is called will throw. @@ -246,7 +248,7 @@ function time(timesStore, traceCategory, implementation, timerFlags, logLabel = if ((timerFlags & kSkipTrace) === 0) { traceLabel = safeTraceLabel(traceLabel); - trace(kTraceBegin, traceCategory, traceLabel, 0); + trace(kAsyncBegin, traceCategory, traceLabel, 0); } timesStore.set(logLabel, process.hrtime()); @@ -286,7 +288,7 @@ function timeEnd( if ((timerFlags & kSkipTrace) === 0) { traceLabel = safeTraceLabel(traceLabel); - trace(kTraceEnd, traceCategory, traceLabel, 0); + trace(kAsyncEnd, traceCategory, traceLabel, 0); } timesStore.delete(logLabel); @@ -385,7 +387,7 @@ function debugWithTimer(set, cb) { ); } - const traceCategory = `node,node.${StringPrototypeToLowerCase(set)}`; + const traceCategory = nodeTraceEventCategory(`node.${StringPrototypeToLowerCase(set)}`); let traceCategoryBuffer; let debugLogCategoryEnabled = false; let timerFlags = kNone; diff --git a/node.gyp b/node.gyp index 368b621b1239b0..e88bac122b93d4 100644 --- a/node.gyp +++ b/node.gyp @@ -200,10 +200,7 @@ 'src/timers.cc', 'src/timer_wrap.cc', 'src/tracing/agent.cc', - 'src/tracing/agent_legacy.cc', - 'src/tracing/node_trace_buffer.cc', - 'src/tracing/node_trace_writer.cc', - 'src/tracing/trace_event.cc', + 'src/tracing/trace_event_helper.cc', 'src/tracing/traced_value.cc', 'src/tty_wrap.cc', 'src/udp_wrap.cc', @@ -339,11 +336,8 @@ 'src/tcp_wrap.h', 'src/timers.h', 'src/tracing/agent.h', - 'src/tracing/agent_legacy.h', - 'src/tracing/node_trace_buffer.h', - 'src/tracing/node_trace_writer.h', + 'src/tracing/trace_event_helper.h', 'src/tracing/trace_event.h', - 'src/tracing/trace_event_common.h', 'src/tracing/traced_value.h', 'src/timer_wrap.h', 'src/timer_wrap-inl.h', @@ -460,6 +454,22 @@ 'src/node_crypto.cc', 'src/node_crypto.h', ], + 'node_tracing_perfetto_sources': [ + 'src/tracing/agent_perfetto.cc', + 'src/tracing/agent_perfetto.h', + 'src/tracing/trace_event_perfetto.cc', + 'src/tracing/trace_event_perfetto.h', + ], + 'node_tracing_legacy_sources': [ + 'src/tracing/agent_legacy.cc', + 'src/tracing/agent_legacy.h', + 'src/tracing/node_trace_buffer.cc', + 'src/tracing/node_trace_buffer.h', + 'src/tracing/node_trace_writer.cc', + 'src/tracing/node_trace_writer.h', + 'src/tracing/trace_event_legacy_inl.h', + 'src/tracing/trace_event_legacy.h', + ], 'node_cctest_openssl_sources': [ 'test/cctest/test_crypto_clienthello.cc', 'test/cctest/test_node_crypto.cc', @@ -963,6 +973,18 @@ }], ], }], + [ 'v8_use_perfetto==1', { + 'sources': [ + '<@(node_tracing_perfetto_sources)', + ], + 'dependencies': [ + 'deps/perfetto/perfetto.gyp:perfetto_sdk', + ], + }, { + 'sources': [ + '<@(node_tracing_legacy_sources)', + ], + }], [ 'v8_enable_inspector==1', { 'includes' : [ 'src/inspector/node_inspector.gypi' ], }, { @@ -1462,6 +1484,11 @@ }, { 'sources!': [ '<@(node_cctest_quic_sources)' ], }], + [ 'v8_use_perfetto==1', { + 'dependencies': [ + 'deps/perfetto/perfetto.gyp:perfetto_sdk', + ], + }], ['v8_enable_inspector==1', { 'defines': [ 'HAVE_INSPECTOR=1', @@ -1783,6 +1810,11 @@ 'NODE_USE_NODE_CODE_CACHE=1', ], }], + [ 'v8_use_perfetto==1', { + 'dependencies': [ + 'deps/perfetto/perfetto.gyp:perfetto_sdk', + ], + }], ['v8_enable_inspector==1', { 'defines': [ 'HAVE_INSPECTOR=1', diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index a493f59465d207..88e8a2f401cac7 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -24,8 +24,6 @@ 'src/inspector/protocol_helper.h', 'src/inspector/runtime_agent.cc', 'src/inspector/runtime_agent.h', - 'src/inspector/tracing_agent.cc', - 'src/inspector/tracing_agent.h', 'src/inspector/worker_agent.cc', 'src/inspector/worker_agent.h', 'src/inspector/network_inspector.cc', @@ -51,6 +49,10 @@ 'src/inspector/notification_emitter.h', 'src/inspector/notification_emitter.cc', ], + 'node_inspector_without_perfetto_sources': [ + 'src/inspector/tracing_agent.cc', + 'src/inspector/tracing_agent.h', + ], 'node_inspector_generated_sources': [ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Forward.h', '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Protocol.cpp', @@ -185,4 +187,11 @@ ], }, ], + 'conditions': [ + ['v8_use_perfetto!=1', { + 'sources': [ + '<@(node_inspector_without_perfetto_sources)', + ], + }], + ], } diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 5e1d9149dc755a..00b4982d9b83a8 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -14,7 +14,9 @@ #include "inspector/runtime_agent.h" #include "inspector/storage_agent.h" #include "inspector/target_agent.h" +#ifndef V8_USE_PERFETTO #include "inspector/tracing_agent.h" +#endif // V8_USE_PERFETTO #include "inspector/worker_agent.h" #include "inspector/worker_inspector.h" #include "inspector_io.h" @@ -238,9 +240,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, StringView(), V8Inspector::ClientTrustLevel::kFullyTrusted); node_dispatcher_ = std::make_unique(this); +#ifndef V8_USE_PERFETTO tracing_agent_ = std::make_unique(env, main_thread_); tracing_agent_->Wire(node_dispatcher_.get()); +#endif // V8_USE_PERFETTO if (worker_manager) { worker_agent_ = std::make_unique(worker_manager); worker_agent_->Wire(node_dispatcher_.get()); @@ -274,8 +278,10 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } ~ChannelImpl() override { +#ifndef V8_USE_PERFETTO tracing_agent_->disable(); tracing_agent_.reset(); // Dispose before the dispatchers +#endif // V8_USE_PERFETTO if (worker_agent_) { worker_agent_->disable(); worker_agent_.reset(); // Dispose before the dispatchers @@ -436,7 +442,9 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } std::unique_ptr runtime_agent_; +#ifndef V8_USE_PERFETTO std::unique_ptr tracing_agent_; +#endif std::unique_ptr worker_agent_; std::shared_ptr target_agent_; std::unique_ptr network_inspector_; diff --git a/src/node_constants.cc b/src/node_constants.cc index 8abf3fd8afe483..db670789dc063a 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -1322,9 +1322,6 @@ void DefineTraceConstants(Local target) { NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_MEMORY_DUMP); NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_MARK); NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_CLOCK_SYNC); - NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_ENTER_CONTEXT); - NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_LEAVE_CONTEXT); - NODE_DEFINE_CONSTANT(target, TRACE_EVENT_PHASE_LINK_IDS); } void CreatePerContextProperties(Local target, diff --git a/src/node_dir.cc b/src/node_dir.cc index c9173d404c79a6..952161d9e2cf1c 100644 --- a/src/node_dir.cc +++ b/src/node_dir.cc @@ -63,16 +63,10 @@ static const char* get_dir_func_name_by_type(uv_fs_type req_type) { #define GET_TRACE_ENABLED \ (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \ TRACING_CATEGORY_NODE2(fs_dir, sync)) != 0) -#define FS_DIR_SYNC_TRACE_BEGIN(syscall, ...) \ - if (GET_TRACE_ENABLED) \ - TRACE_EVENT_BEGIN(TRACING_CATEGORY_NODE2(fs_dir, sync), \ - TRACE_NAME(syscall), \ - ##__VA_ARGS__); -#define FS_DIR_SYNC_TRACE_END(syscall, ...) \ - if (GET_TRACE_ENABLED) \ - TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs_dir, sync), \ - TRACE_NAME(syscall), \ - ##__VA_ARGS__); +#define FS_DIR_SYNC_TRACE(syscall) \ + if (GET_TRACE_ENABLED) { \ + TRACE_EVENT0(TRACING_CATEGORY_NODE2(fs_dir, sync), TRACE_NAME(syscall)); \ + } #define FS_DIR_ASYNC_TRACE_BEGIN0(fs_type, id) \ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(TRACING_CATEGORY_NODE2(fs_dir, async), \ @@ -138,9 +132,11 @@ void DirHandle::MemoryInfo(MemoryTracker* tracker) const { inline void DirHandle::GCClose() { if (closed_) return; uv_fs_t req; - FS_DIR_SYNC_TRACE_BEGIN(closedir); - int ret = uv_fs_closedir(nullptr, &req, dir_, nullptr); - FS_DIR_SYNC_TRACE_END(closedir); + int ret; + { + FS_DIR_SYNC_TRACE(closedir); + ret = uv_fs_closedir(nullptr, &req, dir_, nullptr); + } uv_fs_req_cleanup(&req); closing_ = false; closed_ = true; @@ -201,9 +197,8 @@ void DirHandle::Close(const FunctionCallbackInfo& args) { uv_fs_closedir, dir->dir()); } else { // close() FSReqWrapSync req_wrap_sync("closedir"); - FS_DIR_SYNC_TRACE_BEGIN(closedir); + FS_DIR_SYNC_TRACE(closedir); SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_closedir, dir->dir()); - FS_DIR_SYNC_TRACE_END(closedir); } } @@ -299,10 +294,12 @@ void DirHandle::Read(const FunctionCallbackInfo& args) { AfterDirRead, uv_fs_readdir, dir->dir()); } else { // dir.read(encoding, bufferSize) FSReqWrapSync req_wrap_sync("readdir"); - FS_DIR_SYNC_TRACE_BEGIN(readdir); - int err = - SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_readdir, dir->dir()); - FS_DIR_SYNC_TRACE_END(readdir); + int err; + { + FS_DIR_SYNC_TRACE(readdir); + err = SyncCallAndThrowOnError( + env, &req_wrap_sync, uv_fs_readdir, dir->dir()); + } if (err < 0) { return; // syscall failed, no need to continue, error is already thrown } @@ -377,10 +374,12 @@ static void OpenDir(const FunctionCallbackInfo& args) { THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); FSReqWrapSync req_wrap_sync("opendir", *path); - FS_DIR_SYNC_TRACE_BEGIN(opendir); - int result = - SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_opendir, *path); - FS_DIR_SYNC_TRACE_END(opendir); + int result; + { + FS_DIR_SYNC_TRACE(opendir); + result = + SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_opendir, *path); + } if (result < 0) { return; // syscall failed, no need to continue, error is already thrown } @@ -407,9 +406,11 @@ static void OpenDirSync(const FunctionCallbackInfo& args) { uv_fs_t req; auto make = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); - FS_DIR_SYNC_TRACE_BEGIN(opendir); - int err = uv_fs_opendir(nullptr, &req, *path, nullptr); - FS_DIR_SYNC_TRACE_END(opendir); + int err; + { + FS_DIR_SYNC_TRACE(opendir); + err = uv_fs_opendir(nullptr, &req, *path, nullptr); + } if (err < 0) { return env->ThrowUVException(err, "opendir"); } diff --git a/src/node_file.cc b/src/node_file.cc index b8b8be835980cf..78bb84b9f06c11 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -153,10 +153,16 @@ static const char* get_fs_func_name_by_type(uv_fs_type req_type) { if (GET_TRACE_ENABLED) \ TRACE_EVENT_BEGIN( \ TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__); +#ifdef V8_USE_PERFETTO +#define FS_SYNC_TRACE_END(syscall, ...) \ + if (GET_TRACE_ENABLED) \ + TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs, sync), ##__VA_ARGS__); +#else #define FS_SYNC_TRACE_END(syscall, ...) \ if (GET_TRACE_ENABLED) \ TRACE_EVENT_END( \ TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__); +#endif #define FS_ASYNC_TRACE_BEGIN0(fs_type, id) \ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(TRACING_CATEGORY_NODE2(fs, async), \ diff --git a/src/node_internals.h b/src/node_internals.h index dbe36868600682..631a8d7ccdd957 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -317,15 +317,6 @@ class ThreadPoolWork { const char* type_; }; -#define TRACING_CATEGORY_NODE "node" -#define TRACING_CATEGORY_NODE1(one) \ - TRACING_CATEGORY_NODE "," \ - TRACING_CATEGORY_NODE "." #one -#define TRACING_CATEGORY_NODE2(one, two) \ - TRACING_CATEGORY_NODE "," \ - TRACING_CATEGORY_NODE "." #one "," \ - TRACING_CATEGORY_NODE "." #one "." #two - // Functions defined in node.cc that are exposed via the bootstrapper object #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) diff --git a/src/node_options.h b/src/node_options.h index eb5aadc2347e25..0db6b19bf93cd2 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -334,7 +334,11 @@ class PerProcessOptions : public Options { std::string title; std::string trace_event_categories; +#if defined(V8_USE_PERFETTO) + std::string trace_event_file_pattern = "node_trace.${rotation}.pftrace"; +#else std::string trace_event_file_pattern = "node_trace.${rotation}.log"; +#endif int64_t v8_thread_pool_size = 4; bool zero_fill_all_buffers = false; bool debug_arraybuffer_allocations = false; diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index bb0be77cce78e6..74a2842affc5ac 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -181,6 +181,14 @@ void NodeCategorySet::Initialize(Local target, .Check(); target->Set(context, trace, binding->Get(context, trace).ToLocalChecked()).Check(); + + Local use_perfetto = + FIXED_ONE_BYTE_STRING(env->isolate(), "usePerfetto"); +#if defined(V8_USE_PERFETTO) + target->Set(context, use_perfetto, v8::True(isolate)).Check(); +#else + target->Set(context, use_perfetto, v8::False(isolate)).Check(); +#endif } void NodeCategorySet::RegisterExternalReferences( diff --git a/src/tracing/agent.cc b/src/tracing/agent.cc index 69b956e9bc9d68..2ffc49f31e6ad9 100644 --- a/src/tracing/agent.cc +++ b/src/tracing/agent.cc @@ -1,6 +1,12 @@ #include "tracing/agent.h" + +#ifdef V8_USE_PERFETTO +#include "tracing/agent_perfetto.h" +#else #include "tracing/agent_legacy.h" -#include "tracing/trace_event.h" +#endif + +#include "tracing/trace_event_helper.h" namespace node { namespace tracing { @@ -23,7 +29,11 @@ void Agent::Deleter::operator()(Agent* agent) { std::unique_ptr Agent::CreateDefault() { CHECK_NULL(g_agent); +#ifdef V8_USE_PERFETTO + auto agent = new PerfettoTracingAgent(); +#else auto agent = new LegacyTracingAgent(); +#endif g_agent = agent; TraceEventHelper::SetTracingController(agent->GetTracingController()); diff --git a/src/tracing/agent.h b/src/tracing/agent.h index 78203154c099a5..60ed6ebf5a8d29 100644 --- a/src/tracing/agent.h +++ b/src/tracing/agent.h @@ -43,9 +43,9 @@ class Agent { virtual AgentWriterHandle* GetDefaultWriterHandle() = 0; virtual void AddTraceStateObserver( - v8::TracingController::TraceStateObserver* observer) = 0; + v8::TracingController::TraceStateObserver* observer) {} virtual void RemoveTraceStateObserver( - v8::TracingController::TraceStateObserver* observer) = 0; + v8::TracingController::TraceStateObserver* observer) {} struct Deleter { void operator()(Agent* agent); @@ -88,6 +88,7 @@ class AgentWriterHandle { friend class Agent; friend class LegacyTracingAgent; + friend class PerfettoTracingAgent; }; void AgentWriterHandle::reset() { diff --git a/src/tracing/agent_legacy.h b/src/tracing/agent_legacy.h index 4b2a728dc28a53..cd992d1ee39493 100644 --- a/src/tracing/agent_legacy.h +++ b/src/tracing/agent_legacy.h @@ -6,6 +6,10 @@ // This is an implementation of the legacy V8 tracing agent // defined in `libplatform/v8-tracing.h`. +#ifdef V8_USE_PERFETTO +#error Perfetto is enabled. +#endif + #include "libplatform/v8-tracing.h" #include "node_mutex.h" #include "tracing/agent.h" diff --git a/src/tracing/agent_perfetto.cc b/src/tracing/agent_perfetto.cc new file mode 100644 index 00000000000000..39cf55e00f1639 --- /dev/null +++ b/src/tracing/agent_perfetto.cc @@ -0,0 +1,440 @@ +#include "tracing/agent_perfetto.h" + +#include +#include +#include "debug_utils-inl.h" +#include "env-inl.h" +#include "node_options.h" +#include "trace_event.h" + +#include "trace_event_perfetto.h" + +namespace node { +namespace tracing { +namespace { + +// Perfetto's file writer drains buffers on a fixed period (default 5s) instead +// of continuously; mirror that cadence here. +constexpr uint64_t kReadPeriodMs = 5000; + +// Rotate to a new file once the current one reaches this size, so no single +// trace file grows without bound. +constexpr uint64_t kMaxFileSizeBytes = 64 * 1024 * 1024; // 64 MiB + +void replace_substring(std::string* target, + const std::string& search, + const std::string& insert) { + size_t pos = target->find(search); + for (; pos != std::string::npos; pos = target->find(search, pos)) { + target->replace(pos, search.size(), insert); + pos += insert.size(); + } +} + +std::set flatten( + const std::unordered_map>& map) { + std::set result; + for (const auto& id_value : map) + result.insert(id_value.second.begin(), id_value.second.end()); + return result; +} + +} // namespace + +// Writes trace chunks to a file, rotating by size. It deliberately uses the +// synchronous uv_fs_* APIs, mirroring Perfetto's internal file writer which +// does blocking writev() on its own service thread. +// +// Synchronous writes are the right fit here, not just simpler: +// - These writes run on the dedicated tracing loop thread, so blocking on +// disk stalls neither the application's main thread nor Perfetto's internal +// thread. +// - The buffer is drained periodically (see kReadPeriodMs) and each chunk is +// already contiguous, so there is little I/O to overlap; the thread-pool +// offload of async uv_fs_* would buy little. +// - It keeps buffer lifetime, write ordering, flushing, rotation, and +// shutdown trivial. Async writes would require keeping each buffer alive +// until its callback, serializing to one in-flight write per fd, and a +// condition-variable wait for blocking Flush() (see NodeTraceWriter). +class SimpleWriter : public TraceWriter { + public: + explicit SimpleWriter(std::string log_file_pattern) + : log_file_pattern_(std::move(log_file_pattern)) {} + + ~SimpleWriter() override { + if (fd_ < 0) return; + uv_fs_t req; + uv_fs_close(loop_, &req, fd_, nullptr); + uv_fs_req_cleanup(&req); + } + + void InitializeOnThread(uv_loop_t* loop) override { + loop_ = loop; + OpenNewFileForStreaming(); + } + + // Synchronously writes a chunk, rotating to a new file once it grows too + // large. See the class comment for why the write is synchronous. + void AppendTraceChunk(std::vector chunk) override { + if (fd_ < 0) return; + uv_buf_t buf = + uv_buf_init(chunk.data(), static_cast(chunk.size())); + uv_fs_t req; + int written = uv_fs_write(loop_, &req, fd_, &buf, 1, -1, nullptr); + uv_fs_req_cleanup(&req); + if (written < 0) return; + + bytes_written_ += written; + // Each chunk holds only whole trace packets, so rotating on a chunk + // boundary leaves every file a self-contained, valid trace. + if (bytes_written_ >= kMaxFileSizeBytes) OpenNewFileForStreaming(); + } + + void Flush(bool blocking) override { + if (fd_ < 0) return; + uv_fs_t req; + uv_fs_fsync(loop_, &req, fd_, nullptr); + uv_fs_req_cleanup(&req); + } + + private: + // Opens the next rotation file, evaluating a JS-style template that accepts + // ${pid} and ${rotation}, mirroring NodeTraceWriter::OpenNewFileForStreaming. + void OpenNewFileForStreaming() { + ++file_num_; + uv_fs_t req; + + std::string filepath(log_file_pattern_); + replace_substring(&filepath, "${pid}", std::to_string(uv_os_getpid())); + replace_substring(&filepath, "${rotation}", std::to_string(file_num_)); + + if (fd_ >= 0) { + uv_fs_close(loop_, &req, fd_, nullptr); + uv_fs_req_cleanup(&req); + } + + fd_ = uv_fs_open(loop_, + &req, + filepath.c_str(), + UV_FS_O_CREAT | UV_FS_O_WRONLY | UV_FS_O_TRUNC, + 0644, + nullptr); + uv_fs_req_cleanup(&req); + if (fd_ < 0) { + fprintf(stderr, + "Could not open trace file %s: %s\n", + filepath.c_str(), + uv_strerror(fd_)); + fd_ = -1; + } + bytes_written_ = 0; + } + + std::string log_file_pattern_; + uv_loop_t* loop_ = nullptr; + uv_file fd_ = -1; + int file_num_ = 0; + uint64_t bytes_written_ = 0; +}; + +void PerfettoSessionReader::Deleter::operator()( + PerfettoSessionReader* ptr) const noexcept { + ptr->tracing_session_->FlushBlocking(); + ptr->Read(); + ptr->tracing_session_->Stop(); +} + +PerfettoSessionReader::PerfettoSessionReader( + perfetto::TraceConfig config, std::unique_ptr writer) + : writer_(std::move(writer)) { + tracing_session_ = + perfetto::Tracing::NewTrace(perfetto::BackendType::kUnspecifiedBackend); + tracing_session_->Setup(config); + tracing_session_->SetOnStopCallback( + std::bind(&PerfettoSessionReader::SessionStopCallback, this)); + tracing_session_->StartBlocking(); +} + +PerfettoSessionReader::~PerfettoSessionReader() {} + +void PerfettoSessionReader::ChangeTraceConfig(perfetto::TraceConfig config) { + tracing_session_->ChangeTraceConfig(config); +} + +void PerfettoSessionReader::InitializeOnThread(uv_loop_t* loop) { + tracing_loop_ = loop; + CHECK_EQ(uv_async_init(tracing_loop_, &read_async_, OnReadAsync), 0); + read_async_.data = this; + CHECK_EQ(uv_timer_init(tracing_loop_, &read_timer_), 0); + read_timer_.data = this; + writer_->InitializeOnThread(loop); + + // Drain the trace buffer periodically instead of continuously, mirroring + // Perfetto's file writer which reschedules ReadBuffersIntoFile() every + // write_period_ms. + CHECK_EQ( + uv_timer_start(&read_timer_, OnReadTimer, kReadPeriodMs, kReadPeriodMs), + 0); +} + +void PerfettoSessionReader::Read() { + if (stop_requested_) return; + // Skip if a previous periodic read is still draining the buffer. + bool expected = false; + if (!read_in_progress_.compare_exchange_strong(expected, true)) return; + tracing_session_->ReadTrace(std::bind( + &PerfettoSessionReader::ReadTraceCallback, this, std::placeholders::_1)); +} + +void PerfettoSessionReader::ReadTraceCallback( + perfetto::TracingSession::ReadTraceCallbackArgs args) { + // On Perfetto internal thread. + { + Mutex::ScopedLock lock(chunks_mutex_); + if (args.size > 0) + pending_chunks_.emplace_back(args.data, args.data + args.size); + } + // A single ReadTrace() cycle can yield multiple callbacks; the last one has + // has_more == false, which clears read_in_progress_ so the next timer tick + // can start a new read. + read_in_progress_ = args.has_more; + uv_async_send(&read_async_); +} + +void PerfettoSessionReader::SessionStopCallback() { + stop_requested_ = true; + uv_async_send(&read_async_); +} + +// static +void PerfettoSessionReader::OnReadAsync(uv_async_t* async) { + PerfettoSessionReader* reader = + static_cast(async->data); + std::list> chunks_to_write; + { + Mutex::ScopedLock lock(reader->chunks_mutex_); + std::swap(chunks_to_write, reader->pending_chunks_); + } + + while (!chunks_to_write.empty()) { + std::vector& chunk = chunks_to_write.front(); + reader->writer_->AppendTraceChunk(std::move(chunk)); + chunks_to_write.pop_front(); + } + + if (reader->stop_requested_ && reader->handles_pending_close_ == 0) { + reader->writer_->Flush(true); + + reader->handles_pending_close_ = 2; + uv_timer_stop(&reader->read_timer_); + uv_close(reinterpret_cast(&reader->read_async_), + OnHandleClose); + uv_close(reinterpret_cast(&reader->read_timer_), + OnHandleClose); + } +} + +// static +void PerfettoSessionReader::OnReadTimer(uv_timer_t* timer) { + PerfettoSessionReader* reader = + static_cast(timer->data); + reader->Read(); +} + +// static +void PerfettoSessionReader::OnHandleClose(uv_handle_t* handle) { + PerfettoSessionReader* reader = + static_cast(handle->data); + if (--reader->handles_pending_close_ == 0) delete reader; +} + +PerfettoTracingAgent::PerfettoTracingAgent() { + CHECK_EQ(uv_loop_init(&tracing_loop_), 0); + CHECK_EQ(uv_async_init(&tracing_loop_, + &initialize_writer_async_, + [](uv_async_t* async) { + PerfettoTracingAgent* agent = ContainerOf( + &PerfettoTracingAgent::initialize_writer_async_, + async); + agent->InitializeWritersOnThread(); + }), + 0); + + // Set up the in-process backend that the tracing controller will connect + // to. + perfetto::TracingInitArgs init_args; + init_args.backends = perfetto::BackendType::kInProcessBackend; + perfetto::Tracing::Initialize(init_args); + + node::TrackEvent::Register(); +} + +void PerfettoTracingAgent::InitializeWritersOnThread() { + Mutex::ScopedLock lock(initialize_writer_mutex_); + while (!to_be_initialized_.empty()) { + auto head = *to_be_initialized_.begin(); + head->InitializeOnThread(&tracing_loop_); + to_be_initialized_.erase(head); + } + initialize_writer_condvar_.Broadcast(lock); + + uv_unref(reinterpret_cast(&initialize_writer_async_)); +} + +PerfettoTracingAgent::~PerfettoTracingAgent() { + StopTracing(); + + categories_.clear(); + writers_.clear(); + + uv_close(reinterpret_cast(&initialize_writer_async_), nullptr); + uv_run(&tracing_loop_, UV_RUN_ONCE); + CheckedUvLoopClose(&tracing_loop_); +} + +void PerfettoTracingAgent::Start() { + if (started_) return; + + // This thread should be created *after* async handles are created + // (within NodeTraceWriter and NodeTraceBuffer constructors). + // Otherwise the thread could shut down prematurely. + CHECK_EQ(0, + uv_thread_create( + &thread_, + [](void* arg) { + uv_thread_setname("TraceEventWorker"); + PerfettoTracingAgent* agent = + static_cast(arg); + uv_run(&agent->tracing_loop_, UV_RUN_DEFAULT); + }, + this)); + + started_ = true; +} + +AgentWriterHandle* PerfettoTracingAgent::GetDefaultWriterHandle() { + return tracing_file_writer_.has_value() ? &tracing_file_writer_.value() + : nullptr; +} + +AgentWriterHandle PerfettoTracingAgent::AddClient( + const std::set& categories, + std::unique_ptr writer, + enum UseDefaultCategoryMode mode) { + Start(); + + const std::set* use_categories = &categories; + + std::set categories_with_default; + if (mode == kUseDefaultCategories) { + categories_with_default.insert(categories.begin(), categories.end()); + categories_with_default.insert(categories_[kDefaultHandleId].begin(), + categories_[kDefaultHandleId].end()); + use_categories = &categories_with_default; + } + + int id = next_writer_id_++; + PerfettoSessionReader::Ptr reader = PerfettoSessionReader::Create( + CreateTraceConfig({use_categories->begin(), use_categories->end()}), + std::move(writer)); + + auto* raw = reader.get(); + writers_[id] = std::move(reader); + categories_[id] = {use_categories->begin(), use_categories->end()}; + + { + Mutex::ScopedLock lock(initialize_writer_mutex_); + to_be_initialized_.insert(raw); + uv_async_send(&initialize_writer_async_); + while (to_be_initialized_.count(raw) > 0) + initialize_writer_condvar_.Wait(lock); + } + + return AgentWriterHandle(this, id); +} + +void PerfettoTracingAgent::StartTracing(const std::string& categories) { + if (tracing_file_writer_.has_value()) return; + + using std::operator""sv; + auto parts = std::views::split(categories, ","sv); + + std::set categories_set; + for (const auto& s : parts) { + categories_set.emplace(std::string(s.data(), s.size())); + } + + tracing_file_writer_ = + AddClient(categories_set, + std::make_unique( + per_process::cli_options->trace_event_file_pattern), + kUseDefaultCategories); +} + +void PerfettoTracingAgent::StopTracing() { + if (!started_) return; + // Perform final Flush on TraceBuffer. We don't want the tracing controller + // to flush the buffer again on destruction of the V8::Platform. + writers_.clear(); + started_ = false; + + // Thread should finish when the tracing loop is stopped. + uv_thread_join(&thread_); +} + +void PerfettoTracingAgent::Disconnect(int client) { + if (client == kDefaultHandleId) return; + { + Mutex::ScopedLock lock(initialize_writer_mutex_); + to_be_initialized_.erase(writers_[client].get()); + } + + writers_.erase(client); + categories_.erase(client); +} + +void PerfettoTracingAgent::Enable(int id, + const std::set& categories) { + if (categories.empty()) return; + + categories_[id].insert(categories.begin(), categories.end()); + + writers_[id]->ChangeTraceConfig(CreateTraceConfig(categories_[id])); +} + +void PerfettoTracingAgent::Disable(int id, + const std::set& categories) { + std::multiset& writer_categories = categories_[id]; + for (const std::string& category : categories) { + auto it = writer_categories.find(category); + if (it != writer_categories.end()) writer_categories.erase(it); + } + + writers_[id]->ChangeTraceConfig(CreateTraceConfig(writer_categories)); +} + +std::string PerfettoTracingAgent::GetEnabledCategories() const { + std::string categories; + for (const std::string& category : flatten(categories_)) { + if (!categories.empty()) categories += ','; + categories += category; + } + return categories; +} + +perfetto::TraceConfig PerfettoTracingAgent::CreateTraceConfig( + std::multiset categories) const { + perfetto::TraceConfig perfetto_trace_config; + perfetto_trace_config.add_buffers()->set_size_kb(4096); + auto ds_config = perfetto_trace_config.add_data_sources()->mutable_config(); + ds_config->set_name("track_event"); + perfetto::protos::gen::TrackEventConfig te_config; + te_config.add_disabled_categories("*"); + for (const auto& category : categories) + te_config.add_enabled_categories(category); + ds_config->set_track_event_config_raw(te_config.SerializeAsString()); + return perfetto_trace_config; +} + +} // namespace tracing +} // namespace node diff --git a/src/tracing/agent_perfetto.h b/src/tracing/agent_perfetto.h new file mode 100644 index 00000000000000..f35d8a96513445 --- /dev/null +++ b/src/tracing/agent_perfetto.h @@ -0,0 +1,147 @@ +#ifndef SRC_TRACING_AGENT_PERFETTO_H_ +#define SRC_TRACING_AGENT_PERFETTO_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_mutex.h" +#include "tracing/agent.h" +#include "tracing/trace_event_perfetto.h" +#include "util.h" +#include "uv.h" +#include "v8-platform.h" + +#include +#include +#include +#include + +namespace node { +namespace tracing { + +// Consumes trace data produced by a PerfettoSessionReader. All methods run on +// the agent's dedicated background tracing thread (the loop passed to +// InitializeOnThread), never on the application's main thread or Perfetto's +// internal thread, so implementations may block (e.g. do synchronous I/O). +class TraceWriter { + public: + virtual ~TraceWriter() = default; + // 'chunk' is guaranteed to contain 1 or more full trace packets, which + // can be decoded using trace.proto. No partial or truncated packets are + // exposed. + virtual void AppendTraceChunk(std::vector chunk) = 0; + virtual void Flush(bool blocking) = 0; + virtual void InitializeOnThread(uv_loop_t* loop) {} +}; + +// Owns one Perfetto tracing session and pumps its trace data out to a +// TraceWriter using the consumer ReadTrace() API, reading on a fixed period. +// +// Threading: two threads are involved. +// - Perfetto's internal thread invokes ReadTraceCallback. It only copies the +// borrowed chunk into pending_chunks_ (under chunks_mutex_) and signals +// read_async_; it never touches the writer or the file. +// - The agent's dedicated tracing loop thread runs everything else. A +// repeating timer (read_timer_) starts a read every kReadPeriodMs, and +// OnReadAsync drains pending_chunks_ into the writer there. This is why the +// writer can safely block on synchronous I/O (see TraceWriter). +// read_in_progress_ prevents a timer tick from starting a new read while a +// previous ReadTrace() cycle is still delivering chunks. +class PerfettoSessionReader final { + public: + struct Deleter { + void operator()(PerfettoSessionReader* ptr) const noexcept; + }; + using Ptr = std::unique_ptr; + static Ptr Create(perfetto::TraceConfig config, + std::unique_ptr writer) { + return Ptr(new PerfettoSessionReader(config, std::move(writer))); + } + + void ChangeTraceConfig(perfetto::TraceConfig config); + + void InitializeOnThread(uv_loop_t* loop); + + private: + explicit PerfettoSessionReader(perfetto::TraceConfig config, + std::unique_ptr writer); + ~PerfettoSessionReader(); + void ReadTraceCallback(perfetto::TracingSession::ReadTraceCallbackArgs args); + void SessionStopCallback(); + void Read(); + + static void OnReadAsync(uv_async_t* async); + static void OnReadTimer(uv_timer_t* timer); + static void OnHandleClose(uv_handle_t* handle); + + uv_loop_t* tracing_loop_; + uv_async_t read_async_; + uv_timer_t read_timer_; + int handles_pending_close_ = 0; + std::atomic stop_requested_ = false; + std::atomic read_in_progress_ = false; + + Mutex chunks_mutex_; + std::list> pending_chunks_; + std::unique_ptr tracing_session_ = nullptr; + + std::unique_ptr writer_; +}; + +class PerfettoTracingAgent final : public Agent { + public: + enum UseDefaultCategoryMode { + kUseDefaultCategories, + kIgnoreDefaultCategories + }; + + PerfettoTracingAgent(); + ~PerfettoTracingAgent() override; + + v8::TracingController* GetTracingController() override { return nullptr; } + + AgentWriterHandle AddClient(const std::set& categories, + std::unique_ptr writer, + enum UseDefaultCategoryMode mode); + std::string GetEnabledCategories() const override; + + void StartTracing(const std::string& categories) override; + void StopTracing() override; + AgentWriterHandle* GetDefaultWriterHandle() override; + + private: + static constexpr int kDefaultHandleId = -1; + + void Disconnect(int client) override; + + void Enable(int id, const std::set& categories) override; + void Disable(int id, const std::set& categories) override; + + void InitializeWritersOnThread(); + void Start(); + + perfetto::TraceConfig CreateTraceConfig( + std::multiset categories) const; + + uv_thread_t thread_; + uv_loop_t tracing_loop_; + + bool started_ = false; + + int next_writer_id_ = 1; + std::unordered_map> categories_; + std::unordered_map writers_; + + Mutex initialize_writer_mutex_; + ConditionVariable initialize_writer_condvar_; + uv_async_t initialize_writer_async_; + std::set to_be_initialized_; + + std::optional tracing_file_writer_; +}; + +} // namespace tracing +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_TRACING_AGENT_PERFETTO_H_ diff --git a/src/tracing/trace_event.h b/src/tracing/trace_event.h index a0f8c695ea0160..3169505626c05d 100644 --- a/src/tracing/trace_event.h +++ b/src/tracing/trace_event.h @@ -1,720 +1,27 @@ -// Copyright 2015 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - #ifndef SRC_TRACING_TRACE_EVENT_H_ #define SRC_TRACING_TRACE_EVENT_H_ -#include "v8-platform.h" -#include "tracing/agent_legacy.h" -#include "trace_event_common.h" -#include - -// This header file defines implementation details of how the trace macros in -// trace_event_common.h collect and store trace events. Anything not -// implementation-specific should go in trace_macros_common.h instead of here. - - -// The pointer returned from GetCategoryGroupEnabled() points to a -// value with zero or more of the following bits. Used in this class only. -// The TRACE_EVENT macros should only use the value as a bool. -// These values must be in sync with macro values in trace_log.h in -// chromium. -enum CategoryGroupEnabledFlags { - // Category group enabled for the recording mode. - kEnabledForRecording_CategoryGroupEnabledFlags = 1 << 0, - // Category group enabled by SetEventCallbackEnabled(). - kEnabledForEventCallback_CategoryGroupEnabledFlags = 1 << 2, - // Category group enabled to export events to ETW. - kEnabledForETWExport_CategoryGroupEnabledFlags = 1 << 3, -}; - -// By default, const char* argument values are assumed to have long-lived scope -// and will not be copied. Use this macro to force a const char* to be copied. -#define TRACE_STR_COPY(str) node::tracing::TraceStringWithCopy(str) - -// By default, uint64 ID argument values are not mangled with the Process ID in -// TRACE_EVENT_ASYNC macros. Use this macro to force Process ID mangling. -#define TRACE_ID_MANGLE(id) node::tracing::TraceID::ForceMangle(id) - -// By default, pointers are mangled with the Process ID in TRACE_EVENT_ASYNC -// macros. Use this macro to prevent Process ID mangling. -#define TRACE_ID_DONT_MANGLE(id) node::tracing::TraceID::DontMangle(id) - -// By default, trace IDs are eventually converted to a single 64-bit number. Use -// this macro to add a scope string. -#define TRACE_ID_WITH_SCOPE(scope, id) \ - trace_event_internal::TraceID::WithScope(scope, id) - -#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE() \ - *INTERNAL_TRACE_EVENT_UID(category_group_enabled) & \ - (kEnabledForRecording_CategoryGroupEnabledFlags | \ - kEnabledForEventCallback_CategoryGroupEnabledFlags) - -// The following macro has no implementation, but it needs to exist since -// it gets called from scoped trace events. It cannot call UNIMPLEMENTED() -// since an empty implementation is a valid one. -#define INTERNAL_TRACE_MEMORY(category, name) - -//////////////////////////////////////////////////////////////////////////////// -// Implementation specific tracing API definitions. - -// Get a pointer to the enabled state of the given trace category. Only -// long-lived literal strings should be given as the category group. The -// returned pointer can be held permanently in a local static for example. If -// the unsigned char is non-zero, tracing is enabled. If tracing is enabled, -// TRACE_EVENT_API_ADD_TRACE_EVENT can be called. It's OK if tracing is disabled -// between the load of the tracing state and the call to -// TRACE_EVENT_API_ADD_TRACE_EVENT, because this flag only provides an early out -// for best performance when tracing is disabled. -// const uint8_t* -// TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(const char* category_group) -#define TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED \ - node::tracing::TraceEventHelper::GetCategoryGroupEnabled - -// Get the number of times traces have been recorded. This is used to implement -// the TRACE_EVENT_IS_NEW_TRACE facility. -// unsigned int TRACE_EVENT_API_GET_NUM_TRACES_RECORDED() -#define TRACE_EVENT_API_GET_NUM_TRACES_RECORDED UNIMPLEMENTED() - -// Add a trace event to the platform tracing system. -// uint64_t TRACE_EVENT_API_ADD_TRACE_EVENT( -// char phase, -// const uint8_t* category_group_enabled, -// const char* name, -// const char* scope, -// uint64_t id, -// uint64_t bind_id, -// int num_args, -// const char** arg_names, -// const uint8_t* arg_types, -// const uint64_t* arg_values, -// unsigned int flags) -#define TRACE_EVENT_API_ADD_TRACE_EVENT node::tracing::AddTraceEventImpl - -// Add a trace event to the platform tracing system. -// uint64_t TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( -// char phase, -// const uint8_t* category_group_enabled, -// const char* name, -// const char* scope, -// uint64_t id, -// uint64_t bind_id, -// int num_args, -// const char** arg_names, -// const uint8_t* arg_types, -// const uint64_t* arg_values, -// unsigned int flags, -// int64_t timestamp) -#define TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP \ - node::tracing::AddTraceEventWithTimestampImpl - -// Set the duration field of a COMPLETE trace event. -// void TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( -// const uint8_t* category_group_enabled, -// const char* name, -// uint64_t id) -#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION \ - if (auto controller = \ - node::tracing::TraceEventHelper::GetTracingController()) \ - controller->UpdateTraceEventDuration - -// Adds a metadata event to the trace log. The |AppendValueAsTraceFormat| method -// on the convertable value will be called at flush time. -// TRACE_EVENT_API_ADD_METADATA_EVENT( -// const unsigned char* category_group_enabled, -// const char* event_name, -// const char* arg_name, -// std::unique_ptr arg_value) -#define TRACE_EVENT_API_ADD_METADATA_EVENT node::tracing::AddMetadataEvent - -// Defines atomic operations used internally by the tracing system. -#define TRACE_EVENT_API_ATOMIC_WORD std::atomic -#define TRACE_EVENT_API_ATOMIC_WORD_VALUE intptr_t -#define TRACE_EVENT_API_ATOMIC_LOAD(var) (var).load() -#define TRACE_EVENT_API_ATOMIC_STORE(var, value) (var).store(value) - -//////////////////////////////////////////////////////////////////////////////// - -// Implementation detail: trace event macros create temporary variables -// to keep instrumentation overhead low. These macros give each temporary -// variable a unique name based on the line number to prevent name collisions. -#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_unique_##a##b -#define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b) -#define INTERNAL_TRACE_EVENT_UID(name_prefix) \ - INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__) - -// Implementation detail: internal macro to create static category. -// No barriers are needed, because this code is designed to operate safely -// even when the unsigned char* points to garbage data (which may be the case -// on processors without cache coherency). -// TODO(fmeawad): This implementation contradicts that we can have a different -// configuration for each isolate, -// https://code.google.com/p/v8/issues/detail?id=4563 -#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ - category_group, atomic, category_group_enabled) \ - category_group_enabled = \ - reinterpret_cast(TRACE_EVENT_API_ATOMIC_LOAD(atomic)); \ - if (!category_group_enabled) { \ - category_group_enabled = \ - TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group); \ - TRACE_EVENT_API_ATOMIC_STORE( \ - atomic, reinterpret_cast( \ - category_group_enabled)); \ - } - -#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ - static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) {0}; \ - const uint8_t* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ - category_group, INTERNAL_TRACE_EVENT_UID(atomic), \ - INTERNAL_TRACE_EVENT_UID(category_group_enabled)); - -// Implementation detail: internal macro to create static category and add -// event if the category is enabled. -#define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - node::tracing::AddTraceEvent( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - node::tracing::kGlobalScope, node::tracing::kNoId, \ - node::tracing::kNoId, flags, ##__VA_ARGS__); \ - } \ - } while (0) - -// Implementation detail: internal macro to create static category and add begin -// event if the category is enabled. Also adds the end event when the scope -// ends. -#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, ...) \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - node::tracing::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - uint64_t h = node::tracing::AddTraceEvent( \ - TRACE_EVENT_PHASE_COMPLETE, \ - INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - node::tracing::kGlobalScope, node::tracing::kNoId, \ - node::tracing::kNoId, TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__); \ - INTERNAL_TRACE_EVENT_UID(tracer) \ - .Initialize(INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - h); \ - } - -#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(category_group, name, \ - bind_id, flow_flags, ...) \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - node::tracing::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - unsigned int trace_event_flags = flow_flags; \ - node::tracing::TraceID trace_event_bind_id(bind_id, \ - &trace_event_flags); \ - uint64_t h = node::tracing::AddTraceEvent( \ - TRACE_EVENT_PHASE_COMPLETE, \ - INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - node::tracing::kGlobalScope, node::tracing::kNoId, \ - trace_event_bind_id.raw_id(), trace_event_flags, ##__VA_ARGS__); \ - INTERNAL_TRACE_EVENT_UID(tracer) \ - .Initialize(INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - h); \ - } - -// Implementation detail: internal macro to create static category and add -// event if the category is enabled. -#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category_group, name, id, \ - flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ - node::tracing::TraceID trace_event_trace_id(id, \ - &trace_event_flags); \ - node::tracing::AddTraceEvent( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \ - node::tracing::kNoId, trace_event_flags, ##__VA_ARGS__); \ - } \ - } while (0) - -// Adds a trace event with a given timestamp. -#define INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP(phase, category_group, name, \ - timestamp, flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - node::tracing::AddTraceEventWithTimestamp( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - node::tracing::kGlobalScope, node::tracing::kNoId, \ - node::tracing::kNoId, flags, timestamp, ##__VA_ARGS__); \ - } \ - } while (0) - -// Adds a trace event with a given id and timestamp. Not Implemented. -#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_AND_TIMESTAMP( \ - phase, category_group, name, id, timestamp, flags, ...) \ - UNIMPLEMENTED() - -// Adds a trace event with a given id, thread_id, and timestamp. Not -// Implemented. -#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ - phase, category_group, name, id, thread_id, timestamp, flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ - node::tracing::TraceID trace_event_trace_id(id, \ - &trace_event_flags); \ - node::tracing::AddTraceEventWithTimestamp( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \ - node::tracing::kNoId, trace_event_flags, timestamp, ##__VA_ARGS__);\ - } \ - } while (0) - -#define INTERNAL_TRACE_EVENT_METADATA_ADD(category_group, name, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - TRACE_EVENT_API_ADD_METADATA_EVENT( \ - INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ - ##__VA_ARGS__); \ - } \ - } while(0) - -// Enter and leave a context based on the current scope. -#define INTERNAL_TRACE_EVENT_SCOPED_CONTEXT(category_group, name, context) \ - struct INTERNAL_TRACE_EVENT_UID(ScopedContext) { \ - public: \ - INTERNAL_TRACE_EVENT_UID(ScopedContext)(uint64_t cid) : cid_(cid) { \ - TRACE_EVENT_ENTER_CONTEXT(category_group, name, cid_); \ - } \ - ~INTERNAL_TRACE_EVENT_UID(ScopedContext)() { \ - TRACE_EVENT_LEAVE_CONTEXT(category_group, name, cid_); \ - } \ - \ - private: \ - /* Local class friendly DISALLOW_COPY_AND_ASSIGN */ \ - INTERNAL_TRACE_EVENT_UID(ScopedContext) \ - (const INTERNAL_TRACE_EVENT_UID(ScopedContext)&) {} \ - void operator=(const INTERNAL_TRACE_EVENT_UID(ScopedContext)&) {} \ - uint64_t cid_; \ - }; \ - INTERNAL_TRACE_EVENT_UID(ScopedContext) \ - INTERNAL_TRACE_EVENT_UID(scoped_context)(context); - -namespace node { -namespace tracing { - -// Specify these values when the corresponding argument of AddTraceEvent is not -// used. -const int kZeroNumArgs = 0; -const decltype(nullptr) kGlobalScope = nullptr; -const uint64_t kNoId = 0; - -class TraceEventHelper { - public: - static v8::TracingController* GetTracingController(); - static void SetTracingController(v8::TracingController* controller); - - static inline const uint8_t* GetCategoryGroupEnabled(const char* group) { - v8::TracingController* controller = GetTracingController(); - static const uint8_t disabled = 0; - if (controller == nullptr) [[unlikely]] { - return &disabled; - } - return controller->GetCategoryGroupEnabled(group); - } -}; - -// TraceID encapsulates an ID that can either be an integer or pointer. Pointers -// are by default mangled with the Process ID so that they are unlikely to -// collide when the same pointer is used on different processes. -class TraceID { - public: - class WithScope { - public: - WithScope(const char* scope, uint64_t raw_id) - : scope_(scope), raw_id_(raw_id) {} - uint64_t raw_id() const { return raw_id_; } - const char* scope() const { return scope_; } - - private: - const char* scope_ = nullptr; - uint64_t raw_id_; - }; - - class DontMangle { - public: - explicit DontMangle(const void* raw_id) - : raw_id_(static_cast(reinterpret_cast(raw_id))) {} - explicit DontMangle(uint64_t raw_id) : raw_id_(raw_id) {} - explicit DontMangle(unsigned int raw_id) : raw_id_(raw_id) {} - explicit DontMangle(uint16_t raw_id) : raw_id_(raw_id) {} - explicit DontMangle(unsigned char raw_id) : raw_id_(raw_id) {} - explicit DontMangle(int64_t raw_id) - : raw_id_(static_cast(raw_id)) {} - explicit DontMangle(int raw_id) : raw_id_(static_cast(raw_id)) {} - explicit DontMangle(int16_t raw_id) - : raw_id_(static_cast(raw_id)) {} - explicit DontMangle(signed char raw_id) - : raw_id_(static_cast(raw_id)) {} - explicit DontMangle(WithScope scoped_id) - : scope_(scoped_id.scope()), raw_id_(scoped_id.raw_id()) {} - const char* scope() const { return scope_; } - uint64_t raw_id() const { return raw_id_; } - - private: - const char* scope_ = nullptr; - uint64_t raw_id_; - }; - - class ForceMangle { - public: - explicit ForceMangle(uint64_t raw_id) : raw_id_(raw_id) {} - explicit ForceMangle(unsigned int raw_id) : raw_id_(raw_id) {} - explicit ForceMangle(uint16_t raw_id) : raw_id_(raw_id) {} - explicit ForceMangle(unsigned char raw_id) : raw_id_(raw_id) {} - explicit ForceMangle(int64_t raw_id) - : raw_id_(static_cast(raw_id)) {} - explicit ForceMangle(int raw_id) : raw_id_(static_cast(raw_id)) {} - explicit ForceMangle(int16_t raw_id) - : raw_id_(static_cast(raw_id)) {} - explicit ForceMangle(signed char raw_id) - : raw_id_(static_cast(raw_id)) {} - uint64_t raw_id() const { return raw_id_; } - - private: - uint64_t raw_id_; - }; - - TraceID(const void* raw_id, unsigned int* flags) - : raw_id_(static_cast(reinterpret_cast(raw_id))) { - *flags |= TRACE_EVENT_FLAG_MANGLE_ID; - } - TraceID(ForceMangle raw_id, unsigned int* flags) : raw_id_(raw_id.raw_id()) { - *flags |= TRACE_EVENT_FLAG_MANGLE_ID; - } - TraceID(DontMangle maybe_scoped_id, unsigned int* flags) - : scope_(maybe_scoped_id.scope()), raw_id_(maybe_scoped_id.raw_id()) {} - TraceID(uint64_t raw_id, unsigned int* flags) : raw_id_(raw_id) { - (void)flags; - } - TraceID(unsigned int raw_id, unsigned int* flags) : raw_id_(raw_id) { - (void)flags; - } - TraceID(uint16_t raw_id, unsigned int* flags) : raw_id_(raw_id) { - (void)flags; - } - TraceID(unsigned char raw_id, unsigned int* flags) : raw_id_(raw_id) { - (void)flags; - } - TraceID(int64_t raw_id, unsigned int* flags) - : raw_id_(static_cast(raw_id)) { - (void)flags; - } - TraceID(int raw_id, unsigned int* flags) - : raw_id_(static_cast(raw_id)) { - (void)flags; - } - TraceID(int16_t raw_id, unsigned int* flags) - : raw_id_(static_cast(raw_id)) { - (void)flags; - } - TraceID(signed char raw_id, unsigned int* flags) - : raw_id_(static_cast(raw_id)) { - (void)flags; - } - TraceID(WithScope scoped_id, unsigned int* flags) - : scope_(scoped_id.scope()), raw_id_(scoped_id.raw_id()) {} - - uint64_t raw_id() const { return raw_id_; } - const char* scope() const { return scope_; } - - private: - const char* scope_ = nullptr; - uint64_t raw_id_; -}; - -// Simple union to store various types as uint64_t. -union TraceValueUnion { - bool as_bool; - uint64_t as_uint; - int64_t as_int; - double as_double; - const void* as_pointer; - const char* as_string; -}; - -// Simple container for const char* that should be copied instead of retained. -class TraceStringWithCopy { - public: - explicit TraceStringWithCopy(const char* str) : str_(str) {} - operator const char*() const { return str_; } - - private: - const char* str_; -}; - -static inline uint64_t AddTraceEventImpl( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, unsigned int flags) { - std::unique_ptr arg_convertibles[2]; - if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[0].reset(reinterpret_cast( - static_cast(arg_values[0]))); - } - if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[1].reset(reinterpret_cast( - static_cast(arg_values[1]))); - } - // DCHECK(num_args, 2); - v8::TracingController* controller = - node::tracing::TraceEventHelper::GetTracingController(); - if (controller == nullptr) return 0; - return controller->AddTraceEvent(phase, category_group_enabled, name, scope, id, - bind_id, num_args, arg_names, arg_types, - arg_values, arg_convertibles, flags); -} - -static V8_INLINE uint64_t AddTraceEventWithTimestampImpl( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, unsigned int flags, int64_t timestamp) { - std::unique_ptr arg_convertibles[2]; - if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[0].reset(reinterpret_cast( - static_cast(arg_values[0]))); - } - if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[1].reset(reinterpret_cast( - static_cast(arg_values[1]))); - } - // DCHECK_LE(num_args, 2); - v8::TracingController* controller = - node::tracing::TraceEventHelper::GetTracingController(); - if (controller == nullptr) return 0; - return controller->AddTraceEventWithTimestamp( - phase, category_group_enabled, name, scope, id, bind_id, num_args, - arg_names, arg_types, arg_values, arg_convertibles, flags, timestamp); -} - -static V8_INLINE void AddMetadataEventImpl( - const uint8_t* category_group_enabled, const char* name, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, unsigned int flags) { - std::unique_ptr arg_convertibles[2]; - if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[0].reset(reinterpret_cast( - static_cast(arg_values[0]))); - } - if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { - arg_convertibles[1].reset(reinterpret_cast( - static_cast(arg_values[1]))); - } - node::tracing::Agent* agent = - node::tracing::Agent::GetInstance(); - if (agent == nullptr) return; - static_cast( - agent->GetTracingController()) - ->AddMetadataEvent(category_group_enabled, name, num_args, arg_names, - arg_types, arg_values, arg_convertibles, flags); -} - -// Define SetTraceValue for each allowed type. It stores the type and -// value in the return arguments. This allows this API to avoid declaring any -// structures so that it is portable to third_party libraries. -#define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, union_member, \ - value_type_id) \ - static inline void SetTraceValue(actual_type arg, unsigned char* type, \ - uint64_t* value) { \ - TraceValueUnion type_value; \ - type_value.union_member = arg; \ - *type = value_type_id; \ - *value = type_value.as_uint; \ - } -// Simpler form for int types that can be safely casted. -#define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, value_type_id) \ - static inline void SetTraceValue(actual_type arg, unsigned char* type, \ - uint64_t* value) { \ - *type = value_type_id; \ - *value = static_cast(arg); \ - } - -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(uint64_t, TRACE_VALUE_TYPE_UINT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(uint16_t, TRACE_VALUE_TYPE_UINT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int64_t, TRACE_VALUE_TYPE_INT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int, TRACE_VALUE_TYPE_INT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int16_t, TRACE_VALUE_TYPE_INT) -INTERNAL_DECLARE_SET_TRACE_VALUE_INT(signed char, TRACE_VALUE_TYPE_INT) -INTERNAL_DECLARE_SET_TRACE_VALUE(bool, as_bool, TRACE_VALUE_TYPE_BOOL) -INTERNAL_DECLARE_SET_TRACE_VALUE(double, as_double, TRACE_VALUE_TYPE_DOUBLE) -INTERNAL_DECLARE_SET_TRACE_VALUE(const void*, as_pointer, - TRACE_VALUE_TYPE_POINTER) -INTERNAL_DECLARE_SET_TRACE_VALUE(const char*, as_string, - TRACE_VALUE_TYPE_STRING) -INTERNAL_DECLARE_SET_TRACE_VALUE(const TraceStringWithCopy&, as_string, - TRACE_VALUE_TYPE_COPY_STRING) - -#undef INTERNAL_DECLARE_SET_TRACE_VALUE -#undef INTERNAL_DECLARE_SET_TRACE_VALUE_INT - -static inline void SetTraceValue(v8::ConvertableToTraceFormat* convertable_value, - unsigned char* type, uint64_t* value) { - *type = TRACE_VALUE_TYPE_CONVERTABLE; - *value = static_cast(reinterpret_cast(convertable_value)); -} - -template -static inline typename std::enable_if< - std::is_convertible::value>::type -SetTraceValue(std::unique_ptr ptr, unsigned char* type, uint64_t* value) { - SetTraceValue(ptr.release(), type, value); -} - -// These AddTraceEvent template -// function is defined here instead of in the macro, because the arg_values -// could be temporary objects, such as std::string. In order to store -// pointers to the internal c_str and pass through to the tracing API, -// the arg_values must live throughout these procedures. - -static inline uint64_t AddTraceEvent(char phase, - const uint8_t* category_group_enabled, - const char* name, const char* scope, - uint64_t id, uint64_t bind_id, - unsigned int flags) { - return TRACE_EVENT_API_ADD_TRACE_EVENT(phase, category_group_enabled, name, - scope, id, bind_id, kZeroNumArgs, - nullptr, nullptr, nullptr, flags); -} - -template -static inline uint64_t AddTraceEvent( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, - const char* arg1_name, ARG1_TYPE&& arg1_val) { - const int num_args = 1; - uint8_t arg_type; - uint64_t arg_value; - SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); - return TRACE_EVENT_API_ADD_TRACE_EVENT( - phase, category_group_enabled, name, scope, id, bind_id, num_args, - &arg1_name, &arg_type, &arg_value, flags); -} - -template -static inline uint64_t AddTraceEvent( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, - const char* arg1_name, ARG1_TYPE&& arg1_val, const char* arg2_name, - ARG2_TYPE&& arg2_val) { - const int num_args = 2; - const char* arg_names[2] = {arg1_name, arg2_name}; - unsigned char arg_types[2]; - uint64_t arg_values[2]; - SetTraceValue(std::forward(arg1_val), &arg_types[0], - &arg_values[0]); - SetTraceValue(std::forward(arg2_val), &arg_types[1], - &arg_values[1]); - return TRACE_EVENT_API_ADD_TRACE_EVENT( - phase, category_group_enabled, name, scope, id, bind_id, num_args, - arg_names, arg_types, arg_values, flags); -} - -static V8_INLINE uint64_t AddTraceEventWithTimestamp( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, - int64_t timestamp) { - return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( - phase, category_group_enabled, name, scope, id, bind_id, kZeroNumArgs, - nullptr, nullptr, nullptr, flags, timestamp); -} - -template -static V8_INLINE uint64_t AddTraceEventWithTimestamp( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, - int64_t timestamp, const char* arg1_name, ARG1_TYPE&& arg1_val) { - const int num_args = 1; - uint8_t arg_type; - uint64_t arg_value; - SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); - return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( - phase, category_group_enabled, name, scope, id, bind_id, num_args, - &arg1_name, &arg_type, &arg_value, flags, timestamp); -} +#include "tracing/agent.h" -template -static V8_INLINE uint64_t AddTraceEventWithTimestamp( - char phase, const uint8_t* category_group_enabled, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, - int64_t timestamp, const char* arg1_name, ARG1_TYPE&& arg1_val, - const char* arg2_name, ARG2_TYPE&& arg2_val) { - const int num_args = 2; - const char* arg_names[2] = {arg1_name, arg2_name}; - unsigned char arg_types[2]; - uint64_t arg_values[2]; - SetTraceValue(std::forward(arg1_val), &arg_types[0], - &arg_values[0]); - SetTraceValue(std::forward(arg2_val), &arg_types[1], - &arg_values[1]); - return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( - phase, category_group_enabled, name, scope, id, bind_id, num_args, - arg_names, arg_types, arg_values, flags, timestamp); -} +#if defined(V8_USE_PERFETTO) -template -static V8_INLINE void AddMetadataEvent( - const uint8_t* category_group_enabled, const char* name, - const char* arg1_name, ARG1_TYPE&& arg1_val) { - const int num_args = 1; - uint8_t arg_type; - uint64_t arg_value; - SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); - AddMetadataEventImpl( - category_group_enabled, name, num_args, &arg1_name, &arg_type, &arg_value, - TRACE_EVENT_FLAG_NONE); -} +#define TRACING_CATEGORY_NODE "node" +#define TRACING_CATEGORY_NODE1(one) TRACING_CATEGORY_NODE "." #one +#define TRACING_CATEGORY_NODE2(one, two) TRACING_CATEGORY_NODE "." #one "." #two -// Used by TRACE_EVENTx macros. Do not use directly. -class ScopedTracer { - public: - // Note: members of data_ intentionally left uninitialized. See Initialize. - ScopedTracer() : p_data_(nullptr) {} +#include "tracing/trace_event_perfetto.h" - ~ScopedTracer() { - if (p_data_ && *data_.category_group_enabled) - TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( - data_.category_group_enabled, data_.name, data_.event_handle); - } +#else // defined(V8_USE_PERFETTO) - void Initialize(const uint8_t* category_group_enabled, const char* name, - uint64_t event_handle) { - data_.category_group_enabled = category_group_enabled; - data_.name = name; - data_.event_handle = event_handle; - p_data_ = &data_; - } +#define TRACING_CATEGORY_NODE "node" +#define TRACING_CATEGORY_NODE1(one) \ + TRACING_CATEGORY_NODE "," TRACING_CATEGORY_NODE "." #one +#define TRACING_CATEGORY_NODE2(one, two) \ + TRACING_CATEGORY_NODE "," TRACING_CATEGORY_NODE "." #one \ + "," TRACING_CATEGORY_NODE "." #one "." #two - private: - // This Data struct workaround is to avoid initializing all the members - // in Data during construction of this object, since this object is always - // constructed, even when tracing is disabled. If the members of Data were - // members of this class instead, compiler warnings occur about potential - // uninitialized accesses. - struct Data { - const uint8_t* category_group_enabled; - const char* name; - uint64_t event_handle; - }; - Data* p_data_; - Data data_; -}; +#include "tracing/trace_event_legacy_inl.h" -} // namespace tracing -} // namespace node +#endif #endif // SRC_TRACING_TRACE_EVENT_H_ diff --git a/src/tracing/trace_event.cc b/src/tracing/trace_event_helper.cc similarity index 93% rename from src/tracing/trace_event.cc rename to src/tracing/trace_event_helper.cc index 59306fafb3a10d..9a23b25c1782b8 100644 --- a/src/tracing/trace_event.cc +++ b/src/tracing/trace_event_helper.cc @@ -1,4 +1,4 @@ -#include "tracing/trace_event.h" +#include "tracing/trace_event_helper.h" #include "node.h" namespace node { diff --git a/src/tracing/trace_event_helper.h b/src/tracing/trace_event_helper.h new file mode 100644 index 00000000000000..a4c01a66e70f79 --- /dev/null +++ b/src/tracing/trace_event_helper.h @@ -0,0 +1,33 @@ +#ifndef SRC_TRACING_TRACE_EVENT_HELPER_H_ +#define SRC_TRACING_TRACE_EVENT_HELPER_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8-platform.h" + +namespace node::tracing { + +class TraceEventHelper { + public: + static v8::TracingController* GetTracingController(); + static void SetTracingController(v8::TracingController* controller); + + static inline const uint8_t* GetCategoryGroupEnabled(const char* group) { +#if !defined(V8_USE_PERFETTO) + v8::TracingController* controller = GetTracingController(); + static const uint8_t disabled = 0; + if (controller == nullptr) [[unlikely]] { + return &disabled; + } + return controller->GetCategoryGroupEnabled(group); +#else + return nullptr; +#endif + } +}; + +} // namespace node::tracing + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_TRACING_TRACE_EVENT_HELPER_H_ diff --git a/src/tracing/trace_event_common.h b/src/tracing/trace_event_legacy.h similarity index 99% rename from src/tracing/trace_event_common.h rename to src/tracing/trace_event_legacy.h index be1c68cfae9286..92b2e88d4823e2 100644 --- a/src/tracing/trace_event_common.h +++ b/src/tracing/trace_event_legacy.h @@ -2,8 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef SRC_TRACE_EVENT_COMMON_H -#define SRC_TRACE_EVENT_COMMON_H +#ifndef SRC_TRACING_TRACE_EVENT_LEGACY_H_ +#define SRC_TRACING_TRACE_EVENT_LEGACY_H_ + +#ifdef V8_USE_PERFETTO +#error Perfetto is enabled. +#endif // This header file defines the set of trace_event macros without specifying // how the events actually get collected and stored. If you need to expose trace @@ -1106,4 +1110,4 @@ #define TRACE_EVENT_SCOPE_NAME_PROCESS ('p') #define TRACE_EVENT_SCOPE_NAME_THREAD ('t') -#endif // SRC_TRACE_EVENT_COMMON_H +#endif // SRC_TRACING_TRACE_EVENT_LEGACY_H_ diff --git a/src/tracing/trace_event_legacy_inl.h b/src/tracing/trace_event_legacy_inl.h new file mode 100644 index 00000000000000..4fadb6d8e38f73 --- /dev/null +++ b/src/tracing/trace_event_legacy_inl.h @@ -0,0 +1,710 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SRC_TRACING_TRACE_EVENT_LEGACY_INL_H_ +#define SRC_TRACING_TRACE_EVENT_LEGACY_INL_H_ + +#ifdef V8_USE_PERFETTO +#error Perfetto is enabled. +#endif + +#include "v8-platform.h" +#include "tracing/agent_legacy.h" +#include "tracing/trace_event_helper.h" +#include "tracing/trace_event_legacy.h" +#include + +// This header file defines implementation details of how the trace macros in +// trace_event_common.h collect and store trace events. Anything not +// implementation-specific should go in trace_macros_common.h instead of here. + + +// The pointer returned from GetCategoryGroupEnabled() points to a +// value with zero or more of the following bits. Used in this class only. +// The TRACE_EVENT macros should only use the value as a bool. +// These values must be in sync with macro values in trace_log.h in +// chromium. +enum CategoryGroupEnabledFlags { + // Category group enabled for the recording mode. + kEnabledForRecording_CategoryGroupEnabledFlags = 1 << 0, + // Category group enabled by SetEventCallbackEnabled(). + kEnabledForEventCallback_CategoryGroupEnabledFlags = 1 << 2, + // Category group enabled to export events to ETW. + kEnabledForETWExport_CategoryGroupEnabledFlags = 1 << 3, +}; + +// By default, const char* argument values are assumed to have long-lived scope +// and will not be copied. Use this macro to force a const char* to be copied. +#define TRACE_STR_COPY(str) node::tracing::TraceStringWithCopy(str) + +// By default, uint64 ID argument values are not mangled with the Process ID in +// TRACE_EVENT_ASYNC macros. Use this macro to force Process ID mangling. +#define TRACE_ID_MANGLE(id) node::tracing::TraceID::ForceMangle(id) + +// By default, pointers are mangled with the Process ID in TRACE_EVENT_ASYNC +// macros. Use this macro to prevent Process ID mangling. +#define TRACE_ID_DONT_MANGLE(id) node::tracing::TraceID::DontMangle(id) + +// By default, trace IDs are eventually converted to a single 64-bit number. Use +// this macro to add a scope string. +#define TRACE_ID_WITH_SCOPE(scope, id) \ + trace_event_internal::TraceID::WithScope(scope, id) + +#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE() \ + *INTERNAL_TRACE_EVENT_UID(category_group_enabled) & \ + (kEnabledForRecording_CategoryGroupEnabledFlags | \ + kEnabledForEventCallback_CategoryGroupEnabledFlags) + +// The following macro has no implementation, but it needs to exist since +// it gets called from scoped trace events. It cannot call UNIMPLEMENTED() +// since an empty implementation is a valid one. +#define INTERNAL_TRACE_MEMORY(category, name) + +//////////////////////////////////////////////////////////////////////////////// +// Implementation specific tracing API definitions. + +// Get a pointer to the enabled state of the given trace category. Only +// long-lived literal strings should be given as the category group. The +// returned pointer can be held permanently in a local static for example. If +// the unsigned char is non-zero, tracing is enabled. If tracing is enabled, +// TRACE_EVENT_API_ADD_TRACE_EVENT can be called. It's OK if tracing is disabled +// between the load of the tracing state and the call to +// TRACE_EVENT_API_ADD_TRACE_EVENT, because this flag only provides an early out +// for best performance when tracing is disabled. +// const uint8_t* +// TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(const char* category_group) +#define TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED \ + node::tracing::TraceEventHelper::GetCategoryGroupEnabled + +// Get the number of times traces have been recorded. This is used to implement +// the TRACE_EVENT_IS_NEW_TRACE facility. +// unsigned int TRACE_EVENT_API_GET_NUM_TRACES_RECORDED() +#define TRACE_EVENT_API_GET_NUM_TRACES_RECORDED UNIMPLEMENTED() + +// Add a trace event to the platform tracing system. +// uint64_t TRACE_EVENT_API_ADD_TRACE_EVENT( +// char phase, +// const uint8_t* category_group_enabled, +// const char* name, +// const char* scope, +// uint64_t id, +// uint64_t bind_id, +// int num_args, +// const char** arg_names, +// const uint8_t* arg_types, +// const uint64_t* arg_values, +// unsigned int flags) +#define TRACE_EVENT_API_ADD_TRACE_EVENT node::tracing::AddTraceEventImpl + +// Add a trace event to the platform tracing system. +// uint64_t TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( +// char phase, +// const uint8_t* category_group_enabled, +// const char* name, +// const char* scope, +// uint64_t id, +// uint64_t bind_id, +// int num_args, +// const char** arg_names, +// const uint8_t* arg_types, +// const uint64_t* arg_values, +// unsigned int flags, +// int64_t timestamp) +#define TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP \ + node::tracing::AddTraceEventWithTimestampImpl + +// Set the duration field of a COMPLETE trace event. +// void TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( +// const uint8_t* category_group_enabled, +// const char* name, +// uint64_t id) +#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION \ + if (auto controller = \ + node::tracing::TraceEventHelper::GetTracingController()) \ + controller->UpdateTraceEventDuration + +// Adds a metadata event to the trace log. The |AppendValueAsTraceFormat| method +// on the convertable value will be called at flush time. +// TRACE_EVENT_API_ADD_METADATA_EVENT( +// const unsigned char* category_group_enabled, +// const char* event_name, +// const char* arg_name, +// std::unique_ptr arg_value) +#define TRACE_EVENT_API_ADD_METADATA_EVENT node::tracing::AddMetadataEvent + +// Defines atomic operations used internally by the tracing system. +#define TRACE_EVENT_API_ATOMIC_WORD std::atomic +#define TRACE_EVENT_API_ATOMIC_WORD_VALUE intptr_t +#define TRACE_EVENT_API_ATOMIC_LOAD(var) (var).load() +#define TRACE_EVENT_API_ATOMIC_STORE(var, value) (var).store(value) + +//////////////////////////////////////////////////////////////////////////////// + +// Implementation detail: trace event macros create temporary variables +// to keep instrumentation overhead low. These macros give each temporary +// variable a unique name based on the line number to prevent name collisions. +#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_unique_##a##b +#define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b) +#define INTERNAL_TRACE_EVENT_UID(name_prefix) \ + INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__) + +// Implementation detail: internal macro to create static category. +// No barriers are needed, because this code is designed to operate safely +// even when the unsigned char* points to garbage data (which may be the case +// on processors without cache coherency). +// TODO(fmeawad): This implementation contradicts that we can have a different +// configuration for each isolate, +// https://code.google.com/p/v8/issues/detail?id=4563 +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ + category_group, atomic, category_group_enabled) \ + category_group_enabled = \ + reinterpret_cast(TRACE_EVENT_API_ATOMIC_LOAD(atomic)); \ + if (!category_group_enabled) { \ + category_group_enabled = \ + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group); \ + TRACE_EVENT_API_ATOMIC_STORE( \ + atomic, reinterpret_cast( \ + category_group_enabled)); \ + } + +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ + static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) {0}; \ + const uint8_t* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ + category_group, INTERNAL_TRACE_EVENT_UID(atomic), \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled)); + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + node::tracing::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + node::tracing::kGlobalScope, node::tracing::kNoId, \ + node::tracing::kNoId, flags, ##__VA_ARGS__); \ + } \ + } while (0) + +// Implementation detail: internal macro to create static category and add begin +// event if the category is enabled. Also adds the end event when the scope +// ends. +#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, ...) \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + node::tracing::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + uint64_t h = node::tracing::AddTraceEvent( \ + TRACE_EVENT_PHASE_COMPLETE, \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + node::tracing::kGlobalScope, node::tracing::kNoId, \ + node::tracing::kNoId, TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__); \ + INTERNAL_TRACE_EVENT_UID(tracer) \ + .Initialize(INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + h); \ + } + +#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(category_group, name, \ + bind_id, flow_flags, ...) \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + node::tracing::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned int trace_event_flags = flow_flags; \ + node::tracing::TraceID trace_event_bind_id(bind_id, \ + &trace_event_flags); \ + uint64_t h = node::tracing::AddTraceEvent( \ + TRACE_EVENT_PHASE_COMPLETE, \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + node::tracing::kGlobalScope, node::tracing::kNoId, \ + trace_event_bind_id.raw_id(), trace_event_flags, ##__VA_ARGS__); \ + INTERNAL_TRACE_EVENT_UID(tracer) \ + .Initialize(INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + h); \ + } + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category_group, name, id, \ + flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ + node::tracing::TraceID trace_event_trace_id(id, \ + &trace_event_flags); \ + node::tracing::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \ + node::tracing::kNoId, trace_event_flags, ##__VA_ARGS__); \ + } \ + } while (0) + +// Adds a trace event with a given timestamp. +#define INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP(phase, category_group, name, \ + timestamp, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + node::tracing::AddTraceEventWithTimestamp( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + node::tracing::kGlobalScope, node::tracing::kNoId, \ + node::tracing::kNoId, flags, timestamp, ##__VA_ARGS__); \ + } \ + } while (0) + +// Adds a trace event with a given id and timestamp. Not Implemented. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_AND_TIMESTAMP( \ + phase, category_group, name, id, timestamp, flags, ...) \ + UNIMPLEMENTED() + +// Adds a trace event with a given id, thread_id, and timestamp. Not +// Implemented. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + phase, category_group, name, id, thread_id, timestamp, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ + node::tracing::TraceID trace_event_trace_id(id, \ + &trace_event_flags); \ + node::tracing::AddTraceEventWithTimestamp( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \ + node::tracing::kNoId, trace_event_flags, timestamp, ##__VA_ARGS__);\ + } \ + } while (0) + +#define INTERNAL_TRACE_EVENT_METADATA_ADD(category_group, name, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + TRACE_EVENT_API_ADD_METADATA_EVENT( \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + ##__VA_ARGS__); \ + } \ + } while(0) + +// Enter and leave a context based on the current scope. +#define INTERNAL_TRACE_EVENT_SCOPED_CONTEXT(category_group, name, context) \ + struct INTERNAL_TRACE_EVENT_UID(ScopedContext) { \ + public: \ + INTERNAL_TRACE_EVENT_UID(ScopedContext)(uint64_t cid) : cid_(cid) { \ + TRACE_EVENT_ENTER_CONTEXT(category_group, name, cid_); \ + } \ + ~INTERNAL_TRACE_EVENT_UID(ScopedContext)() { \ + TRACE_EVENT_LEAVE_CONTEXT(category_group, name, cid_); \ + } \ + \ + private: \ + /* Local class friendly DISALLOW_COPY_AND_ASSIGN */ \ + INTERNAL_TRACE_EVENT_UID(ScopedContext) \ + (const INTERNAL_TRACE_EVENT_UID(ScopedContext)&) {} \ + void operator=(const INTERNAL_TRACE_EVENT_UID(ScopedContext)&) {} \ + uint64_t cid_; \ + }; \ + INTERNAL_TRACE_EVENT_UID(ScopedContext) \ + INTERNAL_TRACE_EVENT_UID(scoped_context)(context); + +namespace node { +namespace tracing { + +// Specify these values when the corresponding argument of AddTraceEvent is not +// used. +const int kZeroNumArgs = 0; +const decltype(nullptr) kGlobalScope = nullptr; +const uint64_t kNoId = 0; + +// TraceID encapsulates an ID that can either be an integer or pointer. Pointers +// are by default mangled with the Process ID so that they are unlikely to +// collide when the same pointer is used on different processes. +class TraceID { + public: + class WithScope { + public: + WithScope(const char* scope, uint64_t raw_id) + : scope_(scope), raw_id_(raw_id) {} + uint64_t raw_id() const { return raw_id_; } + const char* scope() const { return scope_; } + + private: + const char* scope_ = nullptr; + uint64_t raw_id_; + }; + + class DontMangle { + public: + explicit DontMangle(const void* raw_id) + : raw_id_(static_cast(reinterpret_cast(raw_id))) {} + explicit DontMangle(uint64_t raw_id) : raw_id_(raw_id) {} + explicit DontMangle(unsigned int raw_id) : raw_id_(raw_id) {} + explicit DontMangle(uint16_t raw_id) : raw_id_(raw_id) {} + explicit DontMangle(unsigned char raw_id) : raw_id_(raw_id) {} + explicit DontMangle(int64_t raw_id) + : raw_id_(static_cast(raw_id)) {} + explicit DontMangle(int raw_id) : raw_id_(static_cast(raw_id)) {} + explicit DontMangle(int16_t raw_id) + : raw_id_(static_cast(raw_id)) {} + explicit DontMangle(signed char raw_id) + : raw_id_(static_cast(raw_id)) {} + explicit DontMangle(WithScope scoped_id) + : scope_(scoped_id.scope()), raw_id_(scoped_id.raw_id()) {} + const char* scope() const { return scope_; } + uint64_t raw_id() const { return raw_id_; } + + private: + const char* scope_ = nullptr; + uint64_t raw_id_; + }; + + class ForceMangle { + public: + explicit ForceMangle(uint64_t raw_id) : raw_id_(raw_id) {} + explicit ForceMangle(unsigned int raw_id) : raw_id_(raw_id) {} + explicit ForceMangle(uint16_t raw_id) : raw_id_(raw_id) {} + explicit ForceMangle(unsigned char raw_id) : raw_id_(raw_id) {} + explicit ForceMangle(int64_t raw_id) + : raw_id_(static_cast(raw_id)) {} + explicit ForceMangle(int raw_id) : raw_id_(static_cast(raw_id)) {} + explicit ForceMangle(int16_t raw_id) + : raw_id_(static_cast(raw_id)) {} + explicit ForceMangle(signed char raw_id) + : raw_id_(static_cast(raw_id)) {} + uint64_t raw_id() const { return raw_id_; } + + private: + uint64_t raw_id_; + }; + + TraceID(const void* raw_id, unsigned int* flags) + : raw_id_(static_cast(reinterpret_cast(raw_id))) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + TraceID(ForceMangle raw_id, unsigned int* flags) : raw_id_(raw_id.raw_id()) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + TraceID(DontMangle maybe_scoped_id, unsigned int* flags) + : scope_(maybe_scoped_id.scope()), raw_id_(maybe_scoped_id.raw_id()) {} + TraceID(uint64_t raw_id, unsigned int* flags) : raw_id_(raw_id) { + (void)flags; + } + TraceID(unsigned int raw_id, unsigned int* flags) : raw_id_(raw_id) { + (void)flags; + } + TraceID(uint16_t raw_id, unsigned int* flags) : raw_id_(raw_id) { + (void)flags; + } + TraceID(unsigned char raw_id, unsigned int* flags) : raw_id_(raw_id) { + (void)flags; + } + TraceID(int64_t raw_id, unsigned int* flags) + : raw_id_(static_cast(raw_id)) { + (void)flags; + } + TraceID(int raw_id, unsigned int* flags) + : raw_id_(static_cast(raw_id)) { + (void)flags; + } + TraceID(int16_t raw_id, unsigned int* flags) + : raw_id_(static_cast(raw_id)) { + (void)flags; + } + TraceID(signed char raw_id, unsigned int* flags) + : raw_id_(static_cast(raw_id)) { + (void)flags; + } + TraceID(WithScope scoped_id, unsigned int* flags) + : scope_(scoped_id.scope()), raw_id_(scoped_id.raw_id()) {} + + uint64_t raw_id() const { return raw_id_; } + const char* scope() const { return scope_; } + + private: + const char* scope_ = nullptr; + uint64_t raw_id_; +}; + +// Simple union to store various types as uint64_t. +union TraceValueUnion { + bool as_bool; + uint64_t as_uint; + int64_t as_int; + double as_double; + const void* as_pointer; + const char* as_string; +}; + +// Simple container for const char* that should be copied instead of retained. +class TraceStringWithCopy { + public: + explicit TraceStringWithCopy(const char* str) : str_(str) {} + operator const char*() const { return str_; } + + private: + const char* str_; +}; + +static inline uint64_t AddTraceEventImpl( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, unsigned int flags) { + std::unique_ptr arg_convertibles[2]; + if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[0].reset(reinterpret_cast( + static_cast(arg_values[0]))); + } + if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[1].reset(reinterpret_cast( + static_cast(arg_values[1]))); + } + // DCHECK(num_args, 2); + v8::TracingController* controller = + node::tracing::TraceEventHelper::GetTracingController(); + if (controller == nullptr) return 0; + return controller->AddTraceEvent(phase, category_group_enabled, name, scope, id, + bind_id, num_args, arg_names, arg_types, + arg_values, arg_convertibles, flags); +} + +static V8_INLINE uint64_t AddTraceEventWithTimestampImpl( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, unsigned int flags, int64_t timestamp) { + std::unique_ptr arg_convertibles[2]; + if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[0].reset(reinterpret_cast( + static_cast(arg_values[0]))); + } + if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[1].reset(reinterpret_cast( + static_cast(arg_values[1]))); + } + // DCHECK_LE(num_args, 2); + v8::TracingController* controller = + node::tracing::TraceEventHelper::GetTracingController(); + if (controller == nullptr) return 0; + return controller->AddTraceEventWithTimestamp( + phase, category_group_enabled, name, scope, id, bind_id, num_args, + arg_names, arg_types, arg_values, arg_convertibles, flags, timestamp); +} + +static V8_INLINE void AddMetadataEventImpl( + const uint8_t* category_group_enabled, const char* name, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, unsigned int flags) { + std::unique_ptr arg_convertibles[2]; + if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[0].reset(reinterpret_cast( + static_cast(arg_values[0]))); + } + if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) { + arg_convertibles[1].reset(reinterpret_cast( + static_cast(arg_values[1]))); + } + node::tracing::Agent* agent = + node::tracing::Agent::GetInstance(); + if (agent == nullptr) return; + static_cast( + agent->GetTracingController()) + ->AddMetadataEvent(category_group_enabled, name, num_args, arg_names, + arg_types, arg_values, arg_convertibles, flags); +} + +// Define SetTraceValue for each allowed type. It stores the type and +// value in the return arguments. This allows this API to avoid declaring any +// structures so that it is portable to third_party libraries. +#define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, union_member, \ + value_type_id) \ + static inline void SetTraceValue(actual_type arg, unsigned char* type, \ + uint64_t* value) { \ + TraceValueUnion type_value; \ + type_value.union_member = arg; \ + *type = value_type_id; \ + *value = type_value.as_uint; \ + } +// Simpler form for int types that can be safely casted. +#define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, value_type_id) \ + static inline void SetTraceValue(actual_type arg, unsigned char* type, \ + uint64_t* value) { \ + *type = value_type_id; \ + *value = static_cast(arg); \ + } + +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(uint64_t, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(uint16_t, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int64_t, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int16_t, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(signed char, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE(bool, as_bool, TRACE_VALUE_TYPE_BOOL) +INTERNAL_DECLARE_SET_TRACE_VALUE(double, as_double, TRACE_VALUE_TYPE_DOUBLE) +INTERNAL_DECLARE_SET_TRACE_VALUE(const void*, as_pointer, + TRACE_VALUE_TYPE_POINTER) +INTERNAL_DECLARE_SET_TRACE_VALUE(const char*, as_string, + TRACE_VALUE_TYPE_STRING) +INTERNAL_DECLARE_SET_TRACE_VALUE(const TraceStringWithCopy&, as_string, + TRACE_VALUE_TYPE_COPY_STRING) + +#undef INTERNAL_DECLARE_SET_TRACE_VALUE +#undef INTERNAL_DECLARE_SET_TRACE_VALUE_INT + +static inline void SetTraceValue(v8::ConvertableToTraceFormat* convertable_value, + unsigned char* type, uint64_t* value) { + *type = TRACE_VALUE_TYPE_CONVERTABLE; + *value = static_cast(reinterpret_cast(convertable_value)); +} + +template +static inline typename std::enable_if< + std::is_convertible::value>::type +SetTraceValue(std::unique_ptr ptr, unsigned char* type, uint64_t* value) { + SetTraceValue(ptr.release(), type, value); +} + +// These AddTraceEvent template +// function is defined here instead of in the macro, because the arg_values +// could be temporary objects, such as std::string. In order to store +// pointers to the internal c_str and pass through to the tracing API, +// the arg_values must live throughout these procedures. + +static inline uint64_t AddTraceEvent(char phase, + const uint8_t* category_group_enabled, + const char* name, const char* scope, + uint64_t id, uint64_t bind_id, + unsigned int flags) { + return TRACE_EVENT_API_ADD_TRACE_EVENT(phase, category_group_enabled, name, + scope, id, bind_id, kZeroNumArgs, + nullptr, nullptr, nullptr, flags); +} + +template +static inline uint64_t AddTraceEvent( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, + const char* arg1_name, ARG1_TYPE&& arg1_val) { + const int num_args = 1; + uint8_t arg_type; + uint64_t arg_value; + SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); + return TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_group_enabled, name, scope, id, bind_id, num_args, + &arg1_name, &arg_type, &arg_value, flags); +} + +template +static inline uint64_t AddTraceEvent( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, + const char* arg1_name, ARG1_TYPE&& arg1_val, const char* arg2_name, + ARG2_TYPE&& arg2_val) { + const int num_args = 2; + const char* arg_names[2] = {arg1_name, arg2_name}; + unsigned char arg_types[2]; + uint64_t arg_values[2]; + SetTraceValue(std::forward(arg1_val), &arg_types[0], + &arg_values[0]); + SetTraceValue(std::forward(arg2_val), &arg_types[1], + &arg_values[1]); + return TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_group_enabled, name, scope, id, bind_id, num_args, + arg_names, arg_types, arg_values, flags); +} + +static V8_INLINE uint64_t AddTraceEventWithTimestamp( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, + int64_t timestamp) { + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( + phase, category_group_enabled, name, scope, id, bind_id, kZeroNumArgs, + nullptr, nullptr, nullptr, flags, timestamp); +} + +template +static V8_INLINE uint64_t AddTraceEventWithTimestamp( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, + int64_t timestamp, const char* arg1_name, ARG1_TYPE&& arg1_val) { + const int num_args = 1; + uint8_t arg_type; + uint64_t arg_value; + SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( + phase, category_group_enabled, name, scope, id, bind_id, num_args, + &arg1_name, &arg_type, &arg_value, flags, timestamp); +} + +template +static V8_INLINE uint64_t AddTraceEventWithTimestamp( + char phase, const uint8_t* category_group_enabled, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, unsigned int flags, + int64_t timestamp, const char* arg1_name, ARG1_TYPE&& arg1_val, + const char* arg2_name, ARG2_TYPE&& arg2_val) { + const int num_args = 2; + const char* arg_names[2] = {arg1_name, arg2_name}; + unsigned char arg_types[2]; + uint64_t arg_values[2]; + SetTraceValue(std::forward(arg1_val), &arg_types[0], + &arg_values[0]); + SetTraceValue(std::forward(arg2_val), &arg_types[1], + &arg_values[1]); + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( + phase, category_group_enabled, name, scope, id, bind_id, num_args, + arg_names, arg_types, arg_values, flags, timestamp); +} + +template +static V8_INLINE void AddMetadataEvent( + const uint8_t* category_group_enabled, const char* name, + const char* arg1_name, ARG1_TYPE&& arg1_val) { + const int num_args = 1; + uint8_t arg_type; + uint64_t arg_value; + SetTraceValue(std::forward(arg1_val), &arg_type, &arg_value); + AddMetadataEventImpl( + category_group_enabled, name, num_args, &arg1_name, &arg_type, &arg_value, + TRACE_EVENT_FLAG_NONE); +} + +// Used by TRACE_EVENTx macros. Do not use directly. +class ScopedTracer { + public: + // Note: members of data_ intentionally left uninitialized. See Initialize. + ScopedTracer() : p_data_(nullptr) {} + + ~ScopedTracer() { + if (p_data_ && *data_.category_group_enabled) + TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( + data_.category_group_enabled, data_.name, data_.event_handle); + } + + void Initialize(const uint8_t* category_group_enabled, const char* name, + uint64_t event_handle) { + data_.category_group_enabled = category_group_enabled; + data_.name = name; + data_.event_handle = event_handle; + p_data_ = &data_; + } + + private: + // This Data struct workaround is to avoid initializing all the members + // in Data during construction of this object, since this object is always + // constructed, even when tracing is disabled. If the members of Data were + // members of this class instead, compiler warnings occur about potential + // uninitialized accesses. + struct Data { + const uint8_t* category_group_enabled; + const char* name; + uint64_t event_handle; + }; + Data* p_data_; + Data data_; +}; + +} // namespace tracing +} // namespace node + +#endif // SRC_TRACING_TRACE_EVENT_LEGACY_INL_H_ diff --git a/src/tracing/trace_event_perfetto.cc b/src/tracing/trace_event_perfetto.cc new file mode 100644 index 00000000000000..87227b95fd5616 --- /dev/null +++ b/src/tracing/trace_event_perfetto.cc @@ -0,0 +1,5 @@ +#include "tracing/trace_event_perfetto.h" + +#if defined(V8_USE_PERFETTO) +PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(node); +#endif diff --git a/src/tracing/trace_event_perfetto.h b/src/tracing/trace_event_perfetto.h new file mode 100644 index 00000000000000..60add492d36ae0 --- /dev/null +++ b/src/tracing/trace_event_perfetto.h @@ -0,0 +1,47 @@ +#ifndef SRC_TRACING_TRACE_EVENT_PERFETTO_H_ +#define SRC_TRACING_TRACE_EVENT_PERFETTO_H_ + +#if !defined(V8_USE_PERFETTO) +#error Perfetto is not enabled. +#endif + +// For now most of Node.js uses legacy trace events. +#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 1 + +#include "perfetto.h" + +#define TRACING_CATEGORY_NODE "node" +#define TRACING_CATEGORY_NODE1(one) TRACING_CATEGORY_NODE "." #one +#define TRACING_CATEGORY_NODE2(one, two) TRACING_CATEGORY_NODE "." #one "." #two + +// List of categories used by built-in Node.js trace events. +// clang-format off +PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE( + node, + perfetto::Category("__metadata"), // TODO(legendecas): remove this + perfetto::Category("node"), + perfetto::Category("node.async_hooks"), + perfetto::Category("node.environment"), + perfetto::Category("node.realm"), + perfetto::Category("node.bootstrap"), + perfetto::Category("node.dns.native"), + perfetto::Category("node.net.native"), + perfetto::Category("node.vm.script"), + perfetto::Category("node.fs_dir.sync"), + perfetto::Category("node.fs_dir.async"), + perfetto::Category("node.fs.sync"), + perfetto::Category("node.fs.async"), + perfetto::Category("node.perf.event_loop"), + perfetto::Category("node.promises.rejections"), + perfetto::Category("node.threadpoolwork.sync"), + perfetto::Category("node.threadpoolwork.async"), +// JavaScript namespaces + perfetto::Category("node.console"), + perfetto::Category("node.http"), + perfetto::Category("node.module_timer"), + ); // NOLINT(whitespace/parens) +// clang-format on + +PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(node); + +#endif // SRC_TRACING_TRACE_EVENT_PERFETTO_H_ diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index a7c9b9b5b30ac1..dcecbfd976ad16 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -258,5 +258,21 @@ std::unique_ptr ProcessMeta::Cast() const { return trace_process; } +#if defined(V8_USE_PERFETTO) +void ProcessMeta::WriteIntoTrace(perfetto::TracedValue context) const { + auto dict = std::move(context).WriteDictionary(); + auto versions_dict = dict.AddDictionary("versions"); + for (const auto& version : per_process::metadata.versions.pairs()) { + versions_dict.Add(perfetto::DynamicString(std::string(version.first)), + version.second); + } + dict.Add("arch", per_process::metadata.arch.c_str()); + dict.Add("platform", per_process::metadata.platform.c_str()); + + auto release_dict = dict.AddDictionary("release"); + release_dict.Add("name", per_process::metadata.release.name.c_str()); +} +#endif + } // namespace tracing } // namespace node diff --git a/src/tracing/traced_value.h b/src/tracing/traced_value.h index 0bc9df81d87562..e04ffff48efbc5 100644 --- a/src/tracing/traced_value.h +++ b/src/tracing/traced_value.h @@ -11,13 +11,24 @@ #include #include +#if defined(V8_USE_PERFETTO) +#include "tracing/trace_event_perfetto.h" +#endif + namespace node { namespace tracing { +#if defined(V8_USE_PERFETTO) +template +T CastTracedValue(const T& value) { + return value; +} +#else template std::unique_ptr CastTracedValue(const T& value) { return value.Cast(); } +#endif class EnvironmentArgs { public: @@ -27,6 +38,20 @@ class EnvironmentArgs { std::unique_ptr Cast() const; +#if defined(V8_USE_PERFETTO) + void WriteIntoTrace(perfetto::TracedValue context) const { + auto dict = std::move(context).WriteDictionary(); + auto args_array = dict.AddArray("args"); + for (const auto& arg : args_) { + args_array.Append(arg); + } + auto exec_args_array = dict.AddArray("exec_args"); + for (const auto& arg : exec_args_) { + exec_args_array.Append(arg); + } + } +#endif + private: std::span args_; std::span exec_args_; @@ -40,6 +65,15 @@ class AsyncWrapArgs { std::unique_ptr Cast() const; +#if defined(V8_USE_PERFETTO) + void WriteIntoTrace(perfetto::TracedValue context) const { + auto dict = std::move(context).WriteDictionary(); + dict.Add("executionAsyncId", execution_async_id_); + dict.Add("triggerAsyncId", trigger_async_id_); + dict.Add("foo", "bar"); + } +#endif + private: int64_t execution_async_id_; int64_t trigger_async_id_; @@ -48,6 +82,10 @@ class AsyncWrapArgs { class ProcessMeta { public: std::unique_ptr Cast() const; + +#if defined(V8_USE_PERFETTO) + void WriteIntoTrace(perfetto::TracedValue context) const; +#endif }; // Do not use this class directly. Define a custom structured class to provide diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 01b7ba07cf6151..099f8d2eaf057e 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -118,6 +118,7 @@ expected.beforePreExec = new Set([ 'NativeModule internal/net', 'NativeModule internal/dns/utils', 'NativeModule internal/modules/esm/get_format', + 'NativeModule internal/trace_events', ]); expected.atRunTime = new Set([ diff --git a/test/parallel/test-trace-events-perfetto-pftrace.js b/test/parallel/test-trace-events-perfetto-pftrace.js new file mode 100644 index 00000000000000..8cfcb52182df9a --- /dev/null +++ b/test/parallel/test-trace-events-perfetto-pftrace.js @@ -0,0 +1,28 @@ +'use strict'; +const common = require('../common'); + +if (!process.config.variables.v8_use_perfetto) + common.skip('perfetto tracing is not enabled'); + +const assert = require('assert'); +const cp = require('child_process'); +const fs = require('fs'); + +const CODE = + 'setTimeout(() => { for (let i = 0; i < 100000; i++) { "test" + i } }, 1)'; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); +// Perfetto builds default the file pattern to a .pftrace extension. +const FILE_NAME = tmpdir.resolve('node_trace.1.pftrace'); + +const proc = cp.spawn(process.execPath, + [ '--trace-events-enabled', '-e', CODE ], + { cwd: tmpdir.path }); + +proc.once('exit', common.mustCall(() => { + assert(fs.existsSync(FILE_NAME)); + // The perfetto trace is a binary protobuf, so just check it has content. + const stat = fs.statSync(FILE_NAME); + assert(stat.size > 0); +})); diff --git a/tools/v8_gypfiles/features.gypi b/tools/v8_gypfiles/features.gypi index 0208ce11983a33..a5227687d22d44 100644 --- a/tools/v8_gypfiles/features.gypi +++ b/tools/v8_gypfiles/features.gypi @@ -200,8 +200,7 @@ # Enable seeded array index hash. 'v8_enable_seeded_array_index_hash%': 1, - # Use Perfetto (https://perfetto.dev) as the default TracingController. Not - # currently implemented. + # Use Perfetto (https://perfetto.dev) as the default TracingController. 'v8_use_perfetto%': 0, # Enable map packing & unpacking (sets -dV8_MAP_PACKING). @@ -463,7 +462,10 @@ 'defines': ['ENABLE_VERIFY_CSA',], }], ['v8_use_perfetto==1', { - 'defines': ['V8_USE_PERFETTO',], + 'defines': [ + 'V8_USE_PERFETTO', + 'V8_USE_PERFETTO_SDK', + ], }], ['v8_enable_map_packing==1', { 'defines': ['V8_MAP_PACKING',], diff --git a/tools/v8_gypfiles/v8.gyp b/tools/v8_gypfiles/v8.gyp index 7953fa44b432d9..94a5435fca65da 100644 --- a/tools/v8_gypfiles/v8.gyp +++ b/tools/v8_gypfiles/v8.gyp @@ -38,6 +38,7 @@ ], }], ], + 'perfetto_gyp_file': '../../deps/perfetto/perfetto.gyp', }, 'includes': ['toolchain.gypi', 'features.gypi'], 'target_defaults': { @@ -292,6 +293,13 @@ 'sources': [ '<(V8_ROOT)/src/init/setup-isolate-full.cc', ], + 'conditions': [ + ['v8_use_perfetto==1', { + 'dependencies': [ + '<(perfetto_gyp_file):perfetto_sdk', + ], + }], + ], }, # v8_init { 'target_name': 'v8_initializers', @@ -312,6 +320,11 @@ '