From abdcdafef542eef23abab78063490b3e7ad343b6 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Thu, 11 Jul 2024 09:22:37 -0500 Subject: [PATCH] ISSUE #518 - Make hot cache invalidation explicit The "add ip mapping" call was flushing the XDP 'hot cache'. That was fine in testing, and not working well at scale: * Each table wipe locks the associated underlying hash map. * Running 1200+ clears means waiting for 1200 occasions in which packets don't already have a hold on the lock (eBPF doesn't expose the locking mechanisms, so we can't change that behavior) * The result is that under load, it can take a REALLY long time to reload the shaping stack without pausing traffic. Instead, add_ip_mapping no longer clears the hot cache. An explicit invalidation call has been added to the bus, and added to the end of the batched IP map updates. This reduces the number of table locks from 1200+ to 2 (once for clearing the IP map, once for clearing the cache). --- src/LibreQoS.py | 1 + src/rust/lqos_bus/src/bus/request.rs | 5 +++++ src/rust/lqos_python/src/lib.rs | 6 ++++++ src/rust/lqos_sys/src/ip_mapping/mod.rs | 5 +++-- src/rust/lqos_sys/src/lib.rs | 2 +- src/rust/lqosd/src/ip_mapping.rs | 4 ++++ src/rust/lqosd/src/main.rs | 2 ++ 7 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/LibreQoS.py b/src/LibreQoS.py index d3a22c9c..22fe0f40 100755 --- a/src/LibreQoS.py +++ b/src/LibreQoS.py @@ -954,6 +954,7 @@ def refreshShapers(): print("Executing XDP-CPUMAP-TC IP filter commands") numXdpCommands = ipMapBatch.length() if enable_actual_shell_commands(): + ipMapBatch.finish_ip_mappings() ipMapBatch.submit() #for command in xdpCPUmapCommands: # logging.info(command) diff --git a/src/rust/lqos_bus/src/bus/request.rs b/src/rust/lqos_bus/src/bus/request.rs index dffd7b11..48932d2e 100644 --- a/src/rust/lqos_bus/src/bus/request.rs +++ b/src/rust/lqos_bus/src/bus/request.rs @@ -70,6 +70,11 @@ pub enum BusRequest { upload: bool, }, + /// After a batch of `MapIpToFlow` requests, this command will + /// clear the hot cache, forcing the XDP program to re-read the + /// mapping table. + ClearHotCache, + /// Requests that the XDP program unmap an IP address/subnet from /// the traffic management system. DelIpFlow { diff --git a/src/rust/lqos_python/src/lib.rs b/src/rust/lqos_python/src/lib.rs index 3276c4c6..065fb143 100644 --- a/src/rust/lqos_python/src/lib.rs +++ b/src/rust/lqos_python/src/lib.rs @@ -234,6 +234,12 @@ impl BatchedCommands { } } + pub fn finish_ip_mappings(&mut self) -> PyResult<()> { + let request = BusRequest::ClearHotCache; + self.batch.push(request); + Ok(()) + } + pub fn length(&self) -> PyResult { Ok(self.batch.len()) } diff --git a/src/rust/lqos_sys/src/ip_mapping/mod.rs b/src/rust/lqos_sys/src/ip_mapping/mod.rs index 6be7b373..cc0cd706 100644 --- a/src/rust/lqos_sys/src/ip_mapping/mod.rs +++ b/src/rust/lqos_sys/src/ip_mapping/mod.rs @@ -36,7 +36,8 @@ pub fn add_ip_to_tc( let mut value = IpHashData { cpu: ip_to_add.cpu, tc_handle: ip_to_add.handle() }; bpf_map.insert_or_update(&mut key, &mut value)?; - clear_hot_cache()?; + // Removed because it should be cleared explicitly at the end of a batch operation + //clear_hot_cache()?; Ok(()) } @@ -97,7 +98,7 @@ pub fn list_mapped_ips() -> Result> { /// Clears the "hot cache", which should be done whenever you change the IP /// mappings - because otherwise cached data will keep going to the previous /// destinations. -fn clear_hot_cache() -> Result<()> { +pub fn clear_hot_cache() -> Result<()> { let mut bpf_map = BpfMap::::from_path("/sys/fs/bpf/ip_to_cpu_and_tc_hotcache")?; bpf_map.clear()?; Ok(()) diff --git a/src/rust/lqos_sys/src/lib.rs b/src/rust/lqos_sys/src/lib.rs index 6147f4f9..2afe4c1c 100644 --- a/src/rust/lqos_sys/src/lib.rs +++ b/src/rust/lqos_sys/src/lib.rs @@ -23,7 +23,7 @@ mod bpf_iterator; pub mod flowbee_data; pub use ip_mapping::{ - add_ip_to_tc, clear_ips_from_tc, del_ip_from_tc, list_mapped_ips, + add_ip_to_tc, clear_ips_from_tc, del_ip_from_tc, list_mapped_ips, clear_hot_cache, }; pub use kernel_wrapper::LibreQoSKernels; pub use linux::num_possible_cpus; diff --git a/src/rust/lqosd/src/ip_mapping.rs b/src/rust/lqosd/src/ip_mapping.rs index 46789ef7..de559bd5 100644 --- a/src/rust/lqosd/src/ip_mapping.rs +++ b/src/rust/lqosd/src/ip_mapping.rs @@ -19,6 +19,10 @@ pub(crate) fn map_ip_to_flow( expect_ack(lqos_sys::add_ip_to_tc(ip_address, *tc_handle, cpu, upload)) } +pub(crate) fn clear_hot_cache() -> BusResponse { + expect_ack(lqos_sys::clear_hot_cache()) +} + pub(crate) fn del_ip_flow(ip_address: &str, upload: bool) -> BusResponse { expect_ack(lqos_sys::del_ip_from_tc(ip_address, upload)) } diff --git a/src/rust/lqosd/src/main.rs b/src/rust/lqosd/src/main.rs index c1d0c937..cba464dd 100644 --- a/src/rust/lqosd/src/main.rs +++ b/src/rust/lqosd/src/main.rs @@ -37,6 +37,7 @@ mod preflight_checks; // Use JemAllocator only on supported platforms #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use jemallocator::Jemalloc; +use crate::ip_mapping::clear_hot_cache; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[global_allocator] @@ -169,6 +170,7 @@ fn handle_bus_requests( BusRequest::MapIpToFlow { ip_address, tc_handle, cpu, upload } => { map_ip_to_flow(ip_address, tc_handle, *cpu, *upload) } + BusRequest::ClearHotCache => clear_hot_cache(), BusRequest::DelIpFlow { ip_address, upload } => { del_ip_flow(ip_address, *upload) }