磁碟 分區 lvm之間await util的統計關係

来源:http://www.cnblogs.com/acool/archive/2017/08/01/7271365.html
-Advertisement-
Play Games

當我們衡量IO負載時,常常會用到await util這兩個指標。那麼磁碟分成分區,分區又組成vg後分給 lvm。他們之間await util的統計有什麼關係呢? ...


最近的項目需要監控機器的IO負載, 提到IO負載,首當其衝的當然是await util這兩個指標。
util: 過去的一段時間內,設備處理IO請求的時間占總時間的百分比。
await: 一個請求在IOscheduler里排隊時間加上物理設備處理時間 (一個IO請求從通用塊設備層提交到IOscheduler時開始計算,到底層處理完這個請求再次返回到通用塊層的時間差)

  • iostat和/proc/diskstats

常見的iostat sar等工具都提供了這兩個指標,當然它們提供的都是一段時間的平均值。但iostat只是負責換算,並不負責這些統計數據的採集。
IO棧在處理IO請求時,採集這些統計數據。/proc/diskstats文件中以行為單位展示了每個邏輯設備的統計信息。

總共14個欄位,解釋如下:
第01列 : 主設備號major
第02列 : 次設備號minor
第03列 : 設備名name
第04列 : 讀請求完成總數rio
第05列 : 合併讀請求總數rmerge
第06列 : 讀扇區總數rsect
第07列 : 讀數據花費的時間rticks,單位是ms
第08列 : 寫請求完成總數wio
第09列 : 合併寫請求總數wmerge
第10列 : 寫扇區總數wsect
第11列 : 寫數據花費時間wticks,單位是ms
第12列 : 正在進行I/O數inFlight
第13列 : IO花費時間ioticks,單位ms
第14列 : IO花費時間time_in_queue,單位ms(加了權重的)
類似iostat的工具其實都是讀取這個文件再經過計算後得出磁碟的util await iops 吞吐這些信息。這個文件所有的統計結果都是累加的,
因此iostat至少需要採集兩次才能計算。以rio為例,(rio1 - rio0) / interval(採集間隔 s)  就是過去interval時間 平均每秒讀iops。

先給出iostat計算await util的演算法。iostat代碼就省略了,不是重點。
await = ((wticks1 - wticks0) + (rticks1 - rticks0)) / ((rio1 + wio1) - (rio0 + wio0))
就是採集兩次diskstats,讀寫總共花費的時間 / 讀寫請求完成個數 = 每個請求平均花費的時間即await.

util = (ioticks1 - ioticks0) / interval
採集兩次diskstats,interval是兩次採集的時間間隔。很好理解,過去的interval毫秒里,有多少毫秒在處理IO。占比就是磁碟的繁忙程度即util。

本文的重點是磁碟 分區 lvm間await util的統計關係。以我的虛擬機為例:有塊物理盤vdb,分成了兩個分區vdb1 vdb2。vdb1 vdb2又組成vg後全部分給了邏輯捲dm-0。
上面我們看到vdb vdb1 vdb2 dm-0在diskstats中都有獨立的統計。那麼當我讀寫vdb2時,會更新vdb1的統計嗎?會更新vdb嗎?讀寫dm-0呢?

從上面iostat的分析我們發現,await util這兩個指標的計算跟(w/r)ticks  (w/r)io  ioticks這幾個統計數據相關。下麵分析下內核代碼,答案自然就浮現出來了。

  • 代碼分析(內核版本:2.6.28)

這裡直切主題,IO棧的相關介紹請查閱其他文章。

先看兩個結構體:

struct disk_stats:存儲每個塊設備的IO統計數據。diskstats里統計相關的除了inFlight都在這裡。
後面就用這個結構來描述,請自行對應到diskstats里的列。

struct disk_stats {
    unsigned long sectors[2];   /* READs and WRITEs */
    unsigned long ios[2];
    unsigned long merges[2];
    unsigned long ticks[2];
    unsigned long io_ticks;
    unsigned long time_in_queue;
};

