chromium/base/trace_event/malloc_dump_provider.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/trace_event/malloc_dump_provider.h"

#include <stddef.h>

#include <unordered_map>

#include "base/allocator/buildflags.h"
#include "base/debug/profiler.h"
#include "base/format_macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/traced_value.h"
#include "build/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_bucket_lookup.h"

#if BUILDFLAG(IS_APPLE)
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#include <features.h>
#endif

#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#include "base/no_destructor.h"
#include "partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc.h"
#endif

#if PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
#include "partition_alloc/partition_alloc_constants.h"
#endif

namespace base {
namespace trace_event {

namespace {
#if BUILDFLAG(IS_WIN)
// A structure containing some information about a given heap.
struct WinHeapInfo {
  size_t committed_size;
  size_t uncommitted_size;
  size_t allocated_size;
  size_t block_count;
};

// NOTE: crbug.com/665516
// Unfortunately, there is no safe way to collect information from secondary
// heaps due to limitations and racy nature of this piece of WinAPI.
void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
  // Iterate through whichever heap our CRT is using.
  HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
  ::HeapLock(crt_heap);
  PROCESS_HEAP_ENTRY heap_entry;
  heap_entry.lpData = nullptr;
  // Walk over all the entries in the main heap.
  while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
    if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
      crt_heap_info->allocated_size += heap_entry.cbData;
      crt_heap_info->block_count++;
    } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
      crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
      crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
    }
  }
  CHECK(::HeapUnlock(crt_heap) == TRUE);
}

void ReportWinHeapStats(MemoryDumpLevelOfDetail level_of_detail,
                        ProcessMemoryDump* pmd,
                        size_t* total_virtual_size,
                        size_t* resident_size,
                        size_t* allocated_objects_size,
                        size_t* allocated_objects_count) {
  // This is too expensive on Windows, crbug.com/780735.
  if (level_of_detail == MemoryDumpLevelOfDetail::kDetailed) {
    WinHeapInfo main_heap_info = {};
    WinHeapMemoryDumpImpl(&main_heap_info);
    *total_virtual_size +=
        main_heap_info.committed_size + main_heap_info.uncommitted_size;
    // Resident size is approximated with committed heap size. Note that it is
    // possible to do this with better accuracy on windows by intersecting the
    // working set with the virtual memory ranges occuipied by the heap. It's
    // not clear that this is worth it, as it's fairly expensive to do.
    *resident_size += main_heap_info.committed_size;
    *allocated_objects_size += main_heap_info.allocated_size;
    *allocated_objects_count += main_heap_info.block_count;

    if (pmd) {
      MemoryAllocatorDump* win_heap_dump =
          pmd->CreateAllocatorDump("malloc/win_heap");
      win_heap_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                               MemoryAllocatorDump::kUnitsBytes,
                               main_heap_info.allocated_size);
    }
  }
}
#endif  // BUILDFLAG(IS_WIN)

#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
void ReportPartitionAllocStats(ProcessMemoryDump* pmd,
                               MemoryDumpLevelOfDetail level_of_detail,
                               size_t* total_virtual_size,
                               size_t* resident_size,
                               size_t* allocated_objects_size,
                               size_t* allocated_objects_count,
                               uint64_t* syscall_count,
                               size_t* cumulative_brp_quarantined_size,
                               size_t* cumulative_brp_quarantined_count) {}
#endif  // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)

#if !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(IS_APPLE)
void ReportAppleAllocStats(size_t* total_virtual_size,
                           size_t* resident_size,
                           size_t* allocated_objects_size) {
  malloc_statistics_t stats = {0};
  malloc_zone_statistics(nullptr, &stats);
  *total_virtual_size += stats.size_allocated;
  *allocated_objects_size += stats.size_in_use;

  // Resident size is approximated pretty well by stats.max_size_in_use.
  // However, on macOS, freed blocks are both resident and reusable, which is
  // semantically equivalent to deallocated. The implementation of libmalloc
  // will also only hold a fixed number of freed regions before actually
  // starting to deallocate them, so stats.max_size_in_use is also not
  // representative of the peak size. As a result, stats.max_size_in_use is
  // typically somewhere between actually resident [non-reusable] pages, and
  // peak size. This is not very useful, so we just use stats.size_in_use for
  // resident_size, even though it's an underestimate and fails to account for
  // fragmentation. See
  // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
  *resident_size += stats.size_in_use;
}
#endif

