123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- // Copyright 2015 The Chromium 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 "gin/v8_isolate_memory_dump_provider.h"
- #include <inttypes.h>
- #include <stddef.h>
- #include "base/logging.h"
- #include "base/strings/stringprintf.h"
- #include "base/trace_event/memory_dump_manager.h"
- #include "base/trace_event/process_memory_dump.h"
- #include "gin/public/isolate_holder.h"
- #include "v8/include/v8-isolate.h"
- #include "v8/include/v8-locker.h"
- #include "v8/include/v8-statistics.h"
- namespace gin {
- V8IsolateMemoryDumpProvider::V8IsolateMemoryDumpProvider(
- IsolateHolder* isolate_holder,
- scoped_refptr<base::SingleThreadTaskRunner> task_runner)
- : isolate_holder_(isolate_holder) {
- DCHECK(task_runner);
- base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
- this, "V8Isolate", task_runner);
- }
- V8IsolateMemoryDumpProvider::~V8IsolateMemoryDumpProvider() {
- base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
- this);
- }
- // Called at trace dump point time. Creates a snapshot with the memory counters
- // for the current isolate.
- bool V8IsolateMemoryDumpProvider::OnMemoryDump(
- const base::trace_event::MemoryDumpArgs& args,
- base::trace_event::ProcessMemoryDump* process_memory_dump) {
- // TODO(ssid): Use MemoryDumpArgs to create light dumps when requested
- // (crbug.com/499731).
- if (isolate_holder_->access_mode() == IsolateHolder::kUseLocker) {
- v8::Locker locked(isolate_holder_->isolate());
- DumpHeapStatistics(args, process_memory_dump);
- } else {
- DumpHeapStatistics(args, process_memory_dump);
- }
- return true;
- }
- namespace {
- // Dump statistics related to code/bytecode when memory-infra.v8.code_stats is
- // enabled.
- void DumpCodeStatistics(base::trace_event::MemoryAllocatorDump* dump,
- IsolateHolder* isolate_holder) {
- // Collecting code statistics is an expensive operation (~10 ms) when
- // compared to other v8 metrics (< 1 ms). So, dump them only when
- // memory-infra.v8.code_stats is enabled.
- // TODO(primiano): This information should be plumbed through TraceConfig.
- // See crbug.com/616441.
- bool dump_code_stats = false;
- TRACE_EVENT_CATEGORY_GROUP_ENABLED(
- TRACE_DISABLED_BY_DEFAULT("memory-infra.v8.code_stats"),
- &dump_code_stats);
- if (!dump_code_stats)
- return;
- v8::HeapCodeStatistics code_statistics;
- if (!isolate_holder->isolate()->GetHeapCodeAndMetadataStatistics(
- &code_statistics)) {
- return;
- }
- dump->AddScalar("code_and_metadata_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- code_statistics.code_and_metadata_size());
- dump->AddScalar("bytecode_and_metadata_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- code_statistics.bytecode_and_metadata_size());
- dump->AddScalar("external_script_source_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- code_statistics.external_script_source_size());
- dump->AddScalar("cpu_profiler_metadata_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- code_statistics.cpu_profiler_metadata_size());
- }
- // Dump the number of native and detached contexts.
- // The result looks as follows in the Chrome trace viewer:
- // ========================================
- // Component object_count
- // - v8
- // - main
- // - contexts
- // - detached_context 10
- // - native_context 20
- // - workers
- // - contexts
- // - detached_context
- // - isolate_0x1234 10
- // - native_context
- // - isolate_0x1234 20
- // ========================================
- void DumpContextStatistics(
- base::trace_event::ProcessMemoryDump* process_memory_dump,
- std::string dump_base_name,
- std::string dump_name_suffix,
- size_t number_of_detached_contexts,
- size_t number_of_native_contexts) {
- std::string dump_name_prefix = dump_base_name + "/contexts";
- std::string native_context_name =
- dump_name_prefix + "/native_context" + dump_name_suffix;
- auto* native_context_dump =
- process_memory_dump->CreateAllocatorDump(native_context_name);
- native_context_dump->AddScalar(
- "object_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects,
- number_of_native_contexts);
- std::string detached_context_name =
- dump_name_prefix + "/detached_context" + dump_name_suffix;
- auto* detached_context_dump =
- process_memory_dump->CreateAllocatorDump(detached_context_name);
- detached_context_dump->AddScalar(
- "object_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects,
- number_of_detached_contexts);
- }
- std::string IsolateTypeString(IsolateHolder::IsolateType isolate_type) {
- switch (isolate_type) {
- case IsolateHolder::IsolateType::kBlinkMainThread:
- return "main";
- case IsolateHolder::IsolateType::kBlinkWorkerThread:
- return "workers";
- case IsolateHolder::IsolateType::kTest:
- LOG(FATAL) << "Unreachable code";
- return "test";
- case IsolateHolder::IsolateType::kUtility:
- return "utility";
- }
- LOG(FATAL) << "Unreachable code";
- }
- bool CanHaveMultipleIsolates(IsolateHolder::IsolateType isolate_type) {
- switch (isolate_type) {
- case IsolateHolder::IsolateType::kBlinkMainThread:
- return false;
- case IsolateHolder::IsolateType::kBlinkWorkerThread:
- return true;
- case IsolateHolder::IsolateType::kTest:
- LOG(FATAL) << "Unreachable code";
- return false;
- case IsolateHolder::IsolateType::kUtility:
- // PDFium and ProxyResolver create one isolate per process.
- return false;
- }
- LOG(FATAL) << "Unreachable code";
- }
- } // namespace
- void V8IsolateMemoryDumpProvider::DumpHeapStatistics(
- const base::trace_event::MemoryDumpArgs& args,
- base::trace_event::ProcessMemoryDump* process_memory_dump) {
- if (args.determinism == base::trace_event::MemoryDumpDeterminism::FORCE_GC) {
- // Force GC in V8 using the same API as DevTools uses in "collectGarbage".
- isolate_holder_->isolate()->LowMemoryNotification();
- }
- std::string isolate_name = base::StringPrintf(
- "isolate_0x%" PRIXPTR,
- reinterpret_cast<uintptr_t>(isolate_holder_->isolate()));
- // Dump statistics of the heap's spaces.
- v8::HeapStatistics heap_statistics;
- // The total heap sizes should be sampled before the individual space sizes
- // because of concurrent allocation. DCHECKs below rely on this order.
- isolate_holder_->isolate()->GetHeapStatistics(&heap_statistics);
- IsolateHolder::IsolateType isolate_type = isolate_holder_->isolate_type();
- std::string dump_base_name = "v8/" + IsolateTypeString(isolate_type);
- std::string dump_name_suffix =
- CanHaveMultipleIsolates(isolate_type) ? "/" + isolate_name : "";
- std::string space_name_prefix = dump_base_name + "/heap";
- size_t known_spaces_used_size = 0;
- size_t known_spaces_size = 0;
- size_t known_spaces_physical_size = 0;
- size_t number_of_spaces = isolate_holder_->isolate()->NumberOfHeapSpaces();
- for (size_t space = 0; space < number_of_spaces; space++) {
- v8::HeapSpaceStatistics space_statistics;
- isolate_holder_->isolate()->GetHeapSpaceStatistics(&space_statistics,
- space);
- const size_t space_size = space_statistics.space_size();
- const size_t space_used_size = space_statistics.space_used_size();
- const size_t space_physical_size = space_statistics.physical_space_size();
- known_spaces_size += space_size;
- known_spaces_used_size += space_used_size;
- known_spaces_physical_size += space_physical_size;
- std::string space_dump_name = dump_base_name + "/heap/" +
- space_statistics.space_name() +
- dump_name_suffix;
- auto* space_dump =
- process_memory_dump->CreateAllocatorDump(space_dump_name);
- space_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- space_physical_size);
- space_dump->AddScalar("virtual_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- space_size);
- space_dump->AddScalar("allocated_objects_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- space_used_size);
- }
- // Sanity checks that all spaces are accounted for in GetHeapSpaceStatistics.
- // Background threads may be running and allocating concurrently, so the sum
- // of space sizes may be exceed the total heap size that was sampled earlier.
- DCHECK_LE(heap_statistics.total_physical_size(), known_spaces_physical_size);
- DCHECK_LE(heap_statistics.used_heap_size(), known_spaces_used_size);
- DCHECK_LE(heap_statistics.total_heap_size(), known_spaces_size);
- // If V8 zaps garbage, all the memory mapped regions become resident,
- // so we add an extra dump to avoid mismatches w.r.t. the total
- // resident values.
- if (heap_statistics.does_zap_garbage()) {
- auto* zap_dump = process_memory_dump->CreateAllocatorDump(
- dump_base_name + "/zapped_for_debug" + dump_name_suffix);
- zap_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- known_spaces_size - known_spaces_physical_size);
- }
- // Dump statistics about malloced memory.
- std::string malloc_name = dump_base_name + "/malloc" + dump_name_suffix;
- auto* malloc_dump = process_memory_dump->CreateAllocatorDump(malloc_name);
- malloc_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- heap_statistics.malloced_memory());
- malloc_dump->AddScalar("peak_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- heap_statistics.peak_malloced_memory());
- const char* system_allocator_name =
- base::trace_event::MemoryDumpManager::GetInstance()
- ->system_allocator_pool_name();
- if (system_allocator_name) {
- process_memory_dump->AddSuballocation(malloc_dump->guid(),
- system_allocator_name);
- }
- DumpContextStatistics(process_memory_dump, dump_base_name, dump_name_suffix,
- heap_statistics.number_of_detached_contexts(),
- heap_statistics.number_of_native_contexts());
- auto* code_stats_dump = process_memory_dump->CreateAllocatorDump(
- dump_base_name + "/code_stats" + dump_name_suffix);
- // Dump statistics related to code and bytecode if requested.
- DumpCodeStatistics(code_stats_dump, isolate_holder_);
- // Dump statistics for global handles.
- auto* global_handles_dump = process_memory_dump->CreateAllocatorDump(
- dump_base_name + "/global_handles" + dump_name_suffix);
- global_handles_dump->AddScalar(
- base::trace_event::MemoryAllocatorDump::kNameSize,
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- heap_statistics.total_global_handles_size());
- global_handles_dump->AddScalar(
- "allocated_objects_size",
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- heap_statistics.used_global_handles_size());
- if (system_allocator_name) {
- process_memory_dump->AddSuballocation(global_handles_dump->guid(),
- system_allocator_name);
- }
- // Dump object statistics only for detailed dumps.
- if (args.level_of_detail !=
- base::trace_event::MemoryDumpLevelOfDetail::DETAILED) {
- return;
- }
- // Dump statistics of the heap's live objects from last GC.
- // TODO(primiano): these should not be tracked in the same trace event as they
- // report stats for the last GC (not the current state). See crbug.com/498779.
- std::string object_name_prefix =
- dump_base_name + "/heap_objects_at_last_gc" + dump_name_suffix;
- bool did_dump_object_stats = false;
- const size_t object_types =
- isolate_holder_->isolate()->NumberOfTrackedHeapObjectTypes();
- for (size_t type_index = 0; type_index < object_types; type_index++) {
- v8::HeapObjectStatistics object_statistics;
- if (!isolate_holder_->isolate()->GetHeapObjectStatisticsAtLastGC(
- &object_statistics, type_index))
- continue;
- std::string dump_name =
- object_name_prefix + "/" + object_statistics.object_type();
- if (object_statistics.object_sub_type()[0] != '\0')
- dump_name += std::string("/") + object_statistics.object_sub_type();
- auto* object_dump = process_memory_dump->CreateAllocatorDump(dump_name);
- object_dump->AddScalar(
- base::trace_event::MemoryAllocatorDump::kNameObjectCount,
- base::trace_event::MemoryAllocatorDump::kUnitsObjects,
- object_statistics.object_count());
- object_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
- base::trace_event::MemoryAllocatorDump::kUnitsBytes,
- object_statistics.object_size());
- did_dump_object_stats = true;
- }
- if (process_memory_dump->GetAllocatorDump(object_name_prefix +
- "/CODE_TYPE")) {
- auto* code_kind_dump = process_memory_dump->CreateAllocatorDump(
- object_name_prefix + "/CODE_TYPE/CODE_KIND");
- auto* code_age_dump = process_memory_dump->CreateAllocatorDump(
- object_name_prefix + "/CODE_TYPE/CODE_AGE");
- process_memory_dump->AddOwnershipEdge(code_kind_dump->guid(),
- code_age_dump->guid());
- }
- if (did_dump_object_stats) {
- process_memory_dump->AddOwnershipEdge(
- process_memory_dump->CreateAllocatorDump(object_name_prefix)->guid(),
- process_memory_dump->GetOrCreateAllocatorDump(space_name_prefix)
- ->guid());
- }
- }
- } // namespace gin
|