Linux服務器開發監控之 IO

簡介

能夠經過以下命令查看與 IO 相關的系統信息。python

tune2fs -l /dev/sda7 ← 讀取superblock信息 # blockdev --getbsz /dev/sda7 ← 獲取block大小

tune2fs -l /dev/sda7 | grep "Block size" ← 同上 # dumpe2fs /dev/sda7 | grep "Block size" ← 同上 # stat /boot/ | grep "IO Block" ← 同上 # fdisk -l ← 硬盤的扇區大小(Sector Size

)ios

在 WiKi 中的定義:A 「block」, a contiguous number of bytes, is the minimum unit of memory that is read from and written to a disk by a disk driver。git

塊是文件系統的抽象,而非磁盤的屬性,通常是 Sector Size 的倍數;扇區大小則是磁盤的物理屬性,它是磁盤設備尋址的最小單元。另外,內核中要求 Block_Size = Sector_Size * (2的n次方),且 Block_Size <= 內存的 Page_Size (頁大小)。github

【文章福利】小編推薦本身的C/C++Linux羣:832218493!整理了一些我的以爲比較好的學習書籍、視頻資料共享在裏面,有須要的能夠自行添加哦!~
在這裏插入圖片描述
在這裏插入圖片描述算法

磁盤使用空間

其實是經過 statvfs() 方法查詢磁盤數據,能夠經過以下命令查看。json

$ python -c 'import os; os.statvfs("/")'

其空間佔用大體以下。bash

+--------------------------+----------------+-------------------------------------------------------+
   |                          |                |                                                       |
   +--------------------------+----------------+-------------------------------------------------------+
   |<-- f_bavail(non-root) -->|<-- reserved -->|<------------- f_bused=f_blocks-f_bfree -------------->|
   |<------------- f_bfree(root) ------------->|                                                       |
   |<----------------------------------------- f_blocks ---------------------------------------------->|

前者是非 root 用戶已經使用的佔非 root 用戶可用空間百分數;後者是保留給 root 用戶以及已經使用磁盤佔整個磁盤空間百分數。對於 extN 類的文件系統通常會保留 1%~5% 的磁盤空間給 root 使用,當 reserved 佔比較大時會致使二者的計算差較大。併發

磁盤類型

主要是要獲取當前系統使用的什麼類型的磁盤 (SCSI、IDE、SSD等),甚至是製造商、機器型號、序列號等信息。ionic

$ dmesg | grep scsi

監控指標

簡單列舉磁盤監控時常見的指標。ide

IOPS 每秒IO數
  對磁盤來講,一次磁盤的連續讀或寫稱爲一次磁盤 IO,當傳輸小塊不連續數據時,該指標有重要參考意義。
Throughput 吞吐量
  硬盤傳輸數據流的速度,單位通常爲 MB/s,在傳輸大塊不連續數據的數據,該指標有重要參考做用。
IO平均大小
  實際上就是吞吐量除以 IOPS,用於判斷磁盤使用模式,通常大於 32K 爲順序讀取爲主,不然隨機讀取爲主。
Utilization 磁盤活動時間百分比
  磁盤處於活動狀態 (數據傳輸、尋道等) 的時間百分比,也即磁盤利用率,通常該值越高對應的磁盤資源爭用越高。
Service Time 服務時間
  磁盤讀寫操做執行的時間,對於機械磁盤包括了尋道、旋轉、數據傳輸等,與磁盤性能相關性較高,另外,也受 CPU、內存影響。
Queue Length 等待隊列長度
  待處理的 IO 請求的數目,注意,若是該磁盤爲磁盤陣列虛擬的邏輯驅動器,須要除以實際磁盤數,以獲取單盤的 IO 隊列。
Wait Time 等待時間
  在隊列中排隊的時間。

iostat 系統級

除了能夠經過該命令查看磁盤信息以外,還能夠用來查看 CPU 信息,分別經過 -d 和 -c 參數控制;可直接經過 iostat -xdm 1 命令顯示磁盤隊列的長度等信息。

Device: rrqm/s wrqm/s   r/s   w/s  rMB/s  wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda       0.02   1.00  0.99  1.84   0.03   0.04    46.98     0.01  2.44    0.47    3.49  0.25

