[Bug]: Agent in Proxmox LXC reports CPU of shared host cores, not the container's actual usage
Welcome!
- I have read the Documentation
- I have checked the Common Issues Guide and my problem was not mentioned there.
- I have searched open and closed issues and my problem was not mentioned before.
- I have verified I am using the latest version available. You can check the latest release here.
Component
Agent
Problem Description
When the agent runs inside an unprivileged Proxmox LXC container, the reported CPU % reflects the utilization of the physical host cores exposed to the container (via lxcfs /proc/stat), not the CPU actually consumed by the container's own processes. An idle container pinned to a host core that a different container is saturating shows near-100% CPU while doing nothing.
Root cause
getCpuMetrics() in agent/cpu.go collects CPU with cpu.Times(false) (gopsutil), which reads /proc/stat unconditionally. There is no cgroup-aware path. Inside an LXC, /proc/stat is served by lxcfs, whose cpuview exposes the raw counters of the host cores in the container's cpuset rather than the container's own cgroup CPU time. So the number tracks whoever is scheduled on those physical cores, regardless of container.
The inflation only appears under cpuset overlap, when a busy neighbor shares a core with an otherwise idle container. Containers on non-overlapping cores report correctly, which is why some users see correct CPU and others don't.
Evidence from my setup
Three containers on the same host:
- Container A (client) is pinned to host cores 2, 5, 6, 7 and runs four workers hard at work on those four cores. Beszel reports ~98% per core — correct.
- Container B (idle) is pinned to host core 2 only and runs nothing. Beszel reports ~58–100%, because core 2 is shared with A. This is wrong: it mirrors core 2's load.
- Container C (idle) is pinned to host cores 0 and 3, which no busy process touches. Beszel reports ~1% — correct, no overlap.
Inside container B, /proc/stat accrues roughly 100 jiffies/s of busy time with no local process consuming CPU, while container C on non-contended cores reads its true ~1%.
Related RAM-in-LXC report: #176 (that one's author notes CPU "works" for them, consistent with a no-overlap cpuset).
Expected Behavior
The agent should report the CPU actually consumed by its own container, not the load of the host cores it happens to share. An idle container should read near 0% regardless of what a neighboring container does on an overlapping core.
Suggested fix: when running inside a container (detectable via cgroup v2), derive CPU from the cgroup's own accounting (cpu.stat, usage_usec) instead of /proc/stat. This is what cAdvisor and node-exporter's cgroup collector do, and it reflects only the container's real usage.
Steps to Reproduce
- On a Proxmox host, create two unprivileged LXCs whose cpusets overlap on at least one host core (the default when small containers float over shared cores).
- Peg the CPU inside container A (e.g. a busy loop on each assigned core).
- Leave container B idle, but pinned to a core that A is using.
- Observe: Beszel shows container B at near 100% CPU, while
topinside B shows every process at 0%.
OS / Architecture
Debian LXC, x86_64 (amd64)
Beszel version
0.19.0
Installation method
Binary
Configuration
Hub Logs
Agent Logs
Source: henrygd/beszel