blob: 8a0b093fbe0a7578926bf3b9430f8cc7e409a109 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "garnet/lib/cpuperf/controller.h"
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/string_printf.h>
#include <zircon/syscalls.h>
namespace cpuperf {
const char kCpuPerfDev[] = "/dev/sys/cpu-trace/cpuperf";
static Controller::Mode GetMode(const cpuperf_config_t& config) {
for (size_t i = 0; i < countof(config.rate); ++i) {
// If any event is doing sampling, then we're in "sample mode".
if (config.rate[i] != 0) {
return Controller::Mode::kSample;
}
}
return Controller::Mode::kTally;
}
static uint32_t GetBufferSize(Controller::Mode mode,
uint32_t requested_size_in_mb) {
switch (mode) {
case Controller::Mode::kSample:
return requested_size_in_mb * 1024 * 1024;
case Controller::Mode::kTally: {
// For "counting mode" we just need something large enough to hold
// the header + records for each event.
unsigned num_events = CPUPERF_MAX_EVENTS;
uint32_t size = (sizeof(cpuperf_buffer_header_t) +
num_events * sizeof(cpuperf_value_record_t));
return (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
}
default:
__UNREACHABLE;
}
}
bool Controller::IsSupported() {
// The device path isn't present if it's not supported.
struct stat stat_buffer;
if (stat(kCpuPerfDev, &stat_buffer) != 0)
return false;
return S_ISCHR(stat_buffer.st_mode);
}
bool Controller::GetProperties(cpuperf_properties_t* props) {
int raw_fd = open(kCpuPerfDev, O_WRONLY);
if (raw_fd < 0) {
FXL_LOG(ERROR) << "Failed to open " << kCpuPerfDev << ": errno=" << errno;
return false;
}
fxl::UniqueFD fd(raw_fd);
auto status = ioctl_cpuperf_get_properties(fd.get(), props);
if (status < 0)
FXL_LOG(ERROR) << "ioctl_cpuperf_get_properties failed: " << status;
return status >= 0;
}
bool Controller::Alloc(int fd, uint32_t num_traces, uint32_t buffer_size,
const cpuperf_config_t& config) {
ioctl_cpuperf_alloc_t alloc;
alloc.num_buffers = num_traces;
alloc.buffer_size = buffer_size;
FXL_VLOG(2) << fxl::StringPrintf("num_buffers=%u, buffer_size=0x%x",
alloc.num_buffers, alloc.buffer_size);
auto status = ioctl_cpuperf_alloc_trace(fd, &alloc);
// TODO(dje): If we get BAD_STATE, a previous run may have crashed without
// resetting the device. The device doesn't reset itself on close yet.
if (status == ZX_ERR_BAD_STATE) {
FXL_VLOG(1) << "Got BAD_STATE trying to allocate a trace,"
<< " resetting device and trying again";
status = ioctl_cpuperf_stop(fd);
if (status != ZX_OK) {
FXL_VLOG(1) << "Stopping device failed: " << status;
}
status = ioctl_cpuperf_free_trace(fd);
if (status != ZX_OK) {
FXL_VLOG(1) << "Freeing previous trace failed: " << status;
}
status = ioctl_cpuperf_alloc_trace(fd, &alloc);
if (status == ZX_OK) {
FXL_VLOG(1) << "Second allocation succeeded";
}
}
if (status != ZX_OK) {
FXL_LOG(ERROR) << "ioctl_cpuperf_alloc_trace failed: status=" << status;
return false;
}
return true;
}
bool Controller::Create(uint32_t buffer_size_in_mb,
const cpuperf_config_t& config,
std::unique_ptr<Controller>* out_controller) {
int raw_fd = open(kCpuPerfDev, O_WRONLY);
if (raw_fd < 0) {
FXL_LOG(ERROR) << "Failed to open " << kCpuPerfDev << ": errno=" << errno;
return false;
}
fxl::UniqueFD fd(raw_fd);
if (buffer_size_in_mb > kMaxBufferSizeInMb) {
FXL_LOG(ERROR) << "Buffer size is too large, max " << kMaxBufferSizeInMb
<< " MB";
return false;
}
Mode mode = GetMode(config);
uint32_t num_traces = zx_system_get_num_cpus();
uint32_t buffer_size = GetBufferSize(mode, buffer_size_in_mb);
if (!Alloc(raw_fd, num_traces, buffer_size, config)) {
return false;
}
out_controller->reset(new Controller(std::move(fd), mode, num_traces,
buffer_size, config));
return true;
}
Controller::Controller(fxl::UniqueFD fd, Mode mode, uint32_t num_traces,
uint32_t buffer_size, const cpuperf_config_t& config)
: fd_(std::move(fd)),
mode_(mode),
num_traces_(num_traces),
buffer_size_(buffer_size),
config_(config) {
}
Controller::~Controller() {
Reset();
}
bool Controller::Start() {
if (started_) {
FXL_LOG(ERROR) << "already started";
return false;
}
if (!Stage()) {
return false;
}
auto status = ioctl_cpuperf_start(fd_.get());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "ioctl_cpuperf_start failed: status=" << status;
} else {
started_ = true;
}
return status == ZX_OK;
}
void Controller::Stop() {
auto status = ioctl_cpuperf_stop(fd_.get());
if (status != ZX_OK) {
// This can get called while tracing is currently stopped.
if (!started_ && status == ZX_ERR_BAD_STATE) {
; // dont report an error in this case
} else {
FXL_LOG(ERROR) << "ioctl_cpuperf_stop failed: status=" << status;
}
} else {
started_ = false;
}
}
bool Controller::Stage() {
FXL_DCHECK(!started_);
auto status = ioctl_cpuperf_stage_config(fd_.get(), &config_);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "ioctl_cpuperf_stage_config failed: status=" << status;
}
return status == ZX_OK;
}
void Controller::Free() {
auto status = ioctl_cpuperf_free_trace(fd_.get());
if (status != ZX_OK) {
// This can get called while tracing is currently stopped.
if (!started_ && status == ZX_ERR_BAD_STATE) {
; // dont report an error in this case
} else {
FXL_LOG(ERROR) << "ioctl_cpuperf_free_trace failed: status=" << status;
}
}
}
void Controller::Reset() {
Stop();
Free();
}
std::unique_ptr<DeviceReader> Controller::GetReader() {
std::unique_ptr<DeviceReader> reader;
if (DeviceReader::Create(fd_.get(), buffer_size_, &reader)) {
return reader;
}
return nullptr;
}
} // namespace cpuperf