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
19namespace cppgc {
20namespace internal {
21
22namespace {
23
24std::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
36HeapStatistics::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
44HeapStatistics::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
51void 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
62void 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
75void 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
94HeapStatistics 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
121bool 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
134bool 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
142bool 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
153bool 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
165bool 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