Watch
0
0
Fork
You've already forked hyperhive
0

container stats: bound hive-c0re's wait for the disk measurement, no total for an agent with no paths

hive-c0re waited on hive-priv's answer with no timeout, so a hung
hive-priv would stall the disk sampler indefinitely. The wait is now
hive-priv's own 10 min bound plus a minute (the bound is shared as
MEASURE_AGENT_DISK_TIMEOUT); a timeout is an error, so the agent shows
unknown.

An agent with none of state/, work/ or the rootfs has no measurable
size. hive-priv now answers with an error for it instead of a total of
0, which read as a real measurement.
This commit is contained in:
atlas 2026-10-05 16:33:11 +02:00 • committed by mara
commit 960cf9ce30
3 changed files with 131 additions and 36 deletions

View file

@ -8,8 +8,9 @@
use anyhow::{Context as _, Result, bail};
use hive_priv_sock::{
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, NetworkIsolation,
PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream,
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery,
MEASURE_AGENT_DISK_TIMEOUT, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse,
PrivStream,
};
use std::os::fd::{AsRawFd as _, OwnedFd, RawFd};
@ -429,25 +430,44 @@ pub async fn read_subvolume_usage(agent_name: &str) -> Result<(u64, u64)> {
parse_qgroup_usage(&stdout).with_context(|| format!("parse qgroup usage for {agent_name}"))
}
/// How long hive-c0re waits for hive-priv's answer to a disk measurement:
/// hive-priv's own bound plus a margin, so a hung hive-priv can't stall the
/// caller while a slow walk still arrives as hive-priv's timeout error.
const MEASURE_AGENT_DISK_WAIT: std::time::Duration =
MEASURE_AGENT_DISK_TIMEOUT.saturating_add(std::time::Duration::from_mins(1));
/// Measure an agent's disk usage in bytes (`state/` + `work/` + container
/// rootfs, one bounded `du` run by hive-priv as root).
///
/// # Errors
/// Returns an error if the hive-priv call fails, the `du` walk times out or
/// exits non-zero, or the returned total isn't a number.
/// Returns an error if the hive-priv call fails or doesn't answer within
/// [`MEASURE_AGENT_DISK_WAIT`], the `du` walk times out or exits non-zero,
/// the agent has none of the paths, or the returned total isn't a number.
pub async fn measure_agent_disk(agent_name: &str) -> Result<u64> {
let (stdout, _) = check(
let resp = within(
MEASURE_AGENT_DISK_WAIT,
call(&PrivRequest::MeasureAgentDisk {
agent_name: agent_name.to_owned(),
})
.await?,
)?;
}),
)
.await?;
let (stdout, _) = check(resp)?;
stdout
.trim()
.parse()
.with_context(|| format!("parse disk usage for {agent_name}: {stdout:?}"))
}
/// Await `fut`, failing if it takes longer than `limit`.
async fn within<T>(
limit: std::time::Duration,
fut: impl std::future::Future<Output = Result<T>>,
) -> Result<T> {
tokio::time::timeout(limit, fut)
.await
.map_err(|_| anyhow::anyhow!("hive-priv did not answer within {}s", limit.as_secs()))?
}
/// Set or clear a btrfs qgroup size limit on an agent's state subvolume.
/// `limit_bytes = None` clears it. Requires quota enabled. Via hive-priv.
///
@ -577,3 +597,26 @@ fn ok(resp: PrivResponse) -> Result<()> {
check(resp)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::within;
use std::time::Duration;
#[tokio::test]
async fn a_silent_hive_priv_times_out_as_an_error() {
let err = within(
Duration::from_millis(20),
std::future::pending::<Result<u64, _>>(),
)
.await
.expect_err("a call that never answers must fail");
assert!(err.to_string().contains("did not answer"), "{err}");
}
#[tokio::test]
async fn an_answer_inside_the_limit_passes_through() {
let got = within(Duration::from_secs(5), async { Ok(7_u64) }).await;
assert_eq!(got.expect("answered in time"), 7);
}
}