关于io:Linux服务器开发监控之-IO

5次阅读

共计 11328 个字符,预计需要花费 29 分钟才能阅读完成。

简介

能够通过如下命令查看与 IO 相干的零碎信息。

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

)

在 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。

块是文件系统的形象,而非磁盘的属性,个别是 Sector Size 的倍数;扇区大小则是磁盘的物理属性,它是磁盘设施寻址的最小单元。另外,内核中要求 Block_Size = Sector_Size * (2 的 n 次方),且 Block_Size <= 内存的 Page_Size (页大小)。

【文章福利】小编举荐本人的 C /C++Linux 群:832218493!整顿了一些集体感觉比拟好的学习书籍、视频材料共享在外面,有须要的能够自行添加哦!~

磁盘应用空间

实际上是通过 statvfs() 办法查问磁盘数据,能够通过如下命令查看。

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

其空间占用大抵如下。

   +--------------------------+----------------+-------------------------------------------------------+
   |                          |                |                                                       |
   +--------------------------+----------------+-------------------------------------------------------+
   |<-- 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 等),甚至是制造商、机器型号、序列号等信息。

$ dmesg | grep scsi

监控指标

简略列举磁盘监控时常见的指标。

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、#1、#2、…、#n 号核,例如:0x01 代表 CPU 的 0 号核,0x05 代表 CPU 的 0 号和 2 号核。
例如,将 9865 绑定到 #0、#1 下面,命令为 taskset -p 0x03 9865;将过程 9864 绑定到 #1、#2、#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 为 89、91 的 IO 优先级
$ ionice -p 89 9
正文完
 0