f0bdf66be8
Three trace tiers: standard, detailed, full.
637 lines
22 KiB
C
637 lines
22 KiB
C
/*
|
|
* 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;
|
|
}
|