mirror of
https://github.com/rustdesk/hbb_common.git
synced 2026-08-28 13:01:12 +00:00
feat: add LogThrottle for sites whose rate a peer controls
Debug output is written to the log file, so a log site that fires per received message lets whoever is sending decide how much a machine writes to disk. Dropping the line instead would hide real faults, so collapse it: one line per interval carrying the count of everything suppressed since the last one, with the first occurrence after a quiet period always reported so an isolated fault is not delayed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
This commit is contained in:
@@ -57,6 +57,7 @@ pub use toml;
|
|||||||
pub use uuid;
|
pub use uuid;
|
||||||
pub mod fingerprint;
|
pub mod fingerprint;
|
||||||
pub use flexi_logger;
|
pub use flexi_logger;
|
||||||
|
pub mod log_throttle;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
#[cfg(feature = "webrtc")]
|
#[cfg(feature = "webrtc")]
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// Collapses a log site whose call rate is set by someone else — a peer's message rate, or a
|
||||||
|
/// retry loop — into at most one line per interval.
|
||||||
|
///
|
||||||
|
/// Debug output is written to the log file, so a site that fires per received packet lets a
|
||||||
|
/// peer decide how much a machine writes to disk. Dropping the line entirely instead would
|
||||||
|
/// hide real faults, so keep one line per interval and carry the count of everything
|
||||||
|
/// suppressed since the last one.
|
||||||
|
///
|
||||||
|
/// Declare one per site (they do not share counts):
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// static DROPPED_ICE: LogThrottle = LogThrottle::new(Duration::from_secs(60));
|
||||||
|
///
|
||||||
|
/// if let Some(n) = DROPPED_ICE.due() {
|
||||||
|
/// log::debug!("dropped {n} ICE candidate(s) with no route");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct LogThrottle {
|
||||||
|
interval: Duration,
|
||||||
|
state: Mutex<ThrottleState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThrottleState {
|
||||||
|
suppressed: u64,
|
||||||
|
last: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogThrottle {
|
||||||
|
pub const fn new(interval: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
interval,
|
||||||
|
state: Mutex::new(ThrottleState {
|
||||||
|
suppressed: 0,
|
||||||
|
last: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record one occurrence. Returns the number of occurrences to report (including this one)
|
||||||
|
/// when a line is due, or `None` while still inside the interval.
|
||||||
|
///
|
||||||
|
/// The first occurrence after a quiet period always reports, so an isolated fault is not
|
||||||
|
/// delayed by the interval.
|
||||||
|
pub fn due(&self) -> Option<u64> {
|
||||||
|
let Ok(mut state) = self.state.lock() else {
|
||||||
|
// A poisoned mutex means another thread panicked mid-update; the count is not worth
|
||||||
|
// propagating that, and staying silent is better than logging per call.
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
state.suppressed += 1;
|
||||||
|
let due = state
|
||||||
|
.last
|
||||||
|
.map_or(true, |last| last.elapsed() >= self.interval);
|
||||||
|
if !due {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
state.last = Some(Instant::now());
|
||||||
|
Some(std::mem::replace(&mut state.suppressed, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_call_reports_immediately() {
|
||||||
|
let t = LogThrottle::new(Duration::from_secs(60));
|
||||||
|
assert_eq!(t.due(), Some(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calls_inside_the_interval_are_counted_not_reported() {
|
||||||
|
let t = LogThrottle::new(Duration::from_secs(60));
|
||||||
|
assert_eq!(t.due(), Some(1));
|
||||||
|
for _ in 0..100 {
|
||||||
|
assert_eq!(t.due(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_next_due_line_carries_everything_suppressed() {
|
||||||
|
let t = LogThrottle::new(Duration::ZERO);
|
||||||
|
assert_eq!(t.due(), Some(1));
|
||||||
|
// A zero interval is always due, so each call reports exactly itself.
|
||||||
|
assert_eq!(t.due(), Some(1));
|
||||||
|
|
||||||
|
let t = LogThrottle::new(Duration::from_millis(30));
|
||||||
|
assert_eq!(t.due(), Some(1));
|
||||||
|
assert_eq!(t.due(), None);
|
||||||
|
assert_eq!(t.due(), None);
|
||||||
|
std::thread::sleep(Duration::from_millis(40));
|
||||||
|
// The two suppressed calls plus this one.
|
||||||
|
assert_eq!(t.due(), Some(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user