v8/test/unittests/heap/cppgc/stats-collector-unittest.cc
Omer Katz adda4c5f98 cppgc: Add UMA support
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}
2021-01-22 15:04:35 +00:00

217 lines
7.7 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/stats-collector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
constexpr size_t kNoMarkedBytes = 0;
constexpr size_t kMinReportedSize = StatsCollector::kAllocationThresholdBytes;
class StatsCollectorTest : public ::testing::Test {
public:
StatsCollectorTest() : stats(nullptr /* metric_recorder */) {}
void FakeAllocate(size_t bytes) {
stats.NotifyAllocation(bytes);
stats.NotifySafePointForConservativeCollection();
}
void FakeFree(size_t bytes) {
stats.NotifyExplicitFree(bytes);
stats.NotifySafePointForConservativeCollection();
}
StatsCollector stats;
};
} // namespace
TEST_F(StatsCollectorTest, NoMarkedBytes) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
stats.NotifyMarkingCompleted(kNoMarkedBytes);
stats.NotifySweepingCompleted();
auto event = stats.GetPreviousEventForTesting();
EXPECT_EQ(0u, event.marked_bytes);
}
TEST_F(StatsCollectorTest, EventPrevGCMarkedObjectSize) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
stats.NotifyMarkingCompleted(1024);
stats.NotifySweepingCompleted();
auto event = stats.GetPreviousEventForTesting();
EXPECT_EQ(1024u, event.marked_bytes);
}
TEST_F(StatsCollectorTest, AllocationNoReportBelowAllocationThresholdBytes) {
constexpr size_t kObjectSize = 17;
EXPECT_LT(kObjectSize, StatsCollector::kAllocationThresholdBytes);
FakeAllocate(kObjectSize);
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AlllocationReportAboveAllocationThresholdBytes) {
constexpr size_t kObjectSize = StatsCollector::kAllocationThresholdBytes;
EXPECT_GE(kObjectSize, StatsCollector::kAllocationThresholdBytes);
FakeAllocate(kObjectSize);
EXPECT_EQ(kObjectSize, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, InitialAllocatedObjectSize) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kNoMarkedBytes);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifySweepingCompleted();
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSize) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifySweepingCompleted();
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSizeNoMarkedBytes) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kNoMarkedBytes);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifySweepingCompleted();
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSizeAllocateAfterMarking) {
stats.NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kMinReportedSize);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(2 * kMinReportedSize, stats.allocated_object_size());
stats.NotifySweepingCompleted();
EXPECT_EQ(2 * kMinReportedSize, stats.allocated_object_size());
}
class MockAllocationObserver : public StatsCollector::AllocationObserver {
public:
MOCK_METHOD(void, AllocatedObjectSizeIncreased, (size_t), (override));
MOCK_METHOD(void, AllocatedObjectSizeDecreased, (size_t), (override));
MOCK_METHOD(void, ResetAllocatedObjectSize, (size_t), (override));
};
TEST_F(StatsCollectorTest, RegisterUnregisterObserver) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
stats.UnregisterObserver(&observer);
}
TEST_F(StatsCollectorTest, ObserveAllocatedObjectSizeIncreaseAndDecrease) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
EXPECT_CALL(observer, AllocatedObjectSizeIncreased(kMinReportedSize));
FakeAllocate(kMinReportedSize);
EXPECT_CALL(observer, AllocatedObjectSizeDecreased(kMinReportedSize));
FakeFree(kMinReportedSize);
stats.UnregisterObserver(&observer);
}
namespace {
void FakeGC(StatsCollector* stats, size_t marked_bytes) {
stats->NotifyMarkingStarted(GarbageCollector::Config::CollectionType::kMajor,
GarbageCollector::Config::IsForcedGC::kNotForced);
stats->NotifyMarkingCompleted(marked_bytes);
stats->NotifySweepingCompleted();
}
} // namespace
TEST_F(StatsCollectorTest, ObserveResetAllocatedObjectSize) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
EXPECT_CALL(observer, AllocatedObjectSizeIncreased(kMinReportedSize));
FakeAllocate(kMinReportedSize);
EXPECT_CALL(observer, ResetAllocatedObjectSize(64));
FakeGC(&stats, 64);
stats.UnregisterObserver(&observer);
}
namespace {
class AllocationObserverTriggeringGC final
: public StatsCollector::AllocationObserver {
public:
AllocationObserverTriggeringGC(StatsCollector* stats, double survival_ratio)
: stats(stats), survival_ratio_(survival_ratio) {}
void AllocatedObjectSizeIncreased(size_t bytes) final {
increase_call_count++;
increased_size_bytes += bytes;
if (increase_call_count == 1) {
FakeGC(stats, bytes * survival_ratio_);
}
}
// // Mock out the rest to trigger warnings if used.
MOCK_METHOD(void, AllocatedObjectSizeDecreased, (size_t), (override));
MOCK_METHOD(void, ResetAllocatedObjectSize, (size_t), (override));
size_t increase_call_count = 0;
size_t increased_size_bytes = 0;
StatsCollector* stats;
double survival_ratio_;
};
} // namespace
TEST_F(StatsCollectorTest, ObserverTriggersGC) {
constexpr double kSurvivalRatio = 0.5;
AllocationObserverTriggeringGC gc_observer(&stats, kSurvivalRatio);
MockAllocationObserver mock_observer;
// Internal detail: First registered observer is also notified first.
stats.RegisterObserver(&gc_observer);
stats.RegisterObserver(&mock_observer);
// Both observers see the exact allocated object size byte count.
EXPECT_CALL(mock_observer,
ResetAllocatedObjectSize(kMinReportedSize * kSurvivalRatio));
EXPECT_CALL(gc_observer,
ResetAllocatedObjectSize(kMinReportedSize * kSurvivalRatio));
// Since the GC clears counters, mock_observer should see an increase call
// with a delta of zero bytes. This expectation makes use of the internal
// detail that first registered observer triggers GC.
EXPECT_CALL(mock_observer, AllocatedObjectSizeIncreased(0));
// Trigger scenario.
FakeAllocate(kMinReportedSize);
EXPECT_EQ(1u, gc_observer.increase_call_count);
EXPECT_EQ(kMinReportedSize, gc_observer.increased_size_bytes);
stats.UnregisterObserver(&gc_observer);
stats.UnregisterObserver(&mock_observer);
}
} // namespace internal
} // namespace cppgc