---
title: 'Warum wir iowait nicht mehr messen'
url: 'https://www.linuxfabrik.ch/de/blog/mystic-iowait'
markdown: 'https://www.linuxfabrik.ch/de/blog/mystic-iowait.md'
lang: de
date: '2026-07-13'
description: 'iowait sieht aus wie ein Mass für überlasteten Storage, ist aber umetikettierte Idle-Zeit und laut Kernel auf Mehrkern-Systemen unbrauchbar. Warum wir es aus disk-io entfernt haben.'
taxonomy:
  category:
    - blog
  tag:
    - linux
    - monitoring-plugins
  archives_month:
    - jul_2026
  archives_year:
    - '2026'
---

# Warum wir iowait nicht mehr messen

[ Home ](https://www.linuxfabrik.ch/de)     [ Blog ](https://www.linuxfabrik.ch/de/blog)      Warum wir iowait nicht mehr messen    

Unser Monitoring-Plugin [disk-io](https://linuxfabrik.github.io/monitoring-plugins/check-plugins/disk-io/) misst kein iowait mehr. Was nach einem Rückschritt klingt, ist ein Fortschritt: iowait sieht wie ein Mass für einen überlasteten Storage aus, ist aber umetikettierte Idle-Zeit, und auf jedem Mehrkern-System laut den Kernel-Entwicklern selbst schlicht unbrauchbar. Ausgelöst durch ein GitHub-Issue für `disk-io` auf ZFS haben wir uns den Quellcode von Kernel, `sysstat`, `psutil`, `glances` und OpenZFS zu Gemüte geführt - mit eindeutigem Ergebnis.

## Das Mysterium iowait

Wir hatten iowait in `disk-io` als Zusatzsignal drin, wurden aber mit dessen Aussage im Datacenter-Betrieb selbst nie ganz warm - der Wert war schwer zu fassen. Der Anlass, das endlich mal genauer anzuschauen, kam mit [Issue #1371](https://github.com/Linuxfabrik/monitoring-plugins/issues/1371): Hier meldet sich `disk-io` auf zwei frisch gestarteten Proxmox-Servern in verschiedenen Rechenzentren mit Werten wie `iowait 377%`. Beide Server waren praktisch im Leerlauf, die Platten hatten mit ein paar Kilobyte pro Sekunde nichts zu tun, die VMs liefen sauber. Gemeinsamkeit der beiden Hosts: ZFS. Die korrekte Einschätzung des Reporters: "These high iowaits must be fantasy numbers."

## Was iowait wirklich ist

Der Linux-Kernel sagt eindeutig: **iowait ist keine Zeit, die eine CPU mit I/O verbringt, sondern Idle-Zeit unter anderem Namen.** Der Kernel betitelt die zuständige Sektion in `kernel/sched/core.c` genau so und definiert den Wert direkt darunter:

```text
IO-wait accounting, and how it's mostly bollocks (on SMP).

The idea behind IO-wait account is to account the idle time that we could
have spend running if it were not for IO. That is, if we were to improve the
storage performance, we'd have a proportional reduction in IO-wait time.
```

iowait ist damit definitionsgemäss Idle-Zeit, nur anders etikettiert. Das zeigt auch der Programmcode: die zuständige Routine bekommt Idle-Zeit übergeben und entscheidet nur noch, in welches Bucket sie wandert. Der Kernel bucht die Idle-Zeit einer CPU auf iowait statt auf idle, sobald mindestens ein Task, der zuletzt auf dieser CPU lief, im I/O-Wartezustand schläft (`kernel/sched/cputime.c`):

```c
void account_idle_time(u64 cputime)
{
    u64 *cpustat = kcpustat_this_cpu->cpustat;
    struct rq *rq = this_rq();

    if (atomic_read(&rq->nr_iowait) > 0)
        cpustat[CPUTIME_IOWAIT] += cputime;
    else
        cpustat[CPUTIME_IDLE] += cputime;
}
```

Der Zähler `nr_iowait` wird inkrementiert, sobald ein Task über `io_schedule()` schlafen geht, was etwas völlig anderes bedeutet als "die Platte war ausgelastet". iowait misst also "verlorene Rechenzeit durch I/O-Warten", nicht "wie ausgelastet ist die Platte". Der erfrischend ehrliche Kommentar in `kernel/sched/core.c` über den Wert dieser Zahl auf Multicore-Systemen:

```text
Worse, since the numbers are provided per CPU, they are sometimes
interpreted per CPU, and that is nonsensical. A blocked task isn't strictly
associated with any one particular CPU, it can wake to another CPU than it
blocked on. This means the per CPU IO-wait number is meaningless.
```

Sauber interpretierbar ist iowait wenn überhaupt nur auf einem Einzelkern-System, und selbst dort hilft der Wert nicht dabei, Aussagen über Disks zu treffen. Grund genug für uns, den Wert aus `disk-io` (wieder) zu entfernen.

## %util

Was ist mit `%util` von `iostat` als Ersatz? Auch hier lohnt der Blick in den Code. `iostat` berechnet `%util` aus dem Zähler `io_ticks` in `/proc/diskstats` (`sysstat`, `rd_stats.c`). `io_ticks` zählt laut Kernel (`block/blk-core.c`, `update_io_ticks`) die Wall-Clock-Zeit, in der mindestens ein I/O-Request in Bearbeitung war, **nicht** die Queue-Tiefe.

Für eine klassische Einzel-Queue-Platte wie eine HDD ist der Wert brauchbar, aber für alles, was Requests parallel abarbeitet (NVMe, SSDs, `dm`/`md`-RAID und ZFS-zvols) ist es das nicht: `%util` steht bei 100%, sobald durchgehend ein Request offen ist, egal wie weit das Gerät von seinem echten Limit entfernt ist. Wir behalten `%util` als reine Trend-Metrik (Linux, per Device), aber alarmieren nicht darauf.

## Warum ZFS so absurde Zahlen liefert

Warum ausgerechnet ZFS `disk-io` auf 377% getrieben hat, beantwortet der Code zu OpenZFS: ZFS leitet seine synchronen I/O-Waits über `cv_wait_io()` respektive `cv_timedwait_io()`, und die enden im Kernel bei `io_schedule()` (`module/os/linux/spl/spl-condvar.c`). Ein Thread, der auf eine physische I/O wartet (`zio_wait()`), oder auf das Commit einer Transaction-Group (`txg_wait_synced()`), schläft also genau über den Pfad, der `nr_iowait` hochzählt. OpenZFS kommentiert das in `module/zfs/txg.c` sogar ausdrücklich:

```text
Callers setting should_quiesce will use cv_wait_io() and be accounted for
as iowait time. Otherwise, the caller is understood to be idle and
cv_wait_sig() is used to prevent incorrectly inflating the system load average.
```

Über viele vCPUs verteilt schiebt das einen überproportionalen Anteil der ansonsten leeren Idle-Zeit in die iowait-Statistik. Ein fast leerer Pool mit ein paar Kilobyte pro Sekunde landet so bei mehreren "gesättigten Cores", während die Platten faktisch nichts tun. Die Zahlen sind echt vom Kernel gemessen. Sie bedeuten nur nicht das, was man vermutet.

## Was `glances` macht

Wir haben zum Vergleich den `glances`-Code hinzugezogen, da es wie wir die `psutil`-Bibliothek nutzt. `glances` macht es nicht besser, was zeigt, dass die Annahme weit verbreitet ist: `glances` alarmiert per Default auf iowait, und zwar gegen genau die Schwelle, die auf Multicore-Systemen am ehesten zu Fehlalarmen führt. Der Default-Schwellwert ist 1 / Anzahl CPU-Kerne, ab dann wird das Feld eingefärbt, ein Event "High CPU I/O waiting" in die Events-Liste geschrieben und optional eine hinterlegte Action ausgelöst (`glances/config.py`, `glances/events_list.py`).

## Fazit

iowait

- misst keine Disk-Sättigung, sondern umetikettierte Idle-Zeit,
- ist laut Kernel auf allen Mehrkern-Systemen unzuverlässig,
- liefert auf ZFS regelmässig Fantasiewerte,
- und taugt damit weder als Alarm- noch als verlässliche Trend-Metrik.

Damit haben wir es aus `disk-io` entfernt (in `cpu-usage` trägt es auch nicht mehr zur Alarmierung bei). `disk-io` misst den realen Durchsatz pro Gerät und vergleicht ihn mit dem je beobachteten Maximum, und es liefert `%util` als Linux-Trend-Metrik mit klar dokumentierter Grenze. Die alten Parameter `--iowait-warning` und `--iowait-critical` bleiben aus Kompatibilitätsgründen erhalten, werden aber ignoriert.

## Ausblick

Wir liefern neu stattdessen das Signal, das die Frage "ist mein Storage langsam" tatsächlich beantwortet: `await`, die durchschnittliche I/O-Latenz pro Gerät. Anders als iowait ist das eine physikalische Grösse, robust gegen Geräte-Parallelismus, und genau die Zahl, die man als Admin im Ernstfall braucht.

`await` kommt aus denselben Feldern in `/proc/diskstats`, die auch `iostat` auswertet: die pro Request aufsummierte Bearbeitungszeit inklusive Wartezeit in der Queue, geteilt durch die Anzahl abgeschlossener Requests (`sysstat`, `rd_stats.c`). Das Ergebnis ist die mittlere Zeit in Millisekunden, die eine I/O vom Absetzen bis zur Quittung braucht, bei Bedarf getrennt nach Lesen und Schreiben (`r_await` und `w_await`).

`await` ist ein Mittelwert ("average wait") und verdeckt damit Ausreisser. Eine Platte kann einen harmlosen Durchschnitt zeigen, während einzelne Zugriffe sekundenlang hängen bleiben. Die Zahl allein sagt daher nicht, ob eine hohe Latenz von der Last oder von einer sterbenden Platte kommt. Wir lesen `await` deshalb zusammen mit dem Durchsatz pro Gerät, nicht als isolierten Einzelwert. Aber im Gegensatz zu iowait misst `await` wenigstens das, was draufsteht. :-)

 [ Vorheriger Beitrag](https://www.linuxfabrik.ch/de/blog/kernel-luecke-ssh-keysign-pwn)

#### Tags

  [ansible](https://www.linuxfabrik.ch/de/blog/tag:ansible) [checklistfabrik](https://www.linuxfabrik.ch/de/blog/tag:checklistfabrik) [cloud](https://www.linuxfabrik.ch/de/blog/tag:cloud) [datacenter](https://www.linuxfabrik.ch/de/blog/tag:datacenter) [event](https://www.linuxfabrik.ch/de/blog/tag:event) [firewallfabrik](https://www.linuxfabrik.ch/de/blog/tag:firewallfabrik) [graylog](https://www.linuxfabrik.ch/de/blog/tag:graylog) [icinga](https://www.linuxfabrik.ch/de/blog/tag:icinga) [lfops](https://www.linuxfabrik.ch/de/blog/tag:lfops) [linux](https://www.linuxfabrik.ch/de/blog/tag:linux) [monitoring-plugins](https://www.linuxfabrik.ch/de/blog/tag:monitoring-plugins) [news](https://www.linuxfabrik.ch/de/blog/tag:news) [nextcloud](https://www.linuxfabrik.ch/de/blog/tag:nextcloud) [security](https://www.linuxfabrik.ch/de/blog/tag:security) 

#### Archiv

- [2026](https://www.linuxfabrik.ch/de/blog/archives_year:2026)
- [2025](https://www.linuxfabrik.ch/de/blog/archives_year:2025)
- [2024](https://www.linuxfabrik.ch/de/blog/archives_year:2024)
- [2023](https://www.linuxfabrik.ch/de/blog/archives_year:2023)
- [2022](https://www.linuxfabrik.ch/de/blog/archives_year:2022)
- [2021](https://www.linuxfabrik.ch/de/blog/archives_year:2021)
- [2020](https://www.linuxfabrik.ch/de/blog/archives_year:2020)

#### Abonnements

 [ Atom](https://www.linuxfabrik.ch/de/blog.atom) [ RSS](https://www.linuxfabrik.ch/de/blog.rss)

---

## Navigation

- Parent: [Blog](https://www.linuxfabrik.ch/de/blog.md)
- Next: [Kernel-Lücke 'ssh-keysign-pwn' (CVE-2026-46333)](https://www.linuxfabrik.ch/de/blog/kernel-luecke-ssh-keysign-pwn.md)
