use std::fmt;
use std::str::FromStr;
use crate::error::{Error, Result};
use crate::route::QuickAckCheck;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Backend {
EpollSpin,
RecvBusy,
UringSpin,
UringSqpoll,
EpollBusy,
UringNapi,
}
impl Backend {
pub const ALL: [Self; 6] = [
Self::EpollSpin,
Self::RecvBusy,
Self::UringSpin,
Self::UringSqpoll,
Self::EpollBusy,
Self::UringNapi,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::EpollSpin => "epoll-spin",
Self::RecvBusy => "recv-busy",
Self::UringSpin => "uring-spin",
Self::UringSqpoll => "uring-sqpoll",
Self::EpollBusy => "epoll-busy",
Self::UringNapi => "uring-napi",
}
}
#[must_use]
pub const fn may_sleep(self) -> bool {
matches!(self, Self::EpollBusy | Self::UringNapi)
}
#[must_use]
pub const fn polls_nic(self) -> bool {
matches!(
self,
Self::RecvBusy | Self::UringSqpoll | Self::EpollBusy | Self::UringNapi
)
}
#[must_use]
pub const fn is_uring(self) -> bool {
matches!(self, Self::UringSpin | Self::UringSqpoll | Self::UringNapi)
}
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for Backend {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::ALL
.into_iter()
.find(|b| b.name() == s)
.ok_or_else(|| Error::Config(format!("unknown backend {s:?}")))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)] pub struct Config {
pub backend: Backend,
pub busy_poll_us: u32,
pub wait_us: u32,
pub loop_cpu: Option<u32>,
pub sqpoll_cpu: Option<u32>,
pub sqpoll_idle_ms: u32,
pub ring_entries: u32,
pub rcvbuf: Option<u32>,
pub rx_timestamps: bool,
pub connect_timeout_ms: u32,
pub connect_cpu: Option<u32>,
pub helper_cpus: Vec<u32>,
pub inline_connect_fallback: bool,
pub prefer_busy_poll: bool,
pub busy_poll_budget: Option<u16>,
pub per_queue: bool,
pub quickack_check: QuickAckCheck,
pub keepalive_idle_s: u32,
pub keepalive_interval_s: u32,
pub keepalive_count: u32,
pub tcp_user_timeout_ms: u32,
}
impl Config {
#[must_use]
pub const fn new(backend: Backend) -> Self {
let (busy_poll_us, wait_us) = match backend {
Backend::EpollBusy | Backend::UringNapi => (50, 100),
Backend::RecvBusy | Backend::UringSqpoll => (1, 0),
Backend::EpollSpin | Backend::UringSpin => (0, 0),
};
Self {
backend,
busy_poll_us,
wait_us,
loop_cpu: None,
sqpoll_cpu: None,
sqpoll_idle_ms: 600_000,
ring_entries: 256,
rcvbuf: Some(4 << 20),
rx_timestamps: true,
connect_timeout_ms: 10_000,
connect_cpu: None,
helper_cpus: Vec::new(),
inline_connect_fallback: false,
prefer_busy_poll: false,
busy_poll_budget: None,
per_queue: false,
quickack_check: QuickAckCheck::Warn,
keepalive_idle_s: 10,
keepalive_interval_s: 5,
keepalive_count: 3,
tcp_user_timeout_ms: 25_000,
}
}
pub fn validate(&self) -> Result<()> {
if i32::try_from(self.tcp_user_timeout_ms).is_err() {
return Err(Error::Config(format!(
"tcp_user_timeout_ms {} is above {}",
self.tcp_user_timeout_ms,
i32::MAX
)));
}
if self.keepalive_idle_s > 32_767
|| (self.keepalive_idle_s > 0
&& (!(1..=32_767).contains(&self.keepalive_interval_s)
|| !(1..=127).contains(&self.keepalive_count)))
{
return Err(Error::Config(format!(
"keepalive: idle {} s (0 to 32767), interval {} s (1 to 32767), count {} (1 to 127)",
self.keepalive_idle_s, self.keepalive_interval_s, self.keepalive_count
)));
}
if i32::try_from(self.busy_poll_us).is_err() {
return Err(Error::Config(format!(
"busy_poll_us {} is above {}",
self.busy_poll_us,
i32::MAX
)));
}
if self.prefer_busy_poll && !self.backend.polls_nic() {
return Err(Error::Config(format!(
"prefer_busy_poll needs a backend that polls the NIC, not {}",
self.backend
)));
}
if self.per_queue && self.backend != Backend::RecvBusy {
return Err(Error::Config(format!(
"per_queue applies to recv-busy, not {}",
self.backend
)));
}
if self.busy_poll_budget.is_some()
&& !matches!(self.backend, Backend::RecvBusy | Backend::EpollBusy)
{
return Err(Error::Config(format!(
"busy_poll_budget applies to recv-busy and epoll-busy, not {}",
self.backend
)));
}
Ok(())
}
pub fn parse(s: &str) -> Result<Self> {
let mut parts = s.split(',').map(str::trim);
let mut cfg = Self::new(parts.next().unwrap_or_default().parse()?);
let cpu_list = |k: &str, v: &str| -> Result<Vec<u32>> {
ergon_tune::CpuSet::parse_list(v)
.map(|s| s.iter().collect())
.map_err(|e| Error::Config(format!("{k}: {e}: {v:?}")))
};
let mut in_list = false;
for kv in parts.filter(|p| !p.is_empty()) {
if in_list && !kv.contains('=') {
cfg.helper_cpus.extend(cpu_list("helper_cpus", kv)?);
continue;
}
in_list = false;
let (k, v) = kv
.split_once('=')
.ok_or_else(|| Error::Config(format!("expected key=value, got {kv:?}")))?;
let num = || -> Result<u32> {
v.parse()
.map_err(|_| Error::Config(format!("{k}: bad number {v:?}")))
};
let flag = || -> Result<bool> {
v.parse()
.map_err(|_| Error::Config(format!("{k}: bad bool {v:?}")))
};
match k {
"busy_poll_us" => cfg.busy_poll_us = num()?,
"wait_us" => cfg.wait_us = num()?,
"loop_cpu" => cfg.loop_cpu = Some(num()?),
"sqpoll_cpu" => cfg.sqpoll_cpu = Some(num()?),
"sqpoll_idle_ms" => cfg.sqpoll_idle_ms = num()?,
"ring_entries" => cfg.ring_entries = num()?,
"connect_timeout_ms" => cfg.connect_timeout_ms = num()?,
"connect_cpu" => cfg.connect_cpu = Some(num()?),
"helper_cpus" => {
cfg.helper_cpus = cpu_list(k, v)?;
in_list = true;
}
"inline_connect_fallback" => cfg.inline_connect_fallback = flag()?,
"rcvbuf" => cfg.rcvbuf = Some(num()?).filter(|&b| b > 0),
"rx_timestamps" => cfg.rx_timestamps = flag()?,
"prefer_busy_poll" => cfg.prefer_busy_poll = flag()?,
"per_queue" => cfg.per_queue = flag()?,
"quickack_check" => cfg.quickack_check = v.parse()?,
"keepalive_idle_s" => cfg.keepalive_idle_s = num()?,
"keepalive_interval_s" => cfg.keepalive_interval_s = num()?,
"keepalive_count" => cfg.keepalive_count = num()?,
"tcp_user_timeout_ms" => cfg.tcp_user_timeout_ms = num()?,
"busy_poll_budget" => {
let b = u16::try_from(num()?)
.map_err(|_| Error::Config(format!("{k}: above 65535: {v:?}")))?;
cfg.busy_poll_budget = Some(b).filter(|&b| b > 0);
}
_ => return Err(Error::Config(format!("unknown key {k:?}"))),
}
}
cfg.validate()?;
Ok(cfg)
}
}
impl FromStr for Config {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}