Skip to content
This repository has been archived by the owner on Apr 7, 2024. It is now read-only.

[pull] master from dbader:master #6

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
on:
pull_request: {}
push: {}

name: Continuous Integration

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node_version: [12, 14, 16, 18]

steps:
- uses: actions/checkout@v3

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
cache: 'npm'
cache-dependency-path: '**/package.json'

- name: Install dependencies
run: npm install

- name: Run loader tests
run: |
npm test
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,25 @@ Where `options` is an object and can contain the following:
Datadog-metrics looks for the APP key in `DATADOG_APP_KEY` by default.
* `defaultTags`: Default tags used for all metric reporting. (optional)
* Set tags that are common to all metrics.
* `reporter`: An object that actually sends the buffered metrics. (optional)
* There are two built-in reporters you can use:
1. `reporters.DataDogReporter` sends metrics to DataDog’s API, and is
the default.
2. `reporters.NullReporter` throws the metrics away. It’s useful for
tests or temporarily disabling your metrics.

Example:

```js
metrics.init({ host: 'myhost', prefix: 'myapp.' });
```

Disabling metrics using `NullReporter`:

```js
metrics.init({ host: 'myhost', reporter: metrics.NullReporter() });
```


### Gauges

Expand Down Expand Up @@ -166,11 +178,11 @@ metrics.increment('test.awesomeness_factor', 10);

### Histograms

`metrics.histogram(key, value[, tags[, timestamp]])`
`metrics.histogram(key, value[, tags[, timestamp[, options]]])`

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.
maximum, average, median, 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()`.
Expand All @@ -181,6 +193,44 @@ Example:
metrics.histogram('test.service_time', 0.248);
```

You can also specify an options object to adjust which aggregations and
percentiles should be calculated. For example, to only calculate an average,
count, and 99th percentile:

```js
metrics.histogram('test.service_time', 0.248, ['tag:value'], Date.now(), {
// Aggregates can include 'max', 'min', 'sum', 'avg', 'median', or 'count'.
aggregates: ['avg', 'count'],
// Percentiles can include any decimal between 0 and 1.
percentiles: [0.99]
});
```

### Distributions

`metrics.distribution(key, value[, tags[, timestamp]])`

Send a distribution value. Distributions are similar to histograms (they create
several metrics for count, average, percentiles, etc.), but they are calculated
server-side on DataDog’s systems. This is much higher-overhead than histograms,
and the individual calculations made from it have to be configured on the
DataDog website instead of in the options for this package.

You should use this in environments where you have many instances of your
application running in parallel, or instances constantly starting and stopping
with different hostnames or identifiers and tagging each one separately is not
feasible. AWS Lambda or serverless functions are a great example of this. In
such environments, you also might want to use a distribution instead of
`increment` or `gauge` (if you have two instances of your app sending those
metrics at the same second, and they are not tagged differently or have
different `host` names, one will overwrite the other — distributions will not).

Example:

```js
metrics.distribution('test.service_time', 0.248);
```

### Flushing

`metrics.flush([onSuccess[, onError]])`
Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const loggers = require('./lib/loggers');
const reporters = require('./lib/reporters');

let sharedLogger = null;

Expand Down Expand Up @@ -45,5 +46,7 @@ module.exports = {
histogram: callOnSharedLogger.bind(undefined, 'histogram'),
distribution: callOnSharedLogger.bind(undefined, 'distribution'),

BufferedMetricsLogger: loggers.BufferedMetricsLogger
BufferedMetricsLogger: loggers.BufferedMetricsLogger,

reporters: reporters
};
18 changes: 17 additions & 1 deletion lib/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const util = require('util');
// --- Metric (base class)
//

const DEFAULT_HISTOGRAM_AGGREGATES = ['max', 'min', 'sum', 'avg', 'count'];
const DEFAULT_HISTOGRAM_AGGREGATES = ['max', 'min', 'sum', 'avg', 'count', 'median'];
const DEFAULT_HISTOGRAM_PERCENTILES = [0.75, 0.85, 0.95, 0.99];

