blob: fc7a776913181bfb4f352703b7bb76824e5ff8f5 [file] [log] [blame]
// 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 "platform/PartitionAllocMemoryDumpProvider.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/heap_profiler_allocation_context.h"
#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include "base/trace_event/heap_profiler_allocation_register.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event_memory_overhead.h"
#include "wtf/allocator/Partitions.h"
#include "wtf/text/WTFString.h"
namespace blink {
namespace {
using namespace WTF;
void reportAllocation(void* address, size_t size, const char* typeName) {
PartitionAllocMemoryDumpProvider::instance()->insert(address, size, typeName);
}
void reportFree(void* address) {
PartitionAllocMemoryDumpProvider::instance()->remove(address);
}
const char kPartitionAllocDumpName[] = "partition_alloc";
const char kPartitionsDumpName[] = "partitions";
std::string getPartitionDumpName(const char* partitionName) {
return base::StringPrintf("%s/%s/%s", kPartitionAllocDumpName,
kPartitionsDumpName, partitionName);
}
// This class is used to invert the dependency of PartitionAlloc on the
// PartitionAllocMemoryDumpProvider. This implements an interface that will
// be called with memory statistics for each bucket in the allocator.
class PartitionStatsDumperImpl final : public PartitionStatsDumper {
DISALLOW_NEW();
WTF_MAKE_NONCOPYABLE(PartitionStatsDumperImpl);
public:
PartitionStatsDumperImpl(
base::trace_event::ProcessMemoryDump* memoryDump,
base::trace_event::MemoryDumpLevelOfDetail levelOfDetail)
: m_memoryDump(memoryDump), m_uid(0), m_totalActiveBytes(0) {}
// PartitionStatsDumper implementation.
void partitionDumpTotals(const char* partitionName,
const PartitionMemoryStats*) override;
void partitionsDumpBucketStats(const char* partitionName,
const PartitionBucketMemoryStats*) override;
size_t totalActiveBytes() const { return m_totalActiveBytes; }
private:
base::trace_event::ProcessMemoryDump* m_memoryDump;
unsigned long m_uid;
size_t m_totalActiveBytes;
};
void PartitionStatsDumperImpl::partitionDumpTotals(
const char* partitionName,
const PartitionMemoryStats* memoryStats) {
m_totalActiveBytes += memoryStats->totalActiveBytes;
std::string dumpName = getPartitionDumpName(partitionName);
base::trace_event::MemoryAllocatorDump* allocatorDump =
m_memoryDump->CreateAllocatorDump(dumpName);
allocatorDump->AddScalar("size", "bytes", memoryStats->totalResidentBytes);
allocatorDump->AddScalar("allocated_objects_size", "bytes",
memoryStats->totalActiveBytes);
allocatorDump->AddScalar("virtual_size", "bytes",
memoryStats->totalMmappedBytes);
allocatorDump->AddScalar("virtual_committed_size", "bytes",
memoryStats->totalCommittedBytes);
allocatorDump->AddScalar("decommittable_size", "bytes",
memoryStats->totalDecommittableBytes);
allocatorDump->AddScalar("discardable_size", "bytes",
memoryStats->totalDiscardableBytes);
}
void PartitionStatsDumperImpl::partitionsDumpBucketStats(
const char* partitionName,
const PartitionBucketMemoryStats* memoryStats) {
ASSERT(memoryStats->isValid);
std::string dumpName = getPartitionDumpName(partitionName);
if (memoryStats->isDirectMap)
dumpName.append(base::StringPrintf("/directMap_%lu", ++m_uid));
else
dumpName.append(base::StringPrintf(
"/bucket_%u", static_cast<unsigned>(memoryStats->bucketSlotSize)));
base::trace_event::MemoryAllocatorDump* allocatorDump =
m_memoryDump->CreateAllocatorDump(dumpName);
allocatorDump->AddScalar("size", "bytes", memoryStats->residentBytes);
allocatorDump->AddScalar("allocated_objects_size", "bytes",
memoryStats->activeBytes);
allocatorDump->AddScalar("slot_size", "bytes", memoryStats->bucketSlotSize);
allocatorDump->AddScalar("decommittable_size", "bytes",
memoryStats->decommittableBytes);
allocatorDump->AddScalar("discardable_size", "bytes",
memoryStats->discardableBytes);
allocatorDump->AddScalar("total_pages_size", "bytes",
memoryStats->allocatedPageSize);
allocatorDump->AddScalar("active_pages", "objects",
memoryStats->numActivePages);
allocatorDump->AddScalar("full_pages", "objects", memoryStats->numFullPages);
allocatorDump->AddScalar("empty_pages", "objects",
memoryStats->numEmptyPages);
allocatorDump->AddScalar("decommitted_pages", "objects",
memoryStats->numDecommittedPages);
}
} // namespace
PartitionAllocMemoryDumpProvider* PartitionAllocMemoryDumpProvider::instance() {
DEFINE_STATIC_LOCAL(PartitionAllocMemoryDumpProvider, instance, ());
return &instance;
}
bool PartitionAllocMemoryDumpProvider::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* memoryDump) {
using base::trace_event::MemoryDumpLevelOfDetail;
MemoryDumpLevelOfDetail levelOfDetail = args.level_of_detail;
if (m_isHeapProfilingEnabled) {
// Overhead should always be reported, regardless of light vs. heavy.
base::trace_event::TraceEventMemoryOverhead overhead;
base::hash_map<base::trace_event::AllocationContext,
base::trace_event::AllocationMetrics>
metricsByContext;
{
MutexLocker locker(m_allocationRegisterMutex);
// Dump only the overhead estimation in non-detailed dumps.
if (levelOfDetail == MemoryDumpLevelOfDetail::DETAILED) {
for (const auto& allocSize : *m_allocationRegister) {
base::trace_event::AllocationMetrics& metrics =
metricsByContext[allocSize.context];
metrics.size += allocSize.size;
metrics.count++;
}
}
m_allocationRegister->EstimateTraceMemoryOverhead(&overhead);
}
memoryDump->DumpHeapUsage(metricsByContext, overhead, "partition_alloc");
}
PartitionStatsDumperImpl partitionStatsDumper(memoryDump, levelOfDetail);
base::trace_event::MemoryAllocatorDump* partitionsDump =
memoryDump->CreateAllocatorDump(base::StringPrintf(
"%s/%s", kPartitionAllocDumpName, kPartitionsDumpName));
// This method calls memoryStats.partitionsDumpBucketStats with memory
// statistics.
WTF::Partitions::dumpMemoryStats(
levelOfDetail != MemoryDumpLevelOfDetail::DETAILED,
&partitionStatsDumper);
base::trace_event::MemoryAllocatorDump* allocatedObjectsDump =
memoryDump->CreateAllocatorDump(Partitions::kAllocatedObjectPoolName);
allocatedObjectsDump->AddScalar("size", "bytes",
partitionStatsDumper.totalActiveBytes());
memoryDump->AddOwnershipEdge(allocatedObjectsDump->guid(),
partitionsDump->guid());
return true;
}
// |m_allocationRegister| should be initialized only when necessary to avoid
// waste of memory.
PartitionAllocMemoryDumpProvider::PartitionAllocMemoryDumpProvider()
: m_allocationRegister(nullptr), m_isHeapProfilingEnabled(false) {}
PartitionAllocMemoryDumpProvider::~PartitionAllocMemoryDumpProvider() {}
void PartitionAllocMemoryDumpProvider::OnHeapProfilingEnabled(bool enabled) {
if (enabled) {
{
MutexLocker locker(m_allocationRegisterMutex);
if (!m_allocationRegister)
m_allocationRegister.reset(new base::trace_event::AllocationRegister());
}
PartitionAllocHooks::setAllocationHook(reportAllocation);
PartitionAllocHooks::setFreeHook(reportFree);
} else {
PartitionAllocHooks::setAllocationHook(nullptr);
PartitionAllocHooks::setFreeHook(nullptr);
}
m_isHeapProfilingEnabled = enabled;
}
void PartitionAllocMemoryDumpProvider::insert(void* address,
size_t size,
const char* typeName) {
base::trace_event::AllocationContext context =
base::trace_event::AllocationContextTracker::GetInstanceForCurrentThread()
->GetContextSnapshot();
context.type_name = typeName;
MutexLocker locker(m_allocationRegisterMutex);
if (m_allocationRegister)
m_allocationRegister->Insert(address, size, context);
}
void PartitionAllocMemoryDumpProvider::remove(void* address) {
MutexLocker locker(m_allocationRegisterMutex);
if (m_allocationRegister)
m_allocationRegister->Remove(address);
}
} // namespace blink