@@ -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 {
@@ -1005,14 +1070,124 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
1005
1070
return cpuProfiler_;
1006
1071
}
1007
1072
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);
1012
1079
}
1013
1080
1014
1081
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
1015
1134
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
1016
1191
}
1017
1192
1018
1193
NAN_GETTER (WallProfiler::GetContext) {
@@ -1045,6 +1220,10 @@ NAN_METHOD(WallProfiler::Dispose) {
1045
1220
}
1046
1221
1047
1222
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
+ }
1048
1227
#if NODE_MAJOR_VERSION >= 24
1049
1228
HandleScope scope (isolate);
1050
1229
auto context = isolate->GetEnteredOrMicrotaskContext ();
@@ -1070,7 +1249,12 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
1070
1249
auto curCount = gcCount.load (std::memory_order_relaxed);
1071
1250
std::atomic_signal_fence (std::memory_order_acquire);
1072
1251
if (curCount == 0 ) {
1073
- gcAsyncId = GetAsyncIdNoGC (isolate);
1252
+ if (collectAsyncId_) {
1253
+ gcAsyncId = GetAsyncIdNoGC (isolate);
1254
+ }
1255
+ if (useCPED_) {
1256
+ gcContext = GetContextPtrSignalSafe (isolate);
1257
+ }
1074
1258
}
1075
1259
gcCount.store (curCount + 1 , std::memory_order_relaxed);
1076
1260
std::atomic_signal_fence (std::memory_order_release);
@@ -1083,20 +1267,25 @@ void WallProfiler::OnGCEnd() {
1083
1267
std::atomic_signal_fence (std::memory_order_release);
1084
1268
if (newCount == 0 ) {
1085
1269
gcAsyncId = -1 ;
1270
+ if (useCPED_) {
1271
+ gcContext.reset ();
1272
+ }
1086
1273
}
1087
1274
}
1088
1275
1089
1276
void WallProfiler::PushContext (int64_t time_from,
1090
1277
int64_t time_to,
1091
1278
int64_t cpu_time,
1092
- double async_id ) {
1279
+ Isolate* isolate ) {
1093
1280
// Be careful this is called in a signal handler context therefore all
1094
1281
// operations must be async signal safe (in particular no allocations).
1095
1282
// Our ring buffer avoids allocations.
1096
- auto context = curContext_.Get ();
1097
- std::atomic_signal_fence (std::memory_order_acquire);
1098
1283
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)});
1100
1289
std::atomic_fetch_add_explicit (
1101
1290
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1102
1291
1U ,
0 commit comments