Commit 21cad564 authored by Troy Hildebrandt's avatar Troy Hildebrandt Committed by Commit Bot

Introduce an Android CPU Frequency sampler.

Adds the ability to record CPU frequency on Android devices, viewable
in the Chrome trace viewer. Records the CPU frequency on a per-cluster
basis, meaning for a Pixel/Pixel 2 we have two separate trace counters
for CPU0-3, and CPU4-7.

The sampler is a TraceLog::EnabledStateObserver, so on Android devices
it will start and stop when tracing is enabled and disabled.

Bug:873303

Change-Id: I1702fa184f16c53f200667528bb88e96957b0e65
Reviewed-on: https://chromium-review.googlesource.com/1123029
Commit-Queue: Troy Hildebrandt <thildebr@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarSiddhartha S <ssid@chromium.org>
Cr-Commit-Position: refs/heads/master@{#587738}
parent 4fc12293
......@@ -998,6 +998,8 @@ jumbo_component("base") {
"trace_event/category_registry.cc",
"trace_event/category_registry.h",
"trace_event/common/trace_event_common.h",
"trace_event/cpufreq_monitor_android.cc",
"trace_event/cpufreq_monitor_android.h",
"trace_event/event_name_filter.cc",
"trace_event/event_name_filter.h",
"trace_event/heap_profiler.h",
......@@ -2505,6 +2507,7 @@ test("base_unittests") {
"timer/timer_unittest.cc",
"tools_sanity_unittest.cc",
"trace_event/blame_context_unittest.cc",
"trace_event/cpufreq_monitor_android_unittest.cc",
"trace_event/event_name_filter_unittest.cc",
"trace_event/heap_profiler_allocation_context_tracker_unittest.cc",
"trace_event/java_heap_dump_provider_android_unittest.cc",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/trace_event/cpufreq_monitor_android.h"
#include <fcntl.h>
#include "base/atomicops.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/trace_event/trace_event.h"
namespace base {
namespace trace_event {
namespace {
const size_t kNumBytesToReadForSampling = 32;
const char kTraceCategory[] = TRACE_DISABLED_BY_DEFAULT("power");
const char kEventTitle[] = "CPU Frequency";
} // namespace
CPUFreqMonitorDelegate::CPUFreqMonitorDelegate() {}
std::string CPUFreqMonitorDelegate::GetScalingCurFreqPathString(
unsigned int cpu_id) const {
return base::StringPrintf(
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu_id);
}
bool CPUFreqMonitorDelegate::IsTraceCategoryEnabled() const {
bool enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled);
return enabled;
}
unsigned int CPUFreqMonitorDelegate::GetKernelMaxCPUs() const {
std::string str;
if (!base::ReadFileToString(
base::FilePath("/sys/devices/system/cpu/kernel_max"), &str)) {
// If we fail to read the kernel_max file, we just assume that CPU0 exists.
return 0;
}
unsigned int kernel_max_cpu = 0;
base::StringToUint(str, &kernel_max_cpu);
return kernel_max_cpu;
}
std::string CPUFreqMonitorDelegate::GetRelatedCPUsPathString(
unsigned int cpu_id) const {
return base::StringPrintf(
"/sys/devices/system/cpu/cpu%d/cpufreq/related_cpus", cpu_id);
}
void CPUFreqMonitorDelegate::GetCPUIds(std::vector<unsigned int>* ids) const {
ids->clear();
unsigned int kernel_max_cpu = GetKernelMaxCPUs();
// CPUs related to one that's already marked for monitoring get set to "false"
// so we don't needlessly monitor CPUs with redundant frequency information.
char cpus_to_monitor[kernel_max_cpu + 1];
std::memset(cpus_to_monitor, 1, kernel_max_cpu + 1);
// Rule out the related CPUs for each one so we only end up with the CPUs
// that are representative of the cluster.
for (unsigned int i = 0; i <= kernel_max_cpu; i++) {
if (!cpus_to_monitor[i])
continue;
std::string filename = GetRelatedCPUsPathString(i);
std::string line;
if (!base::ReadFileToString(base::FilePath(filename), &line))
continue;
// When reading the related_cpus file, we expected the format to be
// something like "0 1 2 3" for CPU0-3 if they're all in one cluster.
for (auto& str_piece :
base::SplitString(line, " ", base::WhitespaceHandling::TRIM_WHITESPACE,
base::SplitResult::SPLIT_WANT_NONEMPTY)) {
unsigned int cpu_id;
if (base::StringToUint(str_piece, &cpu_id)) {
if (cpu_id != i && cpu_id >= 0 && cpu_id <= kernel_max_cpu)
cpus_to_monitor[cpu_id] = 0;
}
}
ids->push_back(i);
}
// If none of the files were readable, we assume CPU0 exists and fall back to
// using that.
if (ids->size() == 0)
ids->push_back(0);
}
void CPUFreqMonitorDelegate::RecordFrequency(unsigned int cpu_id,
unsigned int freq) {
TRACE_COUNTER_ID1(kTraceCategory, kEventTitle, cpu_id, freq);
}
scoped_refptr<SingleThreadTaskRunner>
CPUFreqMonitorDelegate::CreateTaskRunner() {
return base::CreateSingleThreadTaskRunnerWithTraits(
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
base::TaskPriority::BEST_EFFORT},
base::SingleThreadTaskRunnerThreadMode::SHARED);
}
CPUFreqMonitor::CPUFreqMonitor()
: CPUFreqMonitor(std::make_unique<CPUFreqMonitorDelegate>()) {}
CPUFreqMonitor::CPUFreqMonitor(std::unique_ptr<CPUFreqMonitorDelegate> delegate)
: delegate_(std::move(delegate)), weak_ptr_factory_(this) {
TRACE_EVENT_WARMUP_CATEGORY(kTraceCategory);
}
CPUFreqMonitor::~CPUFreqMonitor() {
Stop();
}
// static
CPUFreqMonitor* CPUFreqMonitor::GetInstance() {
static base::NoDestructor<CPUFreqMonitor> instance;
return instance.get();
}
void CPUFreqMonitor::OnTraceLogEnabled() {
GetOrCreateTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&CPUFreqMonitor::Start, weak_ptr_factory_.GetWeakPtr()));
}
void CPUFreqMonitor::OnTraceLogDisabled() {
Stop();
}
void CPUFreqMonitor::Start() {
// It's the responsibility of the caller to ensure that Start/Stop are
// synchronized. If Start/Stop are called asynchronously where this value
// may be incorrect, we have bigger problems.
if (base::subtle::NoBarrier_Load(&is_enabled_) == 1 ||
!delegate_->IsTraceCategoryEnabled()) {
return;
}
std::vector<unsigned int> cpu_ids;
delegate_->GetCPUIds(&cpu_ids);
std::vector<std::pair<unsigned int, base::ScopedFD>> fds;
for (unsigned int id : cpu_ids) {
std::string fstr = delegate_->GetScalingCurFreqPathString(id);
int fd = open(fstr.c_str(), O_RDONLY);
if (fd == -1)
continue;
fds.emplace_back(std::make_pair(id, base::ScopedFD(fd)));
}
// We failed to read any scaling_cur_freq files, no point sampling nothing.
if (fds.size() == 0)
return;
base::subtle::Release_Store(&is_enabled_, 1);
GetOrCreateTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&CPUFreqMonitor::Sample, weak_ptr_factory_.GetWeakPtr(),
std::move(fds)));
}
void CPUFreqMonitor::Stop() {
base::subtle::Release_Store(&is_enabled_, 0);
}
void CPUFreqMonitor::Sample(
std::vector<std::pair<unsigned int, base::ScopedFD>> fds) {
// For the same reason as above we use NoBarrier_Load, because if this value
// is in transition and we use Acquire_Load then we'll never shut down our
// original Sample tasks until the next Stop, so it's still the responsibility
// of callers to sync Start/Stop.
if (base::subtle::NoBarrier_Load(&is_enabled_) == 0)
return;
for (auto& id_fd : fds) {
int fd = id_fd.second.get();
unsigned int freq = 0;
// If we have trouble reading data from the file for any reason we'll end up
// reporting the frequency as nothing.
lseek(fd, 0L, SEEK_SET);
char data[kNumBytesToReadForSampling];
size_t bytes_read = read(fd, data, kNumBytesToReadForSampling);
if (bytes_read > 0) {
if (bytes_read < kNumBytesToReadForSampling)
data[bytes_read] = '\0';
int ret = sscanf(data, "%d", &freq);
if (ret == 0 || ret == std::char_traits<char>::eof())
freq = 0;
}
delegate_->RecordFrequency(id_fd.first, freq);
}
GetOrCreateTaskRunner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CPUFreqMonitor::Sample, weak_ptr_factory_.GetWeakPtr(),
std::move(fds)),
base::TimeDelta::FromMilliseconds(kDefaultCPUFreqSampleIntervalMs));
}
bool CPUFreqMonitor::IsEnabledForTesting() {
return base::subtle::Acquire_Load(&is_enabled_) == 1;
}
const scoped_refptr<SingleThreadTaskRunner>&
CPUFreqMonitor::GetOrCreateTaskRunner() {
if (!task_runner_)
task_runner_ = delegate_->CreateTaskRunner();
return task_runner_;
}
} // namespace trace_event
} // namespace base
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TRACE_EVENT_CPUFREQ_MONITOR_ANDROID_H_
#define BASE_TRACE_EVENT_CPUFREQ_MONITOR_ANDROID_H_
#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/files/scoped_file.h"
#include "base/memory/scoped_refptr.h"
#include "base/trace_event/trace_log.h"
namespace base {
class SingleThreadTaskRunner;
namespace trace_event {
// A delegate to isolate CPU frequency monitor functionality mainly for testing.
class BASE_EXPORT CPUFreqMonitorDelegate {
public:
CPUFreqMonitorDelegate();
virtual ~CPUFreqMonitorDelegate() = default;
// Returns a vector of the minimal set of CPU IDs that we need to monitor to
// get CPU frequency information. For CPUs that operate cores in a cluster,
// i.e. modern Qualcomm 8 cores, this is CPU0 and CPU4.
virtual void GetCPUIds(std::vector<unsigned int>* ids) const;
// Reads the kernel_max_cpu file to determine the max CPU ID, i.e. 7 on an
// 8-core CPU.
virtual unsigned int GetKernelMaxCPUs() const;
// Reads the frequency from the CPUs being monitored and records them.
virtual void RecordFrequency(unsigned int cpu_id, unsigned int freq);
// Returns whether or not the tracing category our CPU Frequency counters are
// in is enabled to determine if we should record.
virtual bool IsTraceCategoryEnabled() const;
// Gets the path to CPU frequency related files for a particular CPU ID.
virtual std::string GetScalingCurFreqPathString(unsigned int cpu_id) const;
virtual std::string GetRelatedCPUsPathString(unsigned int cpu_id) const;
// Allows us to delay creating a task runner, necessary because many tests
// don't like us creating one outside of a ScopedTaskEnvironment.
virtual scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner();
private:
DISALLOW_COPY_AND_ASSIGN(CPUFreqMonitorDelegate);
};
// A class for monitoring the CPU frequency on unique cores/clusters.
class BASE_EXPORT CPUFreqMonitor : public TraceLog::EnabledStateObserver {
public:
// Overhead of reading one cluster on a Nexus 6P is ~0.1ms per CPU. 50ms seems
// frequent enough to get a general idea of CPU frequency trends.
static const size_t kDefaultCPUFreqSampleIntervalMs = 50;
CPUFreqMonitor();
~CPUFreqMonitor() override;
static CPUFreqMonitor* GetInstance();
void Start();
void Stop();
// TraceLog::EnabledStateObserver.
void OnTraceLogEnabled() override;
void OnTraceLogDisabled() override;
bool IsEnabledForTesting();
private:
friend class CPUFreqMonitorTest;
CPUFreqMonitor(std::unique_ptr<CPUFreqMonitorDelegate> delegate);
void Sample(std::vector<std::pair<unsigned int, base::ScopedFD>> fds);
// Uses the delegate's CreateTaskRunner function to lazily create a task
// runner so we don't illegally create a task runner on Chrome startup for
// various tests.
const scoped_refptr<SingleThreadTaskRunner>& GetOrCreateTaskRunner();
base::subtle::Atomic32 is_enabled_ = 0;
scoped_refptr<SingleThreadTaskRunner> task_runner_;
std::unique_ptr<CPUFreqMonitorDelegate> delegate_;
base::WeakPtrFactory<CPUFreqMonitor> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(CPUFreqMonitor);
};
} // namespace trace_event
} // namespace base
#endif // BASE_TRACE_EVENT_CPUFREQ_MONITOR_ANDROID_H_
This diff is collapsed.
......@@ -8,6 +8,7 @@
#include "base/logging.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/trace_log.h"
#include "build/build_config.h"
#include "components/tracing/common/trace_startup_config.h"
#include "components/tracing/common/trace_to_console.h"
#include "components/tracing/common/tracing_switches.h"
......
......@@ -156,6 +156,7 @@
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#include "base/trace_event/cpufreq_monitor_android.h"
#include "components/tracing/common/graphics_memory_dump_provider_android.h"
#include "content/browser/android/browser_startup_controller.h"
#include "content/browser/android/launcher_thread.h"
......@@ -777,6 +778,9 @@ void BrowserMainLoop::PostMainMessageLoopStart() {
screen_orientation_delegate_.reset(
new ScreenOrientationDelegateAndroid());
}
base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(
base::trace_event::CPUFreqMonitor::GetInstance());
#endif
if (parsed_command_line_.HasSwitch(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment