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