blob: a17e58b5b2d4da4c2d3e53f02431e807bd9cf722 [file] [log] [blame]
// Copyright 2020 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 "tracing.h"
#include <lib/fxt/fields.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zircon-internal/ktrace.h>
#include <fstream>
#include <trace-reader/reader.h>
// Performs same action as zx_ktrace_read and does necessary checks.
void Tracing::ReadKernelBuffer(zx_handle_t handle, void* data_buf, uint32_t offset, size_t len,
size_t* bytes_read) {
const zx_status_t status = zx_ktrace_read(handle, data_buf, offset, len, bytes_read);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "zx_trace_read";
}
}
// Rewinds kernel trace buffer.
void Tracing::Rewind() {
const zx_status_t status = zx_ktrace_control(debug_resource_, KTRACE_ACTION_REWIND, 0, nullptr);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "zx_ktrace_control(_, KTRACE_ACTION_REWIND, _, _)";
}
}
// Starts kernel tracing.
void Tracing::Start(uint32_t group_mask) {
const zx_status_t status =
zx_ktrace_control(debug_resource_, KTRACE_ACTION_START, group_mask, nullptr);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "zx_ktrace_control(_, KTRACE_ACTION_START, _, _)";
}
running_ = true;
}
// Stops kernel tracing.
void Tracing::Stop() {
const zx_status_t status = zx_ktrace_control(debug_resource_, KTRACE_ACTION_STOP, 0, nullptr);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "zx_ktrace_control(_, KTRACE_ACTION_STOP, _, _)";
}
running_ = false;
}
size_t Tracing::ReadKernelRecords(trace::TraceReader::RecordConsumer consumer,
trace::TraceReader::ErrorHandler error_handler) {
trace::TraceReader reader(std::move(consumer), std::move(error_handler));
size_t bytes_read = 0;
uint8_t buffer[4096];
size_t available_bytes = 0;
ReadKernelBuffer(debug_resource_, nullptr, 0, 0, &available_bytes);
size_t bytes_processed = 0;
while (bytes_processed < available_bytes) {
ReadKernelBuffer(debug_resource_, buffer, static_cast<uint32_t>(bytes_processed),
sizeof(buffer), &bytes_read);
if (bytes_read < 8) {
break;
}
trace::Chunk chunk{reinterpret_cast<uint64_t*>(buffer), bytes_read / 8};
reader.ReadRecords(chunk);
bytes_processed += bytes_read - (chunk.remaining_words() * 8);
}
return bytes_processed;
}
// Reads trace buffer and converts output into human-readable format. Stores in location defined by
// <filepath>. Will overwrite any existing files with same name.
bool Tracing::WriteHumanReadable(std::ostream& filepath) {
if (running_) {
FX_LOGS(WARNING) << "Tracing was running when human readable translation was started. Tracing "
"stopped.";
Stop();
}
if (!filepath) {
FX_LOGS(ERROR) << "Failed to open file.";
return false;
}
size_t record_count = 0;
size_t error_count = 0;
trace::TraceReader::RecordConsumer accumulate_records = [&record_count,
&filepath](trace::Record rec) {
filepath << rec.ToString() << "\n";
record_count++;
};
trace::TraceReader::ErrorHandler accumulate_errors = [&error_count](const fbl::String& error) {
FX_LOGS(ERROR) << error << "\n";
error_count++;
};
size_t bytes_read =
ReadKernelRecords(std::move(accumulate_records), std::move(accumulate_errors));
filepath << "\nTotal records read: " << std::dec << record_count
<< "\nTotal bytes read: " << std::dec << bytes_read << "\n";
return error_count == 0;
}
// Picks out traces pertaining to name in string_ref and populates stats on them. Returns false if
// name not found.
bool Tracing::PopulateDurationStats(std::string string_ref,
std::vector<DurationStats>* duration_stats,
std::map<uint64_t, QueuingStats>* queuing_stats) {
if (running_) {
FX_LOGS(WARNING) << "Tracing was running when duration stats were started. Tracing stopped.";
Stop();
}
size_t error_count = 0;
bool string_ref_found = false;
trace::TraceReader::RecordConsumer accumulate_records = [duration_stats, queuing_stats,
&string_ref_found,
&string_ref](trace::Record record) {
auto name = record.GetName();
if (name && *name == string_ref) {
string_ref_found = true;
// Match duration records for given string ref.
if (record.type() == trace::RecordType::kEvent) {
auto& event = record.GetEvent();
if (event.type() == trace::EventType::kDurationComplete) {
std::vector<trace::Argument> args;
for (auto&& argument : event.arguments) {
args.emplace_back(std::move(argument));
}
duration_stats->emplace_back(event.timestamp, event.data.GetDurationComplete().end_time,
event.data.GetDurationComplete().end_time - event.timestamp,
std::move(args));
} else if (event.type() == trace::EventType::kDurationBegin) {
duration_stats->emplace_back(event.timestamp);
} else if (event.type() == trace::EventType::kDurationEnd) {
for (auto duration = duration_stats->rbegin(); duration != duration_stats->rend();
duration++) {
if (duration->end_ts_ns == 0) {
duration->end_ts_ns = event.timestamp;
duration->wall_duration_ns = event.timestamp - duration->begin_ts_ns;
for (auto&& argument : event.arguments) {
duration->arguments.emplace_back(std::move(argument));
}
break;
}
}
} else if (event.type() == trace::EventType::kFlowBegin) {
auto [it, inserted] = queuing_stats->emplace(
event.data.GetFlowBegin().id,
QueuingStats(event.timestamp, event.process_thread.thread_koid()));
if (!inserted) {
FX_LOGS(ERROR) << "Failed to insert flow event of id: " << event.data.GetFlowBegin().id
<< ". Id already exists";
}
} else if (event.type() == trace::EventType::kFlowEnd) {
auto flow_iter = queuing_stats->find(event.data.GetFlowEnd().id);
if (flow_iter == queuing_stats->end()) {
return;
}
flow_iter->second.end_ts_ns = event.timestamp;
flow_iter->second.queuing_time_ns =
flow_iter->second.end_ts_ns - flow_iter->second.begin_ts_ns;
}
}
}
};
trace::TraceReader::ErrorHandler accumulate_errors = [&error_count](const fbl::String& error) {
FX_LOGS(ERROR) << error << "\n";
error_count++;
};
ReadKernelRecords(std::move(accumulate_records), std::move(accumulate_errors));
return string_ref_found && error_count == 0;
}