#if (PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(IS_ANDROID)) || \
    (!PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && !BUILDFLAG(IS_WIN) &&    \
     !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA))
void ReportMallinfoStats(ProcessMemoryDump* pmd,
                         size_t* total_virtual_size,
                         size_t* resident_size,
                         size_t* allocated_objects_size,
                         size_t* allocated_objects_count) {
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if __GLIBC_PREREQ(2, 33)
#define MALLINFO2_FOUND_IN_LIBC
  struct mallinfo2 info = mallinfo2();
#endif
#endif  // defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if !defined(MALLINFO2_FOUND_IN_LIBC)
  struct mallinfo info = mallinfo();
#endif
#undef MALLINFO2_FOUND_IN_LIBC
  // In case of Android's jemalloc |arena| is 0 and the outer pages size is
  // reported by |hblkhd|. In case of dlmalloc the total is given by
  // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
  *total_virtual_size += checked_cast<size_t>(info.arena + info.hblkhd);
  size_t total_allocated_size = checked_cast<size_t>(info.uordblks);
  *resident_size += total_allocated_size;

  // Total allocated space is given by |uordblks|.
  *allocated_objects_size += total_allocated_size;

  if (pmd) {
    MemoryAllocatorDump* sys_alloc_dump =
        pmd->CreateAllocatorDump("malloc/sys_malloc");
    sys_alloc_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                              MemoryAllocatorDump::kUnitsBytes,
                              total_allocated_size);
  }
}
#endif

#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
void ReportPartitionAllocThreadCacheStats(
    ProcessMemoryDump* pmd,
    MemoryAllocatorDump* dump,
    const partition_alloc::ThreadCacheStats& stats,
    const std::string& metrics_suffix,
    bool detailed) {}

void ReportPartitionAllocLightweightQuarantineStats(
    MemoryAllocatorDump* dump,
    const partition_alloc::LightweightQuarantineStats& stats) {}
#endif  // PA_BUILDFLAG(USE_PARTITION_ALLOC)

}  // namespace

// static
const char MallocDumpProvider::kAllocatedObjects[] =;

// static
MallocDumpProvider* MallocDumpProvider::GetInstance() {}

#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// static
void MallocDumpProvider::SetExtremeLUDGetStatsCallback(
    ExtremeLUDGetStatsCallback callback) {}

// static
MallocDumpProvider::ExtremeLUDGetStatsCallback&
MallocDumpProvider::GetExtremeLUDGetStatsCallback() {}
#endif  // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)

MallocDumpProvider::MallocDumpProvider() = default;
MallocDumpProvider::~MallocDumpProvider() = default;

// Called at trace dump point time. Creates a snapshot the memory counters for
// the current process.
bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
                                      ProcessMemoryDump* pmd) {}

void MallocDumpProvider::ReportPerMinuteStats(
    uint64_t syscall_count,
    size_t cumulative_brp_quarantined_bytes,
    size_t cumulative_brp_quarantined_count,
    const ExtremeLUDStats& elud_stats,
    MemoryAllocatorDump* malloc_dump,
    MemoryAllocatorDump* partition_alloc_dump,
    MemoryAllocatorDump* elud_dump) {}

#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
std::string GetPartitionDumpName(const char* root_name,
                                 const char* partition_name) {}

MemoryDumpPartitionStatsDumper::MemoryDumpPartitionStatsDumper(
    const char* root_name,
    ProcessMemoryDump* memory_dump,
    MemoryDumpLevelOfDetail level_of_detail)
    :{}

void MemoryDumpPartitionStatsDumper::PartitionDumpTotals(
    const char* partition_name,
    const partition_alloc::PartitionMemoryStats* memory_stats) {}

void MemoryDumpPartitionStatsDumper::PartitionsDumpBucketStats(
    const char* partition_name,
    const partition_alloc::PartitionBucketMemoryStats* memory_stats) {}
#endif  // PA_BUILDFLAG(USE_PARTITION_ALLOC)

}  // namespace trace_event
}  // namespace base