function Metric(key, tags, host) {
Expand Down Expand Up @@ -166,6 +166,12 @@ Histogram.prototype.flush = function () {
return a - b;
};
this.samples.sort(numericalSortAscending);

if (this.aggregates.includes('median')) {
points.push(
this.serializeMetric(this.median(this.samples), 'gauge', this.key + '.median')
);
}

const calcPercentile = function (p) {
const val = this.samples[Math.round(p * this.samples.length) - 1];
Expand All @@ -185,6 +191,16 @@ Histogram.prototype.average = function() {
}
};

Histogram.prototype.median = function(sortedSamples) {
if (this.count === 0) {
return 0;
} else if (this.count % 2 === 1) {
return sortedSamples[(this.count - 1) / 2];
} else {
return (sortedSamples[this.count / 2 - 1] + sortedSamples[this.count / 2]) / 2;
}
};

//
// --- Distribution
//
Expand Down
48 changes: 35 additions & 13 deletions test/metrics_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,41 @@ describe('Histogram', function() {
f.should.have.deep.property('[4].points[0][1]', 2.5);
});

it('should report the median', function() {
var h = new metrics.Histogram('hist');
var f = h.flush();

f.should.have.deep.property('[5].metric', 'hist.median');
f.should.have.deep.property('[5].points[0][1]', 0);

h.addPoint(2);
h.addPoint(3);
h.addPoint(10);

f = h.flush();
f.should.have.deep.property('[5].metric', 'hist.median');
f.should.have.deep.property('[5].points[0][1]', 3);

h.addPoint(4);

f = h.flush();
f.should.have.deep.property('[5].metric', 'hist.median');
f.should.have.deep.property('[5].points[0][1]', 3.5);
});

it('should report the correct percentiles', function() {
var h = new metrics.Histogram('hist');
h.addPoint(1);
var f = h.flush();

f.should.have.deep.property('[5].metric', 'hist.75percentile');
f.should.have.deep.property('[5].points[0][1]', 1);
f.should.have.deep.property('[6].metric', 'hist.85percentile');
f.should.have.deep.property('[6].metric', 'hist.75percentile');
f.should.have.deep.property('[6].points[0][1]', 1);
f.should.have.deep.property('[7].metric', 'hist.95percentile');
f.should.have.deep.property('[7].metric', 'hist.85percentile');
f.should.have.deep.property('[7].points[0][1]', 1);
f.should.have.deep.property('[8].metric', 'hist.99percentile');
f.should.have.deep.property('[8].metric', 'hist.95percentile');
f.should.have.deep.property('[8].points[0][1]', 1);
f.should.have.deep.property('[9].metric', 'hist.99percentile');
f.should.have.deep.property('[9].points[0][1]', 1);

// Create 100 samples from [1..100] so we can
// verify the calculated percentiles.
Expand All @@ -187,14 +209,14 @@ describe('Histogram', function() {
}
f = h.flush();

f.should.have.deep.property('[5].metric', 'hist.75percentile');
f.should.have.deep.property('[5].points[0][1]', 75);
f.should.have.deep.property('[6].metric', 'hist.85percentile');
f.should.have.deep.property('[6].points[0][1]', 85);
f.should.have.deep.property('[7].metric', 'hist.95percentile');
f.should.have.deep.property('[7].points[0][1]', 95);
f.should.have.deep.property('[8].metric', 'hist.99percentile');
f.should.have.deep.property('[8].points[0][1]', 99);
f.should.have.deep.property('[6].metric', 'hist.75percentile');
f.should.have.deep.property('[6].points[0][1]', 75);
f.should.have.deep.property('[7].metric', 'hist.85percentile');
f.should.have.deep.property('[7].points[0][1]', 85);
f.should.have.deep.property('[8].metric', 'hist.95percentile');
f.should.have.deep.property('[8].points[0][1]', 95);
f.should.have.deep.property('[9].metric', 'hist.99percentile');
f.should.have.deep.property('[9].points[0][1]', 99);
});

it('should use custom percentiles and aggregates', function() {
Expand Down
7 changes: 6 additions & 1 deletion test/module_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('datadog-metrics', function() {
flushIntervalSeconds: 0,
reporter: {
report: function(series, onSuccess, onError) {
series.should.have.length(11); // 3 + 8 for the histogram.
series.should.have.length(12); // 3 + 9 for the histogram.
series[0].should.have.deep.property('points[0][1]', 23);
series[0].should.have.deep.property('metric', 'test.gauge');
series[0].tags.should.have.length(0);
Expand Down Expand Up @@ -77,4 +77,9 @@ describe('datadog-metrics', function() {
metrics.histogram('test.histogram', 23);
delete process.env.DATADOG_API_KEY;
});

it('should publicly export built-in reporters', function() {
metrics.reporters.should.have.property('DataDogReporter', reporters.DataDogReporter);
metrics.reporters.should.have.property('NullReporter', reporters.NullReporter);
})
});