Add live server performance monitoring
This commit is contained in:
@@ -71,6 +71,7 @@ import { createYtdlpPool } from './ytdlp-pool.js';
|
||||
import { createWarmQueue } from './warm-queue.js';
|
||||
import { registerDeepSearchRoutes } from './deep-search.js';
|
||||
import { dirname, join as pathJoin } from 'node:path';
|
||||
import { createRequestMonitor, registerRequestMonitorRoutes } from './request-monitor.js';
|
||||
|
||||
// A media proxy must not die because one client's stream hit an edge case
|
||||
// (see /api/play cancel()): log and keep serving instead of crash-looping.
|
||||
@@ -346,6 +347,17 @@ function channelToUrl(c) {
|
||||
|
||||
const app = new Hono();
|
||||
app.use('*', logger());
|
||||
const requestMonitor = createRequestMonitor({
|
||||
snapshotFile: pathJoin(dirname(process.env.DB_PATH || './data/ytplayer.db'), 'ops-metrics.jsonl'),
|
||||
diskPaths: [{ name: 'app-data', path: dirname(process.env.DB_PATH || './data/ytplayer.db') }, { name: 'media', path: process.env.MEDIA_DIR || './data/media' }],
|
||||
adapters: {
|
||||
ytdlp: () => ytdlpPool ? ytdlpPool.stats() : null,
|
||||
media: () => media?.stats ? media.stats() : null,
|
||||
transcription: () => notesDb.transcriptionStats?.() || null,
|
||||
cache: () => ({ ...searchCacheCounters, memoryEntries: searchCache.size }),
|
||||
},
|
||||
});
|
||||
app.use('*', requestMonitor.middleware());
|
||||
registerCatalogRoutes(app, { resolveListener: recommendationListener });
|
||||
|
||||
// ============================================================================
|
||||
@@ -383,6 +395,7 @@ const SEARCH_CACHE_TTL_MS = 10 * 60_000; // fresh: answered from memory
|
||||
const SEARCH_CACHE_STALE_MS = 15 * 24 * 3600_000; // persistent copy: stale-while-revalidate for 15 days
|
||||
const searchCache = new Map(); // lowercased q -> { yt, fetchedAt } (YouTube cards only)
|
||||
const inflightSearches = new Map(); // lowercased q -> Promise<{ yt, youtubeError? }>
|
||||
const searchCacheCounters = { hits: 0, misses: 0 };
|
||||
|
||||
// The YouTube side of one search (up to 200 cards), shared by every caller asking
|
||||
// the same thing at the same time, and written to memory + the persistent cache.
|
||||
@@ -452,11 +465,13 @@ app.get('/api/search', async (c) => {
|
||||
if (row) { hit = { yt: row.results, fetchedAt: row.fetchedAt }; searchCache.set(key, hit); }
|
||||
}
|
||||
const age = hit ? Date.now() - hit.fetchedAt : Infinity;
|
||||
if (hit && age < SEARCH_CACHE_TTL_MS) return answer(hit.yt, { fetchedAt: hit.fetchedAt });
|
||||
if (hit && age < SEARCH_CACHE_TTL_MS) { searchCacheCounters.hits++; return answer(hit.yt, { fetchedAt: hit.fetchedAt }); }
|
||||
if (hit && age < SEARCH_CACHE_STALE_MS) {
|
||||
searchCacheCounters.hits++;
|
||||
fetchYoutube(q).catch(() => { /* keep serving the stale copy */ });
|
||||
return answer(hit.yt, { stale: true, fetchedAt: hit.fetchedAt });
|
||||
}
|
||||
searchCacheCounters.misses++;
|
||||
}
|
||||
try {
|
||||
const { yt, fetchedAt } = await fetchYoutube(q);
|
||||
@@ -2340,6 +2355,7 @@ const uploads = registerUploadRoutes(app, {
|
||||
parseLrc,
|
||||
sanitizeLyrics,
|
||||
});
|
||||
registerRequestMonitorRoutes(app, { adminAuth: notes.requireAdminOrToken, monitor: requestMonitor });
|
||||
const isUpload = (id) => uploads.isUploadId(id);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user