11use crate :: router:: MatchVerdict ;
22use async_trait:: async_trait;
3- use metrics:: { counter, gauge, histogram} ;
3+ use metrics:: { SharedString , counter, gauge, histogram} ;
44use metrics_exporter_prometheus:: PrometheusBuilder ;
55use pingora:: services:: Service ;
66use std:: collections:: { HashMap , VecDeque } ;
@@ -48,6 +48,7 @@ impl<T: MetricsTransport> PrometheusEndpoint<T> {
4848 Ok ( handle) => {
4949 let registry = MetricsRegistry {
5050 _handle : Arc :: new ( handle) ,
51+ labels : Arc :: new ( MetricLabels :: new ( ) ) ,
5152 } ;
5253 (
5354 Self {
@@ -196,9 +197,12 @@ async fn serve_metrics(
196197#[ derive( Clone ) ]
197198pub struct MetricsRegistry {
198199 _handle : Arc < metrics_exporter_prometheus:: PrometheusHandle > ,
200+ labels : Arc < MetricLabels > ,
199201}
200202
201203pub const POOL_KEY_CARDINALITY_CAP : usize = 1024 ;
204+ const METRIC_LABEL_CACHE_CAP : usize = 1024 ;
205+ const METRIC_ROUTE_LABEL_CACHE_CAP : usize = 4096 ;
202206const METRICS_REQUEST_LINE_LIMIT_BYTES : usize = 4096 ;
203207const METRICS_HEADER_LIMIT_BYTES : usize = 16 * 1024 ;
204208const METRICS_READ_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
@@ -215,6 +219,87 @@ struct BoundedKeySet {
215219 order : VecDeque < ( u64 , Instant ) > ,
216220}
217221
222+ struct MetricLabels {
223+ common : LabelCache ,
224+ route : LabelCache ,
225+ status : Mutex < HashMap < u16 , SharedString > > ,
226+ }
227+
228+ impl MetricLabels {
229+ fn new ( ) -> Self {
230+ Self {
231+ common : LabelCache :: new ( METRIC_LABEL_CACHE_CAP ) ,
232+ route : LabelCache :: new ( METRIC_ROUTE_LABEL_CACHE_CAP ) ,
233+ status : Mutex :: new ( HashMap :: new ( ) ) ,
234+ }
235+ }
236+
237+ fn common ( & self , value : & str ) -> SharedString {
238+ self . common . get ( value)
239+ }
240+
241+ fn route ( & self , value : & str ) -> SharedString {
242+ self . route . get ( value)
243+ }
244+
245+ fn status ( & self , value : u16 ) -> SharedString {
246+ let mut guard = self
247+ . status
248+ . lock ( )
249+ . expect ( "metrics status cache lock poisoned" ) ;
250+ if let Some ( existing) = guard. get ( & value) {
251+ return existing. clone ( ) ;
252+ }
253+ let shared = SharedString :: from ( value. to_string ( ) ) ;
254+ guard. insert ( value, shared. clone ( ) ) ;
255+ shared
256+ }
257+ }
258+
259+ struct LabelCache {
260+ cap : usize ,
261+ inner : Mutex < LabelCacheInner > ,
262+ }
263+
264+ struct LabelCacheInner {
265+ map : HashMap < String , SharedString > ,
266+ order : VecDeque < String > ,
267+ }
268+
269+ impl LabelCache {
270+ fn new ( cap : usize ) -> Self {
271+ Self {
272+ cap,
273+ inner : Mutex :: new ( LabelCacheInner {
274+ map : HashMap :: new ( ) ,
275+ order : VecDeque :: new ( ) ,
276+ } ) ,
277+ }
278+ }
279+
280+ fn get ( & self , value : & str ) -> SharedString {
281+ let mut guard = self
282+ . inner
283+ . lock ( )
284+ . expect ( "metrics label cache lock poisoned" ) ;
285+ if let Some ( existing) = guard. map . get ( value) {
286+ return existing. clone ( ) ;
287+ }
288+ let shared = SharedString :: from ( value. to_string ( ) ) ;
289+ let key = value. to_string ( ) ;
290+ guard. map . insert ( key. clone ( ) , shared. clone ( ) ) ;
291+ guard. order . push_back ( key) ;
292+ while guard. map . len ( ) > self . cap {
293+ if let Some ( evicted) = guard. order . pop_front ( ) {
294+ guard. map . remove ( & evicted) ;
295+ } else {
296+ break ;
297+ }
298+ }
299+ shared
300+ }
301+ }
302+
218303impl BoundedKeySet {
219304 fn new ( cap : usize ) -> Self {
220305 Self {
@@ -369,36 +454,43 @@ impl MetricsRegistry {
369454 upstream : & str ,
370455 duration_secs : f64 ,
371456 ) {
457+ let method = self . labels . common ( method) ;
458+ let route = self . labels . route ( route_pattern) ;
459+ let status = self . labels . status ( status) ;
460+ let upstream = self . labels . common ( upstream) ;
461+
372462 counter ! (
373463 "pavis_http_requests_total" ,
374- "method" => method. to_string ( ) ,
375- "route" => route_pattern . to_string ( ) ,
376- "status" => status. to_string ( ) ,
377- "upstream" => upstream. to_string ( ) ,
464+ "method" => method,
465+ "route" => route ,
466+ "status" => status. clone ( ) ,
467+ "upstream" => upstream. clone ( ) ,
378468 )
379469 . increment ( 1 ) ;
380470
381471 histogram ! (
382472 "pavis_http_request_duration_seconds" ,
383- "method" => method. to_string ( ) ,
384- "route" => route_pattern . to_string ( ) ,
385- "status" => status. to_string ( ) ,
386- "upstream" => upstream. to_string ( ) ,
473+ "method" => method,
474+ "route" => route ,
475+ "status" => status,
476+ "upstream" => upstream,
387477 )
388478 . record ( duration_secs) ;
389479 }
390480
391481 pub fn record_upstream_request ( & self , upstream : & str , status : u16 , duration_secs : f64 ) {
482+ let upstream = self . labels . common ( upstream) ;
483+ let status = self . labels . status ( status) ;
392484 counter ! (
393485 "pavis_upstream_requests_total" ,
394- "upstream" => upstream. to_string ( ) ,
395- "status" => status. to_string ( ) ,
486+ "upstream" => upstream. clone ( ) ,
487+ "status" => status. clone ( ) ,
396488 )
397489 . increment ( 1 ) ;
398490
399491 histogram ! (
400492 "pavis_upstream_request_duration_seconds" ,
401- "upstream" => upstream. to_string ( ) ,
493+ "upstream" => upstream,
402494 )
403495 . record ( duration_secs) ;
404496 }
@@ -413,41 +505,47 @@ impl MetricsRegistry {
413505 }
414506
415507 pub fn record_pool_size ( & self , upstream : & str , size : f64 ) {
416- gauge ! ( "pavis_upstream_pool_size" , "upstream" => upstream. to_string( ) ) . set ( size) ;
508+ let upstream = self . labels . common ( upstream) ;
509+ gauge ! ( "pavis_upstream_pool_size" , "upstream" => upstream) . set ( size) ;
417510 }
418511
419512 pub fn record_pool_key_cardinality ( & self , upstream : & str , cardinality : usize , saturated : bool ) {
513+ let upstream = self . labels . common ( upstream) ;
420514 let reported = if saturated {
421515 ( POOL_KEY_CARDINALITY_CAP + 1 ) as f64
422516 } else {
423517 cardinality as f64
424518 } ;
425519 gauge ! (
426520 "pavis_upstream_pool_key_cardinality_approx" ,
427- "upstream" => upstream. to_string ( )
521+ "upstream" => upstream
428522 )
429523 . set ( reported) ;
430524 }
431525
432526 pub fn record_connection_reused ( & self , upstream : & str ) {
527+ let upstream = self . labels . common ( upstream) ;
433528 counter ! (
434529 "pavis_upstream_connection_reused_total" ,
435- "upstream" => upstream. to_string ( )
530+ "upstream" => upstream
436531 )
437532 . increment ( 1 ) ;
438533 }
439534
440535 pub fn record_connection_new ( & self , upstream : & str , reason : & str ) {
536+ let upstream = self . labels . common ( upstream) ;
537+ let reason = self . labels . common ( reason) ;
441538 counter ! (
442539 "pavis_upstream_connection_new_total" ,
443- "upstream" => upstream. to_string ( ) ,
444- "reason" => reason. to_string ( )
540+ "upstream" => upstream,
541+ "reason" => reason
445542 )
446543 . increment ( 1 ) ;
447544 }
448545
449546 pub fn update_config_stats ( & self , version : & str , size_bytes : u64 ) {
450- gauge ! ( "pavis_runtime_config_version" , "version" => version. to_string( ) ) . set ( 1.0 ) ;
547+ let version = self . labels . common ( version) ;
548+ gauge ! ( "pavis_runtime_config_version" , "version" => version) . set ( 1.0 ) ;
451549 gauge ! ( "pavis_runtime_config_size_bytes" ) . set ( size_bytes as f64 ) ;
452550 gauge ! ( "pavis_runtime_reload_last_timestamp" ) . set (
453551 std:: time:: SystemTime :: now ( )
@@ -462,18 +560,21 @@ impl MetricsRegistry {
462560 }
463561
464562 pub fn record_config_validation ( & self , result : & str , reason : & str ) {
563+ let result = self . labels . common ( result) ;
564+ let reason = self . labels . common ( reason) ;
465565 counter ! (
466566 "pavis_config_validation_total" ,
467- "result" => result. to_string ( ) ,
468- "reason" => reason. to_string ( ) ,
567+ "result" => result,
568+ "reason" => reason,
469569 )
470570 . increment ( 1 ) ;
471571 }
472572
473573 pub fn record_config_apply ( & self , result : & str ) {
574+ let result = self . labels . common ( result) ;
474575 counter ! (
475576 "pavis_config_apply_total" ,
476- "result" => result. to_string ( ) ,
577+ "result" => result,
477578 )
478579 . increment ( 1 ) ;
479580 }
0 commit comments