// Copyright 2019 The TCMalloc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tcmalloc/global_stats.h"

#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <utility>

#include "absl/base/nullability.h"
#include "absl/base/optimization.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/time/time.h"
#include "tcmalloc/central_freelist.h"
#include "tcmalloc/common.h"
#include "tcmalloc/cpu_cache.h"
#include "tcmalloc/experiment.h"
#include "tcmalloc/experiment_config.h"
#include "tcmalloc/guarded_page_allocator.h"
#include "tcmalloc/huge_page_filler.h"
#include "tcmalloc/huge_page_options.h"
#include "tcmalloc/huge_pages.h"
#include "tcmalloc/internal/config.h"
#include "tcmalloc/internal/cpu_utils.h"
#include "tcmalloc/internal/logging.h"
#include "tcmalloc/internal/memory_stats.h"
#include "tcmalloc/internal/memory_tag.h"
#include "tcmalloc/internal/optimization.h"
#include "tcmalloc/internal/pageflags.h"
#include "tcmalloc/internal/percpu.h"
#include "tcmalloc/internal/system_allocator.h"
#include "tcmalloc/malloc_hook_invoke.h"
#include "tcmalloc/metadata_object_allocator.h"
#include "tcmalloc/page_allocator.h"
#include "tcmalloc/pagemap.h"
#include "tcmalloc/pages.h"
#include "tcmalloc/parameters.h"
#include "tcmalloc/span.h"
#include "tcmalloc/span_stats.h"
#include "tcmalloc/stack_trace_table.h"
#include "tcmalloc/static_vars.h"
#include "tcmalloc/stats.h"
#include "tcmalloc/thread_cache.h"
#include "tcmalloc/transfer_cache.h"

