From 513d0679a5581281cfbac82ba8aa3b4a4d0a0406 Mon Sep 17 00:00:00 2001 From: Siim Kallas Date: Thu, 28 Sep 2023 14:44:17 +0300 Subject: [PATCH 1/2] fix(otlp-transformer): avoid precision loss when converting HrTime to unix nanoseconds (#4062) Co-authored-by: Marc Pichler --- experimental/CHANGELOG.md | 1 + .../test/logsHelper.ts | 2 +- .../exporter-logs-otlp-http/test/logHelper.ts | 14 +- .../test/logHelper.ts | 4 +- .../test/traceHelper.ts | 20 +-- .../test/node/CollectorTraceExporter.test.ts | 1 - .../test/traceHelper.ts | 30 ++-- .../test/traceHelper.ts | 20 +-- .../test/OTLPMetricExporter.test.ts | 17 +- .../test/metricsHelper.ts | 162 +++++++++-------- .../browser/CollectorMetricExporter.test.ts | 72 +++----- .../test/metricsHelper.ts | 163 ++++++------------ .../test/node/CollectorMetricExporter.test.ts | 32 +--- .../test/OTLPMetricExporter.test.ts | 18 +- .../test/metricsHelper.ts | 126 ++++++++------ .../otlp-transformer/src/common/index.ts | 29 ++++ .../otlp-transformer/src/common/types.ts | 5 + .../src/common/unsigned_long.ts | 122 +++++++++++++ .../packages/otlp-transformer/src/index.ts | 1 + .../otlp-transformer/src/logs/index.ts | 7 +- .../otlp-transformer/src/logs/types.ts | 5 +- .../otlp-transformer/src/metrics/internal.ts | 14 +- .../otlp-transformer/src/metrics/types.ts | 14 +- .../otlp-transformer/src/trace/internal.ts | 8 +- .../otlp-transformer/src/trace/types.ts | 8 +- .../otlp-transformer/test/logs.test.ts | 7 +- .../otlp-transformer/test/metrics.test.ts | 39 +++-- .../otlp-transformer/test/trace.test.ts | 9 +- 28 files changed, 530 insertions(+), 420 deletions(-) create mode 100644 experimental/packages/otlp-transformer/src/common/index.ts create mode 100644 experimental/packages/otlp-transformer/src/common/unsigned_long.ts diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index bc0fb011da..72f0289574 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -56,6 +56,7 @@ All notable changes to experimental packages in this project will be documented ### :bug: (Bug Fix) +* fix(otlp-transformer): Avoid precision loss when converting from HrTime to unix nanoseconds. [#4062](https://github.com/open-telemetry/opentelemetry-js/pull/4062) * fix(exporter-logs-otlp-http): add @opentelemetry/api-logs as dependency ## 0.41.2 diff --git a/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts b/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts index 1dfd82675a..633d1a7833 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts @@ -88,7 +88,7 @@ export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { ensureExportedAttributesAreCorrect(logRecord.attributes); assert.strictEqual( logRecord.timeUnixNano, - '1680253513123241728', + '1680253513123241635', 'timeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts b/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts index 1d9461f873..b5832f7cd4 100644 --- a/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts +++ b/experimental/packages/exporter-logs-otlp-http/test/logHelper.ts @@ -20,6 +20,7 @@ import { Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; import { VERSION } from '@opentelemetry/core'; import { + hrTimeToFixed64Nanos, IAnyValue, IExportLogsServiceRequest, IKeyValue, @@ -76,17 +77,22 @@ export function ensureExportedBodyIsCorrect(body?: IAnyValue) { ); } +function hrTimeToFixed64(hrTime: HrTime) { + const { low, high } = hrTimeToFixed64Nanos(hrTime); + return { low, high }; +} + export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { ensureExportedBodyIsCorrect(logRecord.body); ensureExportedAttributesAreCorrect(logRecord.attributes); - assert.strictEqual( + assert.deepStrictEqual( logRecord.timeUnixNano, - 1680253513123241700, + hrTimeToFixed64(mockedReadableLogRecord.hrTime), 'timeUnixNano is wrong' ); - assert.strictEqual( + assert.deepStrictEqual( logRecord.observedTimeUnixNano, - 1680253513123241700, + hrTimeToFixed64(mockedReadableLogRecord.hrTimeObserved), 'observedTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts b/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts index 54a752af1e..ae72deae33 100644 --- a/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts +++ b/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts @@ -82,12 +82,12 @@ export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { ensureExportedAttributesAreCorrect(logRecord.attributes); assert.strictEqual( logRecord.timeUnixNano, - '1680253513123241728', + '1680253513123241635', 'timeUnixNano is wrong' ); assert.strictEqual( logRecord.observedTimeUnixNano, - '1680253513123241728', + '1680253513123241635', 'observedTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-trace-otlp-grpc/test/traceHelper.ts b/experimental/packages/exporter-trace-otlp-grpc/test/traceHelper.ts index 6ce80bf2c2..62157741a8 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/test/traceHelper.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/test/traceHelper.ts @@ -114,49 +114,49 @@ export function ensureExportedEventsAreCorrect(events: IEvent[]) { [ { attributes: [], - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'fetchStart', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'domainLookupStart', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'domainLookupEnd', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'connectStart', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'connectEnd', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165435513088', + timeUnixNano: '1574120165435513070', name: 'requestStart', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165436923136', + timeUnixNano: '1574120165436923070', name: 'responseStart', droppedAttributesCount: 0, }, { attributes: [], - timeUnixNano: '1574120165438688000', + timeUnixNano: '1574120165438688070', name: 'responseEnd', droppedAttributesCount: 0, }, @@ -235,12 +235,12 @@ export function ensureExportedSpanIsCorrect(span: ISpan) { assert.strictEqual(span.kind, 'SPAN_KIND_INTERNAL', 'kind is wrong'); assert.strictEqual( span.startTimeUnixNano, - '1574120165429803008', + '1574120165429803070', 'startTimeUnixNano is wrong' ); assert.strictEqual( span.endTimeUnixNano, - '1574120165438688000', + '1574120165438688070', 'endTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts index 9238260d30..3de60027dc 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts @@ -316,7 +316,6 @@ describe('OTLPTraceExporter - node with json over http', () => { fakeRequest.on('end', () => { const responseBody = buff.toString(); - const json = JSON.parse(responseBody) 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/exporter-trace-otlp-http/test/traceHelper.ts b/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts index 06e256d253..6726b49017 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/traceHelper.ts @@ -31,6 +31,7 @@ import { ILink, IResource, ISpan, + UnsignedLong, } from '@opentelemetry/otlp-transformer'; if (typeof Buffer === 'undefined') { @@ -243,54 +244,59 @@ export const multiInstrumentationLibraryTrace: ReadableSpan[] = [ }, ]; +function fixed64FromString(str: string) { + const { low, high } = UnsignedLong.fromString(str); + return { low, high }; +} + export function ensureEventsAreCorrect(events: IEvent[]) { assert.deepStrictEqual( events, [ { - timeUnixNano: 1574120165429803000, + timeUnixNano: fixed64FromString('1574120165429803070'), name: 'fetchStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165429803000, + timeUnixNano: fixed64FromString('1574120165429803070'), name: 'domainLookupStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165429803000, + timeUnixNano: fixed64FromString('1574120165429803070'), name: 'domainLookupEnd', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165429803000, + timeUnixNano: fixed64FromString('1574120165429803070'), name: 'connectStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165429803000, + timeUnixNano: fixed64FromString('1574120165429803070'), name: 'connectEnd', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165435513000, + timeUnixNano: fixed64FromString('1574120165435513070'), name: 'requestStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165436923100, + timeUnixNano: fixed64FromString('1574120165436923070'), name: 'responseStart', attributes: [], droppedAttributesCount: 0, }, { - timeUnixNano: 1574120165438688000, + timeUnixNano: fixed64FromString('1574120165438688070'), name: 'responseEnd', attributes: [], droppedAttributesCount: 0, @@ -364,14 +370,14 @@ export function ensureSpanIsCorrect(span: ISpan, useHex = true) { ); assert.strictEqual(span.name, 'documentFetch', 'name is wrong'); assert.strictEqual(span.kind, ESpanKind.SPAN_KIND_INTERNAL, 'kind is wrong'); - assert.strictEqual( + assert.deepStrictEqual( span.startTimeUnixNano, - 1574120165429803008, + fixed64FromString('1574120165429803070'), 'startTimeUnixNano is wrong' ); - assert.strictEqual( + assert.deepStrictEqual( span.endTimeUnixNano, - 1574120165438688000, + fixed64FromString('1574120165438688070'), 'endTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-trace-otlp-proto/test/traceHelper.ts b/experimental/packages/exporter-trace-otlp-proto/test/traceHelper.ts index ff6a9c7b85..3a21d9b79d 100644 --- a/experimental/packages/exporter-trace-otlp-proto/test/traceHelper.ts +++ b/experimental/packages/exporter-trace-otlp-proto/test/traceHelper.ts @@ -109,42 +109,42 @@ export function ensureProtoEventsAreCorrect(events: IEvent[]) { events, [ { - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'fetchStart', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'domainLookupStart', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'domainLookupEnd', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'connectStart', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165429803008', + timeUnixNano: '1574120165429803070', name: 'connectEnd', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165435513088', + timeUnixNano: '1574120165435513070', name: 'requestStart', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165436923136', + timeUnixNano: '1574120165436923070', name: 'responseStart', droppedAttributesCount: 0, }, { - timeUnixNano: '1574120165438688000', + timeUnixNano: '1574120165438688070', name: 'responseEnd', droppedAttributesCount: 0, }, @@ -219,12 +219,12 @@ export function ensureProtoSpanIsCorrect(span: ISpan) { assert.strictEqual(span.kind, 'SPAN_KIND_INTERNAL', 'kind is wrong'); assert.strictEqual( span.startTimeUnixNano, - '1574120165429803008', + '1574120165429803070', 'startTimeUnixNano is wrong' ); assert.strictEqual( span.endTimeUnixNano, - '1574120165438688000', + '1574120165438688070', 'endTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts index a85d2f342e..1df8e0cc12 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts @@ -253,18 +253,23 @@ const testOTLPMetricExporter = (params: TestParams) => { exportedData[0].scopeMetrics[0].metrics[histogramIndex]; ensureExportedCounterIsCorrect( counter, - counter.sum?.dataPoints[0].timeUnixNano, - counter.sum?.dataPoints[0].startTimeUnixNano + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] + .startTime ); ensureExportedObservableGaugeIsCorrect( observableGauge, - observableGauge.gauge?.dataPoints[0].timeUnixNano, - observableGauge.gauge?.dataPoints[0].startTimeUnixNano + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .startTime ); ensureExportedHistogramIsCorrect( histogram, - histogram.histogram?.dataPoints[0].timeUnixNano, - histogram.histogram?.dataPoints[0].startTimeUnixNano, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .startTime, [0, 100], ['0', '2', '0'] ); 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 4713e7aa3f..e6332ff36f 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/metricsHelper.ts @@ -17,6 +17,7 @@ import { Counter, Histogram, + HrTime, ObservableGauge, ObservableResult, ValueType, @@ -31,7 +32,13 @@ import { MetricReader, View, } from '@opentelemetry/sdk-metrics'; -import { IKeyValue, IMetric, IResource } from '@opentelemetry/otlp-transformer'; +import { + hrTimeToFixed64Nanos, + IKeyValue, + IMetric, + IResource, + UnsignedLong, +} from '@opentelemetry/otlp-transformer'; class TestMetricReader extends MetricReader { protected onForceFlush(): Promise { @@ -125,92 +132,99 @@ export function ensureExportedAttributesAreCorrect(attributes: IKeyValue[]) { export function ensureExportedCounterIsCorrect( metric: IMetric, - time?: number, - startTime?: number + time: HrTime, + startTime: HrTime ) { - assert.deepStrictEqual(metric, { - name: 'int-counter', - description: 'sample counter description', - unit: '', - data: 'sum', - sum: { - dataPoints: [ - { - attributes: [], - exemplars: [], - value: 'asInt', - asInt: '1', - flags: 0, - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - }, - ], - isMonotonic: true, - aggregationTemporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE', - }, - }); + assert.strictEqual(metric.name, 'int-counter'); + assert.strictEqual(metric.description, 'sample counter description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.sum?.dataPoints.length, 1); + assert.strictEqual( + metric.sum?.aggregationTemporality, + 'AGGREGATION_TEMPORALITY_CUMULATIVE' + ); + assert.strictEqual(metric.sum?.isMonotonic, true); + + const [dp] = metric.sum.dataPoints; + + assert.deepStrictEqual(dp.attributes, []); + assert.deepStrictEqual(dp.exemplars, []); + assert.strictEqual(dp.asInt, '1'); + assert.strictEqual(dp.flags, 0); + + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); } export function ensureExportedObservableGaugeIsCorrect( metric: IMetric, - time?: number, - startTime?: number + time: HrTime, + startTime: HrTime ) { - assert.deepStrictEqual(metric, { - name: 'double-observable-gauge', - description: 'sample observable gauge description', - unit: '', - data: 'gauge', - gauge: { - dataPoints: [ - { - attributes: [], - exemplars: [], - value: 'asDouble', - asDouble: 6, - flags: 0, - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - }, - ], - }, - }); + assert.strictEqual(metric.name, 'double-observable-gauge'); + assert.strictEqual(metric.description, 'sample observable gauge description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.gauge?.dataPoints.length, 1); + + const [dp] = metric.gauge.dataPoints; + + assert.deepStrictEqual(dp.attributes, []); + assert.deepStrictEqual(dp.exemplars, []); + assert.strictEqual(dp.asDouble, 6); + assert.strictEqual(dp.flags, 0); + + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); } export function ensureExportedHistogramIsCorrect( metric: IMetric, - time?: number, - startTime?: number, + time: HrTime, + startTime: HrTime, explicitBounds: number[] = [Infinity], bucketCounts: string[] = ['2', '0'] ) { - assert.deepStrictEqual(metric, { - name: 'int-histogram', - description: 'sample histogram description', - unit: '', - data: 'histogram', - histogram: { - dataPoints: [ - { - attributes: [], - exemplars: [], - flags: 0, - _sum: 'sum', - _min: 'min', - _max: 'max', - sum: 21, - count: '2', - min: 7, - max: 14, - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - bucketCounts, - explicitBounds, - }, - ], - aggregationTemporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE', - }, - }); + assert.strictEqual(metric.name, 'int-histogram'); + assert.strictEqual(metric.description, 'sample histogram description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.histogram?.dataPoints.length, 1); + assert.strictEqual( + metric.histogram?.aggregationTemporality, + 'AGGREGATION_TEMPORALITY_CUMULATIVE' + ); + + const [dp] = metric.histogram.dataPoints; + + assert.deepStrictEqual(dp.attributes, []); + assert.deepStrictEqual(dp.exemplars, []); + assert.strictEqual(dp.flags, 0); + assert.strictEqual(dp.sum, 21); + assert.strictEqual(dp.count, '2'); + assert.strictEqual(dp.min, 7); + assert.strictEqual(dp.max, 14); + + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); + assert.deepStrictEqual(dp.bucketCounts, bucketCounts); + assert.deepStrictEqual(dp.explicitBounds, explicitBounds); } export function ensureResourceIsCorrect(resource: IResource) { 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 053376b032..cc112a9b55 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 @@ -21,7 +21,7 @@ import { Counter, Histogram, } from '@opentelemetry/api'; -import { ExportResultCode, hrTimeToNanoseconds } from '@opentelemetry/core'; +import { ExportResultCode } from '@opentelemetry/core'; import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import * as sinon from 'sinon'; @@ -138,14 +138,9 @@ describe('OTLPMetricExporter - web', () => { ensureCounterIsCorrect( metric1, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] - .startTime - ) + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] + .startTime ); assert.ok( @@ -154,14 +149,10 @@ describe('OTLPMetricExporter - web', () => { ); ensureObservableGaugeIsCorrect( metric2, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .startTime, 6, 'double-observable-gauge2' ); @@ -172,14 +163,10 @@ describe('OTLPMetricExporter - web', () => { ); ensureHistogramIsCorrect( metric3, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .startTime, [0, 100], [0, 2, 0] ); @@ -273,14 +260,9 @@ describe('OTLPMetricExporter - web', () => { assert.ok(typeof metric1 !== 'undefined', "metric doesn't exist"); ensureCounterIsCorrect( metric1, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] - .startTime - ) + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] + .startTime ); assert.ok( @@ -289,14 +271,10 @@ describe('OTLPMetricExporter - web', () => { ); ensureObservableGaugeIsCorrect( metric2, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .startTime, 6, 'double-observable-gauge2' ); @@ -307,14 +285,10 @@ describe('OTLPMetricExporter - web', () => { ); ensureHistogramIsCorrect( metric3, - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .endTime - ), - hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .startTime, [0, 100], [0, 2, 0] ); 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 6796c155f0..9bbeee652f 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/metricsHelper.ts @@ -22,6 +22,7 @@ import { ObservableCounter, ObservableGauge, ObservableUpDownCounter, + HrTime, } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; @@ -33,6 +34,7 @@ import { View, } from '@opentelemetry/sdk-metrics'; import { + hrTimeToFixed64Nanos, IExportMetricsServiceRequest, IKeyValue, IMetric, @@ -205,28 +207,29 @@ export function ensureWebResourceIsCorrect(resource: IResource) { assert.strictEqual(resource.droppedAttributesCount, 0); } +function hrTimeToFixed64(hrTime: HrTime) { + const { low, high } = hrTimeToFixed64Nanos(hrTime); + return { low, high }; +} + export function ensureCounterIsCorrect( metric: IMetric, - time?: number, - startTime?: number + time: HrTime, + startTime: HrTime ) { - assert.deepStrictEqual(metric, { - name: 'int-counter', - description: 'sample counter description', - unit: '', - sum: { - dataPoints: [ - { - attributes: [], - asInt: 1, - startTimeUnixNano: startTime, - timeUnixNano: time, - }, - ], - isMonotonic: true, - aggregationTemporality: 2, - }, - }); + assert.strictEqual(metric.name, 'int-counter'); + assert.strictEqual(metric.description, 'sample counter description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.sum?.dataPoints.length, 1); + assert.strictEqual(metric.sum?.isMonotonic, true); + assert.strictEqual(metric.sum?.aggregationTemporality, 2); + + const [dp] = metric.sum.dataPoints; + + assert.deepStrictEqual(dp.attributes, []); + assert.strictEqual(dp.asInt, 1); + assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); } export function ensureDoubleCounterIsCorrect( @@ -255,108 +258,50 @@ export function ensureDoubleCounterIsCorrect( export function ensureObservableGaugeIsCorrect( metric: IMetric, - time: number, - startTime: number, + time: HrTime, + startTime: HrTime, value: number, name = 'double-observable-gauge' ) { - assert.deepStrictEqual(metric, { - name, - description: 'sample observable gauge description', - unit: '', - gauge: { - dataPoints: [ - { - attributes: [], - asDouble: value, - startTimeUnixNano: startTime, - timeUnixNano: time, - }, - ], - }, - }); -} + assert.strictEqual(metric.name, name); + assert.strictEqual(metric.description, 'sample observable gauge description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.gauge?.dataPoints.length, 1); -export function ensureObservableCounterIsCorrect( - metric: IMetric, - time: number, - startTime: number, - value: number, - name = 'double-observable-counter' -) { - assert.deepStrictEqual(metric, { - name, - description: 'sample observable counter description', - unit: '', - doubleSum: { - isMonotonic: true, - dataPoints: [ - { - attributes: [], - value, - startTimeUnixNano: startTime, - timeUnixNano: time, - }, - ], - aggregationTemporality: 2, - }, - }); -} + const [dp] = metric.gauge.dataPoints; -export function ensureObservableUpDownCounterIsCorrect( - metric: IMetric, - time: number, - startTime: number, - value: number, - name = 'double-up-down-observable-counter' -) { - assert.deepStrictEqual(metric, { - name, - description: 'sample observable up down counter description', - unit: '', - doubleSum: { - isMonotonic: false, - dataPoints: [ - { - labels: [], - value, - startTimeUnixNano: startTime, - timeUnixNano: time, - }, - ], - aggregationTemporality: 2, - }, - }); + assert.deepStrictEqual(dp.attributes, []); + assert.strictEqual(dp.asDouble, value); + + assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); } export function ensureHistogramIsCorrect( metric: IMetric, - time: number, - startTime: number, + time: HrTime, + startTime: HrTime, explicitBounds: (number | null)[] = [Infinity], bucketCounts: number[] = [2, 0] ) { - assert.deepStrictEqual(metric, { - name: 'int-histogram', - description: 'sample histogram description', - unit: '', - histogram: { - dataPoints: [ - { - attributes: [], - sum: 21, - count: 2, - min: 7, - max: 14, - startTimeUnixNano: startTime, - timeUnixNano: time, - bucketCounts, - explicitBounds, - }, - ], - aggregationTemporality: 2, - }, - }); + assert.strictEqual(metric.name, 'int-histogram'); + assert.strictEqual(metric.description, 'sample histogram description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.histogram?.dataPoints.length, 1); + assert.strictEqual(metric.histogram?.aggregationTemporality, 2); + + const [dp] = metric.histogram.dataPoints; + + assert.deepStrictEqual(dp.attributes, []); + assert.strictEqual(dp.sum, 21); + assert.strictEqual(dp.count, 2); + assert.strictEqual(dp.min, 7); + assert.strictEqual(dp.max, 14); + assert.deepStrictEqual(dp.bucketCounts, bucketCounts); + assert.deepStrictEqual(dp.explicitBounds, explicitBounds); + + assert.deepStrictEqual(dp.startTimeUnixNano, hrTimeToFixed64(startTime)); + assert.deepStrictEqual(dp.timeUnixNano, hrTimeToFixed64(time)); } export function ensureExportMetricsServiceRequestIsSet( 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 0844e5a9f2..2f809e6251 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 @@ -491,13 +491,8 @@ describe('OTLPMetricExporter - node with json over http', () => { assert.ok(typeof metric1 !== 'undefined', "counter doesn't exist"); ensureCounterIsCorrect( metric1, - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime - ), - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0] - .startTime - ) + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].startTime ); assert.ok( typeof metric2 !== 'undefined', @@ -505,28 +500,19 @@ describe('OTLPMetricExporter - node with json over http', () => { ); ensureObservableGaugeIsCorrect( metric2, - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .endTime - ), - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .startTime, 6, 'double-observable-gauge2' ); assert.ok(typeof metric3 !== 'undefined', "histogram doesn't exist"); ensureHistogramIsCorrect( metric3, - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .endTime - ), - core.hrTimeToNanoseconds( - metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] - .startTime - ), + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .startTime, [0, 100], [0, 2, 0] ); 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 4d4856481d..80902daeaa 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 @@ -45,7 +45,6 @@ import { } from '@opentelemetry/exporter-metrics-otlp-http'; import { Stream, PassThrough } from 'stream'; import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; -import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; import { VERSION } from '../src/version'; let fakeRequest: PassThrough; @@ -260,7 +259,7 @@ describe('OTLPMetricExporter - node with proto over http', () => { ServiceClientType.METRICS ); const data = ExportTraceServiceRequestProto.decode(buff); - const json = data?.toJSON() as IExportMetricsServiceRequest; + const json = data?.toJSON() as any; // The order of the metrics is not guaranteed. const counterIndex = metrics.scopeMetrics[0].metrics.findIndex( @@ -283,8 +282,8 @@ describe('OTLPMetricExporter - node with proto over http', () => { assert.ok(typeof metric1 !== 'undefined', "counter doesn't exist"); ensureExportedCounterIsCorrect( metric1, - metric1.sum?.dataPoints[0].timeUnixNano, - metric1.sum?.dataPoints[0].startTimeUnixNano + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[counterIndex].dataPoints[0].startTime ); assert.ok( typeof metric2 !== 'undefined', @@ -292,8 +291,10 @@ describe('OTLPMetricExporter - node with proto over http', () => { ); ensureExportedObservableGaugeIsCorrect( metric2, - metric2.gauge?.dataPoints[0].timeUnixNano, - metric2.gauge?.dataPoints[0].startTimeUnixNano + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .endTime, + metrics.scopeMetrics[0].metrics[observableIndex].dataPoints[0] + .startTime ); assert.ok( typeof metric3 !== 'undefined', @@ -301,8 +302,9 @@ describe('OTLPMetricExporter - node with proto over http', () => { ); ensureExportedHistogramIsCorrect( metric3, - metric3.histogram?.dataPoints[0].timeUnixNano, - metric3.histogram?.dataPoints[0].startTimeUnixNano, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0].endTime, + metrics.scopeMetrics[0].metrics[histogramIndex].dataPoints[0] + .startTime, [0, 100], ['0', '2', '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 27b816fe10..6a47a9a8f6 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/metricsHelper.ts @@ -20,6 +20,7 @@ import { Histogram, ValueType, ObservableGauge, + HrTime, } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; @@ -30,9 +31,11 @@ import { View, } from '@opentelemetry/sdk-metrics'; import { + hrTimeToFixed64Nanos, IExportMetricsServiceRequest, IKeyValue, IMetric, + UnsignedLong, } from '@opentelemetry/otlp-transformer'; import { Stream } from 'stream'; @@ -129,75 +132,86 @@ export function ensureProtoAttributesAreCorrect(attributes: IKeyValue[]) { export function ensureExportedCounterIsCorrect( metric: IMetric, - time?: number, - startTime?: number + time: HrTime, + startTime: HrTime ) { - assert.deepStrictEqual(metric, { - name: 'int-counter', - description: 'sample counter description', - unit: '', - sum: { - dataPoints: [ - { - asInt: '1', - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - }, - ], - isMonotonic: true, - aggregationTemporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE', - }, - }); + assert.strictEqual(metric.name, 'int-counter'); + assert.strictEqual(metric.description, 'sample counter description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.sum?.dataPoints.length, 1); + assert.strictEqual(metric.sum?.isMonotonic, true); + assert.strictEqual( + metric.sum?.aggregationTemporality, + 'AGGREGATION_TEMPORALITY_CUMULATIVE' + ); + + const [dp] = metric.sum.dataPoints; + assert.strictEqual(dp.asInt, '1'); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); } export function ensureExportedObservableGaugeIsCorrect( metric: IMetric, - time?: number, - startTime?: number + time: HrTime, + startTime: HrTime ) { - assert.deepStrictEqual(metric, { - name: 'double-observable-gauge', - description: 'sample observable gauge description', - unit: '', - gauge: { - dataPoints: [ - { - asDouble: 6, - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - }, - ], - }, - }); + assert.strictEqual(metric.name, 'double-observable-gauge'); + assert.strictEqual(metric.description, 'sample observable gauge description'); + assert.strictEqual(metric.unit, ''); + assert.strictEqual(metric.gauge?.dataPoints.length, 1); + + const [dp] = metric.gauge.dataPoints; + assert.strictEqual(dp.asDouble, 6); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); } export function ensureExportedHistogramIsCorrect( metric: IMetric, - time?: number, - startTime?: number, + time: HrTime, + startTime: HrTime, explicitBounds: number[] = [Infinity], bucketCounts: string[] = ['2', '0'] ) { - assert.deepStrictEqual(metric, { - name: 'int-histogram', - description: 'sample histogram description', - unit: '', - histogram: { - dataPoints: [ - { - sum: 21, - count: '2', - min: 7, - max: 14, - startTimeUnixNano: String(startTime), - timeUnixNano: String(time), - bucketCounts, - explicitBounds, - }, - ], - aggregationTemporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE', - }, - }); + assert.strictEqual(metric.name, 'int-histogram'); + assert.strictEqual(metric.description, 'sample histogram description'); + assert.strictEqual(metric.unit, ''); + + assert.strictEqual(metric.histogram?.dataPoints.length, 1); + assert.strictEqual( + metric.histogram.aggregationTemporality, + 'AGGREGATION_TEMPORALITY_CUMULATIVE' + ); + + const [dp] = metric.histogram.dataPoints; + + assert.strictEqual(dp.sum, 21); + assert.strictEqual(dp.count, '2'); + assert.strictEqual(dp.min, 7); + assert.strictEqual(dp.max, 14); + assert.deepStrictEqual(dp.explicitBounds, explicitBounds); + assert.deepStrictEqual(dp.bucketCounts, bucketCounts); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.startTimeUnixNano as string), + hrTimeToFixed64Nanos(startTime) + ); + assert.deepStrictEqual( + UnsignedLong.fromString(dp.timeUnixNano as string), + hrTimeToFixed64Nanos(time) + ); } export function ensureExportMetricsServiceRequestIsSet( diff --git a/experimental/packages/otlp-transformer/src/common/index.ts b/experimental/packages/otlp-transformer/src/common/index.ts new file mode 100644 index 0000000000..19587be3ae --- /dev/null +++ b/experimental/packages/otlp-transformer/src/common/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IFixed64 } from './types'; +import { HrTime } from '@opentelemetry/api'; +import { UnsignedLong } from './unsigned_long'; + +export * from './unsigned_long'; + +const NANOSECONDS = UnsignedLong.fromU32(1_000_000_000); + +export function hrTimeToFixed64Nanos(hrTime: HrTime): IFixed64 { + return UnsignedLong.fromU32(hrTime[0]) + .multiply(NANOSECONDS) + .add(UnsignedLong.fromU32(hrTime[1])); +} diff --git a/experimental/packages/otlp-transformer/src/common/types.ts b/experimental/packages/otlp-transformer/src/common/types.ts index 159a595ba9..6236da4ce6 100644 --- a/experimental/packages/otlp-transformer/src/common/types.ts +++ b/experimental/packages/otlp-transformer/src/common/types.ts @@ -73,3 +73,8 @@ export interface IKeyValueList { /** KeyValueList values */ values: IKeyValue[]; } + +export interface IFixed64 { + low: number; + high: number; +} diff --git a/experimental/packages/otlp-transformer/src/common/unsigned_long.ts b/experimental/packages/otlp-transformer/src/common/unsigned_long.ts new file mode 100644 index 0000000000..d2802e3ba8 --- /dev/null +++ b/experimental/packages/otlp-transformer/src/common/unsigned_long.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2009 The Closure Library Authors + * Copyright 2020 Daniel Wirtz / The long.js Authors. + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Original version by long.js: https://github.com/dcodeIO/long.js/ + +const TWO_PWR_32 = (1 << 16) * (1 << 16); + +export class UnsignedLong { + low: number; + high: number; + + constructor(low: number, high: number) { + this.low = low; + this.high = high; + } + + static fromU32(value: number): UnsignedLong { + return new UnsignedLong(value % TWO_PWR_32 | 0, 0); + } + + multiply(value: UnsignedLong): UnsignedLong { + const a48 = this.high >>> 16; + const a32 = this.high & 0xffff; + const a16 = this.low >>> 16; + const a00 = this.low & 0xffff; + + const b48 = value.high >>> 16; + const b32 = value.high & 0xffff; + const b16 = value.low >>> 16; + const b00 = value.low & 0xffff; + + let c48 = 0; + let c32 = 0; + let c16 = 0; + let c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xffff; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xffff; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xffff; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xffff; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xffff; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xffff; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xffff; + + return new UnsignedLong((c16 << 16) | c00, (c48 << 16) | c32); + } + + add(value: UnsignedLong): UnsignedLong { + const a48 = this.high >>> 16; + const a32 = this.high & 0xffff; + const a16 = this.low >>> 16; + const a00 = this.low & 0xffff; + + const b48 = value.high >>> 16; + const b32 = value.high & 0xffff; + const b16 = value.low >>> 16; + const b00 = value.low & 0xffff; + + let c48 = 0; + let c32 = 0; + let c16 = 0; + let c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xffff; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xffff; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xffff; + c48 += a48 + b48; + c48 &= 0xffff; + + return new UnsignedLong((c16 << 16) | c00, (c48 << 16) | c32); + } + + static fromString(str: string): UnsignedLong { + let result = UnsignedLong.fromU32(0); + + for (let i = 0; i < str.length; i += 8) { + const size = Math.min(8, str.length - i); + const value = parseInt(str.substring(i, i + size)); + if (size < 8) { + const power = UnsignedLong.fromU32(Math.pow(10, size)); + result = result.multiply(power).add(UnsignedLong.fromU32(value)); + } else { + result = result.multiply(UnsignedLong.fromU32(100_000_000)); + result = result.add(UnsignedLong.fromU32(value)); + } + } + + return result; + } +} diff --git a/experimental/packages/otlp-transformer/src/index.ts b/experimental/packages/otlp-transformer/src/index.ts index a8e388e33a..9c325644dc 100644 --- a/experimental/packages/otlp-transformer/src/index.ts +++ b/experimental/packages/otlp-transformer/src/index.ts @@ -15,6 +15,7 @@ */ export * from './common/types'; +export * from './common'; export * from './metrics/types'; export * from './resource/types'; export * from './trace/types'; diff --git a/experimental/packages/otlp-transformer/src/logs/index.ts b/experimental/packages/otlp-transformer/src/logs/index.ts index c499476498..bffb7966dd 100644 --- a/experimental/packages/otlp-transformer/src/logs/index.ts +++ b/experimental/packages/otlp-transformer/src/logs/index.ts @@ -22,8 +22,9 @@ import { IResourceLogs, } from './types'; import { IResource } from '@opentelemetry/resources'; +import { hrTimeToFixed64Nanos } from '../common'; import { toAnyValue, toAttributes, toKeyValue } from '../common/internal'; -import { hexToBase64, hrTimeToNanoseconds } from '@opentelemetry/core'; +import { hexToBase64 } from '@opentelemetry/core'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { IKeyValue } from '../common/types'; import { LogAttributes } from '@opentelemetry/api-logs'; @@ -94,8 +95,8 @@ function logRecordsToResourceLogs( function toLogRecord(log: ReadableLogRecord, useHex?: boolean): ILogRecord { return { - timeUnixNano: hrTimeToNanoseconds(log.hrTime), - observedTimeUnixNano: hrTimeToNanoseconds(log.hrTimeObserved), + timeUnixNano: hrTimeToFixed64Nanos(log.hrTime), + observedTimeUnixNano: hrTimeToFixed64Nanos(log.hrTimeObserved), severityNumber: toSeverityNumber(log.severityNumber), severityText: log.severityText, body: toAnyValue(log.body), diff --git a/experimental/packages/otlp-transformer/src/logs/types.ts b/experimental/packages/otlp-transformer/src/logs/types.ts index 05709af6f5..7704c37083 100644 --- a/experimental/packages/otlp-transformer/src/logs/types.ts +++ b/experimental/packages/otlp-transformer/src/logs/types.ts @@ -16,6 +16,7 @@ import type { IAnyValue, + IFixed64, IInstrumentationScope, IKeyValue, } from '../common/types'; @@ -67,10 +68,10 @@ export interface IScopeLogs { /** Properties of a LogRecord. */ export interface ILogRecord { /** LogRecord timeUnixNano */ - timeUnixNano: number; + timeUnixNano: IFixed64; /** LogRecord observedTimeUnixNano */ - observedTimeUnixNano: number; + observedTimeUnixNano: IFixed64; /** LogRecord severityNumber */ severityNumber?: ESeverityNumber; diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts index b73696b0ab..670741eb30 100644 --- a/experimental/packages/otlp-transformer/src/metrics/internal.ts +++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import { ValueType } from '@opentelemetry/api'; -import { hrTimeToNanoseconds } from '@opentelemetry/core'; import { AggregationTemporality, DataPoint, @@ -25,6 +24,7 @@ import { ResourceMetrics, ScopeMetrics, } from '@opentelemetry/sdk-metrics'; +import { hrTimeToFixed64Nanos } from '../common'; import { toAttributes } from '../common/internal'; import { EAggregationTemporality, @@ -112,8 +112,8 @@ function toSingularDataPoint( ) { const out: INumberDataPoint = { attributes: toAttributes(dataPoint.attributes), - startTimeUnixNano: hrTimeToNanoseconds(dataPoint.startTime), - timeUnixNano: hrTimeToNanoseconds(dataPoint.endTime), + startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), + timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), }; switch (valueType) { @@ -145,8 +145,8 @@ function toHistogramDataPoints(metricData: MetricData): IHistogramDataPoint[] { sum: histogram.sum, min: histogram.min, max: histogram.max, - startTimeUnixNano: hrTimeToNanoseconds(dataPoint.startTime), - timeUnixNano: hrTimeToNanoseconds(dataPoint.endTime), + startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), + timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), }; }); } @@ -172,8 +172,8 @@ function toExponentialHistogramDataPoints( }, scale: histogram.scale, zeroCount: histogram.zeroCount, - startTimeUnixNano: hrTimeToNanoseconds(dataPoint.startTime), - timeUnixNano: hrTimeToNanoseconds(dataPoint.endTime), + startTimeUnixNano: hrTimeToFixed64Nanos(dataPoint.startTime), + timeUnixNano: hrTimeToFixed64Nanos(dataPoint.endTime), }; }); } diff --git a/experimental/packages/otlp-transformer/src/metrics/types.ts b/experimental/packages/otlp-transformer/src/metrics/types.ts index 2728ab1aba..65a2348516 100644 --- a/experimental/packages/otlp-transformer/src/metrics/types.ts +++ b/experimental/packages/otlp-transformer/src/metrics/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { IInstrumentationScope, IKeyValue } from '../common/types'; +import { IFixed64, IInstrumentationScope, IKeyValue } from '../common/types'; import { IResource } from '../resource/types'; /** Properties of an ExportMetricsServiceRequest. */ @@ -134,10 +134,10 @@ export interface INumberDataPoint { attributes: IKeyValue[]; /** NumberDataPoint startTimeUnixNano */ - startTimeUnixNano?: number; + startTimeUnixNano?: IFixed64 | string; /** NumberDataPoint timeUnixNano */ - timeUnixNano?: number; + timeUnixNano?: IFixed64 | string; /** NumberDataPoint asDouble */ asDouble?: number | null; @@ -158,10 +158,10 @@ export interface IHistogramDataPoint { attributes?: IKeyValue[]; /** HistogramDataPoint startTimeUnixNano */ - startTimeUnixNano?: number; + startTimeUnixNano?: IFixed64 | string; /** HistogramDataPoint timeUnixNano */ - timeUnixNano?: number; + timeUnixNano?: IFixed64 | string; /** HistogramDataPoint count */ count?: number; @@ -194,10 +194,10 @@ export interface IExponentialHistogramDataPoint { attributes?: IKeyValue[]; /** ExponentialHistogramDataPoint startTimeUnixNano */ - startTimeUnixNano?: number; + startTimeUnixNano?: IFixed64 | string; /** ExponentialHistogramDataPoint timeUnixNano */ - timeUnixNano?: number; + timeUnixNano?: IFixed64 | string; /** ExponentialHistogramDataPoint count */ count?: number; diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts index 6811a8182f..d9c17855ce 100644 --- a/experimental/packages/otlp-transformer/src/trace/internal.ts +++ b/experimental/packages/otlp-transformer/src/trace/internal.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import type { Link } from '@opentelemetry/api'; -import { hrTimeToNanoseconds } from '@opentelemetry/core'; import type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; +import { hrTimeToFixed64Nanos } from '../common'; import { toAttributes } from '../common/internal'; import { EStatusCode, IEvent, ILink, ISpan } from './types'; import * as core from '@opentelemetry/core'; @@ -36,8 +36,8 @@ export function sdkSpanToOtlpSpan(span: ReadableSpan, useHex?: boolean): ISpan { name: span.name, // Span kind is offset by 1 because the API does not define a value for unset kind: span.kind == null ? 0 : span.kind + 1, - startTimeUnixNano: hrTimeToNanoseconds(span.startTime), - endTimeUnixNano: hrTimeToNanoseconds(span.endTime), + startTimeUnixNano: hrTimeToFixed64Nanos(span.startTime), + endTimeUnixNano: hrTimeToFixed64Nanos(span.endTime), attributes: toAttributes(span.attributes), droppedAttributesCount: span.droppedAttributesCount, events: span.events.map(toOtlpSpanEvent), @@ -72,7 +72,7 @@ export function toOtlpSpanEvent(timedEvent: TimedEvent): IEvent { ? toAttributes(timedEvent.attributes) : [], name: timedEvent.name, - timeUnixNano: hrTimeToNanoseconds(timedEvent.time), + timeUnixNano: hrTimeToFixed64Nanos(timedEvent.time), droppedAttributesCount: timedEvent.droppedAttributesCount || 0, }; } diff --git a/experimental/packages/otlp-transformer/src/trace/types.ts b/experimental/packages/otlp-transformer/src/trace/types.ts index a6cfb82ad7..294f45652f 100644 --- a/experimental/packages/otlp-transformer/src/trace/types.ts +++ b/experimental/packages/otlp-transformer/src/trace/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IInstrumentationScope, IKeyValue } from '../common/types'; +import { IFixed64, IInstrumentationScope, IKeyValue } from '../common/types'; import { IResource } from '../resource/types'; /** Properties of an ExportTraceServiceRequest. */ @@ -81,10 +81,10 @@ export interface ISpan { kind: ESpanKind; /** Span startTimeUnixNano */ - startTimeUnixNano: number; + startTimeUnixNano: IFixed64; /** Span endTimeUnixNano */ - endTimeUnixNano: number; + endTimeUnixNano: IFixed64; /** Span attributes */ attributes: IKeyValue[]; @@ -166,7 +166,7 @@ export const enum EStatusCode { /** Properties of an Event. */ export interface IEvent { /** Event timeUnixNano */ - timeUnixNano: number; + timeUnixNano: IFixed64; /** Event name */ name: string; diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index ef0a4cf9d7..0937c7d152 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -21,6 +21,7 @@ import { createExportLogsServiceRequest, ESeverityNumber, IExportLogsServiceRequest, + UnsignedLong, } from '../src'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; import { SeverityNumber } from '@opentelemetry/api-logs'; @@ -49,10 +50,8 @@ function createExpectedLogJson(useHex: boolean): IExportLogsServiceRequest { scope: { name: 'scope_name_1', version: '0.1.0' }, logRecords: [ { - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - timeUnixNano: 1680253513123241635, - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - observedTimeUnixNano: 1683526948965142784, + timeUnixNano: new UnsignedLong(-162521437, 391214506), + observedTimeUnixNano: new UnsignedLong(584929536, 391976663), severityNumber: ESeverityNumber.SEVERITY_NUMBER_ERROR, severityText: 'error', body: { stringValue: 'some_log_body' }, diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts index 748ef75889..15fd7d0531 100644 --- a/experimental/packages/otlp-transformer/test/metrics.test.ts +++ b/experimental/packages/otlp-transformer/test/metrics.test.ts @@ -25,7 +25,8 @@ import { import * as assert from 'assert'; import { createExportMetricsServiceRequest } from '../src/metrics'; import { EAggregationTemporality } from '../src/metrics/types'; -import { hrTime, hrTimeToNanoseconds } from '@opentelemetry/core'; +import { hrTime } from '@opentelemetry/core'; +import { hrTimeToFixed64Nanos } from '../src/common'; const START_TIME = hrTime(); const END_TIME = hrTime(); @@ -350,8 +351,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), asInt: 10, }, ], @@ -393,8 +394,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), asInt: 10, }, ], @@ -437,8 +438,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), asInt: 10, }, ], @@ -481,8 +482,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), asInt: 10, }, ], @@ -523,8 +524,8 @@ describe('Metrics', () => { dataPoints: [ { attributes: expectedAttributes, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), asDouble: 10.5, }, ], @@ -581,8 +582,8 @@ describe('Metrics', () => { sum: 9, min: 1, max: 8, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), }, ], }, @@ -635,8 +636,8 @@ describe('Metrics', () => { sum: 9, min: undefined, max: undefined, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), }, ], }, @@ -701,8 +702,8 @@ describe('Metrics', () => { bucketCounts: [1, 0, 0, 0, 1, 0, 1, 0], }, negative: { offset: 0, bucketCounts: [0] }, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), }, ], }, @@ -763,8 +764,8 @@ describe('Metrics', () => { bucketCounts: [1, 0, 0, 0, 1, 0, 1, 0], }, negative: { offset: 0, bucketCounts: [0] }, - startTimeUnixNano: hrTimeToNanoseconds(START_TIME), - timeUnixNano: hrTimeToNanoseconds(END_TIME), + startTimeUnixNano: hrTimeToFixed64Nanos(START_TIME), + timeUnixNano: hrTimeToFixed64Nanos(END_TIME), }, ], }, diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts index 9fb0e3cbfb..380c9065c2 100644 --- a/experimental/packages/otlp-transformer/test/trace.test.ts +++ b/experimental/packages/otlp-transformer/test/trace.test.ts @@ -22,6 +22,7 @@ import { createExportTraceServiceRequest, ESpanKind, EStatusCode, + UnsignedLong, } from '../src'; function createExpectedSpanJson(useHex: boolean) { @@ -79,10 +80,8 @@ function createExpectedSpanJson(useHex: boolean) { ], }, ], - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - startTimeUnixNano: 1640715557342725388, - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - endTimeUnixNano: 1640715558642725388, + startTimeUnixNano: new UnsignedLong(1155450124, 382008859), + endTimeUnixNano: new UnsignedLong(-1839517172, 382008859), events: [ { droppedAttributesCount: 0, @@ -95,7 +94,7 @@ function createExpectedSpanJson(useHex: boolean) { }, ], name: 'some event', - timeUnixNano: 1640715558542725400, + timeUnixNano: new UnsignedLong(-1939517172, 382008859), }, ], attributes: [ From f0ceabc57cfbf35b411b583be80d4b05484076e3 Mon Sep 17 00:00:00 2001 From: FelipeEmerim Date: Thu, 28 Sep 2023 09:15:48 -0300 Subject: [PATCH 2/2] fix(exporter-zipkin): round timestamp to nearest integer in zipkin annotations (#4167) Co-authored-by: Marc Pichler --- CHANGELOG.md | 1 + packages/opentelemetry-exporter-zipkin/src/transform.ts | 2 +- .../test/common/transform.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad5c780983..ee112f73dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :bug: (Bug Fix) * fix(sdk-metrics): allow instrument names to contain '/' [#4155](https://github.com/open-telemetry/opentelemetry-js/pull/4155) +* fix(exporter-zipkin): round duration to the nearest int in annotations to be compliant with zipkin protocol [#4167](https://github.com/open-telemetry/opentelemetry-js/pull/4167) @FelipeEmerim ### :books: (Refine Doc) diff --git a/packages/opentelemetry-exporter-zipkin/src/transform.ts b/packages/opentelemetry-exporter-zipkin/src/transform.ts index 54392321f8..06fbe5973a 100644 --- a/packages/opentelemetry-exporter-zipkin/src/transform.ts +++ b/packages/opentelemetry-exporter-zipkin/src/transform.ts @@ -111,7 +111,7 @@ export function _toZipkinAnnotations( events: TimedEvent[] ): zipkinTypes.Annotation[] { return events.map(event => ({ - timestamp: hrTimeToMicroseconds(event.time), + timestamp: Math.round(hrTimeToMicroseconds(event.time)), value: event.name, })); } diff --git a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts index 810b9e32f9..4df7f73f6c 100644 --- a/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts @@ -82,7 +82,7 @@ describe('transform', () => { annotations: [ { value: 'my-event', - timestamp: hrTimeToMicroseconds(span.events[0].time), + timestamp: Math.round(hrTimeToMicroseconds(span.events[0].time)), }, ], duration: Math.round( @@ -329,11 +329,11 @@ describe('transform', () => { assert.deepStrictEqual(annotations, [ { value: 'my-event1', - timestamp: hrTimeToMicroseconds(span.events[0].time), + timestamp: Math.round(hrTimeToMicroseconds(span.events[0].time)), }, { value: 'my-event2', - timestamp: hrTimeToMicroseconds(span.events[1].time), + timestamp: Math.round(hrTimeToMicroseconds(span.events[1].time)), }, ]); });