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

feat: startTimer with exemplar #593

Merged
merged 3 commits into from
Nov 25, 2023
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
### Added

- Allow Pushgateway to now require job names for compatibility with Gravel Gateway.
- Allow `histogram.startTime()` to be used with exemplars.

## [15.0.0] - 2023-10-09

Expand Down
13 changes: 13 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,19 @@ export class Histogram<T extends string = string> {
*/
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => number;

/**
* Start a timer with exemplar. Calling the returned function will observe the duration in
* seconds in the histogram.
* @param labels Object with label keys and values
* @param exemplarLabels Object with label keys and values for exemplars
* @return Function to invoke when timer should be stopped. The value it
* returns is the timed duration.
*/
startTimer(
labels?: LabelValues<T>,
exemplarLabels?: LabelValues<T>,
): (labels?: LabelValues<T>, exemplarLabels?: LabelValues<T>) => number;

/**
* Reset histogram values
*/
Expand Down
29 changes: 27 additions & 2 deletions lib/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ class Histogram extends Metric {
this.type = 'histogram';
this.defaultLabels = {};
this.defaultExemplarLabelSet = {};
this.enableExemplars = false;

if (config.enableExemplars) {
this.enableExemplars = true;
this.observe = this.observeWithExemplar;
} else {
this.observe = this.observeWithoutExemplar;
Expand Down Expand Up @@ -143,6 +145,7 @@ class Histogram extends Metric {
/**
* 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
* @param {object} exemplarLabels - Object with labels for exemplar where key is the label key and value is label value. Can only be one level deep
* @returns {function} - Function to invoke when you want to stop the timer and observe the duration in seconds
* @example
* var end = histogram.startTimer();
Expand All @@ -151,8 +154,10 @@ class Histogram extends Metric {
* console.log('Duration', duration);
* });
*/
startTimer(labels) {
return startTimer.call(this, labels)();
startTimer(labels, exemplarLabels) {
return this.enableExemplars
? startTimerWithExemplar.call(this, labels, exemplarLabels)()
: startTimer.call(this, labels)();
}

labels(...args) {
Expand Down Expand Up @@ -183,6 +188,26 @@ function startTimer(startLabels) {
};
}

function startTimerWithExemplar(startLabels, startExemplarLabels) {
return () => {
const start = process.hrtime();
return (endLabels, endExemplarLabels) => {
const delta = process.hrtime(start);
const value = delta[0] + delta[1] / 1e9;
this.observe({
labels: Object.assign({}, startLabels, endLabels),
value,
exemplarLabels: Object.assign(
{},
startExemplarLabels,
endExemplarLabels,
),
});
return value;
};
};
}

function setValuePair(labels, value, metricName, exemplar, sharedLabels = {}) {
return {
labels,
Expand Down
58 changes: 58 additions & 0 deletions test/exemplarsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,64 @@ describe('Exemplars', () => {
}).toThrowError('Label set size must be smaller than 128 UTF-8 chars');
});

it('should time request, with exemplar', async () => {
jest.useFakeTimers('modern');
jest.setSystemTime(0);
const histogramInstance = new Histogram({
name: 'histogram_start_timer_exemplar_test',
help: 'test',
labelNames: ['method', 'code'],
enableExemplars: true,
});
const end = histogramInstance.startTimer({
method: 'get',
code: '200',
});

jest.advanceTimersByTime(500);
end();

const valuePair = getValueByLabel(
0.5,
(await histogramInstance.get()).values,
);
expect(valuePair.value).toEqual(1);
jest.useRealTimers();
});

it('should allow exemplar labels before and after timers', async () => {
jest.useFakeTimers('modern');
jest.setSystemTime(0);
const histogramInstance = new Histogram({
name: 'histogram_start_timer_exemplar_label_test',
help: 'test',
labelNames: ['method', 'code'],
enableExemplars: true,
});
const end = histogramInstance.startTimer(
{ method: 'get' },
{ traceId: 'trace_id_test_1' },
);

jest.advanceTimersByTime(500);
end({ code: '200' }, { spanId: 'span_id_test_1' });

const vals = (await histogramInstance.get()).values;
expect(getValuesByLabel(0.5, vals)[0].value).toEqual(1);
expect(
getValuesByLabel(0.5, vals)[0].exemplar.labelSet.traceId,
).toEqual('trace_id_test_1');
jest.useRealTimers();
});

function getValueByLabel(label, values, key) {
return values.reduce((acc, val) => {
if (val.labels && val.labels[key || 'le'] === label) {
acc = val;
}
return acc;
}, {});
}
function getValuesByLabel(label, values, key) {
return values.reduce((acc, val) => {
if (val.labels && val.labels[key || 'le'] === label) {
Expand Down
Loading