adda4c5f98
This CL introduces cppgc::HistogramRecorder api which is similar to the v8::metrics::Recorder api and is used by cppgc to report histogram samples to embedders. Embedders should implement the api if they want to collect histograms and provide an instance of it on heap creation. CppHeap uses an adaptor class that implements the HistogramRecorder api and is used to forward the relevant info to the relevant v8::metrics::Recorder. The api used 3 data structures: 2 for incremental steps that need to be reported as they come (marking and sweeping) and 1 for the end of a GC cycle that aggregates statistics over the entire cycle. The data structure only provide the "raw" samples (e.g. atomic mark time, incremental mark time, etc...). The embedder is expected to compute aggregate histogram on its own (e.g. overall marking time). Bug: chromium:1056170 Change-Id: If63ef50a29a21594f654edb83084598980d221ce Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2642258 Commit-Queue: Omer Katz <omerkatz@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#72256}
160 lines
6.5 KiB
C++
160 lines
6.5 KiB
C++
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/heap/cppgc/heap-growing.h"
|
|
|
|
#include "include/cppgc/platform.h"
|
|
#include "src/heap/cppgc/heap.h"
|
|
#include "src/heap/cppgc/stats-collector.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace cppgc {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
class FakeGarbageCollector : public GarbageCollector {
|
|
public:
|
|
explicit FakeGarbageCollector(StatsCollector* stats_collector)
|
|
: stats_collector_(stats_collector) {}
|
|
|
|
void SetLiveBytes(size_t live_bytes) { live_bytes_ = live_bytes; }
|
|
|
|
void CollectGarbage(GarbageCollector::Config config) override {
|
|
stats_collector_->NotifyMarkingStarted(
|
|
GarbageCollector::Config::CollectionType::kMajor,
|
|
GarbageCollector::Config::IsForcedGC::kNotForced);
|
|
stats_collector_->NotifyMarkingCompleted(live_bytes_);
|
|
stats_collector_->NotifySweepingCompleted();
|
|
callcount_++;
|
|
}
|
|
|
|
void StartIncrementalGarbageCollection(
|
|
GarbageCollector::Config config) override {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
size_t epoch() const override { return callcount_; }
|
|
|
|
private:
|
|
StatsCollector* stats_collector_;
|
|
size_t live_bytes_ = 0;
|
|
size_t callcount_ = 0;
|
|
};
|
|
|
|
class MockGarbageCollector : public GarbageCollector {
|
|
public:
|
|
MOCK_METHOD(void, CollectGarbage, (GarbageCollector::Config), (override));
|
|
MOCK_METHOD(void, StartIncrementalGarbageCollection,
|
|
(GarbageCollector::Config), (override));
|
|
MOCK_METHOD(size_t, epoch, (), (const, override));
|
|
};
|
|
|
|
void FakeAllocate(StatsCollector* stats_collector, size_t bytes) {
|
|
stats_collector->NotifyAllocation(bytes);
|
|
stats_collector->NotifySafePointForConservativeCollection();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(HeapGrowingTest, ConservativeGCInvoked) {
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
MockGarbageCollector gc;
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
// Force GC at the first update.
|
|
constraints.initial_heap_size_bytes = 1;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
EXPECT_CALL(gc, CollectGarbage(::testing::_));
|
|
FakeAllocate(&stats_collector, 100 * kMB);
|
|
}
|
|
|
|
TEST(HeapGrowingTest, InitialHeapSize) {
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
MockGarbageCollector gc;
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
// Use larger size to avoid running into small heap optimizations.
|
|
constexpr size_t kObjectSize = 10 * HeapGrowing::kMinLimitIncrease;
|
|
constraints.initial_heap_size_bytes = kObjectSize;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
FakeAllocate(&stats_collector, kObjectSize - 1);
|
|
EXPECT_CALL(gc, CollectGarbage(::testing::_));
|
|
FakeAllocate(&stats_collector, kObjectSize);
|
|
}
|
|
|
|
TEST(HeapGrowingTest, ConstantGrowingFactor) {
|
|
// Use larger size to avoid running into small heap optimizations.
|
|
constexpr size_t kObjectSize = 10 * HeapGrowing::kMinLimitIncrease;
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
FakeGarbageCollector gc(&stats_collector);
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
// Force GC at the first update.
|
|
constraints.initial_heap_size_bytes = HeapGrowing::kMinLimitIncrease;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
EXPECT_EQ(0u, gc.epoch());
|
|
gc.SetLiveBytes(kObjectSize);
|
|
FakeAllocate(&stats_collector, kObjectSize + 1);
|
|
EXPECT_EQ(1u, gc.epoch());
|
|
EXPECT_EQ(1.5 * kObjectSize, growing.limit_for_atomic_gc());
|
|
}
|
|
|
|
TEST(HeapGrowingTest, SmallHeapGrowing) {
|
|
// Larger constant to avoid running into special handling for smaller heaps.
|
|
constexpr size_t kLargeAllocation = 100 * kMB;
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
FakeGarbageCollector gc(&stats_collector);
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
// Force GC at the first update.
|
|
constraints.initial_heap_size_bytes = 1;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
EXPECT_EQ(0u, gc.epoch());
|
|
gc.SetLiveBytes(1);
|
|
FakeAllocate(&stats_collector, kLargeAllocation);
|
|
EXPECT_EQ(1u, gc.epoch());
|
|
EXPECT_EQ(1 + HeapGrowing::kMinLimitIncrease, growing.limit_for_atomic_gc());
|
|
}
|
|
|
|
TEST(HeapGrowingTest, IncrementalGCStarted) {
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
MockGarbageCollector gc;
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
EXPECT_CALL(gc, CollectGarbage(::testing::_)).Times(0);
|
|
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_));
|
|
// Allocate 1 byte less the limit for atomic gc to trigger incremental gc.
|
|
FakeAllocate(&stats_collector, growing.limit_for_atomic_gc() - 1);
|
|
}
|
|
|
|
TEST(HeapGrowingTest, IncrementalGCFinalized) {
|
|
StatsCollector stats_collector(nullptr /* metric_recorder */);
|
|
MockGarbageCollector gc;
|
|
cppgc::Heap::ResourceConstraints constraints;
|
|
HeapGrowing growing(&gc, &stats_collector, constraints,
|
|
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
|
|
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
|
|
EXPECT_CALL(gc, CollectGarbage(::testing::_)).Times(0);
|
|
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_));
|
|
// Allocate 1 byte less the limit for atomic gc to trigger incremental gc.
|
|
size_t bytes_for_incremental_gc = growing.limit_for_atomic_gc() - 1;
|
|
FakeAllocate(&stats_collector, bytes_for_incremental_gc);
|
|
::testing::Mock::VerifyAndClearExpectations(&gc);
|
|
EXPECT_CALL(gc, CollectGarbage(::testing::_));
|
|
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_)).Times(0);
|
|
// Allocate the rest needed to trigger atomic gc ().
|
|
FakeAllocate(&stats_collector, StatsCollector::kAllocationThresholdBytes);
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace cppgc
|