﻿# iotop —— 按进程实时排查磁盘 IO（像 top 一样看 IO）

## 这个工具是做什么的

`iostat` 只到设备级（哪块盘忙），`iotop` 直接告诉你**是哪个进程/线程在读写磁盘**，是定位 iowait 元凶最快的一步。它读内核 taskstats，需要 root。

## 可以回答什么问题

| 问题 | 怎么用 |
|------|--------|
| 哪个进程在大量读写磁盘？ | `iotop -o` 只看有 IO 的进程 |
| 某个进程的 IO 等待时间占比多高？ | `IO>` 列：接近 100% = 几乎全在等 IO |
| 是哪个线程在读/写？ | `iotop` 默认线程模式（`-P` 切回进程聚合） |
| 进程在 swap 吗？ | `SWAPIN` 列非 0 说明在等换入 |

## 数据来源

- **来源接口**：内核 **taskstats** netlink 接口，提供每个进程/线程的实际读写字节、swapin、IO 等待占比。需内核开启 `CONFIG_TASK_IO_ACCOUNTING`。
- **采集方式**：通过 netlink 向内核订阅 per-task 的 IO 统计，按间隔刷新。
- **由此决定的特性**：① **需要 root**（订阅 taskstats 的权限要求），普通用户/受限容器里跑不起来；② 正因为走 taskstats 而非读 `/proc`，才能给出 `pidstat -d` 没有的 `IO>`（IO 等待占比）列；③ 容器里常因拿不到该接口失败，需 `--cap-add SYS_ADMIN` 或在宿主机跑。

```bash
yum install iotop / apt install iotop
sudo iotop               # 必须 root
```

## 一、用法

```bash
sudo iotop               # 交互式实时刷新（默认按 IO 排序）
sudo iotop -o            # --only：只显示当前有 IO 的进程（最常用，屏幕清爽）
sudo iotop -oP           # -P 只看进程，不展开到线程
sudo iotop -a            # --accumulated：显示自 iotop 启动以来的累计量，而非瞬时速率
sudo iotop -d 2          # -d 刷新间隔 2 秒
sudo iotop -u derek      # 只看某用户
sudo iotop -p <PID>      # 只看指定进程
sudo iotop -b -n 5 -o    # --batch 批处理，输出 5 次到 stdout（无交互环境/写日志）
sudo iotop -boqqq -n 3 > io.log   # 批处理 + 静默表头，纯数据落盘
```

## 二、交互键

| 键 | 作用 |
|----|------|
| `o` | 切换"只看有 IO 的进程"（等价 `-o`）|
| `p` | 进程 / 线程视图切换 |
| `a` | 累计 / 瞬时速率切换 |
| `←` `→` | 移动排序列 |
| `r` | 反转排序方向 |
| `q` | 退出 |

## 三、输出与关键列

```bash
Total DISK READ :   0.00 B/s | Total DISK WRITE :  42.50 M/s
Actual DISK READ:   0.00 B/s | Actual DISK WRITE:  40.10 M/s
  TID  PRIO  USER   DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 8922  be/4  derek    0.00 B/s  42.50 M/s   0.00 %  93.20 %  cpu_demo
```

- 顶部 `Total` 是应用层发起的读写；`Actual` 是最终落到磁盘的（差异来自页缓存/合并/延迟写）。
- 列：

| 列 | 含义 | 判读 |
|----|------|------|
| `DISK READ`/`DISK WRITE` | 该进程/线程每秒读/写磁盘速率 | 找吞吐大户 |
| `SWAPIN` | 换页读入占比 | 高 = 内存不足，进程在等被换出的页换回来 |
| `IO>` | 该进程花在**等待 IO** 上的时间百分比 | **接近 100% = 它几乎一直在等 IO**，是 iowait 主要来源 |
| `PRIO` | IO 调度优先级（`be/4` = best-effort 第4级）| 可用 `ionice` 调整 |
| `TID`/`COMMAND` | 线程号 / 命令 | `-P` 切回进程聚合 |

