1 // Copyright 2021 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/heap/cppgc/heap-statistics-collector.h"
6 
7 #include <string>
8 #include <unordered_map>
9 
10 #include "include/cppgc/heap-statistics.h"
11 #include "include/cppgc/name-provider.h"
12 #include "src/heap/cppgc/free-list.h"
13 #include "src/heap/cppgc/globals.h"
14 #include "src/heap/cppgc/heap-base.h"
15 #include "src/heap/cppgc/heap-object-header.h"
16 #include "src/heap/cppgc/raw-heap.h"
17 #include "src/heap/cppgc/stats-collector.h"
18 
19 namespace cppgc {
20 namespace internal {
21 
22 namespace {
23 
GetNormalPageSpaceName(size_t index)24 std::string GetNormalPageSpaceName(size_t index) {
25   // Check that space is not a large object space.
26   DCHECK_NE(RawHeap::kNumberOfRegularSpaces - 1, index);
27   // Handle regular normal page spaces.
28   if (index < RawHeap::kNumberOfRegularSpaces) {
29     return "NormalPageSpace" + std::to_string(index);
30   }
31   // Space is a custom space.
32   return "CustomSpace" +
33          std::to_string(index - RawHeap::kNumberOfRegularSpaces);
34 }
35 
InitializeSpace(HeapStatistics* stats, std::string name)36 HeapStatistics::SpaceStatistics* InitializeSpace(HeapStatistics* stats,
37                                                  std::string name) {
38   stats->space_stats.emplace_back();
39   HeapStatistics::SpaceStatistics* space_stats = &stats->space_stats.back();
40   space_stats->name = std::move(name);
41   return space_stats;
42 }
43 
InitializePage( HeapStatistics::SpaceStatistics* stats)44 HeapStatistics::PageStatistics* InitializePage(
45     HeapStatistics::SpaceStatistics* stats) {
46   stats->page_stats.emplace_back();
47   HeapStatistics::PageStatistics* page_stats = &stats->page_stats.back();
48   return page_stats;
49 }
50 
FinalizePage(HeapStatistics::SpaceStatistics* space_stats, HeapStatistics::PageStatistics** page_stats)51 void FinalizePage(HeapStatistics::SpaceStatistics* space_stats,
52                   HeapStatistics::PageStatistics** page_stats) {
53   if (*page_stats) {
54     DCHECK_NOT_NULL(space_stats);
55     space_stats->committed_size_bytes += (*page_stats)->committed_size_bytes;
56     space_stats->resident_size_bytes += (*page_stats)->resident_size_bytes;
57     space_stats->used_size_bytes += (*page_stats)->used_size_bytes;
58   }
59   *page_stats = nullptr;
60 }
61 
FinalizeSpace(HeapStatistics* stats, HeapStatistics::SpaceStatistics** space_stats, HeapStatistics::PageStatistics** page_stats)62 void FinalizeSpace(HeapStatistics* stats,
63                    HeapStatistics::SpaceStatistics** space_stats,
64                    HeapStatistics::PageStatistics** page_stats) {
65   FinalizePage(*space_stats, page_stats);
66   if (*space_stats) {
67     DCHECK_NOT_NULL(stats);
68     stats->committed_size_bytes += (*space_stats)->committed_size_bytes;
69     stats->resident_size_bytes += (*space_stats)->resident_size_bytes;
70     stats->used_size_bytes += (*space_stats)->used_size_bytes;
71   }
72   *space_stats = nullptr;
73 }
74 
RecordObjectType( std::unordered_map<const void*, size_t>& type_map, std::vector<HeapStatistics::ObjectStatsEntry>& object_statistics, HeapObjectHeader* header, size_t object_size)75 void RecordObjectType(
76     std::unordered_map<const void*, size_t>& type_map,
77     std::vector<HeapStatistics::ObjectStatsEntry>& object_statistics,
78     HeapObjectHeader* header, size_t object_size) {
79   if (!NameProvider::HideInternalNames()) {
80     // Tries to insert a new entry into the typemap with a running counter. If
81     // the entry is already present, just returns the old one.
82     const auto it = type_map.insert({header->GetName().value, type_map.size()});
83     const size_t type_index = it.first->second;
84     if (object_statistics.size() <= type_index) {
85       object_statistics.resize(type_index + 1);
86     }
87     object_statistics[type_index].allocated_bytes += object_size;
88     object_statistics[type_index].object_count++;
89   }
90 }
91 
92 }  // namespace
93 
CollectDetailedStatistics( HeapBase* heap)94 HeapStatistics HeapStatisticsCollector::CollectDetailedStatistics(
95     HeapBase* heap) {
96   HeapStatistics stats;
97   stats.detail_level = HeapStatistics::DetailLevel::kDetailed;
98   current_stats_ = &stats;
99 
100   if (!NameProvider::HideInternalNames()) {
101     // Add a dummy type so that a type index of zero has a valid mapping but
102     // shows an invalid type.
103     type_name_to_index_map_.insert({"Invalid type", 0});
104   }
105 
106   Traverse(heap->raw_heap());
107   FinalizeSpace(current_stats_, &current_space_stats_, &current_page_stats_);
108 
109   if (!NameProvider::HideInternalNames()) {
110     stats.type_names.resize(type_name_to_index_map_.size());
111     for (auto& it : type_name_to_index_map_) {
112       stats.type_names[it.second] = reinterpret_cast<const char*>(it.first);
113     }
114   }
115 
116   DCHECK_EQ(heap->stats_collector()->allocated_memory_size(),
117             stats.resident_size_bytes);
118   return stats;
119 }
120 
VisitNormalPageSpace(NormalPageSpace& space)121 bool HeapStatisticsCollector::VisitNormalPageSpace(NormalPageSpace& space) {
122   DCHECK_EQ(0u, space.linear_allocation_buffer().size());
123 
124   FinalizeSpace(current_stats_, &current_space_stats_, &current_page_stats_);
125 
126   current_space_stats_ =
127       InitializeSpace(current_stats_, GetNormalPageSpaceName(space.index()));
128 
129   space.free_list().CollectStatistics(current_space_stats_->free_list_stats);
130 
131   return false;
132 }
133 
VisitLargePageSpace(LargePageSpace& space)134 bool HeapStatisticsCollector::VisitLargePageSpace(LargePageSpace& space) {
135   FinalizeSpace(current_stats_, &current_space_stats_, &current_page_stats_);
136 
137   current_space_stats_ = InitializeSpace(current_stats_, "LargePageSpace");
138 
139   return false;
140 }
141 
VisitNormalPage(NormalPage& page)142 bool HeapStatisticsCollector::VisitNormalPage(NormalPage& page) {
143   DCHECK_NOT_NULL(current_space_stats_);
144   FinalizePage(current_space_stats_, &current_page_stats_);
145 
146   current_page_stats_ = InitializePage(current_space_stats_);
147   current_page_stats_->committed_size_bytes = kPageSize;
148   current_page_stats_->resident_size_bytes =
149       kPageSize - page.discarded_memory();
150   return false;
151 }
152 
VisitLargePage(LargePage& page)153 bool HeapStatisticsCollector::VisitLargePage(LargePage& page) {
154   DCHECK_NOT_NULL(current_space_stats_);
155   FinalizePage(current_space_stats_, &current_page_stats_);
156 
157   const size_t object_size = page.PayloadSize();
158   const size_t allocated_size = LargePage::AllocationSize(object_size);
159   current_page_stats_ = InitializePage(current_space_stats_);
160   current_page_stats_->committed_size_bytes = allocated_size;
161   current_page_stats_->resident_size_bytes = allocated_size;
162   return false;
163 }
164 
VisitHeapObjectHeader(HeapObjectHeader& header)165 bool HeapStatisticsCollector::VisitHeapObjectHeader(HeapObjectHeader& header) {
166   if (header.IsFree()) return true;
167 
168   DCHECK_NOT_NULL(current_space_stats_);
169   DCHECK_NOT_NULL(current_page_stats_);
170   // For the purpose of heap statistics, the header counts towards the allocated
171   // object size.
172   const size_t allocated_object_size =
173       header.IsLargeObject()
174           ? LargePage::From(
175                 BasePage::FromPayload(const_cast<HeapObjectHeader*>(&header)))
176                 ->PayloadSize()
177           : header.AllocatedSize();
178   RecordObjectType(type_name_to_index_map_,
179                    current_page_stats_->object_statistics, &header,
180                    allocated_object_size);
181   current_page_stats_->used_size_bytes += allocated_object_size;
182   return true;
183 }
184 
185 }  // namespace internal
186 }  // namespace cppgc
187