Skip to content

Commit

Permalink
fix(monitoring-exporter): convert to GCM format based on metric data …
Browse files Browse the repository at this point in the history
…type, not instrument type
  • Loading branch information
aabmass committed Nov 30, 2022
1 parent 3b78d08 commit 277677b
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -811,3 +811,95 @@ exports['MetricExporter snapshot tests UpDownCounter - INT 1'] = [
]
}
]

exports['MetricExporter snapshot tests reconfigure with views counter with histogram view 1'] = [
{
"uri": "/v3/projects/otel-starter-project/metricDescriptors",
"body": {
"type": "custom.googleapis.com/opentelemetry/myrenamedhistogram",
"description": "instrument description",
"displayName": "OpenTelemetry/myrenamedhistogram",
"metricKind": "CUMULATIVE",
"valueType": "DOUBLE",
"unit": "{myunit}",
"labels": [
{
"key": "opentelemetry_task",
"description": "OpenTelemetry task identifier"
}
]
},
"userAgent": [
"opentelemetry-js/1.8.0 google-cloud-metric-exporter/0.14.0 google-api-nodejs-client/5.1.0 (gzip)"
]
},
{
"uri": "/v3/projects/otel-starter-project/timeSeries",
"body": {
"timeSeries": [
{
"metric": {
"type": "custom.googleapis.com/opentelemetry/myrenamedhistogram",
"labels": {
"opentelemetry_task": "opentelemetry_task"
}
},
"resource": {
"type": "global",
"labels": {
"project_id": "otel-starter-project"
}
},
"metricKind": "CUMULATIVE",
"valueType": "DOUBLE",
"points": [
{
"value": {
"distributionValue": {
"count": "1",
"mean": 12.3,
"bucketOptions": {
"explicitBuckets": {
"bounds": [
0,
5,
10,
25,
50,
75,
100,
250,
500,
1000
]
}
},
"bucketCounts": [
"0",
"0",
"0",
"1",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
]
}
},
"interval": {
"startTime": "startTime",
"endTime": "endTime"
}
}
]
}
]
},
"userAgent": [
"opentelemetry-js/1.8.0 google-cloud-metric-exporter/0.14.0 google-api-nodejs-client/5.1.0 (gzip)"
]
}
]
67 changes: 28 additions & 39 deletions packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import {
PushMetricExporter,
ResourceMetrics,
InstrumentDescriptor,
MetricData,
} from '@opentelemetry/sdk-metrics';
import {
ExportResult,
Expand Down Expand Up @@ -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<string, InstrumentDescriptor> =
new Map();
/**
* Set of OTel metric names that have already had their metric descriptors successfully
* created
*/
private createdMetricDescriptors: Set<string> = new Set();

private _monitoring: monitoring_v3.Monitoring;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -186,65 +186,54 @@ 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<boolean> {
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<boolean> {
const authClient = await this._authorize();
const descriptor = transformMetricDescriptor(
instrumentDescriptor,
metric,
this._metricPrefix,
this._displayNamePrefix
);

try {
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;
}
}

Expand Down
65 changes: 38 additions & 27 deletions packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import {
InstrumentDescriptor,
InstrumentType,
Histogram,
MetricData,
DataPoint,
Expand All @@ -39,21 +38,29 @@ 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 {
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,
Expand All @@ -74,30 +81,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;
}
}
Expand All @@ -106,13 +117,13 @@ function transformValueType(valueType: OTValueType): ValueType {
* Converts metric's timeseries to a TimeSeries, so that metric can be
* uploaded to GCM.
*/
export function createTimeSeries<TMetricData extends MetricData>(
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import {Attributes, ValueType} from '@opentelemetry/api';
import * as snapshot from 'snap-shot-it';
import {ExportResult, ExportResultCode} from '@opentelemetry/core';
import {ResourceMetrics} from '@opentelemetry/sdk-metrics';
import {Aggregation, ResourceMetrics, View} from '@opentelemetry/sdk-metrics';
import * as assert from 'assert';
import * as nock from 'nock';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -230,6 +230,35 @@ describe('MetricExporter snapshot tests', () => {
gcmNock.snapshotCalls();
});
});

describe('reconfigure with views', () => {
it('counter with histogram view', async () => {
const resourceMetrics = await generateMetricsData(
(_, meter) => {
meter
.createCounter('mycounter', {
description: 'instrument description',
unit: '{myunit}',
valueType: ValueType.DOUBLE,
})
.add(12.3);
},
{
views: [
new View({
instrumentName: 'mycounter',
aggregation: Aggregation.Histogram(),
name: 'myrenamedhistogram',
}),
],
}
);

const result = await callExporter(resourceMetrics);
assert.deepStrictEqual(result, {code: ExportResultCode.SUCCESS});
gcmNock.snapshotCalls();
});
});
});

function callExporter(resourceMetrics: ResourceMetrics): Promise<ExportResult> {
Expand Down
Loading

0 comments on commit 277677b

Please sign in to comment.