@@ -32,7 +32,9 @@ pub struct Metrics {
3232 request_latency : HistogramVec ,
3333 rpc_errors : IntCounterVec ,
3434 rpc_fallbacks : IntCounterVec ,
35+ db_query_duration : HistogramVec ,
3536 db_timeouts : IntCounterVec ,
37+ db_pool_exhaustion : IntCounterVec ,
3638 ledger_gaps : IntCounterVec ,
3739 email_dlq_size : IntGauge ,
3840 email_queue_depth : IntGauge ,
@@ -93,12 +95,33 @@ impl Metrics {
9395 )
9496 . context ( "rpc_fallbacks metric" ) ?;
9597
98+ let db_query_duration = HistogramVec :: new (
99+ prometheus:: HistogramOpts :: new (
100+ "db_query_duration_seconds" ,
101+ "Database query duration in seconds by query name" ,
102+ )
103+ . buckets ( vec ! [
104+ 0.01 , 0.05 , 0.1 , 0.25 , 0.5 , 1.0 , 2.5 , 5.0 ,
105+ ] ) ,
106+ & [ "query_name" ] ,
107+ )
108+ . context ( "db_query_duration metric" ) ?;
109+
96110 let db_timeouts = IntCounterVec :: new (
97111 prometheus:: Opts :: new ( "db_timeouts_total" , "DB queries that exceeded the timeout, by operation" ) ,
98112 & [ "operation" ] ,
99113 )
100114 . context ( "db_timeouts metric" ) ?;
101115
116+ let db_pool_exhaustion = IntCounterVec :: new (
117+ prometheus:: Opts :: new (
118+ "db_pool_exhaustion_total" ,
119+ "Number of times the connection pool was exhausted, by pool name" ,
120+ ) ,
121+ & [ "pool" ] ,
122+ )
123+ . context ( "db_pool_exhaustion metric" ) ?;
124+
102125 let ledger_gaps = IntCounterVec :: new (
103126 prometheus:: Opts :: new (
104127 "blockchain_ledger_gaps_total" ,
@@ -183,7 +206,9 @@ impl Metrics {
183206 registry. register ( Box :: new ( request_latency. clone ( ) ) ) ?;
184207 registry. register ( Box :: new ( rpc_errors. clone ( ) ) ) ?;
185208 registry. register ( Box :: new ( rpc_fallbacks. clone ( ) ) ) ?;
209+ registry. register ( Box :: new ( db_query_duration. clone ( ) ) ) ?;
186210 registry. register ( Box :: new ( db_timeouts. clone ( ) ) ) ?;
211+ registry. register ( Box :: new ( db_pool_exhaustion. clone ( ) ) ) ?;
187212 registry. register ( Box :: new ( ledger_gaps. clone ( ) ) ) ?;
188213 registry. register ( Box :: new ( email_dlq_size. clone ( ) ) ) ?;
189214 registry. register ( Box :: new ( email_queue_depth. clone ( ) ) ) ?;
@@ -202,7 +227,9 @@ impl Metrics {
202227 request_latency,
203228 rpc_errors,
204229 rpc_fallbacks,
230+ db_query_duration,
205231 db_timeouts,
232+ db_pool_exhaustion,
206233 ledger_gaps,
207234 email_dlq_size,
208235 email_queue_depth,
@@ -253,11 +280,23 @@ impl Metrics {
253280 self . rpc_fallbacks . with_label_values ( & [ & labels[ 0 ] ] ) . inc ( ) ;
254281 }
255282
283+ pub fn observe_db_query_duration ( & self , query_name : & str , duration : Duration ) {
284+ self . db_query_duration
285+ . with_label_values ( & [ query_name] )
286+ . observe ( duration. as_secs_f64 ( ) ) ;
287+ }
288+
256289 pub fn observe_db_timeout ( & self , operation : & str ) {
257290 let labels = normalize_label_values ( & [ operation] ) ;
258291 self . db_timeouts . with_label_values ( & [ & labels[ 0 ] ] ) . inc ( ) ;
259292 }
260293
294+ pub fn observe_db_pool_exhaustion ( & self , pool : & str ) {
295+ self . db_pool_exhaustion
296+ . with_label_values ( & [ pool] )
297+ . inc ( ) ;
298+ }
299+
261300 /// Record a ledger-gap event on `network`, incrementing the counter by `gap_size` ledgers.
262301 pub fn observe_ledger_gap ( & self , network : & str , gap_size : u32 ) {
263302 if gap_size > 0 {
@@ -387,6 +426,25 @@ impl Metrics {
387426mod tests {
388427 use super :: * ;
389428
429+ #[ test]
430+ fn observe_db_query_duration_records_histogram ( ) {
431+ let metrics = Metrics :: new ( ) . unwrap ( ) ;
432+ metrics. observe_db_query_duration ( "test_query" , Duration :: from_millis ( 100 ) ) ;
433+ let output = metrics. render ( ) . unwrap ( ) ;
434+ assert ! ( output. contains( "db_query_duration_seconds" ) ) ;
435+ assert ! ( output. contains( "query_name=\" test_query\" " ) ) ;
436+ }
437+
438+ #[ test]
439+ fn observe_db_pool_exhaustion_increments_counter ( ) {
440+ let metrics = Metrics :: new ( ) . unwrap ( ) ;
441+ metrics. observe_db_pool_exhaustion ( "api" ) ;
442+ let output = metrics. render ( ) . unwrap ( ) ;
443+ assert ! ( output. contains( "db_pool_exhaustion_total" ) ) ;
444+ assert ! ( output. contains( "pool=\" api\" " ) ) ;
445+ assert ! ( output. contains( "1" ) ) ;
446+ }
447+
390448 // ── normalize_label ────────────────────────────────────────────────────────
391449
392450 #[ test]
0 commit comments