摘要:監控系統在linux系統上獲取物理磁碟IO以及使用情況的原理,讓我們一起來探索一下
本文使用語言為c++
複製程式碼
本文原創首發於騰訊雲+社群:linux系統 物理硬碟監控
物理磁碟列表和磁碟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;
}
複製程式碼
參考
Linux /etc/fstab和etc/mtab有什麼區別