0.07
其中參數以下:

rrqm/s wrqm/s
  讀寫請求每秒合併後發送給磁盤的請求。

r/s w/s
  應用發送給系統的請求數目。

argrq-sz
  提交給驅動層IO請求的平均大小(sectors),通常不小於4K,不大於max(readahead_kb, max_sectors_kb);
  可用於判斷當前的 IO 模式,越大表明順序,越小表明隨機;計算公式以下:
  argrq-sz = (rsec + wsec) / (r + w)

argqu-sz Average Queue Size
  在驅動層的隊列排隊的平均長度。

await Average Wait
  平均的等待時間,包括了在隊列中的等待時間,以及磁盤的處理時間。

svctm(ms) Service Time
  請求發送給IO設備後的響應時間,也就是一次磁盤IO請求的服務時間,不過該指標官網說不許確,要取消。
  對於單塊SATA盤,徹底隨機讀時,基本在7ms左右,既尋道+旋轉延遲時間。

%util
  一秒內IO操做所佔的比例,計算公式是(r/s+w/s)*(svctm/1000),例如採集時間爲 1s 其中有 0.8s 在處
  理 IO 請求,那麼 util 爲 80% ;對於一塊磁盤,若是沒有併發IO的概念,那麼這個公式是正確的,但
  是對於RAID磁盤組或者SSD來講,這個計算公式就有問題了,就算這個值超過100%,也不表明存儲有瓶頸,
  容易產生誤導


iostat 統計的是通用塊層通過合併 (rrqm/s, wrqm/s) 後,直接向設備提交的 IO 數據,能夠反映系統總體的 IO 情況,可是距離應用層比較遠,因爲系統預讀、Page Cache、IO調度算法等因素,很難跟代碼中的 write()、read() 對應。

簡言之,這是系統級,沒辦法精確到進程,好比只能告訴你如今磁盤很忙,可是沒辦法告訴你是那個進程在忙,在忙什麼?

/proc/diskstats
該命令會讀取 /proc/diskstats 文件,各個指標詳細的含義能夠參考內核文檔 iostats.txt,其中各個段的含義以下。

filed1  rd_ios
  成功完成讀的總次數;
filed2  rd_merges
  合併寫完成次數,經過合併提升效率,例如兩次4K合併爲8K,這樣只有一次IO操做;合併操做是由IO Scheduler(也叫 Elevator)負責。
filed3  rd_sectors
  成功讀過的扇區總次數;
filed4  rd_ticks
  全部讀操做所花費的毫秒數,每一個讀從__make_request()開始計時,到end_that_request_last()爲止,包括了在隊列中等待的時間;
filed5  wr_ios
  成功完成寫的總次數;
filed6  wr_merges
  合併寫的次數;
filed7  wr_sectors
  成功寫過的扇區總次數;
filed8  wr_ticks
  全部寫操做所花費的毫秒數;
filed9  in_flight
  如今正在進行的IO數目,在IO請求進入隊列時該值加1,在IO結束時該值減1,注意是在進出隊列時,而非交給磁盤時;
filed10 io_ticks
  輸入/輸出操做花費的毫秒數;
filed11 time_in_queue
  是一個權重值,當有上面的IO操做時,這個值就增長。

須要注意 io_ticks 與 rd/wr_ticks 的區別,後者是把每個 IO 所消耗的時間累加在一塊兒,由於硬盤設備一般能夠並行處理多個 IO,因此統計值每每會偏大;而前者表示該設備有 IO 請求在處理的時間,也就是非空閒,不考慮 IO 有多少,只考慮如今有沒有 IO 操做。在實際計算時,會在字段 in_flight 不爲零的時候 io_ticks 保持計時,爲 0 時中止計時。

另外,io_ticks 在統計時不考慮當前有幾個 IO,而 time_in_queue 是用當前的 IO 數量 (in_flight) 乘以時間,統計時間包括了在隊列中的時間以及磁盤處理 IO 的時間。

重要指標

簡單介紹下常見的指標,包括了常常誤解的指標。

