diff --git a/common.gypi b/common.gypi index 52dc3ad96adf..24032442c097 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.22', + 'v8_embedder_string': '-node.24', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-profiler.h b/deps/v8/include/v8-profiler.h index 82769a8dfb4f..df60fd7f66d7 100644 --- a/deps/v8/include/v8-profiler.h +++ b/deps/v8/include/v8-profiler.h @@ -836,6 +836,11 @@ class V8_EXPORT AllocationProfile { * been collected by GC. */ bool is_live; + + /** + * Sample interval in bytes used when this sample was selected. + */ + uint64_t sample_interval; }; /** @@ -1257,6 +1262,22 @@ class V8_EXPORT HeapProfiler { */ void StopSamplingHeapProfiler(); + /** + * Updates the sampling interval for a currently running sampling heap + * profiler. The new interval is used for future sample scheduling. + * + * No-op if the sampling heap profiler is not running. + */ + void SetSamplingHeapProfilerInterval(uint64_t sample_interval); + + /** + * Returns the currently retained allocation samples without materializing + * the full allocation profile tree. + * + * Returns an empty vector if the sampling heap profiler is not running. + */ + std::vector GetSamplingHeapProfilerSamples(); + /** * Returns the sampled profile of allocations allocated (and still live) since * StartSamplingHeapProfiler was called. The ownership of the pointer is diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index f498fee34316..49e3139d432a 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -12098,6 +12098,17 @@ void HeapProfiler::StopSamplingHeapProfiler() { reinterpret_cast(this)->StopSamplingHeapProfiler(); } +void HeapProfiler::SetSamplingHeapProfilerInterval(uint64_t sample_interval) { + reinterpret_cast(this)->SetSamplingHeapProfilerInterval( + sample_interval); +} + +std::vector +HeapProfiler::GetSamplingHeapProfilerSamples() { + return reinterpret_cast(this) + ->GetSamplingHeapProfilerSamples(); +} + AllocationProfile* HeapProfiler::GetAllocationProfile() { return reinterpret_cast(this)->GetAllocationProfile(); } diff --git a/deps/v8/src/profiler/heap-profiler.cc b/deps/v8/src/profiler/heap-profiler.cc index 1bc589b71c04..5b7cb326a599 100644 --- a/deps/v8/src/profiler/heap-profiler.cc +++ b/deps/v8/src/profiler/heap-profiler.cc @@ -236,6 +236,18 @@ void HeapProfiler::StopSamplingHeapProfiler() { MaybeClearStringsStorage(); } +void HeapProfiler::SetSamplingHeapProfilerInterval(uint64_t sample_interval) { + if (sampling_heap_profiler_) { + sampling_heap_profiler_->SetSamplingInterval(sample_interval); + } +} + +std::vector +HeapProfiler::GetSamplingHeapProfilerSamples() { + if (!sampling_heap_profiler_) return {}; + return sampling_heap_profiler_->GetSamples(); +} + v8::AllocationProfile* HeapProfiler::GetAllocationProfile() { if (sampling_heap_profiler_) { return sampling_heap_profiler_->GetAllocationProfile(); diff --git a/deps/v8/src/profiler/heap-profiler.h b/deps/v8/src/profiler/heap-profiler.h index 9f82946bb317..031f32762fac 100644 --- a/deps/v8/src/profiler/heap-profiler.h +++ b/deps/v8/src/profiler/heap-profiler.h @@ -83,6 +83,8 @@ class HeapProfiler : public HeapObjectAllocationTracker { bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth, v8::HeapProfiler::SamplingFlags); void StopSamplingHeapProfiler(); + void SetSamplingHeapProfilerInterval(uint64_t sample_interval); + std::vector GetSamplingHeapProfilerSamples(); bool is_sampling_allocations() { return !!sampling_heap_profiler_; } AllocationProfile* GetAllocationProfile(); diff --git a/deps/v8/src/profiler/sampling-heap-profiler.cc b/deps/v8/src/profiler/sampling-heap-profiler.cc index 7ea26d4064f8..203854adcc22 100644 --- a/deps/v8/src/profiler/sampling-heap-profiler.cc +++ b/deps/v8/src/profiler/sampling-heap-profiler.cc @@ -41,13 +41,14 @@ intptr_t SamplingHeapProfiler::Observer::GetNextSampleInterval(uint64_t rate) { // Samples were collected according to a poisson process. Since we have not // recorded all allocations, we must approximate the shape of the underlying // space of allocations based on the samples we have collected. Given that -// we sample at rate R, the probability that an allocation of size S will be -// sampled is 1-exp(-S/R). This function uses the above probability to +// we sample at interval I, the probability that an allocation of size S will +// be sampled is 1-exp(-S/I). This function uses the above probability to // approximate the true number of allocations with size *size* given that // *count* samples were observed. v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample( - size_t size, unsigned int count) const { - double scale = 1.0 / (1.0 - std::exp(-static_cast(size) / rate_)); + size_t size, unsigned int count, uint64_t sample_interval) const { + double scale = + 1.0 / (1.0 - std::exp(-static_cast(size) / sample_interval)); // Round count instead of truncating. return {size, static_cast(count * scale + 0.5)}; } @@ -57,15 +58,15 @@ SamplingHeapProfiler::SamplingHeapProfiler( v8::HeapProfiler::SamplingFlags flags) : isolate_(Isolate::FromHeap(heap)), heap_(heap), - allocation_observer_(heap_, static_cast(rate), rate, this, + allocation_observer_(heap_, static_cast(rate), this, isolate_->random_number_generator()), names_(names), profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0, next_node_id()), stack_depth_(stack_depth), - rate_(rate), + interval_(rate), flags_(flags) { - CHECK_GT(rate_, 0u); + CHECK_GT(interval_.load(std::memory_order_relaxed), 0u); heap_->AddAllocationObserversToAllSpaces(&allocation_observer_, &allocation_observer_); } @@ -94,10 +95,14 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) { TrustedHeapLayout::InTrustedSpace(heap_object) || !IsTheHole(*obj))); auto loc = Local::FromSlot(obj.location()); + // Record the current sampling interval so that this sample is scaled + // with the exact weight in effect at draw time. + const uint64_t interval_at_sample = + interval_.load(std::memory_order_relaxed); AllocationNode* node = AddStack(); - node->allocations_[size]++; - auto sample = - std::make_unique(size, node, loc, this, next_sample_id()); + node->allocations_[{size, interval_at_sample}]++; + auto sample = std::make_unique(size, node, loc, this, + next_sample_id(), interval_at_sample); sample->global.SetWeak(sample.get(), OnWeakCallback, WeakCallbackType::kParameter); samples_.emplace(sample.get(), std::move(sample)); @@ -120,10 +125,11 @@ void SamplingHeapProfiler::OnWeakCallback( return; } AllocationNode* node = sample->owner; - DCHECK_GT(node->allocations_[sample->size], 0); - node->allocations_[sample->size]--; - if (node->allocations_[sample->size] == 0) { - node->allocations_.erase(sample->size); + const auto key = std::make_pair(sample->size, sample->sample_interval); + DCHECK_GT(node->allocations_[key], 0u); + node->allocations_[key]--; + if (node->allocations_[key] == 0) { + node->allocations_.erase(key); while (node->allocations_.empty() && node->children_.empty() && node->parent_ && !node->parent_->pinned_) { AllocationNode* parent = node->parent_; @@ -263,8 +269,15 @@ v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode( column = pos_info.column + 1; } } - for (auto alloc : node->allocations_) { - allocations.push_back(ScaleSample(alloc.first, alloc.second)); + // Entries are keyed by (size, interval). Scale each with its own interval, + // then merge by size for the public per-size Allocation output. + std::map per_size; + for (const auto& [key, count] : node->allocations_) { + const auto& [size, interval] = key; + per_size[size] += ScaleSample(size, count, interval).count; + } + for (const auto& [size, count] : per_size) { + allocations.push_back({size, count}); } profile->nodes_.push_back(v8::AllocationProfile::Node{ @@ -315,11 +328,25 @@ SamplingHeapProfiler::BuildSamples() const { const Sample* sample = it.second.get(); const bool is_live = !sample->global.IsEmpty(); samples.emplace_back(v8::AllocationProfile::Sample{ - sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count, - sample->sample_id, is_live}); + sample->owner->id_, sample->size, + ScaleSample(sample->size, 1, sample->sample_interval).count, + sample->sample_id, is_live, sample->sample_interval}); } return samples; } +std::vector SamplingHeapProfiler::GetSamples() { + if (flags_ & v8::HeapProfiler::kSamplingForceGC) { + isolate_->heap()->CollectAllGarbage( + GCFlag::kNoFlags, GarbageCollectionReason::kSamplingProfiler); + } + return BuildSamples(); +} + +void SamplingHeapProfiler::SetSamplingInterval(uint64_t sample_interval) { + CHECK_GT(sample_interval, 0u); + interval_.store(sample_interval, std::memory_order_relaxed); +} + } // namespace internal } // namespace v8 diff --git a/deps/v8/src/profiler/sampling-heap-profiler.h b/deps/v8/src/profiler/sampling-heap-profiler.h index 6a1010b99931..6a4f7d861928 100644 --- a/deps/v8/src/profiler/sampling-heap-profiler.h +++ b/deps/v8/src/profiler/sampling-heap-profiler.h @@ -5,10 +5,12 @@ #ifndef V8_PROFILER_SAMPLING_HEAP_PROFILER_H_ #define V8_PROFILER_SAMPLING_HEAP_PROFILER_H_ +#include #include #include #include #include +#include #include "include/v8-profiler.h" #include "src/heap/heap.h" @@ -85,9 +87,12 @@ class SamplingHeapProfiler { } private: + // Keyed by (allocation size, sampling interval at draw time) so that + // samples drawn under different intervals are scaled with the correct + // weight when the tree is translated. // TODO(alph): make use of unordered_map's here. Pay attention to // iterator invalidation during TranslateAllocationNode. - std::map allocations_; + std::map, unsigned int> allocations_; std::map> children_; AllocationNode* const parent_; const int script_id_; @@ -101,12 +106,14 @@ class SamplingHeapProfiler { struct Sample { Sample(size_t size_, AllocationNode* owner_, Local local_, - SamplingHeapProfiler* profiler_, uint64_t sample_id) + SamplingHeapProfiler* profiler_, uint64_t sample_id_, + uint64_t sample_interval_) : size(size_), owner(owner_), global(reinterpret_cast(profiler_->isolate_), local_), profiler(profiler_), - sample_id(sample_id) {} + sample_id(sample_id_), + sample_interval(sample_interval_) {} Sample(const Sample&) = delete; Sample& operator=(const Sample&) = delete; const size_t size; @@ -114,6 +121,7 @@ class SamplingHeapProfiler { Global global; SamplingHeapProfiler* const profiler; const uint64_t sample_id; + const uint64_t sample_interval; }; SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate, @@ -123,19 +131,21 @@ class SamplingHeapProfiler { SamplingHeapProfiler& operator=(const SamplingHeapProfiler&) = delete; v8::AllocationProfile* GetAllocationProfile(); + + void SetSamplingInterval(uint64_t sample_interval); + std::vector GetSamples(); + StringsStorage* names() const { return names_; } private: class Observer : public AllocationObserver { public: - Observer(Heap* heap, intptr_t step_size, uint64_t rate, - SamplingHeapProfiler* profiler, + Observer(Heap* heap, intptr_t step_size, SamplingHeapProfiler* profiler, base::RandomNumberGenerator* random) : AllocationObserver(step_size), profiler_(profiler), heap_(heap), - random_(random), - rate_(rate) {} + random_(random) {} protected: void Step(int bytes_allocated, Address soon_object, size_t size) override { @@ -148,14 +158,16 @@ class SamplingHeapProfiler { } } - intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); } + intptr_t GetNextStepSize() override { + return GetNextSampleInterval( + profiler_->interval_.load(std::memory_order_relaxed)); + } private: intptr_t GetNextSampleInterval(uint64_t rate); SamplingHeapProfiler* const profiler_; Heap* const heap_; base::RandomNumberGenerator* const random_; - uint64_t const rate_; }; void SampleObject(Address soon_object, size_t size); @@ -179,8 +191,8 @@ class SamplingHeapProfiler { v8::AllocationProfile::Node* TranslateAllocationNode( AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node, const std::map>& scripts); - v8::AllocationProfile::Allocation ScaleSample(size_t size, - unsigned int count) const; + v8::AllocationProfile::Allocation ScaleSample(size_t size, unsigned int count, + uint64_t sample_interval) const; AllocationNode* AddStack(); Isolate* const isolate_; @@ -192,7 +204,7 @@ class SamplingHeapProfiler { AllocationNode profile_root_; std::unordered_map> samples_; const int stack_depth_; - const uint64_t rate_; + std::atomic interval_; v8::HeapProfiler::SamplingFlags flags_; }; diff --git a/deps/v8/test/cctest/test-heap-profiler.cc b/deps/v8/test/cctest/test-heap-profiler.cc index 06a6d8e45c17..17c859e1f67d 100644 --- a/deps/v8/test/cctest/test-heap-profiler.cc +++ b/deps/v8/test/cctest/test-heap-profiler.cc @@ -4270,6 +4270,85 @@ TEST(SamplingHeapProfilerApiSamples) { heap_profiler->StopSamplingHeapProfiler(); } +TEST(SamplingHeapProfilerSetInterval) { + v8::HandleScope scope(CcTest::isolate()); + LocalContext env; + v8::HeapProfiler* heap_profiler = env.isolate()->GetHeapProfiler(); + + i::v8_flags.sampling_heap_profiler_suppress_randomness = true; + + const uint64_t initial_interval = 1024; + const uint64_t updated_interval = 4096; + heap_profiler->StartSamplingHeapProfiler(initial_interval); + + CompileRun("for (var i = 0; i < 1024; i++) new Array(64);"); + auto samples_before = heap_profiler->GetSamplingHeapProfilerSamples(); + CHECK_GT(samples_before.size(), 0u); + uint64_t max_id_before = 0; + for (const auto& s : samples_before) { + CHECK_EQ(initial_interval, s.sample_interval); + if (s.sample_id > max_id_before) max_id_before = s.sample_id; + } + + heap_profiler->SetSamplingHeapProfilerInterval(updated_interval); + CompileRun("for (var i = 0; i < 1024; i++) new Array(64);"); + + bool saw_new_sample = false; + for (const auto& s : heap_profiler->GetSamplingHeapProfilerSamples()) { + if (s.sample_id > max_id_before) { + CHECK_EQ(updated_interval, s.sample_interval); + saw_new_sample = true; + } else { + CHECK_EQ(initial_interval, s.sample_interval); + } + } + CHECK(saw_new_sample); + + heap_profiler->StopSamplingHeapProfiler(); +} + +TEST(SamplingHeapProfilerSampleIntervalSurvivesGC) { + v8::HandleScope scope(CcTest::isolate()); + LocalContext env; + v8::HeapProfiler* heap_profiler = env.isolate()->GetHeapProfiler(); + + i::v8_flags.sampling_heap_profiler_suppress_randomness = true; + + const uint64_t interval = 1024; + heap_profiler->StartSamplingHeapProfiler( + interval, 16, + static_cast( + v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC)); + + CompileRun("for (var i = 0; i < 1024; i++) new Array(64);"); + i::heap::InvokeMajorGC(CcTest::heap()); + + bool saw_dead = false; + for (const auto& s : heap_profiler->GetSamplingHeapProfilerSamples()) { + CHECK_EQ(interval, s.sample_interval); + if (!s.is_live) saw_dead = true; + } + CHECK(saw_dead); + + heap_profiler->StopSamplingHeapProfiler(); +} + +TEST(SamplingHeapProfilerSetIntervalNoop) { + v8::HandleScope scope(CcTest::isolate()); + LocalContext env; + v8::HeapProfiler* heap_profiler = env.isolate()->GetHeapProfiler(); + + // Safe before Start. + heap_profiler->SetSamplingHeapProfilerInterval(2048); + CHECK_EQ(0u, heap_profiler->GetSamplingHeapProfilerSamples().size()); + + // Safe after Stop. + heap_profiler->StartSamplingHeapProfiler(1024); + heap_profiler->StopSamplingHeapProfiler(); + heap_profiler->SetSamplingHeapProfilerInterval(2048); + CHECK_EQ(0u, heap_profiler->GetSamplingHeapProfilerSamples().size()); +} + TEST(SamplingHeapProfilerLeftTrimming) { v8::HandleScope scope(CcTest::isolate()); LocalContext env;