From b28d706323964138008e6e1e7bd4fe06da458ce0 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Tue, 29 Nov 2022 22:16:13 +0000 Subject: [PATCH] fix(monitoring-exporter): convert to GCM format based on metric data type, not instrument type --- .../src/monitoring.ts | 76 +++++++++---------- .../src/transform.ts | 72 +++++++++++------- 2 files changed, 80 insertions(+), 68 deletions(-) diff --git a/packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts b/packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts index 3b8d81c9d..ce5050d11 100644 --- a/packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts +++ b/packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts @@ -15,7 +15,7 @@ import { PushMetricExporter, ResourceMetrics, - InstrumentDescriptor, + MetricData, } from '@opentelemetry/sdk-metrics'; import { ExportResult, @@ -63,14 +63,16 @@ export class MetricExporter implements PushMetricExporter { private readonly _metricPrefix: string; private readonly _displayNamePrefix: string; private readonly _auth: GoogleAuth; - private readonly _startTime = new Date().toISOString(); static readonly DEFAULT_DISPLAY_NAME_PREFIX: string = 'OpenTelemetry'; static readonly CUSTOM_OPENTELEMETRY_DOMAIN: string = 'custom.googleapis.com/opentelemetry'; - private registeredInstrumentDescriptors: Map = - new Map(); + /** + * Set of OTel metric names that have already had their metric descriptors successfully + * created + */ + private createdMetricDescriptors: Set = new Set(); private _monitoring: monitoring_v3.Monitoring; @@ -150,9 +152,7 @@ export class MetricExporter implements PushMetricExporter { const timeSeries: TimeSeries[] = []; for (const scopeMetric of resourceMetrics.scopeMetrics) { for (const metric of scopeMetric.metrics) { - const isRegistered = await this._registerMetricDescriptor( - metric.descriptor - ); + const isRegistered = await this._registerMetricDescriptor(metric); if (isRegistered) { timeSeries.push( ...createTimeSeries(metric, resource, this._metricPrefix) @@ -186,65 +186,61 @@ export class MetricExporter implements PushMetricExporter { /** * Returns true if the given metricDescriptor is successfully registered to * Google Cloud Monitoring, or the exact same metric has already been - * registered. Returns false otherwise. - * @param instrumentDescriptor The OpenTelemetry MetricDescriptor. + * registered. Returns false otherwise and should be skipped. + * + * @param metric The OpenTelemetry MetricData. */ private async _registerMetricDescriptor( - instrumentDescriptor: InstrumentDescriptor - ) { - const existingInstrumentDescriptor = - this.registeredInstrumentDescriptors.get(instrumentDescriptor.name); + metric: MetricData + ): Promise { + const isDescriptorCreated = this.createdMetricDescriptors.has( + metric.descriptor.name + ); - if (existingInstrumentDescriptor) { - if (existingInstrumentDescriptor === instrumentDescriptor) { - // Ignore descriptors that are already registered. - return true; - } else { - diag.warn( - 'A different metric with the same name is already registered: %s', - existingInstrumentDescriptor - ); - return false; - } + if (isDescriptorCreated) { + return true; } - try { - await this._createMetricDescriptor(instrumentDescriptor); - this.registeredInstrumentDescriptors.set( - instrumentDescriptor.name, - instrumentDescriptor - ); + const res = await this._createMetricDescriptor(metric); + if (res) { + this.createdMetricDescriptors.add(metric.descriptor.name); return true; - } catch (e) { - const err = asError(e); - diag.error('Error creating metric descriptor: %s', err.message); - return false; } + return false; } /** * Calls CreateMetricDescriptor in the GCM API for the given InstrumentDescriptor - * @param instrumentDescriptor The OpenTelemetry InstrumentDescriptor. + * @param metric The OpenTelemetry MetricData. + * @returns whether or not the descriptor was successfully created */ - private async _createMetricDescriptor( - instrumentDescriptor: InstrumentDescriptor - ) { + private async _createMetricDescriptor(metric: MetricData): Promise { const authClient = await this._authorize(); const descriptor = transformMetricDescriptor( - instrumentDescriptor, + metric, this._metricPrefix, this._displayNamePrefix ); + if (!descriptor) { + diag.info( + 'MetricData with name "%s" contained no points, skipping.', + metric.descriptor.name + ); + return false; + } + try { - await this._monitoring.projects.metricDescriptors.create({ + const res = await this._monitoring.projects.metricDescriptors.create({ name: `projects/${this._projectId}`, requestBody: descriptor, auth: authClient, }); diag.debug('sent metric descriptor', descriptor); + return true; } catch (e) { const err = asError(e); diag.error('Failed to create metric descriptor: %s', err.message); + return false; } } diff --git a/packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts b/packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts index b01c77224..82bbda42a 100644 --- a/packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts +++ b/packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts @@ -14,7 +14,6 @@ import { InstrumentDescriptor, - InstrumentType, Histogram, MetricData, DataPoint, @@ -39,21 +38,34 @@ const OPENTELEMETRY_TASK = 'opentelemetry_task'; const OPENTELEMETRY_TASK_DESCRIPTION = 'OpenTelemetry task identifier'; export const OPENTELEMETRY_TASK_VALUE_DEFAULT = generateDefaultTaskValue(); +/** + * + * @param metric the MetricData to create a descriptor for + * @param metricPrefix prefix to add to metric names + * @param displayNamePrefix prefix to add to display name in the descriptor + * @returns the GCM MetricDescriptor or null if the MetricData was empty + */ export function transformMetricDescriptor( - instrumentDescriptor: InstrumentDescriptor, + metric: MetricData, metricPrefix: string, displayNamePrefix: string -): MetricDescriptor { +): MetricDescriptor | null { + // If no data points + if (!metric.dataPoints[0]) { + return null; + } + + const { + descriptor: {name, description, unit}, + } = metric; + return { - type: transformMetricType(metricPrefix, instrumentDescriptor.name), - description: instrumentDescriptor.description, - displayName: transformDisplayName( - displayNamePrefix, - instrumentDescriptor.name - ), - metricKind: transformMetricKind(instrumentDescriptor.type), - valueType: transformValueType(instrumentDescriptor.valueType), - unit: instrumentDescriptor.unit, + type: transformMetricType(metricPrefix, name), + description, + displayName: transformDisplayName(displayNamePrefix, name), + metricKind: transformMetricKind(metric), + valueType: transformValueType(metric), + unit, labels: [ { key: OPENTELEMETRY_TASK, @@ -74,30 +86,34 @@ function transformDisplayName(displayNamePrefix: string, name: string): string { } /** Transforms a OpenTelemetry instrument type to a GCM MetricKind. */ -function transformMetricKind(instrumentType: InstrumentType): MetricKind { - switch (instrumentType) { - case InstrumentType.COUNTER: - case InstrumentType.OBSERVABLE_COUNTER: - case InstrumentType.HISTOGRAM: - return MetricKind.CUMULATIVE; - case InstrumentType.UP_DOWN_COUNTER: - case InstrumentType.OBSERVABLE_GAUGE: - case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER: +function transformMetricKind(metric: MetricData): MetricKind { + switch (metric.dataPointType) { + case DataPointType.SUM: + return metric.isMonotonic ? MetricKind.CUMULATIVE : MetricKind.GAUGE; + case DataPointType.GAUGE: return MetricKind.GAUGE; + case DataPointType.HISTOGRAM: + return MetricKind.CUMULATIVE; default: - exhaust(instrumentType); - diag.info('Encountered unexpected instrumentType=%s', instrumentType); + exhaust(metric); + diag.info( + 'Encountered unexpected data point type %s', + (metric as MetricData).dataPointType + ); return MetricKind.UNSPECIFIED; } } /** Transforms a OpenTelemetry ValueType to a GCM ValueType. */ -function transformValueType(valueType: OTValueType): ValueType { +function transformValueType(metric: MetricData): ValueType { + const {valueType} = metric.descriptor; if (valueType === OTValueType.DOUBLE) { return ValueType.DOUBLE; } else if (valueType === OTValueType.INT) { return ValueType.INT64; } else { + exhaust(valueType); + diag.info('Encountered unexpected value type %s', valueType); return ValueType.VALUE_TYPE_UNSPECIFIED; } } @@ -106,13 +122,13 @@ function transformValueType(valueType: OTValueType): ValueType { * Converts metric's timeseries to a TimeSeries, so that metric can be * uploaded to GCM. */ -export function createTimeSeries( - metric: TMetricData, +export function createTimeSeries( + metric: MetricData, resource: MonitoredResource, metricPrefix: string ): TimeSeries[] { - const metricKind = transformMetricKind(metric.descriptor.type); - const valueType = transformValueType(metric.descriptor.valueType); + const metricKind = transformMetricKind(metric); + const valueType = transformValueType(metric); return transformPoints(metric, metricPrefix).map(({point, metric}) => ({ metric,