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