#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SizeAlert {
Ok,
Warning(String),
Error(String),
}
#[derive(Debug, Clone)]
pub struct SizeThresholds {
pub warn_threshold: usize,
pub error_threshold: usize,
}
impl SizeThresholds {
pub const DEFAULT: Self = Self {
warn_threshold: 1_572_864, error_threshold: 2_097_152, };
#[must_use]
pub const fn new(warn_threshold: usize, error_threshold: usize) -> Self {
Self {
warn_threshold,
error_threshold,
}
}
}
impl Default for SizeThresholds {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Debug)]
pub struct CheckpointSizeMonitor {
thresholds: SizeThresholds,
}
impl CheckpointSizeMonitor {
#[must_use]
pub const fn new() -> Self {
Self {
thresholds: SizeThresholds::DEFAULT,
}
}
#[must_use]
pub const fn with_thresholds(thresholds: SizeThresholds) -> Self {
Self { thresholds }
}
#[must_use]
pub fn check_size(&self, size_bytes: usize) -> SizeAlert {
if size_bytes >= self.thresholds.error_threshold {
SizeAlert::Error(format!(
"Checkpoint size {} bytes exceeds hard limit {} bytes. \
Consider reducing execution_history_limit in config.",
size_bytes, self.thresholds.error_threshold
))
} else if size_bytes >= self.thresholds.warn_threshold {
let pct_of_error_threshold: u128 = if self.thresholds.error_threshold == 0 {
100
} else {
(size_bytes as u128).saturating_mul(100) / (self.thresholds.error_threshold as u128)
};
SizeAlert::Warning(format!(
"Checkpoint size {} bytes exceeds warning threshold {} bytes; \
current size is {}% of hard limit {} bytes.",
size_bytes,
self.thresholds.warn_threshold,
pct_of_error_threshold,
self.thresholds.error_threshold
))
} else {
SizeAlert::Ok
}
}
#[must_use]
pub fn check_json(&self, json: &str) -> SizeAlert {
self.check_size(json.len())
}
#[deprecated(since = "0.7.3", note = "Use check_json(json) and log at the callsite")]
#[must_use]
pub fn check_json_and_log(&self, json: &str) -> SizeAlert {
self.check_json(json)
}
#[must_use]
pub const fn thresholds(&self) -> &SizeThresholds {
&self.thresholds
}
}
impl Default for CheckpointSizeMonitor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_alert_ok_for_small_checkpoints() {
let monitor = CheckpointSizeMonitor::new();
let alert = monitor.check_size(363_000);
assert_eq!(alert, SizeAlert::Ok);
}
#[test]
fn test_size_alert_warning_approaching_limit() {
let monitor = CheckpointSizeMonitor::new();
let alert = monitor.check_size(1_600_000);
match alert {
SizeAlert::Warning(msg) => {
assert!(msg.contains("1600000"));
assert!(msg.contains("warning threshold"));
assert!(msg.contains("hard limit"));
}
_ => panic!("Expected Warning, got {alert:?}"),
}
}
#[test]
fn test_size_alert_error_exceeds_limit() {
let monitor = CheckpointSizeMonitor::new();
let alert = monitor.check_size(2_100_000);
match alert {
SizeAlert::Error(msg) => {
assert!(msg.contains("2100000"));
assert!(msg.contains("exceeds hard limit"));
}
_ => panic!("Expected Error, got {alert:?}"),
}
}
#[test]
fn test_custom_thresholds() {
let thresholds = SizeThresholds::new(1_000_000, 1_500_000);
let monitor = CheckpointSizeMonitor::with_thresholds(thresholds);
assert_eq!(monitor.check_size(900_000), SizeAlert::Ok);
let alert = monitor.check_size(1_100_000);
assert!(matches!(alert, SizeAlert::Warning(_)));
let alert = monitor.check_size(1_600_000);
assert!(matches!(alert, SizeAlert::Error(_)));
}
#[test]
fn test_check_json() {
let monitor = CheckpointSizeMonitor::new();
let small_json = "x".repeat(100_000); let alert = monitor.check_json(&small_json);
assert_eq!(alert, SizeAlert::Ok);
let large_json = "x".repeat(1_600_000); let alert = monitor.check_json(&large_json);
assert!(matches!(alert, SizeAlert::Warning(_)));
}
#[test]
fn test_warning_percentage_calculation_does_not_overflow() {
let thresholds = SizeThresholds::new(1, usize::MAX);
let monitor = CheckpointSizeMonitor::with_thresholds(thresholds);
let result = std::panic::catch_unwind(|| monitor.check_size(usize::MAX - 1));
assert!(result.is_ok(), "check_size must not panic on large inputs");
let alert = result.unwrap();
assert!(matches!(alert, SizeAlert::Warning(_)));
}
#[test]
fn test_thresholds_default() {
let thresholds = SizeThresholds::default();
assert_eq!(thresholds.warn_threshold, 1_572_864);
assert_eq!(thresholds.error_threshold, 2_097_152);
}
#[test]
fn test_monitor_default() {
let monitor = CheckpointSizeMonitor::default();
assert_eq!(
monitor.thresholds().warn_threshold,
SizeThresholds::DEFAULT.warn_threshold
);
}
}