holdover_test/c_state_analysis/main.c
2025-12-06 13:41:17 +00:00

221 lines
5.8 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* c_state_analysis - Einfaches CLI-Tool zur Analyse von C-State-Daten
*
* Zweck:
* - Liest eine CSV-Datei mit C-State- und Clocksource-Event-Daten ein.
* - Führt eine sehr einfache Auswertung im Sinne der Beschreibung durch:
* * Median der C3-Residency (in Millisekunden) über alle Zeilen.
* * Anzahl der clocksource_switch-Ereignisse (switch_events).
* * Grobe Korrelation zwischen hoher C3-Residency und clocksource_switch-
* Events, umgesetzt als Anteil der Zeilen, in denen gleichzeitig
* (Residency > Schwellwert) und (clocksource_switch == 1) gilt.
*
* Erwartetes CSV-Format (mit Header-Zeile):
* timestamp_ms,c3_residency_ms,clocksource_switch
* 1234.5,5.9,1
* 1235.5,0.7,0
* ...
*
* Aufruf:
* ./c_state_analysis <pfad_zur_csv>
*
* Ausgabe:
* JSON-Struktur im Stil des API-Contracts:
* {
* "analysis_results": {
* "median_c3_residency": <double>,
* "switch_events": <unsigned long>,
* "correlation": <double>
* }
* }
*
* Hinweise:
* - Keine destruktiven Aktionen: Es wird nur gelesen.
* - Keine Netzwerkoperationen.
* - Implementiert eine einfache CSV-Parsing-Logik ohne externe Bibliotheken.
*/
#define LINE_BUF_SIZE 4096
/* Dynamisches Array für double-Werte (C3-Residencies) */
typedef struct {
double *data;
size_t size;
size_t capacity;
} DoubleArray;
static void da_init(DoubleArray *a) {
a->data = NULL;
a->size = 0;
a->capacity = 0;
}
static int da_reserve(DoubleArray *a, size_t new_cap) {
if (new_cap <= a->capacity) return 1;
double *tmp = (double *)realloc(a->data, new_cap * sizeof(double));
if (!tmp) return 0;
a->data = tmp;
a->capacity = new_cap;
return 1;
}
static int da_push_back(DoubleArray *a, double v) {
if (a->size == a->capacity) {
size_t new_cap = a->capacity == 0 ? 128 : a->capacity * 2;
if (!da_reserve(a, new_cap)) return 0;
}
a->data[a->size++] = v;
return 1;
}
static void da_free(DoubleArray *a) {
free(a->data);
a->data = NULL;
a->size = 0;
a->capacity = 0;
}
/* Vergleichsfunktion für qsort (aufsteigend) */
static int cmp_double(const void *a, const void *b) {
double da = *(const double *)a;
double db = *(const double *)b;
if (da < db) return -1;
if (da > db) return 1;
return 0;
}
/* Berechnet den Median eines Double-Arrays; liefert 0.0 bei size == 0 */
static double median(DoubleArray *a) {
if (a->size == 0) return 0.0;
qsort(a->data, a->size, sizeof(double), cmp_double);
if (a->size % 2 == 1) {
return a->data[a->size / 2];
} else {
size_t i = a->size / 2;
return (a->data[i - 1] + a->data[i]) / 2.0;
}
}
/*
* Einfache CSV-Zeile zerlegen.
* Erwartet eine Zeile mit mindestens 3 Spalten:
* timestamp_ms,c3_residency_ms,clocksource_switch
* Timestamp wird ignoriert; wir parsen nur Spalten 2 und 3.
*/
static int parse_csv_line(const char *line, double *c3_residency, int *switch_flag) {
char buf[LINE_BUF_SIZE];
strncpy(buf, line, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *saveptr = NULL;
char *token = NULL;
int col = 0;
double c3 = 0.0;
int sw = 0;
token = strtok_r(buf, ",", &saveptr);
while (token != NULL) {
if (col == 1) {
c3 = atof(token);
} else if (col == 2) {
sw = atoi(token);
}
col++;
token = strtok_r(NULL, ",", &saveptr);
}
if (col < 3) {
return 0; /* Zu wenige Spalten */
}
*c3_residency = c3;
*switch_flag = sw;
return 1;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <c_state_csv_file>\n", argv[0]);
return 1;
}
const char *path = argv[1];
FILE *f = fopen(path, "r");
if (!f) {
perror("fopen");
return 1;
}
char line[LINE_BUF_SIZE];
int is_header = 1;
DoubleArray residencies;
da_init(&residencies);
unsigned long switch_events = 0; /* Gesamtzahl clocksource_switch==1 */
unsigned long total_rows = 0; /* Gesamtzahl gültiger Datenzeilen */
unsigned long joint_high = 0; /* Zeilen mit hoher C3-Residency UND switch */
/* Schwellwert für "hohe" C3-Residency in ms (heuristisch): */
const double HIGH_C3_THRESHOLD_MS = 2.0;
while (fgets(line, sizeof(line), f)) {
/* Header-Zeile überspringen (angenommen erste Zeile ist Header) */
if (is_header) {
is_header = 0;
continue;
}
/* Leere oder sehr kurze Zeilen ignorieren */
if (line[0] == '\n' || line[0] == '\0') {
continue;
}
double c3 = 0.0;
int sw = 0;
if (!parse_csv_line(line, &c3, &sw)) {
continue; /* Zeilen mit falschem Format überspringen */
}
if (!da_push_back(&residencies, c3)) {
fprintf(stderr, "Out of memory while storing data\n");
da_free(&residencies);
fclose(f);
return 1;
}
total_rows++;
if (sw != 0) {
switch_events++;
}
if (c3 > HIGH_C3_THRESHOLD_MS && sw != 0) {
joint_high++;
}
}
fclose(f);
double med_c3 = median(&residencies);
/* "Korrelation" als Anteil der Zeilen mit hoher C3-Residency UND switch */
double corr = 0.0;
if (total_rows > 0) {
corr = (double)joint_high / (double)total_rows;
}
da_free(&residencies);
/* JSON-Ausgabe entsprechend dem API-Contract */
printf("{\n");
printf(" \"analysis_results\": {\n");
printf(" \"median_c3_residency\": %.6f,\n", med_c3);
printf(" \"switch_events\": %lu,\n", switch_events);
printf(" \"correlation\": %.6f\n", corr);
printf(" }\n");
printf("}\n");
return 0;
}