@@ -19,13 +19,16 @@ import {
19
19
InstrumentationNodeModuleDefinition ,
20
20
safeExecuteInTheMiddle ,
21
21
} from '@opentelemetry/instrumentation' ;
22
-
23
22
import {
24
23
context ,
25
24
trace ,
26
25
Span ,
27
26
SpanStatusCode ,
28
27
SpanKind ,
28
+ Histogram ,
29
+ ValueType ,
30
+ Attributes ,
31
+ HrTime ,
29
32
UpDownCounter ,
30
33
} from '@opentelemetry/api' ;
31
34
import type * as pgTypes from 'pg' ;
@@ -42,12 +45,27 @@ import * as utils from './utils';
42
45
import { addSqlCommenterComment } from '@opentelemetry/sql-common' ;
43
46
import { PACKAGE_NAME , PACKAGE_VERSION } from './version' ;
44
47
import { SpanNames } from './enums/SpanNames' ;
48
+ import {
49
+ hrTime ,
50
+ hrTimeDuration ,
51
+ hrTimeToMilliseconds ,
52
+ } from '@opentelemetry/core' ;
53
+ import {
54
+ DBSYSTEMVALUES_POSTGRESQL ,
55
+ SEMATTRS_DB_SYSTEM ,
56
+ ATTR_ERROR_TYPE ,
57
+ ATTR_SERVER_PORT ,
58
+ ATTR_SERVER_ADDRESS ,
59
+ } from '@opentelemetry/semantic-conventions' ;
45
60
import {
46
61
METRIC_DB_CLIENT_CONNECTION_COUNT ,
47
62
METRIC_DB_CLIENT_CONNECTION_PENDING_REQUESTS ,
63
+ METRIC_DB_CLIENT_OPERATION_DURATION ,
64
+ ATTR_DB_NAMESPACE ,
48
65
} from '@opentelemetry/semantic-conventions/incubating' ;
49
66
50
67
export class PgInstrumentation extends InstrumentationBase < PgInstrumentationConfig > {
68
+ private _operationDuration ! : Histogram ;
51
69
private _connectionsCount ! : UpDownCounter ;
52
70
private _connectionPendingRequests ! : UpDownCounter ;
53
71
// Pool events connect, acquire, release and remove can be called
@@ -66,6 +84,20 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
66
84
}
67
85
68
86
override _updateMetricInstruments ( ) {
87
+ this . _operationDuration = this . meter . createHistogram (
88
+ METRIC_DB_CLIENT_OPERATION_DURATION ,
89
+ {
90
+ description : 'Duration of database client operations.' ,
91
+ unit : 's' ,
92
+ valueType : ValueType . DOUBLE ,
93
+ advice : {
94
+ explicitBucketBoundaries : [
95
+ 0.001 , 0.005 , 0.01 , 0.05 , 0.1 , 0.5 , 1 , 5 , 10 ,
96
+ ] ,
97
+ } ,
98
+ }
99
+ ) ;
100
+
69
101
this . _connectionsCounter = {
70
102
idle : 0 ,
71
103
pending : 0 ,
@@ -188,6 +220,27 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
188
220
} ;
189
221
}
190
222
223
+ private recordOperationDuration ( attributes : Attributes , startTime : HrTime ) {
224
+ const metricsAttributes : Attributes = { } ;
225
+ const keysToCopy = [
226
+ SEMATTRS_DB_SYSTEM ,
227
+ ATTR_DB_NAMESPACE ,
228
+ ATTR_ERROR_TYPE ,
229
+ ATTR_SERVER_PORT ,
230
+ ATTR_SERVER_ADDRESS ,
231
+ ] ;
232
+
233
+ keysToCopy . forEach ( key => {
234
+ if ( key in attributes ) {
235
+ metricsAttributes [ key ] = attributes [ key ] ;
236
+ }
237
+ } ) ;
238
+
239
+ const durationSeconds =
240
+ hrTimeToMilliseconds ( hrTimeDuration ( startTime , hrTime ( ) ) ) / 1000 ;
241
+ this . _operationDuration . record ( durationSeconds , metricsAttributes ) ;
242
+ }
243
+
191
244
private _getClientQueryPatch ( ) {
192
245
const plugin = this ;
193
246
return ( original : typeof pgTypes . Client . prototype . query ) => {
@@ -196,6 +249,7 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
196
249
if ( utils . shouldSkipInstrumentation ( plugin . getConfig ( ) ) ) {
197
250
return original . apply ( this , args as never ) ;
198
251
}
252
+ const startTime = hrTime ( ) ;
199
253
200
254
// client.query(text, cb?), client.query(text, values, cb?), and
201
255
// client.query(configObj, cb?) are all valid signatures. We construct
@@ -221,6 +275,17 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
221
275
? ( arg0 as utils . ObjectWithText )
222
276
: undefined ;
223
277
278
+ const attributes : Attributes = {
279
+ [ SEMATTRS_DB_SYSTEM ] : DBSYSTEMVALUES_POSTGRESQL ,
280
+ [ ATTR_DB_NAMESPACE ] : this . database ,
281
+ [ ATTR_SERVER_PORT ] : this . connectionParameters . port ,
282
+ [ ATTR_SERVER_ADDRESS ] : this . connectionParameters . host ,
283
+ } ;
284
+
285
+ const recordDuration = ( ) => {
286
+ plugin . recordOperationDuration ( attributes , startTime ) ;
287
+ } ;
288
+
224
289
const instrumentationConfig = plugin . getConfig ( ) ;
225
290
226
291
const span = utils . handleConfigQuery . call (
@@ -251,7 +316,8 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
251
316
args [ args . length - 1 ] = utils . patchCallback (
252
317
instrumentationConfig ,
253
318
span ,
254
- args [ args . length - 1 ] as PostgresCallback // nb: not type safe.
319
+ args [ args . length - 1 ] as PostgresCallback , // nb: not type safe.
320
+ recordDuration
255
321
) ;
256
322
257
323
// If a parent span exists, bind the callback
@@ -266,7 +332,8 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
266
332
let callback = utils . patchCallback (
267
333
plugin . getConfig ( ) ,
268
334
span ,
269
- queryConfig . callback as PostgresCallback // nb: not type safe.
335
+ queryConfig . callback as PostgresCallback , // nb: not type safe.
336
+ recordDuration
270
337
) ;
271
338
272
339
// If a parent span existed, bind the callback
@@ -324,7 +391,6 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
324
391
try {
325
392
result = original . apply ( this , args as never ) ;
326
393
} catch ( e : unknown ) {
327
- // span.recordException(e);
328
394
span . setStatus ( {
329
395
code : SpanStatusCode . ERROR ,
330
396
message : utils . getErrorMessage ( e ) ,
0 commit comments