@@ -58,6 +58,8 @@ using namespace v8;
58
58
59
59
namespace dd {
60
60
61
+ using ContextPtr = std::shared_ptr<Global<Value>>;
62
+
61
63
// Maximum number of rounds in the GetV8ToEpochOffset
62
64
static constexpr int MAX_EPOCH_OFFSET_ATTEMPTS = 20 ;
63
65
@@ -331,8 +333,7 @@ void SignalHandler::HandleProfilerSignal(int sig,
331
333
auto time_from = Now ();
332
334
old_handler (sig, info, context);
333
335
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);
336
337
}
337
338
#else
338
339
class SignalHandler {
@@ -543,8 +544,10 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
543
544
bool workaroundV8Bug,
544
545
bool collectCpuTime,
545
546
bool collectAsyncId,
546
- bool isMainThread)
547
+ bool isMainThread,
548
+ bool useCPED)
547
549
: samplingPeriod_(samplingPeriod),
550
+ useCPED_ (useCPED),
548
551
includeLines_(includeLines),
549
552
withContexts_(withContexts),
550
553
isMainThread_(isMainThread) {
@@ -555,6 +558,11 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
555
558
workaroundV8Bug_ = workaroundV8Bug && DD_WALL_USE_SIGPROF && detectV8Bug_;
556
559
collectCpuTime_ = collectCpuTime && withContexts;
557
560
collectAsyncId_ = collectAsyncId && withContexts;
561
+ #if NODE_MAJOR_VERSION >= 23
562
+ useCPED_ = useCPED && withContexts;
563
+ #else
564
+ useCPED_ = false ;
565
+ #endif
558
566
559
567
if (withContexts_) {
560
568
contexts_.reserve (duration * 2 / samplingPeriod);
@@ -574,10 +582,18 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
574
582
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
575
583
std::fill (fields_, fields_ + kFieldCount , 0 );
576
584
577
- if (collectAsyncId_) {
585
+ if (collectAsyncId_ || useCPED_ ) {
578
586
isolate->AddGCPrologueCallback (&GCPrologueCallback, this );
579
587
isolate->AddGCEpilogueCallback (&GCEpilogueCallback, this );
580
588
}
589
+
590
+ if (useCPED_) {
591
+ cpedSymbol_.Reset (
592
+ isolate,
593
+ Private::ForApi (isolate,
594
+ String::NewFromUtf8Literal (
595
+ isolate, " dd::WallProfiler::cpedSymbol_" )));
596
+ }
581
597
}
582
598
583
599
void WallProfiler::Dispose (Isolate* isolate, bool removeFromMap) {
@@ -589,16 +605,56 @@ void WallProfiler::Dispose(Isolate* isolate, bool removeFromMap) {
589
605
g_profilers.RemoveProfiler (isolate, this );
590
606
}
591
607
592
- if (collectAsyncId_) {
608
+ if (collectAsyncId_ || useCPED_ ) {
593
609
isolate->RemoveGCPrologueCallback (&GCPrologueCallback, this );
594
610
isolate->RemoveGCEpilogueCallback (&GCEpilogueCallback, this );
595
611
}
596
612
597
613
node::RemoveEnvironmentCleanupHook (
598
614
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 ();
599
624
}
600
625
}
601
626
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
+
602
658
#define DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (name ) \
603
659
auto name##Value = \
604
660
Nan::Get (arg, Nan::New<v8::String>(#name).ToLocalChecked()); \
@@ -651,6 +707,14 @@ NAN_METHOD(WallProfiler::New) {
651
707
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (collectCpuTime);
652
708
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (collectAsyncId);
653
709
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
654
718
655
719
if (withContexts && !DD_WALL_USE_SIGPROF) {
656
720
return Nan::ThrowTypeError (" Contexts are not supported." );
@@ -690,7 +754,8 @@ NAN_METHOD(WallProfiler::New) {
690
754
workaroundV8Bug,
691
755
collectCpuTime,
692
756
collectAsyncId,
693
- isMainThread);
757
+ isMainThread,
758
+ useCPED);
694
759
obj->Wrap (info.This ());
695
760
info.GetReturnValue ().Set (info.This ());
696
761
} else {
@@ -1004,14 +1069,124 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
1004
1069
return cpuProfiler_;
1005
1070
}
1006
1071
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);
1011
1078
}
1012
1079
1013
1080
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
1014
1133
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
1015
1190
}
1016
1191
1017
1192
NAN_GETTER (WallProfiler::GetContext) {
@@ -1044,6 +1219,10 @@ NAN_METHOD(WallProfiler::Dispose) {
1044
1219
}
1045
1220
1046
1221
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
+ }
1047
1226
#if NODE_MAJOR_VERSION >= 24
1048
1227
HandleScope scope (isolate);
1049
1228
auto context = isolate->GetEnteredOrMicrotaskContext ();
@@ -1069,27 +1248,39 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
1069
1248
auto curCount = gcCount.load (std::memory_order_relaxed);
1070
1249
std::atomic_signal_fence (std::memory_order_acquire);
1071
1250
if (curCount == 0 ) {
1072
- gcAsyncId = GetAsyncIdNoGC (isolate);
1251
+ if (collectAsyncId_) {
1252
+ gcAsyncId = GetAsyncIdNoGC (isolate);
1253
+ }
1254
+ if (useCPED_) {
1255
+ gcContext = GetContextPtrSignalSafe (isolate);
1256
+ }
1073
1257
}
1074
1258
std::atomic_signal_fence (std::memory_order_release);
1075
1259
gcCount.store (curCount + 1 , std::memory_order_relaxed);
1076
1260
}
1077
1261
1078
1262
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
+ }
1080
1269
}
1081
1270
1082
1271
void WallProfiler::PushContext (int64_t time_from,
1083
1272
int64_t time_to,
1084
1273
int64_t cpu_time,
1085
- double async_id ) {
1274
+ Isolate* isolate ) {
1086
1275
// Be careful this is called in a signal handler context therefore all
1087
1276
// operations must be async signal safe (in particular no allocations).
1088
1277
// Our ring buffer avoids allocations.
1089
- auto context = curContext_.Get ();
1090
- std::atomic_signal_fence (std::memory_order_acquire);
1091
1278
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)});
1093
1284
std::atomic_fetch_add_explicit (
1094
1285
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1095
1286
1U ,
0 commit comments