diff --git a/bindings/per-isolate-data.cc b/bindings/per-isolate-data.cc index 5898fce2..21f668fd 100644 --- a/bindings/per-isolate-data.cc +++ b/bindings/per-isolate-data.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -64,4 +65,22 @@ std::shared_ptr& PerIsolateData::GetHeapProfilerState() { return heap_profiler_state; } +#if DD_V8_HAS_DICTIONARY_TEMPLATE +v8::Local PerIsolateData::GetDictionaryTemplate( + v8::Isolate* isolate, + DictionaryTemplateId id, + v8::MemorySpan names) { + const size_t index = static_cast(id); + auto& tmpl = dictionary_templates[index]; + auto& arity = dictionary_template_arities[index]; + if (tmpl.IsEmpty()) { + arity = names.size(); + tmpl.Reset(v8::DictionaryTemplate::New(isolate, names)); + } else { + assert(arity == names.size()); + } + return Nan::New(tmpl); +} +#endif + } // namespace dd diff --git a/bindings/per-isolate-data.hh b/bindings/per-isolate-data.hh index 618aa8a9..4221d1ac 100644 --- a/bindings/per-isolate-data.hh +++ b/bindings/per-isolate-data.hh @@ -18,19 +18,45 @@ #include #include +#include #include +#include +#include #include +#include + +// v8::DictionaryTemplate landed in V8 12.3, i.e. Node.js >= 22. +#if V8_MAJOR_VERSION > 12 || (V8_MAJOR_VERSION == 12 && V8_MINOR_VERSION >= 3) +#define DD_V8_HAS_DICTIONARY_TEMPLATE 1 +#else +#define DD_V8_HAS_DICTIONARY_TEMPLATE 0 +#endif namespace dd { struct HeapProfilerState; +#if DD_V8_HAS_DICTIONARY_TEMPLATE +enum class DictionaryTemplateId : size_t { + kWallSampleContext, + kCount, +}; + +constexpr size_t kDictionaryTemplateCount = + static_cast(DictionaryTemplateId::kCount); +#endif + class PerIsolateData { private: Nan::Global wall_profiler_constructor; Nan::Global allocation_node_constructor; Nan::Global time_profile_node_constructor; std::shared_ptr heap_profiler_state; +#if DD_V8_HAS_DICTIONARY_TEMPLATE + std::array, kDictionaryTemplateCount> + dictionary_templates; + std::array dictionary_template_arities{}; +#endif PerIsolateData() {} @@ -41,6 +67,12 @@ class PerIsolateData { Nan::Global& AllocationNodeConstructor(); Nan::Global& TimeProfileNodeConstructor(); std::shared_ptr& GetHeapProfilerState(); +#if DD_V8_HAS_DICTIONARY_TEMPLATE + v8::Local GetDictionaryTemplate( + v8::Isolate* isolate, + DictionaryTemplateId id, + v8::MemorySpan names); +#endif }; } // namespace dd diff --git a/bindings/profilers/wall.cc b/bindings/profilers/wall.cc index 0187af47..32e8ef7a 100644 --- a/bindings/profilers/wall.cc +++ b/bindings/profilers/wall.cc @@ -20,9 +20,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -33,6 +35,12 @@ #include "translate-time-profile.hh" #include "wall.hh" +// #if on an undefined macro is 0, which would silently drop the fast path. +#ifndef DD_V8_HAS_DICTIONARY_TEMPLATE +#error \ + "DD_V8_HAS_DICTIONARY_TEMPLATE undefined; per-isolate-data.hh not included" +#endif + #ifndef _WIN32 #define DD_WALL_USE_SIGPROF true @@ -493,6 +501,14 @@ void WallProfiler::Cleanup(Isolate* isolate) { } } +// NewInstance matches values to names by position; generating both from this +// one list keeps them in sync. +#define DD_SAMPLE_CONTEXT_FIELDS \ + X(timestamp) \ + X(cpuTime) \ + X(context) \ + X(asyncId) + ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile, ContextBuffer& contexts, int64_t startCpuTime) { @@ -511,10 +527,30 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile, // iteration index int deltaIdx = 0; - auto contextKey = String::NewFromUtf8Literal(isolate, "context"); - auto timestampKey = String::NewFromUtf8Literal(isolate, "timestamp"); - auto cpuTimeKey = String::NewFromUtf8Literal(isolate, "cpuTime"); - auto asyncIdKey = String::NewFromUtf8Literal(isolate, "asyncId"); + Local undefined = Undefined(isolate); +#if DD_V8_HAS_DICTIONARY_TEMPLATE +#define X(name) #name, + static constexpr std::string_view kNames[] = {DD_SAMPLE_CONTEXT_FIELDS}; +#undef X + auto tmpl = PerIsolateData::For(isolate)->GetDictionaryTemplate( + isolate, DictionaryTemplateId::kWallSampleContext, kNames); + + auto newSampleContext = [&](auto& values) { + return tmpl->NewInstance(v8Context, values); + }; +#else +#define X(name) String::NewFromUtf8Literal(isolate, #name), + Local keys[] = {DD_SAMPLE_CONTEXT_FIELDS}; +#undef X + + auto newSampleContext = [&](auto& values) { + auto object = Object::New(isolate); + for (size_t i = 0; i < std::size(values); i++) { + object->Set(v8Context, keys[i], values[i].ToLocalChecked()).Check(); + } + return object; + }; +#endif auto V8toEpochOffset = GetV8ToEpochOffset(); auto lastCpuTime = startCpuTime; @@ -565,44 +601,36 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile, array = it->second.contexts; ++it->second.hitcount; } - // Conforms to TimeProfileNodeContext defined in v8-types.ts - Local timedContext = Object::New(isolate); - timedContext - ->Set(v8Context, - timestampKey, - BigInt::New(isolate, sampleTimestamp + V8toEpochOffset)) - .Check(); + Local timestamp = + BigInt::New(isolate, sampleTimestamp + V8toEpochOffset); + Local cpuTime = undefined; + Local context = undefined; + Local asyncId = undefined; + auto* function_name = sample->GetFunctionNameStr(); // If current sample is program, reports its cpu time to the next sample if (strcmp(function_name, "(program)") != 0) { if (collectCpuTime_) { - timedContext - ->Set( - v8Context, - cpuTimeKey, - Number::New(isolate, sampleContext.cpu_time - lastCpuTime)) - .Check(); + cpuTime = + Number::New(isolate, sampleContext.cpu_time - lastCpuTime); lastCpuTime = sampleContext.cpu_time; } // If current sample is neither program nor idle, associate a sampling // context and async ID if (strcmp(function_name, "(idle)") != 0) { if (sampleContext.context) { - timedContext - ->Set(v8Context, - contextKey, - sampleContext.context.get()->Get(isolate)) - .Check(); + context = sampleContext.context.get()->Get(isolate); } if (collectAsyncId_) { - timedContext - ->Set(v8Context, - asyncIdKey, - Number::New(isolate, sampleContext.async_id)) - .Check(); + asyncId = Number::New(isolate, sampleContext.async_id); } } } + +#define X(name) name, + MaybeLocal values[] = {DD_SAMPLE_CONTEXT_FIELDS}; +#undef X + auto timedContext = newSampleContext(values); array->Set(v8Context, array->Length(), timedContext).Check(); // Sample context was consumed, fetch the next one @@ -614,6 +642,7 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile, return contextsByNode; } +#undef DD_SAMPLE_CONTEXT_FIELDS void GCPrologueCallback(Isolate* isolate, GCType type,