UNPKG

12.1 kBJavaScriptView Raw
1const helpers = require('./helpers');
2
3/**
4 * Separated out of statsd.js for clarity, these are the timing and other stats functions that are what are called the most
5 * when using hot-shots
6 */
7function applyStatsFns (Client) {
8
9 /**
10 * Represents the timing stat
11 * @param stat {String|Array} The stat(s) to send
12 * @param time {Number|Date} The time in milliseconds to send or Date object of which the difference is calculated
13 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
14 * @param tags {Array=} The Array of tags to add to metrics. Optional.
15 * @param callback {Function=} Callback when message is done being delivered. Optional.
16 */
17 Client.prototype.timing = function (stat, time, sampleRate, tags, callback) {
18 const t = time instanceof Date ? new Date() - time : time;
19 this.sendAll(stat, t, 'ms', sampleRate, tags, callback);
20 };
21
22 /**
23 * Represents the timing stat by recording the duration a function takes to run (in milliseconds)
24 * @param func {Function} The function to run
25 * @param stat {String|Array} The stat(s) to send
26 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
27 * @param tags {Array=} The Array of tags to add to metrics. Optional.
28 * @param callback {Function=} Callback when message is done being delivered. Optional.
29 */
30 Client.prototype.timer = function (func, stat, sampleRate, tags, callback) {
31 const _this = this;
32
33 return (...args) => {
34 const start = process.hrtime();
35 try {
36 return func(...args);
37 } finally {
38 // get duration in milliseconds
39 const durationComponents = process.hrtime(start);
40 const seconds = durationComponents[0];
41 const nanoseconds = durationComponents[1];
42 const duration = (seconds * 1000) + (nanoseconds / 1E6);
43
44 _this.timing(
45 stat,
46 duration,
47 sampleRate,
48 tags,
49 callback
50 );
51 }
52 };
53 };
54
55 /**
56 * Decorates an async function with timing recording behaviour.
57 *
58 * This version of `timer` will record the time take for the asyncronus action returned by `func`
59 * not just the execution time of `func` itself.
60 *
61 * @param func {<T,A>(...A):Promise<T>} The function to run
62 * @param stat {String|Array} The stat(s) to send
63 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
64 * @param tags {Array=} The Array of tags to add to metrics. Optional.
65 * @param callback {Function=} Callback when message is done being delivered. Optional.
66 */
67 Client.prototype.asyncTimer = function (func, stat, sampleRate, tags, callback) {
68 const self = this;
69 return (...args) => {
70 const end = hrtimer();
71 const p = func(...args);
72 const recordStat = () => { self.timing(stat, end(), sampleRate, tags, callback); };
73 p.then(recordStat, recordStat);
74 return p;
75 };
76 };
77
78 /**
79 * High-resolution timer
80 */
81 function hrtimer() {
82 const start = process.hrtime();
83
84 return () => {
85 const durationComponents = process.hrtime(start);
86 const seconds = durationComponents[0];
87 const nanoseconds = durationComponents[1];
88 const duration = (seconds * 1000) + (nanoseconds / 1E6);
89 return duration;
90 };
91 }
92
93
94 /**
95 * Increments a stat by a specified amount
96 * @param stat {String|Array} The stat(s) to send
97 * @param value The value to send
98 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
99 * @param tags {Array=} The Array of tags to add to metrics. Optional.
100 * @param callback {Function=} Callback when message is done being delivered. Optional.
101 */
102 Client.prototype.increment = function (stat, value, sampleRate, tags, callback) {
103 // allow use of tags without explicit value or sampleRate
104 if (arguments.length < 3) {
105 if (typeof value !== 'number') {
106 tags = value;
107 value = undefined;
108 }
109 }
110 // we explicitly check for undefined and null (and don't do a "! value" check)
111 // so that 0 values are allowed and sent through as-is
112 if (value === undefined || value === null) {
113 value = 1;
114 }
115 this.sendAll(stat, value, 'c', sampleRate, tags, callback);
116 };
117
118 /**
119 * Decrements a stat by a specified amount
120 * @param stat {String|Array} The stat(s) to send
121 * @param value The value to send
122 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
123 * @param tags {Array=} The Array of tags to add to metrics. Optional.
124 * @param callback {Function=} Callback when message is done being delivered. Optional.
125 */
126 Client.prototype.decrement = function (stat, value, sampleRate, tags, callback) {
127 // allow use of tags without explicit value or sampleRate
128 if (arguments.length < 3) {
129 if (typeof value !== 'number') {
130 tags = value;
131 value = undefined;
132 }
133 }
134 // we explicitly check for undefined and null (and don't do a "! value" check)
135 // so that 0 values are allowed and sent through as-is
136 if (value === undefined || value === null) {
137 value = 1;
138 }
139
140 this.sendAll(stat, -value, 'c', sampleRate, tags, callback);
141 };
142
143 /**
144 * Represents the histogram stat
145 * @param stat {String|Array} The stat(s) to send
146 * @param value The value to send
147 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
148 * @param tags {Array=} The Array of tags to add to metrics. Optional.
149 * @param callback {Function=} Callback when message is done being delivered. Optional.
150 */
151 Client.prototype.histogram = function (stat, value, sampleRate, tags, callback) {
152 this.sendAll(stat, value, 'h', sampleRate, tags, callback);
153 };
154
155 /**
156 * Represents the distribution stat
157 * @param stat {String|Array} The stat(s) to send
158 * @param value The value to send
159 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
160 * @param tags {Array=} The Array of tags to add to metrics. Optional.
161 * @param callback {Function=} Callback when message is done being delivered. Optional.
162 */
163 Client.prototype.distribution = function (stat, value, sampleRate, tags, callback) {
164 this.sendAll(stat, value, 'd', sampleRate, tags, callback);
165 };
166
167
168 /**
169 * Gauges a stat by a specified amount
170 * @param stat {String|Array} The stat(s) to send
171 * @param value The value to send
172 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
173 * @param tags {Array=} The Array of tags to add to metrics. Optional.
174 * @param callback {Function=} Callback when message is done being delivered. Optional.
175 */
176 Client.prototype.gauge = function (stat, value, sampleRate, tags, callback) {
177 this.sendAll(stat, value, 'g', sampleRate, tags, callback);
178 };
179
180 /**
181 * Counts unique values by a specified amount
182 * @param stat {String|Array} The stat(s) to send
183 * @param value The value to send
184 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
185 * @param tags {Array=} The Array of tags to add to metrics. Optional.
186 * @param callback {Function=} Callback when message is done being delivered. Optional.
187 */
188 Client.prototype.unique = Client.prototype.set = function (stat, value, sampleRate, tags, callback) {
189 this.sendAll(stat, value, 's', sampleRate, tags, callback);
190 };
191
192 /**
193 * Send a service check
194 * @param name {String} The name of the service check
195 * @param status {Number=} The status of the service check (0 to 3).
196 * @param options
197 * @option date_happened {Date} Assign a timestamp to the event. Default is now.
198 * @option hostname {String} Assign a hostname to the check.
199 * @option message {String} Assign a message to the check.
200 * @param tags {Array=} The Array of tags to add to the check. Optional.
201 * @param callback {Function=} Callback when message is done being delivered. Optional.
202 */
203 Client.prototype.check = function (name, status, options, tags, callback) {
204 if (this.telegraf) {
205 const err = new Error('Not supported by Telegraf / InfluxDB');
206 if (callback) {
207 return callback(err);
208 }
209 else if (this.errorHandler) {
210 return this.errorHandler(err);
211 }
212
213 throw err;
214 }
215
216 const check = ['_sc', this.prefix + name + this.suffix, status], metadata = options || {};
217
218 if (metadata.date_happened) {
219 const timestamp = helpers.formatDate(metadata.date_happened);
220 if (timestamp) {
221 check.push(`d:${timestamp}`);
222 }
223 }
224 if (metadata.hostname) {
225 check.push(`h:${metadata.hostname}`);
226 }
227
228 let mergedTags = this.globalTags;
229 if (tags && typeof(tags) === 'object') {
230 mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf);
231 }
232 if (mergedTags.length > 0) {
233 check.push(`#${mergedTags.join(',')}`);
234 }
235
236 // message has to be the last part of a service check
237 if (metadata.message) {
238 check.push(`m:${metadata.message}`);
239 }
240
241 // allow for tags to be omitted and callback to be used in its place
242 if (typeof tags === 'function' && callback === undefined) {
243 callback = tags;
244 }
245
246 const message = check.join('|');
247 // Service checks are unique in that message has to be the last element in
248 // the stat if provided, so we can't append tags like other checks. This
249 // directly calls the `_send` method to avoid appending tags, since we've
250 // already added them.
251 this._send(message, callback);
252 };
253
254 /**
255 * Send on an event
256 * @param title {String} The title of the event
257 * @param text {String} The description of the event. Optional- title is used if not given.
258 * @param options
259 * @option date_happened {Date} Assign a timestamp to the event. Default is now.
260 * @option hostname {String} Assign a hostname to the event.
261 * @option aggregation_key {String} Assign an aggregation key to the event, to group it with some others.
262 * @option priority {String} Can be ‘normal’ or ‘low’. Default is 'normal'.
263 * @option source_type_name {String} Assign a source type to the event.
264 * @option alert_type {String} Can be ‘error’, ‘warning’, ‘info’ or ‘success’. Default is 'info'.
265 * @param tags {Array=} The Array of tags to add to metrics. Optional.
266 * @param callback {Function=} Callback when message is done being delivered. Optional.
267 */
268 Client.prototype.event = function (title, text, options, tags, callback) {
269 if (this.telegraf) {
270 const err = new Error('Not supported by Telegraf / InfluxDB');
271 if (callback) {
272 return callback(err);
273 }
274 else if (this.errorHandler) {
275 return this.errorHandler(err);
276 }
277
278 throw err;
279 }
280
281 // Convert to strings
282 let message;
283
284 const msgTitle = String(title ? title : '');
285 let msgText = String(text ? text : msgTitle);
286 // Escape new lines (unescaping is supported by DataDog)
287 msgText = msgText.replace(/\n/g, '\\n');
288
289 // start out the message with the event-specific title and text info
290 message = `_e{${msgTitle.length},${msgText.length}}:${msgTitle}|${msgText}`;
291
292 // add in the event-specific options
293 if (options) {
294 if (options.date_happened) {
295 const timestamp = helpers.formatDate(options.date_happened);
296 if (timestamp) {
297 message += `|d:${timestamp}`;
298 }
299 }
300 if (options.hostname) {
301 message += `|h:${options.hostname}`;
302 }
303 if (options.aggregation_key) {
304 message += `|k:${options.aggregation_key}`;
305 }
306 if (options.priority) {
307 message += `|p:${options.priority}`;
308 }
309 if (options.source_type_name) {
310 message += `|s:${options.source_type_name}`;
311 }
312 if (options.alert_type) {
313 message += `|t:${options.alert_type}`;
314 }
315 }
316
317 // allow for tags to be omitted and callback to be used in its place
318 if (typeof tags === 'function' && callback === undefined) {
319 callback = tags;
320 }
321
322 this.send(message, tags, callback);
323 };
324}
325
326module.exports = applyStatsFns;