rebase generation

This commit is contained in:
MaKarin
2026-04-07 19:40:41 +03:00
parent 73ddb1a948
commit aab7bfa691
180 changed files with 15512 additions and 364 deletions

View File

@@ -0,0 +1,169 @@
/**
* KIS-TOiR Performance Monitor
*
* Lightweight instrumentation for the orchestrator. Tracks wall-clock time
* between named marks, accumulates metrics, prints human-readable summaries,
* and exports to JSON/CSV for trend analysis.
*
* Usage:
* import { monitor } from './performance-monitor';
* monitor.mark('start');
* // ... work ...
* monitor.measure('prisma');
* monitor.summary('Full Generation');
* fs.writeFileSync('generation-metrics.json', monitor.export('json'));
*/
import * as fs from "fs";
import * as path from "path";
export interface MetricEntry {
label: string;
duration: number; // milliseconds
timestamp: Date;
}
export type ExportFormat = "json" | "csv";
export class PerformanceMonitor {
private marks: Map<string, number> = new Map();
private metrics: MetricEntry[] = [];
private readonly startTime: number;
constructor() {
this.startTime = Date.now();
this.marks.set("start", this.startTime);
}
/**
* Record a named mark at the current time. A mark is an instant, not a
* duration. Use mark() to set a baseline, then measure() to record elapsed
* time from that baseline.
*/
mark(label: string): void {
this.marks.set(label, Date.now());
}
/**
* Record the elapsed time from the last `start` (or prior matching mark)
* until now. Returns the duration in milliseconds and stores it in the
* metrics collection. Pass `sinceMark` to measure from a specific mark
* rather than the most recent one.
*/
measure(label: string, sinceMark = "start"): number {
const origin = this.marks.get(sinceMark) ?? this.startTime;
const duration = Date.now() - origin;
this.metrics.push({
label,
duration,
timestamp: new Date(),
});
// eslint-disable-next-line no-console
console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`);
return duration;
}
/**
* Manually add a metric entry. Useful for tasks where you already have the
* duration (e.g., measured inside Promise.all bookkeeping).
*/
record(label: string, duration: number): void {
this.metrics.push({
label,
duration,
timestamp: new Date(),
});
// eslint-disable-next-line no-console
console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`);
}
/**
* Print a summary block. If `label` is provided, compares the max metric
* (assumed parallel total) against the sum of metrics (sequential baseline)
* and reports the speedup.
*/
summary(label = "Summary"): void {
const total = Date.now() - this.startTime;
const seqBaseline = this.metrics.reduce((acc, m) => acc + m.duration, 0);
const parallelActual = this.metrics.reduce(
(acc, m) => Math.max(acc, m.duration),
0
);
const improvement =
seqBaseline > 0
? ((seqBaseline - parallelActual) / seqBaseline) * 100
: 0;
// eslint-disable-next-line no-console
console.log(`\n📊 ${label}`);
// eslint-disable-next-line no-console
console.log(` Total wall-clock: ${total.toFixed(2)}ms`);
// eslint-disable-next-line no-console
console.log(` Sum of phases: ${seqBaseline.toFixed(2)}ms (sequential baseline)`);
if (this.metrics.length > 1) {
// eslint-disable-next-line no-console
console.log(
` Longest phase: ${parallelActual.toFixed(2)}ms (parallel lower bound)`
);
// eslint-disable-next-line no-console
console.log(` Speedup achieved: ${improvement.toFixed(1)}%`);
}
}
/**
* Serialize metrics for disk storage / trend analysis.
*/
export(format: ExportFormat = "json"): string {
if (format === "csv") {
const header = "label,duration_ms,timestamp";
const rows = this.metrics.map(
(m) => `${m.label},${m.duration},${m.timestamp.toISOString()}`
);
return [header, ...rows].join("\n");
}
return JSON.stringify(
{
startedAt: new Date(this.startTime).toISOString(),
totalMs: Date.now() - this.startTime,
metrics: this.metrics,
},
null,
2
);
}
/**
* Write metrics to a file alongside other generation outputs. Defaults to
* project-root `generation-metrics.json`.
*/
writeReport(filePath = "generation-metrics.json"): void {
const resolved = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath);
fs.writeFileSync(resolved, this.export("json"));
}
/**
* Return a shallow copy of the recorded metrics. Useful for tests and for
* attaching to GenerationOutput results.
*/
getMetrics(): MetricEntry[] {
return [...this.metrics];
}
/**
* Reset all marks and metrics. The monitor remains usable afterward.
*/
reset(): void {
this.marks.clear();
this.metrics.length = 0;
this.marks.set("start", Date.now());
}
}
/**
* Shared singleton instance for convenience. Multiple independent runs can
* call `monitor.reset()` between them.
*/
export const monitor = new PerformanceMonitor();
export default monitor;