util
這裏重點說一下 iostat 中 util 的含義,該參數能夠理解爲磁盤在處理 IO 請求的總時間,若是是 100% 則代表磁盤一直在處理 IO 請求,這也就意味着 IO 在滿負載運行。

對於一塊磁盤,若是沒有併發 IO 的概念,因此這個公式是正確的,可是如今的磁盤或者對於RAID磁盤組以及SSD來講,這個計算公式就有問題了,就算這個值超過100%,也不表明存儲有瓶頸,容易產生誤導。

舉個簡化的例子:某硬盤處理單個 IO 須要 0.1 秒,也就是有能力達到 10 IOPS,那麼當 10 個 IO 請求依次順序提交的時候,須要 1 秒才能所有完成,在 1 秒的採樣週期裏 %util 達到 100%;而若是 10 個 IO 請求一次性提交的話,0.1 秒就所有完成,在 1 秒的採樣週期裏 %util 只有 10%。

可見,即便 %util 高達 100%,硬盤也仍然有可能還有餘力處理更多的 IO 請求,即沒有達到飽和狀態。不過遺憾的是如今 iostat 沒有提供相似的指標。

在 CentOS 中使用的是 github sysstat,以下是其計算方法。

rw_io_stat_loop()  循環讀取
 |-read_diskstats_stat()            從/proc/diskstats讀取狀態
 |-write_stats()                    輸出採集的監控指標
   |-write_ext_stat()
     |-compute_ext_disk_stats()     計算ext選項,如util
     |-write_plain_ext_stat()

關於該參數的代碼詳細介紹以下。

#define S_VALUE(m,n,p)  (((double) ((n) - (m))) / (p) * HZ)

void read_diskstats_stat(int curr)
{
 struct io_stats sdev;
 ... ...
 if ((fp = fopen(DISKSTATS, "r")) == NULL)
  return;

 while (fgets(line, sizeof(line), fp) != NULL) {
  /* major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq */
  i = sscanf(line, "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u",
      &major, &minor, dev_name,
      &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec,
      &wr_ios, &wr_merges, &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks);

  if (i == 14) {
   /* Device or partition */
   if (!dlist_idx && !DISPLAY_PARTITIONS(flags) &&
       !is_device(dev_name, ACCEPT_VIRTUAL_DEVICES))
    continue;
   sdev.rd_ios     = rd_ios;
   sdev.rd_merges  = rd_merges_or_rd_sec;
   sdev.rd_sectors = rd_sec_or_wr_ios;
   sdev.rd_ticks   = (unsigned int) rd_ticks_or_wr_sec;
   sdev.wr_ios     = wr_ios;
   sdev.wr_merges  = wr_merges;
   sdev.wr_sectors = wr_sec;
   sdev.wr_ticks   = wr_ticks;
   sdev.ios_pgr    = ios_pgr;
   sdev.tot_ticks  = tot_ticks;
   sdev.rq_ticks   = rq_ticks;
        }
     ... ...
  save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);
 }
 fclose(fp);
}

void write_json_ext_stat(int tab, unsigned long long itv, int fctr,
      struct io_hdr_stats *shi, struct io_stats *ioi,
      struct io_stats *ioj, char *devname, struct ext_disk_stats *xds,
      double r_await, double w_await)
{
 xprintf0(tab,
   "{\"disk_device\": \"%s\", \"rrqm\": %.2f, \"wrqm\": %.2f, "
   "\"r\": %.2f, \"w\": %.2f, \"rkB\": %.2f, \"wkB\": %.2f, "
   "\"avgrq-sz\": %.2f, \"avgqu-sz\": %.2f, "
   "\"await\": %.2f, \"r_await\": %.2f, \"w_await\": %.2f, "
   "\"svctm\": %.2f, \"util\": %.2f}",
   devname,
   S_VALUE(ioj->rd_merges, ioi->rd_merges, itv),
   S_VALUE(ioj->wr_merges, ioi->wr_merges, itv),
   S_VALUE(ioj->rd_ios, ioi->rd_ios, itv),
   S_VALUE(ioj->wr_ios, ioi->wr_ios, itv),
   S_VALUE(ioj->rd_sectors, ioi->rd_sectors, itv) / fctr,
   S_VALUE(ioj->wr_sectors, ioi->wr_sectors, itv) / fctr,
   xds->arqsz,
   S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0,
   xds->await,
   r_await,
   w_await,
   xds->svctm,
   shi->used ? xds->util / 10.0 / (double) shi->used
      : xds->util / 10.0); /* shi->used should never be zero here */
}


