blob: d136bfbdff0a85e0a56a4c4cd46ba3f19675de84 [file] [log] [blame]
// 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_