• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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