struct hd_struct:分區結構體。一個物理盤會有多個分區,每個分區由一個hd_struct。包括物理盤自身也對應一個hd_struct。
struct hd_struct {
    sector_t start_sect;
    sector_t nr_sects;
    struct device __dev;
    struct kobject *holder_dir;
    int policy, partno;     //partno為分區編號,磁碟自身該值為0
    ...
    unsigned long stamp;    //一個時間戳,統計ioticks時用到
    int in_flight;          //該分區當前有多少個請求正在處理。對應diskstats里的inFlight
#ifdef  CONFIG_SMP
    struct disk_stats *dkstats; //包含一個disk_stats存儲統計信息
#else
    struct disk_stats dkstats;
#endif
    struct rcu_head rcu_head;
};

 

 通用塊設備層向IOscheduler提交IO請求時,需要把struct bio轉換成struct request。然後調用IOscheduler隊列的入隊函數將request push進等待隊列。入隊函數註冊為__make_request。

static int __make_request(struct request_queue *q, struct bio *bio)
{
    struct request *req;
get_rq:
    ...
    req = get_request_wait(q, rw_flags, bio);  //根據bio創建request
    ...
    init_request_from_bio(req, bio);        //初始化request
    ...
    add_request(q, req);                //將request add進queue
    ...
end_io:
    bio_endio(bio, err);
    return 0;
}

 __make_request簡單來說就是根據傳入的bio,首先判斷能否merge,能則merge。否則創建新的request。然後將request 加進queue里。
當然merge部分的代碼被省略了。
跟await相關的代碼封裝在init_request_from_bio里,看看:

void init_request_from_bio(struct request *req, struct bio *bio)
{
    ....
    req->errors = 0;
    req->hard_sector = req->sector = bio->bi_sector;
    req->ioprio = bio_prio(bio);
    req->start_time = jiffies;      //這個請求push進queue時的jiffies
    blk_rq_bio_prep(req->q, req, bio);
}

 init_request_from_bio在請求進入queue之前用req->start_time記錄當前時間戳,等底層執行完該請求的時刻跟req->start_time求差,不就是該請求的await麽。
但內核統計是以分區為單位的,所以只是將時間差累加到分區對應的disk_stats.ticks里。由iostat再去算出平均的await。
跟util相關的代碼在add_request里。

static inline void add_request(struct request_queue *q, struct request *req)
{
    drive_stat_acct(req, 1);
    __elv_add_request(q, req, ELEVATOR_INSERT_SORT, 0);
}

static void drive_stat_acct(struct request *rq, int new_io)
{
    struct hd_struct *part;
    int rw = rq_data_dir(rq);
    int cpu;

    cpu = part_stat_lock();
    part = disk_map_sector_rcu(rq->rq_disk, rq->sector);    //用req找到分區part

    part_round_stats(cpu, part);        //更新分區統計
    part_inc_in_flight(part);

}

 drive_stat_acct函數里,通過req找到分區part,然後更新分區的統計數據。
其中part_round_stats函數更新disk_stats.io_ticks。part_inc_in_flight函數更新hd_struct.in_flight。

這兩個指標還是息息相關的。通用塊設備層每向下層下發一個request就給hd_struct.in_flight++,
底層每完成一個request,相應hd_struct.in_flight--。這樣in_flight就代表當前有多少個請求正在處理。

而disk_stats.io_ticks的演算法是:每次下發request或者request完成時,檢查hd_struct.in_flight。如果hd_struct.in_flight=0,則認為設備這段時間空閑,否則(只要不是0,不管有多少request正在處理)就認為設備繁忙。這段時間怎麼表示?上面已提到。用hd_struct.stamp記錄。看下代碼:

