| // Copyright 2015 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/devtools_cpu_throttler.h" |
| |
| #include "base/atomicops.h" |
| #include "base/macros.h" |
| #include "base/memory/singleton.h" |
| #include "base/synchronization/cancellation_flag.h" |
| #include "base/threading/platform_thread.h" |
| #include "build/build_config.h" |
| |
| #if defined(OS_POSIX) |
| #include <signal.h> |
| #define USE_SIGNALS 1 |
| #endif |
| |
| using base::subtle::Atomic32; |
| using base::subtle::Acquire_Load; |
| using base::subtle::Release_Store; |
| |
| namespace content { |
| |
| class CPUThrottlingThread final : public base::PlatformThread::Delegate { |
| public: |
| explicit CPUThrottlingThread(double rate); |
| ~CPUThrottlingThread() override; |
| |
| void SetThrottlingRate(double rate); |
| |
| private: |
| void ThreadMain() override; |
| |
| void Start(); |
| void Stop(); |
| void Throttle(); |
| |
| static void SuspendThread(base::PlatformThreadHandle thread_handle); |
| static void ResumeThread(base::PlatformThreadHandle thread_handle); |
| static void Sleep(base::TimeDelta duration); |
| |
| #ifdef USE_SIGNALS |
| void InstallSignalHandler(); |
| void RestoreSignalHandler(); |
| static void HandleSignal(int signal); |
| |
| static bool signal_handler_installed_; |
| static struct sigaction old_signal_handler_; |
| #endif |
| static Atomic32 thread_exists_; |
| static Atomic32 throttling_rate_percent_; |
| |
| base::PlatformThreadHandle throttled_thread_handle_; |
| base::PlatformThreadHandle throttling_thread_handle_; |
| base::CancellationFlag cancellation_flag_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CPUThrottlingThread); |
| }; |
| |
| #ifdef USE_SIGNALS |
| bool CPUThrottlingThread::signal_handler_installed_; |
| struct sigaction CPUThrottlingThread::old_signal_handler_; |
| #endif |
| Atomic32 CPUThrottlingThread::throttling_rate_percent_; |
| Atomic32 CPUThrottlingThread::thread_exists_; |
| |
| CPUThrottlingThread::CPUThrottlingThread(double rate) |
| #ifdef OS_WIN |
| : throttled_thread_handle_( |
| ::OpenThread(THREAD_SUSPEND_RESUME, false, ::GetCurrentThreadId())) { |
| #else |
| : throttled_thread_handle_(base::PlatformThread::CurrentHandle()) { |
| #endif |
| SetThrottlingRate(rate); |
| CHECK(base::subtle::NoBarrier_AtomicExchange(&thread_exists_, 1) == 0); |
| Start(); |
| } |
| |
| CPUThrottlingThread::~CPUThrottlingThread() { |
| Stop(); |
| CHECK(base::subtle::NoBarrier_AtomicExchange(&thread_exists_, 0) == 1); |
| } |
| |
| void CPUThrottlingThread::SetThrottlingRate(double rate) { |
| Release_Store(&throttling_rate_percent_, static_cast<Atomic32>(rate * 100)); |
| } |
| |
| void CPUThrottlingThread::ThreadMain() { |
| base::PlatformThread::SetName("DevToolsCPUThrottlingThread"); |
| while (!cancellation_flag_.IsSet()) { |
| Throttle(); |
| } |
| } |
| |
| #ifdef USE_SIGNALS |
| |
| // static |
| void CPUThrottlingThread::InstallSignalHandler() { |
| // There must be the only one! |
| DCHECK(!signal_handler_installed_); |
| struct sigaction sa; |
| sa.sa_handler = &HandleSignal; |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = SA_RESTART; |
| signal_handler_installed_ = |
| (sigaction(SIGUSR2, &sa, &old_signal_handler_) == 0); |
| } |
| |
| // static |
| void CPUThrottlingThread::RestoreSignalHandler() { |
| if (!signal_handler_installed_) |
| return; |
| sigaction(SIGUSR2, &old_signal_handler_, nullptr); |
| signal_handler_installed_ = false; |
| } |
| |
| // static |
| void CPUThrottlingThread::HandleSignal(int signal) { |
| if (signal != SIGUSR2) |
| return; |
| static base::TimeTicks lastResumeTime; |
| base::TimeTicks now = base::TimeTicks::Now(); |
| base::TimeDelta run_duration = now - lastResumeTime; |
| uint32_t throttling_rate_percent = Acquire_Load(&throttling_rate_percent_); |
| // Limit the observed run duration to 1000μs to deal with the first entrance |
| // to the signal handler. |
| uint32_t run_duration_us = static_cast<uint32_t>( |
| std::min(run_duration.InMicroseconds(), static_cast<int64_t>(1000))); |
| uint32_t sleep_duration_us = |
| run_duration_us * throttling_rate_percent / 100 - run_duration_us; |
| base::TimeTicks wake_up_time = |
| now + base::TimeDelta::FromMicroseconds(sleep_duration_us); |
| do { |
| now = base::TimeTicks::Now(); |
| } while (now < wake_up_time); |
| lastResumeTime = now; |
| } |
| |
| #endif // USE_SIGNALS |
| |
| void CPUThrottlingThread::Throttle() { |
| const int quant_time_us = 200; |
| #if defined(OS_WIN) |
| double rate = Acquire_Load(&throttling_rate_percent_) / 100.; |
| base::TimeDelta run_duration = |
| base::TimeDelta::FromMicroseconds(static_cast<int>(quant_time_us / rate)); |
| base::TimeDelta sleep_duration = |
| base::TimeDelta::FromMicroseconds(quant_time_us) - run_duration; |
| Sleep(run_duration); |
| ::SuspendThread(throttled_thread_handle_.platform_handle()); |
| Sleep(sleep_duration); |
| ::ResumeThread(throttled_thread_handle_.platform_handle()); |
| #else |
| pthread_kill(throttled_thread_handle_.platform_handle(), SIGUSR2); |
| Sleep(base::TimeDelta::FromMicroseconds(quant_time_us)); |
| #endif |
| } |
| |
| void CPUThrottlingThread::Start() { |
| #ifdef USE_SIGNALS |
| InstallSignalHandler(); |
| #elif !defined(OS_WIN) |
| LOG(ERROR) << "CPU throttling is not supported." |
| #endif |
| if (!base::PlatformThread::Create(0, this, &throttling_thread_handle_)) { |
| LOG(ERROR) << "Failed to create throttling thread."; |
| } |
| } |
| |
| void CPUThrottlingThread::Sleep(base::TimeDelta duration) { |
| #if defined(OS_WIN) |
| // We cannot rely on ::Sleep function as it's precision is not enough for |
| // the purpose. Could be up to 16ms jitter. |
| base::TimeTicks wakeup_time = base::TimeTicks::Now() + duration; |
| while (base::TimeTicks::Now() < wakeup_time) {} |
| #else |
| base::PlatformThread::Sleep(duration); |
| #endif |
| } |
| |
| void CPUThrottlingThread::Stop() { |
| cancellation_flag_.Set(); |
| base::PlatformThread::Join(throttling_thread_handle_); |
| #ifdef USE_SIGNALS |
| RestoreSignalHandler(); |
| #endif |
| } |
| |
| DevToolsCPUThrottler::DevToolsCPUThrottler() = default; |
| DevToolsCPUThrottler::~DevToolsCPUThrottler() = default; |
| |
| void DevToolsCPUThrottler::SetThrottlingRate(double rate) { |
| if (rate <= 1) { |
| if (throttling_thread_) { |
| throttling_thread_.reset(); |
| } |
| return; |
| } |
| if (throttling_thread_) { |
| throttling_thread_->SetThrottlingRate(rate); |
| } else { |
| throttling_thread_.reset(new CPUThrottlingThread(rate)); |
| } |
| } |
| |
| // static |
| DevToolsCPUThrottler* DevToolsCPUThrottler::GetInstance() { |
| return base::Singleton<DevToolsCPUThrottler>::get(); |
| } |
| |
| } // namespace content |