mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
perf(analysis): measure tpm over trailing 5s window (#948)
* perf(analysis): measure tpm over trailing 5s instead of full minute Mirror the cover cpm change: a 60s rolling average added too much inertia and flattened real bursts/stalls. Count completions in the trailing 5s window and extrapolate to per-minute, so analysis tpm reacts promptly and decays to 0 within the window when idle. Retention stays at 60s. * docs(changelog): note trailing-window throughput rate (PR #948)
This commit is contained in:
@@ -388,6 +388,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Probe — responsive throughput windows (tpm / cpm)
|
||||||
|
|
||||||
|
**By [@cucadmuh](https://github.com/cucadmuh), PR [#948](https://github.com/Psychotoxical/psysonic/pull/948)**
|
||||||
|
|
||||||
|
* Analysis **tpm** and cover **cpm** (lib + ui) now measure throughput over the trailing **5 seconds** instead of a full-minute rolling average. The figure is still extrapolated to per-minute, but reacts promptly to bursts/stalls and decays to 0 within the window when idle, instead of coasting on minute-long inertia.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ const CONTRIBUTOR_ENTRIES = [
|
|||||||
'Performance Probe: cover pipeline covers-per-minute (cpm) throughput, the cover analogue of analysis tpm (PR #945)',
|
'Performance Probe: cover pipeline covers-per-minute (cpm) throughput, the cover analogue of analysis tpm (PR #945)',
|
||||||
'Performance Probe: live runtime logs tab with depth switch, line cap, and ordered include/exclude word filter (PR #946)',
|
'Performance Probe: live runtime logs tab with depth switch, line cap, and ordered include/exclude word filter (PR #946)',
|
||||||
'Performance Probe: on-demand (ui) cover throughput alongside backfill (lib) cpm (PR #947)',
|
'Performance Probe: on-demand (ui) cover throughput alongside backfill (lib) cpm (PR #947)',
|
||||||
|
'Performance Probe: throughput (analysis tpm, cover cpm) measured over a trailing 5s window so the rate reacts promptly instead of coasting on minute-long inertia (PR #948)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,31 +14,36 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('analysisPerfStore', () => {
|
describe('analysisPerfStore', () => {
|
||||||
it('records last track timings and rolling tpm', () => {
|
const record = (trackId: string): void =>
|
||||||
|
recordAnalysisTrackPerf({ trackId, fetchMs: 1, seedMs: 1, bpmMs: 1, totalMs: 3 });
|
||||||
|
|
||||||
|
it('records last track timings and trailing-window tpm', () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime(1_000_000);
|
vi.setSystemTime(1_000_000);
|
||||||
recordAnalysisTrackPerf({
|
record('t1');
|
||||||
trackId: 't1',
|
|
||||||
fetchMs: 1000,
|
|
||||||
seedMs: 2000,
|
|
||||||
bpmMs: 500,
|
|
||||||
totalMs: 3500,
|
|
||||||
});
|
|
||||||
vi.advanceTimersByTime(100);
|
vi.advanceTimersByTime(100);
|
||||||
expect(getAnalysisTracksPerMinute()).toBeGreaterThan(0);
|
// 1 completion in the trailing 5s → 1 / 5s × 60 = 12 tpm.
|
||||||
|
expect(getAnalysisTracksPerMinute()).toBeCloseTo(12, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prunes completions older than one minute from tpm window', () => {
|
it('extrapolates a burst over the trailing 5s window', () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime(2_000_000);
|
vi.setSystemTime(2_000_000);
|
||||||
recordAnalysisTrackPerf({
|
record('a');
|
||||||
trackId: 'old',
|
vi.advanceTimersByTime(1_000);
|
||||||
fetchMs: 1,
|
record('b');
|
||||||
seedMs: 1,
|
vi.advanceTimersByTime(1_000);
|
||||||
bpmMs: 1,
|
record('c');
|
||||||
totalMs: 3,
|
// 3 completions within the last 5s → 3 / 5s × 60 = 36 tpm.
|
||||||
});
|
expect(getAnalysisTracksPerMinute()).toBeCloseTo(36, 0);
|
||||||
vi.advanceTimersByTime(61_000);
|
});
|
||||||
|
|
||||||
|
it('decays to 0 once completions fall outside the trailing window', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(3_000_000);
|
||||||
|
record('old');
|
||||||
|
expect(getAnalysisTracksPerMinute()).toBeGreaterThan(0);
|
||||||
|
vi.advanceTimersByTime(6_000);
|
||||||
expect(getAnalysisTracksPerMinute()).toBe(0);
|
expect(getAnalysisTracksPerMinute()).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,14 @@ type AnalysisPerfState = {
|
|||||||
completedAt: number[];
|
completedAt: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Completion-timestamp retention (kept generous; the live rate uses a shorter window). */
|
||||||
const WINDOW_MS = 60_000;
|
const WINDOW_MS = 60_000;
|
||||||
|
/**
|
||||||
|
* Throughput is measured over the trailing few seconds only — a full-minute
|
||||||
|
* average has too much inertia and flattens real bursts/stalls. The count in
|
||||||
|
* this window is extrapolated to a per-minute figure for display.
|
||||||
|
*/
|
||||||
|
const RATE_WINDOW_MS = 5_000;
|
||||||
|
|
||||||
let state: AnalysisPerfState = { last: null, completedAt: [] };
|
let state: AnalysisPerfState = { last: null, completedAt: [] };
|
||||||
const listeners = new Set<() => void>();
|
const listeners = new Set<() => void>();
|
||||||
@@ -51,11 +58,12 @@ export function recordAnalysisTrackPerf(payload: {
|
|||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tracks analyzed per minute, measured over the trailing few seconds (0 when idle). */
|
||||||
export function getAnalysisTracksPerMinute(now = Date.now()): number {
|
export function getAnalysisTracksPerMinute(now = Date.now()): number {
|
||||||
const completedAt = pruneCompletedAt(now);
|
const cutoff = now - RATE_WINDOW_MS;
|
||||||
if (completedAt.length === 0) return 0;
|
const count = state.completedAt.filter(t => t >= cutoff).length;
|
||||||
const spanMs = Math.max(1, Math.min(WINDOW_MS, now - completedAt[0]));
|
if (count === 0) return 0;
|
||||||
return (completedAt.length / spanMs) * WINDOW_MS;
|
return (count / RATE_WINDOW_MS) * 60_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAnalysisPerfState(): AnalysisPerfState {
|
export function getAnalysisPerfState(): AnalysisPerfState {
|
||||||
|
|||||||
Reference in New Issue
Block a user