Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions public/js/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = `
<div class="monitor-card requests">
<div class="monitor-card-title">请求数</div>
Expand All @@ -126,17 +143,17 @@ function renderMonitorCards(data) {
<div class="monitor-card tpm">
<div class="monitor-card-title">平均 TPM</div>
<div class="monitor-card-value">${formatNumber(averages.tpm, 2)}</div>
<div class="monitor-card-sub"><span>每分钟 Token 数</span></div>
<div class="monitor-card-sub"><span>${minuteBasisText}</span></div>
</div>
<div class="monitor-card rpm">
<div class="monitor-card-title">平均 RPM</div>
<div class="monitor-card-value">${formatNumber(averages.rpm, 2)}</div>
<div class="monitor-card-sub"><span>每分钟请求数</span></div>
<div class="monitor-card-sub"><span>${minuteBasisText}</span></div>
</div>
<div class="monitor-card rdp">
<div class="monitor-card-title">日均 RDP</div>
<div class="monitor-card-value">${formatNumber(averages.rdp, 2)}</div>
<div class="monitor-card-sub"><span>每日请求数</span></div>
<div class="monitor-card-sub"><span>${dayBasisText}</span></div>
</div>
`;
}
Expand Down
8 changes: 4 additions & 4 deletions src/server/handlers/common/retry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
34 changes: 29 additions & 5 deletions src/utils/usageStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ function normalizeUsage(usage = {}) {
function createBucket(day) {
return {
day,
firstSeen: null,
lastSeen: null,
...emptyTotals(),
models: {}
};
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down
Loading