From 25de71bd60f5f52e0beb85d37b162e0d54f1ebbe Mon Sep 17 00:00:00 2001 From: Velmisov Date: Sun, 12 Jul 2020 18:12:20 +0300 Subject: [PATCH 1/2] feat: add histogram metric --- src/index.js | 2 + src/metric/HistogramMetric.js | 44 ++++++++++ src/metric/HistogramMetric.server.test.js | 99 +++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/metric/HistogramMetric.js create mode 100644 src/metric/HistogramMetric.server.test.js diff --git a/src/index.js b/src/index.js index 1d9a524..6e4e286 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import PullTransport from './transport/PullTransport'; import PushTransport from './transport/PushTransport'; import Metric from './metric/Metric'; import GaugeMetric from './metric/GaugeMetric'; +import HistogramMetric from './metric/HistogramMetric'; export { UMetrics, @@ -12,4 +13,5 @@ export { PullTransport, GaugeMetric, Metric, + HistogramMetric, }; diff --git a/src/metric/HistogramMetric.js b/src/metric/HistogramMetric.js new file mode 100644 index 0000000..3db15dd --- /dev/null +++ b/src/metric/HistogramMetric.js @@ -0,0 +1,44 @@ +import promClient from 'prom-client'; +import Metric from './Metric'; + +class HistogramMetric extends Metric { + constructor(name, buckets = [], options = {}) { + super(name, options); + + this.promMetric = new promClient.Histogram({ + name: this.name, + help: options.help || `${this.name}_help`, + labelNames: Object.keys(this._labels), + buckets, + }); + } + + /** + * @param {string} action + * @param {number} value + * @param {Object.} [_labels] + * @return {HistogramMetric} + * @private + */ + _proxyCall(action, value, _labels) { + const labels = { ...this._labels, ..._labels }; + if (!labels) { + this.promMetric[action](value); + return this; + } + + this.promMetric[action](labels, value); + return this; + } + + /** + * @param {number} value + * @param {Object.} [labels] + * @returns {HistogramMetric} + */ + observe(value, labels) { + return this._proxyCall('observe', value, labels); + } +} + +export default HistogramMetric; diff --git a/src/metric/HistogramMetric.server.test.js b/src/metric/HistogramMetric.server.test.js new file mode 100644 index 0000000..a7aef4f --- /dev/null +++ b/src/metric/HistogramMetric.server.test.js @@ -0,0 +1,99 @@ +import { expect } from 'chai'; +import promClient from 'prom-client'; +import HistogramMetric from './HistogramMetric'; + +/** + * @param {string} metricName + * @return {number} + */ +const getMetricValues = metricName => + promClient.register.getSingleMetric(metricName).get().values; + +/** + * @param {string} metricName + * @param {string} labelName + * @return {string|number} + */ +const getLabelValue = (metricName, labelName) => + promClient.register.getSingleMetric(metricName).get().values[0].labels[ + labelName + ]; + +describe('HistogramMetric', () => { + // Чтобы не ебалось при --watch + afterEach(() => { + promClient.register.clear(); + }); + + it('Can observe value without labels', () => { + const metricName = 'test'; + + const metric = new HistogramMetric(metricName, [5, 100, 1000]); + + metric.observe(5); + metric.observe(50); + metric.observe(100); + metric.observe(1001); + + const metricValues = getMetricValues(metricName); + + expect(metricValues[0].labels.le).to.be.equal(5); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + + expect(metricValues[1].labels.le).to.be.equal(100); + expect(metricValues[1].value).to.be.equal(3); + + expect(metricValues[2].labels.le).to.be.equal(1000); + expect(metricValues[2].value).to.be.equal(3); + + expect(metricValues[3].labels.le).to.be.equal('+Inf'); + expect(metricValues[3].value).to.be.equal(4); + + expect(metricValues[4].value).to.be.equal(1156); + expect(metricValues[4].metricName).to.be.equal(`${metricName}_sum`); + }); + + it('Can observe value with labels', () => { + const metricName = 'test'; + const labelName = 'testLabel'; + + const metric = new HistogramMetric(metricName, [100], { + labels: { testLabel: null }, + }); + + metric.observe(50, { [labelName]: 'testLabel' }); + metric.observe(150, { [labelName]: 'testLabel' }); + + const labelValue = getLabelValue(metricName, labelName); + expect(labelValue).to.be.equal(labelName); + + const metricValues = getMetricValues(metricName); + expect(metricValues[0].labels.le).to.be.equal(100); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + + expect(metricValues[1].labels.le).to.be.equal('+Inf'); + expect(metricValues[1].value).to.be.equal(2); + + expect(metricValues[2].value).to.be.equal(200); + expect(metricValues[2].metricName).to.be.equal(`${metricName}_sum`); + }); + + it('Can observe value without buckets', () => { + const metricName = 'test'; + + const metric = new HistogramMetric(metricName); + + metric.observe(50); + metric.observe(150); + + const metricValues = getMetricValues(metricName); + + expect(metricValues[0].labels.le).to.be.equal('+Inf'); + expect(metricValues[0].value).to.be.equal(2); + + expect(metricValues[1].value).to.be.equal(200); + expect(metricValues[1].metricName).to.be.equal(`${metricName}_sum`); + }); +}); From 9934f8a4ae57adb056eadcc53bd45c052d4e205e Mon Sep 17 00:00:00 2001 From: Velmisov Date: Mon, 13 Jul 2020 21:16:38 +0300 Subject: [PATCH 2/2] feat: add more histogram methods --- src/metric/HistogramMetric.js | 16 +++ src/metric/HistogramMetric.server.test.js | 167 ++++++++++++++++------ 2 files changed, 136 insertions(+), 47 deletions(-) diff --git a/src/metric/HistogramMetric.js b/src/metric/HistogramMetric.js index 3db15dd..0e826cb 100644 --- a/src/metric/HistogramMetric.js +++ b/src/metric/HistogramMetric.js @@ -39,6 +39,22 @@ class HistogramMetric extends Metric { observe(value, labels) { return this._proxyCall('observe', value, labels); } + + /** + * Start a timer where the value in seconds will observed + * @param labels Object with label keys and values + * @return Function to invoke when timer should be stopped + */ + startTimer(labels) { + return this.promMetric.startTimer(labels); + } + + /** + * Reset histogram values + */ + reset() { + this.promMetric.reset(); + } } export default HistogramMetric; diff --git a/src/metric/HistogramMetric.server.test.js b/src/metric/HistogramMetric.server.test.js index a7aef4f..42f3a1f 100644 --- a/src/metric/HistogramMetric.server.test.js +++ b/src/metric/HistogramMetric.server.test.js @@ -25,75 +25,148 @@ describe('HistogramMetric', () => { promClient.register.clear(); }); - it('Can observe value without labels', () => { - const metricName = 'test'; + describe('observe', () => { + it('Can observe value without labels', () => { + const metricName = 'test'; - const metric = new HistogramMetric(metricName, [5, 100, 1000]); + const metric = new HistogramMetric(metricName, [5, 100, 1000]); - metric.observe(5); - metric.observe(50); - metric.observe(100); - metric.observe(1001); + metric.observe(5); + metric.observe(50); + metric.observe(100); + metric.observe(1001); - const metricValues = getMetricValues(metricName); + const metricValues = getMetricValues(metricName); - expect(metricValues[0].labels.le).to.be.equal(5); - expect(metricValues[0].value).to.be.equal(1); - expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + expect(metricValues[0].labels.le).to.be.equal(5); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); - expect(metricValues[1].labels.le).to.be.equal(100); - expect(metricValues[1].value).to.be.equal(3); + expect(metricValues[1].labels.le).to.be.equal(100); + expect(metricValues[1].value).to.be.equal(3); - expect(metricValues[2].labels.le).to.be.equal(1000); - expect(metricValues[2].value).to.be.equal(3); + expect(metricValues[2].labels.le).to.be.equal(1000); + expect(metricValues[2].value).to.be.equal(3); - expect(metricValues[3].labels.le).to.be.equal('+Inf'); - expect(metricValues[3].value).to.be.equal(4); + expect(metricValues[3].labels.le).to.be.equal('+Inf'); + expect(metricValues[3].value).to.be.equal(4); - expect(metricValues[4].value).to.be.equal(1156); - expect(metricValues[4].metricName).to.be.equal(`${metricName}_sum`); - }); + expect(metricValues[4].value).to.be.equal(1156); + expect(metricValues[4].metricName).to.be.equal(`${metricName}_sum`); + }); + + it('Can observe value with labels', () => { + const metricName = 'test'; + const labelName = 'testLabel'; + + const metric = new HistogramMetric(metricName, [100], { + labels: { testLabel: null }, + }); + + metric.observe(50, { [labelName]: 'testLabel' }); + metric.observe(150, { [labelName]: 'testLabel' }); - it('Can observe value with labels', () => { - const metricName = 'test'; - const labelName = 'testLabel'; + const labelValue = getLabelValue(metricName, labelName); + expect(labelValue).to.be.equal(labelName); - const metric = new HistogramMetric(metricName, [100], { - labels: { testLabel: null }, + const metricValues = getMetricValues(metricName); + expect(metricValues[0].labels.le).to.be.equal(100); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + + expect(metricValues[1].labels.le).to.be.equal('+Inf'); + expect(metricValues[1].value).to.be.equal(2); + + expect(metricValues[2].value).to.be.equal(200); + expect(metricValues[2].metricName).to.be.equal(`${metricName}_sum`); }); - metric.observe(50, { [labelName]: 'testLabel' }); - metric.observe(150, { [labelName]: 'testLabel' }); + it('Can observe value without buckets', () => { + const metricName = 'test'; + + const metric = new HistogramMetric(metricName); + + metric.observe(50); + metric.observe(150); - const labelValue = getLabelValue(metricName, labelName); - expect(labelValue).to.be.equal(labelName); + const metricValues = getMetricValues(metricName); - const metricValues = getMetricValues(metricName); - expect(metricValues[0].labels.le).to.be.equal(100); - expect(metricValues[0].value).to.be.equal(1); - expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + expect(metricValues[0].labels.le).to.be.equal('+Inf'); + expect(metricValues[0].value).to.be.equal(2); - expect(metricValues[1].labels.le).to.be.equal('+Inf'); - expect(metricValues[1].value).to.be.equal(2); + expect(metricValues[1].value).to.be.equal(200); + expect(metricValues[1].metricName).to.be.equal(`${metricName}_sum`); + }); + }); - expect(metricValues[2].value).to.be.equal(200); - expect(metricValues[2].metricName).to.be.equal(`${metricName}_sum`); + describe('startTimer', () => { + it('returns function and starts timer', async () => { + const metricName = 'test'; + + const metric = new HistogramMetric(metricName, [0.005, 0.1, 1]); + + let end = metric.startTimer(); + await new Promise(resolve => { + setTimeout(() => { + end(); + resolve(); + }, 2); + }); + end = metric.startTimer(); + await new Promise(resolve => { + setTimeout(() => { + end(); + resolve(); + }, 200); + }); + + const metricValues = getMetricValues(metricName); + + expect(metricValues[0].labels.le).to.be.equal(0.005); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); + + expect(metricValues[1].labels.le).to.be.equal(0.1); + expect(metricValues[1].value).to.be.equal(1); + + expect(metricValues[2].labels.le).to.be.equal(1); + expect(metricValues[2].value).to.be.equal(2); + + expect(metricValues[3].labels.le).to.be.equal('+Inf'); + expect(metricValues[3].value).to.be.equal(2); + }); }); - it('Can observe value without buckets', () => { - const metricName = 'test'; + describe('reset', () => { + it('should reset metrics values', async () => { + const metricName = 'test'; + + const metric = new HistogramMetric(metricName, [5, 100, 1000]); + + metric.observe(5); + metric.observe(101); - const metric = new HistogramMetric(metricName); + metric.reset(); - metric.observe(50); - metric.observe(150); + metric.observe(5); - const metricValues = getMetricValues(metricName); + const metricValues = getMetricValues(metricName); - expect(metricValues[0].labels.le).to.be.equal('+Inf'); - expect(metricValues[0].value).to.be.equal(2); + expect(metricValues[0].labels.le).to.be.equal(5); + expect(metricValues[0].value).to.be.equal(1); + expect(metricValues[0].metricName).to.be.equal(`${metricName}_bucket`); - expect(metricValues[1].value).to.be.equal(200); - expect(metricValues[1].metricName).to.be.equal(`${metricName}_sum`); + expect(metricValues[1].labels.le).to.be.equal(100); + expect(metricValues[1].value).to.be.equal(1); + + expect(metricValues[2].labels.le).to.be.equal(1000); + expect(metricValues[2].value).to.be.equal(1); + + expect(metricValues[3].labels.le).to.be.equal('+Inf'); + expect(metricValues[3].value).to.be.equal(1); + + expect(metricValues[4].value).to.be.equal(5); + expect(metricValues[4].metricName).to.be.equal(`${metricName}_sum`); + }); }); });