// 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.

#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/partition_alloc_buildflags.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_bucket_lookup.h"
#include "partition_alloc/shim/nonscannable_allocator.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 BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#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 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) {
  MemoryDumpPartitionStatsDumper partition_stats_dumper("malloc", pmd,
                                                        level_of_detail);
  bool is_light_dump = level_of_detail == MemoryDumpLevelOfDetail::kBackground;

  auto* allocator = allocator_shim::internal::PartitionAllocMalloc::Allocator();
  allocator->DumpStats("allocator", is_light_dump, &partition_stats_dumper);

  auto* original_allocator =
      allocator_shim::internal::PartitionAllocMalloc::OriginalAllocator();
  if (original_allocator) {
    original_allocator->DumpStats("original", is_light_dump,
                                  &partition_stats_dumper);
  }
  auto& nonscannable_allocator =
      allocator_shim::NonScannableAllocator::Instance();
  if (auto* root = nonscannable_allocator.root())
    root->DumpStats("nonscannable", is_light_dump, &partition_stats_dumper);
  auto& nonquarantinable_allocator =
      allocator_shim::NonQuarantinableAllocator::Instance();
  if (auto* root = nonquarantinable_allocator.root())
    root->DumpStats("nonquarantinable", is_light_dump, &partition_stats_dumper);

  *total_virtual_size += partition_stats_dumper.total_resident_bytes();
  *resident_size += partition_stats_dumper.total_resident_bytes();
  *allocated_objects_size += partition_stats_dumper.total_active_bytes();
  *allocated_objects_count += partition_stats_dumper.total_active_count();
  *syscall_count += partition_stats_dumper.syscall_count();
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
  *cumulative_brp_quarantined_size +=
      partition_stats_dumper.cumulative_brp_quarantined_bytes();
  *cumulative_brp_quarantined_count +=
      partition_stats_dumper.cumulative_brp_quarantined_count();
#endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
}
#endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)

