253 lines
8 KiB
C
253 lines
8 KiB
C
#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;
|
|
}
|