#include "gin/v8_isolate_memory_dump_provider.h"
#include <inttypes.h>
#include <stddef.h>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.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);
}
bool V8IsolateMemoryDumpProvider::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* process_memory_dump) {
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 {
void DumpCodeStatistics(base::trace_event::MemoryAllocatorDump* dump,
IsolateHolder* isolate_holder) {
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());
}
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:
NOTREACHED();
case IsolateHolder::IsolateType::kUtility:
return "utility";
}
NOTREACHED();
}
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:
NOTREACHED();
case IsolateHolder::IsolateType::kUtility:
return true;
}
NOTREACHED();
}
}
void V8IsolateMemoryDumpProvider::DumpHeapStatistics(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* process_memory_dump) {
if (args.determinism == base::trace_event::MemoryDumpDeterminism::kForceGc) {
isolate_holder_->isolate()->LowMemoryNotification();
}
std::string isolate_name = base::StringPrintf(
"isolate_0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(isolate_holder_->isolate()));
v8::HeapStatistics heap_statistics;
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_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_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);
}
DCHECK_LE(heap_statistics.total_heap_size(), known_spaces_size);
if (heap_statistics.does_zap_garbage()) {
auto* zap_dump = process_memory_dump->CreateAllocatorDump(
dump_base_name + "/zapped_for_debug" + dump_name_suffix);
size_t zapped_size_for_debugging =
known_spaces_size >= known_spaces_physical_size
? known_spaces_size - known_spaces_physical_size
: 0;
zap_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
zapped_size_for_debugging);
}
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);
DumpCodeStatistics(code_stats_dump, isolate_holder_);
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);
}
if (args.level_of_detail !=
base::trace_event::MemoryDumpLevelOfDetail::kDetailed) {
return;
}
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());
}
}
}