Skip to content

Commit 20b25d8

Browse files
committed
deps: add HeapProfileSampleLabelsCallback to V8 profiler
Add a callback mechanism to V8's SamplingHeapProfiler that allows embedders to attach key-value string labels to allocation samples. The callback receives the ContinuationPreservedEmbedderData (CPED) and writes labels into the sample. This enables per-context memory attribution (e.g., per-HTTP-route) when combined with AsyncLocalStorage. Changes: - AllocationProfile::Sample gains a `labels` field - New HeapProfileSampleLabelsCallback typedef on HeapProfiler - SamplingHeapProfiler invokes the callback in SampleObject() - BuildSamples() copies labels to public Sample structs - Gated behind V8_HEAP_PROFILER_SAMPLE_LABELS (requires CPED) Signed-off-by: Rudolf Meijering <skaapgif@gmail.com>
1 parent 511a57a commit 20b25d8

6 files changed

Lines changed: 129 additions & 16 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: 34 additions & 3 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,30 @@ 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, invoke it to capture
103+
// context labels (e.g., route name from AsyncLocalStorage).
104+
// V8 reads the CPED and passes it + the stored data pointer to the callback.
105+
// The callback runs inside DisallowGarbageCollection — it must only use
106+
// malloc-based allocations (std::string), not V8 heap allocations.
107+
{
108+
HeapProfiler* hp = isolate_->heap()->heap_profiler();
109+
auto labels_callback = hp->sample_labels_callback();
110+
if (labels_callback) {
111+
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
112+
v8::Local<v8::Value> context =
113+
v8_isolate->GetContinuationPreservedEmbedderData();
114+
if (!context.IsEmpty() && !context->IsUndefined()) {
115+
std::vector<std::pair<std::string, std::string>> labels;
116+
if (labels_callback(hp->sample_labels_data(), context, &labels)) {
117+
sample->labels = std::move(labels);
118+
}
119+
}
120+
}
121+
}
122+
#endif // V8_HEAP_PROFILER_SAMPLE_LABELS
123+
99124
sample->global.SetWeak(sample.get(), OnWeakCallback,
100125
WeakCallbackType::kParameter);
101126
samples_.emplace(sample.get(), std::move(sample));
@@ -312,9 +337,15 @@ SamplingHeapProfiler::BuildSamples() const {
312337
samples.reserve(samples_.size());
313338
for (const auto& it : samples_) {
314339
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});
340+
#ifdef V8_HEAP_PROFILER_SAMPLE_LABELS
341+
samples.emplace_back(sample->owner->id_, sample->size,
342+
ScaleSample(sample->size, 1).count,
343+
sample->sample_id, sample->labels);
344+
#else
345+
samples.emplace_back(sample->owner->id_, sample->size,
346+
ScaleSample(sample->size, 1).count,
347+
sample->sample_id);
348+
#endif
318349
}
319350
return samples;
320351
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ 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;
117118
};
118119

119120
SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate,

tools/v8_gypfiles/features.gypi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,12 @@
523523
'defines': ['V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS',],
524524
}],
525525
['v8_enable_continuation_preserved_embedder_data==1', {
526-
'defines': ['V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA',],
526+
'defines': [
527+
'V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA',
528+
# Enable heap profiler sample labels for per-context memory
529+
# attribution when CPED is available.
530+
'V8_HEAP_PROFILER_SAMPLE_LABELS',
531+
],
527532
}],
528533
['v8_enable_allocation_folding==1', {
529534
'defines': ['V8_ALLOCATION_FOLDING',],

0 commit comments

Comments
 (0)