| // 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. |
| |
| #ifndef ZIRCON_SYSTEM_ULIB_TRACE_ENGINE_CONTEXT_IMPL_H_ |
| #define ZIRCON_SYSTEM_ULIB_TRACE_ENGINE_CONTEXT_IMPL_H_ |
| |
| #include <atomic> |
| #include <mutex> |
| |
| #include <zircon/assert.h> |
| |
| #include <lib/trace-engine/buffer_internal.h> |
| #include <lib/trace-engine/context.h> |
| #include <lib/trace-engine/handler.h> |
| #include <lib/zx/event.h> |
| |
| // Two preprocessor symbols control what symbols we export in a .so: |
| // EXPORT and EXPORT_NO_DDK: |
| // - EXPORT is for symbols exported to both driver and non-driver versions of |
| // the library ("non-driver" is the normal case). |
| // - EXPORT_NO_DDK is for symbols *not* exported in the DDK. |
| // A third variant is supported which is to export nothing. This is for cases |
| // like libvulkan which want tracing but do not have access to |
| // libtrace-engine.so. |
| // Two preprocessor symbols are provided by the build system to select which |
| // variant we are building: STATIC_LIBRARY and DDK_TRACING. Either neither of |
| // them are defined (normal case), or exactly one of them is defined. |
| #if defined(STATIC_LIBRARY) |
| #define EXPORT |
| #define EXPORT_NO_DDK |
| #elif defined(DDK_TRACING) |
| #define EXPORT __EXPORT |
| #define EXPORT_NO_DDK |
| #else |
| #define EXPORT __EXPORT |
| #define EXPORT_NO_DDK __EXPORT |
| #endif |
| |
| using trace::internal::trace_buffer_header; |
| |
| // Return true if there are no buffer acquisitions of the trace context. |
| bool trace_engine_is_buffer_context_released(); |
| |
| // Called from trace_context to notify the engine a buffer needs saving. |
| void trace_engine_request_save_buffer(uint32_t wrapped_count, uint64_t durable_data_end); |
| |
| // Maintains state for a single trace session. |
| // This structure is accessed concurrently from many threads which hold trace |
| // context references. |
| // Implements the opaque type declared in <trace-engine/context.h>. |
| struct trace_context { |
| trace_context(void* buffer, size_t buffer_num_bytes, trace_buffering_mode_t buffering_mode, |
| trace_handler_t* handler); |
| |
| ~trace_context(); |
| |
| const trace_buffer_header* buffer_header() const { return header_; } |
| |
| static size_t min_buffer_size() { return kMinPhysicalBufferSize; } |
| |
| static size_t max_buffer_size() { return kMaxPhysicalBufferSize; } |
| |
| static size_t MaxUsableBufferOffset() { |
| return (1ull << kUsableBufferOffsetBits) - sizeof(uint64_t); |
| } |
| |
| uint32_t generation() const { return generation_; } |
| |
| trace_handler_t* handler() const { return handler_; } |
| |
| trace_buffering_mode_t buffering_mode() const { return buffering_mode_; } |
| |
| uint64_t num_records_dropped() const { |
| return num_records_dropped_.load(std::memory_order_relaxed); |
| } |
| |
| bool UsingDurableBuffer() const { return buffering_mode_ != TRACE_BUFFERING_MODE_ONESHOT; } |
| |
| // Return true if at least one record was dropped. |
| bool WasRecordDropped() const { return num_records_dropped() != 0u; } |
| |
| // Return the number of bytes currently allocated in the rolling buffer(s). |
| size_t RollingBytesAllocated() const; |
| |
| size_t DurableBytesAllocated() const; |
| |
| void ResetDurableBufferPointers(); |
| void ResetRollingBufferPointers(); |
| void ResetBufferPointers(); |
| void InitBufferHeader(); |
| void ClearEntireBuffer(); |
| void ClearRollingBuffers(); |
| void UpdateBufferHeaderAfterStopped(); |
| |
| uint64_t* AllocRecord(size_t num_bytes); |
| uint64_t* AllocDurableRecord(size_t num_bytes); |
| bool AllocThreadIndex(trace_thread_index_t* out_index); |
| bool AllocStringIndex(trace_string_index_t* out_index); |
| |
| // This is called by the handler when it has been notified that a buffer |
| // has been saved. |
| // |wrapped_count| is the wrapped count at the time the buffer save request |
| // was made. Similarly for |durable_data_end|. |
| void MarkRollingBufferSaved(uint32_t wrapped_count, uint64_t durable_data_end); |
| |
| // This is only called from the engine to initiate a buffer save. |
| void HandleSaveRollingBufferRequest(uint32_t wrapped_count, uint64_t durable_data_end); |
| |
| private: |
| // The maximum rolling buffer size in bits. |
| static constexpr size_t kRollingBufferSizeBits = 32; |
| |
| // Maximum size, in bytes, of a rolling buffer. |
| static constexpr size_t kMaxRollingBufferSize = 1ull << kRollingBufferSizeBits; |
| |
| // The number of usable bits in the buffer pointer. |
| // This is several bits more than the maximum buffer size to allow a |
| // buffer pointer to grow without overflow while TraceManager is saving a |
| // buffer in streaming mode. |
| // In this case we don't snap the offset to the end as doing so requires |
| // modifying state and thus obtaining the lock (streaming mode is not |
| // lock-free). Instead the offset keeps growing. |
| // kUsableBufferOffsetBits = 40 bits = 1TB. |
| // Max rolling buffer size = 32 bits = 4GB. |
| // Thus we assume TraceManager can save 4GB of trace before the client |
| // writes 1TB of trace data (lest the offset part of |
| // |rolling_buffer_current_| overflows). But, just in case, if |
| // TraceManager still can't keep up we stop tracing when the offset |
| // approaches overflowing. See AllocRecord(). |
| static constexpr int kUsableBufferOffsetBits = kRollingBufferSizeBits + 8; |
| |
| // The number of bits used to record the buffer pointer. |
| // This includes one more bit to support overflow in offset calcs. |
| static constexpr int kBufferOffsetBits = kUsableBufferOffsetBits + 1; |
| |
| // The number of bits in the wrapped counter. |
| // It important that this counter not wrap (well, technically it can, |
| // the lost information isn't that important, but if it wraps too |
| // quickly the transition from one buffer to the other can break. |
| // The current values allow for a 20 bit counter which is plenty. |
| // A value of 20 also has the benefit that when the entire |
| // offset_plus_counter value is printed in hex the counter is easily read. |
| static constexpr int kWrappedCounterBits = 20; |
| static constexpr int kWrappedCounterShift = 64 - kWrappedCounterBits; |
| |
| static_assert(kBufferOffsetBits + kWrappedCounterBits <= 64, ""); |
| |
| // The physical buffer must be at least this big. |
| // Mostly this is here to simplify buffer size calculations. |
| // It's as small as it is to simplify some testcases. |
| static constexpr size_t kMinPhysicalBufferSize = 4096; |
| |
| // The physical buffer can be at most this big. |
| // To keep things simple we ignore the header. |
| static constexpr size_t kMaxPhysicalBufferSize = kMaxRollingBufferSize; |
| |
| // The minimum size of the durable buffer. |
| // There must be enough space for at least the initialization record. |
| static constexpr size_t kMinDurableBufferSize = 16; |
| |
| // The maximum size of the durable buffer. |
| // We need enough space for: |
| // - initialization record = 16 bytes |
| // - string table (max TRACE_ENCODED_STRING_REF_MAX_INDEX = 0x7fffu entries) |
| // - thread table (max TRACE_ENCODED_THREAD_REF_MAX_INDEX = 0xff entries) |
| // String entries are 8 bytes + length-round-to-8-bytes. |
| // Strings have a max size of TRACE_ENCODED_STRING_REF_MAX_LENGTH bytes |
| // = 32000. We assume most are < 64 bytes. |
| // Thread entries are 8 bytes + pid + tid = 24 bytes. |
| // If we assume 10000 registered strings, typically 64 bytes, plus max |
| // number registered threads, that works out to: |
| // 16 /*initialization record*/ |
| // + 10000 * (8 + 64) /*strings*/ |
| // + 255 * 24 /*threads*/ |
| // = 726136. |
| // We round this up to 1MB. |
| static constexpr size_t kMaxDurableBufferSize = 1024 * 1024; |
| |
| // Given a buffer of size |SIZE| in bytes, not including the header, |
| // return how much to use for the durable buffer. This is further adjusted |
| // to be at most |kMaxDurableBufferSize|, and to account for rolling |
| // buffer size alignment constraints. |
| #define GET_DURABLE_BUFFER_SIZE(size) ((size) / 16) |
| |
| // Ensure the smallest buffer is still large enough to hold |
| // |kMinDurableBufferSize|. |
| static_assert(GET_DURABLE_BUFFER_SIZE(kMinPhysicalBufferSize - sizeof(trace_buffer_header)) >= |
| kMinDurableBufferSize, |
| ""); |
| |
| static uintptr_t GetBufferOffset(uint64_t offset_plus_counter) { |
| return offset_plus_counter & ((1ul << kBufferOffsetBits) - 1); |
| } |
| |
| static uint32_t GetWrappedCount(uint64_t offset_plus_counter) { |
| return static_cast<uint32_t>(offset_plus_counter >> kWrappedCounterShift); |
| } |
| |
| static uint64_t MakeOffsetPlusCounter(uintptr_t offset, uint32_t counter) { |
| return offset | (static_cast<uint64_t>(counter) << kWrappedCounterShift); |
| } |
| |
| static int GetBufferNumber(uint32_t wrapped_count) { return wrapped_count & 1; } |
| |
| bool IsDurableBufferFull() const { |
| return durable_buffer_full_mark_.load(std::memory_order_relaxed) != 0; |
| } |
| |
| // Return true if |buffer_number| is ready to be written to. |
| bool IsRollingBufferReady(int buffer_number) const { |
| return rolling_buffer_full_mark_[buffer_number].load(std::memory_order_relaxed) == 0; |
| } |
| |
| // Return true if the other rolling buffer is ready to be written to. |
| bool IsOtherRollingBufferReady(int buffer_number) const { |
| return IsRollingBufferReady(!buffer_number); |
| } |
| |
| uint32_t CurrentWrappedCount() const { |
| auto current = rolling_buffer_current_.load(std::memory_order_relaxed); |
| return GetWrappedCount(current); |
| } |
| |
| void ComputeBufferSizes(); |
| |
| void MarkDurableBufferFull(uint64_t last_offset); |
| |
| void MarkOneshotBufferFull(uint64_t last_offset); |
| |
| void MarkRollingBufferFull(uint32_t wrapped_count, uint64_t last_offset); |
| |
| bool SwitchRollingBuffer(uint32_t wrapped_count, uint64_t buffer_offset); |
| |
| void SwitchRollingBufferLocked(uint32_t prev_wrapped_count, uint64_t prev_last_offset) |
| __TA_REQUIRES(buffer_switch_mutex_); |
| |
| void StreamingBufferFullCheck(uint32_t wrapped_count, uint64_t buffer_offset); |
| |
| void MarkTracingArtificiallyStopped(); |
| |
| void SnapToEnd(uint32_t wrapped_count) { |
| // Snap to the endpoint for simplicity. |
| // Several threads could all hit buffer-full with each one |
| // continually incrementing the offset. |
| uint64_t full_offset_plus_counter = MakeOffsetPlusCounter(rolling_buffer_size_, wrapped_count); |
| rolling_buffer_current_.store(full_offset_plus_counter, std::memory_order_relaxed); |
| } |
| |
| void MarkRecordDropped() { num_records_dropped_.fetch_add(1, std::memory_order_relaxed); } |
| |
| void NotifyRollingBufferFullLocked(uint32_t wrapped_count, uint64_t durable_data_end) |
| __TA_REQUIRES(buffer_switch_mutex_); |
| |
| // The generation counter associated with this context to distinguish |
| // it from previously created contexts. |
| uint32_t const generation_; |
| |
| // The buffering mode. |
| trace_buffering_mode_t const buffering_mode_; |
| |
| // Buffer start and end pointers. |
| // These encapsulate the entire physical buffer. |
| uint8_t* const buffer_start_; |
| uint8_t* const buffer_end_; |
| |
| // Same as |buffer_start_|, but as a header pointer. |
| trace_buffer_header* const header_; |
| |
| // Durable-record buffer start. |
| uint8_t* durable_buffer_start_; |
| |
| // The size of the durable buffer; |
| size_t durable_buffer_size_; |
| |
| // Rolling buffer start. |
| // To simplify switching between them we don't record the buffer end, |
| // and instead record their size (which is identical). |
| uint8_t* rolling_buffer_start_[2]; |
| |
| // The size of both rolling buffers. |
| size_t rolling_buffer_size_; |
| |
| // Current allocation pointer for durable records. |
| // This only used in circular and streaming modes. |
| // Starts at |durable_buffer_start| and grows from there. |
| // May exceed |durable_buffer_end| when the buffer is full. |
| std::atomic<uint64_t> durable_buffer_current_; |
| |
| // Offset beyond the last successful allocation, or zero if not full. |
| // This only used in circular and streaming modes: There is no separate |
| // buffer for durable records in oneshot mode. |
| // Only ever set to non-zero once in the lifetime of the trace context. |
| std::atomic<uint64_t> durable_buffer_full_mark_; |
| |
| // Allocation pointer of the current buffer for non-durable records, |
| // plus a wrapped counter. These are combined into one so that they can |
| // be atomically fetched together. |
| // The lower |kBufferOffsetBits| bits comprise the offset into the buffer |
| // of the next record to write. The upper |kWrappedCountBits| comprise |
| // the wrapped counter. Bit zero of this counter is the number of the |
| // buffer currently being written to. The counter is used in part for |
| // record keeping purposes, and to support transition from one buffer to |
| // the next. |
| // |
| // To construct: make_offset_plus_counter |
| // To get buffer offset: get_buffer_offset |
| // To get wrapped count: get_wrapped_count |
| // |
| // This value is also used for durable records in oneshot mode: in |
| // oneshot mode durable and non-durable records share the same buffer. |
| std::atomic<uint64_t> rolling_buffer_current_; |
| |
| // Offset beyond the last successful allocation, or zero if not full. |
| // Only ever set to non-zero once when the buffer fills. |
| // This will only be set in oneshot and streaming modes. |
| std::atomic<uint64_t> rolling_buffer_full_mark_[2]; |
| |
| // A count of the number of records that have been dropped. |
| std::atomic<uint64_t> num_records_dropped_{0}; |
| |
| // A count of the number of records that have been dropped. |
| std::atomic<uint64_t> num_records_dropped_after_buffer_switch_{0}; |
| |
| // Set to true if the engine needs to stop tracing for some reason. |
| bool tracing_artificially_stopped_ __TA_GUARDED(buffer_switch_mutex_) = false; |
| |
| // This is used when switching rolling buffers. |
| // It's a relatively rare operation, and this simplifies reasoning about |
| // correctness. |
| mutable std::mutex buffer_switch_mutex_; // TODO(dje): more guards? |
| |
| // Handler associated with the trace session. |
| trace_handler_t* const handler_; |
| |
| // The next thread index to be assigned. |
| std::atomic<trace_thread_index_t> next_thread_index_{TRACE_ENCODED_THREAD_REF_MIN_INDEX}; |
| |
| // The next string table index to be assigned. |
| std::atomic<trace_string_index_t> next_string_index_{TRACE_ENCODED_STRING_REF_MIN_INDEX}; |
| }; |
| |
| #endif // ZIRCON_SYSTEM_ULIB_TRACE_ENGINE_CONTEXT_IMPL_H_ |