如何实现一个Linux性能监控工具

NmonLinux系统下一个简单但强大的性能监控工具。在新年的头一个月里,我花时间阅读了nmon的代码,总结了如何实现一个简单的Linux性能监控工具:

(1)获取性能数据。/proc文件系统是个宝库,你想要的信息几乎都可以从这里得到:
a)CPU利用率:/proc/stat
b)内存利用率:/proc/meminfo/proc/vmstat
c)磁盘利用率:/proc/diskstats
d)网络利用率:/proc/net/dev
e)单独进程的状态:/proc/[pid]目录;
f)其余感兴趣的信息:比如关于系统的负载状况,可以读取/proc/loadavg

(2)理解和解析数据。参考man手册,了解每一项数据的含义,必要时候可以阅读内核代码和学习相关的硬件知识。

(3)展示数据。Nmon使用的是“原始”的ncurses库,当然也可以使用“现代化”的GUI工具以达到更好的用户体验。

如果想进一步了解nmon内部的原理,也可参考我写的这本剖析nmon代码的小册子

Perf笔记(四)——profile应用程序

使用perf record命令可以profile应用程序  (编译程序要使用-g,推荐使用-g -O2

perf record program [args]

或者在程序启动以后,使用-p pid选项:

perf record -p pid

默认情况下,信息会存在perf.data文件里,使用perf report命令可以解析这个文件:

Capture

哪些函数占用CPU比较多,一目了然。

另外,在使用perf record时可以加--call-graph dwarf选项:

--call-graph
 Setup and enable call-graph (stack chain/backtrace) recording, implies -g.
 Default is "fp".

采样结果如下:

Capture

关于ChildrenSelf的含义,perf wiki给了一个详尽的解释。以下列代码为例:

void foo(void) {
    /* do something */
}

void bar(void) {
    /* do something */
    foo();
}

int main(void) {
    bar()
    return 0;
}

Self表示函数本身的overhead:如果foo函数的overhead60%,那么barSelf overhead就是40%(刨除foo所占部分)。因为foobar都是main的子函数,所以二者的overhead都要计算入mainChildren overhead

对于采用--call-graph dwarf选项生成的perf.data做出的火焰图如下:

Capture可以看到显示了完整的函数调用栈。

Perf笔记(三)——tiptop

Tiptop是一个Linux系统性能工具,它通过读取CPU硬件计数器的信息(比如cahche missexecuted instructions per cycle等等),使我们对程序的执行效率有了更清晰的认识:

Capture

Tiptop通过perf_event_open(http://man7.org/linux/man-pages/man2/perfeventopen.2.html)系统调用(2.6.31版本称为perf_counter_open)来完成读取硬件计数器信息:

int perf_event_open(struct perf_event_attr *attr,
                           pid_t pid, int cpu, int group_fd,
                           unsigned long flags);

attr用来指定需要关注哪些硬件计数器;pidcpu指定关注运行在哪些CPU的进程(线程);group_fd用来设定event group,创建group leader时,group_fd设为-1flags可以置为0

perf_event_open执行成功后会返回一个有效的文件描述符,后续可通过ioctlread系统调用对这个文件描述符进行操作,达到想要的目的。

Linux下使用vmstat命令获得系统CPU的使用状态

本文是使用vmstat命令监控CPU使用的续文。

Linux下使用vmstat命令可以得到系统CPU的使用状态:

# vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 1860352    948 131040    0    0  2433   137  252  897  2  7 90  1  0

其中描述CPU状态的是最后5列:

------cpu-----
us sy id wa st
2  7 90  1  0

要注意,上面数字的含义是百分比。即CPU运行user space程序的时间占2%,。。。

各列含义如下:

ususer time):CPU运行user space代码的时间;
sysystem time):CPU运行kernel代码的时间,比如执行系统调用;
ididle time):CPU处于idle状态的时间;
waIO-wait time):CPU处于idle状态,因为所有正在运行的进程都在等待I/O操作完成,因此当前无可以调度的进程;
ststolen time):CPU花费在执行系统上运行的虚拟机的时间。

参考资料:
The precise meaning of I/O wait time in Linux
Linux Performance Analysis in 60,000 Milliseconds

使用vmstat命令监控CPU使用

vmstat命令可以用来监控CPU的使用状况。举例如下:

# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 5201924   1328 5578060    0    0     0     0 1582 6952  2  1 98  0  0
 1  0      0 5200984   1328 5577996    0    0     0     0 2020 20567  9  1 90  0  0
 0  0      0 5198668   1328 5577952    0    0     0     0 1568 7617  5  1 94  0  0
 0  0      0 5194844   1328 5578000    0    0     0   187 1249 7057  1  1 98  0  0
 0  0      0 5199956   1328 5578232    0    0     0     0 1496 7306  4  1 95  0  0

