Skip to content

Commit 2ba6dd5

Browse files
authored
feat(sdk-metrics-base): meter identity (#2901)
1 parent ed2f033 commit 2ba6dd5

File tree

9 files changed

+107
-23
lines changed

9 files changed

+107
-23
lines changed

experimental/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ All notable changes to experimental packages in this project will be documented
3939
* feat(instrumentation-xhr): add applyCustomAttributesOnSpan hook #2134 @mhennoch
4040
* feat(proto): add @opentelemetry/otlp-transformer package with hand-rolled transformation #2746 @dyladan
4141
* feat(sdk-metrics-base): shutdown and forceflush on MeterProvider #2890 @legendecas
42+
* feat(sdk-metrics-base): return the same meter for identical input to getMeter #2901 @legendecas
4243

4344
### :bug: (Bug Fix)
4445

experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,15 @@
1515
*/
1616

1717
import * as metrics from '@opentelemetry/api-metrics';
18-
import { InstrumentationLibrary } from '@opentelemetry/core';
1918
import { createInstrumentDescriptor, InstrumentType } from './InstrumentDescriptor';
2019
import { CounterInstrument, HistogramInstrument, UpDownCounterInstrument } from './Instruments';
21-
import { MeterProviderSharedState } from './state/MeterProviderSharedState';
2220
import { MeterSharedState } from './state/MeterSharedState';
2321

2422
/**
2523
* This class implements the {@link metrics.Meter} interface.
2624
*/
2725
export class Meter implements metrics.Meter {
28-
private _meterSharedState: MeterSharedState;
29-
30-
constructor(meterProviderSharedState: MeterProviderSharedState, instrumentationLibrary: InstrumentationLibrary) {
31-
this._meterSharedState = meterProviderSharedState.getMeterSharedState(instrumentationLibrary);
32-
}
26+
constructor(private _meterSharedState: MeterSharedState) {}
3327

3428
/**
3529
* Create a {@link metrics.Histogram} instrument.

experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import * as api from '@opentelemetry/api';
1818
import * as metrics from '@opentelemetry/api-metrics';
1919
import { Resource } from '@opentelemetry/resources';
20-
import { Meter } from './Meter';
2120
import { MetricReader } from './export/MetricReader';
2221
import { MeterProviderSharedState } from './state/MeterProviderSharedState';
2322
import { InstrumentSelector } from './view/InstrumentSelector';
@@ -115,7 +114,9 @@ export class MeterProvider implements metrics.MeterProvider {
115114
return metrics.NOOP_METER;
116115
}
117116

118-
return new Meter(this._sharedState, { name, version, schemaUrl: options.schemaUrl });
117+
return this._sharedState
118+
.getMeterSharedState({ name, version, schemaUrl: options.schemaUrl })
119+
.meter;
119120
}
120121

121122
/**

experimental/packages/opentelemetry-sdk-metrics-base/src/state/MeterProviderSharedState.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { HrTime } from '@opentelemetry/api';
1818
import { hrTime, InstrumentationLibrary } from '@opentelemetry/core';
1919
import { Resource } from '@opentelemetry/resources';
20+
import { instrumentationLibraryId } from '../utils';
2021
import { ViewRegistry } from '../view/ViewRegistry';
2122
import { MeterSharedState } from './MeterSharedState';
2223
import { MetricCollector } from './MetricCollector';
@@ -30,15 +31,17 @@ export class MeterProviderSharedState {
3031

3132
metricCollectors: MetricCollector[] = [];
3233

33-
meterSharedStates: MeterSharedState[] = [];
34+
meterSharedStates: Map<string, MeterSharedState> = new Map();
3435

3536
constructor(public resource: Resource) {}
3637

3738
getMeterSharedState(instrumentationLibrary: InstrumentationLibrary) {
38-
// TODO: meter identity
39-
// https://github.com/open-telemetry/opentelemetry-js/issues/2593
40-
const meterSharedState = new MeterSharedState(this, instrumentationLibrary);
41-
this.meterSharedStates.push(meterSharedState);
39+
const id = instrumentationLibraryId(instrumentationLibrary);
40+
let meterSharedState = this.meterSharedStates.get(id);
41+
if (meterSharedState == null) {
42+
meterSharedState = new MeterSharedState(this, instrumentationLibrary);
43+
this.meterSharedStates.set(id, meterSharedState);
44+
}
4245
return meterSharedState;
4346
}
4447
}

experimental/packages/opentelemetry-sdk-metrics-base/src/state/MeterSharedState.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import * as metrics from '@opentelemetry/api-metrics';
1919
import { InstrumentationLibrary } from '@opentelemetry/core';
2020
import { InstrumentationLibraryMetrics } from '../export/MetricData';
2121
import { createInstrumentDescriptorWithView, InstrumentDescriptor } from '../InstrumentDescriptor';
22+
import { Meter } from '../Meter';
2223
import { isNotNullish } from '../utils';
2324
import { AsyncMetricStorage } from './AsyncMetricStorage';
2425
import { MeterProviderSharedState } from './MeterProviderSharedState';
@@ -32,8 +33,11 @@ import { SyncMetricStorage } from './SyncMetricStorage';
3233
*/
3334
export class MeterSharedState {
3435
private _metricStorageRegistry = new MetricStorageRegistry();
36+
meter: Meter;
3537

36-
constructor(private _meterProviderSharedState: MeterProviderSharedState, private _instrumentationLibrary: InstrumentationLibrary) {}
38+
constructor(private _meterProviderSharedState: MeterProviderSharedState, private _instrumentationLibrary: InstrumentationLibrary) {
39+
this.meter = new Meter(this);
40+
}
3741

3842
registerMetricStorage(descriptor: InstrumentDescriptor) {
3943
const views = this._meterProviderSharedState.viewRegistry.findViews(descriptor, this._instrumentationLibrary);

experimental/packages/opentelemetry-sdk-metrics-base/src/state/MetricCollector.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ export class MetricCollector implements MetricProducer {
3535

3636
async collect(): Promise<ResourceMetrics> {
3737
const collectionTime = hrTime();
38-
const instrumentationLibraryMetrics = (await Promise.all(this._sharedState.meterSharedStates
39-
.map(meterSharedState => meterSharedState.collect(this, collectionTime))));
38+
const meterCollectionPromises = Array.from(this._sharedState.meterSharedStates.values())
39+
.map(meterSharedState => meterSharedState.collect(this, collectionTime));
40+
const instrumentationLibraryMetrics = await Promise.all(meterCollectionPromises);
4041

4142
return {
4243
resource: this._sharedState.resource,

experimental/packages/opentelemetry-sdk-metrics-base/src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { MetricAttributes } from '@opentelemetry/api-metrics';
18+
import { InstrumentationLibrary } from '@opentelemetry/core';
1819

1920
export type Maybe<T> = T | undefined;
2021

@@ -39,6 +40,14 @@ export function hashAttributes(attributes: MetricAttributes): string {
3940
}, '|#');
4041
}
4142

43+
/**
44+
* Converting the instrumentation library object to a unique identifier string.
45+
* @param instrumentationLibrary
46+
*/
47+
export function instrumentationLibraryId(instrumentationLibrary: InstrumentationLibrary): string {
48+
return `${instrumentationLibrary.name}:${instrumentationLibrary.version ?? ''}:${instrumentationLibrary.schemaUrl ?? ''}`;
49+
}
50+
4251
/**
4352
* Error that is thrown on timeouts.
4453
*/

experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,71 @@ import * as assert from 'assert';
1919
import { CounterInstrument, HistogramInstrument, UpDownCounterInstrument } from '../src/Instruments';
2020
import { Meter } from '../src/Meter';
2121
import { MeterProviderSharedState } from '../src/state/MeterProviderSharedState';
22+
import { MeterSharedState } from '../src/state/MeterSharedState';
2223
import { defaultInstrumentationLibrary, defaultResource } from './util';
2324

2425
const noopObservableCallback: ObservableCallback = _observableResult => {};
2526

2627
describe('Meter', () => {
2728
describe('createCounter', () => {
2829
it('should create counter', () => {
29-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
30+
const meterSharedState = new MeterSharedState(
31+
new MeterProviderSharedState(defaultResource),
32+
defaultInstrumentationLibrary);
33+
const meter = new Meter(meterSharedState);
3034
const counter = meter.createCounter('foobar');
3135
assert(counter instanceof CounterInstrument);
3236
});
3337
});
3438

3539
describe('createUpDownCounter', () => {
3640
it('should create up down counter', () => {
37-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
41+
const meterSharedState = new MeterSharedState(
42+
new MeterProviderSharedState(defaultResource),
43+
defaultInstrumentationLibrary);
44+
const meter = new Meter(meterSharedState);
3845
const counter = meter.createUpDownCounter('foobar');
3946
assert(counter instanceof UpDownCounterInstrument);
4047
});
4148
});
4249

4350
describe('createHistogram', () => {
4451
it('should create histogram', () => {
45-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
52+
const meterSharedState = new MeterSharedState(
53+
new MeterProviderSharedState(defaultResource),
54+
defaultInstrumentationLibrary);
55+
const meter = new Meter(meterSharedState);
4656
const counter = meter.createHistogram('foobar');
4757
assert(counter instanceof HistogramInstrument);
4858
});
4959
});
5060

5161
describe('createObservableGauge', () => {
5262
it('should create observable gauge', () => {
53-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
63+
const meterSharedState = new MeterSharedState(
64+
new MeterProviderSharedState(defaultResource),
65+
defaultInstrumentationLibrary);
66+
const meter = new Meter(meterSharedState);
5467
meter.createObservableGauge('foobar', noopObservableCallback);
5568
});
5669
});
5770

5871
describe('createObservableCounter', () => {
5972
it('should create observable counter', () => {
60-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
73+
const meterSharedState = new MeterSharedState(
74+
new MeterProviderSharedState(defaultResource),
75+
defaultInstrumentationLibrary);
76+
const meter = new Meter(meterSharedState);
6177
meter.createObservableCounter('foobar', noopObservableCallback);
6278
});
6379
});
6480

6581
describe('createObservableUpDownCounter', () => {
6682
it('should create observable up-down-counter', () => {
67-
const meter = new Meter(new MeterProviderSharedState(defaultResource), defaultInstrumentationLibrary);
83+
const meterSharedState = new MeterSharedState(
84+
new MeterProviderSharedState(defaultResource),
85+
defaultInstrumentationLibrary);
86+
const meter = new Meter(meterSharedState);
6887
meter.createObservableUpDownCounter('foobar', noopObservableCallback);
6988
});
7089
});

experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,64 @@ describe('MeterProvider', () => {
5050
assert(meter instanceof Meter);
5151
});
5252

53+
it('should get an identical meter on duplicated calls', () => {
54+
const meterProvider = new MeterProvider();
55+
const meter1 = meterProvider.getMeter('meter1', '1.0.0');
56+
const meter2 = meterProvider.getMeter('meter1', '1.0.0');
57+
assert.strictEqual(meter1, meter2);
58+
});
59+
5360
it('get a noop meter on shutdown', () => {
5461
const meterProvider = new MeterProvider();
5562
meterProvider.shutdown();
5663
const meter = meterProvider.getMeter('meter1', '1.0.0');
5764
assert.strictEqual(meter, NOOP_METER);
5865
});
66+
67+
it('get meter with same identity', async () => {
68+
const meterProvider = new MeterProvider({ resource: defaultResource });
69+
const reader = new TestMetricReader();
70+
meterProvider.addMetricReader(reader);
71+
72+
// Create meter and instrument.
73+
// name+version pair 1
74+
meterProvider.getMeter('meter1', 'v1.0.0');
75+
meterProvider.getMeter('meter1', 'v1.0.0');
76+
// name+version pair 2
77+
meterProvider.getMeter('meter2', 'v1.0.0');
78+
meterProvider.getMeter('meter2', 'v1.0.0');
79+
// name+version pair 3
80+
meterProvider.getMeter('meter1', 'v1.0.1');
81+
meterProvider.getMeter('meter1', 'v1.0.1');
82+
// name+version+schemaUrl pair 4
83+
meterProvider.getMeter('meter1', 'v1.0.1', { schemaUrl: 'https://opentelemetry.io/schemas/1.4.0' });
84+
meterProvider.getMeter('meter1', 'v1.0.1', { schemaUrl: 'https://opentelemetry.io/schemas/1.4.0' });
85+
86+
// Perform collection.
87+
const result = await reader.collect();
88+
89+
// Results came only from de-duplicated meters.
90+
assert.strictEqual(result?.instrumentationLibraryMetrics.length, 4);
91+
92+
// InstrumentationLibrary matches from de-duplicated meters.
93+
assertInstrumentationLibraryMetrics(result?.instrumentationLibraryMetrics[0], {
94+
name: 'meter1',
95+
version: 'v1.0.0'
96+
});
97+
assertInstrumentationLibraryMetrics(result?.instrumentationLibraryMetrics[1], {
98+
name: 'meter2',
99+
version: 'v1.0.0'
100+
});
101+
assertInstrumentationLibraryMetrics(result?.instrumentationLibraryMetrics[2], {
102+
name: 'meter1',
103+
version: 'v1.0.1'
104+
});
105+
assertInstrumentationLibraryMetrics(result?.instrumentationLibraryMetrics[3], {
106+
name: 'meter1',
107+
version: 'v1.0.1',
108+
schemaUrl: 'https://opentelemetry.io/schemas/1.4.0',
109+
});
110+
});
59111
});
60112

61113
describe('addView', () => {

0 commit comments

Comments
 (0)