性能分析之从 IO 高定位到具体文件

简介: 【8月更文挑战第21天】性能分析之从 IO 高定位到具体文件

一、概述

IO 的性能分析一直是性能分析的重点之一,分析的思路是:
image.png

在代码的逻辑清晰的情况下,是完全可以知道哪些文件是频繁读写的。但是对性能分析人员来说,通常是面对一个不是自己编写的系统,有时还是多个团队合作产生的系统。这时就会出现很多的推诿和争执。如果可以迅速地把问题到一个段具体的代码,到一个具体的文件,那就可以提高沟通的效率。

通常情况在 linux 环境下,通过 vmstat 或者 iostat 命令可以发现磁盘 IO 的异常,可以看到系统级的磁盘读写量及 CPU 占用率,但无法明确定位到是什么进程在作祟,安装 iotop 后,可以定位到进程,但并不知道改进程在操作什么文件。

image.png

二、核心原理

本文是考虑从系统级的工具来完成这个操作,比较具有通用性。在这之前需要先理解一下文件的一个重要的属性:inode。

什么是 inode 呢?先来看一个示意图:

image.png

磁盘上最小的存储单元是扇区 sector,每8个扇区组成一个块 block(4096字节)。如下所示:

[root@7DGroup2 ~]# tune2fs -l /dev/vda1|grep  Block
Block count:              10485504
Block size:               4096
Blocks per group:         32768
[root@7DGroup2 ~]#

文件的存储就是由这些 块组成的,当块多了之后就成了如下这样(其实磁盘上的块比这个图中多得多,这里只是示意图):

image.png

其中红色的这部分是存储的文件,我们通常在文件系统中直接ls或者用其他命令操作文件的时候是根据路径来操作的,那些是上层的命令。当我们执行了一个命令之后,操作系统会来找到这些文件做相应的操作,怎么找到这些文件呢,那就需要 inode 了。Inode 用来存储这些文件的元信息,也就是索引节点,它包括的信息有:

  • 字节数
  • User ID
  • Group ID
  • 读、写、执行权限
  • 时间戳,共有三个:ctime 指 inode 上一次变动的时间,mtime 指文件内容上一次变动的时间,atime 指文件上一次打开的时间
  • 链接数,有多少文件名指向这个 inode
  • 文件数据 block 的位置

通过这些信息,我们才能实现对文件的操作。这个 inode 其实也是存储在磁盘上的,也需要占用一些空间,如上图中的绿色部分所示。

当我们在系统级看到 IO 过高的时候,比如下图所示:

image.png

从上图可以看到,这系统几乎所有的 CPU 都在等 IO。这时怎么办?就用我们前面提到的分析的思路,查看进程级和线程级的 IO,进而找到具体的文件。下面我们来具体实现。

这里我们用的是 systemtap,这个工具 7Dgroup 之前的文章中提到的,但没有展开说。后面如果有可能我们再多写些类似的工具原理和使用方法。

Systemtap 的逻辑图如下:

image.png

从逻辑图上看,它工作在内核层面,不是 shell 的层面。
SystemTap 为我们开启了一扇通往系统内核的大门,SystemTap 自带的 examples 中提供一些磁盘 IO 相关的监控例子。

以 iotop.stp 为例,源码如下:

#!/usr/bin/stap
global reads, writes, total_io

probe vfs.read.return {
   
   
    reads[execname()] +=  bytes_read
}

probe vfs.write.return {
   
   
    writes[execname()] +=  bytes_written
}


# print top 10 IO processes every 5 seconds
probe timer.s(5) {
   
   
    foreach (name in writes)
        total_io[name] +=  writes[name]
    foreach (name in reads)
        total_io[name] +=  reads[name]

    printf ("%16s\t%10s\t%10s\n",  "Process", "KB Read", "KB Written")

    foreach (name in total_io-  limit 10)
         printf("%16s\t%10d\t%10d\n", name,
                reads[name]/1024, writes[name]/1024)

    delete reads
    delete writes
    delete total_io
    print("\n")

}

执行的结果是:每隔 5 秒打印读写总量排前 10 位的进程。

image.png

该脚本有两个问题:

  1. 按照进程名字统计,存在统计误差,进程名一致,但 PID 不一样的进程,都统计到一起;

  2. 我们依然不能知道进程操作了什么文件。

通过对 probe点 的分析(sudo stap -L'vfs.{write,read}'),我们可以知道,vfs.read,vfs.write 有局部变量 ino 可以利用,ino 是文件的inode,这样我们就可以明确的探测到读写量最多的进程及文件。

$ sudo stap -L 'vfs.{write,read}'
vfs.read  file:long pos:long buf:long bytes_to_read:long dev:long devname:string  ino:long name:string argstr:string $file:struct file* $buf:char*  $count:size_t $pos:loff_t*
vfs.write  file:long pos:long buf:long bytes_to_write:long dev:long devname:string  ino:long name:string argstr:string $file:struct file* $buf:char const*  $count:size_t $pos:loff_t*

扩展过的脚本如下:

#!/usr/bin/stap
global reads, writes, total_io

probe vfs.read.return {
   
   
     reads[execname(),pid(),ino] += bytes_read
}


probe vfs.write.return {
   
   
     writes[execname(),pid(),ino] += bytes_written
}