上述命令每隔1秒输出系统状态,最后5列是描述的是CPU状况。man手册上关于这5列的含义描述的很清楚:

CPU
       These are percentages of total CPU time.
       us: Time spent running non-kernel code.  (user time, including nice time)
       sy: Time spent running kernel code.  (system time)
       id: Time spent idle.  Prior to Linux 2.5.41, this includes IO-wait time.
       wa: Time spent waiting for IO.  Prior to Linux 2.5.41, included in idle.
       st: Time stolen from a virtual machine.  Prior to Linux 2.6.11, unknown.

vmstat实质上是从/proc/stat文件获得系统状态:

# cat /proc/stat
cpu  381584 711 299364 1398303520 429839 0 251 0 0 0
cpu0 90740 58 44641 174627550 131209 0 120 0 0 0
cpu1 43141 26 22925 174746812 108219 0 10 0 0 0
cpu2 41308 35 25097 174831161 25877 0 40 0 0 0
cpu3 39301 70 27514 174836084 27792 0 4 0 0 0
cpu4 39187 78 46191 174750027 109013 0 0 0 0 0
......

需要注意的是这里数字的单位是Jiffies

另外,vmstat计算CPU时间百分比使用的是“四舍五入”算法(vmstat.c):

static void new_format(void){
    ......
    duse = *cpu_use + *cpu_nic;
    dsys = *cpu_sys + *cpu_xxx + *cpu_yyy;
    didl = *cpu_idl;
    diow = *cpu_iow;
    dstl = *cpu_zzz;
    Div = duse + dsys + didl + diow + dstl;
    if (!Div) Div = 1, didl = 1;
    divo2 = Div / 2UL;
    printf(w_option ? wide_format : format,
           running, blocked,
           unitConvert(kb_swap_used), unitConvert(kb_main_free),
           unitConvert(a_option?kb_inactive:kb_main_buffers),
           unitConvert(a_option?kb_active:kb_main_cached),
           (unsigned)( (unitConvert(*pswpin  * kb_per_page) * hz + divo2) / Div ),
           (unsigned)( (unitConvert(*pswpout * kb_per_page) * hz + divo2) / Div ),
           (unsigned)( (*pgpgin        * hz + divo2) / Div ),
           (unsigned)( (*pgpgout           * hz + divo2) / Div ),
           (unsigned)( (*intr          * hz + divo2) / Div ),
           (unsigned)( (*ctxt          * hz + divo2) / Div ),
           (unsigned)( (100*duse            + divo2) / Div ),
           (unsigned)( (100*dsys            + divo2) / Div ),
           (unsigned)( (100*didl            + divo2) / Div ),
           (unsigned)( (100*diow            + divo2) / Div ),
           (unsigned)( (100*dstl            + divo2) / Div )
    );
    ......
}

所以会出现CPU利用百分比相加大于100的情况:2 + 1 + 98 = 101

另外,在Linux系统上,r字段表示的是当前正在运行和等待运行的task的总和。

 

参考资料:
/proc/stat explained
procps

 

Profiling CPU使用

本文内容取自于《Systems Performance: Enterprise and the Cloud》

Profiling CPU的方法是通过对CPU状态进行周期性地采样,然后进行分析。包含5个步骤:

1. Select the type of profile data to capture, and the rate.
2. Begin sampling at a timed interval.
3. Wait while the activity of interest occurs.
4. End sampling and collect sample data.
5. Process the data.

CPU采样数据基于下面两个因素:

a. User level, kernel level, or both
b. Function and offset (program-counter-based), function only, partial stack trace, or full stack trace

抓取user levelkernel level所有的函数调用栈固然可以完整地得到CPUprofile,但这样会产生太多的数据。因此通常只采样user levelkernel level部分函数调用栈就可以了,有时可能仅需要保留函数的名字。

