f0bdf66be8
Three trace tiers: standard, detailed, full.
305 lines
8.3 KiB
C
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]);
|
|
}
|
|
}
|