摘要:监控系统在linux系统上获取物理磁盘IO以及使用情况的原理,让我们一起来探索一下
本文使用语言为c++
物理磁盘列表和磁盘IO
第一步要解决的问题是先识别物理磁盘是哪些。
- 上图是
/proc/diskstats
的文件内容部分截取,我们可以通过读取/proc/diskstats
获得物理磁盘列表以确认哪些是物理设备(算云硬盘)以及iops等信息 - 1-5列:设备号、编号、设备、读完成次数、合并完成次数
- 6-10列:读扇区次数、读操作花费毫秒数、写完成次数、合并写完成次数、写扇区次数
- 11-14列:写操作花费的毫秒数、正在处理的输入/输出请求数、输入/输出操作花费的毫秒数、输入/输出操作花费的加权毫秒数。
那这个文件内如此多的设备哪些是物理硬盘呢?只要达到下面两个限制条件就判定为物理硬盘。
- 该行有14列,可以使用
sscanf
取到设备名 - 此行中的设备名组装成
/sys/block/设备名/device
,然后看此文件夹是否存在,如果存在则是物理磁盘设备
备注1:判断文件/文件夹是否存在使用函数access(syspath, F_OK)
,存在返回0
备注2:如果设备名为中含有/
的话要转换成!
,如下
while ((slash = strchr(name, '/'))) {
*slash = '!';
}
备注3:目录/sys/block
下的所有子目录代表着系统中当前被发现的所有块设备(其中的内容已经变为了指向它们在/sys/devices/中真实设备的符号链接文件)
到此我们就取到了物理硬盘的iops,接下来我们来看看使用情况和总量是如何拿到的。
物理磁盘使用情况 和总量
物理磁盘使用量
因为我们没有办法直接取到物理硬盘的使用情况,所以我们用一种间接的方式。根据分区和物理硬盘的关系获得物理硬盘的使用情况。比如一个物理硬盘sda
分了sda1 sda2
等两个分区,又知道sda1
的挂载点是/data
,sda2
的挂载点是/home
,通过某种方式查出/data
和home
的使用情况,加起来就是sda
的使用情况了。
我们可以,然后再通过获取挂载点大小的方式知道这些设备的使用情况。/etc/mtab
中不会直接物理硬盘的信息,所以只能通过把属于这个物理硬盘的全部分区加起来才能最后算出我们想要的值。
使用情况计算逻辑
- 通过读
/etc/mtab
的方式拿到各种设备和它的挂载点。(/etc/mtab
文件中不会直接给出物理硬盘的使用情况) - 使用
statfs
获得所挂载的目录使用情况来确定每个设备的使用情况 - 根据分区和物理硬盘的关系获得物理硬盘的使用情况(通常物理磁盘的名称是分区的子串,比如
/dev/sda
是/dev/sda1
的子串)
tips: 物理磁盘设备的名称列表我们已经在上一节取到了。
通过/etc/mtab
文件拿到各种设备和它的挂载点
知道了计算逻辑,我们来看看/etc/mtab
文件内容的含义
- 上图是
/etc/mtab
的内容截取,可以读取/etc/mtab
文件获取设备名和挂载点 - 此文件每行有四列,分别代表的含义是:驱动器、挂载点、文件系统、读写权限
/etc/mtab
记载了当前系统已经装载的文件系统,包括一些操作系统虚拟文件,使用/etc/fstab
也可以监控,不同的是/etc/mtab
文件在mount
挂载、umount
卸载时都会被更新,时刻跟踪当前系统中的分区挂载情况。
用到了以下核心c++函数(读取/etc/mtab)
mount_table = setmntent("/etc/mtab", "r"); //打开文件系统描述文件的文件名,并且返回可以被使用的文件指针getmntent().
mount_entry = getmntent(mount_table);//函数读取文件系统的下一行来自文件流的描述文件并返回指向结构的指针(即循环读取文件)
device = mount_entry->mnt_fsname;
mount_point = mount_entry->mnt_dir;
statfs(mount_point, &s) != 0 //此条件成立时获取成功
endmntent(mount_table);//关闭流和与其相关联的文件系统描述文件。
具体用法见 linux中getmntent、setmntent 、endmntent 函数的详细用法
通过statfs
函数所挂载的目录使用情况(used/total)来确定每个分区的使用情况
#include <sys/vfs.h> /* 或者 <sys/statfs.h> */
// path: 需要查询信息的文件系统的文件路径名。 如/home
// buf:以下结构体的指针变量,用于储存文件系统相关的信息
int statfs(const char *path, struct statfs *buf);
// fd: 需要查询信息的文件系统的文件描述词。
int fstatfs(int fd, struct statfs *buf);
struct statfs
{
long f_type; /* 文件系统类型 */
long f_bsize; /* 经过优化的传输块大小 */
long f_blocks; /* 文件系统数据块总数 */
long f_bfree; /* 可用块数 */
long f_bavail; /* 非超级用户可获取的块数 */
long f_files; /* 文件结点总数 */
long f_ffree; /* 可用文件结点数 */
fsid_t f_fsid; /* 文件系统标识 */
long f_namelen; /* 文件名的最大长度 */
};
返回说明:
- 成功执行时,返回0。失败返回-1
- statfs结构中可用空间块数有两种f_bfree和 f_bavail,前者是硬盘所有剩余空间,后者为非root用户剩余空间,ext3文件系统给root用户分有5%的独享空间,所以这里是不同的地方。这里要强调的是每块的大小一般是4K(×这句话错误,不一定都是4k,正确做法是:总大小=sfs.f_blocks×f_bsize,即块数×每块的大小,单位是bytes,也就是要/1024/1024/1024才是GB单位)。
计算分区的使用情况
#define M (1024*1024)
blocks_used = s.f_blocks - s.f_bfree; //使用量
blocks_percent_used = 0;
if (blocks_used + s.f_bavail)
{
blocks_percent_used = blocks_used * 100 / (blocks_used + s.f_bavail); //使用率
}
/* GNU coreutils 6.10 skips certain mounts, try to be compatible. */
if (strcmp(device, "rootfs") == 0)
continue;
Record record;
record.disk_total_val = CalRound((blocks_used + s.f_bavail) * s.f_bsize, M); //总量
record.disk_use_val = CalRound((s.f_blocks - s.f_bfree) * s.f_bsize, M); //
record.use_precent = blocks_percent_used;
- CalRound函数的作用是四舍五入,感兴趣可以拉到文章底部看代码。
- 在获取使用量情况失败的时候,可能是因为没有挂载获取其他特殊的情况,我们就默认使用量为0
- 备注1:
/dev/root
设备可以从/proc/cmdline
中获取到真实设备名
- 备注2:
rootfs
设备要忽略,此为根文件系统(内核启动时所mount的第一个文件系统)
如果出现lvm格式的逻辑分区怎么计算使用量?
我们根据上面的逻辑可以取到正常一般情况下的part类型的分区使用量,加到物理硬盘中去;如上图,出现lvm格式分区的时候,/etc/mtab
中就没有sda2设备的信息,而且sda2也没有挂载在任意一个文件系统上。这个时候就要拿到sda2下面挂载的三个lvm分区的使用情况。下图是lsblk的输出结果:
可以看到上图中,有一个逻辑卷(dm-2),同时挂在sda2和sdb1上,这是怎么回事?这就要从lvm的概念开始讲起了。
什么是lvm分区?
LVM的重点是可以弹性调整文件系统的容量,并不是如RAID在于对文件的读写性能或是数据的可靠性上。LVM可以将多个物理分区整合在一起,让这些分区看起来就像是一个磁盘一样,而且,还可以在将来添加其他的物理分区或将其从这个LVM管理的磁盘中删除。这样一来,整个硬盘的空间使用上,相当具有弹性。
这里介绍三个概念:
- PV(physical volume):物理卷在逻辑卷管理系统最底层,可为整个物理硬盘或实际物理硬盘上的分区。
- VG(volume group):卷组建立在物理卷上,一卷组中至少要包括一物理卷,卷组建立后可动态的添加卷到卷组中,一个逻辑卷管理系统工程中可有多个卷组。
- LV(logical volume):逻辑卷建立在卷组基础上,卷组中未分配空间可用于建立新的逻辑卷,逻辑卷建立后可以动态扩展和缩小空间。
我们知道了这些就够了,怎么计算lvm格式的使用量并规到物理硬盘上呢?
我们要知道他的写入方式,才能知道算法。lvm有两种写入方式
LVM写入方式:
- 线性模式(linear):假如有/dev/sdb1,/dev/sdb2这2个分区加入到VG当中,并且整个VG只有一个LV时,那么所谓的线性模式就是当/dev/sdb1的容量用完之后,/dev/sdb2的分区才会被使用。在此模式下,使用量就按顺序算到所挂的分区上去。
- 交错模式(triped):将一条数据拆分成两部分,分别写入/dev/sdb1与/dev/sdb2,有点像RAID0。这样子,一份数据用两块硬盘来写入,理论上,读写性能会比较好。在此模式下,使用量就按平均到所挂的分区上去,可能会有点细微的差别,但这是相对准确的方式了。
如何取到lvm类型
执行lvm相关的命令之前必须要安装lvm2这个软件,不过CentOS和其他比较新的Linux发行版已经默认安装了lvm的所需软件,何况我们这里的目的是监控已经创建lvm分区的linux机器(lsblk看到的),那一定有这些软件,就不用担心这个问题了。
但是比较老的版本没有这些参数,比如
那我们用这种方式
ps:直接解析/proc/swaps
的内容有一样的效果哦
现在我们取到了dm-1设备的使用情况和总量,正常来说可以结合lsblk的结果和对应到磁盘上,但是问题来了,有的lsblk输出结果不带有dm-1这种字样,那怎么办呢?
不用怕,我们可以利用VG和LV的名称找到他们的软链接(符号链接)。再用c++的readlink函数取到符号链接所指向的文件
ps: 大家可以看到,这里的lvm使用量都是用命令方式来采集的,如果你有读文件或者系统api等更好的方式,希望你可以留言和我交流,非常感谢!
物理磁盘总量
我们可以直接根据磁盘名(比如/dev/sda)来获取磁盘总量,无论是否有lvm分区,以下是核心代码
unsigned long long AgentDiskRpt::readDiskTotal(const string &deviceName)
{
int fd, ret;
unsigned long long size;
fd = open(deviceName.c_str(), O_RDONLY);
if (fd == -1)
{
close(fd);
return -1;
}
ret = ioctl(fd, BLKGETSIZE64, &size);
if (ret == 0) {
close(fd);
return CalRound(size,M);
}
close(fd);
return 0;
}
遇到nas硬盘怎么计算?
NAS(Network Attached Storage:网络附属存储)按字面简单说就是连接在网络上,具备资料存储功能的装置,因此也称为“网络存储器”。它是一种专用数据存储服务器。它以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽、提高性能、降低总拥有成本、保护投资。其成本远远低于使用服务器存储,而效率却远远高于后者。
nas硬盘,采集的时候当作逻辑磁盘,不是物理硬盘,他是共享的,多个用户共享一块nas盘的时候可以共享数据,所以nas盘不应该统计成物理磁盘,我们这里就没有算作,可以算作逻辑分区,直接在/etc/mtab
里就能读到啦。
其他
CalRound函数
unsigned long long CalRound(unsigned long long value, int base)
{
unsigned long long ret = 0;
if (base <= 1)
return value;
unsigned long long tmp = base / 2;
if (tmp <= 0)
tmp = 1;
if (value % base >= tmp)
ret = value / base + 1;
else
ret = value / base;
return ret;
}
评论