From 298d72720f116ea3cfd40e4ef01af7cfd4ec7361 Mon Sep 17 00:00:00 2001 From: Florian <52180080+florianjs@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:33:51 +0100 Subject: [PATCH] Release v0.5.2 - Bug fixes and improvements --- package.json | 2 +- server/api/admin/stats.get.ts | 18 ++++++--- server/api/admin/stats/history.get.ts | 54 +++++++++++++++++---------- server/plugins/stats-collector.ts | 13 ++++--- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index baafd89..09e7361 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trackarr", - "version": "0.5.1", + "version": "0.5.2", "type": "module", "private": true, "scripts": { diff --git a/server/api/admin/stats.get.ts b/server/api/admin/stats.get.ts index 3f7c0f8..91696c5 100644 --- a/server/api/admin/stats.get.ts +++ b/server/api/admin/stats.get.ts @@ -13,13 +13,15 @@ export default defineEventHandler(async (event) => { .from(schema.torrents); const totalTorrents = torrentsCountResult[0]?.count || 0; - // Get total peers and seeders from Redis using SCAN for safety + // Get total unique peers and seeders from Redis using SCAN for safety // Note: ioredis with keyPrefix - SCAN returns full keys with prefix, // but we need to strip the prefix before passing to other commands + // We use Sets to count unique peers by ip:port (a peer seeding multiple torrents counts as 1) const keyPrefix = process.env.REDIS_KEY_PREFIX || 'ot:'; - let totalPeers = 0; - let totalSeeders = 0; + const uniquePeers = new Set(); + const uniqueSeeders = new Set(); let cursor = '0'; + let scannedKeys = 0; try { do { const [nextCursor, keys] = await redis.scan( @@ -30,6 +32,7 @@ export default defineEventHandler(async (event) => { 100 ); cursor = nextCursor; + scannedKeys += keys.length; for (const fullKey of keys) { // Strip the prefix from the key returned by SCAN to avoid double-prefixing const key = fullKey.startsWith(keyPrefix) @@ -39,8 +42,10 @@ export default defineEventHandler(async (event) => { for (const json of Object.values(peersData)) { try { const peer = JSON.parse(json as string); - totalPeers++; - if (peer.isSeeder) totalSeeders++; + // Use ip:port as unique identifier for a peer + const peerKey = `${peer.ip}:${peer.port}`; + uniquePeers.add(peerKey); + if (peer.isSeeder) uniqueSeeders.add(peerKey); } catch (e) { // Ignore invalid JSON } @@ -51,6 +56,9 @@ export default defineEventHandler(async (event) => { console.error('[Stats] Failed to fetch peer count from Redis:', err); } + const totalPeers = uniquePeers.size; + const totalSeeders = uniqueSeeders.size; + // Try to get tracker, may fail if native modules aren't built let tracker = null; let protocols = { http: false, udp: false, ws: false }; diff --git a/server/api/admin/stats/history.get.ts b/server/api/admin/stats/history.get.ts index 627dcd1..840d524 100644 --- a/server/api/admin/stats/history.get.ts +++ b/server/api/admin/stats/history.get.ts @@ -48,9 +48,9 @@ export default defineEventHandler(async (event) => { ); const dbSize = Number(dbSizeResult[0]?.pg_database_size) || 0; - // Peers & Seeders (SCAN) - let peersCount = 0; - let seedersCount = 0; + // Peers & Seeders (SCAN) - count unique peers by ip:port + const uniquePeers = new Set(); + const uniqueSeeders = new Set(); let cursor = '0'; do { const [nextCursor, keys] = await redis.scan( @@ -69,33 +69,47 @@ export default defineEventHandler(async (event) => { for (const json of Object.values(peersData)) { try { const peer = JSON.parse(json as string); - peersCount++; - if (peer.isSeeder) seedersCount++; + const peerKey = `${peer.ip}:${peer.port}`; + uniquePeers.add(peerKey); + if (peer.isSeeder) uniqueSeeders.add(peerKey); } catch (e) {} } } } while (cursor !== '0'); + const peersCount = uniquePeers.size; + const seedersCount = uniqueSeeders.size; - // Append 'live' data point - // Check if we should append: only if history is empty OR last point is older than 5 mins + // Always append a 'live' data point to show current real-time stats + // This ensures the chart always reflects the current state + const livePoint = { + id: 'live', + usersCount, + torrentsCount, + peersCount, + seedersCount, + redisMemoryUsage, + dbSize, + createdAt: new Date(), + } as any; + + // If the last historical point is very recent (< 1 min), replace it with live data + // Otherwise append as a new point const lastPoint = history[history.length - 1]; const now = Date.now(); - const fiveMinutes = 5 * 60 * 1000; + const oneMinute = 60 * 1000; if ( - !lastPoint || - now - new Date(lastPoint.createdAt).getTime() > fiveMinutes + lastPoint && + now - new Date(lastPoint.createdAt).getTime() < oneMinute ) { - history.push({ - id: 'live', - usersCount, - torrentsCount, - peersCount, - seedersCount, - redisMemoryUsage, - dbSize, - createdAt: new Date(), - } as any); + // Replace the last point with live data + history[history.length - 1] = { + ...lastPoint, + ...livePoint, + id: lastPoint.id, + }; + } else { + history.push(livePoint); } } catch (err) { console.error('[Stats History] Failed to fetch live stats:', err); diff --git a/server/plugins/stats-collector.ts b/server/plugins/stats-collector.ts index 563b8da..08307dd 100644 --- a/server/plugins/stats-collector.ts +++ b/server/plugins/stats-collector.ts @@ -27,12 +27,12 @@ export default defineNitroPlugin((nitroApp) => { .from(schema.torrents); const torrentsCount = torrentsCountResult[0]?.count || 0; - // 3. Peers & Seeders Count (from Redis) + // 3. Peers & Seeders Count (from Redis) - count unique peers by ip:port // Note: ioredis with keyPrefix - SCAN returns full keys with prefix, // but we need to strip the prefix before passing to other commands const keyPrefix = process.env.REDIS_KEY_PREFIX || 'ot:'; - let peersCount = 0; - let seedersCount = 0; + const uniquePeers = new Set(); + const uniqueSeeders = new Set(); let cursor = '0'; do { const [nextCursor, keys] = await redis.scan( @@ -52,12 +52,15 @@ export default defineNitroPlugin((nitroApp) => { for (const json of Object.values(peersData)) { try { const peer = JSON.parse(json as string); - peersCount++; - if (peer.isSeeder) seedersCount++; + const peerKey = `${peer.ip}:${peer.port}`; + uniquePeers.add(peerKey); + if (peer.isSeeder) uniqueSeeders.add(peerKey); } catch (e) {} } } } while (cursor !== '0'); + const peersCount = uniquePeers.size; + const seedersCount = uniqueSeeders.size; // 4. Redis Memory Usage const info = await redis.info('memory');