最近在写程序时需要在 centos5 系统上获取 device mapper 中的虚拟块设备的读写信息。在这个过程中发现由于 go 跨平台的特性,有一些 api 是无法拿到特定平台上的一些特殊信息的,或者是需要一些小技巧来实现。

获取磁盘读写速度

正常情况下 linux 上通过 /proc/diskstats 这个文件来获取磁盘设备的读写信息,这个文件的内容可以通过 cat /proc/diskstats 获取,如下:

   1    0 ram0 0 0 0 0 0 0 0 0 0 0 0
   1    1 ram1 0 0 0 0 0 0 0 0 0 0 0
   1    2 ram2 0 0 0 0 0 0 0 0 0 0 0
   1    3 ram3 0 0 0 0 0 0 0 0 0 0 0
   1    4 ram4 0 0 0 0 0 0 0 0 0 0 0
   1    5 ram5 0 0 0 0 0 0 0 0 0 0 0
   1    6 ram6 0 0 0 0 0 0 0 0 0 0 0
   1    7 ram7 0 0 0 0 0 0 0 0 0 0 0
   1    8 ram8 0 0 0 0 0 0 0 0 0 0 0
   1    9 ram9 0 0 0 0 0 0 0 0 0 0 0
   1   10 ram10 0 0 0 0 0 0 0 0 0 0 0
   1   11 ram11 0 0 0 0 0 0 0 0 0 0 0
   1   12 ram12 0 0 0 0 0 0 0 0 0 0 0
   1   13 ram13 0 0 0 0 0 0 0 0 0 0 0
   1   14 ram14 0 0 0 0 0 0 0 0 0 0 0
   1   15 ram15 0 0 0 0 0 0 0 0 0 0 0
   8    0 sda 29130 11247 905939 500914 155932 727767 7071070 2191673 0 552400 2692588
   8    1 sda1 84 1040 2264 450 31 12 86 382 0 809 832
   8    2 sda2 29026 10190 903379 500111 155901 727755 7070984 2191291 0 551590 2691403
 253    0 dm-0 38942 0 902058 758838 883873 0 7070984 30357342 0 551667 31116195
 253    1 dm-1 142 0 1136 502 0 0 0 0 0 84 502
  22    0 hdc 0 0 0 0 0 0 0 0 0 0 0
   2    0 fd0 0 0 0 0 0 0 0 0 0 0 0
   9    0 md0 0 0 0 0 0 0 0 0 0 0 0

第一列和第二列是设备号,第三列是设备名称,第六列是读取的 sectors 数,第十列是写入的 sectors 数。

和计算 cpu 使用率类似,我们需要读取两次该文件,将两次读取到的值相减以后除以间隔时间来计算读写速度。

虚拟块设备的区别

通过 df -h 可以看到其中 /dev/mapper/VolGroup00-LogVol00 就是一个虚拟块设备:

Filesystem            Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00
                       18G  1.9G   15G  12% /
/dev/sda1              99M   13M   81M  14% /boot
tmpfs                 499M     0  499M   0% /dev/shm

但是获取读写信息时拿到的对应设备名并不是这个,实际上应该是 dm-0

所以我们需要通过设备号来获取他们的对应关系。

获取设备号

在 centos5 上比较特殊,没法使用 lsblk 获取设备号,所以需要用其他的方法。

ls -lh /dev/mapper/

total 0
crw------- 1 root root  10, 60 Jul 11 22:18 control
brw-rw---- 1 root disk 253,  0 Jul 11 22:19 VolGroup00-LogVol00
brw-rw---- 1 root disk 253,  1 Jul 11 22:18 VolGroup00-LogVol01

可以看出 VolGroup00-LogVol00 的设备号为 253,0,既然 ls -lh 可以显示设备号信息,说明这个信息是可以通过 stat 获取到的。

go 中获取文件信息

go 中文件的信息接口如下:

type FileInfo interface {
    Name() string           // 文件的名字(不含扩展名)
    Size() int64            // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    Mode() FileMode         // 文件的模式位
    ModTime() time.Time     // 文件的修改时间
    IsDir() bool            // 等价于
    Mode().IsDir()Sys() interface{} // 底层数据来源(可以返回nil)
}

由于 go 语言需要考虑跨平台的特性,正常情况下只能拿到这些通用的信息。而如果要在 linux 下获取设备号,关键就在于那个 Sys() 函数所返回的 Interface{}

在 linux 平台上,其对应的是一个 Stat_t 结构体:

type Stat_t struct {
    Dev       uint64
    X__pad1   uint16
    Pad_cgo_0 [2]byte
    X__st_ino uint32
    Mode      uint32
    Nlink     uint32
    Uid       uint32
    Gid       uint32
    Rdev      uint64
    X__pad2   uint16
    Pad_cgo_1 [2]byte
    Size      int64
    Blksize   int32
    Blocks    int64
    Atim      Timespec
    Mtim      Timespec
    Ctim      Timespec
    Ino       uint64
}

需要注意这个结构体在不同平台上的定义是不同的。

其中的 Rdev 就是设备号的值,例如查看上文中的设备文件设备号返回结果是 64768,转换成 ls -lh 中显示的格式就是 253,0,转换成 16 进制是 FD00

下面是示例代码片段,需要注意的是这段代码可能并不能在除 linux 之外的其他平台运行。

dev, err := os.Stat("/dev/mapper/VolGroup00-LogVol00")
if err != nil {
    os.Exit(1)
}
sys, ok := dev.Sys().(*syscall.Stat_t)
if !ok {
    os.Exit(1)
}
major := sys.Rdev / 256
minor := sys.Rdev % 256
devNumStr := fmt.Sprintf("%d:%d", major, minor)
fmt.Printf("get dev mapper [%s] [%s]", dev.Name, devNumStr)