void compute_ext_disk_stats(struct stats_disk *sdc, struct stats_disk *sdp,
       unsigned long long itv, struct ext_disk_stats *xds)
{
 double tput
  = ((double) (sdc->nr_ios - sdp->nr_ios)) * HZ / itv;

 xds->util  = S_VALUE(sdp->tot_ticks, sdc->tot_ticks, itv);
 xds->svctm = tput ? xds->util / tput : 0.0;
 /*
  * Kernel gives ticks already in milliseconds for all platforms
  * => no need for further scaling.
  */
 xds->await = (sdc->nr_ios - sdp->nr_ios)
?  ((sdc->rd_ticks - sdp->rd_ticks) + (sdc->wr_ticks - sdp->wr_ticks)) /
  ((double) (sdc->nr_ios - sdp->nr_ios)) : 0.0;
 xds->arqsz = (sdc->nr_ios - sdp->nr_ios)
?  ((sdc->rd_sect - sdp->rd_sect) + (sdc->wr_sect - sdp->wr_sect)) /
  ((double) (sdc->nr_ios - sdp->nr_ios)) : 0.0;
}

實際上就是 /proc/diskstats 中的 filed10 消耗時間佔比。

await

在 Linux 中,每一個 IO 的平均耗時用 await 表示,包括了磁盤處理時間以及隊列排隊時間,因此該指標不能徹底表示設備的性能,包括 IO 調度器等,都會影響該參數值。通常來講,內核中的隊列時間幾乎能夠忽略不計,而 SSD 不一樣產品從 0.01ms 到 1.00 ms 不等,對於機械磁盤能夠參考 io 。

svctm

這個指標在 iostat 以及 sar 上都有註釋 Warning! Do not trust this field any more. This field will be removed in a future sysstat version.,該指標包括了隊列中排隊時間以及磁盤處理時間。

實際上,在 UNIX 中一般經過 avserv 表示硬盤設備的性能,它是指 IO 請求從 SCSI 層發出到 IO 完成以後返回 SCSI 層所消耗的時間,不包括在 SCSI 隊列中的等待時間,因此該指標體現了硬盤設備處理 IO 的速度,又被稱爲 disk service time,若是 avserv 很大,那麼確定是硬件出問題了。

iowait

從 top 中的解釋來講,就是 CPU 在 time waiting for I/O completion 中消耗的時間,而實際上,若是須要等待 IO 完成,實際 CPU 不會一直等待該進程,而是切換到另外的進程繼續執行。

因此在 Server Fault 中將該指標定義爲以下的含義:

iowait is time that the processor/processors are waiting (i.e. is in an
idle state and does nothing), during which there in fact was outstanding
disk I/O requests.

那麼對於多核,iowait 是隻在一個 CPU 上,仍是會消耗在全部 CPU ?若是有 4 個 CPUs,那麼最大是 20% 仍是 100% ?

能夠經過 dd if=/dev/sda of=/dev/null bs=1MB 命令簡單測試下,通常來講,爲了提升 cache 的命中率,會一直使用同一個 CPU ,不過部分系統會將其均分到不一樣的 CPU 上作均衡。另外,也能夠經過 taskset 1 dd if=/dev/sda of=/dev/null bs=1MB 命令將其綁定到單個 CPU 上。

按照二進制形式,從最低位到最高位表明物理 CPU 的 #0、#一、#二、…、#n 號核,例如:0x01 表明 CPU 的 0 號核,0x05 表明 CPU 的 0 號和 2 號核。
例如,將 9865 綁定到 #0、#1 上面,命令爲 taskset -p 0x03 9865;將進程 9864 綁定到 #一、#二、#5~#11 號核上面,從 1 開始計數,命令爲 taskset -cp 1,2,5-11 9865 。

能夠看出,若是是 top <1> 顯示各個 CPU 的指標,則是 100% 計算,而總的統計值則按照 25% 統計。

