xref: /aosp_15_r20/external/cronet/base/power_monitor/speed_limit_observer_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 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/speed_limit_observer_win.h"
6 
7 #include <windows.h>
8 
9 #include <powerbase.h>
10 #include <winternl.h>
11 
12 #include <algorithm>
13 #include <memory>
14 #include <utility>
15 #include <vector>
16 
17 #include "base/logging.h"
18 #include "base/power_monitor/cpu_frequency_utils.h"
19 #include "base/system/sys_info.h"
20 #include "base/timer/elapsed_timer.h"
21 #include "base/trace_event/base_tracing.h"
22 #include "build/build_config.h"
23 
24 namespace {
25 
26 // From ntdef.f
27 #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
28 
29 // We poll for new speed-limit values once every second.
30 constexpr base::TimeDelta kSampleInterval = base::Seconds(1);
31 
32 // Size of moving-average filter which is used to smooth out variations in
33 // speed-limit estimates.
34 size_t kMovingAverageWindowSize = 10;
35 
36 #if BUILDFLAG(ENABLE_BASE_TRACING)
37 constexpr const char kPowerTraceCategory[] = TRACE_DISABLED_BY_DEFAULT("power");
38 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
39 
40 // From
41 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184(v=vs.85).aspx.
42 // Note that this structure definition was accidentally omitted from WinNT.h.
43 typedef struct _PROCESSOR_POWER_INFORMATION {
44   ULONG Number;
45   ULONG MaxMhz;
46   ULONG CurrentMhz;
47   ULONG MhzLimit;
48   ULONG MaxIdleState;
49   ULONG CurrentIdleState;
50 } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
51 
52 // From
53 // https://docs.microsoft.com/en-us/windows/win32/power/system-power-information-str.
54 // Note that this structure definition was accidentally omitted from WinNT.h.
55 typedef struct _SYSTEM_POWER_INFORMATION {
56   ULONG MaxIdlenessAllowed;
57   ULONG Idleness;
58   ULONG TimeRemaining;
59   UCHAR CoolingMode;
60 } SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION;
61 
62 // Returns information about the idleness of the system.
GetCPUIdleness(int * idleness_percent)63 bool GetCPUIdleness(int* idleness_percent) {
64   auto info = std::make_unique<SYSTEM_POWER_INFORMATION>();
65   if (!NT_SUCCESS(CallNtPowerInformation(SystemPowerInformation, nullptr, 0,
66                                          info.get(),
67                                          sizeof(SYSTEM_POWER_INFORMATION)))) {
68     *idleness_percent = 0;
69     return false;
70   }
71   // The current idle level, expressed as a percentage.
72   *idleness_percent = static_cast<int>(info->Idleness);
73   return true;
74 }
75 
76 }  // namespace
77 
78 namespace base {
79 
SpeedLimitObserverWin(SpeedLimitUpdateCallback speed_limit_update_callback)80 SpeedLimitObserverWin::SpeedLimitObserverWin(
81     SpeedLimitUpdateCallback speed_limit_update_callback)
82     : callback_(std::move(speed_limit_update_callback)),
83       num_cpus_(static_cast<size_t>(SysInfo::NumberOfProcessors())),
84       moving_average_(kMovingAverageWindowSize) {
85   DVLOG(1) << __func__ << "(num_CPUs=" << num_cpus() << ")";
86   timer_.Start(FROM_HERE, kSampleInterval, this,
87                &SpeedLimitObserverWin::OnTimerTick);
88 }
89 
~SpeedLimitObserverWin()90 SpeedLimitObserverWin::~SpeedLimitObserverWin() {
91   timer_.Stop();
92 }
93 
GetCurrentSpeedLimit()94 int SpeedLimitObserverWin::GetCurrentSpeedLimit() {
95   const int kSpeedLimitMax = PowerThermalObserver::kSpeedLimitMax;
96 
97   int idleness_percent = 0;
98   if (!GetCPUIdleness(&idleness_percent)) {
99     DLOG(WARNING) << "GetCPUIdleness failed";
100     return kSpeedLimitMax;
101   }
102 
103   // Get the latest estimated throttling level (value between 0.0 and 1.0).
104   float throttling_level = EstimateThrottlingLevel();
105 
106 #if BUILDFLAG(ENABLE_BASE_TRACING)
107   // Emit trace events to investigate issues with power throttling. Run this
108   // block only if tracing is running to avoid executing expensive calls to
109   // EstimateCpuFrequency(...).
110   bool trace_events_enabled;
111   TRACE_EVENT_CATEGORY_GROUP_ENABLED(kPowerTraceCategory,
112                                      &trace_events_enabled);
113   if (trace_events_enabled) {
114     TRACE_COUNTER(kPowerTraceCategory, "idleness", idleness_percent);
115     TRACE_COUNTER(kPowerTraceCategory, "throttling_level",
116                   static_cast<unsigned int>(throttling_level * 100));
117 
118 #if defined(ARCH_CPU_X86_FAMILY)
119     double cpu_frequency = EstimateCpuFrequency();
120     TRACE_COUNTER(kPowerTraceCategory, "frequency_mhz",
121                   static_cast<unsigned int>(cpu_frequency / 1'000'000));
122 #endif
123   }
124 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
125 
126   // Ignore the value if the global idleness is above 90% or throttling value
127   // is very small. This approach avoids false alarms and removes noise from the
128   // measurements.
129   if (idleness_percent > 90 || throttling_level < 0.1f) {
130     moving_average_.Reset();
131     return kSpeedLimitMax;
132   }
133 
134   // The speed limit metric is a value between 0 and 100 [%] where 100 means
135   // "full speed". The corresponding UMA metric is CPU_Speed_Limit.
136   float speed_limit_factor = 1.0f - throttling_level;
137   int speed_limit =
138       static_cast<int>(std::ceil(kSpeedLimitMax * speed_limit_factor));
139 
140   // The previous speed-limit value was below 100 but the new value is now back
141   // at max again. To make this state more "stable or sticky" we reset the MA
142   // filter and return kSpeedLimitMax. As a result, single drops in speedlimit
143   // values will not result in a value less than 100 since the MA filter must
144   // be full before we start to produce any output.
145   if (speed_limit_ < kSpeedLimitMax && speed_limit == kSpeedLimitMax) {
146     moving_average_.Reset();
147     return kSpeedLimitMax;
148   }
149 
150   // Add the latest speed-limit value [0,100] to the MA filter and return its
151   // output after ensuring that the filter is full. We do this to avoid initial
152   // false alarms at startup and after calling Reset() on the filter.
153   moving_average_.AddSample(speed_limit);
154   if (moving_average_.Count() < kMovingAverageWindowSize) {
155     return kSpeedLimitMax;
156   }
157   return moving_average_.Mean();
158 }
159 
OnTimerTick()160 void SpeedLimitObserverWin::OnTimerTick() {
161   // Get the latest (filtered) speed-limit estimate and trigger a new callback
162   // if the new value is different from the last.
163   const int speed_limit = GetCurrentSpeedLimit();
164   if (speed_limit != speed_limit_) {
165     speed_limit_ = speed_limit;
166     callback_.Run(speed_limit_);
167   }
168 
169 #if BUILDFLAG(ENABLE_BASE_TRACING)
170   TRACE_COUNTER(kPowerTraceCategory, "speed_limit",
171                 static_cast<unsigned int>(speed_limit));
172 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
173 }
174 
EstimateThrottlingLevel()175 float SpeedLimitObserverWin::EstimateThrottlingLevel() {
176   float throttling_level = 0.f;
177 
178   // Populate the PROCESSOR_POWER_INFORMATION structures for all logical CPUs
179   // using the CallNtPowerInformation API.
180   std::vector<PROCESSOR_POWER_INFORMATION> info(num_cpus());
181   if (!NT_SUCCESS(CallNtPowerInformation(
182           ProcessorInformation, nullptr, 0, &info[0],
183           static_cast<ULONG>(sizeof(PROCESSOR_POWER_INFORMATION) *
184                              num_cpus())))) {
185     return throttling_level;
186   }
187 
188   // Estimate the level of throttling by measuring how many CPUs that are not
189   // in idle state and how "far away" they are from the most idle state. Local
190   // tests have shown that `MaxIdleState` is typically 2 or 3 and
191   //
192   // `CurrentIdleState` switches to 2 or 1 when some sort of throttling starts
193   // to take place. The Intel Extreme Tuning Utility application has been used
194   // to monitor when any type of throttling (thermal, power-limit, PMAX etc)
195   // starts.
196   //
197   // `CurrentIdleState` contains the CPU C-State + 1. When `MaxIdleState` is
198   // 1, the `CurrentIdleState` will always be 0 and the C-States are not
199   // supported.
200   int num_non_idle_cpus = 0;
201   [[maybe_unused]] int num_active_cpus = 0;
202   float load_fraction_total = 0.0;
203   for (size_t i = 0; i < num_cpus(); ++i) {
204     // Amount of "non-idleness" is the distance from the max idle state.
205     const auto idle_diff = info[i].MaxIdleState - info[i].CurrentIdleState;
206     // Derive a value between 0.0 and 1.0 where 1.0 corresponds to max load on
207     // CPU#i.
208     // Example: MaxIdleState=2, CurrentIdleState=1 => (2 - 1) / 2 = 0.5.
209     // Example: MaxIdleState=2, CurrentIdleState=2 => (2 - 2) / 2 = 1.0.
210     // Example: MaxIdleState=3, CurrentIdleState=1 => (3 - 1) / 3 = 0.6666.
211     // Example: MaxIdleState=3, CurrentIdleState=2 => (3 - 2) / 3 = 0.3333.
212     const float load_fraction =
213         static_cast<float>(idle_diff) / info[i].MaxIdleState;
214     // Accumulate the total load for all CPUs.
215     load_fraction_total += load_fraction;
216     // Used for a sanity check only.
217     num_non_idle_cpus += (info[i].CurrentIdleState < info[i].MaxIdleState);
218 
219     // Count the amount of CPU that are in the C0 state (active). If
220     // `MaxIdleState` is 1, C-states are not supported and we consider the CPU
221     // is active.
222     if (info[i].MaxIdleState == 1 || info[i].CurrentIdleState == 1) {
223       num_active_cpus++;
224     }
225   }
226 
227   DCHECK_LE(load_fraction_total, static_cast<float>(num_non_idle_cpus))
228       << " load_fraction_total: " << load_fraction_total
229       << " num_non_idle_cpus:" << num_non_idle_cpus;
230   throttling_level = (load_fraction_total / num_cpus());
231 
232 #if BUILDFLAG(ENABLE_BASE_TRACING)
233   TRACE_COUNTER(kPowerTraceCategory, "num_active_cpus", num_active_cpus);
234 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
235 
236   return throttling_level;
237 }
238 
239 }  // namespace base
240