Track server clock offset for usage data

This commit is contained in:
Novick Yuan
2026-05-28 14:30:15 +08:00
parent 8aca1f8b93
commit 6de684df59
7 changed files with 178 additions and 12 deletions
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest'
import { calculateServerClockOffsetMs, useServerClock } from '../useServerClock'
describe('useServerClock', () => {
it('calculates offset from the request midpoint', () => {
const offset = calculateServerClockOffsetMs({
server_now_unix_ms: 10_500,
client_send_unix_ms: 20_000,
client_receive_unix_ms: 20_200,
})
expect(offset).toBe(-9_600)
})
it('ignores missing or invalid timing samples', () => {
expect(calculateServerClockOffsetMs(undefined)).toBeNull()
expect(calculateServerClockOffsetMs({
server_now_unix_ms: Number.NaN,
client_send_unix_ms: 20_000,
client_receive_unix_ms: 20_200,
})).toBeNull()
expect(calculateServerClockOffsetMs({
server_now_unix_ms: 10_500,
client_send_unix_ms: 20_200,
client_receive_unix_ms: 20_000,
})).toBeNull()
})
it('keeps the previous offset when a response has no server timing', () => {
const clock = useServerClock()
clock.updateServerClockOffset({
server_now_unix_ms: 10_500,
client_send_unix_ms: 20_000,
client_receive_unix_ms: 20_200,
})
clock.updateServerClockOffset(undefined)
expect(clock.hasServerClockOffset.value).toBe(true)
expect(clock.serverClockOffsetMs.value).toBe(-9_600)
})
})
@@ -0,0 +1,36 @@
import { ref } from 'vue'
import type { ServerTimingMetadata } from '@/api/serverTiming'
export function calculateServerClockOffsetMs(timing: ServerTimingMetadata | null | undefined): number | null {
if (!timing) return null
const { server_now_unix_ms: serverNowUnixMs, client_send_unix_ms: clientSendUnixMs, client_receive_unix_ms: clientReceiveUnixMs } = timing
if (!Number.isFinite(serverNowUnixMs) || !Number.isFinite(clientSendUnixMs) || !Number.isFinite(clientReceiveUnixMs)) {
return null
}
if (clientReceiveUnixMs < clientSendUnixMs) {
return null
}
const clientMidpointUnixMs = clientSendUnixMs + ((clientReceiveUnixMs - clientSendUnixMs) / 2)
return serverNowUnixMs - clientMidpointUnixMs
}
export function useServerClock() {
const serverClockOffsetMs = ref(0)
const hasServerClockOffset = ref(false)
function updateServerClockOffset(timing: ServerTimingMetadata | null | undefined): void {
const offsetMs = calculateServerClockOffsetMs(timing)
if (offsetMs == null) return
serverClockOffsetMs.value = offsetMs
hasServerClockOffset.value = true
}
return {
serverClockOffsetMs,
hasServerClockOffset,
updateServerClockOffset,
}
}
@@ -14,6 +14,7 @@ import { createDefaultStats } from '../types'
import { log } from '@/utils/logger'
import { getErrorStatus } from '@/types/api-error'
import { isUsageProviderVisible, normalizeUsageProviderStats } from '../utils/providerStats'
import { useServerClock } from './useServerClock'
export interface UseUsageDataOptions {
isAdminPage: Ref<boolean>
@@ -65,6 +66,11 @@ export function useUsageData(options: UseUsageDataOptions) {
// 可用的筛选选项(从统计数据获取,而不是从记录中)
const availableModels = ref<string[]>([])
const availableProviders = ref<string[]>([])
const {
serverClockOffsetMs,
hasServerClockOffset,
updateServerClockOffset,
} = useServerClock()
// 增强的模型统计(包含效率分析)
const enhancedModelStats = computed<EnhancedModelStatsItem[]>(() => {
@@ -213,6 +219,7 @@ export function useUsageData(options: UseUsageDataOptions) {
if (requestId !== loadStatsRequestId) {
return false
}
updateServerClockOffset(userData.server_timing)
stats.value = {
total_requests: userData.total_requests || 0,
@@ -366,6 +373,7 @@ export function useUsageData(options: UseUsageDataOptions) {
if (requestId !== loadRecordsRequestId) {
return
}
updateServerClockOffset(response.server_timing)
const nextRecords = (response.records || []) as UsageRecord[]
currentRecords.value = mergeRecordStatus(currentRecords.value, nextRecords)
totalRecords.value = response.total || 0
@@ -375,6 +383,7 @@ export function useUsageData(options: UseUsageDataOptions) {
if (requestId !== loadRecordsRequestId) {
return
}
updateServerClockOffset(userData.server_timing)
const nextRecords = (userData.records || []) as UsageRecord[]
currentRecords.value = mergeRecordStatus(currentRecords.value, nextRecords)
totalRecords.value = userData.pagination?.total || currentRecords.value.length
@@ -557,6 +566,8 @@ export function useUsageData(options: UseUsageDataOptions) {
apiFormatStats,
currentRecords,
totalRecords,
serverClockOffsetMs,
hasServerClockOffset,
// 筛选选项
availableModels,
@@ -568,6 +579,7 @@ export function useUsageData(options: UseUsageDataOptions) {
// 方法
loadStats,
loadRecords,
refreshData
refreshData,
updateServerClockOffset
}
}