Skip to content

Commit 9fa7878

Browse files
committed
feat(observability): trace Transaction
This change adds observability tracing for Transaction along with tests. Updates #2079 Built from PR #2087 Updates #2114
1 parent 1f06871 commit 9fa7878

File tree

8 files changed

+641
-303
lines changed

8 files changed

+641
-303
lines changed

observability-test/batch-transaction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('BatchTransaction', () => {
139139
batchTransaction = new BatchTransaction(SESSION as {} as Session);
140140
batchTransaction.session = SESSION as {} as Session;
141141
batchTransaction.id = ID;
142-
batchTransaction.observabilityOptions = {tracerProvider: provider};
142+
batchTransaction.observabilityOptions_ = {tracerProvider: provider};
143143
REQUEST.callsFake((_, callback) => callback(null, RESPONSE));
144144
});
145145

observability-test/database.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ describe('Database', () => {
241241
database = new Database(INSTANCE, NAME, POOL_OPTIONS);
242242
database.parent = INSTANCE;
243243
database.databaseRole = 'parent_role';
244-
database.observabilityConfig = {
244+
database.observabilityOptions_ = {
245245
tracerProvider: provider,
246246
enableExtendedTracing: false,
247247
};

observability-test/spanner.ts

+25-10
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ describe('EndToEnd', () => {
149149

150150
const instance = spanner.instance('instance');
151151
database = instance.database('database');
152-
database.observabilityConfig = {
152+
database.observabilityOptions_ = {
153153
tracerProvider: provider,
154154
enableExtendedTracing: false,
155155
};
@@ -202,7 +202,7 @@ describe('EndToEnd', () => {
202202

203203
traceExporter.forceFlush();
204204
const spans = traceExporter.getFinishedSpans();
205-
assert.strictEqual(spans.length, 1, 'Exactly 1 span expected');
205+
assert.strictEqual(spans.length, 3, 'Exactly 1 span expected');
206206

207207
const actualSpanNames: string[] = [];
208208
const actualEventNames: string[] = [];
@@ -213,14 +213,18 @@ describe('EndToEnd', () => {
213213
});
214214
});
215215

216-
const expectedSpanNames = ['CloudSpanner.Database.getSnapshot'];
216+
const expectedSpanNames = [
217+
'CloudSpanner.Snapshot.begin',
218+
'CloudSpanner.Database.getSnapshot',
219+
'CloudSpanner.Snapshot.run',
220+
];
217221
assert.deepStrictEqual(
218222
actualSpanNames,
219223
expectedSpanNames,
220224
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
221225
);
222226

223-
const expectedEventNames = [];
227+
const expectedEventNames = ['Begin Transaction'];
224228
assert.deepStrictEqual(
225229
actualEventNames,
226230
expectedEventNames,
@@ -310,7 +314,7 @@ describe('EndToEnd', () => {
310314

311315
traceExporter.forceFlush();
312316
const spans = traceExporter.getFinishedSpans();
313-
assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected');
317+
assert.strictEqual(spans.length, 3, 'Exactly 2 spans expected');
314318

315319
// Sort the spans by duration.
316320
spans.sort((spanA, spanB) => {
@@ -329,6 +333,7 @@ describe('EndToEnd', () => {
329333
const expectedSpanNames = [
330334
'CloudSpanner.Database.runStream',
331335
'CloudSpanner.Database.run',
336+
'CloudSpanner.Snapshot.runStream',
332337
];
333338
assert.deepStrictEqual(
334339
actualSpanNames,
@@ -372,7 +377,7 @@ describe('EndToEnd', () => {
372377

373378
traceExporter.forceFlush();
374379
const spans = traceExporter.getFinishedSpans();
375-
assert.strictEqual(spans.length, 1, 'Exactly 1 span expected');
380+
assert.strictEqual(spans.length, 2, 'Exactly 1 span expected');
376381

377382
const actualEventNames: string[] = [];
378383
const actualSpanNames: string[] = [];
@@ -383,7 +388,10 @@ describe('EndToEnd', () => {
383388
});
384389
});
385390

386-
const expectedSpanNames = ['CloudSpanner.Database.runTransaction'];
391+
const expectedSpanNames = [
392+
'CloudSpanner.Database.runTransaction',
393+
'CloudSpanner.Snapshot.run',
394+
];
387395
assert.deepStrictEqual(
388396
actualSpanNames,
389397
expectedSpanNames,
@@ -410,7 +418,7 @@ describe('EndToEnd', () => {
410418

411419
traceExporter.forceFlush();
412420
const spans = traceExporter.getFinishedSpans();
413-
assert.strictEqual(spans.length, 1, 'Exactly 1 span expected');
421+
assert.strictEqual(spans.length, 2, 'Exactly 1 span expected');
414422

415423
const actualEventNames: string[] = [];
416424
const actualSpanNames: string[] = [];
@@ -421,14 +429,21 @@ describe('EndToEnd', () => {
421429
});
422430
});
423431

424-
const expectedSpanNames = ['CloudSpanner.Database.writeAtLeastOnce'];
432+
const expectedSpanNames = [
433+
'CloudSpanner.Transaction.commit',
434+
'CloudSpanner.Database.writeAtLeastOnce',
435+
];
425436
assert.deepStrictEqual(
426437
actualSpanNames,
427438
expectedSpanNames,
428439
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
429440
);
430441

431-
const expectedEventNames = ['Using Session'];
442+
const expectedEventNames = [
443+
'Starting Commit',
444+
'Commit Done',
445+
'Using Session',
446+
];
432447
assert.deepStrictEqual(
433448
actualEventNames,
434449
expectedEventNames,

observability-test/transaction.ts

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*!
2+
* Copyright 2024 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import {grpc} from 'google-gax';
19+
import {google} from '../protos/protos';
20+
import {Database, Spanner, Transaction} from '../src';
21+
import protobuf = google.spanner.v1;
22+
import * as mock from '../test/mockserver/mockspanner';
23+
import * as mockInstanceAdmin from '../test/mockserver/mockinstanceadmin';
24+
import * as mockDatabaseAdmin from '../test/mockserver/mockdatabaseadmin';
25+
const {
26+
AlwaysOnSampler,
27+
NodeTracerProvider,
28+
InMemorySpanExporter,
29+
} = require('@opentelemetry/sdk-trace-node');
30+
// eslint-disable-next-line n/no-extraneous-require
31+
const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base');
32+
33+
/** A simple result set for SELECT 1. */
34+
function createSelect1ResultSet(): protobuf.ResultSet {
35+
const fields = [
36+
protobuf.StructType.Field.create({
37+
name: 'NUM',
38+
type: protobuf.Type.create({code: protobuf.TypeCode.INT64}),
39+
}),
40+
];
41+
const metadata = new protobuf.ResultSetMetadata({
42+
rowType: new protobuf.StructType({
43+
fields,
44+
}),
45+
});
46+
return protobuf.ResultSet.create({
47+
metadata,
48+
rows: [{values: [{stringValue: '1'}]}],
49+
});
50+
}
51+
52+
interface setupResults {
53+
server: grpc.Server;
54+
spanner: Spanner;
55+
spannerMock: mock.MockSpanner;
56+
}
57+
58+
const selectSql = 'SELECT 1';
59+
const updateSql = 'UPDATE FOO SET BAR=1 WHERE BAZ=2';
60+
61+
async function setup(): Promise<setupResults> {
62+
const server = new grpc.Server();
63+
64+
const spannerMock = mock.createMockSpanner(server);
65+
mockInstanceAdmin.createMockInstanceAdmin(server);
66+
mockDatabaseAdmin.createMockDatabaseAdmin(server);
67+
68+
const port: number = await new Promise((resolve, reject) => {
69+
server.bindAsync(
70+
'0.0.0.0:0',
71+
grpc.ServerCredentials.createInsecure(),
72+
(err, assignedPort) => {
73+
if (err) {
74+
reject(err);
75+
} else {
76+
resolve(assignedPort);
77+
}
78+
}
79+
);
80+
});
81+
spannerMock.putStatementResult(
82+
selectSql,
83+
mock.StatementResult.resultSet(createSelect1ResultSet())
84+
);
85+
spannerMock.putStatementResult(
86+
updateSql,
87+
mock.StatementResult.updateCount(1)
88+
);
89+
90+
const spanner = new Spanner({
91+
projectId: 'observability-project-id',
92+
servicePath: 'localhost',
93+
port,
94+
sslCreds: grpc.credentials.createInsecure(),
95+
});
96+
97+
return Promise.resolve({
98+
spanner: spanner,
99+
server: server,
100+
spannerMock: spannerMock,
101+
});
102+
}
103+
104+
describe('Transaction', () => {
105+
let server: grpc.Server;
106+
let spanner: Spanner;
107+
let database: Database;
108+
let spannerMock: mock.MockSpanner;
109+
let traceExporter: typeof InMemorySpanExporter;
110+
111+
after(() => {
112+
spanner.close();
113+
server.tryShutdown(() => {});
114+
});
115+
116+
before(async () => {
117+
const setupResult = await setup();
118+
spanner = setupResult.spanner;
119+
server = setupResult.server;
120+
spannerMock = setupResult.spannerMock;
121+
122+
const selectSql = 'SELECT 1';
123+
const updateSql = 'UPDATE FOO SET BAR=1 WHERE BAZ=2';
124+
const upsertSql = 'INSERTORUPDATE INTO FOO(BAR, BAZ) VALUES(@bar, @baz)';
125+
spannerMock.putStatementResult(
126+
selectSql,
127+
mock.StatementResult.resultSet(createSelect1ResultSet())
128+
);
129+
spannerMock.putStatementResult(
130+
updateSql,
131+
mock.StatementResult.updateCount(1)
132+
);
133+
spannerMock.putStatementResult(
134+
upsertSql,
135+
mock.StatementResult.updateCount(1)
136+
);
137+
138+
traceExporter = new InMemorySpanExporter();
139+
const sampler = new AlwaysOnSampler();
140+
141+
const provider = new NodeTracerProvider({
142+
sampler: sampler,
143+
exporter: traceExporter,
144+
});
145+
provider.addSpanProcessor(new SimpleSpanProcessor(traceExporter));
146+
147+
const instance = spanner.instance('instance');
148+
database = instance.database('database');
149+
database.observabilityOptions_ = {
150+
tracerProvider: provider,
151+
enableExtendedTracing: false,
152+
};
153+
});
154+
155+
beforeEach(() => {
156+
spannerMock.resetRequests();
157+
});
158+
159+
afterEach(() => {
160+
traceExporter.reset();
161+
});
162+
163+
it('run', done => {
164+
database.getTransaction((err, tx) => {
165+
assert.ifError(err);
166+
167+
tx!.run('SELECT 1', (err, rows) => {
168+
traceExporter.forceFlush();
169+
170+
const spans = traceExporter.getFinishedSpans();
171+
assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected');
172+
173+
const actualSpanNames: string[] = [];
174+
const actualEventNames: string[] = [];
175+
spans.forEach(span => {
176+
actualSpanNames.push(span.name);
177+
span.events.forEach(event => {
178+
actualEventNames.push(event.name);
179+
});
180+
});
181+
182+
const expectedSpanNames = [
183+
'CloudSpanner.Database.getTransaction',
184+
'CloudSpanner.Snapshot.run',
185+
];
186+
assert.deepStrictEqual(
187+
actualSpanNames,
188+
expectedSpanNames,
189+
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
190+
);
191+
192+
const expectedEventNames = ['Using Session'];
193+
assert.strictEqual(
194+
actualEventNames.every(value => expectedEventNames.includes(value)),
195+
true,
196+
`Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}`
197+
);
198+
199+
done();
200+
});
201+
});
202+
});
203+
});

src/batch-transaction.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class BatchTransaction extends Snapshot {
139139

140140
const traceConfig: traceConfig = {
141141
sql: query,
142-
opts: this.observabilityOptions,
142+
opts: this.observabilityOptions_,
143143
};
144144
return startTrace(
145145
'BatchTransaction.createQueryPartitions',
@@ -182,7 +182,7 @@ class BatchTransaction extends Snapshot {
182182
*/
183183
createPartitions_(config, callback) {
184184
const traceConfig: traceConfig = {
185-
opts: this.observabilityOptions,
185+
opts: this.observabilityOptions_,
186186
};
187187

188188
return startTrace(
@@ -259,7 +259,7 @@ class BatchTransaction extends Snapshot {
259259
*/
260260
createReadPartitions(options, callback) {
261261
const traceConfig: traceConfig = {
262-
opts: this.observabilityOptions,
262+
opts: this.observabilityOptions_,
263263
};
264264

265265
return startTrace(

0 commit comments

Comments
 (0)