Skip to content

Commit 0051697

Browse files
authored
fix: Updated pg instrumentation to properly capture the TraceSegment duration for promise based queries (#3695)
1 parent 593fa15 commit 0051697

File tree

6 files changed

+98
-80
lines changed

6 files changed

+98
-80
lines changed

lib/subscribers/pg/connect.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,22 @@
66
'use strict'
77

88
const Subscriber = require('../base.js')
9+
const { wrapPromise } = require('../utils')
910

1011
/**
1112
* Subscribes to the `connect` event for PostgreSQL's (`pg`) `Client` class.
1213
*/
1314
class PgConnectSubscriber extends Subscriber {
1415
constructor({ agent, logger, channelName = 'nr_connect', packageName = 'pg' }) {
1516
super({ agent, logger, channelName, packageName })
16-
this.events = ['asyncEnd']
17+
this.events = ['asyncEnd', 'end']
1718
this.callback = -1
1819
}
1920

21+
end(data) {
22+
wrapPromise.call(this, data)
23+
}
24+
2025
handler(data, ctx) {
2126
return this.createSegment({
2227
name: 'connect',

lib/subscribers/pg/native-connect.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const PgConnectSubscriber = require('./connect.js')
1717
class PgNativeConnectSubscriber extends PgConnectSubscriber {
1818
constructor({ agent, logger, channelName = 'nr_nativeConnect' }) {
1919
super({ agent, logger, channelName })
20-
this.events = ['asyncStart', 'asyncEnd']
20+
this.events = ['asyncStart', 'asyncEnd', 'end']
2121
this.propagateContext = true
2222
}
2323
}

lib/subscribers/pg/native-query.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const PgQuerySubscriber = require('./query.js')
1414
class PgNativeQuerySubscriber extends PgQuerySubscriber {
1515
constructor({ agent, logger, channelName = 'nr_nativeQuery' }) {
1616
super({ agent, logger, channelName })
17-
this.events = ['asyncStart', 'asyncEnd']
17+
this.events = ['asyncStart', 'asyncEnd', 'end']
1818
this.propagateContext = true
1919
}
2020
}

lib/subscribers/pg/query.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const DbQuerySubscriber = require('../db-query.js')
99
const { POSTGRES } = require('#agentlib/metrics/names.js')
1010
const { EventEmitter } = require('node:events')
11+
const { wrapPromise } = require('../utils')
1112

1213
/**
1314
* Defaults to subscribing to the `query` event for PostgreSQL's (`pg`) `Client` class
@@ -18,11 +19,15 @@ const { EventEmitter } = require('node:events')
1819
class PgQuerySubscriber extends DbQuerySubscriber {
1920
constructor({ agent, logger, channelName = 'nr_query', packageName = 'pg' }) {
2021
super({ agent, logger, channelName, packageName, system: POSTGRES.PREFIX })
21-
this.events = ['asyncEnd']
22+
this.events = ['asyncEnd', 'end']
2223
this.operation = 'query'
2324
this.callback = -1
2425
}
2526

27+
end(data) {
28+
wrapPromise.call(this, data)
29+
}
30+
2631
/**
2732
* Extracts the PostgreSQL query from the function arguments.
2833
* @param {object[]} args arguments to the original Client.query call

lib/subscribers/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
function wrapPromise(data) {
2222
const promise = data?.result
23-
if (typeof promise.then !== 'function') {
23+
if (typeof promise?.then !== 'function') {
2424
return promise
2525
}
2626

test/versioned/pg/pg.common.js

Lines changed: 83 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ const tspl = require('@matteo.collina/tspl')
1111

1212
const params = require('../../lib/params')
1313
const helper = require('../../lib/agent_helper')
14-
const findSegment = require('../../lib/metrics_helper').findSegment
15-
const getMetricHostName = require('../../lib/metrics_helper').getMetricHostName
14+
const { findSegment, getMetricHostName } = require('../../lib/metrics_helper')
1615
const { assertPackageMetrics } = require('../../lib/custom-assertions')
1716

1817
function runCommand(client, cmd) {
@@ -275,25 +274,21 @@ module.exports = function runTests(name, clientFactory) {
275274
assert.equal(value.rows[0][COL], colVal, 'Postgres client should still work')
276275

277276
transaction.end()
278-
try {
279-
verify(assert, transaction)
280-
end()
281-
} catch (err) {
282-
end(err)
283-
}
277+
verify(assert, transaction)
278+
end()
284279
})
285280
})
286281
})
287282
})
288283
})
289284

290-
await t.test('Promise style query', (t, end) => {
285+
await t.test('Promise style query', async (t) => {
291286
const { agent, pg } = t.nr
292287
const client = new pg.Client(CON_OBJ)
293288

294289
t.after(() => client.end())
295290

296-
helper.runInTransaction(agent, async function transactionInScope(tx) {
291+
await helper.runInTransaction(agent, async function transactionInScope(tx) {
297292
const transaction = agent.getTransaction()
298293
assert.ok(transaction, 'transaction should be visible')
299294
assert.equal(tx, transaction, 'We got the same transaction')
@@ -303,30 +298,19 @@ module.exports = function runTests(name, clientFactory) {
303298
let insQuery = 'INSERT INTO ' + TABLE_PREPARED + ' (' + PK + ',' + COL
304299
insQuery += ') VALUES($1, $2);'
305300

306-
try {
307-
await client.connect()
308-
} catch (err) {
309-
assert.ifError(err)
310-
}
301+
await client.connect()
302+
const results = await client.query(insQuery, [pkVal, colVal])
311303

312-
try {
313-
const results = await client.query(insQuery, [pkVal, colVal])
304+
assert.ok(agent.getTransaction(), 'transaction should still be visible')
305+
assert.ok(results, 'everything should be peachy after setting')
314306

315-
assert.ok(agent.getTransaction(), 'transaction should still be visible')
316-
assert.ok(results, 'everything should be peachy after setting')
307+
const selQuery = `select * from ${TABLE_PREPARED} where ${PK} = ${pkVal}`
308+
const selectResults = await client.query(selQuery)
317309

318-
const selQuery = `select * from ${TABLE_PREPARED} where ${PK} = ${pkVal}`
319-
const selectResults = await client.query(selQuery)
320-
321-
assert.ok(agent.getTransaction(), 'transaction should still still be visible')
322-
assert.equal(selectResults.rows[0][COL], colVal, 'Postgres client should still work')
323-
transaction.end()
324-
verify(assert, transaction)
325-
end()
326-
} catch (err) {
327-
assert.ifError(err)
328-
end()
329-
}
310+
assert.ok(agent.getTransaction(), 'transaction should still still be visible')
311+
assert.equal(selectResults.rows[0][COL], colVal, 'Postgres client should still work')
312+
transaction.end()
313+
verify(assert, transaction)
330314
})
331315
})
332316

@@ -355,6 +339,15 @@ module.exports = function runTests(name, clientFactory) {
355339
})
356340

357341
pgQuery.on('end', () => {
342+
const connectSegment = findSegment(tx.trace, tx.trace.root, 'connect')
343+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
344+
assert.equal(connectSegment.timer.touched, true)
345+
const selectSegment = findSegment(tx.trace, tx.trace.root, 'Datastore/statement/Postgres/unknown/select')
346+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
347+
// since we know the duration, we're asserting it's within a range as well
348+
assert.equal(selectSegment.timer.touched, true)
349+
const duration = selectSegment.getDurationInMillis()
350+
assert.ok(duration > 2000 && duration < 2100)
358351
const finalTx = agent.getTransaction()
359352
assert.ok(finalTx, 'transaction should still be visible')
360353

@@ -372,79 +365,94 @@ module.exports = function runTests(name, clientFactory) {
372365
})
373366
})
374367

375-
await t.test('Promise style query timings', (t, end) => {
368+
await t.test('Promise style query timings', async (t) => {
376369
const { agent, pg } = t.nr
377370
const client = new pg.Client(CON_OBJ)
378371

379372
t.after(() => client.end())
380373

381-
helper.runInTransaction(agent, async function transactionInScope(tx) {
374+
await helper.runInTransaction(agent, async function transactionInScope(tx) {
382375
const transaction = agent.getTransaction()
383376
assert.ok(transaction, 'transaction should be visible')
384377
assert.equal(tx, transaction, 'We got the same transaction')
385378

386-
try {
387-
await client.connect()
388-
} catch (err) {
389-
assert.ifError(err)
390-
}
391-
392-
try {
393-
const selQuery = 'SELECT pg_sleep(2), now() as sleep;'
394-
395-
const selectResults = await client.query(selQuery)
396-
397-
const finalTx = agent.getTransaction()
398-
assert.ok(finalTx, 'transaction should still be visible')
399-
assert.ok(selectResults, 'Postgres client should still work')
400-
transaction.end()
401-
402-
const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select')
403-
assert.ok(
404-
metrics.total > 2.0,
405-
'Promise style query pg_sleep of 2 seconds should result in > 2 sec timing'
406-
)
379+
await client.connect()
380+
const connectSegment = findSegment(tx.trace, tx.trace.root, 'connect')
381+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
382+
assert.equal(connectSegment.timer.touched, true)
383+
const selQuery = 'SELECT pg_sleep(2), now() as sleep;'
407384

408-
end()
409-
} catch (err) {
410-
assert.ifError(err)
411-
end()
412-
}
385+
const selectResults = await client.query(selQuery)
386+
const selectSegment = findSegment(tx.trace, tx.trace.root, 'Datastore/statement/Postgres/unknown/select')
387+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
388+
// since we know the duration, we're asserting it's within a range as well
389+
assert.equal(selectSegment.timer.touched, true)
390+
const duration = selectSegment.getDurationInMillis()
391+
assert.ok(duration > 2000 && duration < 2100)
392+
393+
const finalTx = agent.getTransaction()
394+
assert.ok(finalTx, 'transaction should still be visible')
395+
assert.ok(selectResults, 'Postgres client should still work')
396+
transaction.end()
397+
398+
const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select')
399+
assert.ok(
400+
metrics.total > 2.0,
401+
'Promise style query pg_sleep of 2 seconds should result in > 2 sec timing'
402+
)
413403
})
414404
})
415405

416-
await t.test('Callback style query timings', (t, end) => {
406+
await t.test('Callback style query timings', async (t) => {
417407
const { agent, pg } = t.nr
418408
const client = new pg.Client(CON_OBJ)
419409

420410
t.after(() => client.end())
421411

422-
helper.runInTransaction(agent, async function transactionInScope(tx) {
412+
await helper.runInTransaction(agent, async function transactionInScope(tx) {
423413
const transaction = agent.getTransaction()
424414
assert.ok(transaction, 'transaction should be visible')
425415
assert.equal(tx, transaction, 'We got the same transaction')
426416

427-
client.connect(function (error) {
428-
assert.ifError(error)
417+
await new Promise((resolve) => {
418+
client.connect(function (error) {
419+
assert.ifError(error)
420+
resolve()
421+
})
422+
})
429423

424+
const connectSegment = findSegment(tx.trace, tx.trace.root, 'connect')
425+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
426+
assert.equal(connectSegment.timer.touched, true)
427+
428+
// wrapping in promise, otherwise any logic within callback
429+
// is counting in instrumented, we do not want to end tx in callback
430+
// as it will produce a truncated segment
431+
const result = await new Promise((resolve) => {
430432
const selQuery = 'SELECT pg_sleep(2), now() as sleep;'
431433

432434
client.query(selQuery, function (error, ok) {
433435
assert.ifError(error)
434-
const finalTx = agent.getTransaction()
435-
assert.ok(finalTx, 'transaction should still be visible')
436-
assert.ok(ok, 'everything should be peachy after setting')
437-
438-
transaction.end()
439-
const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select')
440-
assert.ok(
441-
metrics.total > 2.0,
442-
'Callback style query pg_sleep of 2 seconds should result in > 2 sec timing'
443-
)
444-
445-
end()
436+
resolve(ok)
446437
})
447438
})
439+
440+
const selectSegment = findSegment(tx.trace, tx.trace.root, 'Datastore/statement/Postgres/unknown/select')
441+
// asserting segment is touched to at least assert instrumentation touched it before transaction ends
442+
// since we know the duration, we're asserting it's within a range as well
443+
assert.equal(selectSegment.timer.touched, true)
444+
const duration = selectSegment.getDurationInMillis()
445+
assert.ok(duration > 2000 && duration < 2100)
446+
const finalTx = agent.getTransaction()
447+
assert.ok(finalTx, 'transaction should still be visible')
448+
assert.ok(result, 'everything should be peachy after setting')
449+
450+
transaction.end()
451+
const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select')
452+
assert.ok(
453+
metrics.total > 2.0,
454+
'Callback style query pg_sleep of 2 seconds should result in > 2 sec timing'
455+
)
448456
})
449457
})
450458

0 commit comments

Comments
 (0)