Skip to content

Commit e8e5f80

Browse files
committed
deps: add heap profile sample labels to V8 profiler
Add a callback mechanism to V8's SamplingHeapProfiler that allows embedders to attach key-value string labels to allocation samples. At allocation time, SampleObject() captures the ContinuationPreservedEmbedderData (CPED) as a Global<Value> on each internal Sample. At profile-read time, BuildSamples() invokes the registered HeapProfileSampleLabelsCallback with each sample's stored CPED, allowing embedders to resolve labels from the async context. This two-phase approach (capture at allocation, resolve at read) avoids running embedder callbacks inside DisallowGarbageCollection scopes and is immune to CPED identity changes caused by unrelated AsyncLocalStorage stores. Changes: - AllocationProfile::Sample gains a `labels` field - New HeapProfileSampleLabelsCallback typedef on HeapProfiler - Internal Sample stores `Global<Value> cped` at allocation time - BuildSamples() invokes labels callback with stored CPED - Seven cctests covering callback API, GC behavior, unregistration - features.gypi enables v8_enable_continuation_preserved_embedder_data Signed-off-by: Rudolf Meijering <skaapgif@gmail.com>
1 parent 511a57a commit e8e5f80

7 files changed

Lines changed: 492 additions & 18 deletions

File tree

deps/v8/include/v8-profiler.h

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
#include <unordered_set>
1212
#include <vector>
1313

