blob: d3560c4f6ce9c1da11c95e5de9c2b3ec0bf90db6 [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/reader.h"
#include <inttypes.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <zircon/syscalls.h>
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
namespace cpuperf {
Reader::Reader(int fd, uint32_t buffer_size)
: fd_(fd), buffer_size_(buffer_size), num_cpus_(zx_system_get_num_cpus()) {
FXL_DCHECK(fd_ >= 0);
uintptr_t addr;
auto status = zx::vmar::root_self()->allocate(
0u, buffer_size_, ZX_VM_FLAG_CAN_MAP_READ, &vmar_, &addr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Unable to obtain vmar for reading trace data: "
<< status;
}
}
bool Reader::GetProperties(cpuperf_properties_t* props) {
auto status = ioctl_cpuperf_get_properties(fd_, props);
if (status < 0)
FXL_LOG(ERROR) << "ioctl_cpuperf_get_properties failed: " << status;
return status >= 0;
}
bool Reader::GetConfig(cpuperf_config_t* config) {
auto status = ioctl_cpuperf_get_config(fd_, config);
if (status < 0)
FXL_LOG(ERROR) << "ioctl_cpuperf_get_config failed: " << status;
return status >= 0;
}
bool Reader::MapBufferVmo(zx_handle_t vmo) {
uintptr_t addr;
current_vmo_.reset(vmo);
if (buffer_start_) {
addr = reinterpret_cast<uintptr_t>(buffer_start_);
auto status = vmar_.unmap(addr, buffer_size_);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Unable to unmap previous buffer vmo: " << status;
return false;
}
}
auto status =
vmar_.map(0, current_vmo_, 0, buffer_size_, ZX_VM_FLAG_PERM_READ, &addr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Unable to map buffer vmo: " << status;
return false;
}
buffer_start_ = reinterpret_cast<const uint8_t*>(addr);
return true;
}
static bool ReadBufferHeader(const void* buffer, uint32_t cpu,
cpuperf_buffer_header_t* hdr) {
memcpy(hdr, buffer, sizeof(*hdr));
FXL_LOG(INFO) << "cpu " << cpu << ": buffer version " << hdr->version << ", "
<< hdr->capture_end << " bytes";
uint32_t expected_version = CPUPERF_BUFFER_VERSION;
if (hdr->version != expected_version) {
FXL_LOG(ERROR) << "Unsupported buffer version, got " << hdr->version
<< " instead of " << expected_version;
return false;
}
uint64_t kernel_ticks_per_second = hdr->ticks_per_second;
uint64_t user_ticks_per_second = zx_ticks_per_second();
if (kernel_ticks_per_second != user_ticks_per_second) {
FXL_LOG(WARNING) << "Kernel and userspace are using different tracing"
<< " timebases, tracks may be misaligned:"
<< " kernel_ticks_per_second=" << kernel_ticks_per_second
<< " user_ticks_per_second=" << user_ticks_per_second;
}
return true;
}
bool Reader::ReadNextRecord(uint32_t* cpu, SampleRecord* record) {
while (current_cpu_ < num_cpus_) {
// If this is the first cpu, or if we're done with this cpu's records,
// move to the next cpu.
if (next_record_ == nullptr || next_record_ >= capture_end_) {
if (next_record_ != nullptr)
++current_cpu_;
if (current_cpu_ >= num_cpus_)
break;
ioctl_cpuperf_buffer_handle_req_t req;
req.descriptor = current_cpu_;
zx_handle_t handle;
auto ioctl_status = ioctl_cpuperf_get_buffer_handle(fd_, &req, &handle);
if (ioctl_status < 0) {
FXL_LOG(ERROR) << "ioctl_cpuperf_get_buffer_handle failed: "
<< ioctl_status;
return false;
}
// Out with the old, in with the new.
if (!MapBufferVmo(handle))
return false;
cpuperf_buffer_header_t header;
if (!ReadBufferHeader(buffer_start_, current_cpu_, &header))
return false;
next_record_ = buffer_start_ + sizeof(header);
capture_end_ = buffer_start_ + header.capture_end;
ticks_per_second_ = header.ticks_per_second;
if (next_record_ > capture_end_) {
FXL_LOG(WARNING) << "Bad trace data for cpu " << current_cpu_
<< ", end point within header";
continue;
}
if (next_record_ == capture_end_)
continue;
}
const cpuperf_record_header_t* hdr =
reinterpret_cast<const cpuperf_record_header_t*>(next_record_);
if (next_record_ + sizeof(*hdr) > capture_end_) {
FXL_LOG(WARNING) << "Bad trace data for cpu " << current_cpu_
<< ", no space for final record header";
// Bump |next_record_| so that we'll skip to the next cpu.
next_record_ = capture_end_;
continue;
}
auto record_type = RecordType(hdr);
auto record_size = RecordSize(hdr);
if (record_size == 0) {
FXL_LOG(WARNING) << "Bad trace data for cpu " << current_cpu_
<< ", bad record type: "
<< static_cast<unsigned>(hdr->type);
// Bump |next_record_| so that we'll skip to the next cpu.
next_record_ = capture_end_;
continue;
}
if (next_record_ + record_size > capture_end_) {
FXL_LOG(WARNING) << "Bad trace data for cpu " << current_cpu_
<< ", no space for final record";
// Bump |next_record_| so that we'll skip to the next cpu.
next_record_ = capture_end_;
continue;
}
// There can be millions of records. This is useful for small test runs,
// but otherwise is too painful. The verbosity level is chosen to
// recognize that.
FXL_VLOG(10) << fxl::StringPrintf("ReadNextRecord: cpu=%u, offset=%zu",
current_cpu_,
next_record_ - buffer_start_);
switch (record_type) {
case CPUPERF_RECORD_TIME:
record->time =
reinterpret_cast<const cpuperf_time_record_t*>(next_record_);
time_ = record->time->time;
break;
case CPUPERF_RECORD_TICK:
record->tick =
reinterpret_cast<const cpuperf_tick_record_t*>(next_record_);
break;
case CPUPERF_RECORD_COUNT:
record->count =
reinterpret_cast<const cpuperf_count_record_t*>(next_record_);
break;
case CPUPERF_RECORD_VALUE:
record->value =
reinterpret_cast<const cpuperf_value_record_t*>(next_record_);
break;
case CPUPERF_RECORD_PC:
record->pc = reinterpret_cast<const cpuperf_pc_record_t*>(next_record_);
break;
default:
// We shouldn't get here because RecordSize() should have returned
// zero and we would have skipped to the next cpu.
FXL_NOTREACHED();
}
next_record_ += record_size;
*cpu = current_cpu_;
return true;
}
return false;
}
cpuperf_record_type_t Reader::RecordType(const cpuperf_record_header_t* hdr) {
switch (hdr->type) {
case CPUPERF_RECORD_TIME:
case CPUPERF_RECORD_TICK:
case CPUPERF_RECORD_COUNT:
case CPUPERF_RECORD_VALUE:
case CPUPERF_RECORD_PC:
return static_cast<cpuperf_record_type_t>(hdr->type);
default:
return CPUPERF_RECORD_RESERVED;
}
}
size_t Reader::RecordSize(const cpuperf_record_header_t* hdr) {
switch (hdr->type) {
case CPUPERF_RECORD_TIME:
return sizeof(cpuperf_time_record_t);
case CPUPERF_RECORD_TICK:
return sizeof(cpuperf_tick_record_t);
case CPUPERF_RECORD_COUNT:
return sizeof(cpuperf_count_record_t);
case CPUPERF_RECORD_VALUE:
return sizeof(cpuperf_value_record_t);
case CPUPERF_RECORD_PC:
return sizeof(cpuperf_pc_record_t);
default:
return 0;
}
}
} // namespace cpuperf