如何監測Linux進程的實時IO讀寫狀況

Linux Kernel 2.6.20 以上的內核支持進程 IO 統計,能夠用相似 iotop 這樣的工具來監測每一個進程對 IO 操做的狀況,就像用 top 來實時查看進程內存、CPU 等佔用狀況那樣。可是對於 2.6.20 如下的 Linux 內核版本就沒那麼幸運了。筆者寫了一個簡單的 Python 腳本用來在 linux kernel < 2.6.20 下打印進程 IO 情況。node

Kernel < 2.6.20python

這個腳本的想法很簡單,把 dmesg 的結果重定向到一個文件後再解析出來,每隔1秒鐘打印一次進程 IO 讀寫的統計信息,執行這個腳本須要 root:linux

  
  
  
  
  1. #!/usr/bin/python  
  2. # Monitoring per-process disk I/O activity  
  3. # written by http://www.vpsee.com   
  4.  
  5. import sys, os, time, signal, re  
  6.  
  7. class DiskIO:  
  8.     def __init__(self, pname=None, pid=None, reads=0, writes=0):  
  9.         self.pname = pname  
  10.         self.pid = pid  
  11.         self.reads = 0 
  12.         self.writes = 0 
  13.  
  14. def main():  
  15.     argc = len(sys.argv)  
  16.     if argc != 1:  
  17.         print "usage: ./iotop" 
  18.         sys.exit(0)  
  19.  
  20.     if os.getuid() != 0:  
  21.         print "must be run as root" 
  22.         sys.exit(0)  
  23.  
  24.     signal.signal(signal.SIGINT, signal_handler)  
  25.     os.system('echo 1 > /proc/sys/vm/block_dump')  
  26.     print "TASK              PID       READ      WRITE" 
  27.     while True:  
  28.         os.system('dmesg -c > /tmp/diskio.log')  
  29.         l = []  
  30.         f = open('/tmp/diskio.log''r')  
  31.         line = f.readline()  
  32.         while line:  
  33.             m = re.match(\  
  34.                 '^(\S+)\((\d+)\): (READ|WRITE) block (\d+) on (\S+)', line)  
  35.             if m != None:  
  36.                 if not l:  
  37.                     l.append(DiskIO(m.group(1), m.group(2)))  
  38.                     line = f.readline()  
  39.                     continue 
  40.                 found = False 
  41.                 for item in l:  
  42.                     if item.pid == m.group(2):  
  43.                         found = True 
  44.                         if m.group(3) == "READ":  
  45.                             item.reads = item.reads + 1 
  46.                         elif m.group(3) == "WRITE":  
  47.                             item.writes = item.writes + 1 
  48.                 if not found:  
  49.                     l.append(DiskIO(m.group(1), m.group(2)))  
  50.             line = f.readline()  
  51.         time.sleep(1)  
  52.         for item in l:  
  53.             print "%-10s %10s %10d %10d" % \  
  54.                 (item.pname, item.pid, item.reads, item.writes)  
  55.  
  56. def signal_handler(signal, frame):  
  57.     os.system('echo 0 > /proc/sys/vm/block_dump')  
  58.     sys.exit(0)  
  59.  
  60. if __name__=="__main__":  
  61.     main()  
  62.  

Kernel >= 2.6.20bash

若是想用 iotop 來實時查看進程 IO 活動情況的話,須要下載和升級新內核(2.6.20 或以上版本)。編譯新內核時須要打開 TASK_DELAY_ACCT 和 TASK_IO_ACCOUNTING 選項。解壓內核後進入配置界面:app

# tar jxvf linux-2.6.30.5.tar.bz2
# mv linux-2.6.30.5 /usr/src/
# cd /usr/src/linux-2.6.30.5

# make menuconfig

選擇 Kernel hacking –> Collect scheduler debugging info 和 Collect scheduler statistics,保存內核後編譯內核:ide

# make; make modules; make modules_install; make install

修改 grub,確認能正確啓動新內核:工具

# vi /boot/grub/menu.lst

