1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/power_monitor/cpu_frequency_utils.h"
6
7 #include "base/strings/stringprintf.h"
8 #include "base/system/sys_info.h"
9 #include "base/time/time.h"
10 #include "base/timer/elapsed_timer.h"
11 #include "base/trace_event/base_tracing.h"
12 #include "build/build_config.h"
13
14 #if BUILDFLAG(IS_WIN)
15 #include <windows.h>
16
17 #include <powerbase.h>
18 #include <processthreadsapi.h>
19 #include <winternl.h>
20 #endif
21
22 namespace base {
23 namespace {
24
25 #if BUILDFLAG(IS_WIN)
26 // From
27 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184(v=vs.85).aspx.
28 // Note that this structure definition was accidentally omitted from WinNT.h.
29 typedef struct _PROCESSOR_POWER_INFORMATION {
30 ULONG Number;
31 ULONG MaxMhz;
32 ULONG CurrentMhz;
33 ULONG MhzLimit;
34 ULONG MaxIdleState;
35 ULONG CurrentIdleState;
36 } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
37 #endif
38
39 } // namespace
40
EstimateCpuFrequency()41 double EstimateCpuFrequency() {
42 std::optional<CpuThroughputEstimationResult> result = EstimateCpuThroughput();
43 return result ? result->estimated_frequency : 0.0;
44 }
45
EstimateCpuThroughput()46 std::optional<CpuThroughputEstimationResult> EstimateCpuThroughput() {
47 #if defined(ARCH_CPU_X86_FAMILY)
48 TRACE_EVENT0("power", "EstimateCpuThroughput");
49
50 #if BUILDFLAG(IS_WIN)
51 DWORD start_processor_number = GetCurrentProcessorNumber();
52 #endif
53
54 // The heuristic to estimate CPU frequency is based on UIforETW code.
55 // see: https://github.com/google/UIforETW/blob/main/UIforETW/CPUFrequency.cpp
56 // https://github.com/google/UIforETW/blob/main/UIforETW/SpinALot64.asm
57 base::ElapsedTimer timer;
58 base::ElapsedThreadTimer thread_timer;
59 const int kAmountOfIterations = 50000;
60 const int kAmountOfInstructions = 10;
61 for (int i = 0; i < kAmountOfIterations; ++i) {
62 __asm__ __volatile__(
63 "addl %%eax, %%eax\n"
64 "addl %%eax, %%eax\n"
65 "addl %%eax, %%eax\n"
66 "addl %%eax, %%eax\n"
67 "addl %%eax, %%eax\n"
68 "addl %%eax, %%eax\n"
69 "addl %%eax, %%eax\n"
70 "addl %%eax, %%eax\n"
71 "addl %%eax, %%eax\n"
72 "addl %%eax, %%eax\n"
73 :
74 :
75 : "eax");
76 }
77
78 const base::TimeDelta elapsed_thread_time = thread_timer.Elapsed();
79 const base::TimeDelta elapsed = timer.Elapsed();
80 const double estimated_frequency =
81 (kAmountOfIterations * kAmountOfInstructions) / elapsed.InSecondsF();
82
83 CpuThroughputEstimationResult result{
84 .estimated_frequency = estimated_frequency,
85 .migrated = false,
86 .wall_time = elapsed,
87 .thread_time = elapsed_thread_time,
88 };
89
90 #if BUILDFLAG(IS_WIN)
91 result.migrated = start_processor_number != GetCurrentProcessorNumber();
92 #endif
93
94 return result;
95 #else
96 return std::nullopt;
97 #endif
98 }
99
GetCpuFrequencyInfo()100 BASE_EXPORT CpuFrequencyInfo GetCpuFrequencyInfo() {
101 CpuFrequencyInfo cpu_info{
102 .max_mhz = 0,
103 .mhz_limit = 0,
104 .type = CpuFrequencyInfo::CoreType::kPerformance,
105 .num_active_cpus = 0,
106 };
107
108 #if BUILDFLAG(IS_WIN)
109 unsigned long fastest = std::numeric_limits<unsigned long>::min();
110 unsigned long slowest = std::numeric_limits<unsigned long>::max();
111
112 DWORD current_processor_number = GetCurrentProcessorNumber();
113 size_t num_cpu = static_cast<size_t>(base::SysInfo::NumberOfProcessors());
114 std::vector<PROCESSOR_POWER_INFORMATION> info(num_cpu);
115 if (!NT_SUCCESS(CallNtPowerInformation(
116 ProcessorInformation, nullptr, 0, &info[0],
117 static_cast<ULONG>(sizeof(PROCESSOR_POWER_INFORMATION) * num_cpu)))) {
118 return cpu_info;
119 }
120
121 for (const auto& i : info) {
122 if (current_processor_number == i.Number) {
123 cpu_info.max_mhz = i.MaxMhz;
124 cpu_info.mhz_limit = i.MhzLimit;
125 }
126 fastest = std::max(fastest, i.MaxMhz);
127 slowest = std::min(slowest, i.MaxMhz);
128
129 // Count the amount of CPU that are in the C0 state (active).
130 // `CurrentIdleState` contains the CPU C-State + 1. When `MaxIdleState` is
131 // 1, the `CurrentIdleState` will always be 0 and the C-States are not
132 // supported and we consider the CPU is active.
133 if (i.MaxIdleState == 1 || i.CurrentIdleState == 1) {
134 cpu_info.num_active_cpus++;
135 }
136 }
137
138 // If the CPU frequency is the fastest of all the cores, or the CPU is
139 // homogeneous, report the core as being a performance core.
140 if (cpu_info.max_mhz == fastest) {
141 cpu_info.type = CpuFrequencyInfo::CoreType::kPerformance;
142 } else if (cpu_info.max_mhz == slowest) {
143 // If the system is heterogenous, and the current CPU is the slowest, report
144 // it as an efficiency core.
145 cpu_info.type = CpuFrequencyInfo::CoreType::kEfficiency;
146 } else {
147 // Otherwise, the CPU is neither the fastest or the slowest, so report it as
148 // "balanced".
149 cpu_info.type = CpuFrequencyInfo::CoreType::kBalanced;
150 }
151 #endif
152
153 return cpu_info;
154 }
155
156 #if BUILDFLAG(IS_WIN)
GenerateCpuInfoForTracingMetadata(base::Value::Dict * metadata)157 void GenerateCpuInfoForTracingMetadata(base::Value::Dict* metadata) {
158 size_t num_cpu = static_cast<size_t>(base::SysInfo::NumberOfProcessors());
159 std::vector<PROCESSOR_POWER_INFORMATION> info(num_cpu);
160 if (!NT_SUCCESS(CallNtPowerInformation(
161 ProcessorInformation, nullptr, 0, &info[0],
162 static_cast<ULONG>(sizeof(PROCESSOR_POWER_INFORMATION) * num_cpu)))) {
163 return;
164 }
165
166 // Output information for each cores. The cores frequencies may differ due to
167 // little/big cores.
168 for (const auto& i : info) {
169 const ULONG cpu_number = i.Number;
170
171 // The maximum CPU frequency for a given core.
172 metadata->Set(base::StringPrintf("cpu-max-frequency-core%lu", cpu_number),
173 static_cast<int>(i.MaxMhz));
174
175 // The maximum CPU frequency that the power settings will allow. This
176 // setting can be changed by the users or by changing the power plan.
177 if (i.MhzLimit != i.MaxMhz) {
178 metadata->Set(
179 base::StringPrintf("cpu-limit-frequency-core%lu", cpu_number),
180 static_cast<int>(i.MhzLimit));
181 }
182
183 // The MaxIdleState field contains the maximum supported C-state. The value
184 // is zero when the C-State is not supported.
185 if (i.MaxIdleState != 0) {
186 metadata->Set(
187 base::StringPrintf("cpu-max-idle-state-core%lu", cpu_number),
188 static_cast<int>(i.MaxIdleState));
189 }
190 }
191 }
192 #endif
193
194 } // namespace base
195