Skip to content

Commit 2134f00

Browse files
authored
fix: Stackdriver batch limit for TimeSeries (#88)
* Adding batch limit, unit test, and fixes to unit tests * Improving previous unit test * Fix lint * Fix lint * Preventing passed-in array mutation * Updating comment on helper function
1 parent ef3c4ad commit 2134f00

File tree

4 files changed

+107
-4
lines changed

4 files changed

+107
-4
lines changed

packages/opentelemetry-cloud-monitoring-exporter/src/monitoring.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import { GoogleAuth, JWT } from 'google-auth-library';
2525
import { google } from 'googleapis';
2626
import { transformMetricDescriptor, createTimeSeries } from './transform';
2727
import { TimeSeries } from './types';
28+
import { partitionList } from './utils';
29+
30+
// Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per
31+
// CreateTimeSeries call.
32+
const MAX_BATCH_EXPORT_SIZE = 200;
2833

2934
const OT_USER_AGENT = {
3035
product: 'opentelemetry-js',
@@ -111,7 +116,13 @@ export class MetricExporter implements IMetricExporter {
111116
);
112117
}
113118
}
114-
this._sendTimeSeries(timeSeries);
119+
120+
for (const batchedTimeSeries of partitionList(
121+
timeSeries,
122+
MAX_BATCH_EXPORT_SIZE
123+
)) {
124+
await this._sendTimeSeries(batchedTimeSeries);
125+
}
115126
cb(ExportResult.SUCCESS);
116127
}
117128

packages/opentelemetry-cloud-monitoring-exporter/src/transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ function transformValueType(valueType: OTValueType): ValueType {
9999
}
100100

101101
/**
102-
* Converts metric's timeseries to a list of TimeSeries, so that metric can be
102+
* Converts metric's timeseries to a TimeSeries, so that metric can be
103103
* uploaded to StackDriver.
104104
*/
105105
export function createTimeSeries(
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { TimeSeries } from './types';
16+
17+
/** Returns the minimum number of arrays of max size chunkSize, partitioned from the given array. */
18+
export function partitionList(list: TimeSeries[], chunkSize: number) {
19+
const listCopy = [...list];
20+
const results = [];
21+
while (listCopy.length) {
22+
results.push(listCopy.splice(0, chunkSize));
23+
}
24+
return results;
25+
}

packages/opentelemetry-cloud-monitoring-exporter/test/monitoring.test.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ describe('MetricExporter', () => {
5353
let exporter: MetricExporter;
5454
let logger: ConsoleLogger;
5555
/* tslint:disable no-any */
56-
let metricDescriptors: sinon.SinonSpy<[any, any], any>;
56+
let metricDescriptors: sinon.SinonSpy<[any, any, any], any>;
57+
/* tslint:disable no-any */
58+
let timeSeries: sinon.SinonSpy<[any, any, any], any>;
5759
let debug: sinon.SinonSpy;
5860
let info: sinon.SinonSpy;
5961
let warn: sinon.SinonSpy;
@@ -69,7 +71,11 @@ describe('MetricExporter', () => {
6971

7072
metricDescriptors = sinon.spy(
7173
/* tslint:disable no-any */
72-
(request: any, callback: (err: Error | null) => void): any => {
74+
(
75+
request: any,
76+
params: any,
77+
callback: (err: Error | null) => void
78+
): any => {
7379
callback(null);
7480
}
7581
);
@@ -81,6 +87,24 @@ describe('MetricExporter', () => {
8187
metricDescriptors as any
8288
);
8389

90+
timeSeries = sinon.spy(
91+
/* tslint:disable no-any */
92+
(
93+
request: any,
94+
params: any,
95+
callback: (err: Error | null) => void
96+
): any => {
97+
callback(null);
98+
}
99+
);
100+
101+
sinon.replace(
102+
MetricExporter['_monitoring'].projects.timeSeries,
103+
'create',
104+
/* tslint:disable no-any */
105+
timeSeries as any
106+
);
107+
84108
sinon.replace(exporter['_auth'], 'getClient', () => {
85109
if (getClientShouldFail) {
86110
throw new Error('fail');
@@ -137,6 +161,49 @@ describe('MetricExporter', () => {
137161
'custom.googleapis.com/opentelemetry/name'
138162
);
139163

164+
assert.equal(metricDescriptors.callCount, 1);
165+
assert.equal(timeSeries.callCount, 1);
166+
167+
assert.deepStrictEqual(result, ExportResult.SUCCESS);
168+
});
169+
170+
it('should enforce batch size limit on metrics', async () => {
171+
const meter = new MeterProvider().getMeter('test-meter');
172+
173+
const labels: Labels = { ['keyb']: 'value2', ['keya']: 'value1' };
174+
let nMetrics = 401;
175+
while (nMetrics > 0) {
176+
nMetrics -= 1;
177+
const counter = meter.createCounter(`name${nMetrics.toString()}`, {
178+
labelKeys: ['keya', 'keyb'],
179+
});
180+
counter.bind(labels).add(10);
181+
}
182+
meter.collect();
183+
const records = meter.getBatcher().checkPointSet();
184+
185+
const result = await new Promise((resolve, reject) => {
186+
exporter.export(records, result => {
187+
resolve(result);
188+
});
189+
});
190+
191+
assert.deepStrictEqual(
192+
metricDescriptors.getCall(0).args[0].resource.type,
193+
'custom.googleapis.com/opentelemetry/name400'
194+
);
195+
assert.deepStrictEqual(
196+
metricDescriptors.getCall(100).args[0].resource.type,
197+
'custom.googleapis.com/opentelemetry/name300'
198+
);
199+
assert.deepStrictEqual(
200+
metricDescriptors.getCall(400).args[0].resource.type,
201+
'custom.googleapis.com/opentelemetry/name0'
202+
);
203+
204+
assert.equal(metricDescriptors.callCount, 401);
205+
assert.equal(timeSeries.callCount, 3);
206+
140207
assert.deepStrictEqual(result, ExportResult.SUCCESS);
141208
});
142209
});

0 commit comments

Comments
 (0)