下面是一个使用DTraceCPU采样的例子:

 # dtrace -qn 'profile-997 /arg1/ {@[execname, ufunc(arg1)] = count();} tick-10s{exit(0)}'

 top                                                 libc.so.7`0x801154fec                                             1
 top                                                 libc.so.7`0x8011e5f28                                             1
 top                                                 libc.so.7`0x8011f18a9                                             1

 

Linux kernel 笔记 (60)——scheduling domain

NUMA系统上,由于不同CPU直接访问本地内存和远端内存的时间相差很大,所以更好地调度算法就显得很重要。Linux kernel引入了scheduling domain的概念。可以参看下面例子:

[root@localhost ~]# cd /proc/sys/kernel/sched_domain/
[root@localhost sched_domain]# ls
cpu0  cpu1  cpu2  cpu3  cpu4  cpu5  cpu6  cpu7
[root@localhost sched_domain]# ls -alt *
cpu0:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu1:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu2:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu3:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu4:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu5:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu6:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

cpu7:
total 0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain0
dr-xr-xr-x. 1 root root 0 Feb 26 20:06 domain1
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 .
dr-xr-xr-x. 1 root root 0 Feb 26 19:37 ..

/proc/sys/kernel/sched_domain/目录下每个CPU都有一个自己的目录,并且每个CPU目录下都有和自己相关的domain信息。

multi-level系统中,也拥有multi-levelscheduling domain(内核中结构体是struct sched_domain)。每个scheduling domain包含一组共享属性和调度策略的CPU;每个scheduling domain包含至少一个或多个CPU group(内核中结构体是struct sched_group),每个CPU group会被scheduling domain看做一个独立的单元。

scheduling domain的核心代码位于kernel\sched\core.c中,关于/proc/sys/kernel/sched_domain/cpu$/domain$中各个文件的含义,都可以在这里找到。

NUMA系统上,如果一个node利用率非常高,比如高于90%,而另一个node利用率可能只有60%~70%,这时可以尝试disable wakeup affinity

参考资料:
Scheduling domains
sched-domains.txt
Does domain0 in /proc/sys/kernel/sched_domain/cpu$ refer top-level domain in the system?
How to understan /proc/sys/kernel/sched_domain/cpu$/domain$/flags?

Perf笔记(二)

Perf_events所处理的hardware event(硬件事件)需要CPU的支持,而目前主流的CPU基本都包含了PMUPerformance Monitoring Unit,性能监控单元)。PMU用来统计性能相关的参数,像cache命中率,指令周期等等。由于这些统计工作是硬件完成的,所以CPU开销很小。

X86体系结构为例,PMU包含了两种MSRsModel-Specific Registers,之所以称之为Model-Specific,是因为不同modelCPU,有些register是不同的):Performance Event Select RegistersPerformance Monitoring CountersPMC)。当想对某种性能事件(performance event)进行统计时,需要对Performance Event Select Register进行设置,统计结果会存在Performance Monitoring Counter中。

perf_events工作在采样模式(samplingperf record命令即工作在这种模式)时,由于采样事件发生时和实际处理采样事件之间有时间上的delay,以及CPU流水线和乱序执行等因素,所以得到的指令地址IP(Instruction Pointer)并不是当时产生采样事件的IP,这个称之为skid。为了改善这种状况,使IP值更加准确,Intel使用PEBSPrecise Event-Based Sampling),而AMD则使用IBSInstruction-Based Sampling)。

PEBS为例:每次采样事件发生时,会先把采样数据存到一个缓冲区中(PEBS buffer),当缓冲区内容达到某一值时,再一次性处理,这样可以很好地解决skid问题。

执行一下perf list --help命令,会看到下面内容:

The p modifier can be used for specifying how precise the instruction address should be. The p modifier can be specified multiple times:

       0 - SAMPLE_IP can have arbitrary skid
       1 - SAMPLE_IP must have constant skid
       2 - SAMPLE_IP requested to have 0 skid
       3 - SAMPLE_IP must have 0 skid

For Intel systems precise event sampling is implemented with PEBS which supports up to precise-level 2.

现在可以理解,经常看到的类似“perf record -e "cpu/mem-loads/pp" -a”命令中,pp就是指定IP精度的。

Perf笔记(一)

Perf_events是目前在Linux上使用广泛的profiling/tracing工具,除了本身是内核(kernel)的组成部分以外,还提供了用户空间(user-space)的命令行工具(“perf”,“perf-record”,“perf-stat”等等)。

perf_events提供两种工作模式:采样模式(sampling)和计数模式(counting)。“perf record”命令工作在采样模式:周期性地做事件采样,并把信息记录下来,默认保存在perf.data文件;而“perf stat”命令工作在计数模式:仅仅统计某个事件发生的次数。

我们经常看到类似这样的命令:“perf record -a ...... sleep 10”。在这里,“sleep”这个命令相当于一个“dummy”命令,没有做任何有意义的工作,它的作用是让“perf record”命令对整个系统进行采样,并在10秒后自动结束采样工作。