From a834861418de8d162f9842e9d5640d19ed3a68a8 Mon Sep 17 00:00:00 2001 From: Povilas Versockas Date: Wed, 20 Nov 2024 08:08:54 -0800 Subject: [PATCH] feat(sdk-metrics): add aggregation cardinality limit (#5128) Co-authored-by: Marc Pichler --- CHANGELOG.md | 2 + .../src/export/CardinalitySelector.ts | 21 ++ .../sdk-metrics/src/export/MetricReader.ts | 18 ++ .../src/state/AsyncMetricStorage.ts | 8 +- .../src/state/DeltaMetricProcessor.ts | 47 ++++- .../sdk-metrics/src/state/MeterSharedState.ts | 13 +- .../sdk-metrics/src/state/MetricCollector.ts | 9 + .../src/state/SyncMetricStorage.ts | 8 +- packages/sdk-metrics/src/view/View.ts | 15 ++ .../sdk-metrics/test/MeterProvider.test.ts | 179 ++++++++++++++++++ .../test/state/AsyncMetricStorage.test.ts | 2 + .../test/state/DeltaMetricProcessor.test.ts | 29 +++ .../test/state/MetricStorageRegistry.test.ts | 6 + .../test/state/SyncMetricStorage.test.ts | 2 + .../state/TemporalMetricProcessor.test.ts | 3 + 15 files changed, 349 insertions(+), 13 deletions(-) create mode 100644 packages/sdk-metrics/src/export/CardinalitySelector.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bafe75e9601..adbcd497163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ For semantic convention package changes, see the [semconv CHANGELOG](packages/se ### :boom: Breaking Change +* feat(sdk-metrics): Add support for aggregation cardinality limit with a default limit of 2000. This limit can be customized via views [#5182](https://github.com/open-telemetry/opentelemetry-js/pull/5128) + ### :rocket: (Enhancement) ### :bug: (Bug Fix) diff --git a/packages/sdk-metrics/src/export/CardinalitySelector.ts b/packages/sdk-metrics/src/export/CardinalitySelector.ts new file mode 100644 index 00000000000..a274898b9a2 --- /dev/null +++ b/packages/sdk-metrics/src/export/CardinalitySelector.ts @@ -0,0 +1,21 @@ +/* + * 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 { InstrumentType } from '../InstrumentDescriptor'; +/** + * Cardinality Limit selector based on metric instrument types. + */ +export type CardinalitySelector = (instrumentType: InstrumentType) => number; diff --git a/packages/sdk-metrics/src/export/MetricReader.ts b/packages/sdk-metrics/src/export/MetricReader.ts index 8aad601d70f..e87d55884db 100644 --- a/packages/sdk-metrics/src/export/MetricReader.ts +++ b/packages/sdk-metrics/src/export/MetricReader.ts @@ -32,6 +32,7 @@ import { DEFAULT_AGGREGATION_SELECTOR, DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR, } from './AggregationSelector'; +import { CardinalitySelector } from './CardinalitySelector'; export interface MetricReaderOptions { /** @@ -45,6 +46,11 @@ export interface MetricReaderOptions { * not configured, cumulative is used for all instruments. */ aggregationTemporalitySelector?: AggregationTemporalitySelector; + /** + * Cardinality selector based on metric instrument types. If not configured, + * a default value is used. + */ + cardinalitySelector?: CardinalitySelector; /** * **Note, this option is experimental**. Additional MetricProducers to use as a source of * aggregated metric data in addition to the SDK's metric data. The resource returned by @@ -68,6 +74,7 @@ export abstract class MetricReader { private _sdkMetricProducer?: MetricProducer; private readonly _aggregationTemporalitySelector: AggregationTemporalitySelector; private readonly _aggregationSelector: AggregationSelector; + private readonly _cardinalitySelector?: CardinalitySelector; constructor(options?: MetricReaderOptions) { this._aggregationSelector = @@ -76,6 +83,7 @@ export abstract class MetricReader { options?.aggregationTemporalitySelector ?? DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR; this._metricProducers = options?.metricProducers ?? []; + this._cardinalitySelector = options?.cardinalitySelector; } /** @@ -116,6 +124,16 @@ export abstract class MetricReader { return this._aggregationTemporalitySelector(instrumentType); } + /** + * Select the cardinality limit for the given {@link InstrumentType} for this + * reader. + */ + selectCardinalityLimit(instrumentType: InstrumentType): number { + return this._cardinalitySelector + ? this._cardinalitySelector(instrumentType) + : 2000; // default value if no selector is provided + } + /** * Handle once the SDK has initialized this {@link MetricReader} * Overriding this method is optional. diff --git a/packages/sdk-metrics/src/state/AsyncMetricStorage.ts b/packages/sdk-metrics/src/state/AsyncMetricStorage.ts index 6bebafdc1f1..be2cf7f25ac 100644 --- a/packages/sdk-metrics/src/state/AsyncMetricStorage.ts +++ b/packages/sdk-metrics/src/state/AsyncMetricStorage.ts @@ -43,10 +43,14 @@ export class AsyncMetricStorage> _instrumentDescriptor: InstrumentDescriptor, aggregator: Aggregator, private _attributesProcessor: AttributesProcessor, - collectorHandles: MetricCollectorHandle[] + collectorHandles: MetricCollectorHandle[], + private _aggregationCardinalityLimit?: number ) { super(_instrumentDescriptor); - this._deltaMetricStorage = new DeltaMetricProcessor(aggregator); + this._deltaMetricStorage = new DeltaMetricProcessor( + aggregator, + this._aggregationCardinalityLimit + ); this._temporalMetricStorage = new TemporalMetricProcessor( aggregator, collectorHandles diff --git a/packages/sdk-metrics/src/state/DeltaMetricProcessor.ts b/packages/sdk-metrics/src/state/DeltaMetricProcessor.ts index 2764727de24..288885f8f93 100644 --- a/packages/sdk-metrics/src/state/DeltaMetricProcessor.ts +++ b/packages/sdk-metrics/src/state/DeltaMetricProcessor.ts @@ -15,7 +15,7 @@ */ import { Context, HrTime, Attributes } from '@opentelemetry/api'; -import { Maybe } from '../utils'; +import { Maybe, hashAttributes } from '../utils'; import { Accumulation, Aggregator } from '../aggregator/types'; import { AttributeHashMap } from './HashMap'; @@ -31,8 +31,17 @@ export class DeltaMetricProcessor> { // TODO: find a reasonable mean to clean the memo; // https://github.com/open-telemetry/opentelemetry-specification/pull/2208 private _cumulativeMemoStorage = new AttributeHashMap(); + private _cardinalityLimit: number; + private _overflowAttributes = { 'otel.metric.overflow': true }; + private _overflowHashCode: string; - constructor(private _aggregator: Aggregator) {} + constructor( + private _aggregator: Aggregator, + aggregationCardinalityLimit?: number + ) { + this._cardinalityLimit = (aggregationCardinalityLimit ?? 2000) - 1; + this._overflowHashCode = hashAttributes(this._overflowAttributes); + } record( value: number, @@ -40,10 +49,22 @@ export class DeltaMetricProcessor> { _context: Context, collectionTime: HrTime ) { - const accumulation = this._activeCollectionStorage.getOrDefault( - attributes, - () => this._aggregator.createAccumulation(collectionTime) - ); + let accumulation = this._activeCollectionStorage.get(attributes); + + if (!accumulation) { + if (this._activeCollectionStorage.size >= this._cardinalityLimit) { + const overflowAccumulation = this._activeCollectionStorage.getOrDefault( + this._overflowAttributes, + () => this._aggregator.createAccumulation(collectionTime) + ); + overflowAccumulation?.record(value); + return; + } + + accumulation = this._aggregator.createAccumulation(collectionTime); + this._activeCollectionStorage.set(attributes, accumulation); + } + accumulation?.record(value); } @@ -66,6 +87,19 @@ export class DeltaMetricProcessor> { hashCode )!; delta = this._aggregator.diff(previous, accumulation); + } else { + // If the cardinality limit is reached, we need to change the attributes + if (this._cumulativeMemoStorage.size >= this._cardinalityLimit) { + attributes = this._overflowAttributes; + hashCode = this._overflowHashCode; + if (this._cumulativeMemoStorage.has(attributes, hashCode)) { + const previous = this._cumulativeMemoStorage.get( + attributes, + hashCode + )!; + delta = this._aggregator.diff(previous, accumulation); + } + } } // Merge with uncollected active delta. if (this._activeCollectionStorage.has(attributes, hashCode)) { @@ -92,6 +126,7 @@ export class DeltaMetricProcessor> { collect() { const unreportedDelta = this._activeCollectionStorage; this._activeCollectionStorage = new AttributeHashMap(); + return unreportedDelta; } } diff --git a/packages/sdk-metrics/src/state/MeterSharedState.ts b/packages/sdk-metrics/src/state/MeterSharedState.ts index 2c0c1a5105b..028a43634e5 100644 --- a/packages/sdk-metrics/src/state/MeterSharedState.ts +++ b/packages/sdk-metrics/src/state/MeterSharedState.ts @@ -142,7 +142,8 @@ export class MeterSharedState { viewDescriptor, aggregator, view.attributesProcessor, - this._meterProviderSharedState.metricCollectors + this._meterProviderSharedState.metricCollectors, + view.aggregationCardinalityLimit ) as R; this.metricStorageRegistry.register(viewStorage); return viewStorage; @@ -162,12 +163,17 @@ export class MeterSharedState { if (compatibleStorage != null) { return compatibleStorage; } + const aggregator = aggregation.createAggregator(descriptor); + const cardinalityLimit = collector.selectCardinalityLimit( + descriptor.type + ); const storage = new MetricStorageType( descriptor, aggregator, AttributesProcessor.Noop(), - [collector] + [collector], + cardinalityLimit ) as R; this.metricStorageRegistry.registerForCollector(collector, storage); return storage; @@ -190,6 +196,7 @@ interface MetricStorageConstructor { instrumentDescriptor: InstrumentDescriptor, aggregator: Aggregator>, attributesProcessor: AttributesProcessor, - collectors: MetricCollectorHandle[] + collectors: MetricCollectorHandle[], + aggregationCardinalityLimit?: number ): MetricStorage; } diff --git a/packages/sdk-metrics/src/state/MetricCollector.ts b/packages/sdk-metrics/src/state/MetricCollector.ts index f1f1dacdb13..3b52a3e4d56 100644 --- a/packages/sdk-metrics/src/state/MetricCollector.ts +++ b/packages/sdk-metrics/src/state/MetricCollector.ts @@ -90,6 +90,14 @@ export class MetricCollector implements MetricProducer { selectAggregation(instrumentType: InstrumentType) { return this._metricReader.selectAggregation(instrumentType); } + + /** + * Select the cardinality limit for the given {@link InstrumentType} for this + * collector. + */ + selectCardinalityLimit(instrumentType: InstrumentType): number { + return this._metricReader.selectCardinalityLimit?.(instrumentType) ?? 2000; + } } /** @@ -98,4 +106,5 @@ export class MetricCollector implements MetricProducer { */ export interface MetricCollectorHandle { selectAggregationTemporality: AggregationTemporalitySelector; + selectCardinalityLimit(instrumentType: InstrumentType): number; } diff --git a/packages/sdk-metrics/src/state/SyncMetricStorage.ts b/packages/sdk-metrics/src/state/SyncMetricStorage.ts index 2e97d20d8d1..9d01e263861 100644 --- a/packages/sdk-metrics/src/state/SyncMetricStorage.ts +++ b/packages/sdk-metrics/src/state/SyncMetricStorage.ts @@ -42,10 +42,14 @@ export class SyncMetricStorage> instrumentDescriptor: InstrumentDescriptor, aggregator: Aggregator, private _attributesProcessor: AttributesProcessor, - collectorHandles: MetricCollectorHandle[] + collectorHandles: MetricCollectorHandle[], + private _aggregationCardinalityLimit?: number ) { super(instrumentDescriptor); - this._deltaMetricStorage = new DeltaMetricProcessor(aggregator); + this._deltaMetricStorage = new DeltaMetricProcessor( + aggregator, + this._aggregationCardinalityLimit + ); this._temporalMetricStorage = new TemporalMetricProcessor( aggregator, collectorHandles diff --git a/packages/sdk-metrics/src/view/View.ts b/packages/sdk-metrics/src/view/View.ts index 1e8d4fb0e05..9a8e7cc60f1 100644 --- a/packages/sdk-metrics/src/view/View.ts +++ b/packages/sdk-metrics/src/view/View.ts @@ -61,6 +61,15 @@ export type ViewOptions = { * aggregation: new LastValueAggregation() */ aggregation?: Aggregation; + /** + * Alters the metric stream: + * Sets a limit on the number of unique attribute combinations (cardinality) that can be aggregated. + * If not provided, the default limit will be used. + * + * @example sets the cardinality limit to 1000 + * aggregationCardinalityLimit: 1000 + */ + aggregationCardinalityLimit?: number; /** * Instrument selection criteria: * The original type of the Instrument(s). @@ -138,6 +147,7 @@ export class View { readonly attributesProcessor: AttributesProcessor; readonly instrumentSelector: InstrumentSelector; readonly meterSelector: MeterSelector; + readonly aggregationCardinalityLimit?: number; /** * Create a new {@link View} instance. @@ -161,6 +171,10 @@ export class View { * Alters the metric stream: * If provided, the attributes that are not in the list will be ignored. * If not provided, all attribute keys will be used by default. + * @param viewOptions.aggregationCardinalityLimit + * Alters the metric stream: + * Sets a limit on the number of unique attribute combinations (cardinality) that can be aggregated. + * If not provided, the default limit of 2000 will be used. * @param viewOptions.aggregation * Alters the metric stream: * Alters the {@link Aggregation} of the metric stream. @@ -232,5 +246,6 @@ export class View { version: viewOptions.meterVersion, schemaUrl: viewOptions.meterSchemaUrl, }); + this.aggregationCardinalityLimit = viewOptions.aggregationCardinalityLimit; } } diff --git a/packages/sdk-metrics/test/MeterProvider.test.ts b/packages/sdk-metrics/test/MeterProvider.test.ts index 450087ed865..cc534adcb1d 100644 --- a/packages/sdk-metrics/test/MeterProvider.test.ts +++ b/packages/sdk-metrics/test/MeterProvider.test.ts @@ -21,6 +21,7 @@ import { DataPointType, ExplicitBucketHistogramAggregation, HistogramMetricData, + DataPoint, } from '../src'; import { assertScopeMetrics, @@ -602,6 +603,184 @@ describe('MeterProvider', () => { }); }); + describe('aggregationCardinalityLimit with view should apply the cardinality limit', () => { + it('should respect the aggregationCardinalityLimit', async () => { + const reader = new TestMetricReader(); + const meterProvider = new MeterProvider({ + resource: defaultResource, + readers: [reader], + views: [ + new View({ + instrumentName: 'test-counter', + aggregationCardinalityLimit: 2, // Set cardinality limit to 2 + }), + ], + }); + + const meter = meterProvider.getMeter('meter1', 'v1.0.0'); + const counter = meter.createCounter('test-counter'); + + // Add values with different attributes + counter.add(1, { attr1: 'value1' }); + counter.add(1, { attr2: 'value2' }); + counter.add(1, { attr3: 'value3' }); + counter.add(1, { attr1: 'value1' }); + + // Perform collection + const { resourceMetrics, errors } = await reader.collect(); + + assert.strictEqual(errors.length, 0); + assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); + assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); + + const metricData = resourceMetrics.scopeMetrics[0].metrics[0]; + assert.strictEqual(metricData.dataPoints.length, 2); + + // Check if the overflow data point is present + const overflowDataPoint = ( + metricData.dataPoints as DataPoint[] + ).find((dataPoint: DataPoint) => + Object.prototype.hasOwnProperty.call( + dataPoint.attributes, + 'otel.metric.overflow' + ) + ); + assert.ok(overflowDataPoint); + assert.strictEqual(overflowDataPoint.value, 2); + }); + + it('should respect the aggregationCardinalityLimit for observable counter', async () => { + const reader = new TestMetricReader(); + const meterProvider = new MeterProvider({ + resource: defaultResource, + readers: [reader], + views: [ + new View({ + instrumentName: 'test-observable-counter', + aggregationCardinalityLimit: 2, // Set cardinality limit to 2 + }), + ], + }); + + const meter = meterProvider.getMeter('meter1', 'v1.0.0'); + const observableCounter = meter.createObservableCounter( + 'test-observable-counter' + ); + observableCounter.addCallback(observableResult => { + observableResult.observe(1, { attr1: 'value1' }); + observableResult.observe(2, { attr2: 'value2' }); + observableResult.observe(3, { attr3: 'value3' }); + observableResult.observe(4, { attr1: 'value1' }); + }); + + // Perform collection + const { resourceMetrics, errors } = await reader.collect(); + + assert.strictEqual(errors.length, 0); + assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); + assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); + + const metricData = resourceMetrics.scopeMetrics[0].metrics[0]; + assert.strictEqual(metricData.dataPoints.length, 2); + + // Check if the overflow data point is present + const overflowDataPoint = ( + metricData.dataPoints as DataPoint[] + ).find((dataPoint: DataPoint) => + Object.prototype.hasOwnProperty.call( + dataPoint.attributes, + 'otel.metric.overflow' + ) + ); + assert.ok(overflowDataPoint); + assert.strictEqual(overflowDataPoint.value, 3); + }); + }); + + describe('aggregationCardinalityLimit via MetricReader should apply the cardinality limit', () => { + it('should respect the aggregationCardinalityLimit set via MetricReader', async () => { + const reader = new TestMetricReader({ + cardinalitySelector: (instrumentType: InstrumentType) => 2, // Set cardinality limit to 2 via cardinalitySelector + }); + const meterProvider = new MeterProvider({ + resource: defaultResource, + readers: [reader], + }); + + const meter = meterProvider.getMeter('meter1', 'v1.0.0'); + const counter = meter.createCounter('test-counter'); + + // Add values with different attributes + counter.add(1, { attr1: 'value1' }); + counter.add(1, { attr2: 'value2' }); + counter.add(1, { attr3: 'value3' }); + counter.add(1, { attr1: 'value1' }); + + // Perform collection + const { resourceMetrics, errors } = await reader.collect(); + + assert.strictEqual(errors.length, 0); + assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); + assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); + + const metricData = resourceMetrics.scopeMetrics[0].metrics[0]; + assert.strictEqual(metricData.dataPoints.length, 2); + + // Check if the overflow data point is present + const overflowDataPoint = ( + metricData.dataPoints as DataPoint[] + ).find((dataPoint: DataPoint) => + Object.prototype.hasOwnProperty.call( + dataPoint.attributes, + 'otel.metric.overflow' + ) + ); + assert.ok(overflowDataPoint); + assert.strictEqual(overflowDataPoint.value, 2); + }); + }); + + describe('default aggregationCardinalityLimit should apply the cardinality limit', () => { + it('should respect the default aggregationCardinalityLimit', async () => { + const reader = new TestMetricReader(); + const meterProvider = new MeterProvider({ + resource: defaultResource, + readers: [reader], + }); + + const meter = meterProvider.getMeter('meter1', 'v1.0.0'); + const counter = meter.createCounter('test-counter'); + + // Add values with different attributes + for (let i = 0; i < 2001; i++) { + const attributes = { [`attr${i}`]: `value${i}` }; + counter.add(1, attributes); + } + + // Perform collection + const { resourceMetrics, errors } = await reader.collect(); + + assert.strictEqual(errors.length, 0); + assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); + assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); + + const metricData = resourceMetrics.scopeMetrics[0].metrics[0]; + assert.strictEqual(metricData.dataPoints.length, 2000); + + // Check if the overflow data point is present + const overflowDataPoint = ( + metricData.dataPoints as DataPoint[] + ).find((dataPoint: DataPoint) => + Object.prototype.hasOwnProperty.call( + dataPoint.attributes, + 'otel.metric.overflow' + ) + ); + assert.ok(overflowDataPoint); + assert.strictEqual(overflowDataPoint.value, 2); + }); + }); + describe('shutdown', () => { it('should shutdown all registered metric readers', async () => { const reader1 = new TestMetricReader(); diff --git a/packages/sdk-metrics/test/state/AsyncMetricStorage.test.ts b/packages/sdk-metrics/test/state/AsyncMetricStorage.test.ts index b4a5df19238..e940c71c18c 100644 --- a/packages/sdk-metrics/test/state/AsyncMetricStorage.test.ts +++ b/packages/sdk-metrics/test/state/AsyncMetricStorage.test.ts @@ -34,10 +34,12 @@ import { HrTime } from '@opentelemetry/api'; const deltaCollector: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.DELTA, + selectCardinalityLimit: () => 2000, }; const cumulativeCollector: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.CUMULATIVE, + selectCardinalityLimit: () => 2000, }; describe('AsyncMetricStorage', () => { diff --git a/packages/sdk-metrics/test/state/DeltaMetricProcessor.test.ts b/packages/sdk-metrics/test/state/DeltaMetricProcessor.test.ts index ec0a3d6fff3..a14f89df53a 100644 --- a/packages/sdk-metrics/test/state/DeltaMetricProcessor.test.ts +++ b/packages/sdk-metrics/test/state/DeltaMetricProcessor.test.ts @@ -120,6 +120,35 @@ describe('DeltaMetricProcessor', () => { const accumulation = accumulations.get({}); assert.strictEqual(accumulation?.toPointValue(), 20); }); + + it('should respect the cardinality limit', () => { + const cardinalityLimit = 2; + const metricProcessor = new DeltaMetricProcessor( + new SumAggregator(true), + cardinalityLimit + ); + + { + const measurements = new AttributeHashMap(); + measurements.set({ attribute: '1' }, 10); + measurements.set({ attribute: '2' }, 20); + measurements.set({ attribute: '3' }, 30); + metricProcessor.batchCumulate(measurements, [0, 0]); + } + + const accumulations = metricProcessor.collect(); + assert.strictEqual(accumulations.size, 2); + { + const accumulation = accumulations.get({ attribute: '1' }); + assert.strictEqual(accumulation?.toPointValue(), 10); + } + { + const overflowAccumulation = accumulations.get({ + 'otel.metric.overflow': true, + }); + assert.strictEqual(overflowAccumulation?.toPointValue(), 30); + } + }); }); describe('collect', () => { diff --git a/packages/sdk-metrics/test/state/MetricStorageRegistry.test.ts b/packages/sdk-metrics/test/state/MetricStorageRegistry.test.ts index 8a1513e351c..35e3faa1fa9 100644 --- a/packages/sdk-metrics/test/state/MetricStorageRegistry.test.ts +++ b/packages/sdk-metrics/test/state/MetricStorageRegistry.test.ts @@ -58,11 +58,17 @@ describe('MetricStorageRegistry', () => { selectAggregationTemporality: () => { throw new Error('should not be invoked'); }, + selectCardinalityLimit: () => { + throw new Error('should not be invoked'); + }, }; const collectorHandle2: MetricCollectorHandle = { selectAggregationTemporality: () => { throw new Error('should not be invoked'); }, + selectCardinalityLimit: () => { + throw new Error('should not be invoked'); + }, }; describe('register', () => { diff --git a/packages/sdk-metrics/test/state/SyncMetricStorage.test.ts b/packages/sdk-metrics/test/state/SyncMetricStorage.test.ts index e2e0378a454..e12a291a9d5 100644 --- a/packages/sdk-metrics/test/state/SyncMetricStorage.test.ts +++ b/packages/sdk-metrics/test/state/SyncMetricStorage.test.ts @@ -33,10 +33,12 @@ import { const deltaCollector: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.DELTA, + selectCardinalityLimit: () => 2000, }; const cumulativeCollector: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.CUMULATIVE, + selectCardinalityLimit: () => 2000, }; describe('SyncMetricStorage', () => { diff --git a/packages/sdk-metrics/test/state/TemporalMetricProcessor.test.ts b/packages/sdk-metrics/test/state/TemporalMetricProcessor.test.ts index 77edc36b17f..932d45c5f15 100644 --- a/packages/sdk-metrics/test/state/TemporalMetricProcessor.test.ts +++ b/packages/sdk-metrics/test/state/TemporalMetricProcessor.test.ts @@ -31,14 +31,17 @@ import { const deltaCollector1: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.DELTA, + selectCardinalityLimit: () => 2000, }; const deltaCollector2: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.DELTA, + selectCardinalityLimit: () => 2000, }; const cumulativeCollector1: MetricCollectorHandle = { selectAggregationTemporality: () => AggregationTemporality.CUMULATIVE, + selectCardinalityLimit: () => 2000, }; describe('TemporalMetricProcessor', () => {