Add logging tools: hiperiso-log analyzer and trace event definitions
Three trace tiers: standard, detailed, full.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -std=c11
|
||||
LDFLAGS = -static
|
||||
|
||||
OBJS = main.o trace_parser.o serial_parser.o report.o
|
||||
|
||||
hiperiso-log: $(OBJS)
|
||||
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) hiperiso-log
|
||||
|
||||
.PHONY: clean
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* main.c - CLI entry point for hiperiso-log.
|
||||
*
|
||||
* Usage:
|
||||
* hiperiso-log analyze <log_dir>
|
||||
* hiperiso-log trace <trace.bin> [--format json|csv|text]
|
||||
* hiperiso-log serial <serial.log> [--stages]
|
||||
*/
|
||||
|
||||
#include "trace_parser.h"
|
||||
#include "serial_parser.h"
|
||||
#include "report.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s analyze <log_dir>\n", prog);
|
||||
fprintf(stderr, " %s trace <trace.bin> [--format json|csv|text]\n", prog);
|
||||
fprintf(stderr, " %s serial <serial.log> [--stages]\n", prog);
|
||||
}
|
||||
|
||||
static int cmd_analyze(const char *log_dir)
|
||||
{
|
||||
if (report_generate(log_dir) != 0) {
|
||||
fprintf(stderr, "Error: failed to generate report for '%s'\n", log_dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Report generated successfully in %s/\n", log_dir);
|
||||
printf(" report.json - machine-readable analysis\n");
|
||||
printf(" report.txt - human-readable summary\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_trace(const char *trace_file, const char *format)
|
||||
{
|
||||
trace_summary_t summary;
|
||||
if (trace_parse_file(trace_file, &summary) != 0) {
|
||||
fprintf(stderr, "Error: failed to parse trace file '%s'\n", trace_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(format, "json") == 0)
|
||||
trace_print_json(&summary, stdout);
|
||||
else if (strcmp(format, "csv") == 0)
|
||||
trace_print_csv(&summary, stdout);
|
||||
else
|
||||
trace_print_text(&summary, stdout);
|
||||
|
||||
trace_free_summary(&summary);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_serial(const char *serial_file, int show_stages)
|
||||
{
|
||||
serial_summary_t summary;
|
||||
if (serial_parse_file(serial_file, &summary) != 0) {
|
||||
fprintf(stderr, "Error: failed to parse serial log '%s'\n", serial_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (show_stages)
|
||||
serial_print_stages(&summary, stdout);
|
||||
else
|
||||
serial_print_text(&summary, stdout);
|
||||
|
||||
serial_free_summary(&summary);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "analyze") == 0) {
|
||||
if (argc < 3) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
return cmd_analyze(argv[2]);
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "trace") == 0) {
|
||||
if (argc < 3) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
const char *format = "text";
|
||||
for (int i = 3; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
|
||||
format = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return cmd_trace(argv[2], format);
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "serial") == 0) {
|
||||
if (argc < 3) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
int show_stages = 0;
|
||||
for (int i = 3; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--stages") == 0)
|
||||
show_stages = 1;
|
||||
}
|
||||
return cmd_serial(argv[2], show_stages);
|
||||
}
|
||||
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
* report.c - Generate analysis reports from boot logs.
|
||||
*
|
||||
* Reads serial.log, trace.bin, qemu.cmdline, and env.txt from a log
|
||||
* directory, then writes report.json and report.txt into the same directory.
|
||||
*
|
||||
* Also derives a structured "analysis" (boot result, failure domain, graphics
|
||||
* hints) from the serial console and writes a flat analysis.meta sidecar
|
||||
* (KEY=VALUE) so the initramfs can fold derived fields into the canonical
|
||||
* session manifest without a JSON parser.
|
||||
*/
|
||||
|
||||
#include "report.h"
|
||||
#include "trace_parser.h"
|
||||
#include "serial_parser.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define MAX_PATH 4096
|
||||
#define MAX_CMDLINE 4096
|
||||
#define MAX_ENV_ENTRIES 64
|
||||
#define MAX_ENV_KEY 128
|
||||
#define MAX_ENV_VALUE 512
|
||||
#define MAX_LINEBUF (MAX_ENV_KEY + MAX_ENV_VALUE + 64)
|
||||
|
||||
typedef struct {
|
||||
char key[MAX_ENV_KEY];
|
||||
char value[MAX_ENV_VALUE];
|
||||
} env_entry_t;
|
||||
|
||||
/* ── Derived analysis (Redbear telemetry) ───────────────────── */
|
||||
typedef struct {
|
||||
const char *boot_result; /* "success" | "failure" | "incomplete" | "no_serial" */
|
||||
const char *failure_domain; /* "none" | "firmware" | "bootloader" | "kernel" |
|
||||
* "hardware" | "userspace" | "unknown" */
|
||||
int reached_login; /* saw login/complete stage */
|
||||
int kernel_panic_count; /* error lines containing "panic" */
|
||||
int graphics_drm_seen; /* any DRM/KMS line observed */
|
||||
char graphics_drivers[256]; /* comma-separated unique driver names found */
|
||||
char graphics_resolution[64]; /* first WxH token found, or "" */
|
||||
} analysis_t;
|
||||
|
||||
static void trim_trailing_nl(char *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r'))
|
||||
s[--len] = '\0';
|
||||
}
|
||||
|
||||
static int read_env_file(const char *path, env_entry_t *entries,
|
||||
int max_entries, int *count)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
*count = 0;
|
||||
char buf[MAX_LINEBUF];
|
||||
|
||||
while (*count < max_entries && fgets(buf, sizeof(buf), f)) {
|
||||
char *colon = strchr(buf, ':');
|
||||
if (!colon)
|
||||
continue;
|
||||
*colon = '\0';
|
||||
|
||||
char *value = colon + 1;
|
||||
while (*value == ' ' || *value == '\t')
|
||||
value++;
|
||||
trim_trailing_nl(value);
|
||||
|
||||
if (strlen(buf) == 0)
|
||||
continue;
|
||||
|
||||
strncpy(entries[*count].key, buf, MAX_ENV_KEY - 1);
|
||||
entries[*count].key[MAX_ENV_KEY - 1] = '\0';
|
||||
strncpy(entries[*count].value, value, MAX_ENV_VALUE - 1);
|
||||
entries[*count].value[MAX_ENV_VALUE - 1] = '\0';
|
||||
(*count)++;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_cmdline(const char *path, char *cmdline, size_t maxlen)
|
||||
{
|
||||
cmdline[0] = '\0';
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
if (!fgets(cmdline, (int)maxlen, f)) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
trim_trailing_nl(cmdline);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void json_escape(FILE *out, const char *str)
|
||||
{
|
||||
fputc('"', out);
|
||||
while (*str) {
|
||||
unsigned char ch = (unsigned char)*str;
|
||||
switch (ch) {
|
||||
case '"': fputs("\\\"", out); break;
|
||||
case '\\': fputs("\\\\", out); break;
|
||||
case '\n': fputs("\\n", out); break;
|
||||
case '\r': fputs("\\r", out); break;
|
||||
case '\t': fputs("\\t", out); break;
|
||||
default:
|
||||
if (ch < 0x20)
|
||||
fprintf(out, "\\u%04x", ch);
|
||||
else
|
||||
fputc((int)ch, out);
|
||||
}
|
||||
str++;
|
||||
}
|
||||
fputc('"', out);
|
||||
}
|
||||
|
||||
static void build_path(char *dst, size_t dstsz,
|
||||
const char *dir, const char *file)
|
||||
{
|
||||
snprintf(dst, dstsz, "%s/%s", dir, file);
|
||||
}
|
||||
|
||||
/* ── Analysis derivation ────────────────────────────────────── */
|
||||
|
||||
static const char *stage_to_domain(boot_stage_t s)
|
||||
{
|
||||
switch (s) {
|
||||
case STAGE_FIRMWARE: return "firmware";
|
||||
case STAGE_BOOTLOADER: return "bootloader";
|
||||
case STAGE_KERNEL_INIT: return "kernel";
|
||||
case STAGE_HARDWARE_INIT: return "hardware";
|
||||
case STAGE_USERSPACE: return "userspace";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan a single text line for a WxH resolution token (e.g. "1024x768").
|
||||
* Returns 1 and fills out[] on success, 0 if none found. */
|
||||
static int extract_resolution(const char *s, char *out, size_t outsz)
|
||||
{
|
||||
for (const char *p = s; *p; p++) {
|
||||
if (*p < '0' || *p > '9')
|
||||
continue;
|
||||
const char *wstart = p;
|
||||
while (*p >= '0' && *p <= '9')
|
||||
p++;
|
||||
int wlen = (int)(p - wstart);
|
||||
if (wlen < 3 || wlen > 4)
|
||||
continue;
|
||||
if (*p != 'x' && *p != 'X')
|
||||
continue;
|
||||
p++;
|
||||
const char *hstart = p;
|
||||
while (*p >= '0' && *p <= '9')
|
||||
p++;
|
||||
int hlen = (int)(p - hstart);
|
||||
if (hlen < 3 || hlen > 4)
|
||||
continue;
|
||||
snprintf(out, outsz, "%.*sx%.*s", wlen, wstart, hlen, hstart);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* GPU driver names we can detect from serial console text. These are
|
||||
* matched as substrings; only names literally present are reported. */
|
||||
static const char *known_gpu_drivers[] = {
|
||||
"i915", "amdgpu", "radeon", "nouveau", "nvidia",
|
||||
"virtio_gpu", "virtio-gpu", "cirrus", "vmwgfx",
|
||||
"mgag200", "simpledrm", "ast", "panfrost", NULL
|
||||
};
|
||||
|
||||
static void scan_graphics(const serial_summary_t *s, analysis_t *a)
|
||||
{
|
||||
a->graphics_drivers[0] = '\0';
|
||||
a->graphics_resolution[0] = '\0';
|
||||
a->graphics_drm_seen = 0;
|
||||
|
||||
int first = 1;
|
||||
for (serial_line_t *sl = s->lines; sl; sl = sl->next) {
|
||||
const char *t = sl->text;
|
||||
if (!t || !*t)
|
||||
continue;
|
||||
|
||||
if (strstr(t, "[drm]") || strstr(t, "[DRM]") ||
|
||||
strstr(t, "drm_kms_helper") || strstr(t, "DRM:") ||
|
||||
strstr(t, "fb:"))
|
||||
a->graphics_drm_seen = 1;
|
||||
|
||||
for (int i = 0; known_gpu_drivers[i]; i++) {
|
||||
if (strstr(t, known_gpu_drivers[i]) &&
|
||||
!strstr(a->graphics_drivers, known_gpu_drivers[i])) {
|
||||
if (!first)
|
||||
strncat(a->graphics_drivers, ",",
|
||||
sizeof(a->graphics_drivers) -
|
||||
strlen(a->graphics_drivers) - 1);
|
||||
strncat(a->graphics_drivers, known_gpu_drivers[i],
|
||||
sizeof(a->graphics_drivers) -
|
||||
strlen(a->graphics_drivers) - 1);
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (a->graphics_resolution[0] == '\0')
|
||||
extract_resolution(t, a->graphics_resolution,
|
||||
sizeof(a->graphics_resolution));
|
||||
}
|
||||
}
|
||||
|
||||
static void derive_analysis(const serial_summary_t *s, int has_serial,
|
||||
analysis_t *a)
|
||||
{
|
||||
memset(a, 0, sizeof(*a));
|
||||
a->boot_result = "no_serial";
|
||||
a->failure_domain = "unknown";
|
||||
a->kernel_panic_count = 0;
|
||||
|
||||
if (!has_serial || !s)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < s->error_count; i++) {
|
||||
if (strstr(s->error_messages[i], "panic") ||
|
||||
strstr(s->error_messages[i], "Panic"))
|
||||
a->kernel_panic_count++;
|
||||
}
|
||||
|
||||
a->reached_login = (s->lines_per_stage[STAGE_LOGIN] > 0 ||
|
||||
s->lines_per_stage[STAGE_COMPLETE] > 0);
|
||||
|
||||
/* Highest stage with observed lines (excluding error/unknown). */
|
||||
boot_stage_t progressed = STAGE_UNKNOWN;
|
||||
for (int i = STAGE_FIRMWARE; i < STAGE_ERROR; i++) {
|
||||
if (s->lines_per_stage[i] > 0)
|
||||
progressed = (boot_stage_t)i;
|
||||
}
|
||||
|
||||
if (a->kernel_panic_count > 0) {
|
||||
a->boot_result = "failure";
|
||||
a->failure_domain = (progressed != STAGE_UNKNOWN)
|
||||
? stage_to_domain(progressed) : "unknown";
|
||||
} else if (a->reached_login) {
|
||||
a->boot_result = (s->error_count > 0) ? "success_with_warnings" : "success";
|
||||
a->failure_domain = "none";
|
||||
} else if (s->error_count > 0) {
|
||||
a->boot_result = "failure";
|
||||
a->failure_domain = (progressed != STAGE_UNKNOWN)
|
||||
? stage_to_domain(progressed) : "unknown";
|
||||
} else {
|
||||
a->boot_result = "incomplete";
|
||||
a->failure_domain = (progressed != STAGE_UNKNOWN)
|
||||
? stage_to_domain(progressed) : "unknown";
|
||||
}
|
||||
|
||||
scan_graphics(s, a);
|
||||
}
|
||||
|
||||
/* ── analysis.meta flat sidecar (consumed by initramfs init) ── */
|
||||
|
||||
static int write_analysis_meta(const char *path, const analysis_t *a,
|
||||
const serial_summary_t *serial, int has_serial,
|
||||
const trace_summary_t *trace, int has_trace)
|
||||
{
|
||||
FILE *out = fopen(path, "w");
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
const char *stage = "unknown";
|
||||
if (has_serial && serial->final_stage >= 0 &&
|
||||
serial->final_stage < STAGE_COUNT)
|
||||
stage = boot_stage_names[serial->final_stage];
|
||||
|
||||
/* stages_reached: comma-separated list of stages that had activity */
|
||||
char stages_reached[384] = "";
|
||||
if (has_serial) {
|
||||
int first = 1;
|
||||
for (int i = STAGE_FIRMWARE; i <= STAGE_COMPLETE; i++) {
|
||||
if (serial->lines_per_stage[i] > 0) {
|
||||
if (!first) {
|
||||
strncat(stages_reached, ",",
|
||||
sizeof(stages_reached) - strlen(stages_reached) - 1);
|
||||
}
|
||||
strncat(stages_reached, boot_stage_names[i],
|
||||
sizeof(stages_reached) - strlen(stages_reached) - 1);
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* trace_duration_ms: from trace timestamps, already parsed */
|
||||
uint64_t trace_duration_ms = 0;
|
||||
if (has_trace && trace->last_timestamp > trace->first_timestamp)
|
||||
trace_duration_ms = (trace->last_timestamp - trace->first_timestamp) / 1000000ULL;
|
||||
|
||||
fprintf(out, "BOOT_RESULT=%s\n", a->boot_result);
|
||||
fprintf(out, "FAILURE_DOMAIN=%s\n", a->failure_domain);
|
||||
fprintf(out, "BOOT_STAGE_FINAL=%s\n", stage);
|
||||
fprintf(out, "STAGES_REACHED=%s\n", stages_reached);
|
||||
fprintf(out, "REACHED_LOGIN=%d\n", a->reached_login ? 1 : 0);
|
||||
fprintf(out, "KERNEL_PANIC_COUNT=%d\n", a->kernel_panic_count);
|
||||
fprintf(out, "ERROR_COUNT=%d\n", has_serial ? serial->error_count : 0);
|
||||
fprintf(out, "HAS_SERIAL=%d\n", has_serial ? 1 : 0);
|
||||
fprintf(out, "HAS_TRACE=%d\n", has_trace ? 1 : 0);
|
||||
fprintf(out, "BOOT_DURATION_MS=%" PRIu64 "\n",
|
||||
has_serial ? serial->boot_duration_ms : (uint64_t)0);
|
||||
fprintf(out, "TRACE_DURATION_MS=%" PRIu64 "\n", trace_duration_ms);
|
||||
fprintf(out, "GRAPHICS_DRIVERS=%s\n", a->graphics_drivers);
|
||||
fprintf(out, "GRAPHICS_RESOLUTION=%s\n", a->graphics_resolution);
|
||||
fprintf(out, "GRAPHICS_DRM_SEEN=%d\n", a->graphics_drm_seen ? 1 : 0);
|
||||
|
||||
fclose(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── report.json ────────────────────────────────────────────── */
|
||||
|
||||
static int write_report_json(const char *path,
|
||||
const env_entry_t *env, int env_count,
|
||||
const char *cmdline,
|
||||
const serial_summary_t *serial, int has_serial,
|
||||
const trace_summary_t *trace, int has_trace,
|
||||
const analysis_t *analysis)
|
||||
{
|
||||
FILE *out = fopen(path, "w");
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
fprintf(out, "{\n");
|
||||
|
||||
fprintf(out, " \"environment\": {");
|
||||
for (int i = 0; i < env_count; i++) {
|
||||
if (i > 0)
|
||||
fprintf(out, ",");
|
||||
fprintf(out, "\n ");
|
||||
json_escape(out, env[i].key);
|
||||
fprintf(out, ": ");
|
||||
json_escape(out, env[i].value);
|
||||
}
|
||||
fprintf(out, env_count > 0 ? "\n },\n" : "},\n");
|
||||
|
||||
fprintf(out, " \"qemu_cmdline\": ");
|
||||
json_escape(out, cmdline);
|
||||
fprintf(out, ",\n");
|
||||
|
||||
const char *stage = "unknown";
|
||||
if (has_serial) {
|
||||
if (serial->final_stage >= 0 && serial->final_stage < STAGE_COUNT)
|
||||
stage = boot_stage_names[serial->final_stage];
|
||||
}
|
||||
fprintf(out, " \"boot_stage\": ");
|
||||
json_escape(out, stage);
|
||||
fprintf(out, ",\n");
|
||||
|
||||
fprintf(out, " \"boot_duration_ms\": %" PRIu64 ",\n",
|
||||
has_serial ? serial->boot_duration_ms : (uint64_t)0);
|
||||
|
||||
fprintf(out, " \"errors\": [");
|
||||
for (int i = 0; has_serial && i < serial->error_count; i++) {
|
||||
if (i > 0)
|
||||
fprintf(out, ", ");
|
||||
json_escape(out, serial->error_messages[i]);
|
||||
}
|
||||
fprintf(out, "],\n");
|
||||
|
||||
if (has_serial) {
|
||||
fprintf(out, " \"serial_summary\": {\n");
|
||||
fprintf(out, " \"total_lines\": %zu,\n", serial->total_lines);
|
||||
for (int i = 0; i < STAGE_COUNT; i++) {
|
||||
fprintf(out, " \"lines_%s\": %zu%s\n",
|
||||
boot_stage_names[i],
|
||||
serial->lines_per_stage[i],
|
||||
i < STAGE_COUNT - 1 ? "," : "");
|
||||
}
|
||||
fprintf(out, " },\n");
|
||||
} else {
|
||||
fprintf(out, " \"serial_summary\": null,\n");
|
||||
}
|
||||
|
||||
if (has_trace) {
|
||||
uint64_t trace_dur_ms = 0;
|
||||
if (trace->last_timestamp > trace->first_timestamp)
|
||||
trace_dur_ms = (trace->last_timestamp - trace->first_timestamp) / 1000000ULL;
|
||||
|
||||
fprintf(out, " \"trace_summary\": {\n");
|
||||
fprintf(out, " \"total_events\": %" PRIu64 ",\n", trace->total_events);
|
||||
fprintf(out, " \"disk_reads\": %" PRIu64 ",\n", trace->total_disk_reads);
|
||||
fprintf(out, " \"disk_writes\": %" PRIu64 ",\n", trace->total_disk_writes);
|
||||
fprintf(out, " \"serial_writes\": %" PRIu64 ",\n", trace->total_serial_writes);
|
||||
fprintf(out, " \"first_timestamp_ns\": %" PRIu64 ",\n", trace->first_timestamp);
|
||||
fprintf(out, " \"last_timestamp_ns\": %" PRIu64 ",\n", trace->last_timestamp);
|
||||
fprintf(out, " \"duration_ms\": %" PRIu64 "\n", trace_dur_ms);
|
||||
fprintf(out, " },\n");
|
||||
} else {
|
||||
fprintf(out, " \"trace_summary\": null,\n");
|
||||
}
|
||||
|
||||
/* Derived analysis block (additive, always present). */
|
||||
fprintf(out, " \"analysis\": {\n");
|
||||
fprintf(out, " \"boot_result\": ");
|
||||
json_escape(out, analysis->boot_result);
|
||||
fprintf(out, ",\n");
|
||||
fprintf(out, " \"failure_domain\": ");
|
||||
json_escape(out, analysis->failure_domain);
|
||||
fprintf(out, ",\n");
|
||||
fprintf(out, " \"boot_stage_final\": ");
|
||||
json_escape(out, stage);
|
||||
fprintf(out, ",\n");
|
||||
fprintf(out, " \"stages_reached\": [");
|
||||
if (has_serial) {
|
||||
int first_stg = 1;
|
||||
for (int i = STAGE_FIRMWARE; i <= STAGE_COMPLETE; i++) {
|
||||
if (serial->lines_per_stage[i] > 0) {
|
||||
if (!first_stg)
|
||||
fprintf(out, ", ");
|
||||
json_escape(out, boot_stage_names[i]);
|
||||
first_stg = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(out, "],\n");
|
||||
fprintf(out, " \"reached_login\": %s,\n",
|
||||
analysis->reached_login ? "true" : "false");
|
||||
fprintf(out, " \"kernel_panic_count\": %d,\n",
|
||||
analysis->kernel_panic_count);
|
||||
fprintf(out, " \"error_count\": %d,\n",
|
||||
has_serial ? serial->error_count : 0);
|
||||
fprintf(out, " \"has_serial\": %s,\n", has_serial ? "true" : "false");
|
||||
fprintf(out, " \"has_trace\": %s,\n", has_trace ? "true" : "false");
|
||||
fprintf(out, " \"boot_duration_ms\": %" PRIu64 ",\n",
|
||||
has_serial ? serial->boot_duration_ms : (uint64_t)0);
|
||||
{
|
||||
uint64_t tdm = 0;
|
||||
if (has_trace && trace->last_timestamp > trace->first_timestamp)
|
||||
tdm = (trace->last_timestamp - trace->first_timestamp) / 1000000ULL;
|
||||
fprintf(out, " \"trace_duration_ms\": %" PRIu64 ",\n", tdm);
|
||||
}
|
||||
fprintf(out, " \"graphics\": {\n");
|
||||
fprintf(out, " \"drm_seen\": %s,\n",
|
||||
analysis->graphics_drm_seen ? "true" : "false");
|
||||
fprintf(out, " \"drivers\": [");
|
||||
{
|
||||
const char *p = analysis->graphics_drivers;
|
||||
int first_drv = 1;
|
||||
while (*p) {
|
||||
const char *comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if (len > 0) {
|
||||
if (!first_drv)
|
||||
fprintf(out, ", ");
|
||||
fprintf(out, "\"%.*s\"", (int)len, p);
|
||||
first_drv = 0;
|
||||
}
|
||||
if (!comma)
|
||||
break;
|
||||
p = comma + 1;
|
||||
}
|
||||
}
|
||||
fprintf(out, "],\n");
|
||||
fprintf(out, " \"resolution\": ");
|
||||
json_escape(out, analysis->graphics_resolution);
|
||||
fprintf(out, "\n");
|
||||
fprintf(out, " }\n");
|
||||
fprintf(out, " }\n");
|
||||
|
||||
fprintf(out, "}\n");
|
||||
fclose(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── report.txt ─────────────────────────────────────────────── */
|
||||
|
||||
static int write_report_txt(const char *path,
|
||||
const env_entry_t *env, int env_count,
|
||||
const char *cmdline,
|
||||
const serial_summary_t *serial, int has_serial,
|
||||
const trace_summary_t *trace, int has_trace,
|
||||
const analysis_t *analysis)
|
||||
{
|
||||
FILE *out = fopen(path, "w");
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
fprintf(out, "========================================\n");
|
||||
fprintf(out, " hiperiso Boot Analysis Report\n");
|
||||
fprintf(out, "========================================\n\n");
|
||||
|
||||
fprintf(out, "--- Environment ---\n");
|
||||
for (int i = 0; i < env_count; i++)
|
||||
fprintf(out, " %-20s %s\n", env[i].key, env[i].value);
|
||||
if (env_count == 0)
|
||||
fprintf(out, " (not available)\n");
|
||||
fprintf(out, "\n");
|
||||
|
||||
fprintf(out, "--- QEMU Command Line ---\n");
|
||||
if (cmdline[0])
|
||||
fprintf(out, " %s\n", cmdline);
|
||||
else
|
||||
fprintf(out, " (not available)\n");
|
||||
fprintf(out, "\n");
|
||||
|
||||
if (has_serial) {
|
||||
const char *stage = (serial->final_stage >= 0 &&
|
||||
serial->final_stage < STAGE_COUNT)
|
||||
? boot_stage_names[serial->final_stage]
|
||||
: "unknown";
|
||||
|
||||
fprintf(out, "--- Serial Console Summary ---\n");
|
||||
fprintf(out, " Total lines: %zu\n", serial->total_lines);
|
||||
fprintf(out, " Final boot stage: %s\n", stage);
|
||||
fprintf(out, " Boot duration: %" PRIu64 " ms\n",
|
||||
serial->boot_duration_ms);
|
||||
fprintf(out, "\n Lines per stage:\n");
|
||||
for (int i = 0; i < STAGE_COUNT; i++)
|
||||
fprintf(out, " %-20s %zu\n",
|
||||
boot_stage_names[i], serial->lines_per_stage[i]);
|
||||
|
||||
if (serial->error_count > 0) {
|
||||
fprintf(out, "\n Errors (%d):\n", serial->error_count);
|
||||
for (int i = 0; i < serial->error_count; i++)
|
||||
fprintf(out, " %s\n", serial->error_messages[i]);
|
||||
}
|
||||
fprintf(out, "\n");
|
||||
} else {
|
||||
fprintf(out, "--- Serial Console: NOT AVAILABLE ---\n\n");
|
||||
}
|
||||
|
||||
if (has_trace) {
|
||||
fprintf(out, "--- Trace Summary ---\n");
|
||||
trace_print_text(trace, out);
|
||||
} else {
|
||||
fprintf(out, "--- Trace: NOT AVAILABLE ---\n");
|
||||
}
|
||||
|
||||
fprintf(out, "\n--- Derived Analysis ---\n");
|
||||
fprintf(out, " Boot result: %s\n", analysis->boot_result);
|
||||
fprintf(out, " Failure domain: %s\n", analysis->failure_domain);
|
||||
if (has_serial) {
|
||||
fprintf(out, " Stage progression: ");
|
||||
int first_stg = 1;
|
||||
for (int i = STAGE_FIRMWARE; i <= STAGE_COMPLETE; i++) {
|
||||
if (serial->lines_per_stage[i] > 0) {
|
||||
fprintf(out, "%s%s",
|
||||
first_stg ? "" : " \xe2\x86\x92 ",
|
||||
boot_stage_names[i]);
|
||||
first_stg = 0;
|
||||
}
|
||||
}
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, " Reached login: %s\n",
|
||||
analysis->reached_login ? "yes" : "no");
|
||||
fprintf(out, " Kernel panics: %d\n", analysis->kernel_panic_count);
|
||||
fprintf(out, " Error lines: %d\n",
|
||||
has_serial ? serial->error_count : 0);
|
||||
fprintf(out, " Graphics DRM seen: %s\n",
|
||||
analysis->graphics_drm_seen ? "yes" : "no");
|
||||
if (analysis->graphics_drivers[0])
|
||||
fprintf(out, " Graphics drivers: %s\n", analysis->graphics_drivers);
|
||||
if (analysis->graphics_resolution[0])
|
||||
fprintf(out, " Graphics res: %s\n", analysis->graphics_resolution);
|
||||
|
||||
fprintf(out, "\n========================================\n");
|
||||
fprintf(out, " End of Report\n");
|
||||
fprintf(out, "========================================\n");
|
||||
|
||||
fclose(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int report_generate(const char *log_dir)
|
||||
{
|
||||
if (!log_dir)
|
||||
return -1;
|
||||
|
||||
char path[MAX_PATH];
|
||||
env_entry_t env[MAX_ENV_ENTRIES];
|
||||
int env_count = 0;
|
||||
char cmdline[MAX_CMDLINE];
|
||||
cmdline[0] = '\0';
|
||||
|
||||
build_path(path, sizeof(path), log_dir, "env.txt");
|
||||
read_env_file(path, env, MAX_ENV_ENTRIES, &env_count);
|
||||
|
||||
build_path(path, sizeof(path), log_dir, "qemu.cmdline");
|
||||
read_cmdline(path, cmdline, sizeof(cmdline));
|
||||
|
||||
serial_summary_t serial;
|
||||
memset(&serial, 0, sizeof(serial));
|
||||
build_path(path, sizeof(path), log_dir, "serial.log");
|
||||
int has_serial = (serial_parse_file(path, &serial) == 0);
|
||||
|
||||
trace_summary_t trace;
|
||||
memset(&trace, 0, sizeof(trace));
|
||||
build_path(path, sizeof(path), log_dir, "trace.bin");
|
||||
int has_trace = (trace_parse_file(path, &trace) == 0);
|
||||
|
||||
analysis_t analysis;
|
||||
derive_analysis(has_serial ? &serial : NULL, has_serial, &analysis);
|
||||
|
||||
int rc = 0;
|
||||
|
||||
build_path(path, sizeof(path), log_dir, "report.json");
|
||||
if (write_report_json(path, env, env_count, cmdline,
|
||||
&serial, has_serial, &trace, has_trace,
|
||||
&analysis) != 0)
|
||||
rc = -1;
|
||||
|
||||
build_path(path, sizeof(path), log_dir, "report.txt");
|
||||
if (write_report_txt(path, env, env_count, cmdline,
|
||||
&serial, has_serial, &trace, has_trace,
|
||||
&analysis) != 0)
|
||||
rc = -1;
|
||||
|
||||
/* Flat sidecar for the initramfs to fold into the session manifest. */
|
||||
build_path(path, sizeof(path), log_dir, "analysis.meta");
|
||||
if (write_analysis_meta(path, &analysis, &serial, has_serial,
|
||||
&trace, has_trace) != 0)
|
||||
rc = -1;
|
||||
|
||||
if (has_serial)
|
||||
serial_free_summary(&serial);
|
||||
if (has_trace)
|
||||
trace_free_summary(&trace);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* report.h - Generate analysis reports from boot logs.
|
||||
*
|
||||
* Reads serial.log, trace.bin, qemu.cmdline, and env.txt from a log
|
||||
* directory, then writes report.json and report.txt into the same directory.
|
||||
*/
|
||||
|
||||
#ifndef REPORT_H
|
||||
#define REPORT_H
|
||||
|
||||
/*
|
||||
* Generate report.json and report.txt inside <log_dir>.
|
||||
*
|
||||
* Input files (all optional — missing files are handled gracefully):
|
||||
* <log_dir>/env.txt Key: Value lines
|
||||
* <log_dir>/qemu.cmdline Single-line QEMU command
|
||||
* <log_dir>/serial.log Serial console text
|
||||
* <log_dir>/trace.bin QEMU simpletrace binary
|
||||
*
|
||||
* Returns 0 on success, -1 if the report files could not be written.
|
||||
*/
|
||||
int report_generate(const char *log_dir);
|
||||
|
||||
#endif /* REPORT_H */
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* serial_parser.h - Parse serial.log text files and identify boot stages.
|
||||
*
|
||||
* Reads the QEMU serial console capture and classifies each line into a
|
||||
* boot stage (firmware → bootloader → kernel → hardware → userspace → login).
|
||||
*/
|
||||
|
||||
#ifndef SERIAL_PARSER_H
|
||||
#define SERIAL_PARSER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Boot progression stages (ordered by typical sequence). */
|
||||
typedef enum {
|
||||
STAGE_UNKNOWN = 0,
|
||||
STAGE_FIRMWARE, /* UEFI / OVMF initialization */
|
||||
STAGE_BOOTLOADER, /* GRUB / systemd-boot */
|
||||
STAGE_KERNEL_INIT, /* "Linux version", "Booting Linux" */
|
||||
STAGE_HARDWARE_INIT, /* hardware detection messages */
|
||||
STAGE_USERSPACE, /* systemd / init starting */
|
||||
STAGE_LOGIN, /* login prompt */
|
||||
STAGE_COMPLETE, /* boot finished */
|
||||
STAGE_ERROR, /* panic, oops, BUG */
|
||||
STAGE_COUNT /* sentinel — number of stages */
|
||||
} boot_stage_t;
|
||||
|
||||
/* Human-readable names for each stage (indexed by boot_stage_t). */
|
||||
extern const char *boot_stage_names[STAGE_COUNT];
|
||||
|
||||
/* A single serial-log line (node in a singly-linked list). */
|
||||
typedef struct serial_line {
|
||||
char *text; /* malloc'd line content (no trailing \n) */
|
||||
boot_stage_t stage;
|
||||
struct serial_line *next;
|
||||
} serial_line_t;
|
||||
|
||||
/* Aggregate statistics over all parsed lines. */
|
||||
typedef struct serial_summary {
|
||||
serial_line_t *lines; /* linked-list head */
|
||||
size_t total_lines;
|
||||
size_t lines_per_stage[STAGE_COUNT];
|
||||
boot_stage_t final_stage; /* highest stage reached (or STAGE_ERROR) */
|
||||
char error_messages[8][256]; /* up to 8 error lines */
|
||||
int error_count;
|
||||
uint64_t boot_duration_ms; /* estimated from [X.XXXXXX] timestamps */
|
||||
} serial_summary_t;
|
||||
|
||||
/*
|
||||
* Parse a serial console log.
|
||||
* Returns 0 on success, -1 on error (file not found, read failure).
|
||||
* Caller must call serial_free_summary() to release memory.
|
||||
*/
|
||||
int serial_parse_file(const char *path, serial_summary_t *summary);
|
||||
|
||||
/* Free all dynamically-allocated memory in *summary. */
|
||||
void serial_free_summary(serial_summary_t *summary);
|
||||
|
||||
/* Print per-stage line counts and boot duration. */
|
||||
void serial_print_stages(const serial_summary_t *summary, FILE *out);
|
||||
|
||||
/* Print a human-readable summary. */
|
||||
void serial_print_text(const serial_summary_t *summary, FILE *out);
|
||||
|
||||
#endif /* SERIAL_PARSER_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* simpletrace.h - QEMU simpletrace binary format constants.
|
||||
*
|
||||
* Based on the actual QEMU source: scripts/simpletrace.py and trace/simple.c.
|
||||
* The format is NOT documented as stable — trace files must be parsed with
|
||||
* the matching simpletrace implementation.
|
||||
*
|
||||
* All fields are stored in native byte-order (little-endian on x86_64).
|
||||
*
|
||||
* File layout:
|
||||
*
|
||||
* File header (24 bytes, Python struct '=QQQ'):
|
||||
* uint64_t header_event_id = TRACE_HEADER_EVENT_ID (sentinel)
|
||||
* uint64_t header_magic = TRACE_HEADER_MAGIC
|
||||
* uint64_t log_version = TRACE_HEADER_VERSION
|
||||
*
|
||||
* Records — each begins with a uint64 discriminator ("rectype"):
|
||||
*
|
||||
* rectype == TRACE_RECORD_TYPE_MAPPING (0):
|
||||
* uint64_t event_id
|
||||
* uint32_t name_length
|
||||
* char name[name_length] (no NUL terminator on disk)
|
||||
*
|
||||
* rectype == TRACE_RECORD_TYPE_EVENT (1):
|
||||
* uint64_t event_id
|
||||
* uint64_t timestamp_ns
|
||||
* uint32_t record_length (total: 24 + args payload length)
|
||||
* uint32_t record_pid
|
||||
* uint8_t args[record_length - 24]
|
||||
*/
|
||||
|
||||
#ifndef SIMPLETRACE_H
|
||||
#define SIMPLETRACE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* --- File header constants --- */
|
||||
|
||||
/* First uint64 of the file header — identifies this as the header record. */
|
||||
#define TRACE_HEADER_EVENT_ID 0xffffffffffffffffULL
|
||||
|
||||
/* Magic value in the second uint64 of the file header. */
|
||||
#define TRACE_HEADER_MAGIC 0xf2b177cb0aa429b4ULL
|
||||
|
||||
/* Supported log format versions (0, 2, 3, 4 have existed; 4 is current). */
|
||||
#define TRACE_HEADER_VERSION 4
|
||||
|
||||
/* Pseudo event-id used when QEMU drops events due to a full ring buffer. */
|
||||
#define TRACE_DROPPED_EVENT_ID 0xfffffffffffffffeULL
|
||||
|
||||
/* --- Record type discriminators (first uint64 of each record) --- */
|
||||
|
||||
#define TRACE_RECORD_TYPE_MAPPING 0
|
||||
#define TRACE_RECORD_TYPE_EVENT 1
|
||||
|
||||
/* --- Field / header sizes --- */
|
||||
|
||||
/* File header: 3 x uint64 = 24 bytes. */
|
||||
#define TRACE_LOG_HEADER_LEN 24
|
||||
|
||||
/* Event-record body after the rectype uint64 (Python '=QQII'): 24 bytes.
|
||||
* uint64 event_id + uint64 timestamp_ns + uint32 record_length + uint32 pid */
|
||||
#define TRACE_REC_HEADER_LEN 24
|
||||
|
||||
/* Record-type discriminator field. */
|
||||
#define TRACE_REC_TYPE_LEN 8
|
||||
|
||||
#endif /* SIMPLETRACE_H */
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* trace_parser.c - Parse QEMU simpletrace binary files.
|
||||
*
|
||||
* Format reference: QEMU scripts/simpletrace.py (log format version 4).
|
||||
* All multi-byte fields are little-endian (native on x86_64).
|
||||
*/
|
||||
|
||||
#include "trace_parser.h"
|
||||
#include "simpletrace.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define MAX_EVENTS_IN_LIST 1000000
|
||||
|
||||
static const char *known_event_names[] = {
|
||||
"serial_read",
|
||||
"serial_write",
|
||||
"ide_sector_read",
|
||||
"cd_read_sector",
|
||||
"cd_read_sector_sync",
|
||||
"blk_co_preadv",
|
||||
"virtio_blk_handle_read",
|
||||
"cpu_in",
|
||||
"cpu_out",
|
||||
"pci_cfg_read",
|
||||
"pci_cfg_write",
|
||||
"ide_ioport_read",
|
||||
"ide_ioport_write",
|
||||
"memory_region_ops_read",
|
||||
"memory_region_ops_write",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int is_disk_read(const char *name)
|
||||
{
|
||||
return strcmp(name, "ide_sector_read") == 0 ||
|
||||
strcmp(name, "cd_read_sector") == 0 ||
|
||||
strcmp(name, "cd_read_sector_sync") == 0 ||
|
||||
strcmp(name, "blk_co_preadv") == 0 ||
|
||||
strcmp(name, "virtio_blk_handle_read") == 0;
|
||||
}
|
||||
|
||||
static int is_disk_write(const char *name)
|
||||
{
|
||||
return strcmp(name, "blk_co_pwritev") == 0 ||
|
||||
strcmp(name, "virtio_blk_handle_write") == 0 ||
|
||||
strcmp(name, "ide_sector_write") == 0;
|
||||
}
|
||||
|
||||
static int read_u64(FILE *f, uint64_t *val)
|
||||
{
|
||||
uint8_t b[8];
|
||||
if (fread(b, 1, 8, f) != 8)
|
||||
return -1;
|
||||
*val = (uint64_t)b[0]
|
||||
| ((uint64_t)b[1] << 8)
|
||||
| ((uint64_t)b[2] << 16)
|
||||
| ((uint64_t)b[3] << 24)
|
||||
| ((uint64_t)b[4] << 32)
|
||||
| ((uint64_t)b[5] << 40)
|
||||
| ((uint64_t)b[6] << 48)
|
||||
| ((uint64_t)b[7] << 56);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_u32(FILE *f, uint32_t *val)
|
||||
{
|
||||
uint8_t b[4];
|
||||
if (fread(b, 1, 4, f) != 4)
|
||||
return -1;
|
||||
*val = (uint32_t)b[0]
|
||||
| ((uint32_t)b[1] << 8)
|
||||
| ((uint32_t)b[2] << 16)
|
||||
| ((uint32_t)b[3] << 24);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void init_known_names(trace_summary_t *summary)
|
||||
{
|
||||
for (int i = 0; known_event_names[i] != NULL && i < TRACE_MAX_EVENT_ID; i++) {
|
||||
size_t len = strlen(known_event_names[i]);
|
||||
if (len >= 64)
|
||||
len = 63;
|
||||
memcpy(summary->event_names[i], known_event_names[i], len);
|
||||
summary->event_names[i][len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void resolve_name(trace_summary_t *summary, trace_event_t *ev)
|
||||
{
|
||||
if (ev->event_id < TRACE_MAX_EVENT_ID && summary->event_names[ev->event_id][0]) {
|
||||
strncpy(ev->name, summary->event_names[ev->event_id], 63);
|
||||
ev->name[63] = '\0';
|
||||
} else {
|
||||
snprintf(ev->name, sizeof(ev->name), "event_%" PRIu64, ev->event_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_stats(trace_summary_t *summary, const trace_event_t *ev)
|
||||
{
|
||||
summary->total_events++;
|
||||
if (ev->event_id < TRACE_MAX_EVENT_ID)
|
||||
summary->events_by_id[ev->event_id]++;
|
||||
|
||||
if (summary->first_timestamp == 0 || ev->timestamp_ns < summary->first_timestamp)
|
||||
summary->first_timestamp = ev->timestamp_ns;
|
||||
if (ev->timestamp_ns > summary->last_timestamp)
|
||||
summary->last_timestamp = ev->timestamp_ns;
|
||||
|
||||
if (is_disk_read(ev->name))
|
||||
summary->total_disk_reads++;
|
||||
else if (is_disk_write(ev->name))
|
||||
summary->total_disk_writes++;
|
||||
if (strcmp(ev->name, "serial_write") == 0)
|
||||
summary->total_serial_writes++;
|
||||
}
|
||||
|
||||
static void handle_mapping(FILE *f, trace_summary_t *summary)
|
||||
{
|
||||
uint64_t event_id;
|
||||
uint32_t name_len;
|
||||
|
||||
if (read_u64(f, &event_id) != 0)
|
||||
return;
|
||||
if (read_u32(f, &name_len) != 0)
|
||||
return;
|
||||
|
||||
if (name_len == 0 || name_len > 4096) {
|
||||
if (name_len > 0)
|
||||
fseek(f, (long)name_len, SEEK_CUR);
|
||||
return;
|
||||
}
|
||||
|
||||
char namebuf[64];
|
||||
size_t copylen = name_len < 63 ? name_len : 63;
|
||||
|
||||
if (copylen <= sizeof(namebuf) - 1) {
|
||||
if (fread(namebuf, 1, copylen, f) != copylen)
|
||||
return;
|
||||
namebuf[copylen] = '\0';
|
||||
|
||||
if (event_id < TRACE_MAX_EVENT_ID) {
|
||||
memcpy(summary->event_names[event_id], namebuf, copylen + 1);
|
||||
}
|
||||
|
||||
if (name_len > copylen) {
|
||||
if (fseek(f, (long)(name_len - copylen), SEEK_CUR) != 0)
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (fseek(f, (long)name_len, SEEK_CUR) != 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int handle_event(FILE *f, trace_summary_t *summary, trace_event_t **tail)
|
||||
{
|
||||
uint64_t event_id, timestamp_ns;
|
||||
uint32_t record_length, record_pid;
|
||||
|
||||
if (read_u64(f, &event_id) != 0) return -1;
|
||||
if (read_u64(f, ×tamp_ns) != 0) return -1;
|
||||
if (read_u32(f, &record_length) != 0) return -1;
|
||||
if (read_u32(f, &record_pid) != 0) return -1;
|
||||
|
||||
uint32_t args_len = 0;
|
||||
if (record_length >= TRACE_REC_HEADER_LEN)
|
||||
args_len = record_length - TRACE_REC_HEADER_LEN;
|
||||
|
||||
uint8_t *args = NULL;
|
||||
if (args_len > 0) {
|
||||
args = malloc(args_len);
|
||||
if (!args)
|
||||
return -1;
|
||||
if (fread(args, 1, args_len, f) != args_len) {
|
||||
free(args);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
trace_event_t *ev = calloc(1, sizeof(trace_event_t));
|
||||
if (!ev) {
|
||||
free(args);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ev->event_id = event_id;
|
||||
ev->timestamp_ns = timestamp_ns;
|
||||
ev->pid = record_pid;
|
||||
ev->args_len = args_len;
|
||||
ev->args = args;
|
||||
|
||||
resolve_name(summary, ev);
|
||||
update_stats(summary, ev);
|
||||
|
||||
if (!summary->events)
|
||||
summary->events = ev;
|
||||
else
|
||||
(*tail)->next = ev;
|
||||
*tail = ev;
|
||||
summary->event_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trace_parse_file(const char *path, trace_summary_t *summary)
|
||||
{
|
||||
if (!path || !summary)
|
||||
return -1;
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
memset(summary, 0, sizeof(*summary));
|
||||
init_known_names(summary);
|
||||
|
||||
uint64_t hdr_event_id, hdr_magic, hdr_version;
|
||||
if (read_u64(f, &hdr_event_id) != 0 ||
|
||||
read_u64(f, &hdr_magic) != 0 ||
|
||||
read_u64(f, &hdr_version) != 0) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hdr_event_id != TRACE_HEADER_EVENT_ID ||
|
||||
hdr_magic != TRACE_HEADER_MAGIC) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_event_t *tail = NULL;
|
||||
|
||||
for (;;) {
|
||||
if (summary->event_count >= MAX_EVENTS_IN_LIST) {
|
||||
uint64_t rectype;
|
||||
while (read_u64(f, &rectype) == 0) {
|
||||
if (rectype == TRACE_RECORD_TYPE_MAPPING) {
|
||||
handle_mapping(f, summary);
|
||||
} else {
|
||||
uint64_t event_id, timestamp_ns;
|
||||
uint32_t record_length, record_pid;
|
||||
if (read_u64(f, &event_id) != 0) break;
|
||||
if (read_u64(f, ×tamp_ns) != 0) break;
|
||||
if (read_u32(f, &record_length) != 0) break;
|
||||
if (read_u32(f, &record_pid) != 0) break;
|
||||
summary->total_events++;
|
||||
if (event_id < TRACE_MAX_EVENT_ID)
|
||||
summary->events_by_id[event_id]++;
|
||||
if (timestamp_ns > summary->last_timestamp)
|
||||
summary->last_timestamp = timestamp_ns;
|
||||
uint32_t al = 0;
|
||||
if (record_length >= TRACE_REC_HEADER_LEN)
|
||||
al = record_length - TRACE_REC_HEADER_LEN;
|
||||
if (al > 0)
|
||||
fseek(f, (long)al, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t rectype;
|
||||
if (read_u64(f, &rectype) != 0)
|
||||
break;
|
||||
|
||||
if (rectype == TRACE_RECORD_TYPE_MAPPING) {
|
||||
handle_mapping(f, summary);
|
||||
} else if (rectype == TRACE_RECORD_TYPE_EVENT) {
|
||||
if (handle_event(f, summary, &tail) != 0)
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void trace_free_summary(trace_summary_t *summary)
|
||||
{
|
||||
if (!summary)
|
||||
return;
|
||||
|
||||
trace_event_t *ev = summary->events;
|
||||
while (ev) {
|
||||
trace_event_t *next = ev->next;
|
||||
free(ev->args);
|
||||
free(ev);
|
||||
ev = next;
|
||||
}
|
||||
summary->events = NULL;
|
||||
summary->event_count = 0;
|
||||
}
|
||||
|
||||
void trace_print_text(const trace_summary_t *summary, FILE *out)
|
||||
{
|
||||
fprintf(out, "=== Trace Summary ===\n");
|
||||
fprintf(out, "Total events: %" PRIu64 "\n", summary->total_events);
|
||||
fprintf(out, "Events in list: %zu\n", summary->event_count);
|
||||
fprintf(out, "First timestamp: %" PRIu64 " ns\n", summary->first_timestamp);
|
||||
fprintf(out, "Last timestamp: %" PRIu64 " ns\n", summary->last_timestamp);
|
||||
|
||||
if (summary->last_timestamp > summary->first_timestamp) {
|
||||
double dur_ms = (double)(summary->last_timestamp - summary->first_timestamp) / 1000000.0;
|
||||
fprintf(out, "Duration: %.3f ms\n", dur_ms);
|
||||
}
|
||||
|
||||
fprintf(out, "\nI/O Counts:\n");
|
||||
fprintf(out, " Disk reads: %" PRIu64 "\n", summary->total_disk_reads);
|
||||
fprintf(out, " Disk writes: %" PRIu64 "\n", summary->total_disk_writes);
|
||||
fprintf(out, " Serial writes: %" PRIu64 "\n", summary->total_serial_writes);
|
||||
|
||||
fprintf(out, "\n--- Events by ID ---\n");
|
||||
for (int i = 0; i < TRACE_MAX_EVENT_ID; i++) {
|
||||
if (summary->events_by_id[i] > 0) {
|
||||
const char *nm = summary->event_names[i][0]
|
||||
? summary->event_names[i] : "(unknown)";
|
||||
fprintf(out, " [%3d] %-40s %" PRIu64 "\n", i, nm, summary->events_by_id[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trace_print_json(const trace_summary_t *summary, FILE *out)
|
||||
{
|
||||
fprintf(out, "{\n");
|
||||
fprintf(out, " \"total_events\": %" PRIu64 ",\n", summary->total_events);
|
||||
fprintf(out, " \"event_count\": %zu,\n", summary->event_count);
|
||||
fprintf(out, " \"first_timestamp_ns\": %" PRIu64 ",\n", summary->first_timestamp);
|
||||
fprintf(out, " \"last_timestamp_ns\": %" PRIu64 ",\n", summary->last_timestamp);
|
||||
fprintf(out, " \"disk_reads\": %" PRIu64 ",\n", summary->total_disk_reads);
|
||||
fprintf(out, " \"disk_writes\": %" PRIu64 ",\n", summary->total_disk_writes);
|
||||
fprintf(out, " \"serial_writes\": %" PRIu64 ",\n", summary->total_serial_writes);
|
||||
|
||||
fprintf(out, " \"events\": [");
|
||||
int first = 1;
|
||||
for (int i = 0; i < TRACE_MAX_EVENT_ID; i++) {
|
||||
if (summary->events_by_id[i] > 0) {
|
||||
const char *nm = summary->event_names[i][0]
|
||||
? summary->event_names[i] : "unknown";
|
||||
fprintf(out, "%s\n {\"id\": %d, \"name\": \"%s\", \"count\": %" PRIu64 "}",
|
||||
first ? "" : ",", i, nm, summary->events_by_id[i]);
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
fprintf(out, first ? "]\n" : "\n ]\n");
|
||||
|
||||
fprintf(out, "}\n");
|
||||
}
|
||||
|
||||
void trace_print_csv(const trace_summary_t *summary, FILE *out)
|
||||
{
|
||||
fprintf(out, "event_id,event_name,count\n");
|
||||
for (int i = 0; i < TRACE_MAX_EVENT_ID; i++) {
|
||||
if (summary->events_by_id[i] > 0) {
|
||||
const char *nm = summary->event_names[i][0]
|
||||
? summary->event_names[i] : "unknown";
|
||||
fprintf(out, "%d,%s,%" PRIu64 "\n", i, nm, summary->events_by_id[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* trace_parser.h - Parse QEMU simpletrace .bin files.
|
||||
*
|
||||
* Provides structured access to trace events, summary statistics, and
|
||||
* formatted output (text / JSON / CSV).
|
||||
*/
|
||||
|
||||
#ifndef TRACE_PARSER_H
|
||||
#define TRACE_PARSER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Maximum number of distinct event IDs we track (QEMU uses sequential IDs). */
|
||||
#define TRACE_MAX_EVENT_ID 256
|
||||
|
||||
/* A single parsed trace event (node in a singly-linked list). */
|
||||
typedef struct trace_event {
|
||||
uint64_t event_id;
|
||||
uint64_t timestamp_ns;
|
||||
uint64_t pid;
|
||||
uint32_t args_len;
|
||||
uint8_t *args; /* raw argument bytes (malloc'd, may be NULL) */
|
||||
char name[64]; /* resolved name, or "event_<id>" fallback */
|
||||
struct trace_event *next;
|
||||
} trace_event_t;
|
||||
|
||||
/* Aggregate statistics over all parsed events. */
|
||||
typedef struct trace_summary {
|
||||
uint64_t total_events;
|
||||
uint64_t events_by_id[TRACE_MAX_EVENT_ID]; /* count per event_id */
|
||||
char event_names[TRACE_MAX_EVENT_ID][64]; /* name mapping */
|
||||
uint64_t first_timestamp;
|
||||
uint64_t last_timestamp;
|
||||
uint64_t total_disk_reads;
|
||||
uint64_t total_disk_writes;
|
||||
uint64_t total_serial_writes;
|
||||
trace_event_t *events; /* linked-list head (all parsed events) */
|
||||
size_t event_count; /* number of nodes in the list */
|
||||
} trace_summary_t;
|
||||
|
||||
/*
|
||||
* Parse a QEMU simpletrace binary file.
|
||||
* Returns 0 on success (even if the file contained 0 events), -1 on error
|
||||
* (file not found, bad magic, read failure).
|
||||
* Caller must call trace_free_summary() to release memory.
|
||||
*/
|
||||
int trace_parse_file(const char *path, trace_summary_t *summary);
|
||||
|
||||
/* Free all dynamically-allocated memory in *summary. */
|
||||
void trace_free_summary(trace_summary_t *summary);
|
||||
|
||||
/* Output helpers — write formatted summaries to *out. */
|
||||
void trace_print_text(const trace_summary_t *summary, FILE *out);
|
||||
void trace_print_json(const trace_summary_t *summary, FILE *out);
|
||||
void trace_print_csv(const trace_summary_t *summary, FILE *out);
|
||||
|
||||
#endif /* TRACE_PARSER_H */
|
||||
@@ -0,0 +1,18 @@
|
||||
# hiperiso trace events — Tier 2 (detailed)
|
||||
# Standard set plus PCI config-space access, port I/O, and AHCI details.
|
||||
# Used by: qemu -trace events=trace-detailed.events
|
||||
serial_read
|
||||
serial_write
|
||||
cd_read_sector
|
||||
cd_read_sector_sync
|
||||
blk_co_preadv
|
||||
blk_co_pwritev
|
||||
ahci_command_complete
|
||||
cpu_in
|
||||
cpu_out
|
||||
pci_cfg_read
|
||||
pci_cfg_write
|
||||
ahci_port_read
|
||||
ahci_port_write
|
||||
ahci_irq_raise
|
||||
ahci_irq_lower
|
||||
@@ -0,0 +1,21 @@
|
||||
# hiperiso trace events — Tier 3 (full)
|
||||
# Detailed set plus every memory-mapped I/O access. Very high overhead
|
||||
# (EPT violation per access) — debug only, expect 10-100x boot slowdown.
|
||||
# Used by: qemu -trace events=trace-full.events
|
||||
serial_read
|
||||
serial_write
|
||||
cd_read_sector
|
||||
cd_read_sector_sync
|
||||
blk_co_preadv
|
||||
blk_co_pwritev
|
||||
ahci_command_complete
|
||||
cpu_in
|
||||
cpu_out
|
||||
pci_cfg_read
|
||||
pci_cfg_write
|
||||
ahci_port_read
|
||||
ahci_port_write
|
||||
ahci_irq_raise
|
||||
ahci_irq_lower
|
||||
memory_region_ops_read
|
||||
memory_region_ops_write
|
||||
@@ -0,0 +1,10 @@
|
||||
# hiperiso trace events — Tier 1 (standard)
|
||||
# Always-on: serial console capture + basic disk/CD-ROM I/O.
|
||||
# Used by: qemu -trace events=trace-standard.events
|
||||
serial_read
|
||||
serial_write
|
||||
cd_read_sector
|
||||
cd_read_sector_sync
|
||||
blk_co_preadv
|
||||
blk_co_pwritev
|
||||
ahci_command_complete
|
||||
Reference in New Issue
Block a user