#include "base/profiler/stack_sampling_profiler.h"
#include <algorithm>
#include <cmath>
#include <map>
#include <optional>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/profiler/profiler_buildflags.h"
#include "base/profiler/stack_buffer.h"
#include "base/profiler/stack_sampler.h"
#include "base/profiler/stack_unwind_data.h"
#include "base/profiler/unwinder.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/thread_annotations.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include "base/win/static_constants.h"
#endif
#if BUILDFLAG(IS_APPLE)
#include "base/mac/mac_util.h"
#endif
namespace base {
class ScopedAllowThreadRecallForStackSamplingProfiler
: public ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {};
namespace {
constexpr WaitableEvent::ResetPolicy kResetPolicy =
WaitableEvent::ResetPolicy::MANUAL;
const int kNullProfilerId = -1;
TimeTicks GetNextSampleTimeImpl(TimeTicks scheduled_current_sample_time,
TimeDelta sampling_interval,
TimeTicks now) {
const TimeTicks earliest_next_sample_time = now + sampling_interval / 2;
const TimeDelta minimum_time_delta_to_next_sample =
earliest_next_sample_time - scheduled_current_sample_time;
const int64_t required_sampling_intervals = static_cast<int64_t>(
std::ceil(minimum_time_delta_to_next_sample / sampling_interval));
return scheduled_current_sample_time +
required_sampling_intervals * sampling_interval;
}
}
class StackSamplingProfiler::SamplingThread : public Thread {
public:
class TestPeer {
public:
static void Reset();
static void DisableIdleShutdown();
static void ShutdownAssumingIdle(bool simulate_intervening_add);
private:
static void ShutdownTaskAndSignalEvent(SamplingThread* sampler,
int add_events,
WaitableEvent* event);
};
struct CollectionContext {
CollectionContext(PlatformThreadId thread_id,
const SamplingParams& params,
WaitableEvent* finished,
std::unique_ptr<StackSampler> sampler)
: collection_id(next_collection_id.GetNext()),
thread_id(thread_id),
params(params),
finished(finished),
sampler(std::move(sampler)) {}
~CollectionContext() = default;
const int collection_id;
const PlatformThreadId thread_id;
const SamplingParams params;
const raw_ptr<WaitableEvent>
finished;
std::unique_ptr<StackSampler> sampler;
TimeTicks next_sample_time;
TimeTicks profile_start_time;
int sample_count = 0;
bool stopping = false;
static AtomicSequenceNumber next_collection_id;
};
static SamplingThread* GetInstance();
SamplingThread(const SamplingThread&) = delete;
SamplingThread& operator=(const SamplingThread&) = delete;
int Add(std::unique_ptr<CollectionContext> collection);
void AddAuxUnwinder(int collection_id, std::unique_ptr<Unwinder> unwinder);
void ApplyMetadataToPastSamples(base::TimeTicks period_start,
base::TimeTicks period_end,
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id);
void AddProfileMetadata(uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id);
void Remove(int collection_id);
private:
friend struct DefaultSingletonTraits<SamplingThread>;
enum ThreadExecutionState {
NOT_STARTED,
RUNNING,
EXITING,
};
SamplingThread();
~SamplingThread() override;
scoped_refptr<SingleThreadTaskRunner> GetOrCreateTaskRunnerForAdd();
scoped_refptr<SingleThreadTaskRunner> GetTaskRunner(
ThreadExecutionState* out_state);
scoped_refptr<SingleThreadTaskRunner> GetTaskRunnerOnSamplingThread();
void FinishCollection(std::unique_ptr<CollectionContext> collection);
void ScheduleShutdownIfIdle();
void AddCollectionTask(std::unique_ptr<CollectionContext> collection);
void AddAuxUnwinderTask(int collection_id,
std::unique_ptr<Unwinder> unwinder);
void ApplyMetadataToPastSamplesTask(
base::TimeTicks period_start,
base::TimeTicks period_end,
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id);
void AddProfileMetadataTask(uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id);
void RemoveCollectionTask(int collection_id);
void ScheduleCollectionStop(int collection_id);
void RecordSampleTask(int collection_id);
void ShutdownTask(int add_events);
void CleanUp() override;
std::unique_ptr<StackBuffer> stack_buffer_;
std::map<int, std::unique_ptr<CollectionContext>> active_collections_;
Lock thread_execution_state_lock_;
ThreadExecutionState thread_execution_state_
GUARDED_BY(thread_execution_state_lock_) = NOT_STARTED;
scoped_refptr<SingleThreadTaskRunner> thread_execution_state_task_runner_
GUARDED_BY(thread_execution_state_lock_);
bool thread_execution_state_disable_idle_shutdown_for_testing_
GUARDED_BY(thread_execution_state_lock_) = false;
int thread_execution_state_add_events_
GUARDED_BY(thread_execution_state_lock_) = 0;
};
void StackSamplingProfiler::SamplingThread::TestPeer::Reset() {
SamplingThread* sampler = SamplingThread::GetInstance();
ThreadExecutionState state;
{
AutoLock lock(sampler->thread_execution_state_lock_);
state = sampler->thread_execution_state_;
DCHECK(sampler->active_collections_.empty());
}
if (state == RUNNING) {
ShutdownAssumingIdle(false);
state = EXITING;
}
if (state == EXITING) {
sampler->Stop();
}
{
AutoLock lock(sampler->thread_execution_state_lock_);
sampler->thread_execution_state_ = NOT_STARTED;
sampler->thread_execution_state_task_runner_ = nullptr;
sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = false;
sampler->thread_execution_state_add_events_ = 0;
}
}
void StackSamplingProfiler::SamplingThread::TestPeer::DisableIdleShutdown() {
SamplingThread* sampler = SamplingThread::GetInstance();
{
AutoLock lock(sampler->thread_execution_state_lock_);
sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = true;
}
}
void StackSamplingProfiler::SamplingThread::TestPeer::ShutdownAssumingIdle(
bool simulate_intervening_add) {
SamplingThread* sampler = SamplingThread::GetInstance();
ThreadExecutionState state;
scoped_refptr<SingleThreadTaskRunner> task_runner =
sampler->GetTaskRunner(&state);
DCHECK_EQ(RUNNING, state);
DCHECK(task_runner);
int add_events;
{
AutoLock lock(sampler->thread_execution_state_lock_);
add_events = sampler->thread_execution_state_add_events_;
if (simulate_intervening_add) {
++sampler->thread_execution_state_add_events_;
}
}
WaitableEvent executed(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner->PostTask(
FROM_HERE, BindOnce(&ShutdownTaskAndSignalEvent, Unretained(sampler),
add_events, Unretained(&executed)));
executed.Wait();
}
void StackSamplingProfiler::SamplingThread::TestPeer::
ShutdownTaskAndSignalEvent(SamplingThread* sampler,
int add_events,
WaitableEvent* event) {
sampler->ShutdownTask(add_events);
event->Signal();
}
AtomicSequenceNumber StackSamplingProfiler::SamplingThread::CollectionContext::
next_collection_id;
StackSamplingProfiler::SamplingThread::SamplingThread()
: Thread("StackSamplingProfiler") {}
StackSamplingProfiler::SamplingThread::~SamplingThread() = default;
StackSamplingProfiler::SamplingThread*
StackSamplingProfiler::SamplingThread::GetInstance() {
return Singleton<SamplingThread, LeakySingletonTraits<SamplingThread>>::get();
}
int StackSamplingProfiler::SamplingThread::Add(
std::unique_ptr<CollectionContext> collection) {
int collection_id = collection->collection_id;
scoped_refptr<SingleThreadTaskRunner> task_runner =
GetOrCreateTaskRunnerForAdd();
task_runner->PostTask(
FROM_HERE, BindOnce(&SamplingThread::AddCollectionTask, Unretained(this),
std::move(collection)));
return collection_id;
}
void StackSamplingProfiler::SamplingThread::AddAuxUnwinder(
int collection_id,
std::unique_ptr<Unwinder> unwinder) {
ThreadExecutionState state;
scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
if (state != RUNNING) {
return;
}
DCHECK(task_runner);
task_runner->PostTask(
FROM_HERE, BindOnce(&SamplingThread::AddAuxUnwinderTask, Unretained(this),
collection_id, std::move(unwinder)));
}
void StackSamplingProfiler::SamplingThread::ApplyMetadataToPastSamples(
base::TimeTicks period_start,
base::TimeTicks period_end,
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
ThreadExecutionState state;
scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
if (state != RUNNING) {
return;
}
DCHECK(task_runner);
task_runner->PostTask(
FROM_HERE, BindOnce(&SamplingThread::ApplyMetadataToPastSamplesTask,
Unretained(this), period_start, period_end, name_hash,
key, value, thread_id));
}
void StackSamplingProfiler::SamplingThread::AddProfileMetadata(
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
ThreadExecutionState state;
scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
if (state != RUNNING) {
return;
}
DCHECK(task_runner);
task_runner->PostTask(
FROM_HERE, BindOnce(&SamplingThread::AddProfileMetadataTask,
Unretained(this), name_hash, key, value, thread_id));
}
void StackSamplingProfiler::SamplingThread::Remove(int collection_id) {
ThreadExecutionState state;
scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
if (state != RUNNING) {
return;
}
DCHECK(task_runner);
task_runner->PostTask(FROM_HERE,
BindOnce(&SamplingThread::ScheduleCollectionStop,
Unretained(this), collection_id));
}
scoped_refptr<SingleThreadTaskRunner>
StackSamplingProfiler::SamplingThread::GetOrCreateTaskRunnerForAdd() {
AutoLock lock(thread_execution_state_lock_);
++thread_execution_state_add_events_;
if (thread_execution_state_ == RUNNING) {
DCHECK(thread_execution_state_task_runner_);
DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
return thread_execution_state_task_runner_;
}
if (thread_execution_state_ == EXITING) {
ScopedAllowThreadRecallForStackSamplingProfiler allow_thread_join;
Stop();
}
DCHECK(!stack_buffer_);
stack_buffer_ = StackSampler::CreateStackBuffer();
Start();
thread_execution_state_ = RUNNING;
thread_execution_state_task_runner_ = Thread::task_runner();
DetachFromSequence();
return thread_execution_state_task_runner_;
}
scoped_refptr<SingleThreadTaskRunner>
StackSamplingProfiler::SamplingThread::GetTaskRunner(
ThreadExecutionState* out_state) {
AutoLock lock(thread_execution_state_lock_);
if (out_state) {
*out_state = thread_execution_state_;
}
if (thread_execution_state_ == RUNNING) {
DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
DCHECK(thread_execution_state_task_runner_);
} else {
DCHECK(!thread_execution_state_task_runner_);
}
return thread_execution_state_task_runner_;
}
scoped_refptr<SingleThreadTaskRunner>
StackSamplingProfiler::SamplingThread::GetTaskRunnerOnSamplingThread() {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
return Thread::task_runner();
}
void StackSamplingProfiler::SamplingThread::FinishCollection(
std::unique_ptr<CollectionContext> collection) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
DCHECK_EQ(0u, active_collections_.count(collection->collection_id));
TimeDelta profile_duration = TimeTicks::Now() -
collection->profile_start_time +
collection->params.sampling_interval;
collection->sampler->GetStackUnwindData()
->profile_builder()
->OnProfileCompleted(profile_duration,
collection->params.sampling_interval);
WaitableEvent* collection_finished = collection->finished;
collection.reset();
collection_finished->Signal();
ScheduleShutdownIfIdle();
}
void StackSamplingProfiler::SamplingThread::ScheduleShutdownIfIdle() {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
if (!active_collections_.empty()) {
return;
}
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::SamplingThread::ScheduleShutdownIfIdle");
int add_events;
{
AutoLock lock(thread_execution_state_lock_);
if (thread_execution_state_disable_idle_shutdown_for_testing_) {
return;
}
add_events = thread_execution_state_add_events_;
}
GetTaskRunnerOnSamplingThread()->PostDelayedTask(
FROM_HERE,
BindOnce(&SamplingThread::ShutdownTask, Unretained(this), add_events),
Seconds(60));
}
void StackSamplingProfiler::SamplingThread::AddAuxUnwinderTask(
int collection_id,
std::unique_ptr<Unwinder> unwinder) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
auto loc = active_collections_.find(collection_id);
if (loc == active_collections_.end()) {
return;
}
loc->second->sampler->AddAuxUnwinder(std::move(unwinder));
}
void StackSamplingProfiler::SamplingThread::ApplyMetadataToPastSamplesTask(
base::TimeTicks period_start,
base::TimeTicks period_end,
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
MetadataRecorder::Item item(name_hash, key, thread_id, value);
for (auto& id_collection_pair : active_collections_) {
if (thread_id && id_collection_pair.second->thread_id != thread_id) {
continue;
}
id_collection_pair.second->sampler->GetStackUnwindData()
->profile_builder()
->ApplyMetadataRetrospectively(period_start, period_end, item);
}
}
void StackSamplingProfiler::SamplingThread::AddProfileMetadataTask(
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
MetadataRecorder::Item item(name_hash, key, thread_id, value);
for (auto& id_collection_pair : active_collections_) {
if (thread_id && id_collection_pair.second->thread_id != thread_id) {
continue;
}
id_collection_pair.second->sampler->GetStackUnwindData()
->profile_builder()
->AddProfileMetadata(item);
}
}
void StackSamplingProfiler::SamplingThread::AddCollectionTask(
std::unique_ptr<CollectionContext> collection) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
const int collection_id = collection->collection_id;
const TimeDelta initial_delay = collection->params.initial_delay;
collection->sampler->Initialize();
active_collections_.insert(
std::make_pair(collection_id, std::move(collection)));
GetTaskRunnerOnSamplingThread()->PostDelayedTask(
FROM_HERE,
BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
collection_id),
initial_delay);
{
AutoLock lock(thread_execution_state_lock_);
++thread_execution_state_add_events_;
}
}
void StackSamplingProfiler::SamplingThread::RemoveCollectionTask(
int collection_id) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
auto found = active_collections_.find(collection_id);
if (found == active_collections_.end()) {
return;
}
std::unique_ptr<CollectionContext> collection = std::move(found->second);
size_t count = active_collections_.erase(collection_id);
DCHECK_EQ(1U, count);
FinishCollection(std::move(collection));
}
void StackSamplingProfiler::SamplingThread::ScheduleCollectionStop(
int collection_id) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
auto found = active_collections_.find(collection_id);
if (found == active_collections_.end()) {
return;
}
CollectionContext* collection = found->second.get();
collection->stopping = true;
collection->sampler->Stop(BindOnce(&SamplingThread::RemoveCollectionTask,
Unretained(this), collection_id));
}
void StackSamplingProfiler::SamplingThread::RecordSampleTask(
int collection_id) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
auto found = active_collections_.find(collection_id);
if (found == active_collections_.end()) {
return;
}
CollectionContext* collection = found->second.get();
if (collection->stopping) {
return;
}
if (collection->sample_count == 0) {
collection->profile_start_time = TimeTicks::Now();
collection->next_sample_time = TimeTicks::Now();
}
bool more_collections_remaining =
++collection->sample_count < collection->params.samples_per_profile;
collection->sampler->RecordStackFrames(
stack_buffer_.get(), collection->thread_id,
more_collections_remaining
? DoNothing()
: BindOnce(&SamplingThread::RemoveCollectionTask, Unretained(this),
collection_id));
if (more_collections_remaining) {
collection->next_sample_time = GetNextSampleTimeImpl(
collection->next_sample_time, collection->params.sampling_interval,
TimeTicks::Now());
bool success = GetTaskRunnerOnSamplingThread()->PostDelayedTask(
FROM_HERE,
BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
collection_id),
std::max(collection->next_sample_time - TimeTicks::Now(), TimeDelta()));
DCHECK(success);
return;
}
}
void StackSamplingProfiler::SamplingThread::ShutdownTask(int add_events) {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
AutoLock lock(thread_execution_state_lock_);
if (thread_execution_state_add_events_ != add_events) {
return;
}
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::SamplingThread::ShutdownTask");
DCHECK(active_collections_.empty());
StopSoon();
DetachFromSequence();
thread_execution_state_ = EXITING;
thread_execution_state_task_runner_ = nullptr;
stack_buffer_.reset();
}
void StackSamplingProfiler::SamplingThread::CleanUp() {
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
DCHECK(active_collections_.empty());
Thread::CleanUp();
}
void StackSamplingProfiler::TestPeer::Reset() {
SamplingThread::TestPeer::Reset();
}
bool StackSamplingProfiler::TestPeer::IsSamplingThreadRunning() {
return SamplingThread::GetInstance()->IsRunning();
}
void StackSamplingProfiler::TestPeer::DisableIdleShutdown() {
SamplingThread::TestPeer::DisableIdleShutdown();
}
void StackSamplingProfiler::TestPeer::PerformSamplingThreadIdleShutdown(
bool simulate_intervening_start) {
SamplingThread::TestPeer::ShutdownAssumingIdle(simulate_intervening_start);
}
TimeTicks StackSamplingProfiler::TestPeer::GetNextSampleTime(
TimeTicks scheduled_current_sample_time,
TimeDelta sampling_interval,
TimeTicks now) {
return GetNextSampleTimeImpl(scheduled_current_sample_time, sampling_interval,
now);
}
bool StackSamplingProfiler::IsSupportedForCurrentPlatform() {
#if (BUILDFLAG(IS_WIN) && defined(ARCH_CPU_X86_64)) || BUILDFLAG(IS_MAC) || \
(BUILDFLAG(IS_IOS) && defined(ARCH_CPU_64_BITS)) || \
(BUILDFLAG(IS_ANDROID) && \
((defined(ARCH_CPU_ARMEL) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)) || \
(defined(ARCH_CPU_ARM64)))) || \
(BUILDFLAG(IS_CHROMEOS) && \
(defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)))
#if BUILDFLAG(IS_WIN)
if (GetModuleHandleA(base::win::kApplicationVerifierDllName)) {
return false;
}
if (GetModuleHandleA("tmmon64.dll") || GetModuleHandleA("tmmonmgr64.dll")) {
return false;
}
#endif
return true;
#else
return false;
#endif
}
StackSamplingProfiler::StackSamplingProfiler(
SamplingProfilerThreadToken thread_token,
const SamplingParams& params,
std::unique_ptr<ProfileBuilder> profile_builder,
UnwindersFactory core_unwinders_factory,
RepeatingClosure record_sample_callback,
StackSamplerTestDelegate* test_delegate)
: thread_token_(thread_token),
params_(params),
sampler_(StackSampler::Create(
thread_token,
std::make_unique<StackUnwindData>(std::move(profile_builder)),
std::move(core_unwinders_factory),
std::move(record_sample_callback),
test_delegate)),
profiling_inactive_(kResetPolicy, WaitableEvent::InitialState::SIGNALED),
profiler_id_(kNullProfilerId) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::StackSamplingProfiler");
}
StackSamplingProfiler::~StackSamplingProfiler() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::~StackSamplingProfiler");
Stop();
ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
profiling_inactive_.Wait();
}
void StackSamplingProfiler::Start() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::Start");
if (!sampler_) {
return;
}
static_assert(kResetPolicy == WaitableEvent::ResetPolicy::MANUAL,
"The reset policy must be set to MANUAL");
if (!profiling_inactive_.IsSignaled()) {
profiling_inactive_.Wait();
}
profiling_inactive_.Reset();
DCHECK_EQ(kNullProfilerId, profiler_id_);
profiler_id_ = SamplingThread::GetInstance()->Add(
std::make_unique<SamplingThread::CollectionContext>(
thread_token_.id, params_, &profiling_inactive_,
std::move(sampler_)));
DCHECK_NE(kNullProfilerId, profiler_id_);
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::Started", "profiler_id", profiler_id_);
}
void StackSamplingProfiler::Stop() {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
"StackSamplingProfiler::Stop", "profiler_id", profiler_id_);
SamplingThread::GetInstance()->Remove(profiler_id_);
profiler_id_ = kNullProfilerId;
}
void StackSamplingProfiler::AddAuxUnwinder(std::unique_ptr<Unwinder> unwinder) {
if (profiler_id_ == kNullProfilerId) {
if (sampler_) {
sampler_->AddAuxUnwinder(std::move(unwinder));
}
return;
}
SamplingThread::GetInstance()->AddAuxUnwinder(profiler_id_,
std::move(unwinder));
}
void StackSamplingProfiler::ApplyMetadataToPastSamples(
base::TimeTicks period_start,
base::TimeTicks period_end,
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
SamplingThread::GetInstance()->ApplyMetadataToPastSamples(
period_start, period_end, name_hash, key, value, thread_id);
}
void StackSamplingProfiler::AddProfileMetadata(
uint64_t name_hash,
std::optional<int64_t> key,
int64_t value,
std::optional<PlatformThreadId> thread_id) {
SamplingThread::GetInstance()->AddProfileMetadata(name_hash, key, value,
thread_id);
}
}