mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
feat(perf): add on-demand (ui) throughput to cover pipeline cpm (#947)
* feat(perf): add on-demand (ui) throughput to cover pipeline cpm Cover cpm previously measured only the native backfill (lib) via the cover:library-progress done delta. Add a parallel UI series: every completed on-demand Rust cover ensure (grid / now-playing) records a timestamp, surfaced as a covers-per-minute rate. Both are exposed in the live cover diag, shown as separate Backfill (lib) / On-demand (ui) cards in the Monitor tab (each pinnable to the overlay) and as lib/ui rows in the Cover pipeline overlay block. * docs(changelog): note cover on-demand (ui) throughput (PR #947) Add CHANGELOG entry and credits line for the UI cover cpm metric. * fix(perf): source on-demand (ui) cpm from backend produced-cover count The JS ensure-queue counter never tracked: produced covers return hit:true (only misses/errors are hit:false), and ensure-queue dedup/HMR made client counting unreliable. Count on-demand covers natively in ensure_inner on the produce path (non-bulk, past the cache-hit gate), expose a cumulative uiEnsuredTotal in the pipeline stats, and derive the per-minute rate on the frontend from polled deltas — mirroring the lib backfill series. * perf(cover): measure cpm over trailing 5s instead of full minute A 60s rolling average added too much inertia, flattening real bursts and stalls in both the lib backfill and on-demand (ui) cover throughput. Compute the rate from the trailing 5s of samples (still extrapolated to per-minute), so the figure reacts promptly and decays to 0 within the window when idle.
This commit is contained in:
@@ -2,7 +2,9 @@ import { describe, expect, it, beforeEach, vi, afterEach } from 'vitest';
|
||||
import {
|
||||
getCoverCachedPerMinute,
|
||||
getCoverPerfState,
|
||||
getCoverUiPerMinute,
|
||||
recordCoverProgress,
|
||||
recordCoverUiTotal,
|
||||
resetCoverPerfStateForTest,
|
||||
} from './coverPerfStore';
|
||||
|
||||
@@ -15,26 +17,29 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('coverPerfStore', () => {
|
||||
it('derives covers-per-minute from done deltas over time', () => {
|
||||
it('derives covers-per-minute from done deltas over the trailing window', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1_000_000);
|
||||
recordCoverProgress({ done: 100, total: 1000, pending: 900 });
|
||||
vi.advanceTimersByTime(30_000);
|
||||
recordCoverProgress({ done: 130, total: 1000, pending: 870 });
|
||||
// +30 covers over 30s ≈ 60 cpm.
|
||||
expect(getCoverCachedPerMinute()).toBeCloseTo(60, 0);
|
||||
expect(getCoverPerfState().done).toBe(130);
|
||||
vi.advanceTimersByTime(1_000);
|
||||
recordCoverProgress({ done: 110, total: 1000, pending: 890 });
|
||||
vi.advanceTimersByTime(1_000);
|
||||
recordCoverProgress({ done: 120, total: 1000, pending: 880 });
|
||||
// +20 covers over the last 2s ≈ 600 cpm (no minute-long inertia).
|
||||
expect(getCoverCachedPerMinute()).toBeCloseTo(600, 0);
|
||||
expect(getCoverPerfState().done).toBe(120);
|
||||
});
|
||||
|
||||
it('returns 0 with a single sample and prunes the window after a minute', () => {
|
||||
it('returns 0 with a single sample and decays once the trailing window empties', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(2_000_000);
|
||||
recordCoverProgress({ done: 10 });
|
||||
expect(getCoverCachedPerMinute()).toBe(0);
|
||||
vi.advanceTimersByTime(20_000);
|
||||
vi.advanceTimersByTime(1_000);
|
||||
recordCoverProgress({ done: 20 });
|
||||
expect(getCoverCachedPerMinute()).toBeGreaterThan(0);
|
||||
vi.advanceTimersByTime(61_000);
|
||||
// No fresh samples for >5s → trailing window empties → back to 0.
|
||||
vi.advanceTimersByTime(6_000);
|
||||
expect(getCoverCachedPerMinute()).toBe(0);
|
||||
});
|
||||
|
||||
@@ -48,4 +53,31 @@ describe('coverPerfStore', () => {
|
||||
expect(getCoverCachedPerMinute()).toBe(0);
|
||||
expect(getCoverPerfState().done).toBe(5);
|
||||
});
|
||||
|
||||
it('derives UI covers-per-minute from backend total deltas over the trailing window', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(4_000_000);
|
||||
expect(getCoverUiPerMinute()).toBe(0);
|
||||
recordCoverUiTotal(100);
|
||||
// A single sample has no delta yet.
|
||||
expect(getCoverUiPerMinute()).toBe(0);
|
||||
vi.advanceTimersByTime(2_000);
|
||||
recordCoverUiTotal(130);
|
||||
// +30 produced over the last 2s ≈ 900 cpm; lib series stays untouched.
|
||||
expect(getCoverUiPerMinute()).toBeCloseTo(900, 0);
|
||||
expect(getCoverCachedPerMinute()).toBe(0);
|
||||
// Idle poll keeps reporting the same total → delta 0 → rate decays to 0.
|
||||
vi.advanceTimersByTime(6_000);
|
||||
recordCoverUiTotal(130);
|
||||
expect(getCoverUiPerMinute()).toBe(0);
|
||||
});
|
||||
|
||||
it('resets the UI window on a backwards jump (process restart)', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(5_000_000);
|
||||
recordCoverUiTotal(500);
|
||||
vi.advanceTimersByTime(5_000);
|
||||
recordCoverUiTotal(3);
|
||||
expect(getCoverUiPerMinute()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user