blob: 85768761e57b54e54d8652de2b32c5852b5e419d [file] [log] [blame]
// Copyright 2026 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/trace_manager/traceev2.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-engine/fields.h>
#include <lib/trace-provider/provider.h>
#include <memory>
#include <fbl/algorithm.h>
#include "src/performance/trace_manager/shared_buffer.h"
#include "src/performance/trace_manager/util.h"
#include "zircon/syscalls.h"
// LINT.IfChange
// Pulled from trace_engine's context_impl.h
static constexpr size_t kMaxDurableBufferSize = size_t{1024} * 1024;
// LINT.ThenChange(//zircon/system/ulib/trace-engine/context_impl.h)
namespace tracing {
using State = Tracee::State;
TraceeV2::TraceeV2(async::Executor& executor, std::shared_ptr<const BufferForwarder> output,
ProviderConnection* connection)
: output_(std::move(output)),
connection_(connection),
executor_(executor),
weak_ptr_factory_(this) {}
bool TraceeV2::operator==(ProviderConnection* connection) const {
return connection_ == connection;
}
bool TraceeV2::Initialize(std::vector<std::string> categories, size_t buffer_size,
fuchsia_tracing::BufferingMode buffering_mode) {
FX_DCHECK(state_ == State::kReady);
FX_DCHECK(!buffer_);
// HACK(https://fxbug.dev/308796439): Until we get kernel trace streaming, kernel tracing is
// special: it always allocates a fixed sized buffer in the kernel set by a boot arg. We're not at
// liberty here in trace_manager to check what the bootarg is, but the default is 32MB. For
// ktrace_provider, we should allocate a buffer at least large enough to hold the full kernel
// trace.
if (connection_->name == "ktrace_provider") {
buffer_size = std::max(buffer_size, size_t{32} * 1024 * 1024);
// In streaming and circular mode, part of the trace buffer will be reserved for the durable
// buffer. If ktrace attempts to write 32MiB of data, and our buffer is also 32MiB, we'll drop
// data because our usable buffer size will be slightly smaller.
//
// For the same reason, we need to add on some additional space for the metadata records that
// trace-engine writes since the partially fill the buffer.
if (buffering_mode != fuchsia_tracing::BufferingMode::kOneshot) {
buffer_size += kMaxDurableBufferSize + size_t{zx_system_get_page_size()};
}
}
zx::result<SharedBuffer> shared_buffer =
SharedBuffer::Create(buffer_size, buffering_mode, connection_->name, connection_->id);
if (shared_buffer.is_error()) {
return false;
}
zx::vmo buffer_vmo_for_provider;
if (zx_status_t status = shared_buffer->Vmo()->duplicate(
ZX_RIGHTS_BASIC | ZX_RIGHTS_IO | ZX_RIGHT_MAP, &buffer_vmo_for_provider);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << std::format("{} Failed to duplicate trace buffer for provider",
connection_->name);
return false;
}
fuchsia_tracing_provider::ProviderConfigV2 provider_config;
provider_config.buffering_mode(buffering_mode);
provider_config.buffer(std::move(buffer_vmo_for_provider));
provider_config.categories(std::move(categories));
auto result = connection_->provider->Initialize({std::move(provider_config)});
if (result.is_error()) {
FX_LOGS(ERROR) << std::format("{}: Failed to initialize provider: ", *connection_)
<< result.error_value();
return false;
}
buffer_ = std::move(*shared_buffer);
TransitionToState(State::kInitialized);
return true;
}
void TraceeV2::Terminate(fit::closure cb) {
if (state_ == State::kTerminating || state_ == State::kTerminated) {
return;
}
connection_->provider->Terminate().ThenExactlyOnce(
[weak = weak_ptr_factory_.GetWeakPtr(), cb = std::move(cb),
conn = this->connection_](fidl::Result<fuchsia_tracing_provider::ProviderV2::Terminate>& e) {
if (e.is_error()) {
FX_LOGS(ERROR) << std::format("{}: Failed to terminate provider: {}", *conn,
e.error_value().FormatDescription());
return;
}
if (weak) {
FX_DCHECK(weak->state() != State::kReady && weak->state() != State::kTerminated);
weak->TransitionToState(State::kTerminated);
}
cb();
});
TransitionToState(State::kTerminating);
}
void TraceeV2::Start(fuchsia_tracing::BufferDisposition buffer_disposition,
const std::vector<std::string>& additional_categories, fit::closure cb) {
// TraceSession should not call us unless we're ready, either because this
// is the first time, or subsequent times after tracing has fully stopped
// from the preceding time.
FX_DCHECK(state_ == State::kInitialized || state_ == State::kStopped);
fuchsia_tracing_provider::StartOptions start_options;
start_options.buffer_disposition(buffer_disposition);
start_options.additional_categories(additional_categories);
connection_->provider->Start({std::move(start_options)})
.ThenExactlyOnce([weak = weak_ptr_factory_.GetWeakPtr(), cb = std::move(cb),
conn = this->connection_](
fidl::Result<fuchsia_tracing_provider::ProviderV2::Start>& e) {
if (e.is_error()) {
FX_LOGS(ERROR) << std::format("{}: Failed to start provider: {}", *conn,
e.error_value().FormatDescription());
return;
}
if (weak) {
if (weak->state() == State::kStarting) {
weak->TransitionToState(State::kStarted);
cb();
} else {
FX_LOGS(WARNING) << std::format("{}: Received Started confirmation in state ", *conn)
<< weak->state();
}
}
});
TransitionToState(State::kStarting);
was_started_ = true;
results_written_ = false;
}
void TraceeV2::Stop(fit::closure cb) {
if (state_ != State::kStarting && state_ != State::kStarted) {
if (state_ == State::kInitialized) {
// We must have gotten added after tracing started while tracing was
// being stopped. Mark us as stopped so TraceSession won't try to wait
// for us to do so.
TransitionToState(State::kStopped);
}
return;
}
connection_->provider->Stop().ThenExactlyOnce(
[weak = weak_ptr_factory_.GetWeakPtr(), cb = std::move(cb),
conn = this->connection_](fidl::Result<fuchsia_tracing_provider::ProviderV2::Stop>& e) {
if (e.is_error()) {
FX_LOGS(ERROR) << std::format("{}: Failed to stop provider: {}", *conn,
e.error_value().FormatDescription());
return;
}
if (!weak) {
return;
}
switch (weak->state()) {
case Tracee::State::kReady:
case Tracee::State::kInitialized:
case Tracee::State::kStarting:
case Tracee::State::kStarted:
case Tracee::State::kStopped:
case Tracee::State::kTerminated:
FX_LOGS(WARNING) << std::format("{}: Received Stopped confirmation in state ", *conn)
<< weak->state();
break;
case Tracee::State::kStopping:
weak->TransitionToState(State::kStopped);
break;
case Tracee::State::kTerminating:
break;
}
cb();
});
TransitionToState(State::kStopping);
}
void TraceeV2::TransitionToState(State new_state) {
FX_LOGS(DEBUG) << std::format("{}: Transitioning from ", *connection_) << state_ << " to "
<< new_state;
state_ = new_state;
}
TransferStatus TraceeV2::TransferRecords() const {
FX_DCHECK(buffer_);
// Regardless of whether we succeed or fail, mark results as being written.
results_written_ = true;
auto [status, stats] = buffer_->TransferAll(output_);
if (status != TransferStatus::kComplete) {
return status;
}
provider_stats_.name(connection_->name);
provider_stats_.pid(connection_->pid);
provider_stats_.buffering_mode(buffer_->BufferingMode());
provider_stats_.buffer_wrapped_count(stats.wrapped_count);
provider_stats_.records_dropped(stats.records_dropped);
provider_stats_.percentage_durable_buffer_used(stats.durable_used_percent);
provider_stats_.non_durable_bytes_written(stats.non_durable_bytes_written);
return TransferStatus::kComplete;
}
std::optional<fuchsia_tracing_controller::ProviderStats> TraceeV2::GetStats() const {
if (state_ == State::kTerminated || state_ == State::kStopped) {
return std::move(provider_stats_);
}
return std::nullopt;
}
zx::result<> TraceeV2::TransferBuffer(uint32_t wrapped_count, uint64_t durable_data_end) {
FX_DCHECK(buffer_);
if (!buffer_->StreamingTransfer(output_, wrapped_count, durable_data_end)) {
return zx::error(ZX_ERR_BAD_STATE);
}
FX_LOGS(DEBUG) << std::format("Buffer saved for {}, wrapped_count={}, durable_data_end={}",
*connection_, wrapped_count, durable_data_end);
fit::result<fidl::OneWayError> res = connection_->provider->NotifyBufferSaved(
{{.wrapped_count = wrapped_count, .durable_data_end = durable_data_end}});
if (res.is_error()) {
return zx::error(res.error_value().status());
}
return zx::ok();
}
} // namespace tracing