Skip to content

Commit

Permalink
Add remove() method for handling dynamic labels (#242)
Browse files Browse the repository at this point in the history
* Add remove() method for handling dynamic labels

Fixes #187

* update types with remove() method

* updated changelog
  • Loading branch information
lizardruss authored and siimon committed Feb 1, 2019
1 parent a1ab1de commit 66f45d9
Show file tree
Hide file tree
Showing 15 changed files with 641 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added a `remove()` method on each metric type, based on [Prometheus "Writing Client Libraries" section on labels](https://prometheus.io/docs/instrumenting/writing_clientlibs/#labels)

## [11.2.1]

### Breaking
Expand Down
24 changes: 24 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ export class Counter {
* Reset counter values
*/
reset(): void;

/**
* Remove metrics for the given label values
* @param values Label values
*/
remove(...values: string[]): void;
}

export namespace Counter {
Expand Down Expand Up @@ -307,6 +313,12 @@ export class Gauge {
* Reset gauge values
*/
reset(): void;

/**
* Remove metrics for the given label values
* @param values Label values
*/
remove(...values: string[]): void;
}

export namespace Gauge {
Expand Down Expand Up @@ -413,6 +425,12 @@ export class Histogram {
* @return Configured histogram with given labels
*/
labels(...values: string[]): Histogram.Internal;

/**
* Remove metrics for the given label values
* @param values Label values
*/
remove(...values: string[]): void;
}

export namespace Histogram {
Expand Down Expand Up @@ -509,6 +527,12 @@ export class Summary {
* @return Configured summary with given labels
*/
labels(...values: string[]): Summary.Internal;

/**
* Remove metrics for the given label values
* @param values Label values
*/
remove(...values: string[]): void;
}

export namespace Summary {
Expand Down
8 changes: 7 additions & 1 deletion lib/counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const {
hashObject,
isObject,
printDeprecationObjectConstructor,
getLabels
getLabels,
removeLabels
} = require('./util');

const {
Expand Down Expand Up @@ -122,6 +123,11 @@ class Counter {
inc: inc.call(this, labels, hash)
};
}

remove() {
const labels = getLabels(this.labelNames, arguments) || {};
return removeLabels.call(this, this.hashMap, labels);
}
}

const reset = function() {
Expand Down
8 changes: 7 additions & 1 deletion lib/gauge.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const {
getLabels,
hashObject,
isObject,
printDeprecationObjectConstructor
printDeprecationObjectConstructor,
removeLabels
} = require('./util');
const {
validateMetricName,
Expand Down Expand Up @@ -168,6 +169,11 @@ class Gauge {
startTimer: startTimer.call(this, labels)
};
}

remove() {
const labels = getLabels(this.labelNames, arguments);
removeLabels.call(this, this.hashMap, labels);
}
}

function setToCurrentTime(labels) {
Expand Down
8 changes: 7 additions & 1 deletion lib/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const {
getLabels,
hashObject,
isObject,
printDeprecationObjectConstructor
printDeprecationObjectConstructor,
removeLabels
} = require('./util');
const {
validateMetricName,
Expand Down Expand Up @@ -149,6 +150,11 @@ class Histogram {
startTimer: startTimer.call(this, labels)
};
}

remove() {
const labels = getLabels(this.labelNames, arguments);
removeLabels.call(this, this.hashMap, labels);
}
}

function startTimer(startLabels) {
Expand Down
8 changes: 7 additions & 1 deletion lib/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const {
getLabels,
hashObject,
isObject,
printDeprecationObjectConstructor
printDeprecationObjectConstructor,
removeLabels
} = require('./util');
const {
validateLabel,
Expand Down Expand Up @@ -155,6 +156,11 @@ class Summary {
startTimer: startTimer.call(this, labels)
};
}

remove() {
const labels = getLabels(this.labelNames, arguments);
removeLabels.call(this, this.hashMap, labels);
}
}

function extractSummariesForExport(summaryOfLabels, percentiles) {
Expand Down
5 changes: 5 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ exports.getValueAsString = function getValueString(value) {
}
};

exports.removeLabels = function removeLabels(hashMap, labels) {
const hash = hashObject(labels);
delete hashMap[hash];
};

exports.setValue = function setValue(hashMap, value, labels, timestamp) {
const hash = hashObject(labels);
hashMap[hash] = {
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/counterTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ exports[`counter global registry with a parameter for each variable labels shoul

exports[`counter global registry with a parameter for each variable labels should throw error if one of lables isn't in labelNames list provided to constructor 1`] = `"Added label \\"status\\" is not included in initial labelset: [ 'method', 'endpoint' ]"`;

exports[`counter global registry with a parameter for each variable remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`counter global registry with a parameter for each variable should not allow invalid date as timestamp 1`] = `"Timestamp is not a valid date or number: Invalid Date"`;

exports[`counter global registry with a parameter for each variable should not allow non number as timestamp 1`] = `"Timestamp is not a valid date or number: blah"`;
Expand All @@ -12,6 +14,8 @@ exports[`counter global registry with a parameter for each variable should not b

exports[`counter global registry with a parameter for each variable should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`;

exports[`counter remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`counter with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`counter with params as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`;
Expand Down
2 changes: 2 additions & 0 deletions test/__snapshots__/gaugeTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

exports[`gauge global registry with a parameter for each variable should not allow non numbers 1`] = `"Value is not a valid number: asd"`;

exports[`gauge global registry with a parameter for each variable with remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`gauge global registry with a parameter for each variable with timestamp should not allow invalid dates 1`] = `"Timestamp is not a valid date or number: Invalid Date"`;

exports[`gauge global registry with a parameter for each variable with timestamp should not allow non numbers 1`] = `"Timestamp is not a valid date or number: blah"`;
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/histogramTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

exports[`histogram with a parameter for each variable labels should not allow different number of labels 1`] = `"Invalid number of arguments"`;

exports[`histogram with a parameter for each variable remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`histogram with a parameter for each variable should not allow le as a custom label 1`] = `"le is a reserved label keyword"`;

exports[`histogram with a parameter for each variable should not allow non numbers 1`] = `"Value is not a valid number: asd"`;

exports[`histogram with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`;

exports[`histogram with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`histogram with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`;

exports[`histogram with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`;
4 changes: 4 additions & 0 deletions test/__snapshots__/summaryTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

exports[`summary global registry with a parameter for each variable labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`summary global registry with a parameter for each variable remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`summary global registry with a parameter for each variable should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`;

exports[`summary global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;

exports[`summary global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`;
82 changes: 82 additions & 0 deletions test/counterTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,46 @@ describe('counter', () => {
});
});

describe('remove', () => {
beforeEach(() => {
instance = new Counter('gauge_test_3', 'help', [
'method',
'endpoint'
]);
instance.labels('GET', '/test').inc();
instance.labels('POST', '/test').inc();
});

afterEach(() => {
globalRegistry.clear();
});

it('should remove matching label', () => {
instance.remove('POST', '/test');

const values = instance.get().values;
expect(values).toHaveLength(1);
expect(values[0].value).toEqual(1);
expect(values[0].labels.method).toEqual('GET');
expect(values[0].labels.endpoint).toEqual('/test');
expect(values[0].timestamp).toEqual(undefined);
});

it('should remove all labels', () => {
instance.remove('GET', '/test');
instance.remove('POST', '/test');

expect(instance.get().values).toHaveLength(0);
});

it('should throw error if label lengths does not match', () => {
const fn = function() {
instance.remove('GET');
};
expect(fn).toThrowErrorMatchingSnapshot();
});
});

describe('empty labels', () => {
beforeEach(() => {
instance = new Counter('gauge_test_3', 'test');
Expand Down Expand Up @@ -232,6 +272,48 @@ describe('counter', () => {
});
});
});

describe('remove', () => {
beforeEach(() => {
instance = new Counter({
name: 'gauge_test_3',
help: 'help',
labelNames: ['method', 'endpoint']
});
instance.inc({ method: 'GET', endpoint: '/test' });
instance.inc({ method: 'POST', endpoint: '/test' });
});

afterEach(() => {
globalRegistry.clear();
});

it('should remove matching label', () => {
instance.remove('POST', '/test');

const values = instance.get().values;
expect(values).toHaveLength(1);
expect(values[0].value).toEqual(1);
expect(values[0].labels.method).toEqual('GET');
expect(values[0].labels.endpoint).toEqual('/test');
expect(values[0].timestamp).toEqual(undefined);
});

it('should remove all labels', () => {
instance.remove('GET', '/test');
instance.remove('POST', '/test');

expect(instance.get().values).toHaveLength(0);
});

it('should throw error if label lengths does not match', () => {
const fn = function() {
instance.remove('GET');
};
expect(fn).toThrowErrorMatchingSnapshot();
});
});

describe('without registry', () => {
beforeEach(() => {
instance = new Counter({
Expand Down
50 changes: 50 additions & 0 deletions test/gaugeTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ describe('gauge', () => {
});
});

describe('with remove', () => {
beforeEach(() => {
instance = new Gauge('name', 'help', ['code']);
instance.set({ code: '200' }, 20);
instance.set({ code: '400' }, 0);
});
it('should be able to remove matching label', () => {
instance.remove('200');
const values = instance.get().values;
expect(values.length).toEqual(1);
expect(values[0].labels.code).toEqual('400');
expect(values[0].value).toEqual(0);
});
it('should be able to remove all labels', () => {
instance.remove('200');
instance.remove('400');
expect(instance.get().values.length).toEqual(0);
});
it('should throw error if label lengths does not match', () => {
const fn = function() {
instance.remove('200', 'GET');
};
expect(fn).toThrowErrorMatchingSnapshot();
});
});

describe('with timestamp', () => {
beforeEach(() => {
instance = new Gauge('name', 'help', ['code']);
Expand Down Expand Up @@ -296,6 +322,30 @@ describe('gauge', () => {
});
});

describe('with remove', () => {
beforeEach(() => {
instance = new Gauge({
name: 'name',
help: 'help',
labelNames: ['code']
});
instance.set({ code: '200' }, 20);
instance.set({ code: '400' }, 0);
});
it('should be able to remove matching label', () => {
instance.remove('200');
const values = instance.get().values;
expect(values.length).toEqual(1);
expect(values[0].labels.code).toEqual('400');
expect(values[0].value).toEqual(0);
});
it('should be able to remove all labels', () => {
instance.remove('200');
instance.remove('400');
expect(instance.get().values.length).toEqual(0);
});
});

describe('with timestamp', () => {
beforeEach(() => {
instance = new Gauge('name', 'help', ['code']);
Expand Down
Loading

0 comments on commit 66f45d9

Please sign in to comment.