| // 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. |
| |
| // This file provides the FIDL interface for "trace record". |
| |
| #include "src/performance/trace/tracer.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 <memory> |
| #include <utility> |
| |
| #include <trace-reader/reader.h> |
| |
| namespace tracing { |
| |
| Tracer::Tracer(fidl::Client<controller::Provisioner> provisioner) |
| : provisioner_(std::move(provisioner)), dispatcher_(nullptr), wait_(this) { |
| FX_DCHECK(provisioner_); |
| wait_.set_trigger(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED); |
| } |
| |
| Tracer::~Tracer() { CloseSocket(); } |
| |
| void Tracer::Initialize(controller::TraceConfig config, bool binary, BytesConsumer bytes_consumer, |
| RecordConsumer record_consumer, ErrorHandler error_handler, |
| FailCallback fail_callback, DoneCallback done_callback, |
| AlertCallback alert_callback) { |
| FX_DCHECK(state_ == State::kReady); |
| |
| zx::socket outgoing_socket; |
| zx_status_t status = zx::socket::create(0u, &socket_, &outgoing_socket); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create socket: status=" << status; |
| Fail(); |
| return; |
| } |
| |
| auto [client_end, server_end] = fidl::Endpoints<controller::Session>::Create(); |
| auto res = provisioner_->InitializeTracing({{.controller = std::move(server_end), |
| .config = std::move(config), |
| .output = std::move(outgoing_socket)}}); |
| if (res.is_error()) { |
| FX_LOGS(ERROR) << "Failed to initialize tracing: " << res.error_value(); |
| Fail(); |
| return; |
| } |
| controller_ = |
| fidl::Client<controller::Session>{std::move(client_end), async_get_default_dispatcher()}; |
| BeginWatchAlert(); |
| |
| binary_ = binary; |
| bytes_consumer_ = std::move(bytes_consumer); |
| reader_ = |
| std::make_unique<trace::TraceReader>(std::move(record_consumer), std::move(error_handler)); |
| |
| fail_callback_ = std::move(fail_callback); |
| done_callback_ = std::move(done_callback); |
| alert_callback_ = std::move(alert_callback); |
| |
| dispatcher_ = async_get_default_dispatcher(); |
| wait_.set_object(socket_.get()); |
| status = wait_.Begin(dispatcher_); |
| FX_CHECK(status == ZX_OK) << "Failed to add handler: status=" << status; |
| |
| state_ = State::kInitialized; |
| } |
| |
| void Tracer::Start(StartCallback start_callback) { |
| FX_DCHECK(state_ == State::kInitialized); |
| |
| start_callback_ = std::move(start_callback); |
| |
| // All our categories are passed when we initialize, and we're just |
| // starting tracing so the buffer is already empty, so there's nothing to |
| // pass for |StartOptions| here. |
| controller_->StartTracing({}).Then( |
| [this](fidl::Result<controller::Session::StartTracing> result) { start_callback_(result); }); |
| |
| state_ = State::kStarted; |
| } |
| |
| void Tracer::Terminate() { |
| FX_DCHECK(state_ != State::kReady); |
| state_ = State::kTerminating; |
| controller::StopOptions stop_options{{.write_results = true}}; |
| controller_->StopTracing(std::move(stop_options)) |
| .Then([](const fidl::Result<controller::Session::StopTracing>& result) { |
| // TODO(dje): Print provider stats. |
| }); |
| // We're done. Close the fidl connection. |
| controller_ = fidl::Client<controller::Session>(); |
| } |
| |
| void Tracer::OnHandleReady(async_dispatcher_t* dispatcher, async::WaitBase* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| FX_DCHECK(state_ == State::kStarted || state_ == State::kTerminating); |
| |
| if (status != ZX_OK) { |
| OnHandleError(status); |
| return; |
| } |
| |
| if (signal->observed & ZX_SOCKET_READABLE) { |
| DrainSocket(dispatcher); |
| } else if (signal->observed & ZX_SOCKET_PEER_CLOSED) { |
| Done(); |
| } else { |
| FX_CHECK(false); |
| } |
| } |
| |
| void Tracer::DrainSocket(async_dispatcher_t* dispatcher) { |
| for (;;) { |
| size_t actual; |
| zx_status_t status = |
| socket_.read(0u, buffer_.data() + buffer_end_, buffer_.size() - buffer_end_, &actual); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| status = wait_.Begin(dispatcher); |
| if (status != ZX_OK) { |
| OnHandleError(status); |
| } |
| return; |
| } |
| |
| if (status || actual == 0) { |
| if (status == ZX_ERR_PEER_CLOSED) { |
| Done(); |
| } else { |
| FX_LOGS(ERROR) << "Failed to read data from socket: status=" << status; |
| Fail(); |
| } |
| return; |
| } |
| |
| buffer_end_ += actual; |
| size_t bytes_available = buffer_end_; |
| FX_DCHECK(bytes_available > 0); |
| |
| size_t bytes_consumed; |
| if (binary_) { |
| bytes_consumer_(buffer_.data(), bytes_available); |
| bytes_consumed = bytes_available; |
| } else { |
| trace::Chunk chunk(reinterpret_cast<const uint64_t*>(buffer_.data()), |
| trace::BytesToWords(bytes_available)); |
| if (!reader_->ReadRecords(chunk)) { |
| FX_LOGS(ERROR) << "Trace stream is corrupted"; |
| Fail(); |
| return; |
| } |
| bytes_consumed = bytes_available - trace::WordsToBytes(chunk.remaining_words()); |
| } |
| |
| bytes_available -= bytes_consumed; |
| memmove(buffer_.data(), buffer_.data() + bytes_consumed, bytes_available); |
| buffer_end_ = bytes_available; |
| } |
| } |
| |
| void Tracer::OnHandleError(zx_status_t status) { |
| FX_LOGS(ERROR) << "Failed to wait on socket: status=" << status; |
| Fail(); |
| } |
| |
| void Tracer::CloseSocket() { |
| if (socket_) { |
| wait_.Cancel(); |
| wait_.set_object(ZX_HANDLE_INVALID); |
| dispatcher_ = nullptr; |
| socket_.reset(); |
| } |
| } |
| |
| void Tracer::Fail() { fail_callback_(); } |
| |
| void Tracer::Done() { |
| FX_DCHECK(state_ == State::kStarted || state_ == State::kTerminating); |
| |
| state_ = State::kTerminated; |
| reader_.reset(); |
| |
| CloseSocket(); |
| |
| // TODO(dje): Watch for errors finishing writing of the trace file. |
| |
| if (done_callback_) { |
| async::PostTask(async_get_default_dispatcher(), std::move(done_callback_)); |
| } |
| } |
| |
| void Tracer::BeginWatchAlert() { |
| if (!controller_) { |
| FX_LOGS(ERROR) << "Session not initalized."; |
| return; |
| } |
| |
| controller_->WatchAlert().Then([this](fidl::Result<controller::Session::WatchAlert> result) { |
| if (result.is_error()) { |
| FX_LOGS(ERROR) << "Failed to watch alert: " << result.error_value(); |
| return; |
| } |
| alert_callback_(std::move(result->alert_name())); |
| BeginWatchAlert(); |
| }); |
| } |
| |
| } // namespace tracing |