Skip to content

Commit

Permalink
feat(monitoring-exporter): add disableCreateMetricDescriptors option …
Browse files Browse the repository at this point in the history
…to skip checking for metric descriptor existence (#623)

* Issue #621

This change creates an option to skip checking if metric descriptors exist before
sending metrics. Skipping the metric descriptor check can prevent the clients from
getting rate limited by Google when a large number are all started at the same time.

* Updated option name based on PR feedback.

* Updated comment based on PR feedback.
  • Loading branch information
sethrwebster authored Sep 22, 2023
1 parent b4758ac commit 666a6d4
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export interface ExporterOptions {
* monitoring.googleapis.com:443.
*/
apiEndpoint?: string;
/**
* Disable calling CreateMetricDescriptor before sending time series to Cloud Monitoring.
* Metric descriptors will be
* {@link https://cloud.google.com/monitoring/custom-metrics/creating-metrics#auto-creation | auto-created}
* if needed, but may be missing descriptions. This can prevent hitting a rate limit in
* Cloud Monitoring when a large number of clients are all started up at the same time.
*/
disableCreateMetricDescriptors?: boolean;
}

export interface Credentials {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class MetricExporter implements PushMetricExporter {
private _projectId: string | void | Promise<string | void>;
private readonly _metricPrefix: string;
private readonly _auth: GoogleAuth;
private readonly _disableCreateMetricDescriptors: boolean;

static readonly DEFAULT_METRIC_PREFIX: string = 'workload.googleapis.com';

Expand All @@ -73,6 +74,8 @@ export class MetricExporter implements PushMetricExporter {

constructor(options: ExporterOptions = {}) {
this._metricPrefix = options.prefix ?? MetricExporter.DEFAULT_METRIC_PREFIX;
this._disableCreateMetricDescriptors =
!!options.disableCreateMetricDescriptors;

this._auth = new GoogleAuth({
credentials: options.credentials,
Expand Down Expand Up @@ -145,7 +148,9 @@ 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);
const isRegistered =
this._disableCreateMetricDescriptors ||
(await this._registerMetricDescriptor(metric));
if (isRegistered) {
timeSeries.push(
...createTimeSeries(metric, resource, this._metricPrefix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,68 @@ describe('MetricExporter', () => {
});
});

assert.strictEqual(metricDescriptorsGet.callCount, 1);
assert.strictEqual(metricDescriptorCreate.callCount, 1);
assert.strictEqual(timeSeries.callCount, 1);
assert.deepStrictEqual(result.code, ExportResultCode.SUCCESS);

// Second time around, MetricDescriptorCreate.create() should be skipped
metricDescriptorCreate.resetHistory();
metricDescriptorsGet.resetHistory();
timeSeries.resetHistory();
result = await new Promise<ExportResult>(resolve => {
exporter.export(resourceMetrics, result => {
resolve(result);
});
});

assert.strictEqual(metricDescriptorsGet.callCount, 0);
assert.strictEqual(metricDescriptorCreate.callCount, 0);
assert.strictEqual(timeSeries.callCount, 1);
assert.deepStrictEqual(result.code, ExportResultCode.SUCCESS);
});

it('should skip fetching the MetricDescriptors when disableCreateMetricDescriptors is set', async () => {
const exporterSkipDescriptorCreate = new MetricExporter({
disableCreateMetricDescriptors: true,
});
sinon.replace(
exporterSkipDescriptorCreate['_monitoring'].projects
.metricDescriptors,
'create',
metricDescriptorCreate as sinon.SinonSpy
);
sinon.replace(
exporterSkipDescriptorCreate['_monitoring'].projects
.metricDescriptors,
'get',
metricDescriptorsGet as sinon.SinonSpy
);
sinon.replace(
exporterSkipDescriptorCreate['_monitoring'].projects.timeSeries,
'create',
timeSeries as any
);
sinon.replace(
exporterSkipDescriptorCreate['_auth'],
'getClient',
() => {
if (getClientShouldFail) {
throw new Error('fail');
}
return {} as any;
}
);

resourceMetrics = await generateMetricsData();

const result = await new Promise<ExportResult>(resolve => {
exporterSkipDescriptorCreate.export(resourceMetrics, result => {
resolve(result);
});
});

assert.strictEqual(metricDescriptorsGet.callCount, 0);
assert.strictEqual(metricDescriptorCreate.callCount, 0);
assert.strictEqual(timeSeries.callCount, 1);
assert.deepStrictEqual(result.code, ExportResultCode.SUCCESS);
Expand Down

0 comments on commit 666a6d4

Please sign in to comment.