1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "gin/v8_isolate_memory_dump_provider.h"
10 #include "base/check_op.h"
11 #include "base/logging.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/task/single_thread_task_runner.h"
14 #include "base/trace_event/memory_dump_manager.h"
15 #include "base/trace_event/process_memory_dump.h"
16 #include "gin/public/isolate_holder.h"
17 #include "v8/include/v8-isolate.h"
18 #include "v8/include/v8-locker.h"
19 #include "v8/include/v8-statistics.h"
23 V8IsolateMemoryDumpProvider::V8IsolateMemoryDumpProvider(
24 IsolateHolder* isolate_holder,
25 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
26 : isolate_holder_(isolate_holder) {
28 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
29 this, "V8Isolate", task_runner);
32 V8IsolateMemoryDumpProvider::~V8IsolateMemoryDumpProvider() {
33 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
37 // Called at trace dump point time. Creates a snapshot with the memory counters
38 // for the current isolate.
39 bool V8IsolateMemoryDumpProvider::OnMemoryDump(
40 const base::trace_event::MemoryDumpArgs& args,
41 base::trace_event::ProcessMemoryDump* process_memory_dump) {
42 // TODO(ssid): Use MemoryDumpArgs to create light dumps when requested
43 // (crbug.com/499731).
45 if (isolate_holder_->access_mode() == IsolateHolder::kUseLocker) {
46 v8::Locker locked(isolate_holder_->isolate());
47 DumpHeapStatistics(args, process_memory_dump);
49 DumpHeapStatistics(args, process_memory_dump);
56 // Dump statistics related to code/bytecode when memory-infra.v8.code_stats is
58 void DumpCodeStatistics(base::trace_event::MemoryAllocatorDump* dump,
59 IsolateHolder* isolate_holder) {
60 // Collecting code statistics is an expensive operation (~10 ms) when
61 // compared to other v8 metrics (< 1 ms). So, dump them only when
62 // memory-infra.v8.code_stats is enabled.
63 // TODO(primiano): This information should be plumbed through TraceConfig.
64 // See crbug.com/616441.
65 bool dump_code_stats = false;
66 TRACE_EVENT_CATEGORY_GROUP_ENABLED(
67 TRACE_DISABLED_BY_DEFAULT("memory-infra.v8.code_stats"),
72 v8::HeapCodeStatistics code_statistics;
73 if (!isolate_holder->isolate()->GetHeapCodeAndMetadataStatistics(
78 dump->AddScalar("code_and_metadata_size",
79 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
80 code_statistics.code_and_metadata_size());
81 dump->AddScalar("bytecode_and_metadata_size",
82 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
83 code_statistics.bytecode_and_metadata_size());
84 dump->AddScalar("external_script_source_size",
85 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
86 code_statistics.external_script_source_size());
87 dump->AddScalar("cpu_profiler_metadata_size",
88 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
89 code_statistics.cpu_profiler_metadata_size());
92 // Dump the number of native and detached contexts.
93 // The result looks as follows in the Chrome trace viewer:
94 // ========================================
95 // Component object_count
99 // - detached_context 10
100 // - native_context 20
103 // - detached_context
104 // - isolate_0x1234 10
106 // - isolate_0x1234 20
107 // ========================================
108 void DumpContextStatistics(
109 base::trace_event::ProcessMemoryDump* process_memory_dump,
110 std::string dump_base_name,
111 std::string dump_name_suffix,
112 size_t number_of_detached_contexts,
113 size_t number_of_native_contexts) {
114 std::string dump_name_prefix = dump_base_name + "/contexts";
115 std::string native_context_name =
116 dump_name_prefix + "/native_context" + dump_name_suffix;
117 auto* native_context_dump =
118 process_memory_dump->CreateAllocatorDump(native_context_name);
119 native_context_dump->AddScalar(
120 "object_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects,
121 number_of_native_contexts);
122 std::string detached_context_name =
123 dump_name_prefix + "/detached_context" + dump_name_suffix;
124 auto* detached_context_dump =
125 process_memory_dump->CreateAllocatorDump(detached_context_name);
126 detached_context_dump->AddScalar(
127 "object_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects,
128 number_of_detached_contexts);
131 std::string IsolateTypeString(IsolateHolder::IsolateType isolate_type) {
132 switch (isolate_type) {
133 case IsolateHolder::IsolateType::kBlinkMainThread:
135 case IsolateHolder::IsolateType::kBlinkWorkerThread:
137 case IsolateHolder::IsolateType::kTest:
138 LOG(FATAL) << "Unreachable code";
140 case IsolateHolder::IsolateType::kUtility:
143 LOG(FATAL) << "Unreachable code";
146 bool CanHaveMultipleIsolates(IsolateHolder::IsolateType isolate_type) {
147 switch (isolate_type) {
148 case IsolateHolder::IsolateType::kBlinkMainThread:
150 case IsolateHolder::IsolateType::kBlinkWorkerThread:
152 case IsolateHolder::IsolateType::kTest:
153 LOG(FATAL) << "Unreachable code";
155 case IsolateHolder::IsolateType::kUtility:
156 // PDFium and ProxyResolver create one isolate per process.
159 LOG(FATAL) << "Unreachable code";
164 void V8IsolateMemoryDumpProvider::DumpHeapStatistics(
165 const base::trace_event::MemoryDumpArgs& args,
166 base::trace_event::ProcessMemoryDump* process_memory_dump) {
167 if (args.determinism == base::trace_event::MemoryDumpDeterminism::kForceGc) {
168 // Force GC in V8 using the same API as DevTools uses in "collectGarbage".
169 isolate_holder_->isolate()->LowMemoryNotification();
171 std::string isolate_name = base::StringPrintf(
172 "isolate_0x%" PRIXPTR,
173 reinterpret_cast<uintptr_t>(isolate_holder_->isolate()));
175 // Dump statistics of the heap's spaces.
176 v8::HeapStatistics heap_statistics;
177 // The total heap sizes should be sampled before the individual space sizes
178 // because of concurrent allocation. DCHECKs below rely on this order.
179 isolate_holder_->isolate()->GetHeapStatistics(&heap_statistics);
181 IsolateHolder::IsolateType isolate_type = isolate_holder_->isolate_type();
182 std::string dump_base_name = "v8/" + IsolateTypeString(isolate_type);
183 std::string dump_name_suffix =
184 CanHaveMultipleIsolates(isolate_type) ? "/" + isolate_name : "";
186 std::string space_name_prefix = dump_base_name + "/heap";
188 size_t known_spaces_size = 0;
189 size_t known_spaces_physical_size = 0;
190 size_t number_of_spaces = isolate_holder_->isolate()->NumberOfHeapSpaces();
191 for (size_t space = 0; space < number_of_spaces; space++) {
192 v8::HeapSpaceStatistics space_statistics;
193 isolate_holder_->isolate()->GetHeapSpaceStatistics(&space_statistics,
195 const size_t space_size = space_statistics.space_size();
196 const size_t space_used_size = space_statistics.space_used_size();
197 const size_t space_physical_size = space_statistics.physical_space_size();
199 known_spaces_size += space_size;
200 known_spaces_physical_size += space_physical_size;
202 std::string space_dump_name = dump_base_name + "/heap/" +
203 space_statistics.space_name() +
207 process_memory_dump->CreateAllocatorDump(space_dump_name);
208 space_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
209 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
210 space_physical_size);
211 space_dump->AddScalar("virtual_size",
212 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
215 space_dump->AddScalar("allocated_objects_size",
216 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
220 // Sanity check that all spaces are accounted for in GetHeapSpaceStatistics.
221 // Background threads may be running and allocating concurrently, so the sum
222 // of space sizes may exceed the total heap size that was sampled earlier.
223 DCHECK_LE(heap_statistics.total_heap_size(), known_spaces_size);
225 // If V8 zaps garbage, all the memory mapped regions become resident,
226 // so we add an extra dump to avoid mismatches w.r.t. the total
228 if (heap_statistics.does_zap_garbage()) {
229 auto* zap_dump = process_memory_dump->CreateAllocatorDump(
230 dump_base_name + "/zapped_for_debug" + dump_name_suffix);
231 size_t zapped_size_for_debugging =
232 known_spaces_size >= known_spaces_physical_size
233 ? known_spaces_size - known_spaces_physical_size
235 zap_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
236 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
237 zapped_size_for_debugging);
240 // Dump statistics about malloced memory.
241 std::string malloc_name = dump_base_name + "/malloc" + dump_name_suffix;
242 auto* malloc_dump = process_memory_dump->CreateAllocatorDump(malloc_name);
243 malloc_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
244 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
245 heap_statistics.malloced_memory());
246 malloc_dump->AddScalar("peak_size",
247 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
248 heap_statistics.peak_malloced_memory());
249 const char* system_allocator_name =
250 base::trace_event::MemoryDumpManager::GetInstance()
251 ->system_allocator_pool_name();
252 if (system_allocator_name) {
253 process_memory_dump->AddSuballocation(malloc_dump->guid(),
254 system_allocator_name);
257 DumpContextStatistics(process_memory_dump, dump_base_name, dump_name_suffix,
258 heap_statistics.number_of_detached_contexts(),
259 heap_statistics.number_of_native_contexts());
261 auto* code_stats_dump = process_memory_dump->CreateAllocatorDump(
262 dump_base_name + "/code_stats" + dump_name_suffix);
264 // Dump statistics related to code and bytecode if requested.
265 DumpCodeStatistics(code_stats_dump, isolate_holder_);
267 // Dump statistics for global handles.
268 auto* global_handles_dump = process_memory_dump->CreateAllocatorDump(
269 dump_base_name + "/global_handles" + dump_name_suffix);
270 global_handles_dump->AddScalar(
271 base::trace_event::MemoryAllocatorDump::kNameSize,
272 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
273 heap_statistics.total_global_handles_size());
274 global_handles_dump->AddScalar(
275 "allocated_objects_size",
276 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
277 heap_statistics.used_global_handles_size());
278 if (system_allocator_name) {
279 process_memory_dump->AddSuballocation(global_handles_dump->guid(),
280 system_allocator_name);
283 // Dump object statistics only for detailed dumps.
284 if (args.level_of_detail !=
285 base::trace_event::MemoryDumpLevelOfDetail::kDetailed) {
289 // Dump statistics of the heap's live objects from last GC.
290 // TODO(primiano): these should not be tracked in the same trace event as they
291 // report stats for the last GC (not the current state). See crbug.com/498779.
292 std::string object_name_prefix =
293 dump_base_name + "/heap_objects_at_last_gc" + dump_name_suffix;
294 bool did_dump_object_stats = false;
295 const size_t object_types =
296 isolate_holder_->isolate()->NumberOfTrackedHeapObjectTypes();
297 for (size_t type_index = 0; type_index < object_types; type_index++) {
298 v8::HeapObjectStatistics object_statistics;
299 if (!isolate_holder_->isolate()->GetHeapObjectStatisticsAtLastGC(
300 &object_statistics, type_index))
303 std::string dump_name =
304 object_name_prefix + "/" + object_statistics.object_type();
305 if (object_statistics.object_sub_type()[0] != '\0')
306 dump_name += std::string("/") + object_statistics.object_sub_type();
307 auto* object_dump = process_memory_dump->CreateAllocatorDump(dump_name);
309 object_dump->AddScalar(
310 base::trace_event::MemoryAllocatorDump::kNameObjectCount,
311 base::trace_event::MemoryAllocatorDump::kUnitsObjects,
312 object_statistics.object_count());
313 object_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
314 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
315 object_statistics.object_size());
316 did_dump_object_stats = true;
319 if (process_memory_dump->GetAllocatorDump(object_name_prefix +
321 auto* code_kind_dump = process_memory_dump->CreateAllocatorDump(
322 object_name_prefix + "/CODE_TYPE/CODE_KIND");
323 auto* code_age_dump = process_memory_dump->CreateAllocatorDump(
324 object_name_prefix + "/CODE_TYPE/CODE_AGE");
325 process_memory_dump->AddOwnershipEdge(code_kind_dump->guid(),
326 code_age_dump->guid());
329 if (did_dump_object_stats) {
330 process_memory_dump->AddOwnershipEdge(
331 process_memory_dump->CreateAllocatorDump(object_name_prefix)->guid(),
332 process_memory_dump->GetOrCreateAllocatorDump(space_name_prefix)