# print top 10 IO processes every 5 seconds
probe timer.s(5) {
   
   
    foreach  ([name,process,inode] in writes)
         total_io[name,process,inode] += writes[name,process,inode]
    foreach ([name,process,inode] in reads)
         total_io[name,process,inode] += reads[name,process,inode]
    printf  ("%16s\t%8s\t%8s\t%10s\t%10s\n", "Process",  "PID", "inode", "KB Read", "KB  Written")
    foreach  ([name,process,inode] in total_io- limit 10)
         printf("%16s\t%8d\t%8d\t%10d\t%10d\n", name,process,inode,
                reads[name,process,inode]/1024, writes[name,process,inode]/1024)

    delete reads
    delete writes
    delete total_io
    print("\n")
}

三、一个实验

我们来做个实验,执行 dd 命令来做一个高磁盘读写操作。
执行命令如下:

dd bs=64k count=4k if=/dev/zero of=test oflag=dsync

这条命令执行的效果是:dd 在执行时每次都会进行同步写入操作,每次从 /dev/zero 读取 64 k数据,然后写入当前目录下的 test 文件,一共重复 4 K次。在 linux 系统中, /dev/zero 是一个特殊的文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00)。

iotop.stp 监控结果如下:

image.png

通过监控,我们知道了,PID 为 2978 的 dd 进程 读取 inode 为 1047 的文件,写入 inode 为 663624 的文件,这两个是读写最多的操作。
通常情况下,我们并不知道 inode 对应文件的位置,可以通过 find / -inum 1047 找到对应的文件。
通过 stat 命令,我们可以看到文件inode详细的描述。

$ stat /dev/zero
   文件:"/dev/zero"
   大小:0           块:0          IO 块:4096   字符特殊文件
设备:5h/5d     Inode:1047        硬链接:1     设备类型:1,5
权限:(0666/crw-rw-rw-)  Uid:(    0/     root)   Gid:(    0/     root)
环境:system_u:object_r:zero_device_t:s0
最近访问:2017-05-02  10:50:03.242425632 +0800
最近更改:2017-05-02  10:50:03.242425632 +0800
最近改动:2017-05-02  10:50:03.242425632 +0800
创建时间:-

这个分析思路在任何一个系统中都可以说是能用的,只是不同的系统用的工具不同。这次用的环境是 CentOS,那在其他的系统中,只能找到相对应的其他工具了。

四、总结

再次强调,了解原理、理清思路是性能分析的重点。工具的使用是为了验证思路的正确性。千万不要舍本逐末。

目录
相关文章
|
22天前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
117 1
|
5月前
|
XML JSON Go
Go语言中的文件与IO:JSON、CSV、XML处理
本文介绍了 Go 语言中对 JSON、CSV 和 XML 三种常见数据格式的处理方法。通过标准库 `encoding/json`、`encoding/csv` 和 `encoding/xml`,可以实现结构体与数据格式之间的序列化与反序列化。JSON 适合 Web API 和前后端通信,因其清晰易读;CSV 适用于表格数据和轻量级交换;XML 则支持复杂嵌套结构,常用于配置文件和 SOAP 协议。文中提供代码示例,涵盖基本使用、嵌套结构处理及实战建议,帮助开发者高效操作这些格式。
|
5月前
|
Unix Go
Go语言中的文件与IO:文件读写
本文介绍了 Go 语言中文件操作的基础方法,涵盖打开与关闭文件、读取和写入文件内容、追加写入以及复制文件等功能。通过 `os`、`bufio` 和 `io` 等标准库包,提供了高效且灵活的实现方式,如使用 `os.ReadFile` 读取整个文件、`bufio.Scanner` 逐行读取、`os.Create` 创建文件以及 `io.Copy` 复制文件内容。同时强调了错误处理的重要性,例如使用 `defer` 确保文件关闭,并推荐注意文件权限设置(如 UNIX 系统中的 `0644`)。最后以表格形式总结了常用操作及其推荐方法,便于快速查阅和应用。
|
5月前
|
Go 数据处理
Go语言中的文件与IO:bufio 和 scanner
Go 标准库中的 `bufio` 包高效读写功能,适用于文件和数据处理。`bufio.Reader` 支持按行或分隔符读取,`bufio.Writer` 提供高性能写入并需调用 `Flush()` 确保数据写入。`bufio.Scanner` 是处理文本文件(如日志、配置)的利器,可按行、单词等分割内容。本文详解其用法,并给出实践建议,如统计字符数、模拟 `tail -f` 和词频分析等。
|
9月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
343 34
|
12月前
|
Java 测试技术 Maven
Maven clean 提示文件 java.io.IOException
在使用Maven进行项目打包时,遇到了`Failed to delete`错误,尝试手动删除目标文件也失败,提示`java.io.IOException`。经过分析,发现问题是由于`sys-info.log`文件被其他进程占用。解决方法是关闭IDEA和相关Java进程,清理隐藏的Java进程后重新尝试Maven clean操作。最终问题得以解决。总结:遇到此类问题时,可以通过任务管理器清理相关进程或重启电脑来解决。
|
11月前
|
存储 Java API
【JavaEE】——文件IO(万字长文)
文件路径,文本文件,二进制文件,File类,文件流,字节流(InputStream,OutputStream)字符流(Reader,Writer)
|
搜索推荐 索引
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
167 2
|
编解码 Java 程序员
【文件IO】文件内容操作
【文件IO】文件内容操作
181 2
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
135 1