keymetrics/pm2-io-apm

View on GitHub
src/utils/EDS.ts

Summary

Maintainability
A
0 mins
Test Coverage
import BinaryHeap from './BinaryHeap'
import units from './units'

export default class ExponentiallyDecayingSample {
  private RESCALE_INTERVAL = 1 * units.HOURS
  private ALPHA = 0.015
  private SIZE = 1028

  private _elements
  private _rescaleInterval
  private _alpha
  private _size
  private _landmark
  private _nextRescale
  private _mean

  constructor (options?) {
    options = options || {}

    this._elements = new BinaryHeap({
      score: function (element) {
        return -element.priority
      }
    })

    this._rescaleInterval = options.rescaleInterval || this.RESCALE_INTERVAL
    this._alpha = options.alpha || this.ALPHA
    this._size = options.size || this.SIZE
    this._random = options.random || this._random
    this._landmark = null
    this._nextRescale = null
    this._mean = null
  }

  update (value, timestamp?) {
    const now = Date.now()
    if (!this._landmark) {
      this._landmark = now
      this._nextRescale = this._landmark + this._rescaleInterval
    }

    timestamp = timestamp || now

    const newSize = this._elements.size() + 1

    const element = {
      priority: this._priority(timestamp - this._landmark),
      value: value
    }

    if (newSize <= this._size) {
      this._elements.add(element)
    } else if (element.priority > this._elements.first().priority) {
      this._elements.removeFirst()
      this._elements.add(element)
    }

    if (now >= this._nextRescale) this._rescale(now)
  }

  toSortedArray () {
    return this._elements
      .toSortedArray()
      .map(function (element) {
        return element.value
      })
  }

  toArray () {
    return this._elements
      .toArray()
      .map(function (element) {
        return element.value
      })
  }

  _weight (age) {
    // We divide by 1000 to not run into huge numbers before reaching a
    // rescale event.
    return Math.exp(this._alpha * (age / 1000))
  }

  _priority (age) {
    return this._weight(age) / this._random()
  }

  _random () {
    return Math.random()
  }

  _rescale (now) {
    now = now || Date.now()

    const self = this
    const oldLandmark = this._landmark
    this._landmark = now || Date.now()
    this._nextRescale = now + this._rescaleInterval

    const factor = self._priority(-(self._landmark - oldLandmark))

    this._elements
      .toArray()
      .forEach(function (element) {
        element.priority *= factor
      })
  }

  avg (now) {
    let sum = 0
    this._elements
      .toArray()
      .forEach(function (element) {
        sum += element.value
      })
    return (sum / this._elements.size())
  }
}