GOOGLE_MALLOC_SECTION_BEGIN
namespace tcmalloc {
namespace tcmalloc_internal {

using subtle::percpu::RseqVcpuMode;

static absl::string_view MadviseString() {
  MadvisePreference pref = tc_globals.system_allocator().madvise_preference();

  switch (pref) {
    case MadvisePreference::kNever:
      return "MADVISE_NEVER";
    case MadvisePreference::kDontNeed:
      return "MADVISE_DONTNEED";
    case MadvisePreference::kFreeOnly:
      return "MADVISE_FREE_ONLY";
    case MadvisePreference::kFreeAndDontNeed:
      return "MADVISE_FREE_AND_DONTNEED";
  }

  ABSL_UNREACHABLE();
}

// Get stats into "r".  Also, if class_count != NULL, class_count[k]
// will be set to the total number of objects of size class k in the
// central cache, transfer cache, and per-thread and per-CPU caches.
// If small_spans is non-NULL, it is filled.  Same for large_spans.
// The boolean report_residence determines whether residence information
// should be captured or not. Residence info requires a potentially
// costly OS call, and is not necessary in all situations.
void ExtractStats(TCMallocStats& r, uint64_t* absl_nullable class_count,
                  SpanStats* absl_nullable span_stats,
                  SmallSpanStats* absl_nullable small_spans,
                  LargeSpanStats* absl_nullable large_spans,
                  bool report_residence) {
  r.central_bytes = 0;
  r.transfer_bytes = 0;
  for (int size_class = 0; size_class < kNumClasses; ++size_class) {
    const size_t length = tc_globals.central_freelist(size_class).length();
    const size_t tc_length = tc_globals.transfer_cache().tc_length(size_class);
    const size_t sharded_tc_length =
        tc_globals.sharded_transfer_cache().TotalObjectsOfClass(size_class);
    const size_t cache_overhead =
        tc_globals.central_freelist(size_class).OverheadBytes();
    const size_t size = tc_globals.sizemap().class_to_size(size_class);
    r.central_bytes += (size * length) + cache_overhead;
    r.transfer_bytes += (size * tc_length);
    if (class_count) {
      // Sum the lengths of all per-class freelists, except the per-thread
      // freelists, which get counted when we call GetThreadStats(), below.
      class_count[size_class] = length + tc_length + sharded_tc_length;
      if (UsePerCpuCache(tc_globals)) {
        class_count[size_class] +=
            tc_globals.cpu_cache().TotalObjectsOfClass(size_class);
      }
    }
    if (span_stats) {
      span_stats[size_class] =
          tc_globals.central_freelist(size_class).GetSpanStats();
    }
  }

  // Add stats from per-thread heaps
  r.thread_bytes = 0;

  r.span_stats = tc_globals.span_allocator().stats();
  r.stack_stats = tc_globals.sampledallocation_allocator().stats();
  r.linked_sample_stats = tc_globals.linked_sample_allocator().stats();
  r.tc_stats = ThreadCache::GetStats(&r.thread_bytes, class_count);

  {  // scope
    PageHeapSpinLockHolder l;
    r.metadata_bytes = tc_globals.metadata_bytes();
    r.pagemap_bytes = tc_globals.pagemap().bytes();
    r.pageheap = tc_globals.page_allocator().stats();
    r.peak_stats = tc_globals.page_allocator().peak_stats();
    if (small_spans != nullptr) {
      tc_globals.page_allocator().GetSmallSpanStats(small_spans);
    }
    if (large_spans != nullptr) {
      tc_globals.page_allocator().GetLargeSpanStats(large_spans);
    }

    // TODO(b/207622377):  Arena is thread-safe, but we take the pageheap_lock
    // to present a consistent view of memory usage.
    r.arena = tc_globals.arena().stats();

    const PageReleaseStats release_stats =
        tc_globals.page_allocator().GetReleaseStats();

    r.num_released_total = release_stats.total;
    r.num_released_release_memory_to_system =
        release_stats.release_memory_to_system;
    r.num_released_process_background_actions =
        release_stats.process_background_actions;
    r.num_released_soft_limit_exceeded = release_stats.soft_limit_exceeded;
    r.num_released_hard_limit_exceeded = release_stats.hard_limit_exceeded;

    r.per_cpu_bytes = 0;
    r.sharded_transfer_bytes = 0;
    r.percpu_metadata_bytes_res = 0;
    r.percpu_metadata_bytes = 0;
    if (UsePerCpuCache(tc_globals)) {
      r.per_cpu_bytes = tc_globals.cpu_cache().TotalUsedBytes();
      r.sharded_transfer_bytes =
          tc_globals.sharded_transfer_cache().TotalBytes();

      if (report_residence) {
        auto percpu_metadata = tc_globals.cpu_cache().MetadataMemoryUsage();
        r.percpu_metadata_bytes_res = percpu_metadata.resident_size;
        r.percpu_metadata_bytes = percpu_metadata.virtual_size;

        TC_ASSERT_GE(r.metadata_bytes, r.percpu_metadata_bytes);
        r.metadata_bytes = r.metadata_bytes - r.percpu_metadata_bytes +
                           r.percpu_metadata_bytes_res;
      }
    }
  }
  // We can access the pagemap without holding the pageheap_lock since it
  // is static data, and we are only taking address and size which are
  // constants.
  if (report_residence) {
    auto resident_bytes = tc_globals.pagemap_residence();
    r.pagemap_root_bytes_res = resident_bytes;
    TC_ASSERT_GE(r.metadata_bytes, r.pagemap_bytes);
    r.metadata_bytes = r.metadata_bytes - r.pagemap_bytes + resident_bytes;
  } else {
    r.pagemap_root_bytes_res = 0;
  }
}

void ExtractTCMallocStats(TCMallocStats& r, bool report_residence) {
  ExtractStats(r, nullptr, nullptr, nullptr, nullptr, report_residence);
}

// Because different fields of stats are computed from state protected
// by different locks, they may be inconsistent.  Prevent underflow
// when subtracting to avoid gigantic results.
static uint64_t StatSub(uint64_t a, uint64_t b) {
  return (a >= b) ? (a - b) : 0;
}

// Return approximate number of bytes in use by app.
uint64_t InUseByApp(const TCMallocStats& stats) {
  return StatSub(stats.pageheap.system_bytes,
                 stats.thread_bytes + stats.central_bytes +
                     stats.transfer_bytes + stats.per_cpu_bytes +
                     stats.sharded_transfer_bytes + stats.pageheap.free_bytes +
                     stats.pageheap.unmapped_bytes);
}

uint64_t VirtualMemoryUsed(const TCMallocStats& stats) {
  return stats.pageheap.system_bytes + stats.metadata_bytes +
         stats.arena.bytes_unallocated + stats.arena.bytes_unavailable +
         stats.arena.bytes_nonresident;
}

uint64_t UnmappedBytes(const TCMallocStats& stats) {
  return stats.pageheap.unmapped_bytes + stats.arena.bytes_nonresident;
}

uint64_t PhysicalMemoryUsed(const TCMallocStats& stats) {
  return StatSub(VirtualMemoryUsed(stats), UnmappedBytes(stats));
}

// The number of bytes either in use by the app or fragmented so that
// it cannot be (arbitrarily) reused.
uint64_t RequiredBytes(const TCMallocStats& stats) {
  return StatSub(PhysicalMemoryUsed(stats), stats.pageheap.free_bytes);
}

size_t ExternalBytes(const TCMallocStats& stats) {
  return stats.pageheap.free_bytes + stats.central_bytes + stats.per_cpu_bytes +
         stats.sharded_transfer_bytes + stats.transfer_bytes +
         stats.thread_bytes + stats.metadata_bytes +
         stats.arena.bytes_unavailable + stats.arena.bytes_unallocated;
}

size_t HeapSizeBytes(const BackingStats& stats) {
  return StatSub(stats.system_bytes, stats.unmapped_bytes);
}

size_t LocalBytes(const TCMallocStats& stats) {
  return stats.thread_bytes + stats.per_cpu_bytes +
         stats.sharded_transfer_bytes;
}

size_t SlackBytes(const BackingStats& stats) {
  return stats.free_bytes + stats.unmapped_bytes;
}

static void PrintHooksState(Printer& printer) {
  int new_hooks = new_hooks_.size();
  int delete_hooks = delete_hooks_.size();
  int sampled_new_hooks = sampled_new_hooks_.size();
  int sampled_delete_hooks = sampled_delete_hooks_.size();

  printer.printf(
      "MALLOC HOOKS: NEW=%d DELETE=%d SAMPLED_NEW=%d SAMPLED_DELETE=%d\n",
      new_hooks, delete_hooks, sampled_new_hooks, sampled_delete_hooks);
}

static int CountAllowedCpus() {
  CpuSet allowed_cpus;
  if (!allowed_cpus.GetAffinity(0)) {
    return 0;
  }

  return allowed_cpus.Count();
}

static absl::string_view SizeClassConfigurationString(
    SizeClassConfiguration config) {
  switch (config) {
    case SizeClassConfiguration::kPow2Only:
      return "SIZE_CLASS_POW2_ONLY";
    case SizeClassConfiguration::kLegacy:
      // TODO(b/242710633): remove this opt out.
      return "SIZE_CLASS_LEGACY";
    case SizeClassConfiguration::kReuse:
      return "SIZE_CLASS_REUSE";
    case SizeClassConfiguration::kReuseRelaxedBelow64:
      return "SIZE_CLASS_REUSE_RELAXED_BELOW_64";
  }

  ASSUME(false);
  return "SIZE_CLASS_UNKNOWN";
}

static absl::string_view HeapPartitioningModeString() {
  if (tc_globals.active_partitions() == 1) {
    return "PARTITIONING_MODE_DISABLED";
  } else if (kSecurityPartitions == 1 && tc_globals.active_partitions() > 1) {
    return "PARTITIONING_MODE_NUMA";
  } else if (tc_globals.active_partitions() > 1) {
    return Parameters::heap_partitioning_mode() == HeapPartitioningMode::kLight
               ? "PARTITIONING_MODE_SECURITY_LIGHT"
               : "PARTITIONING_MODE_SECURITY";
  }
  return "PARTITIONING_MODE_UNKNOWN";
}

static absl::string_view PerCpuTypeString(RseqVcpuMode mode) {
  switch (mode) {
    case RseqVcpuMode::kNone:
      return "NONE";
    case RseqVcpuMode::kMM:
      return "MM";
  }

  ASSUME(false);
  return "NONE";
}

void DumpStats(Printer& out, int level) {
  TCMallocStats stats;
  uint64_t class_count[kNumClasses];
  SpanStats span_stats[kNumClasses];
  if (level >= 2) {
    ExtractStats(stats, class_count, span_stats, nullptr, nullptr, true);
  } else {
    ExtractTCMallocStats(stats, true);
  }

  static const double MiB = 1048576.0;

  out.printf(
      "See https://github.com/google/tcmalloc/tree/master/docs/stats.md for an explanation of "
      "this page\n");

  const uint64_t virtual_memory_used = VirtualMemoryUsed(stats);
  const uint64_t physical_memory_used = PhysicalMemoryUsed(stats);
  const uint64_t unmapped_bytes = UnmappedBytes(stats);
  const uint64_t bytes_in_use_by_app = InUseByApp(stats);

#ifdef TCMALLOC_INTERNAL_SMALL_BUT_SLOW
  out.printf("NOTE:  SMALL MEMORY MODEL IS IN USE, PERFORMANCE MAY SUFFER.\n");
#endif
  // clang-format off
  // Avoid clang-format complaining about the way that this text is laid out.
  out.printf(
      "------------------------------------------------\n"
      "MALLOC:   %12u (%7.1f MiB) Bytes in use by application\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in page heap freelist\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in central cache freelist\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in per-CPU cache freelist\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in Sharded cache freelist\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in transfer cache freelist\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in thread cache freelists\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in malloc metadata\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in malloc metadata Arena unallocated\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes in malloc metadata Arena unavailable\n"

      "MALLOC:   ------------\n"
      "MALLOC: = %12u (%7.1f MiB) Actual memory used (physical + swap)\n"
      "MALLOC: + %12u (%7.1f MiB) Bytes released to OS (aka unmapped)\n"
      "MALLOC:   ------------\n"
      "MALLOC: = %12u (%7.1f MiB) Virtual address space used\n"
      "MALLOC:\n"
      "MALLOC:   %12u               Spans in use\n"
      "MALLOC:   %12u (%7.1f MiB) Spans created\n"
      "MALLOC:   %12u               Thread heaps in use\n"
      "MALLOC:   %12u (%7.1f MiB) Thread heaps created\n"
      "MALLOC:   %12u               Stack traces in use\n"
      "MALLOC:   %12u (%7.1f MiB) Stack traces created\n"
      "MALLOC:   %12u               Table buckets in use\n"
      "MALLOC:   %12u (%7.1f MiB) Table buckets created\n"
      "MALLOC:   %12u (%7.1f MiB) Pagemap bytes used\n"
      "MALLOC:   %12u (%7.1f MiB) Pagemap root resident bytes\n"
      "MALLOC:   %12u (%7.1f MiB) per-CPU slab bytes used\n"
      "MALLOC:   %12u (%7.1f MiB) per-CPU slab resident bytes\n"
      "MALLOC:   %12u (%7.1f MiB) malloc metadata Arena non-resident bytes\n"
      "MALLOC:   %12u (%7.1f MiB) Actual memory used at peak\n"
      "MALLOC:   %12u (%7.1f MiB) Estimated in-use at peak\n"
      "MALLOC:   %12.4f               Realized fragmentation (%%)\n"
      "MALLOC:   %12u               Tcmalloc page size\n"
      "MALLOC:   %12u               Tcmalloc hugepage size\n"
      "MALLOC:   %12u               CPUs Allowed in Mask\n"
      "MALLOC:   %12u               Arena blocks\n",
      bytes_in_use_by_app, bytes_in_use_by_app / MiB,
      stats.pageheap.free_bytes, stats.pageheap.free_bytes / MiB,
      stats.central_bytes, stats.central_bytes / MiB,
      stats.per_cpu_bytes, stats.per_cpu_bytes / MiB,
      stats.sharded_transfer_bytes, stats.sharded_transfer_bytes / MiB,
      stats.transfer_bytes, stats.transfer_bytes / MiB,
      stats.thread_bytes, stats.thread_bytes / MiB,
      stats.metadata_bytes, stats.metadata_bytes / MiB,
      stats.arena.bytes_unallocated, stats.arena.bytes_unallocated / MiB,
      stats.arena.bytes_unavailable, stats.arena.bytes_unavailable / MiB,
      physical_memory_used, physical_memory_used / MiB,
      unmapped_bytes, unmapped_bytes / MiB,
      virtual_memory_used, virtual_memory_used / MiB,
      uint64_t(stats.span_stats.in_use),
      uint64_t(stats.span_stats.total),
      (stats.span_stats.total * sizeof(Span)) / MiB,
      uint64_t(stats.tc_stats.in_use),
      uint64_t(stats.tc_stats.total),
      (stats.tc_stats.total * sizeof(ThreadCache)) / MiB,
      uint64_t(stats.stack_stats.in_use),
      uint64_t(stats.stack_stats.total),
      (stats.stack_stats.total * sizeof(StackTrace)) / MiB,
      uint64_t(stats.linked_sample_stats.in_use),
      uint64_t(stats.linked_sample_stats.total),
      (stats.linked_sample_stats.total * sizeof(StackTraceTable::LinkedSample)) / MiB,
      uint64_t(stats.pagemap_bytes),
      stats.pagemap_bytes / MiB,
      stats.pagemap_root_bytes_res, stats.pagemap_root_bytes_res / MiB,
      uint64_t(stats.percpu_metadata_bytes),
      stats.percpu_metadata_bytes / MiB,
      stats.percpu_metadata_bytes_res, stats.percpu_metadata_bytes_res / MiB,
      stats.arena.bytes_nonresident, stats.arena.bytes_nonresident / MiB,
      uint64_t(stats.peak_stats.backed_bytes),
      stats.peak_stats.backed_bytes / MiB,
      uint64_t(stats.peak_stats.sampled_application_bytes),
      stats.peak_stats.sampled_application_bytes / MiB,
      100. * safe_div(stats.peak_stats.backed_bytes - stats.peak_stats.sampled_application_bytes, stats.peak_stats.sampled_application_bytes),
      uint64_t(kPageSize),
      uint64_t(kHugePageSize),
      CountAllowedCpus(),
      stats.arena.blocks
  );
  // clang-format on

  out.printf("MALLOC EXPERIMENTS:");
  WalkExperiments([&](absl::string_view name, bool active) {
    const char* value = active ? "1" : "0";
    out.printf(" %s=%s", name, value);
  });
  out.printf("\n");

  PrintHooksState(out);
  out.printf(
      "MALLOC SAMPLED PROFILES: %zu bytes (current), %zu bytes (internal "
      "fragmentation), %zu bytes (peak), %zu count (total)\n",
      static_cast<size_t>(tc_globals.sampled_objects_size_.value()),
      tc_globals.sampled_internal_fragmentation_.value(),
      tc_globals.peak_heap_tracker().CurrentPeakSize(),
      tc_globals.total_sampled_count_.value());

  MemoryStats memstats;
  if (GetMemoryStats(&memstats)) {
    uint64_t rss = memstats.rss;
    uint64_t vss = memstats.vss;
    // clang-format off
    out.printf(
        "\n"
        "Total process stats (inclusive of non-malloc sources):\n"
        "TOTAL: %12u (%7.1f MiB) Bytes resident (physical memory used)\n"
        "TOTAL: %12u (%7.1f MiB) Bytes mapped (virtual memory used)\n",
        rss, rss / MiB, vss, vss / MiB);
    // clang-format on
  }

  out.printf("\nMachine-level hugepage fragmentation stats:\n");
  const size_t num_nodes = tc_globals.numa_topology().num_nodes();
  for (size_t node = 0; node < num_nodes; node++) {
    std::optional<double> hugepage_frag_ratio =
        GetHugepageFragmentationRatio(node);
    if (hugepage_frag_ratio.has_value()) {
      out.printf("TOTAL: %12.3f Hugepage fragmentation ratio (node%zu)\n",
                 *hugepage_frag_ratio, node);
    }
  }

  out.printf(
      "------------------------------------------------\n"
      "Call ReleaseMemoryToSystem() to release freelist memory to the OS"
      " (via madvise()).\n"
      "Bytes released to the OS take up virtual address space"
      " but no physical memory.\n");
  if (level >= 2) {
    out.printf("------------------------------------------------\n");
    out.printf("Total size of freelists for per-thread and per-CPU caches,\n");
    out.printf("transfer cache, and central cache, as well as number of\n");
    out.printf("live pages, returned/requested spans by size class\n");
    out.printf("------------------------------------------------\n");

    uint64_t cumulative = 0;
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      uint64_t class_bytes = class_count[size_class] *
                             tc_globals.sizemap().class_to_size(size_class);

      cumulative += class_bytes;
      out.printf(
          // clang-format off
          "class %3d [ %8zu bytes ] : %8u objs; %5.1f MiB; %6.1f cum MiB; "
          "%8u live pages; spans: %10zu ret / %10zu req = %5.4f; "
          "%10zu objs/span;\n",
          // clang-format on
          size_class, tc_globals.sizemap().class_to_size(size_class),
          class_count[size_class], class_bytes / MiB, cumulative / MiB,
          (span_stats[size_class].num_live_spans() *
           tc_globals.sizemap().class_to_pages(size_class))
              .raw_num(),
          span_stats[size_class].num_spans_returned,
          span_stats[size_class].num_spans_requested,
          span_stats[size_class].prob_returned(),
          tc_globals.central_freelist(size_class).objects_per_span());
    }

    out.printf("------------------------------------------------\n");
    out.printf("Central cache freelist: Span utilization histogram\n");
    out.printf("Non-cumulative number of spans with allocated objects < N\n");
    out.printf("------------------------------------------------\n");
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      tc_globals.central_freelist(size_class).PrintSpanUtilStats(out);
    }

