| //===-- xray_fdr_logging.cpp -----------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is a part of XRay, a dynamic runtime instrumentation system. |
| // |
| // Here we implement the Flight Data Recorder mode for XRay, where we use |
| // compact structures to store records in memory as well as when writing out the |
| // data to files. |
| // |
| //===----------------------------------------------------------------------===// |
| #include "xray_fdr_logging.h" |
| #include <cassert> |
| #include <errno.h> |
| #include <limits> |
| #include <memory> |
| #include <pthread.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "sanitizer_common/sanitizer_allocator_internal.h" |
| #include "sanitizer_common/sanitizer_atomic.h" |
| #include "sanitizer_common/sanitizer_common.h" |
| #include "xray/xray_interface.h" |
| #include "xray/xray_records.h" |
| #include "xray_allocator.h" |
| #include "xray_buffer_queue.h" |
| #include "xray_defs.h" |
| #include "xray_fdr_controller.h" |
| #include "xray_fdr_flags.h" |
| #include "xray_fdr_log_writer.h" |
| #include "xray_flags.h" |
| #include "xray_recursion_guard.h" |
| #include "xray_tsc.h" |
| #include "xray_utils.h" |
| |
| namespace __xray { |
| |
| static atomic_sint32_t LoggingStatus = { |
| XRayLogInitStatus::XRAY_LOG_UNINITIALIZED}; |
| |
| namespace { |
| |
| // Group together thread-local-data in a struct, then hide it behind a function |
| // call so that it can be initialized on first use instead of as a global. We |
| // force the alignment to 64-bytes for x86 cache line alignment, as this |
| // structure is used in the hot path of implementation. |
| struct XRAY_TLS_ALIGNAS(64) ThreadLocalData { |
| BufferQueue::Buffer Buffer{}; |
| BufferQueue *BQ = nullptr; |
| |
| using LogWriterStorage = |
| typename std::aligned_storage<sizeof(FDRLogWriter), |
| alignof(FDRLogWriter)>::type; |
| |
| LogWriterStorage LWStorage; |
| FDRLogWriter *Writer = nullptr; |
| |
| using ControllerStorage = |
| typename std::aligned_storage<sizeof(FDRController<>), |
| alignof(FDRController<>)>::type; |
| ControllerStorage CStorage; |
| FDRController<> *Controller = nullptr; |
| }; |
| |
| } // namespace |
| |
| static_assert(std::is_trivially_destructible<ThreadLocalData>::value, |
| "ThreadLocalData must be trivially destructible"); |
| |
| // Use a global pthread key to identify thread-local data for logging. |
| static pthread_key_t Key; |
| |
| // Global BufferQueue. |
| static std::aligned_storage<sizeof(BufferQueue)>::type BufferQueueStorage; |
| static BufferQueue *BQ = nullptr; |
| |
| // Global thresholds for function durations. |
| static atomic_uint64_t ThresholdTicks{0}; |
| |
| // Global for ticks per second. |
| static atomic_uint64_t TicksPerSec{0}; |
| |
| static atomic_sint32_t LogFlushStatus = { |
| XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING}; |
| |
| // This function will initialize the thread-local data structure used by the FDR |
| // logging implementation and return a reference to it. The implementation |
| // details require a bit of care to maintain. |
| // |
| // First, some requirements on the implementation in general: |
| // |
| // - XRay handlers should not call any memory allocation routines that may |
| // delegate to an instrumented implementation. This means functions like |
| // malloc() and free() should not be called while instrumenting. |
| // |
| // - We would like to use some thread-local data initialized on first-use of |
| // the XRay instrumentation. These allow us to implement unsynchronized |
| // routines that access resources associated with the thread. |
| // |
| // The implementation here uses a few mechanisms that allow us to provide both |
| // the requirements listed above. We do this by: |
| // |
| // 1. Using a thread-local aligned storage buffer for representing the |
| // ThreadLocalData struct. This data will be uninitialized memory by |
| // design. |
| // |
| // 2. Not requiring a thread exit handler/implementation, keeping the |
| // thread-local as purely a collection of references/data that do not |
| // require cleanup. |
| // |
| // We're doing this to avoid using a `thread_local` object that has a |
| // non-trivial destructor, because the C++ runtime might call std::malloc(...) |
| // to register calls to destructors. Deadlocks may arise when, for example, an |
| // externally provided malloc implementation is XRay instrumented, and |
| // initializing the thread-locals involves calling into malloc. A malloc |
| // implementation that does global synchronization might be holding a lock for a |
| // critical section, calling a function that might be XRay instrumented (and |
| // thus in turn calling into malloc by virtue of registration of the |
| // thread_local's destructor). |
| #if XRAY_HAS_TLS_ALIGNAS |
| static_assert(alignof(ThreadLocalData) >= 64, |
| "ThreadLocalData must be cache line aligned."); |
| #endif |
| static ThreadLocalData &getThreadLocalData() { |
| thread_local typename std::aligned_storage< |
| sizeof(ThreadLocalData), alignof(ThreadLocalData)>::type TLDStorage{}; |
| |
| if (pthread_getspecific(Key) == NULL) { |
| new (reinterpret_cast<ThreadLocalData *>(&TLDStorage)) ThreadLocalData{}; |
| pthread_setspecific(Key, &TLDStorage); |
| } |
| |
| return *reinterpret_cast<ThreadLocalData *>(&TLDStorage); |
| } |
| |
| static XRayFileHeader &fdrCommonHeaderInfo() { |
| static std::aligned_storage<sizeof(XRayFileHeader)>::type HStorage; |
| static pthread_once_t OnceInit = PTHREAD_ONCE_INIT; |
| static bool TSCSupported = true; |
| static uint64_t CycleFrequency = NanosecondsPerSecond; |
| pthread_once( |
| &OnceInit, +[] { |
| XRayFileHeader &H = reinterpret_cast<XRayFileHeader &>(HStorage); |
| // Version 2 of the log writes the extents of the buffer, instead of |
| // relying on an end-of-buffer record. |
| // Version 3 includes PID metadata record. |
| // Version 4 includes CPU data in the custom event records. |
| // Version 5 uses relative deltas for custom and typed event records, |
| // and removes the CPU data in custom event records (similar to how |
| // function records use deltas instead of full TSCs and rely on other |
| // metadata records for TSC wraparound and CPU migration). |
| H.Version = 5; |
| H.Type = FileTypes::FDR_LOG; |
| |
| // Test for required CPU features and cache the cycle frequency |
| TSCSupported = probeRequiredCPUFeatures(); |
| if (TSCSupported) |
| CycleFrequency = getTSCFrequency(); |
| H.CycleFrequency = CycleFrequency; |
| |
| // FIXME: Actually check whether we have 'constant_tsc' and |
| // 'nonstop_tsc' before setting the values in the header. |
| H.ConstantTSC = 1; |
| H.NonstopTSC = 1; |
| }); |
| return reinterpret_cast<XRayFileHeader &>(HStorage); |
| } |
| |
| // This is the iterator implementation, which knows how to handle FDR-mode |
| // specific buffers. This is used as an implementation of the iterator function |
| // needed by __xray_set_buffer_iterator(...). It maintains a global state of the |
| // buffer iteration for the currently installed FDR mode buffers. In particular: |
| // |
| // - If the argument represents the initial state of XRayBuffer ({nullptr, 0}) |
| // then the iterator returns the header information. |
| // - If the argument represents the header information ({address of header |
| // info, size of the header info}) then it returns the first FDR buffer's |
| // address and extents. |
| // - It will keep returning the next buffer and extents as there are more |
| // buffers to process. When the input represents the last buffer, it will |
| // return the initial state to signal completion ({nullptr, 0}). |
| // |
| // See xray/xray_log_interface.h for more details on the requirements for the |
| // implementations of __xray_set_buffer_iterator(...) and |
| // __xray_log_process_buffers(...). |
| XRayBuffer fdrIterator(const XRayBuffer B) { |
| DCHECK(internal_strcmp(__xray_log_get_current_mode(), "xray-fdr") == 0); |
| DCHECK(BQ->finalizing()); |
| |
| if (BQ == nullptr || !BQ->finalizing()) { |
| if (Verbosity()) |
| Report( |
| "XRay FDR: Failed global buffer queue is null or not finalizing!\n"); |
| return {nullptr, 0}; |
| } |
| |
| // We use a global scratch-pad for the header information, which only gets |
| // initialized the first time this function is called. We'll update one part |
| // of this information with some relevant data (in particular the number of |
| // buffers to expect). |
| static std::aligned_storage<sizeof(XRayFileHeader)>::type HeaderStorage; |
| static pthread_once_t HeaderOnce = PTHREAD_ONCE_INIT; |
| pthread_once( |
| &HeaderOnce, +[] { |
| reinterpret_cast<XRayFileHeader &>(HeaderStorage) = |
| fdrCommonHeaderInfo(); |
| }); |
| |
| // We use a convenience alias for code referring to Header from here on out. |
| auto &Header = reinterpret_cast<XRayFileHeader &>(HeaderStorage); |
| if (B.Data == nullptr && B.Size == 0) { |
| Header.FdrData = FdrAdditionalHeaderData{BQ->ConfiguredBufferSize()}; |
| return XRayBuffer{static_cast<void *>(&Header), sizeof(Header)}; |
| } |
| |
| static BufferQueue::const_iterator It{}; |
| static BufferQueue::const_iterator End{}; |
| static uint8_t *CurrentBuffer{nullptr}; |
| static size_t SerializedBufferSize = 0; |
| if (B.Data == static_cast<void *>(&Header) && B.Size == sizeof(Header)) { |
| // From this point on, we provide raw access to the raw buffer we're getting |
| // from the BufferQueue. We're relying on the iterators from the current |
| // Buffer queue. |
| It = BQ->cbegin(); |
| End = BQ->cend(); |
| } |
| |
| if (CurrentBuffer != nullptr) { |
| deallocateBuffer(CurrentBuffer, SerializedBufferSize); |
| CurrentBuffer = nullptr; |
| } |
| |
| if (It == End) |
| return {nullptr, 0}; |
| |
| // Set up the current buffer to contain the extents like we would when writing |
| // out to disk. The difference here would be that we still write "empty" |
| // buffers, or at least go through the iterators faithfully to let the |
| // handlers see the empty buffers in the queue. |
| // |
| // We need this atomic fence here to ensure that writes happening to the |
| // buffer have been committed before we load the extents atomically. Because |
| // the buffer is not explicitly synchronised across threads, we rely on the |
| // fence ordering to ensure that writes we expect to have been completed |
| // before the fence are fully committed before we read the extents. |
| atomic_thread_fence(memory_order_acquire); |
| auto BufferSize = atomic_load(It->Extents, memory_order_acquire); |
| SerializedBufferSize = BufferSize + sizeof(MetadataRecord); |
| CurrentBuffer = allocateBuffer(SerializedBufferSize); |
| if (CurrentBuffer == nullptr) |
| return {nullptr, 0}; |
| |
| // Write out the extents as a Metadata Record into the CurrentBuffer. |
| MetadataRecord ExtentsRecord; |
| ExtentsRecord.Type = uint8_t(RecordType::Metadata); |
| ExtentsRecord.RecordKind = |
| uint8_t(MetadataRecord::RecordKinds::BufferExtents); |
| internal_memcpy(ExtentsRecord.Data, &BufferSize, sizeof(BufferSize)); |
| auto AfterExtents = |
| static_cast<char *>(internal_memcpy(CurrentBuffer, &ExtentsRecord, |
| sizeof(MetadataRecord))) + |
| sizeof(MetadataRecord); |
| internal_memcpy(AfterExtents, It->Data, BufferSize); |
| |
| XRayBuffer Result; |
| Result.Data = CurrentBuffer; |
| Result.Size = SerializedBufferSize; |
| ++It; |
| return Result; |
| } |
| |
| // Must finalize before flushing. |
| XRayLogFlushStatus fdrLoggingFlush() XRAY_NEVER_INSTRUMENT { |
| if (atomic_load(&LoggingStatus, memory_order_acquire) != |
| XRayLogInitStatus::XRAY_LOG_FINALIZED) { |
| if (Verbosity()) |
| Report("Not flushing log, implementation is not finalized.\n"); |
| return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING; |
| } |
| |
| if (atomic_exchange(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHING, |
| memory_order_release) == |
| XRayLogFlushStatus::XRAY_LOG_FLUSHING) { |
| if (Verbosity()) |
| Report("Not flushing log, implementation is still flushing.\n"); |
| return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING; |
| } |
| |
| if (BQ == nullptr) { |
| if (Verbosity()) |
| Report("Cannot flush when global buffer queue is null.\n"); |
| return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING; |
| } |
| |
| // We wait a number of milliseconds to allow threads to see that we've |
| // finalised before attempting to flush the log. |
| SleepForMillis(fdrFlags()->grace_period_ms); |
| |
| // At this point, we're going to uninstall the iterator implementation, before |
| // we decide to do anything further with the global buffer queue. |
| __xray_log_remove_buffer_iterator(); |
| |
| // Once flushed, we should set the global status of the logging implementation |
| // to "uninitialized" to allow for FDR-logging multiple runs. |
| auto ResetToUnitialized = at_scope_exit([] { |
| atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_UNINITIALIZED, |
| memory_order_release); |
| }); |
| |
| auto CleanupBuffers = at_scope_exit([] { |
| auto &TLD = getThreadLocalData(); |
| if (TLD.Controller != nullptr) |
| TLD.Controller->flush(); |
| }); |
| |
| if (fdrFlags()->no_file_flush) { |
| if (Verbosity()) |
| Report("XRay FDR: Not flushing to file, 'no_file_flush=true'.\n"); |
| |
| atomic_store(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHED, |
| memory_order_release); |
| return XRayLogFlushStatus::XRAY_LOG_FLUSHED; |
| } |
| |
| // We write out the file in the following format: |
| // |
| // 1) We write down the XRay file header with version 1, type FDR_LOG. |
| // 2) Then we use the 'apply' member of the BufferQueue that's live, to |
| // ensure that at this point in time we write down the buffers that have |
| // been released (and marked "used") -- we dump the full buffer for now |
| // (fixed-sized) and let the tools reading the buffers deal with the data |
| // afterwards. |
| // |
| LogWriter *LW = LogWriter::Open(); |
| if (LW == nullptr) { |
| auto Result = XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING; |
| atomic_store(&LogFlushStatus, Result, memory_order_release); |
| return Result; |
| } |
| |
| XRayFileHeader Header = fdrCommonHeaderInfo(); |
| Header.FdrData = FdrAdditionalHeaderData{BQ->ConfiguredBufferSize()}; |
| LW->WriteAll(reinterpret_cast<char *>(&Header), |
| reinterpret_cast<char *>(&Header) + sizeof(Header)); |
| |
| // Release the current thread's buffer before we attempt to write out all the |
| // buffers. This ensures that in case we had only a single thread going, that |
| // we are able to capture the data nonetheless. |
| auto &TLD = getThreadLocalData(); |
| if (TLD.Controller != nullptr) |
| TLD.Controller->flush(); |
| |
| BQ->apply([&](const BufferQueue::Buffer &B) { |
| // Starting at version 2 of the FDR logging implementation, we only write |
| // the records identified by the extents of the buffer. We use the Extents |
| // from the Buffer and write that out as the first record in the buffer. We |
| // still use a Metadata record, but fill in the extents instead for the |
| // data. |
| MetadataRecord ExtentsRecord; |
| auto BufferExtents = atomic_load(B.Extents, memory_order_acquire); |
| DCHECK(BufferExtents <= B.Size); |
| ExtentsRecord.Type = uint8_t(RecordType::Metadata); |
| ExtentsRecord.RecordKind = |
| uint8_t(MetadataRecord::RecordKinds::BufferExtents); |
| internal_memcpy(ExtentsRecord.Data, &BufferExtents, sizeof(BufferExtents)); |
| if (BufferExtents > 0) { |
| LW->WriteAll(reinterpret_cast<char *>(&ExtentsRecord), |
| reinterpret_cast<char *>(&ExtentsRecord) + |
| sizeof(MetadataRecord)); |
| LW->WriteAll(reinterpret_cast<char *>(B.Data), |
| reinterpret_cast<char *>(B.Data) + BufferExtents); |
| } |
| }); |
| |
| atomic_store(&LogFlushStatus, XRayLogFlushStatus::XRAY_LOG_FLUSHED, |
| memory_order_release); |
| return XRayLogFlushStatus::XRAY_LOG_FLUSHED; |
| } |
| |
| XRayLogInitStatus fdrLoggingFinalize() XRAY_NEVER_INSTRUMENT { |
| s32 CurrentStatus = XRayLogInitStatus::XRAY_LOG_INITIALIZED; |
| if (!atomic_compare_exchange_strong(&LoggingStatus, &CurrentStatus, |
| XRayLogInitStatus::XRAY_LOG_FINALIZING, |
| memory_order_release)) { |
| if (Verbosity()) |
| Report("Cannot finalize log, implementation not initialized.\n"); |
| return static_cast<XRayLogInitStatus>(CurrentStatus); |
| } |
| |
| // Do special things to make the log finalize itself, and not allow any more |
| // operations to be performed until re-initialized. |
| if (BQ == nullptr) { |
| if (Verbosity()) |
| Report("Attempting to finalize an uninitialized global buffer!\n"); |
| } else { |
| BQ->finalize(); |
| } |
| |
| atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_FINALIZED, |
| memory_order_release); |
| return XRayLogInitStatus::XRAY_LOG_FINALIZED; |
| } |
| |
| struct TSCAndCPU { |
| uint64_t TSC = 0; |
| unsigned char CPU = 0; |
| }; |
| |
| static TSCAndCPU getTimestamp() XRAY_NEVER_INSTRUMENT { |
| // We want to get the TSC as early as possible, so that we can check whether |
| // we've seen this CPU before. We also do it before we load anything else, |
| // to allow for forward progress with the scheduling. |
| TSCAndCPU Result; |
| |
| // Test once for required CPU features |
| static pthread_once_t OnceProbe = PTHREAD_ONCE_INIT; |
| static bool TSCSupported = true; |
| pthread_once( |
| &OnceProbe, +[] { TSCSupported = probeRequiredCPUFeatures(); }); |
| |
| if (TSCSupported) { |
| Result.TSC = __xray::readTSC(Result.CPU); |
| } else { |
| // FIXME: This code needs refactoring as it appears in multiple locations |
| timespec TS; |
| int result = clock_gettime(CLOCK_REALTIME, &TS); |
| if (result != 0) { |
| Report("clock_gettime(2) return %d, errno=%d", result, int(errno)); |
| TS = {0, 0}; |
| } |
| Result.CPU = 0; |
| Result.TSC = TS.tv_sec * __xray::NanosecondsPerSecond + TS.tv_nsec; |
| } |
| return Result; |
| } |
| |
| thread_local atomic_uint8_t Running{0}; |
| |
| static bool setupTLD(ThreadLocalData &TLD) XRAY_NEVER_INSTRUMENT { |
| // Check if we're finalizing, before proceeding. |
| { |
| auto Status = atomic_load(&LoggingStatus, memory_order_acquire); |
| if (Status == XRayLogInitStatus::XRAY_LOG_FINALIZING || |
| Status == XRayLogInitStatus::XRAY_LOG_FINALIZED) { |
| if (TLD.Controller != nullptr) { |
| TLD.Controller->flush(); |
| TLD.Controller = nullptr; |
| } |
| return false; |
| } |
| } |
| |
| if (UNLIKELY(TLD.Controller == nullptr)) { |
| // Set up the TLD buffer queue. |
| if (UNLIKELY(BQ == nullptr)) |
| return false; |
| TLD.BQ = BQ; |
| |
| // Check that we have a valid buffer. |
| if (TLD.Buffer.Generation != BQ->generation() && |
| TLD.BQ->releaseBuffer(TLD.Buffer) != BufferQueue::ErrorCode::Ok) |
| return false; |
| |
| // Set up a buffer, before setting up the log writer. Bail out on failure. |
| if (TLD.BQ->getBuffer(TLD.Buffer) != BufferQueue::ErrorCode::Ok) |
| return false; |
| |
| // Set up the Log Writer for this thread. |
| if (UNLIKELY(TLD.Writer == nullptr)) { |
| auto *LWStorage = reinterpret_cast<FDRLogWriter *>(&TLD.LWStorage); |
| new (LWStorage) FDRLogWriter(TLD.Buffer); |
| TLD.Writer = LWStorage; |
| } else { |
| TLD.Writer->resetRecord(); |
| } |
| |
| auto *CStorage = reinterpret_cast<FDRController<> *>(&TLD.CStorage); |
| new (CStorage) |
| FDRController<>(TLD.BQ, TLD.Buffer, *TLD.Writer, clock_gettime, |
| atomic_load_relaxed(&ThresholdTicks)); |
| TLD.Controller = CStorage; |
| } |
| |
| DCHECK_NE(TLD.Controller, nullptr); |
| return true; |
| } |
| |
| void fdrLoggingHandleArg0(int32_t FuncId, |
| XRayEntryType Entry) XRAY_NEVER_INSTRUMENT { |
| auto TC = getTimestamp(); |
| auto &TSC = TC.TSC; |
| auto &CPU = TC.CPU; |
| RecursionGuard Guard{Running}; |
| if (!Guard) |
| return; |
| |
| auto &TLD = getThreadLocalData(); |
| if (!setupTLD(TLD)) |
| return; |
| |
| switch (Entry) { |
| case XRayEntryType::ENTRY: |
| case XRayEntryType::LOG_ARGS_ENTRY: |
| TLD.Controller->functionEnter(FuncId, TSC, CPU); |
| return; |
| case XRayEntryType::EXIT: |
| TLD.Controller->functionExit(FuncId, TSC, CPU); |
| return; |
| case XRayEntryType::TAIL: |
| TLD.Controller->functionTailExit(FuncId, TSC, CPU); |
| return; |
| case XRayEntryType::CUSTOM_EVENT: |
| case XRayEntryType::TYPED_EVENT: |
| break; |
| } |
| } |
| |
| void fdrLoggingHandleArg1(int32_t FuncId, XRayEntryType Entry, |
| uint64_t Arg) XRAY_NEVER_INSTRUMENT { |
| auto TC = getTimestamp(); |
| auto &TSC = TC.TSC; |
| auto &CPU = TC.CPU; |
| RecursionGuard Guard{Running}; |
| if (!Guard) |
| return; |
| |
| auto &TLD = getThreadLocalData(); |
| if (!setupTLD(TLD)) |
| return; |
| |
| switch (Entry) { |
| case XRayEntryType::ENTRY: |
| case XRayEntryType::LOG_ARGS_ENTRY: |
| TLD.Controller->functionEnterArg(FuncId, TSC, CPU, Arg); |
| return; |
| case XRayEntryType::EXIT: |
| TLD.Controller->functionExit(FuncId, TSC, CPU); |
| return; |
| case XRayEntryType::TAIL: |
| TLD.Controller->functionTailExit(FuncId, TSC, CPU); |
| return; |
| case XRayEntryType::CUSTOM_EVENT: |
| case XRayEntryType::TYPED_EVENT: |
| break; |
| } |
| } |
| |
| void fdrLoggingHandleCustomEvent(void *Event, |
| std::size_t EventSize) XRAY_NEVER_INSTRUMENT { |
| auto TC = getTimestamp(); |
| auto &TSC = TC.TSC; |
| auto &CPU = TC.CPU; |
| RecursionGuard Guard{Running}; |
| if (!Guard) |
| return; |
| |
| // Complain when we ever get at least one custom event that's larger than what |
| // we can possibly support. |
| if (EventSize > |
| static_cast<std::size_t>(std::numeric_limits<int32_t>::max())) { |
| static pthread_once_t Once = PTHREAD_ONCE_INIT; |
| pthread_once( |
| &Once, +[] { |
| Report("Custom event size too large; truncating to %d.\n", |
| std::numeric_limits<int32_t>::max()); |
| }); |
| } |
| |
| auto &TLD = getThreadLocalData(); |
| if (!setupTLD(TLD)) |
| return; |
| |
| int32_t ReducedEventSize = static_cast<int32_t>(EventSize); |
| TLD.Controller->customEvent(TSC, CPU, Event, ReducedEventSize); |
| } |
| |
| void fdrLoggingHandleTypedEvent( |
| uint16_t EventType, const void *Event, |
| std::size_t EventSize) noexcept XRAY_NEVER_INSTRUMENT { |
| auto TC = getTimestamp(); |
| auto &TSC = TC.TSC; |
| auto &CPU = TC.CPU; |
| RecursionGuard Guard{Running}; |
| if (!Guard) |
| return; |
| |
| // Complain when we ever get at least one typed event that's larger than what |
| // we can possibly support. |
| if (EventSize > |
| static_cast<std::size_t>(std::numeric_limits<int32_t>::max())) { |
| static pthread_once_t Once = PTHREAD_ONCE_INIT; |
| pthread_once( |
| &Once, +[] { |
| Report("Typed event size too large; truncating to %d.\n", |
| std::numeric_limits<int32_t>::max()); |
| }); |
| } |
| |
| auto &TLD = getThreadLocalData(); |
| if (!setupTLD(TLD)) |
| return; |
| |
| int32_t ReducedEventSize = static_cast<int32_t>(EventSize); |
| TLD.Controller->typedEvent(TSC, CPU, EventType, Event, ReducedEventSize); |
| } |
| |
| XRayLogInitStatus fdrLoggingInit(size_t, size_t, void *Options, |
| size_t OptionsSize) XRAY_NEVER_INSTRUMENT { |
| if (Options == nullptr) |
| return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED; |
| |
| s32 CurrentStatus = XRayLogInitStatus::XRAY_LOG_UNINITIALIZED; |
| if (!atomic_compare_exchange_strong(&LoggingStatus, &CurrentStatus, |
| XRayLogInitStatus::XRAY_LOG_INITIALIZING, |
| memory_order_release)) { |
| if (Verbosity()) |
| Report("Cannot initialize already initialized implementation.\n"); |
| return static_cast<XRayLogInitStatus>(CurrentStatus); |
| } |
| |
| if (Verbosity()) |
| Report("Initializing FDR mode with options: %s\n", |
| static_cast<const char *>(Options)); |
| |
| // TODO: Factor out the flags specific to the FDR mode implementation. For |
| // now, use the global/single definition of the flags, since the FDR mode |
| // flags are already defined there. |
| FlagParser FDRParser; |
| FDRFlags FDRFlags; |
| registerXRayFDRFlags(&FDRParser, &FDRFlags); |
| FDRFlags.setDefaults(); |
| |
| // Override first from the general XRAY_DEFAULT_OPTIONS compiler-provided |
| // options until we migrate everyone to use the XRAY_FDR_OPTIONS |
| // compiler-provided options. |
| FDRParser.ParseString(useCompilerDefinedFlags()); |
| FDRParser.ParseString(useCompilerDefinedFDRFlags()); |
| auto *EnvOpts = GetEnv("XRAY_FDR_OPTIONS"); |
| if (EnvOpts == nullptr) |
| EnvOpts = ""; |
| FDRParser.ParseString(EnvOpts); |
| |
| // FIXME: Remove this when we fully remove the deprecated flags. |
| if (internal_strlen(EnvOpts) == 0) { |
| FDRFlags.func_duration_threshold_us = |
| flags()->xray_fdr_log_func_duration_threshold_us; |
| FDRFlags.grace_period_ms = flags()->xray_fdr_log_grace_period_ms; |
| } |
| |
| // The provided options should always override the compiler-provided and |
| // environment-variable defined options. |
| FDRParser.ParseString(static_cast<const char *>(Options)); |
| *fdrFlags() = FDRFlags; |
| auto BufferSize = FDRFlags.buffer_size; |
| auto BufferMax = FDRFlags.buffer_max; |
| |
| if (BQ == nullptr) { |
| bool Success = false; |
| BQ = reinterpret_cast<BufferQueue *>(&BufferQueueStorage); |
| new (BQ) BufferQueue(BufferSize, BufferMax, Success); |
| if (!Success) { |
| Report("BufferQueue init failed.\n"); |
| return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED; |
| } |
| } else { |
| if (BQ->init(BufferSize, BufferMax) != BufferQueue::ErrorCode::Ok) { |
| if (Verbosity()) |
| Report("Failed to re-initialize global buffer queue. Init failed.\n"); |
| return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED; |
| } |
| } |
| |
| static pthread_once_t OnceInit = PTHREAD_ONCE_INIT; |
| pthread_once( |
| &OnceInit, +[] { |
| atomic_store(&TicksPerSec, |
| probeRequiredCPUFeatures() ? getTSCFrequency() |
| : __xray::NanosecondsPerSecond, |
| memory_order_release); |
| pthread_key_create( |
| &Key, +[](void *TLDPtr) { |
| if (TLDPtr == nullptr) |
| return; |
| auto &TLD = *reinterpret_cast<ThreadLocalData *>(TLDPtr); |
| if (TLD.BQ == nullptr) |
| return; |
| if (TLD.Buffer.Data == nullptr) |
| return; |
| auto EC = TLD.BQ->releaseBuffer(TLD.Buffer); |
| if (EC != BufferQueue::ErrorCode::Ok) |
| Report("At thread exit, failed to release buffer at %p; " |
| "error=%s\n", |
| TLD.Buffer.Data, BufferQueue::getErrorString(EC)); |
| }); |
| }); |
| |
| atomic_store(&ThresholdTicks, |
| atomic_load_relaxed(&TicksPerSec) * |
| fdrFlags()->func_duration_threshold_us / 1000000, |
| memory_order_release); |
| // Arg1 handler should go in first to avoid concurrent code accidentally |
| // falling back to arg0 when it should have ran arg1. |
| __xray_set_handler_arg1(fdrLoggingHandleArg1); |
| // Install the actual handleArg0 handler after initialising the buffers. |
| __xray_set_handler(fdrLoggingHandleArg0); |
| __xray_set_customevent_handler(fdrLoggingHandleCustomEvent); |
| __xray_set_typedevent_handler(fdrLoggingHandleTypedEvent); |
| |
| // Install the buffer iterator implementation. |
| __xray_log_set_buffer_iterator(fdrIterator); |
| |
| atomic_store(&LoggingStatus, XRayLogInitStatus::XRAY_LOG_INITIALIZED, |
| memory_order_release); |
| |
| if (Verbosity()) |
| Report("XRay FDR init successful.\n"); |
| return XRayLogInitStatus::XRAY_LOG_INITIALIZED; |
| } |
| |
| bool fdrLogDynamicInitializer() XRAY_NEVER_INSTRUMENT { |
| XRayLogImpl Impl{ |
| fdrLoggingInit, |
| fdrLoggingFinalize, |
| fdrLoggingHandleArg0, |
| fdrLoggingFlush, |
| }; |
| auto RegistrationResult = __xray_log_register_mode("xray-fdr", Impl); |
| if (RegistrationResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK && |
| Verbosity()) { |
| Report("Cannot register XRay FDR mode to 'xray-fdr'; error = %d\n", |
| RegistrationResult); |
| return false; |
| } |
| |
| if (flags()->xray_fdr_log || |
| !internal_strcmp(flags()->xray_mode, "xray-fdr")) { |
| auto SelectResult = __xray_log_select_mode("xray-fdr"); |
| if (SelectResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK && |
| Verbosity()) { |
| Report("Cannot select XRay FDR mode as 'xray-fdr'; error = %d\n", |
| SelectResult); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace __xray |
| |
| static auto UNUSED Unused = __xray::fdrLogDynamicInitializer(); |