Skip to content

Commit c837e59

Browse files
committed
Allow use of CPED to store sampling context
1 parent 916d921 commit c837e59

File tree

3 files changed

+236
-23
lines changed

3 files changed

+236
-23
lines changed

bindings/profilers/wall.cc

Lines changed: 207 additions & 16 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 {
@@ -1004,14 +1069,124 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
10041069
return cpuProfiler_;
10051070
}
10061071

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

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

10171192
NAN_GETTER(WallProfiler::GetContext) {
@@ -1044,6 +1219,10 @@ NAN_METHOD(WallProfiler::Dispose) {
10441219
}
10451220

10461221
double GetAsyncIdNoGC(v8::Isolate* isolate) {
1222+
if (!isolate->IsInUse()) {
1223+
// Must not try to create a handle scope if isolate is not in use.
1224+
return -1;
1225+
}
10471226
#if NODE_MAJOR_VERSION >= 24
10481227
HandleScope scope(isolate);
10491228
auto context = isolate->GetEnteredOrMicrotaskContext();
@@ -1069,27 +1248,39 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
10691248
auto curCount = gcCount.load(std::memory_order_relaxed);
10701249
std::atomic_signal_fence(std::memory_order_acquire);
10711250
if (curCount == 0) {
1072-
gcAsyncId = GetAsyncIdNoGC(isolate);
1251+
if (collectAsyncId_) {
1252+
gcAsyncId = GetAsyncIdNoGC(isolate);
1253+
}
1254+
if (useCPED_) {
1255+
gcContext = GetContextPtrSignalSafe(isolate);
1256+
}
10731257
}
10741258
std::atomic_signal_fence(std::memory_order_release);
10751259
gcCount.store(curCount + 1, std::memory_order_relaxed);
10761260
}
10771261

10781262
void WallProfiler::OnGCEnd() {
1079-
gcCount.fetch_sub(1, std::memory_order_relaxed);
1263+
auto oldCount = gcCount.fetch_sub(1, std::memory_order_relaxed);
1264+
if (oldCount == 1 && useCPED_) {
1265+
// Not strictly necessary, as we'll reset it to something else on next GC,
1266+
// but why retain it longer than needed?
1267+
gcContext.reset();
1268+
}
10801269
}
10811270

10821271
void WallProfiler::PushContext(int64_t time_from,
10831272
int64_t time_to,
10841273
int64_t cpu_time,
1085-
double async_id) {
1274+
Isolate* isolate) {
10861275
// Be careful this is called in a signal handler context therefore all
10871276
// operations must be async signal safe (in particular no allocations).
10881277
// Our ring buffer avoids allocations.
1089-
auto context = curContext_.Get();
1090-
std::atomic_signal_fence(std::memory_order_acquire);
10911278
if (contexts_.size() < contexts_.capacity()) {
1092-
contexts_.push_back({context, time_from, time_to, cpu_time, async_id});
1279+
contexts_.push_back({GetContextPtrSignalSafe(isolate),
1280+
time_from,
1281+
time_to,
1282+
cpu_time,
1283+
GetAsyncId(isolate)});
10931284
std::atomic_fetch_add_explicit(
10941285
reinterpret_cast<std::atomic<uint32_t>*>(&fields_[kSampleCount]),
10951286
1U,

0 commit comments

Comments
 (0)