Add trace_cmd_tool/main.c

This commit is contained in:
Mika 2025-12-17 12:03:31 +00:00
commit a751ca787d

253
trace_cmd_tool/main.c Normal file
View file

@ -0,0 +1,253 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
/*
* trace_cmd_tool
* --------------
* Kleines Linux-CLI-Tool, das Kernel-Traces mit "trace-cmd" startet
* und ein sehr einfaches Analyse-Frontend bereitstellt.
*
* Ziel im Kontext des Experiments:
* - Traces zu einem gegebenen trace_id für eine Dauer (Sekunden) erfassen
* - Metadata zu Laufzeit und Trace-Datei ausgeben
* - Ausgabe ist textuell/human-readable; strukturierte Daten koennen
* leicht weiterverarbeitet werden (z. B. durch ein separates
* Analyse-Skript wie trace_agg.py).
*
* Vereinfachte API-Entsprechung zur Vorgabe:
* struct trace_result {
* char timestamp[64]; // ISO-8601 Startzeit des Runs
* char event_data[256]; // Kurzbeschreibung / Pfad zur Trace-Datei
* };
*
* int run_trace(const char *trace_id, int duration, struct trace_result *out);
*
* Nutzung (Beispiele):
* ./trace_cmd_tool run --id offset_test --duration 5
* ./trace_cmd_tool analyze --file trace_offset_test.dat
*
* Hinweis:
* - Das Programm ruft extern "trace-cmd" auf (muss im PATH vorhanden sein).
* - Es werden nur Dateien erzeugt (trace-*.dat); keine destruktiven Aktionen.
*/
struct trace_result {
char timestamp[64];
char event_data[256];
};
static void print_usage(const char *prog)
{
fprintf(stderr,
"Usage:\n"
" %s run --id <trace_id> --duration <seconds> [--events <ftrace-events>]\n"
" %s analyze --file <trace_file>\n\n"
"Options:\n"
" --id Logischer Name des Trace-Runs (z.B. offset_test)\n"
" --duration Dauer des Traces in Sekunden (integer > 0)\n"
" --events Optional: Komma-separierte Liste von ftrace-Events\n"
" (Standard: sched:sched_switch,irq:*,timer:*)\n"
" --file Vorhandene trace-cmd-Datei zur einfachen Analyse\n\n"
"Beispiele:\n"
" %s run --id offset_vm --duration 10\n"
" %s analyze --file trace_offset_vm.dat\n",
prog, prog, prog, prog);
}
/*
* Hilfsfunktion: ISO-8601 Zeitstempel in Buffer schreiben.
*/
static void get_iso_timestamp(char *buf, size_t buflen)
{
time_t now = time(NULL);
struct tm tm;
if (now == (time_t)-1) {
snprintf(buf, buflen, "unknown-time");
return;
}
if (!gmtime_r(&now, &tm)) {
snprintf(buf, buflen, "unknown-time");
return;
}
strftime(buf, buflen, "%Y-%m-%dT%H:%M:%SZ", &tm);
}
/*
* run_trace: Startet trace-cmd record mit angegebener Dauer.
*
* trace_id: Logischer Name; wird im Dateinamen verwendet.
* duration: Dauer des Traces in Sekunden.
* out: trace_result* fuer Metadaten (kann NULL sein).
*
* Rueckgabe: 0 bei Erfolg, !=0 bei Fehler.
*/
static int run_trace(const char *trace_id, int duration, const char *events, struct trace_result *out)
{
if (!trace_id || duration <= 0) {
fprintf(stderr, "[run_trace] invalid arguments\n");
return -1;
}
char timestamp[64];
get_iso_timestamp(timestamp, sizeof(timestamp));
char trace_file[256];
snprintf(trace_file, sizeof(trace_file), "trace_%s.dat", trace_id);
/*
* trace-cmd-Aufruf konstruieren.
* -p function (oder andere Profile) koennten ergänzt werden;
* hier bleiben wir bei einer simplen Event-Auswahl.
*/
const char *default_events = "sched:sched_switch,irq:*,timer:*";
const char *ev = events && events[0] ? events : default_events;
char cmd[1024];
snprintf(cmd, sizeof(cmd),
"trace-cmd record -e %s -o %s sleep %d",
ev, trace_file, duration);
printf("[info] Starting trace-cmd run...\n");
printf("[info] trace_id : %s\n", trace_id);
printf("[info] duration : %d s\n", duration);
printf("[info] events : %s\n", ev);
printf("[info] output file : %s\n", trace_file);
printf("[info] start(UTC) : %s\n", timestamp);
int ret = system(cmd);
if (ret == -1) {
fprintf(stderr, "[error] failed to execute trace-cmd (system error: %s)\n", strerror(errno));
return -1;
}
if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
printf("[info] trace-cmd finished successfully.\n");
} else {
fprintf(stderr, "[error] trace-cmd failed (status=%d).\n", ret);
return -1;
}
if (out) {
memset(out, 0, sizeof(*out));
snprintf(out->timestamp, sizeof(out->timestamp), "%s", timestamp);
snprintf(out->event_data, sizeof(out->event_data), "trace_file=%s", trace_file);
}
return 0;
}
/*
* Sehr einfache Auswertung eines bestehenden trace-cmd-Files.
* Hier werden nur Grobinfos ausgegeben, so dass z.B. CI-Jobs
* eine minimale Laufzeitkontrolle haben, bevor eine tiefere
* Python-Analyse laeuft.
*/
static int analyze_trace_file(const char *filepath)
{
if (!filepath || !filepath[0]) {
fprintf(stderr, "[analyze] no file specified\n");
return -1;
}
printf("[analyze] Trace file : %s\n", filepath);
/*
* Wir rufen trace-cmd report --stat auf, um Basisstatistiken zu
* erhalten (Eventanzahl/Prozessinfos etc.). Dies ist bewusst
* leichtgewichtig und nicht destruktiv.
*/
char cmd[1024];
snprintf(cmd, sizeof(cmd), "trace-cmd report --stat %s", filepath);
int ret = system(cmd);
if (ret == -1) {
fprintf(stderr, "[error] failed to execute trace-cmd report (system error: %s)\n", strerror(errno));
return -1;
}
if (!(WIFEXITED(ret) && WEXITSTATUS(ret) == 0)) {
fprintf(stderr, "[error] trace-cmd report failed (status=%d).\n", ret);
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
const char *mode = argv[1];
if (strcmp(mode, "run") == 0) {
const char *trace_id = NULL;
const char *duration_str = NULL;
const char *events = NULL;
for (int i = 2; i < argc; ++i) {
if (strcmp(argv[i], "--id") == 0 && i + 1 < argc) {
trace_id = argv[++i];
} else if (strcmp(argv[i], "--duration") == 0 && i + 1 < argc) {
duration_str = argv[++i];
} else if (strcmp(argv[i], "--events") == 0 && i + 1 < argc) {
events = argv[++i];
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
if (!trace_id || !duration_str) {
fprintf(stderr, "[error] --id and --duration are required in run mode.\n\n");
print_usage(argv[0]);
return 1;
}
char *endptr = NULL;
long duration = strtol(duration_str, &endptr, 10);
if (!duration_str[0] || *endptr != '\0' || duration <= 0) {
fprintf(stderr, "[error] invalid duration: %s\n", duration_str);
return 1;
}
struct trace_result result;
int rc = run_trace(trace_id, (int)duration, events, &result);
if (rc == 0) {
printf("\n[trace_result]\n");
printf("timestamp = %s\n", result.timestamp);
printf("event_data = %s\n", result.event_data);
}
return rc == 0 ? 0 : 1;
} else if (strcmp(mode, "analyze") == 0) {
const char *file = NULL;
for (int i = 2; i < argc; ++i) {
if (strcmp(argv[i], "--file") == 0 && i + 1 < argc) {
file = argv[++i];
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
if (!file) {
fprintf(stderr, "[error] --file is required in analyze mode.\n\n");
print_usage(argv[0]);
return 1;
}
return analyze_trace_file(file) == 0 ? 0 : 1;
}
fprintf(stderr, "[error] unknown mode: %s\n\n", mode);
print_usage(argv[0]);
return 1;
}