Skip to content

Commit 30a5bcf

Browse files
committed
Allow use of CPED to store sampling context
1 parent 1ee92d0 commit 30a5bcf

File tree

3 files changed

+233
-22
lines changed

3 files changed

+233
-22
lines changed

bindings/profilers/wall.cc

Lines changed: 204 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ using namespace v8;
5858

5959
namespace dd {
6060

61+
using ContextPtr = std::shared_ptr<Global<Value>>;
62+
6163
// Maximum number of rounds in the GetV8ToEpochOffset
6264
static constexpr int MAX_EPOCH_OFFSET_ATTEMPTS = 20;
6365

@@ -331,8 +333,7 @@ void SignalHandler::HandleProfilerSignal(int sig,
331333
auto time_from = Now();
332334
old_handler(sig, info, context);
333335
auto time_to = Now();
334-
auto async_id = prof->GetAsyncId(isolate);
335-
prof->PushContext(time_from, time_to, cpu_time, async_id);
336+
prof->PushContext(time_from, time_to, cpu_time, isolate);
336337
}
337338
#else
338339
class SignalHandler {
@@ -543,8 +544,10 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
543544
bool workaroundV8Bug,
544545
bool collectCpuTime,
545546
bool collectAsyncId,
546-
bool isMainThread)
547+
bool isMainThread,
548+
bool useCPED)
547549
: samplingPeriod_(samplingPeriod),
550+
useCPED_(useCPED),
548551
includeLines_(includeLines),
549552
withContexts_(withContexts),
550553
isMainThread_(isMainThread) {
@@ -555,6 +558,11 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
555558
workaroundV8Bug_ = workaroundV8Bug && DD_WALL_USE_SIGPROF && detectV8Bug_;
556559
collectCpuTime_ = collectCpuTime && withContexts;
557560
collectAsyncId_ = collectAsyncId && withContexts;
561+
#if NODE_MAJOR_VERSION >= 23
562+
useCPED_ = useCPED && withContexts;
563+
#else
564+
useCPED_ = false;
565+
#endif
558566

559567
if (withContexts_) {
560568
contexts_.reserve(duration * 2 / samplingPeriod);
@@ -574,10 +582,18 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
574582
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
575583
std::fill(fields_, fields_ + kFieldCount, 0);
576584

577-
if (collectAsyncId_) {
585+
if (collectAsyncId_ || useCPED_) {
578586
isolate->AddGCPrologueCallback(&GCPrologueCallback, this);
579587
isolate->AddGCEpilogueCallback(&GCEpilogueCallback, this);
580588
}
589+
590+
if (useCPED_) {
591+
cpedSymbol_.Reset(
592+
isolate,
593+
Private::ForApi(isolate,
594+
String::NewFromUtf8Literal(
595+
isolate, "dd::WallProfiler::cpedSymbol_")));
596+
}
581597
}
582598

583599
void WallProfiler::Dispose(Isolate* isolate, bool removeFromMap) {
@@ -589,16 +605,56 @@ void WallProfiler::Dispose(Isolate* isolate, bool removeFromMap) {
589605
g_profilers.RemoveProfiler(isolate, this);
590606
}
591607

592-
if (collectAsyncId_) {
608+
if (collectAsyncId_ || useCPED_) {
593609
isolate->RemoveGCPrologueCallback(&GCPrologueCallback, this);
594610
isolate->RemoveGCEpilogueCallback(&GCEpilogueCallback, this);
595611
}
596612

597613
node::RemoveEnvironmentCleanupHook(
598614
isolate, &WallProfiler::CleanupHook, isolate);
615+
616+
for (auto it = liveContextPtrs_.begin(); it != liveContextPtrs_.end();) {
617+
auto ptr = *it;
618+
ptr->UnregisterFromGC();
619+
delete ptr;
620+
++it;
621+
}
622+
liveContextPtrs_.clear();
623+
deadContextPtrs_.clear();
599624
}
600625
}
601626

627+
class PersistentContextPtr : AtomicContextPtr {
628+
std::vector<PersistentContextPtr*>* dead;
629+
Persistent<Object> per;
630+
631+
PersistentContextPtr(std::vector<PersistentContextPtr*>* dead) : dead(dead) {}
632+
633+
void UnregisterFromGC() {
634+
if (!per.IsEmpty()) {
635+
per.ClearWeak();
636+
per.Reset();
637+
}
638+
}
639+
640+
void MarkDead() { dead->push_back(this); }
641+
642+
void RegisterForGC(Isolate* isolate, const Local<Object>& obj) {
643+
// Register a callback to delete this object when the object is GCed
644+
per.Reset(isolate, obj);
645+
per.SetWeak(
646+
this,
647+
[](const WeakCallbackInfo<PersistentContextPtr>& data) {
648+
auto ptr = data.GetParameter();
649+
ptr->MarkDead();
650+
ptr->UnregisterFromGC();
651+
},
652+
WeakCallbackType::kParameter);
653+
}
654+
655+
friend class WallProfiler;
656+
};
657+
602658
#define DD_WALL_PROFILER_GET_BOOLEAN_CONFIG(name) \
603659
auto name##Value = \
604660
Nan::Get(arg, Nan::New<v8::String>(#name).ToLocalChecked()); \
@@ -651,6 +707,14 @@ NAN_METHOD(WallProfiler::New) {
651707
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG(collectCpuTime);
652708
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG(collectAsyncId);
653709
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG(isMainThread);
710+
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG(useCPED);
711+
712+
#if NODE_MAJOR_VERSION < 23
713+
if (useCPED) {
714+
return Nan::ThrowTypeError(
715+
"useCPED is not supported on this Node.js version.");
716+
}
717+
#endif
654718

655719
if (withContexts && !DD_WALL_USE_SIGPROF) {
656720
return Nan::ThrowTypeError("Contexts are not supported.");
@@ -690,7 +754,8 @@ NAN_METHOD(WallProfiler::New) {
690754
workaroundV8Bug,
691755
collectCpuTime,
692756
collectAsyncId,
693-
isMainThread);
757+
isMainThread,
758+
useCPED);
694759
obj->Wrap(info.This());
695760
info.GetReturnValue().Set(info.This());
696761
} else {
@@ -1005,14 +1070,124 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
10051070
return cpuProfiler_;
10061071
}
10071072

1008-
v8::Local<v8::Value> WallProfiler::GetContext(Isolate* isolate) {
1009-
auto context = curContext_.Get();
1010-
if (!context) return v8::Undefined(isolate);
1011-
return context->Get(isolate);
1073+
Local<Value> WallProfiler::GetContext(Isolate* isolate) {
1074+
auto context = GetContextPtr(isolate);
1075+
if (context) {
1076+
return context->Get(isolate);
1077+
}
1078+
return Undefined(isolate);
10121079
}
10131080

10141081
void WallProfiler::SetContext(Isolate* isolate, Local<Value> value) {
1082+
#if NODE_MAJOR_VERSION >= 23
1083+
if (!useCPED_) {
1084+
curContext_.Set(isolate, value);
1085+
return;
1086+
}
1087+
1088+
// Clean up dead context pointers
1089+
for (auto it = deadContextPtrs_.begin(); it != deadContextPtrs_.end();) {
1090+
auto ptr = *it;
1091+
liveContextPtrs_.erase(ptr);
1092+
delete ptr;
1093+
++it;
1094+
}
1095+
deadContextPtrs_.clear();
1096+
1097+
auto cped = isolate->GetContinuationPreservedEmbedderData();
1098+
// No Node AsyncContextFrame in this continuation yet
1099+
if (!cped->IsObject()) return;
1100+
1101+
auto v8Ctx = isolate->GetCurrentContext();
1102+
// This should always be called from a V8 context, but check just in case.
1103+
if (v8Ctx.IsEmpty()) return;
1104+
1105+
auto cpedObj = cped.As<Object>();
1106+
auto localSymbol = cpedSymbol_.Get(isolate);
1107+
auto maybeProfData = cpedObj->GetPrivate(v8Ctx, localSymbol);
1108+
if (maybeProfData.IsEmpty()) return;
1109+
1110+
PersistentContextPtr* contextPtr = nullptr;
1111+
auto profData = maybeProfData.ToLocalChecked();
1112+
if (profData->IsUndefined()) {
1113+
contextPtr = new PersistentContextPtr(&deadContextPtrs_);
1114+
1115+
auto external = External::New(isolate, contextPtr);
1116+
setInProgress.store(true, std::memory_order_relaxed);
1117+
std::atomic_signal_fence(std::memory_order_release);
1118+
auto maybeSetResult = cpedObj->SetPrivate(v8Ctx, localSymbol, external);
1119+
std::atomic_signal_fence(std::memory_order_release);
1120+
setInProgress.store(false, std::memory_order_relaxed);
1121+
if (maybeSetResult.IsNothing()) {
1122+
delete contextPtr;
1123+
return;
1124+
}
1125+
liveContextPtrs_.insert(contextPtr);
1126+
contextPtr->RegisterForGC(isolate, cpedObj);
1127+
} else {
1128+
contextPtr =
1129+
static_cast<PersistentContextPtr*>(profData.As<External>()->Value());
1130+
}
1131+
1132+
contextPtr->Set(isolate, value);
1133+
#else
10151134
curContext_.Set(isolate, value);
1135+
#endif
1136+
}
1137+
1138+
ContextPtr WallProfiler::GetContextPtrSignalSafe(Isolate* isolate) {
1139+
auto isSetInProgress = setInProgress.load(std::memory_order_relaxed);
1140+
std::atomic_signal_fence(std::memory_order_acquire);
1141+
if (isSetInProgress) {
1142+
// New sample context is being set. Safe behavior is to not try attempt
1143+
// Object::Get on it and just return empty right now.
1144+
return ContextPtr();
1145+
}
1146+
1147+
if (useCPED_) {
1148+
auto curGcCount = gcCount.load(std::memory_order_relaxed);
1149+
std::atomic_signal_fence(std::memory_order_acquire);
1150+
if (curGcCount > 0) {
1151+
return gcContext;
1152+
}
1153+
}
1154+
1155+
return GetContextPtr(isolate);
1156+
}
1157+
1158+
ContextPtr WallProfiler::GetContextPtr(Isolate* isolate) {
1159+
#if NODE_MAJOR_VERSION >= 23
1160+
if (!useCPED_) {
1161+
return curContext_.Get();
1162+
}
1163+
1164+
if (!isolate->IsInUse()) {
1165+
// Must not try to create a handle scope if isolate is not in use.
1166+
return ContextPtr();
1167+
}
1168+
HandleScope scope(isolate);
1169+
1170+
auto cped = isolate->GetContinuationPreservedEmbedderData();
1171+
if (cped->IsObject()) {
1172+
auto v8Ctx = isolate->GetEnteredOrMicrotaskContext();
1173+
if (!v8Ctx.IsEmpty()) {
1174+
auto cpedObj = cped.As<Object>();
1175+
auto localSymbol = cpedSymbol_.Get(isolate);
1176+
auto maybeProfData = cpedObj->GetPrivate(v8Ctx, localSymbol);
1177+
if (!maybeProfData.IsEmpty()) {
1178+
auto profData = maybeProfData.ToLocalChecked();
1179+
if (!profData->IsUndefined()) {
1180+
return static_cast<PersistentContextPtr*>(
1181+
profData.As<External>()->Value())
1182+
->Get();
1183+
}
1184+
}
1185+
}
1186+
}
1187+
return ContextPtr();
1188+
#else
1189+
return curContext_.Get();
1190+
#endif
10161191
}
10171192

10181193
NAN_GETTER(WallProfiler::GetContext) {
@@ -1045,6 +1220,10 @@ NAN_METHOD(WallProfiler::Dispose) {
10451220
}
10461221

10471222
double GetAsyncIdNoGC(v8::Isolate* isolate) {
1223+
if (!isolate->IsInUse()) {
1224+
// Must not try to create a handle scope if isolate is not in use.
1225+
return -1;
1226+
}
10481227
#if NODE_MAJOR_VERSION >= 24
10491228
HandleScope scope(isolate);
10501229
auto context = isolate->GetEnteredOrMicrotaskContext();
@@ -1070,7 +1249,12 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
10701249
auto curCount = gcCount.load(std::memory_order_relaxed);
10711250
std::atomic_signal_fence(std::memory_order_acquire);
10721251
if (curCount == 0) {
1073-
gcAsyncId = GetAsyncIdNoGC(isolate);
1252+
if (collectAsyncId_) {
1253+
gcAsyncId = GetAsyncIdNoGC(isolate);
1254+
}
1255+
if (useCPED_) {
1256+
gcContext = GetContextPtrSignalSafe(isolate);
1257+
}
10741258
}
10751259
gcCount.store(curCount + 1, std::memory_order_relaxed);
10761260
std::atomic_signal_fence(std::memory_order_release);
@@ -1083,20 +1267,25 @@ void WallProfiler::OnGCEnd() {
10831267
std::atomic_signal_fence(std::memory_order_release);
10841268
if (newCount == 0) {
10851269
gcAsyncId = -1;
1270+
if (useCPED_) {
1271+
gcContext.reset();
1272+
}
10861273
}
10871274
}
10881275

10891276
void WallProfiler::PushContext(int64_t time_from,
10901277
int64_t time_to,
10911278
int64_t cpu_time,
1092-
double async_id) {
1279+
Isolate* isolate) {
10931280
// Be careful this is called in a signal handler context therefore all
10941281
// operations must be async signal safe (in particular no allocations).
10951282
// Our ring buffer avoids allocations.
1096-
auto context = curContext_.Get();
1097-
std::atomic_signal_fence(std::memory_order_acquire);
10981283
if (contexts_.size() < contexts_.capacity()) {
1099-
contexts_.push_back({context, time_from, time_to, cpu_time, async_id});
1284+
contexts_.push_back({GetContextPtrSignalSafe(isolate),
1285+
time_from,
1286+
time_to,
1287+
cpu_time,
1288+
GetAsyncId(isolate)});
11001289
std::atomic_fetch_add_explicit(
11011290
reinterpret_cast<std::atomic<uint32_t>*>(&fields_[kSampleCount]),
11021291
1U,

0 commit comments

Comments
 (0)