| // Copyright 2022 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/system_cpu/cpu_probe_win.h" |
| |
| #include <pdh.h> |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "base/cpu.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/win/scoped_pdh_query.h" |
| #include "components/system_cpu/cpu_sample.h" |
| |
| namespace system_cpu { |
| |
| namespace { |
| |
| constexpr wchar_t kHypervisorLogicalProcessorCounter[] = |
| L"\\Hyper-V Hypervisor Logical Processor(_Total)\\% Total Run Time"; |
| |
| constexpr wchar_t kProcessorCounter[] = |
| L"\\Processor(_Total)\\% Processor Time"; |
| |
| } // namespace |
| |
| // Helper class that performs the actual I/O. It must run on a |
| // SequencedTaskRunner that is properly configured for blocking I/O |
| // operations, and uses CONTINUE_ON_SHUTDOWN since Pdh functions can hang (see |
| // https://crbug.com/1499644). This means it must not use any globals that |
| // are deleted on browser shutdown. |
| class CpuProbeWin::BlockingTaskRunnerHelper final { |
| public: |
| BlockingTaskRunnerHelper(); |
| ~BlockingTaskRunnerHelper(); |
| |
| BlockingTaskRunnerHelper(const BlockingTaskRunnerHelper&) = delete; |
| BlockingTaskRunnerHelper& operator=(const BlockingTaskRunnerHelper&) = delete; |
| |
| std::optional<CpuSample> Update(); |
| |
| private: |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| // Used to derive CPU utilization. |
| base::win::ScopedPdhQuery cpu_query_ GUARDED_BY_CONTEXT(sequence_checker_); |
| |
| // This "handle" doesn't need to be freed but its lifetime is associated |
| // with cpu_query_. |
| PDH_HCOUNTER cpu_percent_utilization_ GUARDED_BY_CONTEXT(sequence_checker_); |
| |
| // True if PdhCollectQueryData has been called. |
| // |
| // It requires two data samples to calculate a formatted data value. So |
| // PdhCollectQueryData should be called twice before calling |
| // PdhGetFormattedCounterValue. |
| // Detailed information can be found in the following website: |
| // https://learn.microsoft.com/en-us/windows/win32/perfctrs/collecting-performance-data |
| bool got_baseline_ GUARDED_BY_CONTEXT(sequence_checker_) = false; |
| }; |
| |
| CpuProbeWin::BlockingTaskRunnerHelper::BlockingTaskRunnerHelper() = default; |
| |
| CpuProbeWin::BlockingTaskRunnerHelper::~BlockingTaskRunnerHelper() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| std::optional<CpuSample> CpuProbeWin::BlockingTaskRunnerHelper::Update() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| PDH_STATUS pdh_status; |
| |
| if (!cpu_query_.is_valid()) { |
| cpu_query_ = base::win::ScopedPdhQuery::Create(); |
| if (!cpu_query_.is_valid()) { |
| return std::nullopt; |
| } |
| |
| // When running in a VM, to provide a useful compute pressure signal, we |
| // must measure the usage of the physical CPU rather than the virtual CPU |
| // of the particular guest we are running in. The Microsoft documentation |
| // explains how to get this data when running under Hyper-V: |
| // https://learn.microsoft.com/en-us/windows-server/administration/performance-tuning/role/hyper-v-server/configuration#cpu-statistics |
| const bool is_running_in_vm = base::CPU().is_running_in_vm(); |
| const auto* query_info = is_running_in_vm |
| ? kHypervisorLogicalProcessorCounter |
| : kProcessorCounter; |
| pdh_status = PdhAddEnglishCounter(cpu_query_.get(), query_info, NULL, |
| &cpu_percent_utilization_); |
| |
| // When Chrome is running under a different hypervisor, we can add the |
| // Hyper-V performance counter successfully but it isn't available to |
| // obtain data. Fall back to the normal one in this case. |
| if (is_running_in_vm && pdh_status == ERROR_SUCCESS) { |
| pdh_status = PdhCollectQueryData(cpu_query_.get()); |
| if (pdh_status != ERROR_SUCCESS) { |
| pdh_status = PdhAddEnglishCounter(cpu_query_.get(), kProcessorCounter, |
| NULL, &cpu_percent_utilization_); |
| } |
| } |
| |
| if (pdh_status != ERROR_SUCCESS) { |
| cpu_query_.reset(); |
| LOG(ERROR) << "PdhAddEnglishCounter failed: " |
| << logging::SystemErrorCodeToString(pdh_status); |
| return std::nullopt; |
| } |
| } |
| |
| pdh_status = PdhCollectQueryData(cpu_query_.get()); |
| if (pdh_status != ERROR_SUCCESS) { |
| LOG(ERROR) << "PdhCollectQueryData failed: " |
| << logging::SystemErrorCodeToString(pdh_status); |
| return std::nullopt; |
| } |
| |
| if (!got_baseline_) { |
| got_baseline_ = true; |
| return std::nullopt; |
| } |
| |
| PDH_FMT_COUNTERVALUE counter_value; |
| pdh_status = PdhGetFormattedCounterValue( |
| cpu_percent_utilization_, PDH_FMT_DOUBLE, NULL, &counter_value); |
| if (pdh_status != ERROR_SUCCESS) { |
| LOG(ERROR) << "PdhGetFormattedCounterValue failed: " |
| << logging::SystemErrorCodeToString(pdh_status); |
| return std::nullopt; |
| } |
| |
| return CpuSample{counter_value.doubleValue / 100.0}; |
| } |
| |
| // static |
| std::unique_ptr<CpuProbeWin> CpuProbeWin::Create() { |
| return base::WrapUnique(new CpuProbeWin()); |
| } |
| |
| CpuProbeWin::CpuProbeWin() { |
| // BlockingTaskRunnerHelper makes heavy use of Pdh* functions than can load |
| // DLL's, which must happen in the foreground to avoid a priority inversion |
| // in the Windows DLL loader lock. Tasks on the helper sequence can be |
| // delayed (BEST_EFFORT priority) but once started must run in the foreground |
| // (MUST_USE_FOREGROUND policy) to avoid being descheduled while another |
| // foreground thread is waiting for the loader lock. |
| helper_ = base::SequenceBound<BlockingTaskRunnerHelper>( |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::ThreadPolicy::MUST_USE_FOREGROUND, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})); |
| } |
| |
| CpuProbeWin::~CpuProbeWin() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void CpuProbeWin::Update(SampleCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| helper_.AsyncCall(&BlockingTaskRunnerHelper::Update) |
| .Then(std::move(callback)); |
| } |
| |
| base::WeakPtr<CpuProbe> CpuProbeWin::GetWeakPtr() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace system_cpu |