Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: always use sum aggregator for sum & count #487

Open
wants to merge 3 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ project adheres to [Semantic Versioning](http://semver.org/).
Pushgateway's typedef were missing promise return type. That was
causing vscode to think that push/pushAdd and delete didn't promise
resulting in incorrect behavior.

- fix: always using sum aggregator for sum & count in histogram & summary

### Added

Expand Down
93 changes: 68 additions & 25 deletions lib/metricAggregators.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,56 @@

const { Grouper, hashObject } = require('./util');

/**
* Returns the sum of all label values passed
* @param {Object} v Label values
* @return {Number} sum of values
*/
function sum(v) {
return v.reduce((p, c) => p + c.value, 0);
}

/**
* Returns the first label value
* @param {Object} v Label values
* @return {Number} first value.
*/
function first(v) {
return v[0].value;
}

/**
* @return {undefined} Undefined; omits the values.
*/
function omit() {}

/**
* Returns the mean of all label values
* @param {Object} v Label values
* @return {Number} mean of values.
*/
function average(v) {
return v.reduce((p, c) => p + c.value, 0) / v.length;
}

/**
* Returns the minimum of all label values
* @param {Object} v Label values
* @return {Number} min of values.
*/
function min(v) {
return v.reduce((p, c) => Math.min(p, c.value), Infinity);
}

/**
* Returns the maximum of all label values
* @param {Object} v Label values
* @return {Number} max of values.
*/
function max(v) {
return v.reduce((p, c) => Math.max(p, c.value), -Infinity);
}

/**
* Returns a new function that applies the `aggregatorFn` to the values.
* @param {Function} aggregatorFn function to apply to values.
Expand Down Expand Up @@ -29,12 +79,20 @@ function AggregatorFactory(aggregatorFn) {
byLabels.forEach(values => {
if (values.length === 0) return;
const valObj = {
value: aggregatorFn(values),
labels: values[0].labels,
};
if (values[0].metricName) {
valObj.metricName = values[0].metricName;
}
// Should always `sum` if it's `sum` or `count` that's part of histogram & summary
if (
values[0].metricName === `${result.name}_sum` ||
values[0].metricName === `${result.name}_count`
) {
valObj.value = sum(values);
} else {
valObj.value = aggregatorFn(values);
}
Comment on lines +87 to +95
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good find, thanks and sorry for the year-long delay reviewing it.

I think this can wrongly identify custom metrics that are happen to be named something_sum/something_count though. Can you use the type instead? (metrics[0].type)

// NB: Timestamps are omitted.
result.values.push(valObj);
});
Expand All @@ -48,34 +106,19 @@ exports.AggregatorFactory = AggregatorFactory;
* Functions that can be used to aggregate metrics from multiple registries.
*/
exports.aggregators = {
/**
* @return The sum of values.
*/
sum: AggregatorFactory(v => v.reduce((p, c) => p + c.value, 0)),
/**
* @return The first value.
*/
first: AggregatorFactory(v => v[0].value),
/**
* @return {undefined} Undefined; omits the metric.
*/
omit: () => {},
/**
* @return The arithmetic mean of the values.
*/
average: AggregatorFactory(
v => v.reduce((p, c) => p + c.value, 0) / v.length,
),
sum: AggregatorFactory(sum),

first: AggregatorFactory(first),

omit,

average: AggregatorFactory(average),
/**
* @return The minimum of the values.
*/
min: AggregatorFactory(v =>
v.reduce((p, c) => Math.min(p, c.value), Infinity),
),
min: AggregatorFactory(min),
/**
* @return The maximum of the values.
*/
max: AggregatorFactory(v =>
v.reduce((p, c) => Math.max(p, c.value), -Infinity),
),
max: AggregatorFactory(max),
};
70 changes: 70 additions & 0 deletions test/clusterTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@ describe('AggregatorRegistry', () => {
const Registry = require('../lib/cluster');
// These mimic the output of `getMetricsAsJSON`.
const metricsArr1 = [
{
name: 'test_summary',
help: 'Example of a summary',
type: 'summary',
values: [
{
labels: { quantile: 0.5 },
value: 4,
},
{
metricName: 'test_summary_sum',
labels: {},
value: 10,
},
{
metricName: 'test_summary_count',
labels: {},
value: 3,
},
],
aggregator: 'average',
},
{
name: 'test_histogram',
help: 'Example of a histogram',
Expand Down Expand Up @@ -102,6 +124,28 @@ describe('AggregatorRegistry', () => {
},
];
const metricsArr2 = [
{
name: 'test_summary',
help: 'Example of a summary',
type: 'summary',
values: [
{
labels: { quantile: 0.5 },
value: 6,
},
{
metricName: 'test_summary_sum',
labels: {},
value: 20,
},
{
metricName: 'test_summary_count',
labels: {},
value: 2,
},
],
aggregator: 'average',
},
{
name: 'test_histogram',
help: 'Example of a histogram',
Expand Down Expand Up @@ -246,5 +290,31 @@ describe('AggregatorRegistry', () => {
aggregator: 'first',
});
});

it('uses `aggregate` method defined for test_summary and summation for `count` & `sum`', async () => {
const summary = aggregated.getSingleMetric('test_summary').get();
expect(summary).toEqual({
name: 'test_summary',
help: 'Example of a summary',
type: 'summary',
values: [
{
labels: { quantile: 0.5 },
value: 5,
},
{
labels: {},
value: 30,
metricName: 'test_summary_sum',
},
{
labels: {},
value: 5,
metricName: 'test_summary_count',
},
],
aggregator: 'average',
});
});
});
});