iostat源码解析

2023-10-28 06:10
文章标签 源码 解析 iostat

本文主要是介绍iostat源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

iostat命令是报告cpu的统计信息和磁盘的i/o统计信息。iostat命令通过观察存储设备实际的工作时间和它们的平均传输率来监控系统的i/o负载。
这个不需要root权限,数据来源可以直接通过访问procfs获取。

基本用法和输出的基本含义

iostat -d -x 1#表示显示设备状态,显示扩展信息,每秒输出一次

iostat指标解读

性能指标含义提示
Device显示设备或者分区的Name这些设备可以在/dev下找到
r/s每秒发送给磁盘的请求数合并后的请求数
w/s每秒发送给磁盘的请求数合并后的请求数
rkB/s每秒从磁盘读取的数据量单位为kB
wkB/s每秒向磁盘写入的数据量单位为kB
rrqm/s每秒合并的读请求数%rrqm表示合并读请求的百分比
wrqm/s每秒合并的写请求数%wrqm表示合并写请求的百分比
r_await读请求处理完成等待时间包括队列中的等待时间和设备实际处理的时间,单位ms
w_await写请求处理完成等待时间包括队列中的等待时间和设备实际处理的时间,单位ms
aqu-sz平均请求队列长度旧版本中为avgqu-sz
rareq-sz平均读请求大小单位为kB
wareq-sz平均写请求大小单位为kB
svctm处理IO请求所需的平均时间
(不包括等待时间)
单位为毫秒
%util磁盘处理IO的时间百分比即使用率,由于可能存在并行IO,100%不表明磁盘IO饱和

iostat数据来源diskstats

这里的数据来源指的是/proc/diskstats

iostat是以/proc/diskstats为基础计算出来的,这是前提。而且很多查IO的方法都是通过/proc/diskstats为基础计算出来的。

由于/proc/diskstats并未把队列等待时间和硬盘处理时间分开,所以凡是以它为基础的工具都不可能分别提供disk service time以及与queue有关的值。

这里举sda为例

8 0 sda 63147 32179 3914580 61983 287312 216951 7881680 434138 0 190628 267912 0 0 0 0

每个参数对应的含义:
前3个字段分别代表major, minor, device; 后面的15个字段含义如下:

indexNameunitsdescription
1read I/Osrequestsnumber of read I/Os processed
2read mergesrequestsnumber of read I/Os merged with in-queue I/O
3read sectorssectorsnumber of sectors read
4read ticksmillisecondstotal wait time for read requests
5write I/Osrequestsnumber of write I/Os processed
6write mergesrequestsnumber of write I/Os merged with in-queue I/O
7write sectorssectorsnumber of sectors written
8write ticksmillisecondstotal wait time for write requests
9in_flightrequestsnumber of I/Os currently in flight
10io_ticksmillisecondstotal time this block device has been active
11time_in_queuemillisecondstotal wait time for all requests
12discard I/Osrequestsnumber of discard I/Os processed
13discard mergesrequestsnumber of discard I/Os merged with in-queue I/O
14discard sectorssectorsnumber of sectors discarded
15discard ticksmillisecondstotal wait time for discard requests

这里面大部分字段都是很容易理解的,稍微难理解的在于io_ticks。初看之下,明明已经有了rd_ticks和wr_ticks 为什么还需一个io_ticks。注意rd_ticks和wr_ticks是把每一个IO消耗时间累加起来,但是硬盘设备一般可以并行处理多个IO,因此,rd_ticks和wr_ticks之和一般会比自然时间(wall-clock time)要大。而io_ticks 不关心队列中有多少个IO在排队,它只关心设备有IO的时间。即不考虑IO有多少,只考虑IO有没有。在实际运算中,in_flight不是0的时候保持计时,而in_flight 等于0的时候,时间不累加到io_ticks。

内核中的数据结构

