diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index a21b835e14..571b4af38e 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -25,6 +25,8 @@ jobs: with: node-version: ${{ matrix.node_version }} + - run: npm install -g npm@latest + - name: restore lerna id: cache uses: actions/cache@v3 diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 8c13df0ddb..786b78e978 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -6,10 +6,19 @@ All notable changes to experimental packages in this project will be documented ### :boom: Breaking Change +* feature(views): move views registration to MeterProvider constructor [#3066](https://github.com/open-telemetry/opentelemetry-js/pull/3066) @pichlermarc +* feat(sdk-metrics-base): split up Singular into Sum and Gauge in MetricData [#3079](https://github.com/open-telemetry/opentelemetry-js/pull/3079) @pichlermarc + * removes `DataPointType.SINGULAR`, and replaces it with `DataPointType.SUM` and `DataPointType.GAUGE` + * removes `SingularMetricData` and replaces it with `SumMetricData` (including an additional `isMonotonic` flag) and `GaugeMetricData` + ### :rocket: (Enhancement) +* feat(otlp-proto): pre-compile proto files [#3098](https://github.com/open-telemetry/opentelemetry-js/pull/3098) @legendecas + ### :bug: (Bug Fix) +* fix(histogram): fix maximum when only values < -1 are provided [#3086](https://github.com/open-telemetry/opentelemetry-js/pull/3086) @pichlermarc + ### :books: (Refine Doc) ### :house: (Internal) diff --git a/experimental/packages/exporter-trace-otlp-proto/test/OTLPTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-proto/test/OTLPTraceExporter.test.ts index 486a4fad6b..f8f8ec87d0 100644 --- a/experimental/packages/exporter-trace-otlp-proto/test/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-proto/test/OTLPTraceExporter.test.ts @@ -30,7 +30,7 @@ import { MockedResponse, } from './traceHelper'; import { CompressionAlgorithm, OTLPExporterNodeConfigBase, OTLPExporterError } from '@opentelemetry/otlp-exporter-base'; -import { getExportRequestProto } from '@opentelemetry/otlp-proto-exporter-base'; +import { getExportRequestProto, ServiceClientType } from '@opentelemetry/otlp-proto-exporter-base'; import { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer'; let fakeRequest: PassThrough; @@ -208,8 +208,8 @@ describe('OTLPTraceExporter - node with proto over http', () => { let buff = Buffer.from(''); fakeRequest.on('end', () => { - const ExportTraceServiceRequestProto = getExportRequestProto(); - const data = ExportTraceServiceRequestProto?.decode(buff); + const ExportTraceServiceRequestProto = getExportRequestProto(ServiceClientType.SPANS); + const data = ExportTraceServiceRequestProto.decode(buff); const json = data?.toJSON() as IExportTraceServiceRequest; const span1 = json.resourceSpans?.[0].scopeSpans?.[0].spans?.[0]; assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); @@ -294,8 +294,8 @@ describe('OTLPTraceExporter - node with proto over http', () => { let buff = Buffer.from(''); fakeRequest.on('end', () => { const unzippedBuff = zlib.gunzipSync(buff); - const ExportTraceServiceRequestProto = getExportRequestProto(); - const data = ExportTraceServiceRequestProto?.decode(unzippedBuff); + const ExportTraceServiceRequestProto = getExportRequestProto(ServiceClientType.SPANS); + const data = ExportTraceServiceRequestProto.decode(unzippedBuff); const json = data?.toJSON() as IExportTraceServiceRequest; const span1 = json.resourceSpans?.[0].scopeSpans?.[0].spans?.[0]; assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts index c2511d17c5..62376db7ba 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts @@ -24,6 +24,7 @@ import { ExplicitBucketHistogramAggregation, MeterProvider, MetricReader, + View, } from '@opentelemetry/sdk-metrics-base'; import { IKeyValue, IMetric, IResource } from '@opentelemetry/otlp-transformer'; @@ -59,7 +60,15 @@ export async function collect() { } export function setUp() { - meterProvider = new MeterProvider({ resource: testResource }); + meterProvider = new MeterProvider({ + resource: testResource, + views: [ + new View({ + aggregation: new ExplicitBucketHistogramAggregation([0, 100]), + instrumentName: 'int-histogram', + }) + ] + }); reader = new TestMetricReader(); meterProvider.addMetricReader( reader @@ -96,10 +105,7 @@ export function mockObservableGauge( } export function mockHistogram(): Histogram { - const name = 'int-histogram'; - meterProvider.addView({ aggregation: new ExplicitBucketHistogramAggregation([0, 100]) }); - - return meter.createHistogram(name, { + return meter.createHistogram('int-histogram', { description: 'sample histogram description', valueType: ValueType.INT, }); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/browser/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/browser/CollectorMetricExporter.test.ts index f1e00dce99..c76ca3ebdc 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/browser/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/browser/CollectorMetricExporter.test.ts @@ -27,7 +27,7 @@ import { ensureExportMetricsServiceRequestIsSet, ensureHeadersContain, ensureHistogramIsCorrect, ensureObservableGaugeIsCorrect, - ensureWebResourceIsCorrect, + ensureWebResourceIsCorrect, HISTOGRAM_AGGREGATION_VIEW, mockCounter, mockHistogram, mockObservableGauge, @@ -47,7 +47,7 @@ describe('OTLPMetricExporter - web', () => { let errorStub: sinon.SinonStub; beforeEach(async () => { - setUp(); + setUp([HISTOGRAM_AGGREGATION_VIEW]); stubOpen = sinon.stub(XMLHttpRequest.prototype, 'open'); sinon.stub(XMLHttpRequest.prototype, 'send'); stubBeacon = sinon.stub(navigator, 'sendBeacon'); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts index 3725a52f70..90e4a39cd6 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts @@ -30,7 +30,8 @@ import { AggregationTemporality, ExplicitBucketHistogramAggregation, MeterProvider, - MetricReader + MetricReader, + View } from '@opentelemetry/sdk-metrics-base'; import { IExportMetricsServiceRequest, @@ -61,6 +62,11 @@ class TestMetricReader extends MetricReader { } } +export const HISTOGRAM_AGGREGATION_VIEW = new View({ + aggregation: new ExplicitBucketHistogramAggregation([0, 100]), + instrumentName: 'int-histogram', +}); + const defaultResource = Resource.default().merge(new Resource({ service: 'ui', version: 1, @@ -78,8 +84,8 @@ export async function collect() { return (await reader.collect())!; } -export function setUp() { - meterProvider = new MeterProvider({ resource: defaultResource }); +export function setUp(views?: View[]) { + meterProvider = new MeterProvider({ resource: defaultResource, views }); reader = new TestMetricReader(); meterProvider.addMetricReader( reader @@ -124,7 +130,6 @@ export function mockDoubleCounter(): Counter { } - export function mockObservableCounter( callback: (observableResult: ObservableResult) => void, name = 'double-observable-counter' @@ -158,18 +163,7 @@ export function mockObservableUpDownCounter( } export function mockHistogram(): Histogram { - const name = 'int-histogram'; - - meterProvider.addView({ - aggregation: new ExplicitBucketHistogramAggregation([0, 100]) - }, - { - instrument: { - name: name - } - }); - - return meter.createHistogram(name, { + return meter.createHistogram('int-histogram', { description: 'sample histogram description', valueType: ValueType.INT, }); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts index c1b16cd278..ddd4780b15 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts @@ -35,7 +35,10 @@ import { mockCounter, mockObservableGauge, mockHistogram, - collect, shutdown, setUp, + collect, + shutdown, + setUp, + HISTOGRAM_AGGREGATION_VIEW, } from '../metricsHelper'; import { MockedResponse } from './nodeHelpers'; import { AggregationTemporality, ResourceMetrics } from '@opentelemetry/sdk-metrics-base'; @@ -54,7 +57,7 @@ describe('OTLPMetricExporter - node with json over http', () => { let metrics: ResourceMetrics; beforeEach(async () => { - setUp(); + setUp([HISTOGRAM_AGGREGATION_VIEW]); }); afterEach(async () => { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts index c513289d46..ad81bb5e6a 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts @@ -16,7 +16,7 @@ import { diag } from '@opentelemetry/api'; import { ExportResultCode } from '@opentelemetry/core'; -import { getExportRequestProto } from '@opentelemetry/otlp-proto-exporter-base'; +import { getExportRequestProto, ServiceClientType } from '@opentelemetry/otlp-proto-exporter-base'; import * as assert from 'assert'; import * as http from 'http'; import * as sinon from 'sinon'; @@ -236,8 +236,8 @@ describe('OTLPMetricExporter - node with proto over http', () => { let buff = Buffer.from(''); fakeRequest.on('end', () => { - const ExportTraceServiceRequestProto = getExportRequestProto(); - const data = ExportTraceServiceRequestProto?.decode(buff); + const ExportTraceServiceRequestProto = getExportRequestProto(ServiceClientType.METRICS); + const data = ExportTraceServiceRequestProto.decode(buff); const json = data?.toJSON() as IExportMetricsServiceRequest; const metric1 = json.resourceMetrics[0].scopeMetrics[0].metrics[0]; diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts index bb78daf397..0652789689 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts @@ -27,7 +27,8 @@ import { AggregationTemporality, ExplicitBucketHistogramAggregation, MeterProvider, - MetricReader + MetricReader, + View } from '@opentelemetry/sdk-metrics-base'; import { IExportMetricsServiceRequest, IKeyValue, IMetric } from '@opentelemetry/otlp-transformer'; import { Stream } from 'stream'; @@ -64,7 +65,15 @@ export async function collect() { } export function setUp() { - meterProvider = new MeterProvider({ resource: testResource }); + meterProvider = new MeterProvider({ + resource: testResource, + views: [ + new View({ + aggregation: new ExplicitBucketHistogramAggregation([0, 100]), + instrumentName: 'int-histogram', + }) + ] + }); reader = new TestMetricReader(); meterProvider.addMetricReader( reader @@ -102,7 +111,6 @@ export function mockObservableGauge( export function mockHistogram(): Histogram { const name = 'int-histogram'; - meterProvider.addView({ aggregation: new ExplicitBucketHistogramAggregation([0, 100]) }); return meter.createHistogram(name, { description: 'sample histogram description', diff --git a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts index 1b84efe08b..c11ea5213c 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts @@ -104,23 +104,15 @@ function valueString(value: number) { } function toPrometheusType( - instrumentType: InstrumentType, - dataPointType: DataPointType, + metricData: MetricData, ): PrometheusDataTypeLiteral { - switch (dataPointType) { - case DataPointType.SINGULAR: - if ( - instrumentType === InstrumentType.COUNTER || - instrumentType === InstrumentType.OBSERVABLE_COUNTER - ) { + switch (metricData.dataPointType) { + case DataPointType.SUM: + if (metricData.isMonotonic) { return 'counter'; } - /** - * - HISTOGRAM - * - UP_DOWN_COUNTER - * - OBSERVABLE_GAUGE - * - OBSERVABLE_UP_DOWN_COUNTER - */ + return 'gauge'; + case DataPointType.GAUGE: return 'gauge'; case DataPointType.HISTOGRAM: return 'histogram'; @@ -210,13 +202,13 @@ export class PrometheusSerializer { metricData.descriptor.description || 'description missing' )}`; const type = `# TYPE ${name} ${toPrometheusType( - metricData.descriptor.type, - dataPointType + metricData )}`; let results = ''; switch (dataPointType) { - case DataPointType.SINGULAR: { + case DataPointType.SUM: + case DataPointType.GAUGE: { results = metricData.dataPoints .map(it => this._serializeSingularDataPoint(name, metricData.descriptor.type, it)) .join(''); diff --git a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts index f0dc004e25..daea0b209f 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts @@ -18,13 +18,15 @@ import * as assert from 'assert'; import { MetricAttributes, UpDownCounter } from '@opentelemetry/api-metrics'; import { AggregationTemporality, - MeterProvider, - MetricReader, DataPoint, DataPointType, ExplicitBucketHistogramAggregation, - SumAggregation, Histogram, + LastValueAggregation, + MeterProvider, + MetricReader, + SumAggregation, + View, } from '@opentelemetry/sdk-metrics-base'; import * as sinon from 'sinon'; import { PrometheusSerializer } from '../src'; @@ -45,6 +47,7 @@ class TestMetricReader extends MetricReader { } async onForceFlush() {} + async onShutdown() {} } @@ -67,11 +70,12 @@ describe('PrometheusSerializer', () => { describe('Singular', () => { async function testSerializer(serializer: PrometheusSerializer) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider( + { + views: [new View({ aggregation: new SumAggregation(), instrumentName: '*' })] + } + ); meterProvider.addMetricReader(reader); - meterProvider.addView({ - aggregation: new SumAggregation(), - }); const meter = meterProvider.getMeter('test'); const counter = meter.createCounter('test_total'); @@ -82,7 +86,7 @@ describe('PrometheusSerializer', () => { assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); const metric = resourceMetrics.scopeMetrics[0].metrics[0]; - assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR); + assert.strictEqual(metric.dataPointType, DataPointType.SUM); const pointData = metric.dataPoints as DataPoint[]; assert.strictEqual(pointData.length, 1); @@ -112,9 +116,13 @@ describe('PrometheusSerializer', () => { describe('Histogram', () => { async function testSerializer(serializer: PrometheusSerializer) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider({ + views: [new View({ + aggregation: new ExplicitBucketHistogramAggregation([1, 10, 100]), + instrumentName: '*' + })] + }); meterProvider.addMetricReader(reader); - meterProvider.addView({ aggregation: new ExplicitBucketHistogramAggregation([1, 10, 100]) }); const meter = meterProvider.getMeter('test'); const histogram = meter.createHistogram('test'); @@ -164,12 +172,13 @@ describe('PrometheusSerializer', () => { }); describe('serialize an instrumentation metrics', () => { - describe('Singular', () => { + describe('monotonic Sum', () => { async function testSerializer(serializer: PrometheusSerializer) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider({ + views: [new View({ aggregation: new SumAggregation(), instrumentName: '*' })] + }); meterProvider.addMetricReader(reader); - meterProvider.addView({ aggregation: new SumAggregation() }); const meter = meterProvider.getMeter('test'); const counter = meter.createCounter('test_total', { @@ -188,7 +197,7 @@ describe('PrometheusSerializer', () => { return result; } - it('should serialize metric record with sum aggregator', async () => { + it('should serialize metric record', async () => { const serializer = new PrometheusSerializer(); const result = await testSerializer(serializer); assert.strictEqual( @@ -200,7 +209,7 @@ describe('PrometheusSerializer', () => { ); }); - it('serialize metric record with sum aggregator without timestamp', async () => { + it('should serialize metric record without timestamp', async () => { const serializer = new PrometheusSerializer(undefined, false); const result = await testSerializer(serializer); assert.strictEqual( @@ -213,12 +222,118 @@ describe('PrometheusSerializer', () => { }); }); + describe('non-monotonic Sum', () => { + async function testSerializer(serializer: PrometheusSerializer) { + const reader = new TestMetricReader(); + const meterProvider = new MeterProvider({ + views: [ + new View({ aggregation: new SumAggregation(), instrumentName: '*' }) + ] + }); + meterProvider.addMetricReader(reader); + const meter = meterProvider.getMeter('test'); + + const counter = meter.createUpDownCounter('test_total', { + description: 'foobar', + }); + counter.add(1, { val: '1' }); + counter.add(1, { val: '2' }); + + 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 scopeMetrics = resourceMetrics.scopeMetrics[0]; + + return serializer['_serializeScopeMetrics'](scopeMetrics); + } + + it('should serialize metric record', async () => { + const serializer = new PrometheusSerializer(); + const result = await testSerializer(serializer); + assert.strictEqual( + result, + '# HELP test_total foobar\n' + + '# TYPE test_total gauge\n' + + `test_total{val="1"} 1 ${mockedHrTimeMs}\n` + + `test_total{val="2"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + const result = await testSerializer(serializer); + assert.strictEqual( + result, + '# HELP test_total foobar\n' + + '# TYPE test_total gauge\n' + + 'test_total{val="1"} 1\n' + + 'test_total{val="2"} 1\n' + ); + }); + }); + + describe('Gauge', () => { + async function testSerializer(serializer: PrometheusSerializer) { + const reader = new TestMetricReader(); + const meterProvider = new MeterProvider({ + views: [ + new View({aggregation: new LastValueAggregation(), instrumentName: '*' }) + ] + }); + meterProvider.addMetricReader(reader); + const meter = meterProvider.getMeter('test'); + + const counter = meter.createUpDownCounter('test_total', { + description: 'foobar', + }); + counter.add(1, { val: '1' }); + counter.add(1, { val: '2' }); + + 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 scopeMetrics = resourceMetrics.scopeMetrics[0]; + + return serializer['_serializeScopeMetrics'](scopeMetrics); + } + + it('should serialize metric record', async () => { + const serializer = new PrometheusSerializer(); + const result = await testSerializer(serializer); + assert.strictEqual( + result, + '# HELP test_total foobar\n' + + '# TYPE test_total gauge\n' + + `test_total{val="1"} 1 ${mockedHrTimeMs}\n` + + `test_total{val="2"} 1 ${mockedHrTimeMs}\n` + ); + }); + + it('serialize metric record without timestamp', async () => { + const serializer = new PrometheusSerializer(undefined, false); + const result = await testSerializer(serializer); + assert.strictEqual( + result, + '# HELP test_total foobar\n' + + '# TYPE test_total gauge\n' + + 'test_total{val="1"} 1\n' + + 'test_total{val="2"} 1\n' + ); + }); + }); + describe('with ExplicitBucketHistogramAggregation', () => { async function testSerializer(serializer: PrometheusSerializer) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider({ + views: [new View({ + aggregation: new ExplicitBucketHistogramAggregation([1, 10, 100]), + instrumentName: '*' + })] + }); meterProvider.addMetricReader(reader); - meterProvider.addView({ aggregation: new ExplicitBucketHistogramAggregation([1, 10, 100]) }); const meter = meterProvider.getMeter('test'); const histogram = meter.createHistogram('test', { @@ -267,9 +382,10 @@ describe('PrometheusSerializer', () => { describe('validate against metric conventions', () => { async function getCounterResult(name: string, serializer: PrometheusSerializer) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider({ + views: [new View({ aggregation: new SumAggregation(), instrumentName: '*' })] + }); meterProvider.addMetricReader(reader); - meterProvider.addView({ aggregation: new SumAggregation() }); const meter = meterProvider.getMeter('test'); const counter = meter.createCounter(name); @@ -280,7 +396,7 @@ describe('PrometheusSerializer', () => { assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); const metric = resourceMetrics.scopeMetrics[0].metrics[0]; - assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR); + assert.strictEqual(metric.dataPointType, DataPointType.SUM); const pointData = metric.dataPoints as DataPoint[]; assert.strictEqual(pointData.length, 1); @@ -306,9 +422,10 @@ describe('PrometheusSerializer', () => { describe('serialize non-normalized values', () => { async function testSerializer(serializer: PrometheusSerializer, name: string, fn: (counter: UpDownCounter) => void) { const reader = new TestMetricReader(); - const meterProvider = new MeterProvider(); + const meterProvider = new MeterProvider({ + views: [new View({ aggregation: new SumAggregation(), instrumentName: '*' })] + }); meterProvider.addMetricReader(reader); - meterProvider.addView({ aggregation: new SumAggregation() }); const meter = meterProvider.getMeter('test'); const counter = meter.createUpDownCounter(name); @@ -319,7 +436,7 @@ describe('PrometheusSerializer', () => { assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); const metric = resourceMetrics.scopeMetrics[0].metrics[0]; - assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR); + assert.strictEqual(metric.dataPointType, DataPointType.SUM); const pointData = metric.dataPoints as DataPoint[]; assert.strictEqual(pointData.length, 1); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/README.md b/experimental/packages/opentelemetry-sdk-metrics-base/README.md index c5ebc65cde..2d90184b6a 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/README.md +++ b/experimental/packages/opentelemetry-sdk-metrics-base/README.md @@ -58,6 +58,19 @@ async function batchObservableCallback(batchObservableResult) { } ``` +Views can be registered when instantiating a `MeterProvider`: + +```js +const meterProvider = new MeterProvider({ + views: [ + // override the bucket boundaries on `my.histogram` to [0, 50, 100] + new View({ aggregation: new ExplicitBucketHistogramAggregation([0, 50, 100]), instrumentName: 'my.histogram'}), + // rename 'my.counter' to 'my.renamed.counter' + new View({ name: 'my.renamed.counter', instrumentName: 'my.counter'}) + ] +}) +``` + ## Example See [examples/prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/examples/prometheus) for an end-to-end example, including exporting metrics. diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts index 41abed5e4d..003bb41a6b 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts @@ -19,15 +19,9 @@ import * as metrics from '@opentelemetry/api-metrics'; import { Resource } from '@opentelemetry/resources'; import { MetricReader } from './export/MetricReader'; import { MeterProviderSharedState } from './state/MeterProviderSharedState'; -import { InstrumentSelector } from './view/InstrumentSelector'; -import { MeterSelector } from './view/MeterSelector'; -import { View } from './view/View'; import { MetricCollector } from './state/MetricCollector'; -import { Aggregation } from './view/Aggregation'; -import { FilteringAttributesProcessor } from './view/AttributesProcessor'; -import { InstrumentType } from './InstrumentDescriptor'; -import { PatternPredicate } from './view/Predicate'; import { ForceFlushOptions, ShutdownOptions } from './types'; +import { View } from './view/View'; /** * MeterProviderOptions provides an interface for configuring a MeterProvider. @@ -35,68 +29,7 @@ import { ForceFlushOptions, ShutdownOptions } from './types'; export interface MeterProviderOptions { /** Resource associated with metric telemetry */ resource?: Resource; -} - -export type ViewOptions = { - /** - * If not provided, the Instrument name will be used by default. This will be used as the name of the metrics stream. - */ - name?: string, - /** - * If not provided, the Instrument description will be used by default. - */ - description?: string, - /** - * If provided, the attributes that are not in the list will be ignored. - * If not provided, all the attribute keys will be used by default. - */ - attributeKeys?: string[], - /** - * The {@link Aggregation} aggregation to be used. - */ - aggregation?: Aggregation, - - // TODO: Add ExemplarReservoir -}; - -export type SelectorOptions = { - /** - * Selects instrument by their fields. - */ - instrument?: { - /** - * The type of the Instrument(s). - */ - type?: InstrumentType, - /** - * Name of the Instrument(s) with wildcard support. - */ - name?: string, - } - /** - * Selects instrument by fields of their meter. - */ - meter?: { - /** - * The name of the Meter. - */ - name?: string; - /** - * The version of the Meter. - */ - version?: string; - /** - * The schema URL of the Meter. - */ - schemaUrl?: string; - } -}; - -function isViewOptionsEmpty(options: ViewOptions): boolean { - return (options.name == null && - options.aggregation == null && - options.attributeKeys == null && - options.description == null); + views?: View[]; } /** @@ -108,6 +41,11 @@ export class MeterProvider implements metrics.MeterProvider { constructor(options?: MeterProviderOptions) { this._sharedState = new MeterProviderSharedState(options?.resource ?? Resource.empty()); + if(options?.views != null && options.views.length > 0){ + for(const view of options.views){ + this._sharedState.viewRegistry.addView(view); + } + } } /** @@ -137,37 +75,6 @@ export class MeterProvider implements metrics.MeterProvider { this._sharedState.metricCollectors.push(collector); } - addView(options: ViewOptions, selectorOptions?: SelectorOptions) { - if (isViewOptionsEmpty(options)) { - throw new Error('Cannot create view with no view arguments supplied'); - } - - // the SDK SHOULD NOT allow Views with a specified name to be declared with instrument selectors that - // may select more than one instrument (e.g. wild card instrument name) in the same Meter. - if (options.name != null && - (selectorOptions?.instrument?.name == null || - PatternPredicate.hasWildcard(selectorOptions.instrument.name))) { - throw new Error('Views with a specified name must be declared with an instrument selector that selects at most one instrument per meter.'); - } - - // Create AttributesProcessor if attributeKeys are defined set. - let attributesProcessor = undefined; - if (options.attributeKeys != null) { - attributesProcessor = new FilteringAttributesProcessor(options.attributeKeys); - } - - const view = new View({ - name: options.name, - description: options.description, - aggregation: options.aggregation, - attributesProcessor: attributesProcessor - }); - const instrument = new InstrumentSelector(selectorOptions?.instrument); - const meter = new MeterSelector(selectorOptions?.meter); - - this._sharedState.viewRegistry.addView(view, instrument, meter); - } - /** * Flush all buffered data and shut down the MeterProvider and all registered * MetricReaders. diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Histogram.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Histogram.ts index 60a1d484d4..8566f4aa7f 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Histogram.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Histogram.ts @@ -39,7 +39,7 @@ function createNewEmptyCheckpoint(boundaries: number[]): Histogram { count: 0, hasMinMax: false, min: Infinity, - max: -1 + max: -Infinity }; } @@ -115,7 +115,7 @@ export class HistogramAggregator implements Aggregator { } let min = Infinity; - let max = -1; + let max = -Infinity; if (this._recordMinMax) { if (previousValue.hasMinMax && deltaValue.hasMinMax) { @@ -167,7 +167,7 @@ export class HistogramAggregator implements Aggregator { sum: currentValue.sum - previousValue.sum, hasMinMax: false, min: Infinity, - max: -1 + max: -Infinity }); } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/LastValue.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/LastValue.ts index 0905b03daf..dfdee59b65 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/LastValue.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/LastValue.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { LastValue, AggregatorKind, Aggregator, Accumulation, AccumulationRecord } from './types'; +import { Accumulation, AccumulationRecord, Aggregator, AggregatorKind, LastValue } from './types'; import { HrTime } from '@opentelemetry/api'; import { hrTime, hrTimeToMicroseconds } from '@opentelemetry/core'; -import { DataPointType, SingularMetricData } from '../export/MetricData'; +import { DataPointType, GaugeMetricData } from '../export/MetricData'; import { InstrumentDescriptor } from '../InstrumentDescriptor'; import { Maybe } from '../utils'; import { AggregationTemporality } from '../export/AggregationTemporality'; @@ -74,11 +74,11 @@ export class LastValueAggregator implements Aggregator { descriptor: InstrumentDescriptor, aggregationTemporality: AggregationTemporality, accumulationByAttributes: AccumulationRecord[], - endTime: HrTime): Maybe { + endTime: HrTime): Maybe { return { descriptor, aggregationTemporality, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: accumulationByAttributes.map(([attributes, accumulation]) => { return { attributes, diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Sum.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Sum.ts index 1482841106..f5e18d796f 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Sum.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/aggregator/Sum.ts @@ -16,7 +16,7 @@ import { Sum, AggregatorKind, Aggregator, Accumulation, AccumulationRecord } from './types'; import { HrTime } from '@opentelemetry/api'; -import { DataPointType, SingularMetricData } from '../export/MetricData'; +import { DataPointType, SumMetricData } from '../export/MetricData'; import { InstrumentDescriptor } from '../InstrumentDescriptor'; import { Maybe } from '../utils'; import { AggregationTemporality } from '../export/AggregationTemporality'; @@ -83,11 +83,11 @@ export class SumAggregator implements Aggregator { descriptor: InstrumentDescriptor, aggregationTemporality: AggregationTemporality, accumulationByAttributes: AccumulationRecord[], - endTime: HrTime): Maybe { + endTime: HrTime): Maybe { return { descriptor, aggregationTemporality, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, dataPoints: accumulationByAttributes.map(([attributes, accumulation]) => { return { attributes, @@ -95,7 +95,8 @@ export class SumAggregator implements Aggregator { endTime, value: accumulation.toPointValue(), }; - }) + }), + isMonotonic: this.monotonic }; } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricData.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricData.ts index 74efb96531..0522f10dea 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricData.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricData.ts @@ -38,8 +38,14 @@ export interface BaseMetricData { * Represents a metric data aggregated by either a LastValueAggregation or * SumAggregation. */ -export interface SingularMetricData extends BaseMetricData { - readonly dataPointType: DataPointType.SINGULAR; +export interface SumMetricData extends BaseMetricData { + readonly dataPointType: DataPointType.SUM; + readonly dataPoints: DataPoint[]; + readonly isMonotonic: boolean; +} + +export interface GaugeMetricData extends BaseMetricData { + readonly dataPointType: DataPointType.GAUGE; readonly dataPoints: DataPoint[]; } @@ -54,7 +60,7 @@ export interface HistogramMetricData extends BaseMetricData { /** * Represents an aggregated metric data. */ -export type MetricData = SingularMetricData | HistogramMetricData; +export type MetricData = SumMetricData | GaugeMetricData | HistogramMetricData; export interface ScopeMetrics { scope: InstrumentationScope; @@ -87,10 +93,6 @@ export interface CollectionResult { * The aggregated point data type. */ export enum DataPointType { - /** - * A singular metric data point has only a single numeric value. - */ - SINGULAR, /** * A histogram data point contains a histogram statistics of collected * values with a list of explicit bucket boundaries and statistics such @@ -104,6 +106,15 @@ export enum DataPointType { * and sum of all collected values. */ EXPONENTIAL_HISTOGRAM, + /** + * A gauge metric data point has only a single numeric value. + */ + GAUGE, + /** + * A sum metric data point has a single numeric value and a + * monotonicity-indicator. + */ + SUM } /** diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts index a43ec3bcbc..e60bb31dae 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts @@ -27,3 +27,4 @@ export * from './MeterProvider'; export * from './ObservableResult'; export { TimeoutError } from './utils'; export * from './view/Aggregation'; +export * from './view/View'; diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/view/View.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/view/View.ts index 179b5807ef..ecc3f92c5c 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/view/View.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/view/View.ts @@ -14,53 +14,199 @@ * limitations under the License. */ +import { PatternPredicate } from './Predicate'; +import { AttributesProcessor, FilteringAttributesProcessor } from './AttributesProcessor'; +import { InstrumentSelector } from './InstrumentSelector'; +import { MeterSelector } from './MeterSelector'; import { Aggregation } from './Aggregation'; -import { AttributesProcessor } from './AttributesProcessor'; +import { InstrumentType } from '../InstrumentDescriptor'; -// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view - -export interface ViewStreamConfig { +export type ViewOptions = { /** - * the name of the resulting metric to generate, or null if the same as the instrument. + * Alters the metric stream: + * This will be used as the name of the metrics stream. + * If not provided, the original Instrument name will be used. */ name?: string; /** - * the name of the resulting metric to generate, or null if the same as the instrument. + * Alters the metric stream: + * This will be used as the description of the metrics stream. + * If not provided, the original Instrument description will be used by default. + * + * @example changes the description of all selected instruments to 'sample description' + * description: 'sample description' */ description?: string; /** - * the aggregation used for this 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. + * + * @example drops all attributes with top-level keys except for 'myAttr' and 'myOtherAttr' + * attributeKeys: ['myAttr', 'myOtherAttr'] + * @example drops all attributes + * attributeKeys: [] + */ + attributeKeys?: string[]; + /** + * Alters the metric stream: + * Alters the {@link Aggregation} of the metric stream. + * + * @example changes the aggregation of the selected instrument(s) to ExplicitBucketHistogramAggregation + * aggregation: new ExplicitBucketHistogramAggregation([1, 10, 100]) + * @example changes the aggregation of the selected instrument(s) to LastValueAggregation + * aggregation: new LastValueAggregation() */ aggregation?: Aggregation; /** - * processor of attributes before performing aggregation. + * Instrument selection criteria: + * The original type of the Instrument(s). + * + * @example selects all counters + * instrumentType: InstrumentType.COUNTER + * @example selects all histograms + * instrumentType: InstrumentType.HISTOGRAM + */ + instrumentType?: InstrumentType; + /** + * Instrument selection criteria: + * Original name of the Instrument(s) with wildcard support. + * + * @example select all instruments + * instrumentName: '*' + * @example select all instruments starting with 'my.instruments.' + * instrumentName: 'my.instruments.*' + * @example select all instruments named 'my.instrument.requests' exactly + * instrumentName: 'my.instruments.requests' + */ + instrumentName?: string; + /** + * Instrument selection criteria: + * The name of the Meter. No wildcard support, name must match the meter exactly. + * + * @example select all meters named 'example.component.app' exactly + * meterName: 'example.component.app' + */ + meterName?: string; + /** + * Instrument selection criteria: + * The version of the Meter. No wildcard support, version must match exactly. + * + * @example + * meterVersion: '1.0.1' */ - attributesProcessor?: AttributesProcessor; + meterVersion?: string; + /** + * Instrument selection criteria: + * The schema URL of the Meter. No wildcard support, schema URL must match exactly. + * + * @example Select all meters with schema URL 'https://example.com/schema' exactly. + * meterSchemaUrl: 'https://example.com/schema' + */ + meterSchemaUrl?: string; +}; + +function isSelectorNotProvided(options: ViewOptions): boolean { + return (options.instrumentName == null && + options.instrumentType == null && + options.meterName == null && + options.meterVersion == null && + options.meterSchemaUrl == null); } /** - * A View provides the flexibility to customize the metrics that are output by - * the SDK. For example, the view can - * - customize which Instruments are to be processed/ignored. - * - customize the aggregation. - * - customize which attribute(s) are to be reported as metrics dimension(s). - * - add additional dimension(s) from the {@link Context}. + * Can be passed to a {@link MeterProvider} to select instruments and alter their metric stream. */ export class View { readonly name?: string; readonly description?: string; readonly aggregation: Aggregation; readonly attributesProcessor: AttributesProcessor; + readonly instrumentSelector: InstrumentSelector; + readonly meterSelector: MeterSelector; /** - * Construct a metric view + * Create a new {@link View} instance. * - * @param config how the result metric streams were configured + * Parameters can be categorized as two types: + * Instrument selection criteria: Used to describe the instrument(s) this view will be applied to. + * Will be treated as additive (the Instrument has to meet all the provided criteria to be selected). + * + * Metric stream altering: Alter the metric stream of instruments selected by instrument selection criteria. + * + * @param viewOptions {@link ViewOptions} for altering the metric stream and instrument selection. + * @param viewOptions.name + * Alters the metric stream: + * This will be used as the name of the metrics stream. + * If not provided, the original Instrument name will be used. + * @param viewOptions.description + * Alters the metric stream: + * This will be used as the description of the metrics stream. + * If not provided, the original Instrument description will be used by default. + * @param viewOptions.attributeKeys + * 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.aggregation + * Alters the metric stream: + * Alters the {@link Aggregation} of the metric stream. + * @param viewOptions.instrumentName + * Instrument selection criteria: + * Original name of the Instrument(s) with wildcard support. + * @param viewOptions.instrumentType + * Instrument selection criteria: + * The original type of the Instrument(s). + * @param viewOptions.meterName + * Instrument selection criteria: + * The name of the Meter. No wildcard support, name must match the meter exactly. + * @param viewOptions.meterVersion + * Instrument selection criteria: + * The version of the Meter. No wildcard support, version must match exactly. + * @param viewOptions.meterSchemaUrl + * Instrument selection criteria: + * The schema URL of the Meter. No wildcard support, schema URL must match exactly. + * + * @example + * // Create a view that changes the Instrument 'my.instrument' to use to an + * // ExplicitBucketHistogramAggregation with the boundaries [20, 30, 40] + * new View({ + * aggregation: new ExplicitBucketHistogramAggregation([20, 30, 40]), + * instrumentName: 'my.instrument' + * }) */ - constructor(config?: ViewStreamConfig) { - this.name = config?.name; - this.description = config?.description; - this.aggregation = config?.aggregation ?? Aggregation.Default(); - this.attributesProcessor = config?.attributesProcessor ?? AttributesProcessor.Noop(); + constructor(viewOptions: ViewOptions) { + // If no criteria is provided, the SDK SHOULD treat it as an error. + // It is recommended that the SDK implementations fail fast. + if (isSelectorNotProvided(viewOptions)) { + throw new Error('Cannot create view with no selector arguments supplied'); + } + + // the SDK SHOULD NOT allow Views with a specified name to be declared with instrument selectors that + // may select more than one instrument (e.g. wild card instrument name) in the same Meter. + if (viewOptions.name != null && + (viewOptions?.instrumentName == null || + PatternPredicate.hasWildcard(viewOptions.instrumentName))) { + throw new Error('Views with a specified name must be declared with an instrument selector that selects at most one instrument per meter.'); + } + + // Create AttributesProcessor if attributeKeys are defined set. + if (viewOptions.attributeKeys != null) { + this.attributesProcessor = new FilteringAttributesProcessor(viewOptions.attributeKeys); + } else { + this.attributesProcessor = AttributesProcessor.Noop(); + } + + this.name = viewOptions.name; + this.description = viewOptions.description; + this.aggregation = viewOptions.aggregation ?? Aggregation.Default(); + this.instrumentSelector = new InstrumentSelector({ + name: viewOptions.instrumentName, + type: viewOptions.instrumentType, + }); + this.meterSelector = new MeterSelector({ + name: viewOptions.meterName, + version: viewOptions.meterVersion, + schemaUrl: viewOptions.meterSchemaUrl + }); } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/view/ViewRegistry.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/view/ViewRegistry.ts index 40fb5ebc51..1a26387d10 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/view/ViewRegistry.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/view/ViewRegistry.ts @@ -20,22 +20,14 @@ import { InstrumentSelector } from './InstrumentSelector'; import { MeterSelector } from './MeterSelector'; import { View } from './View'; -interface RegisteredView { - instrumentSelector: InstrumentSelector; - meterSelector: MeterSelector; - view: View; -} - export class ViewRegistry { - private static DEFAULT_VIEW = new View(); - private _registeredViews: RegisteredView[] = []; + private static DEFAULT_VIEW = new View({ + instrumentName: '*' + }); + private _registeredViews: View[] = []; - addView(view: View, instrumentSelector: InstrumentSelector = new InstrumentSelector(), meterSelector: MeterSelector = new MeterSelector()) { - this._registeredViews.push({ - instrumentSelector, - meterSelector, - view, - }); + addView(view: View) { + this._registeredViews.push(view); } findViews(instrument: InstrumentDescriptor, meter: InstrumentationScope): View[] { @@ -43,8 +35,7 @@ export class ViewRegistry { .filter(registeredView => { return this._matchInstrument(registeredView.instrumentSelector, instrument) && this._matchMeter(registeredView.meterSelector, meter); - }) - .map(it => it.view); + }); if (views.length === 0) { return [ViewRegistry.DEFAULT_VIEW]; 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 bac7776c67..bf4c496084 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts @@ -83,7 +83,8 @@ describe('Instruments', () => { type: InstrumentType.COUNTER, valueType: ValueType.INT, }, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { attributes: {}, @@ -99,7 +100,8 @@ describe('Instruments', () => { // add negative values should not be observable. counter.add(-1.1); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { attributes: {}, @@ -125,7 +127,8 @@ describe('Instruments', () => { counter.add(1, { foo: 'bar' }); counter.add(1.2, { foo: 'bar' }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { attributes: {}, @@ -141,7 +144,8 @@ describe('Instruments', () => { // add negative values should not be observable. counter.add(-1.1); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { attributes: {}, @@ -199,7 +203,8 @@ describe('Instruments', () => { type: InstrumentType.UP_DOWN_COUNTER, valueType: ValueType.INT, }, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -224,7 +229,8 @@ describe('Instruments', () => { upDownCounter.add(4, { foo: 'bar' }); upDownCounter.add(1.1, { foo: 'bar' }); await validateExport(deltaReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -488,7 +494,8 @@ describe('Instruments', () => { }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -501,7 +508,8 @@ describe('Instruments', () => { ], }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -543,7 +551,8 @@ describe('Instruments', () => { }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -556,7 +565,8 @@ describe('Instruments', () => { ], }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { attributes: {}, @@ -603,7 +613,7 @@ describe('Instruments', () => { }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: [ { attributes: {}, @@ -616,7 +626,7 @@ describe('Instruments', () => { ], }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: [ { attributes: {}, @@ -629,7 +639,7 @@ describe('Instruments', () => { ], }); await validateExport(cumulativeReader, { - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: [ { attributes: {}, @@ -667,8 +677,9 @@ interface ValidateMetricData { resource?: Resource; instrumentationScope?: InstrumentationScope; descriptor?: InstrumentDescriptor; - dataPointType?: DataPointType, + dataPointType?: DataPointType; dataPoints?: Partial>>[]; + isMonotonic?: boolean; } async function validateExport(reader: MetricReader, expected: ValidateMetricData) { diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts index 0d4e5d80db..3d7dc68c6a 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts @@ -25,6 +25,7 @@ import { } from './util'; import { TestMetricReader } from './export/TestMetricReader'; import * as sinon from 'sinon'; +import { View } from '../src/view/View'; describe('MeterProvider', () => { afterEach(() => { @@ -112,78 +113,22 @@ describe('MeterProvider', () => { }); describe('addView', () => { - it('with named view and instrument wildcard should throw', () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); - - // Throws with wildcard character only. - assert.throws(() => meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - name: '*' - } - })); - - // Throws with wildcard character in instrument name. - assert.throws(() => meterProvider.addView({ - name: 'renamed-instrument' - }, { - instrument: { - name: 'other.instrument.*' - } - })); - }); - - it('with named view and instrument type selector should throw', () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); - - assert.throws(() => meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - type: InstrumentType.COUNTER - } - })); - }); - - it('with named view and no instrument selector should throw', () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); - - assert.throws(() => meterProvider.addView({ - name: 'renamed-instrument' - })); - - assert.throws(() => meterProvider.addView({ - name: 'renamed-instrument' - }, - {})); - }); - - it('with no view parameters should throw', () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); - - assert.throws(() => meterProvider.addView({})); - }); - it('with existing instrument should rename', async () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); + const meterProvider = new MeterProvider({ + resource: defaultResource, + // Add view to rename 'non-renamed-instrument' to 'renamed-instrument' + views: [ + new View({ + name: 'renamed-instrument', + description: 'my renamed instrument', + instrumentName: 'non-renamed-instrument', + }) + ] + }); const reader = new TestMetricReader(); meterProvider.addMetricReader(reader); - // Add view to rename 'non-renamed-instrument' to 'renamed-instrument' - meterProvider.addView({ - name: 'renamed-instrument', - description: 'my renamed instrument' - }, - { - instrument: { - name: 'non-renamed-instrument', - }, - }); - // Create meter and instrument. const myMeter = meterProvider.getMeter('meter1', 'v1.0.0'); const counter = myMeter.createCounter('non-renamed-instrument'); @@ -206,7 +151,7 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); // View updated name and description. - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'renamed-instrument', type: InstrumentType.COUNTER, description: 'my renamed instrument' @@ -228,21 +173,21 @@ describe('MeterProvider', () => { }); it('with attributeKeys should drop non-listed attributes', async () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); - - const reader = new TestMetricReader(); - meterProvider.addMetricReader(reader); // Add view to drop all attributes except 'attrib1' - meterProvider.addView({ - attributeKeys: ['attrib1'] - }, - { - instrument: { - name: 'non-renamed-instrument', - } + const meterProvider = new MeterProvider({ + resource: defaultResource, + views: [ + new View({ + attributeKeys: ['attrib1'], + instrumentName: 'non-renamed-instrument' + }) + ] }); + const reader = new TestMetricReader(); + meterProvider.addMetricReader(reader); + // Create meter and instrument. const myMeter = meterProvider.getMeter('meter1', 'v1.0.0'); const counter = myMeter.createCounter('non-renamed-instrument'); @@ -265,7 +210,7 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); // View updated name and description. - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'non-renamed-instrument', type: InstrumentType.COUNTER, }); @@ -285,21 +230,17 @@ describe('MeterProvider', () => { }); it('with no meter name should apply view to instruments of all meters', async () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); + // Add view that renames 'test-counter' to 'renamed-instrument' + const meterProvider = new MeterProvider({ + resource: defaultResource, + views: [ + new View({ name: 'renamed-instrument', instrumentName: 'test-counter' }) + ] + }); const reader = new TestMetricReader(); meterProvider.addMetricReader(reader); - // Add view that renames 'test-counter' to 'renamed-instrument' - meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - name: 'test-counter' - } - }); - // Create two meters. const meter1 = meterProvider.getMeter('meter1', 'v1.0.0'); const meter2 = meterProvider.getMeter('meter2', 'v1.0.0'); @@ -329,7 +270,7 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); // View updated the name to 'renamed-instrument' and instrument is still a Counter - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'renamed-instrument', type: InstrumentType.COUNTER, }); @@ -344,31 +285,28 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[1].metrics.length, 1); // View updated the name to 'renamed-instrument' and instrument is still a Counter - assertMetricData(resourceMetrics.scopeMetrics[1].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[1].metrics[0], DataPointType.SUM, { name: 'renamed-instrument', type: InstrumentType.COUNTER }); }); it('with meter name should apply view to only the selected meter', async () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); + const meterProvider = new MeterProvider({ + resource: defaultResource, + views: [ + // Add view that renames 'test-counter' to 'renamed-instrument' on 'meter1' + new View({ + name: 'renamed-instrument', + instrumentName: 'test-counter', + meterName: 'meter1' + }) + ] + }); const reader = new TestMetricReader(); meterProvider.addMetricReader(reader); - // Add view that renames 'test-counter' to 'renamed-instrument' on 'meter1' - meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - name: 'test-counter' - }, - meter: { - name: 'meter1' - } - }); - // Create two meters. const meter1 = meterProvider.getMeter('meter1', 'v1.0.0'); const meter2 = meterProvider.getMeter('meter2', 'v1.0.0'); @@ -398,7 +336,7 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); // View updated the name to 'renamed-instrument' and instrument is still a Counter - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'renamed-instrument', type: InstrumentType.COUNTER }); @@ -413,42 +351,32 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[1].metrics.length, 1); // No updated name on 'test-counter'. - assertMetricData(resourceMetrics.scopeMetrics[1].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[1].metrics[0], DataPointType.SUM, { name: 'test-counter', type: InstrumentType.COUNTER }); }); it('with different instrument types does not throw', async () => { - const meterProvider = new MeterProvider({ resource: defaultResource }); + const meterProvider = new MeterProvider({ + resource: defaultResource, + // Add Views to rename both instruments (of different types) to the same name. + views: [ + new View({ + name: 'renamed-instrument', + instrumentName: 'test-counter', + meterName: 'meter1' + }), + new View({ + name: 'renamed-instrument', + instrumentName: 'test-histogram', + meterName: 'meter1' + }) + ] + }); const reader = new TestMetricReader(); meterProvider.addMetricReader(reader); - // Add Views to rename both instruments (of different types) to the same name. - meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - name: 'test-counter' - }, - meter: { - name: 'meter1' - } - }); - - meterProvider.addView({ - name: 'renamed-instrument' - }, - { - instrument: { - name: 'test-histogram' - }, - meter: { - name: 'meter1' - } - }); - // Create meter and instruments. const meter = meterProvider.getMeter('meter1', 'v1.0.0'); const counter = meter.createCounter('test-counter', { unit: 'ms' }); @@ -475,7 +403,7 @@ describe('MeterProvider', () => { assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 2); // Both 'renamed-instrument' are still exported with their types. - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'renamed-instrument', type: InstrumentType.COUNTER }); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Histogram.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Histogram.test.ts index b1f6e05c0a..3d0b3a1264 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Histogram.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Histogram.test.ts @@ -51,6 +51,29 @@ describe('HistogramAggregator', () => { assert.deepStrictEqual(aggregator.merge(prev, delta), expected); }); + + it('with only negatives', () => { + const aggregator = new HistogramAggregator([1, 10, 100], true); + const prev = aggregator.createAccumulation([0, 0]); + prev.record(-10); + prev.record(-20); + + const delta = aggregator.createAccumulation([1, 1]); + delta.record(-5); + delta.record(-30); + + assert.deepStrictEqual(aggregator.merge(prev, delta).toPointValue(), { + buckets: { + boundaries: [1, 10, 100], + counts: [4, 0, 0, 0] + }, + count: 4, + hasMinMax: true, + max: -5, + min: -30, + sum: -65 + }); + }); }); describe('diff', () => { @@ -77,7 +100,7 @@ describe('HistogramAggregator', () => { sum: 13, hasMinMax: false, min: Infinity, - max: -1 + max: -Infinity }); assert.deepStrictEqual(aggregator.diff(prev, curr), expected); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/LastValue.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/LastValue.test.ts index 420cabde65..b309af6b6c 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/LastValue.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/LastValue.test.ts @@ -114,7 +114,7 @@ describe('LastValueAggregator', () => { const expected: MetricData = { descriptor: defaultInstrumentDescriptor, aggregationTemporality: AggregationTemporality.CUMULATIVE, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: [ { attributes: {}, diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Sum.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Sum.test.ts index ac3df809a6..67034ba6ca 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Sum.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/aggregator/Sum.test.ts @@ -93,7 +93,8 @@ describe('SumAggregator', () => { const expected: MetricData = { descriptor: defaultInstrumentDescriptor, aggregationTemporality: AggregationTemporality.CUMULATIVE, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { attributes: {}, diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/AsyncMetricStorage.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/AsyncMetricStorage.test.ts index dc0e7fe072..0500d012e8 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/AsyncMetricStorage.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/AsyncMetricStorage.test.ts @@ -68,7 +68,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 3); assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, collectionTime, collectionTime); assertDataPoint(metric.dataPoints[1], { key: '2' }, 2, collectionTime, collectionTime); @@ -85,7 +85,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 0); } @@ -102,7 +102,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 3); // All values were diffed. StartTime is being reset for gaps. assertDataPoint(metric.dataPoints[0], { key: '1' }, 3, collectionTime, collectionTime); @@ -140,7 +140,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 100, collectionTime, collectionTime); lastCollectionTime = collectionTime; @@ -159,7 +159,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, lastCollectionTime, collectionTime); lastCollectionTime = collectionTime; @@ -178,7 +178,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 49, lastCollectionTime, collectionTime); } @@ -213,7 +213,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 100, collectionTime, collectionTime); lastCollectionTime = collectionTime; @@ -232,7 +232,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, -99, lastCollectionTime, collectionTime); lastCollectionTime = collectionTime; @@ -252,7 +252,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 49, lastCollectionTime, collectionTime); } @@ -292,7 +292,7 @@ describe('AsyncMetricStorage', () => { collectionTime); startTime = collectionTime; - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 3); assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, startTime, collectionTime); assertDataPoint(metric.dataPoints[1], { key: '2' }, 2, startTime, collectionTime); @@ -309,7 +309,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 3); assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, startTime, collectionTime); assertDataPoint(metric.dataPoints[1], { key: '2' }, 2, startTime, collectionTime); @@ -329,7 +329,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 3); assertDataPoint(metric.dataPoints[0], { key: '1' }, 4, startTime, collectionTime); assertDataPoint(metric.dataPoints[1], { key: '2' }, 5, startTime, collectionTime); @@ -367,7 +367,7 @@ describe('AsyncMetricStorage', () => { collectionTime); startTime = collectionTime; - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 100, startTime, collectionTime); } @@ -385,7 +385,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); // The startTime should be reset. assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, collectionTime, collectionTime); @@ -405,7 +405,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 50, startTime, collectionTime); } @@ -441,7 +441,7 @@ describe('AsyncMetricStorage', () => { collectionTime); startTime = collectionTime; - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 100, startTime, collectionTime); } @@ -459,7 +459,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); // No reset on the value or the startTime assertDataPoint(metric.dataPoints[0], { key: '1' }, 1, startTime, collectionTime); @@ -478,7 +478,7 @@ describe('AsyncMetricStorage', () => { collectors, collectionTime); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], { key: '1' }, 50, startTime, collectionTime); } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MeterSharedState.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MeterSharedState.test.ts index 1ecc5732c1..bacbe87e3f 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MeterSharedState.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MeterSharedState.test.ts @@ -21,7 +21,8 @@ import { Meter, MeterProvider, DataPointType, - CollectionResult + CollectionResult, + View } from '../../src'; import { assertMetricData, defaultInstrumentationScope, defaultResource, sleep } from '../util'; import { TestMetricReader } from '../export/TestMetricReader'; @@ -33,8 +34,8 @@ describe('MeterSharedState', () => { }); describe('collect', () => { - function setupInstruments() { - const meterProvider = new MeterProvider({ resource: defaultResource }); + function setupInstruments(views?: View[]) { + const meterProvider = new MeterProvider({ resource: defaultResource, views: views }); const cumulativeReader = new TestMetricReader(() => AggregationTemporality.CUMULATIVE); meterProvider.addMetricReader(cumulativeReader); @@ -51,7 +52,7 @@ describe('MeterSharedState', () => { }) as Meter; const meterSharedState = meter['_meterSharedState'] as MeterSharedState; - return { metricCollectors, cumulativeCollector, deltaCollector, meter, meterSharedState, meterProvider }; + return { metricCollectors, cumulativeCollector, deltaCollector, meter, meterSharedState }; } it('should collect sync metrics', async () => { @@ -68,7 +69,7 @@ describe('MeterSharedState', () => { assert.strictEqual(errors.length, 0); assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1); - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'test', }); })); @@ -76,12 +77,12 @@ describe('MeterSharedState', () => { it('should collect sync metrics with views', async () => { /** preparing test instrumentations */ - const { metricCollectors, meter, meterProvider } = setupInstruments(); + const { metricCollectors, meter } = setupInstruments([ + new View({ name: 'foo', instrumentName: 'test' }), + new View({ name: 'bar', instrumentName: 'test' }) + ]); /** creating metric events */ - meterProvider.addView({ name: 'foo' }, { instrument: { name: 'test' } }); - meterProvider.addView({ name: 'bar' }, { instrument: { name: 'test' } }); - const counter = meter.createCounter('test'); /** collect metrics */ @@ -91,10 +92,10 @@ describe('MeterSharedState', () => { assert.strictEqual(errors.length, 0); assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 2); - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'foo', }); - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[1], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[1], DataPointType.SUM, { name: 'bar', }); })); @@ -135,20 +136,12 @@ describe('MeterSharedState', () => { it('should call observable callback once with view-ed async instruments', async () => { /** preparing test instrumentations */ - const { metricCollectors, meter, meterProvider } = setupInstruments(); + const { metricCollectors, meter } = setupInstruments([ + new View({ name: 'foo', instrumentName: 'test' }), + new View({ name: 'bar', instrumentName: 'test' }) + ]); /** creating metric events */ - meterProvider.addView({ name: 'foo' }, { - instrument: { - name: 'test', - }, - }); - meterProvider.addView({ name: 'bar' }, { - instrument: { - name: 'test', - }, - }); - let observableCalledCount = 0; const observableCounter = meter.createObservableCounter('test'); observableCounter.addCallback(observableResult => { @@ -164,10 +157,10 @@ describe('MeterSharedState', () => { assert.strictEqual(errors.length, 0); assert.strictEqual(resourceMetrics.scopeMetrics.length, 1); assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 2); - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[0], DataPointType.SUM, { name: 'foo' }); - assertMetricData(resourceMetrics.scopeMetrics[0].metrics[1], DataPointType.SINGULAR, { + assertMetricData(resourceMetrics.scopeMetrics[0].metrics[1], DataPointType.SUM, { name: 'bar' }); } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts index 2070eb2e41..77047d9f52 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts @@ -86,7 +86,7 @@ describe('MetricCollector', () => { /** checking batch[0] */ const metricData1 = metrics[0]; - assertMetricData(metricData1, DataPointType.SINGULAR, { + assertMetricData(metricData1, DataPointType.SUM, { name: 'counter1' }); assert.strictEqual(metricData1.dataPoints.length, 2); @@ -95,7 +95,7 @@ describe('MetricCollector', () => { /** checking batch[1] */ const metricData2 = metrics[1]; - assertMetricData(metricData2, DataPointType.SINGULAR, { + assertMetricData(metricData2, DataPointType.SUM, { name: 'counter2' }); assert.strictEqual(metricData2.dataPoints.length, 1); @@ -136,7 +136,7 @@ describe('MetricCollector', () => { /** checking batch[0] */ const metricData1 = metrics[0]; - assertMetricData(metricData1, DataPointType.SINGULAR, { + assertMetricData(metricData1, DataPointType.SUM, { name: 'observable1' }); assert.strictEqual(metricData1.dataPoints.length, 2); @@ -145,7 +145,7 @@ describe('MetricCollector', () => { /** checking batch[1] */ const metricData2 = metrics[1]; - assertMetricData(metricData2, DataPointType.SINGULAR, { + assertMetricData(metricData2, DataPointType.SUM, { name: 'observable2' }); assert.strictEqual(metricData2.dataPoints.length, 2); @@ -154,7 +154,7 @@ describe('MetricCollector', () => { /** checking batch[2] */ const metricData3 = metrics[2]; - assertMetricData(metricData3, DataPointType.SINGULAR, { + assertMetricData(metricData3, DataPointType.SUM, { name: 'observable3' }); assert.strictEqual(metricData3.dataPoints.length, 0); @@ -200,13 +200,13 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** observer1 */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[0].dataPoints.length, 0); /** observer2 */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer2' }); assert.strictEqual(metrics[1].dataPoints.length, 1); @@ -230,14 +230,14 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** observer1 */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[0].dataPoints.length, 1); assertDataPoint(metrics[0].dataPoints[0], {}, 100); /** observer2 */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer2' }); assert.strictEqual(metrics[1].dataPoints.length, 1); @@ -268,13 +268,13 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** counter1 data points are collected */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'counter1' }); assert.strictEqual(metrics[0].dataPoints.length, 1); /** observer1 data points are not collected */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[1].dataPoints.length, 0); @@ -320,13 +320,13 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** observer1 */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[0].dataPoints.length, 0); /** observer2 */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer2' }); assert.strictEqual(metrics[1].dataPoints.length, 1); @@ -350,14 +350,14 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** observer1 */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[0].dataPoints.length, 1); assertDataPoint(metrics[0].dataPoints[0], {}, 100); /** observer2 */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer2' }); assert.strictEqual(metrics[1].dataPoints.length, 1); @@ -390,13 +390,13 @@ describe('MetricCollector', () => { assert.strictEqual(metrics.length, 2); /** counter1 data points are collected */ - assertMetricData(metrics[0], DataPointType.SINGULAR, { + assertMetricData(metrics[0], DataPointType.SUM, { name: 'counter1' }); assert.strictEqual(metrics[0].dataPoints.length, 1); /** observer1 data points are not collected */ - assertMetricData(metrics[1], DataPointType.SINGULAR, { + assertMetricData(metrics[1], DataPointType.SUM, { name: 'observer1' }); assert.strictEqual(metrics[1].dataPoints.length, 0); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/SyncMetricStorage.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/SyncMetricStorage.test.ts index 1a95d4c12a..13f0b91492 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/SyncMetricStorage.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/SyncMetricStorage.test.ts @@ -68,7 +68,7 @@ describe('SyncMetricStorage', () => { collectors, [3, 3]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], {}, 6, [0, 0], [3, 3]); } @@ -80,7 +80,7 @@ describe('SyncMetricStorage', () => { collectors, [4, 4]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 0); } @@ -91,7 +91,7 @@ describe('SyncMetricStorage', () => { [deltaCollector], [6, 6]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], {}, 1, [5, 5], [6, 6]); } @@ -115,7 +115,7 @@ describe('SyncMetricStorage', () => { collectors, [3, 3]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], {}, 6, [0, 0], [3, 3]); } @@ -127,7 +127,7 @@ describe('SyncMetricStorage', () => { collectors, [4, 4]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], {}, 6, [0, 0], [4, 4]); } @@ -139,7 +139,7 @@ describe('SyncMetricStorage', () => { collectors, [6, 6]); - assertMetricData(metric, DataPointType.SINGULAR); + assertMetricData(metric, DataPointType.SUM); assert.strictEqual(metric.dataPoints.length, 1); assertDataPoint(metric.dataPoints[0], {}, 7, [0, 0], [6, 6]); } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/TemporalMetricProcessor.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/TemporalMetricProcessor.test.ts index 5595d23f25..27bc2f1bbf 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/TemporalMetricProcessor.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/TemporalMetricProcessor.test.ts @@ -63,7 +63,7 @@ describe('TemporalMetricProcessor', () => { [2, 2]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 1); @@ -80,7 +80,7 @@ describe('TemporalMetricProcessor', () => { [4, 4]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 1); @@ -97,7 +97,7 @@ describe('TemporalMetricProcessor', () => { [5, 5]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 0); @@ -126,7 +126,7 @@ describe('TemporalMetricProcessor', () => { [2, 2]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 1); @@ -142,7 +142,7 @@ describe('TemporalMetricProcessor', () => { [3, 3]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 1); @@ -170,7 +170,7 @@ describe('TemporalMetricProcessor', () => { [2, 2]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.CUMULATIVE); assert.strictEqual(metric.dataPoints.length, 1); @@ -187,7 +187,7 @@ describe('TemporalMetricProcessor', () => { [4, 4]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.CUMULATIVE); assert.strictEqual(metric.dataPoints.length, 1); @@ -216,7 +216,7 @@ describe('TemporalMetricProcessor', () => { [2, 2]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.CUMULATIVE); assert.strictEqual(metric.dataPoints.length, 1); @@ -233,7 +233,7 @@ describe('TemporalMetricProcessor', () => { [4, 4]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.DELTA); assert.strictEqual(metric.dataPoints.length, 1); @@ -248,7 +248,7 @@ describe('TemporalMetricProcessor', () => { [5, 5]); assertMetricData(metric, - DataPointType.SINGULAR, + DataPointType.SUM, defaultInstrumentDescriptor, AggregationTemporality.CUMULATIVE); assert.strictEqual(metric.dataPoints.length, 1); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/view/View.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/view/View.test.ts index 99520d9933..d6ad05da8b 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/view/View.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/view/View.test.ts @@ -15,18 +15,69 @@ */ import * as assert from 'assert'; -import { Aggregation } from '../../src/view/Aggregation'; import { AttributesProcessor } from '../../src/view/AttributesProcessor'; import { View } from '../../src/view/View'; +import { InstrumentType, Aggregation, ExplicitBucketHistogramAggregation } from '../../src'; describe('View', () => { describe('constructor', () => { - it('should construct view without arguments', () => { - const view = new View(); - assert.strictEqual(view.name, undefined); - assert.strictEqual(view.description, undefined); - assert.strictEqual(view.aggregation, Aggregation.Default()); - assert.strictEqual(view.attributesProcessor, AttributesProcessor.Noop()); + it('should construct default view with no view arguments provided', () => { + { + const view = new View({instrumentName: '*'}); + assert.strictEqual(view.name, undefined); + assert.strictEqual(view.description, undefined); + assert.strictEqual(view.aggregation, Aggregation.Default()); + assert.strictEqual(view.attributesProcessor, AttributesProcessor.Noop()); + } + { + const view = new View({meterName: '*'}); + assert.strictEqual(view.name, undefined); + assert.strictEqual(view.description, undefined); + assert.strictEqual(view.aggregation, Aggregation.Default()); + assert.strictEqual(view.attributesProcessor, AttributesProcessor.Noop()); + } + }); + + it('without at least one selector option should throw', () => { + // would do nothing + assert.throws(() => new View({})); + // would implicitly rename all instruments to 'name' + assert.throws(() => new View({ name: 'name' })); + // would implicitly drop all attribute keys on all instruments except 'key' + assert.throws(() => new View({ attributeKeys: ['key'] })); + // would implicitly rename all instruments to description + assert.throws(() => new View({ description: 'description' })); + // would implicitly change all instruments to use histogram aggregation + assert.throws(() => new View({ + aggregation: new ExplicitBucketHistogramAggregation([1, 100]) + })); + }); + + it('with named view and no instrument selector should throw', () => { + assert.throws(() => new View({ + name: 'named-view' + })); + }); + + it('with named view and instrument wildcard should throw', () => { + // Throws with wildcard character only. + assert.throws(() => new View({ + name: 'renamed-instrument', + instrumentName: '*' + })); + + // Throws with wildcard character in instrument name. + assert.throws(() => new View({ + name: 'renamed-instrument', + instrumentName: 'instrument.name.*' + })); + }); + + it('with named view and instrument type selector should throw', () => { + assert.throws(() => new View({ + name: 'renamed-instrument', + instrumentType: InstrumentType.COUNTER + })); }); }); }); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/view/ViewRegistry.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/view/ViewRegistry.test.ts index 3e8aaaa1a2..a56d7b65e0 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/view/ViewRegistry.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/view/ViewRegistry.test.ts @@ -15,12 +15,10 @@ */ import * as assert from 'assert'; -import { InstrumentType } from '../../src/InstrumentDescriptor'; +import { InstrumentType } from '../../src'; import { ViewRegistry } from '../../src/view/ViewRegistry'; -import { View } from '../../src/view/View'; -import { InstrumentSelector } from '../../src/view/InstrumentSelector'; -import { MeterSelector } from '../../src/view/MeterSelector'; import { defaultInstrumentationScope, defaultInstrumentDescriptor } from '../util'; +import { View } from '../../src'; describe('ViewRegistry', () => { @@ -35,13 +33,8 @@ describe('ViewRegistry', () => { describe('InstrumentSelector', () => { it('should match view with instrument name', () => { const registry = new ViewRegistry(); - registry.addView(new View({ name: 'no-filter' })); - registry.addView(new View({ name: 'foo' }), new InstrumentSelector({ - name: 'foo', - })); - registry.addView(new View({ name: 'bar' }), new InstrumentSelector({ - name: 'bar' - })); + registry.addView(new View({ name: 'foo', instrumentName: 'foo' })); + registry.addView(new View({ name: 'bar', instrumentName: 'bar' })); { const views = registry.findViews({ @@ -49,9 +42,8 @@ describe('ViewRegistry', () => { name: 'foo' }, defaultInstrumentationScope); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'foo'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'foo'); } { @@ -60,20 +52,22 @@ describe('ViewRegistry', () => { name: 'bar' }, defaultInstrumentationScope); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'bar'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'bar'); } }); it('should match view with instrument type', () => { const registry = new ViewRegistry(); - registry.addView(new View({ name: 'no-filter' })); - registry.addView(new View({ name: 'counter' }), new InstrumentSelector({ - type: InstrumentType.COUNTER, + registry.addView(new View({ + name: 'counter', + instrumentName: 'default_metric', + instrumentType: InstrumentType.COUNTER })); - registry.addView(new View({ name: 'histogram' }), new InstrumentSelector({ - type: InstrumentType.HISTOGRAM, + registry.addView(new View({ + name: 'histogram', + instrumentName: 'default_metric', + instrumentType: InstrumentType.HISTOGRAM })); { @@ -82,9 +76,8 @@ describe('ViewRegistry', () => { type: InstrumentType.COUNTER }, defaultInstrumentationScope); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'counter'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'counter'); } { @@ -93,9 +86,8 @@ describe('ViewRegistry', () => { type: InstrumentType.HISTOGRAM }, defaultInstrumentationScope); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'histogram'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'histogram'); } }); }); @@ -103,13 +95,8 @@ describe('ViewRegistry', () => { describe('MeterSelector', () => { it('should match view with meter name', () => { const registry = new ViewRegistry(); - registry.addView(new View({ name: 'no-filter' })); - registry.addView(new View({ name: 'foo' }), undefined, new MeterSelector({ - name: 'foo' - })); - registry.addView(new View({ name: 'bar' }), undefined, new MeterSelector({ - name: 'bar' - })); + registry.addView(new View({ name: 'foo', instrumentName: 'default_metric', meterName: 'foo' })); + registry.addView(new View({ name: 'bar', instrumentName: 'default_metric', meterName: 'bar' })); { const views = registry.findViews(defaultInstrumentDescriptor, { @@ -117,9 +104,8 @@ describe('ViewRegistry', () => { name: 'foo', }); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'foo'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'foo'); } { @@ -128,9 +114,8 @@ describe('ViewRegistry', () => { name: 'bar' }); - assert.strictEqual(views.length, 2); - assert.strictEqual(views[0].name, 'no-filter'); - assert.strictEqual(views[1].name, 'bar'); + assert.strictEqual(views.length, 1); + assert.strictEqual(views[0].name, 'bar'); } }); }); diff --git a/experimental/packages/otlp-proto-exporter-base/.gitignore b/experimental/packages/otlp-proto-exporter-base/.gitignore new file mode 100644 index 0000000000..c82683cbd7 --- /dev/null +++ b/experimental/packages/otlp-proto-exporter-base/.gitignore @@ -0,0 +1,2 @@ +src/generated/* +!src/generated/.gitkeep diff --git a/experimental/packages/otlp-proto-exporter-base/package.json b/experimental/packages/otlp-proto-exporter-base/package.json index 97cbb4dcdd..6a2379ff3e 100644 --- a/experimental/packages/otlp-proto-exporter-base/package.json +++ b/experimental/packages/otlp-proto-exporter-base/package.json @@ -7,15 +7,14 @@ "repository": "open-telemetry/opentelemetry-js", "scripts": { "prepublishOnly": "npm run compile", - "compile": "tsc --build", + "compile": "npm run protos && tsc --build", "clean": "tsc --build --clean", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", - "postcompile": "npm run submodule && npm run protos:copy", - "protos:copy": "cpx protos/opentelemetry/**/*.* build/protos/opentelemetry", + "protos": "npm run submodule && node scripts/protos.js", "submodule": "git submodule sync --recursive && git submodule update --init --recursive", "version": "node ../../../scripts/version-update.js", - "watch": "npm run protos:copy && tsc -w", + "watch": "npm run protos && tsc -w", "precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies", "prewatch": "npm run precompile" }, @@ -37,7 +36,6 @@ "build/src/**/*.js", "build/src/**/*.js.map", "build/src/**/*.d.ts", - "build/protos/**/*.proto", "doc", "LICENSE", "README.md" @@ -52,9 +50,9 @@ "@types/node": "14.17.33", "@types/sinon": "10.0.6", "codecov": "3.8.3", - "cpx": "1.5.0", "mocha": "7.2.0", "nyc": "15.1.0", + "protobufjs": "^6.9.0", "rimraf": "3.0.2", "sinon": "12.0.1", "ts-loader": "8.3.0", @@ -67,7 +65,6 @@ "dependencies": { "@grpc/proto-loader": "^0.6.9", "@opentelemetry/core": "1.4.0", - "@opentelemetry/otlp-exporter-base": "0.30.0", - "protobufjs": "^6.9.0" + "@opentelemetry/otlp-exporter-base": "0.30.0" } } diff --git a/experimental/packages/otlp-proto-exporter-base/scripts/protos.js b/experimental/packages/otlp-proto-exporter-base/scripts/protos.js new file mode 100644 index 0000000000..c5ab118f18 --- /dev/null +++ b/experimental/packages/otlp-proto-exporter-base/scripts/protos.js @@ -0,0 +1,58 @@ +'use strict'; + +const cp = require('child_process'); +const path = require('path'); + +const generatedPath = path.resolve(__dirname, '../src/generated'); +const protosPath = path.resolve(__dirname, '../protos'); +const protos = [ + 'opentelemetry/proto/common/v1/common.proto', + 'opentelemetry/proto/resource/v1/resource.proto', + 'opentelemetry/proto/trace/v1/trace.proto', + 'opentelemetry/proto/collector/trace/v1/trace_service.proto', + 'opentelemetry/proto/metrics/v1/metrics.proto', + 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto', +].map(it => { + return path.join(protosPath, it); +}); + +function exec(command, argv) { + return new Promise((resolve, reject) => { + const child = cp.spawn(process.execPath, [command, ...argv], { + stdio: ['ignore', 'inherit', 'inherit'], + }); + child.on('exit', (code, signal) => { + if (code !== 0) { + reject(new Error(`${command} exited with non-zero code(${code}, ${signal})`)); + return; + } + resolve(); + }) + }); +} + +function pbts(pbjsOutFile) { + const pbtsPath = path.resolve(__dirname, '../node_modules/.bin/pbts'); + const pbtsOptions = [ + '-o', path.join(generatedPath, 'root.d.ts'), + ] + return exec(pbtsPath, [...pbtsOptions, pbjsOutFile]); +} + +async function pbjs(files) { + const pbjsPath = path.resolve(__dirname, '../node_modules/.bin/pbjs'); + const outFile = path.join(generatedPath, 'root.js'); + const pbjsOptions = [ + '-t', 'static-module', + '-w', 'commonjs', + '--null-defaults', + '-o', outFile, + ]; + await exec(pbjsPath, [...pbjsOptions, ...files]); + return outFile; +} + +(async function main() { + const pbjsOut = await pbjs(protos); + await pbts(pbjsOut); +})(); diff --git a/experimental/packages/otlp-proto-exporter-base/src/OTLPProtoExporterNodeBase.ts b/experimental/packages/otlp-proto-exporter-base/src/OTLPProtoExporterNodeBase.ts index 6ff136ed94..c08e2b5ef8 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/OTLPProtoExporterNodeBase.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/OTLPProtoExporterNodeBase.ts @@ -60,16 +60,6 @@ export abstract class OTLPProtoExporterNodeBase< promise.then(popPromise, popPromise); } - override onInit(config: OTLPExporterNodeConfigBase): void { - // defer to next tick and lazy load to avoid loading protobufjs too early - // and making this impossible to be instrumented - setImmediate(() => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { onInit } = require('./util'); - onInit(this, config); - }); - } - override send( objects: ExportItem[], onSuccess: () => void, diff --git a/experimental/packages/otlp-proto-exporter-base/src/generated/.gitkeep b/experimental/packages/otlp-proto-exporter-base/src/generated/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/packages/otlp-proto-exporter-base/src/util.ts b/experimental/packages/otlp-proto-exporter-base/src/util.ts index 0ab12f2e3e..dcb4fa1c73 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/util.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/util.ts @@ -14,50 +14,29 @@ * limitations under the License. */ -import * as path from 'path'; - import { ServiceClientType } from './types'; import { OTLPProtoExporterNodeBase } from './OTLPProtoExporterNodeBase'; -import type { Type } from 'protobufjs'; -import * as protobufjs from 'protobufjs'; import { CompressionAlgorithm, OTLPExporterError, - OTLPExporterNodeConfigBase, sendWithHttp } from '@opentelemetry/otlp-exporter-base'; +import type * as protobuf from 'protobufjs'; +import * as root from './generated/root'; -let ExportRequestProto: Type | undefined; - -export function getExportRequestProto(): Type | undefined { - return ExportRequestProto; +export interface ExportRequestType unknown }> { + create(properties?: T): R; + encode(message: T, writer?: protobuf.Writer): protobuf.Writer; + decode(reader: (protobuf.Reader | Uint8Array), length?: number): R; } -export function onInit( - collector: OTLPProtoExporterNodeBase, - _config: OTLPExporterNodeConfigBase -): void { - const dir = path.resolve(__dirname, '..', 'protos'); - const root = new protobufjs.Root(); - root.resolvePath = function (origin, target) { - return `${dir}/${target}`; - }; - if (collector.getServiceClientType() === ServiceClientType.SPANS) { - const proto = root.loadSync([ - 'opentelemetry/proto/common/v1/common.proto', - 'opentelemetry/proto/resource/v1/resource.proto', - 'opentelemetry/proto/trace/v1/trace.proto', - 'opentelemetry/proto/collector/trace/v1/trace_service.proto', - ]); - ExportRequestProto = proto?.lookupType('ExportTraceServiceRequest'); +export function getExportRequestProto( + clientType: ServiceClientType, +): ExportRequestType { + if (clientType === ServiceClientType.SPANS) { + return root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest as unknown as ExportRequestType; } else { - const proto = root.loadSync([ - 'opentelemetry/proto/common/v1/common.proto', - 'opentelemetry/proto/resource/v1/resource.proto', - 'opentelemetry/proto/metrics/v1/metrics.proto', - 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto', - ]); - ExportRequestProto = proto?.lookupType('ExportMetricsServiceRequest'); + return root.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest as unknown as ExportRequestType; } } @@ -70,9 +49,10 @@ export function send( ): void { const serviceRequest = collector.convert(objects); - const message = getExportRequestProto()?.create(serviceRequest); + const exportRequestType = getExportRequestProto(collector.getServiceClientType()); + const message = exportRequestType.create(serviceRequest); if (message) { - const body = getExportRequestProto()?.encode(message).finish(); + const body = exportRequestType.encode(message).finish(); if (body) { sendWithHttp( collector, diff --git a/experimental/packages/otlp-proto-exporter-base/tsconfig.json b/experimental/packages/otlp-proto-exporter-base/tsconfig.json index 0282e8dc83..3e05eef24c 100644 --- a/experimental/packages/otlp-proto-exporter-base/tsconfig.json +++ b/experimental/packages/otlp-proto-exporter-base/tsconfig.json @@ -1,11 +1,13 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { + "allowJs": true, "rootDir": ".", "outDir": "build" }, "include": [ "src/**/*.ts", + "src/generated/*.js", "test/**/*.ts" ], "references": [ diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index e1319a6b17..8c8f89b04c 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -20,10 +20,9 @@ import { DataPoint, DataPointType, Histogram, - ScopeMetrics, - InstrumentType, MetricData, - ResourceMetrics + ResourceMetrics, + ScopeMetrics } from '@opentelemetry/sdk-metrics-base'; import { toAttributes } from '../common/internal'; import { @@ -69,24 +68,18 @@ export function toMetric(metricData: MetricData): IMetric { const aggregationTemporality = toAggregationTemporality(metricData.aggregationTemporality); - if (metricData.dataPointType === DataPointType.SINGULAR) { - const dataPoints = toSingularDataPoints(metricData); - const isMonotonic = metricData.descriptor.type === InstrumentType.COUNTER || - metricData.descriptor.type === InstrumentType.OBSERVABLE_COUNTER; - if (isSum(metricData)) { - out.sum = { - aggregationTemporality, - isMonotonic, - dataPoints - }; - - } else { - // Instrument is a gauge. - out.gauge = { - dataPoints - }; - } - } else if (isHistogram(metricData)) { + if (metricData.dataPointType === DataPointType.SUM) { + out.sum = { + aggregationTemporality, + isMonotonic: metricData.isMonotonic, + dataPoints: toSingularDataPoints(metricData) + }; + } else if (metricData.dataPointType === DataPointType.GAUGE) { + // Instrument is a gauge. + out.gauge = { + dataPoints: toSingularDataPoints(metricData) + }; + } else if (metricData.dataPointType === DataPointType.HISTOGRAM) { out.histogram = { aggregationTemporality, dataPoints: toHistogramDataPoints(metricData) @@ -145,17 +138,6 @@ function toHistogramDataPoints( }); } -function isSum(metric: MetricData) { - return (metric.descriptor.type === InstrumentType.COUNTER || - metric.descriptor.type === InstrumentType.UP_DOWN_COUNTER || - metric.descriptor.type === InstrumentType.OBSERVABLE_COUNTER || - metric.descriptor.type === InstrumentType.OBSERVABLE_UP_DOWN_COUNTER); -} - -function isHistogram(metric: MetricData) { - return metric.dataPointType === DataPointType.HISTOGRAM; -} - function toAggregationTemporality( temporality: AggregationTemporality, ): EAggregationTemporality { diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index e9047b3f0c..beb9785f70 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -70,7 +70,31 @@ describe('Metrics', () => { valueType: ValueType.INT, }, aggregationTemporality, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, + dataPoints: [ + { + value: value, + startTime: START_TIME, + endTime: END_TIME, + attributes: { 'string-attribute': 'some attribute value' } + } + ] + }; + } + + function createUpDownCounterData(value: number, aggregationTemporality: AggregationTemporality): MetricData { + return { + descriptor: { + description: 'this is a description', + type: InstrumentType.UP_DOWN_COUNTER, + name: 'up-down-counter', + unit: '1', + valueType: ValueType.INT, + }, + aggregationTemporality, + dataPointType: DataPointType.SUM, + isMonotonic: false, dataPoints: [ { value: value, @@ -92,7 +116,8 @@ describe('Metrics', () => { valueType: ValueType.INT, }, aggregationTemporality, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.SUM, + isMonotonic: true, dataPoints: [ { value: value, @@ -104,6 +129,30 @@ describe('Metrics', () => { }; } + function createObservableUpDownCounterData(value: number, aggregationTemporality: AggregationTemporality): MetricData { + return { + descriptor: { + description: 'this is a description', + type: InstrumentType.OBSERVABLE_UP_DOWN_COUNTER, + name: 'observable-up-down-counter', + unit: '1', + valueType: ValueType.INT, + }, + aggregationTemporality, + dataPointType: DataPointType.SUM, + isMonotonic: false, + dataPoints: [ + { + value: value, + startTime: START_TIME, + endTime: END_TIME, + attributes: { 'string-attribute': 'some attribute value' } + } + ] + }; + } + + function createObservableGaugeData(value: number): MetricData { return { descriptor: { @@ -114,7 +163,7 @@ describe('Metrics', () => { valueType: ValueType.DOUBLE, }, aggregationTemporality: AggregationTemporality.CUMULATIVE, - dataPointType: DataPointType.SINGULAR, + dataPointType: DataPointType.GAUGE, dataPoints: [ { value: value, @@ -184,7 +233,7 @@ describe('Metrics', () => { }; } - it('serializes a sum metric record', () => { + it('serializes a monotonic sum metric record', () => { const metrics = createResourceMetrics([createCounterData(10, AggregationTemporality.DELTA)]); const exportRequest = createExportMetricsServiceRequest([metrics]); assert.ok(exportRequest); @@ -224,7 +273,47 @@ describe('Metrics', () => { }); }); - it('serializes an observable sum metric record', () => { + it('serializes a non-monotonic sum metric record', () => { + const metrics = createResourceMetrics([createUpDownCounterData(10, AggregationTemporality.DELTA)]); + const exportRequest = createExportMetricsServiceRequest([metrics]); + assert.ok(exportRequest); + + assert.deepStrictEqual(exportRequest, { + resourceMetrics: [ + { + resource: expectedResource, + schemaUrl: undefined, + scopeMetrics: [ + { + scope: expectedScope, + schemaUrl: expectedSchemaUrl, + metrics: [ + { + name: 'up-down-counter', + description: 'this is a description', + unit: '1', + sum: { + dataPoints: [ + { + attributes: expectedAttributes, + startTimeUnixNano: hrTimeToNanoseconds(START_TIME), + timeUnixNano: hrTimeToNanoseconds(END_TIME), + asInt: 10, + }, + ], + aggregationTemporality: EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA, + isMonotonic: false, + }, + }, + ], + }, + ], + }, + ], + }); + }); + + it('serializes an observable monotonic sum metric record', () => { const exportRequest = createExportMetricsServiceRequest( [createResourceMetrics([createObservableCounterData(10, AggregationTemporality.DELTA)])] ); @@ -265,6 +354,47 @@ describe('Metrics', () => { }); }); + it('serializes an observable non-monotonic sum metric record', () => { + const exportRequest = createExportMetricsServiceRequest( + [createResourceMetrics([createObservableUpDownCounterData(10, AggregationTemporality.DELTA)])] + ); + assert.ok(exportRequest); + + assert.deepStrictEqual(exportRequest, { + resourceMetrics: [ + { + resource: expectedResource, + schemaUrl: undefined, + scopeMetrics: [ + { + scope: expectedScope, + schemaUrl: expectedSchemaUrl, + metrics: [ + { + name: 'observable-up-down-counter', + description: 'this is a description', + unit: '1', + sum: { + dataPoints: [ + { + attributes: expectedAttributes, + startTimeUnixNano: hrTimeToNanoseconds(START_TIME), + timeUnixNano: hrTimeToNanoseconds(END_TIME), + asInt: 10, + }, + ], + aggregationTemporality: EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA, + isMonotonic: false, + }, + }, + ], + }, + ], + }, + ], + }); + }); + it('serializes a gauge metric record', () => { const exportRequest = createExportMetricsServiceRequest( [createResourceMetrics([createObservableGaugeData(10.5)])] diff --git a/package.json b/package.json index 5aee41c22e..4222ca5218 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "OpenTelemetry is a distributed tracing and stats collection framework.", "scripts": { "precompile": "lerna run version", - "compile": "tsc --build", + "compile": "lerna run protos && tsc --build", "prewatch": "npm run precompile", "watch": "tsc --build --watch", "clean": "tsc --build --clean",