Skip to content

Commit bab9a8b

Browse files
authoredJul 17, 2024··
fix: Updated cassandra-driver instrumentation to properly trace promise based executions (#2351)
1 parent d212b15 commit bab9a8b

File tree

2 files changed

+208
-136
lines changed

2 files changed

+208
-136
lines changed
 

‎lib/instrumentation/cassandra-driver.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = function initialize(_agent, cassandra, _moduleName, shim) {
2929
ClientProto,
3030
['connect', 'shutdown'],
3131
function operationSpec(shim, _fn, name) {
32-
return new OperationSpec({ callback: shim.LAST, name })
32+
return new OperationSpec({ callback: shim.LAST, name, promise: true })
3333
}
3434
)
3535

@@ -39,7 +39,8 @@ module.exports = function initialize(_agent, cassandra, _moduleName, shim) {
3939
'_execute',
4040
new QuerySpec({
4141
query: shim.FIRST,
42-
callback: shim.LAST
42+
callback: shim.LAST,
43+
promise: true
4344
})
4445
)
4546

@@ -64,7 +65,8 @@ module.exports = function initialize(_agent, cassandra, _moduleName, shim) {
6465
'_innerExecute',
6566
new QuerySpec({
6667
query: shim.FIRST,
67-
callback: shim.LAST
68+
callback: shim.LAST,
69+
promise: true
6870
})
6971
)
7072

@@ -109,7 +111,8 @@ module.exports = function initialize(_agent, cassandra, _moduleName, shim) {
109111
'batch',
110112
new QuerySpec({
111113
query: findBatchQueryArg,
112-
callback: shim.LAST
114+
callback: shim.LAST,
115+
promise: true
113116
})
114117
)
115118
}

‎test/versioned/cassandra-driver/query.tap.js

+201-132
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const helper = require('../../lib/agent_helper')
1111

1212
const agent = helper.instrumentMockedAgent()
1313
const cassandra = require('cassandra-driver')
14+
const { findSegment } = require('../../lib/metrics_helper')
1415

1516
// constants for keyspace and table creation
1617
const KS = 'test'
@@ -25,6 +26,26 @@ const client = new cassandra.Client({
2526
localDataCenter: 'datacenter1'
2627
})
2728

29+
const colValArr = ['Jim', 'Bob', 'Joe']
30+
const pkValArr = [111, 222, 333]
31+
let insQuery = 'INSERT INTO ' + KS + '.' + FAM + ' (' + PK + ',' + COL
32+
insQuery += ') VALUES(?, ?);'
33+
34+
const insArr = [
35+
{ query: insQuery, params: [pkValArr[0], colValArr[0]] },
36+
{ query: insQuery, params: [pkValArr[1], colValArr[1]] },
37+
{ query: insQuery, params: [pkValArr[2], colValArr[2]] }
38+
]
39+
40+
const hints = [
41+
['int', 'varchar'],
42+
['int', 'varchar'],
43+
['int', 'varchar']
44+
]
45+
46+
let selQuery = 'SELECT * FROM ' + KS + '.' + FAM + ' WHERE '
47+
selQuery += PK + ' = 111;'
48+
2849
/**
2950
* Deletion of testing keyspace if already exists,
3051
* then recreation of a testable keyspace and table
@@ -33,7 +54,7 @@ const client = new cassandra.Client({
3354
* @param Callback function to set off running the tests
3455
*/
3556

