Files
Cli-Proxy-API-Management-Ce…/src/utils/recentRequests.ts
T
Supra4E8C 632be0bbe0 refactor: remove unused chart configuration and latency utilities
- Deleted chartConfig.ts and its exports from index.ts, as they are no longer needed.
- Removed latency.ts and its related functions for latency statistics and formatting.
- Cleaned up usageIndex.ts by removing functions related to indexing usage details, as they are not in use.
2026-05-02 03:41:14 +08:00

172 lines
4.8 KiB
TypeScript

export type StatusBlockState = 'success' | 'failure' | 'mixed' | 'idle';
export interface StatusBlockDetail {
success: number;
failure: number;
rate: number;
startTime: number;
endTime: number;
}
export interface StatusBarData {
blocks: StatusBlockState[];
blockDetails: StatusBlockDetail[];
successRate: number;
totalSuccess: number;
totalFailure: number;
}
export interface RecentRequestBucket {
time?: string;
success: number;
failed: number;
}
export type ApiKeyUsageResponse = Record<string, Record<string, RecentRequestBucket[]>>;
const RECENT_REQUEST_BLOCK_COUNT = 20;
const RECENT_REQUEST_BLOCK_DURATION_MS = 10 * 60 * 1000;
const toFiniteNumber = (value: unknown): number => {
const numberValue = typeof value === 'number' ? value : Number(value);
return Number.isFinite(numberValue) ? numberValue : 0;
};
export function buildRecentRequestCompositeKey(baseUrl: unknown, apiKey: unknown): string {
const normalizedBaseUrl = String(baseUrl ?? '').trim();
const normalizedApiKey = String(apiKey ?? '').trim();
return `${normalizedBaseUrl}|${normalizedApiKey}`;
}
export function normalizeRecentRequestAuthIndex(value: unknown): string | null {
if (typeof value === 'number' && Number.isFinite(value)) {
return value.toString();
}
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed ? trimmed : null;
}
return null;
}
export function normalizeRecentRequestBuckets(input: unknown): RecentRequestBucket[] {
if (!Array.isArray(input)) {
return [];
}
return input.slice(-RECENT_REQUEST_BLOCK_COUNT).map((item) => {
const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {};
const time = typeof record.time === 'string' ? record.time : undefined;
return {
...(time ? { time } : {}),
success: toFiniteNumber(record.success),
failed: toFiniteNumber(record.failed),
};
});
}
export function mergeRecentRequestBucketGroups(
groups: RecentRequestBucket[][]
): RecentRequestBucket[] {
const normalizedGroups = groups
.map((group) => normalizeRecentRequestBuckets(group))
.filter((group) => group.length > 0);
if (normalizedGroups.length === 0) {
return [];
}
const mergedLength = Math.min(
RECENT_REQUEST_BLOCK_COUNT,
Math.max(...normalizedGroups.map((group) => group.length))
);
const merged: RecentRequestBucket[] = Array.from({ length: mergedLength }, () => ({
success: 0,
failed: 0,
}));
normalizedGroups.forEach((group) => {
const tail = group.slice(-mergedLength);
const offset = mergedLength - tail.length;
tail.forEach((bucket, index) => {
const target = merged[offset + index];
target.success += bucket.success;
target.failed += bucket.failed;
if (!target.time && bucket.time) {
target.time = bucket.time;
}
});
});
return merged;
}
export function sumRecentRequests(
buckets: RecentRequestBucket[]
): { success: number; failure: number } {
return normalizeRecentRequestBuckets(buckets).reduce(
(total, bucket) => ({
success: total.success + bucket.success,
failure: total.failure + bucket.failed,
}),
{ success: 0, failure: 0 }
);
}
export function statusBarDataFromRecentRequests(buckets: RecentRequestBucket[]): StatusBarData {
const normalizedBuckets = normalizeRecentRequestBuckets(buckets);
const emptyBucketCount = Math.max(0, RECENT_REQUEST_BLOCK_COUNT - normalizedBuckets.length);
const blockStats = [
...Array.from({ length: emptyBucketCount }, () => ({ success: 0, failed: 0 })),
...normalizedBuckets.slice(-RECENT_REQUEST_BLOCK_COUNT),
];
const now = Date.now();
const windowStart = now - RECENT_REQUEST_BLOCK_COUNT * RECENT_REQUEST_BLOCK_DURATION_MS;
const blocks: StatusBlockState[] = [];
const blockDetails: StatusBarData['blockDetails'] = [];
let totalSuccess = 0;
let totalFailure = 0;
blockStats.forEach((bucket, index) => {
const success = bucket.success;
const failure = bucket.failed;
const total = success + failure;
totalSuccess += success;
totalFailure += failure;
if (total === 0) {
blocks.push('idle');
} else if (failure === 0) {
blocks.push('success');
} else if (success === 0) {
blocks.push('failure');
} else {
blocks.push('mixed');
}
const blockStartTime = windowStart + index * RECENT_REQUEST_BLOCK_DURATION_MS;
blockDetails.push({
success,
failure,
rate: total > 0 ? success / total : -1,
startTime: blockStartTime,
endTime: blockStartTime + RECENT_REQUEST_BLOCK_DURATION_MS,
});
});
const total = totalSuccess + totalFailure;
return {
blocks,
blockDetails,
successRate: total > 0 ? (totalSuccess / total) * 100 : 100,
totalSuccess,
totalFailure,
};
}