其它 常見問題處理。

問題1:如何獲取真正的 serviice time(svctm)
?能夠經過 fio 等壓測工具,經過設置爲同步 IO,僅設置一個線程,以及 io_depth 也設置爲 1,壓測出來的就是真正的 service time(svctm)。

問題2:怎樣得到 IO 最大並行度,或者說如何得到真正的 util% 使用率?
最大並行度 = 壓測滿(r/s + w/s) * (真實svctm / 1000)
公式基本同樣,只是將 svctm 換成了上次計算的值。

問題3:如何判斷存在 IO 瓶頸了?
實際上在如上算出真實的最大並行度,能夠直接參考 avgqu-sz 值,也就是隊列中的值,通常來講超過兩倍可能就會存在問題。例如一塊機械盤,串行 IO (每次1個IO),那麼 avgqu-sz 持續大於 2 既表明持續有兩倍讀寫能力的 IO 請求在等待;或者當 RAIDs、SSD 等並行,這裏假定並行度爲 5.63,那麼 avgqu-sz 持續大於10,才表明持續有兩倍讀寫能力的 IO 請求在等待。

iotop pidstat iodump 進程級
一個 Python 腳本,能夠查看官網 guichaz.free.fr/iotop,另外一個經過 C 實現的監控可參考 IOPP。

pidstat 用於統計進程的狀態,包括 IO 情況,能夠查看當前系統哪些進程在佔用 IO 。

----- 只顯示IO

pidstat -d 1

上述二者均是統計的 /proc/pid/io 中的信息;另可參考 io/iotop.stp,這是 iotop 的複製版。

iodump 是一個統計每個進程(線程)所消耗的磁盤 IO 工具,是一個 perl 腳本,其原理是打開有關 IO 的內核記錄消息開關,然後讀取消息而後分析輸出。

echo 1 >/proc/sys/vm/block_dump # 打開有關IO內核消息的開關 # while true; do sleep 1; dmesg -c ; done | perl iodump # 而後分析

上述輸出的單位爲塊 (block),每塊的大小取決於建立文件系統時指定的塊大小。

ioprofile 業務級

ioprofile 命令本質上等價於 lsof + strace,能夠查看當前進程。

blktrace
blktrace 是塊層 IO 路徑監控和分析工具,做者 Jens Axboe 是內核 IO 模塊的維護者,目前就任於 FusionIO,同時他仍是著名 IO 評測工具 fio 的做者,使用它能夠深刻了解 IO 通路。

yum install blktrace # 在CentOS中安裝

$ make # 解壓源碼後直接安裝
$ man -l doc/blktrace.8 # 查看幫助

$ grep 'CONFIG_BLK_DEV_IO_TRACE' /boot/config-`uname -r`
大部分實現代碼都在 blktrace.c,利用 tracepoint 的特性,註冊了一些 trace 關鍵點,能夠查看 Documentation/tracepoint.txt 文件;交互機制利用了 relayfs 特性,看看 Documentation/filesystems/relay.txt

其源碼能夠從 brick.kernel.dk 下載,詳細使用參考 blktrace User Guide 原理

該工具包括了內核空間和用戶空間兩部分實現,內核空間裏主要是給塊層 IO 路徑上的關鍵點添加 tracepoint,而後藉助於 relayfs 系統特性將收集到的數據寫到 buffer 去,再從用戶空間去收集。

目前,內核空間部分的代碼已經集成到主線代碼裏面去了,能夠看看內核代碼 block/blktrace.c 文件是否是存在,編譯的時候把對應的這個 trace 選項選擇上就能夠了。

此時撈取的信息還比較原始,能夠經過用戶空間的 blkparse、btt、seekwatcher 這樣的工具來分析收集到的數據。

注意,使用以前要確保 debugfs 已經掛載,默認會掛載在 /sys/kernel/debug 。

使用
典型的使用以下,其中 /dev/sdaa、/dev/sdc 做爲 LVM volume adb3/vol。

blktrace -d /dev/sda -o - | blkparse -i - -o blkparse.out # 簡單用法,Ctrl-C退出 # btrace /dev/sda # 同上 # blktrace /dev/sdaa /dev/sdc & # 離線處理。1. 後臺運行採集

