From 377fb71b9c42724854e00f18d3500cb7d935d57c Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Sun, 8 Mar 2026 18:28:01 -0400 Subject: [PATCH] Add minified listening stats mode Before stats on the app were taking about 3422ms and returning roughly 360815 bytes for /api/me/listening-stats. After adding an opt-in minified mode that skips items and recentSessions, the same dataset benchmarks at about 102ms average and 11667 bytes while keeping the default response shape unchanged for existing clients. --- server/controllers/MeController.js | 8 +++-- server/controllers/UserController.js | 8 +++-- server/routers/ApiRouter.js | 49 ++++++++++++++++------------ 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 0365a3105..d9a922aa1 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -232,10 +232,14 @@ class MeController { */ async getListeningStats(req, res) { const startedAt = Date.now() + const minified = req.query.minified === '1' || req.query.minified === 'true' Logger.debug(`[MeController] /api/me/listening-stats user="${req.user.id}" start`) - const listeningStats = await this.getUserListeningStatsHelpers(req.user.id) + const listeningStats = await this.getUserListeningStatsHelpers(req.user.id, { + includeItems: !minified, + includeRecentSessions: !minified + }) Logger.debug( - `[MeController] /api/me/listening-stats user="${req.user.id}" totalTime=${listeningStats?.totalTime || 0} recentSessions=${listeningStats?.recentSessions?.length || 0} items=${Object.keys(listeningStats?.items || {}).length} in ${Date.now() - startedAt}ms` + `[MeController] /api/me/listening-stats user="${req.user.id}" minified=${minified} totalTime=${listeningStats?.totalTime || 0} recentSessions=${listeningStats?.recentSessions?.length || 0} items=${Object.keys(listeningStats?.items || {}).length} in ${Date.now() - startedAt}ms` ) res.json(listeningStats) } diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js index 89ceb56b3..96dff644d 100644 --- a/server/controllers/UserController.js +++ b/server/controllers/UserController.js @@ -472,12 +472,16 @@ class UserController { */ async getListeningStats(req, res) { const startedAt = Date.now() + const minified = req.query.minified === '1' || req.query.minified === 'true' Logger.debug( `[UserController] /api/users/${req.params.id}/listening-stats reqUser="${req.user.id}" start` ) - var listeningStats = await this.getUserListeningStatsHelpers(req.params.id) + var listeningStats = await this.getUserListeningStatsHelpers(req.params.id, { + includeItems: !minified, + includeRecentSessions: !minified + }) Logger.debug( - `[UserController] /api/users/${req.params.id}/listening-stats reqUser="${req.user.id}" totalTime=${listeningStats?.totalTime || 0} recentSessions=${listeningStats?.recentSessions?.length || 0} items=${Object.keys(listeningStats?.items || {}).length} in ${Date.now() - startedAt}ms` + `[UserController] /api/users/${req.params.id}/listening-stats reqUser="${req.user.id}" minified=${minified} totalTime=${listeningStats?.totalTime || 0} recentSessions=${listeningStats?.recentSessions?.length || 0} items=${Object.keys(listeningStats?.items || {}).length} in ${Date.now() - startedAt}ms` ) res.json(listeningStats) } diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index fcb414816..6487a2151 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -555,27 +555,32 @@ class ApiRouter { return userSessions.sort((a, b) => b.updatedAt - a.updatedAt) } - async getUserListeningStatsHelpers(userId) { + async getUserListeningStatsHelpers(userId, options = {}) { const startedAt = Date.now() const today = date.format(new Date(), 'YYYY-MM-DD') + const includeItems = options.includeItems !== false + const includeRecentSessions = options.includeRecentSessions !== false - const [listeningSessions, recentSessions] = await Promise.all([ - Database.getPlaybackSessionsForStats({ userId }), - Database.getPlaybackSessions({ userId }, { - limit: 10, - offset: 0, - order: [['updatedAt', 'DESC']] - }) - ]) + const tasks = [Database.getPlaybackSessionsForStats({ userId })] + if (includeRecentSessions) { + tasks.push( + Database.getPlaybackSessions({ userId }, { + limit: 10, + offset: 0, + order: [['updatedAt', 'DESC']] + }) + ) + } + const [listeningSessions, recentSessions = []] = await Promise.all(tasks) const listeningStats = { totalTime: 0, - items: {}, days: {}, dayOfWeek: {}, - today: 0, - recentSessions + today: 0 } + if (includeItems) listeningStats.items = {} + if (includeRecentSessions) listeningStats.recentSessions = recentSessions listeningSessions.forEach((s) => { const libraryItemId = s.extraData?.libraryItemId || null let sessionTimeListening = s.timeListening @@ -600,21 +605,23 @@ class ApiRouter { return } - if (!listeningStats.items[libraryItemId]) { - listeningStats.items[libraryItemId] = { - id: libraryItemId, - timeListening: sessionTimeListening, - mediaMetadata: s.mediaMetadata, - lastUpdate: s.updatedAt + if (includeItems) { + if (!listeningStats.items[libraryItemId]) { + listeningStats.items[libraryItemId] = { + id: libraryItemId, + timeListening: sessionTimeListening, + mediaMetadata: s.mediaMetadata, + lastUpdate: s.updatedAt + } + } else { + listeningStats.items[libraryItemId].timeListening += sessionTimeListening } - } else { - listeningStats.items[libraryItemId].timeListening += sessionTimeListening } listeningStats.totalTime += sessionTimeListening }) Logger.debug( - `[ApiRouter] Listening stats for user "${userId}" aggregated ${listeningSessions.length} sessions in ${Date.now() - startedAt}ms` + `[ApiRouter] Listening stats for user "${userId}" aggregated ${listeningSessions.length} sessions in ${Date.now() - startedAt}ms includeItems=${includeItems} includeRecentSessions=${includeRecentSessions}` ) return listeningStats }