prometheus/node_exporter · error
interrupts empty
Error message
interrupts empty
What it means
parseInterrupts parses /proc/interrupts. The file must begin with a CPU header line (one column per CPU). If the first `scanner.Scan()` returns false, the file is empty and there is no header to count CPUs from, so the parser returns "interrupts empty". This typically means the kernel's interrupts proc file could not be read or is genuinely empty.
Solutions
- Check the file on the host: `wc -c /proc/interrupts` and `head -1 /proc/interrupts`; if truly empty, the kernel/procfs is not providing it.
- Disable the interrupts collector with `--collector.interrupts` omitted / use `--collector.disable-defaults` and opt into what works.
- Fix the environment: correct /proc mount or container runtime masking (lxcfs, gVisor, seccomp filters) so /proc/interrupts is visible.
- Upgrade the kernel if the platform genuinely doesn't expose interrupts via procfs; nothing the exporter can parse otherwise.
Defensive patterns
Strategy: validation
Validate before calling
// Verify /proc/interrupts is readable and non-empty before enabling the collector
import "os"
func interruptsProcUsable() error {
f, err := os.Open("/proc/interrupts")
if err != nil { return err }
defer f.Close()
buf := make([]byte, 1)
if _, err := f.Read(buf); err != nil {
return errors.New("/proc/interrupts is empty or unreadable")
}
return nil
} Try / catch
if _, err := getInterrupts(); err != nil {
if err.Error() == "interrupts empty" {
logger.Warn("interrupts collector unavailable: /proc/interrupts empty (sandboxed/containerized env?)")
return
}
return err
} Prevention
- Check `head -1 /proc/interrupts` in the target environment before deploying with the interrupts collector.
- Avoid masked/minimal /proc mounts (lxcfs, gVisor) when this collector is enabled.
- Use --collector.disable-defaults with explicit enables so an unusable collector doesn't mask others.
- Alert on node_scrape_collector_success{collector="interrupts"} == 0.
When it happens
Trigger: getInterrupts opens /proc/interrupts and hands the reader to parseInterrupts; the very first scan hits EOF immediately — /proc/interrupts is zero bytes (e.g. permission/restricted /proc, unusual kernel config with CONFIG_GENERIC_IRQ not exposing it, or a corrupted/unsupported procfs in a VM/container). Also triggered by tests passing an empty reader.
Common situations: Running inside containers or hardened VMs where /proc is masked or minimal (e.g. some hypervisors, gVisor, LXC with lxcfs); hardened seccomp/AppArmor setups hiding proc entries; broken /proc mounts; unit tests feeding empty fixtures.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Could not derive a human-readable chip type for
- ZFS / ZFS statistics are not available
- failed to open procfs
- failed to open procfs
- couldn't get buddyinfo
AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07).
Data as JSON: /api/errors/a2df8cff928051d9.
Report an issue: GitHub.
Appendix: source
Thrown at collector/interrupts_linux.go:83
func getInterrupts() (map[string]interrupt, error) {
file, err := os.Open(procFilePath("interrupts"))
if err != nil {
return nil, err
}
defer file.Close()
return parseInterrupts(file)
}
func parseInterrupts(r io.Reader) (map[string]interrupt, error) {
var (
interrupts = map[string]interrupt{}
scanner = bufio.NewScanner(r)
)
if !scanner.Scan() {
return nil, errors.New("interrupts empty")
}
cpuNum := len(strings.Fields(scanner.Text())) // one header per cpu
for scanner.Scan() {
// On aarch64 there can be zero space between the name/label
// and the values, so we need to split on `:` before using
// strings.Fields() to split on fields.
group := strings.SplitN(scanner.Text(), ":", 2)
if len(group) > 1 {
parts := strings.Fields(group[1])
if len(parts) < cpuNum+1 { // irq + one column per cpu + details,
continue // we ignore ERR and MIS for now
}
intName := strings.TrimLeft(group[0], " ")
intr := interrupt{
values: parts[0:cpuNum],
}View on GitHub (pinned to 17ddd77c59)