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_, ¤t_space_stats_, ¤t_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_, ¤t_space_stats_, ¤t_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_, ¤t_space_stats_, ¤t_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_, ¤t_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_, ¤t_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