• 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 
20 namespace {
21 
22 // From ntdef.f
23 #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
24 
25 // We poll for new speed-limit values once every second.
26 constexpr base::TimeDelta kSampleInterval = base::Seconds(1);
27 
28 // Size of moving-average filter which is used to smooth out variations in
29 // speed-limit estimates.
30 constexpr size_t kMovingAverageWindowSize = 10;
31 
32 // From
33 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184(v=vs.85).aspx.
34 // Note that this structure definition was accidentally omitted from WinNT.h.
35 typedef struct _PROCESSOR_POWER_INFORMATION {
36   ULONG Number;
37   ULONG MaxMhz;
38   ULONG CurrentMhz;
39   ULONG MhzLimit;
40   ULONG MaxIdleState;
41   ULONG CurrentIdleState;
42 } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
43 
44 // From
45 // https://docs.microsoft.com/en-us/windows/win32/power/system-power-information-str.
46 // Note that this structure definition was accidentally omitted from WinNT.h.
47 typedef struct _SYSTEM_POWER_INFORMATION {
48   ULONG MaxIdlenessAllowed;
49   ULONG Idleness;
50   ULONG TimeRemaining;
51   UCHAR CoolingMode;
52 } SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION;
53 
54 // Returns information about the idleness of the system.
GetCPUIdleness(int * idleness_percent)55 bool GetCPUIdleness(int* idleness_percent) {
56   auto info = std::make_unique<SYSTEM_POWER_INFORMATION>();
57   if (!NT_SUCCESS(CallNtPowerInformation(SystemPowerInformation, nullptr, 0,
58                                          info.get(),
59                                          sizeof(SYSTEM_POWER_INFORMATION)))) {
60     *idleness_percent = 0;
61     return false;
62   }
63   // The current idle level, expressed as a percentage.
64   *idleness_percent = static_cast<int>(info->Idleness);
65   return true;
66 }
67 
68 }  // namespace
69 
70 namespace base {
71 
SpeedLimitObserverWin(SpeedLimitUpdateCallback speed_limit_update_callback)72 SpeedLimitObserverWin::SpeedLimitObserverWin(
73     SpeedLimitUpdateCallback speed_limit_update_callback)
74     : callback_(std::move(speed_limit_update_callback)),
75       num_cpus_(static_cast<size_t>(SysInfo::NumberOfProcessors())),
76       moving_average_(kMovingAverageWindowSize) {
77   DVLOG(1) << __func__ << "(num_CPUs=" << num_cpus() << ")";
78   timer_.Start(FROM_HERE, kSampleInterval, this,
79                &SpeedLimitObserverWin::OnTimerTick);
80 }
81 
~SpeedLimitObserverWin()82 SpeedLimitObserverWin::~SpeedLimitObserverWin() {
83   timer_.Stop();
84 }
85 
GetCurrentSpeedLimit()86 int SpeedLimitObserverWin::GetCurrentSpeedLimit() {
87   const int kSpeedLimitMax = PowerThermalObserver::kSpeedLimitMax;
88 
89   int idleness_percent = 0;
90   if (!GetCPUIdleness(&idleness_percent)) {
91     DLOG(WARNING) << "GetCPUIdleness failed";
92     return kSpeedLimitMax;
93   }
94 
95   // Get the latest estimated throttling level (value between 0.0 and 1.0).
96   float throttling_level = EstimateThrottlingLevel();
97 
98   // Ignore the value if the global idleness is above 90% or throttling value
99   // is very small. This approach avoids false alarms and removes noise from the
100   // measurements.
101   if (idleness_percent > 90 || throttling_level < 0.1f) {
102     moving_average_.Reset();
103     return kSpeedLimitMax;
104   }
105 
106   // The speed limit metric is a value between 0 and 100 [%] where 100 means
107   // "full speed". The corresponding UMA metric is CPU_Speed_Limit.
108   float speed_limit_factor = 1.0f - throttling_level;
109   int speed_limit =
110       static_cast<int>(std::ceil(kSpeedLimitMax * speed_limit_factor));
111 
112   // The previous speed-limit value was below 100 but the new value is now back
113   // at max again. To make this state more "stable or sticky" we reset the MA
114   // filter and return kSpeedLimitMax. As a result, single drops in speedlimit
115   // values will not result in a value less than 100 since the MA filter must
116   // be full before we start to produce any output.
117   if (speed_limit_ < kSpeedLimitMax && speed_limit == kSpeedLimitMax) {
118     moving_average_.Reset();
119     return kSpeedLimitMax;
120   }
121 
122   // Add the latest speeed-limit value [0,100] to the MA filter and return its
123   // output after ensuring that the filter is full. We do this to avoid initial
124   // false alarms at startup and after calling Reset() on the filter.
125   moving_average_.AddSample(speed_limit);
126   if (moving_average_.Size() < kMovingAverageWindowSize) {
127     return kSpeedLimitMax;
128   }
129   return moving_average_.GetAverageRoundedDown();
130 }
131 
OnTimerTick()132 void SpeedLimitObserverWin::OnTimerTick() {
133   // Get the latest (filtered) speed-limit estimate and trigger a new callback
134   // if the new value is different from the last.
135   const int speed_limit = GetCurrentSpeedLimit();
136   if (speed_limit != speed_limit_) {
137     speed_limit_ = speed_limit;
138     callback_.Run(speed_limit_);
139   }
140 }
141 
EstimateThrottlingLevel()142 float SpeedLimitObserverWin::EstimateThrottlingLevel() {
143   float throttling_level = 0.f;
144 
145   // Populate the PROCESSOR_POWER_INFORMATION structures for all logical CPUs
146   // using the CallNtPowerInformation API.
147   std::vector<PROCESSOR_POWER_INFORMATION> info(num_cpus());
148   if (!NT_SUCCESS(CallNtPowerInformation(
149           ProcessorInformation, nullptr, 0, &info[0],
150           static_cast<ULONG>(sizeof(PROCESSOR_POWER_INFORMATION) *
151                              num_cpus())))) {
152     return throttling_level;
153   }
154 
155   // Estimate the level of throttling by measuring how many CPUs that are not
156   // in idle state and how "far away" they are from the most idle state. Local
157   // tests have shown that `MaxIdleState` is typically 2 or 3 and
158   // `CurrentIdleState` switches to 2 or 1 when some sort of trottling starts
159   // to take place. `CurrentIdleState` equal to 0 can happen on devices where
160   // `MaxIdleState` equals 1 but it seems hard to provoke when `MaxIdleState`
161   // is larger than 1.
162   // The Intel Extreme Tuning Utility application has been used to monitor when
163   // any type of throttling (thermal, power-limit, PMAX etc) starts.
164   int num_non_idle_cpus = 0;
165   float load_fraction_total = 0.0;
166   for (size_t i = 0; i < num_cpus(); ++i) {
167     // Amount of "non-idleness" is the distance from the max idle state.
168     const auto idle_diff = info[i].MaxIdleState - info[i].CurrentIdleState;
169     // Derive a value between 0.0 and 1.0 where 1.0 corresponds to max load on
170     // CPU#i.
171     // Example: MaxIdleState=2, CurrentIdleState=1 => (2 - 1) / 2 = 0.5.
172     // Example: MaxIdleState=2, CurrentIdleState=2 => (2 - 2) / 2 = 1.0.
173     // Example: MaxIdleState=3, CurrentIdleState=1 => (3 - 1) / 3 = 0.6666.
174     // Example: MaxIdleState=3, CurrentIdleState=2 => (3 - 2) / 3 = 0.3333.
175     const float load_fraction =
176         static_cast<float>(idle_diff) / info[i].MaxIdleState;
177     // Accumulate the total load for all CPUs.
178     load_fraction_total += load_fraction;
179     // Used for a sanity check only.
180     num_non_idle_cpus += (info[i].CurrentIdleState < info[i].MaxIdleState);
181   }
182   DCHECK_LE(load_fraction_total, static_cast<float>(num_non_idle_cpus))
183       << " load_fraction_total: " << load_fraction_total
184       << " num_non_idle_cpus:" << num_non_idle_cpus;
185   throttling_level = (load_fraction_total / num_cpus());
186   return throttling_level;
187 }
188 
189 }  // namespace base
190