#if !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 (BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(IS_ANDROID)) || \
    (!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 BUILDFLAG(USE_PARTITION_ALLOC)
void ReportPartitionAllocThreadCacheStats(
    ProcessMemoryDump* pmd,
    MemoryAllocatorDump* dump,
    const partition_alloc::ThreadCacheStats& stats,
    const std::string& metrics_suffix,
    bool detailed) {
  dump->AddScalar("alloc_count", MemoryAllocatorDump::kTypeScalar,
                  stats.alloc_count);
  dump->AddScalar("alloc_hits", MemoryAllocatorDump::kTypeScalar,
                  stats.alloc_hits);
  dump->AddScalar("alloc_misses", MemoryAllocatorDump::kTypeScalar,
                  stats.alloc_misses);

  dump->AddScalar("alloc_miss_empty", MemoryAllocatorDump::kTypeScalar,
                  stats.alloc_miss_empty);
  dump->AddScalar("alloc_miss_too_large", MemoryAllocatorDump::kTypeScalar,
                  stats.alloc_miss_too_large);

  dump->AddScalar("cache_fill_count", MemoryAllocatorDump::kTypeScalar,
                  stats.cache_fill_count);
  dump->AddScalar("cache_fill_hits", MemoryAllocatorDump::kTypeScalar,
                  stats.cache_fill_hits);
  dump->AddScalar("cache_fill_misses", MemoryAllocatorDump::kTypeScalar,
                  stats.cache_fill_misses);

  dump->AddScalar("batch_fill_count", MemoryAllocatorDump::kTypeScalar,
                  stats.batch_fill_count);

  dump->AddScalar(MemoryAllocatorDump::kNameSize,
                  MemoryAllocatorDump::kUnitsBytes, stats.bucket_total_memory);
  dump->AddScalar("metadata_overhead", MemoryAllocatorDump::kUnitsBytes,
                  stats.metadata_overhead);

  if (stats.alloc_count) {
    int hit_rate_percent =
        static_cast<int>((100 * stats.alloc_hits) / stats.alloc_count);
    base::UmaHistogramPercentage(
        "Memory.PartitionAlloc.ThreadCache.HitRate" + metrics_suffix,
        hit_rate_percent);
    int batch_fill_rate_percent =
        static_cast<int>((100 * stats.batch_fill_count) / stats.alloc_count);
    base::UmaHistogramPercentage(
        "Memory.PartitionAlloc.ThreadCache.BatchFillRate" + metrics_suffix,
        batch_fill_rate_percent);

#if PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
    if (detailed) {
      partition_alloc::internal::BucketIndexLookup lookup{};
      std::string name = dump->absolute_name();
      for (size_t i = 0; i < partition_alloc::kNumBuckets; i++) {
        size_t bucket_size = lookup.bucket_sizes()[i];
        if (bucket_size == partition_alloc::kInvalidBucketSize)
          continue;
        // Covers all normal buckets, that is up to ~1MiB, so 7 digits.
        std::string dump_name =
            base::StringPrintf("%s/buckets_alloc/%07d", name.c_str(),
                               static_cast<int>(bucket_size));
        auto* buckets_alloc_dump = pmd->CreateAllocatorDump(dump_name);
        buckets_alloc_dump->AddScalar("count",
                                      MemoryAllocatorDump::kUnitsObjects,
                                      stats.allocs_per_bucket_[i]);
      }
    }
#endif  // PA_CONFIG(THREAD_CACHE_ALLOC_STATS)
  }
}

void ReportPartitionAllocLightweightQuarantineStats(
    MemoryAllocatorDump* dump,
    const partition_alloc::LightweightQuarantineStats& stats) {
  dump->AddScalar("count", MemoryAllocatorDump::kUnitsObjects, stats.count);
  dump->AddScalar("size_in_bytes", MemoryAllocatorDump::kUnitsBytes,
                  stats.size_in_bytes);
  dump->AddScalar("cumulative_count", MemoryAllocatorDump::kUnitsObjects,
                  stats.cumulative_count);
  dump->AddScalar("cumulative_size_in_bytes", MemoryAllocatorDump::kUnitsBytes,
                  stats.cumulative_size_in_bytes);
  dump->AddScalar("quarantine_miss_count", MemoryAllocatorDump::kUnitsObjects,
                  stats.quarantine_miss_count);
}
#endif  // BUILDFLAG(USE_PARTITION_ALLOC)

}  // namespace

// static
const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";

// static
MallocDumpProvider* MallocDumpProvider::GetInstance() {
  return Singleton<MallocDumpProvider,
                   LeakySingletonTraits<MallocDumpProvider>>::get();
}

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) {
  {
    base::AutoLock auto_lock(emit_metrics_on_memory_dump_lock_);
    if (!emit_metrics_on_memory_dump_) {
      return true;
    }
  }

  size_t total_virtual_size = 0;
  size_t resident_size = 0;
  size_t allocated_objects_size = 0;
  size_t allocated_objects_count = 0;
  uint64_t syscall_count = 0;
  size_t cumulative_brp_quarantined_size = 0;
  size_t cumulative_brp_quarantined_count = 0;
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  uint64_t pa_only_resident_size;
  uint64_t pa_only_allocated_objects_size;
#endif

#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  ReportPartitionAllocStats(
      pmd, args.level_of_detail, &total_virtual_size, &resident_size,
      &allocated_objects_size, &allocated_objects_count, &syscall_count,
      &cumulative_brp_quarantined_size, &cumulative_brp_quarantined_count);

  pa_only_resident_size = resident_size;
  pa_only_allocated_objects_size = allocated_objects_size;

  // Even when PartitionAlloc is used, WinHeap / System malloc is still used as
  // well, report its statistics.
#if BUILDFLAG(IS_ANDROID)
  ReportMallinfoStats(pmd, &total_virtual_size, &resident_size,
                      &allocated_objects_size, &allocated_objects_count);
