259 lines
7.2 KiB
C
259 lines
7.2 KiB
C
// Cross-platform "where did it hang?" logger.
|
|
//
|
|
// On DOS/Amiga/ST: joeylog.txt is held open across calls and FLUSHED
|
|
// AFTER EVERY LINE -- safe logs are more important than fast logs. A
|
|
// crash, hang, or hard kill therefore always leaves every line emitted
|
|
// so far on disk.
|
|
//
|
|
// On the IIgs the per-line-flush design is a triple failure
|
|
// (claudeDocs/PERF-AUDIT.md #80/#82): the libc fflush does not commit through the
|
|
// GS/OS FST (nothing persists until fclose), each real FST write costs
|
|
// seconds of emulated floppy time, and a write can wedge indefinitely
|
|
// inside SmartPort firmware retries -- one such wedge stalled the UBER
|
|
// benchmark for 18+ emulated minutes. So on IIgs the lines land in a
|
|
// RAM ring instead: gJoeyLogRing/gJoeyLogRingHead are non-static so
|
|
// headless-MAME Lua probes can drain the log LIVE from emulated memory
|
|
// (scripts/bench-iigs.sh), and jlLogFlush() writes the accumulated ring
|
|
// to joeylog.txt in one open/write/close pass (fclose is the only
|
|
// operation that actually commits) for on-hardware persistence.
|
|
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
#include "joey/platform.h"
|
|
#include "joey/debug.h"
|
|
|
|
|
|
static const char *kLogPath = "joeylog.txt";
|
|
|
|
#ifdef JOEY_LOG_SERIAL_TEE
|
|
// Supplied by the port. Declared ahead of the platform split because BOTH log
|
|
// implementations below tee through it.
|
|
void jlpLogTee(const char *text);
|
|
#endif
|
|
|
|
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
|
|
// Ring capacity. Head is a free-running total-bytes-written counter
|
|
// (uint16_t, wraps at 65536); the ring index is head % size, so the
|
|
// size must divide 65536 for the wrap to stay aligned. A Lua reader
|
|
// tracks its own last-read count and detects overruns by delta > size.
|
|
#define JOEY_LOG_RING_BYTES 8192u
|
|
|
|
uint8_t gJoeyLogRing[JOEY_LOG_RING_BYTES];
|
|
uint16_t gJoeyLogRingHead = 0;
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void ringAppend(const char *text);
|
|
|
|
|
|
static void ringAppend(const char *text) {
|
|
uint16_t head;
|
|
uint16_t idx;
|
|
|
|
#ifdef JOEY_LOG_SERIAL_TEE
|
|
// Opt-in live mirror of the log to the port's serial line. The 8 KB ring
|
|
// holds only the last ~100 lines and jlLogFlush needs a writable disk with
|
|
// room on it; a run that outlives either loses exactly the output being
|
|
// hunted. The tee is unbounded, needs no disk, and arrives in real time,
|
|
// which is what makes a wedged run diagnosable at all. OFF by default:
|
|
// it is a debugging build option, not a shipping cost.
|
|
jlpLogTee(text);
|
|
#endif
|
|
// Explicit read-modify-write of the global (never `++`/`+=` on a
|
|
// global -- the 65816 inc-abs/DBR trap).
|
|
head = gJoeyLogRingHead;
|
|
while (*text != '\0') {
|
|
idx = (uint16_t)(head & (JOEY_LOG_RING_BYTES - 1u));
|
|
gJoeyLogRing[idx] = (uint8_t)*text;
|
|
text++;
|
|
head = (uint16_t)(head + 1u);
|
|
}
|
|
gJoeyLogRingHead = head;
|
|
}
|
|
|
|
|
|
void jlLog(const char *msg) {
|
|
if (msg == NULL) {
|
|
return;
|
|
}
|
|
ringAppend(msg);
|
|
ringAppend("\n");
|
|
}
|
|
|
|
|
|
void jlLogF(const char *fmt, ...) {
|
|
char line[200];
|
|
va_list args;
|
|
|
|
if (fmt == NULL) {
|
|
return;
|
|
}
|
|
va_start(args, fmt);
|
|
(void)vsnprintf(line, sizeof(line), fmt, args);
|
|
va_end(args);
|
|
ringAppend(line);
|
|
// Callers' formats mostly end in \n already; jlLog adds one for
|
|
// plain strings, so nothing is appended here.
|
|
}
|
|
|
|
|
|
// One-shot dump of everything the ring has seen to joeylog.txt.
|
|
// open/write/CLOSE per call: fclose is the only point the FST commits
|
|
// (#80), and batching all lines into one pass keeps the floppy cost to
|
|
// a single visit instead of one per line. Mode "w": the dump always
|
|
// contains the ENTIRE surviving ring, so truncate-on-flush both makes
|
|
// repeated flushes idempotent and lets jlLogReset stay disk-free (see
|
|
// below).
|
|
void jlLogFlush(void) {
|
|
FILE *fp;
|
|
uint16_t head;
|
|
uint16_t start;
|
|
uint16_t count;
|
|
uint16_t i;
|
|
uint16_t idx;
|
|
|
|
head = gJoeyLogRingHead;
|
|
if (head == 0u) {
|
|
return;
|
|
}
|
|
fp = fopen(kLogPath, "w");
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
// If the ring wrapped, the oldest surviving byte is head - size.
|
|
count = head;
|
|
start = 0;
|
|
if (count > JOEY_LOG_RING_BYTES) {
|
|
start = (uint16_t)(head - JOEY_LOG_RING_BYTES);
|
|
count = JOEY_LOG_RING_BYTES;
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
idx = (uint16_t)((uint16_t)(start + i) & (JOEY_LOG_RING_BYTES - 1u));
|
|
fputc((int)gJoeyLogRing[idx], fp);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
// Ring-only: NO disk I/O here. The old truncate write (fopen "w" +
|
|
// fclose) was the first floppy WRITE any app performed and wedged
|
|
// forever in SmartPort firmware retries under headless MAME -- the
|
|
// same #82 failure class the RAM ring exists to avoid. UBER never
|
|
// calls jlLogReset, so the write path lay unexercised until the
|
|
// spacetaxi acceptance test hit it (hang before the first log line,
|
|
// PC spinning in the ROM WAIT loop). jlLogFlush opens with "w" now,
|
|
// so the on-disk file still ends up truncated-to-current on the next
|
|
// flush.
|
|
void jlLogReset(void) {
|
|
gJoeyLogRingHead = 0;
|
|
}
|
|
|
|
#else /* not JOEYLIB_PLATFORM_IIGS */
|
|
|
|
static FILE *gLogFp = NULL;
|
|
/* 16 KB full buffer so a single log line's many small vfprintf writes
|
|
* coalesce into one host write; jlLog/jlLogF then fflush once per
|
|
* line. Default stdio buffers are only ~512 bytes, which would split a
|
|
* long line across multiple writes. */
|
|
#define JOEY_LOG_BUF_BYTES 16384
|
|
static char gLogBuf[JOEY_LOG_BUF_BYTES];
|
|
|
|
|
|
/* Lazy-open. Returns NULL if the open failed (silently disable). */
|
|
static FILE *logFile(void) {
|
|
if (gLogFp == NULL) {
|
|
gLogFp = fopen(kLogPath, "a");
|
|
if (gLogFp != NULL) {
|
|
(void)setvbuf(gLogFp, gLogBuf, _IOFBF, sizeof(gLogBuf));
|
|
}
|
|
}
|
|
return gLogFp;
|
|
}
|
|
|
|
|
|
void jlLog(const char *msg) {
|
|
FILE *fp;
|
|
if (msg == NULL) {
|
|
return;
|
|
}
|
|
#ifdef JOEY_LOG_SERIAL_TEE
|
|
jlpLogTee(msg);
|
|
jlpLogTee("\n");
|
|
#endif
|
|
fp = logFile();
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
fputs(msg, fp);
|
|
fputc('\n', fp);
|
|
fflush(fp);
|
|
}
|
|
|
|
|
|
void jlLogF(const char *fmt, ...) {
|
|
FILE *fp;
|
|
va_list args;
|
|
if (fmt == NULL) {
|
|
return;
|
|
}
|
|
fp = logFile();
|
|
#ifdef JOEY_LOG_SERIAL_TEE
|
|
// Format once into a buffer so the same bytes reach the file and the wire.
|
|
// Only under the tee: default builds keep the straight vfprintf, which has
|
|
// no line-length ceiling.
|
|
{
|
|
char line[200];
|
|
|
|
va_start(args, fmt);
|
|
(void)vsnprintf(line, sizeof(line), fmt, args);
|
|
va_end(args);
|
|
jlpLogTee(line);
|
|
jlpLogTee("\n");
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
fputs(line, fp);
|
|
fputc('\n', fp);
|
|
fflush(fp);
|
|
return;
|
|
}
|
|
#else
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
va_start(args, fmt);
|
|
vfprintf(fp, fmt, args);
|
|
va_end(args);
|
|
fputc('\n', fp);
|
|
fflush(fp);
|
|
#endif
|
|
}
|
|
|
|
|
|
void jlLogFlush(void) {
|
|
if (gLogFp != NULL) {
|
|
fflush(gLogFp);
|
|
}
|
|
}
|
|
|
|
|
|
void jlLogReset(void) {
|
|
if (gLogFp != NULL) {
|
|
fclose(gLogFp);
|
|
gLogFp = NULL;
|
|
}
|
|
/* Truncate by opening for write then closing; subsequent
|
|
* jlLog* will reopen for append. */
|
|
{
|
|
FILE *fp = fopen(kLogPath, "w");
|
|
if (fp != NULL) {
|
|
fclose(fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* JOEYLIB_PLATFORM_IIGS */
|