blob: 439e8003e223028afcd6fd5e05fab7652bfceefd [file] [log] [blame] [edit]
// 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/perfmon/controller.h"
#include <fuchsia/perfmon/cpu/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/syslog/cpp/macros.h>
#include <sys/stat.h>
#include <fbl/algorithm.h>
#include "garnet/lib/perfmon/config_impl.h"
#include "garnet/lib/perfmon/controller_impl.h"
#include "garnet/lib/perfmon/device_reader.h"
#include "garnet/lib/perfmon/properties_impl.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace perfmon {
// Shorten some long FIDL names.
using FidlPerfmonAllocation = ::fuchsia::perfmon::cpu::Allocation;
const char kPerfMonDev[] = "/dev/sys/cpu-trace/perfmon";
static uint32_t RoundUpToPages(uint32_t value) {
uint32_t size = fbl::round_up(value, Controller::kPageSize);
FX_DCHECK(size & ~(Controller::kPageSize - 1));
return size >> Controller::kLog2PageSize;
}
static uint32_t GetBufferSizeInPages(CollectionMode mode, uint32_t requested_size_in_pages) {
switch (mode) {
case CollectionMode::kSample:
return requested_size_in_pages;
case CollectionMode::kTally: {
// For tally mode we just need something large enough to hold
// the header + records for each event.
unsigned num_events = kMaxNumEvents;
uint32_t size = (sizeof(BufferHeader) + num_events * sizeof(ValueRecord));
return RoundUpToPages(size);
}
default:
__UNREACHABLE;
}
}
bool Controller::IsSupported() {
// The device path isn't present if it's not supported.
struct stat stat_buffer;
if (stat(kPerfMonDev, &stat_buffer) != 0)
return false;
return S_ISCHR(stat_buffer.st_mode);
}
bool Controller::GetProperties(Properties* props) {
::fuchsia::perfmon::cpu::ControllerSyncPtr controller_ptr;
zx_status_t status =
fdio_service_connect(kPerfMonDev, controller_ptr.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Error connecting to " << kPerfMonDev << ": " << status;
return false;
}
FidlPerfmonProperties properties;
status = controller_ptr->GetProperties(&properties);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get properties: " << status;
return false;
}
internal::FidlToPerfmonProperties(properties, props);
return true;
}
static bool Initialize(::fuchsia::perfmon::cpu::ControllerSyncPtr* controller_ptr,
uint32_t num_traces, uint32_t buffer_size_in_pages) {
FidlPerfmonAllocation allocation;
allocation.num_buffers = num_traces;
allocation.buffer_size_in_pages = buffer_size_in_pages;
FX_VLOGS(2) << fxl::StringPrintf("num_buffers=%u, buffer_size_in_pages=0x%x", num_traces,
buffer_size_in_pages);
::fuchsia::perfmon::cpu::Controller_Initialize_Result result;
zx_status_t status = (*controller_ptr)->Initialize(allocation, &result);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Initialize failed: status=" << status;
return false;
}
if (result.is_err() && result.err() != ZX_ERR_BAD_STATE) {
FX_LOGS(ERROR) << "Initialize failed: error=" << result.err();
return false;
}
// 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 (result.is_err()) {
FX_DCHECK(result.err() == ZX_ERR_BAD_STATE);
FX_VLOGS(2) << "Got BAD_STATE trying to initialize a trace,"
<< " resetting device and trying again";
status = (*controller_ptr)->Stop();
if (status != ZX_OK) {
FX_VLOGS(2) << "Stopping device failed: status=" << status;
return false;
}
status = (*controller_ptr)->Terminate();
if (status != ZX_OK) {
FX_VLOGS(2) << "Terminating previous trace failed: status=" << status;
return false;
}
status = (*controller_ptr)->Initialize(allocation, &result);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Initialize try #2 failed: status=" << status;
return false;
}
if (result.is_err()) {
FX_LOGS(ERROR) << "Initialize try #2 failed: error=" << result.err();
return false;
}
FX_VLOGS(2) << "Second Initialize attempt succeeded";
}
return true;
}
bool Controller::Create(uint32_t buffer_size_in_pages, const Config config,
std::unique_ptr<Controller>* out_controller) {
if (buffer_size_in_pages > kMaxBufferSizeInPages) {
FX_LOGS(ERROR) << "Buffer size is too large, max " << kMaxBufferSizeInPages << " pages";
return false;
}
::fuchsia::perfmon::cpu::ControllerSyncPtr controller_ptr;
zx_status_t status =
fdio_service_connect(kPerfMonDev, controller_ptr.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Error connecting to " << kPerfMonDev << ": " << status;
return false;
}
CollectionMode mode = config.GetMode();
uint32_t num_traces = zx_system_get_num_cpus();
// For "tally" mode we only need a small fixed amount, so toss what the
// caller provided and use our own value.
uint32_t actual_buffer_size_in_pages = GetBufferSizeInPages(mode, buffer_size_in_pages);
if (!Initialize(&controller_ptr, num_traces, actual_buffer_size_in_pages)) {
return false;
}
out_controller->reset(new internal::ControllerImpl(std::move(controller_ptr), num_traces,
buffer_size_in_pages, std::move(config)));
return true;
}
} // namespace perfmon