fix(analysis): native library backfill coordinator for advanced strategy (#881)

* feat(analysis): native library backfill coordinator for advanced strategy

Move advanced analytics scheduling from the webview loop into a Rust
background worker (configure + spawn_blocking batch/enqueue), matching
cover backfill. Scan hash+BPM gap tracks instead of the full library;
suppress low-priority analysis UI events; limit loudness refresh IPC to
the playback window.

* fix(analysis): flat backfill configure IPC and restore probe track-perf

Use flattened Tauri args like cover backfill so release/prod invoke works;
keep emitting analysis:track-perf for library low-priority work so Performance
Probe tpm/last-track stats update while waveform/enrichment UI events stay suppressed.

* chore(analysis): fix clippy and drop duplicate TS backfill policy

Allow too_many_arguments on library_analysis_backfill_configure for CI;
remove unused frontend batch API and TS watermark helpers now owned in Rust;
clarify analysis_emits_ui_events comment for low-priority track-perf.

* docs: CHANGELOG and credits for PR #881 native analysis backfill
This commit is contained in:
cucadmuh
2026-05-28 10:49:31 +03:00
committed by GitHub
parent df3533bb5a
commit b24a7fc5cb
21 changed files with 888 additions and 387 deletions
@@ -1,48 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
computeLibraryBackfillTargetDepth,
libraryBackfillNeedsTopUp,
libraryBackfillPipelineBacklog,
libraryBackfillTopUpLimit,
} from './libraryAnalysisBackfillPolicy';
describe('libraryAnalysisBackfillPolicy', () => {
it('targets workers × 3 with floor and cap', () => {
expect(computeLibraryBackfillTargetDepth(1)).toBe(8);
expect(computeLibraryBackfillTargetDepth(4)).toBe(12);
expect(computeLibraryBackfillTargetDepth(8)).toBe(24);
expect(computeLibraryBackfillTargetDepth(100)).toBe(240);
});
const zeroCpu = { cpuQueued: 0, cpuDecodeActive: 0 };
it('measures backlog as HTTP plus CPU seed pipeline load', () => {
expect(
libraryBackfillPipelineBacklog({
httpQueued: 5,
httpDownloadActive: 3,
...zeroCpu,
}),
).toBe(8);
expect(
libraryBackfillPipelineBacklog({
httpQueued: 0,
httpDownloadActive: 0,
cpuQueued: 4,
cpuDecodeActive: 2,
}),
).toBe(6);
});
it('requests top-up while backlog stays below target', () => {
const stats = { httpQueued: 2, httpDownloadActive: 1, ...zeroCpu };
expect(libraryBackfillNeedsTopUp(stats, 8)).toBe(true);
expect(libraryBackfillTopUpLimit(stats, 8)).toBe(20);
});
it('stops top-up when backlog meets target', () => {
const stats = { httpQueued: 20, httpDownloadActive: 4, ...zeroCpu };
expect(libraryBackfillNeedsTopUp(stats, 8)).toBe(false);
expect(libraryBackfillTopUpLimit(stats, 8)).toBe(0);
});
});
@@ -1,41 +0,0 @@
import type { AnalysisPipelineQueueStatsDto } from '../../api/analysis';
import { LIBRARY_ANALYSIS_BACKFILL_BATCH_SIZE } from '../../api/analysis';
/** Target backlog ≈ workers × multiplier (min floor, max cap). */
export const LIBRARY_BACKLOG_DEPTH_MULTIPLIER = 3;
export const LIBRARY_BACKLOG_MIN = 8;
export const LIBRARY_BACKLOG_MAX = 240;
export function computeLibraryBackfillTargetDepth(workers: number): number {
const w = Math.max(1, Math.round(workers));
return Math.min(
LIBRARY_BACKLOG_MAX,
Math.max(LIBRARY_BACKLOG_MIN, w * LIBRARY_BACKLOG_DEPTH_MULTIPLIER),
);
}
type PipelineBacklogStats = Pick<
AnalysisPipelineQueueStatsDto,
'httpQueued' | 'httpDownloadActive' | 'cpuQueued' | 'cpuDecodeActive'
>;
/** HTTP + in-flight CPU seed jobs (decode backpressure must not look like an empty pipeline). */
export function libraryBackfillPipelineBacklog(stats: PipelineBacklogStats): number {
return (
stats.httpQueued
+ stats.httpDownloadActive
+ stats.cpuQueued
+ stats.cpuDecodeActive
);
}
export function libraryBackfillNeedsTopUp(stats: PipelineBacklogStats, workers: number): boolean {
return libraryBackfillPipelineBacklog(stats) < computeLibraryBackfillTargetDepth(workers);
}
export function libraryBackfillTopUpLimit(stats: PipelineBacklogStats, workers: number): number {
const target = computeLibraryBackfillTargetDepth(workers);
const deficit = target - libraryBackfillPipelineBacklog(stats);
if (deficit <= 0) return 0;
return Math.min(LIBRARY_ANALYSIS_BACKFILL_BATCH_SIZE, deficit);
}