% mkfs -t ext3 /dev/adb3/vol                                      # 2. 作些IO操做
% kill -15 9713                                                   # 3. 中止採集
% blkparse sdaa sdc sdo > events                                  # 4. 解析後查看

在 blktrace 中,-d 表示監控哪一個設備,-o - 表示將監控輸出到標準輸出;在 blkparse 中,-i - 表示從標準輸入獲取信息,-o 表示將解析的內容記錄在 blkparse.out 。

以下是輸出的詳細信息。
在這裏插入圖片描述

其中 event 對應了事件表;後面一列表明瞭操做類型,包括了 R(read)、W(write)、B(barrier operation)、S(synchronous operation),其中 event 有以下類型:

在這裏插入圖片描述

詳解
仍以以下簡單命令爲例。

$ blktrace -d /dev/sda -o sda                 # 輸出 sda.blktrace.N 文件,N 爲物理 CPU 個數。
$ ls /sys/kernel/debug/block/sda              # 查看debugfs中的文件
dropped  msg  trace0  trace1  trace2  trace3
$ blkparse -i sda.blktrace.0                  # 解析成可讀內容
$ blkrawverify sda                            # 校驗,其中sda爲blktrace的-o選項

其中 blktrace 經過 ioctl() 執行 BLKTRACESETUP、BLKTRACESTART、BLKTRACESTOP、BLKTRACETEARDOWN 操做,此時會在 debugfs 目錄的 block/DEV 目錄下寫入數據。

FIO
FIO 是個很是強大的 IO 性能測試工具,其做者 Jens Axboe 是 Linux 內核 IO 部分的 maintainer,能夠絕不誇張的說,若是你把全部的 FIO 參數都搞明白了,基本上就把 Linux IO 協議棧的問題搞的差很少明白了。

一個 IO 壓測工具,源碼以及二進制文件能夠參考 github-axboe,或者直接從 freecode.com 上下載。另外,該工具同時提供了一個圖形界面 gfio 。

在 CentOS 中能夠經過以下方式安裝。

yum --enablerepo=epel install fio

源碼編譯
能夠直接從 github 上下載源碼,而後經過以下方式進行編譯。

----- 編譯,注意依賴libaio

$ make

----- 查看幫助

$ man -l fio.1

----- 經過命令行指定參數,進行簡單測試

$ fio --name=global --rw=randread --size=128m --name=job1 --name=job2

----- 也能夠經過配置文件進行測試

$ cat foobar.fio
[global]
rw=randread
size=128m
[job1]
[job2]
$ fio foobar.fio

能夠經過命令行啓動,不過此時參數較多,可使用配置文件。

源碼解析
其版本經過 FIO_VERSION 宏定義,並經過 fio_version_string 變量定義。

main()
  |-parse_options()
  |  |-parse_cmd_line()                    解析命令行,如-i顯示全部的ioengines
  |  |  |-add_job()                        file1: xxxxxx 打印job信息
  |  |-log_info()                          fio-2.10.0
  |-fio_backend()
  |  |-create_disk_util_thread()           用於實時顯示狀態
  |  |  |-setup_disk_util()
  |  |  |-disk_thread_main()               經過pthread建立線程
  |  |     |-print_thread_status()
  |  |
  |  |-run_threads()                       Starting N processes
  |  |  |-setup_files()                    Laying out IO file(s)
  |  |  |-pthread_create()                 若是配置使用線程,調用thread_main
  |  |  |-fork()                           或者調用建立進程,一樣爲thread_main
  |  |
  |  |-show_run_stats()
  |     |-show_thread_status_normal()      用於顯示最終的狀態
  |        |-show_latencies()              顯示lat信息
  |        |-... ...                       CPU、IO depth
ioengines 經過 fio_libaio_register() 相似的函數初始化。

其它

ionice
獲取或設置程序的 IO 調度與優先級。

ionice [-c class] [-n level] [-t] -p PID...
ionice [-c class] [-n level] [-t] COMMAND [ARG]

----- 獲取進程ID爲8九、91的IO優先級
$ ionice -p 89 9
相關文章
相關標籤/搜索