UNPKG

ipfs-bitswap

Version:

JavaScript implementation of the Bitswap data exchange protocol used by IPFS

155 lines (124 loc) 4.31 kB
import { EventEmitter } from 'events' import MovingAverage, { type IMovingAverage } from '@vascosantos/moving-average' export type Op = [string, number, number] export interface StatOptions { enabled: boolean computeThrottleTimeout: number computeThrottleMaxQueueSize: number movingAverageIntervals: number[] } export class Stat extends EventEmitter { private readonly _options: StatOptions private readonly _queue: Op[] private _stats: Record<string, bigint> private _frequencyLastTime: number private _frequencyAccumulators: Record<string, number> private _movingAverages: Record<string, Record<number, IMovingAverage>> private _enabled: boolean private _timeout?: ReturnType<typeof setTimeout> constructor (initialCounters: string[], options: StatOptions) { super() this._options = options this._queue = [] this._stats = {} this._frequencyLastTime = Date.now() this._frequencyAccumulators = {} this._movingAverages = {} this._update = this._update.bind(this) initialCounters.forEach((key) => { this._stats[key] = BigInt(0) this._movingAverages[key] = {} this._options.movingAverageIntervals.forEach((interval) => { const ma = this._movingAverages[key][interval] = MovingAverage(interval) ma.push(this._frequencyLastTime, 0) }) }) this._enabled = this._options.enabled } enable (): void { this._enabled = true } disable (): void { this._enabled = false } stop (): void { if (this._timeout != null) { clearTimeout(this._timeout) } } get snapshot (): Record<string, bigint> { return Object.assign({}, this._stats) } get movingAverages (): Record<string, Record<number, IMovingAverage>> { return Object.assign({}, this._movingAverages) } push (counter: string, inc: number): void { if (this._enabled) { this._queue.push([counter, inc, Date.now()]) this._resetComputeTimeout() } } _resetComputeTimeout (): void { if (this._timeout != null) { clearTimeout(this._timeout) } this._timeout = setTimeout(this._update, this._nextTimeout()) } _nextTimeout (): number { // calculate the need for an update, depending on the queue length const urgency = this._queue.length / this._options.computeThrottleMaxQueueSize return Math.max(this._options.computeThrottleTimeout * (1 - urgency), 0) } _update (): void { this._timeout = undefined if (this._queue.length > 0) { let last while (this._queue.length > 0) { const op = last = this._queue.shift() ;(op != null) && this._applyOp(op) } (last != null) && this._updateFrequency(last[2]) // contains timestamp of last op this.emit('update', this._stats) } } _updateFrequency (latestTime: number): void { const timeDiff = latestTime - this._frequencyLastTime if (timeDiff > 0) { Object.keys(this._stats).forEach((key) => { this._updateFrequencyFor(key, timeDiff, latestTime) }) } this._frequencyLastTime = latestTime } _updateFrequencyFor (key: string, timeDiffMS: number, latestTime: number): void { const count = this._frequencyAccumulators[key] ?? 0 this._frequencyAccumulators[key] = 0 const hz = (count / timeDiffMS) * 1000 let movingAverages = this._movingAverages[key] if (movingAverages == null) { movingAverages = this._movingAverages[key] = {} } this._options.movingAverageIntervals.forEach((movingAverageInterval) => { let movingAverage = movingAverages[movingAverageInterval] if (movingAverage == null) { movingAverage = movingAverages[movingAverageInterval] = MovingAverage(movingAverageInterval) } movingAverage.push(latestTime, hz) }) } _applyOp (op: Op): void { const key = op[0] const inc = op[1] if (typeof inc !== 'number') { throw new Error(`invalid increment number: ${inc}`) } if (!Object.prototype.hasOwnProperty.call(this._stats, key)) { this._stats[key] = BigInt(0) } this._stats[key] = BigInt(this._stats[key]) + BigInt(inc) if (this._frequencyAccumulators[key] == null) { this._frequencyAccumulators[key] = 0 } this._frequencyAccumulators[key] += inc } }