出了新內核外,iotop 還須要 Python 2.5 或以上才能運行,因此若是當前 Python 是 2.4 的話須要下載和安裝最新的 Python 包。這裏使用源代碼編譯安裝:ui

# tar jxvf Python-2.6.2.tar.bz2
# cd Python-2.6.2
# ./configure
# make; make install

別忘了下載 setuptools:spa

# mv setuptools-0.6c9-py2.6.egg.sh setuptools-0.6c9-py2.6.egg
# sh setuptools-0.6c9-py2.6.egg

有網友對以上腳本提出問題,問到 WRITE 爲何會出現是 0 的狀況,這是個好問題,筆者在這裏好好解釋一下。首先看看咱們怎麼樣才能實時監測不一樣進程的 IO 活動情況。debug

block_dump

Linux 內核裏提供了一個 block_dump 參數用來把 block 讀寫(WRITE/READ)情況 dump 到日誌裏,這樣能夠經過 dmesg 命令來查看,具體操做步驟是:

# sysctl vm.block_dump=1
or
# echo 1 > /proc/sys/vm/block_dump

而後就能夠經過 dmesg 就能夠觀察到各個進程 IO 活動的情況了:

# dmesg -c
kjournald(542): WRITE block 222528 on dm-0
kjournald(542): WRITE block 222552 on dm-0
bash(18498): dirtied inode 5892488 (ld-linux-x86-64.so.2) on dm-0
bash(18498): dirtied inode 5892482 (ld-2.5.so) on dm-0
dmesg(18498): dirtied inode 11262038 (ld.so.cache) on dm-0
dmesg(18498): dirtied inode 5892496 (libc.so.6) on dm-0
dmesg(18498): dirtied inode 5892489 (libc-2.5.so) on dm-0

問題

一位細心的網友提到這樣一個問題:爲何會有 WRITE block 0 的狀況出現呢?筆者跟蹤了一段時間,發現確實有 WRITE 0 的狀況出現,好比:

# dmesg -c
...
pdflush(23123): WRITE block 0 on sdb1
pdflush(23123): WRITE block 16 on sdb1
pdflush(23123): WRITE block 104 on sdb1
pdflush(23123): WRITE block 40884480 on sdb1
...

答案

原來咱們把 WRITE block 0,WRITE block 16, WRITE block 104 這裏麪包含的數字理解錯了,這些數字不是表明寫了多少 blocks,是表明寫到哪一個 block,爲了尋找真相,筆者追到 Linux 2.6.18 內核代碼裏,在 ll_rw_blk.c 裏找到了答案:

$ vi linux-2.6.18/block/ll_rw_blk.c

  
  
  
  
  1. void submit_bio(int rw, struct bio *bio)  
  2. {  
  3.         int count = bio_sectors(bio);  
  4.  
  5.         BIO_BUG_ON(!bio->bi_size);  
  6.         BIO_BUG_ON(!bio->bi_io_vec);  
  7.         bio->bi_rw |= rw;  
  8.         if (rw & WRITE)  
  9.                 count_vm_events(PGPGOUT, count);  
  10.         else 
  11.                 count_vm_events(PGPGIN, count);  
  12.  
  13.         if (unlikely(block_dump)) {  
  14.                 char b[BDEVNAME_SIZE];  
  15.                 printk(KERN_DEBUG "%s(%d): %s block %Lu on %s\n",  
  16.                         current->comm, current->pid,  
  17.                         (rw & WRITE) ? "WRITE" : "READ",  
  18.                         (unsigned long long)bio->bi_sector,  
  19.                         bdevname(bio->bi_bdev,b));  
  20.         }  
  21.  
  22.         generic_make_request(bio);  
  23. }  

很明顯從上面代碼能夠看出 WRITE block 0 on sdb1,這裏的 0 是 bio->bi_sector,是寫到哪一個 sector,不是 WRITE 了多少 blocks 的意思。還有,若是 block 設備被分紅多個區的話,這個 bi_sector(sector number)是從這個分區開始計數,好比 block 0 on sdb1 就是 sdb1 分區上的第0個 sector 開始。

相關文章
相關標籤/搜索