import * as fsp from 'fs/promises'; import * as path from 'path'; import type { PerfDiagEntry } from './diagnostics.models'; import { formatPerfDiagLine, pushRingBuffer, resolveDiagnosticsFilePath } from './diagnostics.rules'; const DEFAULT_RING_CAPACITY = 120; const FLUSH_DEBOUNCE_MS = 250; export interface PerfDiagWriterOptions { userDataPath: string; sessionId: string; ringCapacity?: number; } export class PerfDiagWriter { private readonly filePath: string; private readonly ringCapacity: number; private readonly pendingLines: string[] = []; private ring: PerfDiagEntry[] = []; private flushTimer: NodeJS.Timeout | null = null; private flushInFlight: Promise | null = null; private disabled = false; constructor(options: PerfDiagWriterOptions) { this.filePath = resolveDiagnosticsFilePath(options.userDataPath, options.sessionId); this.ringCapacity = options.ringCapacity ?? DEFAULT_RING_CAPACITY; } get snapshotFilePath(): string { return this.filePath; } get bufferedEntries(): readonly PerfDiagEntry[] { return this.ring; } append(entry: PerfDiagEntry): void { if (this.disabled) { return; } try { this.ring = pushRingBuffer(this.ring, entry, this.ringCapacity); this.pendingLines.push(`${formatPerfDiagLine(entry)}\n`); this.scheduleFlush(); } catch { this.disabled = true; } } async flush(): Promise { if (this.disabled || this.pendingLines.length === 0) { return; } if (this.flushInFlight) { await this.flushInFlight; return; } const lines = this.pendingLines.splice(0, this.pendingLines.length); this.flushInFlight = this.writeLines(lines) .catch(() => { this.disabled = true; }) .finally(() => { this.flushInFlight = null; }); await this.flushInFlight; } async flushSnapshot(label: string): Promise { this.append({ collectedAt: Date.now(), source: 'main', type: 'session', payload: { event: label, filePath: this.filePath, entries: this.ring } }); await this.flush(); } private scheduleFlush(): void { if (this.flushTimer) { return; } this.flushTimer = setTimeout(() => { this.flushTimer = null; void this.flush(); }, FLUSH_DEBOUNCE_MS); } private async writeLines(lines: string[]): Promise { await fsp.mkdir(path.dirname(this.filePath), { recursive: true }); await fsp.appendFile(this.filePath, lines.join(''), 'utf8'); } }