Skip to content

Commit d51aae9

Browse files
authored
feat: (observability) add spans for BatchTransaction and Table (googleapis#2115)
This change is part of a series of changes to add OpenTelemetry traces, focused on BatchTransaction and Table. While here, made the tests for sessionPool spans much more precise to avoid flakes. Updates googleapis#2079 Built from PR googleapis#2087 Updates googleapis#2114
1 parent 3300ab5 commit d51aae9

File tree

7 files changed

+731
-134
lines changed

7 files changed

+731
-134
lines changed
+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
/* eslint-disable prefer-rest-params */
18+
19+
import {util} from '@google-cloud/common';
20+
import * as pfy from '@google-cloud/promisify';
21+
import * as assert from 'assert';
22+
import {before, beforeEach, afterEach, describe, it} from 'mocha';
23+
import * as extend from 'extend';
24+
import * as proxyquire from 'proxyquire';
25+
import * as sinon from 'sinon';
26+
const {
27+
AlwaysOnSampler,
28+
NodeTracerProvider,
29+
InMemorySpanExporter,
30+
} = require('@opentelemetry/sdk-trace-node');
31+
// eslint-disable-next-line n/no-extraneous-require
32+
const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base');
33+
import {Session, Spanner} from '../src';
34+
import * as bt from '../src/batch-transaction';
35+
36+
const fakePfy = extend({}, pfy, {
37+
promisifyAll(klass, options) {
38+
if (klass.name !== 'BatchTransaction') {
39+
return;
40+
}
41+
assert.deepStrictEqual(options.exclude, ['identifier']);
42+
},
43+
});
44+
45+
class FakeTimestamp {
46+
calledWith_: IArguments;
47+
constructor() {
48+
this.calledWith_ = arguments;
49+
}
50+
}
51+
52+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53+
const fakeCodec: any = {
54+
encode: util.noop,
55+
Timestamp: FakeTimestamp,
56+
Int() {},
57+
Float() {},
58+
SpannerDate() {},
59+
convertProtoTimestampToDate() {},
60+
};
61+
62+
const SPANNER = {
63+
routeToLeaderEnabled: true,
64+
};
65+
66+
const INSTANCE = {
67+
parent: SPANNER,
68+
};
69+
70+
const DATABASE = {
71+
formattedName_: 'database',
72+
parent: INSTANCE,
73+
};
74+
75+
class FakeTransaction {
76+
calledWith_: IArguments;
77+
session;
78+
constructor(session) {
79+
this.calledWith_ = arguments;
80+
this.session = session;
81+
}
82+
static encodeKeySet(): object {
83+
return {};
84+
}
85+
static encodeParams(): object {
86+
return {};
87+
}
88+
89+
_getSpanner(): Spanner {
90+
return SPANNER as Spanner;
91+
}
92+
93+
run() {}
94+
read() {}
95+
}
96+
97+
describe('BatchTransaction', () => {
98+
const sandbox = sinon.createSandbox();
99+
100+
// tslint:disable-next-line variable-name
101+
let BatchTransaction: typeof bt.BatchTransaction;
102+
let batchTransaction: bt.BatchTransaction;
103+
104+
before(() => {
105+
BatchTransaction = proxyquire('../src/batch-transaction.js', {
106+
'@google-cloud/precise-date': {PreciseDate: FakeTimestamp},
107+
'@google-cloud/promisify': fakePfy,
108+
'./codec.js': {codec: fakeCodec},
109+
'./transaction.js': {Snapshot: FakeTransaction},
110+
}).BatchTransaction;
111+
});
112+
113+
const traceExporter = new InMemorySpanExporter();
114+
const sampler = new AlwaysOnSampler();
115+
116+
const provider = new NodeTracerProvider({
117+
sampler: sampler,
118+
exporter: traceExporter,
119+
});
120+
provider.addSpanProcessor(new SimpleSpanProcessor(traceExporter));
121+
122+
afterEach(() => {
123+
traceExporter.reset();
124+
sandbox.restore();
125+
});
126+
127+
const REQUEST = sandbox.stub();
128+
const SESSION = {
129+
parent: DATABASE,
130+
formattedName_: 'abcdef',
131+
request: REQUEST,
132+
};
133+
const ID = '0xdeadbeef';
134+
135+
const PARTITIONS = [{partitionToken: 'a'}, {partitionToken: 'b'}];
136+
const RESPONSE = {partitions: PARTITIONS};
137+
138+
beforeEach(() => {
139+
batchTransaction = new BatchTransaction(SESSION as {} as Session);
140+
batchTransaction.session = SESSION as {} as Session;
141+
batchTransaction.id = ID;
142+
batchTransaction.observabilityOptions = {tracerProvider: provider};
143+
REQUEST.callsFake((_, callback) => callback(null, RESPONSE));
144+
});
145+
146+
const GAX_OPTS = {};
147+
148+
const QUERY = {
149+
sql: 'SELECT * FROM Singers',
150+
gaxOptions: GAX_OPTS,
151+
params: {},
152+
types: {},
153+
};
154+
155+
it('createQueryPartitions', done => {
156+
const REQUEST = sandbox.stub();
157+
158+
const res = batchTransaction.createQueryPartitions(
159+
QUERY,
160+
(err, part, resp) => {
161+
assert.ifError(err);
162+
traceExporter.forceFlush();
163+
const spans = traceExporter.getFinishedSpans();
164+
assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected');
165+
166+
// Sort the spans by duration.
167+
spans.sort((spanA, spanB) => {
168+
spanA.duration < spanB.duration;
169+
});
170+
171+
const actualSpanNames: string[] = [];
172+
spans.forEach(span => {
173+
actualSpanNames.push(span.name);
174+
});
175+
176+
const expectedSpanNames = [
177+
'CloudSpanner.BatchTransaction.createPartitions_',
178+
'CloudSpanner.BatchTransaction.createQueryPartitions',
179+
];
180+
assert.deepStrictEqual(
181+
actualSpanNames,
182+
expectedSpanNames,
183+
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
184+
);
185+
186+
// Ensure that createPartitions_ is a child span of createQueryPartitions.
187+
const spanCreatePartitions_ = spans[0];
188+
const spanCreateQueryPartitions = spans[1];
189+
assert.ok(
190+
spanCreateQueryPartitions.spanContext().traceId,
191+
'Expected that createQueryPartitions has a defined traceId'
192+
);
193+
assert.ok(
194+
spanCreatePartitions_.spanContext().traceId,
195+
'Expected that createPartitions_ has a defined traceId'
196+
);
197+
assert.deepStrictEqual(
198+
spanCreatePartitions_.spanContext().traceId,
199+
spanCreateQueryPartitions.spanContext().traceId,
200+
'Expected that both spans share a traceId'
201+
);
202+
assert.ok(
203+
spanCreateQueryPartitions.spanContext().spanId,
204+
'Expected that createQueryPartitions has a defined spanId'
205+
);
206+
assert.ok(
207+
spanCreatePartitions_.spanContext().spanId,
208+
'Expected that createPartitions_ has a defined spanId'
209+
);
210+
assert.deepStrictEqual(
211+
spanCreatePartitions_.parentSpanId,
212+
spanCreateQueryPartitions.spanContext().spanId,
213+
'Expected that createQueryPartitions is the parent to createPartitions_'
214+
);
215+
done();
216+
}
217+
);
218+
});
219+
220+
it('createReadPartitions', done => {
221+
const REQUEST = sandbox.stub();
222+
const response = {};
223+
REQUEST.callsFake((_, callback) => callback(null, response));
224+
225+
const res = batchTransaction.createReadPartitions(
226+
QUERY,
227+
(err, part, resp) => {
228+
assert.ifError(err);
229+
traceExporter.forceFlush();
230+
const spans = traceExporter.getFinishedSpans();
231+
assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected');
232+
233+
// Sort the spans by duration.
234+
spans.sort((spanA, spanB) => {
235+
spanA.duration < spanB.duration;
236+
});
237+
238+
const actualSpanNames: string[] = [];
239+
spans.forEach(span => {
240+
actualSpanNames.push(span.name);
241+
});
242+
const expectedSpanNames = [
243+
'CloudSpanner.BatchTransaction.createPartitions_',
244+
'CloudSpanner.BatchTransaction.createReadPartitions',
245+
];
246+
assert.deepStrictEqual(
247+
actualSpanNames,
248+
expectedSpanNames,
249+
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
250+
);
251+
done();
252+
}
253+
);
254+
});
255+
});

0 commit comments

Comments
 (0)