From 596fd86356fad2de4a39214dc4eac7f7cf2395a1 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Sun, 13 Apr 2025 18:43:24 +0100 Subject: [PATCH] src: internalize `v8::ConvertableToTraceFormat` in traces `v8::ConvertableToTraceFormat` is only available in legacy V8 tracing API and no longer supported in perfetto. This internalize `node::tracing::TracedValue` and `v8::ConvertableToTraceFormat` by defining specialized trace argument classes. The newly defined structured trace argument classes can be easily converted to `perfetto::TracedValue` by perfetto traced value protocol. For example, when adding perfetto support, `CastTracedValue` will be a no-op and these classes can add a new conversion method like: ```cpp class Foo { void WriteIntoTrace(TracedValue context) const { auto dict = std::move(context).WriteDictionary(); dict->Add("key", 42); dict->Add("foo", "bar"); dict->Add("member", member_); } }; ``` --- src/async_wrap.cc | 28 ++++++------- src/env.cc | 10 +---- src/node_v8_platform-inl.h | 30 +++----------- src/tracing/traced_value.cc | 49 +++++++++++++++++++++-- src/tracing/traced_value.h | 69 +++++++++++++++++++++++++++++--- test/cctest/test_traced_value.cc | 26 ++++++++++++ 6 files changed, 156 insertions(+), 56 deletions(-) diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 30490b90e77b64..e761fff7fabad9 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -602,21 +602,19 @@ void AsyncWrap::AsyncReset(Local resource, double execution_async_id) { } switch (provider_type()) { -#define V(PROVIDER) \ - case PROVIDER_ ## PROVIDER: \ - if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \ - TRACING_CATEGORY_NODE1(async_hooks))) { \ - auto data = tracing::TracedValue::Create(); \ - data->SetInteger("executionAsyncId", \ - static_cast(env()->execution_async_id())); \ - data->SetInteger("triggerAsyncId", \ - static_cast(get_trigger_async_id())); \ - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( \ - TRACING_CATEGORY_NODE1(async_hooks), \ - #PROVIDER, static_cast(get_async_id()), \ - "data", std::move(data)); \ - } \ - break; +#define V(PROVIDER) \ + case PROVIDER_##PROVIDER: \ + if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \ + TRACING_CATEGORY_NODE1(async_hooks))) { \ + tracing::AsyncWrapArgs data(env()->execution_async_id(), \ + get_trigger_async_id()); \ + TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE1(async_hooks), \ + #PROVIDER, \ + static_cast(get_async_id()), \ + "data", \ + tracing::CastTracedValue(data)); \ + } \ + break; NODE_ASYNC_PROVIDER_TYPES(V) #undef V default: diff --git a/src/env.cc b/src/env.cc index 5d6102dbdb1a20..f9835b4752035c 100644 --- a/src/env.cc +++ b/src/env.cc @@ -914,18 +914,12 @@ Environment::Environment(IsolateData* isolate_data, if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACING_CATEGORY_NODE1(environment)) != 0) { - auto traced_value = tracing::TracedValue::Create(); - traced_value->BeginArray("args"); - for (const std::string& arg : args) traced_value->AppendString(arg); - traced_value->EndArray(); - traced_value->BeginArray("exec_args"); - for (const std::string& arg : exec_args) traced_value->AppendString(arg); - traced_value->EndArray(); + tracing::EnvironmentArgs traced_value(args, exec_args); TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE1(environment), "Environment", this, "args", - std::move(traced_value)); + tracing::CastTracedValue(traced_value)); } if (options_->permission) { diff --git a/src/node_v8_platform-inl.h b/src/node_v8_platform-inl.h index fc0922035aac07..57a725a988f445 100644 --- a/src/node_v8_platform-inl.h +++ b/src/node_v8_platform-inl.h @@ -38,31 +38,11 @@ class NodeTraceStateObserver TRACE_EVENT_METADATA1( "__metadata", "thread_name", "name", "JavaScriptMainThread"); - auto trace_process = tracing::TracedValue::Create(); - trace_process->BeginDictionary("versions"); - -#define V(key) \ - trace_process->SetString(#key, per_process::metadata.versions.key.c_str()); - - NODE_VERSIONS_KEYS(V) -#undef V - - trace_process->EndDictionary(); - - trace_process->SetString("arch", per_process::metadata.arch.c_str()); - trace_process->SetString("platform", - per_process::metadata.platform.c_str()); - - trace_process->BeginDictionary("release"); - trace_process->SetString("name", - per_process::metadata.release.name.c_str()); -#if NODE_VERSION_IS_LTS - trace_process->SetString("lts", per_process::metadata.release.lts.c_str()); -#endif - trace_process->EndDictionary(); - TRACE_EVENT_METADATA1( - "__metadata", "node", "process", std::move(trace_process)); - + tracing::ProcessMeta trace_process; + TRACE_EVENT_METADATA1("__metadata", + "node", + "process", + tracing::CastTracedValue(trace_process)); // This only runs the first time tracing is enabled controller_->RemoveTraceStateObserver(this); } diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index 662b0357a19305..ddcd8559956f99 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -9,10 +9,10 @@ #include #endif -#include -#include #include -#include + +#include "node_metadata.h" +#include "util.h" #if defined(_STLP_VENDOR_CSTD) // STLPort doesn't import fpclassify into the std namespace. @@ -218,5 +218,48 @@ void TracedValue::AppendAsTraceFormat(std::string* out) const { *out += root_is_array_ ? ']' : '}'; } +std::unique_ptr EnvironmentArgs::Cast() const { + auto traced_value = tracing::TracedValue::Create(); + traced_value->BeginArray("args"); + for (const std::string& arg : args_) traced_value->AppendString(arg); + traced_value->EndArray(); + traced_value->BeginArray("exec_args"); + for (const std::string& arg : exec_args_) traced_value->AppendString(arg); + traced_value->EndArray(); + return traced_value; +} + +std::unique_ptr AsyncWrapArgs::Cast() const { + auto data = tracing::TracedValue::Create(); + data->SetInteger("executionAsyncId", execution_async_id_); + data->SetInteger("triggerAsyncId", trigger_async_id_); + return data; +} + +std::unique_ptr ProcessMeta::Cast() const { + auto trace_process = tracing::TracedValue::Create(); + trace_process->BeginDictionary("versions"); + +#define V(key) \ + trace_process->SetString(#key, per_process::metadata.versions.key.c_str()); + + NODE_VERSIONS_KEYS(V) +#undef V + + trace_process->EndDictionary(); + + trace_process->SetString("arch", per_process::metadata.arch.c_str()); + trace_process->SetString("platform", per_process::metadata.platform.c_str()); + + trace_process->BeginDictionary("release"); + trace_process->SetString("name", per_process::metadata.release.name.c_str()); +#if NODE_VERSION_IS_LTS + trace_process->SetString("lts", per_process::metadata.release.lts.c_str()); +#endif + trace_process->EndDictionary(); + + return trace_process; +} + } // namespace tracing } // namespace node diff --git a/src/tracing/traced_value.h b/src/tracing/traced_value.h index 93c5be799b63a4..0bc9df81d87562 100644 --- a/src/tracing/traced_value.h +++ b/src/tracing/traced_value.h @@ -5,17 +5,76 @@ #ifndef SRC_TRACING_TRACED_VALUE_H_ #define SRC_TRACING_TRACED_VALUE_H_ -#include "node.h" -#include "util.h" -#include "v8.h" +#include "v8-platform.h" -#include -#include +#include +#include #include namespace node { namespace tracing { +template +std::unique_ptr CastTracedValue(const T& value) { + return value.Cast(); +} + +class EnvironmentArgs { + public: + EnvironmentArgs(std::span args, + std::span exec_args) + : args_(args), exec_args_(exec_args) {} + + std::unique_ptr Cast() const; + + private: + std::span args_; + std::span exec_args_; +}; + +class AsyncWrapArgs { + public: + AsyncWrapArgs(int64_t execution_async_id, int64_t trigger_async_id) + : execution_async_id_(execution_async_id), + trigger_async_id_(trigger_async_id) {} + + std::unique_ptr Cast() const; + + private: + int64_t execution_async_id_; + int64_t trigger_async_id_; +}; + +class ProcessMeta { + public: + std::unique_ptr Cast() const; +}; + +// Do not use this class directly. Define a custom structured class to provide +// a conversion method so that the class can be used with both V8 legacy +// trace API and perfetto API. +// +// These classes provide a JSON-inspired way to write structed data into traces. +// +// To define how a custom class should be written into the trace, users should +// define one of the two following functions: +// - Foo::Cast(TracedValue) const +// (preferred for code which depends on perfetto directly) +// +// After defining a conversion method, the object can be used as a +// TRACE_EVENT argument: +// +// Foo foo; +// TRACE_EVENT("cat", "Event", "arg", CastTracedValue(foo)); +// +// class Foo { +// std::unique_ptr Cast() const { +// auto traced_value = tracing::TracedValue::Create(); +// dict->SetInteger("key", 42); +// dict->SetString("foo", "bar"); +// return traced_value; +// } +// } class TracedValue : public v8::ConvertableToTraceFormat { public: ~TracedValue() override = default; diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc index fa938c881ef06c..f877e9d0b00e87 100644 --- a/test/cctest/test_traced_value.cc +++ b/test/cctest/test_traced_value.cc @@ -94,3 +94,29 @@ TEST(TracedValue, EscapingArray) { EXPECT_EQ(check, string); } + +TEST(TracedValue, EnvironmentArgs) { + std::vector args{"a", "bb", "ccc"}; + std::vector exec_args{"--inspect", "--a-long-arg"}; + node::tracing::EnvironmentArgs env_args(args, exec_args); + + std::string string; + env_args.Cast()->AppendAsTraceFormat(&string); + + static const char* check = "{\"args\":[\"a\",\"bb\",\"ccc\"]," + "\"exec_args\":[\"--inspect\",\"--a-long-arg\"]}"; + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, AsyncWrapArgs) { + node::tracing::AsyncWrapArgs aw_args(1, 1); + + std::string string; + aw_args.Cast()->AppendAsTraceFormat(&string); + + static const char* check = "{\"executionAsyncId\":1," + "\"triggerAsyncId\":1}"; + + EXPECT_EQ(check, string); +}