use std::time::{Duration, Instant};
fn format_duration(duration: Duration) -> String {
let total_secs = duration.as_secs();
if total_secs >= 3600 {
let hours = total_secs / 3600;
let minutes = (total_secs % 3600) / 60;
format!("{}h{}m", hours, minutes)
} else if total_secs >= 60 {
let minutes = total_secs / 60;
let seconds = total_secs % 60;
format!("{}m{}s", minutes, seconds)
} else if total_secs >= 1 {
format!("{}s", total_secs)
} else {
format!("{}ms", duration.as_millis())
}
}
#[derive(Debug, PartialEq, PartialOrd)]
pub enum Limit {
Rate(u32),
Duration(Duration),
}
#[derive(Debug)]
struct State {
count: u32,
duration: Duration,
last_timestamp: Option<Instant>,
}
impl State {
fn new() -> Self {
State {
count: 0,
duration: Duration::from_secs(0),
last_timestamp: None,
}
}
fn reset(&mut self) {
self.count = 0;
self.duration = Duration::from_secs(0);
self.last_timestamp = None;
}
fn exceeds_limit(&self, limit: &Limit) -> bool {
match limit {
Limit::Rate(limit_count) => self.count >= *limit_count,
Limit::Duration(limit_duration) => self.duration >= *limit_duration,
}
}
}
pub struct RateLog {
limit: Limit,
current: State,
message: String,
#[cfg(test)]
output: String,
}
impl RateLog {
pub fn new(limit: Limit) -> Self {
let current = State::new();
RateLog {
limit,
current,
message: String::new(),
#[cfg(test)]
output: String::new(),
}
}
pub fn log(&mut self, msg: &str) {
let now = Instant::now();
if self.message != msg {
self.message = msg.to_string();
self.current.reset();
println!("{msg}");
#[cfg(test)]
{
self.output.push_str(msg);
}
} else {
self.current.count += 1;
if let Some(last_call) = self.current.last_timestamp {
let elapsed = now.duration_since(last_call);
self.current.duration += elapsed;
}
if self.current.exceeds_limit(&self.limit) {
let output = format!(
"Message: \"{}\" repeat for {} times in the past {}",
msg,
self.current.count,
format_duration(self.current.duration)
);
self.current.reset();
println!("{output}");
#[cfg(test)]
{
self.output.push_str(&output);
}
}
}
self.current.last_timestamp = Some(now);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_log_exceed_time() {
let mut rate_log = RateLog::new(Limit::Rate(3));
rate_log.log("message1");
assert_eq!(rate_log.output, "message1");
rate_log.output.clear();
rate_log.log("message1");
assert_eq!(rate_log.output, "");
rate_log.log("message1");
assert_eq!(rate_log.output, "");
rate_log.log("message1");
assert_eq!(
rate_log.output,
"Message: \"message1\" repeat for 3 times in the past 0ms"
);
rate_log.output.clear();
rate_log.log("message1");
assert_eq!(rate_log.output, "");
rate_log.log("message1");
assert_eq!(rate_log.output, "");
rate_log.log("message1");
assert_eq!(
rate_log.output,
"Message: \"message1\" repeat for 3 times in the past 0ms"
);
rate_log.output.clear();
}
#[test]
fn test_rate_log_exceed_duration() {
use std::thread;
let mut rate_log = RateLog::new(Limit::Duration(Duration::from_millis(50)));
rate_log.log("message2");
assert_eq!(rate_log.output, "message2");
rate_log.output.clear();
thread::sleep(Duration::from_millis(20));
rate_log.log("message2");
assert_eq!(rate_log.output, "");
thread::sleep(Duration::from_millis(40));
rate_log.log("message2");
assert_eq!(
rate_log.output,
"Message: \"message2\" repeat for 2 times in the past 60ms"
);
rate_log.output.clear();
rate_log.log("message2");
assert_eq!(rate_log.output, "");
thread::sleep(Duration::from_millis(50));
rate_log.log("message2");
assert_eq!(
rate_log.output,
"Message: \"message2\" repeat for 2 times in the past 50ms"
);
rate_log.output.clear();
}
#[test]
fn test_format_duration() {
let duration_ms = Duration::from_millis(500);
assert_eq!(format_duration(duration_ms), "500ms");
let duration_s = Duration::from_secs(45);
assert_eq!(format_duration(duration_s), "45s");
let duration_min = Duration::from_secs(3 * 60 + 25); assert_eq!(format_duration(duration_min), "3m25s");
let duration_hour = Duration::from_secs(2 * 3600 + 45 * 60); assert_eq!(format_duration(duration_hour), "2h45m");
}
}