Files
hiperiso/logging/hiperiso-log/serial_parser.c
T
2026-06-30 14:30:52 +03:00

305 lines
8.3 KiB
C

/*
* serial_parser.c - Parse serial console logs and classify boot stages.
*
* Reads the text captured by QEMU's -serial file:serial.log and classifies
* each line into a boot stage (firmware → bootloader → kernel → hardware →
* userspace → login). Kernel timestamps in [X.XXXXXX] prefix are used to
* estimate boot duration.
*/
#include "serial_parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
const char *boot_stage_names[STAGE_COUNT] = {
"unknown",
"firmware",
"bootloader",
"kernel_init",
"hardware_init",
"userspace",
"login",
"complete",
"error"
};
static int is_benign_warning(const char *line)
{
if (strstr(line, "ACPI:") || strstr(line, "ACPI Error:") ||
strstr(line, "acpi"))
return 1;
if (strstr(line, "[drm:") || strstr(line, "drm:") ||
strstr(line, "DRM:"))
return 1;
if (strstr(line, "scheduling while atomic") ||
strstr(line, "warning:"))
return 1;
if (strstr(line, "can't find") || strstr(line, "not found.") ||
strstr(line, "unknown symbol"))
return 1;
return 0;
}
static boot_stage_t classify_line(const char *line)
{
if (strstr(line, "kernel panic") || strstr(line, "Kernel panic") ||
strstr(line, "Kernel Panic") || strstr(line, "not syncing."))
return STAGE_ERROR;
if ((strstr(line, "BUG:") || strstr(line, "Oops:") ||
strstr(line, "Call Trace")) && !is_benign_warning(line))
return STAGE_ERROR;
if (strstr(line, "BdsDxe") || strstr(line, "OVMF") ||
strstr(line, "UEFI") || strstr(line, "SEC:") ||
strstr(line, "PEI:") || strstr(line, "DXE:"))
return STAGE_FIRMWARE;
if (strstr(line, "grub") || strstr(line, "GRUB") ||
strstr(line, "GNU GRUB") || strstr(line, "systemd-boot"))
return STAGE_BOOTLOADER;
if (strstr(line, "Linux version") || strstr(line, "Booting Linux"))
return STAGE_KERNEL_INIT;
if (strstr(line, "login:") || strstr(line, "Login") ||
strstr(line, "Welcome"))
return STAGE_LOGIN;
if (strstr(line, "boot finished") || strstr(line, "Boot complete") ||
strstr(line, "System finished") || strstr(line, "Reached target"))
return STAGE_COMPLETE;
if (strstr(line, "systemd") || strstr(line, "Starting") ||
strstr(line, "Mounted") || strstr(line, "Started"))
return STAGE_USERSPACE;
if (strstr(line, "[drm]") || strstr(line, "usb") || strstr(line, "USB") ||
strstr(line, "pci") || strstr(line, "PCI") ||
strstr(line, "ahci") || strstr(line, "AHCI") ||
strstr(line, "SCSI") || strstr(line, "scsi") ||
strstr(line, "ata") || strstr(line, "ATA") ||
strstr(line, "virtio")|| strstr(line, "VIRTIO")||
strstr(line, "input:")|| strstr(line, "eth") ||
strstr(line, "acpi") || strstr(line, "ACPI"))
return STAGE_HARDWARE_INIT;
return STAGE_UNKNOWN;
}
static int extract_timestamp(const char *line, double *ts)
{
const char *p = strchr(line, '[');
if (!p)
return -1;
p++;
char *end;
double val = strtod(p, &end);
if (end == p)
return -1;
if (*end != ']')
return -1;
*ts = val;
return 0;
}
static char *read_line(FILE *f)
{
size_t cap = 256;
size_t len = 0;
char *buf = malloc(cap);
if (!buf)
return NULL;
int c;
while ((c = fgetc(f)) != EOF) {
if (c == '\n')
break;
if (len + 1 >= cap) {
size_t newcap = cap * 2;
char *tmp = realloc(buf, newcap);
if (!tmp) {
free(buf);
return NULL;
}
buf = tmp;
cap = newcap;
}
buf[len++] = (char)c;
}
if (len == 0 && c == EOF) {
free(buf);
return NULL;
}
buf[len] = '\0';
if (len > 0 && buf[len - 1] == '\r')
buf[len - 1] = '\0';
return buf;
}
int serial_parse_file(const char *path, serial_summary_t *summary)
{
if (!path || !summary)
return -1;
FILE *f = fopen(path, "r");
if (!f)
return -1;
memset(summary, 0, sizeof(*summary));
summary->final_stage = STAGE_UNKNOWN;
serial_line_t *tail = NULL;
double first_ts = -1.0;
double last_ts = -1.0;
for (;;) {
char *text = read_line(f);
if (!text)
break;
if (text[0] == '\0') {
free(text);
continue;
}
serial_line_t *sl = calloc(1, sizeof(serial_line_t));
if (!sl) {
free(text);
break;
}
sl->text = text;
sl->stage = classify_line(text);
if (!summary->lines)
summary->lines = sl;
else
tail->next = sl;
tail = sl;
summary->total_lines++;
if (sl->stage >= 0 && sl->stage < STAGE_COUNT)
summary->lines_per_stage[sl->stage]++;
if (sl->stage != STAGE_UNKNOWN) {
if (sl->stage == STAGE_ERROR) {
summary->final_stage = STAGE_ERROR;
} else if (sl->stage > summary->final_stage) {
summary->final_stage = sl->stage;
}
}
if (sl->stage == STAGE_ERROR && summary->error_count < 8) {
strncpy(summary->error_messages[summary->error_count],
text, 255);
summary->error_messages[summary->error_count][255] = '\0';
summary->error_count++;
}
double ts;
if (extract_timestamp(text, &ts) == 0) {
if (first_ts < 0)
first_ts = ts;
last_ts = ts;
}
}
fclose(f);
if (first_ts >= 0 && last_ts >= first_ts)
summary->boot_duration_ms = (uint64_t)((last_ts - first_ts) * 1000.0);
return 0;
}
void serial_free_summary(serial_summary_t *summary)
{
if (!summary)
return;
serial_line_t *sl = summary->lines;
while (sl) {
serial_line_t *next = sl->next;
free(sl->text);
free(sl);
sl = next;
}
summary->lines = NULL;
summary->total_lines = 0;
}
void serial_print_stages(const serial_summary_t *summary, FILE *out)
{
const char *final = (summary->final_stage >= 0 &&
summary->final_stage < STAGE_COUNT)
? boot_stage_names[summary->final_stage]
: "unknown";
fprintf(out, "=== Boot Stages ===\n\n");
fprintf(out, "Final stage: %s\n\n", final);
fprintf(out, "Lines per stage:\n");
for (int i = 0; i < STAGE_COUNT; i++) {
fprintf(out, " %-20s %5zu%s\n",
boot_stage_names[i],
summary->lines_per_stage[i],
summary->lines_per_stage[i] > 0 ? " <--" : "");
}
if (summary->boot_duration_ms > 0) {
fprintf(out, "\nBoot duration: %" PRIu64 " ms (%.2f s)\n",
summary->boot_duration_ms,
summary->boot_duration_ms / 1000.0);
}
if (summary->error_count > 0) {
fprintf(out, "\nErrors (%d):\n", summary->error_count);
for (int i = 0; i < summary->error_count; i++)
fprintf(out, " %s\n", summary->error_messages[i]);
}
}
void serial_print_text(const serial_summary_t *summary, FILE *out)
{
fprintf(out, "=== Serial Console Summary ===\n");
fprintf(out, "Total lines: %zu\n", summary->total_lines);
const char *final = (summary->final_stage >= 0 &&
summary->final_stage < STAGE_COUNT)
? boot_stage_names[summary->final_stage]
: "unknown";
fprintf(out, "Final boot stage: %s\n", final);
if (summary->boot_duration_ms > 0) {
fprintf(out, "Boot duration: %" PRIu64 " ms\n",
summary->boot_duration_ms);
}
fprintf(out, "\nLines per stage:\n");
for (int i = 0; i < STAGE_COUNT; i++) {
if (summary->lines_per_stage[i] > 0) {
fprintf(out, " %-20s %zu\n",
boot_stage_names[i],
summary->lines_per_stage[i]);
}
}
if (summary->error_count > 0) {
fprintf(out, "\nErrors (%d):\n", summary->error_count);
for (int i = 0; i < summary->error_count; i++)
fprintf(out, " %s\n", summary->error_messages[i]);
}
}