Skip to content

Commit

Permalink
feat(api): add wrapMeter() for experimental metrics API features
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc committed Apr 18, 2024
1 parent 95e22f4 commit 9853047
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 39 deletions.
10 changes: 10 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
"default": "./build/src/experimental/index.js"
}
},
"typesVersions": {
"<4.9": {
"*": [
"./build/src/index.d.ts"
],
"experimental": [
"./build/src/experimental/index.d.ts"
]
}
},
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
"clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
Expand Down
9 changes: 9 additions & 0 deletions api/src/experimental/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Trace
export { wrapTracer, SugaredTracer } from './trace/SugaredTracer';
export { SugaredSpanOptions } from './trace/SugaredOptions';

// Metrics
export {
wrapMeter,
Gauge,
IExperimentalMeter,
} from './metrics/ExperimentalMeter';
129 changes: 129 additions & 0 deletions api/src/experimental/metrics/ExperimentalMeter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Meter } from '../../metrics/Meter';
import { MetricAttributes, MetricOptions } from '../../metrics/Metric';
import { Context } from '../../context/types';
import { NOOP_GAUGE_METRIC } from './Gauge';

/**
* @experimental
*
* Records only the last value that is added to it, discards any others.
*/
export interface Gauge<
AttributesTypes extends MetricAttributes = MetricAttributes,
> {
/**
* @experimental
* Records a measurement.
*/
record(value: number, attributes?: AttributesTypes, context?: Context): void;
}

/**
* Steps for adding new experimental Meter API features:
* - implement the specification in the `@opentelemetry/sdk-metrics` package, do NOT use any new types
* - SDKs may peer-depend on old API versions where these types are not yet available
* - we MUST duplicate any new types across API and SDK unless we drop support for older API versions
* - dropping support for old API versions MUST be done in SDK Major versions
* - failure to do this will result in failing builds for users that update the SDK but not the API
* - add the new functionality to the {@link IExperimentalMeter} interface
* - implement the functionality in the {@link ExperimentalMeter}
* - if the underlying {@link Meter} used as an {@link IExperimentalMeter} throws an error, return a No-Op implementation.
*
* Users may now use {@link wrapMeter} to get access to experimental SDK functionality via the API
*
* To stabilize a feature:
* - move the function interface from `IExperimentalMeter` to `Meter`
* - replace the implemented method with a property of the same type as the one from `Meter`, move any auxiliary types to stable
* - In ExperimentalMeter
* - remove the implementation that falls back to No-Op on errors.
* - assign the function that was moved to `Meter` to the property defined in the last step
* - Implement the no-op case in {@link NoopMeter}
* - ensure any `@experimental` annotations are removed
*/

/**
* @experimental
*
* Meter that offers experimental functionality IF that functionality is implemented by the SDK.
* MAY return a no-op otherwise. Stable features continue to work as expected.
*/
export interface IExperimentalMeter extends Meter {
/**
* @experimental Will be added to {@link Meter} in a future version when the specification is marked stable
*
* Creates and returns a new `Gauge`.
* @param name the name of the metric.
* @param [options] the metric options.
*/
createGauge<AttributesTypes extends MetricAttributes = MetricAttributes>(
name: string,
options?: MetricOptions
): Gauge<AttributesTypes>;
}

class ExperimentalMeter implements IExperimentalMeter {
private _meter: Meter;
constructor(meter: Meter) {
this._meter = meter;
this.addBatchObservableCallback =
meter.addBatchObservableCallback.bind(meter);
this.createCounter = meter.createCounter.bind(meter);
this.createObservableGauge = meter.createObservableGauge.bind(meter);
this.createHistogram = meter.createHistogram.bind(meter);
this.createObservableCounter = meter.createObservableCounter.bind(meter);
this.createObservableUpDownCounter =
meter.createObservableUpDownCounter.bind(meter);
this.createUpDownCounter = meter.createUpDownCounter.bind(meter);
this.removeBatchObservableCallback =
meter.removeBatchObservableCallback.bind(meter);
}
addBatchObservableCallback: Meter['addBatchObservableCallback'];
createCounter: Meter['createCounter'];
createObservableGauge: Meter['createObservableGauge'];
createHistogram: Meter['createHistogram'];
createObservableCounter: Meter['createObservableCounter'];
createUpDownCounter: Meter['createUpDownCounter'];
createObservableUpDownCounter: Meter['createObservableUpDownCounter'];
removeBatchObservableCallback: Meter['removeBatchObservableCallback'];

/**
* Creates and returns a new `Gauge`.
* @param name the name of the metric.
* @param [options] the metric options.
*/
createGauge(name: string, options?: MetricOptions): Gauge {
try {
return (this._meter as ExperimentalMeter).createGauge(name, options);
} catch (e) {
return NOOP_GAUGE_METRIC;
}
}
}

/**
* @experimental
*
* Wraps {@link Meter} so that it offers experimental functionality IF that functionality is implemented by the
* registered SDK. MAY return a no-op instrument if the functionality is not implemented by the SDK.
* Stable features continue to work as expected.
*
* @param meter
*/
export function wrapMeter(meter: Meter): IExperimentalMeter {
return new ExperimentalMeter(meter);
}
31 changes: 31 additions & 0 deletions api/src/experimental/metrics/Gauge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Gauge } from './ExperimentalMeter';
import { MetricAttributes } from '../../metrics/Metric';
import { NoopMetric } from '../../metrics/NoopMeter';

