mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-09-03 20:52:53 -05:00
Merge pull request #1143 from LibreQoE/chore/warn_tsc
Warn when expensive clocksource is active
This commit is contained in:
@@ -109,6 +109,23 @@ journalctl -u lqosd --since "10 minutes ago"
|
||||
|
||||
Si el log muestra `LibreQoS failed to attach the XDP/TC kernel` o `Unable to load the XDP/TC kernel`, trate el arranque de `lqosd` como fallido. La WebUI y el bus local no arrancan hasta que el programa del kernel se cargue y se adjunte correctamente. El error de carga incluye el valor de retorno bruto, el número errno y el código errno, por ejemplo `raw=-11, errno=11, code=EAGAIN`. Revise si hay un programa XDP existente, un hook TC ocupado, falta de soporte del driver o mapas BPF fijados obsoletos antes de reiniciar `lqosd`.
|
||||
|
||||
### Uso de CPU elevado en horas pico (clocksource TSC inestable)
|
||||
|
||||
Si la CPU se satura al 100% durante las horas pico y el throughput/latencia se degradan, el kernel de Linux puede haber marcado el TSC de la CPU como un clocksource inestable y haber vuelto a uno más lento (normalmente HPET). Los operadores de LibreQoS han observado este comportamiento en algunos hosts AMD Ryzen con los estados C de la CPU habilitados, a veces solo después de reiniciar.
|
||||
|
||||
LibreQoS advierte al arrancar cuando HPET o el temporizador PM de ACPI está activo. Si ve esta advertencia de lqosd, compruebe el clocksource directamente:
|
||||
|
||||
```bash
|
||||
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
|
||||
```
|
||||
|
||||
Si la salida es `hpet` o `acpi_pm` en un host físico de LibreQoS, soluciónelo de una de estas formas:
|
||||
|
||||
- añadiendo `tsc=reliable` (o `tsc=nowatchdog`) a la línea de comandos del kernel en `/etc/default/grub` (`GRUB_CMDLINE_LINUX_DEFAULT`) y ejecutando `sudo update-grub`, y luego reiniciando, o
|
||||
- deshabilitando los estados C de la CPU en el BIOS/UEFI.
|
||||
|
||||
En los hosts donde el watchdog de estabilidad degrada el TSC, añadir únicamente `clocksource=tsc` no evita esa degradación.
|
||||
|
||||
### Depuración avanzada de lqosd
|
||||
|
||||
```bash
|
||||
|
||||
@@ -141,6 +141,23 @@ sudo systemctl edit lqosd
|
||||
|
||||
Set `LQOSD_MEMORY_WATCHDOG_DISABLED=1` only when you are actively watching memory pressure through another tool.
|
||||
|
||||
### High CPU usage at peak traffic (unstable TSC clocksource)
|
||||
|
||||
If the CPU pegs at 100% during peak hours and throughput/latency suffer, the Linux kernel may have marked the CPU's TSC as an unstable clocksource and fallen back to a slower one (usually HPET). LibreQoS operators have observed this on some AMD Ryzen hosts with CPU C-states enabled, sometimes only after a reboot.
|
||||
|
||||
LibreQoS warns at startup when HPET or the ACPI PM timer is active. If you see this lqosd warning, check the clocksource directly:
|
||||
|
||||
```bash
|
||||
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
|
||||
```
|
||||
|
||||
If the output is `hpet` or `acpi_pm` on a physical LibreQoS host, fix it by either:
|
||||
|
||||
- adding `tsc=reliable` (or `tsc=nowatchdog`) to the kernel command line in `/etc/default/grub` (`GRUB_CMDLINE_LINUX_DEFAULT`) and running `sudo update-grub`, then rebooting, or
|
||||
- disabling CPU C-states in the BIOS/UEFI.
|
||||
|
||||
On hosts where the stability watchdog demotes the TSC, adding only `clocksource=tsc` does not prevent the demotion.
|
||||
|
||||
### Advanced lqosd debug
|
||||
|
||||
At the command-line, run:
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::node_manager::{WarningLevel, add_global_warning};
|
||||
use anyhow::Result;
|
||||
use lqos_config::Config;
|
||||
use lqos_sys::interface_name_to_index;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
fn check_queues(interface: &str) -> Result<()> {
|
||||
let path = format!("/sys/class/net/{interface}/queues/");
|
||||
@@ -174,8 +174,55 @@ fn check_bridge_status(config: &Config, interfaces: &[IpLinkInterface]) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine whether the host clocksource is healthy for packet-level timing.
|
||||
///
|
||||
/// When the kernel selects a slower clocksource, per-packet reads in the XDP/TC
|
||||
/// hot path and userspace timing reads can become far more expensive, driving
|
||||
/// high CPU usage at peak traffic.
|
||||
///
|
||||
/// Returns a warning message when an expensive x86 fallback clocksource is
|
||||
/// active. Returns `None` for the TSC and paravirtual clocksources.
|
||||
fn clocksource_warning(current: &str) -> Option<String> {
|
||||
let current = current.trim();
|
||||
if !matches!(current, "hpet" | "acpi_pm") {
|
||||
return None;
|
||||
}
|
||||
Some(format!(
|
||||
"Active clocksource is '{current}' instead of 'tsc'. A slower clocksource can make packet-level and userspace timing expensive and cause high CPU at peak. See the Troubleshooting guide: 'High CPU usage at peak traffic (unstable TSC clocksource)'."
|
||||
))
|
||||
}
|
||||
|
||||
/// Reads the active clocksource from sysfs and, when it is an expensive x86
|
||||
/// fallback, records a node_manager global warning and a log line.
|
||||
///
|
||||
/// Side effects: reads `/sys/devices/system/clocksource/clocksource0/`, and on
|
||||
/// a problem emits a `warn!` log line and a global warning. This is
|
||||
/// intentionally non-fatal: shaping startup continues either way.
|
||||
fn check_clocksource() {
|
||||
// The TSC is an x86 clocksource; other architectures name their stable
|
||||
// counter differently and never expose a usable "tsc".
|
||||
if !matches!(std::env::consts::ARCH, "x86" | "x86_64") {
|
||||
return;
|
||||
}
|
||||
|
||||
let base = Path::new("/sys/devices/system/clocksource/clocksource0");
|
||||
let current_path = base.join("current_clocksource");
|
||||
let Ok(current) = std::fs::read_to_string(¤t_path) else {
|
||||
debug!("Unable to read {current_path:?}; skipping clocksource check");
|
||||
return;
|
||||
};
|
||||
let Some(warning) = clocksource_warning(¤t) else {
|
||||
return;
|
||||
};
|
||||
warn!("{warning}");
|
||||
add_global_warning(WarningLevel::Warning, warning);
|
||||
}
|
||||
|
||||
/// Runs a series of preflight checks to ensure that the configuration is sane
|
||||
pub fn preflight_checks() -> Result<()> {
|
||||
// Warn (but do not block) if the host clocksource is unhealthy.
|
||||
check_clocksource();
|
||||
|
||||
// Are we able to load the configuration?
|
||||
let config = lqos_config::load_config().map_err(|_| {
|
||||
error!("Failed to load configuration file - /etc/lqos.conf");
|
||||
@@ -226,3 +273,30 @@ pub fn preflight_checks() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn healthy_tsc_clocksource_produces_no_warning() {
|
||||
assert_eq!(clocksource_warning("tsc\n"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demoted_tsc_clocksource_produces_warning() {
|
||||
let warning = clocksource_warning("hpet\n").unwrap();
|
||||
assert!(warning.contains("'hpet'"));
|
||||
assert!(warning.contains("tsc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acpi_pm_clocksource_produces_warning() {
|
||||
assert!(clocksource_warning("acpi_pm\n").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paravirtual_clocksource_produces_no_warning() {
|
||||
assert_eq!(clocksource_warning("kvm-clock\n"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user