Global: adds a simple tracer

This commit is contained in:
Carter Li
2026-06-29 16:20:08 +08:00
parent 3ae1bfdc24
commit 08d419d75c
2 changed files with 166 additions and 0 deletions
+22
View File
@@ -105,6 +105,7 @@ cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND"
option(ENABLE_ZLIB "Enable zlib" ON)
option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF)
option(ENABLE_ASAN "Build fastfetch with ASAN (address sanitizer)" OFF)
option(ENABLE_TRACER "Build fastfetch with function tracing" OFF)
option(ENABLE_LTO "Enable link-time optimization in release mode if supported" ON)
option(BUILD_FLASHFETCH "Build flashfetch" ON) # Also build the flashfetch binary
option(BUILD_TESTS "Build tests" OFF) # Also create test executables
@@ -1365,6 +1366,22 @@ elseif(GNU)
)
endif()
if(ENABLE_TRACER)
message(STATUS "Tracer is enabled")
list(APPEND LIBFASTFETCH_SRC src/common/impl/tracer.c)
if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -finstrument-functions-after-inlining")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -finstrument-functions -Dinline=\"__attribute__((no_instrument_function)) inline\"")
endif()
if(NOT WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic")
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -gcodeview -fuse-ld=lld")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--pdb=${CMAKE_BINARY_DIR}/fastfetch.pdb")
endif()
endif()
# Proprietary GPU driver APIs
if(LINUX OR FreeBSD OR WIN32)
list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_nvidia.c)
@@ -1889,6 +1906,11 @@ elseif(WIN32)
PRIVATE "runtimeobject"
)
endif()
if(ENABLE_TRACER)
target_link_libraries(libfastfetch
PRIVATE "dbghelp"
)
endif()
elseif(FreeBSD)
target_link_libraries(libfastfetch
PRIVATE "m"
+144
View File
@@ -0,0 +1,144 @@
// DON'T CALL ANY EXTERNAL FUNCTION IN THIS FILE
#include <stdio.h>
#include <time.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#if !_WIN32
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>
#else
#include <windows.h>
#include <dbghelp.h>
#endif
#if _WIN32
static LARGE_INTEGER freq;
#define NtCurrentProcess() ((HANDLE) (-1))
#endif
static struct trace_event {
uint64_t ts;
uint64_t tid;
void* func;
} events[4 * 1024 * 1024]; // 4M events, 96 MiB memory usage
static _Atomic uint32_t event_count;
__attribute__((no_instrument_function, always_inline)) static inline uint64_t get_time_us() {
#if !_WIN32
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
#else
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (uint64_t) (now.QuadPart * 1000000 / freq.QuadPart);
#endif
}
#if _WIN32
__attribute__((constructor, no_instrument_function)) void trace_init() {
QueryPerformanceFrequency(&freq);
}
#endif
__attribute__((destructor, no_instrument_function)) void trace_fini() {
#if _WIN32
uint32_t pid = (uint32_t) GetCurrentProcessId();
#else
uint32_t pid = (uint32_t) getpid();
#endif
char fileName[64];
snprintf(fileName, sizeof(fileName), "trace_%d.json", pid);
FILE* trace_file = fopen(fileName, "wb");
if (!trace_file) {
fprintf(stderr, "Failed to open trace file %s for writing\n", fileName);
abort();
}
fputs("[\n", trace_file);
#if _WIN32
SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
SymInitialize(NtCurrentProcess(), NULL, TRUE);
#endif
const char* fnName = NULL;
uint32_t count = atomic_load_explicit(&event_count, memory_order_acquire);
for (uint32_t i = 0; i < count; ++i) {
void* fn = events[i].func;
uint64_t ts = events[i].ts;
bool is_exit = (ts >> 63) != 0;
ts &= ~(1ULL << 63); // clear the highest bit
uint64_t tid = events[i].tid;
#if _WIN32
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO) buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
DWORD64 displacement = 0;
if (SymFromAddr(NtCurrentProcess(), (DWORD64) fn, &displacement, pSymbol)) {
fnName = pSymbol->Name;
}
#else
char buffer[32];
Dl_info info;
if (dladdr(fn, &info) && info.dli_sname) {
fnName = info.dli_sname;
}
#endif
else {
snprintf(buffer, sizeof(buffer), "%p", fn);
fnName = buffer;
}
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 ? ',' : ' ');
}
fputs("\n]\n", trace_file);
fclose(trace_file);
fprintf(stderr, "Trace written to trace_%d.json with %u events. Use https://ui.perfetto.dev/ to view it.\n", pid, count);
#if _WIN32
SymCleanup(NtCurrentProcess());
#endif
}
__attribute__((no_instrument_function)) static void write_event(void* this_fn, bool is_exit) {
uint32_t idx = atomic_fetch_add_explicit(&event_count, 1, memory_order_relaxed);
if (__builtin_expect(idx >= sizeof(events) / sizeof(events[0]), false)) {
abort();
}
events[idx].ts = get_time_us();
#if _WIN32
events[idx].tid = (uint64_t) GetCurrentThreadId();
#else
events[idx].tid = (uint64_t) (uintptr_t) pthread_self();
#endif
events[idx].func = this_fn;
if (is_exit) {
events[idx].ts |= (1ULL << 63); // set the highest bit to indicate function exit
}
}
__attribute__((no_instrument_function)) void __cyg_profile_func_enter(void* this_fn, void* call_site) {
(void) call_site;
write_event(this_fn, false);
}
__attribute__((no_instrument_function)) void __cyg_profile_func_exit(void* this_fn, void* call_site) {
(void) call_site;
write_event(this_fn, true);
}