void part_round_stats(int cpu, struct hd_struct *part)
{
    unsigned long now = jiffies;    //獲取當前時間戳

    if (part->partno)       //如果是分區,則同步更新主分區即物理盤的統計。
        part_round_stats_single(cpu, &part_to_disk(part)->part0, now);  
    part_round_stats_single(cpu, part, now);   //更新io_ticks
}

static void part_round_stats_single(int cpu, struct hd_struct *part,
                    unsigned long now)
{
    if (now == part->stamp)
        return;

    if (part->in_flight) {          //in_flight非0,需要更新。
        __part_stat_add(cpu, part, time_in_queue,
                part->in_flight * (now - part->stamp)); //將(now - part->stamp)*in_flight 累加到hd_struct.disk_stats.io_ticks上
        __part_stat_add(cpu, part, io_ticks, (now - part->stamp)); //將(now - part->stamp)累加到hd_struct.disk_stats.io_ticks上
    }
    part->stamp = now;      //stamp更新為當前時間
}
如果該設備是分區,同步更新其物理盤分區。in_flight非0時更新io_ticks和time_in_queue。time_in_queue累加時乘了in_flight。所以是加了權重的IO花費時間。

part_inc_in_flight函數就負責in_flight的自增了。
static inline void part_inc_in_flight(struct hd_struct *part)
{
    part->in_flight++;
    if (part->partno)       //物理盤同步自增
        part_to_disk(part)->part0.in_flight++;
}

 前面把request進入隊列時的代碼分析了,作為呼應,貼出request完成時的代碼。
請求完成的函數棧也是很長,大概是scsi_softirq_done->...->blk_end_request->blk_end_io->end_that_request_last。

static void end_that_request_last(struct request *req, int error)
{
    struct gendisk *disk = req->rq_disk;
    ...
    if (disk && blk_fs_request(req) && req != &req->q->bar_rq) {
        unsigned long duration = jiffies - req->start_time;   //完成時間-請求時間=該請求的await
        const int rw = rq_data_dir(req);
        struct hd_struct *part;
        int cpu;

        cpu = part_stat_lock();
        part = disk_map_sector_rcu(disk, req->sector);

        part_stat_inc(cpu, part, ios[rw]);              //該分區完成讀/寫請求數+1
        part_stat_add(cpu, part, ticks[rw], duration);  //單個請求的await累加到分區的統計里。
        part_round_stats(cpu, part);                   //更新disk_stats.io_ticks
        part_dec_in_flight(part);                   //hd_struct.in_flight--

        part_stat_unlock();
    }

    ...
}

#define part_stat_inc(cpu, gendiskp, field)             \
    part_stat_add(cpu, gendiskp, field, 1)


#define part_stat_add(cpu, part, field, addnd)  do {            \   //addnd為1
    __part_stat_add((cpu), (part), field, addnd);           \
    if ((part)->partno)                     \
        __part_stat_add((cpu), &part_to_disk((part))->part0,    \       //物理盤同步+1
                field, addnd);              \
} while (0)

 這裡除了part_stat_inc巨集,其他代碼跟前面的呼應,不重覆展開了。每完成一個request,在part_stat_inc里將hd_struct.disk_stats.ios+1。對應到diskstats里就是wio rio了。當然物理盤也同步+1。

至此,與await util相關的統計指標都分析了。
util不管有多少請求在處理,只要in_flight非0,就認為磁碟忙碌。也就解釋了很多博客都強調util 100%磁碟並不一定真的忙碌。

同時,分區和物理盤的統計關係也清晰了。更新分區統計時也會同步更新物理盤。所以物理盤的統計是其所有分區之和。

再分析下lvm。
lvm是靠Device Mapper實現的,有自己的hd_struct。其mapped_device只是一個邏輯設備,上層對lvm發起的IO請求,最終被轉發到物理設備處理。
因此mapped_device的入隊函數被註冊為dm_request。dm_request做兩件事,更新自己的IO統計,轉發請求。