## 四、典型排查

- `top`/`iostat` 见 iowait 高 → `sudo iotop -o` 一眼看出谁 `DISK WRITE`/`IO>` 最高。
- `IO>` 某进程接近 100% 但 `DISK READ/WRITE` 数值不大 → 它在做**同步小 IO**（每次都等落盘），吞吐不高但把时间全耗在等待上——正是 cpu_demo 场景2 那种"频繁小写 + 隐式刷盘"的模式。
- 定位到进程后：`strace -e trace=write,fsync,read -p <PID>` 看具体在读写哪个文件、是否频繁 fsync。

## 五、关键指标速查

| 指标 | 缩写/英文 | 正常值 | 异常信号 |
|------|----------|--------|---------|
| 磁盘读速率 | DISK READ / Disk Read | 视业务 | 突然暴涨 → 大量读盘 |
| 磁盘写速率 | DISK WRITE / Disk Write | 视业务 | 突然暴涨 + iowait 高 → 写瓶颈 |
| IO 等待占比 | `IO>` / IO Wait % | < 10% | 接近 100% → 进程几乎所有时间在等 IO |
| 换页读入 | `SWAPIN` / Swap In % | 0% | > 0 → 内存不足，该进程被换出页正在换回 |
| IO 优先级 | PRIO / IO Priority | be/4（默认） | rt → 实时 IO 优先级，可能饿死其他 IO |

## 六、常见排查方法与分析

### 6.1 定位 iowait 元凶

```bash
top → %Cpu wa 高
  → iostat -x 1 → 确认哪块盘 await 高
    → sudo iotop -o → 直接看 DISK WRITE/READ 和 IO> 最高的进程
      → IO> 接近 100% 但吞吐不大 → 同步小 IO（每次都等落盘）
      → DISK WRITE 几十 MB/s 但 IO> 不高 → 大块顺序写（正常）
```

### 6.2 IO> 高但吞吐量不大

**现象**：`IO>` 某进程 90%+，但 `DISK READ/WRITE` 只有几 KB/s。

**根因**：同步小 IO——频繁 `write()` + `fsync()`，每次等待落盘再发下一个。虽然每次数据量小，但等待时间全耗在"等落盘"上。

**排查**：

```bash
strace -e trace=write,fsync -p <PID>
# 如果看到 write() → fsync() → write() → fsync()... 重复出现
# → 典型的小 IO + 隐式刷盘模式
# → 改成批量写 + 统一 fsync，或使用缓冲 IO
```

### 6.3 某个进程在 swap

```bash
sudo iotop -o → SWAPIN 列非零
  → 内存不足的证据
    → free -h 看 available
    → ps aux --sort=-%mem | head 找吃内存进程
```

### 6.4 无 root 时的进程级 IO 定位

```bash
pidstat -d 1 → 看 kB_rd/s, kB_wr/s, iodely
# 虽然没有 IO> 占比，但能看出谁在读写
# 结合 iostat -x 看设备级延迟
```

## 七、交叉引用

- **设备级 IO**：[iostat](/tools/disk/iostat.md)（先确认哪块盘有问题再看进程）
- **进程级 IO（无 root）**：[pidstat](/tools/cpu/pidstat.md) `-d`（无 root 时的替代方案）
- **系统调用定位**：[strace](/tools/code/strace.md)（看到具体在读写什么文件）
- **IO 调度器**：[kernel-tuning](/tools/proc/kernel-tuning.md) §二（块设备队列/scheduler 调优）
- **CPU iowait 入口**：[top](/tools/cpu/top.md) `wa` 列

## 八、无 root / 容器受限时的替代

```bash
pidstat -d 1             # sysstat 提供，普通用户即可看进程级 kB_rd/s、kB_wr/s（但无 IO> 等待占比）
```

> 容器里 iotop 常因拿不到 taskstats 权限失败，需 `--cap-add SYS_ADMIN` 或在宿主机上跑。

