use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ChannelStrategy {
#[serde(rename = "fixed")]
#[default]
Fixed,
#[serde(rename = "adaptive")]
Adaptive,
}
impl std::str::FromStr for ChannelStrategy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("fixed") {
Ok(ChannelStrategy::Fixed)
} else if s.eq_ignore_ascii_case("adaptive") {
Ok(ChannelStrategy::Adaptive)
} else {
let mut args = fluent_bundle::FluentArgs::new();
args.set("strategy", s);
args.set("valid", "fixed, adaptive");
Err(crate::i18n::tr_args(
"config-unknown_channel_strategy",
args,
))
}
}
}
impl std::fmt::Display for ChannelStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChannelStrategy::Fixed => write!(f, "fixed"),
ChannelStrategy::Adaptive => write!(f, "adaptive"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PerformanceConfig {
#[serde(default = "default_channel_capacity")]
pub channel_capacity: usize,
#[serde(default = "default_worker_threads")]
pub worker_threads: usize,
#[serde(default)]
pub channel_strategy: ChannelStrategy,
#[serde(default = "default_expand_threshold")]
pub expand_threshold_percent: u8,
#[serde(default = "default_shrink_threshold")]
pub shrink_threshold_percent: u8,
#[serde(default = "default_shrink_wait")]
pub shrink_wait_seconds: u64,
#[serde(default = "default_min_capacity")]
pub min_capacity: usize,
#[serde(default = "default_max_capacity")]
pub max_capacity: usize,
#[serde(default)]
pub rate_limit: Option<u64>,
}
fn default_channel_capacity() -> usize {
10000
}
fn default_worker_threads() -> usize {
3
}
fn default_expand_threshold() -> u8 {
80
}
fn default_shrink_threshold() -> u8 {
20
}
fn default_shrink_wait() -> u64 {
30
}
fn default_min_capacity() -> usize {
1000
}
fn default_max_capacity() -> usize {
50000
}
impl Default for PerformanceConfig {
fn default() -> Self {
Self {
channel_capacity: default_channel_capacity(),
worker_threads: default_worker_threads(),
channel_strategy: ChannelStrategy::default(),
expand_threshold_percent: default_expand_threshold(),
shrink_threshold_percent: default_shrink_threshold(),
shrink_wait_seconds: default_shrink_wait(),
min_capacity: default_min_capacity(),
max_capacity: default_max_capacity(),
rate_limit: None,
}
}
}
impl PerformanceConfig {
pub fn validate(&mut self) {
self.expand_threshold_percent = self.expand_threshold_percent.min(100);
self.shrink_threshold_percent = self.shrink_threshold_percent.min(100);
if self.min_capacity > self.max_capacity {
tracing::warn!(
min = self.min_capacity,
max = self.max_capacity,
"min_capacity > max_capacity, swapping"
);
std::mem::swap(&mut self.min_capacity, &mut self.max_capacity);
}
if self.shrink_threshold_percent >= self.expand_threshold_percent {
tracing::warn!("{}", crate::i18n::tr("warn-threshold_reset"));
self.shrink_threshold_percent = default_shrink_threshold();
self.expand_threshold_percent = default_expand_threshold();
}
if let Some(rate) = self.rate_limit
&& rate == 0
{
tracing::warn!("{}", crate::i18n::tr("warn-rate_limit_zero"));
self.rate_limit = None;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_channel_strategy_from_str() {
assert_eq!(
"fixed".parse::<ChannelStrategy>().unwrap(),
ChannelStrategy::Fixed
);
assert_eq!(
"FIXED".parse::<ChannelStrategy>().unwrap(),
ChannelStrategy::Fixed
);
assert_eq!(
"adaptive".parse::<ChannelStrategy>().unwrap(),
ChannelStrategy::Adaptive
);
assert_eq!(
"Adaptive".parse::<ChannelStrategy>().unwrap(),
ChannelStrategy::Adaptive
);
assert!("unknown".parse::<ChannelStrategy>().is_err());
}
#[test]
fn test_channel_strategy_display() {
assert_eq!(ChannelStrategy::Fixed.to_string(), "fixed");
assert_eq!(ChannelStrategy::Adaptive.to_string(), "adaptive");
}
#[test]
fn test_performance_config_default() {
let cfg = PerformanceConfig::default();
assert_eq!(cfg.channel_capacity, 10000);
assert_eq!(cfg.worker_threads, 3);
assert_eq!(cfg.expand_threshold_percent, 80);
assert_eq!(cfg.shrink_threshold_percent, 20);
assert_eq!(cfg.min_capacity, 1000);
assert_eq!(cfg.max_capacity, 50000);
}
#[test]
fn test_validate_clamps_percentages() {
let mut cfg = PerformanceConfig::default();
cfg.expand_threshold_percent = 150;
cfg.shrink_threshold_percent = 50;
cfg.validate();
assert_eq!(cfg.expand_threshold_percent, 100);
assert_eq!(cfg.shrink_threshold_percent, 50);
}
#[test]
fn test_validate_swaps_min_max() {
let mut cfg = PerformanceConfig::default();
cfg.min_capacity = 99999;
cfg.max_capacity = 100;
cfg.validate();
assert_eq!(cfg.min_capacity, 100);
assert_eq!(cfg.max_capacity, 99999);
}
#[test]
fn test_validate_resets_shrink_ge_expand() {
let mut cfg = PerformanceConfig::default();
cfg.shrink_threshold_percent = 80;
cfg.expand_threshold_percent = 50;
cfg.validate();
assert_eq!(cfg.shrink_threshold_percent, default_shrink_threshold());
assert_eq!(cfg.expand_threshold_percent, default_expand_threshold());
}
#[test]
fn test_validate_rate_limit_zero_reset_to_none() {
let mut cfg = PerformanceConfig::default();
cfg.rate_limit = Some(0);
cfg.validate();
assert_eq!(cfg.rate_limit, None);
}
#[test]
fn test_validate_rate_limit_positive_preserved() {
let mut cfg = PerformanceConfig::default();
cfg.rate_limit = Some(10000);
cfg.validate();
assert_eq!(cfg.rate_limit, Some(10000));
}
#[test]
fn test_validate_rate_limit_none_unchanged() {
let mut cfg = PerformanceConfig::default();
cfg.rate_limit = None;
cfg.validate();
assert_eq!(cfg.rate_limit, None);
}
}