1#![allow(dead_code)]
40
41use std::sync::atomic::{AtomicU64, Ordering};
42use std::time::Instant;
43
44#[derive(Debug, Clone)]
46pub struct LogEntry {
47 pub term: u64,
49 pub index: u64,
51 pub command: String,
53}
54
55impl LogEntry {
56 #[must_use]
58 pub fn new(term: u64, index: u64, command: impl Into<String>) -> Self {
59 Self {
60 term,
61 index,
62 command: command.into(),
63 }
64 }
65
66 #[must_use]
68 pub fn is_valid(&self) -> bool {
69 self.term > 0 && self.index > 0
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum RaftRole {
76 Leader,
78 Follower,
80 Candidate,
82}
83
84impl RaftRole {
85 #[must_use]
87 pub fn can_accept_writes(&self) -> bool {
88 matches!(self, Self::Leader)
89 }
90
91 #[must_use]
93 pub fn name(&self) -> &str {
94 match self {
95 Self::Leader => "Leader",
96 Self::Follower => "Follower",
97 Self::Candidate => "Candidate",
98 }
99 }
100}
101
102impl std::fmt::Display for RaftRole {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 write!(f, "{}", self.name())
105 }
106}
107
108#[derive(Debug)]
110pub struct RaftState {
111 pub current_term: u64,
113 pub voted_for: Option<String>,
115 pub commit_index: u64,
117 pub last_applied: u64,
119 pub role: RaftRole,
121}
122
123impl RaftState {
124 #[must_use]
126 pub fn new() -> Self {
127 Self {
128 current_term: 0,
129 voted_for: None,
130 commit_index: 0,
131 last_applied: 0,
132 role: RaftRole::Follower,
133 }
134 }
135
136 pub fn advance_term(&mut self, new_term: u64) {
138 if new_term > self.current_term {
139 self.current_term = new_term;
140 self.voted_for = None;
141 }
142 }
143
144 pub fn become_candidate(&mut self) {
146 self.current_term += 1;
147 self.role = RaftRole::Candidate;
148 self.voted_for = None;
149 }
150
151 pub fn become_leader(&mut self) {
153 self.role = RaftRole::Leader;
154 }
155
156 pub fn become_follower(&mut self, term: u64) {
158 self.current_term = term;
159 self.role = RaftRole::Follower;
160 self.voted_for = None;
161 }
162
163 pub fn vote_for(&mut self, candidate_id: impl Into<String>) {
165 self.voted_for = Some(candidate_id.into());
166 }
167
168 pub fn update_commit_index(&mut self, index: u64) {
170 if index > self.commit_index {
171 self.commit_index = index;
172 }
173 }
174
175 pub fn apply_up_to(&mut self, index: u64) {
177 if index <= self.commit_index && index > self.last_applied {
178 self.last_applied = index;
179 }
180 }
181}
182
183impl Default for RaftState {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[derive(Debug, Default)]
191pub struct RaftLog {
192 pub entries: Vec<LogEntry>,
194}
195
196impl RaftLog {
197 #[must_use]
199 pub fn new() -> Self {
200 Self {
201 entries: Vec::new(),
202 }
203 }
204
205 pub fn append(&mut self, entry: LogEntry) {
207 self.entries.push(entry);
208 }
209
210 #[must_use]
212 pub fn get(&self, index: u64) -> Option<&LogEntry> {
213 if index == 0 {
214 return None;
215 }
216 self.entries.get((index - 1) as usize)
217 }
218
219 #[must_use]
221 pub fn last_index(&self) -> u64 {
222 self.entries.len() as u64
223 }
224
225 #[must_use]
227 pub fn last_term(&self) -> u64 {
228 self.entries.last().map_or(0, |e| e.term)
229 }
230
231 #[must_use]
233 pub fn committed_entries(&self, commit_index: u64) -> Vec<&LogEntry> {
234 self.entries
235 .iter()
236 .filter(|e| e.index <= commit_index)
237 .collect()
238 }
239
240 pub fn truncate_after(&mut self, last_kept_index: u64) {
242 self.entries.retain(|e| e.index <= last_kept_index);
243 }
244
245 #[must_use]
247 pub fn is_empty(&self) -> bool {
248 self.entries.is_empty()
249 }
250}
251
252#[derive(Debug, Default)]
261pub struct RaftMetrics {
262 propose_commit_samples: AtomicU64,
264 propose_commit_sum_us: AtomicU64,
266 propose_commit_max_us: AtomicU64,
268 heartbeat_samples: AtomicU64,
270 heartbeat_sum_us: AtomicU64,
272 heartbeat_max_us: AtomicU64,
274}
275
276#[derive(Debug, Clone)]
278pub struct RaftMetricsSnapshot {
279 pub propose_commit_samples: u64,
281 pub propose_commit_avg_ms: f64,
283 pub propose_commit_max_ms: f64,
285 pub heartbeat_samples: u64,
287 pub heartbeat_rtt_avg_ms: f64,
289 pub heartbeat_rtt_max_ms: f64,
291}
292
293impl RaftMetrics {
294 #[must_use]
296 pub fn new() -> Self {
297 Self::default()
298 }
299
300 pub fn record_propose_commit(&self, propose_start: Instant) {
305 let elapsed_us = propose_start.elapsed().as_micros() as u64;
306 self.propose_commit_samples.fetch_add(1, Ordering::Relaxed);
307 self.propose_commit_sum_us
308 .fetch_add(elapsed_us, Ordering::Relaxed);
309 let mut current = self.propose_commit_max_us.load(Ordering::Relaxed);
311 while elapsed_us > current {
312 match self.propose_commit_max_us.compare_exchange_weak(
313 current,
314 elapsed_us,
315 Ordering::Relaxed,
316 Ordering::Relaxed,
317 ) {
318 Ok(_) => break,
319 Err(c) => current = c,
320 }
321 }
322 }
323
324 pub fn record_heartbeat_rtt(&self, send_start: Instant) {
327 let elapsed_us = send_start.elapsed().as_micros() as u64;
328 self.heartbeat_samples.fetch_add(1, Ordering::Relaxed);
329 self.heartbeat_sum_us
330 .fetch_add(elapsed_us, Ordering::Relaxed);
331 let mut current = self.heartbeat_max_us.load(Ordering::Relaxed);
332 while elapsed_us > current {
333 match self.heartbeat_max_us.compare_exchange_weak(
334 current,
335 elapsed_us,
336 Ordering::Relaxed,
337 Ordering::Relaxed,
338 ) {
339 Ok(_) => break,
340 Err(c) => current = c,
341 }
342 }
343 }
344
345 pub fn record_propose_commit_us(&self, latency_us: u64) {
348 self.propose_commit_samples.fetch_add(1, Ordering::Relaxed);
349 self.propose_commit_sum_us
350 .fetch_add(latency_us, Ordering::Relaxed);
351 let mut current = self.propose_commit_max_us.load(Ordering::Relaxed);
352 while latency_us > current {
353 match self.propose_commit_max_us.compare_exchange_weak(
354 current,
355 latency_us,
356 Ordering::Relaxed,
357 Ordering::Relaxed,
358 ) {
359 Ok(_) => break,
360 Err(c) => current = c,
361 }
362 }
363 }
364
365 pub fn record_heartbeat_rtt_us(&self, rtt_us: u64) {
367 self.heartbeat_samples.fetch_add(1, Ordering::Relaxed);
368 self.heartbeat_sum_us.fetch_add(rtt_us, Ordering::Relaxed);
369 let mut current = self.heartbeat_max_us.load(Ordering::Relaxed);
370 while rtt_us > current {
371 match self.heartbeat_max_us.compare_exchange_weak(
372 current,
373 rtt_us,
374 Ordering::Relaxed,
375 Ordering::Relaxed,
376 ) {
377 Ok(_) => break,
378 Err(c) => current = c,
379 }
380 }
381 }
382
383 #[must_use]
385 pub fn report(&self) -> RaftMetricsSnapshot {
386 let pc_samples = self.propose_commit_samples.load(Ordering::Relaxed);
387 let pc_sum = self.propose_commit_sum_us.load(Ordering::Relaxed);
388 let pc_max = self.propose_commit_max_us.load(Ordering::Relaxed);
389
390 let hb_samples = self.heartbeat_samples.load(Ordering::Relaxed);
391 let hb_sum = self.heartbeat_sum_us.load(Ordering::Relaxed);
392 let hb_max = self.heartbeat_max_us.load(Ordering::Relaxed);
393
394 let pc_avg_ms = if pc_samples > 0 {
395 pc_sum as f64 / pc_samples as f64 / 1000.0
396 } else {
397 0.0
398 };
399
400 let hb_avg_ms = if hb_samples > 0 {
401 hb_sum as f64 / hb_samples as f64 / 1000.0
402 } else {
403 0.0
404 };
405
406 RaftMetricsSnapshot {
407 propose_commit_samples: pc_samples,
408 propose_commit_avg_ms: pc_avg_ms,
409 propose_commit_max_ms: pc_max as f64 / 1000.0,
410 heartbeat_samples: hb_samples,
411 heartbeat_rtt_avg_ms: hb_avg_ms,
412 heartbeat_rtt_max_ms: hb_max as f64 / 1000.0,
413 }
414 }
415
416 pub fn reset(&self) {
418 self.propose_commit_samples.store(0, Ordering::Relaxed);
419 self.propose_commit_sum_us.store(0, Ordering::Relaxed);
420 self.propose_commit_max_us.store(0, Ordering::Relaxed);
421 self.heartbeat_samples.store(0, Ordering::Relaxed);
422 self.heartbeat_sum_us.store(0, Ordering::Relaxed);
423 self.heartbeat_max_us.store(0, Ordering::Relaxed);
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_log_entry_is_valid() {
433 assert!(LogEntry::new(1, 1, "cmd").is_valid());
434 assert!(!LogEntry::new(0, 1, "cmd").is_valid()); assert!(!LogEntry::new(1, 0, "cmd").is_valid()); assert!(!LogEntry::new(0, 0, "cmd").is_valid());
437 }
438
439 #[test]
440 fn test_raft_role_can_accept_writes() {
441 assert!(RaftRole::Leader.can_accept_writes());
442 assert!(!RaftRole::Follower.can_accept_writes());
443 assert!(!RaftRole::Candidate.can_accept_writes());
444 }
445
446 #[test]
447 fn test_raft_role_display() {
448 assert_eq!(RaftRole::Leader.to_string(), "Leader");
449 assert_eq!(RaftRole::Follower.to_string(), "Follower");
450 assert_eq!(RaftRole::Candidate.to_string(), "Candidate");
451 }
452
453 #[test]
454 fn test_raft_state_initial() {
455 let state = RaftState::new();
456 assert_eq!(state.current_term, 0);
457 assert!(state.voted_for.is_none());
458 assert_eq!(state.commit_index, 0);
459 assert_eq!(state.last_applied, 0);
460 assert_eq!(state.role, RaftRole::Follower);
461 }
462
463 #[test]
464 fn test_raft_state_advance_term() {
465 let mut state = RaftState::new();
466 state.vote_for("node1");
467 state.advance_term(5);
468 assert_eq!(state.current_term, 5);
469 assert!(state.voted_for.is_none());
471
472 state.advance_term(3);
474 assert_eq!(state.current_term, 5);
475 }
476
477 #[test]
478 fn test_raft_state_become_candidate() {
479 let mut state = RaftState::new();
480 state.become_candidate();
481 assert_eq!(state.current_term, 1);
482 assert_eq!(state.role, RaftRole::Candidate);
483 }
484
485 #[test]
486 fn test_raft_state_become_leader() {
487 let mut state = RaftState::new();
488 state.become_candidate();
489 state.become_leader();
490 assert_eq!(state.role, RaftRole::Leader);
491 }
492
493 #[test]
494 fn test_raft_state_become_follower() {
495 let mut state = RaftState::new();
496 state.become_leader();
497 state.become_follower(7);
498 assert_eq!(state.role, RaftRole::Follower);
499 assert_eq!(state.current_term, 7);
500 assert!(state.voted_for.is_none());
501 }
502
503 #[test]
504 fn test_raft_state_update_commit_index() {
505 let mut state = RaftState::new();
506 state.update_commit_index(5);
507 assert_eq!(state.commit_index, 5);
508 state.update_commit_index(3);
510 assert_eq!(state.commit_index, 5);
511 }
512
513 #[test]
514 fn test_raft_state_apply_up_to() {
515 let mut state = RaftState::new();
516 state.update_commit_index(10);
517 state.apply_up_to(7);
518 assert_eq!(state.last_applied, 7);
519 state.apply_up_to(15);
521 assert_eq!(state.last_applied, 7);
522 }
523
524 #[test]
525 fn test_raft_log_append_and_get() {
526 let mut log = RaftLog::new();
527 assert!(log.is_empty());
528 assert_eq!(log.last_index(), 0);
529 assert_eq!(log.last_term(), 0);
530
531 log.append(LogEntry::new(1, 1, "set x=1"));
532 log.append(LogEntry::new(1, 2, "set y=2"));
533 log.append(LogEntry::new(2, 3, "set z=3"));
534
535 assert_eq!(log.last_index(), 3);
536 assert_eq!(log.last_term(), 2);
537 assert!(!log.is_empty());
538 }
539
540 #[test]
541 fn test_raft_log_get_valid_index() {
542 let mut log = RaftLog::new();
543 log.append(LogEntry::new(1, 1, "cmd1"));
544 log.append(LogEntry::new(2, 2, "cmd2"));
545
546 let e = log.get(1).expect("get should return a value");
547 assert_eq!(e.command, "cmd1");
548 assert_eq!(e.term, 1);
549 }
550
551 #[test]
552 fn test_raft_log_get_invalid_index() {
553 let log = RaftLog::new();
554 assert!(log.get(0).is_none());
555 assert!(log.get(1).is_none());
556 }
557
558 #[test]
559 fn test_raft_log_committed_entries() {
560 let mut log = RaftLog::new();
561 log.append(LogEntry::new(1, 1, "a"));
562 log.append(LogEntry::new(1, 2, "b"));
563 log.append(LogEntry::new(2, 3, "c"));
564
565 let committed = log.committed_entries(2);
566 assert_eq!(committed.len(), 2);
567 assert_eq!(committed[0].command, "a");
568 assert_eq!(committed[1].command, "b");
569 }
570
571 #[test]
572 fn test_raft_log_truncate_after() {
573 let mut log = RaftLog::new();
574 log.append(LogEntry::new(1, 1, "a"));
575 log.append(LogEntry::new(1, 2, "b"));
576 log.append(LogEntry::new(2, 3, "c"));
577
578 log.truncate_after(2);
579 assert_eq!(log.last_index(), 2);
580 assert!(log.get(3).is_none());
581 }
582
583 #[test]
586 fn test_raft_metrics_captures_latency() {
587 let metrics = RaftMetrics::new();
588
589 metrics.record_propose_commit_us(2_000); metrics.record_propose_commit_us(4_000); metrics.record_propose_commit_us(6_000); metrics.record_heartbeat_rtt_us(500); metrics.record_heartbeat_rtt_us(1_500); let snap = metrics.report();
599
600 assert_eq!(snap.propose_commit_samples, 3);
601 assert!((snap.propose_commit_avg_ms - 4.0).abs() < 0.01);
603 assert!((snap.propose_commit_max_ms - 6.0).abs() < 0.01);
605
606 assert_eq!(snap.heartbeat_samples, 2);
607 assert!((snap.heartbeat_rtt_avg_ms - 1.0).abs() < 0.01);
609 assert!((snap.heartbeat_rtt_max_ms - 1.5).abs() < 0.01);
611 }
612
613 #[test]
614 fn test_raft_metrics_empty_report() {
615 let metrics = RaftMetrics::new();
616 let snap = metrics.report();
617 assert_eq!(snap.propose_commit_samples, 0);
618 assert_eq!(snap.heartbeat_samples, 0);
619 assert_eq!(snap.propose_commit_avg_ms, 0.0);
620 assert_eq!(snap.heartbeat_rtt_avg_ms, 0.0);
621 }
622
623 #[test]
624 fn test_raft_metrics_reset() {
625 let metrics = RaftMetrics::new();
626 metrics.record_propose_commit_us(1_000);
627 metrics.record_heartbeat_rtt_us(500);
628 metrics.reset();
629
630 let snap = metrics.report();
631 assert_eq!(snap.propose_commit_samples, 0);
632 assert_eq!(snap.heartbeat_samples, 0);
633 }
634
635 #[test]
636 fn test_raft_metrics_instant_recording() {
637 let metrics = RaftMetrics::new();
638 let t = Instant::now();
639 std::thread::sleep(std::time::Duration::from_micros(100));
641 metrics.record_propose_commit(t);
642 let snap = metrics.report();
643 assert_eq!(snap.propose_commit_samples, 1);
644 assert!(snap.propose_commit_max_ms >= 0.0);
645 }
646
647 #[test]
648 fn test_raft_metrics_max_tracks_correctly() {
649 let metrics = RaftMetrics::new();
650 metrics.record_propose_commit_us(100);
651 metrics.record_propose_commit_us(9_000); metrics.record_propose_commit_us(500);
653
654 let snap = metrics.report();
655 assert!((snap.propose_commit_max_ms - 9.0).abs() < 0.01);
656 }
657}