From daaff7dfe98746161b84da73a54bbf7051bd52dc Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Thu, 11 Feb 2021 14:57:15 +0100 Subject: [PATCH] cppgc: Collect heap statistics HeapBase::CollectStatistics returns a HeapStatistics struct that can be used by blink to populate a memory dump. Bug: chromium:1056170 Change-Id: Ic147a02ba6b4aa77bf92cfca067da70b7e1af55b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2689181 Commit-Queue: Omer Katz Reviewed-by: Michael Lippautz Cr-Commit-Position: refs/heads/master@{#72660} --- BUILD.gn | 3 + include/DEPS | 1 + include/cppgc/heap-statistics.h | 110 ++++++++++++ include/v8-cppgc.h | 10 ++ src/heap/cppgc-js/cpp-heap.cc | 6 + src/heap/cppgc/free-list.cc | 21 +++ src/heap/cppgc/free-list.h | 3 + src/heap/cppgc/gc-info-table.h | 3 +- src/heap/cppgc/heap-base.cc | 15 ++ src/heap/cppgc/heap-base.h | 3 + src/heap/cppgc/heap-page.cc | 11 +- src/heap/cppgc/heap-page.h | 2 + src/heap/cppgc/heap-statistics-collector.cc | 158 ++++++++++++++++++ src/heap/cppgc/heap-statistics-collector.h | 35 ++++ src/heap/cppgc/stats-collector.cc | 4 + src/heap/cppgc/stats-collector.h | 1 + test/unittests/BUILD.gn | 1 + test/unittests/heap/cppgc/gc-info-unittest.cc | 2 +- .../heap-statistics-collector-unittest.cc | 131 +++++++++++++++ 19 files changed, 512 insertions(+), 8 deletions(-) create mode 100644 include/cppgc/heap-statistics.h create mode 100644 src/heap/cppgc/heap-statistics-collector.cc create mode 100644 src/heap/cppgc/heap-statistics-collector.h create mode 100644 test/unittests/heap/cppgc/heap-statistics-collector-unittest.cc diff --git a/BUILD.gn b/BUILD.gn index 2b6029fd10..9de9f2ab32 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -4666,6 +4666,7 @@ v8_source_set("cppgc_base") { "include/cppgc/garbage-collected.h", "include/cppgc/heap-consistency.h", "include/cppgc/heap-state.h", + "include/cppgc/heap-statistics.h", "include/cppgc/heap.h", "include/cppgc/internal/api-constants.h", "include/cppgc/internal/atomic-entry-flag.h", @@ -4719,6 +4720,8 @@ v8_source_set("cppgc_base") { "src/heap/cppgc/heap-space.cc", "src/heap/cppgc/heap-space.h", "src/heap/cppgc/heap-state.cc", + "src/heap/cppgc/heap-statistics-collector.cc", + "src/heap/cppgc/heap-statistics-collector.h", "src/heap/cppgc/heap-visitor.h", "src/heap/cppgc/heap.cc", "src/heap/cppgc/heap.h", diff --git a/include/DEPS b/include/DEPS index 4ff917b7fe..fd150b7143 100644 --- a/include/DEPS +++ b/include/DEPS @@ -4,6 +4,7 @@ include_rules = [ "+cppgc/common.h", # Used by v8-cppgc.h to bridge to cppgc. "+cppgc/custom-space.h", + "+cppgc/heap-statistics.h", "+cppgc/internal/process-heap.h", "+cppgc/internal/write-barrier.h", "+cppgc/visitor.h", diff --git a/include/cppgc/heap-statistics.h b/include/cppgc/heap-statistics.h new file mode 100644 index 0000000000..d454c5687a --- /dev/null +++ b/include/cppgc/heap-statistics.h @@ -0,0 +1,110 @@ +// Copyright 2021 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. + +#ifndef INCLUDE_CPPGC_HEAP_STATISTICS_H_ +#define INCLUDE_CPPGC_HEAP_STATISTICS_H_ + +#include +#include +#include + +namespace cppgc { + +/** + * `HeapStatistics` contains memory consumption and utilization statistics for a + * cppgc heap. + */ +struct HeapStatistics final { + /** + * Specifies the detail level of the heap statistics. Brief statistics contain + * only the top-level allocated and used memory statistics for the entire + * heap. Detailed statistics also contain a break down per space and page, as + * well as freelist statistics and object type histograms. Note that used + * memory reported by brief statistics and detailed statistics might differ + * slightly. + */ + enum DetailLevel : uint8_t { + kBrief, + kDetailed, + }; + + /** + * Statistics of object types. For each type the statistics record its name, + * how many objects of that type were allocated, and the overall size used by + * these objects. + */ + struct ObjectStatistics { + /** Number of distinct types in the heap. */ + size_t num_types = 0; + /** Name of each type in the heap. */ + std::vector type_name; + /** Number of allocated objects per each type. */ + std::vector type_count; + /** Overall size of allocated objects per each type. */ + std::vector type_bytes; + }; + + /** + * Page granularity statistics. For each page the statistics record the + * allocated memory size and overall used memory size for the page. + */ + struct PageStatistics { + /** Overall amount of memory allocated for the page. */ + size_t allocated_size_bytes = 0; + /** Amount of memory actually used on the page. */ + size_t used_size_bytes = 0; + }; + + /** + * Stastistics of the freelist (used only in non-large object spaces). For + * each bucket in the freelist the statistics record the bucket size, the + * number of freelist entries in the bucket, and the overall allocated memory + * consumed by these freelist entries. + */ + struct FreeListStatistics { + /** bucket sizes in the freelist. */ + std::vector bucket_size; + /** number of freelist entries per bucket. */ + std::vector free_count; + /** memory size concumed by freelist entries per size. */ + std::vector free_size; + }; + + /** + * Space granularity statistics. For each space the statistics record the + * space name, the amount of allocated memory and overall used memory for the + * space. The statistics also contain statistics for each of the space's + * pages, its freelist and the objects allocated on the space. + */ + struct SpaceStatistics { + /** The space name */ + std::string name; + /** Overall amount of memory allocated for the space. */ + size_t allocated_size_bytes = 0; + /** Amount of memory actually used on the space. */ + size_t used_size_bytes = 0; + /** Statistics for each of the pages in the space. */ + std::vector page_stats; + /** Statistics for the freelist of the space. */ + FreeListStatistics free_list_stats; + /** Statistics for object allocated on the space. Filled only when + * NameProvider::HideInternalNames() is false. */ + ObjectStatistics object_stats; + }; + + /** Overall amount of memory allocated for the heap. */ + size_t allocated_size_bytes = 0; + /** Amount of memory actually used on the heap. */ + size_t used_size_bytes = 0; + /** Detail level of this HeapStatistics. */ + DetailLevel detail_level; + + /** Statistics for each of the spaces in the heap. Filled only when + * detail_level is kDetailed. */ + std::vector space_stats; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_STATISTICS_H_ diff --git a/include/v8-cppgc.h b/include/v8-cppgc.h index 6bde94c863..27e38778d2 100644 --- a/include/v8-cppgc.h +++ b/include/v8-cppgc.h @@ -10,6 +10,7 @@ #include #include "cppgc/custom-space.h" +#include "cppgc/heap-statistics.h" #include "cppgc/internal/process-heap.h" #include "cppgc/internal/write-barrier.h" #include "cppgc/visitor.h" @@ -106,6 +107,15 @@ class V8_EXPORT CppHeap { */ void Terminate(); + /** + * \param detail_level specifies whether should return detailed + * statistics or only brief summary statistics. + * \returns current CppHeap statistics regarding memory consumption + * and utilization. + */ + cppgc::HeapStatistics CollectStatistics( + cppgc::HeapStatistics::DetailLevel detail_level); + private: CppHeap() = default; diff --git a/src/heap/cppgc-js/cpp-heap.cc b/src/heap/cppgc-js/cpp-heap.cc index 80a34a1264..da38ed309b 100644 --- a/src/heap/cppgc-js/cpp-heap.cc +++ b/src/heap/cppgc-js/cpp-heap.cc @@ -58,6 +58,12 @@ cppgc::HeapHandle& CppHeap::GetHeapHandle() { void CppHeap::Terminate() { internal::CppHeap::From(this)->Terminate(); } +cppgc::HeapStatistics CppHeap::CollectStatistics( + cppgc::HeapStatistics::DetailLevel detail_level) { + return internal::CppHeap::From(this)->AsBase().CollectStatistics( + detail_level); +} + void JSHeapConsistency::DijkstraMarkingBarrierSlow( cppgc::HeapHandle& heap_handle, const TracedReferenceBase& ref) { auto& heap_base = cppgc::internal::HeapBase::From(heap_handle); diff --git a/src/heap/cppgc/free-list.cc b/src/heap/cppgc/free-list.cc index b7252eefe9..934aeaf3f8 100644 --- a/src/heap/cppgc/free-list.cc +++ b/src/heap/cppgc/free-list.cc @@ -191,5 +191,26 @@ bool FreeList::IsConsistent(size_t index) const { !free_list_tails_[index]->Next()); } +void FreeList::CollectStatistics( + HeapStatistics::FreeListStatistics& free_list_stats) { + std::vector& bucket_size = free_list_stats.bucket_size; + std::vector& free_count = free_list_stats.free_count; + std::vector& free_size = free_list_stats.free_size; + DCHECK(bucket_size.empty()); + DCHECK(free_count.empty()); + DCHECK(free_size.empty()); + for (size_t i = 0; i < kPageSizeLog2; ++i) { + size_t entry_count = 0; + size_t entry_size = 0; + for (Entry* entry = free_list_heads_[i]; entry; entry = entry->Next()) { + ++entry_count; + entry_size += entry->GetSize(); + } + bucket_size.push_back(static_cast(1) << i); + free_count.push_back(entry_count); + free_size.push_back(entry_size); + } +} + } // namespace internal } // namespace cppgc diff --git a/src/heap/cppgc/free-list.h b/src/heap/cppgc/free-list.h index ba578f3820..6906952102 100644 --- a/src/heap/cppgc/free-list.h +++ b/src/heap/cppgc/free-list.h @@ -7,6 +7,7 @@ #include +#include "include/cppgc/heap-statistics.h" #include "src/base/macros.h" #include "src/heap/cppgc/globals.h" #include "src/heap/cppgc/heap-object-header.h" @@ -45,6 +46,8 @@ class V8_EXPORT_PRIVATE FreeList { bool Contains(Block) const; + void CollectStatistics(HeapStatistics::FreeListStatistics&); + private: class Entry; diff --git a/src/heap/cppgc/gc-info-table.h b/src/heap/cppgc/gc-info-table.h index b29a33ba0e..61de294426 100644 --- a/src/heap/cppgc/gc-info-table.h +++ b/src/heap/cppgc/gc-info-table.h @@ -63,7 +63,8 @@ class V8_EXPORT GCInfoTable final { return table_[index]; } - GCInfoIndex NumberOfGCInfosForTesting() const { return current_index_; } + GCInfoIndex NumberOfGCInfos() const { return current_index_; } + GCInfoIndex LimitForTesting() const { return limit_; } GCInfo& TableSlotForTesting(GCInfoIndex index) { return table_[index]; } diff --git a/src/heap/cppgc/heap-base.cc b/src/heap/cppgc/heap-base.cc index 6c25d5eaeb..f9d517642f 100644 --- a/src/heap/cppgc/heap-base.cc +++ b/src/heap/cppgc/heap-base.cc @@ -10,6 +10,7 @@ #include "src/heap/cppgc/globals.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/heap-page.h" +#include "src/heap/cppgc/heap-statistics-collector.h" #include "src/heap/cppgc/heap-visitor.h" #include "src/heap/cppgc/marker.h" #include "src/heap/cppgc/marking-verifier.h" @@ -133,5 +134,19 @@ void HeapBase::Terminate() { disallow_gc_scope_++; } +HeapStatistics HeapBase::CollectStatistics( + HeapStatistics::DetailLevel detail_level) { + if (detail_level == HeapStatistics::DetailLevel::kBrief) { + return {stats_collector_->allocated_memory_size(), + stats_collector_->allocated_object_size(), + HeapStatistics::DetailLevel::kBrief, + {}}; + } + + sweeper_.FinishIfRunning(); + object_allocator_.ResetLinearAllocationBuffers(); + return HeapStatisticsCollector().CollectStatistics(this); +} + } // namespace internal } // namespace cppgc diff --git a/src/heap/cppgc/heap-base.h b/src/heap/cppgc/heap-base.h index 9c7ebb672d..59a4d5206c 100644 --- a/src/heap/cppgc/heap-base.h +++ b/src/heap/cppgc/heap-base.h @@ -8,6 +8,7 @@ #include #include +#include "include/cppgc/heap-statistics.h" #include "include/cppgc/heap.h" #include "include/cppgc/internal/persistent-node.h" #include "include/cppgc/macros.h" @@ -163,6 +164,8 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle { void EnableTestingAPIsForTesting() { testing_enabled_ = true; } bool TestingEnabled() const { return testing_enabled_; } + HeapStatistics CollectStatistics(HeapStatistics::DetailLevel); + protected: virtual void FinalizeIncrementalGarbageCollectionIfNeeded( cppgc::Heap::StackState) = 0; diff --git a/src/heap/cppgc/heap-page.cc b/src/heap/cppgc/heap-page.cc index 87a597ac21..29f0380271 100644 --- a/src/heap/cppgc/heap-page.cc +++ b/src/heap/cppgc/heap-page.cc @@ -179,13 +179,12 @@ LargePage::LargePage(HeapBase* heap, BaseSpace* space, size_t size) LargePage::~LargePage() = default; -namespace { -size_t LargePageAllocationSize(size_t payload_size) { +// static +size_t LargePage::AllocationSize(size_t payload_size) { const size_t page_header_size = RoundUp(sizeof(LargePage), kAllocationGranularity); return page_header_size + payload_size; } -} // namespace // static LargePage* LargePage::Create(PageBackend* page_backend, LargePageSpace* space, @@ -194,14 +193,14 @@ LargePage* LargePage::Create(PageBackend* page_backend, LargePageSpace* space, DCHECK_NOT_NULL(space); DCHECK_LE(kLargeObjectSizeThreshold, size); - const size_t allocation_size = LargePageAllocationSize(size); + const size_t allocation_size = AllocationSize(size); auto* heap = space->raw_heap()->heap(); void* memory = page_backend->AllocateLargePageMemory(allocation_size); LargePage* page = new (memory) LargePage(heap, space, size); page->SynchronizedStore(); page->heap()->stats_collector()->NotifyAllocatedMemory( - LargePageAllocationSize(page->PayloadSize())); + AllocationSize(page->PayloadSize())); return page; } @@ -215,7 +214,7 @@ void LargePage::Destroy(LargePage* page) { page->~LargePage(); PageBackend* backend = page->heap()->page_backend(); page->heap()->stats_collector()->NotifyFreedMemory( - LargePageAllocationSize(page->PayloadSize())); + AllocationSize(page->PayloadSize())); backend->FreeLargePageMemory(reinterpret_cast
(page)); } diff --git a/src/heap/cppgc/heap-page.h b/src/heap/cppgc/heap-page.h index 0a228a6131..5e238e5bb7 100644 --- a/src/heap/cppgc/heap-page.h +++ b/src/heap/cppgc/heap-page.h @@ -185,6 +185,8 @@ class V8_EXPORT_PRIVATE NormalPage final : public BasePage { class V8_EXPORT_PRIVATE LargePage final : public BasePage { public: + // Returns the allocation size required for a payload of size |size|. + static size_t AllocationSize(size_t size); // Allocates a new page in the detached state. static LargePage* Create(PageBackend*, LargePageSpace*, size_t); // Destroys and frees the page. The page must be detached from the diff --git a/src/heap/cppgc/heap-statistics-collector.cc b/src/heap/cppgc/heap-statistics-collector.cc new file mode 100644 index 0000000000..a7118de713 --- /dev/null +++ b/src/heap/cppgc/heap-statistics-collector.cc @@ -0,0 +1,158 @@ +// Copyright 2021 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-statistics-collector.h" + +#include + +#include "include/cppgc/name-provider.h" +#include "src/heap/cppgc/free-list.h" +#include "src/heap/cppgc/heap-base.h" +#include "src/heap/cppgc/heap-object-header.h" +#include "src/heap/cppgc/raw-heap.h" +#include "src/heap/cppgc/stats-collector.h" + +namespace cppgc { +namespace internal { + +namespace { + +std::string GetNormalPageSpaceName(size_t index) { + // Check that space is not a large object space. + DCHECK_NE(RawHeap::kNumberOfRegularSpaces - 1, index); + // Handle regular normal page spaces. + if (index < RawHeap::kNumberOfRegularSpaces) { + return "NormalPageSpace" + std::to_string(index); + } + // Space is a custom space. + return "CustomSpace" + + std::to_string(index - RawHeap::kNumberOfRegularSpaces); +} + +HeapStatistics::SpaceStatistics* InitializeSpace(HeapStatistics* stats, + std::string name) { + stats->space_stats.emplace_back(); + HeapStatistics::SpaceStatistics* space_stats = &stats->space_stats.back(); + space_stats->name = std::move(name); + + if (!NameProvider::HideInternalNames()) { + const size_t num_types = GlobalGCInfoTable::Get().NumberOfGCInfos(); + space_stats->object_stats.num_types = num_types; + space_stats->object_stats.type_name.resize(num_types); + space_stats->object_stats.type_count.resize(num_types); + space_stats->object_stats.type_bytes.resize(num_types); + } + + return space_stats; +} + +void FinalizePage(HeapStatistics::SpaceStatistics* space_stats, + HeapStatistics::PageStatistics** page_stats) { + if (*page_stats) { + DCHECK_NOT_NULL(space_stats); + space_stats->allocated_size_bytes += (*page_stats)->allocated_size_bytes; + space_stats->used_size_bytes += (*page_stats)->used_size_bytes; + } + *page_stats = nullptr; +} + +void FinalizeSpace(HeapStatistics* stats, + HeapStatistics::SpaceStatistics** space_stats, + HeapStatistics::PageStatistics** page_stats) { + FinalizePage(*space_stats, page_stats); + if (*space_stats) { + DCHECK_NOT_NULL(stats); + stats->allocated_size_bytes += (*space_stats)->allocated_size_bytes; + stats->used_size_bytes += (*space_stats)->used_size_bytes; + } + *space_stats = nullptr; +} + +void RecordObjectType(HeapStatistics::SpaceStatistics* space_stats, + HeapObjectHeader* header, size_t object_size) { + if (!NameProvider::HideInternalNames()) { + // Detailed names available. + GCInfoIndex gc_info_index = header->GetGCInfoIndex(); + space_stats->object_stats.type_count[gc_info_index]++; + space_stats->object_stats.type_bytes[gc_info_index] += object_size; + if (space_stats->object_stats.type_name[gc_info_index].empty()) { + space_stats->object_stats.type_name[gc_info_index] = + header->GetName().value; + } + } +} + +} // namespace + +HeapStatistics HeapStatisticsCollector::CollectStatistics(HeapBase* heap) { + HeapStatistics stats; + stats.detail_level = HeapStatistics::DetailLevel::kDetailed; + current_stats_ = &stats; + + Traverse(&heap->raw_heap()); + FinalizeSpace(current_stats_, ¤t_space_stats_, ¤t_page_stats_); + + DCHECK_EQ(heap->stats_collector()->allocated_memory_size(), + stats.allocated_size_bytes); + return stats; +} + +bool HeapStatisticsCollector::VisitNormalPageSpace(NormalPageSpace* space) { + DCHECK_EQ(0u, space->linear_allocation_buffer().size()); + + FinalizeSpace(current_stats_, ¤t_space_stats_, ¤t_page_stats_); + + current_space_stats_ = + InitializeSpace(current_stats_, GetNormalPageSpaceName(space->index())); + + space->free_list().CollectStatistics(current_space_stats_->free_list_stats); + + return false; +} + +bool HeapStatisticsCollector::VisitLargePageSpace(LargePageSpace* space) { + FinalizeSpace(current_stats_, ¤t_space_stats_, ¤t_page_stats_); + + current_space_stats_ = InitializeSpace(current_stats_, "LargePageSpace"); + + return false; +} + +bool HeapStatisticsCollector::VisitNormalPage(NormalPage* page) { + DCHECK_NOT_NULL(current_space_stats_); + FinalizePage(current_space_stats_, ¤t_page_stats_); + current_space_stats_->page_stats.emplace_back( + HeapStatistics::PageStatistics{kPageSize, 0}); + current_page_stats_ = ¤t_space_stats_->page_stats.back(); + return false; +} + +bool HeapStatisticsCollector::VisitLargePage(LargePage* page) { + DCHECK_NOT_NULL(current_space_stats_); + FinalizePage(current_space_stats_, ¤t_page_stats_); + HeapObjectHeader* object_header = page->ObjectHeader(); + size_t object_size = page->PayloadSize(); + RecordObjectType(current_space_stats_, object_header, object_size); + size_t allocated_size = LargePage::AllocationSize(object_size); + current_space_stats_->allocated_size_bytes += allocated_size; + current_space_stats_->used_size_bytes += object_size; + current_space_stats_->page_stats.emplace_back( + HeapStatistics::PageStatistics{allocated_size, object_size}); + + return true; +} + +bool HeapStatisticsCollector::VisitHeapObjectHeader(HeapObjectHeader* header) { + DCHECK(!header->IsLargeObject()); + DCHECK_NOT_NULL(current_space_stats_); + DCHECK_NOT_NULL(current_page_stats_); + if (header->IsFree()) return true; + size_t object_size = header->GetSize(); + RecordObjectType(current_space_stats_, header, object_size); + current_page_stats_->used_size_bytes += object_size; + return true; +} + +} // namespace internal +} // namespace cppgc diff --git a/src/heap/cppgc/heap-statistics-collector.h b/src/heap/cppgc/heap-statistics-collector.h new file mode 100644 index 0000000000..52c92198a8 --- /dev/null +++ b/src/heap/cppgc/heap-statistics-collector.h @@ -0,0 +1,35 @@ +// Copyright 2021 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. + +#ifndef V8_HEAP_CPPGC_HEAP_STATISTICS_COLLECTOR_H_ +#define V8_HEAP_CPPGC_HEAP_STATISTICS_COLLECTOR_H_ + +#include "include/cppgc/heap-statistics.h" +#include "src/heap/cppgc/heap-visitor.h" + +namespace cppgc { +namespace internal { + +class HeapStatisticsCollector : private HeapVisitor { + friend class HeapVisitor; + + public: + HeapStatistics CollectStatistics(HeapBase*); + + private: + bool VisitNormalPageSpace(NormalPageSpace*); + bool VisitLargePageSpace(LargePageSpace*); + bool VisitNormalPage(NormalPage*); + bool VisitLargePage(LargePage*); + bool VisitHeapObjectHeader(HeapObjectHeader*); + + HeapStatistics* current_stats_; + HeapStatistics::SpaceStatistics* current_space_stats_ = nullptr; + HeapStatistics::PageStatistics* current_page_stats_ = nullptr; +}; + +} // namespace internal +} // namespace cppgc + +#endif // V8_HEAP_CPPGC_HEAP_STATISTICS_COLLECTOR_H_ diff --git a/src/heap/cppgc/stats-collector.cc b/src/heap/cppgc/stats-collector.cc index a39049f2c9..ab7c02e217 100644 --- a/src/heap/cppgc/stats-collector.cc +++ b/src/heap/cppgc/stats-collector.cc @@ -215,6 +215,10 @@ void StatsCollector::NotifySweepingCompleted() { } } +size_t StatsCollector::allocated_memory_size() const { + return memory_allocated_bytes_; +} + size_t StatsCollector::allocated_object_size() const { // During sweeping we refer to the current Event as that already holds the // correct marking information. In all other phases, the previous event holds diff --git a/src/heap/cppgc/stats-collector.h b/src/heap/cppgc/stats-collector.h index 093977c0e1..a2299a80e6 100644 --- a/src/heap/cppgc/stats-collector.h +++ b/src/heap/cppgc/stats-collector.h @@ -263,6 +263,7 @@ class V8_EXPORT_PRIVATE StatsCollector final { // is finished at this point. void NotifySweepingCompleted(); + size_t allocated_memory_size() const; // Size of live objects in bytes on the heap. Based on the most recent marked // bytes and the bytes allocated since last marking. size_t allocated_object_size() const; diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn index 3ecc0c50d0..6c36a0462b 100644 --- a/test/unittests/BUILD.gn +++ b/test/unittests/BUILD.gn @@ -100,6 +100,7 @@ v8_source_set("cppgc_unittests_sources") { "heap/cppgc/heap-growing-unittest.cc", "heap/cppgc/heap-object-header-unittest.cc", "heap/cppgc/heap-page-unittest.cc", + "heap/cppgc/heap-statistics-collector-unittest.cc", "heap/cppgc/heap-unittest.cc", "heap/cppgc/incremental-marking-schedule-unittest.cc", "heap/cppgc/logging-unittest.cc", diff --git a/test/unittests/heap/cppgc/gc-info-unittest.cc b/test/unittests/heap/cppgc/gc-info-unittest.cc index 9c48621e10..3d951dc6cf 100644 --- a/test/unittests/heap/cppgc/gc-info-unittest.cc +++ b/test/unittests/heap/cppgc/gc-info-unittest.cc @@ -23,7 +23,7 @@ constexpr GCInfo GetEmptyGCInfo() { return {nullptr, nullptr, nullptr, false}; } TEST(GCInfoTableTest, InitialEmpty) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); - EXPECT_EQ(GCInfoTable::kMinIndex, table.NumberOfGCInfosForTesting()); + EXPECT_EQ(GCInfoTable::kMinIndex, table.NumberOfGCInfos()); } TEST(GCInfoTableTest, ResizeToMaxIndex) { diff --git a/test/unittests/heap/cppgc/heap-statistics-collector-unittest.cc b/test/unittests/heap/cppgc/heap-statistics-collector-unittest.cc new file mode 100644 index 0000000000..eb077b3912 --- /dev/null +++ b/test/unittests/heap/cppgc/heap-statistics-collector-unittest.cc @@ -0,0 +1,131 @@ +// Copyright 2021 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-statistics-collector.h" + +#include "include/cppgc/heap-statistics.h" +#include "src/base/macros.h" +#include "src/heap/cppgc/globals.h" +#include "test/unittests/heap/cppgc/tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cppgc { +namespace internal { + +class HeapStatisticsCollectorTest : public testing::TestWithHeap {}; + +TEST_F(HeapStatisticsCollectorTest, EmptyHeapBriefStatisitcs) { + HeapStatistics brief_stats = Heap::From(GetHeap())->CollectStatistics( + HeapStatistics::DetailLevel::kBrief); + EXPECT_EQ(HeapStatistics::DetailLevel::kBrief, brief_stats.detail_level); + EXPECT_EQ(0u, brief_stats.used_size_bytes); + EXPECT_EQ(0u, brief_stats.used_size_bytes); + EXPECT_TRUE(brief_stats.space_stats.empty()); +} + +TEST_F(HeapStatisticsCollectorTest, EmptyHeapDetailedStatisitcs) { + HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics( + HeapStatistics::DetailLevel::kDetailed); + EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed, + detailed_stats.detail_level); + EXPECT_EQ(0u, detailed_stats.used_size_bytes); + EXPECT_EQ(0u, detailed_stats.used_size_bytes); + EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size()); + for (HeapStatistics::SpaceStatistics& space_stats : + detailed_stats.space_stats) { + EXPECT_EQ(0u, space_stats.used_size_bytes); + EXPECT_EQ(0u, space_stats.used_size_bytes); + EXPECT_TRUE(space_stats.page_stats.empty()); + if (space_stats.name == "LargePageSpace") { + // Large page space has no free list. + EXPECT_TRUE(space_stats.free_list_stats.bucket_size.empty()); + EXPECT_TRUE(space_stats.free_list_stats.free_count.empty()); + EXPECT_TRUE(space_stats.free_list_stats.free_size.empty()); + } else { + EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.bucket_size.size()); + EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.free_count.size()); + EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.free_size.size()); + } + } +} + +namespace { +template +class GCed : public GarbageCollected> { + public: + void Trace(Visitor*) const {} + + private: + char array_[Size]; +}; +} // namespace + +TEST_F(HeapStatisticsCollectorTest, NonEmptyNormalPage) { + MakeGarbageCollected>(GetHeap()->GetAllocationHandle()); + static constexpr size_t used_size = + RoundUp(1 + sizeof(HeapObjectHeader)); + HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics( + HeapStatistics::DetailLevel::kDetailed); + EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed, + detailed_stats.detail_level); + EXPECT_EQ(kPageSize, detailed_stats.allocated_size_bytes); + EXPECT_EQ(used_size, detailed_stats.used_size_bytes); + EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size()); + bool found_non_empty_space = false; + for (const HeapStatistics::SpaceStatistics& space_stats : + detailed_stats.space_stats) { + if (space_stats.page_stats.empty()) { + EXPECT_EQ(0u, space_stats.allocated_size_bytes); + EXPECT_EQ(0u, space_stats.used_size_bytes); + continue; + } + EXPECT_NE("LargePageSpace", space_stats.name); + EXPECT_FALSE(found_non_empty_space); + found_non_empty_space = true; + EXPECT_EQ(kPageSize, space_stats.allocated_size_bytes); + EXPECT_EQ(used_size, space_stats.used_size_bytes); + EXPECT_EQ(1u, space_stats.page_stats.size()); + EXPECT_EQ(kPageSize, space_stats.page_stats.back().allocated_size_bytes); + EXPECT_EQ(used_size, space_stats.page_stats.back().used_size_bytes); + } + EXPECT_TRUE(found_non_empty_space); +} + +TEST_F(HeapStatisticsCollectorTest, NonEmptyLargePage) { + MakeGarbageCollected>( + GetHeap()->GetAllocationHandle()); + static constexpr size_t used_size = RoundUp( + kLargeObjectSizeThreshold + sizeof(HeapObjectHeader)); + static constexpr size_t allocated_size = + RoundUp(used_size + sizeof(LargePage)); + HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics( + HeapStatistics::DetailLevel::kDetailed); + EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed, + detailed_stats.detail_level); + EXPECT_EQ(allocated_size, detailed_stats.allocated_size_bytes); + EXPECT_EQ(used_size, detailed_stats.used_size_bytes); + EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size()); + bool found_non_empty_space = false; + for (const HeapStatistics::SpaceStatistics& space_stats : + detailed_stats.space_stats) { + if (space_stats.page_stats.empty()) { + EXPECT_EQ(0u, space_stats.allocated_size_bytes); + EXPECT_EQ(0u, space_stats.used_size_bytes); + continue; + } + EXPECT_EQ("LargePageSpace", space_stats.name); + EXPECT_FALSE(found_non_empty_space); + found_non_empty_space = true; + EXPECT_EQ(allocated_size, space_stats.allocated_size_bytes); + EXPECT_EQ(used_size, space_stats.used_size_bytes); + EXPECT_EQ(1u, space_stats.page_stats.size()); + EXPECT_EQ(allocated_size, + space_stats.page_stats.back().allocated_size_bytes); + EXPECT_EQ(used_size, space_stats.page_stats.back().used_size_bytes); + } + EXPECT_TRUE(found_non_empty_space); +} + +} // namespace internal +} // namespace cppgc