Skip to content

Commit a025108

Browse files
authored
feat: implement metrics sdk (open-telemetry#415)
* feat: add metrics sdk * refactor: based on comments and add Gauge support * fix: add JSDoc comment * fix: pass label values to handle and generate TimeSeries * fix: add MeterConfig and logger option
1 parent 502f9e4 commit a025108

File tree

9 files changed

+658
-20
lines changed

9 files changed

+658
-20
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*!
2+
* Copyright 2019, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as types from '@opentelemetry/types';
18+
import { TimeSeries } from './export/types';
19+
20+
/**
21+
* CounterHandle allows the SDK to observe/record a single metric event. The
22+
* value of single handle in the `Counter` associated with specified label
23+
* values.
24+
*/
25+
export class CounterHandle implements types.CounterHandle {
26+
private _data = 0;
27+
28+
constructor(
29+
private readonly _disabled: boolean,
30+
private readonly _monotonic: boolean,
31+
private readonly _labelValues: string[],
32+
private readonly _logger: types.Logger
33+
) {}
34+
35+
add(value: number): void {
36+
if (this._disabled) return;
37+
38+
if (this._monotonic && value < 0) {
39+
this._logger.error('Monotonic counter cannot descend.');
40+
return;
41+
}
42+
this._data = this._data + value;
43+
}
44+
45+
/**
46+
* Returns the TimeSeries with one or more Point.
47+
*
48+
* @param timestamp The time at which the counter is recorded.
49+
* @returns The TimeSeries.
50+
*/
51+
getTimeSeries(timestamp: types.HrTime): TimeSeries {
52+
return {
53+
labelValues: this._labelValues.map(value => ({ value })),
54+
points: [{ value: this._data, timestamp }],
55+
};
56+
}
57+
}
58+
59+
/**
60+
* GaugeHandle allows the SDK to observe/record a single metric event. The
61+
* value of single handle in the `Gauge` associated with specified label values.
62+
*/
63+
export class GaugeHandle implements types.GaugeHandle {
64+
private _data = 0;
65+
66+
constructor(
67+
private readonly _disabled: boolean,
68+
private readonly _monotonic: boolean,
69+
private readonly _labelValues: string[],
70+
private readonly _logger: types.Logger
71+
) {}
72+
73+
set(value: number): void {
74+
if (this._disabled) return;
75+
76+
if (this._monotonic && value < this._data) {
77+
this._logger.error('Monotonic gauge cannot descend.');
78+
return;
79+
}
80+
this._data = value;
81+
}
82+
83+
/**
84+
* Returns the TimeSeries with one or more Point.
85+
*
86+
* @param timestamp The time at which the gauge is recorded.
87+
* @returns The TimeSeries.
88+
*/
89+
getTimeSeries(timestamp: types.HrTime): TimeSeries {
90+
return {
91+
labelValues: this._labelValues.map(value => ({ value })),
92+
points: [{ value: this._data, timestamp }],
93+
};
94+
}
95+
}
96+
97+
/**
98+
* MeasureHandle is an implementation of the {@link MeasureHandle} interface.
99+
*/
100+
export class MeasureHandle implements types.MeasureHandle {
101+
record(
102+
value: number,
103+
distContext?: types.DistributedContext,
104+
spanContext?: types.SpanContext
105+
): void {
106+
// @todo: implement this method.
107+
return;
108+
}
109+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*!
2+
* Copyright 2019, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as types from '@opentelemetry/types';
18+
import { ConsoleLogger } from '@opentelemetry/core';
19+
import { CounterHandle, GaugeHandle, MeasureHandle } from './Handle';
20+
import { Metric, CounterMetric, GaugeMetric } from './Metric';
21+
import {
22+
MetricOptions,
23+
DEFAULT_METRIC_OPTIONS,
24+
DEFAULT_CONFIG,
25+
MeterConfig,
26+
} from './types';
27+
28+
/**
29+
* Meter is an implementation of the {@link Meter} interface.
30+
*/
31+
export class Meter implements types.Meter {
32+
private readonly _logger: types.Logger;
33+
34+
/**
35+
* Constructs a new Meter instance.
36+
*/
37+
constructor(config: MeterConfig = DEFAULT_CONFIG) {
38+
this._logger = config.logger || new ConsoleLogger(config.logLevel);
39+
}
40+
41+
/**
42+
* Creates and returns a new {@link Measure}.
43+
* @param name the name of the metric.
44+
* @param [options] the metric options.
45+
*/
46+
createMeasure(
47+
name: string,
48+
options?: types.MetricOptions
49+
): Metric<MeasureHandle> {
50+
// @todo: implement this method
51+
throw new Error('not implemented yet');
52+
}
53+
54+
/**
55+
* Creates a new counter metric. Generally, this kind of metric when the
56+
* value is a quantity, the sum is of primary interest, and the event count
57+
* and value distribution are not of primary interest.
58+
* @param name the name of the metric.
59+
* @param [options] the metric options.
60+
*/
61+
createCounter(
62+
name: string,
63+
options?: types.MetricOptions
64+
): Metric<CounterHandle> {
65+
const opt: MetricOptions = {
66+
// Counters are defined as monotonic by default
67+
monotonic: true,
68+
logger: this._logger,
69+
...DEFAULT_METRIC_OPTIONS,
70+
...options,
71+
};
72+
return new CounterMetric(name, opt);
73+
}
74+
75+
/**
76+
* Creates a new gauge metric. Generally, this kind of metric should be used
77+
* when the metric cannot be expressed as a sum or because the measurement
78+
* interval is arbitrary. Use this kind of metric when the measurement is not
79+
* a quantity, and the sum and event count are not of interest.
80+
* @param name the name of the metric.
81+
* @param [options] the metric options.
82+
*/
83+
createGauge(
84+
name: string,
85+
options?: types.MetricOptions
86+
): Metric<GaugeHandle> {
87+
const opt: MetricOptions = {
88+
// Gauges are defined as non-monotonic by default
89+
monotonic: false,
90+
logger: this._logger,
91+
...DEFAULT_METRIC_OPTIONS,
92+
...options,
93+
};
94+
return new GaugeMetric(name, opt);
95+
}
96+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*!
2+
* Copyright 2019, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as types from '@opentelemetry/types';
18+
import { hashLabelValues } from './Utils';
19+
import { CounterHandle, GaugeHandle } from './Handle';
20+
import { MetricOptions } from './types';
21+
22+
/** This is a SDK implementation of {@link Metric} interface. */
23+
export abstract class Metric<T> implements types.Metric<T> {
24+
protected readonly _monotonic: boolean;
25+
protected readonly _disabled: boolean;
26+
protected readonly _logger: types.Logger;
27+
private readonly _handles: Map<String, T> = new Map();
28+
29+
constructor(name: string, options: MetricOptions) {
30+
this._monotonic = options.monotonic;
31+
this._disabled = options.disabled;
32+
this._logger = options.logger;
33+
}
34+
35+
/**
36+
* Returns a Handle associated with specified label values.
37+
* It is recommended to keep a reference to the Handle instead of always
38+
* calling this method for each operation.
39+
* @param labelValues the list of label values.
40+
*/
41+
getHandle(labelValues: string[]): T {
42+
const hash = hashLabelValues(labelValues);
43+
if (this._handles.has(hash)) return this._handles.get(hash)!;
44+
45+
const handle = this._makeHandle(labelValues);
46+
this._handles.set(hash, handle);
47+
return handle;
48+
}
49+
50+
/**
51+
* Returns a Handle for a metric with all labels not set.
52+
*/
53+
getDefaultHandle(): T {
54+
// @todo: implement this method
55+
this._logger.error('not implemented yet');
56+
throw new Error('not implemented yet');
57+
}
58+
59+
/**
60+
* Removes the Handle from the metric, if it is present.
61+
* @param labelValues the list of label values.
62+
*/
63+
removeHandle(labelValues: string[]): void {
64+
this._handles.delete(hashLabelValues(labelValues));
65+
}
66+
67+
/**
68+
* Clears all Handles from the Metric.
69+
*/
70+
clear(): void {
71+
this._handles.clear();
72+
}
73+
74+
setCallback(fn: () => void): void {
75+
// @todo: implement this method
76+
this._logger.error('not implemented yet');
77+
return;
78+
}
79+
80+
protected abstract _makeHandle(labelValues: string[]): T;
81+
}
82+
83+
/** This is a SDK implementation of Counter Metric. */
84+
export class CounterMetric extends Metric<CounterHandle> {
85+
constructor(name: string, options: MetricOptions) {
86+
super(name, options);
87+
}
88+
protected _makeHandle(labelValues: string[]): CounterHandle {
89+
return new CounterHandle(
90+
this._disabled,
91+
this._monotonic,
92+
labelValues,
93+
this._logger
94+
);
95+
}
96+
}
97+
98+
/** This is a SDK implementation of Gauge Metric. */
99+
export class GaugeMetric extends Metric<GaugeHandle> {
100+
constructor(name: string, options: MetricOptions) {
101+
super(name, options);
102+
}
103+
protected _makeHandle(labelValues: string[]): GaugeHandle {
104+
return new GaugeHandle(
105+
this._disabled,
106+
this._monotonic,
107+
labelValues,
108+
this._logger
109+
);
110+
}
111+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*!
2+
* Copyright 2019, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const COMMA_SEPARATOR = ',';
18+
19+
/**
20+
* Returns a string(comma separated) from the list of label values.
21+
*
22+
* @param labelValues The list of the label values.
23+
* @returns The hashed label values string.
24+
*/
25+
export function hashLabelValues(labelValues: string[]): string {
26+
return labelValues.sort().join(COMMA_SEPARATOR);
27+
}

packages/opentelemetry-metrics/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
17+
export * from './Handle';
18+
export * from './Meter';
19+
export * from './Metric';

0 commit comments

Comments
 (0)