@@ -2,8 +2,8 @@ use candidate_selection::Normalized;
22
33#[ derive( Clone , Debug , Default ) ]
44pub struct Performance {
5- fast : Frame ,
6- slow : Frame ,
5+ fast : ShortTerm ,
6+ slow : LongTerm ,
77}
88
99const FAST_BIAS : f64 = 0.8 ;
@@ -25,14 +25,8 @@ impl Performance {
2525 }
2626
2727 pub fn feedback ( & mut self , success : bool , latency_ms : u16 ) {
28- for frame in [ & mut self . fast , & mut self . slow ] {
29- frame. total_latency_ms += latency_ms as f64 ;
30- if success {
31- frame. success_count += 1.0 ;
32- } else {
33- frame. failure_count += 1.0 ;
34- }
35- }
28+ self . fast . feedback ( success, latency_ms) ;
29+ self . slow . feedback ( success, latency_ms) ;
3630 }
3731
3832 pub fn decay ( & mut self ) {
@@ -41,26 +35,28 @@ impl Performance {
4135 }
4236
4337 fn success_rate ( & self ) -> Normalized {
44- let fast = self . fast . success_rate ( ) . as_f64 ( ) ;
45- let slow = self . slow . success_rate ( ) . as_f64 ( ) ;
46- Normalized :: new ( ( fast * FAST_BIAS ) + ( slow * ( 1.0 - FAST_BIAS ) ) ) . unwrap_or ( Normalized :: ONE )
38+ let fast = self . fast . success_rate ( ) ;
39+ let slow = self . slow . success_rate ( ) ;
40+ let success_rate = ( fast * FAST_BIAS ) + ( slow * ( 1.0 - FAST_BIAS ) ) ;
41+ // limit an individual indexer's success rate to 99%
42+ Normalized :: new ( success_rate. min ( 0.99 ) ) . unwrap ( )
4743 }
4844
4945 fn latency_ms ( & self ) -> u16 {
5046 let fast = self . fast . latency_ms ( ) as f64 ;
51- let slow = self . slow . latency_ms ( ) as f64 ;
47+ let slow = self . slow . latency_percentile ( 99 ) as f64 ;
5248 ( ( fast * FAST_BIAS ) + ( slow * ( 1.0 - FAST_BIAS ) ) ) as u16
5349 }
5450}
5551
5652#[ derive( Clone , Debug , Default ) ]
57- struct Frame {
53+ struct ShortTerm {
5854 total_latency_ms : f64 ,
5955 success_count : f64 ,
6056 failure_count : f64 ,
6157}
6258
63- impl Frame {
59+ impl ShortTerm {
6460 fn decay ( & mut self , rate_hz : f64 ) {
6561 debug_assert ! ( ( 0.0 < rate_hz) && ( rate_hz < 1.0 ) ) ;
6662 let retain = 1.0 - rate_hz;
@@ -69,13 +65,20 @@ impl Frame {
6965 self . failure_count *= retain;
7066 }
7167
72- fn success_rate ( & self ) -> Normalized {
73- // add 1 to pull success rate upward
68+ fn feedback ( & mut self , success : bool , latency_ms : u16 ) {
69+ self . total_latency_ms += latency_ms as f64 ;
70+ if success {
71+ self . success_count += 1.0 ;
72+ } else {
73+ self . failure_count += 1.0 ;
74+ }
75+ }
76+
77+ fn success_rate ( & self ) -> f64 {
78+ // add 1 to pull success rate upward, and avoid divide by zero
7479 let s = self . success_count + 1.0 ;
7580 let f = self . failure_count ;
76- let p = s / ( s + f) ;
77- // limit an individual indexer's success rate to 99%
78- Normalized :: new ( p. min ( 0.99 ) ) . unwrap ( )
81+ s / ( s + f)
7982 }
8083
8184 fn latency_ms ( & self ) -> u16 {
@@ -84,3 +87,71 @@ impl Frame {
8487 avg_latency_ms as u16
8588 }
8689}
90+
91+ #[ derive( Clone , Debug , Default ) ]
92+ struct LongTerm {
93+ latency_hist : [ f32 ; 29 ] ,
94+ failure_count : f64 ,
95+ }
96+
97+ const LATENCY_BINS : [ u16 ; 29 ] = [
98+ 32 , 64 , 96 , 128 , // + 2^5
99+ 192 , 256 , 320 , 384 , // + 2^6
100+ 512 , 640 , 768 , 896 , // + 2^7
101+ 1152 , 1408 , 1664 , 1920 , // + 2^8
102+ 2432 , 2944 , 3456 , 3968 , // + 2^9
103+ 4992 , 6016 , 7040 , 8064 , // + 2^10
104+ 10112 , 12160 , 14208 , 16256 , // + 2^11
105+ 20352 , // + 2^12
106+ ] ;
107+
108+ impl LongTerm {
109+ fn decay ( & mut self , rate_hz : f64 ) {
110+ debug_assert ! ( ( 0.0 < rate_hz) && ( rate_hz < 1.0 ) ) ;
111+ let retain = 1.0 - rate_hz;
112+ self . failure_count *= retain;
113+ for count in & mut self . latency_hist {
114+ * count *= retain as f32 ;
115+ }
116+ }
117+
118+ fn feedback ( & mut self , success : bool , latency_ms : u16 ) {
119+ if !success {
120+ self . failure_count += 1.0 ;
121+ }
122+
123+ for ( count, bin_value) in self
124+ . latency_hist
125+ . iter_mut ( )
126+ . zip ( & LATENCY_BINS )
127+ . take ( LATENCY_BINS . len ( ) - 1 )
128+ {
129+ if latency_ms <= * bin_value {
130+ * count += 1.0 ;
131+ return ;
132+ }
133+ }
134+ * self . latency_hist . last_mut ( ) . unwrap ( ) += 1.0 ;
135+ }
136+
137+ fn success_rate ( & self ) -> f64 {
138+ // add 1 to pull success rate upward, and avoid divide by zero
139+ let total = self . latency_hist . iter ( ) . map ( |c| * c as f64 ) . sum :: < f64 > ( ) + 1.0 ;
140+ let s = total - self . failure_count ;
141+ let f = self . failure_count ;
142+ s / ( s + f)
143+ }
144+
145+ pub fn latency_percentile ( & self , p : u8 ) -> u16 {
146+ debug_assert ! ( ( 1 ..=99 ) . contains( & p) ) ;
147+ let target = self . latency_hist . iter ( ) . map ( |c| * c as f64 ) . sum :: < f64 > ( ) * ( p as f64 / 100.0 ) ;
148+ let mut sum = 0.0 ;
149+ for ( count, bin_value) in self . latency_hist . iter ( ) . zip ( & LATENCY_BINS ) {
150+ sum += * count as f64 ;
151+ if sum >= target {
152+ return * bin_value;
153+ }
154+ }
155+ panic ! ( "failed to calculate latency percentile" ) ;
156+ }
157+ }
0 commit comments