From d914b4bc8d3dac36834a17b98573e0dcaf02b627 Mon Sep 17 00:00:00 2001 From: "marc.pichler" Date: Mon, 7 Nov 2022 12:58:21 +0100 Subject: [PATCH 1/2] feat(console-metric-exporter): add temporality configuration --- experimental/CHANGELOG.md | 1 + .../src/export/AggregationSelector.ts | 15 +- .../src/export/ConsoleMetricExporter.ts | 23 ++- .../test/export/ConsoleMetricExporter.test.ts | 150 +++++++++++------- .../test/export/utils.ts | 11 +- 5 files changed, 132 insertions(+), 68 deletions(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 426ca3d990..35bbc031eb 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to experimental packages in this project will be documented * feat(detectors): add browser detector module [#3292](https://github.com/open-telemetry/opentelemetry-js/pull/3292) @abinet18 * deps: remove unused proto-loader dependencies and update grpc-js and proto-loader versions [#3337](https://github.com/open-telemetry/opentelemetry-js/pull/3337) @seemk * feat(metrics-exporters): configure temporality via environment variable [#3305](https://github.com/open-telemetry/opentelemetry-js/pull/3305) @pichlermarc +* feat(console-metric-exporter): add temporality configuration [#3387](https://github.com/open-telemetry/opentelemetry-js/pull/3387) @pichlermarc ### :bug: (Bug Fix) diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts index b0ef5f36ad..55c2a1ed07 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts @@ -29,4 +29,17 @@ export type AggregationSelector = (instrumentType: InstrumentType) => Aggregatio export type AggregationTemporalitySelector = (instrumentType: InstrumentType) => AggregationTemporality; export const DEFAULT_AGGREGATION_SELECTOR: AggregationSelector = _instrumentType => Aggregation.Default(); -export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE; +export const CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE; +export const DELTA_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = (instrumentType: InstrumentType) => { + switch (instrumentType) { + case InstrumentType.COUNTER: + case InstrumentType.OBSERVABLE_COUNTER: + case InstrumentType.HISTOGRAM: + case InstrumentType.OBSERVABLE_GAUGE: + return AggregationTemporality.DELTA; + case InstrumentType.UP_DOWN_COUNTER: + case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER: + return AggregationTemporality.CUMULATIVE; + } +}; +export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR; diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts index 39b268f193..3b9623608b 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts @@ -13,15 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ExportResult, ExportResultCode } from '@opentelemetry/core'; +import { + ExportResult, + ExportResultCode +} from '@opentelemetry/core'; import { InstrumentType } from '../InstrumentDescriptor'; import { AggregationTemporality } from './AggregationTemporality'; import { ResourceMetrics } from './MetricData'; import { PushMetricExporter } from './MetricExporter'; +import { + AggregationTemporalitySelector, + CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR, + DELTA_AGGREGATION_TEMPORALITY_SELECTOR +} from './AggregationSelector'; + +interface ConsoleMetricExporterOptions { + temporalityPreference?: AggregationTemporality +} /* eslint-disable no-console */ export class ConsoleMetricExporter implements PushMetricExporter { protected _shutdown = false; + protected _temporalitySelector: AggregationTemporalitySelector; + + constructor(options?: ConsoleMetricExporterOptions) { + this._temporalitySelector = options?.temporalityPreference === AggregationTemporality.DELTA ? + DELTA_AGGREGATION_TEMPORALITY_SELECTOR : + CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR; + } export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void { if (this._shutdown) { @@ -38,7 +57,7 @@ export class ConsoleMetricExporter implements PushMetricExporter { } selectAggregationTemporality(_instrumentType: InstrumentType): AggregationTemporality { - return AggregationTemporality.CUMULATIVE; + return this._temporalitySelector(_instrumentType); } shutdown(): Promise { diff --git a/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts b/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts index 6097aeb555..f434c0f767 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts @@ -22,6 +22,12 @@ import { MeterProvider } from '../../src/MeterProvider'; import { defaultResource } from '../util'; import * as assert from 'assert'; import * as sinon from 'sinon'; +import { assertAggregationTemporalitySelector } from './utils'; +import { + CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR, + DELTA_AGGREGATION_TEMPORALITY_SELECTOR +} from '../../src/export/AggregationSelector'; +import { AggregationTemporality } from '../../src'; async function waitForNumberOfExports(exporter: sinon.SinonSpy<[metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void], void>, numberOfExports: number): Promise { @@ -38,71 +44,95 @@ async function waitForNumberOfExports(exporter: sinon.SinonSpy<[metrics: Resourc /* eslint-disable no-console */ describe('ConsoleMetricExporter', () => { - let previousConsoleDir: any; - let exporter: ConsoleMetricExporter; - let meterProvider: MeterProvider; - let meterReader: PeriodicExportingMetricReader; - let meter: metrics.Meter; - - beforeEach(() => { - previousConsoleDir = console.dir; - console.dir = () => {}; - - exporter = new ConsoleMetricExporter(); - meterProvider = new MeterProvider({ resource: defaultResource }); - meter = meterProvider.getMeter('ConsoleMetricExporter', '1.0.0'); - meterReader = new PeriodicExportingMetricReader({ - exporter: exporter, - exportIntervalMillis: 100, - exportTimeoutMillis: 100 + describe('export', () => { + let previousConsoleDir: any; + let exporter: ConsoleMetricExporter; + let meterProvider: MeterProvider; + let meterReader: PeriodicExportingMetricReader; + let meter: metrics.Meter; + + beforeEach(() => { + previousConsoleDir = console.dir; + console.dir = () => {}; + + exporter = new ConsoleMetricExporter(); + meterProvider = new MeterProvider({ resource: defaultResource }); + meter = meterProvider.getMeter('ConsoleMetricExporter', '1.0.0'); + meterReader = new PeriodicExportingMetricReader({ + exporter: exporter, + exportIntervalMillis: 100, + exportTimeoutMillis: 100 + }); + meterProvider.addMetricReader(meterReader); }); - meterProvider.addMetricReader(meterReader); - }); - afterEach(async () => { - console.dir = previousConsoleDir; + afterEach(async () => { + console.dir = previousConsoleDir; + + await meterReader.shutdown(); + }); + + it('should export information about metric', async () => { + const counter = meter.createCounter('counter_total', { + description: 'a test description', + }); + const counterAttribute = { key1: 'attributeValue1' }; + counter.add(10, counterAttribute); + counter.add(10, counterAttribute); + + const histogram = meter.createHistogram('histogram', { description: 'a histogram' }); + histogram.record(10); + histogram.record(100); + histogram.record(1000); + + const spyConsole = sinon.spy(console, 'dir'); + const spyExport = sinon.spy(exporter, 'export'); + + await waitForNumberOfExports(spyExport, 1); + const resourceMetrics = spyExport.args[0]; + const firstResourceMetric = resourceMetrics[0]; + const consoleArgs = spyConsole.args[0]; + const consoleMetric = consoleArgs[0]; + const keys = Object.keys(consoleMetric).sort().join(','); - await meterReader.shutdown(); + const expectedKeys = [ + 'dataPointType', + 'dataPoints', + 'descriptor', + ].join(','); + + assert.ok(firstResourceMetric.resource.attributes.resourceKey === 'my-resource', 'resourceKey'); + assert.ok(keys === expectedKeys, 'expectedKeys'); + assert.ok(consoleMetric.descriptor.name === 'counter_total', 'name'); + assert.ok(consoleMetric.descriptor.description === 'a test description', 'description'); + assert.ok(consoleMetric.descriptor.type === 'COUNTER', 'type'); + assert.ok(consoleMetric.descriptor.unit === '', 'unit'); + assert.ok(consoleMetric.descriptor.valueType === 1, 'valueType'); + assert.ok(consoleMetric.dataPoints[0].attributes.key1 === 'attributeValue1', 'ensure metric attributes exists'); + + assert.ok(spyExport.calledOnce); + }); }); - it('should export information about metric', async () => { - const counter = meter.createCounter('counter_total', { - description: 'a test description', + describe('constructor', () => { + it('with no arguments should select cumulative temporality', () => { + const exporter = new ConsoleMetricExporter(); + assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + }); + + it('with empty options should select cumulative temporality', () => { + const exporter = new ConsoleMetricExporter({}); + assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + }); + + it('with cumulative preference should select cumulative temporality', () => { + const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.CUMULATIVE }); + assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + }); + + it('with delta preference should select delta temporality', () => { + const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.DELTA }); + assertAggregationTemporalitySelector(exporter, DELTA_AGGREGATION_TEMPORALITY_SELECTOR); }); - const counterAttribute = { key1: 'attributeValue1' }; - counter.add(10, counterAttribute); - counter.add(10, counterAttribute); - - const histogram = meter.createHistogram('histogram', { description: 'a histogram' }); - histogram.record(10); - histogram.record(100); - histogram.record(1000); - - const spyConsole = sinon.spy(console, 'dir'); - const spyExport = sinon.spy(exporter, 'export'); - - await waitForNumberOfExports(spyExport, 1); - const resourceMetrics = spyExport.args[0]; - const firstResourceMetric = resourceMetrics[0]; - const consoleArgs = spyConsole.args[0]; - const consoleMetric = consoleArgs[0]; - const keys = Object.keys(consoleMetric).sort().join(','); - - const expectedKeys = [ - 'dataPointType', - 'dataPoints', - 'descriptor', - ].join(','); - - assert.ok(firstResourceMetric.resource.attributes.resourceKey === 'my-resource', 'resourceKey'); - assert.ok(keys === expectedKeys, 'expectedKeys'); - assert.ok(consoleMetric.descriptor.name === 'counter_total', 'name'); - assert.ok(consoleMetric.descriptor.description === 'a test description', 'description'); - assert.ok(consoleMetric.descriptor.type === 'COUNTER', 'type'); - assert.ok(consoleMetric.descriptor.unit === '', 'unit'); - assert.ok(consoleMetric.descriptor.valueType === 1, 'valueType'); - assert.ok(consoleMetric.dataPoints[0].attributes.key1 === 'attributeValue1', 'ensure metric attributes exists'); - - assert.ok(spyExport.calledOnce); }); }); diff --git a/experimental/packages/opentelemetry-sdk-metrics/test/export/utils.ts b/experimental/packages/opentelemetry-sdk-metrics/test/export/utils.ts index cd8a9cc7ae..3ad8543be7 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/test/export/utils.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/test/export/utils.ts @@ -18,7 +18,8 @@ import { AggregationSelector, AggregationTemporalitySelector, InstrumentType, - MetricReader + MetricReader, + PushMetricExporter } from '../../src'; import * as assert from 'assert'; @@ -36,9 +37,9 @@ const instrumentTypes = [ * @param reader * @param expectedSelector */ -export function assertAggregationSelector(reader: MetricReader, expectedSelector: AggregationSelector) { +export function assertAggregationSelector(reader: MetricReader | PushMetricExporter, expectedSelector: AggregationSelector) { for (const instrumentType of instrumentTypes) { - assert.strictEqual(reader.selectAggregation(instrumentType), + assert.strictEqual(reader.selectAggregation?.(instrumentType), expectedSelector(instrumentType), `incorrect aggregation selection for ${InstrumentType[instrumentType]}`); } @@ -49,9 +50,9 @@ export function assertAggregationSelector(reader: MetricReader, expectedSelector * @param reader * @param expectedSelector */ -export function assertAggregationTemporalitySelector(reader: MetricReader, expectedSelector: AggregationTemporalitySelector) { +export function assertAggregationTemporalitySelector(reader: MetricReader | PushMetricExporter, expectedSelector: AggregationTemporalitySelector) { for (const instrumentType of instrumentTypes) { - assert.strictEqual(reader.selectAggregationTemporality(instrumentType), + assert.strictEqual(reader.selectAggregationTemporality?.(instrumentType), expectedSelector(instrumentType), `incorrect aggregation temporality selection for ${InstrumentType[instrumentType]}`); } From 485bde38539421e002ab7603b3af23c0f235acfb Mon Sep 17 00:00:00 2001 From: "marc.pichler" Date: Wed, 9 Nov 2022 11:09:48 +0100 Subject: [PATCH 2/2] feat(console-metric-exporter): use a temporality selector instead --- .../src/export/AggregationSelector.ts | 15 +------- .../src/export/ConsoleMetricExporter.ts | 9 ++--- .../test/export/ConsoleMetricExporter.test.ts | 35 +++++++++++++------ 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts index 55c2a1ed07..b0ef5f36ad 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/AggregationSelector.ts @@ -29,17 +29,4 @@ export type AggregationSelector = (instrumentType: InstrumentType) => Aggregatio export type AggregationTemporalitySelector = (instrumentType: InstrumentType) => AggregationTemporality; export const DEFAULT_AGGREGATION_SELECTOR: AggregationSelector = _instrumentType => Aggregation.Default(); -export const CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE; -export const DELTA_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = (instrumentType: InstrumentType) => { - switch (instrumentType) { - case InstrumentType.COUNTER: - case InstrumentType.OBSERVABLE_COUNTER: - case InstrumentType.HISTOGRAM: - case InstrumentType.OBSERVABLE_GAUGE: - return AggregationTemporality.DELTA; - case InstrumentType.UP_DOWN_COUNTER: - case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER: - return AggregationTemporality.CUMULATIVE; - } -}; -export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR; +export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE; diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts index 3b9623608b..0b990dfe5e 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/ConsoleMetricExporter.ts @@ -23,12 +23,11 @@ import { ResourceMetrics } from './MetricData'; import { PushMetricExporter } from './MetricExporter'; import { AggregationTemporalitySelector, - CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR, - DELTA_AGGREGATION_TEMPORALITY_SELECTOR + DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR, } from './AggregationSelector'; interface ConsoleMetricExporterOptions { - temporalityPreference?: AggregationTemporality + temporalitySelector?: AggregationTemporalitySelector } /* eslint-disable no-console */ @@ -37,9 +36,7 @@ export class ConsoleMetricExporter implements PushMetricExporter { protected _temporalitySelector: AggregationTemporalitySelector; constructor(options?: ConsoleMetricExporterOptions) { - this._temporalitySelector = options?.temporalityPreference === AggregationTemporality.DELTA ? - DELTA_AGGREGATION_TEMPORALITY_SELECTOR : - CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR; + this._temporalitySelector = options?.temporalitySelector ?? DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR; } export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void { diff --git a/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts b/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts index f434c0f767..7cd8048bf9 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/test/export/ConsoleMetricExporter.test.ts @@ -24,10 +24,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { assertAggregationTemporalitySelector } from './utils'; import { - CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR, - DELTA_AGGREGATION_TEMPORALITY_SELECTOR + DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR } from '../../src/export/AggregationSelector'; -import { AggregationTemporality } from '../../src'; +import { + AggregationTemporality, + InstrumentType +} from '../../src'; async function waitForNumberOfExports(exporter: sinon.SinonSpy<[metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void], void>, numberOfExports: number): Promise { @@ -117,22 +119,35 @@ describe('ConsoleMetricExporter', () => { describe('constructor', () => { it('with no arguments should select cumulative temporality', () => { const exporter = new ConsoleMetricExporter(); - assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + assertAggregationTemporalitySelector(exporter, DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR); }); it('with empty options should select cumulative temporality', () => { const exporter = new ConsoleMetricExporter({}); - assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + assertAggregationTemporalitySelector(exporter, DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR); }); it('with cumulative preference should select cumulative temporality', () => { - const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.CUMULATIVE }); - assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR); + const exporter = new ConsoleMetricExporter({ temporalitySelector: _ => AggregationTemporality.CUMULATIVE }); + assertAggregationTemporalitySelector(exporter, _ => AggregationTemporality.CUMULATIVE); }); - it('with delta preference should select delta temporality', () => { - const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.DELTA }); - assertAggregationTemporalitySelector(exporter, DELTA_AGGREGATION_TEMPORALITY_SELECTOR); + it('with mixed preference should select matching temporality', () => { + // use delta-ish example as a representation of a commonly used "mixed" preference. + const selector = (instrumentType: InstrumentType) => { + switch (instrumentType) { + case InstrumentType.COUNTER: + case InstrumentType.OBSERVABLE_COUNTER: + case InstrumentType.HISTOGRAM: + case InstrumentType.OBSERVABLE_GAUGE: + return AggregationTemporality.DELTA; + case InstrumentType.UP_DOWN_COUNTER: + case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER: + return AggregationTemporality.CUMULATIVE; + } + }; + const exporter = new ConsoleMetricExporter({ temporalitySelector: selector }); + assertAggregationTemporalitySelector(exporter, selector); }); }); });