path: root/include/linux/blk_types.h
enum stat_group {STAT_READ,STAT_WRITE,STAT_DISCARD,NR_STAT_GROUPS
};path: root/include/linux/genhd.h
struct disk_stats {u64 nsecs[NR_STAT_GROUPS];unsigned long sectors[NR_STAT_GROUPS];unsigned long ios[NR_STAT_GROUPS];unsigned long merges[NR_STAT_GROUPS];unsigned long io_ticks;unsigned long time_in_queue;local_t in_flight[2];
};path: root/block/genhd.c
static int diskstats_show(struct seq_file *seqf, void *v)
{struct gendisk *gp = v;struct disk_part_iter piter;struct hd_struct *hd;char buf[BDEVNAME_SIZE];unsigned int inflight;/*if (&disk_to_dev(gp)->kobj.entry == block_class.devices.next)seq_puts(seqf,  "major minor name""     rio rmerge rsect ruse wio wmerge ""wsect wuse running use aveq""\n\n");*/disk_part_iter_init(&piter, gp, DISK_PITER_INCL_EMPTY_PART0);while ((hd = disk_part_iter_next(&piter))) {inflight = part_in_flight(gp->queue, hd);seq_printf(seqf, "%4d %7d %s ""%lu %lu %lu %u ""%lu %lu %lu %u ""%u %u %u ""%lu %lu %lu %u\n",MAJOR(part_devt(hd)), MINOR(part_devt(hd)),disk_name(gp, hd->partno, buf),part_stat_read(hd, ios[STAT_READ]),part_stat_read(hd, merges[STAT_READ]),part_stat_read(hd, sectors[STAT_READ]),(unsigned int)part_stat_read_msecs(hd, STAT_READ),part_stat_read(hd, ios[STAT_WRITE]),part_stat_read(hd, merges[STAT_WRITE]),part_stat_read(hd, sectors[STAT_WRITE]),(unsigned int)part_stat_read_msecs(hd, STAT_WRITE),inflight,jiffies_to_msecs(part_stat_read(hd, io_ticks)),jiffies_to_msecs(part_stat_read(hd, time_in_queue)),part_stat_read(hd, ios[STAT_DISCARD]),part_stat_read(hd, merges[STAT_DISCARD]),part_stat_read(hd, sectors[STAT_DISCARD]),(unsigned int)part_stat_read_msecs(hd, STAT_DISCARD),);}disk_part_iter_exit(&piter);return 0;
}

用户态iostat的数据结构

用户态程序打开/proc/diskstats文件,将数据填入到struct io_stats的结构中。

/** Structures for I/O stats.* These are now dynamically allocated.*/
struct io_stats {/* # of sectors read */unsigned long rd_sectors	__attribute__ ((aligned (8)));/* # of sectors written */unsigned long wr_sectors	__attribute__ ((packed));/* # of sectors discarded */unsigned long dc_sectors	__attribute__ ((packed));/* # of read operations issued to the device */unsigned long rd_ios		__attribute__ ((packed));/* # of read requests merged */unsigned long rd_merges		__attribute__ ((packed));/* # of write operations issued to the device */unsigned long wr_ios		__attribute__ ((packed));/* # of write requests merged */unsigned long wr_merges		__attribute__ ((packed));/* # of discard operations issued to the device */unsigned long dc_ios		__attribute__ ((packed));/* # of discard requests merged */unsigned long dc_merges		__attribute__ ((packed));/* Time of read requests in queue */unsigned int  rd_ticks		__attribute__ ((packed));/* Time of write requests in queue */unsigned int  wr_ticks		__attribute__ ((packed));/* Time of discard requests in queue */unsigned int  dc_ticks		__attribute__ ((packed));/* # of I/Os in progress */unsigned int  ios_pgr		__attribute__ ((packed));/* # of ticks total (for this device) for I/O */unsigned int  tot_ticks		__attribute__ ((packed));/* # of ticks requests spent in queue */unsigned int  rq_ticks		__attribute__ ((packed));
};

采集数据到显示数据的计算

通过获取的数据,我们可以直接得到以下信息。

r/s
w/s
rkB/s
wkB/s
rrqm/s
wrqm/s

这几项的计算是非常简单的,就是采样两次,后一次的值减去前一次的值,然后除以时间间隔,得到平均值即可。因为这些/proc/diskstats中对应的值都是累加的,后一次减去前一次,即得到采样时间间隔内的新增量。不赘述。

r_wait和w_wait的计算

xios.r_await = (ioi->rd_ios - ioj->rd_ios) ?(ioi->rd_ticks - ioj->rd_ticks) /((double) (ioi->rd_ios - ioj->rd_ios)) : 0.0;xios.w_await = (ioi->wr_ios - ioj->wr_ios) ?(ioi->wr_ticks - ioj->wr_ticks) /((double) (ioi->wr_ios - ioj->wr_ios)) : 0.0;

r _ a w a i t = ( 间 隔 期 间 所 有 读 I O 花 费 的 时 间 ) / ( 间 隔 期 间 读 请 求 的 个 数 ) w _ a w a i t = ( 间 隔 期 间 所 有 写 I O 花 费 的 时 间 ) / ( 间 隔 期 间 写 请 求 的 个 数 ) r\_await = (间隔期间所有读IO花费的时间)/(间隔期间读请求的个数) \\ w\_await = (间隔期间所有写IO花费的时间)/(间隔期间写请求的个数) r_await=IO/w_await=IO/

aqu-sz 平均队列深度的计算

//其中rq_ticks即diskstats中的time_in_queue,注意这里的1000是ms->s
#define S_VALUE(m,n,p)		(((double) ((n) - (m))) / (p) * 100)
S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0);

这里看下内核的实现:

