Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions bindings/per-isolate-data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#include <cassert>
#include <mutex>
#include <unordered_map>
#include <utility>
Expand Down Expand Up @@ -64,4 +65,22 @@ std::shared_ptr<HeapProfilerState>& PerIsolateData::GetHeapProfilerState() {
return heap_profiler_state;
}

#if DD_V8_HAS_DICTIONARY_TEMPLATE
v8::Local<v8::DictionaryTemplate> PerIsolateData::GetDictionaryTemplate(
v8::Isolate* isolate,
DictionaryTemplateId id,
v8::MemorySpan<const std::string_view> names) {
const size_t index = static_cast<size_t>(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
32 changes: 32 additions & 0 deletions bindings/per-isolate-data.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,45 @@

#include <nan.h>
#include <node.h>
#include <v8-version.h>
#include <v8.h>
#include <array>
#include <cstddef>
#include <memory>
#include <string_view>

// 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<size_t>(DictionaryTemplateId::kCount);
#endif

class PerIsolateData {
private:
Nan::Global<v8::Function> wall_profiler_constructor;
Nan::Global<v8::Function> allocation_node_constructor;
Nan::Global<v8::Function> time_profile_node_constructor;
std::shared_ptr<HeapProfilerState> heap_profiler_state;
#if DD_V8_HAS_DICTIONARY_TEMPLATE
std::array<Nan::Global<v8::DictionaryTemplate>, kDictionaryTemplateCount>
dictionary_templates;
std::array<size_t, kDictionaryTemplateCount> dictionary_template_arities{};
#endif

PerIsolateData() {}

Expand All @@ -41,6 +67,12 @@ class PerIsolateData {
Nan::Global<v8::Function>& AllocationNodeConstructor();
Nan::Global<v8::Function>& TimeProfileNodeConstructor();
std::shared_ptr<HeapProfilerState>& GetHeapProfilerState();
#if DD_V8_HAS_DICTIONARY_TEMPLATE
v8::Local<v8::DictionaryTemplate> GetDictionaryTemplate(
v8::Isolate* isolate,
DictionaryTemplateId id,
v8::MemorySpan<const std::string_view> names);
#endif
};

} // namespace dd
83 changes: 56 additions & 27 deletions bindings/profilers/wall.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
#include <v8-profiler.h>
#include <cinttypes>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <mutex>
#include <string_view>
#include <type_traits>
#include <unordered_set>
#include <vector>
Expand All @@ -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

Expand Down Expand Up @@ -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) {
Expand All @@ -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<Value> 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<String> 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;

Expand Down Expand Up @@ -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<Object> timedContext = Object::New(isolate);
timedContext
->Set(v8Context,
timestampKey,
BigInt::New(isolate, sampleTimestamp + V8toEpochOffset))
.Check();
Local<Value> timestamp =
BigInt::New(isolate, sampleTimestamp + V8toEpochOffset);
Local<Value> cpuTime = undefined;
Local<Value> context = undefined;
Local<Value> 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<Value> 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
Expand All @@ -614,6 +642,7 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile,

return contextsByNode;
}
#undef DD_SAMPLE_CONTEXT_FIELDS

void GCPrologueCallback(Isolate* isolate,
GCType type,
Expand Down
Loading