diff --git a/.github/workflows/peer-api.yaml b/.github/workflows/peer-api.yaml
index ab623897a1b..b767ff6813a 100644
--- a/.github/workflows/peer-api.yaml
+++ b/.github/workflows/peer-api.yaml
@@ -27,4 +27,4 @@ jobs:
- name: Check API dependency semantics (experimental)
working-directory: experimental
- run: lerna exec --ignore propagation-validation-server --ignore @opentelemetry/selenium-tests --ignore @opentelemetry/api-metrics-wip "node ../../../scripts/peer-api-check.js"
+ run: lerna exec --ignore propagation-validation-server --ignore @opentelemetry/selenium-tests --ignore @opentelemetry/api-metrics-wip --ignore @opentelemetry/otlp-transformer "node ../../../scripts/peer-api-check.js"
diff --git a/.nycrc b/.nycrc
index 47b8c170df5..520f016fc2b 100644
--- a/.nycrc
+++ b/.nycrc
@@ -12,7 +12,8 @@
"test/**/*.*",
".eslintrc.js",
"karma.conf.js",
- "webpack/*.js"
+ "webpack/*.js",
+ "src/generated/**"
],
"all": true
}
diff --git a/experimental/packages/otlp-transformer/.eslintignore b/experimental/packages/otlp-transformer/.eslintignore
new file mode 100644
index 00000000000..345f1a599ed
--- /dev/null
+++ b/experimental/packages/otlp-transformer/.eslintignore
@@ -0,0 +1,2 @@
+build
+src/generated
diff --git a/experimental/packages/otlp-transformer/.eslintrc.js b/experimental/packages/otlp-transformer/.eslintrc.js
new file mode 100644
index 00000000000..b9004d20258
--- /dev/null
+++ b/experimental/packages/otlp-transformer/.eslintrc.js
@@ -0,0 +1,8 @@
+module.exports = {
+ "env": {
+ "mocha": true,
+ "commonjs": true,
+ "shared-node-browser": true
+ },
+ ...require('../../../eslint.config.js')
+}
diff --git a/experimental/packages/otlp-transformer/.gitignore b/experimental/packages/otlp-transformer/.gitignore
new file mode 100644
index 00000000000..c83f90a2b01
--- /dev/null
+++ b/experimental/packages/otlp-transformer/.gitignore
@@ -0,0 +1 @@
+src/generated
diff --git a/experimental/packages/otlp-transformer/README.md b/experimental/packages/otlp-transformer/README.md
new file mode 100644
index 00000000000..2c53ce82cac
--- /dev/null
+++ b/experimental/packages/otlp-transformer/README.md
@@ -0,0 +1,115 @@
+# OpenTelemetry Protocol
+
+[![NPM Published Version][npm-img]][npm-url]
+[![Apache License][license-image]][license-image]
+
+This package provides everything needed to serialize [OpenTelemetry SDK][sdk] traces and metrics into the [OpenTelemetry Protocol][otlp] format using [protocol buffers][protobuf] or JSON.
+It also contains service clients for exporting traces and metrics to the OpenTelemetry Collector or a compatible receiver using using OTLP over [gRPC][grpc].
+This module uses [`protobufjs`][protobufjs] for serialization and is compatible with [`@grpc/grpc-js`][grpc-js].
+
+## Quick Start
+
+To get started you will need to install a compatible OpenTelemetry API.
+
+### Install Peer Dependencies
+
+```sh
+npm install \
+ @opentelemetry/api \
+ @grpc/grpc-js # only required if you are using gRPC
+```
+
+### Serialize Traces and Metrics
+
+This module exports functions to serialize traces and metrics from the OpenTelemetry SDK into protocol buffers which can be sent over HTTP to the OpenTelemetry collector or a compatible receiver.
+
+```typescript
+import { createExportTraceServiceRequest, createExportMetricsServiceRequest } from "@opentelemetry/otlp-transformer";
+
+const serializedSpans = createExportTraceServiceRequest(readableSpans);
+const serializedMetrics = createExportMetricsServiceRequest(readableMetrics);
+```
+
+### Create gRPC Service Clients
+
+This module also contains gRPC service clients for exporting traces and metrics to an OpenTelemetry collector or compatible receiver over gRPC.
+In order to avoid bundling a gRPC module with this module, it is required to construct an RPC implementation to pass to the constructor of the service clients.
+Any RPC implementation compatible with `grpc` or `@grpc/grpc-js` may be used, but `@grpc/grpc-js` is recommended.
+
+```typescript
+import type { RPCImpl } from 'protobufjs';
+import { makeGenericClientConstructor, credentials } from '@gprc/grpc-js';
+import { MetricServiceClient, TraceServiceClient } from "@opentelemetry/otlp-transformer";
+
+// Construct a RPC Implementation according to protobufjs docs
+const GrpcClientConstructor = makeGenericClientConstructor({});
+
+const metricGRPCClient = new GrpcClientConstructor(
+ "http://localhost:4317/v1/metrics", // default collector metrics endpoint
+ credentials.createInsecure(),
+);
+
+const traceGRPCClient = new GrpcClientConstructor(
+ "http://localhost:4317/v1/traces", // default collector traces endpoint
+ credentials.createInsecure(),
+);
+
+const metricRpc: RPCImpl = function(method, requestData, callback) {
+ metricGRPCClient.makeUnaryRequest(
+ method.name,
+ arg => arg,
+ arg => arg,
+ requestData,
+ callback
+ );
+}
+
+const traceRpc: RPCImpl = function(method, requestData, callback) {
+ traceGRPCClient.makeUnaryRequest(
+ method.name,
+ arg => arg,
+ arg => arg,
+ requestData,
+ callback
+ );
+}
+
+// Construct service clients to use RPC Implementations
+const metricServiceClient = new MetricServiceClient({
+ rpcImpl: metricRpc,
+ startTime: Date.now(), // exporter start time in milliseconds
+});
+
+const traceServiceClient = new TraceServiceClient({
+ rpcImpl: traceRpc,
+});
+
+// Export ReadableSpan[] and ReadableMetric[] over gRPC
+await metricServiceClient.export(readableMetrics);
+await traceServiceClient.export(readableSpans);
+```
+
+## Useful links
+
+- For more information on OpenTelemetry, visit:
+- For more about OpenTelemetry JavaScript:
+- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
+
+## License
+
+Apache 2.0 - See [LICENSE][license-url] for more information.
+
+[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions
+[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
+[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
+[npm-url]: https://www.npmjs.com/package/@opentelemetry/otlp-transformer
+[npm-img]: https://badge.fury.io/js/%40opentelemetry%otlp-transformer.svg
+
+[sdk]: https://github.com/open-telemetry/opentelemetry-js
+[otlp]: https://github.com/open-telemetry/opentelemetry-proto
+
+[protobuf]: https://developers.google.com/protocol-buffers
+[grpc]: https://grpc.io/
+
+[protobufjs]: https://www.npmjs.com/package/protobufjs
+[grpc-js]: https://www.npmjs.com/package/@grpc/grpc-js
diff --git a/experimental/packages/otlp-transformer/karma.conf.js b/experimental/packages/otlp-transformer/karma.conf.js
new file mode 100644
index 00000000000..6174839d651
--- /dev/null
+++ b/experimental/packages/otlp-transformer/karma.conf.js
@@ -0,0 +1,24 @@
+/*!
+ * 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
+ *
+ * http://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.
+ */
+
+const karmaWebpackConfig = require('../../../karma.webpack');
+const karmaBaseConfig = require('../../../karma.base');
+
+module.exports = (config) => {
+ config.set(Object.assign({}, karmaBaseConfig, {
+ webpack: karmaWebpackConfig
+ }))
+};
diff --git a/experimental/packages/otlp-transformer/package.json b/experimental/packages/otlp-transformer/package.json
new file mode 100644
index 00000000000..de44519a92b
--- /dev/null
+++ b/experimental/packages/otlp-transformer/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "@opentelemetry/otlp-transformer",
+ "private": true,
+ "publishConfig": {
+ "access": "restricted"
+ },
+ "version": "0.27.0",
+ "description": "Transform OpenTelemetry SDK data into OTLP",
+ "module": "build/esm/index.js",
+ "esnext": "build/esnext/index.js",
+ "types": "build/src/index.d.ts",
+ "repository": "open-telemetry/opentelemetry-js",
+ "scripts": {
+ "compile": "tsc --build tsconfig.all.json",
+ "clean": "tsc --build --clean tsconfig.all.json",
+ "lint": "eslint . --ext .ts",
+ "lint:fix": "eslint . --ext .ts --fix",
+ "tdd": "npm run test -- --watch-extensions ts --watch",
+ "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
+ "test:browser": "nyc karma start --single-run",
+ "watch": "tsc --build -w tsconfig.all.json"
+ },
+ "keywords": [
+ "opentelemetry",
+ "nodejs",
+ "grpc",
+ "protobuf",
+ "otlp",
+ "tracing",
+ "metrics"
+ ],
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "files": [
+ "build/src/**/*.js",
+ "build/src/**/*.js.map",
+ "build/src/**/*.d.ts",
+ "LICENSE",
+ "README.md"
+ ],
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.0.0 <1.1.0",
+ "@opentelemetry/api-metrics": "~0.27.0"
+ },
+ "devDependencies": {
+ "@opentelemetry/api": "1.0.4",
+ "@opentelemetry/api-metrics": "0.27.0",
+ "@types/mocha": "8.2.3",
+ "@types/webpack-env": "1.16.3",
+ "codecov": "3.8.3",
+ "istanbul-instrumenter-loader": "3.0.1",
+ "karma": "6.3.16",
+ "karma-chrome-launcher": "3.1.0",
+ "karma-coverage-istanbul-reporter": "3.0.3",
+ "karma-mocha": "2.0.1",
+ "karma-spec-reporter": "0.0.32",
+ "karma-webpack": "4.0.2",
+ "mkdirp": "1.0.4",
+ "mocha": "7.2.0",
+ "nyc": "15.1.0",
+ "protobufjs": "6.11.2",
+ "rimraf": "3.0.2",
+ "ts-loader": "8.3.0",
+ "ts-mocha": "8.0.0",
+ "typescript": "4.4.4",
+ "webpack": "4.46.0"
+ },
+ "dependencies": {
+ "@opentelemetry/core": "1.0.1",
+ "@opentelemetry/resources": "1.0.1",
+ "@opentelemetry/sdk-metrics-base": "0.27.0",
+ "@opentelemetry/sdk-trace-base": "1.0.1"
+ }
+}
diff --git a/experimental/packages/otlp-transformer/src/common/internal.ts b/experimental/packages/otlp-transformer/src/common/internal.ts
new file mode 100644
index 00000000000..7221f4dce68
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/common/internal.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { SpanAttributes } from '@opentelemetry/api';
+import { IAnyValue, IKeyValue } from './types';
+
+export function toAttributes(
+ attributes: SpanAttributes
+): IKeyValue[] {
+ return Object.keys(attributes).map(key => toKeyValue(key, attributes[key]));
+}
+
+export function toKeyValue(
+ key: string,
+ value: unknown
+): IKeyValue {
+ return {
+ key: key,
+ value: toAnyValue(value),
+ };
+}
+
+export function toAnyValue(value: unknown): IAnyValue {
+ const t = typeof value;
+ if (t === 'string') return { stringValue: value as string };
+ if (t === 'number') {
+ if (!Number.isInteger(value)) return { doubleValue: value as number };
+ return { intValue: value as number };
+ }
+ if (t === 'boolean') return { boolValue: value as boolean };
+ if (value instanceof Uint8Array) return { bytesValue: value };
+ if (Array.isArray(value)) return { arrayValue: { values: value.map(toAnyValue) } };
+ if (t === 'object' && value != null) return { kvlistValue: { values: Object.entries(value as object).map(([k, v]) => toKeyValue(k, v)) } };
+
+ return {};
+}
+
+export function hexToBuf(hex: string): Uint8Array | undefined {
+ const ints = hex.match(/[\da-f]{2}/gi)?.map(h => parseInt(h, 16));
+ return ints && new Uint8Array(ints);
+}
+
+function i2hex(i: number): string {
+ return ('0' + i.toString(16)).slice(-2);
+}
+
+export function bufToHex(buf?: Uint8Array | null): string | undefined {
+ if (buf == null || buf.length === 0) return undefined;
+ return Array.from(buf).map(i2hex).join('');
+}
diff --git a/experimental/packages/otlp-transformer/src/common/types.ts b/experimental/packages/otlp-transformer/src/common/types.ts
new file mode 100644
index 00000000000..c706015b843
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/common/types.ts
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+/** Properties of an InstrumentationLibrary. */
+export interface IInstrumentationLibrary {
+ /** InstrumentationLibrary name */
+ name: string
+
+ /** InstrumentationLibrary version */
+ version?: string;
+}
+
+/** Properties of a KeyValue. */
+export interface IKeyValue {
+ /** KeyValue key */
+ key: string;
+
+ /** KeyValue value */
+ value: IAnyValue;
+}
+
+/** Properties of an AnyValue. */
+export interface IAnyValue {
+ /** AnyValue stringValue */
+ stringValue?: (string | null);
+
+ /** AnyValue boolValue */
+ boolValue?: (boolean | null);
+
+ /** AnyValue intValue */
+ intValue?: (number | Long | null);
+
+ /** AnyValue doubleValue */
+ doubleValue?: (number | null);
+
+ /** AnyValue arrayValue */
+ arrayValue?: IArrayValue;
+
+ /** AnyValue kvlistValue */
+ kvlistValue?: IKeyValueList;
+
+ /** AnyValue bytesValue */
+ bytesValue?: Uint8Array;
+}
+
+/** Properties of an ArrayValue. */
+export interface IArrayValue {
+ /** ArrayValue values */
+ values: IAnyValue[];
+}
+
+/** Properties of a KeyValueList. */
+export interface IKeyValueList {
+ /** KeyValueList values */
+ values: IKeyValue[];
+}
diff --git a/experimental/packages/otlp-transformer/src/index.ts b/experimental/packages/otlp-transformer/src/index.ts
new file mode 100644
index 00000000000..b7052c8f54d
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/index.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+export * from './common/types';
+// export * from './metrics/types';
+export * from './resource/types';
+export * from './trace/types';
+
+export { createExportTraceServiceRequest } from './trace';
+// export { createExportMetricsServiceRequest } from './metrics';
diff --git a/experimental/packages/otlp-transformer/src/metrics/index.ts b/experimental/packages/otlp-transformer/src/metrics/index.ts
new file mode 100644
index 00000000000..634dd2146ed
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/metrics/index.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 { InstrumentationLibrary } from '@opentelemetry/core';
+import type { Resource } from '@opentelemetry/resources';
+import type { MetricRecord } from '@opentelemetry/sdk-metrics-base';
+import { toAttributes } from '../common/internal';
+import { toMetric } from './internal';
+import type { IExportMetricsServiceRequest } from './types';
+
+export function createExportMetricsServiceRequest(metricRecords: MetricRecord[], startTime: number): IExportMetricsServiceRequest | null {
+ if (metricRecords.length === 0) {
+ return null;
+ }
+
+ return {
+ resourceMetrics: metricRecordsToResourceMetrics(metricRecords).map(({ resource, resourceMetrics, resourceSchemaUrl }) => ({
+ resource: {
+ attributes: toAttributes(resource.attributes),
+ droppedAttributesCount: 0,
+ },
+ instrumentationLibraryMetrics: resourceMetrics.map(({ instrumentationLibrary, instrumentationLibraryMetrics, librarySchemaUrl }) => ({
+ instrumentationLibrary: {
+ name: instrumentationLibrary.name,
+ version: instrumentationLibrary.version,
+ },
+ metrics: instrumentationLibraryMetrics.map(m => toMetric(m, startTime)),
+ schemaUrl: librarySchemaUrl,
+ })),
+ schemaUrl: resourceSchemaUrl,
+ }))
+ };
+}
+
+type IntermediateResourceMetrics = {
+ resource: Resource,
+ resourceMetrics: IntermediateInstrumentationLibraryMetrics[],
+ resourceSchemaUrl?: string,
+};
+
+type IntermediateInstrumentationLibraryMetrics = {
+ instrumentationLibrary: InstrumentationLibrary,
+ instrumentationLibraryMetrics: MetricRecord[],
+ librarySchemaUrl?: string,
+};
+
+function metricRecordsToResourceMetrics(metricRecords: MetricRecord[]): IntermediateResourceMetrics[] {
+ const resourceMap: Map> = new Map();
+
+ for (const record of metricRecords) {
+ let ilmMap = resourceMap.get(record.resource);
+
+ if (!ilmMap) {
+ ilmMap = new Map();
+ resourceMap.set(record.resource, ilmMap);
+ }
+
+ const instrumentationLibraryKey = `${record.instrumentationLibrary.name}@${record.instrumentationLibrary.name || ''}:${record.instrumentationLibrary.schemaUrl || ''}`;
+ let records = ilmMap.get(instrumentationLibraryKey);
+
+ if (!records) {
+ records = [];
+ ilmMap.set(instrumentationLibraryKey, records);
+ }
+
+ records.push(record);
+ }
+
+ const out: IntermediateResourceMetrics[] = [];
+
+ const resourceMapEntryIterator = resourceMap.entries();
+ let resourceMapEntry = resourceMapEntryIterator.next();
+ while (!resourceMapEntry.done) {
+ const [resource, ilmMap] = resourceMapEntry.value;
+ const resourceMetrics: IntermediateInstrumentationLibraryMetrics[] = [];
+ const ilmIterator = ilmMap.values();
+ let ilmEntry = ilmIterator.next();
+ while (!ilmEntry.done) {
+ const instrumentationLibraryMetrics = ilmEntry.value;
+ if (instrumentationLibraryMetrics.length > 0) {
+ const lib = instrumentationLibraryMetrics[0].instrumentationLibrary;
+ resourceMetrics.push({ instrumentationLibrary: lib, instrumentationLibraryMetrics, librarySchemaUrl: lib.schemaUrl });
+ }
+ ilmEntry = ilmIterator.next();
+ }
+ // TODO SDK types don't provide resource schema URL at this time
+ out.push({ resource, resourceMetrics });
+ resourceMapEntry = resourceMapEntryIterator.next();
+ }
+
+ return out;
+}
diff --git a/experimental/packages/otlp-transformer/src/metrics/internal.ts b/experimental/packages/otlp-transformer/src/metrics/internal.ts
new file mode 100644
index 00000000000..dbcd6ec540a
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/metrics/internal.ts
@@ -0,0 +1,137 @@
+/*
+ * 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 { AggregationTemporality, ValueType } from '@opentelemetry/api-metrics';
+import { hrTimeToNanoseconds } from '@opentelemetry/core';
+import type { Histogram, MetricRecord, Point } from '@opentelemetry/sdk-metrics-base';
+import { AggregatorKind, MetricKind } from '@opentelemetry/sdk-metrics-base';
+import { toAttributes } from '../common/internal';
+import { EAggregationTemporality, IGauge, IHistogram, IHistogramDataPoint, IMetric, INumberDataPoint, ISum } from './types';
+
+
+export function toMetric(metric: MetricRecord, startTime: number): IMetric {
+ const out: IMetric = {
+ description: metric.descriptor.description,
+ name: metric.descriptor.name,
+ unit: metric.descriptor.unit,
+ };
+
+ if (isSum(metric)) {
+ out.sum = toSum(metric, startTime);
+ } else if (metric.aggregator.kind === AggregatorKind.LAST_VALUE) {
+ out.gauge = toGauge(metric, startTime);
+ } else if (metric.aggregator.kind === AggregatorKind.HISTOGRAM) {
+ out.histogram = toHistogram(metric, startTime);
+ }
+
+ return out;
+}
+
+function isSum(metric: MetricRecord) {
+ return metric.aggregator.kind === AggregatorKind.SUM ||
+ metric.descriptor.metricKind === MetricKind.OBSERVABLE_COUNTER ||
+ metric.descriptor.metricKind === MetricKind.OBSERVABLE_UP_DOWN_COUNTER;
+}
+
+function toAggregationTemporality(
+ metric: MetricRecord
+): EAggregationTemporality {
+ if (metric.descriptor.metricKind === MetricKind.OBSERVABLE_GAUGE) {
+ return EAggregationTemporality.AGGREGATION_TEMPORALITY_UNSPECIFIED;
+ }
+
+ if (metric.aggregationTemporality === AggregationTemporality.AGGREGATION_TEMPORALITY_DELTA) {
+ return EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA;
+ }
+
+ if (metric.aggregationTemporality === AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE) {
+ return EAggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE;
+ }
+
+ return EAggregationTemporality.AGGREGATION_TEMPORALITY_UNSPECIFIED;
+}
+
+function toSum(
+ metric: MetricRecord,
+ startTime: number,
+): ISum {
+ return {
+ dataPoints: [toNumberDataPoint(metric, startTime)],
+ isMonotonic:
+ metric.descriptor.metricKind === MetricKind.COUNTER ||
+ metric.descriptor.metricKind === MetricKind.OBSERVABLE_COUNTER,
+ aggregationTemporality: toAggregationTemporality(metric),
+ };
+}
+
+function toGauge(
+ metric: MetricRecord,
+ startTime: number,
+): IGauge {
+ return {
+ dataPoints: [toNumberDataPoint(metric, startTime)],
+ };
+}
+
+function toHistogram(
+ metric: MetricRecord,
+ startTime: number,
+): IHistogram {
+ return {
+ dataPoints: [toHistogramDataPoint(metric, startTime)],
+ aggregationTemporality: toAggregationTemporality(metric),
+ };
+}
+
+function toNumberDataPoint(
+ metric: MetricRecord,
+ startTime: number,
+): INumberDataPoint {
+ const out: INumberDataPoint = {
+ attributes: toAttributes(metric.attributes),
+ startTimeUnixNano: startTime,
+ timeUnixNano: hrTimeToNanoseconds(
+ metric.aggregator.toPoint().timestamp
+ ),
+ };
+
+ if (metric.descriptor.valueType === ValueType.INT) {
+ out.asInt = metric.aggregator.toPoint().value as number;
+ }
+
+ if (metric.descriptor.valueType === ValueType.DOUBLE) {
+ out.asDouble = metric.aggregator.toPoint().value as number;
+ }
+
+ return out;
+}
+
+function toHistogramDataPoint(
+ metric: MetricRecord,
+ startTime: number,
+): IHistogramDataPoint {
+ const point = metric.aggregator.toPoint() as Point;
+ return {
+ attributes: toAttributes(metric.attributes),
+ bucketCounts: point.value.buckets.counts,
+ explicitBounds: point.value.buckets.boundaries,
+ count: point.value.count,
+ sum: point.value.sum,
+ startTimeUnixNano: startTime,
+ timeUnixNano: hrTimeToNanoseconds(
+ metric.aggregator.toPoint().timestamp
+ ),
+ };
+}
diff --git a/experimental/packages/otlp-transformer/src/metrics/types.ts b/experimental/packages/otlp-transformer/src/metrics/types.ts
new file mode 100644
index 00000000000..b5900c31e03
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/metrics/types.ts
@@ -0,0 +1,352 @@
+/*
+ * 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 { IInstrumentationLibrary, IKeyValue } from '../common/types';
+import { IResource } from '../resource/types';
+
+/** Properties of an ExportMetricsServiceRequest. */
+export interface IExportMetricsServiceRequest {
+
+ /** ExportMetricsServiceRequest resourceMetrics */
+ resourceMetrics: IResourceMetrics[]
+}
+
+
+/** Properties of a ResourceMetrics. */
+export interface IResourceMetrics {
+ /** ResourceMetrics resource */
+ resource?: IResource;
+
+ /** ResourceMetrics instrumentationLibraryMetrics */
+ instrumentationLibraryMetrics: IInstrumentationLibraryMetrics[]
+
+ /** ResourceMetrics schemaUrl */
+ schemaUrl?: string;
+}
+
+
+/** Properties of an InstrumentationLibraryMetrics. */
+export interface IInstrumentationLibraryMetrics {
+
+ /** InstrumentationLibraryMetrics instrumentationLibrary */
+ instrumentationLibrary?: IInstrumentationLibrary;
+
+ /** InstrumentationLibraryMetrics metrics */
+ metrics: IMetric[];
+
+ /** InstrumentationLibraryMetrics schemaUrl */
+ schemaUrl?: string;
+}
+
+/** Properties of a Metric. */
+export interface IMetric {
+
+ /** Metric name */
+ name: string;
+
+ /** Metric description */
+ description?: string;
+
+ /** Metric unit */
+ unit?: string;
+
+ /** Metric gauge */
+ gauge?: IGauge;
+
+ /** Metric sum */
+ sum?: ISum;
+
+ /** Metric histogram */
+ histogram?: IHistogram;
+
+ /** Metric exponentialHistogram */
+ exponentialHistogram?: IExponentialHistogram;
+
+ /** Metric summary */
+ summary?: ISummary;
+}
+
+/** Properties of a Gauge. */
+export interface IGauge {
+ /** Gauge dataPoints */
+ dataPoints: INumberDataPoint[]
+}
+
+/** Properties of a Sum. */
+export interface ISum {
+
+ /** Sum dataPoints */
+ dataPoints: INumberDataPoint[];
+
+ /** Sum aggregationTemporality */
+ aggregationTemporality: EAggregationTemporality
+
+ /** Sum isMonotonic */
+ isMonotonic?: (boolean | null);
+}
+
+/** Properties of a Histogram. */
+export interface IHistogram {
+ /** Histogram dataPoints */
+ dataPoints?: IHistogramDataPoint[]
+
+ /** Histogram aggregationTemporality */
+ aggregationTemporality?: EAggregationTemporality
+}
+
+/** Properties of an ExponentialHistogram. */
+export interface IExponentialHistogram {
+ /** ExponentialHistogram dataPoints */
+ dataPoints: IExponentialHistogramDataPoint[];
+
+ /** ExponentialHistogram aggregationTemporality */
+ aggregationTemporality?: EAggregationTemporality;
+}
+
+/** Properties of a Summary. */
+export interface ISummary {
+ /** Summary dataPoints */
+ dataPoints: ISummaryDataPoint[];
+}
+
+/** Properties of a NumberDataPoint. */
+export interface INumberDataPoint {
+
+ /** NumberDataPoint attributes */
+ attributes: IKeyValue[]
+
+ /** NumberDataPoint startTimeUnixNano */
+ startTimeUnixNano?: number;
+
+ /** NumberDataPoint timeUnixNano */
+ timeUnixNano?: number;
+
+ /** NumberDataPoint asDouble */
+ asDouble?: (number | null);
+
+ /** NumberDataPoint asInt */
+ asInt?: number;
+
+ /** NumberDataPoint exemplars */
+ exemplars?: IExemplar[];
+
+ /** NumberDataPoint flags */
+ flags?: number;
+}
+
+
+/** Properties of a HistogramDataPoint. */
+export interface IHistogramDataPoint {
+ /** HistogramDataPoint attributes */
+ attributes?: IKeyValue[];
+
+ /** HistogramDataPoint startTimeUnixNano */
+ startTimeUnixNano?: number;
+
+ /** HistogramDataPoint timeUnixNano */
+ timeUnixNano?: number;
+
+ /** HistogramDataPoint count */
+ count?: number;
+
+ /** HistogramDataPoint sum */
+ sum?: number;
+
+ /** HistogramDataPoint bucketCounts */
+ bucketCounts?: number[]
+
+ /** HistogramDataPoint explicitBounds */
+ explicitBounds?: number[]
+
+ /** HistogramDataPoint exemplars */
+ exemplars?: IExemplar[];
+
+ /** HistogramDataPoint flags */
+ flags?: number;
+}
+
+/** Properties of an ExponentialHistogramDataPoint. */
+export interface IExponentialHistogramDataPoint {
+
+ /** ExponentialHistogramDataPoint attributes */
+ attributes?: IKeyValue[];
+
+ /** ExponentialHistogramDataPoint startTimeUnixNano */
+ startTimeUnixNano?: number;
+
+ /** ExponentialHistogramDataPoint timeUnixNano */
+ timeUnixNano?: string;
+
+ /** ExponentialHistogramDataPoint count */
+ count?: number;
+
+ /** ExponentialHistogramDataPoint sum */
+ sum?: number;
+
+ /** ExponentialHistogramDataPoint scale */
+ scale?: number;
+
+ /** ExponentialHistogramDataPoint zeroCount */
+ zeroCount?: number;
+
+ /** ExponentialHistogramDataPoint positive */
+ positive?: IBuckets;
+
+ /** ExponentialHistogramDataPoint negative */
+ negative?: IBuckets;
+
+ /** ExponentialHistogramDataPoint flags */
+ flags?: number;
+
+ /** ExponentialHistogramDataPoint exemplars */
+ exemplars?: IExemplar[]
+}
+
+
+/** Properties of a SummaryDataPoint. */
+export interface ISummaryDataPoint {
+ /** SummaryDataPoint attributes */
+ attributes?: IKeyValue[];
+
+ /** SummaryDataPoint startTimeUnixNano */
+ startTimeUnixNano?: number;
+
+ /** SummaryDataPoint timeUnixNano */
+ timeUnixNano?: string;
+
+ /** SummaryDataPoint count */
+ count?: number;
+
+ /** SummaryDataPoint sum */
+ sum?: number;
+
+ /** SummaryDataPoint quantileValues */
+ quantileValues?: IValueAtQuantile[];
+
+ /** SummaryDataPoint flags */
+ flags?: number;
+}
+
+/** Properties of a ValueAtQuantile. */
+export interface IValueAtQuantile {
+ /** ValueAtQuantile quantile */
+ quantile?: number;
+
+ /** ValueAtQuantile value */
+ value?: number;
+}
+
+/** Properties of a Buckets. */
+export interface IBuckets {
+ /** Buckets offset */
+ offset?: number;
+
+ /** Buckets bucketCounts */
+ bucketCounts?: number[];
+}
+
+/** Properties of an Exemplar. */
+export interface IExemplar {
+ /** Exemplar filteredAttributes */
+ filteredAttributes?: IKeyValue[];
+
+ /** Exemplar timeUnixNano */
+ timeUnixNano?: string;
+
+ /** Exemplar asDouble */
+ asDouble?: number;
+
+ /** Exemplar asInt */
+ asInt?: number;
+
+ /** Exemplar spanId */
+ spanId?: string;
+
+ /** Exemplar traceId */
+ traceId?: string;
+}
+
+/**
+ * AggregationTemporality defines how a metric aggregator reports aggregated
+ * values. It describes how those values relate to the time interval over
+ * which they are aggregated.
+ */
+export const enum EAggregationTemporality {
+ /* UNSPECIFIED is the default AggregationTemporality, it MUST not be used. */
+ AGGREGATION_TEMPORALITY_UNSPECIFIED = 0,
+
+ /** DELTA is an AggregationTemporality for a metric aggregator which reports
+ changes since last report time. Successive metrics contain aggregation of
+ values from continuous and non-overlapping intervals.
+
+ The values for a DELTA metric are based only on the time interval
+ associated with one measurement cycle. There is no dependency on
+ previous measurements like is the case for CUMULATIVE metrics.
+
+ For example, consider a system measuring the number of requests that
+ it receives and reports the sum of these requests every second as a
+ DELTA metric:
+
+ 1. The system starts receiving at time=t_0.
+ 2. A request is received, the system measures 1 request.
+ 3. A request is received, the system measures 1 request.
+ 4. A request is received, the system measures 1 request.
+ 5. The 1 second collection cycle ends. A metric is exported for the
+ number of requests received over the interval of time t_0 to
+ t_0+1 with a value of 3.
+ 6. A request is received, the system measures 1 request.
+ 7. A request is received, the system measures 1 request.
+ 8. The 1 second collection cycle ends. A metric is exported for the
+ number of requests received over the interval of time t_0+1 to
+ t_0+2 with a value of 2. */
+ AGGREGATION_TEMPORALITY_DELTA = 1,
+
+ /** CUMULATIVE is an AggregationTemporality for a metric aggregator which
+ reports changes since a fixed start time. This means that current values
+ of a CUMULATIVE metric depend on all previous measurements since the
+ start time. Because of this, the sender is required to retain this state
+ in some form. If this state is lost or invalidated, the CUMULATIVE metric
+ values MUST be reset and a new fixed start time following the last
+ reported measurement time sent MUST be used.
+
+ For example, consider a system measuring the number of requests that
+ it receives and reports the sum of these requests every second as a
+ CUMULATIVE metric:
+
+ 1. The system starts receiving at time=t_0.
+ 2. A request is received, the system measures 1 request.
+ 3. A request is received, the system measures 1 request.
+ 4. A request is received, the system measures 1 request.
+ 5. The 1 second collection cycle ends. A metric is exported for the
+ number of requests received over the interval of time t_0 to
+ t_0+1 with a value of 3.
+ 6. A request is received, the system measures 1 request.
+ 7. A request is received, the system measures 1 request.
+ 8. The 1 second collection cycle ends. A metric is exported for the
+ number of requests received over the interval of time t_0 to
+ t_0+2 with a value of 5.
+ 9. The system experiences a fault and loses state.
+ 10. The system recovers and resumes receiving at time=t_1.
+ 11. A request is received, the system measures 1 request.
+ 12. The 1 second collection cycle ends. A metric is exported for the
+ number of requests received over the interval of time t_1 to
+ t_0+1 with a value of 1.
+
+ Note: Even though, when reporting changes since last report time, using
+ CUMULATIVE is valid, it is not recommended. This may cause problems for
+ systems that do not use start_time to determine when the aggregation
+ value was reset (e.g. Prometheus). */
+ AGGREGATION_TEMPORALITY_CUMULATIVE = 2
+}
diff --git a/experimental/packages/otlp-transformer/src/resource/types.ts b/experimental/packages/otlp-transformer/src/resource/types.ts
new file mode 100644
index 00000000000..7755b756e91
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/resource/types.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { IKeyValue } from '../common/types';
+
+/** Properties of a Resource. */
+export interface IResource {
+ /** Resource attributes */
+ attributes: IKeyValue[];
+
+ /** Resource droppedAttributesCount */
+ droppedAttributesCount: number;
+}
diff --git a/experimental/packages/otlp-transformer/src/trace/index.ts b/experimental/packages/otlp-transformer/src/trace/index.ts
new file mode 100644
index 00000000000..1329fe7f031
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/trace/index.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { InstrumentationLibrary } from '@opentelemetry/core';
+import type { Resource } from '@opentelemetry/resources';
+import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
+import { toAttributes } from '../common/internal';
+import { sdkSpanToOtlpSpan } from './internal';
+import { IExportTraceServiceRequest } from './types';
+
+export function createExportTraceServiceRequest(spans: ReadableSpan[]): IExportTraceServiceRequest | null {
+ return {
+ resourceSpans: spanRecordsToResourceSpans(spans).map(({ resource, resourceSpans, resourceSchemaUrl }) => ({
+ resource: {
+ attributes: toAttributes(resource.attributes),
+ droppedAttributesCount: 0,
+ },
+ instrumentationLibrarySpans: resourceSpans.map(({ instrumentationLibrary, instrumentationLibrarySpans, librarySchemaUrl }) => ({
+ instrumentationLibrary,
+ spans: instrumentationLibrarySpans.map(sdkSpanToOtlpSpan),
+ schemaUrl: librarySchemaUrl,
+ })),
+ schemaUrl: resourceSchemaUrl,
+ }))
+ };
+}
+
+type ResourceSpans = {
+ resource: Resource,
+ resourceSpans: InstrumentationLibrarySpans[],
+ resourceSchemaUrl?: string,
+};
+
+type InstrumentationLibrarySpans = {
+ instrumentationLibrary: InstrumentationLibrary,
+ instrumentationLibrarySpans: ReadableSpan[],
+ librarySchemaUrl?: string,
+};
+
+function spanRecordsToResourceSpans(spans: ReadableSpan[]): ResourceSpans[] {
+ const resourceMap: Map> = new Map();
+
+ for (const record of spans) {
+ let ilmMap = resourceMap.get(record.resource);
+
+ if (!ilmMap) {
+ ilmMap = new Map();
+ resourceMap.set(record.resource, ilmMap);
+ }
+
+ // TODO this is duplicated in basic tracer. Consolidate on a common helper in core
+ const instrumentationLibraryKey = `${record.instrumentationLibrary.name}@${record.instrumentationLibrary.version || ''}:${record.instrumentationLibrary.schemaUrl || ''}`;
+ let records = ilmMap.get(instrumentationLibraryKey);
+
+ if (!records) {
+ records = [];
+ ilmMap.set(instrumentationLibraryKey, records);
+ }
+
+ records.push(record);
+ }
+
+ const out: ResourceSpans[] = [];
+
+ const entryIterator = resourceMap.entries();
+ let entry = entryIterator.next();
+ while (!entry.done) {
+ const [resource, ilmMap] = entry.value;
+ const resourceSpans: InstrumentationLibrarySpans[] = [];
+ const ilmIterator = ilmMap.values();
+ let ilmEntry = ilmIterator.next();
+ while (!ilmEntry.done) {
+ const instrumentationLibrarySpans = ilmEntry.value;
+ if (instrumentationLibrarySpans.length > 0) {
+ const { name, version, schemaUrl } = instrumentationLibrarySpans[0].instrumentationLibrary;
+ resourceSpans.push({ instrumentationLibrary: { name, version }, instrumentationLibrarySpans, librarySchemaUrl: schemaUrl });
+ }
+ ilmEntry = ilmIterator.next();
+ }
+ // TODO SDK types don't provide resource schema URL at this time
+ out.push({ resource, resourceSpans });
+ entry = entryIterator.next();
+ }
+
+ return out;
+}
diff --git a/experimental/packages/otlp-transformer/src/trace/internal.ts b/experimental/packages/otlp-transformer/src/trace/internal.ts
new file mode 100644
index 00000000000..02fe74af96b
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/trace/internal.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { Link } from '@opentelemetry/api';
+import { hrTimeToNanoseconds } from '@opentelemetry/core';
+import type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base';
+import { toAttributes } from '../common/internal';
+import { EStatusCode, IEvent, ILink, ISpan } from './types';
+
+export function sdkSpanToOtlpSpan(
+ span: ReadableSpan,
+): ISpan {
+ const ctx = span.spanContext();
+ const status = span.status;
+ return {
+ traceId: ctx.traceId,
+ spanId: ctx.spanId,
+ parentSpanId: span.parentSpanId,
+ 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),
+ attributes: toAttributes(span.attributes),
+ droppedAttributesCount: 0,
+ events: span.events.map(toOtlpSpanEvent),
+ droppedEventsCount: 0,
+ status: {
+ // API and proto enums share the same values
+ code: status.code as unknown as EStatusCode,
+ message: status.message,
+ },
+ links: span.links.map(toOtlpLink),
+ droppedLinksCount: 0,
+ };
+}
+
+export function toOtlpLink(link: Link): ILink {
+ return {
+ attributes: link.attributes ? toAttributes(link.attributes) : [],
+ spanId: link.context.spanId,
+ traceId: link.context.traceId,
+ droppedAttributesCount: 0,
+ };
+}
+
+export function toOtlpSpanEvent(
+ timedEvent: TimedEvent
+): IEvent {
+ return {
+ attributes: timedEvent.attributes ? toAttributes(timedEvent.attributes) : [],
+ name: timedEvent.name,
+ timeUnixNano: hrTimeToNanoseconds(timedEvent.time),
+ droppedAttributesCount: 0,
+ };
+}
diff --git a/experimental/packages/otlp-transformer/src/trace/types.ts b/experimental/packages/otlp-transformer/src/trace/types.ts
new file mode 100644
index 00000000000..e14437a55ed
--- /dev/null
+++ b/experimental/packages/otlp-transformer/src/trace/types.ts
@@ -0,0 +1,188 @@
+/*
+ * 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 { IInstrumentationLibrary, IKeyValue } from '../common/types';
+import { IResource } from '../resource/types';
+
+/** Properties of an ExportTraceServiceRequest. */
+export interface IExportTraceServiceRequest {
+
+ /** ExportTraceServiceRequest resourceSpans */
+ resourceSpans?: IResourceSpans[];
+}
+
+/** Properties of a ResourceSpans. */
+export interface IResourceSpans {
+
+ /** ResourceSpans resource */
+ resource?: IResource;
+
+ /** ResourceSpans instrumentationLibrarySpans */
+ instrumentationLibrarySpans: IInstrumentationLibrarySpans[];
+
+ /** ResourceSpans schemaUrl */
+ schemaUrl?: string;
+}
+
+/** Properties of an InstrumentationLibrarySpans. */
+export interface IInstrumentationLibrarySpans {
+
+ /** InstrumentationLibrarySpans instrumentationLibrary */
+ instrumentationLibrary?: IInstrumentationLibrary;
+
+ /** InstrumentationLibrarySpans spans */
+ spans?: ISpan[]
+
+ /** InstrumentationLibrarySpans schemaUrl */
+ schemaUrl?: (string | null);
+}
+
+
+/** Properties of a Span. */
+export interface ISpan {
+ /** Span traceId */
+ traceId: string;
+
+ /** Span spanId */
+ spanId: string;
+
+ /** Span traceState */
+ traceState?: (string | null);
+
+ /** Span parentSpanId */
+ parentSpanId?: string;
+
+ /** Span name */
+ name: string;
+
+ /** Span kind */
+ kind: ESpanKind;
+
+ /** Span startTimeUnixNano */
+ startTimeUnixNano: number;
+
+ /** Span endTimeUnixNano */
+ endTimeUnixNano: number;
+
+ /** Span attributes */
+ attributes: IKeyValue[];
+
+ /** Span droppedAttributesCount */
+ droppedAttributesCount: number
+
+ /** Span events */
+ events: IEvent[];
+
+ /** Span droppedEventsCount */
+ droppedEventsCount: number
+
+ /** Span links */
+ links: ILink[];
+
+ /** Span droppedLinksCount */
+ droppedLinksCount: number
+
+ /** Span status */
+ status: IStatus;
+}
+
+/**
+ * SpanKind is the type of span. Can be used to specify additional relationships between spans
+ * in addition to a parent/child relationship.
+ */
+export enum ESpanKind {
+ /** Unspecified. Do NOT use as default. Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. */
+ SPAN_KIND_UNSPECIFIED = 0,
+
+ /** Indicates that the span represents an internal operation within an application,
+ * as opposed to an operation happening at the boundaries. Default value.
+ */
+ SPAN_KIND_INTERNAL = 1,
+
+ /** Indicates that the span covers server-side handling of an RPC or other
+ * remote network request.
+ */
+ SPAN_KIND_SERVER = 2,
+
+ /** Indicates that the span describes a request to some remote service.
+ */
+ SPAN_KIND_CLIENT = 3,
+
+ /** Indicates that the span describes a producer sending a message to a broker.
+ * Unlike CLIENT and SERVER, there is often no direct critical path latency relationship
+ * between producer and consumer spans. A PRODUCER span ends when the message was accepted
+ * by the broker while the logical processing of the message might span a much longer time.
+ */
+ SPAN_KIND_PRODUCER = 4,
+
+ /** Indicates that the span describes consumer receiving a message from a broker.
+ * Like the PRODUCER kind, there is often no direct critical path latency relationship
+ * between producer and consumer spans.
+ */
+ SPAN_KIND_CONSUMER = 5,
+}
+
+/** Properties of a Status. */
+export interface IStatus {
+ /** Status message */
+ message?: string;
+
+ /** Status code */
+ code: EStatusCode;
+}
+
+/** StatusCode enum. */
+export const enum EStatusCode {
+ /** The default status. */
+ STATUS_CODE_UNSET = 0,
+ /** The Span has been evaluated by an Application developers or Operator to have completed successfully. */
+ STATUS_CODE_OK = 1,
+ /** The Span contains an error. */
+ STATUS_CODE_ERROR = 2,
+}
+
+/** Properties of an Event. */
+export interface IEvent {
+ /** Event timeUnixNano */
+ timeUnixNano: number;
+
+ /** Event name */
+ name: string;
+
+ /** Event attributes */
+ attributes: IKeyValue[];
+
+ /** Event droppedAttributesCount */
+ droppedAttributesCount: number;
+}
+
+/** Properties of a Link. */
+export interface ILink {
+ /** Link traceId */
+ traceId: string;
+
+ /** Link spanId */
+ spanId: string;
+
+ /** Link traceState */
+ traceState?: string;
+
+ /** Link attributes */
+ attributes: IKeyValue[];
+
+ /** Link droppedAttributesCount */
+ droppedAttributesCount: number;
+}
diff --git a/experimental/packages/otlp-transformer/test/common.test.ts b/experimental/packages/otlp-transformer/test/common.test.ts
new file mode 100644
index 00000000000..05902ca941f
--- /dev/null
+++ b/experimental/packages/otlp-transformer/test/common.test.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { toAnyValue } from '../src/common/internal';
+import * as assert from 'assert';
+
+describe('common', () => {
+ describe('toAnyValue', () => {
+ it('serializes an array', () => {
+ const anyValue = toAnyValue([1, 'two', false, 2.5, new Uint8Array([0, 1, 2]), { somekey: 'somevalue' }]);
+ assert.deepStrictEqual(anyValue, {
+ arrayValue: {
+ values: [
+ {
+ intValue: 1
+ },
+ {
+ stringValue: 'two'
+ },
+ {
+ boolValue: false
+ },
+ {
+ doubleValue: 2.5
+ },
+ {
+ bytesValue: new Uint8Array([0, 1, 2])
+ },
+ {
+ kvlistValue: {
+ values: [
+ {
+ key: 'somekey',
+ value: {
+ stringValue: 'somevalue'
+ }
+ }
+ ]
+ }
+ },
+ ]
+ }
+ });
+ });
+ });
+});
diff --git a/experimental/packages/otlp-transformer/test/index-webpack.ts b/experimental/packages/otlp-transformer/test/index-webpack.ts
new file mode 100644
index 00000000000..061a48ccfa7
--- /dev/null
+++ b/experimental/packages/otlp-transformer/test/index-webpack.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+const testsContext = require.context('.', true, /test$/);
+testsContext.keys().forEach(testsContext);
+
+const srcContext = require.context('.', true, /src$/);
+srcContext.keys().forEach(srcContext);
diff --git a/experimental/packages/otlp-transformer/test/metrics.test.ts b/experimental/packages/otlp-transformer/test/metrics.test.ts
new file mode 100644
index 00000000000..4156aeec1fd
--- /dev/null
+++ b/experimental/packages/otlp-transformer/test/metrics.test.ts
@@ -0,0 +1,398 @@
+/*
+ * 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 { AggregationTemporality, ValueType } from '@opentelemetry/api-metrics';
+import { Resource } from '@opentelemetry/resources';
+import {
+ HistogramAggregator,
+ LastValueAggregator,
+ MetricKind,
+ MetricRecord,
+ SumAggregator
+} from '@opentelemetry/sdk-metrics-base';
+import * as assert from 'assert';
+import { createExportMetricsServiceRequest } from '../src/metrics';
+import { EAggregationTemporality } from '../src/metrics/types';
+
+const START_TIME = 1640715235584374000;
+
+describe('Metrics', () => {
+ describe('createExportMetricsServiceRequest', () => {
+ let sumRecord: MetricRecord;
+ let sumAggregator: SumAggregator;
+ let observableSumRecord: MetricRecord;
+ let observableSumAggregator: SumAggregator;
+ let gaugeRecord: MetricRecord;
+ let gaugeAggregator: LastValueAggregator;
+ let histRecord: MetricRecord;
+ let histAggregator: HistogramAggregator;
+ let resource: Resource;
+
+ beforeEach(() => {
+ resource = new Resource({
+ 'resource-attribute': 'resource attribute value',
+ });
+ sumAggregator = new SumAggregator();
+ sumRecord = {
+ aggregationTemporality:
+ AggregationTemporality.AGGREGATION_TEMPORALITY_DELTA,
+ attributes: { 'string-attribute': 'some attribute value' },
+ descriptor: {
+ description: 'this is a description',
+ metricKind: MetricKind.COUNTER,
+ name: 'counter',
+ unit: '1',
+ valueType: ValueType.INT,
+ },
+ aggregator: sumAggregator,
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ schemaUrl: 'http://url.to.schema'
+ },
+ resource,
+ };
+ observableSumAggregator = new SumAggregator();
+ observableSumRecord = {
+ aggregationTemporality:
+ AggregationTemporality.AGGREGATION_TEMPORALITY_DELTA,
+ attributes: { 'string-attribute': 'some attribute value' },
+ descriptor: {
+ description: 'this is a description',
+ metricKind: MetricKind.OBSERVABLE_COUNTER,
+ name: 'counter',
+ unit: '1',
+ valueType: ValueType.INT,
+ },
+ aggregator: observableSumAggregator,
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ schemaUrl: 'http://url.to.schema'
+ },
+ resource,
+ };
+ gaugeAggregator = new LastValueAggregator();
+ gaugeRecord = {
+ aggregationTemporality:
+ AggregationTemporality.AGGREGATION_TEMPORALITY_UNSPECIFIED,
+ attributes: { 'string-attribute': 'some attribute value' },
+ descriptor: {
+ description: 'this is a description',
+ metricKind: MetricKind.OBSERVABLE_GAUGE,
+ name: 'gauge',
+ unit: '1',
+ valueType: ValueType.DOUBLE,
+ },
+ aggregator: gaugeAggregator,
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ schemaUrl: 'http://url.to.schema'
+ },
+ resource,
+ };
+ histAggregator = new HistogramAggregator([5]);
+ histRecord = {
+ aggregationTemporality:
+ AggregationTemporality.AGGREGATION_TEMPORALITY_UNSPECIFIED,
+ attributes: { 'string-attribute': 'some attribute value' },
+ descriptor: {
+ description: 'this is a description',
+ metricKind: MetricKind.HISTOGRAM,
+ name: 'hist',
+ unit: '1',
+ valueType: ValueType.INT,
+ },
+ aggregator: histAggregator,
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ schemaUrl: 'http://url.to.schema'
+ },
+ resource,
+ };
+ });
+
+ it('returns null on an empty list', () => {
+ assert.strictEqual(createExportMetricsServiceRequest([], 0), null);
+ });
+
+ it('serializes a sum metric record', () => {
+ sumAggregator.update(10);
+ // spoof the update time
+ sumAggregator['_lastUpdateTime'] = [1640715557, 342725388];
+ const exportRequest = createExportMetricsServiceRequest(
+ [sumRecord],
+ START_TIME
+ );
+ assert.ok(exportRequest);
+
+ assert.deepStrictEqual(exportRequest, {
+ resourceMetrics: [
+ {
+ resource: {
+ attributes: [
+ {
+ key: 'resource-attribute',
+ value: {
+ stringValue: 'resource attribute value',
+ },
+ },
+ ],
+ droppedAttributesCount: 0,
+ },
+ schemaUrl: undefined,
+ instrumentationLibraryMetrics: [
+ {
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ },
+ schemaUrl: 'http://url.to.schema',
+ metrics: [
+ {
+ name: 'counter',
+ description: 'this is a description',
+ unit: '1',
+ sum: {
+ dataPoints: [
+ {
+ attributes: [
+ {
+ key: 'string-attribute',
+ value: {
+ stringValue: 'some attribute value',
+ },
+ },
+ ],
+ startTimeUnixNano: START_TIME,
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ timeUnixNano: 1640715557342725388,
+ asInt: 10,
+ },
+ ],
+ aggregationTemporality: EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA,
+ isMonotonic: true,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ it('serializes an observable sum metric record', () => {
+ observableSumAggregator.update(10);
+ // spoof the update time
+ observableSumAggregator['_lastUpdateTime'] = [1640715557, 342725388];
+ const exportRequest = createExportMetricsServiceRequest(
+ [observableSumRecord],
+ START_TIME
+ );
+ assert.ok(exportRequest);
+
+ assert.deepStrictEqual(exportRequest, {
+ resourceMetrics: [
+ {
+ resource: {
+ attributes: [
+ {
+ key: 'resource-attribute',
+ value: {
+ stringValue: 'resource attribute value',
+ },
+ },
+ ],
+ droppedAttributesCount: 0,
+ },
+ schemaUrl: undefined,
+ instrumentationLibraryMetrics: [
+ {
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ },
+ schemaUrl: 'http://url.to.schema',
+ metrics: [
+ {
+ name: 'counter',
+ description: 'this is a description',
+ unit: '1',
+ sum: {
+ dataPoints: [
+ {
+ attributes: [
+ {
+ key: 'string-attribute',
+ value: {
+ stringValue: 'some attribute value',
+ },
+ },
+ ],
+ startTimeUnixNano: START_TIME,
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ timeUnixNano: 1640715557342725388,
+ asInt: 10,
+ },
+ ],
+ aggregationTemporality: EAggregationTemporality.AGGREGATION_TEMPORALITY_DELTA,
+ isMonotonic: true,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ it('serializes a gauge metric record', () => {
+ gaugeAggregator.update(10.5);
+ // spoof the update time
+ gaugeAggregator['_lastUpdateTime'] = [1640715557, 342725388];
+ const exportRequest = createExportMetricsServiceRequest(
+ [gaugeRecord],
+ START_TIME
+ );
+ assert.ok(exportRequest);
+
+ assert.deepStrictEqual(exportRequest, {
+ resourceMetrics: [
+ {
+ resource: {
+ attributes: [
+ {
+ key: 'resource-attribute',
+ value: {
+ stringValue: 'resource attribute value',
+ },
+ },
+ ],
+ droppedAttributesCount: 0,
+ },
+ schemaUrl: undefined,
+ instrumentationLibraryMetrics: [
+ {
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ },
+ schemaUrl: 'http://url.to.schema',
+ metrics: [
+ {
+ name: 'gauge',
+ description: 'this is a description',
+ unit: '1',
+ gauge: {
+ dataPoints: [
+ {
+ attributes: [
+ {
+ key: 'string-attribute',
+ value: {
+ stringValue: 'some attribute value',
+ },
+ },
+ ],
+ startTimeUnixNano: START_TIME,
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ timeUnixNano: 1640715557342725388,
+ asDouble: 10.5,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ it('serializes a histogram metric record', () => {
+ histAggregator.update(2);
+ histAggregator.update(7);
+ // spoof the update time
+ histAggregator['_lastUpdateTime'] = [1640715557, 342725388];
+ const exportRequest = createExportMetricsServiceRequest(
+ [histRecord],
+ START_TIME
+ );
+ assert.ok(exportRequest);
+
+ assert.deepStrictEqual(exportRequest, {
+ resourceMetrics: [
+ {
+ resource: {
+ attributes: [
+ {
+ key: 'resource-attribute',
+ value: {
+ stringValue: 'resource attribute value',
+ },
+ },
+ ],
+ droppedAttributesCount: 0,
+ },
+ schemaUrl: undefined,
+ instrumentationLibraryMetrics: [
+ {
+ instrumentationLibrary: {
+ name: 'mylib',
+ version: '0.1.0',
+ },
+ schemaUrl: 'http://url.to.schema',
+ metrics: [
+ {
+ name: 'hist',
+ description: 'this is a description',
+ unit: '1',
+ histogram: {
+ aggregationTemporality: EAggregationTemporality.AGGREGATION_TEMPORALITY_UNSPECIFIED,
+ dataPoints: [
+ {
+ attributes: [
+ {
+ key: 'string-attribute',
+ value: {
+ stringValue: 'some attribute value',
+ },
+ },
+ ],
+ bucketCounts: [1, 1],
+ count: 2,
+ explicitBounds: [5],
+ sum: 9,
+ startTimeUnixNano: START_TIME,
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ timeUnixNano: 1640715557342725388,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/experimental/packages/otlp-transformer/test/trace.test.ts b/experimental/packages/otlp-transformer/test/trace.test.ts
new file mode 100644
index 00000000000..3bd7be6b4c7
--- /dev/null
+++ b/experimental/packages/otlp-transformer/test/trace.test.ts
@@ -0,0 +1,239 @@
+/*
+ * 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 { SpanKind, SpanStatusCode } from '@opentelemetry/api';
+import { TraceState } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
+import * as assert from 'assert';
+import { createExportTraceServiceRequest, ESpanKind, EStatusCode } from '../src';
+
+describe('Trace', () => {
+ describe('createExportTraceServiceRequest', () => {
+ let resource: Resource;
+ let span: ReadableSpan;
+ let expectedSpanJson: any;
+
+ beforeEach(() => {
+ resource = new Resource({
+ 'resource-attribute': 'resource attribute value',
+ });
+ span = {
+ spanContext: () => ({
+ spanId: '0000000000000002',
+ traceFlags: 1,
+ traceId: '00000000000000000000000000000001',
+ isRemote: false,
+ traceState: new TraceState(''),
+ }),
+ parentSpanId: '0000000000000001',
+ attributes: { 'string-attribute': 'some attribute value' },
+ duration: [1, 300000000],
+ endTime: [1640715558, 642725388],
+ ended: true,
+ events: [
+ {
+ name: 'some event',
+ time: [1640715558, 542725388],
+ attributes: {
+ 'event-attribute': 'some string value'
+ }
+ }
+ ],
+ instrumentationLibrary: {
+ name: 'myLib',
+ version: '0.1.0',
+ schemaUrl: 'http://url.to.schema',
+ },
+ kind: SpanKind.CLIENT,
+ links: [
+ {
+ context: {
+ spanId: '0000000000000003',
+ traceId: '00000000000000000000000000000002',
+ traceFlags: 1,
+ isRemote: false,
+ traceState: new TraceState('')
+ },
+ attributes: {
+ 'link-attribute': 'string value'
+ }
+ }
+ ],
+ name: 'span-name',
+ resource,
+ startTime: [1640715557, 342725388],
+ status: {
+ code: SpanStatusCode.OK,
+ },
+ };
+
+ expectedSpanJson = {
+ resourceSpans: [
+ {
+ resource: {
+ attributes: [
+ {
+ key: 'resource-attribute',
+ value: { stringValue: 'resource attribute value' },
+ },
+ ],
+ droppedAttributesCount: 0,
+ },
+ schemaUrl: undefined,
+ instrumentationLibrarySpans: [
+ {
+ instrumentationLibrary: { name: 'myLib', version: '0.1.0' },
+ spans: [
+ {
+ traceId: '00000000000000000000000000000001',
+ spanId: '0000000000000002',
+ parentSpanId: '0000000000000001',
+ name: 'span-name',
+ kind: ESpanKind.SPAN_KIND_CLIENT,
+ links: [
+ {
+ droppedAttributesCount: 0,
+ spanId: '0000000000000003',
+ traceId: '00000000000000000000000000000002',
+ attributes: [
+ {
+ key: 'link-attribute',
+ value: {
+ stringValue: 'string value'
+ }
+ }
+ ]
+ }
+ ],
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ startTimeUnixNano: 1640715557342725388,
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
+ endTimeUnixNano: 1640715558642725388,
+ events: [
+ {
+ droppedAttributesCount: 0,
+ attributes: [
+ {
+ key: 'event-attribute',
+ value: {
+ stringValue: 'some string value'
+ }
+ }
+ ],
+ name: 'some event',
+ timeUnixNano: 1640715558542725400
+ }
+ ],
+ attributes: [
+ {
+ key: 'string-attribute',
+ value: { stringValue: 'some attribute value' },
+ },
+ ],
+ droppedAttributesCount: 0,
+ droppedEventsCount: 0,
+ droppedLinksCount: 0,
+ status: {
+ code: EStatusCode.STATUS_CODE_OK,
+ message: undefined,
+ },
+ },
+ ],
+ schemaUrl: 'http://url.to.schema',
+ },
+ ],
+ },
+ ],
+ };
+ });
+
+ it('returns null on an empty list', () => {
+ assert.deepStrictEqual(createExportTraceServiceRequest([]), { resourceSpans: [] });
+ });
+
+ it('serializes a span', () => {
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.deepStrictEqual(exportRequest, expectedSpanJson);
+ });
+
+ it('serializes a span', () => {
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.deepStrictEqual(exportRequest, expectedSpanJson);
+ });
+
+ it('serializes a span without a parent', () => {
+ (span as any).parentSpanId = undefined;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].parentSpanId, undefined);
+
+ });
+
+ describe('status code', () => {
+ it('error', () => {
+ span.status.code = SpanStatusCode.ERROR;
+ span.status.message = 'error message';
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ const spanStatus = exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].status;
+ assert.strictEqual(spanStatus?.code, EStatusCode.STATUS_CODE_ERROR);
+ assert.strictEqual(spanStatus?.message, 'error message');
+ });
+
+ it('unset', () => {
+ span.status.code = SpanStatusCode.UNSET;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].status.code, EStatusCode.STATUS_CODE_UNSET);
+ });
+ });
+
+ describe('span kind', () => {
+ it('consumer', () => {
+ (span as any).kind = SpanKind.CONSUMER;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_CONSUMER);
+ });
+ it('internal', () => {
+ (span as any).kind = SpanKind.INTERNAL;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_INTERNAL);
+ });
+ it('producer', () => {
+ (span as any).kind = SpanKind.PRODUCER;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_PRODUCER);
+ });
+ it('server', () => {
+ (span as any).kind = SpanKind.SERVER;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_SERVER);
+ });
+ it('unspecified', () => {
+ (span as any).kind = undefined;
+ const exportRequest = createExportTraceServiceRequest([span]);
+ assert.ok(exportRequest);
+ assert.strictEqual(exportRequest.resourceSpans?.[0].instrumentationLibrarySpans[0].spans?.[0].kind, ESpanKind.SPAN_KIND_UNSPECIFIED);
+ });
+ });
+ });
+});
diff --git a/experimental/packages/otlp-transformer/tsconfig.all.json b/experimental/packages/otlp-transformer/tsconfig.all.json
new file mode 100644
index 00000000000..06c54913342
--- /dev/null
+++ b/experimental/packages/otlp-transformer/tsconfig.all.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.json" },
+ { "path": "./tsconfig.esm.json" },
+ { "path": "./tsconfig.esnext.json" }
+ ]
+}
diff --git a/experimental/packages/otlp-transformer/tsconfig.esm.json b/experimental/packages/otlp-transformer/tsconfig.esm.json
new file mode 100644
index 00000000000..379f547a469
--- /dev/null
+++ b/experimental/packages/otlp-transformer/tsconfig.esm.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.base.esm.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "build/esm",
+ "tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo"
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
diff --git a/experimental/packages/otlp-transformer/tsconfig.esnext.json b/experimental/packages/otlp-transformer/tsconfig.esnext.json
new file mode 100644
index 00000000000..cb78dd6ff39
--- /dev/null
+++ b/experimental/packages/otlp-transformer/tsconfig.esnext.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.base.esnext.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "build/esnext",
+ "tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo"
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
diff --git a/experimental/packages/otlp-transformer/tsconfig.json b/experimental/packages/otlp-transformer/tsconfig.json
new file mode 100644
index 00000000000..ed9d0830bdd
--- /dev/null
+++ b/experimental/packages/otlp-transformer/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "build"
+ },
+ "include": [
+ "src/**/*.ts",
+ "test/**/*.ts"
+ ]
+}
diff --git a/experimental/tsconfig.json b/experimental/tsconfig.json
index 27e09c30569..54a089b0bb4 100644
--- a/experimental/tsconfig.json
+++ b/experimental/tsconfig.json
@@ -38,6 +38,9 @@
{
"path": "packages/opentelemetry-sdk-node"
},
+ {
+ "path": "packages/otlp-transformer"
+ },
{
"path": "backwards-compatability/node10"
},