    out.printf("\n");
    out.printf("------------------------------------------------\n");
    out.printf("Central cache freelist: Span lifetime histogram\n");
    out.printf("Non-cumulative number of spans lifetime a < N\n");
    out.printf("------------------------------------------------\n");
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      tc_globals.central_freelist(size_class).PrintSpanLifetimeStats(out);
    }

    out.printf("\n");
    out.printf("------------------------------------------------\n");
    out.printf("Central cache freelist: Number of spans used histogram\n");
    out.printf(
        "Number of spans used to fill a batch of allocations, from 0 to "
        "N\n");
    out.printf("------------------------------------------------\n");
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      tc_globals.central_freelist(size_class).PrintNumSpansUsed(out);
    }

    out.printf("\n");
    out.printf("------------------------------------------------\n");
    out.printf("Central cache freelist: Long lived spans moved histogram\n");
    out.printf(
        "Number of spans moved to the long lived spans list, from 0 to N\n");
    out.printf("------------------------------------------------\n");
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      tc_globals.central_freelist(size_class).PrintLongLivedSpansMoved(out);
    }

    out.printf("------------------------------------------------\n");
    out.printf("Central cache freelist: Same-span returns\n");
    out.printf("------------------------------------------------\n");
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      tc_globals.central_freelist(size_class).PrintSameSpanStats(out);
    }

    tc_globals.transfer_cache().Print(tc_globals.per_size_class_counts(), out);
    tc_globals.sharded_transfer_cache().Print(
        tc_globals.per_size_class_counts(), out);

    if (UsePerCpuCache(tc_globals)) {
      tc_globals.cpu_cache().Print(out);
    }

    PageFlags pageflags;
    tc_globals.page_allocator().Print(out, MemoryTag::kNormal, pageflags);
    if (tc_globals.active_partitions() > 1) {
      tc_globals.page_allocator().Print(out, MemoryTag::kNormalP1, pageflags);
    }
    tc_globals.page_allocator().Print(out, MemoryTag::kSampled, pageflags);
    if (Parameters::heap_partitioning_mode() == HeapPartitioningMode::kFull) {
      tc_globals.page_allocator().Print(out, MemoryTag::kSampledP1, pageflags);
    }
    tc_globals.page_allocator().Print(out, MemoryTag::kCold, pageflags);
    tc_globals.guardedpage_allocator().Print(out);

    out.printf("------------------------------------------------\n");
    out.printf("Configured limits and related statistics\n");
    out.printf("------------------------------------------------\n");
    uint64_t soft_limit_bytes =
        tc_globals.page_allocator().limit(PageAllocator::kSoft);
    uint64_t hard_limit_bytes =
        tc_globals.page_allocator().limit(PageAllocator::kHard);

    out.printf("PARAMETER desired_usage_limit_bytes %u\n", soft_limit_bytes);
    out.printf("PARAMETER hard_usage_limit_bytes %u\n", hard_limit_bytes);
    out.printf("Number of times soft limit was hit: %lld\n",
               tc_globals.page_allocator().limit_hits(PageAllocator::kSoft));
    out.printf("Number of times hard limit was hit: %lld\n",
               tc_globals.page_allocator().limit_hits(PageAllocator::kHard));
    out.printf("Number of times memory shrank below soft limit: %lld\n",
               tc_globals.page_allocator().successful_shrinks_after_limit_hit(
                   PageAllocator::kSoft));
    out.printf("Number of times memory shrank below hard limit: %lld\n",
               tc_globals.page_allocator().successful_shrinks_after_limit_hit(
                   PageAllocator::kHard));

    out.printf("Total number of pages released: %llu (%7.1f MiB)\n",
               stats.num_released_total.in_pages().raw_num(),
               stats.num_released_total.in_mib());
    out.printf(
        "Number of pages released by ReleaseMemoryToSystem: %llu (%7.1f "
        "MiB)\n",
        stats.num_released_release_memory_to_system.in_pages().raw_num(),
        stats.num_released_release_memory_to_system.in_mib());
    out.printf(
        "Number of pages released by ProcessBackgroundActions: %llu "
        "(%7.1f MiB)\n",
        stats.num_released_process_background_actions.in_pages().raw_num(),
        stats.num_released_process_background_actions.in_mib());
    out.printf(
        "Number of pages released after soft limit hits: %llu (%7.1f "
        "MiB)\n",
        stats.num_released_soft_limit_exceeded.in_pages().raw_num(),
        stats.num_released_soft_limit_exceeded.in_mib());
    out.printf(
        "Number of pages released after hard limit hits: %llu (%7.1f "
        "MiB)\n",
        stats.num_released_hard_limit_exceeded.in_pages().raw_num(),
        stats.num_released_hard_limit_exceeded.in_mib());
    out.printf("Number of active %spartitions: %llu\n",
               tc_globals.active_partitions() > 1
                   ? (tc_globals.multiple_non_numa_partitions() ? "security "
                                                                : "NUMA ")
                   : "",
               tc_globals.active_partitions());

    out.printf("------------------------------------------------\n");
    out.printf("Parameters\n");
    out.printf("------------------------------------------------\n");
    out.printf("PARAMETER tcmalloc_per_cpu_caches %d\n",
               Parameters::per_cpu_caches() ? 1 : 0);
    out.printf("PARAMETER tcmalloc_max_per_cpu_cache_size %d\n",
               Parameters::max_per_cpu_cache_size());
    out.printf("PARAMETER tcmalloc_max_total_thread_cache_bytes %lld\n",
               Parameters::max_total_thread_cache_bytes());
    out.printf("PARAMETER malloc_release_bytes_per_sec %llu\n",
               Parameters::background_release_rate());
    out.printf("PARAMETER tcmalloc_skip_subrelease_short_interval %s\n",
               absl::FormatDuration(
                   Parameters::filler_skip_subrelease_short_interval()));
    out.printf("PARAMETER tcmalloc_skip_subrelease_long_interval %s\n",
               absl::FormatDuration(
                   Parameters::filler_skip_subrelease_long_interval()));
    out.printf("PARAMETER tcmalloc_release_partial_alloc_pages %d\n",
               Parameters::release_partial_alloc_pages() ? 1 : 0);
    out.printf("PARAMETER tcmalloc_release_pages_from_huge_region %d\n",
               Parameters::release_pages_from_huge_region() ? 1 : 0);
    out.printf("PARAMETER tcmalloc_huge_region_adaptive_release %d\n",
               Parameters::huge_region_adaptive_release() ? 1 : 0);
    out.printf("PARAMETER madvise_cold_regions_nohugepage %d\n",
               Parameters::madvise_cold_regions_nohugepage() ==
                       MadviseRegionsNoHugepage::kEnabled
                   ? 1
                   : 0);
    out.printf("PARAMETER tcmalloc_use_wider_slabs %d\n",
               tc_globals.cpu_cache().UseWiderSlabs() ? 1 : 0);
    out.printf("PARAMETER heap_partitioning %d\n",
               tc_globals.multiple_non_numa_partitions() ? 1 : 0);

    out.printf(
        "PARAMETER size_class_config %s\n",
        SizeClassConfigurationString(tc_globals.size_class_configuration()));
    out.printf("PARAMETER percpu_vcpu_type %s\n",
               PerCpuTypeString(subtle::percpu::GetRseqVcpuMode()));
    out.printf("PARAMETER madvise %s\n", MadviseString());
    out.printf("PARAMETER tcmalloc_resize_size_class_max_capacity %d\n",
               Parameters::resize_size_class_max_capacity() ? 1 : 0);
    out.printf(
        "PARAMETER tcmalloc_dense_trackers_sorted_on_spans_allocated 1\n");
    out.printf("PARAMETER min_hot_access_hint %d\n",
               static_cast<int>(Parameters::min_hot_access_hint()));
    out.printf(
        "PARAMETER tcmalloc_usermode_hugepage_collapse %d\n",
        Parameters::usermode_hugepage_collapse() == EnableCollapse::kEnabled
            ? 1
            : 0);
    out.printf("PARAMETER tcmalloc_subrelease_unbacked_hugepages %d\n",
               Parameters::subrelease_unbacked_hugepages() ==
                       SubreleaseUnbackedMode::kEnabled
                   ? 1
                   : 0);

    out.printf("PARAMETER tcmalloc_back_small_allocations %d\n",
               Parameters::back_small_allocations() ? 1 : 0);
    out.printf("PARAMETER tcmalloc_back_size_threshold_bytes %d\n",
               Parameters::back_size_threshold_bytes());
    out.printf("PARAMETER tcmalloc_span_lifetime_tracking %d\n",
               Parameters::span_lifetime_tracking() ==
                       central_freelist_internal::LifetimeTracking::kEnabled
                   ? 1
                   : 0);
    out.printf("PARAMETER background_process_sleep_interval_ns %lld\n",
               absl::ToInt64Nanoseconds(
                   Parameters::background_process_sleep_interval()));

    if (UsePerCpuCache(tc_globals)) {
      out.printf("PARAMETER tcmalloc_cpu_cache_touched %d\n",
                 tc_globals.cpu_cache().CountTouchedCpus());
      out.printf("PARAMETER max_tcmalloc_cpu_cache_touched %d\n",
                 tc_globals.cpu_cache().MaxTouchedCpu());
    }
    out.printf("PARAMETER tcmalloc_enable_unfiltered_collapse %d\n",
               Parameters::enable_unfiltered_collapse() ==
                       EnableUnfilteredCollapse::kEnabled
                   ? 1
                   : 0);
    out.printf("PARAMETER tcmalloc_release_stale_pages %d\n",
               Parameters::release_stale_pages() == ReleaseStalePages::kEnabled
                   ? 1
                   : 0);
  }
}

