From d6cf52e305aafe887f2caf8de2defe43e8d994fc Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 27 Jan 2022 17:40:40 +0800 Subject: [PATCH 1/2] test(sdk-metrics-base): test instrument interfaces --- .../test/Instruments.test.ts | 323 ++++++++++++++++++ .../test/util.ts | 18 +- 2 files changed, 335 insertions(+), 6 deletions(-) create mode 100644 experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts new file mode 100644 index 0000000000..a78d854378 --- /dev/null +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts @@ -0,0 +1,323 @@ +/* + * 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 * as assert from 'assert'; +import * as sinon from 'sinon'; +import { InstrumentationLibrary } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; +import { AggregationTemporality, InstrumentDescriptor, MeterProvider, MetricReader, PointData, PointDataType } from '../src'; +import { TestMetricReader } from './export/TestMetricReader'; +import { assertMetricData, assertPointData, commonValues, commonAttributes, defaultResource, defaultInstrumentationLibrary } from './util'; +import { Histogram } from '../src/aggregator/types'; +import { ObservableResult } from '@opentelemetry/api-metrics-wip'; + +describe('Instruments', () => { + describe('Counter', () => { + it('should add common values and attributes', () => { + const { meter } = setup(); + const counter = meter.createCounter('test'); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + counter.add(values, attributes); + } + } + }); + + it('should add valid values', async () => { + const { meter, cumulativeReader } = setup(); + const counter = meter.createCounter('test'); + + counter.add(1); + counter.add(1); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 2, + }, + ], + }); + + // add negative values should not be observable. + counter.add(-1); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 2, + }, + ], + }); + }); + }); + + describe('UpDownCounter', () => { + it('should add common values and attributes', () => { + const { meter } = setup(); + const upDownCounter = meter.createUpDownCounter('test'); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + upDownCounter.add(values, attributes); + } + } + }); + + it('should add values', async () => { + const { meter, deltaReader } = setup(); + const upDownCounter = meter.createUpDownCounter('test'); + + upDownCounter.add(3); + upDownCounter.add(-1); + await validateExport(deltaReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 2, + }, + ], + }); + }); + }); + + describe('Histogram', () => { + it('should record common values and attributes', () => { + const { meter } = setup(); + const histogram = meter.createHistogram('test'); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + + histogram.record(values, attributes); + } + } + }); + + it('should record values', async () => { + const { meter, deltaReader } = setup(); + const histogram = meter.createHistogram('test'); + + histogram.record(10); + histogram.record(-1); + await validateExport(deltaReader, { + pointDataType: PointDataType.HISTOGRAM, + pointData: [ + { + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + }, + count: 2, + sum: 9, + }, + }, + ], + }); + }); + }); + + describe('ObservableCounter', () => { + it('should observe common values and attributes', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableCounter('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let callCount = 0; + meter.createObservableCounter('test', observableResult => { + observableResult.observe(++callCount); + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 2, + }, + ], + }); + }); + }); + + describe('ObservableUpDownCounter', () => { + it('should observe common values and attributes', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableUpDownCounter('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let callCount = 0; + meter.createObservableUpDownCounter('test', observableResult => { + observableResult.observe(++callCount); + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 2, + }, + ], + }); + }); + }); + + describe('ObservableGauge', () => { + it('should observe common values and attributes', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableGauge('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let num = 0; + meter.createObservableGauge('test', observableResult => { + num += 10; + if (num === 30) { + observableResult.observe(-1); + } else { + observableResult.observe(num); + } + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 10, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: 20, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + point: -1, + }, + ], + }); + }); + }); +}); + +function setup() { + const meterProvider = new MeterProvider({ resource: defaultResource }); + const meter = meterProvider.getMeter(defaultInstrumentationLibrary.name, defaultInstrumentationLibrary.version, { + schemaUrl: defaultInstrumentationLibrary.schemaUrl, + }); + const deltaReader = new TestMetricReader(AggregationTemporality.DELTA); + meterProvider.addMetricReader(deltaReader); + const cumulativeReader = new TestMetricReader(AggregationTemporality.CUMULATIVE); + meterProvider.addMetricReader(cumulativeReader); + + return { + meterProvider, + meter, + deltaReader, + cumulativeReader, + }; +} + +interface ValidateMetricData { + resource?: Resource; + instrumentationLibrary?: InstrumentationLibrary; + instrumentDescriptor?: InstrumentDescriptor; + pointDataType: PointDataType, + pointData: Partial>>[]; +} + +async function validateExport(deltaReader: MetricReader, expected: ValidateMetricData) { + const metricData = await deltaReader.collect(); + const metric = metricData[0]; + + assertMetricData( + metric, + expected.pointDataType, + expected.instrumentDescriptor ?? null, + expected.instrumentationLibrary, + expected.resource + ); + assert.strictEqual(metric.pointData.length, expected.pointData.length); + + for (let idx = 0; idx < metric.pointData.length; idx++) { + const expectedPointData = expected.pointData[idx]; + assertPointData( + metric.pointData[idx], + expectedPointData.attributes ?? {}, + expectedPointData.point as any, + expectedPointData.startTime, + expectedPointData.endTime + ); + } +} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts index 917277e436..d3d9e9e9b6 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts @@ -56,14 +56,20 @@ export const sleep = (time: number) => export function assertMetricData( actual: unknown, pointDataType?: PointDataType, - instrumentDescriptor: Partial = defaultInstrumentDescriptor, - instrumentationLibrary: Partial = defaultInstrumentationLibrary, - resource: Resource = defaultResource, + instrumentDescriptor: Partial | null = defaultInstrumentDescriptor, + instrumentationLibrary: Partial | null = defaultInstrumentationLibrary, + resource: Resource | null = defaultResource, ): asserts actual is MetricData { const it = actual as MetricData; - assert.deepStrictEqual(it.resource, resource); - assertPartialDeepStrictEqual(it.instrumentDescriptor, instrumentDescriptor); - assertPartialDeepStrictEqual(it.instrumentationLibrary, instrumentationLibrary); + if (resource != null) { + assert.deepStrictEqual(it.resource, resource); + } + if (instrumentDescriptor != null) { + assertPartialDeepStrictEqual(it.instrumentDescriptor, instrumentDescriptor); + } + if (instrumentationLibrary != null) { + assertPartialDeepStrictEqual(it.instrumentationLibrary, instrumentationLibrary); + } if (isNotNullish(pointDataType)) { assert.strictEqual(it.pointDataType, pointDataType); } else { From 437a286a0fb906509dcf28282266f9b6c70560a1 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 28 Jan 2022 00:43:27 +0800 Subject: [PATCH 2/2] fixup! --- .../test/Instruments.test.ts | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts index a78d854378..67c89891f3 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts @@ -26,7 +26,7 @@ import { ObservableResult } from '@opentelemetry/api-metrics-wip'; describe('Instruments', () => { describe('Counter', () => { - it('should add common values and attributes', () => { + it('should add common values and attributes without exceptions', () => { const { meter } = setup(); const counter = meter.createCounter('test'); @@ -43,12 +43,18 @@ describe('Instruments', () => { counter.add(1); counter.add(1); + counter.add(1, { foo: 'bar' }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 2, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); @@ -58,15 +64,20 @@ describe('Instruments', () => { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 2, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); }); }); describe('UpDownCounter', () => { - it('should add common values and attributes', () => { + it('should add common values and attributes without exceptions', () => { const { meter } = setup(); const upDownCounter = meter.createUpDownCounter('test'); @@ -83,19 +94,25 @@ describe('Instruments', () => { upDownCounter.add(3); upDownCounter.add(-1); + upDownCounter.add(4, { foo: 'bar' }); await validateExport(deltaReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 2, }, + { + attributes: { foo: 'bar' }, + point: 4, + } ], }); }); }); describe('Histogram', () => { - it('should record common values and attributes', () => { + it('should record common values and attributes without exceptions', () => { const { meter } = setup(); const histogram = meter.createHistogram('test'); @@ -113,10 +130,12 @@ describe('Instruments', () => { histogram.record(10); histogram.record(-1); + histogram.record(100, { foo: 'bar' }); await validateExport(deltaReader, { pointDataType: PointDataType.HISTOGRAM, pointData: [ { + attributes: {}, point: { buckets: { boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], @@ -126,13 +145,24 @@ describe('Instruments', () => { sum: 9, }, }, + { + attributes: { foo: 'bar' }, + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + }, + count: 1, + sum: 100, + }, + }, ], }); }); }); describe('ObservableCounter', () => { - it('should observe common values and attributes', async () => { + it('should observe common values and attributes without exceptions', async () => { const { meter, deltaReader } = setup(); const callback = sinon.spy((observableResult: ObservableResult) => { for (const values of commonValues) { @@ -152,12 +182,18 @@ describe('Instruments', () => { let callCount = 0; meter.createObservableCounter('test', observableResult => { observableResult.observe(++callCount); + observableResult.observe(1, { foo: 'bar' }); }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, + point: 1, + }, + { + attributes: { foo: 'bar' }, point: 1, }, ], @@ -166,15 +202,20 @@ describe('Instruments', () => { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 2, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); }); }); describe('ObservableUpDownCounter', () => { - it('should observe common values and attributes', async () => { + it('should observe common values and attributes without exceptions', async () => { const { meter, deltaReader } = setup(); const callback = sinon.spy((observableResult: ObservableResult) => { for (const values of commonValues) { @@ -194,12 +235,18 @@ describe('Instruments', () => { let callCount = 0; meter.createObservableUpDownCounter('test', observableResult => { observableResult.observe(++callCount); + observableResult.observe(1, { foo: 'bar' }); }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, + point: 1, + }, + { + attributes: { foo: 'bar' }, point: 1, }, ], @@ -208,15 +255,20 @@ describe('Instruments', () => { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 2, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); }); }); describe('ObservableGauge', () => { - it('should observe common values and attributes', async () => { + it('should observe common values and attributes without exceptions', async () => { const { meter, deltaReader } = setup(); const callback = sinon.spy((observableResult: ObservableResult) => { for (const values of commonValues) { @@ -241,30 +293,46 @@ describe('Instruments', () => { } else { observableResult.observe(num); } + observableResult.observe(1, { foo: 'bar' }); }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 10, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: 20, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); await validateExport(cumulativeReader, { pointDataType: PointDataType.SINGULAR, pointData: [ { + attributes: {}, point: -1, }, + { + attributes: { foo: 'bar' }, + point: 1, + }, ], }); });