Skip to content

Commit 3ea13e8

Browse files
committed
deps: V8: cherry-pick 92ac9425fa52
Original commit message: [profiler] Adjustable heap profiler sampling interval This change makes the V8 sampling heap profiler interval adjustable while heap sampling is running. Today, the sampling interval is fixed for the whole profiling session. Developers who want to adapt sampling under high allocation load have to stop the profiler and start it again with a new interval, which loses continuity. This PR adds a small API surface for adaptive heap sampling: - SetSamplingHeapProfilerInterval(sample_interval) updates the active sampling interval. - GetSamplingHeapProfilerSamples() returns the current retained samples without materializing the full allocation profile tree. - AllocationProfile::Sample now includes sample_interval, so consumers know which interval was used when the sample was generated. The new sample getter is intended for lightweight polling. It avoids the extra work done by GetAllocationProfile(), such as translating the allocation tree and resolving script metadata, when callers only need sample information. This keeps the adaptive policy outside V8: embedders can decide how to adjust the interval, while V8 exposes the mechanism needed to do it. Change-Id: I7cff46a13c02617d04149c8d5c273a8d9ef4bb9f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8130742 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Dominik Inführ <dinfuehr@chromium.org> Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Cr-Commit-Position: refs/heads/main@{#109934} Refs: v8/v8@92ac942 Signed-off-by: ishabi <ilyasshabi94@gmail.com>
1 parent c56cb09 commit 3ea13e8

8 files changed

Lines changed: 195 additions & 31 deletions

File tree

‎common.gypi‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
# Reset this number to 0 on major V8 upgrades.
4646
# Increment by one for each non-official patch applied to deps/v8.
47-
'v8_embedder_string': '-node.22',
47+
'v8_embedder_string': '-node.24',
4848

4949
##### V8 defaults for Node.js #####
5050

‎deps/v8/include/v8-profiler.h‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,11 @@ class V8_EXPORT AllocationProfile {
836836
* been collected by GC.
837837
*/
838838
bool is_live;
839+
840+
/**
841+
* Sample interval in bytes used when this sample was selected.
842+
*/
843+
uint64_t sample_interval;
839844
};
840845

841846
/**
@@ -1257,6 +1262,22 @@ class V8_EXPORT HeapProfiler {
12571262
*/
12581263
void StopSamplingHeapProfiler();
12591264

1265+
/**
1266+
* Updates the sampling interval for a currently running sampling heap
1267+
* profiler. The new interval is used for future sample scheduling.
1268+
*
1269+
* No-op if the sampling heap profiler is not running.
1270+
*/
1271+
void SetSamplingHeapProfilerInterval(uint64_t sample_interval);
1272+
1273+
/**
1274+
* Returns the currently retained allocation samples without materializing
1275+
* the full allocation profile tree.
1276+
*
1277+
* Returns an empty vector if the sampling heap profiler is not running.
1278+
*/
1279+
std::vector<AllocationProfile::Sample> GetSamplingHeapProfilerSamples();
1280+
12601281
/**
12611282
* Returns the sampled profile of allocations allocated (and still live) since
12621283
* StartSamplingHeapProfiler was called. The ownership of the pointer is

‎deps/v8/src/api/api.cc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12098,6 +12098,17 @@ void HeapProfiler::StopSamplingHeapProfiler() {
1209812098
reinterpret_cast<i::HeapProfiler*>(this)->StopSamplingHeapProfiler();
1209912099
}
1210012100

12101+
void HeapProfiler::SetSamplingHeapProfilerInterval(uint64_t sample_interval) {
12102+
reinterpret_cast<i::HeapProfiler*>(this)->SetSamplingHeapProfilerInterval(
12103+
sample_interval);
12104+
}
12105+
12106+
std::vector<AllocationProfile::Sample>
12107+
HeapProfiler::GetSamplingHeapProfilerSamples() {
12108+
return reinterpret_cast<i::HeapProfiler*>(this)
12109+
->GetSamplingHeapProfilerSamples();
12110+
}
12111+
1210112112
AllocationProfile* HeapProfiler::GetAllocationProfile() {
1210212113
return reinterpret_cast<i::HeapProfiler*>(this)->GetAllocationProfile();
1210312114
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ void HeapProfiler::StopSamplingHeapProfiler() {
236236
MaybeClearStringsStorage();
237237
}
238238

239+
void HeapProfiler::SetSamplingHeapProfilerInterval(uint64_t sample_interval) {
240+
if (sampling_heap_profiler_) {
241+
sampling_heap_profiler_->SetSamplingInterval(sample_interval);
242+
}
243+
}
244+
245+
std::vector<v8::AllocationProfile::Sample>
246+
HeapProfiler::GetSamplingHeapProfilerSamples() {
247+
if (!sampling_heap_profiler_) return {};
248+
return sampling_heap_profiler_->GetSamples();
249+
}
250+
239251
v8::AllocationProfile* HeapProfiler::GetAllocationProfile() {
240252
if (sampling_heap_profiler_) {
241253
return sampling_heap_profiler_->GetAllocationProfile();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class HeapProfiler : public HeapObjectAllocationTracker {
8383
bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth,
8484
v8::HeapProfiler::SamplingFlags);
8585
void StopSamplingHeapProfiler();
86+
void SetSamplingHeapProfilerInterval(uint64_t sample_interval);
87+
std::vector<v8::AllocationProfile::Sample> GetSamplingHeapProfilerSamples();
8688
bool is_sampling_allocations() { return !!sampling_heap_profiler_; }
8789
AllocationProfile* GetAllocationProfile();
8890

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

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ intptr_t SamplingHeapProfiler::Observer::GetNextSampleInterval(uint64_t rate) {
4141
// Samples were collected according to a poisson process. Since we have not
4242
// recorded all allocations, we must approximate the shape of the underlying
4343
// space of allocations based on the samples we have collected. Given that
44-
// we sample at rate R, the probability that an allocation of size S will be
45-
// sampled is 1-exp(-S/R). This function uses the above probability to
44+
// we sample at interval I, the probability that an allocation of size S will
45+
// be sampled is 1-exp(-S/I). This function uses the above probability to
4646
// approximate the true number of allocations with size *size* given that
4747
// *count* samples were observed.
4848
v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample(
49-
size_t size, unsigned int count) const {
50-
double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
49+
size_t size, unsigned int count, uint64_t sample_interval) const {
50+
double scale =
51+
1.0 / (1.0 - std::exp(-static_cast<double>(size) / sample_interval));
5152
// Round count instead of truncating.
5253
return {size, static_cast<unsigned int>(count * scale + 0.5)};
5354
}
@@ -57,15 +58,15 @@ SamplingHeapProfiler::SamplingHeapProfiler(
5758
v8::HeapProfiler::SamplingFlags flags)
5859
: isolate_(Isolate::FromHeap(heap)),
5960
heap_(heap),
60-
allocation_observer_(heap_, static_cast<intptr_t>(rate), rate, this,
61+
allocation_observer_(heap_, static_cast<intptr_t>(rate), this,
6162
isolate_->random_number_generator()),
6263
names_(names),
6364
profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
6465
next_node_id()),
6566
stack_depth_(stack_depth),
66-
rate_(rate),
67+
interval_(rate),
6768
flags_(flags) {
68-
CHECK_GT(rate_, 0u);
69+
CHECK_GT(interval_.load(std::memory_order_relaxed), 0u);
6970
heap_->AddAllocationObserversToAllSpaces(&allocation_observer_,
7071
&allocation_observer_);
7172
}
@@ -94,10 +95,14 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
9495
TrustedHeapLayout::InTrustedSpace(heap_object) || !IsTheHole(*obj)));
9596
auto loc = Local<v8::Value>::FromSlot(obj.location());
9697

98+
// Record the current sampling interval so that this sample is scaled
99+
// with the exact weight in effect at draw time.
100+
const uint64_t interval_at_sample =
101+
interval_.load(std::memory_order_relaxed);
97102
AllocationNode* node = AddStack();
98-
node->allocations_[size]++;
99-
auto sample =
100-
std::make_unique<Sample>(size, node, loc, this, next_sample_id());
103+
node->allocations_[{size, interval_at_sample}]++;
104+
auto sample = std::make_unique<Sample>(size, node, loc, this,
105+
next_sample_id(), interval_at_sample);
101106
sample->global.SetWeak(sample.get(), OnWeakCallback,
102107
WeakCallbackType::kParameter);
103108
samples_.emplace(sample.get(), std::move(sample));
@@ -120,10 +125,11 @@ void SamplingHeapProfiler::OnWeakCallback(
120125
return;
121126
}
122127
AllocationNode* node = sample->owner;
123-
DCHECK_GT(node->allocations_[sample->size], 0);
124-
node->allocations_[sample->size]--;
125-
if (node->allocations_[sample->size] == 0) {
126-
node->allocations_.erase(sample->size);
128+
const auto key = std::make_pair(sample->size, sample->sample_interval);
129+
DCHECK_GT(node->allocations_[key], 0u);
130+
node->allocations_[key]--;
131+
if (node->allocations_[key] == 0) {
132+
node->allocations_.erase(key);
127133
while (node->allocations_.empty() && node->children_.empty() &&
128134
node->parent_ && !node->parent_->pinned_) {
129135
AllocationNode* parent = node->parent_;
@@ -263,8 +269,15 @@ v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
263269
column = pos_info.column + 1;
264270
}
265271
}
266-
for (auto alloc : node->allocations_) {
267-
allocations.push_back(ScaleSample(alloc.first, alloc.second));
272+
// Entries are keyed by (size, interval). Scale each with its own interval,
273+
// then merge by size for the public per-size Allocation output.
274+
std::map<size_t, unsigned int> per_size;
275+
for (const auto& [key, count] : node->allocations_) {
276+
const auto& [size, interval] = key;
277+
per_size[size] += ScaleSample(size, count, interval).count;
278+
}
279+
for (const auto& [size, count] : per_size) {
280+
allocations.push_back({size, count});
268281
}
269282

270283
profile->nodes_.push_back(v8::AllocationProfile::Node{
@@ -315,11 +328,25 @@ SamplingHeapProfiler::BuildSamples() const {
315328
const Sample* sample = it.second.get();
316329
const bool is_live = !sample->global.IsEmpty();
317330
samples.emplace_back(v8::AllocationProfile::Sample{
318-
sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
319-
sample->sample_id, is_live});
331+
sample->owner->id_, sample->size,
332+
ScaleSample(sample->size, 1, sample->sample_interval).count,
333+
sample->sample_id, is_live, sample->sample_interval});
320334
}
321335
return samples;
322336
}
323337

338+
std::vector<v8::AllocationProfile::Sample> SamplingHeapProfiler::GetSamples() {
339+
if (flags_ & v8::HeapProfiler::kSamplingForceGC) {
340+
isolate_->heap()->CollectAllGarbage(
341+
GCFlag::kNoFlags, GarbageCollectionReason::kSamplingProfiler);
342+
}
343+
return BuildSamples();
344+
}
345+
346+
void SamplingHeapProfiler::SetSamplingInterval(uint64_t sample_interval) {
347+
CHECK_GT(sample_interval, 0u);
348+
interval_.store(sample_interval, std::memory_order_relaxed);
349+
}
350+
324351
} // namespace internal
325352
} // namespace v8

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
#ifndef V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
66
#define V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
77

8+
#include <atomic>
89
#include <deque>
910
#include <map>
1011
#include <memory>
1112
#include <unordered_map>
13+
#include <vector>
1214

1315
#include "include/v8-profiler.h"
1416
#include "src/heap/heap.h"
@@ -85,9 +87,12 @@ class SamplingHeapProfiler {
8587
}
8688

8789
private:
90+
// Keyed by (allocation size, sampling interval at draw time) so that
91+
// samples drawn under different intervals are scaled with the correct
92+
// weight when the tree is translated.
8893
// TODO(alph): make use of unordered_map's here. Pay attention to
8994
// iterator invalidation during TranslateAllocationNode.
90-
std::map<size_t, unsigned int> allocations_;
95+
std::map<std::pair<size_t, uint64_t>, unsigned int> allocations_;
9196
std::map<FunctionId, std::unique_ptr<AllocationNode>> children_;
9297
AllocationNode* const parent_;
9398
const int script_id_;
@@ -101,19 +106,22 @@ class SamplingHeapProfiler {
101106

102107
struct Sample {
103108
Sample(size_t size_, AllocationNode* owner_, Local<Value> local_,
104-
SamplingHeapProfiler* profiler_, uint64_t sample_id)
109+
SamplingHeapProfiler* profiler_, uint64_t sample_id_,
110+
uint64_t sample_interval_)
105111
: size(size_),
106112
owner(owner_),
107113
global(reinterpret_cast<v8::Isolate*>(profiler_->isolate_), local_),
108114
profiler(profiler_),
109-
sample_id(sample_id) {}
115+
sample_id(sample_id_),
116+
sample_interval(sample_interval_) {}
110117
Sample(const Sample&) = delete;
111118
Sample& operator=(const Sample&) = delete;
112119
const size_t size;
113120
AllocationNode* const owner;
114121
Global<Value> global;
115122
SamplingHeapProfiler* const profiler;
116123
const uint64_t sample_id;
124+
const uint64_t sample_interval;
117125
};
118126

119127
SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate,
@@ -123,19 +131,21 @@ class SamplingHeapProfiler {
123131
SamplingHeapProfiler& operator=(const SamplingHeapProfiler&) = delete;
124132

125133
v8::AllocationProfile* GetAllocationProfile();
134+
135+
void SetSamplingInterval(uint64_t sample_interval);
136+
std::vector<v8::AllocationProfile::Sample> GetSamples();
137+
126138
StringsStorage* names() const { return names_; }
127139

128140
private:
129141
class Observer : public AllocationObserver {
130142
public:
131-
Observer(Heap* heap, intptr_t step_size, uint64_t rate,
132-
SamplingHeapProfiler* profiler,
143+
Observer(Heap* heap, intptr_t step_size, SamplingHeapProfiler* profiler,
133144
base::RandomNumberGenerator* random)
134145
: AllocationObserver(step_size),
135146
profiler_(profiler),
136147
heap_(heap),
137-
random_(random),
138-
rate_(rate) {}
148+
random_(random) {}
139149

140150
protected:
141151
void Step(int bytes_allocated, Address soon_object, size_t size) override {
@@ -148,14 +158,16 @@ class SamplingHeapProfiler {
148158
}
149159
}
150160

151-
intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); }
161+
intptr_t GetNextStepSize() override {
162+
return GetNextSampleInterval(
163+
profiler_->interval_.load(std::memory_order_relaxed));
164+
}
152165

153166
private:
154167
intptr_t GetNextSampleInterval(uint64_t rate);
155168
SamplingHeapProfiler* const profiler_;
156169
Heap* const heap_;
157170
base::RandomNumberGenerator* const random_;
158-
uint64_t const rate_;
159171
};
160172

161173
void SampleObject(Address soon_object, size_t size);
@@ -179,8 +191,8 @@ class SamplingHeapProfiler {
179191
v8::AllocationProfile::Node* TranslateAllocationNode(
180192
AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
181193
const std::map<int, Handle<Script>>& scripts);
182-
v8::AllocationProfile::Allocation ScaleSample(size_t size,
183-
unsigned int count) const;
194+
v8::AllocationProfile::Allocation ScaleSample(size_t size, unsigned int count,
195+
uint64_t sample_interval) const;
184196
AllocationNode* AddStack();
185197

186198
Isolate* const isolate_;
@@ -192,7 +204,7 @@ class SamplingHeapProfiler {
192204
AllocationNode profile_root_;
193205
std::unordered_map<Sample*, std::unique_ptr<Sample>> samples_;
194206
const int stack_depth_;
195-
const uint64_t rate_;
207+
std::atomic<uint64_t> interval_;
196208
v8::HeapProfiler::SamplingFlags flags_;
197209
};
198210

0 commit comments

Comments
 (0)