master ohmu/fastfetch-openbsd / src / common / impl / tracer.c
ohmu  ·  2026-10-15
  1// DON'T CALL ANY FASTFETCH FUNCTION IN THIS FILE (to avoid recursion when -finstrument-functions is enabled)
  2
  3#include <stdio.h>
  4#include <time.h>
  5#include <stdatomic.h>
  6#include <stdbool.h>
  7#include <stdint.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <inttypes.h>
 11
 12#if !_WIN32
 13    #include <dlfcn.h>
 14    #include <pthread.h>
 15    #include <unistd.h>
 16#else
 17    #include <windows.h>
 18    #include <dbghelp.h>
 19#endif
 20
 21#if _WIN32
 22static LARGE_INTEGER freq;
 23    #define NtCurrentProcess() ((HANDLE) (-1))
 24#endif
 25
 26static struct trace_event {
 27    uint64_t ts;
 28    uint64_t tid;
 29    void* func;
 30} events[4 * 1024 * 1024]; // 4M events, 96 MiB memory usage
 31static _Atomic uint32_t event_count;
 32
 33[[gnu::no_instrument_function, gnu::always_inline]] static inline uint64_t get_time_us() {
 34#if !_WIN32
 35    struct timespec ts;
 36    clock_gettime(CLOCK_MONOTONIC, &ts);
 37    return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
 38#else
 39    LARGE_INTEGER now;
 40    QueryPerformanceCounter(&now);
 41    return (uint64_t) (now.QuadPart * 1000000 / freq.QuadPart);
 42#endif
 43}
 44
 45#if _WIN32
 46[[gnu::constructor, gnu::no_instrument_function]] void trace_init() {
 47    QueryPerformanceFrequency(&freq);
 48}
 49#endif
 50
 51[[gnu::destructor, gnu::no_instrument_function]] void trace_fini() {
 52#if _WIN32
 53    uint32_t pid = (uint32_t) GetCurrentProcessId();
 54#else
 55    uint32_t pid = (uint32_t) getpid();
 56#endif
 57
 58    char fileName[64];
 59    snprintf(fileName, sizeof(fileName), "trace_%d.json", pid);
 60    FILE* trace_file = fopen(fileName, "wb");
 61    if (!trace_file) {
 62        fprintf(stderr, "Failed to open trace file %s for writing\n", fileName);
 63        abort();
 64    }
 65
 66    fputs("[\n", trace_file);
 67
 68#if _WIN32
 69    SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
 70    SymInitialize(NtCurrentProcess(), nullptr, TRUE);
 71#endif
 72
 73    const char* fnName = nullptr;
 74
 75    uint32_t count = atomic_load_explicit(&event_count, memory_order_acquire);
 76    for (uint32_t i = 0; i < count; ++i) {
 77        void* fn = events[i].func;
 78        uint64_t ts = events[i].ts;
 79        bool is_exit = (ts >> 63) != 0;
 80        ts &= ~(1ULL << 63); // clear the highest bit
 81        uint64_t tid = events[i].tid;
 82
 83#if _WIN32
 84        char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
 85        PSYMBOL_INFO pSymbol = (PSYMBOL_INFO) buffer;
 86        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
 87        pSymbol->MaxNameLen = MAX_SYM_NAME;
 88
 89        DWORD64 displacement = 0;
 90        if (SymFromAddr(NtCurrentProcess(), (DWORD64) fn, &displacement, pSymbol)) {
 91            fnName = pSymbol->Name;
 92        }
 93#else
 94        char buffer[32];
 95        Dl_info info;
 96        if (dladdr(fn, &info) && info.dli_sname) {
 97            fnName = info.dli_sname;
 98        }
 99#endif
100
101        else {
102            snprintf(buffer, sizeof(buffer), "%p", fn);
103            fnName = buffer;
104        }
105
106        fprintf(trace_file, "{\"name\":\"%s\",\"ph\":\"%c\",\"pid\":%" PRIu32 ",\"tid\":%" PRIu64 ",\"ts\":%" PRIu64 "}%c\n", fnName, is_exit ? 'E' : 'B', pid, tid, ts, i < count - 1 ? ',' : ' ');
107    }
108
109    fputc(']', trace_file);
110    fclose(trace_file);
111    fprintf(stderr, "Trace written to trace_%d.json with %u events. Use https://ui.perfetto.dev/ to view it.\n", pid, count);
112
113#if _WIN32
114    SymCleanup(NtCurrentProcess());
115#endif
116}
117
118[[gnu::no_instrument_function]] static void write_event(void* this_fn, bool is_exit) {
119    uint32_t idx = atomic_fetch_add_explicit(&event_count, 1, memory_order_relaxed);
120    if (__builtin_expect(idx >= sizeof(events) / sizeof(events[0]), false)) {
121        abort();
122    }
123
124    events[idx].ts = get_time_us();
125#if ENABLE_THREADS
126    #if _WIN32
127    events[idx].tid = (uint64_t) GetCurrentThreadId();
128    #else
129    events[idx].tid = (uint64_t) (uintptr_t) pthread_self();
130    #endif
131#else
132    events[idx].tid = 0;
133#endif
134    events[idx].func = this_fn;
135    if (is_exit) {
136        events[idx].ts |= (1ULL << 63); // set the highest bit to indicate function exit
137    }
138}
139
140[[gnu::no_instrument_function]] void __cyg_profile_func_enter(void* this_fn, void* call_site) {
141    (void) call_site;
142    write_event(this_fn, false);
143}
144
145[[gnu::no_instrument_function]] void __cyg_profile_func_exit(void* this_fn, void* call_site) {
146    (void) call_site;
147    write_event(this_fn, true);
148}