blob: 4fe9e6fd4dcc8938445946b5a11e53851e093e47 [file] [log] [blame]
// Copyright 2016 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 "src/performance/ktrace_provider/app.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fit/defer.h>
#include <lib/fxt/fields.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-engine/instrumentation.h>
#include <lib/trace-provider/provider.h>
#include <lib/zx/channel.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/syscalls/log.h>
#include <iterator>
#include "src/performance/ktrace_provider/device_reader.h"
namespace ktrace_provider {
namespace {
struct KTraceCategory {
const char* name;
uint32_t group;
const char* description;
};
constexpr KTraceCategory kGroupCategories[] = {
{"kernel", KTRACE_GRP_ALL, "All ktrace categories"},
{"kernel:meta", KTRACE_GRP_META, "Thread and process names"},
{"kernel:lifecycle", KTRACE_GRP_LIFECYCLE, "<unused>"},
{"kernel:sched", KTRACE_GRP_SCHEDULER, "Process and thread scheduling information"},
{"kernel:tasks", KTRACE_GRP_TASKS, "<unused>"},
{"kernel:ipc", KTRACE_GRP_IPC, "Emit an event for each FIDL call"},
{"kernel:irq", KTRACE_GRP_IRQ, "Emit a duration event for interrupts"},
{"kernel:probe", KTRACE_GRP_PROBE, "Userspace defined zx_ktrace_write events"},
{"kernel:arch", KTRACE_GRP_ARCH, "Hypervisor vcpus"},
{"kernel:syscall", KTRACE_GRP_SYSCALL, "Emit an event for each syscall"},
{"kernel:vm", KTRACE_GRP_VM, "Virtual memory events such as paging, mappings, and accesses"},
{"kernel:restricted", KTRACE_GRP_RESTRICTED,
"Duration events for when restricted mode is entered"},
};
// Meta category to retain current contents of ktrace buffer.
constexpr char kRetainCategory[] = "kernel:retain";
constexpr char kLogCategory[] = "log";
template <typename T>
void LogFidlFailure(const char* rqst_name, const fidl::Result<T>& result) {
if (result.is_error()) {
FX_LOGS(ERROR) << "Ktrace FIDL " << rqst_name
<< " failed: " << result.error_value().status_string();
} else if (result->status() != ZX_OK) {
FX_PLOGS(ERROR, result->status()) << "Ktrace " << rqst_name << " failed";
}
}
zx::result<> RequestKtraceStop(const zx::resource& debug_resource) {
return zx::make_result(zx_ktrace_control(debug_resource.get(), KTRACE_ACTION_STOP, 0, nullptr));
}
zx::result<> RequestKtraceRewind(const zx::resource& debug_resource) {
return zx::make_result(zx_ktrace_control(debug_resource.get(), KTRACE_ACTION_REWIND, 0, nullptr));
}
zx::result<> RequestKtraceStart(const zx::resource& debug_resource,
trace_buffering_mode_t buffering_mode, uint32_t group_mask) {
switch (buffering_mode) {
// ktrace does not currently support streaming, so for now we preserve the
// legacy behavior of falling back on one-shot mode.
case trace_buffering_mode_t::TRACE_BUFFERING_MODE_STREAMING:
case trace_buffering_mode_t::TRACE_BUFFERING_MODE_ONESHOT:
return zx::make_result(
zx_ktrace_control(debug_resource.get(), KTRACE_ACTION_START, group_mask, nullptr));
case trace_buffering_mode_t::TRACE_BUFFERING_MODE_CIRCULAR:
return zx::make_result(zx_ktrace_control(debug_resource.get(), KTRACE_ACTION_START_CIRCULAR,
group_mask, nullptr));
default:
return zx::error(ZX_ERR_INVALID_ARGS);
};
}
} // namespace
std::vector<trace::KnownCategory> GetKnownCategories() {
std::vector<trace::KnownCategory> known_categories = {
{.name = kRetainCategory,
.description = "Retain the previous contents of the buffer instead of clearing it out"},
};
for (const auto& category : kGroupCategories) {
known_categories.emplace_back(category.name, category.description);
}
return known_categories;
}
App::App(zx::resource debug_resource, const fxl::CommandLine& command_line)
: debug_resource_(std::move(debug_resource)) {
trace_observer_.Start(async_get_default_dispatcher(), [this] {
if (zx::result res = UpdateState(); res.is_error()) {
FX_PLOGS(ERROR, res.error_value()) << "Update state failed";
}
});
}
App::~App() = default;
zx::result<> App::UpdateState() {
uint32_t group_mask = 0;
bool capture_log = false;
bool retain_current_data = false;
if (trace_state() == TRACE_STARTED) {
size_t num_enabled_categories = 0;
for (const auto& category : kGroupCategories) {
if (trace_is_category_enabled(category.name)) {
group_mask |= category.group;
++num_enabled_categories;
}
}
// Avoid capturing log traces in the default case by detecting whether all
// categories are enabled or not.
capture_log = trace_is_category_enabled(kLogCategory) &&
num_enabled_categories != std::size(kGroupCategories);
// The default case is everything is enabled, but |kRetainCategory| must be
// explicitly passed.
retain_current_data = trace_is_category_enabled(kRetainCategory) &&
num_enabled_categories != std::size(kGroupCategories);
}
if (current_group_mask_ != group_mask) {
trace_context_t* ctx = trace_acquire_context();
if (zx::result res = StopKTrace(); res.is_error()) {
return res.take_error();
}
if (zx::result res =
StartKTrace(group_mask, trace_context_get_buffering_mode(ctx), retain_current_data);
res.is_error()) {
return res.take_error();
}
if (ctx != nullptr) {
trace_release_context(ctx);
}
}
if (capture_log) {
log_importer_.Start();
} else {
log_importer_.Stop();
}
return zx::ok();
}
zx::result<> App::StartKTrace(uint32_t group_mask, trace_buffering_mode_t buffering_mode,
bool retain_current_data) {
FX_DCHECK(!context_);
if (!group_mask) {
return zx::ok(); // nothing to trace
}
FX_LOGS(INFO) << "Starting ktrace";
context_ = trace_acquire_prolonged_context();
if (!context_) {
// Tracing was disabled in the meantime.
return zx::ok();
}
current_group_mask_ = group_mask;
if (zx::result res = RequestKtraceStop(debug_resource_); res.is_error()) {
return res.take_error();
}
if (!retain_current_data) {
if (zx::result res = RequestKtraceRewind(debug_resource_); res.is_error()) {
return res.take_error();
}
}
if (zx::result res = RequestKtraceStart(debug_resource_, buffering_mode, group_mask);
res.is_error()) {
return res.take_error();
}
FX_LOGS(DEBUG) << "Ktrace started";
return zx::ok();
}
void DrainBuffer(std::unique_ptr<DrainContext> drain_context) {
if (!drain_context) {
return;
}
trace_context_t* buffer_context = trace_acquire_context();
auto d = fit::defer([buffer_context]() { trace_release_context(buffer_context); });
for (std::optional<uint64_t> fxt_header = drain_context->reader.PeekNextHeader();
fxt_header.has_value(); fxt_header = drain_context->reader.PeekNextHeader()) {
size_t record_size_bytes = fxt::RecordFields::RecordSize::Get<size_t>(*fxt_header) * 8;
// We try to be a bit too clever here and check that there is enough space before writing a
// record to the buffer. If we're in streaming mode, and there isn't space for the record, this
// will show up as a dropped record even though we retry later. Unfortunately, there isn't
// currently a good api exposed.
//
// TODO(issues.fuchsia.dev/304532640): Investigate a method to allow trace providers to wait on
// a full buffer
if (void* dst = trace_context_alloc_record(buffer_context, record_size_bytes); dst != nullptr) {
const uint64_t* record = drain_context->reader.ReadNextRecord();
memcpy(dst, reinterpret_cast<const char*>(record), record_size_bytes);
} else {
if (trace_context_get_buffering_mode(buffer_context) == TRACE_BUFFERING_MODE_STREAMING) {
// We are writing out our data on the async loop. Notifying the trace manager to begin
// saving the data also requires the context and occurs on the loop. If we run out of space,
// we'll release the loop and reschedule ourself to allow the buffer saving to begin.
//
// We are memcpy'ing data here and trace_manager is writing the buffer to a socket (likely
// shared with ffx), the cost to copy the kernel buffer to the trace buffer here pales in
// comparison to the cost of what trace_manager is doing. We'll poll here with a slight
// delay until the buffer is ready.
async::PostDelayedTask(
async_get_default_dispatcher(),
[drain_context = std::move(drain_context)]() mutable {
DrainBuffer(std::move(drain_context));
},
zx::msec(100));
return;
}
// Outside of streaming mode, we aren't going to get more space. We'll need to read in this
// record and just drop it. Rather than immediately exiting, we allow the loop to continue so
// that we correctly enumerate all the dropped records for statistical reporting.
drain_context->reader.ReadNextRecord();
}
}
// Done writing trace data
size_t bytes_read = drain_context->reader.number_bytes_read();
zx::duration time_taken = zx::clock::get_monotonic() - drain_context->start;
double bytes_per_sec = static_cast<double>(bytes_read) /
static_cast<double>(std::max(int64_t{1}, time_taken.to_usecs()));
FX_LOGS(INFO) << "Import of " << drain_context->reader.number_records_read() << " kernel records"
<< "(" << bytes_read << " bytes) took: " << time_taken.to_msecs()
<< "ms. MBytes/sec: " << bytes_per_sec;
FX_LOGS(DEBUG) << "Ktrace stopped";
}
zx::result<> App::StopKTrace() {
if (!context_) {
return zx::ok(); // not currently tracing
}
auto d = fit::defer([this]() {
trace_release_prolonged_context(context_);
context_ = nullptr;
current_group_mask_ = 0u;
});
FX_DCHECK(current_group_mask_);
FX_LOGS(INFO) << "Stopping ktrace";
if (zx::result res = RequestKtraceStop(debug_resource_); res.is_error()) {
return res;
}
auto drain_context = DrainContext::Create(debug_resource_);
if (!drain_context) {
FX_LOGS(ERROR) << "Failed to start reading kernel buffer";
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx_status_t result = async::PostTask(async_get_default_dispatcher(),
[drain_context = std::move(drain_context)]() mutable {
DrainBuffer(std::move(drain_context));
});
if (result != ZX_OK) {
FX_PLOGS(ERROR, result) << "Failed to schedule buffer writer";
return zx::error(result);
}
return zx::ok();
}
} // namespace ktrace_provider