| // Copyright 2017 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 "session.h" |
| |
| #include <inttypes.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/trace-provider/provider.h> |
| #include <lib/zx/vmar.h> |
| #include <stdio.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <utility> |
| |
| #include "utils.h" |
| |
| namespace trace { |
| namespace internal { |
| |
| Session::Session(async_dispatcher_t* dispatcher, void* buffer, size_t buffer_num_bytes, |
| zx::fifo fifo, std::vector<std::string> categories) |
| : dispatcher_(dispatcher), |
| buffer_(buffer), |
| buffer_num_bytes_(buffer_num_bytes), |
| fifo_(std::move(fifo)), |
| fifo_wait_(this, fifo_.get(), ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED), |
| categories_(std::move(categories)) { |
| // Build a quick lookup table for IsCategoryEnabled(). |
| for (const auto& cat : categories_) { |
| enabled_category_set_.emplace(StringSetEntry(cat.c_str())); |
| } |
| } |
| |
| Session::~Session() { |
| zx_status_t status = |
| zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(buffer_), buffer_num_bytes_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| status = fifo_wait_.Cancel(); |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_NOT_FOUND); |
| } |
| |
| void Session::InitializeEngine(async_dispatcher_t* dispatcher, |
| trace_buffering_mode_t buffering_mode, zx::vmo buffer, zx::fifo fifo, |
| std::vector<std::string> categories) { |
| ZX_DEBUG_ASSERT(buffer); |
| ZX_DEBUG_ASSERT(fifo); |
| |
| // If the engine isn't stopped flag an error. No one else should be |
| // starting/stopping the engine so testing this here is ok. |
| switch (trace_state()) { |
| case TRACE_STOPPED: |
| break; |
| case TRACE_STOPPING: |
| fprintf(stderr, |
| "Session for process %" PRIu64 |
| ": cannot initialize engine, still stopping from previous trace\n", |
| GetPid()); |
| return; |
| case TRACE_STARTED: |
| // We can get here if the app errantly tried to create two providers. |
| // This is a bug in the app, provide extra assistance for diagnosis. |
| // Including the pid here has been extraordinarily helpful. |
| fprintf(stderr, |
| "Session for process %" PRIu64 |
| ": engine is already initialized. Is there perchance two" |
| " providers in this app?\n", |
| GetPid()); |
| return; |
| default: |
| __UNREACHABLE; |
| } |
| |
| uint64_t buffer_num_bytes; |
| zx_status_t status = buffer.get_size(&buffer_num_bytes); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Session: error getting buffer size, status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| uintptr_t buffer_ptr; |
| status = zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0u, buffer, 0u, |
| buffer_num_bytes, &buffer_ptr); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Session: error mapping buffer, status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| auto session = new Session(dispatcher, reinterpret_cast<void*>(buffer_ptr), buffer_num_bytes, |
| std::move(fifo), std::move(categories)); |
| |
| status = session->fifo_wait_.Begin(dispatcher); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Session: error starting fifo wait, status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| delete session; |
| return; |
| } |
| |
| status = trace_engine_initialize(dispatcher, session, buffering_mode, session->buffer_, |
| session->buffer_num_bytes_); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Session: error starting engine, status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| delete session; |
| } else { |
| // The session will be destroyed in |TraceTerminated()|. |
| } |
| } |
| |
| void Session::StartEngine(trace_start_mode_t start_mode) { |
| // If the engine isn't stopped flag an error. No one else should be |
| // starting/stopping the engine so testing this here is ok. |
| switch (trace_state()) { |
| case TRACE_STOPPED: |
| break; |
| case TRACE_STOPPING: |
| fprintf(stderr, |
| "Session for process %" PRIu64 |
| ": cannot start engine, still stopping from previous trace\n", |
| GetPid()); |
| return; |
| case TRACE_STARTED: |
| // Ignore. |
| return; |
| default: |
| __UNREACHABLE; |
| } |
| |
| zx_status_t status = trace_engine_start(start_mode); |
| if (status != ZX_OK) { |
| // There's nothing more we can do here. There's currently no easy way |
| // to inform trace-manager of the error because we don't have a copy |
| // of "this", we're a static method and we give ownership of "this" to |
| // the trace engine. When the trace-engine wants to invoke one of our |
| // methods it does so via the "handler" API. But we're not in the |
| // engine here. Fortunately there's nothing more we need to do here. |
| // The kinds of errors we can get here fall into three categories: |
| // 1) Can't happen: If it can't happen then just let trace-manager |
| // timeout waiting for us to start. |
| // 2) To be ignored: The FIDL provider protocol specifies that if a |
| // Start request is received when the engine is not stopped then the |
| // request is to be ignored. |
| // 3) Async loop shutting down: If the provider is shutting down its |
| // async loop then the engine is about to be terminated anyway. |
| // |
| // Just log the error for debugging purposes. |
| // Ignore BAD_STATE as that is case #2. |
| if (status != ZX_ERR_BAD_STATE) { |
| fprintf(stderr, "Session: error starting engine, status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| } |
| } |
| } |
| |
| void Session::StopEngine() { trace_engine_stop(ZX_OK); } |
| |
| void Session::TerminateEngine() { trace_engine_terminate(); } |
| |
| void Session::HandleFifo(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status == ZX_ERR_CANCELED) { |
| // The wait could be canceled if we're shutting down, e.g., the |
| // program is exiting. |
| return; |
| } |
| |
| if (status != ZX_OK) { |
| fprintf(stderr, "Session: FIFO wait failed: status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| } else if (signal->observed & ZX_FIFO_READABLE) { |
| if (ReadFifoMessage()) { |
| zx_status_t status = wait->Begin(dispatcher); |
| if (status == ZX_OK) { |
| return; |
| } |
| fprintf(stderr, "Session: Error re-registering FIFO wait: status=%d(%s)\n", status, |
| zx_status_get_string(status)); |
| } |
| } else { |
| ZX_DEBUG_ASSERT(signal->observed & ZX_FIFO_PEER_CLOSED); |
| } |
| |
| // TraceManager is gone or other error with the fifo. |
| TerminateEngine(); |
| } |
| |
| bool Session::ReadFifoMessage() { |
| trace_provider_packet_t packet; |
| auto status = fifo_.read(sizeof(packet), &packet, 1u, nullptr); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| if (packet.data16 != 0) { |
| fprintf(stderr, "Session: data16 field non-zero from TraceManager: %u\n", packet.data16); |
| return false; |
| } |
| switch (packet.request) { |
| case TRACE_PROVIDER_BUFFER_SAVED: { |
| auto wrapped_count = packet.data32; |
| auto durable_data_end = packet.data64; |
| #if 0 // TODO(fxbug.dev/22887): Don't delete this, save for conversion to syslog. |
| fprintf(stderr, "Session: Received buffer_saved message" |
| ", wrapped_count=%u, durable_data_end=0x%" PRIx64 "\n", |
| wrapped_count, durable_data_end); |
| #endif |
| status = MarkBufferSaved(wrapped_count, durable_data_end); |
| if (status == ZX_ERR_BAD_STATE) { |
| // This happens when tracing has stopped. Ignore it. |
| } else if (status != ZX_OK) { |
| fprintf(stderr, "Session: MarkBufferSaved failed: status=%d\n", status); |
| return false; |
| } |
| break; |
| } |
| default: |
| fprintf(stderr, "Session: Bad request from TraceManager: %u\n", packet.request); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| zx_status_t Session::MarkBufferSaved(uint32_t wrapped_count, uint64_t durable_data_end) { |
| return trace_engine_mark_buffer_saved(wrapped_count, durable_data_end); |
| } |
| |
| bool Session::IsCategoryEnabled(const char* category) { |
| if (categories_.size() == 0) { |
| // If none are specified, enable all categories. |
| return true; |
| } |
| return enabled_category_set_.find(StringSetEntry(category)) != enabled_category_set_.end(); |
| } |
| |
| void Session::SendFifoPacket(const trace_provider_packet_t* packet) { |
| auto status = fifo_.write(sizeof(*packet), packet, 1, nullptr); |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_PEER_CLOSED); |
| } |
| |
| void Session::TraceStarted() { |
| trace_provider_packet_t packet{}; |
| packet.request = TRACE_PROVIDER_STARTED; |
| packet.data32 = TRACE_PROVIDER_FIFO_PROTOCOL_VERSION; |
| SendFifoPacket(&packet); |
| } |
| |
| void Session::TraceStopped(zx_status_t disposition) { |
| trace_provider_packet_t packet{}; |
| packet.request = TRACE_PROVIDER_STOPPED; |
| SendFifoPacket(&packet); |
| } |
| |
| void Session::TraceTerminated() { |
| // Destruction can race with HandleFifo, e.g., if the dispatcher runs on a background thread |
| // and tracing terminates on a different thread. Handle this by running the destructor on the |
| // dispatcher's thread (which we assume is single-threaded). It may also happen that the task |
| // is not run. This can happen if the loop is quit and destructed before the task is run. |
| // Handle this by letting destruction of the closure delete the session. |
| std::unique_ptr<Session> session{this}; |
| async::PostTask(dispatcher_, [session = std::move(session)]() {}); |
| } |
| |
| void Session::NotifyBufferFull(uint32_t wrapped_count, uint64_t durable_data_end) { |
| trace_provider_packet_t packet{}; |
| packet.request = TRACE_PROVIDER_SAVE_BUFFER; |
| packet.data32 = wrapped_count; |
| packet.data64 = durable_data_end; |
| auto status = fifo_.write(sizeof(packet), &packet, 1, nullptr); |
| // There's something wrong in our protocol or implementation if we fill |
| // the fifo buffer. |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_PEER_CLOSED); |
| } |
| |
| void Session::SendAlert(const char* alert_name) { |
| trace_provider_packet_t packet{}; |
| |
| size_t alert_name_length = strlen(alert_name); |
| if (alert_name_length > sizeof(packet.data16) + sizeof(packet.data32) + sizeof(packet.data64)) { |
| fprintf(stderr, "Session: Alert name too long: %s\n", alert_name); |
| return; |
| } |
| |
| packet.request = TRACE_PROVIDER_ALERT; |
| memcpy(&packet.data16, alert_name, alert_name_length); |
| auto status = fifo_.write(sizeof(packet), &packet, 1, nullptr); |
| ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_PEER_CLOSED); |
| } |
| |
| } // namespace internal |
| } // namespace trace |