36-
async function cassSetup(runTest) {
57+
async function cassSetup() {
3758
const setupClient = new cassandra.Client({
3859
contactPoints: [params.cassandra_host],
3960
protocolOptions: params.cassandra_port,
@@ -66,146 +87,151 @@ async function cassSetup(runTest) {
6687
await runCommand(famCreate)
6788

6889
setupClient.shutdown()
69-
runTest()
7090
}
7191

7292
test('Cassandra instrumentation', { timeout: 5000 }, async function testInstrumentation(t) {
73-
t.plan(1)
74-
await cassSetup(runTest)
75-
76-
function runTest() {
77-
t.test('executeBatch', function (t) {
78-
t.notOk(agent.getTransaction(), 'no transaction should be in play')
79-
helper.runInTransaction(agent, function transactionInScope(tx) {
80-
const transaction = agent.getTransaction()
81-
t.ok(transaction, 'transaction should be visible')
82-
t.equal(tx, transaction, 'We got the same transaction')
83-
const colValArr = ['Jim', 'Bob', 'Joe']
84-
const pkValArr = [111, 222, 333]
85-
let insQuery = 'INSERT INTO ' + KS + '.' + FAM + ' (' + PK + ',' + COL
86-
insQuery += ') VALUES(?, ?);'
87-
88-
const insArr = [
89-
{ query: insQuery, params: [pkValArr[0], colValArr[0]] },
90-
{ query: insQuery, params: [pkValArr[1], colValArr[1]] },
91-
{ query: insQuery, params: [pkValArr[2], colValArr[2]] }
92-
]
93-
94-
const hints = [
95-
['int', 'varchar'],
96-
['int', 'varchar'],
97-
['int', 'varchar']
98-
]
99-
100-
client.batch(insArr, { hints: hints }, function done(error, ok) {
93+
t.before(async function () {
94+
await cassSetup()
95+
})
96+
97+
t.teardown(function tearDown() {
98+
helper.unloadAgent(agent)
99+
client.shutdown()
100+
})
101+
102+
t.afterEach(() => {
103+
agent.queries.clear()
104+
agent.metrics.clear()
105+
})
106+
107+
t.test('executeBatch - callback style', function (t) {
108+
t.notOk(agent.getTransaction(), 'no transaction should be in play')
109+
helper.runInTransaction(agent, function transactionInScope(tx) {
110+
const transaction = agent.getTransaction()
111+
t.ok(transaction, 'transaction should be visible')
112+
t.equal(tx, transaction, 'We got the same transaction')
113+
114+
client.batch(insArr, { hints: hints }, function done(error, ok) {
115+
if (error) {
116+
t.error(error)
117+
return t.end()
118+
}
119+
120+
t.ok(agent.getTransaction(), 'transaction should still be visible')
121+
t.ok(ok, 'everything should be peachy after setting')
122+
123+
client.execute(selQuery, function (error, value) {
101124
if (error) {
102-
t.fail(error)
103-
return t.end()
125+
return t.error(error)
104126
}
105127

106-
t.ok(agent.getTransaction(), 'transaction should still be visible')
107-
t.ok(ok, 'everything should be peachy after setting')
108-
109-
let selQuery = 'SELECT * FROM ' + KS + '.' + FAM + ' WHERE '
110-
selQuery += PK + ' = 111;'
111-
client.execute(selQuery, function (error, value) {
112-
if (error) {
113-
return t.fail(error)
114-
}
128+
t.ok(agent.getTransaction(), 'transaction should still still be visible')
129+
t.equal(value.rows[0][COL], colValArr[0], 'Cassandra client should still work')
130+
131+
t.equal(
132+
transaction.trace.root.children.length,
133+
1,
134+
'there should be only one child of the root'
135+
)
136+
verifyTrace(t, transaction.trace, KS + '.' + FAM)
137+
transaction.end()
138+
checkMetric(t)
139+
t.end()
140+
})
141+
})
142+
})
143+
})
115144

145+
t.test('executeBatch - promise style', function (t) {
146+
t.notOk(agent.getTransaction(), 'no transaction should be in play')
147+
helper.runInTransaction(agent, function transactionInScope(tx) {
148+
const transaction = agent.getTransaction()
149+
t.ok(transaction, 'transaction should be visible')
150+
t.equal(tx, transaction, 'We got the same transaction')
151+
152+
client.batch(insArr, { hints: hints }).then(function () {
153+
client
154+
.execute(selQuery)
155+
.then((result) => {
116156
t.ok(agent.getTransaction(), 'transaction should still still be visible')
117-
t.equal(value.rows[0][COL], colValArr[0], 'Cassandra client should still work')
118-
119-
const trace = transaction.trace
120-
t.ok(trace, 'trace should exist')
121-
t.ok(trace.root, 'root element should exist')
122-
123-
t.equal(trace.root.children.length, 1, 'there should be only one child of the root')
124-
125-
const setSegment = trace.root.children[0]
126-
t.ok(setSegment, 'trace segment for insert should exist')
127-
if (setSegment) {
128-
t.equal(
129-
setSegment.name,
130-
'Datastore/statement/Cassandra/test.testFamily/insert/batch',
131-
'should register the executeBatch'
132-
)
133-
t.ok(
134-
setSegment.children.length >= 2,
135-
'set should have atleast a dns lookup and callback child'
136-
)
137-
138-
const setSegmentAttributes = setSegment.getAttributes()
139-
t.equal(setSegmentAttributes.product, 'Cassandra', 'should set product attribute')
140-
t.equal(setSegmentAttributes.port_path_or_id, '9042', 'should set port attribute')
141-
t.equal(
142-
setSegmentAttributes.database_name,
143-
'test',
144-
'should set database_name attribute'
145-
)
146-
t.equal(
147-
setSegmentAttributes.host,
148-
agent.config.getHostnameSafe(),
149-
'should set host attribute'
150-
)
151-
152-
const childIndex = setSegment.children.length - 1
153-
const getSegment = setSegment.children[childIndex].children[0]
154-
t.ok(getSegment, 'trace segment for select should exist')
155-
if (getSegment) {
156-
t.equal(
157-
getSegment.name,
158-
'Datastore/statement/Cassandra/test.testFamily/select',
159-
'should register the execute'
160-
)
161-
162-
t.ok(getSegment.children.length >= 1, 'get should have a callback segment')
163-
164-
const getSegmentAttributes = getSegment.getAttributes()
165-
t.equal(
166-
getSegmentAttributes.product,
167-
'Cassandra',
168-
'get should set product attribute'
169-
)
170-
t.equal(
171-
getSegmentAttributes.port_path_or_id,
172-
'9042',
173-
'get should set port attribute'
174-
)
175-
t.equal(
176-
getSegmentAttributes.database_name,
177-
'test',
178-
'get should set database_name attribute'
179-
)
180-
t.equal(
181-
getSegmentAttributes.host,
182-
agent.config.getHostnameSafe(),
183-
'get should set host attribute'
184-
)
185-
186-
t.ok(getSegment.timer.hrDuration, 'trace segment should have ended')
187-
}
188-
}
189-
157+
t.equal(result.rows[0][COL], colValArr[0], 'Cassandra client should still work')
158+
159+
t.equal(
160+
transaction.trace.root.children.length,
161+
2,
162+
'there should be two children of the root'
163+
)
164+
verifyTrace(t, transaction.trace, KS + '.' + FAM)
190165
transaction.end()
191-
checkMetric('Datastore/operation/Cassandra/insert', 1)
192-
checkMetric('Datastore/allWeb', 2)
193-
checkMetric('Datastore/Cassandra/allWeb', 2)
194-
checkMetric('Datastore/Cassandra/all', 2)
195-
checkMetric('Datastore/all', 2)
196-
checkMetric('Datastore/statement/Cassandra/test.testFamily/insert', 1)
197-
checkMetric('Datastore/operation/Cassandra/select', 1)
198-
checkMetric('Datastore/statement/Cassandra/test.testFamily/select', 1)
199-
166+
checkMetric(t)
167+
})
168+
.catch((error) => {
169+
t.error(error)
170+
})
171+
.finally(() => {
200172
t.end()
201173
})
174+
})
175+
})
176+
})
177+
178+
t.test('executeBatch - slow query', function (t) {
179+
t.notOk(agent.getTransaction(), 'no transaction should be in play')
180+
helper.runInTransaction(agent, function transactionInScope(tx) {
181+
// enable slow queries
182+
agent.config.transaction_tracer.explain_threshold = 1
183+
agent.config.transaction_tracer.record_sql = 'raw'
184+
agent.config.slow_sql.enabled = true
185+
186+
const transaction = agent.getTransaction()
187+
t.ok(transaction, 'transaction should be visible')
188+
t.equal(tx, transaction, 'We got the same transaction')
189+
190+
client.batch(insArr, { hints: hints }, function done(error, ok) {
191+
if (error) {
192+
t.error(error)
193+
return t.end()
194+
}
195+
196+
const slowQuery = 'SELECT * FROM ' + KS + '.' + FAM
197+
t.ok(agent.getTransaction(), 'transaction should still be visible')
198+
t.ok(ok, 'everything should be peachy after setting')
199+
200+
client.execute(slowQuery, function (error) {
201+
if (error) {
202+
return t.error(error)
203+
}
204+
205+
verifyTrace(t, transaction.trace, KS + '.' + FAM)
206+
transaction.end()
207+
t.ok(agent.queries.samples.size > 0, 'there should be a slow query')
208+
checkMetric(t)
209+
t.end()
202210
})
203211
})
212+
})
213+
})
204214

205-
function checkMetric(name, count, scoped) {
206-
const agentMetrics = agent.metrics._metrics
207-
const metric = agentMetrics[scoped ? 'scoped' : 'unscoped'][name]
208-
t.ok(metric, 'metric "' + name + '" should exist')
215+
function checkMetric(t, scoped) {
216+
const agentMetrics = agent.metrics._metrics
217+
218+
const expected = {
219+
'Datastore/operation/Cassandra/insert': 1,
220+
'Datastore/allWeb': 2,
221+
'Datastore/Cassandra/allWeb': 2,
222+
'Datastore/Cassandra/all': 2,
223+
'Datastore/all': 2,
224+
'Datastore/statement/Cassandra/test.testFamily/insert': 1,
225+
'Datastore/operation/Cassandra/select': 1,
226+
'Datastore/statement/Cassandra/test.testFamily/select': 1
227+
}
228+
229+
for (const expectedMetric in expected) {
230+
if (expected.hasOwnProperty(expectedMetric)) {
231+
const count = expected[expectedMetric]
232+
233+
const metric = agentMetrics[scoped ? 'scoped' : 'unscoped'][expectedMetric]
234+
t.ok(metric, 'metric "' + expectedMetric + '" should exist')
209235
if (!metric) {
210236
return
211237
}
@@ -217,11 +243,54 @@ test('Cassandra instrumentation', { timeout: 5000 }, async function testInstrume
217243
t.ok(metric.max, 'should have set max')
218244
t.ok(metric.sumOfSquares, 'should have set sumOfSquares')
219245
}
220-
})
246+
}
247+
}
221248

222-
t.teardown(function tearDown() {
223-
helper.unloadAgent(agent)
224-
client.shutdown()
225-
})
249+
function verifyTrace(t, trace, table) {
250+
t.ok(trace, 'trace should exist')
251+
t.ok(trace.root, 'root element should exist')
252+
253+
const setSegment = findSegment(
254+
trace.root,
255+
'Datastore/statement/Cassandra/' + table + '/insert/batch'
256+
)
257+
258+
t.ok(setSegment, 'trace segment for insert should exist')
259+
260+
if (setSegment) {
261+
verifyTraceSegment(t, setSegment, 'insert/batch')
262+
263+
t.ok(
264+
setSegment.children.length >= 2,
265+
'set should have at least a dns lookup and callback/promise child'
266+
)
267+
268+
const getSegment = findSegment(
269+
trace.root,
270+
'Datastore/statement/Cassandra/' + table + '/select'
271+
)
272+
t.ok(getSegment, 'trace segment for select should exist')
273+
274+
if (getSegment) {
275+
verifyTraceSegment(t, getSegment, 'select')
276+
277+
t.ok(getSegment.children.length >= 1, 'get should have a callback/promise segment')
278+
t.ok(getSegment.timer.hrDuration, 'trace segment should have ended')
279+
}
280+
}
281+
}
282+
283+
function verifyTraceSegment(t, segment, queryType) {
284+
t.equal(
285+
segment.name,
286+
'Datastore/statement/Cassandra/' + KS + '.' + FAM + '/' + queryType,
287+
'should register the execute'
288+
)
289+
290+
const segmentAttributes = segment.getAttributes()
291+
t.equal(segmentAttributes.product, 'Cassandra', 'should set product attribute')
292+
t.equal(segmentAttributes.port_path_or_id, '9042', 'should set port attribute')
293+
t.equal(segmentAttributes.database_name, 'test', 'should set database_name attribute')
294+
t.equal(segmentAttributes.host, agent.config.getHostnameSafe(), 'should set host attribute')
226295
}
227296
})

0 commit comments

Comments
 (0)
Please sign in to comment.