path: root/drivers/md/dm-stats.cdifference = now - shared->stamp;if (!difference)return;in_flight_read = (unsigned)atomic_read(&shared->in_flight[READ]);in_flight_write = (unsigned)atomic_read(&shared->in_flight[WRITE]);if (in_flight_read)p->io_ticks[READ] += difference;if (in_flight_write)p->io_ticks[WRITE] += difference;if (in_flight_read + in_flight_write) {p->io_ticks_total += difference;p->time_in_queue += (in_flight_read + in_flight_write) * difference;}shared->stamp = now;

举个栗子

当第一个IO完成的时候,队列中250个IO,250个IO都等了4ms,即time_in_queue + = (250 * 4) ,当第二个IO完成的时候,time_in_queue += (249 * 4),当所有IO都完成的时候,time_in_queue = 4*(250+249+248….+1)

根据 t i m e _ i n _ q u e u e / 1000 time\_in\_queue/1000 time_in_queue/1000,得到了平均队列长度。

$$
time_in_queue += \Delta t_n * inflight_n \

aqu_sz = 间隔时间内inflight的期望值 = \frac{\Delta t_i * inflight_i + … + \Delta t_j * inflight_j}{\Delta t_i + … + \Delta t_i}
$$

rareq-sz & wareq-sz 平均请求的sector大小

xios.rarqsz = (ioi->rd_ios - ioj->rd_ios) ?(ioi->rd_sectors - ioj->rd_sectors) / ((double) (ioi->rd_ios - ioj->rd_ios)) :0.0;
xios.warqsz = (ioi->wr_ios - ioj->wr_ios) ?(ioi->wr_sectors - ioj->wr_sectors) / ((double) (ioi->wr_ios - ioj->wr_ios)) :0.0;

r a r e q _ s z = ( 间 隔 期 间 读 取 的 s e c t o r 增 长 量 ) / ( 间 隔 期 间 读 请 求 的 个 数 ) w a r e q _ s z = ( 间 隔 期 间 写 入 的 s e c t o r 增 长 量 ) / ( 间 隔 期 间 写 请 求 的 个 数 ) rareq\_sz = (间隔期间读取的sector增长量)/(间隔期间读请求的个数) \\ wareq\_sz = (间隔期间写入的sector增长量)/(间隔期间写请求的个数) rareq_sz=(sector)/wareq_sz=(sector)/

%util 磁盘设备饱和度(数据不准确)

path: root/drivers/md/dm-stats.c
//其中的io_ticks_total即是队列非空的总时间difference = now - shared->stamp;if (!difference)return;in_flight_read = (unsigned)atomic_read(&shared->in_flight[READ]);in_flight_write = (unsigned)atomic_read(&shared->in_flight[WRITE]);if (in_flight_read)p->io_ticks[READ] += difference;if (in_flight_write)p->io_ticks[WRITE] += difference;if (in_flight_read + in_flight_write) {p->io_ticks_total += difference;p->time_in_queue += (in_flight_read + in_flight_write) * difference;}shared->stamp = now;

最简单的例子是,某硬盘处理单个IO请求需要0.1秒,有能力同时处理10个。但是当10个请求依次提交的时候,需要1秒钟才能完成这10%的请求,,在1秒的采样周期里,%util达到了100%。但是如果10个请一次性提交的话, 硬盘可以在0.1秒内全部完成,这时候,%util只有10%。

在上面的例子中,一秒中10个IO,即IOPS=10的时候,%util就达到了100%,这并不能表明,该盘的IOPS就只能到10,事实上,纵使%util到了100%,硬盘可能仍然有很大的余力处理更多的请求,即并未达到饱和的状态。

那么有没有一个指标用来衡量硬盘设备的饱和程度呢。很遗憾,iostat没有一个指标可以衡量磁盘设备的饱和度。

明明已经有了rd_ticks和wr_ticks 为什么还需一个io_ticks。注意rd_ticks和wr_ticks是把每一个IO消耗时间累加起来,但是硬盘设备一般可以并行处理多个IO,因此,rd_ticks和wr_ticks之和一般会比自然时间(wall-clock time)要大。而io_ticks 不关心队列中有多少个IO在排队,它只关心设备有IO的时间。即不考虑IO有多少,只考虑IO有没有。在实际运算中,in_flight不是0的时候保持计时,而in_flight 等于0的时候,时间不累加到io_ticks。

svctm 处理IO请求所需的平均时间

    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; 

对于iostat这个功能而言,%util固然会给人带来一定的误解和苦扰,但是svctm给人带来的误解更多。一直以来,人们希望了解块设备处理单个IO的service time,这个指标直接地反应了硬盘的能力。
但是service time和iostat无关,iostat没有任何一个参数能够提供这方面的信息。这个值其实并没有什么意义,事实上,这个值不是独立的,它是根据其他值计算出来的。

这篇关于iostat源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/291229

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [