UNPKG

newrelic

Version:
230 lines (199 loc) 5.67 kB
/* * Copyright 2020 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ 'use strict' /** * Explicit enumeration of the states a transaction can be in: * * PENDING upon instantiation (implicitly, no start time set) * RUNNING while timer is running (implicitly, start time is set but no stop * time is set). * STOPPED timer has been completed (implicitly, start time and stop time * are set, but the timer has not yet been harvested). * DEAD timer has been harvested and can only have its duration read. */ const PENDING = 1 const RUNNING = 2 const STOPPED = 3 function hrToMillis(hr) { // process.hrTime gives you [second, nanosecond] duration pairs return hr[0] * 1e3 + hr[1] / 1e6 } /** * A mildly tricky timer that tracks its own state and allows its duration * to be set manually. */ function Timer() { this.state = PENDING this.touched = false this.hrDuration = null this.hrstart = null this.start = null this.durationInMillis = null } /** * Start measuring time elapsed. * * Uses process.hrtime if available, Date.now() otherwise. */ Timer.prototype.begin = function begin() { if (this.state > PENDING) { return } this.start = Date.now() this.hrstart = process.hrtime() this.state = RUNNING } /** * End measurement. */ Timer.prototype.end = function end() { if (this.state > RUNNING) { return } if (this.state === PENDING) { this.begin() } this.hrDuration = process.hrtime(this.hrstart) this.touched = true this.state = STOPPED } /** * Update the duration of the timer without ending it.. */ Timer.prototype.touch = function touch() { this.touched = true if (this.state > RUNNING) { return } if (this.state === PENDING) { this.begin() } this.hrDuration = process.hrtime(this.hrstart) } /** * End the segment if it is still running, if touched use that time instead of * "now". Returns a boolean indicating whether the end time changed. */ Timer.prototype.softEnd = function softEnd() { if (this.state > RUNNING) { return false } if (this.state === PENDING) { this.begin() } this.state = STOPPED if (this.touched) { return false } this.hrDuration = process.hrtime(this.hrstart) this.touched = true return true } /** * @returns {boolean} Is this timer currently running? */ Timer.prototype.isRunning = function isRunning() { return this.state === RUNNING } /** * @returns {boolean} Is this timer still alive? */ Timer.prototype.isActive = function isActive() { return this.state < STOPPED } /** * @returns {boolean} Has the timer been touched or ended? */ Timer.prototype.hasEnd = function hasEnd() { return !!this.hrDuration } /* * Sets duration and stops the timer, since the passed-in duration will take precedence * over the measured duration. * @param {number} duration The duration the timer should report. */ Timer.prototype.overwriteDurationInMillis = overwriteDurationInMillis function overwriteDurationInMillis(duration) { this.touched = true this.durationInMillis = duration this.state = STOPPED } /** * When testing, it's convenient to be able to control time. Stops the timer * as a byproduct. * * @param {number} duration How long the timer ran. * @param {number} start When the timer started running (optional). */ Timer.prototype.setDurationInMillis = function setDurationInMillis(duration, start) { if (this.state > RUNNING) { return } if (this.state === PENDING && !start && start !== 0) { this.begin() } this.state = STOPPED this.durationInMillis = duration // this assignment is incorrect, process.hrtime doesn't time from epoch, which // is the assumption being made here. since hrstart isn't used // anywhere except to calculate duration, and we are setting duration // this is fine. this.hrstart = [Math.floor(start / 1e3), (start % 1e3) * 1e6] if (start != null) { this.start = start } } /** * Returns how long the timer has been running (if it's still running) or * how long it ran (if it's been ended or touched). */ Timer.prototype.getDurationInMillis = function getDurationInMillis() { if (this.state === PENDING) { return 0 } // only set by setDurationInMillis if (this.durationInMillis !== null && this.durationInMillis >= 0) { return this.durationInMillis } // prioritize .end() and .touch() if (this.hrDuration) { return hrToMillis(this.hrDuration) } return hrToMillis(process.hrtime(this.hrstart)) } /** * Get a single object containing the interval this timer was active. * * @returns {Array} 2-tuple of start time in milliseconds, end time in * milliseconds. */ Timer.prototype.toRange = function toRange() { return [this.start, this.start + this.getDurationInMillis()] } /** * Abstract away the nonsense related to having both an * hrtime start time and a regular one, and always return * milliseconds since start. * * @param {Timer} other The point relative to which this timer started. * @returns {number} The offset in (floating-point) milliseconds. */ Timer.prototype.startedRelativeTo = function startedRelativeTo(other) { if (this.hrstart && other.hrstart) { const s = this.hrstart[0] - other.hrstart[0] const ns = this.hrstart[1] - other.hrstart[1] return hrToMillis([s, ns]) } return this.start - other.start } /** * Returns true if this timer ends after the other. * * @param {Timer} other other timer */ Timer.prototype.endsAfter = function compare(other) { return this.getDurationInMillis() + this.start > other.getDurationInMillis() + other.start } module.exports = Timer