void PrintMemoryStatsInPbtxt(PbtxtRegion& region) {
  MemoryStats memstats;
  if (GetMemoryStats(memstats)) {
    PrintMemoryStatsInPbtxt(region, memstats);
  }
}

void PrintMemoryStatsInPbtxt(PbtxtRegion& region, const MemoryStats& stats) {
  // We have observed negative and large positive values for total_resident
  // memory.  We record the data from the /proc/self/statm file to identify
  // the underlying issue.
  if (stats.rss < 0 || stats.rss >= (1LL << 60)) {
    region.PrintString("proc_self_statm",
                       absl::string_view(stats.buf, stats.rc));
  }
  region.PrintI64("total_resident", uint64_t(stats.rss));
  region.PrintI64("total_mapped", uint64_t(stats.vss));
}

void DumpStatsInPbtxt(Printer& out, int level) {
  TCMallocStats stats;
  uint64_t class_count[kNumClasses];
  SpanStats span_stats[kNumClasses];
  if (level >= 2) {
    ExtractStats(stats, class_count, span_stats, nullptr, nullptr, true);
  } else {
    ExtractTCMallocStats(stats, true);
  }

  const uint64_t bytes_in_use_by_app = InUseByApp(stats);
  const uint64_t virtual_memory_used = VirtualMemoryUsed(stats);
  const uint64_t physical_memory_used = PhysicalMemoryUsed(stats);
  const uint64_t unmapped_bytes = UnmappedBytes(stats);

  PbtxtRegion region(out, kTop);
  region.PrintI64("in_use_by_app", bytes_in_use_by_app);
  region.PrintI64("page_heap_freelist", stats.pageheap.free_bytes);
  region.PrintI64("central_cache_freelist", stats.central_bytes);
  region.PrintI64("per_cpu_cache_freelist", stats.per_cpu_bytes);
  region.PrintI64("sharded_transfer_cache_freelist",
                  stats.sharded_transfer_bytes);
  region.PrintI64("transfer_cache_freelist", stats.transfer_bytes);
  region.PrintI64("thread_cache_freelists", stats.thread_bytes);
  region.PrintI64("malloc_metadata", stats.metadata_bytes);
  region.PrintI64("malloc_metadata_arena_unavailable",
                  stats.arena.bytes_unavailable);
  region.PrintI64("malloc_metadata_arena_unallocated",
                  stats.arena.bytes_unallocated);
  region.PrintI64("actual_mem_used", physical_memory_used);
  region.PrintI64("unmapped", unmapped_bytes);
  region.PrintI64("virtual_address_space_used", virtual_memory_used);
  region.PrintI64("num_spans", uint64_t(stats.span_stats.in_use));
  region.PrintI64("num_spans_created", uint64_t(stats.span_stats.total));
  region.PrintI64("num_thread_heaps", uint64_t(stats.tc_stats.in_use));
  region.PrintI64("num_thread_heaps_created", uint64_t(stats.tc_stats.total));
  region.PrintI64("num_stack_traces", uint64_t(stats.stack_stats.in_use));
  region.PrintI64("num_stack_traces_created",
                  uint64_t(stats.stack_stats.total));
  region.PrintI64("num_table_buckets",
                  uint64_t(stats.linked_sample_stats.in_use));
  region.PrintI64("num_table_buckets_created",
                  uint64_t(stats.linked_sample_stats.total));
  region.PrintI64("pagemap_size", uint64_t(stats.pagemap_bytes));
  region.PrintI64("pagemap_root_residence", stats.pagemap_root_bytes_res);
  region.PrintI64("percpu_slab_size", stats.percpu_metadata_bytes);
  region.PrintI64("percpu_slab_residence", stats.percpu_metadata_bytes_res);
  region.PrintI64("peak_backed", stats.peak_stats.backed_bytes);
  region.PrintI64("peak_application_demand",
                  stats.peak_stats.sampled_application_bytes);
  region.PrintI64("tcmalloc_page_size", uint64_t(kPageSize));
  region.PrintI64("tcmalloc_huge_page_size", uint64_t(kHugePageSize));
  region.PrintI64("cpus_allowed", CountAllowedCpus());
  region.PrintI64("arena_blocks", stats.arena.blocks);

  // Print hooks stats.
  region.PrintI64("new_hooks_present", uint64_t(new_hooks_.size()));
  region.PrintI64("delete_hooks_present", uint64_t(delete_hooks_.size()));
  region.PrintI64("sampled_new_hooks_present",
                  uint64_t(sampled_new_hooks_.size()));
  region.PrintI64("sampled_delete_hooks_present",
                  uint64_t(sampled_delete_hooks_.size()));

  {
    auto sampled_profiles = region.CreateSubRegion("sampled_profiles");
    sampled_profiles.PrintI64("current_bytes",
                              tc_globals.sampled_objects_size_.value());
    sampled_profiles.PrintI64(
        "current_fragmentation_bytes",
        tc_globals.sampled_internal_fragmentation_.value());
    sampled_profiles.PrintI64("peak_bytes",
                              tc_globals.peak_heap_tracker().CurrentPeakSize());
  }

  // Print total process stats (inclusive of non-malloc sources).
  PrintMemoryStatsInPbtxt(region);

  const size_t num_nodes = tc_globals.numa_topology().num_nodes();
  for (size_t node = 0; node < num_nodes; node++) {
    std::optional<double> hugepage_frag_ratio =
        GetHugepageFragmentationRatio(node);
    if (hugepage_frag_ratio.has_value()) {
      PbtxtRegion entry =
          region.CreateSubRegion("hugepage_fragmentation_ratio");
      entry.PrintI64("node", node);
      entry.PrintDouble("ratio", *hugepage_frag_ratio);
    }
  }

  region.PrintI64("total_sampled_count",
                  tc_globals.total_sampled_count_.value());

  if (level >= 2) {
    for (int size_class = 1; size_class < kNumClasses; ++size_class) {
      uint64_t class_bytes = class_count[size_class] *
                             tc_globals.sizemap().class_to_size(size_class);
      PbtxtRegion entry = region.CreateSubRegion("freelist");
      entry.PrintI64("sizeclass",
                     tc_globals.sizemap().class_to_size(size_class));
      entry.PrintI64(
          "objects_per_span",
          tc_globals.central_freelist(size_class).objects_per_span());
      entry.PrintI64("bytes", class_bytes);
      entry.PrintI64("num_spans_requested",
                     span_stats[size_class].num_spans_requested);
      entry.PrintI64("num_spans_returned",
                     span_stats[size_class].num_spans_returned);
      entry.PrintI64("obj_capacity", span_stats[size_class].obj_capacity);
      tc_globals.central_freelist(size_class).PrintSpanUtilStatsInPbtxt(entry);
      tc_globals.central_freelist(size_class)
          .PrintSpanLifetimeStatsInPbtxt(entry);
      tc_globals.central_freelist(size_class).PrintNumSpansUsedInPbtxt(entry);
      tc_globals.central_freelist(size_class)
          .PrintLongLivedSpansMovedInPbtxt(entry);
      tc_globals.central_freelist(size_class).PrintSameSpanStatsInPbtxt(entry);
    }

    tc_globals.transfer_cache().PrintInPbtxt(tc_globals.per_size_class_counts(),
                                             region);
    tc_globals.sharded_transfer_cache().PrintInPbtxt(
        tc_globals.per_size_class_counts(), region);
  }
  if (UsePerCpuCache(tc_globals)) {
    tc_globals.cpu_cache().PrintInPbtxt(region);
  }

  PageFlags pageflags;
  tc_globals.page_allocator().PrintInPbtxt(region, MemoryTag::kNormal,
                                           pageflags);
  if (tc_globals.active_partitions() > 1) {
    tc_globals.page_allocator().PrintInPbtxt(region, MemoryTag::kNormalP1,
                                             pageflags);
  }
  tc_globals.page_allocator().PrintInPbtxt(region, MemoryTag::kSampled,
                                           pageflags);
  if (Parameters::heap_partitioning_mode() == HeapPartitioningMode::kFull) {
    tc_globals.page_allocator().PrintInPbtxt(region, MemoryTag::kSampledP1,
                                             pageflags);
  }
  tc_globals.page_allocator().PrintInPbtxt(region, MemoryTag::kCold, pageflags);
  // We do not collect tracking information in pbtxt.

  size_t soft_limit_bytes =
      tc_globals.page_allocator().limit(PageAllocator::kSoft);
  size_t hard_limit_bytes =
      tc_globals.page_allocator().limit(PageAllocator::kHard);

  region.PrintI64("desired_usage_limit_bytes", soft_limit_bytes);
  region.PrintI64("hard_usage_limit_bytes", hard_limit_bytes);
  region.PrintI64("soft_limit_hits",
                  tc_globals.page_allocator().limit_hits(PageAllocator::kSoft));
  region.PrintI64("hard_limit_hits",
                  tc_globals.page_allocator().limit_hits(PageAllocator::kHard));
  region.PrintI64(
      "successful_shrinks_after_soft_limit_hit",
      tc_globals.page_allocator().successful_shrinks_after_limit_hit(
          PageAllocator::kSoft));
  region.PrintI64(
      "successful_shrinks_after_hard_limit_hit",
      tc_globals.page_allocator().successful_shrinks_after_limit_hit(
          PageAllocator::kHard));

  region.PrintI64("num_released_total_pages",
                  stats.num_released_total.in_pages().raw_num());
  region.PrintI64(
      "num_released_release_memory_to_system_pages",
      stats.num_released_release_memory_to_system.in_pages().raw_num());
  region.PrintI64(
      "num_released_process_background_actions_pages",
      stats.num_released_process_background_actions.in_pages().raw_num());
  region.PrintI64("num_released_soft_limit_exceeded_pages",
                  stats.num_released_soft_limit_exceeded.in_pages().raw_num());
  region.PrintI64("num_released_hard_limit_exceeded_pages",
                  stats.num_released_hard_limit_exceeded.in_pages().raw_num());

  {
    auto gwp_asan = region.CreateSubRegion("gwp_asan");
    tc_globals.guardedpage_allocator().PrintInPbtxt(gwp_asan);
  }

  region.PrintI64("memory_release_failures",
                  tc_globals.system_allocator().release_errors());

  region.PrintBool("tcmalloc_per_cpu_caches", Parameters::per_cpu_caches());
  region.PrintI64("tcmalloc_max_per_cpu_cache_size",
                  Parameters::max_per_cpu_cache_size());
  region.PrintI64("tcmalloc_max_total_thread_cache_bytes",
                  Parameters::max_total_thread_cache_bytes());
  region.PrintI64("malloc_release_bytes_per_sec",
                  static_cast<int64_t>(Parameters::background_release_rate()));
  region.PrintI64("tcmalloc_skip_subrelease_short_interval_ns",
                  absl::ToInt64Nanoseconds(
                      Parameters::filler_skip_subrelease_short_interval()));
  region.PrintI64("tcmalloc_skip_subrelease_long_interval_ns",
                  absl::ToInt64Nanoseconds(
                      Parameters::filler_skip_subrelease_long_interval()));
  region.PrintBool("tcmalloc_release_partial_alloc_pages",
                   Parameters::release_partial_alloc_pages());
  region.PrintBool("tcmalloc_release_pages_from_huge_region",
                   Parameters::release_pages_from_huge_region());
  region.PrintBool("tcmalloc_huge_region_adaptive_release",
                   Parameters::huge_region_adaptive_release());
  region.PrintBool("madvise_cold_regions_nohugepage",
                   Parameters::madvise_cold_regions_nohugepage() ==
                       MadviseRegionsNoHugepage::kEnabled);
  region.PrintI64("profile_sampling_interval",
                  Parameters::profile_sampling_interval());
  region.PrintRaw("percpu_vcpu_type",
                  PerCpuTypeString(subtle::percpu::GetRseqVcpuMode()));
  region.PrintBool("tcmalloc_use_wider_slabs",
                   tc_globals.cpu_cache().UseWiderSlabs());
  region.PrintI64("active_partitions", tc_globals.active_partitions());
  region.PrintRaw("heap_partitioning", HeapPartitioningModeString());
  region.PrintBool("tcmalloc_dense_trackers_sorted_on_spans_allocated", true);
  region.PrintI64("min_hot_access_hint",
                  static_cast<int>(Parameters::min_hot_access_hint()));
  region.PrintBool(
      "usermode_hugepage_collapse",
      Parameters::usermode_hugepage_collapse() == EnableCollapse::kEnabled);
  region.PrintBool("subrelease_unbacked_hugepages",
                   Parameters::subrelease_unbacked_hugepages() ==
                       SubreleaseUnbackedMode::kEnabled);

  region.PrintBool("back_small_allocations",
                   Parameters::back_small_allocations());
  region.PrintI64("back_size_threshold_bytes",
                  Parameters::back_size_threshold_bytes());

  region.PrintRaw(
      "size_class_config",
      SizeClassConfigurationString(tc_globals.size_class_configuration()));
  region.PrintRaw("madvise", MadviseString());
  region.PrintBool("tcmalloc_resize_size_class_max_capacity",
                   Parameters::resize_size_class_max_capacity());
  region.PrintBool("tcmalloc_span_lifetime_tracking",
                   Parameters::span_lifetime_tracking() ==
                       central_freelist_internal::LifetimeTracking::kEnabled);

  region.PrintI64("background_process_sleep_interval_ns",
                  absl::ToInt64Nanoseconds(
                      Parameters::background_process_sleep_interval()));

  region.PrintBool("tcmalloc_enable_unfiltered_collapse",
                   Parameters::enable_unfiltered_collapse() ==
                       EnableUnfilteredCollapse::kEnabled);

  region.PrintBool(
      "tcmalloc_release_stale_pages",
      Parameters::release_stale_pages() == ReleaseStalePages::kEnabled);
}

