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

  1. 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.
  2. Disable the interrupts collector with `--collector.interrupts` omitted / use `--collector.disable-defaults` and opt into what works.
  3. Fix the environment: correct /proc mount or container runtime masking (lxcfs, gVisor, seccomp filters) so /proc/interrupts is visible.
  4. 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

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


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)