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