近期要在公司內部作個Linux IO方面的培訓, 整理下手頭的資料給你們分享下node
各類IO監視工具在Linux IO 體系結構中的位置python
源自 Linux Performance and Tuning Guidelines.pdfmysql
1 系統級IO監控
iostat
iostat -xdm 1 # 我的習慣ios
%util 表明磁盤繁忙程度。100% 表示磁盤繁忙, 0%表示磁盤空閒。可是注意,磁盤繁忙不表明磁盤(帶寬)利用率高 算法
argrq-sz 提交給驅動層的IO請求大小,通常不小於4K,不大於max(readahead_kb, max_sectors_kb)sql
可用於判斷當前的IO模式,通常狀況下,尤爲是磁盤繁忙時, 越大表明順序,越小表明隨機bash
svctm 一次IO請求的服務時間,對於單塊盤,徹底隨機讀時,基本在7ms左右,既尋道+旋轉延遲時間多線程
注: 各統計量之間關係app
=======================================ide
%util = ( r/s + w/s) * svctm / 1000 # 隊列長度 = 到達率 * 平均服務時間
avgrq-sz = ( rMB/s + wMB/s) * 2048 / (r/s + w/s) # 2048 爲 1M / 512
=======================================
總結:
iostat 統計的是通用塊層通過合併(rrqm/s, wrqm/s)後,直接向設備提交的IO數據,能夠反映系統總體的IO情況,可是有如下2個缺點:
1 距離業務層比較遙遠,跟代碼中的write,read不對應(因爲系統預讀 + pagecache + IO調度算法等因素, 也很難對應)
2 是系統級,沒辦法精確到進程,好比只能告訴你如今磁盤很忙,可是沒辦法告訴你是誰在忙,在忙什麼?
2 進程級IO監控
iotop 和 pidstat (僅rhel6u系列)
iotop 顧名思義, io版的top
pidstat 顧名思義, 統計進程(pid)的stat,進程的stat天然包括進程的IO情況
這兩個命令,均可以按進程統計IO情況,所以能夠回答你如下二個問題
-
-
當前系統哪些進程在佔用IO,百分比是多少?
-
佔用IO的進程是在讀?仍是在寫?讀寫量是多少?
-
pidstat 參數不少,僅給出幾個我的習慣
pidstat -d 1 #只顯示IO
pidstat -u -r -d -t 1 # -d IO 信息,
# -r 缺頁及內存信息
# -u CPU使用率
# -t 以線程爲統計單位
# 1 1秒統計一次
iotop, 很簡單,直接敲命令
block_dump, iodump
iotop 和 pidstat 用着很爽,但二者都依賴於/proc/pid/io文件導出的統計信息, 這個對於老一些的內核是沒有的,好比rhel5u2
所以只好用以上2個窮人版命令來替代:
echo 1 > /proc/sys/vm/block_dump # 開啓block_dump,此時會把io信息輸入到dmesg中
# 源碼: submit_bio@ll_rw_blk.c:3213
watch -n 1 "dmesg -c | grep -oP \"\w+\(\d+\): (WRITE|READ)\" | sort | uniq -c"
# 不停的dmesg -c
echo 0 > /proc/sys/vm/block_dump # 不用時關閉
也可使用現成的腳本 iodump, 具體參見 http://code.google.com/p/maatkit/source/browse/trunk/util/iodump?r=5389
iotop.stp
systemtap腳本,一看就知道是iotop命令的窮人複製版,須要安裝Systemtap, 默認每隔5秒輸出一次信息
stap iotop.stp # examples/io/iotop.stp
總結
進程級IO監控 ,
-
能夠回答系統級IO監控不能回答的2個問題
-
距離業務層相對較近(例如,能夠統計進程的讀寫量)
可是也沒有辦法跟業務層的read,write聯繫在一塊兒,同時顆粒度較粗,沒有辦法告訴你,當前進程讀寫了哪些文件? 耗時? 大小 ?
3 業務級IO監控
ioprofile
ioprofile 命令本質上是 lsof + strace, 具體下載可見 http://code.google.com/p/maatkit/
ioprofile 能夠回答你如下三個問題:
1 當前進程某時間內,在業務層面讀寫了哪些文件(read, write)?
2 讀寫次數是多少?(read, write的調用次數)
3 讀寫數據量多少?(read, write的byte數)
假設某個行爲會觸發程序一次IO動做,例如: "一個頁面點擊,致使後臺讀取A,B,C文件"
============================================
./io_event # 假設模擬一次IO行爲,讀取A文件一次, B文件500次, C文件500次
ioprofile -p `pidof io_event` -c count # 讀寫次數
ioprofile -p `pidof io_event` -c times # 讀寫耗時
ioprofile -p `pidof io_event` -c sizes # 讀寫大小
注: ioprofile 僅支持多線程程序,對單線程程序不支持. 對於單線程程序的IO業務級分析,strace足以。
總結:
ioprofile本質上是strace,所以能夠看到read,write的調用軌跡,能夠作業務層的io分析(mmap方式無能爲力)
4 文件級IO監控
文件級IO監控能夠配合/補充"業務級和進程級"IO分析
文件級IO分析,主要針對單個文件, 回答當前哪些進程正在對某個文件進行讀寫操做.
1 lsof 或者 ls /proc/pid/fd
2 inodewatch.stp
lsof 告訴你 當前文件由哪些進程打開
lsof ../io # io目錄 當前由 bash 和 lsof 兩個進程打開
lsof 命令 只能回答靜態的信息, 而且"打開" 並不必定"讀取", 對於 cat ,echo這樣的命令, 打開和讀取都是瞬間的,lsof很難捕捉
能夠用 inodewatch.stp 來彌補
stap inodewatch.stp major minor inode # 主設備號, 輔設備號, 文件inode節點號
stap inodewatch.stp 0xfd 0x00 523170 # 主設備號, 輔設備號, inode號,能夠經過 stat 命令得到
[root@server-mysql ~]# stat test.c File: `test.c' Size: 375 Blocks: 8 IO Block: 4096 regular file Device: 803h/2051d Inode: 1208533 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2016-06-28 23:27:56.327543866 +0800 Modify: 2015-12-27 22:13:27.654476214 +0800 Change: 2015-12-27 22:13:27.823852491 +0800
5 IO模擬器
iotest.py # 見附錄
開發人員能夠 利用 ioprofile (或者 strace) 作詳細分析系統的IO路徑,而後在程序層面作相應的優化。
可是通常狀況下調整程序,代價比較大,尤爲是當不肯定修改方案到底能不能有效時,最好有某種模擬途徑以快速驗證。
覺得咱們的業務爲例,發現某次查詢時,系統的IO訪問模式以下:
訪問了A文件一次
訪問了B文件500次, 每次16字節, 平均間隔 502K
訪問了C文件500次, 每次200字節, 平均間隔 4M
這裏 B,C文件是交錯訪問的, 既
1 先訪問B,讀16字節,
2 再訪問C,讀200字節,
3 回到B,跳502K後再讀16字節,
4 回到C,跳4M後,再讀200字節
5 重複500次
strace 文件以下:
一個簡單樸素的想法, 將B,C交錯讀,改爲先批量讀B , 再批量讀C,所以調整strace 文件以下:
將調整後的strace文件, 做爲輸入交給 iotest.py, iotest.py 按照 strace 文件中的訪問模式, 模擬相應的IO
iotest.py -s io.strace -f fmap
fmap 爲映射文件,將strace中的222,333等fd,映射到實際的文件中
===========================
111 = /opt/work/io/A.data
222 = /opt/work/io/B.data
333 = /opt/work/io/C.data
===========================
6 磁盤碎片整理
一句話: 只要磁盤容量不常年保持80%以上,基本上不用擔憂碎片問題。
若是實在擔憂,能夠用 defrag 腳本
7 其餘IO相關命令
blockdev 系列
=======================================
blockdev --getbsz /dev/sdc1 # 查看sdc1盤的塊大小
block blockdev --getra /dev/sdc1 # 查看sdc1盤的預讀(readahead_kb)大小
blockdev --setra 256 /dev/sdc1 # 設置sdc1盤的預讀(readahead_kb)大小,低版的內核經過/sys設置,有時會失敗,不如blockdev靠譜
=======================================
附錄 iotest.py
#! /usr/bin/env python# -*- coding: gbk -*-import os import re import timeit from ctypes import CDLL, create_string_buffer, c_ulong, c_longlong from optparse import OptionParser usage = '''%prog -s strace.log -f fileno.map ''' _glibc = None _glibc_pread = None _c_char_buf = None _open_file = [] def getlines(filename): _lines = [] with open(filename,'r') as _f: for line in _f: if line.strip() != "": _lines.append(line.strip()) return _lines def parsecmdline(): parser = OptionParser(usage) parser.add_option("-s", "--strace", dest="strace_filename", help="strace file", metavar="FILE") parser.add_option("-f", "--fileno", dest="fileno_filename", help="fileno file", metavar="FILE") (options, args) = parser.parse_args() if options.strace_filename is None: parser.error("strace is not specified.") ifnot os.path.exists(options.strace_filename): parser.error("strace file does not exist.") if options.fileno_filename is None: parser.error("fileno is not specified.") ifnot os.path.exists(options.strace_filename): parser.error("fileno file does not exist.") return options.strace_filename, options.fileno_filename # [type, ...]# [pread, fno, count, offset]# pread(15, "", 4348, 140156928)def parse_strace(filename): lines = getlines(filename) action = [] _regex_str = r'(pread|pread64)[^\d]*(\d+),\s*[^,]*,\s*([\dkKmM*+\-. ]*),\s*([\dkKmM*+\-. ]*)'for i in lines: _match = re.match(_regex_str, i) if _match is None: continue# 跳過無效行 _type, _fn, _count, _off = _match.group(1), _match.group(2), _match.group(3), _match.group(4) _off = _off.replace('k', " * 1024 ").replace('K', " * 1024 ").replace('m', " * 1048576 ").replace('M', " * 1048576 ") _count = _count.replace('k', " * 1024 ").replace('K', " * 1024 ").replace('m', " * 1048576 ").replace('M', " * 1048576 ") #print _off action.append([_type, _fn, str(int(eval(_count))), str(int(eval(_off))) ]) return action def parse_fileno(filename): lines = getlines(filename) fmap = {} for i in lines: if i.strip().startswith("#"): continue# 註釋行 _split = [j.strip() for j in i.split("=")] if len(_split) != 2: continue# 無效行 fno, fname = _split[0], _split[1] fmap[fno] = fname return fmap def simulate_before(strace, fmap): global _open_file, _c_char_buf rfmap = {} for i in fmap.values(): _f = open(i, "r+b") #print "open {0}:{1}".format(_f.fileno(), i) _open_file.append(_f) rfmap[i] = str(_f.fileno()) # 反向映射 to_read = 4 * 1024 # 默認4K buffor i in strace: i[1] = rfmap[fmap[i[1]]] # fid -> fname -> fid 映射轉換 to_read = max(to_read, int(i[2])) #print "read buffer len: %d Byte" % to_read _c_char_buf = create_string_buffer(to_read) def simulate_after(): global _open_file for _f in _open_file: _f.close() def simulate(actions): #timeit.time.sleep(10) # 休息2秒鐘, 以便IO間隔 start = timeit.time.time() for act in actions: __simulate__(act) finish = timeit.time.time() return finish - start def__simulate__(act): global _glibc, _glibc_pread, _c_char_buf if"pread"in act[0]: _fno = int(act[1]) _buf = _c_char_buf _count = c_ulong(int(act[2])) _off = c_longlong(int(act[3])) _glibc_pread(_fno, _buf, _count, _off) #print _glibc.time(None)else: passpassdef loadlibc(): global _glibc, _glibc_pread _glibc = CDLL("libc.so.6") _glibc_pread = _glibc.pread64 if__name__ == "__main__": _strace, _fileno = parsecmdline() # 解析命令行參數 loadlibc() # 加載動態庫 _action = parse_strace(_strace) # 解析 action 文件 _fmap = parse_fileno(_fileno) # 解析 文件名映射 文件 simulate_before(_action, _fmap) # 預處理#print "total io operate: %d" % (len(_action))#for act in _action: print " ".join(act)print"%f" % simulate(_action)