Skip to content
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
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,11 @@ export class Summary<T extends string = NoLabelNameType> {
*/
reset(): void;

/**
* Initialize the metrics for the given combination of labels to zero
*/
zero(labels: LabelValues<T>): void;

/**
* Return the child for given labels
* @param values Label values
Expand Down
42 changes: 26 additions & 16 deletions lib/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,9 @@ class Summary extends Metric {
this.store = new LabelMap(this.labelNames);

if (this.labelNames.length === 0) {
this.store.getOrAdd({}, storedLabels => {
return {
labels: storedLabels,
td: new timeWindowQuantiles(this.maxAgeSeconds, this.ageBuckets),
count: 0,
sum: 0,
};
});
this.store.getOrAdd({}, storedLabels =>
createBaseValues(storedLabels, this.maxAgeSeconds, this.ageBuckets),
);
}
}

Expand Down Expand Up @@ -96,6 +91,18 @@ class Summary extends Metric {
}
}

/**
* Initialize the metrics for the given combination of labels to zero.
* @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep
* @returns {void}
*/
zero(labels) {
this.store.validate(labels);
this.store.merge(labels, {
value: createBaseValues(labels, this.maxAgeSeconds, this.ageBuckets),
});
}

/**
* Start a timer that could be used to logging durations.
* @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep
Expand Down Expand Up @@ -179,14 +186,8 @@ function observe(labels) {

const summaryOfLabel = this.store.getOrAdd(
labelValuePair.labels,
storedLabels => {
return {
labels: storedLabels,
td: new timeWindowQuantiles(this.maxAgeSeconds, this.ageBuckets),
count: 0,
sum: 0,
};
},
storedLabels =>
createBaseValues(storedLabels, this.maxAgeSeconds, this.ageBuckets),
);

summaryOfLabel.td.push(labelValuePair.value);
Expand All @@ -212,4 +213,13 @@ function convertLabelsAndValues(labels, value) {
};
}

function createBaseValues(labels, maxAgeSeconds, ageBuckets) {
return {
labels,
td: new timeWindowQuantiles(maxAgeSeconds, ageBuckets),
count: 0,
sum: 0,
};
}

module.exports = Summary;
109 changes: 109 additions & 0 deletions test/summaryTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,115 @@ describe.each([
});
});
});

describe('zero', () => {
beforeEach(() => {
globalRegistry.clear();
instance = new Summary({
name: 'summary_labels',
help: 'Summary with labels fn',
labelNames: ['method'],
});
});

it('should zero the given label', async () => {
instance.zero({ method: 'POST' });
const values = getValuesByLabel(
'POST',
(await instance.get()).values,
'method',
);
expect(values).not.toHaveLength(0);
values.forEach(val => {
expect(val.value).toEqual(0);
});

const sumValues = getValuesByName('summary_labels_sum', values);
expect(sumValues).toHaveLength(1);
expect(sumValues[0].value).toEqual(0);

const countValues = getValuesByName('summary_labels_count', values);
expect(countValues).toHaveLength(1);
expect(countValues[0].value).toEqual(0);
});

it('should export the metric after zeroing', async () => {
instance.zero({ method: 'POST' });
const values = getValuesByLabel(
'POST',
(await instance.get()).values,
'method',
);
expect(values).not.toHaveLength(0);
});

it('should not duplicate the metric', async () => {
instance.zero({ method: 'POST' });
instance.observe({ method: 'POST' }, 1);
const values = getValuesByName(
'summary_labels_count',
(await instance.get()).values,
);
expect(values).toHaveLength(1);
expect(values[0].value).toEqual(1);

const sumValues = getValuesByName(
'summary_labels_sum',
(await instance.get()).values,
);
expect(sumValues).toHaveLength(1);
expect(sumValues[0].value).toEqual(1);
});

it('should reset the label combination if zero is called after observe', async () => {
instance.observe({ method: 'POST' }, 50);
let countValues = getValuesByName(
'summary_labels_count',
(await instance.get()).values,
);
expect(countValues[0].value).toEqual(1);

instance.zero({ method: 'POST' });
const values = getValuesByLabel(
'POST',
(await instance.get()).values,
'method',
);
values.forEach(val => {
expect(val.value).toEqual(0);
});

countValues = getValuesByName('summary_labels_count', values);
expect(countValues[0].value).toEqual(0);

const sumValues = getValuesByName('summary_labels_sum', values);
expect(sumValues[0].value).toEqual(0);
});

it('should throw error when invalid label names are provided', () => {
expect(() => {
instance.zero({ invalid_label: 'POST' });
}).toThrow(/not included in initial labelset/);
});

function getValuesByName(name, values) {
return values.reduce((acc, val) => {
if (val.metricName === name) {
acc.push(val);
}
return acc;
}, []);
}

function getValuesByLabel(label, values, key) {
return values.reduce((acc, val) => {
if (val.labels && val.labels[key] === label) {
acc.push(val);
}
return acc;
}, []);
}
});
});
});
describe('without registry', () => {
Expand Down