/**
* @experimetnal
*/
export class NoopGaugeMetric extends NoopMetric implements Gauge {
record(_value: number, _attributes: MetricAttributes): void {}
}

/**
* @experimental
*/
export const NOOP_GAUGE_METRIC = new NoopGaugeMetric();
1 change: 0 additions & 1 deletion api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export { MeterProvider } from './metrics/MeterProvider';
export {
ValueType,
Counter,
Gauge,
Histogram,
MetricOptions,
Observable,
Expand Down
11 changes: 0 additions & 11 deletions api/src/metrics/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import {
BatchObservableCallback,
Counter,
Gauge,
Histogram,
MetricAttributes,
MetricOptions,
Expand Down Expand Up @@ -46,16 +45,6 @@ export interface MeterOptions {
* for the exported metric are deferred.
*/
export interface Meter {
/**
* Creates and returns a new `Gauge`.
* @param name the name of the metric.
* @param [options] the metric options.
*/
createGauge<AttributesTypes extends MetricAttributes = MetricAttributes>(
name: string,
options?: MetricOptions
): Gauge<AttributesTypes>;

/**
* Creates and returns a new `Histogram`.
* @param name the name of the metric.
Expand Down
9 changes: 0 additions & 9 deletions api/src/metrics/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,6 @@ export interface UpDownCounter<
add(value: number, attributes?: AttributesTypes, context?: Context): void;
}

export interface Gauge<
AttributesTypes extends MetricAttributes = MetricAttributes,
> {
/**
* Records a measurement.
*/
record(value: number, attributes?: AttributesTypes, context?: Context): void;
}

export interface Histogram<
AttributesTypes extends MetricAttributes = MetricAttributes,
> {
Expand Down
13 changes: 0 additions & 13 deletions api/src/metrics/NoopMeter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Meter } from './Meter';
import {
BatchObservableCallback,
Counter,
Gauge,
Histogram,
MetricAttributes,
MetricOptions,
Expand All @@ -37,13 +36,6 @@ import {
export class NoopMeter implements Meter {
constructor() {}

/**
* @see {@link Meter.createGauge}
*/
createGauge(_name: string, _options?: MetricOptions): Histogram {
return NOOP_GAUGE_METRIC;
}

/**
* @see {@link Meter.createHistogram}
*/
Expand Down Expand Up @@ -122,10 +114,6 @@ export class NoopUpDownCounterMetric
add(_value: number, _attributes: MetricAttributes): void {}
}

export class NoopGaugeMetric extends NoopMetric implements Gauge {
record(_value: number, _attributes: MetricAttributes): void {}
}

export class NoopHistogramMetric extends NoopMetric implements Histogram {
record(_value: number, _attributes: MetricAttributes): void {}
}
Expand All @@ -152,7 +140,6 @@ export const NOOP_METER = new NoopMeter();

// Synchronous instruments
export const NOOP_COUNTER_METRIC = new NoopCounterMetric();
export const NOOP_GAUGE_METRIC = new NoopGaugeMetric();
export const NOOP_HISTOGRAM_METRIC = new NoopHistogramMetric();
export const NOOP_UP_DOWN_COUNTER_METRIC = new NoopUpDownCounterMetric();

Expand Down
7 changes: 4 additions & 3 deletions api/test/common/noop-implementations/noop-meter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import {
NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC,
NOOP_UP_DOWN_COUNTER_METRIC,
createNoopMeter,
NOOP_GAUGE_METRIC,
} from '../../../src/metrics/NoopMeter';
import { NoopMeterProvider } from '../../../src/metrics/NoopMeterProvider';
import { wrapMeter } from '../../../src/experimental';
import { NOOP_GAUGE_METRIC } from '../../../src/experimental/metrics/Gauge';

const attributes = {};
const options = {
Expand Down Expand Up @@ -119,12 +120,12 @@ describe('NoopMeter', () => {

it('gauge should not crash', () => {
const meter = new NoopMeterProvider().getMeter('test-noop');
const observableGauge = meter.createGauge('some-name');
const observableGauge = wrapMeter(meter).createGauge('some-name');

// ensure the correct noop const is returned
assert.strictEqual(observableGauge, NOOP_GAUGE_METRIC);

const gaugeWithOptions = meter.createGauge('some-name', options);
const gaugeWithOptions = wrapMeter(meter).createGauge('some-name', options);
assert.strictEqual(gaugeWithOptions, NOOP_GAUGE_METRIC);
});

Expand Down
5 changes: 3 additions & 2 deletions packages/sdk-metrics/test/Instruments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
defaultResource,
} from './util';
import { ObservableResult, ValueType } from '@opentelemetry/api';
import { wrapMeter } from '@opentelemetry/api/experimental';

describe('Instruments', () => {
describe('Counter', () => {
Expand Down Expand Up @@ -768,7 +769,7 @@ describe('Instruments', () => {
describe('Gauge', () => {
it('should record common values and attributes without exceptions', async () => {
const { meter } = setup();
const gauge = meter.createGauge('test');
const gauge = wrapMeter(meter).createGauge('test');

for (const values of commonValues) {
for (const attributes of commonAttributes) {
Expand All @@ -779,7 +780,7 @@ describe('Instruments', () => {

it('should record values', async () => {
const { meter, cumulativeReader } = setup();
const gauge = meter.createGauge('test');
const gauge = wrapMeter(meter).createGauge('test');

gauge.record(1, { foo: 'bar' });
gauge.record(-1);
Expand Down

0 comments on commit 9853047

Please sign in to comment.