Skip to content

Commit 9c3771d

Browse files
committed
chore: add batch unit test and update gauge double handling
1 parent 693cc8c commit 9c3771d

File tree

4 files changed

+71
-14
lines changed

4 files changed

+71
-14
lines changed

src/metrics/spanner-metrics-exporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {status} from '@grpc/grpc-js';
2121

2222
// Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per
2323
// CreateTimeSeries call.
24-
const MAX_BATCH_EXPORT_SIZE = 200;
24+
export const MAX_BATCH_EXPORT_SIZE = 200;
2525

2626
/**
2727
* Format and sends metrics information to Google Cloud Monitoring.

src/metrics/transform.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ function _transformValueType(metric: MetricData): ValueType {
7171
dataPointType === DataPointType.EXPONENTIAL_HISTOGRAM
7272
) {
7373
return ValueType.DISTRIBUTION;
74-
} else if (name.includes('count')) {
74+
} else if (dataPointType === DataPointType.SUM) {
7575
return ValueType.INT64;
76+
} else if (dataPointType === DataPointType.GAUGE) {
77+
return ValueType.DOUBLE;
7678
}
7779
console.warn('Encountered unexpected metric %s', name);
7880
return ValueType.VALUE_TYPE_UNSPECIFIED;
@@ -211,9 +213,12 @@ function _normalizeLabelKey(key: string): string {
211213
/** Transforms a OpenTelemetry Point's value to a GCM Point value. */
212214
function _transformNumberValue(valueType: ValueType, value: number) {
213215
if (valueType === ValueType.INT64) {
214-
return {int64Value: value.toString()};
216+
return {int64Value: Math.round(value).toString()};
215217
} else if (valueType === ValueType.DOUBLE) {
216-
return {doubleValue: value};
218+
const doubleString = Number.isInteger(value)
219+
? `${value}.0`
220+
: value.toString();
221+
return {doubleValue: doubleString};
217222
}
218223
throw Error(`unsupported value type: ${valueType}`);
219224
}
@@ -275,6 +280,7 @@ function _transformExponentialHistogramValue(value: ExponentialHistogram) {
275280
};
276281
}
277282

