Repository: dbader/node-datadog-metrics
Pull request #22 (2017-03-14 08:06:44)

Add ability to set custom timestamps
Addresses #16 


Patch:
diff --git a/README.md b/README.md
index a7c0118..0bc4267 100644
--- a/README.md
+++ b/README.md
@@ -127,13 +127,14 @@ metrics.init({ host: 'myhost', prefix: 'myapp.' });
 
 ### Gauges
 
-`metrics.gauge(key, value[, tags])`
+`metrics.gauge(key, value[, tags[, timestamp]])`
 
 Record the current *value* of a metric. They most recent value in
 a given flush interval will be recorded. Optionally, specify a set of
 tags to associate with the metric. This should be used for sum values
 such as total hard disk space, process uptime, total number of active
-users, or number of rows in a database table.
+users, or number of rows in a database table. The optional timestamp
+is in milliseconds since 1 Jan 1970 00:00:00 UTC, e.g. from `Date.now()`.
 
 Example:
 
@@ -143,11 +144,13 @@ metrics.gauge('test.mem_free', 23);
 
 ### Counters
 
-`metrics.increment(key[, value[, tags]])`
+`metrics.increment(key[, value[, tags[, timestamp]]])`
 
 Increment the counter by the given *value* (or `1` by default). Optionally,
 specify a list of *tags* to associate with the metric. This is useful for
 counting things such as incrementing a counter each time a page is requested.
+The optional timestamp is in milliseconds since 1 Jan 1970 00:00:00 UTC,
+e.g. from `Date.now()`.
 
 Example:
 
@@ -158,12 +161,14 @@ metrics.increment('test.awesomeness_factor', 10);
 
 ### Histograms
 
-`metrics.histogram(key, value[, tags])`
+`metrics.histogram(key, value[, tags[, timestamp]])`
 
 Sample a histogram value. Histograms will produce metrics that
 describe the distribution of the recorded values, namely the minimum,
 maximum, average, count and the 75th, 85th, 95th and 99th percentiles.
 Optionally, specify a list of *tags* to associate with the metric.
+The optional timestamp is in milliseconds since 1 Jan 1970 00:00:00 UTC,
+e.g. from `Date.now()`.
 
 Example:
 
diff --git a/lib/aggregators.js b/lib/aggregators.js
index 5fdc0ff..29de0e2 100644
--- a/lib/aggregators.js
+++ b/lib/aggregators.js
@@ -13,13 +13,13 @@ Aggregator.prototype.makeBufferKey = function(key, tags) {
     return key + '#' + tags.concat().sort().join('.');
 };
 
