| // 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 "garnet/bin/trace_manager/tracee.h" |
| |
| #include <fbl/algorithm.h> |
| #include <lib/async/default.h> |
| #include <trace-engine/fields.h> |
| #include <trace-provider/provider.h> |
| |
| #include "garnet/bin/trace_manager/trace_session.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace tracing { |
| |
| namespace { |
| |
| // Writes |len| bytes from |buffer| to |socket|. Returns |
| // TransferStatus::kComplete if the entire buffer has been |
| // successfully transferred. A return value of |
| // TransferStatus::kReceiverDead indicates that the peer was closed |
| // during the transfer. |
| Tracee::TransferStatus WriteBufferToSocket(const zx::socket& socket, |
| const void* buffer, size_t len) { |
| auto data = reinterpret_cast<const uint8_t*>(buffer); |
| size_t offset = 0; |
| while (offset < len) { |
| zx_status_t status = ZX_OK; |
| size_t actual = 0; |
| if ((status = socket.write(0u, data + offset, len - offset, &actual)) < 0) { |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| zx_signals_t pending = 0; |
| status = socket.wait_one(ZX_SOCKET_WRITABLE | ZX_SOCKET_PEER_CLOSED, |
| zx::time::infinite(), &pending); |
| if (status < 0) { |
| FXL_LOG(ERROR) << "Wait on socket failed: " << status; |
| return Tracee::TransferStatus::kWriteError; |
| } |
| |
| if (pending & ZX_SOCKET_WRITABLE) |
| continue; |
| |
| if (pending & ZX_SOCKET_PEER_CLOSED) { |
| FXL_LOG(ERROR) << "Peer closed while writing to socket"; |
| return Tracee::TransferStatus::kReceiverDead; |
| } |
| } |
| |
| return Tracee::TransferStatus::kWriteError; |
| } |
| offset += actual; |
| } |
| |
| return Tracee::TransferStatus::kComplete; |
| } |
| |
| fuchsia::tracelink::BufferingMode EngineBufferingModeToTracelinkMode( |
| trace_buffering_mode_t mode) { |
| switch (mode) { |
| case TRACE_BUFFERING_MODE_ONESHOT: |
| return fuchsia::tracelink::BufferingMode::ONESHOT; |
| case TRACE_BUFFERING_MODE_CIRCULAR: |
| return fuchsia::tracelink::BufferingMode::CIRCULAR; |
| case TRACE_BUFFERING_MODE_STREAMING: |
| return fuchsia::tracelink::BufferingMode::STREAMING; |
| default: |
| __UNREACHABLE; |
| } |
| } |
| |
| uint64_t GetBufferWordsWritten(const uint64_t* buffer, uint64_t size_in_words) { |
| const uint64_t* start = buffer; |
| const uint64_t* current = start; |
| const uint64_t* end = start + size_in_words; |
| |
| while (current < end) { |
| auto length = trace::RecordFields::RecordSize::Get<uint16_t>(*current); |
| if (length == 0 || length > trace::RecordFields::kMaxRecordSizeBytes || |
| current + length >= end) { |
| break; |
| } |
| current += length; |
| } |
| |
| return current - start; |
| } |
| |
| } // namespace |
| |
| Tracee::Tracee(const TraceSession* session, const TraceProviderBundle* bundle) |
| : session_(session), |
| bundle_(bundle), |
| wait_(this), |
| weak_ptr_factory_(this) {} |
| |
| Tracee::~Tracee() { |
| if (dispatcher_) { |
| wait_.Cancel(); |
| wait_.set_object(ZX_HANDLE_INVALID); |
| dispatcher_ = nullptr; |
| } |
| } |
| |
| bool Tracee::operator==(TraceProviderBundle* bundle) const { |
| return bundle_ == bundle; |
| } |
| |
| bool Tracee::Start(fidl::VectorPtr<std::string> categories, |
| size_t buffer_size, |
| fuchsia::tracelink::BufferingMode buffering_mode, |
| fit::closure started_callback, |
| fit::closure stopped_callback) { |
| FXL_DCHECK(state_ == State::kReady); |
| FXL_DCHECK(!buffer_vmo_); |
| FXL_DCHECK(started_callback); |
| FXL_DCHECK(stopped_callback); |
| |
| zx::vmo buffer_vmo; |
| zx_status_t status = zx::vmo::create(buffer_size, 0u, &buffer_vmo); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to create trace buffer: status=" << status; |
| return false; |
| } |
| |
| zx::vmo buffer_vmo_for_provider; |
| status = buffer_vmo.duplicate(ZX_RIGHTS_BASIC | ZX_RIGHTS_IO | ZX_RIGHT_MAP, |
| &buffer_vmo_for_provider); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to duplicate trace buffer for provider: status=" |
| << status; |
| return false; |
| } |
| |
| zx::fifo fifo, fifo_for_provider; |
| status = zx::fifo::create(kFifoSizeInPackets, sizeof(trace_provider_packet_t), |
| 0u, &fifo, &fifo_for_provider); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to create trace buffer fifo: status=" << status; |
| return false; |
| } |
| |
| bundle_->provider->Start(buffering_mode, std::move(buffer_vmo_for_provider), |
| std::move(fifo_for_provider), std::move(categories)); |
| |
| buffering_mode_ = buffering_mode; |
| buffer_vmo_ = std::move(buffer_vmo); |
| buffer_vmo_size_ = buffer_size; |
| fifo_ = std::move(fifo); |
| started_callback_ = std::move(started_callback); |
| stopped_callback_ = std::move(stopped_callback); |
| wait_.set_object(fifo_.get()); |
| wait_.set_trigger(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED); |
| dispatcher_ = async_get_default_dispatcher(); |
| status = wait_.Begin(dispatcher_); |
| FXL_CHECK(status == ZX_OK) << "Failed to add handler: status=" << status; |
| TransitionToState(State::kStartPending); |
| return true; |
| } |
| |
| void Tracee::Stop() { |
| if (state_ != State::kStarted) |
| return; |
| bundle_->provider->Stop(); |
| TransitionToState(State::kStopping); |
| } |
| |
| void Tracee::TransitionToState(State new_state) { |
| FXL_VLOG(2) << *bundle_ << ": Transitioning from " << state_ << " to " |
| << new_state; |
| state_ = new_state; |
| } |
| |
| void Tracee::OnHandleReady(async_dispatcher_t* dispatcher, |
| async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| OnHandleError(status); |
| return; |
| } |
| |
| zx_signals_t pending = signal->observed; |
| FXL_VLOG(2) << *bundle_ << ": pending=0x" << std::hex << pending; |
| FXL_DCHECK(pending & (ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED)); |
| FXL_DCHECK(state_ == State::kStartPending || state_ == State::kStarted || |
| state_ == State::kStopping); |
| |
| if (pending & ZX_FIFO_READABLE) { |
| OnFifoReadable(dispatcher, wait); |
| // Keep reading packets, one per call, until the peer goes away. |
| status = wait->Begin(dispatcher); |
| if (status != ZX_OK) |
| OnHandleError(status); |
| return; |
| } |
| |
| FXL_DCHECK(pending & ZX_FIFO_PEER_CLOSED); |
| wait_.set_object(ZX_HANDLE_INVALID); |
| dispatcher_ = nullptr; |
| TransitionToState(State::kStopped); |
| fit::closure stopped_callback = std::move(stopped_callback_); |
| FXL_DCHECK(stopped_callback); |
| stopped_callback(); |
| } |
| |
| void Tracee::OnFifoReadable(async_dispatcher_t* dispatcher, |
| async::WaitBase* wait) { |
| trace_provider_packet_t packet; |
| auto status2 = |
| zx_fifo_read(wait_.object(), sizeof(packet), &packet, 1u, nullptr); |
| FXL_DCHECK(status2 == ZX_OK); |
| if (packet.reserved != 0) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Received bad packet, non-zero reserved field: " |
| << packet.reserved; |
| Stop(); |
| return; |
| } |
| |
| switch (packet.request) { |
| case TRACE_PROVIDER_STARTED: |
| // The provider should only be signalling us when it has finished |
| // startup. |
| if (packet.data32 != TRACE_PROVIDER_FIFO_PROTOCOL_VERSION) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Received bad packet, unexpected version: " |
| << packet.data32; |
| Stop(); |
| break; |
| } |
| if (packet.data64 != 0) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Received bad packet, non-zero data64 field: " |
| << packet.data64; |
| Stop(); |
| break; |
| } |
| if (state_ == State::kStartPending) { |
| TransitionToState(State::kStarted); |
| fit::closure started_callback = std::move(started_callback_); |
| FXL_DCHECK(started_callback); |
| started_callback(); |
| } else { |
| FXL_LOG(WARNING) << *bundle_ |
| << ": Received TRACE_PROVIDER_STARTED in state " |
| << state_; |
| } |
| break; |
| case TRACE_PROVIDER_SAVE_BUFFER: |
| if (buffering_mode_ != fuchsia::tracelink::BufferingMode::STREAMING) { |
| FXL_LOG(WARNING) << *bundle_ |
| << ": Received TRACE_PROVIDER_SAVE_BUFFER in mode " |
| << ModeName(buffering_mode_); |
| } else if (state_ == State::kStarted || state_ == State::kStopping) { |
| uint32_t wrapped_count = packet.data32; |
| uint64_t durable_data_end = packet.data64; |
| // Schedule the write with the main async loop. |
| FXL_VLOG(2) << "Buffer save request from " << *bundle_ |
| << ", wrapped_count=" << wrapped_count |
| << ", durable_data_end=0x" << std::hex << durable_data_end; |
| async::PostTask(dispatcher_, |
| [weak = weak_ptr_factory_.GetWeakPtr(), |
| wrapped_count, durable_data_end] { |
| if (weak) { |
| weak->TransferBuffer(weak->session_->destination(), |
| wrapped_count, durable_data_end); |
| } |
| }); |
| } else { |
| FXL_LOG(WARNING) << *bundle_ |
| << ": Received TRACE_PROVIDER_SAVE_BUFFER in state " |
| << state_; |
| } |
| break; |
| default: |
| FXL_LOG(ERROR) << *bundle_ << ": Received bad packet, unknown request: " |
| << packet.request; |
| Stop(); |
| break; |
| } |
| } |
| |
| void Tracee::OnHandleError(zx_status_t status) { |
| FXL_VLOG(2) << *bundle_ << ": error=" << status; |
| FXL_DCHECK(status == ZX_ERR_CANCELED); |
| FXL_DCHECK(state_ == State::kStartPending || state_ == State::kStarted || |
| state_ == State::kStopping); |
| wait_.set_object(ZX_HANDLE_INVALID); |
| dispatcher_ = nullptr; |
| TransitionToState(State::kStopped); |
| } |
| |
| bool Tracee::VerifyBufferHeader( |
| const trace::internal::BufferHeaderReader* header) const { |
| if (EngineBufferingModeToTracelinkMode(static_cast<trace_buffering_mode_t>( |
| header->buffering_mode())) != buffering_mode_) { |
| FXL_LOG(ERROR) << *bundle_ << ": header corrupt, wrong buffering mode: " |
| << header->buffering_mode(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Tracee::TransferStatus Tracee::DoWriteChunk(const zx::socket& socket, |
| uint64_t vmo_offset, uint64_t size, |
| const char* name, |
| bool by_size) const { |
| FXL_VLOG(2) << *bundle_ << ": Writing chunk for " << name << ": vmo offset 0x" |
| << std::hex << vmo_offset << ", size 0x" << std::hex << size |
| << (by_size ? ", by-size" : ", by-record"); |
| |
| // TODO(dje): Loop on smaller buffer. |
| // Better yet, be able to pass the entire vmo to the socket (still need to |
| // support multiple chunks: the writer will need vmo,offset,size parameters). |
| |
| uint64_t size_in_words = trace::BytesToWords(size); |
| // For paranoia purposes verify size is a multiple of the word size so we |
| // don't risk overflowing the buffer later. |
| FXL_DCHECK(trace::WordsToBytes(size_in_words) == size); |
| std::vector<uint64_t> buffer(size_in_words); |
| |
| if (buffer_vmo_.read(buffer.data(), vmo_offset, size) != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ << ": Failed to read data from buffer_vmo: " |
| << "offset=" << vmo_offset << ", size=" << size; |
| return TransferStatus::kProviderError; |
| } |
| |
| uint64_t bytes_written; |
| if (!by_size) { |
| uint64_t words_written = GetBufferWordsWritten(buffer.data(), size_in_words); |
| bytes_written = trace::WordsToBytes(words_written); |
| } else { |
| bytes_written = size; |
| } |
| |
| auto status = WriteBufferToSocket(socket, buffer.data(), bytes_written); |
| if (status != TransferStatus::kComplete) { |
| FXL_LOG(ERROR) << *bundle_ << ": Failed to write " << name << " records"; |
| } |
| return status; |
| } |
| |
| Tracee::TransferStatus Tracee::WriteChunkByRecords(const zx::socket& socket, |
| uint64_t vmo_offset, |
| uint64_t size, |
| const char* name) const { |
| return DoWriteChunk(socket, vmo_offset, size, name, false); |
| } |
| |
| Tracee::TransferStatus Tracee::WriteChunkBySize(const zx::socket& socket, |
| uint64_t vmo_offset, |
| uint64_t size, |
| const char* name) const { |
| return DoWriteChunk(socket, vmo_offset, size, name, true); |
| } |
| |
| Tracee::TransferStatus Tracee::WriteChunk(const zx::socket& socket, |
| uint64_t offset, uint64_t last, |
| uint64_t end, uint64_t buffer_size, |
| const char* name) const { |
| ZX_DEBUG_ASSERT(last <= buffer_size); |
| ZX_DEBUG_ASSERT(end <= buffer_size); |
| ZX_DEBUG_ASSERT(end == 0 || last <= end); |
| offset += last; |
| if (buffering_mode_ == fuchsia::tracelink::BufferingMode::ONESHOT || |
| // If end is zero then the header wasn't updated when tracing stopped. |
| end == 0) { |
| uint64_t size = buffer_size - last; |
| return WriteChunkByRecords(socket, offset, size, name); |
| } else { |
| uint64_t size = end - last; |
| return WriteChunkBySize(socket, offset, size, name); |
| } |
| } |
| |
| Tracee::TransferStatus Tracee::TransferRecords(const zx::socket& socket) const { |
| FXL_DCHECK(socket); |
| FXL_DCHECK(buffer_vmo_); |
| |
| auto transfer_status = TransferStatus::kComplete; |
| |
| if ((transfer_status = WriteProviderIdRecord(socket)) != |
| TransferStatus::kComplete) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to write provider info record to trace."; |
| return transfer_status; |
| } |
| |
| trace::internal::trace_buffer_header header_buffer; |
| if (buffer_vmo_.read(&header_buffer, 0, sizeof(header_buffer)) != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ << ": Failed to read header from buffer_vmo"; |
| return TransferStatus::kProviderError; |
| } |
| |
| fbl::unique_ptr<trace::internal::BufferHeaderReader> header; |
| auto error = trace::internal::BufferHeaderReader::Create( |
| &header_buffer, buffer_vmo_size_, &header); |
| if (error != "") { |
| FXL_LOG(ERROR) << *bundle_ << ": header corrupt, " << error.c_str(); |
| return TransferStatus::kProviderError; |
| } |
| if (!VerifyBufferHeader(header.get())) { |
| return TransferStatus::kProviderError; |
| } |
| |
| if (header->num_records_dropped() > 0) { |
| FXL_LOG(WARNING) << *bundle_ << ": " << header->num_records_dropped() |
| << " records were dropped"; |
| // If we can't write the buffer overflow record, it's not the end of the |
| // world. |
| if (WriteProviderBufferOverflowEvent(socket) != TransferStatus::kComplete) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to write provider event (buffer overflow)" |
| " record to trace."; |
| } |
| } |
| |
| if (buffering_mode_ != fuchsia::tracelink::BufferingMode::ONESHOT) { |
| uint64_t offset = header->get_durable_buffer_offset(); |
| uint64_t last = last_durable_data_end_; |
| uint64_t end = header->durable_data_end(); |
| uint64_t buffer_size = header->durable_buffer_size(); |
| if ((transfer_status = WriteChunk(socket, offset, last, end, buffer_size, |
| "durable")) != |
| TransferStatus::kComplete) { |
| return transfer_status; |
| } |
| } |
| |
| // There's only two buffers, thus the earlier one is not the current one. |
| // It's important to process them in chronological order on the off |
| // chance that the earlier buffer provides a stringref or threadref |
| // referenced by the later buffer. |
| // |
| // We want to handle the case of still capturing whatever records we can if |
| // the process crashes, in which case the header won't be up to date. In |
| // oneshot mode we're covered: We run through the records and see what's |
| // there. In circular and streaming modes after a buffer gets reused we can't |
| // do that. But if the process crashes it may be the last trace records that |
| // are important: we don't want to lose them. As a compromise, if the header |
| // is marked as valid use it. Otherwise run through the buffer to count the |
| // records we see. |
| |
| auto write_rolling_chunk = [this, &header, &socket](int buffer_number) |
| -> Tracee::TransferStatus { |
| uint64_t offset = header->GetRollingBufferOffset(buffer_number); |
| uint64_t last = 0; |
| uint64_t end = header->rolling_data_end(buffer_number); |
| uint64_t buffer_size = header->rolling_buffer_size(); |
| auto name = buffer_number == 0 ? "rolling buffer 0" : "rolling buffer 1"; |
| return WriteChunk(socket, offset, last, end, buffer_size, name); |
| }; |
| |
| if (header->wrapped_count() > 0) { |
| int buffer_number = get_buffer_number(header->wrapped_count() - 1); |
| transfer_status = write_rolling_chunk(buffer_number); |
| if (transfer_status != TransferStatus::kComplete) { |
| return transfer_status; |
| } |
| } |
| int buffer_number = get_buffer_number(header->wrapped_count()); |
| transfer_status = write_rolling_chunk(buffer_number); |
| if (transfer_status != TransferStatus::kComplete) { |
| return transfer_status; |
| } |
| |
| // Print some stats to assist things like buffer size calculations. |
| // Don't print anything if nothing was written. |
| if ((header->buffering_mode() == TRACE_BUFFERING_MODE_ONESHOT && |
| header->rolling_data_end(0) > kInitRecordSizeBytes) || |
| ((header->buffering_mode() != TRACE_BUFFERING_MODE_ONESHOT) && |
| header->durable_data_end() > kInitRecordSizeBytes)) { |
| FXL_LOG(INFO) << *bundle_ << " trace stats"; |
| FXL_LOG(INFO) << "Wrapped count: " << header->wrapped_count(); |
| FXL_LOG(INFO) << "# records dropped: " << header->num_records_dropped(); |
| FXL_LOG(INFO) << "Durable buffer: 0x" << std::hex |
| << header->durable_data_end() << ", size 0x" << std::hex |
| << header->durable_buffer_size(); |
| FXL_LOG(INFO) << "Non-durable buffer: 0x" << std::hex |
| << header->rolling_data_end(0) << ",0x" << std::hex |
| << header->rolling_data_end(1) << ", size 0x" << std::hex |
| << header->rolling_buffer_size(); |
| } |
| |
| return TransferStatus::kComplete; |
| } |
| |
| void Tracee::TransferBuffer(const zx::socket& socket, uint32_t wrapped_count, |
| uint64_t durable_data_end) { |
| FXL_DCHECK(buffering_mode_ == fuchsia::tracelink::BufferingMode::STREAMING); |
| FXL_DCHECK(socket); |
| FXL_DCHECK(buffer_vmo_); |
| |
| if (!DoTransferBuffer(socket, wrapped_count, durable_data_end)) { |
| Stop(); |
| } |
| |
| last_wrapped_count_ = wrapped_count; |
| last_durable_data_end_ = durable_data_end; |
| NotifyBufferSaved(wrapped_count, durable_data_end); |
| } |
| |
| bool Tracee::DoTransferBuffer(const zx::socket& socket, uint32_t wrapped_count, |
| uint64_t durable_data_end) { |
| if (wrapped_count == 0 && last_wrapped_count_ == 0) { |
| // ok |
| } else if (wrapped_count != last_wrapped_count_ + 1) { |
| FXL_LOG(ERROR) << *bundle_ << ": unexpected wrapped_count from provider: " |
| << wrapped_count; |
| return false; |
| } else if (durable_data_end < last_durable_data_end_ || |
| (durable_data_end & 7) != 0) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": unexpected durable_data_end from provider: " |
| << durable_data_end; |
| return false; |
| } |
| |
| auto transfer_status = TransferStatus::kComplete; |
| int buffer_number = get_buffer_number(wrapped_count); |
| |
| if ((transfer_status = WriteProviderIdRecord(socket)) != |
| TransferStatus::kComplete) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": Failed to write provider section record to trace."; |
| return false; |
| } |
| |
| trace::internal::trace_buffer_header header_buffer; |
| if (buffer_vmo_.read(&header_buffer, 0, sizeof(header_buffer)) != ZX_OK) { |
| FXL_LOG(ERROR) << *bundle_ << ": Failed to read header from buffer_vmo"; |
| return false; |
| } |
| |
| fbl::unique_ptr<trace::internal::BufferHeaderReader> header; |
| auto error = trace::internal::BufferHeaderReader::Create( |
| &header_buffer, buffer_vmo_size_, &header); |
| if (error != "") { |
| FXL_LOG(ERROR) << *bundle_ << ": header corrupt, " << error.c_str(); |
| return false; |
| } |
| if (!VerifyBufferHeader(header.get())) { |
| return false; |
| } |
| |
| // Don't use |header.durable_data_end| here, we want the value at the time |
| // the message was sent. |
| if (durable_data_end < kInitRecordSizeBytes || |
| durable_data_end > header->durable_buffer_size() || |
| (durable_data_end & 7) != 0 || |
| durable_data_end < last_durable_data_end_) { |
| FXL_LOG(ERROR) << *bundle_ |
| << ": bad durable_data_end: " << durable_data_end; |
| return false; |
| } |
| |
| // However we can use rolling_data_end from the header. |
| // This buffer is no longer being written to until we save it. |
| // [And if it does get written to it'll potentially result in corrupt |
| // data, but that's not our problem; as long as we can't crash, which is |
| // always the rule here.] |
| uint64_t rolling_data_end = header->rolling_data_end(buffer_number); |
| |
| // Only transfer what's new in the durable buffer since the last time. |
| uint64_t durable_buffer_offset = header->get_durable_buffer_offset(); |
| if (durable_data_end > last_durable_data_end_) { |
| uint64_t size = durable_data_end - last_durable_data_end_; |
| if ((transfer_status = |
| WriteChunkBySize(socket, |
| durable_buffer_offset + last_durable_data_end_, |
| size, "durable")) != TransferStatus::kComplete) { |
| return false; |
| } |
| } |
| |
| uint64_t buffer_offset = header->GetRollingBufferOffset(buffer_number); |
| auto name = |
| buffer_number == 0 ? "rolling buffer 0" : "rolling buffer 1"; |
| if ((transfer_status = |
| WriteChunkBySize(socket, buffer_offset, rolling_data_end, |
| name)) != TransferStatus::kComplete) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void Tracee::NotifyBufferSaved(uint32_t wrapped_count, |
| uint64_t durable_data_end) { |
| FXL_VLOG(2) << "Buffer saved for " << *bundle_ |
| << ", wrapped_count=" << wrapped_count |
| << ", durable_data_end=" << durable_data_end; |
| trace_provider_packet_t packet{}; |
| packet.request = TRACE_PROVIDER_BUFFER_SAVED; |
| packet.data32 = wrapped_count; |
| packet.data64 = durable_data_end; |
| auto status = fifo_.write(sizeof(packet), &packet, 1, nullptr); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| // The FIFO should never fill. If it does then the provider is sending us |
| // buffer full notifications but not reading our replies. Terminate the |
| // connection. |
| Stop(); |
| } else { |
| FXL_DCHECK(status == ZX_OK || status == ZX_ERR_PEER_CLOSED); |
| } |
| } |
| |
| Tracee::TransferStatus Tracee::WriteProviderIdRecord( |
| const zx::socket& socket) const { |
| if (provider_info_record_written_) { |
| return WriteProviderSectionRecord(socket); |
| } else { |
| auto status = WriteProviderInfoRecord(socket); |
| provider_info_record_written_ = true; |
| return status; |
| } |
| } |
| |
| Tracee::TransferStatus Tracee::WriteProviderInfoRecord( |
| const zx::socket& socket) const { |
| FXL_VLOG(2) << *bundle_ << ": writing provider info record"; |
| std::string label(""); // TODO(ZX-1875): Provide meaningful labels or remove |
| // labels from the trace wire format altogether. |
| size_t num_words = 1u + trace::BytesToWords(trace::Pad(label.size())); |
| std::vector<uint64_t> record(num_words); |
| record[0] = |
| trace::ProviderInfoMetadataRecordFields::Type::Make( |
| trace::ToUnderlyingType(trace::RecordType::kMetadata)) | |
| trace::ProviderInfoMetadataRecordFields::RecordSize::Make(num_words) | |
| trace::ProviderInfoMetadataRecordFields::MetadataType::Make( |
| trace::ToUnderlyingType(trace::MetadataType::kProviderInfo)) | |
| trace::ProviderInfoMetadataRecordFields::Id::Make(bundle_->id) | |
| trace::ProviderInfoMetadataRecordFields::NameLength::Make(label.size()); |
| memcpy(&record[1], label.c_str(), label.size()); |
| return WriteBufferToSocket(socket, reinterpret_cast<uint8_t*>(record.data()), |
| trace::WordsToBytes(num_words)); |
| } |
| |
| Tracee::TransferStatus Tracee::WriteProviderSectionRecord( |
| const zx::socket& socket) const { |
| FXL_VLOG(2) << *bundle_ << ": writing provider section record"; |
| size_t num_words = 1u; |
| std::vector<uint64_t> record(num_words); |
| record[0] = |
| trace::ProviderSectionMetadataRecordFields::Type::Make( |
| trace::ToUnderlyingType(trace::RecordType::kMetadata)) | |
| trace::ProviderSectionMetadataRecordFields::RecordSize::Make(num_words) | |
| trace::ProviderSectionMetadataRecordFields::MetadataType::Make( |
| trace::ToUnderlyingType(trace::MetadataType::kProviderSection)) | |
| trace::ProviderSectionMetadataRecordFields::Id::Make(bundle_->id); |
| return WriteBufferToSocket(socket, reinterpret_cast<uint8_t*>(record.data()), |
| trace::WordsToBytes(num_words)); |
| } |
| |
| Tracee::TransferStatus Tracee::WriteProviderBufferOverflowEvent( |
| const zx::socket& socket) const { |
| size_t num_words = 1u; |
| std::vector<uint64_t> record(num_words); |
| record[0] = |
| trace::ProviderEventMetadataRecordFields::Type::Make( |
| trace::ToUnderlyingType(trace::RecordType::kMetadata)) | |
| trace::ProviderEventMetadataRecordFields::RecordSize::Make(num_words) | |
| trace::ProviderEventMetadataRecordFields::MetadataType::Make( |
| trace::ToUnderlyingType(trace::MetadataType::kProviderEvent)) | |
| trace::ProviderEventMetadataRecordFields::Id::Make(bundle_->id) | |
| trace::ProviderEventMetadataRecordFields::Event::Make( |
| trace::ToUnderlyingType(trace::ProviderEventType::kBufferOverflow)); |
| return WriteBufferToSocket(socket, reinterpret_cast<uint8_t*>(record.data()), |
| trace::WordsToBytes(num_words)); |
| } |
| |
| const char* Tracee::ModeName(fuchsia::tracelink::BufferingMode mode) { |
| switch (mode) { |
| case fuchsia::tracelink::BufferingMode::ONESHOT: |
| return "oneshot"; |
| case fuchsia::tracelink::BufferingMode::CIRCULAR: |
| return "circular"; |
| case fuchsia::tracelink::BufferingMode::STREAMING: |
| return "streaming"; |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& out, Tracee::State state) { |
| switch (state) { |
| case Tracee::State::kReady: |
| out << "ready"; |
| break; |
| case Tracee::State::kStartPending: |
| out << "start pending"; |
| break; |
| case Tracee::State::kStarted: |
| out << "started"; |
| break; |
| case Tracee::State::kStopping: |
| out << "stopping"; |
| break; |
| case Tracee::State::kStopped: |
| out << "stopped"; |
| break; |
| } |
| |
| return out; |
| } |
| |
| } // namespace tracing |