| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/renderer/devtools/v8_sampling_profiler.h" |
| |
| #if defined(OS_POSIX) |
| #include <signal.h> |
| #define USE_SIGNALS |
| #endif |
| |
| #if defined(OS_WIN) |
| #include <windows.h> |
| #endif |
| |
| #include "base/format_macros.h" |
| #include "base/location.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/cancellation_flag.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/trace_event_argument.h" |
| #include "content/renderer/devtools/lock_free_circular_queue.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "v8/include/v8.h" |
| |
| using base::trace_event::ConvertableToTraceFormat; |
| using base::trace_event::TraceLog; |
| using base::trace_event::TracedValue; |
| using v8::Isolate; |
| |
| namespace content { |
| |
| namespace { |
| |
| class PlatformDataCommon { |
| public: |
| base::PlatformThreadId thread_id() { return thread_id_; } |
| |
| protected: |
| PlatformDataCommon() : thread_id_(base::PlatformThread::CurrentId()) {} |
| |
| private: |
| base::PlatformThreadId thread_id_; |
| }; |
| |
| #if defined(USE_SIGNALS) |
| |
| class PlatformData : public PlatformDataCommon { |
| public: |
| PlatformData() : vm_tid_(pthread_self()) {} |
| pthread_t vm_tid() const { return vm_tid_; } |
| |
| private: |
| pthread_t vm_tid_; |
| }; |
| |
| #elif defined(OS_WIN) |
| |
| class PlatformData : public PlatformDataCommon { |
| public: |
| // Get a handle to the calling thread. This is the thread that we are |
| // going to profile. We need to make a copy of the handle because we are |
| // going to use it in the sampler thread. Using GetThreadHandle() will |
| // not work in this case. We're using OpenThread because DuplicateHandle |
| // doesn't work in Chrome's sandbox. |
| PlatformData() |
| : thread_handle_(::OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | |
| THREAD_QUERY_INFORMATION, |
| false, |
| ::GetCurrentThreadId())) {} |
| |
| ~PlatformData() { |
| if (thread_handle_ == NULL) |
| return; |
| ::CloseHandle(thread_handle_); |
| thread_handle_ = NULL; |
| } |
| |
| HANDLE thread_handle() { return thread_handle_; } |
| |
| private: |
| HANDLE thread_handle_; |
| }; |
| #endif |
| |
| std::string PtrToString(const void* value) { |
| return base::StringPrintf( |
| "0x%" PRIx64, static_cast<uint64>(reinterpret_cast<intptr_t>(value))); |
| } |
| |
| class SampleRecord { |
| public: |
| static const int kMaxFramesCountLog2 = 8; |
| static const unsigned kMaxFramesCount = (1u << kMaxFramesCountLog2) - 1; |
| |
| SampleRecord() {} |
| |
| base::TimeTicks timestamp() const { return timestamp_; } |
| void Collect(v8::Isolate* isolate, |
| base::TimeTicks timestamp, |
| const v8::RegisterState& state); |
| scoped_refptr<ConvertableToTraceFormat> ToTraceFormat() const; |
| |
| private: |
| base::TimeTicks timestamp_; |
| unsigned vm_state_ : 4; |
| unsigned frames_count_ : kMaxFramesCountLog2; |
| const void* frames_[kMaxFramesCount]; |
| |
| DISALLOW_COPY_AND_ASSIGN(SampleRecord); |
| }; |
| |
| void SampleRecord::Collect(v8::Isolate* isolate, |
| base::TimeTicks timestamp, |
| const v8::RegisterState& state) { |
| v8::SampleInfo sample_info; |
| isolate->GetStackSample(state, (void**)frames_, kMaxFramesCount, |
| &sample_info); |
| timestamp_ = timestamp; |
| frames_count_ = sample_info.frames_count; |
| vm_state_ = sample_info.vm_state; |
| } |
| |
| scoped_refptr<ConvertableToTraceFormat> SampleRecord::ToTraceFormat() const { |
| scoped_refptr<TracedValue> data(new TracedValue()); |
| const char* vm_state = nullptr; |
| switch (vm_state_) { |
| case v8::StateTag::JS: |
| vm_state = "js"; |
| break; |
| case v8::StateTag::GC: |
| vm_state = "gc"; |
| break; |
| case v8::StateTag::COMPILER: |
| vm_state = "compiler"; |
| break; |
| case v8::StateTag::OTHER: |
| vm_state = "other"; |
| break; |
| case v8::StateTag::EXTERNAL: |
| vm_state = "external"; |
| break; |
| case v8::StateTag::IDLE: |
| vm_state = "idle"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| data->SetString("vm_state", vm_state); |
| data->BeginArray("stack"); |
| for (unsigned i = 0; i < frames_count_; ++i) { |
| data->AppendString(PtrToString(frames_[i])); |
| } |
| data->EndArray(); |
| return data; |
| } |
| |
| } // namespace |
| |
| // The class implements a sampler responsible for sampling a single thread. |
| class Sampler { |
| public: |
| ~Sampler(); |
| |
| static scoped_ptr<Sampler> CreateForCurrentThread(); |
| static Sampler* GetInstance() { return tls_instance_.Pointer()->Get(); } |
| |
| // These methods are called from the sampling thread. |
| void Start(); |
| void Stop(); |
| void Sample(); |
| |
| void DoSample(const v8::RegisterState& state); |
| |
| void SetEventsToCollectForTest(int code_added_events, int sample_events) { |
| code_added_events_to_collect_for_test_ = code_added_events; |
| sample_events_to_collect_for_test_ = sample_events; |
| } |
| |
| bool EventsCollectedForTest() const { |
| return base::subtle::NoBarrier_Load(&code_added_events_count_) >= |
| code_added_events_to_collect_for_test_ && |
| base::subtle::NoBarrier_Load(&samples_count_) >= |
| sample_events_to_collect_for_test_; |
| } |
| |
| private: |
| Sampler(); |
| |
| static void InstallJitCodeEventHandler(Isolate* isolate, void* data); |
| static void HandleJitCodeEvent(const v8::JitCodeEvent* event); |
| static scoped_refptr<ConvertableToTraceFormat> JitCodeEventToTraceFormat( |
| const v8::JitCodeEvent* event); |
| |
| void InjectPendingEvents(); |
| |
| static const unsigned kNumberOfSamples = 10; |
| typedef LockFreeCircularQueue<SampleRecord, kNumberOfSamples> SamplingQueue; |
| |
| PlatformData platform_data_; |
| Isolate* isolate_; |
| scoped_ptr<SamplingQueue> samples_data_; |
| base::subtle::Atomic32 code_added_events_count_; |
| base::subtle::Atomic32 samples_count_; |
| int code_added_events_to_collect_for_test_; |
| int sample_events_to_collect_for_test_; |
| |
| static base::LazyInstance<base::ThreadLocalPointer<Sampler>>::Leaky |
| tls_instance_; |
| }; |
| |
| base::LazyInstance<base::ThreadLocalPointer<Sampler>>::Leaky |
| Sampler::tls_instance_ = LAZY_INSTANCE_INITIALIZER; |
| |
| Sampler::Sampler() |
| : isolate_(Isolate::GetCurrent()), |
| code_added_events_count_(0), |
| samples_count_(0), |
| code_added_events_to_collect_for_test_(0), |
| sample_events_to_collect_for_test_(0) { |
| DCHECK(isolate_); |
| DCHECK(!GetInstance()); |
| tls_instance_.Pointer()->Set(this); |
| } |
| |
| Sampler::~Sampler() { |
| DCHECK(GetInstance()); |
| tls_instance_.Pointer()->Set(nullptr); |
| } |
| |
| // static |
| scoped_ptr<Sampler> Sampler::CreateForCurrentThread() { |
| return scoped_ptr<Sampler>(new Sampler()); |
| } |
| |
| void Sampler::Start() { |
| samples_data_.reset(new SamplingQueue()); |
| v8::JitCodeEventHandler handler = &HandleJitCodeEvent; |
| isolate_->RequestInterrupt(&InstallJitCodeEventHandler, |
| reinterpret_cast<void*>(handler)); |
| } |
| |
| void Sampler::Stop() { |
| isolate_->RequestInterrupt(&InstallJitCodeEventHandler, nullptr); |
| samples_data_.reset(); |
| } |
| |
| #if ARCH_CPU_64_BITS |
| #define REG_64_32(reg64, reg32) reg64 |
| #else |
| #define REG_64_32(reg64, reg32) reg32 |
| #endif // ARCH_CPU_64_BITS |
| |
| void Sampler::Sample() { |
| #if defined(OS_WIN) |
| const DWORD kSuspendFailed = static_cast<DWORD>(-1); |
| if (::SuspendThread(platform_data_.thread_handle()) == kSuspendFailed) |
| return; |
| CONTEXT context; |
| memset(&context, 0, sizeof(context)); |
| context.ContextFlags = CONTEXT_FULL; |
| if (::GetThreadContext(platform_data_.thread_handle(), &context) != 0) { |
| v8::RegisterState state; |
| state.pc = reinterpret_cast<void*>(context.REG_64_32(Rip, Eip)); |
| state.sp = reinterpret_cast<void*>(context.REG_64_32(Rsp, Esp)); |
| state.fp = reinterpret_cast<void*>(context.REG_64_32(Rbp, Ebp)); |
| // TODO(alph): It is not needed to buffer the events on Windows. |
| // We can just collect and fire trace event right away. |
| DoSample(state); |
| } |
| ::ResumeThread(platform_data_.thread_handle()); |
| #elif defined(USE_SIGNALS) |
| int error = pthread_kill(platform_data_.vm_tid(), SIGPROF); |
| if (error) { |
| LOG(ERROR) << "pthread_kill failed with error " << error << " " |
| << strerror(error); |
| } |
| #endif |
| InjectPendingEvents(); |
| } |
| |
| void Sampler::DoSample(const v8::RegisterState& state) { |
| // Called in the sampled thread signal handler. |
| // Because of that it is not allowed to do any memory allocation here. |
| base::TimeTicks timestamp = base::TimeTicks::Now(); |
| SampleRecord* record = samples_data_->StartEnqueue(); |
| if (!record) |
| return; |
| record->Collect(isolate_, timestamp, state); |
| samples_data_->FinishEnqueue(); |
| } |
| |
| void Sampler::InjectPendingEvents() { |
| SampleRecord* record = samples_data_->Peek(); |
| while (record) { |
| TRACE_EVENT_SAMPLE_WITH_TID_AND_TIMESTAMP1( |
| TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), "V8Sample", |
| platform_data_.thread_id(), |
| (record->timestamp() - base::TimeTicks()).InMicroseconds(), "data", |
| record->ToTraceFormat()); |
| samples_data_->Remove(); |
| record = samples_data_->Peek(); |
| base::subtle::NoBarrier_AtomicIncrement(&samples_count_, 1); |
| } |
| } |
| |
| // static |
| void Sampler::InstallJitCodeEventHandler(Isolate* isolate, void* data) { |
| // Called on the sampled V8 thread. |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), |
| "Sampler::InstallJitCodeEventHandler"); |
| v8::JitCodeEventHandler handler = |
| reinterpret_cast<v8::JitCodeEventHandler>(data); |
| isolate->SetJitCodeEventHandler( |
| v8::JitCodeEventOptions::kJitCodeEventEnumExisting, handler); |
| } |
| |
| // static |
| void Sampler::HandleJitCodeEvent(const v8::JitCodeEvent* event) { |
| // Called on the sampled V8 thread. |
| Sampler* sampler = GetInstance(); |
| // The sampler may have already been destroyed. |
| // That's fine, we're not interested in these events anymore. |
| if (!sampler) |
| return; |
| switch (event->type) { |
| case v8::JitCodeEvent::CODE_ADDED: |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), |
| "JitCodeAdded", TRACE_EVENT_SCOPE_THREAD, "data", |
| JitCodeEventToTraceFormat(event)); |
| base::subtle::NoBarrier_AtomicIncrement( |
| &sampler->code_added_events_count_, 1); |
| break; |
| |
| case v8::JitCodeEvent::CODE_MOVED: |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), |
| "JitCodeMoved", TRACE_EVENT_SCOPE_THREAD, "data", |
| JitCodeEventToTraceFormat(event)); |
| break; |
| |
| case v8::JitCodeEvent::CODE_REMOVED: |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), |
| "JitCodeRemoved", TRACE_EVENT_SCOPE_THREAD, "data", |
| JitCodeEventToTraceFormat(event)); |
| break; |
| |
| case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO: |
| case v8::JitCodeEvent::CODE_START_LINE_INFO_RECORDING: |
| case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: |
| break; |
| } |
| } |
| |
| // static |
| scoped_refptr<ConvertableToTraceFormat> Sampler::JitCodeEventToTraceFormat( |
| const v8::JitCodeEvent* event) { |
| switch (event->type) { |
| case v8::JitCodeEvent::CODE_ADDED: { |
| scoped_refptr<TracedValue> data(new TracedValue()); |
| data->SetString("code_start", PtrToString(event->code_start)); |
| data->SetInteger("code_len", static_cast<unsigned>(event->code_len)); |
| data->SetString("name", std::string(event->name.str, event->name.len)); |
| if (!event->script.IsEmpty()) { |
| data->SetInteger("script_id", event->script->GetId()); |
| } |
| return data; |
| } |
| |
| case v8::JitCodeEvent::CODE_MOVED: { |
| scoped_refptr<TracedValue> data(new TracedValue()); |
| data->SetString("code_start", PtrToString(event->code_start)); |
| data->SetInteger("code_len", static_cast<unsigned>(event->code_len)); |
| data->SetString("new_code_start", PtrToString(event->new_code_start)); |
| return data; |
| } |
| |
| case v8::JitCodeEvent::CODE_REMOVED: { |
| scoped_refptr<TracedValue> data(new TracedValue()); |
| data->SetString("code_start", PtrToString(event->code_start)); |
| data->SetInteger("code_len", static_cast<unsigned>(event->code_len)); |
| return data; |
| } |
| |
| case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO: |
| case v8::JitCodeEvent::CODE_START_LINE_INFO_RECORDING: |
| case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: |
| return nullptr; |
| } |
| return nullptr; |
| } |
| |
| class V8SamplingThread : public base::PlatformThread::Delegate { |
| public: |
| V8SamplingThread(Sampler*, base::WaitableEvent*); |
| |
| // Implementation of PlatformThread::Delegate: |
| void ThreadMain() override; |
| |
| void Start(); |
| void Stop(); |
| |
| private: |
| void Sample(); |
| void InstallSamplers(); |
| void RemoveSamplers(); |
| void StartSamplers(); |
| void StopSamplers(); |
| |
| static void InstallSignalHandler(); |
| static void RestoreSignalHandler(); |
| #ifdef USE_SIGNALS |
| static void HandleProfilerSignal(int signal, siginfo_t* info, void* context); |
| #endif |
| |
| static void HandleJitCodeEvent(const v8::JitCodeEvent* event); |
| |
| Sampler* render_thread_sampler_; |
| base::CancellationFlag cancellation_flag_; |
| base::WaitableEvent* waitable_event_for_testing_; |
| base::PlatformThreadHandle sampling_thread_handle_; |
| std::vector<Sampler*> samplers_; |
| |
| #ifdef USE_SIGNALS |
| static bool signal_handler_installed_; |
| static struct sigaction old_signal_handler_; |
| #endif |
| |
| DISALLOW_COPY_AND_ASSIGN(V8SamplingThread); |
| }; |
| |
| #ifdef USE_SIGNALS |
| bool V8SamplingThread::signal_handler_installed_; |
| struct sigaction V8SamplingThread::old_signal_handler_; |
| #endif |
| |
| V8SamplingThread::V8SamplingThread(Sampler* render_thread_sampler, |
| base::WaitableEvent* event) |
| : render_thread_sampler_(render_thread_sampler), |
| waitable_event_for_testing_(event) { |
| } |
| |
| void V8SamplingThread::ThreadMain() { |
| base::PlatformThread::SetName("V8SamplingProfilerThread"); |
| InstallSamplers(); |
| StartSamplers(); |
| InstallSignalHandler(); |
| bool enabled_hires; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile.hires"), &enabled_hires); |
| const int kSamplingFrequencyMicroseconds = enabled_hires ? 100 : 1000; |
| while (!cancellation_flag_.IsSet()) { |
| Sample(); |
| if (waitable_event_for_testing_ && |
| render_thread_sampler_->EventsCollectedForTest()) { |
| waitable_event_for_testing_->Signal(); |
| } |
| // TODO(alph): make the samples firing interval not depend on the sample |
| // taking duration. |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMicroseconds(kSamplingFrequencyMicroseconds)); |
| } |
| RestoreSignalHandler(); |
| StopSamplers(); |
| RemoveSamplers(); |
| } |
| |
| void V8SamplingThread::Sample() { |
| for (Sampler* sampler : samplers_) { |
| sampler->Sample(); |
| } |
| } |
| |
| void V8SamplingThread::InstallSamplers() { |
| // Note that the list does not own samplers. |
| samplers_.push_back(render_thread_sampler_); |
| // TODO: add worker samplers. |
| } |
| |
| void V8SamplingThread::RemoveSamplers() { |
| samplers_.clear(); |
| } |
| |
| void V8SamplingThread::StartSamplers() { |
| for (Sampler* sampler : samplers_) { |
| sampler->Start(); |
| } |
| } |
| |
| void V8SamplingThread::StopSamplers() { |
| for (Sampler* sampler : samplers_) { |
| sampler->Stop(); |
| } |
| } |
| |
| // static |
| void V8SamplingThread::InstallSignalHandler() { |
| #ifdef USE_SIGNALS |
| // There must be the only one! |
| DCHECK(!signal_handler_installed_); |
| struct sigaction sa; |
| sa.sa_sigaction = &HandleProfilerSignal; |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = SA_RESTART | SA_SIGINFO; |
| signal_handler_installed_ = |
| (sigaction(SIGPROF, &sa, &old_signal_handler_) == 0); |
| #endif |
| } |
| |
| // static |
| void V8SamplingThread::RestoreSignalHandler() { |
| #ifdef USE_SIGNALS |
| if (!signal_handler_installed_) |
| return; |
| sigaction(SIGPROF, &old_signal_handler_, 0); |
| signal_handler_installed_ = false; |
| #endif |
| } |
| |
| #ifdef USE_SIGNALS |
| // static |
| void V8SamplingThread::HandleProfilerSignal(int signal, |
| siginfo_t* info, |
| void* context) { |
| if (signal != SIGPROF) |
| return; |
| ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context); |
| mcontext_t& mcontext = ucontext->uc_mcontext; |
| v8::RegisterState state; |
| |
| #if defined(ARCH_CPU_ARM_FAMILY) |
| state.pc = reinterpret_cast<void*>(mcontext.REG_64_32(pc, arm_pc)); |
| state.sp = reinterpret_cast<void*>(mcontext.REG_64_32(sp, arm_sp)); |
| state.fp = reinterpret_cast<void*>(mcontext.REG_64_32(regs[29], arm_fp)); |
| |
| #elif defined(ARCH_CPU_X86_FAMILY) |
| #if defined(OS_MACOSX) |
| state.pc = reinterpret_cast<void*>(mcontext->__ss.REG_64_32(__rip, __eip)); |
| state.sp = reinterpret_cast<void*>(mcontext->__ss.REG_64_32(__rsp, __esp)); |
| state.fp = reinterpret_cast<void*>(mcontext->__ss.REG_64_32(__rbp, __ebp)); |
| #else |
| state.pc = |
| reinterpret_cast<void*>(mcontext.gregs[REG_64_32(REG_RIP, REG_EIP)]); |
| state.sp = |
| reinterpret_cast<void*>(mcontext.gregs[REG_64_32(REG_RSP, REG_ESP)]); |
| state.fp = |
| reinterpret_cast<void*>(mcontext.gregs[REG_64_32(REG_RBP, REG_EBP)]); |
| #endif // OS_MACOS |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| state.pc = reinterpret_cast<void*>(mcontext.pc); |
| state.sp = reinterpret_cast<void*>(mcontext.gregs[29]); |
| state.fp = reinterpret_cast<void*>(mcontext.gregs[30]); |
| #endif // ARCH_CPU_MIPS_FAMILY |
| |
| Sampler::GetInstance()->DoSample(state); |
| } |
| #endif |
| |
| void V8SamplingThread::Start() { |
| if (!base::PlatformThread::Create(0, this, &sampling_thread_handle_)) { |
| DCHECK(false) << "failed to create sampling thread"; |
| } |
| } |
| |
| void V8SamplingThread::Stop() { |
| cancellation_flag_.Set(); |
| base::PlatformThread::Join(sampling_thread_handle_); |
| } |
| |
| V8SamplingProfiler::V8SamplingProfiler(bool underTest) |
| : sampling_thread_(nullptr), |
| render_thread_sampler_(Sampler::CreateForCurrentThread()), |
| task_runner_(base::ThreadTaskRunnerHandle::Get()) { |
| DCHECK(underTest || RenderThreadImpl::current()); |
| // Force the "v8.cpu_profile*" categories to show up in the trace viewer. |
| TraceLog::GetCategoryGroupEnabled( |
| TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile")); |
| TraceLog::GetCategoryGroupEnabled( |
| TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile.hires")); |
| TraceLog::GetInstance()->AddEnabledStateObserver(this); |
| } |
| |
| V8SamplingProfiler::~V8SamplingProfiler() { |
| TraceLog::GetInstance()->RemoveEnabledStateObserver(this); |
| DCHECK(!sampling_thread_.get()); |
| } |
| |
| void V8SamplingProfiler::StartSamplingThread() { |
| DCHECK(!sampling_thread_.get()); |
| sampling_thread_.reset(new V8SamplingThread( |
| render_thread_sampler_.get(), waitable_event_for_testing_.get())); |
| sampling_thread_->Start(); |
| } |
| |
| void V8SamplingProfiler::StopSamplingThread() { |
| if (!sampling_thread_.get()) |
| return; |
| sampling_thread_->Stop(); |
| sampling_thread_.reset(); |
| } |
| |
| void V8SamplingProfiler::OnTraceLogEnabled() { |
| bool enabled; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("v8.cpu_profile"), &enabled); |
| if (!enabled) |
| return; |
| |
| // Do not enable sampling profiler in continuous mode, as losing |
| // Jit code events may not be afforded. |
| // TODO(alph): add support of infinite recording of meta trace events. |
| base::trace_event::TraceRecordMode record_mode = |
| TraceLog::GetInstance()->GetCurrentTraceConfig().GetTraceRecordMode(); |
| if (record_mode == base::trace_event::TraceRecordMode::RECORD_CONTINUOUSLY) |
| return; |
| |
| task_runner_->PostTask(FROM_HERE, |
| base::Bind(&V8SamplingProfiler::StartSamplingThread, |
| base::Unretained(this))); |
| } |
| |
| void V8SamplingProfiler::OnTraceLogDisabled() { |
| task_runner_->PostTask(FROM_HERE, |
| base::Bind(&V8SamplingProfiler::StopSamplingThread, |
| base::Unretained(this))); |
| } |
| |
| void V8SamplingProfiler::EnableSamplingEventForTesting(int code_added_events, |
| int sample_events) { |
| render_thread_sampler_->SetEventsToCollectForTest(code_added_events, |
| sample_events); |
| waitable_event_for_testing_.reset(new base::WaitableEvent(false, false)); |
| } |
| |
| void V8SamplingProfiler::WaitSamplingEventForTesting() { |
| waitable_event_for_testing_->Wait(); |
| } |
| |
| } // namespace content |