* perf(server): bound Git process starts Git refresh bursts could keep creating short-lived processes at the concurrency ceiling, causing sustained process churn on constrained hosts. Apply one daemon-global start-rate and concurrency policy to every Git command while preserving the legacy concurrency environment variable. * build(nix): update npm dependency hash * fix(server): hold Git slots until process exit * fix(server): enforce Git limits across process lifetime * fix(server): align Git metrics with process exit * Fix Git limiter test deadlocks * Tune default Git process rate
79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { GitCommandRuntimeMetricsWindow } from "./git-command-runtime-metrics.js";
|
|
|
|
function createMetricsWindow(concurrencyLimit = 2, maxProcessesPerSecond = 8) {
|
|
let now = 1_000;
|
|
return {
|
|
metrics: new GitCommandRuntimeMetricsWindow(
|
|
{ concurrencyLimit, maxProcessesPerSecond },
|
|
() => now,
|
|
),
|
|
advance(ms: number) {
|
|
now += ms;
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("GitCommandRuntimeMetricsWindow", () => {
|
|
test("separates queue wait from execution time", () => {
|
|
const { metrics, advance } = createMetricsWindow();
|
|
const command = metrics.submit("status");
|
|
advance(40);
|
|
metrics.start(command);
|
|
advance(15);
|
|
metrics.finish(command, { success: true, timedOut: false });
|
|
|
|
expect(metrics.snapshotAndReset()).toMatchObject({
|
|
submitted: 1,
|
|
started: 1,
|
|
completed: 1,
|
|
failed: 0,
|
|
timedOut: 0,
|
|
queueWaitMs: { count: 1, p50Ms: 40, p95Ms: 40, maxMs: 40 },
|
|
executionMs: { count: 1, p50Ms: 15, p95Ms: 15, maxMs: 15 },
|
|
operationsTop: [["status", 1]],
|
|
});
|
|
});
|
|
|
|
test("reports live queue pressure across window resets", () => {
|
|
const { metrics, advance } = createMetricsWindow(1);
|
|
const active = metrics.submit("fetch");
|
|
metrics.start(active);
|
|
const pending = metrics.submit("rev-parse");
|
|
metrics.observeLimiter(1, 1);
|
|
advance(25);
|
|
|
|
expect(metrics.snapshotAndReset()).toMatchObject({
|
|
concurrencyLimit: 1,
|
|
maxProcessesPerSecond: 8,
|
|
active: 1,
|
|
pending: 1,
|
|
peakActive: 1,
|
|
peakPending: 1,
|
|
oldestPendingMs: 25,
|
|
submitted: 2,
|
|
started: 1,
|
|
});
|
|
|
|
advance(10);
|
|
metrics.finish(active, { success: true, timedOut: false });
|
|
metrics.start(pending);
|
|
metrics.observeLimiter(1, 0);
|
|
advance(5);
|
|
metrics.finish(pending, { success: false, timedOut: true });
|
|
|
|
expect(metrics.snapshotAndReset()).toMatchObject({
|
|
active: 0,
|
|
pending: 0,
|
|
peakActive: 1,
|
|
peakPending: 1,
|
|
submitted: 0,
|
|
started: 1,
|
|
completed: 2,
|
|
failed: 1,
|
|
timedOut: 1,
|
|
queueWaitMs: { count: 1, p50Ms: 35, p95Ms: 35, maxMs: 35 },
|
|
});
|
|
});
|
|
});
|