@@ -2,8 +2,8 @@ use candidate_selection::Normalized;
2
2
3
3
#[ derive( Clone , Debug , Default ) ]
4
4
pub struct Performance {
5
- fast : Frame ,
6
- slow : Frame ,
5
+ fast : ShortTerm ,
6
+ slow : LongTerm ,
7
7
}
8
8
9
9
const FAST_BIAS : f64 = 0.8 ;
@@ -25,14 +25,8 @@ impl Performance {
25
25
}
26
26
27
27
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) ;
36
30
}
37
31
38
32
pub fn decay ( & mut self ) {
@@ -41,26 +35,28 @@ impl Performance {
41
35
}
42
36
43
37
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 ( )
47
43
}
48
44
49
45
fn latency_ms ( & self ) -> u16 {
50
46
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 ;
52
48
( ( fast * FAST_BIAS ) + ( slow * ( 1.0 - FAST_BIAS ) ) ) as u16
53
49
}
54
50
}
55
51
56
52
#[ derive( Clone , Debug , Default ) ]
57
- struct Frame {
53
+ struct ShortTerm {
58
54
total_latency_ms : f64 ,
59
55
success_count : f64 ,
60
56
failure_count : f64 ,
61
57
}
62
58
63
- impl Frame {
59
+ impl ShortTerm {
64
60
fn decay ( & mut self , rate_hz : f64 ) {
65
61
debug_assert ! ( ( 0.0 < rate_hz) && ( rate_hz < 1.0 ) ) ;
66
62
let retain = 1.0 - rate_hz;
@@ -69,13 +65,20 @@ impl Frame {
69
65
self . failure_count *= retain;
70
66
}
71
67
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
74
79
let s = self . success_count + 1.0 ;
75
80
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)
79
82
}
80
83
81
84
fn latency_ms ( & self ) -> u16 {
@@ -84,3 +87,71 @@ impl Frame {
84
87
avg_latency_ms as u16
85
88
}
86
89
}
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