#elif BUILDFLAG(IS_WIN)
  ReportWinHeapStats(args.level_of_detail, pmd, &total_virtual_size,
                     &resident_size, &allocated_objects_size,
                     &allocated_objects_count);
#endif  // BUILDFLAG(IS_ANDROID), BUILDFLAG(IS_WIN)

#elif BUILDFLAG(IS_APPLE)
  ReportAppleAllocStats(&total_virtual_size, &resident_size,
                        &allocated_objects_size);
#elif BUILDFLAG(IS_WIN)
  ReportWinHeapStats(args.level_of_detail, nullptr, &total_virtual_size,
                     &resident_size, &allocated_objects_size,
                     &allocated_objects_count);
#elif BUILDFLAG(IS_FUCHSIA)
// TODO(fuchsia): Port, see https://crbug.com/706592.
#else
  ReportMallinfoStats(/*pmd=*/nullptr, &total_virtual_size, &resident_size,
                      &allocated_objects_size, &allocated_objects_count);
#endif

  MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
  outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
                        total_virtual_size);
  outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                        MemoryAllocatorDump::kUnitsBytes, resident_size);

  MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
  inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                        MemoryAllocatorDump::kUnitsBytes,
                        allocated_objects_size);
  if (allocated_objects_count != 0) {
    inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
                          MemoryAllocatorDump::kUnitsObjects,
                          allocated_objects_count);
  }

#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  base::trace_event::MemoryAllocatorDump* partitions_dump =
      pmd->CreateAllocatorDump("malloc/partitions");
  pmd->AddOwnershipEdge(inner_dump->guid(), partitions_dump->guid());
#endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)

  int64_t waste = static_cast<int64_t>(resident_size - allocated_objects_size);

  // With PartitionAlloc, reported size under malloc/partitions is the resident
  // size, so it already includes fragmentation. Meaning that "malloc/"'s size
  // would double-count fragmentation if we report it under
  // "malloc/metadata_fragmentation_caches" as well.
  //
  // Still report waste, as on some platforms, PartitionAlloc doesn't capture
  // all of malloc()'s memory footprint.
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  int64_t pa_waste = static_cast<int64_t>(pa_only_resident_size -
                                          pa_only_allocated_objects_size);
  waste -= pa_waste;
#endif

  if (waste > 0) {
    // Explicitly specify why is extra memory resident. In mac and ios it
    // accounts for the fragmentation and metadata.
    MemoryAllocatorDump* other_dump =
        pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
    other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                          MemoryAllocatorDump::kUnitsBytes,
                          static_cast<uint64_t>(waste));
  }

  ReportPerMinuteStats(syscall_count, cumulative_brp_quarantined_size,
                       cumulative_brp_quarantined_count, outer_dump,
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
                       partitions_dump
#else
                       nullptr
#endif
  );

  return true;
}