283+
/** Transforms an OpenTelemetry time value to a GCM time value. */
278284
function _formatHrTimeToGcmTime(hrTime) {
279285
return {
280286
seconds: hrTime[0],

test/metrics/spanner-metrics-exporter.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import * as assert from 'assert';
1616
import * as sinon from 'sinon';
1717
import {MeterProvider, MetricReader} from '@opentelemetry/sdk-metrics';
1818
import {GoogleAuth} from 'google-auth-library';
19-
import {CloudMonitoringMetricsExporter} from '../../src/metrics/spanner-metrics-exporter';
19+
import {
20+
CloudMonitoringMetricsExporter,
21+
MAX_BATCH_EXPORT_SIZE,
22+
} from '../../src/metrics/spanner-metrics-exporter';
2023
import {
2124
SPANNER_METER_NAME,
2225
METRIC_NAME_ATTEMPT_COUNT,
@@ -86,7 +89,7 @@ describe('Export', () => {
8689
let gfe_connectivity_error_count: Counter;
8790
let attempt_latency: Histogram;
8891
let operation_latency: Histogram;
89-
let gfe_latencies: Histogram;
92+
let gfe_latency: Histogram;
9093
let metricAttributes: {[key: string]: string};
9194
let exporter: CloudMonitoringMetricsExporter;
9295

@@ -139,7 +142,7 @@ describe('Export', () => {
139142
unit: 'ms',
140143
});
141144

142-
gfe_latencies = meter.createHistogram(METRIC_NAME_GFE_LATENCIES, {
145+
gfe_latency = meter.createHistogram(METRIC_NAME_GFE_LATENCIES, {
143146
description: 'Test GFE latencies in ms',
144147
unit: 'ms',
145148
});
@@ -151,7 +154,7 @@ describe('Export', () => {
151154
gfe_connectivity_error_count.add(12, metricAttributes);
152155
attempt_latency.record(30, metricAttributes);
153156
operation_latency.record(45, metricAttributes);
154-
gfe_latencies.record(22, metricAttributes);
157+
gfe_latency.record(22, metricAttributes);
155158

156159
const {errors, resourceMetrics} = await reader.collect();
157160
if (errors.length !== 0) {
@@ -224,4 +227,44 @@ describe('Export', () => {
224227

225228
assert(sendTimeSeriesStub.calledOnce);
226229
});
230+
231+
it('should batch exports into multiple calls', async () => {
232+
// Create metircs larger than the batch size
233+
const numberOfDistinctMetrics = MAX_BATCH_EXPORT_SIZE * 2 + 1;
234+
for (let i = 0; i < numberOfDistinctMetrics; i++) {
235+
attempt_counter.add(1, {...metricAttributes, testId: `batch-test-${i}`});
236+
}
237+
238+
const {resourceMetrics} = await reader.collect();
239+
240+
const sendTimeSeriesStub = sinon
241+
.stub(exporter as any, '_sendTimeSeries')
242+
.resolves();
243+
const resultCallbackSpy = sinon.spy();
244+
245+
exporter.export(resourceMetrics, resultCallbackSpy);
246+
247+
await new Promise(resolve => setImmediate(resolve));
248+
249+
// Confirm number of metrics for each batch
250+
const expectedNumberOfCalls = Math.ceil(
251+
numberOfDistinctMetrics / MAX_BATCH_EXPORT_SIZE,
252+
);
253+
assert.strictEqual(sendTimeSeriesStub.callCount, expectedNumberOfCalls);
254+
assert.strictEqual(
255+
sendTimeSeriesStub.getCall(0).args[0].length,
256+
MAX_BATCH_EXPORT_SIZE,
257+
);
258+
assert.strictEqual(
259+
sendTimeSeriesStub.getCall(1).args[0].length,
260+
MAX_BATCH_EXPORT_SIZE,
261+
);
262+
assert.strictEqual(
263+
sendTimeSeriesStub.getCall(2).args[0].length,
264+
numberOfDistinctMetrics % MAX_BATCH_EXPORT_SIZE,
265+
);
266+
267+
const callbackResult = resultCallbackSpy.getCall(0).args[0];
268+
assert.strictEqual(callbackResult.code, ExportResultCode.SUCCESS);
269+
});
227270
});

test/metrics/transform.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('transform', () => {
6161
let metricExponentialHistogram: ExponentialHistogramMetricData;
6262
let metricUnknown;
6363
let sumDataPoint: DataPoint<number>;
64+
let gaugeDataPoint: DataPoint<number>;
6465
let histogramDataPoint: DataPoint<Histogram>;
6566
let exponentialHistogramDataPoint: DataPoint<ExponentialHistogram>;
6667

@@ -93,14 +94,14 @@ describe('transform', () => {
9394
aggregationTemporality: AggregationTemporality.DELTA,
9495
isMonotonic: true,
9596
dataPointType: DataPointType.SUM,
96-
descriptor: {valueType: OTValueType.DOUBLE, name: 'some_count'} as any,
97+
descriptor: {valueType: OTValueType.INT, name: 'some_count'} as any,
9798
};
9899

99100
metricGauge = {
100101
dataPoints: [],
101102
aggregationTemporality: '' as any,
102103
dataPointType: DataPointType.GAUGE,
103-
descriptor: {valueType: OTValueType.INT, name: 'a_count'} as any,
104+
descriptor: {valueType: OTValueType.DOUBLE, name: 'a_count'} as any,
104105
};
105106

106107
metricHistogram = {
@@ -131,6 +132,13 @@ describe('transform', () => {
131132
endTime: process.hrtime(),
132133
};
133134

135+
gaugeDataPoint = {
136+
attributes,
137+
value: 0.0,
138+
startTime: process.hrtime(),
139+
endTime: process.hrtime(),
140+
};
141+
134142
histogramDataPoint = {
135143
attributes,
136144
startTime: process.hrtime(),
@@ -244,7 +252,7 @@ describe('transform', () => {
244252
it('should transform otel value types to GCM value types', () => {
245253
assert.strictEqual(_transformValueType(metricSum), ValueType.INT64);
246254

247-
assert.strictEqual(_transformValueType(metricGauge), ValueType.INT64);
255+
assert.strictEqual(_transformValueType(metricGauge), ValueType.DOUBLE);
248256

249257
assert.strictEqual(
250258
_transformValueType(metricHistogram),
@@ -279,15 +287,15 @@ describe('transform', () => {
279287

280288
const gaugeExpectation = {
281289
value: {
282-
int64Value: '0',
290+
doubleValue: '0.0',
283291
},
284292
interval: {
285-
endTime: _formatHrTimeToGcmTime(sumDataPoint.endTime),
293+
endTime: _formatHrTimeToGcmTime(gaugeDataPoint.endTime),
286294
},
287295
};
288296

289297
assert.deepStrictEqual(
290-
_transformPoint(metricGauge, sumDataPoint),
298+
_transformPoint(metricGauge, gaugeDataPoint),
291299
gaugeExpectation,
292300
);
293301

0 commit comments

Comments
 (0)