bool GetNumericProperty(const char* name_data, size_t name_size,
                        size_t* absl_nonnull value) {
  TC_ASSERT(name_data != nullptr || name_size == 0);
  TC_ASSERT_NE(value, nullptr);
  const absl::string_view name(name_data, name_size);

  // LINT.IfChange
  // This is near the top since ReleasePerCpuMemoryToOS() calls it frequently.
  if (name == "tcmalloc.per_cpu_caches_active") {
    *value = tc_globals.CpuCacheActive();
    return true;
  }

  if (name == "generic.virtual_memory_used") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = VirtualMemoryUsed(stats);
    return true;
  }

  if (name == "generic.physical_memory_used") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = PhysicalMemoryUsed(stats);
    return true;
  }

  if (name == "generic.current_allocated_bytes" ||
      name == "generic.bytes_in_use_by_app") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = InUseByApp(stats);
    return true;
  }

  if (name == "generic.peak_memory_usage") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = static_cast<uint64_t>(stats.peak_stats.backed_bytes);
    return true;
  }

  if (name == "generic.realized_fragmentation") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = static_cast<uint64_t>(
        100. * safe_div(stats.peak_stats.backed_bytes -
                            stats.peak_stats.sampled_application_bytes,
                        stats.peak_stats.sampled_application_bytes));

    return true;
  }

  if (name == "generic.heap_size") {
    PageHeapSpinLockHolder l;
    BackingStats stats = tc_globals.page_allocator().stats();
    *value = HeapSizeBytes(stats);
    return true;
  }

  if (name == "tcmalloc.central_cache_free") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.central_bytes;
    return true;
  }

  if (name == "tcmalloc.cpu_free") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.per_cpu_bytes;
    return true;
  }

  if (name == "tcmalloc.sharded_transfer_cache_free") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.sharded_transfer_bytes;
    return true;
  }

  if (name == "tcmalloc.slack_bytes") {
    // Kept for backwards compatibility.  Now defined externally as:
    //    pageheap_free_bytes + pageheap_unmapped_bytes.
    PageHeapSpinLockHolder l;
    BackingStats stats = tc_globals.page_allocator().stats();
    *value = SlackBytes(stats);
    return true;
  }

  if (name == "tcmalloc.pageheap_free_bytes" ||
      name == "tcmalloc.page_heap_free") {
    PageHeapSpinLockHolder l;
    *value = tc_globals.page_allocator().stats().free_bytes;
    return true;
  }

  if (name == "tcmalloc.pageheap_unmapped_bytes" ||
      name == "tcmalloc.page_heap_unmapped") {
    PageHeapSpinLockHolder l;
    // Arena non-resident bytes aren't on the page heap, but they are unmapped.
    *value = tc_globals.page_allocator().stats().unmapped_bytes +
             tc_globals.arena().stats().bytes_nonresident;
    return true;
  }

  if (name == "tcmalloc.sampled_internal_fragmentation") {
    *value = tc_globals.sampled_internal_fragmentation_.value();
    return true;
  }

  if (name == "tcmalloc.max_total_thread_cache_bytes") {
    *value = ThreadCache::overall_thread_cache_size();
    return true;
  }

  if (name == "tcmalloc.current_total_thread_cache_bytes" ||
      name == "tcmalloc.thread_cache_free") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.thread_bytes;
    return true;
  }

  if (name == "tcmalloc.thread_cache_count") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.tc_stats.in_use;
    return true;
  }

  if (name == "tcmalloc.local_bytes") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = LocalBytes(stats);
    return true;
  }

  if (name == "tcmalloc.external_fragmentation_bytes") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = ExternalBytes(stats);
    return true;
  }

  if (name == "tcmalloc.metadata_bytes") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, true);
    *value = stats.metadata_bytes;
    return true;
  }

  if (name == "tcmalloc.transfer_cache_free") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = stats.transfer_bytes;
    return true;
  }

  auto limit_kind = (name == "tcmalloc.hard_usage_limit_bytes")
                        ? PageAllocator::kHard
                        : PageAllocator::kSoft;
  if (limit_kind == PageAllocator::kHard ||
      name == "tcmalloc.desired_usage_limit_bytes") {
    *value = tc_globals.page_allocator().limit(limit_kind);
    return true;
  }
  if (name == "tcmalloc.soft_limit_hits") {
    *value = tc_globals.page_allocator().limit_hits(PageAllocator::kSoft);
    return true;
  }
  if (name == "tcmalloc.hard_limit_hits") {
    *value = tc_globals.page_allocator().limit_hits(PageAllocator::kHard);
    return true;
  }
  if (name == "tcmalloc.successful_shrinks_after_soft_limit_hit") {
    *value = tc_globals.page_allocator().successful_shrinks_after_limit_hit(
        PageAllocator::kSoft);
    return true;
  }
  if (name == "tcmalloc.successful_shrinks_after_hard_limit_hit") {
    *value = tc_globals.page_allocator().successful_shrinks_after_limit_hit(
        PageAllocator::kHard);
    return true;
  }

  for (const auto& [property_name, field] :
       std::initializer_list<std::pair<absl::string_view /*property_name*/,
                                       Length PageReleaseStats::* /*field*/>>{
           {"tcmalloc.num_released_total_bytes", &PageReleaseStats::total},
           {"tcmalloc.num_released_release_memory_to_system_bytes",
            &PageReleaseStats::release_memory_to_system},
           {"tcmalloc.num_released_process_background_actions_bytes",
            &PageReleaseStats::process_background_actions},
           {"tcmalloc.num_released_soft_limit_exceeded_bytes",
            &PageReleaseStats::soft_limit_exceeded},
           {"tcmalloc.num_released_hard_limit_exceeded_bytes",
            &PageReleaseStats::hard_limit_exceeded}}) {
    if (name == property_name) {
      const PageHeapSpinLockHolder l;
      *value =
          (tc_globals.page_allocator().GetReleaseStats().*field).in_bytes();
      return true;
    }
  }

  if (name == "tcmalloc.required_bytes") {
    TCMallocStats stats;
    ExtractTCMallocStats(stats, false);
    *value = RequiredBytes(stats);
    return true;
  }

  if (name == "tcmalloc.security_partitioning_active") {
    *value = kSecurityPartitions > 1
                 ? static_cast<int>(Parameters::heap_partitioning_mode())
                 : 0;
    return true;
  }

  const absl::string_view kExperimentPrefix = "tcmalloc.experiment.";
  if (absl::StartsWith(name, kExperimentPrefix)) {
    std::optional<Experiment> exp =
        FindExperimentByName(absl::StripPrefix(name, kExperimentPrefix));
    if (exp.has_value()) {
      *value = IsExperimentActive(*exp) ? 1 : 0;
      return true;
    }
  }

  // LINT.ThenChange(//depot/google3/tcmalloc/testing/malloc_extension_test.cc)
  return false;
}

}  // namespace tcmalloc_internal
}  // namespace tcmalloc
GOOGLE_MALLOC_SECTION_END
