/* ac-trace — Unified BPF trace loader for AC Native OS. * * Usage: * ac-trace syscall — syscall frequency/latency profiling * ac-trace frame — DRM page flip jitter detection * ac-trace alsa — ALSA xrun tracing * ac-trace input — evdev input latency histogram * ac-trace alloc — kernel memory allocation profiling * ac-trace stop — stop all active traces * ac-trace status — show active tracers * ac-trace dump — write current buffers to /mnt/trace/ * * Each subcommand loads its pre-compiled BPF bytecode (embedded at build time * via xxd -i), attaches the probes, and streams output to /mnt/trace/.csv. * * Build: musl-gcc -static -O2 -o ac-trace ac-trace.c -lbpf -lelf -lz * (or link against vendored minimal libbpf) */ #include #include #include #include #include #include #include #include /* BPF bytecode skeletons are included at build time. * Each is generated by: xxd -i progs/.bpf.o > .skel.h * For now, this file provides the CLI framework; the actual BPF loading * uses libbpf's bpf_object__open_mem() + bpf_object__load(). */ #ifdef HAS_LIBBPF #include #include /* Embedded BPF ELF bytecodes (generated by xxd -i during build) */ #include "syscall_profile_bpf.skel.h" #include "frame_jitter_bpf.skel.h" #include "alsa_xrun_bpf.skel.h" #include "input_latency_bpf.skel.h" #include "alloc_profile_bpf.skel.h" #endif #define TRACE_DIR "/mnt/trace" #define PID_FILE "/tmp/.ac-trace.pid" static volatile int running = 1; static void sig_handler(int sig) { (void)sig; running = 0; } static void ensure_trace_dir(void) { mkdir(TRACE_DIR, 0755); } static void write_pid(void) { FILE *f = fopen(PID_FILE, "w"); if (f) { fprintf(f, "%d\n", getpid()); fclose(f); } } static void remove_pid(void) { unlink(PID_FILE); } static int is_running(void) { FILE *f = fopen(PID_FILE, "r"); if (!f) return 0; int pid = 0; if (fscanf(f, "%d", &pid) == 1) { fclose(f); return kill(pid, 0) == 0; } fclose(f); return 0; } static void cmd_status(void) { if (is_running()) { FILE *f = fopen(PID_FILE, "r"); int pid = 0; if (f) { fscanf(f, "%d", &pid); fclose(f); } printf("ac-trace active (pid %d)\n", pid); } else { printf("ac-trace inactive\n"); } /* List any existing trace files */ struct stat st; const char *names[] = {"syscall", "frame", "alsa", "input", "alloc"}; for (int i = 0; i < 5; i++) { char path[128]; snprintf(path, sizeof(path), TRACE_DIR "/%s.csv", names[i]); if (stat(path, &st) == 0) { printf(" %s.csv %ldB %s", names[i], (long)st.st_size, ctime(&st.st_mtime)); } } } static void cmd_stop(void) { FILE *f = fopen(PID_FILE, "r"); if (!f) { printf("no active trace\n"); return; } int pid = 0; if (fscanf(f, "%d", &pid) == 1 && kill(pid, 0) == 0) { kill(pid, SIGTERM); printf("stopped ac-trace (pid %d)\n", pid); } fclose(f); remove_pid(); } /* Stub implementations for when libbpf is not available. * These use ftrace/tracefs directly as a fallback. */ static int cmd_syscall_ftrace(void) { printf("syscall profiling via ftrace (BPF not available)\n"); ensure_trace_dir(); write_pid(); /* Enable syscall tracing via tracefs */ FILE *f; f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (!f) { perror("tracefs not mounted"); remove_pid(); return 1; } fprintf(f, "nop\n"); fclose(f); f = fopen("/sys/kernel/tracing/events/raw_syscalls/sys_enter/enable", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } f = fopen("/sys/kernel/tracing/events/raw_syscalls/sys_exit/enable", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } printf("tracing syscalls → " TRACE_DIR "/syscall.csv (ctrl-c to stop)\n"); /* Poll trace_pipe and write CSV */ FILE *pipe = fopen("/sys/kernel/tracing/trace_pipe", "r"); FILE *out = fopen(TRACE_DIR "/syscall.csv", "w"); if (!pipe || !out) { perror("open trace_pipe/output"); remove_pid(); return 1; } fprintf(out, "timestamp,pid,syscall_nr,latency_us\n"); char line[512]; while (running && fgets(line, sizeof(line), pipe)) { /* Parse ftrace output and write simplified CSV */ fputs(line, out); fflush(out); } /* Cleanup */ f = fopen("/sys/kernel/tracing/events/raw_syscalls/sys_enter/enable", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } f = fopen("/sys/kernel/tracing/events/raw_syscalls/sys_exit/enable", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } fclose(pipe); fclose(out); remove_pid(); printf("stopped. output: " TRACE_DIR "/syscall.csv\n"); return 0; } static int cmd_frame_ftrace(void) { printf("frame jitter tracing via ftrace\n"); ensure_trace_dir(); write_pid(); /* Enable function_graph tracer on DRM functions */ FILE *f; f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (!f) { perror("tracefs"); remove_pid(); return 1; } fprintf(f, "function_graph\n"); fclose(f); f = fopen("/sys/kernel/tracing/set_graph_function", "w"); if (f) { fprintf(f, "drm_atomic_helper_commit\n"); fclose(f); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } printf("tracing DRM commits → " TRACE_DIR "/frame.csv (ctrl-c to stop)\n"); FILE *pipe = fopen("/sys/kernel/tracing/trace_pipe", "r"); FILE *out = fopen(TRACE_DIR "/frame.csv", "w"); if (!pipe || !out) { perror("open"); remove_pid(); return 1; } fprintf(out, "timestamp,function,duration_us\n"); char line[512]; while (running && fgets(line, sizeof(line), pipe)) { fputs(line, out); fflush(out); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "nop\n"); fclose(f); } fclose(pipe); fclose(out); remove_pid(); printf("stopped. output: " TRACE_DIR "/frame.csv\n"); return 0; } static int cmd_alsa_ftrace(void) { printf("ALSA xrun tracing via ftrace\n"); ensure_trace_dir(); write_pid(); /* Try to enable ALSA tracepoints if available */ FILE *f; f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (!f) { perror("tracefs"); remove_pid(); return 1; } fprintf(f, "nop\n"); fclose(f); f = fopen("/sys/kernel/tracing/set_ftrace_filter", "w"); if (f) { fprintf(f, "snd_pcm_xrun\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "function\n"); fclose(f); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } printf("tracing ALSA xruns → " TRACE_DIR "/alsa.csv (ctrl-c to stop)\n"); FILE *pipe = fopen("/sys/kernel/tracing/trace_pipe", "r"); FILE *out = fopen(TRACE_DIR "/alsa.csv", "w"); if (!pipe || !out) { perror("open"); remove_pid(); return 1; } fprintf(out, "timestamp,event\n"); char line[512]; while (running && fgets(line, sizeof(line), pipe)) { fputs(line, out); fflush(out); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "nop\n"); fclose(f); } fclose(pipe); fclose(out); remove_pid(); printf("stopped. output: " TRACE_DIR "/alsa.csv\n"); return 0; } static int cmd_input_ftrace(void) { printf("input latency tracing via ftrace\n"); ensure_trace_dir(); write_pid(); FILE *f; f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (!f) { perror("tracefs"); remove_pid(); return 1; } fprintf(f, "nop\n"); fclose(f); f = fopen("/sys/kernel/tracing/set_ftrace_filter", "w"); if (f) { fprintf(f, "input_event\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "function\n"); fclose(f); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } printf("tracing input events → " TRACE_DIR "/input.csv (ctrl-c to stop)\n"); FILE *pipe = fopen("/sys/kernel/tracing/trace_pipe", "r"); FILE *out = fopen(TRACE_DIR "/input.csv", "w"); if (!pipe || !out) { perror("open"); remove_pid(); return 1; } fprintf(out, "timestamp,function,latency_us\n"); char line[512]; while (running && fgets(line, sizeof(line), pipe)) { fputs(line, out); fflush(out); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "nop\n"); fclose(f); } fclose(pipe); fclose(out); remove_pid(); printf("stopped. output: " TRACE_DIR "/input.csv\n"); return 0; } static int cmd_alloc_ftrace(void) { printf("allocation profiling via ftrace\n"); ensure_trace_dir(); write_pid(); FILE *f; f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (!f) { perror("tracefs"); remove_pid(); return 1; } fprintf(f, "nop\n"); fclose(f); f = fopen("/sys/kernel/tracing/set_ftrace_filter", "w"); if (f) { fprintf(f, "__kmalloc\nkfree\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "function\n"); fclose(f); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "1\n"); fclose(f); } printf("tracing allocations → " TRACE_DIR "/alloc.csv (ctrl-c to stop)\n"); FILE *pipe = fopen("/sys/kernel/tracing/trace_pipe", "r"); FILE *out = fopen(TRACE_DIR "/alloc.csv", "w"); if (!pipe || !out) { perror("open"); remove_pid(); return 1; } fprintf(out, "timestamp,function\n"); char line[512]; while (running && fgets(line, sizeof(line), pipe)) { fputs(line, out); fflush(out); } f = fopen("/sys/kernel/tracing/tracing_on", "w"); if (f) { fprintf(f, "0\n"); fclose(f); } f = fopen("/sys/kernel/tracing/current_tracer", "w"); if (f) { fprintf(f, "nop\n"); fclose(f); } fclose(pipe); fclose(out); remove_pid(); printf("stopped. output: " TRACE_DIR "/alloc.csv\n"); return 0; } static void usage(void) { printf("ac-trace — BPF/ftrace tracer for AC Native OS\n\n" "Usage: ac-trace \n\n" "Commands:\n" " syscall Syscall frequency/latency profiling\n" " frame DRM page flip jitter detection\n" " alsa ALSA xrun (underrun) tracing\n" " input evdev input latency histogram\n" " alloc Kernel memory allocation profiling\n" " stop Stop active trace session\n" " status Show active tracers and output files\n" " dump Copy trace buffers to /mnt/trace/\n"); } int main(int argc, char **argv) { if (argc < 2) { usage(); return 1; } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); const char *cmd = argv[1]; if (strcmp(cmd, "status") == 0) { cmd_status(); return 0; } if (strcmp(cmd, "stop") == 0) { cmd_stop(); return 0; } if (strcmp(cmd, "dump") == 0) { printf("trace files in " TRACE_DIR "/:\n"); cmd_status(); return 0; } /* Check if already running */ if (is_running()) { fprintf(stderr, "ac-trace already running. Use 'ac-trace stop' first.\n"); return 1; } /* Dispatch to tracer (ftrace fallback when libbpf not linked) */ if (strcmp(cmd, "syscall") == 0) return cmd_syscall_ftrace(); if (strcmp(cmd, "frame") == 0) return cmd_frame_ftrace(); if (strcmp(cmd, "alsa") == 0) return cmd_alsa_ftrace(); if (strcmp(cmd, "input") == 0) return cmd_input_ftrace(); if (strcmp(cmd, "alloc") == 0) return cmd_alloc_ftrace(); fprintf(stderr, "unknown command: %s\n", cmd); usage(); return 1; }