Skip to content

Commit

Permalink
feat(opencensus-shim): implement OpenCensus metric producer
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass committed Aug 16, 2023
1 parent 853a7b6 commit 15e00a9
Show file tree
Hide file tree
Showing 12 changed files with 751 additions and 5 deletions.
2 changes: 2 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ All notable changes to experimental packages in this project will be documented

### :rocket: (Enhancement)

* feat(opencensus-shim): implement OpenCensus metric producer [#4066](https://github.com/open-telemetry/opentelemetry-js/pull/4066)

### :bug: (Bug Fix)

### :books: (Refine Doc)
Expand Down
2 changes: 2 additions & 0 deletions experimental/packages/shim-opencensus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
},
"dependencies": {
"@opentelemetry/core": "1.15.2",
"@opentelemetry/resources": "1.15.2",
"@opentelemetry/sdk-metrics": "1.15.2",
"require-in-the-middle": "^7.1.1",
"semver": "^7.5.1"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as oc from '@opencensus/core';
import { Resource } from '@opentelemetry/resources';
import {
CollectionResult,
MetricData,
MetricProducer,
ScopeMetrics,
} from '@opentelemetry/sdk-metrics';
import { mapOcMetric } from './metric-transform';
import { VERSION } from './version';

const SCOPE = {
name: '@opentelemetry/shim-opencensus',
version: VERSION,
} as const;

/**
* A {@link MetricProducer} which collects metrics from OpenCensus. Provide an instance to your
* {@link MetricReader} when you create it to include all OpenCensus metrics in the collection
* result:
*
* @example
* ```
* const meterProvider = new MeterProvider();
* const reader = new PeriodicExportingMetricReader({
* metricProducers: [new OpenCensusMetricProducer()],
* exporter: exporter,
* });
* meterProvider.addMetricReader(reader);
* ```
*/
export class OpenCensusMetricProducer implements MetricProducer {
async collect(): Promise<CollectionResult> {
const metrics = await this._collectOpenCensus();
const scopeMetrics: ScopeMetrics[] =
metrics.length === 0
? []
: [
{
scope: SCOPE,
metrics,
},
];

return {
errors: [],
resourceMetrics: {
// Resource is ignored by the SDK, it just uses the SDK's resource
resource: Resource.EMPTY,
scopeMetrics,
},
};
}

private async _collectOpenCensus(): Promise<MetricData[]> {
const metrics: MetricData[] = [];

// The use of oc.Metrics.getMetricProducerManager() was adapted from
// https://github.com/census-instrumentation/opencensus-node/blob/d46c8891b15783803d724b717db9a8c22cb73d6a/packages/opencensus-exporter-stackdriver/src/stackdriver-monitoring.ts#L122
for (const metricProducer of oc.Metrics.getMetricProducerManager().getAllMetricProducer()) {
for (const metric of metricProducer.getMetrics()) {
const metricData = mapOcMetric(metric);
if (metricData !== null) {
metrics.push(metricData);
}
}
}

return metrics;
}
}
2 changes: 1 addition & 1 deletion experimental/packages/shim-opencensus/src/ShimSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as oc from '@opencensus/core';
import { ShimTracer } from './ShimTracer';
import { AttributeValue, Span, SpanStatusCode, diag } from '@opentelemetry/api';
import { mapMessageEvent, reverseMapSpanContext } from './transform';
import { mapMessageEvent, reverseMapSpanContext } from './trace-transform';

// Copied from
// https://github.com/census-instrumentation/opencensus-node/blob/v0.1.0/packages/opencensus-core/src/trace/model/span.ts#L61
Expand Down
2 changes: 1 addition & 1 deletion experimental/packages/shim-opencensus/src/ShimTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
Tracer,
} from '@opentelemetry/api';
import { DEFAULT_SPAN_NAME, ShimSpan } from './ShimSpan';
import { mapSpanContext, mapSpanKind } from './transform';
import { mapSpanContext, mapSpanKind } from './trace-transform';
import { shimPropagation } from './propagation';

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down
210 changes: 210 additions & 0 deletions experimental/packages/shim-opencensus/src/metric-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as oc from '@opencensus/core';
import { Attributes, HrTime, ValueType, diag } from '@opentelemetry/api';
import {
AggregationTemporality,
DataPoint,
DataPointType,
GaugeMetricData,
HistogramMetricData,
InstrumentType,
MetricData,
SumMetricData,
} from '@opentelemetry/sdk-metrics';
import { FlatMap } from '@opentelemetry/sdk-metrics/src/utils';

type BaseMetric = Omit<MetricData, 'dataPoints' | 'dataPointType'>;
interface MappedType {
type: InstrumentType;
valueType: ValueType;
dataPointType:
| DataPointType.GAUGE
| DataPointType.SUM
| DataPointType.HISTOGRAM;
}
const ZEROED_HRTIME: HrTime = [0, 0];

