-
Notifications
You must be signed in to change notification settings - Fork 713
chore(Spanner)!: upgrade OpenTelemetry to v2 #9329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,8 +17,11 @@ import * as os from 'os'; | |
| import * as process from 'process'; | ||
| import {MeterProvider, MetricReader} from '@opentelemetry/sdk-metrics'; | ||
| import {Counter, Histogram, context, ROOT_CONTEXT} from '@opentelemetry/api'; | ||
| import {detectResources, Resource} from '@opentelemetry/resources'; | ||
| import {GcpDetectorSync} from '@google-cloud/opentelemetry-resource-util'; | ||
| import { | ||
| detectResources, | ||
| resourceFromAttributes, | ||
| } from '@opentelemetry/resources'; | ||
| import {gcpDetector} from '@opentelemetry/resource-detector-gcp'; | ||
| import * as Constants from './constants'; | ||
| import {MetricsTracer} from './metrics-tracer'; | ||
| const version = require('../../../package.json').version; | ||
|
|
@@ -122,7 +125,7 @@ export class MetricsTracerFactory { | |
| */ | ||
| public getMeterProvider(readers: MetricReader[] = []): MeterProvider { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method could silently ignore the Test case for verification: import * as assert from 'assert';
import * as sinon from 'sinon';
import {grpc} from 'google-gax';
import * as mock from '../mockserver/mockspanner';
import {Spanner} from '../../src';
import {MetricsTracerFactory} from '../../src/metrics/metrics-tracer-factory';
import {MetricReader} from '@opentelemetry/sdk-metrics';
class InMemoryMetricReader extends MetricReader {
protected async onForceFlush(): Promise<void> {}
protected async onShutdown(): Promise<void> {}
}
it('should bind MetricReader passed to getMeterProvider([reader]) even if getMeterProvider() was called earlier', async () => {
const sandbox = sinon.createSandbox();
const server = new grpc.Server();
const spannerMock = mock.createMockSpanner(server);
const port = await new Promise<number>((resolve, reject) => {
server.bindAsync('0.0.0.0:0', grpc.ServerCredentials.createInsecure(), (err, p) =>
err ? reject(err) : resolve(p)
);
});
spannerMock.putStatementResult(
'SELECT 1',
mock.StatementResult.resultSet(mock.createSimpleResultSet())
);
try {
await MetricsTracerFactory.resetInstance();
sandbox.stub(MetricsTracerFactory as any, '_detectClientLocation').resolves('us-central1');
const spanner = new Spanner({
projectId: 'test-project',
servicePath: 'localhost',
port,
sslCreds: grpc.credentials.createInsecure(),
});
(spanner as any)._metricsEnabled = true;
MetricsTracerFactory.enabled = true;
const factory = MetricsTracerFactory.getInstance('test-project')!;
// 1. Call getMeterProvider() first (initializes _meterProvider with readers: [])
factory.getMeterProvider();
// 2. Now pass a reader (simulating configureMetrics_ async callback completing afterwards)
const reader = new InMemoryMetricReader();
factory.getMeterProvider([reader]);
const database = spanner.instance('inst').database('db');
await database.run('SELECT 1');
// FAILS in current PR: throws "MetricReader is not bound to a MetricProducer"
const {resourceMetrics} = await reader.collect();
assert.ok(resourceMetrics.scopeMetrics.length > 0);
await database.close();
await spanner.close();
} finally {
sandbox.restore();
server.tryShutdown(() => {});
await MetricsTracerFactory.resetInstance();
}
}); |
||
| if (this._meterProvider === null) { | ||
| const resource = new Resource({ | ||
| const resource = resourceFromAttributes({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A race condition between Test case: import * as assert from 'assert';
import * as sinon from 'sinon';
import {grpc} from 'google-gax';
import * as mock from '../mockserver/mockspanner';
import {Spanner} from '../../src';
import {MetricsTracerFactory} from '../../src/metrics/metrics-tracer-factory';
import {transformResourceMetricToTimeSeriesArray} from '../../src/metrics/transform';
import {MetricReader} from '@opentelemetry/sdk-metrics';
class InMemoryMetricReader extends MetricReader {
protected async onForceFlush(): Promise<void> {}
protected async onShutdown(): Promise<void> {}
}
it('should export the detected GCP location rather than global when _detectClientLocation resolves asynchronously', async () => {
const sandbox = sinon.createSandbox();
const server = new grpc.Server();
const spannerMock = mock.createMockSpanner(server);
const port = await new Promise<number>((resolve, reject) => {
server.bindAsync('0.0.0.0:0', grpc.ServerCredentials.createInsecure(), (err, p) =>
err ? reject(err) : resolve(p)
);
});
spannerMock.putStatementResult(
'SELECT 1',
mock.StatementResult.resultSet(mock.createSimpleResultSet())
);
let resolveLocation!: (loc: string) => void;
const locationPromise = new Promise<string>(resolve => {
resolveLocation = resolve;
});
try {
await MetricsTracerFactory.resetInstance();
sandbox
.stub(MetricsTracerFactory as any, '_detectClientLocation')
.returns(locationPromise);
const spanner = new Spanner({
projectId: 'test-project',
servicePath: 'localhost',
port,
sslCreds: grpc.credentials.createInsecure(),
});
(spanner as any)._metricsEnabled = true;
MetricsTracerFactory.enabled = true;
// 1. Replicate exact synchronous sequence from configureMetrics_ (src/index.ts:1691-1700):
// getMeterProvider runs synchronously while locationPromise is still pending
const reader = new InMemoryMetricReader();
const factory = MetricsTracerFactory.getInstance('test-project')!;
factory.getMeterProvider([reader]);
// 2. Deterministically resolve locationPromise now and flush microtasks
resolveLocation('us-central1');
await locationPromise;
await Promise.resolve();
assert.strictEqual((factory as any)._location, 'us-central1');
const database = spanner.instance('inst').database('db');
await database.run('SELECT 1');
const {resourceMetrics} = await reader.collect();
const timeSeries = transformResourceMetricToTimeSeriesArray(resourceMetrics, 'test-project');
const exportedLocation = timeSeries[0].resource.labels.location;
// FAILS in current PR: exportedLocation is 'global' instead of 'us-central1'
assert.strictEqual(exportedLocation, 'us-central1');
await database.close();
await spanner.close();
} finally {
sandbox.restore();
server.tryShutdown(() => {});
await MetricsTracerFactory.resetInstance();
}
}); |
||
| [Constants.MONITORED_RES_LABEL_KEY_PROJECT]: this._projectId, | ||
| [Constants.MONITORED_RES_LABEL_KEY_CLIENT_HASH]: this._clientHash, | ||
| [Constants.MONITORED_RES_LABEL_KEY_LOCATION]: this._location, | ||
|
|
@@ -454,14 +457,14 @@ export class MetricsTracerFactory { | |
|
|
||
| /** | ||
| * Gets the location (region) of the client, otherwise returns to the "global" region. | ||
| * Uses GcpDetectorSync to detect the region from the environment. | ||
| * Uses the GCP resource detector to detect the region from the environment. | ||
| * @returns The detected region string, or "global" if not found. | ||
| */ | ||
| private static async _detectClientLocation(): Promise<string> { | ||
| const defaultRegion = 'global'; | ||
| try { | ||
| const resource = await detectResources({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could fail to detect the location correctly on zonal GKE clusters (it should also check the Test case (albeit a bit 'fake', as we cannot really replicate the behavior of GKE in a test case): import * as assert from 'assert';
import * as sinon from 'sinon';
import {gcpDetector} from '@opentelemetry/resource-detector-gcp';
import {MetricsTracerFactory} from '../../src/metrics/metrics-tracer-factory';
it('should detect client location on a zonal GKE cluster where cloud.availability_zone is set', async () => {
const sandbox = sinon.createSandbox();
try {
// On a zonal GKE cluster, gcpDetector sets cloud.availability_zone and leaves cloud.region undefined
sandbox.stub(gcpDetector, 'detect').returns({
attributes: {
'cloud.platform': Promise.resolve('gcp_kubernetes_engine'),
'cloud.availability_zone': Promise.resolve('us-central1-a'),
},
});
const detectedLocation = await (MetricsTracerFactory as any)._detectClientLocation();
// FAILS in current PR: returns 'global' instead of 'us-central1-a'
assert.strictEqual(detectedLocation, 'us-central1-a');
} finally {
sandbox.restore();
}
}); |
||
| detectors: [new GcpDetectorSync()], | ||
| detectors: [gcpDetector], | ||
| }); | ||
|
|
||
| await resource?.waitForAsyncAttributes?.(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,7 +64,7 @@ | |
| .onThirdCall() | ||
| .returns({add: addGfeConnectivityErrorCountStub}); | ||
|
|
||
| sandbox.stub(MeterProvider.prototype, 'getMeter').returns(meterStub as any); | ||
|
|
||
| // metrics provider and related objects | ||
| mockExporter = sandbox.createStubInstance(CloudMonitoringMetricsExporter); | ||
|
|
@@ -79,13 +79,11 @@ | |
| MetricsTracerFactory.enabled = true; | ||
| sandbox.resetHistory(); | ||
| await MetricsTracerFactory.resetInstance(); | ||
| const provider = | ||
| MetricsTracerFactory.getInstance('project-id')!.getMeterProvider(); | ||
| const reader = new PeriodicExportingMetricReader({ | ||
| exporter: mockExporter, | ||
| exportIntervalMillis: 60000, | ||
| }); | ||
| provider.addMetricReader(reader); | ||
| MetricsTracerFactory.getInstance('project-id')!.getMeterProvider([reader]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This now triggers a real network call to try to detect the GCP location. And while that does not fail, it does (probably) emit a warning in the error log when running the test (and potentially also causes the test to be slower). So it should probably also be mocked in this test: beforeEach(async () => {
MetricsTracerFactory.enabled = true;
sandbox.resetHistory();
sandbox
.stub(MetricsTracerFactory as any, '_detectClientLocation')
.resolves('global');
await MetricsTracerFactory.resetInstance();
... |
||
| }); | ||
|
|
||
| afterEach(async () => { | ||
|
|
@@ -166,12 +164,12 @@ | |
| '1.1a2bc3d4.1.1.1.1', | ||
| ); | ||
|
|
||
| assert.strictEqual((factory as any)._currentOperationTracers.size, 1); | ||
|
|
||
| factory!.clearCurrentTracer('1.1a2bc3d4.1.1.1'); | ||
|
|
||
| assert.strictEqual((factory as any)._currentOperationTracers.size, 0); | ||
| assert.strictEqual((factory as any)._currentOperationLastUpdatedMs.size, 0); | ||
| }); | ||
|
|
||
| it('should correctly set default attributes', () => { | ||
|
|
@@ -199,7 +197,7 @@ | |
| describe('getInstanceAttributes', () => { | ||
| let factory: MetricsTracerFactory; | ||
| beforeEach(() => { | ||
| factory = new (MetricsTracerFactory as any)(); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.