UNPKG

@apollo/server

Version:
88 lines (74 loc) 2.85 kB
export interface DurationHistogramOptions { initSize?: number; buckets?: number[]; } export class DurationHistogram { // Note that it's legal for the values in "buckets" to be non-integers; they // will be floored by toArray (which is called by the protobuf encoder). // (We take advantage of this for field latencies specifically, because // the ability to return a non-1 weight from fieldLevelInstrumentation // means we want to build up our histograms as floating-point rather than // rounding after every operation.) private readonly buckets: number[]; static readonly BUCKET_COUNT = 384; static readonly EXPONENT_LOG = Math.log(1.1); toArray(): number[] { let bufferedZeroes = 0; const outputArray: number[] = []; for (const value of this.buckets) { if (value === 0) { bufferedZeroes++; } else { if (bufferedZeroes === 1) { outputArray.push(0); } else if (bufferedZeroes !== 0) { outputArray.push(-bufferedZeroes); } outputArray.push(Math.floor(value)); bufferedZeroes = 0; } } return outputArray; } static durationToBucket(durationNs: number): number { const log = Math.log(durationNs / 1000.0); const unboundedBucket = Math.ceil(log / DurationHistogram.EXPONENT_LOG); // Compare <= 0 to catch -0 and -infinity return unboundedBucket <= 0 || Number.isNaN(unboundedBucket) ? 0 : unboundedBucket >= DurationHistogram.BUCKET_COUNT ? DurationHistogram.BUCKET_COUNT - 1 : unboundedBucket; } incrementDuration(durationNs: number, value = 1): DurationHistogram { this.incrementBucket(DurationHistogram.durationToBucket(durationNs), value); return this; } incrementBucket(bucket: number, value = 1) { if (bucket >= DurationHistogram.BUCKET_COUNT) { // Since we don't have fixed size arrays I'd rather throw the error manually throw Error('Bucket is out of bounds of the buckets array'); } // Extend the array if we haven't gotten it long enough to handle the new bucket if (bucket >= this.buckets.length) { const oldLength = this.buckets.length; this.buckets.length = bucket + 1; this.buckets.fill(0, oldLength); } this.buckets[bucket] += value; } combine(otherHistogram: DurationHistogram) { for (let i = 0; i < otherHistogram.buckets.length; i++) { this.incrementBucket(i, otherHistogram.buckets[i]); } } constructor(options?: DurationHistogramOptions) { const initSize = options?.initSize || 74; const buckets = options?.buckets; const arrayInitSize = Math.max(buckets?.length || 0, initSize); this.buckets = Array<number>(arrayInitSize).fill(0); if (buckets) { buckets.forEach((val, index) => (this.buckets[index] = val)); } } }