Add SystemPowerMonitor in //components/power_metrics
It monitors system-wide power consumption. Specifically, it first obtains absolute energy data from power_metrics::EnergyMetricsProvider, then calculates the power consumption and calls TRACE_COUNTER to write power value into trace log. Bug: 1385251 Change-Id: I756755df3f6ec91688aef7dc4156b845246056ea Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4472031 Reviewed-by: Mark Pearson <mpearson@chromium.org> Commit-Queue: Francois Pierre Doray <fdoray@chromium.org> Reviewed-by: Francois Pierre Doray <fdoray@chromium.org> Cr-Commit-Position: refs/heads/main@{#1145923}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
4e10c9c110
commit
d2956aa221
base/trace_event
chrome/browser
components/power_metrics
@@ -259,6 +259,7 @@
|
|||||||
X(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache")) \
|
X(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache")) \
|
||||||
X(TRACE_DISABLED_BY_DEFAULT("skia.shaders")) \
|
X(TRACE_DISABLED_BY_DEFAULT("skia.shaders")) \
|
||||||
X(TRACE_DISABLED_BY_DEFAULT("SyncFileSystem")) \
|
X(TRACE_DISABLED_BY_DEFAULT("SyncFileSystem")) \
|
||||||
|
X(TRACE_DISABLED_BY_DEFAULT("system_power")) \
|
||||||
X(TRACE_DISABLED_BY_DEFAULT("system_stats")) \
|
X(TRACE_DISABLED_BY_DEFAULT("system_stats")) \
|
||||||
X(TRACE_DISABLED_BY_DEFAULT("thread_pool_diagnostics")) \
|
X(TRACE_DISABLED_BY_DEFAULT("thread_pool_diagnostics")) \
|
||||||
X(TRACE_DISABLED_BY_DEFAULT("toplevel.ipc")) \
|
X(TRACE_DISABLED_BY_DEFAULT("toplevel.ipc")) \
|
||||||
|
@@ -6035,7 +6035,6 @@ static_library("browser") {
|
|||||||
"//components/crash/core/app",
|
"//components/crash/core/app",
|
||||||
"//components/metal_util",
|
"//components/metal_util",
|
||||||
"//components/policy/core/common:common_constants",
|
"//components/policy/core/common:common_constants",
|
||||||
"//components/power_metrics",
|
|
||||||
"//components/remote_cocoa/browser:browser",
|
"//components/remote_cocoa/browser:browser",
|
||||||
"//sandbox/mac:seatbelt",
|
"//sandbox/mac:seatbelt",
|
||||||
"//sandbox/policy",
|
"//sandbox/policy",
|
||||||
@@ -6213,6 +6212,7 @@ static_library("browser") {
|
|||||||
"//chrome/browser/enterprise/connectors/device_trust/key_management/core",
|
"//chrome/browser/enterprise/connectors/device_trust/key_management/core",
|
||||||
"//chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence",
|
"//chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence",
|
||||||
"//chrome/services/system_signals/public/cpp/browser",
|
"//chrome/services/system_signals/public/cpp/browser",
|
||||||
|
"//components/power_metrics",
|
||||||
]
|
]
|
||||||
public_deps +=
|
public_deps +=
|
||||||
[ "//chrome/browser/enterprise/connectors/analysis:sdk_manager" ]
|
[ "//chrome/browser/enterprise/connectors/analysis:sdk_manager" ]
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include "base/task/thread_pool.h"
|
#include "base/task/thread_pool.h"
|
||||||
#include "base/threading/scoped_blocking_call.h"
|
#include "base/threading/scoped_blocking_call.h"
|
||||||
#include "base/time/time.h"
|
#include "base/time/time.h"
|
||||||
|
#include "base/trace_event/trace_log.h"
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
#include "build/chromeos_buildflags.h"
|
#include "build/chromeos_buildflags.h"
|
||||||
#include "build/config/compiler/compiler_buildflags.h"
|
#include "build/config/compiler/compiler_buildflags.h"
|
||||||
@@ -110,6 +111,10 @@
|
|||||||
#include "components/user_manager/user_manager.h"
|
#include "components/user_manager/user_manager.h"
|
||||||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
||||||
|
|
||||||
|
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||||
|
#include "components/power_metrics/system_power_monitor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// The number of restarts to wait until removing the enable-benchmarking flag.
|
// The number of restarts to wait until removing the enable-benchmarking flag.
|
||||||
@@ -741,6 +746,11 @@ void ChromeBrowserMainExtraPartsMetrics::PostBrowserStart() {
|
|||||||
pressure_metrics_reporter_ = std::make_unique<PressureMetricsReporter>();
|
pressure_metrics_reporter_ = std::make_unique<PressureMetricsReporter>();
|
||||||
#endif // BUILDFLAG(IS_LINUX)
|
#endif // BUILDFLAG(IS_LINUX)
|
||||||
|
|
||||||
|
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||||
|
base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(
|
||||||
|
power_metrics::SystemPowerMonitor::GetInstance());
|
||||||
|
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||||
|
|
||||||
HandleEnableBenchmarkingCountdownAsync();
|
HandleEnableBenchmarkingCountdownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -48,6 +48,13 @@ static_library("power_metrics") {
|
|||||||
"energy_metrics_provider_linux.h",
|
"energy_metrics_provider_linux.h",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_win || is_linux) {
|
||||||
|
sources += [
|
||||||
|
"system_power_monitor.cc",
|
||||||
|
"system_power_monitor.h",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source_set("unit_tests") {
|
source_set("unit_tests") {
|
||||||
@@ -57,6 +64,7 @@ source_set("unit_tests") {
|
|||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
":power_metrics",
|
":power_metrics",
|
||||||
|
"//base",
|
||||||
"//testing/gtest",
|
"//testing/gtest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -68,7 +76,11 @@ source_set("unit_tests") {
|
|||||||
configs += [ "//build/config/compiler:enable_arc" ]
|
configs += [ "//build/config/compiler:enable_arc" ]
|
||||||
|
|
||||||
data = [ "test/data/" ]
|
data = [ "test/data/" ]
|
||||||
|
}
|
||||||
|
|
||||||
deps += [ "//base" ]
|
if (is_win || is_linux) {
|
||||||
|
sources += [ "system_power_monitor_unittest.cc" ]
|
||||||
|
|
||||||
|
deps += [ "//base/test:test_support" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
228
components/power_metrics/system_power_monitor.cc
Normal file
228
components/power_metrics/system_power_monitor.cc
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
// Copyright 2023 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "components/power_metrics/system_power_monitor.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "base/functional/bind.h"
|
||||||
|
#include "base/no_destructor.h"
|
||||||
|
#include "base/notreached.h"
|
||||||
|
#include "base/task/task_traits.h"
|
||||||
|
#include "base/task/thread_pool.h"
|
||||||
|
#include "base/trace_event/trace_event.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char kTraceCategory[] =
|
||||||
|
TRACE_DISABLED_BY_DEFAULT("system_power");
|
||||||
|
|
||||||
|
constexpr const char kPackagePowerTraceCounterName[] = "Package Power (mW)";
|
||||||
|
constexpr const char kCpuPowerTraceCounterName[] = "CPU Power (mW)";
|
||||||
|
constexpr const char kIntegratedGpuPowerTraceCounterName[] = "iGPU Power (mW)";
|
||||||
|
constexpr const char kDramPowerTraceCounterName[] = "DRAM Power (mW)";
|
||||||
|
constexpr const char kPsysPowerTraceCounterName[] = "Psys Power (mW)";
|
||||||
|
constexpr const char kVddcrVddTraceCounterName[] = "VDDCR VDD (mW)";
|
||||||
|
constexpr const char kVddcrSocTraceCounterName[] = "VDDCR SOC (mW)";
|
||||||
|
constexpr const char kCurrentSocketTraceCounterName[] = "Current Socket (mW)";
|
||||||
|
constexpr const char kApuPowerTraceCounterName[] = "APU Power (mW)";
|
||||||
|
|
||||||
|
// Here we determine if the specified metric is valid according to whether its
|
||||||
|
// corresponding value in the provided sample is greater than 0, since the
|
||||||
|
// absolute energy must be greater than 0.
|
||||||
|
bool GenerateValidMetrics(const EnergyMetricsProvider::EnergyMetrics& sample,
|
||||||
|
std::vector<const char*>& valid_metrics) {
|
||||||
|
if (sample.package_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kPackagePowerTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.cpu_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kCpuPowerTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.gpu_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kIntegratedGpuPowerTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.dram_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kDramPowerTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.psys_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kPsysPowerTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.vdd_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kVddcrVddTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.soc_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kVddcrSocTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.socket_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kCurrentSocketTraceCounterName);
|
||||||
|
}
|
||||||
|
if (sample.apu_nanojoules > 0) {
|
||||||
|
valid_metrics.push_back(kApuPowerTraceCounterName);
|
||||||
|
}
|
||||||
|
return !valid_metrics.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CalculateNanojoulesDeltaFromSamples(
|
||||||
|
const EnergyMetricsProvider::EnergyMetrics& new_sample,
|
||||||
|
const EnergyMetricsProvider::EnergyMetrics& old_sample,
|
||||||
|
const char* metric) {
|
||||||
|
if (std::strcmp(metric, kPackagePowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.package_nanojoules -
|
||||||
|
old_sample.package_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kCpuPowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.cpu_nanojoules -
|
||||||
|
old_sample.cpu_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kIntegratedGpuPowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.gpu_nanojoules -
|
||||||
|
old_sample.gpu_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kDramPowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.dram_nanojoules -
|
||||||
|
old_sample.dram_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kPsysPowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.psys_nanojoules -
|
||||||
|
old_sample.psys_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kVddcrVddTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.vdd_nanojoules -
|
||||||
|
old_sample.vdd_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kVddcrSocTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.soc_nanojoules -
|
||||||
|
old_sample.soc_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kCurrentSocketTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.socket_nanojoules -
|
||||||
|
old_sample.socket_nanojoules);
|
||||||
|
} else if (std::strcmp(metric, kApuPowerTraceCounterName) == 0) {
|
||||||
|
return static_cast<int64_t>(new_sample.apu_nanojoules -
|
||||||
|
old_sample.apu_nanojoules);
|
||||||
|
}
|
||||||
|
NOTREACHED_NORETURN() << "Unexpected metric: " << metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SystemPowerMonitorDelegate::SystemPowerMonitorDelegate() = default;
|
||||||
|
SystemPowerMonitorDelegate::~SystemPowerMonitorDelegate() = default;
|
||||||
|
|
||||||
|
void SystemPowerMonitorDelegate::RecordSystemPower(const char* metric,
|
||||||
|
base::TimeTicks timestamp,
|
||||||
|
int64_t power) {
|
||||||
|
TRACE_COUNTER_WITH_TIMESTAMP1(TRACE_DISABLED_BY_DEFAULT("system_power"),
|
||||||
|
metric, timestamp, power);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemPowerMonitorDelegate::IsTraceCategoryEnabled() const {
|
||||||
|
bool enabled;
|
||||||
|
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemPowerMonitorHelper::SystemPowerMonitorHelper(
|
||||||
|
std::unique_ptr<EnergyMetricsProvider> provider,
|
||||||
|
std::unique_ptr<SystemPowerMonitorDelegate> delegate)
|
||||||
|
: provider_(std::move(provider)), delegate_(std::move(delegate)) {}
|
||||||
|
|
||||||
|
SystemPowerMonitorHelper::~SystemPowerMonitorHelper() = default;
|
||||||
|
|
||||||
|
void SystemPowerMonitorHelper::Start() {
|
||||||
|
CHECK(provider_);
|
||||||
|
CHECK(!timer_.IsRunning());
|
||||||
|
if (!delegate_->IsTraceCategoryEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the provider fails to capture valid sample at the first time, we
|
||||||
|
// determine that it is unable to provide valid data and give up starting the
|
||||||
|
// timer.
|
||||||
|
auto sample = provider_->CaptureMetrics();
|
||||||
|
if (!sample.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid redundant loops on invalid metrics, we select the valid metrics
|
||||||
|
// before start.
|
||||||
|
CHECK(valid_metrics_.empty());
|
||||||
|
if (!GenerateValidMetrics(sample.value(), valid_metrics_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_sample_ = sample.value();
|
||||||
|
last_timestamp_ = base::TimeTicks::Now();
|
||||||
|
|
||||||
|
timer_.Start(FROM_HERE, kDefaultSampleInterval,
|
||||||
|
base::BindRepeating(&SystemPowerMonitorHelper::Sample,
|
||||||
|
base::Unretained(this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemPowerMonitorHelper::Stop() {
|
||||||
|
timer_.Stop();
|
||||||
|
valid_metrics_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemPowerMonitorHelper::Sample() {
|
||||||
|
// If the provider fails to capture valid metrics after the timer started,
|
||||||
|
// we leave the timer running.
|
||||||
|
auto sample = provider_->CaptureMetrics();
|
||||||
|
if (!sample.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::TimeTicks timestamp = base::TimeTicks::Now();
|
||||||
|
base::TimeDelta interval = timestamp - last_timestamp_;
|
||||||
|
CHECK(interval.is_positive());
|
||||||
|
|
||||||
|
for (auto const* metric : valid_metrics_) {
|
||||||
|
int64_t nanojoules = CalculateNanojoulesDeltaFromSamples(
|
||||||
|
sample.value(), last_sample_, metric);
|
||||||
|
CHECK_GE(nanojoules, 0ll);
|
||||||
|
|
||||||
|
int64_t milliwatts = nanojoules / interval.InMicroseconds();
|
||||||
|
delegate_->RecordSystemPower(metric, last_timestamp_, milliwatts);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_sample_ = sample.value();
|
||||||
|
last_timestamp_ = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemPowerMonitorHelper::IsTimerRunningForTesting() {
|
||||||
|
return timer_.IsRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemPowerMonitor::SystemPowerMonitor()
|
||||||
|
: SystemPowerMonitor(EnergyMetricsProvider::Create(),
|
||||||
|
std::make_unique<SystemPowerMonitorDelegate>()) {}
|
||||||
|
|
||||||
|
SystemPowerMonitor::SystemPowerMonitor(
|
||||||
|
std::unique_ptr<EnergyMetricsProvider> provider,
|
||||||
|
std::unique_ptr<SystemPowerMonitorDelegate> delegate) {
|
||||||
|
helper_ = base::SequenceBound<SystemPowerMonitorHelper>(
|
||||||
|
base::ThreadPool::CreateSequencedTaskRunner(
|
||||||
|
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
|
||||||
|
base::TaskPriority::BEST_EFFORT}),
|
||||||
|
std::move(provider), std::move(delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemPowerMonitor::~SystemPowerMonitor() = default;
|
||||||
|
|
||||||
|
// static
|
||||||
|
SystemPowerMonitor* SystemPowerMonitor::GetInstance() {
|
||||||
|
static base::NoDestructor<SystemPowerMonitor> instance;
|
||||||
|
return instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemPowerMonitor::OnTraceLogEnabled() {
|
||||||
|
helper_.AsyncCall(&SystemPowerMonitorHelper::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemPowerMonitor::OnTraceLogDisabled() {
|
||||||
|
helper_.AsyncCall(&SystemPowerMonitorHelper::Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
base::SequenceBound<SystemPowerMonitorHelper>*
|
||||||
|
SystemPowerMonitor::GetHelperForTesting() {
|
||||||
|
return helper_ ? &helper_ : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
107
components/power_metrics/system_power_monitor.h
Normal file
107
components/power_metrics/system_power_monitor.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2023 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_POWER_METRICS_SYSTEM_POWER_MONITOR_H_
|
||||||
|
#define COMPONENTS_POWER_METRICS_SYSTEM_POWER_MONITOR_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/threading/sequence_bound.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "base/timer/timer.h"
|
||||||
|
#include "base/trace_event/trace_log.h"
|
||||||
|
#include "components/power_metrics/energy_metrics_provider.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
// A delegate to isolate System Power Monitor functionality mainly for
|
||||||
|
// testing.
|
||||||
|
class SystemPowerMonitorDelegate {
|
||||||
|
public:
|
||||||
|
SystemPowerMonitorDelegate();
|
||||||
|
|
||||||
|
SystemPowerMonitorDelegate(const SystemPowerMonitorDelegate&) = delete;
|
||||||
|
SystemPowerMonitorDelegate& operator=(const SystemPowerMonitorDelegate&) =
|
||||||
|
delete;
|
||||||
|
|
||||||
|
virtual ~SystemPowerMonitorDelegate();
|
||||||
|
|
||||||
|
// Emits trace counter. The metric string stands for trace counter name,
|
||||||
|
// timestamp is the counter timestamp, power is the corresponding counter
|
||||||
|
// value of system power consumption in units of milliwatts.
|
||||||
|
virtual void RecordSystemPower(const char* metric,
|
||||||
|
base::TimeTicks timestamp,
|
||||||
|
int64_t power);
|
||||||
|
|
||||||
|
// Returns whether the tracing category is enabled to determine if we should
|
||||||
|
// record.
|
||||||
|
virtual bool IsTraceCategoryEnabled() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Manages a timer to regularly sample and emit trace events, whose start and
|
||||||
|
// stop are controlled by System Power Monitor.
|
||||||
|
class SystemPowerMonitorHelper {
|
||||||
|
public:
|
||||||
|
// Default sampling interval, which should be set to larger or equal to 50 ms.
|
||||||
|
static constexpr base::TimeDelta kDefaultSampleInterval =
|
||||||
|
base::Milliseconds(50);
|
||||||
|
|
||||||
|
SystemPowerMonitorHelper(
|
||||||
|
std::unique_ptr<EnergyMetricsProvider> provider,
|
||||||
|
std::unique_ptr<SystemPowerMonitorDelegate> delegate);
|
||||||
|
|
||||||
|
SystemPowerMonitorHelper(const SystemPowerMonitorHelper&) = delete;
|
||||||
|
SystemPowerMonitorHelper& operator=(const SystemPowerMonitorHelper&) = delete;
|
||||||
|
|
||||||
|
~SystemPowerMonitorHelper();
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
void Sample();
|
||||||
|
|
||||||
|
bool IsTimerRunningForTesting();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<const char*> valid_metrics_;
|
||||||
|
EnergyMetricsProvider::EnergyMetrics last_sample_;
|
||||||
|
base::TimeTicks last_timestamp_;
|
||||||
|
base::RepeatingTimer timer_;
|
||||||
|
|
||||||
|
// Used to derive instant system energy metrics.
|
||||||
|
std::unique_ptr<EnergyMetricsProvider> provider_;
|
||||||
|
std::unique_ptr<SystemPowerMonitorDelegate> delegate_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Monitors system-wide power consumption. Gets data from EnergyMetricsProvider.
|
||||||
|
class SystemPowerMonitor
|
||||||
|
: public base::trace_event::TraceLog::EnabledStateObserver {
|
||||||
|
public:
|
||||||
|
SystemPowerMonitor();
|
||||||
|
|
||||||
|
SystemPowerMonitor(const SystemPowerMonitor&) = delete;
|
||||||
|
SystemPowerMonitor& operator=(const SystemPowerMonitor&) = delete;
|
||||||
|
|
||||||
|
~SystemPowerMonitor() override;
|
||||||
|
|
||||||
|
static SystemPowerMonitor* GetInstance();
|
||||||
|
|
||||||
|
// TraceLog::EnabledStateObserver.
|
||||||
|
void OnTraceLogEnabled() override;
|
||||||
|
void OnTraceLogDisabled() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class SystemPowerMonitorTest;
|
||||||
|
|
||||||
|
SystemPowerMonitor(std::unique_ptr<EnergyMetricsProvider> provider,
|
||||||
|
std::unique_ptr<SystemPowerMonitorDelegate> delegate);
|
||||||
|
|
||||||
|
base::SequenceBound<SystemPowerMonitorHelper>* GetHelperForTesting();
|
||||||
|
|
||||||
|
base::SequenceBound<SystemPowerMonitorHelper> helper_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
||||||
|
|
||||||
|
#endif // COMPONENTS_POWER_METRICS_SYSTEM_POWER_MONITOR_H_
|
253
components/power_metrics/system_power_monitor_unittest.cc
Normal file
253
components/power_metrics/system_power_monitor_unittest.cc
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
// Copyright 2023 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "components/power_metrics/system_power_monitor.h"
|
||||||
|
|
||||||
|
#include "base/memory/raw_ptr.h"
|
||||||
|
#include "base/test/task_environment.h"
|
||||||
|
#include "base/test/test_future.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
class FakeProvider : public EnergyMetricsProvider {
|
||||||
|
public:
|
||||||
|
void set_metrics(EnergyMetrics metrics) { metrics_ = metrics; }
|
||||||
|
|
||||||
|
absl::optional<EnergyMetrics> CaptureMetrics() override { return metrics_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
absl::optional<EnergyMetrics> metrics_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeDelegate : public SystemPowerMonitorDelegate {
|
||||||
|
public:
|
||||||
|
void set_trace_category_enabled(bool enabled) {
|
||||||
|
trace_category_enabled_ = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordSystemPower(const char* category,
|
||||||
|
base::TimeTicks timestamp,
|
||||||
|
int64_t power) override {
|
||||||
|
timestamp_ = timestamp;
|
||||||
|
if (strcmp(category, "Package Power (mW)") == 0) {
|
||||||
|
system_power_.package_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "CPU Power (mW)") == 0) {
|
||||||
|
system_power_.cpu_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "iGPU Power (mW)") == 0) {
|
||||||
|
system_power_.gpu_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "DRAM Power (mW)") == 0) {
|
||||||
|
system_power_.dram_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "Psys Power (mW)") == 0) {
|
||||||
|
system_power_.psys_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "VDDCR VDD (mW)") == 0) {
|
||||||
|
system_power_.vdd_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "VDDCR SOC (mW)") == 0) {
|
||||||
|
system_power_.soc_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "Current Socket (mW)") == 0) {
|
||||||
|
system_power_.socket_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
} else if (strcmp(category, "APU Power (mW)") == 0) {
|
||||||
|
system_power_.apu_nanojoules = static_cast<uint64_t>(power);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTraceCategoryEnabled() const override {
|
||||||
|
return trace_category_enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnergyMetricsProvider::EnergyMetrics& SystemPower() { return system_power_; }
|
||||||
|
|
||||||
|
base::TimeTicks timestamp() { return timestamp_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// We use EnergyMetrics to save recorded power data in milliwatts for
|
||||||
|
// simplicity.
|
||||||
|
EnergyMetricsProvider::EnergyMetrics system_power_;
|
||||||
|
base::TimeTicks timestamp_;
|
||||||
|
bool trace_category_enabled_{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
class SystemPowerMonitorHelperTest : public testing::Test {
|
||||||
|
public:
|
||||||
|
SystemPowerMonitorHelperTest()
|
||||||
|
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
auto provider = std::make_unique<FakeProvider>();
|
||||||
|
provider_ = provider.get();
|
||||||
|
auto delegate = std::make_unique<FakeDelegate>();
|
||||||
|
delegate_ = delegate.get();
|
||||||
|
helper_ = std::make_unique<SystemPowerMonitorHelper>(std::move(provider),
|
||||||
|
std::move(delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override { helper_.reset(); }
|
||||||
|
|
||||||
|
base::test::TaskEnvironment& task_environment() { return task_environment_; }
|
||||||
|
SystemPowerMonitorHelper* helper() { return helper_.get(); }
|
||||||
|
FakeDelegate* delegate() { return delegate_.get(); }
|
||||||
|
FakeProvider* provider() { return provider_.get(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
base::test::TaskEnvironment task_environment_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SystemPowerMonitorHelper> helper_;
|
||||||
|
raw_ptr<FakeDelegate> delegate_;
|
||||||
|
raw_ptr<FakeProvider> provider_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SystemPowerMonitorTest : public testing::Test {
|
||||||
|
public:
|
||||||
|
SystemPowerMonitorTest() : task_environment_() {}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
auto provider = std::make_unique<FakeProvider>();
|
||||||
|
|
||||||
|
// Assign a valid metric to provider, so the timer can start successfully.
|
||||||
|
provider->set_metrics({1llu});
|
||||||
|
monitor_.reset(new SystemPowerMonitor(std::move(provider),
|
||||||
|
std::make_unique<FakeDelegate>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override { monitor_.reset(); }
|
||||||
|
|
||||||
|
SystemPowerMonitor* monitor() { return monitor_.get(); }
|
||||||
|
base::SequenceBound<SystemPowerMonitorHelper>* helper() {
|
||||||
|
return monitor_->GetHelperForTesting();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
base::test::TaskEnvironment task_environment_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SystemPowerMonitor> monitor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorHelperTest, MonitorHelperStartStop) {
|
||||||
|
provider()->set_metrics({1llu});
|
||||||
|
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
|
||||||
|
helper()->Stop();
|
||||||
|
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
|
||||||
|
helper()->Stop();
|
||||||
|
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorHelperTest, TimerStartFailed_InvalidSample) {
|
||||||
|
// We haven't set metrics for provider, so monitor gets an
|
||||||
|
// absl::nullopt sample at the beginning and it will not start.
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorHelperTest, TimerStartFailed_MetricsAllZero) {
|
||||||
|
// If the metrics are all 0, we determine that there is no valid metric
|
||||||
|
// provided, so monitor will not start.
|
||||||
|
provider()->set_metrics({});
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorHelperTest, TraceCategoryEnableDisable) {
|
||||||
|
provider()->set_metrics({1llu});
|
||||||
|
|
||||||
|
delegate()->set_trace_category_enabled(false);
|
||||||
|
ASSERT_FALSE(delegate()->IsTraceCategoryEnabled());
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
|
||||||
|
|
||||||
|
delegate()->set_trace_category_enabled(true);
|
||||||
|
ASSERT_TRUE(delegate()->IsTraceCategoryEnabled());
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorHelperTest, TestSample) {
|
||||||
|
EnergyMetricsProvider::EnergyMetrics sample1 = {
|
||||||
|
100000llu, 100000llu, 100000llu, 100000llu, 100000llu,
|
||||||
|
100000llu, 100000llu, 100000llu, 100000llu};
|
||||||
|
EnergyMetricsProvider::EnergyMetrics sample2 = {
|
||||||
|
200000llu, 300000llu, 400000llu, 500000llu, 600000llu,
|
||||||
|
700000llu, 800000llu, 900000llu, 1000000llu};
|
||||||
|
|
||||||
|
provider()->set_metrics(sample1);
|
||||||
|
helper()->Start();
|
||||||
|
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
|
||||||
|
|
||||||
|
provider()->set_metrics(sample2);
|
||||||
|
task_environment().FastForwardBy(
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval);
|
||||||
|
auto power = delegate()->SystemPower();
|
||||||
|
EXPECT_EQ(delegate()->timestamp() +
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval,
|
||||||
|
task_environment().NowTicks());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.package_nanojoules,
|
||||||
|
(sample2.package_nanojoules - sample1.package_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.cpu_nanojoules,
|
||||||
|
(sample2.cpu_nanojoules - sample1.cpu_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.gpu_nanojoules,
|
||||||
|
(sample2.gpu_nanojoules - sample1.gpu_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.dram_nanojoules,
|
||||||
|
(sample2.dram_nanojoules - sample1.dram_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.psys_nanojoules,
|
||||||
|
(sample2.psys_nanojoules - sample1.psys_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.vdd_nanojoules,
|
||||||
|
(sample2.vdd_nanojoules - sample1.vdd_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.soc_nanojoules,
|
||||||
|
(sample2.soc_nanojoules - sample1.soc_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.socket_nanojoules,
|
||||||
|
(sample2.socket_nanojoules - sample1.socket_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
EXPECT_EQ(
|
||||||
|
power.apu_nanojoules,
|
||||||
|
(sample2.apu_nanojoules - sample1.apu_nanojoules) /
|
||||||
|
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SystemPowerMonitorTest, TraceLogEnableDisable) {
|
||||||
|
ASSERT_NE(helper(), nullptr);
|
||||||
|
|
||||||
|
base::test::TestFuture<bool> future_enable;
|
||||||
|
monitor()->OnTraceLogEnabled();
|
||||||
|
helper()
|
||||||
|
->AsyncCall(&SystemPowerMonitorHelper::IsTimerRunningForTesting)
|
||||||
|
.Then(base::BindOnce(
|
||||||
|
[](base::OnceCallback<void(bool)> callback, bool is_running) {
|
||||||
|
std::move(callback).Run(is_running);
|
||||||
|
},
|
||||||
|
future_enable.GetCallback()));
|
||||||
|
EXPECT_TRUE(future_enable.Get());
|
||||||
|
|
||||||
|
base::test::TestFuture<bool> future_disable;
|
||||||
|
monitor()->OnTraceLogDisabled();
|
||||||
|
helper()
|
||||||
|
->AsyncCall(&SystemPowerMonitorHelper::IsTimerRunningForTesting)
|
||||||
|
.Then(base::BindOnce(
|
||||||
|
[](base::OnceCallback<void(bool)> callback, bool is_running) {
|
||||||
|
std::move(callback).Run(is_running);
|
||||||
|
},
|
||||||
|
future_disable.GetCallback()));
|
||||||
|
EXPECT_FALSE(future_disable.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
Reference in New Issue
Block a user