Skip to content

Commit 173cab2

Browse files
authored
feat(metrics): add registerMetric and getMetrics (#437)
* feat(metrics): add registerMetric and getMetric functionality * fix: getTimeSeries and add more tests * fix: minor * fix: add JSDoc * fix: minor * fix: remove * fix: replace String -> string * fix: avoid casting * fix: use GAUGE_DOUBLE and COUNTER_DOUBLE type * fix: add ValueType to indicate type of the metric * fix: move ValueType.DOUBLE to DEFAULT_METRIC_OPTIONS * fix: use Number.isInteger * fix: log an error instead of throw error * fix: add more test and @todo comment * fix: link #474 isssue
1 parent 283458e commit 173cab2

File tree

8 files changed

+350
-53
lines changed

8 files changed

+350
-53
lines changed

packages/opentelemetry-metrics/src/Handle.ts

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,87 +17,103 @@
1717
import * as types from '@opentelemetry/types';
1818
import { TimeSeries } from './export/types';
1919

20+
/**
21+
* This class represent the base to handle, which is responsible for generating
22+
* the TimeSeries.
23+
*/
24+
export class BaseHandle {
25+
protected _data = 0;
26+
27+
constructor(private readonly _labels: string[]) {}
28+
29+
/**
30+
* Returns the TimeSeries with one or more Point.
31+
*
32+
* @param timestamp The time at which the handle is recorded.
33+
* @returns The TimeSeries.
34+
*/
35+
getTimeSeries(timestamp: types.HrTime): TimeSeries {
36+
return {
37+
labelValues: this._labels.map(value => ({ value })),
38+
points: [{ value: this._data, timestamp }],
39+
};
40+
}
41+
}
42+
2043
/**
2144
* CounterHandle allows the SDK to observe/record a single metric event. The
2245
* value of single handle in the `Counter` associated with specified label
2346
* values.
2447
*/
25-
export class CounterHandle implements types.CounterHandle {
26-
private _data = 0;
27-
48+
export class CounterHandle extends BaseHandle implements types.CounterHandle {
2849
constructor(
2950
private readonly _disabled: boolean,
3051
private readonly _monotonic: boolean,
52+
private readonly _valueType: types.ValueType,
3153
private readonly _labelValues: string[],
3254
private readonly _logger: types.Logger
33-
) {}
55+
) {
56+
super(_labelValues);
57+
}
3458

3559
add(value: number): void {
3660
if (this._disabled) return;
3761

3862
if (this._monotonic && value < 0) {
39-
this._logger.error('Monotonic counter cannot descend.');
63+
this._logger.error(
64+
`Monotonic counter cannot descend for ${this._labelValues}`
65+
);
4066
return;
4167
}
68+
if (this._valueType === types.ValueType.INT && !Number.isInteger(value)) {
69+
this._logger.warn(
70+
`INT counter cannot accept a floating-point value for ${this._labelValues}, ignoring the fractional digits.`
71+
);
72+
value = Math.trunc(value);
73+
}
4274
this._data = this._data + value;
4375
}
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-
}
5776
}
5877

5978
/**
6079
* GaugeHandle allows the SDK to observe/record a single metric event. The
6180
* value of single handle in the `Gauge` associated with specified label values.
6281
*/
63-
export class GaugeHandle implements types.GaugeHandle {
64-
private _data = 0;
65-
82+
export class GaugeHandle extends BaseHandle implements types.GaugeHandle {
6683
constructor(
6784
private readonly _disabled: boolean,
6885
private readonly _monotonic: boolean,
86+
private readonly _valueType: types.ValueType,
6987
private readonly _labelValues: string[],
7088
private readonly _logger: types.Logger
71-
) {}
89+
) {
90+
super(_labelValues);
91+
}
7292

7393
set(value: number): void {
7494
if (this._disabled) return;
7595

7696
if (this._monotonic && value < this._data) {
77-
this._logger.error('Monotonic gauge cannot descend.');
97+
this._logger.error(
98+
`Monotonic gauge cannot descend for ${this._labelValues}`
99+
);
78100
return;
79101
}
80-
this._data = value;
81-
}
82102

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-
};
103+
if (this._valueType === types.ValueType.INT && !Number.isInteger(value)) {
104+
this._logger.warn(
105+
`INT gauge cannot accept a floating-point value for ${this._labelValues}, ignoring the fractional digits.`
106+
);
107+
value = Math.trunc(value);
108+
}
109+
this._data = value;
94110
}
95111
}
96112