-Aggregator.prototype.addPoint = function(Type, key, value, tags, host) {
+Aggregator.prototype.addPoint = function(Type, key, value, tags, host, timestampInMillis) {
     var bufferKey = this.makeBufferKey(key, tags);
     if (!this.buffer.hasOwnProperty(bufferKey)) {
         this.buffer[bufferKey] = new Type(key, tags, host);
     }
 
-    this.buffer[bufferKey].addPoint(value);
+    this.buffer[bufferKey].addPoint(value, timestampInMillis);
 };
 
 Aggregator.prototype.flush = function() {
diff --git a/lib/loggers.js b/lib/loggers.js
index 1b841bd..a7809c6 100644
--- a/lib/loggers.js
+++ b/lib/loggers.js
@@ -63,20 +63,20 @@ function BufferedMetricsLogger(opts) {
 }
 
 // Prepend the global key prefix and set the default host.
-BufferedMetricsLogger.prototype.addPoint = function(Type, key, value, tags) {
-    this.aggregator.addPoint(Type, this.prefix + key, value, tags, this.host);
+BufferedMetricsLogger.prototype.addPoint = function(Type, key, value, tags, timestampInMillis) {
+    this.aggregator.addPoint(Type, this.prefix + key, value, tags, this.host, timestampInMillis);
 };
 
-BufferedMetricsLogger.prototype.gauge = function(key, value, tags) {
-    this.addPoint(Gauge, key, value, tags);
+BufferedMetricsLogger.prototype.gauge = function(key, value, tags, timestampInMillis) {
+    this.addPoint(Gauge, key, value, tags, timestampInMillis);
 };
 
-BufferedMetricsLogger.prototype.increment = function(key, value, tags) {
-    this.addPoint(Counter, key, value || 1, tags);
+BufferedMetricsLogger.prototype.increment = function(key, value, tags, timestampInMillis) {
+    this.addPoint(Counter, key, value || 1, tags, timestampInMillis);
 };
 
-BufferedMetricsLogger.prototype.histogram = function(key, value, tags) {
-    this.addPoint(Histogram, key, value, tags);
+BufferedMetricsLogger.prototype.histogram = function(key, value, tags, timestampInMillis) {
+    this.addPoint(Histogram, key, value, tags, timestampInMillis);
 };
 
 BufferedMetricsLogger.prototype.flush = function(onSuccess, onError) {
diff --git a/lib/metrics.js b/lib/metrics.js
index a44613f..f7869bb 100644
--- a/lib/metrics.js
+++ b/lib/metrics.js
@@ -19,12 +19,14 @@ Metric.prototype.flush = function() {
     return null;
 };
 
-Metric.prototype.posixTimestamp = function() {
-    return Math.round(Date.now() / 1000);
+Metric.prototype.posixTimestamp = function(timestampInMillis) {
+    // theoretically, 0 is a valid timestamp, albeit unlikely
+    var timestamp = timestampInMillis === undefined ? Date.now() : timestampInMillis;
+    return Math.round(timestamp / 1000);
 };
 
-Metric.prototype.updateTimestamp = function() {
-    this.timestamp = this.posixTimestamp();
+Metric.prototype.updateTimestamp = function(timestampInMillis) {
+    this.timestamp = this.posixTimestamp(timestampInMillis);
 };
 
 Metric.prototype.serializeMetric = function(value, type, key) {
@@ -59,9 +61,9 @@ function Gauge(key, tags, host) {
 
 util.inherits(Gauge, Metric);
 
-Gauge.prototype.addPoint = function(val) {
+Gauge.prototype.addPoint = function(val, timestampInMillis) {
     this.value = val;
-    this.updateTimestamp();
+    this.updateTimestamp(timestampInMillis);
 };
 
 Gauge.prototype.flush = function() {
@@ -88,9 +90,9 @@ function Counter(key, tags, host) {
 
 util.inherits(Counter, Metric);
 
-Counter.prototype.addPoint = function(val) {
+Counter.prototype.addPoint = function(val, timestampInMillis) {
     this.value += val;
-    this.updateTimestamp();
+    this.updateTimestamp(timestampInMillis);
 };
 
 Counter.prototype.flush = function() {
@@ -122,8 +124,8 @@ function Histogram(key, tags, host) {
 
 util.inherits(Histogram, Metric);
 
-Histogram.prototype.addPoint = function(val) {
-    this.updateTimestamp();
+Histogram.prototype.addPoint = function(val, timestampInMillis) {
+    this.updateTimestamp(timestampInMillis);
 
     this.min = Math.min(val, this.min);
     this.max = Math.max(val, this.max);


Test patch:
diff --git a/test/loggers_tests.js b/test/loggers_tests.js
index 2ac7493..f194242 100644
--- a/test/loggers_tests.js
+++ b/test/loggers_tests.js
@@ -16,7 +16,15 @@ describe('BufferedMetricsLogger', function() {
         var l = new BufferedMetricsLogger({
             reporter: new reporters.NullReporter()
         });
-        l.gauge('test.gauge', 23);
+        l.aggregator = {
+            addPoint: function(Type, key, value, tags, host, timestampInMillis) {
+                key.should.equal('test.gauge');
+                value.should.equal(23);
+                tags.should.eql(['a:a']);
+                timestampInMillis.should.eql(1234567890);
+            }
+        };
+        l.gauge('test.gauge', 23, ['a:a'], 1234567890);
     });
 
     it('should have an increment() metric', function() {
@@ -33,19 +41,29 @@ describe('BufferedMetricsLogger', function() {
         l.increment('test.counter');
 
         l.aggregator = {
-            addPoint: function(Type, key, value, tags, host) {
+            addPoint: function(Type, key, value, tags, host, timestampInMillis) {
                 key.should.equal('test.counter2');
                 value.should.equal(23);
+                tags.should.eql(['z:z', 'a:a']);
+                timestampInMillis.should.equal(1234567890);
             }
         };
-        l.increment('test.counter2', 23);
+        l.increment('test.counter2', 23, ['z:z', 'a:a'], 1234567890);
     });
 
     it('should have a histogram() metric', function() {
         var l = new BufferedMetricsLogger({
             reporter: new reporters.NullReporter()
         });
-        l.histogram('test.histogram', 23);
+        l.aggregator = {
+            addPoint: function(Type, key, value, tags, host, timestampInMillis) {
+                key.should.equal('test.histogram');
+                value.should.equal(23);
+                tags.should.eql(['a:a']);
+                timestampInMillis.should.eql(1234567890);
+            }
+        };
+        l.histogram('test.histogram', 23, ['a:a'], 1234567890);
     });
 
     it('should allow setting a default host', function() {
@@ -70,7 +88,7 @@ describe('BufferedMetricsLogger', function() {
         });
         l.aggregator = {
             addPoint: function(Type, key, value, tags, host) {
-                key.should.startsWith('mynamespace.test.');
+                key.should.startWith('mynamespace.test.');
             }
         };
         l.gauge('test.gauge', 23);
diff --git a/test/metrics_tests.js b/test/metrics_tests.js
index 242b255..f88f766 100644
--- a/test/metrics_tests.js
+++ b/test/metrics_tests.js
@@ -17,10 +17,17 @@ describe('Metric', function() {
         m.host.should.equal('myhost');
     });
 
-    it('should update the timestamp when a data point is added', function() {
+    it('should update the timestamp with Date.now when a data point is added', function() {
         var m = new metrics.Metric();
-        m.updateTimestamp(123);
-        m.timestamp.should.be.a('number');
+        var now = Date.now();
+        m.updateTimestamp();
+        var diff = (m.timestamp * 1000) - now;
+        Math.abs(diff).should.lessThan(1000); // within one second
+    });
+    it('should update the timestamp when a data point is added with a timestamp in ms', function() {
+        var m = new metrics.Metric();
+        m.updateTimestamp(123000);
+        m.timestamp.should.equal(123);
     });
 });
 
@@ -42,6 +49,19 @@ describe('Gauge', function() {
         f[0].should.have.deep.property('points[0][0]', g.timestamp);
         f[0].should.have.deep.property('points[0][1]', 1);
     });
+
+    it('should flush correctly when given timestamp', function() {
+        var g = new metrics.Gauge('the.key', ['mytag'], 'myhost');
+        g.addPoint(1, 123000);
+        var f = g.flush();
+        f.should.have.length(1);
+        f[0].should.have.deep.property('metric', 'the.key');
+        f[0].should.have.deep.property('tags[0]', 'mytag');
+        f[0].should.have.deep.property('host', 'myhost');
+        f[0].should.have.deep.property('type', 'gauge');
+        f[0].should.have.deep.property('points[0][0]', 123);
+        f[0].should.have.deep.property('points[0][1]', 1);
+    });
 });
 
 describe('Counter', function() {
@@ -62,6 +82,19 @@ describe('Counter', function() {
         f[0].should.have.deep.property('points[0][0]', g.timestamp);
         f[0].should.have.deep.property('points[0][1]', 1);
     });
+
+    it('should flush correctly', function() {
+        var g = new metrics.Counter('the.key', ['mytag'], 'myhost');
+        g.addPoint(1, 123000);
+        var f = g.flush();
+        f.should.have.length(1);
+        f[0].should.have.deep.property('metric', 'the.key');
+        f[0].should.have.deep.property('tags[0]', 'mytag');
+        f[0].should.have.deep.property('host', 'myhost');
+        f[0].should.have.deep.property('type', 'counter');
+        f[0].should.have.deep.property('points[0][0]', 123);
+        f[0].should.have.deep.property('points[0][1]', 1);
+    });
 });
 
 describe('Histogram', function() {