mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-11-02 20:30:15 -06:00
Merge pull request #943 from LibreQoE/928_ecores
ISSUE #928: Exclude efficiency cores from CPU mapping
This commit is contained in:
12 files changed
+417
-20
No files matched your search
+5
-1
@@ -35,6 +35,7 @@ from liblqos_python import is_lqosd_alive, clear_ip_mappings, delete_ip_mapping,
|
||||
on_a_stick, get_tree_weights, get_weights, is_network_flat, get_libreqos_directory, enable_insight_topology, \
|
||||
is_insight_enabled, \
|
||||
fast_queues_fq_codel, \
|
||||
shaping_cpu_count, \
|
||||
Bakery
|
||||
|
||||
# Optional: urgent issue submission (available in newer liblqos_python)
|
||||
@@ -189,7 +190,10 @@ def findQueuesAvailable(interfaceName):
|
||||
else:
|
||||
queuesAvailable = queues_available_override()
|
||||
print(f"Interface {interfaceName} NIC queues (Override):\t\t\t" + str(queuesAvailable))
|
||||
cpuCount = multiprocessing.cpu_count()
|
||||
try:
|
||||
cpuCount = shaping_cpu_count()
|
||||
except Exception:
|
||||
cpuCount = multiprocessing.cpu_count()
|
||||
print("CPU cores:\t\t\t" + str(cpuCount))
|
||||
if queuesAvailable < 2:
|
||||
raise SystemError(f'Only 1 NIC rx/tx queue available for interface {interfaceName}. You will need to use a NIC with 2 or more rx/tx queues available.')
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
//! CPU topology helpers (hybrid P-core/E-core detection).
|
||||
//!
|
||||
//! LibreQoS primarily runs on Linux and may be deployed on hybrid CPUs that
|
||||
//! include both performance cores ("P-cores") and efficiency cores ("E-cores").
|
||||
//! When possible, we detect E-cores via sysfs and provide a list of CPUs that
|
||||
//! should be used for shaping / CPU binning.
|
||||
|
||||
use crate::Config;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
const POSSIBLE_CPUS_PATH: &str = "/sys/devices/system/cpu/possible";
|
||||
|
||||
const CPU_CORE_SYSFS_PATHS: [&str; 2] = [
|
||||
"/sys/bus/event_source/devices/cpu_core/cpus",
|
||||
"/sys/devices/cpu_core/cpus",
|
||||
];
|
||||
|
||||
const CPU_ATOM_SYSFS_PATHS: [&str; 2] = [
|
||||
"/sys/bus/event_source/devices/cpu_atom/cpus",
|
||||
"/sys/devices/cpu_atom/cpus",
|
||||
];
|
||||
|
||||
/// Indicates which mechanism was used to choose shaping CPUs.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ShapingCpuSource {
|
||||
/// Exclusion is disabled; use all possible CPUs.
|
||||
ExclusionDisabled,
|
||||
/// Used `/sys/.../cpu_core/cpus` to identify P-cores.
|
||||
CpuCoreSysfs,
|
||||
/// Used `/sys/.../cpu_atom/cpus` to identify E-cores, then computed `possible - atom`.
|
||||
PossibleMinusAtomSysfs,
|
||||
/// Hybrid sysfs paths were unavailable; used `/sys/devices/system/cpu/possible`.
|
||||
FallbackAllPossible,
|
||||
/// Could not read `/sys/devices/system/cpu/possible`; used Rust's available parallelism.
|
||||
FallbackAvailableParallelism,
|
||||
}
|
||||
|
||||
/// Result of hybrid CPU detection.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ShapingCpuDetection {
|
||||
/// Whether efficiency core exclusion was requested.
|
||||
pub exclude_efficiency_cores: bool,
|
||||
/// Source used to determine the shaping CPU list.
|
||||
pub source: ShapingCpuSource,
|
||||
/// All possible CPU IDs (best effort).
|
||||
pub possible: Vec<u32>,
|
||||
/// Detected performance-core CPU IDs (may be empty if unknown).
|
||||
pub performance: Vec<u32>,
|
||||
/// Detected efficiency-core CPU IDs (may be empty if unknown).
|
||||
pub efficiency: Vec<u32>,
|
||||
/// Final CPU list that should be used for shaping/bins.
|
||||
pub shaping: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Errors that can occur when parsing a Linux cpulist string.
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CpuListParseError {
|
||||
/// Input was empty or contained no CPU entries.
|
||||
#[error("CPU list is empty")]
|
||||
Empty,
|
||||
/// A numeric component failed to parse.
|
||||
#[error("Unable to parse number in CPU list")]
|
||||
ParseNumber,
|
||||
/// A range was malformed (e.g. `3-1`).
|
||||
#[error("Invalid range in CPU list")]
|
||||
InvalidRange,
|
||||
}
|
||||
|
||||
fn parse_cpu_list(input: &str) -> Result<Vec<u32>, CpuListParseError> {
|
||||
let trimmed = input.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err(CpuListParseError::Empty);
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for part in trimmed.split(',') {
|
||||
let p = part.trim();
|
||||
if p.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some((start_s, end_s)) = p.split_once('-') {
|
||||
let start = start_s.trim().parse::<u32>().map_err(|_| CpuListParseError::ParseNumber)?;
|
||||
let end = end_s.trim().parse::<u32>().map_err(|_| CpuListParseError::ParseNumber)?;
|
||||
if end < start {
|
||||
return Err(CpuListParseError::InvalidRange);
|
||||
}
|
||||
for cpu in start..=end {
|
||||
out.push(cpu);
|
||||
}
|
||||
} else {
|
||||
let cpu = p.parse::<u32>().map_err(|_| CpuListParseError::ParseNumber)?;
|
||||
out.push(cpu);
|
||||
}
|
||||
}
|
||||
if out.is_empty() {
|
||||
return Err(CpuListParseError::Empty);
|
||||
}
|
||||
out.sort_unstable();
|
||||
out.dedup();
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn try_read_cpu_list(path: &str) -> Option<Vec<u32>> {
|
||||
if !Path::new(path).exists() {
|
||||
return None;
|
||||
}
|
||||
let raw = std::fs::read_to_string(path).ok()?;
|
||||
let list = parse_cpu_list(&raw).ok()?;
|
||||
(!list.is_empty()).then_some(list)
|
||||
}
|
||||
|
||||
fn first_cpu_list(paths: &[&str]) -> Option<Vec<u32>> {
|
||||
for p in paths.iter().copied() {
|
||||
if let Some(list) = try_read_cpu_list(p) {
|
||||
return Some(list);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn possible_cpu_list() -> (Vec<u32>, ShapingCpuSource) {
|
||||
if let Some(list) = try_read_cpu_list(POSSIBLE_CPUS_PATH) {
|
||||
return (list, ShapingCpuSource::FallbackAllPossible);
|
||||
}
|
||||
|
||||
let fallback = std::thread::available_parallelism()
|
||||
.map(|n| (0..(n.get() as u32)).collect::<Vec<u32>>())
|
||||
.unwrap_or_else(|_| vec![0]);
|
||||
(fallback, ShapingCpuSource::FallbackAvailableParallelism)
|
||||
}
|
||||
|
||||
/// Detects the set of CPUs that should be used for shaping/CPU binning.
|
||||
///
|
||||
/// When `cfg.exclude_efficiency_cores` is enabled, this attempts to identify
|
||||
/// performance cores via sysfs. If detection fails or yields an empty set,
|
||||
/// this falls back to "all possible CPUs".
|
||||
pub fn detect_shaping_cpus(cfg: &Config) -> ShapingCpuDetection {
|
||||
let exclude = cfg.exclude_efficiency_cores;
|
||||
|
||||
let (possible, possible_source) = possible_cpu_list();
|
||||
|
||||
if !exclude {
|
||||
return ShapingCpuDetection {
|
||||
exclude_efficiency_cores: exclude,
|
||||
source: ShapingCpuSource::ExclusionDisabled,
|
||||
possible: possible.clone(),
|
||||
performance: possible.clone(),
|
||||
efficiency: Vec::new(),
|
||||
shaping: possible,
|
||||
};
|
||||
}
|
||||
|
||||
let perf_opt = first_cpu_list(&CPU_CORE_SYSFS_PATHS);
|
||||
let eff_opt = first_cpu_list(&CPU_ATOM_SYSFS_PATHS);
|
||||
|
||||
// Prefer cpu_core list if present.
|
||||
if let Some(mut perf) = perf_opt.clone() {
|
||||
// If both are present, remove overlaps just in case.
|
||||
if let Some(eff) = eff_opt.clone() {
|
||||
if !eff.is_empty() && !perf.is_empty() {
|
||||
let eff_set: HashSet<u32> = eff.iter().copied().collect();
|
||||
perf.retain(|c| !eff_set.contains(c));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure it is a subset of possible CPUs, if that list exists.
|
||||
if !possible.is_empty() {
|
||||
let poss_set: HashSet<u32> = possible.iter().copied().collect();
|
||||
perf.retain(|c| poss_set.contains(c));
|
||||
}
|
||||
|
||||
if !perf.is_empty() {
|
||||
return ShapingCpuDetection {
|
||||
exclude_efficiency_cores: exclude,
|
||||
source: ShapingCpuSource::CpuCoreSysfs,
|
||||
possible,
|
||||
performance: perf.clone(),
|
||||
efficiency: eff_opt.unwrap_or_default(),
|
||||
shaping: perf,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Next-best: if we know efficiency CPUs, compute possible - efficiency.
|
||||
if let Some(eff) = eff_opt.clone() {
|
||||
if !eff.is_empty() && !possible.is_empty() {
|
||||
let eff_set: HashSet<u32> = eff.iter().copied().collect();
|
||||
let perf: Vec<u32> = possible.iter().copied().filter(|c| !eff_set.contains(c)).collect();
|
||||
if !perf.is_empty() {
|
||||
return ShapingCpuDetection {
|
||||
exclude_efficiency_cores: exclude,
|
||||
source: ShapingCpuSource::PossibleMinusAtomSysfs,
|
||||
possible,
|
||||
performance: perf.clone(),
|
||||
efficiency: eff,
|
||||
shaping: perf,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: use all possible CPUs.
|
||||
ShapingCpuDetection {
|
||||
exclude_efficiency_cores: exclude,
|
||||
source: possible_source,
|
||||
possible: possible.clone(),
|
||||
performance: possible.clone(),
|
||||
efficiency: eff_opt.unwrap_or_default(),
|
||||
shaping: possible,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_single() {
|
||||
assert_eq!(parse_cpu_list("0").unwrap(), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_range() {
|
||||
assert_eq!(parse_cpu_list("0-3").unwrap(), vec![0, 1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_mixed() {
|
||||
assert_eq!(parse_cpu_list("0-3,8,10-12").unwrap(), vec![0, 1, 2, 3, 8, 10, 11, 12]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_whitespace_and_dedup() {
|
||||
assert_eq!(parse_cpu_list(" 0-1, 1 , 2 \n").unwrap(), vec![0, 1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_invalid_range() {
|
||||
assert_eq!(parse_cpu_list("3-1").unwrap_err(), CpuListParseError::InvalidRange);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cpu_list_empty() {
|
||||
assert_eq!(parse_cpu_list(" \n").unwrap_err(), CpuListParseError::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,13 @@ pub struct Config {
|
||||
/// Disable ICMP Ping Monitoring for Devices in the hosts view
|
||||
pub disable_icmp_ping: Option<bool>,
|
||||
|
||||
/// Exclude efficiency cores (E-cores) from CPU assignment / shaping where possible.
|
||||
///
|
||||
/// On hybrid CPUs, this attempts to detect performance cores (P-cores) via sysfs and
|
||||
/// restricts LibreQoS CPU binning and XDP redirection to those CPUs.
|
||||
#[serde(default = "default_true")]
|
||||
pub exclude_efficiency_cores: bool,
|
||||
|
||||
/// Enable per-circuit TemporalHeatmap collection.
|
||||
#[serde(default = "default_true")]
|
||||
pub enable_circuit_heatmaps: bool,
|
||||
@@ -241,6 +248,7 @@ impl Default for Config {
|
||||
webserver_listen: None,
|
||||
stormguard: None,
|
||||
disable_icmp_ping: Some(false),
|
||||
exclude_efficiency_cores: true,
|
||||
enable_circuit_heatmaps: true,
|
||||
enable_site_heatmaps: true,
|
||||
enable_asn_heatmaps: true,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![warn(missing_docs)]
|
||||
pub mod authentication;
|
||||
mod cpu_topology;
|
||||
mod etc;
|
||||
mod network_json;
|
||||
mod program_control;
|
||||
@@ -15,6 +16,7 @@ mod qoo_profiles;
|
||||
mod shaped_devices;
|
||||
|
||||
pub use authentication::{UserRole, WebUser, WebUsers};
|
||||
pub use cpu_topology::{CpuListParseError, ShapingCpuDetection, ShapingCpuSource, detect_shaping_cpus};
|
||||
pub use etc::{
|
||||
BridgeConfig, Config, LazyQueueMode, SingleInterfaceConfig, StormguardConfig, Tunables,
|
||||
disable_xdp_bridge, enable_long_term_stats, load_config, update_config,
|
||||
|
||||
@@ -598,6 +598,7 @@ fn liblqos_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(enable_actual_shell_commands, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(use_bin_packing_to_balance_cpu, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(monitor_mode_only, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(shaping_cpu_count, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(run_shell_commands_as_sudo, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(generated_pn_download_mbps, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(generated_pn_upload_mbps, m)?)?;
|
||||
@@ -1149,6 +1150,16 @@ fn monitor_mode_only() -> PyResult<bool> {
|
||||
Ok(config.queues.monitor_only)
|
||||
}
|
||||
|
||||
/// Returns the number of CPUs that should be used for shaping / binning.
|
||||
///
|
||||
/// On hybrid CPUs, this may exclude efficiency cores (E-cores) when configured.
|
||||
#[pyfunction]
|
||||
fn shaping_cpu_count() -> PyResult<u32> {
|
||||
let config = lqos_config::load_config().unwrap();
|
||||
let det = lqos_config::detect_shaping_cpus(config.as_ref());
|
||||
Ok(det.shaping.len() as u32)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn run_shell_commands_as_sudo() -> PyResult<bool> {
|
||||
let config = lqos_config::load_config().unwrap();
|
||||
|
||||
@@ -288,6 +288,12 @@ int tc_iphash_to_cpu(struct __sk_buff *skb)
|
||||
// We can short-circuit the redirect and bypass the second
|
||||
// LPM lookup! Yay!
|
||||
skb->priority = meta->tc_handle;
|
||||
// Ensure the selected MQ queue matches the TC handle's major.
|
||||
// This decouples shaping correctness from CPU selection.
|
||||
__u16 major = meta->tc_handle >> 16;
|
||||
if (major != 0) {
|
||||
skb->queue_mapping = major;
|
||||
}
|
||||
#ifdef TRACING
|
||||
{
|
||||
__u64 now = bpf_ktime_get_ns();
|
||||
@@ -336,6 +342,11 @@ int tc_iphash_to_cpu(struct __sk_buff *skb)
|
||||
bpf_debug("(TC) Mapped to TC handle %x", ip_info.tc_handle);
|
||||
#endif
|
||||
skb->priority = ip_info.tc_handle;
|
||||
// Ensure the selected MQ queue matches the TC handle's major.
|
||||
__u16 major = ip_info.tc_handle >> 16;
|
||||
if (major != 0) {
|
||||
skb->queue_mapping = major;
|
||||
}
|
||||
#ifdef TRACING
|
||||
{
|
||||
__u64 now = bpf_ktime_get_ns();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::{linux::map_txq_config_base_setup, num_possible_cpus};
|
||||
use crate::{linux::map_txq_config_shaping, num_possible_cpus};
|
||||
use anyhow::{Error, Result};
|
||||
use libbpf_sys::{bpf_map_update_elem, bpf_obj_get};
|
||||
use std::{ffi::CString, os::raw::c_void};
|
||||
use tracing::debug;
|
||||
|
||||
//* Provides an interface for querying the number of CPUs eBPF can
|
||||
//* see, and marking CPUs as available. Currently marks ALL eBPF
|
||||
//* usable CPUs as available.
|
||||
//* see, and marking CPUs as available. Callers may provide a shaping
|
||||
//* CPU allowlist (e.g. exclude E-cores on hybrid CPUs).
|
||||
|
||||
pub(crate) struct CpuMapping {
|
||||
fd_cpu_map: i32,
|
||||
@@ -33,15 +33,32 @@ impl CpuMapping {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn mark_cpus_available(&self) -> Result<()> {
|
||||
pub(crate) fn mark_cpus_available(&self, shaping_physical_cpus: &[u32]) -> Result<()> {
|
||||
let cpu_count = num_possible_cpus()?;
|
||||
|
||||
let queue_size = 2048u32;
|
||||
let val_ptr: *const u32 = &queue_size;
|
||||
for cpu in 0..cpu_count {
|
||||
debug!("Mapping core #{cpu}");
|
||||
// Insert into the cpu map
|
||||
let cpu_ptr: *const u32 = &cpu;
|
||||
|
||||
// Determine which physical CPUs we will redirect to.
|
||||
let mut shaping: Vec<u32> = if shaping_physical_cpus.is_empty() {
|
||||
(0..cpu_count).collect()
|
||||
} else {
|
||||
shaping_physical_cpus
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|c| *c < cpu_count)
|
||||
.collect()
|
||||
};
|
||||
shaping.sort_unstable();
|
||||
shaping.dedup();
|
||||
if shaping.is_empty() {
|
||||
shaping = (0..cpu_count).collect();
|
||||
}
|
||||
|
||||
// Populate CPU map entries for each destination CPU (physical CPU IDs).
|
||||
for cpu_dest in shaping.iter().copied() {
|
||||
debug!("Mapping destination CPU #{cpu_dest} into cpumap");
|
||||
let cpu_ptr: *const u32 = &cpu_dest;
|
||||
let error = unsafe {
|
||||
bpf_map_update_elem(
|
||||
self.fd_cpu_map,
|
||||
@@ -51,27 +68,36 @@ impl CpuMapping {
|
||||
)
|
||||
};
|
||||
if error != 0 {
|
||||
return Err(Error::msg("Unable to map CPU"));
|
||||
return Err(Error::msg("Unable to map CPU in cpumap"));
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into the available list
|
||||
// Populate logical->physical mapping table for ALL possible CPU indices.
|
||||
// This prevents stale mappings from implicitly redirecting to CPU0 due to
|
||||
// uninitialized array values.
|
||||
let shaping_len = shaping.len() as u32;
|
||||
for logical_cpu in 0..cpu_count {
|
||||
let cpu_dest = shaping[(logical_cpu % shaping_len) as usize];
|
||||
debug!("Mapping logical CPU #{logical_cpu} -> physical CPU #{cpu_dest}");
|
||||
let key_ptr: *const u32 = &logical_cpu;
|
||||
let val_ptr: *const u32 = &cpu_dest;
|
||||
let error = unsafe {
|
||||
bpf_map_update_elem(
|
||||
self.fd_cpu_available,
|
||||
cpu_ptr as *const c_void,
|
||||
cpu_ptr as *const c_void,
|
||||
key_ptr as *const c_void,
|
||||
val_ptr as *const c_void,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if error != 0 {
|
||||
return Err(Error::msg("Unable to add to available CPUs list"));
|
||||
}
|
||||
} // CPU loop
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn setup_base_txq_config(&self) -> Result<()> {
|
||||
Ok(map_txq_config_base_setup(self.fd_txq_config)?)
|
||||
pub(crate) fn setup_base_txq_config(&self, shaping_physical_cpus: &[u32]) -> Result<()> {
|
||||
Ok(map_txq_config_shaping(self.fd_txq_config, shaping_physical_cpus)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,65 @@ pub fn map_txq_config_base_setup(map_fd: i32) -> Result<(), MapTxqConfigError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Populate `map_txq_config` for a shaping CPU set.
|
||||
///
|
||||
/// The map is keyed by the **physical CPU** that will execute the TC program
|
||||
/// (i.e. the destination of XDP redirection). Values set `queue_mapping` and
|
||||
/// `htb_major` to match the logical queue index used by LibreQoS (major IDs
|
||||
/// start at 1).
|
||||
pub fn map_txq_config_shaping(
|
||||
map_fd: i32,
|
||||
shaping_physical_cpus: &[u32],
|
||||
) -> Result<(), MapTxqConfigError> {
|
||||
let possible_cpus = num_possible_cpus().map_err(|_| MapTxqConfigError::NumCpusError)?;
|
||||
if map_fd < 0 {
|
||||
error!("ERR: (bad map_fd:{map_fd}) cannot proceed without access to txq_config map");
|
||||
return Err(MapTxqConfigError::BadMapFd);
|
||||
}
|
||||
|
||||
if shaping_physical_cpus.is_empty() {
|
||||
return map_txq_config_base_setup(map_fd);
|
||||
}
|
||||
|
||||
// Clear prior values to avoid stale queue mappings on excluded CPUs.
|
||||
let mut txq_cfg = TxqConfig::default();
|
||||
for cpu in 0..possible_cpus {
|
||||
let key_ptr: *const u32 = &cpu;
|
||||
let val_ptr: *const TxqConfig = &txq_cfg;
|
||||
let err = unsafe {
|
||||
bpf_map_update_elem(map_fd, key_ptr as *const c_void, val_ptr as *mut c_void, 0)
|
||||
};
|
||||
if err != 0 {
|
||||
error!("Unable to clear TXQ map");
|
||||
return Err(MapTxqConfigError::BpfMapUpdateFail);
|
||||
}
|
||||
}
|
||||
|
||||
// Program shaping CPUs: physical CPU -> queue index+1.
|
||||
for (logical_idx, &physical_cpu) in shaping_physical_cpus.iter().enumerate() {
|
||||
// Ignore CPUs outside the possible range.
|
||||
if physical_cpu >= possible_cpus {
|
||||
continue;
|
||||
}
|
||||
let queue_u16: u16 = (logical_idx as u16).saturating_add(1);
|
||||
txq_cfg.queue_mapping = queue_u16;
|
||||
txq_cfg.htb_major = queue_u16;
|
||||
|
||||
let key_ptr: *const u32 = &physical_cpu;
|
||||
let val_ptr: *const TxqConfig = &txq_cfg;
|
||||
|
||||
let err = unsafe {
|
||||
bpf_map_update_elem(map_fd, key_ptr as *const c_void, val_ptr as *mut c_void, 0)
|
||||
};
|
||||
if err != 0 {
|
||||
error!("Unable to update TXQ map for physical CPU {}", physical_cpu);
|
||||
return Err(MapTxqConfigError::BpfMapUpdateFail);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MapTxqConfigError {
|
||||
#[error("Unable to determine number of CPUs")]
|
||||
|
||||
@@ -273,11 +273,30 @@ pub fn attach_xdp_and_tc_to_interface(
|
||||
};
|
||||
|
||||
// Configure CPU Maps
|
||||
let shaping_physical_cpus: Vec<u32> = match lqos_config::load_config() {
|
||||
Ok(cfg) => {
|
||||
let det = lqos_config::detect_shaping_cpus(cfg.as_ref());
|
||||
info!(
|
||||
"CPU topology: exclude_efficiency_cores={} source={:?} possible={} performance={} efficiency={} shaping={}",
|
||||
det.exclude_efficiency_cores,
|
||||
det.source,
|
||||
det.possible.len(),
|
||||
det.performance.len(),
|
||||
det.efficiency.len(),
|
||||
det.shaping.len()
|
||||
);
|
||||
det.shaping
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Unable to load config for CPU topology detection: {:?}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
{
|
||||
let cpu_map = CpuMapping::new()?;
|
||||
crate::cpu_map::xps_setup_default_disable(interface_name)?;
|
||||
cpu_map.mark_cpus_available()?;
|
||||
cpu_map.setup_base_txq_config()?;
|
||||
cpu_map.mark_cpus_available(&shaping_physical_cpus)?;
|
||||
cpu_map.setup_base_txq_config(&shaping_physical_cpus)?;
|
||||
} // Scope block to ensure the CPU maps are closed
|
||||
|
||||
// Attach the TC program
|
||||
|
||||
@@ -157,6 +157,7 @@ function updateConfig() {
|
||||
window.config.queue_check_period_ms = parseInt(document.getElementById("queueCheckPeriod").value);
|
||||
window.config.disable_webserver = document.getElementById("disableWebserver").checked;
|
||||
window.config.disable_icmp_ping = document.getElementById("disableIcmpPing").checked;
|
||||
window.config.exclude_efficiency_cores = document.getElementById("excludeEfficiencyCores").checked;
|
||||
window.config.enable_circuit_heatmaps = document.getElementById("enableCircuitHeatmaps").checked;
|
||||
window.config.enable_site_heatmaps = document.getElementById("enableSiteHeatmaps").checked;
|
||||
window.config.enable_asn_heatmaps = document.getElementById("enableAsnHeatmaps").checked;
|
||||
@@ -193,6 +194,7 @@ loadConfig(() => {
|
||||
document.getElementById("disableWebserver").checked = window.config.disable_webserver ?? false;
|
||||
document.getElementById("webserverListen").value = window.config.webserver_listen ?? "";
|
||||
document.getElementById("disableIcmpPing").checked = window.config.disable_icmp_ping ?? false;
|
||||
document.getElementById("excludeEfficiencyCores").checked = window.config.exclude_efficiency_cores ?? true;
|
||||
document.getElementById("enableCircuitHeatmaps").checked = window.config.enable_circuit_heatmaps ?? true;
|
||||
document.getElementById("enableSiteHeatmaps").checked = window.config.enable_site_heatmaps ?? true;
|
||||
document.getElementById("enableAsnHeatmaps").checked = window.config.enable_asn_heatmaps ?? true;
|
||||
|
||||
@@ -89,6 +89,12 @@
|
||||
<div class="form-text">Disable ICMP ping monitoring for devices in the hosts view</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="excludeEfficiencyCores">
|
||||
<label class="form-check-label" for="excludeEfficiencyCores">Exclude Efficiency Cores</label>
|
||||
<div class="form-text">Avoid assigning shaping work to E-cores on hybrid CPUs (recommended)</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="saveButton" class="btn btn-outline-primary">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,9 @@ fn compute_stick_offset(config: &Config) -> Result<u32> {
|
||||
count_tx_queues(&interface)?
|
||||
};
|
||||
|
||||
let cpu_count = lqos_sys::num_possible_cpus().map_err(|e| anyhow::anyhow!("{e:?}"))?;
|
||||
// Keep stick_offset consistent with CPU binning / cpumap behavior. On hybrid CPUs this may
|
||||
// exclude efficiency cores, reducing the number of usable shaping CPUs.
|
||||
let cpu_count = lqos_config::detect_shaping_cpus(config).shaping.len() as u32;
|
||||
|
||||
let queues_available = u32::min(queues_available, cpu_count);
|
||||
let queues_per_direction = queues_available / 2;
|
||||
@@ -83,4 +85,3 @@ fn count_tx_queues(interface: &str) -> Result<u32> {
|
||||
Ok(tx_queues)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user