static int dm_request(struct request_queue *q, struct bio *bio)
{
    int r = -EIO;
    int rw = bio_data_dir(bio);
    struct mapped_device *md = q->queuedata;
    int cpu;

    cpu = part_stat_lock();             
    part_stat_inc(cpu, &dm_disk(md)->part0, ios[rw]);       //更新統計
    part_stat_add(cpu, &dm_disk(md)->part0, sectors[rw], bio_sectors(bio));
    part_stat_unlock();
    ...
    r = __split_bio(md, bio);
    up_read(&md->io_lock);
    return 0;
}

static int __split_bio(struct mapped_device *md, struct bio *bio)
{
    struct clone_info ci;
    int error = 0;
    ...
    start_io_acct(ci.io);       //更新統計
    while (ci.sector_count && !error)
        error = __clone_and_map(&ci);

    /* drop the extra reference count */
    dec_pending(ci.io, error);
    dm_table_put(ci.map);

    return 0;
}

static void start_io_acct(struct dm_io *io)
{
    struct mapped_device *md = io->md;
    int cpu;
    io->start_time = jiffies;
    cpu = part_stat_lock();
    part_round_stats(cpu, &dm_disk(md)->part0);
    part_stat_unlock();
    dm_disk(md)->part0.in_flight = atomic_inc_return(&md->pending);
}

 可以看出,lvm雖然是邏輯設備。但是IO統計是獨立的。因此讀寫lvm時,首先更新lvm的IO統計,請求被轉發到分區時更新該分區的IO統計,當然該分區所屬的物理盤也會更新。

  • 總結:

讀寫lvm,更新lvmIO統計 更新該請求所屬分區的IO統計(lvm可能由多個分區組成)  更新物理盤IO統計。
讀寫分區,更新分區IO統計  更新物理盤IO統計。
讀寫盤,只更新物理盤IO統計。

如有不對,請指正。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 · 概況 · 手工安裝 · 引言 · 創建HDFS目錄 · 創建元資料庫 · 配置文件 · 測試 · 原理 · 架構 · 與關係型資料庫對比 · API · WordCount · 命令 · 數據類型 · 文件存儲格式 · 數據格式 · 資料庫  ...
  • 安裝命令:yum install -y unzip zip 同時安裝unzip和zip ...
  • 一、Linux系統的安裝: 關於Linux系統的安裝,如果條件允許最好使用物理機安裝,也可以採用VMware和VirtualBox虛擬機,這裡我採用的是VirtualBox。 1.首先是VirtualBox和Ubuntu系統的下載,下載完成後安裝VirtualBox; 2.安裝完成VirtualBo ...
  • 1、下載jdk8 登錄網址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 選擇對應jdk版本下載 2、解壓 3、然後將文件夾jdk1.8.0_144移動到目錄/opt下 4、修改 ...
  • 啟動docker docker安裝後出現Cannot connect to the Docker daemon You need to add user into docker group. by 'sudo gpasswd -a xxxx docker' (xxx is your user nam ...
  • 搭建Nginx+PHP環境 搭建Nginx+PHP環境用於Memcached的PHP擴展的測試 1. 安裝PHP [root@chunlin PHP] tar zxf php 7.1.5.tar.gz [root@chunlin PHP] cd php 7.1.5 [root@chunlin php ...
  • 開頭指定腳本解釋器 #!/bin/bash 或 #!/bin/sh 開頭加版權信息 #Date: 2017-8-01 22:50 #Author: yang qiang wei #Mail: [email protected] #Funtion: This is delete ... #version: 1.0 ...
  • 原文發表於cu:2016-03-29 參考文檔: 一.環境 1. 拓撲示意 2. 環境 Client:Win7 x86_64, 10.19.1.200 Server:CentOS6.7 x86_64,eth0 10.19.1.100,eth0 172.16.1.100 Intranet:172.16 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...