功能说明
该示例把一组观测值归入预定义边界的累积桶,并据此估算分位数。观测值记入第一个上界不小于它的桶,随后把各桶累加成累积计数。求分位时先用 q 乘以总数得到目标秩,找到首个累积计数达到该秩的桶,再在桶的上下界之间线性插值。
关键代码
边界先排序,避免调用方传入乱序导致归类错误。归类循环找首个使样本不大于边界的下标,正无穷桶即最后一个下标。累积化把每个桶变成不大于其边界的样本数,与 Prometheus 的桶语义一致。分位函数在目标所在桶内按“桶内已越过目标的比例”插值,桶计数为零时直接返回上界,避免除零。
使用注意
插值给出的分位是桶内均匀分布的近似,边界过疏会让估算粗糙,边界应按延迟分布合理设置。最末桶若为开区间,超出最大边界的样本被归入上界为“前界两倍”的兜底桶,真实系统应显式设置足够大的末界。分位数无法跨实例平均——平均值可聚合,分位不可——应发布为桶而非已算好的分位值。
代码示例
export interface Histogram { readonly bounds: readonly number[]; readonly counts: readonly number[]; readonly total: number; }
export function observe(bounds: readonly number[], samples: readonly number[]): Histogram {
const sorted = [...bounds].sort((a, b) => a - b);
const cumulative = new Array<number>(sorted.length + 1).fill(0);
for (const sample of samples) {
let i = 0;
while (i < sorted.length && sample > (sorted[i] as number)) i += 1;
cumulative[i] += 1;
}
for (let i = 1; i < cumulative.length; i++) cumulative[i] += cumulative[i - 1] as number;
return { bounds: sorted, counts: cumulative, total: samples.length };
}
export function quantile(hist: Histogram, q: number): number {
if (q < 0 || q > 1) throw new RangeError('q must be in [0, 1]');
const target = q * hist.total;
let prev = 0;
for (let i = 0; i < hist.counts.length; i++) {
const count = hist.counts[i] as number;
if (count >= target) {
const upper = i < hist.bounds.length ? (hist.bounds[i] as number) : prev * 2;
const lower = i === 0 ? 0 : (hist.bounds[i - 1] as number);
const bucketCount = count - prev;
if (bucketCount === 0) return upper;
return lower + ((target - prev) / bucketCount) * (upper - lower);
}
prev = count;
}
return hist.bounds[hist.bounds.length - 1] ?? 0;
}