mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-09-03 20:52:53 -05:00
Merge pull request #1137 from LibreQoE/fix/lazy-queue-memory-preflight-warning
Allow lazy queue memory preflight warnings
This commit is contained in:
@@ -228,7 +228,7 @@ Flujo general:
|
||||
|
||||
Controles clave:
|
||||
|
||||
1. `lazy_queues`: diferir creación de partes de la jerarquía hasta uso real.
|
||||
1. `lazy_queues`: diferir la creación de partes de la jerarquía hasta el uso real. Cuando está activado, una comprobación previa que solo indica falta de memoria se informa como advertencia para que la actualización pueda continuar; asegúrese de que el host tenga RAM suficiente.
|
||||
2. `lazy_expire_seconds`: remover estado de cola inactivo tras timeout.
|
||||
|
||||
Efecto práctico:
|
||||
|
||||
@@ -261,7 +261,7 @@ High-level flow:
|
||||
|
||||
Key controls:
|
||||
|
||||
1. `lazy_queues`: defer creating parts of the hierarchy until active use.
|
||||
1. `lazy_queues`: defer creating parts of the hierarchy until active use. When enabled, a memory-only queue preflight result is reported as a warning so the refresh can continue; ensure the host still has adequate RAM.
|
||||
2. `lazy_expire_seconds`: remove inactive queue state after timeout.
|
||||
|
||||
Practical effect:
|
||||
|
||||
@@ -2860,6 +2860,10 @@ def refreshShapers():
|
||||
},
|
||||
"TC_QDISC_CAPACITY",
|
||||
)
|
||||
elif qdiscBudgetEstimate.get("memory_warning_only"):
|
||||
preflightSummary = qdiscBudgetEstimate.get("summary") or ""
|
||||
logging.warning(preflightSummary)
|
||||
print("WARNING: " + preflightSummary)
|
||||
|
||||
# Save queuingStructure
|
||||
queuingStructure = {}
|
||||
|
||||
@@ -1693,6 +1693,8 @@ pub struct BakeryPreflightSnapshot {
|
||||
pub memory_guard_min_available_bytes: u64,
|
||||
/// Whether the memory preflight passed.
|
||||
pub memory_ok: bool,
|
||||
/// Whether the memory-only failure is an allowed lazy-queue warning.
|
||||
pub memory_warning_only: bool,
|
||||
/// Per-interface planned qdisc counts.
|
||||
pub interfaces: Vec<BakeryCapacityInterfaceSnapshot>,
|
||||
}
|
||||
@@ -2459,18 +2461,27 @@ fn refresh_live_capacity_snapshot(config: &Config, force: bool) {
|
||||
/// This function is not pure: it updates retained in-memory Bakery telemetry state.
|
||||
pub fn record_qdisc_preflight_snapshot(snapshot: BakeryPreflightSnapshot) {
|
||||
let ok = snapshot.ok;
|
||||
let memory_warning_only = snapshot.memory_warning_only;
|
||||
let summary = snapshot.message.clone();
|
||||
{
|
||||
let mut state = telemetry_state().write();
|
||||
state.preflight = Some(snapshot);
|
||||
}
|
||||
push_bakery_event(
|
||||
if ok {
|
||||
if memory_warning_only {
|
||||
"preflight_warning"
|
||||
} else if ok {
|
||||
"preflight_ok"
|
||||
} else {
|
||||
"preflight_blocked"
|
||||
},
|
||||
if ok { "info" } else { "warning" },
|
||||
if memory_warning_only {
|
||||
"warning"
|
||||
} else if ok {
|
||||
"info"
|
||||
} else {
|
||||
"warning"
|
||||
},
|
||||
summary,
|
||||
);
|
||||
}
|
||||
@@ -2571,15 +2582,17 @@ pub struct QdiscBudgetEstimate {
|
||||
pub memory_guard_min_available_bytes: u64,
|
||||
/// Whether the memory preflight passed.
|
||||
pub memory_ok: bool,
|
||||
/// Whether lazy queue mode makes a memory-only preflight failure non-blocking.
|
||||
pub memory_warning_only: bool,
|
||||
}
|
||||
|
||||
impl QdiscBudgetEstimate {
|
||||
/// Returns `true` when all planned per-interface counts fit within the safe budget.
|
||||
/// Returns `true` when the qdisc count fits and memory preflight passes or is a lazy-queue warning.
|
||||
pub fn ok(&self) -> bool {
|
||||
self.interfaces
|
||||
.values()
|
||||
.all(|count| *count <= self.safe_budget)
|
||||
&& self.memory_ok
|
||||
&& (self.memory_ok || self.memory_warning_only)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2723,6 +2736,14 @@ pub fn estimate_full_reload_auto_qdisc_budget(
|
||||
.saturating_sub(memory_guard_min_available_bytes)
|
||||
>= estimated_total_memory_bytes
|
||||
});
|
||||
let qdisc_counts_fit = interfaces
|
||||
.values()
|
||||
.all(|count| *count <= SAFE_QDISC_BUDGET_PER_INTERFACE);
|
||||
let memory_warning_only = lazy_queue_memory_preflight_is_warning(
|
||||
config.queues.lazy_queues.as_ref(),
|
||||
qdisc_counts_fit,
|
||||
memory_ok,
|
||||
);
|
||||
|
||||
QdiscBudgetEstimate {
|
||||
interfaces,
|
||||
@@ -2733,9 +2754,23 @@ pub fn estimate_full_reload_auto_qdisc_budget(
|
||||
memory_snapshot,
|
||||
memory_guard_min_available_bytes,
|
||||
memory_ok,
|
||||
memory_warning_only,
|
||||
}
|
||||
}
|
||||
|
||||
fn lazy_queue_memory_preflight_is_warning(
|
||||
lazy_queue_mode: Option<&LazyQueueMode>,
|
||||
qdisc_counts_fit: bool,
|
||||
memory_ok: bool,
|
||||
) -> bool {
|
||||
qdisc_counts_fit
|
||||
&& !memory_ok
|
||||
&& matches!(
|
||||
lazy_queue_mode,
|
||||
Some(LazyQueueMode::Htb | LazyQueueMode::Full)
|
||||
)
|
||||
}
|
||||
|
||||
fn desired_shaping_tree_active(config: &Arc<Config>) -> bool {
|
||||
!config.queues.queue_mode.is_observe()
|
||||
}
|
||||
@@ -14356,6 +14391,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_warning_only_allows_preflight_to_continue() {
|
||||
let mut estimate = QdiscBudgetEstimate {
|
||||
interfaces: BTreeMap::from([("eth0".to_string(), 1)]),
|
||||
interface_details: BTreeMap::new(),
|
||||
safe_budget: SAFE_QDISC_BUDGET_PER_INTERFACE,
|
||||
hard_limit: HARD_QDISC_HANDLE_LIMIT_PER_INTERFACE,
|
||||
estimated_total_memory_bytes: 1,
|
||||
memory_snapshot: None,
|
||||
memory_guard_min_available_bytes: BAKERY_MEMORY_GUARD_MIN_AVAILABLE_BYTES,
|
||||
memory_ok: false,
|
||||
memory_warning_only: true,
|
||||
};
|
||||
|
||||
assert!(estimate.ok());
|
||||
estimate.memory_warning_only = false;
|
||||
assert!(!estimate.ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_qdisc_memory_estimate_deduplicates_qdisc_identity() {
|
||||
let commands = vec![
|
||||
|
||||
@@ -3646,11 +3646,15 @@ impl Bakery {
|
||||
.interfaces
|
||||
.values()
|
||||
.all(|count| *count <= estimate.safe_budget);
|
||||
let preflight_status = match (qdisc_counts_fit, estimate.memory_ok) {
|
||||
(true, true) => "fits preflight",
|
||||
(false, true) => "exceeds qdisc-count preflight",
|
||||
(true, false) => "passed qdisc-count preflight but failed memory preflight",
|
||||
(false, false) => "exceeds qdisc-count preflight and failed memory preflight",
|
||||
let preflight_status = if estimate.memory_warning_only {
|
||||
"passed qdisc-count preflight; memory preflight is a non-blocking lazy-queue warning"
|
||||
} else {
|
||||
match (qdisc_counts_fit, estimate.memory_ok) {
|
||||
(true, true) => "fits preflight",
|
||||
(false, true) => "exceeds qdisc-count preflight",
|
||||
(true, false) => "passed qdisc-count preflight but failed memory preflight",
|
||||
(false, false) => "exceeds qdisc-count preflight and failed memory preflight",
|
||||
}
|
||||
};
|
||||
let memory_summary = if let Some(snapshot) = estimate.memory_snapshot.as_ref() {
|
||||
let required_available_bytes = estimate
|
||||
@@ -3738,6 +3742,7 @@ impl Bakery {
|
||||
estimate.estimated_total_memory_bytes,
|
||||
)?;
|
||||
result.set_item("memory_ok", estimate.memory_ok)?;
|
||||
result.set_item("memory_warning_only", estimate.memory_warning_only)?;
|
||||
result.set_item(
|
||||
"memory_guard_min_available_bytes",
|
||||
estimate.memory_guard_min_available_bytes,
|
||||
|
||||
@@ -907,6 +907,7 @@ fn handle_bus_requests(requests: &[BusRequest], responses: &mut Vec<BusResponse>
|
||||
memory_available_bytes: *memory_available_bytes,
|
||||
memory_guard_min_available_bytes: *memory_guard_min_available_bytes,
|
||||
memory_ok: *memory_ok,
|
||||
memory_warning_only: *ok && !*memory_ok,
|
||||
interfaces: interfaces
|
||||
.iter()
|
||||
.map(|entry| lqos_bakery::BakeryCapacityInterfaceSnapshot {
|
||||
|
||||
@@ -99,6 +99,9 @@ function classifyEvent(entry) {
|
||||
} else if (event === "preflight_ok") {
|
||||
outcome = "Passed";
|
||||
outcomeClass = "bg-success-subtle text-success border border-success-subtle";
|
||||
} else if (event === "preflight_warning") {
|
||||
outcome = "Memory Warning";
|
||||
outcomeClass = "bg-warning-subtle text-warning border border-warning-subtle";
|
||||
} else if (event === "preflight_blocked") {
|
||||
outcome = "Blocked";
|
||||
outcomeClass = "bg-warning-subtle text-warning border border-warning-subtle";
|
||||
|
||||
@@ -37,6 +37,9 @@ function bakeryMemoryBadge(preflight) {
|
||||
if (preflight.memoryOk) {
|
||||
return mkBadge("Memory OK", "bg-success-subtle text-success border border-success-subtle", preflight.message || "");
|
||||
}
|
||||
if (preflight.ok) {
|
||||
return mkBadge("Memory Warning", "bg-warning-subtle text-warning border border-warning-subtle", preflight.message || "");
|
||||
}
|
||||
return mkBadge("Memory Guard", "bg-danger-subtle text-danger border border-danger-subtle", preflight.message || "");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user