1 //===-- xray_inmemory_log.cc ------------------------------------*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file is a part of XRay, a dynamic runtime instrumentation system.
12 // Implementation of a simple in-memory log of XRay events. This defines a
13 // logging function that's compatible with the XRay handler interface, and
14 // routines for exporting data to files.
16 //===----------------------------------------------------------------------===//
24 #include <sys/syscall.h>
25 #include <sys/types.h>
29 #include "sanitizer_common/sanitizer_allocator_internal.h"
30 #include "sanitizer_common/sanitizer_libc.h"
31 #include "xray/xray_records.h"
32 #include "xray_defs.h"
33 #include "xray_flags.h"
34 #include "xray_inmemory_log.h"
35 #include "xray_interface_internal.h"
37 #include "xray_utils.h"
41 __sanitizer::SpinMutex LogMutex;
43 // We use elements of this type to record the entry TSC of every function ID we
44 // see as we're tracing a particular thread's execution.
45 struct alignas(16) StackEntry {
53 static_assert(sizeof(StackEntry) == 16, "Wrong size for StackEntry");
55 struct alignas(64) ThreadLocalData {
56 void *InMemoryBuffer = nullptr;
57 size_t BufferSize = 0;
58 size_t BufferOffset = 0;
59 void *ShadowStack = nullptr;
61 size_t StackEntries = 0;
66 static pthread_key_t PThreadKey;
68 static __sanitizer::atomic_uint8_t BasicInitialized{0};
70 BasicLoggingOptions GlobalOptions;
72 thread_local volatile bool RecursionGuard = false;
74 static uint64_t thresholdTicks() XRAY_NEVER_INSTRUMENT {
75 static uint64_t TicksPerSec = probeRequiredCPUFeatures()
77 : __xray::NanosecondsPerSecond;
78 static const uint64_t ThresholdTicks =
79 TicksPerSec * GlobalOptions.DurationFilterMicros / 1000000;
80 return ThresholdTicks;
83 static int openLogFile() XRAY_NEVER_INSTRUMENT {
88 // Test for required CPU features and cache the cycle frequency
89 static bool TSCSupported = probeRequiredCPUFeatures();
90 static uint64_t CycleFrequency =
91 TSCSupported ? getTSCFrequency() : __xray::NanosecondsPerSecond;
93 // Since we're here, we get to write the header. We set it up so that the
94 // header will only be written once, at the start, and let the threads
95 // logging do writes which just append.
96 XRayFileHeader Header;
97 Header.Version = 2; // Version 2 includes tail exit records.
98 Header.Type = FileTypes::NAIVE_LOG;
99 Header.CycleFrequency = CycleFrequency;
101 // FIXME: Actually check whether we have 'constant_tsc' and 'nonstop_tsc'
102 // before setting the values in the header.
103 Header.ConstantTSC = 1;
104 Header.NonstopTSC = 1;
105 retryingWriteAll(F, reinterpret_cast<char *>(&Header),
106 reinterpret_cast<char *>(&Header) + sizeof(Header));
110 int getGlobalFd() XRAY_NEVER_INSTRUMENT {
111 static int Fd = openLogFile();
115 ThreadLocalData &getThreadLocalData() XRAY_NEVER_INSTRUMENT {
116 thread_local ThreadLocalData TLD;
117 thread_local bool UNUSED TOnce = [] {
118 if (GlobalOptions.ThreadBufferSize == 0) {
119 if (__sanitizer::Verbosity())
120 Report("Not initializing TLD since ThreadBufferSize == 0.\n");
123 TLD.TID = __sanitizer::GetTid();
124 pthread_setspecific(PThreadKey, &TLD);
125 TLD.Fd = getGlobalFd();
126 TLD.InMemoryBuffer = reinterpret_cast<XRayRecord *>(
127 InternalAlloc(sizeof(XRayRecord) * GlobalOptions.ThreadBufferSize,
128 nullptr, alignof(XRayRecord)));
129 TLD.BufferSize = GlobalOptions.ThreadBufferSize;
130 TLD.BufferOffset = 0;
131 if (GlobalOptions.MaxStackDepth == 0) {
132 if (__sanitizer::Verbosity())
133 Report("Not initializing the ShadowStack since MaxStackDepth == 0.\n");
135 TLD.StackEntries = 0;
136 TLD.ShadowStack = nullptr;
139 TLD.ShadowStack = reinterpret_cast<StackEntry *>(
140 InternalAlloc(sizeof(StackEntry) * GlobalOptions.MaxStackDepth, nullptr,
141 alignof(StackEntry)));
142 TLD.StackSize = GlobalOptions.MaxStackDepth;
143 TLD.StackEntries = 0;
144 if (__sanitizer::Verbosity() >= 2) {
145 static auto UNUSED Once = [] {
146 auto ticks = thresholdTicks();
147 Report("Ticks threshold: %d\n", ticks);
156 template <class RDTSC>
157 void InMemoryRawLog(int32_t FuncId, XRayEntryType Type,
158 RDTSC ReadTSC) XRAY_NEVER_INSTRUMENT {
159 auto &TLD = getThreadLocalData();
160 auto &InMemoryBuffer = TLD.InMemoryBuffer;
161 int Fd = getGlobalFd();
165 // Use a simple recursion guard, to handle cases where we're already logging
166 // and for one reason or another, this function gets called again in the same
170 RecursionGuard = true;
171 auto ExitGuard = __sanitizer::at_scope_exit([] { RecursionGuard = false; });
174 uint64_t TSC = ReadTSC(CPU);
177 case XRayEntryType::ENTRY:
178 case XRayEntryType::LOG_ARGS_ENTRY: {
179 // Short circuit if we've reached the maximum depth of the stack.
180 if (TLD.StackEntries++ >= TLD.StackSize)
183 // When we encounter an entry event, we keep track of the TSC and the CPU,
184 // and put it in the stack.
190 auto StackEntryPtr = static_cast<char *>(TLD.ShadowStack) +
191 (sizeof(StackEntry) * (TLD.StackEntries - 1));
192 __sanitizer::internal_memcpy(StackEntryPtr, &E, sizeof(StackEntry));
195 case XRayEntryType::EXIT:
196 case XRayEntryType::TAIL: {
197 if (TLD.StackEntries == 0)
200 if (--TLD.StackEntries >= TLD.StackSize)
203 // When we encounter an exit event, we check whether all the following are
206 // - The Function ID is the same as the most recent entry in the stack.
207 // - The CPU is the same as the most recent entry in the stack.
208 // - The Delta of the TSCs is less than the threshold amount of time we're
209 // looking to record.
211 // If all of these conditions are true, we pop the stack and don't write a
212 // record and move the record offset back.
214 auto StackEntryPtr = static_cast<char *>(TLD.ShadowStack) +
215 (sizeof(StackEntry) * TLD.StackEntries);
216 __sanitizer::internal_memcpy(&StackTop, StackEntryPtr, sizeof(StackEntry));
217 if (StackTop.FuncId == FuncId && StackTop.CPU == CPU &&
218 StackTop.TSC < TSC) {
219 auto Delta = TSC - StackTop.TSC;
220 if (Delta < thresholdTicks()) {
221 assert(TLD.BufferOffset > 0);
222 TLD.BufferOffset -= StackTop.Type == XRayEntryType::ENTRY ? 1 : 2;
229 // Should be unreachable.
230 assert(false && "Unsupported XRayEntryType encountered.");
234 // First determine whether the delta between the function's enter record and
235 // the exit record is higher than the threshold.
236 __xray::XRayRecord R;
237 R.RecordType = RecordTypes::NORMAL;
243 auto EntryPtr = static_cast<char *>(InMemoryBuffer) +
244 (sizeof(__xray::XRayRecord) * TLD.BufferOffset);
245 __sanitizer::internal_memcpy(EntryPtr, &R, sizeof(R));
246 if (++TLD.BufferOffset == TLD.BufferSize) {
247 __sanitizer::SpinMutexLock L(&LogMutex);
248 auto RecordBuffer = reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer);
249 retryingWriteAll(Fd, reinterpret_cast<char *>(RecordBuffer),
250 reinterpret_cast<char *>(RecordBuffer + TLD.BufferOffset));
251 TLD.BufferOffset = 0;
252 TLD.StackEntries = 0;
256 template <class RDTSC>
257 void InMemoryRawLogWithArg(int32_t FuncId, XRayEntryType Type, uint64_t Arg1,
258 RDTSC ReadTSC) XRAY_NEVER_INSTRUMENT {
259 auto &TLD = getThreadLocalData();
260 auto &InMemoryBuffer = TLD.InMemoryBuffer;
261 auto &Offset = TLD.BufferOffset;
262 const auto &BuffLen = TLD.BufferSize;
263 int Fd = getGlobalFd();
267 // First we check whether there's enough space to write the data consecutively
268 // in the thread-local buffer. If not, we first flush the buffer before
269 // attempting to write the two records that must be consecutive.
270 if (Offset + 2 > BuffLen) {
271 __sanitizer::SpinMutexLock L(&LogMutex);
272 auto RecordBuffer = reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer);
273 retryingWriteAll(Fd, reinterpret_cast<char *>(RecordBuffer),
274 reinterpret_cast<char *>(RecordBuffer + Offset));
276 TLD.StackEntries = 0;
279 // Then we write the "we have an argument" record.
280 InMemoryRawLog(FuncId, Type, ReadTSC);
284 RecursionGuard = true;
285 auto ExitGuard = __sanitizer::at_scope_exit([] { RecursionGuard = false; });
287 // And from here on write the arg payload.
288 __xray::XRayArgPayload R;
289 R.RecordType = RecordTypes::ARG_PAYLOAD;
294 &reinterpret_cast<__xray::XRayArgPayload *>(&InMemoryBuffer)[Offset];
295 std::memcpy(EntryPtr, &R, sizeof(R));
296 if (++Offset == BuffLen) {
297 __sanitizer::SpinMutexLock L(&LogMutex);
298 auto RecordBuffer = reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer);
299 retryingWriteAll(Fd, reinterpret_cast<char *>(RecordBuffer),
300 reinterpret_cast<char *>(RecordBuffer + Offset));
302 TLD.StackEntries = 0;
306 void basicLoggingHandleArg0RealTSC(int32_t FuncId,
307 XRayEntryType Type) XRAY_NEVER_INSTRUMENT {
308 InMemoryRawLog(FuncId, Type, __xray::readTSC);
311 void basicLoggingHandleArg0EmulateTSC(int32_t FuncId, XRayEntryType Type)
312 XRAY_NEVER_INSTRUMENT {
313 InMemoryRawLog(FuncId, Type, [](uint8_t &CPU) XRAY_NEVER_INSTRUMENT {
315 int result = clock_gettime(CLOCK_REALTIME, &TS);
317 Report("clock_gettimg(2) return %d, errno=%d.", result, int(errno));
321 return TS.tv_sec * __xray::NanosecondsPerSecond + TS.tv_nsec;
325 void basicLoggingHandleArg1RealTSC(int32_t FuncId, XRayEntryType Type,
326 uint64_t Arg1) XRAY_NEVER_INSTRUMENT {
327 InMemoryRawLogWithArg(FuncId, Type, Arg1, __xray::readTSC);
330 void basicLoggingHandleArg1EmulateTSC(int32_t FuncId, XRayEntryType Type,
331 uint64_t Arg1) XRAY_NEVER_INSTRUMENT {
332 InMemoryRawLogWithArg(
333 FuncId, Type, Arg1, [](uint8_t &CPU) XRAY_NEVER_INSTRUMENT {
335 int result = clock_gettime(CLOCK_REALTIME, &TS);
337 Report("clock_gettimg(2) return %d, errno=%d.", result, int(errno));
341 return TS.tv_sec * __xray::NanosecondsPerSecond + TS.tv_nsec;
345 static void TLDDestructor(void *P) XRAY_NEVER_INSTRUMENT {
346 ThreadLocalData &TLD = *reinterpret_cast<ThreadLocalData *>(P);
347 auto ExitGuard = __sanitizer::at_scope_exit([&TLD] {
348 // Clean up dynamic resources.
349 if (TLD.InMemoryBuffer)
350 InternalFree(TLD.InMemoryBuffer);
352 InternalFree(TLD.ShadowStack);
353 if (__sanitizer::Verbosity())
354 Report("Cleaned up log for TID: %d\n", TLD.TID);
357 if (TLD.Fd == -1 || TLD.BufferOffset == 0) {
358 if (__sanitizer::Verbosity())
359 Report("Skipping buffer for TID: %d; Fd = %d; Offset = %llu\n", TLD.TID,
360 TLD.Fd, TLD.BufferOffset);
365 __sanitizer::SpinMutexLock L(&LogMutex);
366 retryingWriteAll(TLD.Fd, reinterpret_cast<char *>(TLD.InMemoryBuffer),
367 reinterpret_cast<char *>(TLD.InMemoryBuffer) +
368 (sizeof(__xray::XRayRecord) * TLD.BufferOffset));
371 // Because this thread's exit could be the last one trying to write to
372 // the file and that we're not able to close out the file properly, we
373 // sync instead and hope that the pending writes are flushed as the
378 XRayLogInitStatus basicLoggingInit(size_t BufferSize, size_t BufferMax,
380 size_t OptionsSize) XRAY_NEVER_INSTRUMENT {
381 static bool UNUSED Once = [] {
382 pthread_key_create(&PThreadKey, TLDDestructor);
386 uint8_t Expected = 0;
387 if (!__sanitizer::atomic_compare_exchange_strong(
388 &BasicInitialized, &Expected, 1, __sanitizer::memory_order_acq_rel)) {
389 if (__sanitizer::Verbosity())
390 Report("Basic logging already initialized.\n");
391 return XRayLogInitStatus::XRAY_LOG_INITIALIZED;
394 if (OptionsSize != sizeof(BasicLoggingOptions)) {
395 Report("Invalid options size, potential ABI mismatch; expected %d got %d",
396 sizeof(BasicLoggingOptions), OptionsSize);
397 return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
400 static auto UseRealTSC = probeRequiredCPUFeatures();
401 if (!UseRealTSC && __sanitizer::Verbosity())
402 Report("WARNING: Required CPU features missing for XRay instrumentation, "
403 "using emulation instead.\n");
405 GlobalOptions = *reinterpret_cast<BasicLoggingOptions *>(Options);
406 __xray_set_handler_arg1(UseRealTSC ? basicLoggingHandleArg1RealTSC
407 : basicLoggingHandleArg1EmulateTSC);
408 __xray_set_handler(UseRealTSC ? basicLoggingHandleArg0RealTSC
409 : basicLoggingHandleArg0EmulateTSC);
410 __xray_remove_customevent_handler();
412 return XRayLogInitStatus::XRAY_LOG_INITIALIZED;
415 XRayLogInitStatus basicLoggingFinalize() XRAY_NEVER_INSTRUMENT {
416 uint8_t Expected = 0;
417 if (!__sanitizer::atomic_compare_exchange_strong(
418 &BasicInitialized, &Expected, 0, __sanitizer::memory_order_acq_rel) &&
419 __sanitizer::Verbosity())
420 Report("Basic logging already finalized.\n");
422 // Nothing really to do aside from marking state of the global to be
425 return XRayLogInitStatus::XRAY_LOG_FINALIZED;
428 XRayLogFlushStatus basicLoggingFlush() XRAY_NEVER_INSTRUMENT {
429 // This really does nothing, since flushing the logs happen at the end of a
430 // thread's lifetime, or when the buffers are full.
431 return XRayLogFlushStatus::XRAY_LOG_FLUSHED;
434 // This is a handler that, effectively, does nothing.
435 void basicLoggingHandleArg0Empty(int32_t, XRayEntryType) XRAY_NEVER_INSTRUMENT {
438 bool basicLogDynamicInitializer() XRAY_NEVER_INSTRUMENT {
441 basicLoggingFinalize,
442 basicLoggingHandleArg0Empty,
445 auto RegistrationResult = __xray_log_register_mode("xray-basic", Impl);
446 if (RegistrationResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK &&
447 __sanitizer::Verbosity())
448 Report("Cannot register XRay Basic Mode to 'xray-basic'; error = %d\n",
450 if (flags()->xray_naive_log ||
451 !__sanitizer::internal_strcmp(flags()->xray_mode, "xray-basic")) {
452 __xray_set_log_impl(Impl);
453 BasicLoggingOptions Options;
454 Options.DurationFilterMicros =
455 flags()->xray_naive_log_func_duration_threshold_us;
456 Options.MaxStackDepth = flags()->xray_naive_log_max_stack_depth;
457 Options.ThreadBufferSize = flags()->xray_naive_log_thread_buffer_size;
458 __xray_log_init(flags()->xray_naive_log_thread_buffer_size, 0, &Options,
459 sizeof(BasicLoggingOptions));
460 static auto UNUSED Once = [] {
461 static auto UNUSED &TLD = getThreadLocalData();
462 __sanitizer::Atexit(+[] { TLDDestructor(&TLD); });
469 } // namespace __xray
471 static auto UNUSED Unused = __xray::basicLogDynamicInitializer();