910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/services/heap_profiling/public/cpp/profiling_client.h"

#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/debug/stack_trace.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include "base/trace_event/malloc_dump_provider.h"
#include "base/trace_event/memory_dump_manager.h"
#include "build/build_config.h"
#include "partition_alloc/buildflags.h"

#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
    PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
#include "partition_alloc/shim/allocator_interception_apple.h"
#endif  // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
        // && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)

using base::allocator::dispatcher::AllocationSubsystem;

namespace heap_profiling {

ProfilingClient::ProfilingClient() = default;
ProfilingClient::~ProfilingClient() = default;

void ProfilingClient::BindToInterface(
    mojo::PendingReceiver<mojom::ProfilingClient> receiver) {
  receivers_.Add(this, std::move(receiver));
}

#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
    PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
void ShimNewMallocZonesAndReschedule(base::Time end_time,
                                     base::TimeDelta delay) {
  allocator_shim::ShimNewMallocZones();

  if (base::Time::Now() > end_time) {
    return;
  }

  base::TimeDelta next_delay = delay * 2;
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&ShimNewMallocZonesAndReschedule, end_time, next_delay),
      delay);
}
#endif  // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
        // && BULDFLAG(USE_ALLOCATOR_SHIM)

void ProfilingClient::StartProfiling(mojom::ProfilingParamsPtr params,
                                     StartProfilingCallback callback) {
  if (started_profiling_)
    return;
  started_profiling_ = true;

#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
    PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
  // On macOS, this call is necessary to shim malloc zones that were created
  // after startup. This cannot be done during shim initialization because the
  // task scheduler has not yet been initialized.
  //
  // Wth PartitionAlloc, the shims are already in place, calling this leads to
  // an infinite loop.
  base::Time end_time = base::Time::Now() + base::Minutes(1);
  base::TimeDelta initial_delay = base::Seconds(1);
  ShimNewMallocZonesAndReschedule(end_time, initial_delay);
#endif  // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
        // && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)

  StartProfilingInternal(std::move(params), std::move(callback));
}

namespace {

bool g_initialized_ = false;

base::Lock& GetOnInitAllocatorShimLock() {
  static base::NoDestructor<base::Lock> instance;
  return *instance;
}

base::OnceClosure& GetOnInitAllocatorShimCallback() {
  static base::NoDestructor<base::OnceClosure> instance;
  return *instance;
}

scoped_refptr<base::TaskRunner>& GetOnInitAllocatorShimTaskRunner() {
  static base::NoDestructor<scoped_refptr<base::TaskRunner>> instance;
  return *instance;
}

// In NATIVE stack mode, whether to insert stack names into the backtraces.
bool g_include_thread_names = false;

void InitAllocationRecorder(mojom::ProfilingParamsPtr params) {
  using base::trace_event::AllocationContextTracker;
  using CaptureMode = base::trace_event::AllocationContextTracker::CaptureMode;

#if !BUILDFLAG(IS_WIN) || !defined(OFFICIAL_BUILD)
  // Must be done before hooking any functions that make stack traces. Windows
  // release builds crash if symbols are requested after sandbox lockdown, but
  // will still produce address-only stacks if this function not called.
  base::debug::EnableInProcessStackDumping();
#endif

  if (params->stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES) {
    g_include_thread_names = true;
    base::SamplingHeapProfiler::Get()->EnableRecordThreadNames();
  }

  switch (params->stack_mode) {
    case mojom::StackMode::NATIVE_WITH_THREAD_NAMES:
    case mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES:
      // This would track task contexts only.
      AllocationContextTracker::SetCaptureMode(CaptureMode::kNativeStack);
      break;
  }
}

// Notifies the test clients that allocation hooks have been initialized.
void AllocatorHooksHaveBeenInitialized() {
  base::AutoLock lock(GetOnInitAllocatorShimLock());
  g_initialized_ = true;
  if (!GetOnInitAllocatorShimCallback())
    return;
  GetOnInitAllocatorShimTaskRunner()->PostTask(
      FROM_HERE, std::move(GetOnInitAllocatorShimCallback()));
}

mojom::AllocatorType ConvertType(AllocationSubsystem type) {
  switch (type) {
    case AllocationSubsystem::kAllocatorShim:
      return mojom::AllocatorType::kMalloc;
    case AllocationSubsystem::kPartitionAllocator:
      return mojom::AllocatorType::kPartitionAlloc;
    case AllocationSubsystem::kManualForTesting:
      NOTREACHED();
  }
}

}  // namespace

void InitTLSSlot() {
  base::SamplingHeapProfiler::Init();
}

bool SetOnInitAllocatorShimCallbackForTesting(
    base::OnceClosure callback,
    scoped_refptr<base::TaskRunner> task_runner) {
  base::AutoLock lock(GetOnInitAllocatorShimLock());
  if (g_initialized_)
    return true;
  GetOnInitAllocatorShimCallback() = std::move(callback);
  GetOnInitAllocatorShimTaskRunner() = task_runner;
  return false;
}

void ProfilingClient::StartProfilingInternal(mojom::ProfilingParamsPtr params,
                                             StartProfilingCallback callback) {
  size_t sampling_rate = params->sampling_rate;
  InitAllocationRecorder(std::move(params));
  auto* profiler = base::SamplingHeapProfiler::Get();
  profiler->SetSamplingInterval(sampling_rate);
  profiler->Start();
  AllocatorHooksHaveBeenInitialized();
  std::move(callback).Run();
}

void ProfilingClient::RetrieveHeapProfile(
    RetrieveHeapProfileCallback callback) {
  auto* profiler = base::SamplingHeapProfiler::Get();
  std::vector<base::SamplingHeapProfiler::Sample> samples =
      profiler->GetSamples(/*profile_id=*/0);
  // It's important to retrieve strings after samples, as otherwise it could
  // miss a string referenced by a sample.
  std::vector<const char*> strings = profiler->GetStrings();
  mojom::HeapProfilePtr profile = mojom::HeapProfile::New();
  profile->samples.reserve(samples.size());
  std::unordered_set<const char*> thread_names;
  for (const auto& sample : samples) {
    auto mojo_sample = mojom::HeapProfileSample::New();
    mojo_sample->allocator = ConvertType(sample.allocator);
    mojo_sample->size = sample.size;
    mojo_sample->total = sample.total;
    mojo_sample->context_id = reinterpret_cast<uintptr_t>(sample.context);
    mojo_sample->stack.reserve(sample.stack.size() +
                               (g_include_thread_names ? 1 : 0));
    mojo_sample->stack.insert(
        mojo_sample->stack.end(),
        reinterpret_cast<const uintptr_t*>(sample.stack.data()),
        reinterpret_cast<const uintptr_t*>(
            UNSAFE_TODO(sample.stack.data() + sample.stack.size())));
    if (g_include_thread_names) {
      static const char* kUnknownThreadName = "<unknown>";
      const char* thread_name =
          sample.thread_name ? sample.thread_name : kUnknownThreadName;
      mojo_sample->stack.push_back(reinterpret_cast<uintptr_t>(thread_name));
      thread_names.insert(thread_name);
    }
    profile->samples.push_back(std::move(mojo_sample));
  }
  profile->strings.reserve(strings.size() + thread_names.size());
  for (const char* string : strings)
    profile->strings.emplace(reinterpret_cast<uintptr_t>(string), string);
  for (const char* string : thread_names)
    profile->strings.emplace(reinterpret_cast<uintptr_t>(string), string);

  std::move(callback).Run(std::move(profile));
}

}  // namespace heap_profiling