void MallocDumpProvider::ReportPerMinuteStats(
    uint64_t syscall_count,
    size_t cumulative_brp_quarantined_bytes,
    size_t cumulative_brp_quarantined_count,
    MemoryAllocatorDump* malloc_dump,
    MemoryAllocatorDump* partition_alloc_dump) {
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  uint64_t new_syscalls = syscall_count - last_syscall_count_;
  size_t new_brp_quarantined_bytes =
      cumulative_brp_quarantined_bytes - last_cumulative_brp_quarantined_bytes_;
  size_t new_brp_quarantined_count =
      cumulative_brp_quarantined_count - last_cumulative_brp_quarantined_count_;
  base::TimeDelta time_since_last_dump =
      base::TimeTicks::Now() - last_memory_dump_time_;
  uint64_t syscalls_per_minute = static_cast<uint64_t>(
      (60 * new_syscalls) / time_since_last_dump.InSecondsF());
  malloc_dump->AddScalar("syscalls_per_minute", "count", syscalls_per_minute);
  if (partition_alloc_dump) {
    size_t brp_quarantined_bytes_per_minute =
        (60 * new_brp_quarantined_bytes) / time_since_last_dump.InSecondsF();
    size_t brp_quarantined_count_per_minute =
        (60 * new_brp_quarantined_count) / time_since_last_dump.InSecondsF();
    partition_alloc_dump->AddScalar("brp_quarantined_bytes_per_minute",
                                    MemoryAllocatorDump::kUnitsBytes,
                                    brp_quarantined_bytes_per_minute);
    partition_alloc_dump->AddScalar("brp_quarantined_count_per_minute",
                                    MemoryAllocatorDump::kNameObjectCount,
                                    brp_quarantined_count_per_minute);
  }

  last_memory_dump_time_ = base::TimeTicks::Now();
  last_syscall_count_ = syscall_count;
  last_cumulative_brp_quarantined_bytes_ = cumulative_brp_quarantined_bytes;
  last_cumulative_brp_quarantined_count_ = cumulative_brp_quarantined_count;
#endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}

#if BUILDFLAG(USE_PARTITION_ALLOC)
std::string GetPartitionDumpName(const char* root_name,
                                 const char* partition_name) {
  return base::StringPrintf("%s/%s/%s", root_name,
                            MemoryDumpPartitionStatsDumper::kPartitionsDumpName,
                            partition_name);
}

MemoryDumpPartitionStatsDumper::MemoryDumpPartitionStatsDumper(
    const char* root_name,
    ProcessMemoryDump* memory_dump,
    MemoryDumpLevelOfDetail level_of_detail)
    : root_name_(root_name),
      memory_dump_(memory_dump),
      detailed_(level_of_detail != MemoryDumpLevelOfDetail::kBackground) {}

void MemoryDumpPartitionStatsDumper::PartitionDumpTotals(
    const char* partition_name,
    const partition_alloc::PartitionMemoryStats* memory_stats) {
  total_mmapped_bytes_ += memory_stats->total_mmapped_bytes;
  total_resident_bytes_ += memory_stats->total_resident_bytes;
  total_active_bytes_ += memory_stats->total_active_bytes;
  total_active_count_ += memory_stats->total_active_count;
  syscall_count_ += memory_stats->syscall_count;
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
  cumulative_brp_quarantined_bytes_ +=
      memory_stats->cumulative_brp_quarantined_bytes;
  cumulative_brp_quarantined_count_ +=
      memory_stats->cumulative_brp_quarantined_count;
#endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)

  std::string dump_name = GetPartitionDumpName(root_name_, partition_name);
  MemoryAllocatorDump* allocator_dump =
      memory_dump_->CreateAllocatorDump(dump_name);

  auto total_committed_bytes = memory_stats->total_committed_bytes;
  auto total_active_bytes = memory_stats->total_active_bytes;
  size_t wasted = total_committed_bytes - total_active_bytes;
  DCHECK_GE(total_committed_bytes, total_active_bytes);
  size_t fragmentation =
      total_committed_bytes == 0 ? 0 : 100 * wasted / total_committed_bytes;

  allocator_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_resident_bytes);
  allocator_dump->AddScalar("allocated_objects_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_active_bytes);
  allocator_dump->AddScalar("allocated_objects_count", "count",
                            memory_stats->total_active_count);
  allocator_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_mmapped_bytes);
  allocator_dump->AddScalar("virtual_committed_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_committed_bytes);
  allocator_dump->AddScalar("max_committed_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->max_committed_bytes);
  allocator_dump->AddScalar("allocated_size", MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_allocated_bytes);
  allocator_dump->AddScalar("max_allocated_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->max_allocated_bytes);
  allocator_dump->AddScalar("decommittable_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_decommittable_bytes);
  allocator_dump->AddScalar("discardable_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_discardable_bytes);
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
  allocator_dump->AddScalar("brp_quarantined_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->total_brp_quarantined_bytes);
  allocator_dump->AddScalar("brp_quarantined_count", "count",
                            memory_stats->total_brp_quarantined_count);
#endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
  allocator_dump->AddScalar("syscall_count", "count",
                            memory_stats->syscall_count);
  allocator_dump->AddScalar("syscall_total_time_ms", "ms",
                            memory_stats->syscall_total_time_ns / 1e6);
  allocator_dump->AddScalar("fragmentation", "percent", fragmentation);
  allocator_dump->AddScalar("wasted", MemoryAllocatorDump::kUnitsBytes, wasted);

  if (memory_stats->has_thread_cache) {
    const auto& thread_cache_stats = memory_stats->current_thread_cache_stats;
    auto* thread_cache_dump = memory_dump_->CreateAllocatorDump(
        dump_name + "/thread_cache/main_thread");
    ReportPartitionAllocThreadCacheStats(memory_dump_, thread_cache_dump,
                                         thread_cache_stats, ".MainThread",
                                         detailed_);

    const auto& all_thread_caches_stats = memory_stats->all_thread_caches_stats;
    auto* all_thread_caches_dump =
        memory_dump_->CreateAllocatorDump(dump_name + "/thread_cache");
    ReportPartitionAllocThreadCacheStats(memory_dump_, all_thread_caches_dump,
                                         all_thread_caches_stats, "",
                                         detailed_);
  }

  if (memory_stats->has_scheduler_loop_quarantine) {
    MemoryAllocatorDump* quarantine_dump_total =
        memory_dump_->CreateAllocatorDump(dump_name +
                                          "/scheduler_loop_quarantine");
    ReportPartitionAllocLightweightQuarantineStats(
        quarantine_dump_total,
        memory_stats->scheduler_loop_quarantine_stats_total);
  }
}

void MemoryDumpPartitionStatsDumper::PartitionsDumpBucketStats(
    const char* partition_name,
    const partition_alloc::PartitionBucketMemoryStats* memory_stats) {
  DCHECK(memory_stats->is_valid);
  std::string dump_name = GetPartitionDumpName(root_name_, partition_name);
  if (memory_stats->is_direct_map) {
    dump_name.append(base::StringPrintf("/buckets/directMap_%" PRIu64, ++uid_));
  } else {
    // Normal buckets go up to ~1MiB, 7 digits.
    dump_name.append(base::StringPrintf("/buckets/bucket_%07" PRIu32,
                                        memory_stats->bucket_slot_size));
  }

  MemoryAllocatorDump* allocator_dump =
      memory_dump_->CreateAllocatorDump(dump_name);
  allocator_dump->AddScalar(MemoryAllocatorDump::kNameSize,
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->resident_bytes);
  allocator_dump->AddScalar("allocated_objects_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->active_bytes);
  allocator_dump->AddScalar("slot_size", MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->bucket_slot_size);
  allocator_dump->AddScalar("decommittable_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->decommittable_bytes);
  allocator_dump->AddScalar("discardable_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->discardable_bytes);
  // TODO(bartekn): Rename the scalar names.
  allocator_dump->AddScalar("total_slot_span_size",
                            MemoryAllocatorDump::kUnitsBytes,
                            memory_stats->allocated_slot_span_size);
  allocator_dump->AddScalar("active_slot_spans",
                            MemoryAllocatorDump::kUnitsObjects,
                            memory_stats->num_active_slot_spans);
  allocator_dump->AddScalar("full_slot_spans",
                            MemoryAllocatorDump::kUnitsObjects,
                            memory_stats->num_full_slot_spans);
  allocator_dump->AddScalar("empty_slot_spans",
                            MemoryAllocatorDump::kUnitsObjects,
                            memory_stats->num_empty_slot_spans);
  allocator_dump->AddScalar("decommitted_slot_spans",
                            MemoryAllocatorDump::kUnitsObjects,
                            memory_stats->num_decommitted_slot_spans);
}
#endif  // BUILDFLAG(USE_PARTITION_ALLOC)

}  // namespace trace_event
}  // namespace base