export function mapOcMetric(metric: oc.Metric): MetricData | null {
const { description, name, unit, type } = metric.descriptor;
const mappedType = mapOcMetricDescriptorType(type);
if (mappedType === null) {
return null;
}

const baseMetric: BaseMetric = {
aggregationTemporality: AggregationTemporality.CUMULATIVE,
descriptor: {
description,
name,
unit,
type: mappedType.type,
valueType: mappedType.valueType,
},
};

switch (mappedType.dataPointType) {
case DataPointType.GAUGE:
return gauge(metric, mappedType.dataPointType, baseMetric);
case DataPointType.SUM:
return sum(metric, mappedType.dataPointType, baseMetric);
case DataPointType.HISTOGRAM:
return histogram(metric, mappedType.dataPointType, baseMetric);
}
}

function mapOcMetricDescriptorType(
type: oc.MetricDescriptorType
): MappedType | null {
switch (type) {
case oc.MetricDescriptorType.GAUGE_INT64:
return {
type: InstrumentType.OBSERVABLE_GAUGE,
valueType: ValueType.INT,
dataPointType: DataPointType.GAUGE,
};
case oc.MetricDescriptorType.GAUGE_DOUBLE:
return {
type: InstrumentType.OBSERVABLE_GAUGE,
valueType: ValueType.DOUBLE,
dataPointType: DataPointType.GAUGE,
};

case oc.MetricDescriptorType.CUMULATIVE_INT64:
return {
type: InstrumentType.COUNTER,
valueType: ValueType.INT,
dataPointType: DataPointType.SUM,
};
case oc.MetricDescriptorType.CUMULATIVE_DOUBLE:
return {
type: InstrumentType.COUNTER,
valueType: ValueType.DOUBLE,
dataPointType: DataPointType.SUM,
};

case oc.MetricDescriptorType.CUMULATIVE_DISTRIBUTION:
return {
type: InstrumentType.HISTOGRAM,
valueType: ValueType.DOUBLE,
dataPointType: DataPointType.HISTOGRAM,
};

case oc.MetricDescriptorType.SUMMARY:
case oc.MetricDescriptorType.GAUGE_DISTRIBUTION:
case oc.MetricDescriptorType.UNSPECIFIED:
diag.warn(
'Got unsupported metric MetricDescriptorType from OpenCensus: %s',
type
);
return null;
}
}

function gauge(
metric: oc.Metric,
dataPointType: DataPointType.GAUGE,
baseMetric: BaseMetric
): GaugeMetricData {
return {
...baseMetric,
dataPoints: dataPoints(metric, value => value as number),
dataPointType,
};
}

function sum(
metric: oc.Metric,
dataPointType: DataPointType.SUM,
baseMetric: BaseMetric
): SumMetricData {
return {
...baseMetric,
dataPoints: dataPoints(metric, value => value as number),
isMonotonic: true,
dataPointType,
};
}

function histogram(
metric: oc.Metric,
dataPointType: DataPointType.HISTOGRAM,
baseMetric: BaseMetric
): HistogramMetricData {
return {
...baseMetric,
dataPoints: dataPoints(metric, value => {
const {
bucketOptions: {
explicit: { bounds },
},
buckets,
count,
sum: distSum,
} = value as oc.DistributionValue;

return {
buckets: {
boundaries: bounds,
counts: buckets.map(bucket => bucket.count),
},
count,
sum: distSum,
};
}),
dataPointType,
};
}

function dataPoints<T>(
metric: oc.Metric,
valueMapper: (value: oc.TimeSeriesPoint['value']) => T
): DataPoint<T>[] {
return FlatMap(metric.timeseries, ts => {
const attributes = zipOcLabels(metric.descriptor.labelKeys, ts.labelValues);

// use zeroed hrTime if it is undefined, which probably shouldn't happen
const startTime = ocTimestampToHrTime(ts.startTimestamp) ?? ZEROED_HRTIME;

return ts.points.map(
(point): DataPoint<T> => ({
startTime,
attributes,
value: valueMapper(point.value),
endTime: ocTimestampToHrTime(point.timestamp) ?? ZEROED_HRTIME,
})
);
});
}

function ocTimestampToHrTime(ts: oc.Timestamp | undefined): HrTime | null {
if (ts === undefined || ts.seconds === null) {
return null;
}
return [ts.seconds, ts.nanos ?? 0];
}

function zipOcLabels(
labelKeys: oc.LabelKey[],
labelValues: oc.LabelValue[]
): Attributes {
const attributes: Attributes = {};
for (let i = 0; i < labelKeys.length; i++) {
attributes[labelKeys[i].key] = labelValues[i].value ?? '';
}
return attributes;
}
2 changes: 1 addition & 1 deletion experimental/packages/shim-opencensus/src/propagation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
TextMapGetter,
TextMapSetter,
} from '@opentelemetry/api';
import { mapSpanContext, reverseMapSpanContext } from './transform';
import { mapSpanContext, reverseMapSpanContext } from './trace-transform';

class Getter implements TextMapGetter<void> {
constructor(private ocGetter: oc.HeaderGetter) {}
Expand Down
Loading

0 comments on commit 15e00a9

Please sign in to comment.