@@ -73,6 +73,9 @@ pub struct DomainFronter {
7373 inflight : Arc < Mutex < HashMap < String , broadcast:: Sender < Vec < u8 > > > > > ,
7474 coalesced : AtomicU64 ,
7575 blacklist : Arc < std:: sync:: Mutex < HashMap < String , Instant > > > ,
76+ relay_calls : AtomicU64 ,
77+ relay_failures : AtomicU64 ,
78+ bytes_relayed : AtomicU64 ,
7679}
7780
7881const BLACKLIST_COOLDOWN_SECS : u64 = 600 ;
@@ -138,9 +141,27 @@ impl DomainFronter {
138141 inflight : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
139142 coalesced : AtomicU64 :: new ( 0 ) ,
140143 blacklist : Arc :: new ( std:: sync:: Mutex :: new ( HashMap :: new ( ) ) ) ,
144+ relay_calls : AtomicU64 :: new ( 0 ) ,
145+ relay_failures : AtomicU64 :: new ( 0 ) ,
146+ bytes_relayed : AtomicU64 :: new ( 0 ) ,
141147 } )
142148 }
143149
150+ pub fn snapshot_stats ( & self ) -> StatsSnapshot {
151+ let bl = self . blacklist . lock ( ) . unwrap ( ) ;
152+ StatsSnapshot {
153+ relay_calls : self . relay_calls . load ( Ordering :: Relaxed ) ,
154+ relay_failures : self . relay_failures . load ( Ordering :: Relaxed ) ,
155+ coalesced : self . coalesced . load ( Ordering :: Relaxed ) ,
156+ bytes_relayed : self . bytes_relayed . load ( Ordering :: Relaxed ) ,
157+ cache_hits : self . cache . hits ( ) ,
158+ cache_misses : self . cache . misses ( ) ,
159+ cache_bytes : self . cache . size ( ) ,
160+ blacklisted_scripts : bl. len ( ) ,
161+ total_scripts : self . script_ids . len ( ) ,
162+ }
163+ }
164+
144165 pub fn cache ( & self ) -> & ResponseCache {
145166 & self . cache
146167 }
@@ -287,6 +308,7 @@ impl DomainFronter {
287308 body : & [ u8 ] ,
288309 cache_key_opt : Option < & str > ,
289310 ) -> Vec < u8 > {
311+ self . relay_calls . fetch_add ( 1 , Ordering :: Relaxed ) ;
290312 let bytes = match timeout (
291313 Duration :: from_secs ( REQUEST_TIMEOUT_SECS ) ,
292314 self . do_relay_with_retry ( method, url, headers, body) ,
@@ -295,14 +317,17 @@ impl DomainFronter {
295317 {
296318 Ok ( Ok ( bytes) ) => bytes,
297319 Ok ( Err ( e) ) => {
320+ self . relay_failures . fetch_add ( 1 , Ordering :: Relaxed ) ;
298321 tracing:: error!( "Relay failed: {}" , e) ;
299322 return error_response ( 502 , & format ! ( "Relay error: {}" , e) ) ;
300323 }
301324 Err ( _) => {
325+ self . relay_failures . fetch_add ( 1 , Ordering :: Relaxed ) ;
302326 tracing:: error!( "Relay timeout" ) ;
303327 return error_response ( 504 , "Relay timeout" ) ;
304328 }
305329 } ;
330+ self . bytes_relayed . fetch_add ( bytes. len ( ) as u64 , Ordering :: Relaxed ) ;
306331
307332 if let Some ( k) = cache_key_opt {
308333 if let Some ( ttl) = parse_ttl ( & bytes, url) {
@@ -767,6 +792,46 @@ fn parse_relay_json(body: &[u8]) -> Result<Vec<u8>, FronterError> {
767792 Ok ( out)
768793}
769794
795+ #[ derive( Debug , Clone , Copy ) ]
796+ pub struct StatsSnapshot {
797+ pub relay_calls : u64 ,
798+ pub relay_failures : u64 ,
799+ pub coalesced : u64 ,
800+ pub bytes_relayed : u64 ,
801+ pub cache_hits : u64 ,
802+ pub cache_misses : u64 ,
803+ pub cache_bytes : usize ,
804+ pub blacklisted_scripts : usize ,
805+ pub total_scripts : usize ,
806+ }
807+
808+ impl StatsSnapshot {
809+ pub fn hit_rate ( & self ) -> f64 {
810+ let total = self . cache_hits + self . cache_misses ;
811+ if total == 0 {
812+ 0.0
813+ } else {
814+ ( self . cache_hits as f64 / total as f64 ) * 100.0
815+ }
816+ }
817+
818+ pub fn fmt_line ( & self ) -> String {
819+ format ! (
820+ "stats: relay={} ({}KB) failures={} coalesced={} cache={}/{} ({:.0}% hit, {}KB) scripts={}/{} active" ,
821+ self . relay_calls,
822+ self . bytes_relayed / 1024 ,
823+ self . relay_failures,
824+ self . coalesced,
825+ self . cache_hits,
826+ self . cache_hits + self . cache_misses,
827+ self . hit_rate( ) ,
828+ self . cache_bytes / 1024 ,
829+ self . total_scripts - self . blacklisted_scripts,
830+ self . total_scripts,
831+ )
832+ }
833+ }
834+
770835fn should_blacklist ( status : u16 , body : & str ) -> bool {
771836 if status == 429 || status == 403 {
772837 return true ;
0 commit comments