14+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
15+
#include <string>
16+
#include <utility>
17+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
18+
1419
#include "cppgc/common.h" // NOLINT(build/include_directory)
1520
#include "v8-local-handle.h" // NOLINT(build/include_directory)
1621
#include "v8-message.h" // NOLINT(build/include_directory)
@@ -791,26 +796,39 @@ class V8_EXPORT AllocationProfile {
791796
* Represent a single sample recorded for an allocation.
792797
*/
793798
struct Sample {
794-
/**
795-
* id of the node in the profile tree.
796-
*/
799+
Sample(uint32_t node_id, size_t size, unsigned int count,
800+
uint64_t sample_id)
801+
: node_id(node_id), size(size), count(count), sample_id(sample_id) {}
802+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
803+
Sample(uint32_t node_id, size_t size, unsigned int count,
804+
uint64_t sample_id,
805+
std::vector<std::pair<std::string, std::string>> labels)
806+
: node_id(node_id),
807+
size(size),
808+
count(count),
809+
sample_id(sample_id),
810+
labels(std::move(labels)) {}
811+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
812+
813+
/** id of the node in the profile tree. */
797814
uint32_t node_id;
798-
799-
/**
800-
* Size of the sampled allocation object.
801-
*/
815+
/** Size of the sampled allocation object. */
802816
size_t size;
803-
804-
/**
805-
* The number of objects of such size that were sampled.
806-
*/
817+
/** The number of objects of such size that were sampled. */
807818
unsigned int count;
808-
809819
/**
810820
* Unique time-ordered id of the allocation sample. Can be used to track
811821
* what samples were added or removed between two snapshots.
812822
*/
813823
uint64_t sample_id;
824+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
825+
/**
826+
* Embedder-provided labels captured at allocation time via the
827+
* HeapProfileSampleLabelsCallback. Each pair is (key, value).
828+
* Empty if no callback is registered or the callback returned no labels.
829+
*/
830+
std::vector<std::pair<std::string, std::string>> labels;
831+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
814832
};
815833

816834
/**
@@ -1001,6 +1019,24 @@ class V8_EXPORT HeapProfiler {
10011019
v8::Isolate* isolate, const v8::Local<v8::Value>& v8_value,
10021020
uint16_t class_id, void* data);
10031021

1022+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
1023+
/**
1024+
* Callback invoked during sampling heap profiler allocation events to
1025+
* retrieve embedder-defined labels for the current execution context.
1026+
*
1027+
* |data| is the opaque pointer passed to SetHeapProfileSampleLabelsCallback.
1028+
* |context| is the ContinuationPreservedEmbedderData (CPED) value, which
1029+
* the embedder can use to look up the current async context (e.g., route).
1030+
*
1031+
* Write labels to out_labels and return true, or return false if no labels
1032+
* apply. The caller provides a stack-local vector; returning false avoids
1033+
* any heap allocation on the hot path.
1034+
*/
1035+
using HeapProfileSampleLabelsCallback = bool (*)(
1036+
void* data, v8::Local<v8::Value> context,
1037+
std::vector<std::pair<std::string, std::string>>* out_labels);
1038+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
1039+
10041040
/** Returns the number of snapshots taken. */
10051041
int GetSnapshotCount();
10061042

@@ -1261,6 +1297,18 @@ class V8_EXPORT HeapProfiler {
12611297

12621298
void SetGetDetachednessCallback(GetDetachednessCallback callback, void* data);
12631299

1300+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
1301+
/**
1302+
* Registers a callback that the sampling heap profiler invokes on each
1303+
* allocation to retrieve embedder-defined string labels. The labels are
1304+
* stored on AllocationProfile::Sample::labels.
1305+
*
1306+
* Pass nullptr to clear the callback.
1307+
*/
1308+
void SetHeapProfileSampleLabelsCallback(
1309+
HeapProfileSampleLabelsCallback callback, void* data = nullptr);
1310+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
1311+
12641312
/**
12651313
* Returns whether the heap profiler is currently taking a snapshot.
12661314
*/

deps/v8/src/api/api.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11829,6 +11829,14 @@ void HeapProfiler::SetGetDetachednessCallback(GetDetachednessCallback callback,
1182911829
data);
1183011830
}
1183111831

11832+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
11833+
void HeapProfiler::SetHeapProfileSampleLabelsCallback(
11834+
HeapProfileSampleLabelsCallback callback, void* data) {
11835+
reinterpret_cast<i::HeapProfiler*>(this)
11836+
->SetHeapProfileSampleLabelsCallback(callback, data);
11837+
}
11838+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
11839+
1183211840
bool HeapProfiler::IsTakingSnapshot() {
1183311841
return reinterpret_cast<i::HeapProfiler*>(this)->IsTakingSnapshot();
1183411842
}

deps/v8/src/profiler/heap-profiler.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@ class HeapProfiler : public HeapObjectAllocationTracker {
7979
bool is_sampling_allocations() { return !!sampling_heap_profiler_; }
8080
AllocationProfile* GetAllocationProfile();
8181

82+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
83+
void SetHeapProfileSampleLabelsCallback(
84+
v8::HeapProfiler::HeapProfileSampleLabelsCallback callback,
85+
void* data) {
86+
sample_labels_callback_ = callback;
87+
sample_labels_data_ = data;
88+
}
89+
90+
v8::HeapProfiler::HeapProfileSampleLabelsCallback
91+
sample_labels_callback() const {
92+
return sample_labels_callback_;
93+
}
94+
void* sample_labels_data() const { return sample_labels_data_; }
95+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
96+
8297
void StartHeapObjectsTracking(bool track_allocations);
8398
void StopHeapObjectsTracking();
8499
AllocationTracker* allocation_tracker() const {
@@ -176,6 +191,11 @@ class HeapProfiler : public HeapObjectAllocationTracker {
176191
std::pair<v8::HeapProfiler::GetDetachednessCallback, void*>
177192
get_detachedness_callback_;
178193
std::unique_ptr<HeapProfilerNativeMoveListener> native_move_listener_;
194+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
195+
v8::HeapProfiler::HeapProfileSampleLabelsCallback sample_labels_callback_ =
196+
nullptr;
197+
void* sample_labels_data_ = nullptr;
198+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
179199
};
180200

181201
} // namespace internal

deps/v8/src/profiler/sampling-heap-profiler.cc

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "src/execution/isolate.h"
1616
#include "src/heap/heap-layout-inl.h"
1717
#include "src/heap/heap.h"
18+
#include "src/profiler/heap-profiler.h"
1819
#include "src/profiler/strings-storage.h"
1920

2021
namespace v8 {
@@ -96,6 +97,26 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
9697
node->allocations_[size]++;
9798
auto sample =
9899
std::make_unique<Sample>(size, node, loc, this, next_sample_id());
100+
101+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
102+
// If an embedder labels callback is registered, capture the CPED
103+
// (ContinuationPreservedEmbedderData) for later label resolution in
104+
// BuildSamples(). Storing as Global keeps the AsyncContextFrame alive
105+
// and prevents it from being GC'd while the sample exists.
106+
// Global::Reset is safe inside DisallowGC (uses malloc, not V8 heap).
107+
{
108+
HeapProfiler* hp = isolate_->heap()->heap_profiler();
109+
if (hp->sample_labels_callback()) {
110+
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
111+
v8::Local<v8::Value> context =
112+
v8_isolate->GetContinuationPreservedEmbedderData();
113+
if (!context.IsEmpty() && !context->IsUndefined()) {
114+
sample->cped.Reset(v8_isolate, context);
115+
}
116+
}
117+
}
118+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
119+
99120
sample->global.SetWeak(sample.get(), OnWeakCallback,
100121
WeakCallbackType::kParameter);
101122
samples_.emplace(sample.get(), std::move(sample));
@@ -307,14 +328,34 @@ v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
307328
}
308329

309330
const std::vector<v8::AllocationProfile::Sample>
310-
SamplingHeapProfiler::BuildSamples() const {
331+
SamplingHeapProfiler::BuildSamples() {
311332
std::vector<v8::AllocationProfile::Sample> samples;
312333
samples.reserve(samples_.size());
334+
335+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
336+
HeapProfiler* hp = heap_->heap_profiler();
337+
auto callback = hp->sample_labels_callback();
338+
void* callback_data = hp->sample_labels_data();
339+
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
340+
#endif
341+
313342
for (const auto& it : samples_) {
314343
const Sample* sample = it.second.get();
315-
samples.emplace_back(v8::AllocationProfile::Sample{
316-
sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
317-
sample->sample_id});
344+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
345+
std::vector<std::pair<std::string, std::string>> labels;
346+
if (callback && !sample->cped.IsEmpty()) {
347+
HandleScope scope(isolate_);
348+
v8::Local<v8::Value> cped_local = sample->cped.Get(v8_isolate);
349+
callback(callback_data, cped_local, &labels);
350+
}
351+
samples.emplace_back(sample->owner->id_, sample->size,
352+
ScaleSample(sample->size, 1).count,
353+
sample->sample_id, std::move(labels));
354+
#else
355+
samples.emplace_back(sample->owner->id_, sample->size,
356+
ScaleSample(sample->size, 1).count,
357+
sample->sample_id);
358+
#endif
318359
}
319360
return samples;
320361
}

deps/v8/src/profiler/sampling-heap-profiler.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ class SamplingHeapProfiler {
114114
Global<Value> global;
115115
SamplingHeapProfiler* const profiler;
116116
const uint64_t sample_id;
117+
std::vector<std::pair<std::string, std::string>> labels;
118+
// ContinuationPreservedEmbedderData captured at allocation time.
119+
// Stored as Global to prevent GC of the AsyncContextFrame while
120+
// the sample exists. Labels are resolved from this at read time
121+
// (in BuildSamples) via the registered labels callback.
122+
Global<Value> cped;
117123
};
118124

119125
SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate,
@@ -160,7 +166,7 @@ class SamplingHeapProfiler {
160166

161167
void SampleObject(Address soon_object, size_t size);
162168

163-
const std::vector<v8::AllocationProfile::Sample> BuildSamples() const;
169+
const std::vector<v8::AllocationProfile::Sample> BuildSamples();
164170

165171
AllocationNode* FindOrAddChildNode(AllocationNode* parent, const char* name,
166172
int script_id, int start_position);

0 commit comments

Comments
 (0)