| // 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: |
| 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(); |
| stats.NotifyMarkingCompleted(kNoMarkedBytes); |
| auto event = stats.NotifySweepingCompleted(); |
| EXPECT_EQ(0u, event.marked_bytes); |
| } |
| |
| TEST_F(StatsCollectorTest, EventPrevGCMarkedObjectSize) { |
| stats.NotifyMarkingStarted(); |
| stats.NotifyMarkingCompleted(1024); |
| auto event = stats.NotifySweepingCompleted(); |
| 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(); |
| 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(); |
| 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(); |
| 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(); |
| 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(); |
| 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 |