UNPKG

hot-shots

Version:

Node.js client for StatsD, DogStatsD, and Telegraf

363 lines (326 loc) 13.7 kB
const helpers = require('./helpers'); /** * Separated out of statsd.js for clarity, these are the timing and other stats functions that are what are called the most * when using hot-shots */ function applyStatsFns (Client) { /** * Represents the timing stat * @param stat {String|Array} The stat(s) to send * @param time {Number|Date} The time in milliseconds to send or Date object of which the difference is calculated * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.timing = function (stat, time, sampleRate, tags, callback) { const t = time instanceof Date ? new Date() - time : time; this.sendAll(stat, t, 'ms', sampleRate, tags, callback); }; /** * Represents the timing stat by recording the duration a function takes to run (in milliseconds) * @param func {Function} The function to run * @param stat {String|Array} The stat(s) to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.timer = function (func, stat, sampleRate, tags, callback) { const _this = this; return (...args) => { const start = process.hrtime(); try { return func(...args); } finally { // get duration in milliseconds const durationComponents = process.hrtime(start); const seconds = durationComponents[0]; const nanoseconds = durationComponents[1]; const duration = (seconds * 1000) + (nanoseconds / 1E6); _this.timing( stat, duration, sampleRate, tags, callback ); } }; }; /** * Decorates an async function with timing recording behaviour. * * This version of `timer` will record the time take for the asynchronous action returned by `func` * not just the execution time of `func` itself. * * @param func {<T,A>(...A):Promise<T>} The function to run * @param stat {String|Array} The stat(s) to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.asyncTimer = function (func, stat, sampleRate, tags, callback) { const self = this; return (...args) => { const end = hrtimer(); const p = func(...args); const recordStat = () => { self.timing(stat, end(), sampleRate, tags, callback); }; p.then(recordStat, recordStat); return p; }; }; /** * Decorates an async function with timing recording behaviour, reported as a distribution. * * This version of `timer` will record the time take for the asynchronous action returned by `func` * not just the execution time of `func` itself. * * @param func {<T,A>(...A):Promise<T>} The function to run * @param stat {String|Array} The stat(s) to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.asyncDistTimer = function (func, stat, sampleRate, tags, callback) { const self = this; return (...args) => { const end = hrtimer(); const p = func(...args); const recordStat = () => self.distribution(stat, end(), sampleRate, tags, callback); p.then(recordStat, recordStat); return p; }; }; /** * High-resolution timer */ function hrtimer() { const start = process.hrtime(); return () => { const durationComponents = process.hrtime(start); const seconds = durationComponents[0]; const nanoseconds = durationComponents[1]; const duration = (seconds * 1000) + (nanoseconds / 1E6); return duration; }; } /** * Increments a stat by a specified amount * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.increment = function (stat, value, sampleRate, tags, callback) { // allow use of tags without explicit value or sampleRate if (arguments.length < 3) { if (typeof value !== 'number') { tags = value; value = undefined; } } // we explicitly check for undefined and null (and don't do a "! value" check) // so that 0 values are allowed and sent through as-is if (value === undefined || value === null) { value = 1; } this.sendAll(stat, value, 'c', sampleRate, tags, callback); }; /** * Decrements a stat by a specified amount * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.decrement = function (stat, value, sampleRate, tags, callback) { // allow use of tags without explicit value or sampleRate if (arguments.length < 3) { if (typeof value !== 'number') { tags = value; value = undefined; } } // we explicitly check for undefined and null (and don't do a "! value" check) // so that 0 values are allowed and sent through as-is if (value === undefined || value === null) { value = 1; } this.sendAll(stat, -value, 'c', sampleRate, tags, callback); }; /** * Represents the histogram stat * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.histogram = function (stat, value, sampleRate, tags, callback) { this.sendAll(stat, value, 'h', sampleRate, tags, callback); }; /** * Represents the distribution stat * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.distribution = function (stat, value, sampleRate, tags, callback) { this.sendAll(stat, value, 'd', sampleRate, tags, callback); }; /** * Gauges a stat by a specified amount * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.gauge = function (stat, value, sampleRate, tags, callback) { this.sendAll(stat, value, 'g', sampleRate, tags, callback); }; /** * Gauges a stat by a delta * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.gaugeDelta = function (stat, value, sampleRate, tags, callback) { const sign = value >= 0 ? '+' : '-'; this.sendAll(stat, `${sign}${Math.abs(value)}`, 'g', sampleRate, tags, callback); }; /** * Counts unique values by a specified amount * @param stat {String|Array} The stat(s) to send * @param value The value to send * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.unique = Client.prototype.set = function (stat, value, sampleRate, tags, callback) { this.sendAll(stat, value, 's', sampleRate, tags, callback); }; /** * Send a service check * @param name {String} The name of the service check * @param status {Number=} The status of the service check (0 to 3). * @param options * @option date_happened {Date} Assign a timestamp to the event. Default is now. * @option hostname {String} Assign a hostname to the check. * @option message {String} Assign a message to the check. * @param tags {Array=} The Array of tags to add to the check. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.check = function (name, status, options, tags, callback) { if (this.telegraf) { const err = new Error('Not supported by Telegraf / InfluxDB'); if (callback) { return callback(err); } else if (this.errorHandler) { return this.errorHandler(err); } throw err; } const check = ['_sc', this.prefix + name + this.suffix, status], metadata = options || {}; if (metadata.date_happened) { const timestamp = helpers.formatDate(metadata.date_happened); if (timestamp) { check.push(`d:${timestamp}`); } } if (metadata.hostname) { check.push(`h:${metadata.hostname}`); } let mergedTags = this.globalTags; if (tags && typeof(tags) === 'object') { mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf); } if (mergedTags.length > 0) { check.push(`#${mergedTags.join(',')}`); } // message has to be the last part of a service check if (metadata.message) { check.push(`m:${metadata.message}`); } // allow for tags to be omitted and callback to be used in its place if (typeof tags === 'function' && callback === undefined) { callback = tags; } const message = check.join('|'); // Service checks are unique in that message has to be the last element in // the stat if provided, so we can't append tags like other checks. This // directly calls the `_send` method to avoid appending tags, since we've // already added them. this._send(message, callback); }; /** * Send on an event * @param title {String} The title of the event * @param text {String} The description of the event. Optional- title is used if not given. * @param options * @option date_happened {Date} Assign a timestamp to the event. Default is now. * @option hostname {String} Assign a hostname to the event. * @option aggregation_key {String} Assign an aggregation key to the event, to group it with some others. * @option priority {String} Can be ‘normal’ or ‘low’. Default is 'normal'. * @option source_type_name {String} Assign a source type to the event. * @option alert_type {String} Can be ‘error’, ‘warning’, ‘info’ or ‘success’. Default is 'info'. * @param tags {Array=} The Array of tags to add to metrics. Optional. * @param callback {Function=} Callback when message is done being delivered. Optional. */ Client.prototype.event = function (title, text, options, tags, callback) { if (this.telegraf) { const err = new Error('Not supported by Telegraf / InfluxDB'); if (callback) { return callback(err); } else if (this.errorHandler) { return this.errorHandler(err); } throw err; } // Convert to strings let message; const msgTitle = String(title ? title : ''); let msgText = String(text ? text : msgTitle); // Escape new lines (unescaping is supported by DataDog) msgText = msgText.replace(/\n/g, '\\n'); // start out the message with the event-specific title and text info message = `_e{${msgTitle.length},${msgText.length}}:${msgTitle}|${msgText}`; // add in the event-specific options if (options) { if (options.date_happened) { const timestamp = helpers.formatDate(options.date_happened); if (timestamp) { message += `|d:${timestamp}`; } } if (options.hostname) { message += `|h:${options.hostname}`; } if (options.aggregation_key) { message += `|k:${options.aggregation_key}`; } if (options.priority) { message += `|p:${options.priority}`; } if (options.source_type_name) { message += `|s:${options.source_type_name}`; } if (options.alert_type) { message += `|t:${options.alert_type}`; } } // allow for tags to be omitted and callback to be used in its place if (typeof tags === 'function' && callback === undefined) { callback = tags; } this.send(message, tags, callback); }; } module.exports = applyStatsFns;