Skip to content

Commit

Permalink
fix(sdk-metrics): await export when async attributes are pending
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc committed Nov 8, 2024
1 parent 6f4f3fc commit 617305a
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 19 deletions.
30 changes: 12 additions & 18 deletions packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
import { MetricReader } from './MetricReader';
import { PushMetricExporter } from './MetricExporter';
import { callWithTimeout, TimeoutError } from '../utils';
import { diag } from '@opentelemetry/api';
import { MetricProducer } from './MetricProducer';

export type PeriodicExportingMetricReaderOptions = {
Expand Down Expand Up @@ -127,25 +126,20 @@ export class PeriodicExportingMetricReader extends MetricReader {
);
}

const doExport = async () => {
const result = await internal._export(this._exporter, resourceMetrics);
if (result.code !== ExportResultCode.SUCCESS) {
throw new Error(
`PeriodicExportingMetricReader: metrics export failed (error ${result.error})`
);
if (resourceMetrics.resource.asyncAttributesPending) {
try {
await resourceMetrics.resource.waitForAsyncAttributes?.();
} catch (e) {
api.diag.debug('Error while resolving async portion of resource: ', e);
globalErrorHandler(e);

Check warning on line 134 in packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts

View check run for this annotation

Codecov / codecov/patch

packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts#L133-L134

Added lines #L133 - L134 were not covered by tests
}
};
}

// Avoid scheduling a promise to make the behavior more predictable and easier to test
if (resourceMetrics.resource.asyncAttributesPending) {
resourceMetrics.resource
.waitForAsyncAttributes?.()
.then(doExport, err =>
diag.debug('Error while resolving async portion of resource: ', err)
)
.catch(globalErrorHandler);
} else {
await doExport();
const result = await internal._export(this._exporter, resourceMetrics);
if (result.code !== ExportResultCode.SUCCESS) {
throw new Error(
`PeriodicExportingMetricReader: metrics export failed (error ${result.error})`
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

import { PeriodicExportingMetricReader } from '../../src/export/PeriodicExportingMetricReader';
import { AggregationTemporality } from '../../src/export/AggregationTemporality';
import { Aggregation, InstrumentType, PushMetricExporter } from '../../src';
import {
Aggregation,
CollectionResult,
InstrumentType,
MetricProducer,
PushMetricExporter,
} from '../../src';
import { ResourceMetrics } from '../../src/export/MetricData';
import * as assert from 'assert';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -296,6 +302,55 @@ describe('PeriodicExportingMetricReader', () => {
await reader.shutdown();
});

it('should complete actions before promise resolves when async resource attributes are pending', async () => {
// arrange
const waitForAsyncAttributesStub = sinon.stub().returns(
new Promise<void>(resolve =>
setTimeout(() => {
resolve();
}, 10)
)
);
const resourceMetrics: ResourceMetrics = {
resource: {
attributes: {},
merge: sinon.stub(),
asyncAttributesPending: true, // ensure we try to await async attributes
waitForAsyncAttributes: waitForAsyncAttributesStub, // resolve when awaited
},
scopeMetrics: [],
};

const mockCollectionResult: CollectionResult = {
errors: [],
resourceMetrics,
};
const producerStubs: MetricProducer = {
collect: sinon.stub().resolves(mockCollectionResult),
};

const exporter = new TestMetricExporter();

const reader = new PeriodicExportingMetricReader({
exporter: exporter,
exportIntervalMillis: MAX_32_BIT_INT,
exportTimeoutMillis: 80,
});

reader.setMetricProducer(producerStubs);

// act
await reader.forceFlush();

// assert
sinon.assert.calledOnce(waitForAsyncAttributesStub);
assert.strictEqual(
exporter.getExports().length,
1,
'Expected exactly 1 export to happen when awaiting forceFlush'
);
});

it('should throw TimeoutError when forceFlush takes too long', async () => {
const exporter = new TestMetricExporter();
exporter.forceFlushTime = 60;
Expand Down

0 comments on commit 617305a

Please sign in to comment.