mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
Track server clock offset for usage data
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user