Repository: brightcove/hot-shots
Pull request #60 (2018-02-22 16:50:44)

Add timer decorator function
If you use the `timing` function to send stats such as how long a function or external request takes to execute, you likely are manually starting and stopping a timer using something like `process.hrtime()` before sending that time on to your StatsD server.

This decorator function abstracts that work away and takes care of tracking execution time and sending that info on to your StatsD server using the `timing` function for you.

Sample use of `timer`:

```Javascript
function add(a, b) {
  return a + b;
}

// Would send the time taken to execute 'add' as the 'time'
// argument to the 'timing' function
timer(add, 'add_execution_time')(2, 3);

```

Before this change, this behaviour would be achieved with something like the following:

```Javascript
var start = process.hrtime();
try {
 return add(2, 3);
} finally {
  var duration = process.hrtime(start)[1] / 1000000;
  metricsClient.timing('add-execute-time', duration, sampleRate, tags)
}
```


Patch:
diff --git a/README.md b/README.md
index c36e688..b4f44a5 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,14 @@ The check method has the following API:
   // Timing: sends a timing command with the specified milliseconds
   client.timing('response_time', 42);
 
+  // Timer: Returns a function that you call to record how long the first
+  // parameter takes to execute (in milliseconds) and then sends that value
+  // using 'client.timing'.
+  // The parameters after the first one (in this case 'fn')
+  // match those in 'client.timing'.
+  var fn = function(a, b) { return a + b };
+  client.timer(fn, 'fn_execution_time')(2, 2);
+
   // Increment: Increments a stat by a value (default is 1)
   client.increment('my_counter');
 
diff --git a/lib/statsd.js b/lib/statsd.js
index 932495b..4119bb0 100644
--- a/lib/statsd.js
+++ b/lib/statsd.js
@@ -107,6 +107,36 @@ Client.prototype.timing = function (stat, time, sampleRate, tags, callback) {
   this.sendAll(stat, time, '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) {
+  var _this = this;
+
+  return function () {
+    var start = process.hrtime();
+    try {
+      return func.apply(null, arguments);
+    } finally {
+      // get duration in milliseconds
+      var duration = process.hrtime(start)[1] / 1000000;
+
+      _this.timing(
+        stat,
+        duration,
+        sampleRate,
+        tags,
+        callback
+      );
+    }
+  };
+};
+
 /**
  * Increments a stat by a specified amount
  * @param stat {String|Array} The stat(s) to send


Test patch:
diff --git a/test/test_statsd.js b/test/test_statsd.js
index ad9d2c6..741b67f 100644
--- a/test/test_statsd.js
+++ b/test/test_statsd.js
@@ -487,6 +487,46 @@ function doTests(StatsD) {
 
   });
 
+  describe('#timer', function() {
+    it('should send stat and time to execute to timing function', function(finished) {
+      udpTest(function (message, server) {
+        // Search for a string similar to 'test:0.123|ms'
+        var re = RegExp("(test:)([0-9]+\.[0-9]+)\\|{1}(ms)");
+        assert.equal(true, re.test(message));
+        server.close();
+        finished();
+      }, function (server) {
+        var address = server.address(),
+          statsd = new StatsD(address.address, address.port);
+
+        var testFunc = function(a, b) {
+          return a + b;
+        };
+
+        statsd.timer(testFunc, 'test')(2, 2);
+      });
+    });
+
+    it('should send data with tags to timing function', function(finished) {
+      udpTest(function (message, server) {
+        // Search for a string similar to 'test:0.123|ms|#foo,bar'
+        var re = RegExp("(test:)([0-9]+\.[0-9]+)\\|{1}(ms)\\|{1}\\#(foo,bar)");
+        assert.equal(true, re.test(message));
+        server.close();
+        finished();
+      }, function (server) {
+        var address = server.address(),
+          statsd = new StatsD(address.address, address.port);
+
+        var testFunc = function (a, b) {
+          return a + b;
+        };
+
+        statsd.timer(testFunc, 'test', undefined, ['foo', 'bar'])(2, 2);
+      });
+    });
+  });
+
   describe('#timing', function(){
     it('should send proper time format without prefix, suffix, sampling and callback', function(finished){
       udpTest(function(message, server){