97113
/**
98114
* MeasureHandle is an implementation of the {@link MeasureHandle} interface.
99115
*/
100-
export class MeasureHandle implements types.MeasureHandle {
116+
export class MeasureHandle extends BaseHandle implements types.MeasureHandle {
101117
record(
102118
value: number,
103119
distContext?: types.DistributedContext,

packages/opentelemetry-metrics/src/Meter.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ import {
2121
NOOP_GAUGE_METRIC,
2222
NOOP_MEASURE_METRIC,
2323
} from '@opentelemetry/core';
24-
import { CounterMetric, GaugeMetric } from './Metric';
24+
import { BaseHandle } from './Handle';
25+
import { Metric, CounterMetric, GaugeMetric } from './Metric';
2526
import {
2627
MetricOptions,
2728
DEFAULT_METRIC_OPTIONS,
2829
DEFAULT_CONFIG,
2930
MeterConfig,
3031
} from './types';
32+
import { ReadableMetric } from './export/types';
3133

3234
/**
3335
* Meter is an implementation of the {@link Meter} interface.
3436
*/
3537
export class Meter implements types.Meter {
3638
private readonly _logger: types.Logger;
39+
private readonly _metrics = new Map();
3740

3841
/**
3942
* Constructs a new Meter instance.
@@ -85,7 +88,9 @@ export class Meter implements types.Meter {
8588
...DEFAULT_METRIC_OPTIONS,
8689
...options,
8790
};
88-
return new CounterMetric(name, opt);
91+
const counter = new CounterMetric(name, opt);
92+
this._registerMetric(name, counter);
93+
return counter;
8994
}
9095

9196
/**
@@ -113,7 +118,38 @@ export class Meter implements types.Meter {
113118
...DEFAULT_METRIC_OPTIONS,
114119
...options,
115120
};
116-
return new GaugeMetric(name, opt);
121+
const gauge = new GaugeMetric(name, opt);
122+
this._registerMetric(name, gauge);
123+
return gauge;
124+
}
125+
126+
/**
127+
* Gets a collection of Metric`s to be exported.
128+
* @returns The list of metrics.
129+
*/
130+
getMetrics(): ReadableMetric[] {
131+
return Array.from(this._metrics.values())
132+
.map(metric => metric.get())
133+
.filter(metric => !!metric);
134+
}
135+
136+
/**
137+
* Registers metric to register.
138+
* @param name The name of the metric.
139+
* @param metric The metric to register.
140+
*/
141+
private _registerMetric<T extends BaseHandle>(
142+
name: string,
143+
metric: Metric<T>
144+
): void {
145+
if (this._metrics.has(name)) {
146+
// @todo (issue/474): decide how to handle already registered metric
147+
this._logger.error(
148+
`A metric with the name ${name} has already been registered.`
149+
);
150+
return;
151+
}
152+
this._metrics.set(name, metric);
117153
}
118154

119155
/**

packages/opentelemetry-metrics/src/Metric.ts

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,35 @@
1515
*/
1616

1717
import * as types from '@opentelemetry/types';
18+
import { hrTime } from '@opentelemetry/core';
1819
import { hashLabelValues } from './Utils';
19-
import { CounterHandle, GaugeHandle } from './Handle';
20+
import { CounterHandle, GaugeHandle, BaseHandle } from './Handle';
2021
import { MetricOptions } from './types';
22+
import {
23+
ReadableMetric,
24+
MetricDescriptor,
25+
MetricDescriptorType,
26+
} from './export/types';
2127

2228
/** This is a SDK implementation of {@link Metric} interface. */
23-
export abstract class Metric<T> implements types.Metric<T> {
29+
export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
2430
protected readonly _monotonic: boolean;
2531
protected readonly _disabled: boolean;
32+
protected readonly _valueType: types.ValueType;
2633
protected readonly _logger: types.Logger;
27-
private readonly _handles: Map<String, T> = new Map();
34+
private readonly _metricDescriptor: MetricDescriptor;
35+
private readonly _handles: Map<string, T> = new Map();
2836

29-
constructor(name: string, options: MetricOptions) {
30-
this._monotonic = options.monotonic;
31-
this._disabled = options.disabled;
32-
this._logger = options.logger;
37+
constructor(
38+
private readonly _name: string,
39+
private readonly _options: MetricOptions,
40+
private readonly _type: MetricDescriptorType
41+
) {
42+
this._monotonic = _options.monotonic;
43+
this._disabled = _options.disabled;
44+
this._valueType = _options.valueType;
45+
this._logger = _options.logger;
46+
this._metricDescriptor = this._getMetricDescriptor();
3347
}
3448

3549
/**
@@ -77,18 +91,52 @@ export abstract class Metric<T> implements types.Metric<T> {
7791
return;
7892
}
7993

94+
/**
95+
* Provides a ReadableMetric with one or more TimeSeries.
96+
* @returns The ReadableMetric, or null if TimeSeries is not present in
97+
* Metric.
98+
*/
99+
get(): ReadableMetric | null {
100+
if (this._handles.size === 0) return null;
101+
102+
const timestamp = hrTime();
103+
return {
104+
descriptor: this._metricDescriptor,
105+
timeseries: Array.from(this._handles, ([_, handle]) =>
106+
handle.getTimeSeries(timestamp)
107+
),
108+
};
109+
}
110+
111+
private _getMetricDescriptor(): MetricDescriptor {
112+
return {
113+
name: this._name,
114+
description: this._options.description,
115+
unit: this._options.unit,
116+
labelKeys: this._options.labelKeys,
117+
type: this._type,
118+
};
119+
}
120+
80121
protected abstract _makeHandle(labelValues: string[]): T;
81122
}
82123

83124
/** This is a SDK implementation of Counter Metric. */
84125
export class CounterMetric extends Metric<CounterHandle> {
85126
constructor(name: string, options: MetricOptions) {
86-
super(name, options);
127+
super(
128+
name,
129+
options,
130+
options.valueType === types.ValueType.DOUBLE
131+
? MetricDescriptorType.COUNTER_DOUBLE
132+
: MetricDescriptorType.COUNTER_INT64
133+
);
87134
}
88135
protected _makeHandle(labelValues: string[]): CounterHandle {
89136
return new CounterHandle(
90137
this._disabled,
91138
this._monotonic,
139+
this._valueType,
92140
labelValues,
93141
this._logger
94142
);
@@ -98,12 +146,19 @@ export class CounterMetric extends Metric<CounterHandle> {
98146
/** This is a SDK implementation of Gauge Metric. */
99147
export class GaugeMetric extends Metric<GaugeHandle> {
100148
constructor(name: string, options: MetricOptions) {
101-
super(name, options);
149+
super(
150+
name,
151+
options,
152+
options.valueType === types.ValueType.DOUBLE
153+
? MetricDescriptorType.GAUGE_DOUBLE
154+
: MetricDescriptorType.GAUGE_INT64
155+
);
102156
}
103157
protected _makeHandle(labelValues: string[]): GaugeHandle {
104158
return new GaugeHandle(
105159
this._disabled,
106160
this._monotonic,
161+
this._valueType,
107162
labelValues,
108163
this._logger
109164
);

packages/opentelemetry-metrics/src/export/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface ReadableMetric {
4343

4444
// The resource for the metric. If unset, it may be set to a default value
4545
// provided for a sequence of messages in an RPC stream.
46-
resource: Resource;
46+
resource?: Resource;
4747
}
4848

4949
/** Properties of a Metric type and its schema */

packages/opentelemetry-metrics/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
export * from './Handle';
1818
export * from './Meter';
1919
export * from './Metric';
20+
export * from './export/types';

packages/opentelemetry-metrics/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { LogLevel } from '@opentelemetry/core';
18-
import { Logger } from '@opentelemetry/types';
18+
import { Logger, ValueType } from '@opentelemetry/types';
1919

2020
/** Options needed for SDK metric creation. */
2121
export interface MetricOptions {
@@ -42,6 +42,9 @@ export interface MetricOptions {
4242

4343
/** User provided logger. */
4444
logger: Logger;
45+
46+
/** Indicates the type of the recorded value. */
47+
valueType: ValueType;
4548
}
4649

4750
/** MeterConfig provides an interface for configuring a Meter. */
@@ -64,4 +67,5 @@ export const DEFAULT_METRIC_OPTIONS = {
6467
description: '',
6568
unit: '1',
6669
labelKeys: [],
70+
valueType: ValueType.DOUBLE,
6771
};

0 commit comments

Comments
 (0)