// Copyright 2020 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. //============================================================================== // #include "pw_trace/trace.h" #include "pw_preprocessor/util.h" #include "pw_trace_tokenized/trace_callback.h" #include "pw_trace_tokenized/trace_tokenized.h" #include "pw_varint/varint.h" namespace pw { namespace trace { TokenizedTraceImpl TokenizedTrace::instance_; CallbacksImpl Callbacks::instance_; void TokenizedTraceImpl::HandleTraceEvent(uint32_t trace_token, EventType event_type, const char* module, uint32_t trace_id, uint8_t flags, const void* data_buffer, size_t data_size) { // Early exit if disabled and no callbacks are register to receive events // while disabled. if (!enabled_ && Callbacks::Instance().GetCalledOnEveryEventCount() == 0) { return; } // Create trace event PW_TRACE_QUEUE_LOCK(); if (!event_queue_ .TryPushBack(trace_token, event_type, module, trace_id, flags, data_buffer, data_size) .ok()) { // Queue full dropping sample // TODO(rgoliver): Allow other strategies, for example: drop oldest, try // empty queue, or block. } PW_TRACE_QUEUE_UNLOCK(); // Sample is now in queue (if not dropped), try to empty the queue if not // already being emptied. if (PW_TRACE_TRY_LOCK()) { while (!event_queue_.IsEmpty()) { HandleNextItemInQueue(event_queue_.PeekFront()); event_queue_.PopFront(); } PW_TRACE_UNLOCK(); } } void TokenizedTraceImpl::HandleNextItemInQueue( const volatile TraceQueue::QueueEventBlock* event_block) { // Get next item in queue uint32_t trace_token = event_block->trace_token; EventType event_type = event_block->event_type; const char* module = event_block->module; uint32_t trace_id = event_block->trace_id; uint8_t flags = event_block->flags; const std::byte* data_buffer = const_cast(event_block->data_buffer); size_t data_size = event_block->data_size; // Call any event callback which is registered to receive every event. pw_trace_TraceEventReturnFlags ret_flags = 0; ret_flags |= Callbacks::Instance().CallEventCallbacks(CallbacksImpl::kCallOnEveryEvent, trace_token, event_type, module, trace_id, flags); // Return if disabled. if ((PW_TRACE_EVENT_RETURN_FLAGS_SKIP_EVENT & ret_flags) || !enabled_) { return; } // Call any event callback not already called. ret_flags |= Callbacks::Instance().CallEventCallbacks( CallbacksImpl::kCallOnlyWhenEnabled, trace_token, event_type, module, trace_id, flags); // Return if disabled (from a callback) or if a callback has indicated the // sample should be skipped. if ((PW_TRACE_EVENT_RETURN_FLAGS_SKIP_EVENT & ret_flags) || !enabled_) { return; } // Create header to store trace info static constexpr size_t kMaxHeaderSize = sizeof(trace_token) + pw::varint::kMaxVarint64SizeBytes + // time pw::varint::kMaxVarint64SizeBytes; // trace_id std::byte header[kMaxHeaderSize]; memcpy(header, &trace_token, sizeof(trace_token)); size_t header_size = sizeof(trace_token); // Compute delta of time elapsed since last trace entry. PW_TRACE_TIME_TYPE trace_time = pw_trace_GetTraceTime(); PW_TRACE_TIME_TYPE delta = (last_trace_time_ == 0) ? 0 : PW_TRACE_GET_TIME_DELTA(last_trace_time_, trace_time); header_size += pw::varint::Encode( delta, std::span(&header[header_size], kMaxHeaderSize - header_size)); last_trace_time_ = trace_time; // Calculate packet id if needed. if (PW_TRACE_HAS_TRACE_ID(event_type)) { header_size += pw::varint::Encode(trace_id, std::span(&header[header_size], kMaxHeaderSize - header_size)); } // Send encoded output to any registered trace sinks. Callbacks::Instance().CallSinks( std::span(header, header_size), std::span( reinterpret_cast(data_buffer), data_size)); // Disable after processing if an event callback had set the flag. if (PW_TRACE_EVENT_RETURN_FLAGS_DISABLE_AFTER_PROCESSING & ret_flags) { enabled_ = false; } } pw_trace_TraceEventReturnFlags CallbacksImpl::CallEventCallbacks( CallOnEveryEvent called_on_every_event, uint32_t trace_ref, EventType event_type, const char* module, uint32_t trace_id, uint8_t flags) { pw_trace_TraceEventReturnFlags ret_flags = 0; for (size_t i = 0; i < PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS; i++) { if (event_callbacks_[i].callback && event_callbacks_[i].called_on_every_event == called_on_every_event) { ret_flags |= Callbacks::Instance().GetEventCallback(i)->callback( event_callbacks_[i].user_data, trace_ref, event_type, module, trace_id, flags); } } return ret_flags; } void CallbacksImpl::CallSinks(std::span header, std::span data) { for (size_t sink_idx = 0; sink_idx < PW_TRACE_CONFIG_MAX_SINKS; sink_idx++) { void* user_data = sink_callbacks_[sink_idx].user_data; if (sink_callbacks_[sink_idx].start_block) { sink_callbacks_[sink_idx].start_block(user_data, header.size() + data.size()); } if (sink_callbacks_[sink_idx].add_bytes) { sink_callbacks_[sink_idx].add_bytes( user_data, header.data(), header.size()); if (data.size() > 0) { sink_callbacks_[sink_idx].add_bytes( user_data, data.data(), data.size()); } } if (sink_callbacks_[sink_idx].end_block) { sink_callbacks_[sink_idx].end_block(user_data); } } } pw::Status CallbacksImpl::RegisterSink(SinkStartBlock start_func, SinkAddBytes add_bytes_func, SinkEndBlock end_block_func, void* user_data, SinkHandle* handle) { pw_Status status = PW_STATUS_RESOURCE_EXHAUSTED; PW_TRACE_LOCK(); for (size_t sink_idx = 0; sink_idx < PW_TRACE_CONFIG_MAX_SINKS; sink_idx++) { if (IsSinkFree(sink_idx)) { sink_callbacks_[sink_idx].start_block = start_func; sink_callbacks_[sink_idx].add_bytes = add_bytes_func; sink_callbacks_[sink_idx].end_block = end_block_func; sink_callbacks_[sink_idx].user_data = user_data; if (handle) { *handle = sink_idx; } status = PW_STATUS_OK; break; } } PW_TRACE_UNLOCK(); return status; } pw::Status CallbacksImpl::UnregisterSink(SinkHandle handle) { PW_TRACE_LOCK(); if (handle >= PW_TRACE_CONFIG_MAX_SINKS) { return PW_STATUS_INVALID_ARGUMENT; } sink_callbacks_[handle].start_block = nullptr; sink_callbacks_[handle].add_bytes = nullptr; sink_callbacks_[handle].end_block = nullptr; PW_TRACE_UNLOCK(); return PW_STATUS_OK; } pw::Status CallbacksImpl::UnregisterAllSinks() { for (size_t sink_idx = 0; sink_idx < PW_TRACE_CONFIG_MAX_SINKS; sink_idx++) { UnregisterSink(sink_idx) .IgnoreError(); // TODO(pwbug/387): Handle Status properly } return PW_STATUS_OK; } CallbacksImpl::SinkCallbacks* CallbacksImpl::GetSink(SinkHandle handle) { if (handle >= PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS) { return nullptr; } return &sink_callbacks_[handle]; } pw::Status CallbacksImpl::RegisterEventCallback( EventCallback callback, CallOnEveryEvent called_on_every_event, void* user_data, EventCallbackHandle* handle) { pw_Status status = PW_STATUS_RESOURCE_EXHAUSTED; PW_TRACE_LOCK(); for (size_t i = 0; i < PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS; i++) { if (event_callbacks_[i].callback == nullptr) { event_callbacks_[i].callback = callback; event_callbacks_[i].user_data = user_data; event_callbacks_[i].called_on_every_event = called_on_every_event; called_on_every_event_count_ += called_on_every_event ? 1 : 0; if (handle) { *handle = i; } status = PW_STATUS_OK; break; } } PW_TRACE_UNLOCK(); return status; } pw::Status CallbacksImpl::UnregisterEventCallback(EventCallbackHandle handle) { PW_TRACE_LOCK(); if (handle >= PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS) { return PW_STATUS_INVALID_ARGUMENT; } event_callbacks_[handle].callback = nullptr; event_callbacks_[handle].user_data = nullptr; called_on_every_event_count_ += event_callbacks_[handle].called_on_every_event ? 1 : 0; event_callbacks_[handle].called_on_every_event = kCallOnlyWhenEnabled; PW_TRACE_UNLOCK(); return PW_STATUS_OK; } pw::Status CallbacksImpl::UnregisterAllEventCallbacks() { for (size_t i = 0; i < PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS; i++) { UnregisterEventCallback(i) .IgnoreError(); // TODO(pwbug/387): Handle Status properly } return PW_STATUS_OK; } CallbacksImpl::EventCallbacks* CallbacksImpl::GetEventCallback( EventCallbackHandle handle) { if (handle >= PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS) { return nullptr; } return &event_callbacks_[handle]; } // C functions PW_EXTERN_C_START void pw_trace_Enable(bool enable) { TokenizedTrace::Instance().Enable(enable); } bool pw_trace_IsEnabled() { return TokenizedTrace::Instance().IsEnabled(); } void pw_trace_TraceEvent(uint32_t trace_token, pw_trace_EventType event_type, const char* module, uint32_t trace_id, uint8_t flags, const void* data_buffer, size_t data_size) { TokenizedTrace::Instance().HandleTraceEvent( trace_token, event_type, module, trace_id, flags, data_buffer, data_size); } pw_Status pw_trace_RegisterSink(pw_trace_SinkStartBlock start_func, pw_trace_SinkAddBytes add_bytes_func, pw_trace_SinkEndBlock end_block_func, void* user_data, pw_trace_SinkHandle* handle) { return Callbacks::Instance() .RegisterSink( start_func, add_bytes_func, end_block_func, user_data, handle) .code(); } pw_Status pw_trace_UnregisterSink(pw_trace_EventCallbackHandle handle) { return Callbacks::Instance().UnregisterSink(handle).code(); } pw_Status pw_trace_RegisterEventCallback( pw_trace_EventCallback callback, pw_trace_ShouldCallOnEveryEvent called_on_every_event, void* user_data, pw_trace_EventCallbackHandle* handle) { return Callbacks::Instance() .RegisterEventCallback( callback, static_cast(called_on_every_event), user_data, handle) .code(); } pw_Status pw_trace_UnregisterEventCallback( pw_trace_EventCallbackHandle handle) { return Callbacks::Instance().UnregisterEventCallback(handle).code(); } PW_EXTERN_C_END } // namespace trace } // namespace pw