From 10d0292a4c45e8266eb62a0c454328b4e6f5f151 Mon Sep 17 00:00:00 2001 From: liuw1535 <130421582+liuw1535@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:54:24 +0800 Subject: [PATCH] Fix monitor averages and retry failure accounting --- public/js/monitor.js | 23 ++++++++++++++++--- src/server/handlers/common/retry.js | 8 +++---- src/utils/usageStats.js | 34 ++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/public/js/monitor.js b/public/js/monitor.js index 152bced6..f02d5b43 100644 --- a/public/js/monitor.js +++ b/public/js/monitor.js @@ -30,6 +30,20 @@ function formatTokenNumber(value) { return formatNumber(number); } + +function formatAverageMinuteBasis(minutes) { + const number = Number(minutes) || 0; + if (number <= 0) return '暂无请求数据'; + if (number >= 24 * 60) return `按 ${formatNumber(number / (24 * 60), 1)} 天有请求时长统计`; + if (number >= 60) return `按 ${formatNumber(number / 60, 1)} 小时有请求时长统计`; + return `按 ${formatNumber(Math.max(number, 1), 0)} 分钟有请求时长统计`; +} + +function formatAverageDayBasis(days) { + const number = Number(days) || 0; + return number > 0 ? `按 ${formatNumber(number, 0)} 个有请求日期统计` : '暂无请求数据'; +} + function initMonitorPage() { const savedDays = Number(localStorage.getItem('monitorRangeDays')) || 7; monitorState.rangeDays = [7, 14, 30].includes(savedDays) ? savedDays : 7; @@ -106,6 +120,9 @@ function renderMonitorCards(data) { const totals = data.totals || {}; const averages = data.averages || {}; + const averageBasis = data.averageBasis || {}; + const minuteBasisText = formatAverageMinuteBasis(averageBasis.activeMinutes); + const dayBasisText = formatAverageDayBasis(averageBasis.activeDays); cards.innerHTML = `
请求数
@@ -126,17 +143,17 @@ function renderMonitorCards(data) {
平均 TPM
${formatNumber(averages.tpm, 2)}
-
每分钟 Token 数
+
${minuteBasisText}
平均 RPM
${formatNumber(averages.rpm, 2)}
-
每分钟请求数
+
${minuteBasisText}
日均 RDP
${formatNumber(averages.rdp, 2)}
-
每日请求数
+
${dayBasisText}
`; } diff --git a/src/server/handlers/common/retry.js b/src/server/handlers/common/retry.js index 367d5fb3..6043aa73 100644 --- a/src/server/handlers/common/retry.js +++ b/src/server/handlers/common/retry.js @@ -287,15 +287,15 @@ export async function with429Retry(fn, maxRetries, options = {}, legacyOnAttempt } return await fn(attempt, shouldUseCredits); } catch (error) { - if (typeof retryOptions.onAttemptFailure === 'function') { - retryOptions.onAttemptFailure(error, attempt); - } - const status = getStatus(error); if (status !== 429 && status !== 503) { throw error; } + if (typeof retryOptions.onAttemptFailure === 'function') { + retryOptions.onAttemptFailure(error, attempt); + } + const hint = getRetryHint(error); const errorType = status === 503 ? '503' : '429'; const modelId = retryOptions.modelId || null; diff --git a/src/utils/usageStats.js b/src/utils/usageStats.js index 528ce79f..bbe81bc3 100644 --- a/src/utils/usageStats.js +++ b/src/utils/usageStats.js @@ -61,6 +61,8 @@ function normalizeUsage(usage = {}) { function createBucket(day) { return { day, + firstSeen: null, + lastSeen: null, ...emptyTotals(), models: {} }; @@ -79,7 +81,12 @@ class UsageStatsStore { const raw = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); for (const bucket of raw.buckets || []) { if (bucket?.day) { - this.buckets.set(Number(bucket.day), bucket); + const day = Number(bucket.day); + this.buckets.set(day, { + ...createBucket(day), + ...bucket, + models: bucket.models || {} + }); } } this.prune(); @@ -112,6 +119,8 @@ class UsageStatsStore { record({ timestamp = Date.now(), statusCode = 0, model = 'unknown', usage = {}, successCount, failedCount } = {}) { const day = startOfUtcDay(timestamp); const bucket = this.buckets.get(day) || createBucket(day); + bucket.firstSeen = bucket.firstSeen === null ? timestamp : Math.min(bucket.firstSeen, timestamp); + bucket.lastSeen = bucket.lastSeen === null ? timestamp : Math.max(bucket.lastSeen, timestamp); const normalizedUsage = normalizeUsage(usage); const modelName = typeof model === 'string' && model.trim() ? model.trim() : 'unknown'; const failed = statusCode >= 400 || statusCode === 0; @@ -185,7 +194,18 @@ class UsageStatsStore { } } - const minutes = safeDays * 24 * 60; + const activeDailyBuckets = daily + .map(item => this.buckets.get(Date.parse(`${item.date}T00:00:00.000Z`))) + .filter(bucket => bucket && bucket.requests > 0); + const activeDays = activeDailyBuckets.length; + const activeMinutes = activeDailyBuckets.reduce((sum, bucket) => { + const dayStart = bucket.day; + const dayEnd = Math.min(dayStart + DAY_MS, now); + const observedEnd = bucket.lastSeen === null ? dayEnd : Math.min(bucket.lastSeen, dayEnd); + const observedStart = bucket.firstSeen === null ? dayStart : Math.min(Math.max(bucket.firstSeen, dayStart), dayEnd); + const elapsedMs = Math.max(60 * 1000, observedEnd - observedStart); + return sum + elapsedMs / (60 * 1000); + }, 0); const models = Array.from(modelMap.entries()) .map(([model, stats]) => ({ model, ...stats })) .sort((a, b) => b.totalTokens - a.totalTokens || b.requests - a.requests); @@ -194,9 +214,13 @@ class UsageStatsStore { rangeDays: safeDays, totals, averages: { - tpm: totals.totalTokens / minutes, - rpm: totals.requests / minutes, - rdp: totals.requests / safeDays + tpm: activeMinutes > 0 ? totals.totalTokens / activeMinutes : 0, + rpm: activeMinutes > 0 ? totals.requests / activeMinutes : 0, + rdp: activeDays > 0 ? totals.requests / activeDays : 0 + }, + averageBasis: { + activeDays, + activeMinutes }, models, daily,