rustfs_io_core/
lock_optimizer.rs1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
21use std::time::{Duration, Instant};
22
23#[derive(Debug, Clone)]
25pub struct LockOptimizeConfig {
26 pub enabled: bool,
28 pub acquire_timeout: Duration,
30 pub max_hold_time_warning: Duration,
32 pub adaptive_spin: bool,
34 pub max_spin_iterations: usize,
36}
37
38impl Default for LockOptimizeConfig {
39 fn default() -> Self {
40 Self {
41 enabled: true,
42 acquire_timeout: Duration::from_secs(5),
43 max_hold_time_warning: Duration::from_millis(100),
44 adaptive_spin: true,
45 max_spin_iterations: 1000,
46 }
47 }
48}
49
50#[derive(Debug, Default)]
52pub struct LockStats {
53 pub locks_acquired: AtomicU64,
55 pub locks_released_early: AtomicU64,
57 pub total_hold_time_ns: AtomicU64,
59 pub max_hold_time_ns: AtomicU64,
61 pub contentions: AtomicU64,
63 pub spin_successes: AtomicU64,
65 pub spin_failures: AtomicU64,
67}
68
69impl LockStats {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn record_acquire(&self) {
77 self.locks_acquired.fetch_add(1, Ordering::Relaxed);
78 }
79
80 pub fn record_release(&self, hold_time: Duration) {
82 let ns = hold_time.as_nanos() as u64;
83 self.total_hold_time_ns.fetch_add(ns, Ordering::Relaxed);
84
85 let mut current = self.max_hold_time_ns.load(Ordering::Relaxed);
87 while ns > current {
88 match self
89 .max_hold_time_ns
90 .compare_exchange_weak(current, ns, Ordering::Relaxed, Ordering::Relaxed)
91 {
92 Ok(_) => break,
93 Err(actual) => current = actual,
94 }
95 }
96 }
97
98 pub fn record_early_release(&self) {
100 self.locks_released_early.fetch_add(1, Ordering::Relaxed);
101 }
102
103 pub fn record_contention(&self) {
105 self.contentions.fetch_add(1, Ordering::Relaxed);
106 }
107
108 pub fn record_spin_success(&self) {
110 self.spin_successes.fetch_add(1, Ordering::Relaxed);
111 }
112
113 pub fn record_spin_failure(&self) {
115 self.spin_failures.fetch_add(1, Ordering::Relaxed);
116 }
117
118 pub fn total_acquired(&self) -> u64 {
120 self.locks_acquired.load(Ordering::Relaxed)
121 }
122
123 pub fn avg_hold_time(&self) -> Duration {
125 let total = self.total_hold_time_ns.load(Ordering::Relaxed);
126 let count = self.locks_acquired.load(Ordering::Relaxed);
127 total.checked_div(count).map(Duration::from_nanos).unwrap_or(Duration::ZERO)
128 }
129
130 pub fn max_hold_time(&self) -> Duration {
132 Duration::from_nanos(self.max_hold_time_ns.load(Ordering::Relaxed))
133 }
134
135 pub fn contention_rate(&self) -> f64 {
137 let acquired = self.locks_acquired.load(Ordering::Relaxed);
138 let contentions = self.contentions.load(Ordering::Relaxed);
139 if acquired == 0 {
140 0.0
141 } else {
142 contentions as f64 / acquired as f64
143 }
144 }
145
146 pub fn spin_success_rate(&self) -> f64 {
148 let successes = self.spin_successes.load(Ordering::Relaxed);
149 let failures = self.spin_failures.load(Ordering::Relaxed);
150 let total = successes + failures;
151 if total == 0 { 0.0 } else { successes as f64 / total as f64 }
152 }
153
154 pub fn reset(&self) {
156 self.locks_acquired.store(0, Ordering::Relaxed);
157 self.locks_released_early.store(0, Ordering::Relaxed);
158 self.total_hold_time_ns.store(0, Ordering::Relaxed);
159 self.max_hold_time_ns.store(0, Ordering::Relaxed);
160 self.contentions.store(0, Ordering::Relaxed);
161 self.spin_successes.store(0, Ordering::Relaxed);
162 self.spin_failures.store(0, Ordering::Relaxed);
163 }
164}
165
166pub struct LockOptimizer {
168 config: LockOptimizeConfig,
170 stats: LockStats,
172 current_spin: AtomicUsize,
174}
175
176impl LockOptimizer {
177 pub fn new(config: LockOptimizeConfig) -> Self {
179 Self {
180 config,
181 stats: LockStats::new(),
182 current_spin: AtomicUsize::new(100),
183 }
184 }
185
186 pub fn with_defaults() -> Self {
188 Self::new(LockOptimizeConfig::default())
189 }
190
191 pub fn config(&self) -> &LockOptimizeConfig {
193 &self.config
194 }
195
196 pub fn stats(&self) -> &LockStats {
198 &self.stats
199 }
200
201 pub fn on_acquire(&self) {
203 if !self.config.enabled {
204 return;
205 }
206 self.stats.record_acquire();
207 }
208
209 pub fn on_release(&self, hold_time: Duration) {
211 if !self.config.enabled {
212 return;
213 }
214 self.stats.record_release(hold_time);
215
216 if hold_time < self.config.acquire_timeout / 2 {
218 self.stats.record_early_release();
219 }
220 }
221
222 pub fn on_contention(&self) {
224 if !self.config.enabled {
225 return;
226 }
227 self.stats.record_contention();
228 }
229
230 pub fn try_spin<F>(&self, mut try_acquire: F) -> bool
234 where
235 F: FnMut() -> bool,
236 {
237 if !self.config.enabled || !self.config.adaptive_spin {
238 return false;
239 }
240
241 let spin_count = self.current_spin.load(Ordering::Relaxed).min(self.config.max_spin_iterations);
242
243 for _ in 0..spin_count {
244 if try_acquire() {
245 self.stats.record_spin_success();
246 self.adapt_spin(true);
247 return true;
248 }
249 std::hint::spin_loop();
251 }
252
253 self.stats.record_spin_failure();
254 self.adapt_spin(false);
255 false
256 }
257
258 fn adapt_spin(&self, success: bool) {
260 let current = self.current_spin.load(Ordering::Relaxed);
261 let new_count = if success {
262 (current * 2).min(self.config.max_spin_iterations)
264 } else {
265 (current / 2).max(10)
267 };
268 self.current_spin.store(new_count, Ordering::Relaxed);
269 }
270
271 pub fn current_spin_count(&self) -> usize {
273 self.current_spin.load(Ordering::Relaxed)
274 }
275
276 pub fn is_hold_time_excessive(&self, hold_time: Duration) -> bool {
278 hold_time > self.config.max_hold_time_warning
279 }
280
281 pub fn reset_stats(&self) {
283 self.stats.reset();
284 }
285}
286
287pub struct LockGuard<'a> {
289 optimizer: &'a LockOptimizer,
290 start: Instant,
291}
292
293impl<'a> LockGuard<'a> {
294 pub fn new(optimizer: &'a LockOptimizer) -> Self {
296 optimizer.on_acquire();
297 Self {
298 optimizer,
299 start: Instant::now(),
300 }
301 }
302}
303
304impl Drop for LockGuard<'_> {
305 fn drop(&mut self) {
306 self.optimizer.on_release(self.start.elapsed());
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn test_lock_stats() {
316 let stats = LockStats::new();
317
318 stats.record_acquire();
319 stats.record_acquire();
320 stats.record_release(Duration::from_millis(10));
321 stats.record_release(Duration::from_millis(20));
322
323 assert_eq!(stats.total_acquired(), 2);
324 assert!(stats.avg_hold_time() >= Duration::from_millis(15));
325 assert_eq!(stats.max_hold_time(), Duration::from_millis(20));
326 }
327
328 #[test]
329 fn test_contention_rate() {
330 let stats = LockStats::new();
331
332 stats.record_acquire();
333 stats.record_acquire();
334 stats.record_acquire();
335 stats.record_contention();
336
337 assert!((stats.contention_rate() - 0.3333333333333333).abs() < 0.01);
338 }
339
340 #[test]
341 fn test_spin_stats() {
342 let stats = LockStats::new();
343
344 stats.record_spin_success();
345 stats.record_spin_success();
346 stats.record_spin_failure();
347
348 assert!((stats.spin_success_rate() - 0.6666666666666666).abs() < 0.01);
349 }
350
351 #[test]
352 fn test_lock_optimizer() {
353 let optimizer = LockOptimizer::with_defaults();
354
355 {
356 let _guard = LockGuard::new(&optimizer);
357 std::thread::sleep(Duration::from_millis(10));
358 }
359
360 assert_eq!(optimizer.stats().total_acquired(), 1);
361 assert!(optimizer.stats().avg_hold_time() >= Duration::from_millis(10));
362 }
363
364 #[test]
365 fn test_adaptive_spin() {
366 let optimizer = LockOptimizer::with_defaults();
367
368 let acquired = optimizer.try_spin(|| true);
370 assert!(acquired);
371 assert!(optimizer.current_spin_count() > 100); let acquired = optimizer.try_spin(|| false);
375 assert!(!acquired);
376 assert!(optimizer.current_spin_count() < 200); }
378
379 #[test]
380 fn test_disabled_optimizer() {
381 let config = LockOptimizeConfig {
382 enabled: false,
383 ..Default::default()
384 };
385 let optimizer = LockOptimizer::new(config);
386
387 optimizer.on_acquire();
388 optimizer.on_release(Duration::from_millis(10));
389
390 assert_eq!(optimizer.stats().total_acquired(), 0);
392 }
393}