能够经过以下命令查看与 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 等待时间 在队列中排队的时间。
除了能够经过该命令查看磁盘信息以外,还能够用来查看 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 消耗时间占比。
在 Linux 中,每一个 IO 的平均耗时用 await 表示,包括了磁盘处理时间以及队列排队时间,因此该指标不能彻底表示设备的性能,包括 IO 调度器等,都会影响该参数值。通常来讲,内核中的队列时间几乎能够忽略不计,而 SSD 不一样产品从 0.01ms 到 1.00 ms 不等,对于机械磁盘能够参考 io 。
这个指标在 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 很大,那么确定是硬件出问题了。
从 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 命令本质上等价于 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