mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
- Add queue health check for fast-fail when overloaded (P90 > threshold) - Implement socket identity verification with UUID token - Add wait time statistics (P50/P90/P99) and queue stats tracking - Add admin endpoints for queue stats and cleanup - Add CLEAR_CONCURRENCY_QUEUES_ON_STARTUP config option - Update documentation with troubleshooting and proxy config guide
106 lines
3.2 KiB
JavaScript
106 lines
3.2 KiB
JavaScript
/**
|
||
* 统计计算工具函数
|
||
* 提供百分位数计算、等待时间统计等通用统计功能
|
||
*/
|
||
|
||
/**
|
||
* 计算百分位数(使用 nearest-rank 方法)
|
||
* @param {number[]} sortedArray - 已排序的数组(升序)
|
||
* @param {number} percentile - 百分位数 (0-100)
|
||
* @returns {number} 百分位值
|
||
*
|
||
* 边界情况说明:
|
||
* - percentile=0: 返回最小值 (index=0)
|
||
* - percentile=100: 返回最大值 (index=len-1)
|
||
* - percentile=50 且 len=2: 返回第一个元素(nearest-rank 向下取)
|
||
*
|
||
* 算法说明(nearest-rank 方法):
|
||
* - index = ceil(percentile / 100 * len) - 1
|
||
* - 示例:len=100, P50 → ceil(50) - 1 = 49(第50个元素,0-indexed)
|
||
* - 示例:len=100, P99 → ceil(99) - 1 = 98(第99个元素)
|
||
*/
|
||
function getPercentile(sortedArray, percentile) {
|
||
const len = sortedArray.length
|
||
if (len === 0) {
|
||
return 0
|
||
}
|
||
if (len === 1) {
|
||
return sortedArray[0]
|
||
}
|
||
|
||
// 边界处理:percentile <= 0 返回最小值
|
||
if (percentile <= 0) {
|
||
return sortedArray[0]
|
||
}
|
||
// 边界处理:percentile >= 100 返回最大值
|
||
if (percentile >= 100) {
|
||
return sortedArray[len - 1]
|
||
}
|
||
|
||
const index = Math.ceil((percentile / 100) * len) - 1
|
||
return sortedArray[index]
|
||
}
|
||
|
||
/**
|
||
* 计算等待时间分布统计
|
||
* @param {number[]} waitTimes - 等待时间数组(无需预先排序)
|
||
* @returns {Object|null} 统计对象,空数组返回 null
|
||
*
|
||
* 返回对象包含:
|
||
* - sampleCount: 样本数量(始终包含,便于调用方判断可靠性)
|
||
* - count: 样本数量(向后兼容)
|
||
* - min: 最小值
|
||
* - max: 最大值
|
||
* - avg: 平均值(四舍五入)
|
||
* - p50: 50百分位数(中位数)
|
||
* - p90: 90百分位数
|
||
* - p99: 99百分位数
|
||
* - sampleSizeWarning: 样本量不足时的警告信息(样本 < 10)
|
||
* - p90Unreliable: P90 统计不可靠标记(样本 < 10)
|
||
* - p99Unreliable: P99 统计不可靠标记(样本 < 100)
|
||
*
|
||
* 可靠性标记说明(详见 design.md Decision 6):
|
||
* - 样本 < 10: P90 和 P99 都不可靠
|
||
* - 样本 < 100: P99 不可靠(P90 需要 10 个样本,P99 需要 100 个样本)
|
||
* - 即使标记为不可靠,仍返回计算值供参考
|
||
*/
|
||
function calculateWaitTimeStats(waitTimes) {
|
||
if (!waitTimes || waitTimes.length === 0) {
|
||
return null
|
||
}
|
||
|
||
const sorted = [...waitTimes].sort((a, b) => a - b)
|
||
const sum = sorted.reduce((a, b) => a + b, 0)
|
||
const len = sorted.length
|
||
|
||
const stats = {
|
||
sampleCount: len, // 新增:始终包含样本数
|
||
count: len, // 向后兼容
|
||
min: sorted[0],
|
||
max: sorted[len - 1],
|
||
avg: Math.round(sum / len),
|
||
p50: getPercentile(sorted, 50),
|
||
p90: getPercentile(sorted, 90),
|
||
p99: getPercentile(sorted, 99)
|
||
}
|
||
|
||
// 渐进式可靠性标记(详见 design.md Decision 6)
|
||
// 样本 < 10: P90 不可靠(P90 至少需要 ceil(100/10) = 10 个样本)
|
||
if (len < 10) {
|
||
stats.sampleSizeWarning = 'Results may be inaccurate due to small sample size'
|
||
stats.p90Unreliable = true
|
||
}
|
||
|
||
// 样本 < 100: P99 不可靠(P99 至少需要 ceil(100/1) = 100 个样本)
|
||
if (len < 100) {
|
||
stats.p99Unreliable = true
|
||
}
|
||
|
||
return stats
|
||
}
|
||
|
||
module.exports = {
|
||
getPercentile,
|
||
calculateWaitTimeStats
|
||
}
|