• 谁动了我的cpu——oprofile使用札记


    引言

    cpu无端占用高?应用程序响应慢?苦于没有分析的工具?

    oprofile利用cpu硬件层面提供的性能计数器(performance counter),通过计数采样,帮助我们从进程、函数、代码层面找出占用cpu的"罪魁祸首"。下面我们通过实例,了解oprofile的具体使用方法。

    常用命令

    使用oprofile进行cpu使用情况检测,需要经过初始化、启动检测、导出检测数据、查看检测结果等步骤,以下为常用的oprofile命令。

    初始化

    • opcontrol --no-vmlinux : 指示oprofile启动检测后,不记录内核模块、内核代码相关统计数据
    • opcontrol --init : 加载oprofile模块、oprofile驱动程序

    检测控制

    • opcontrol --start : 指示oprofile启动检测
    • opcontrol --dump : 指示将oprofile检测到的数据写入文件
    • opcontrol --reset : 清空之前检测的数据记录
    • opcontrol -h : 关闭oprofile进程

    查看检测结果

    • opreport : 以镜像(image)的角度显示检测结果,进程、动态库、内核模块属于镜像范畴
    • opreport -l : 以函数的角度显示检测结果
    • opreport -l test : 以函数的角度,针对test进程显示检测结果
    • opannotate -s test : 以代码的角度,针对test进程显示检测结果
    • opannotate -s /lib64/libc-2.4.so : 以代码的角度,针对libc-2.4.so库显示检测结果

    opreport输出解析

    正如以上命令解析所言,不加参数的opreport命令从镜像的角度显示cpu的使用情况:

    linux # opreport
    CPU: Core 2, speed 2128.07 MHz (estimated)
    Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000
    CPU_CLK_UNHALT.........|
      samples |           %|
    ------------------------
       31645719     87.6453      no-vmlinux  
        4361113     10.3592      libend.so
           7683      0.1367      libpython2.4.so.1.0
           7046      0.1253      op_test
            ⋯⋯

    以上列表按以下形式输出:

                  samples |                            %|
    -----------------------------------------------------
         镜像内发生的采样次数     采样次数所占总采样次数的百分比      镜像名称

    因我们在初始化时执行了"opcontrol --no-vmlinux"命令,指示oprofile不对模块和内核进行检测,因而在探测结果中,模块和内核一同显示成no-vmlinux镜像。输出中,libend.so和libpython2.4.so.1.0均为动态库,op_test为进程。以上采样数据表明,检测时间内,cpu主要执行内核和模块代码,用于执行libend.so库函数的比重亦较大,达到10%左右。 

    进一步地,我们可以查看到进程、动态库中的每个函数在检测时间内占用cpu的情况:

    linux # opreport -l
     samples           %        image name        app name         symbol name
    31645719     87.4472        no-vmlinux      no-vmlinux         /no-vmlinux
     4361113     10.3605         libend.so       libend.so             endless
        7046      0.1253           op_test         op_test                main
        ⋯⋯

    以上输出显示消耗cpu的函数为libend.so库中的endless函数,以及op_test程序中的main函数。

    进行oprofile初始化时,若我们执行opcontrol --vmlinux=vmlinux-`uname -r`,指定oprofile对内核和内核模块进行探测,在执行opreport查看检测结果时,内核和内核模块就不再显示为no-vmlinux,而是内核和各个内核模块作为单独的镜像,显示相应cpu占用情况。

    使用opannotate从代码层看cpu占用情况

    以上介绍了使用oprofile的opreport命令,分别从进程和函数层面查看cpu使用情况的方法。看到这里,有的同学可能有这样的疑问:使用opreport,我找到了消耗cpu的进程A,找到了进程A中最消耗cpu的函数B,进一步地,是否有办法找到函数B中最消耗cpu的那一行代码呢?

    oprofile中的opannotate命令可以帮助我们完成这个任务,结合具备调试信息的程序、带有debuginfo的动态库,opannotate命令可显示代码层面占用cpu的统计信息。下面我们通过几个简单的程序实例,说明opannotate命令的使用方法。

    首先,我们需要一个消耗cpu的程序,该程序代码如下:

    //op_test.c
    extern void endless();
    int main()
    {
      int i = 0, j = 0;
      for (; i < 10000000; i++ )
         {
               j++;
         }
      endless();
      return 0;
    }

    该程序引用了外部函数endless,endless函数定义如下:

    //end.c
    void endless()
    {
      int i = 0;
      while(1)
         {
             i++;
         }
    }

    endless函数同样很简单,下面我们将定义了endless函数的end.c进行带调试信息地编译,并生成libend.so动态库文件:

    linux # gcc -c -g -fPIC end.c
    linux # gcc -shared -fPIC -o libend.so end.o
    linux # cp libend.so /usr/lib64/libend.so

    接着,带调试信息地编译op_test.c,生成op_test执行文件:

    linux # gcc -g -lend -o op_test op_test.c

    之后,我们开启oprofile进行检测,并拉起op_test进程:

    linux # opcontrol --reset
    linux # opcontrol --start
    linux # ./op_test &

    在程序运行一段时间后,导出检测数据,使用opannotate进行结果查看:

    linux # opcontrol --dump
    linux # opannotate -s op_test
    /*
     * Total samples for file : "/tmp/lx/op_test.c"
     *
     * 7046  100.00
     */
                   : int main()
                   :{ /*main total : 7046  100.000 */
                  :    int i = 0, j = 0;
    6447   91.4987 :    for (; i < 10000000; i++ )
                   :    {
     599    8.5013 :          j++;
                   :    }
                 :    endless();
                 :    return 0;
                   :}

    以上输出表明,在op_test程序的main函数中,主要消耗cpu的是for循环所在行代码,因该段代码不仅包含变量i的自增运算,还将i与10000000进行比较。

    下面显示对自编动态库libend.so的检测结果:

    linux # opannotate -s /usr/lib64/libend.so
    /*
     * Total samples for file : "/tmp/lx/end.c"
     *
     * 4361113  100.00
     */
                     : void endless()
                     : {
                    :     int i = 0;
                    :     while(1)
                     :     {
      25661   0.6652 :          i++;
    4335452  99.3348 :     }
                     : }

    查看c库代码占用cpu情况

    以上使用opannotate,分别查看了应用程序代码、自编动态库代码的cpu占用情况,对于c库中的代码,我们是否也能查看其消耗cpu的情况呢?

    在使用oprofile查看c库代码信息前,需要安装glibc的debuginfo包,安装debuginfo包之后,我们即可以通过opannotate查看到c库代码,以下展示了malloc底层实现函数_int_malloc的部分代码:

    linux # opannotate -s /lib64/libc-2.4.so
    /*
     ----------------malloc---------------------
     */
                    :Void_t *
                    :_int_malloc( mstate av, size_t bytes )
                    :{  /* _int_malloc total: 118396  94.9249 */
                         ⋯⋯
                    :       assert((fwd->size & NON_MAIN_ARENA) == 0);
    115460  92.5709 :       while((unsigned long)(size) < (unsigned long)(fwd->size)) {
      1161   0.9308 :            fwd = fwd->fd;
                    :            assert((fwd->size & NON_MAIN_ARENA) == 0);
                    :        }
                    :}

    在进行程序性能调优时,根据oprofile检测到的c库代码占用cpu的统计信息,可以判别程序性能瓶颈是否由c库代码引起。若oprofile检测结果显示cpu被过多地用于执行c库中的代码,我们可进一步地采用修改c库代码、升级glibc版本等方法解决c库引发的应用程序性能问题。

    小结

    本文介绍了使用oprofile工具从进程、函数和代码层面检测cpu使用情况的方法,对于代码层面,分别介绍了查看程序代码、自编动态库代码以及gblic代码cpu统计情况的方法,中间过程使用到opcontrol、opreport、opannotate三个常用的oprofile命令。

    当系统出现cpu使用率异常偏高情况时,oprofile不但可以帮助我们分析出是哪一个进程异常使用cpu,还可以揪出进程中占用cpu的函数、代码。在分析应用程序性能瓶颈、进行性能调优时,我们可以通过oprofile,得出程序代码的cpu使用情况,找到最消耗cpu的那部分代码进行分析与调优,做到有的放矢。另外,进行程序性能调优时,我们不应仅仅关注自己编写的上层代码,也应考虑底层库函数,甚至内核对应用程序性能的影响。

    关于oprofile工具可用于分析的场景,本文仅介绍了cpu使用情况一种,我们还可以通过oprofile查看高速缓存的利用率、错误的转移预测等信息,"opcontrol --list-events"命令显示了oprofile可检测到的所有事件,更多的oprofile使用方法,请参看oprofile manual

    Reference: oprofile manual

  • 相关阅读:
    什么是根文件系统
    构建基本的嵌入式Linux根文件系统
    “文件系统”与“根文件系统”详解
    C#中NameValueCollection类用法详解
    别把西红柿连续种在同一块地里
    asp.net 服务器控件的 ID,ClientID,UniqueID 的区别
    不要为框架作过多的假设
    构件技术
    asp.net中控件id,clientid,uniqueid的区别
    系统架构图怎么画
  • 原文地址:https://www.cnblogs.com/bangerlee/p/2659435.html
Copyright © 2020-2023  润新知