Centos下的IO監控與分析

 

 

近期要在公司內部作個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情況,所以能夠回答你如下二個問題

    1. 當前系統哪些進程在佔用IO,百分比是多少?

    2. 佔用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監控 ,

  1. 能夠回答系統級IO監控不能回答的2個問題

  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) 
複製代碼
相關文章
相關標籤/搜索