定位 UNIX 上常见问题的经验总结

标签: IT技术 Unix | 发表时间:2012-06-19 17:35 | 作者:童海波
出处:http://blog.jobbole.com

简介: 本文主要对 UNIX 平台常见的问题进行了分类,介绍一些常见问题分析时使用的方法和命令,对以下三种常见问题的分析方法做了简单介绍:UNIX 下 Crash 问题的分析方法、UNIX 下内存泄露问题的分析方法和 UNIX 下 performance 问题的分析方法。

同时通过对下面两个例子的介绍,巩固了上面问题分析的介绍:

● 一个多线程应用的性能问题的分析

● 一个 crash 问题的分析

UNIX 程序常见问题分类

UNIX 下运行程序,经常会遇到以下几类问题 :

1. Crash

2. 内存泄露

3. 句柄泄露

4. 进程不响应

5. 性能不满足预期

6. 逻辑错误

UNIX 程序常见问题的分析方法

UNIX 下 Crash 问题的分析方法

crash 原理和 core 文件生成原因 ( 信号的介绍 )

Crash 是进程崩溃,是由于应用进程做了错误的操作 ( 例如,数组拷贝越界导致对系统内存进行了写操作,使用了错误的指针地址 ), 操作系统向应用进程发送了信号,如果应用进程没有做特殊处理,应用进程将 core dump 在进程当前的工作目录下生成一个 core 文件,core 文件复制了该进程的存储图像,是一个内存映像。

不是所有的信号默认行为都是 crash, 常见默认 crash 信号主要有:

SIGABRT

SIGBUS

SIGSEGV

SIGILL

SIGPIPE

可以通过 kill –l (适用所有 UNIX 平台)查看信号的信息。

查看针对某个进程的所有信号的默认行为(例如:在 Solaris 平台使用 psig pid 命令查看,其他平台的命令略有不同,请参考各自平台用户手册).

[[email protected] SunOS a]# psig  25040
 25040:  /qatest/ModelerServer/5.0.0.0.64/modelersrv_15_0 -server
 HUP     caught  0x10002958c     0
 INT     caught  0x100029580     0
 QUIT    default
 ILL     default
 TRAP    default
 ABRT    default
 EMT     default
 FPE     default
 KILL    default
 BUS     default
 SEGV    default
 SYS     default
 PIPE    ignored
 ALRM    default
 TERM    caught  0x100029580     0
 USR1    default
 USR2    default
 CLD     caught  0x100067f44     NOCLDSTOP

下面列举一些常见信号的默认操作以及可能产生的原因:

例如:Solaris 平台如下。下面的信息参考 Solaris 内核结构第 2 版第二章(Solaris 进程模型) 第 75 页,其他平台基本相同,请参考各自平台用户手册:

信号 值 处理动作 发出信号的原因

SIGHUP 缺省的动作是终止进程 终端挂起或者控制进程终止

SIGINT 缺省的动作是终止进程 键盘中断(如 break 键被按下)

SIGQUIT 缺省的动作是终止进程并进行内核映像转储(dump core)键盘的退出键被按下

SIGILL 缺省的动作是终止进程并进行内核映像转储(dump core)非法指令

SIGABRT 缺省的动作是终止进程并进行内核映像转储(dump core)由 abort(3) 发出的退出指令

SIGFPE 缺省的动作是终止进程并进行内核映像转储(dump core)浮点异常

SIGKILL 9 AEF Kill 信号 终止信号

SIGSEGV 缺省的动作是终止进程并进行内核映像转储(dump core)无效的内存引用

SIGPIPE 缺省的动作是终止进程 管道破裂 : 写一个没有读端口的管道

SIGALRM 缺省的动作是终止进程 由 alarm(2) 发出的信号

SIGTERM 缺省的动作是终止进程 终止信号

SIGUSR1 缺省的动作是终止进程 用户自定义信号 1

SIGUSR2 缺省的动作是终止进程 用户自定义信号 2

SIGCHLD 缺省的动作是忽略此信号 子进程结束信号

SIGSTOP DEF 终止进程

SIGBUS 缺省的动作是终止进程并进行内核映像转储(dump core)总线错误 ( 错误的内存访问 )

core 文件分析一般思路

首先使用 file 命令(所有 UNIX 平台适用)查看 core 文件生成的源程序

bash-3.00$ file core
 core:           ELF 64-bit MSB core file SPARCV9 Version 1, from 'qatest'

从以上结果可以看出,该 core 文件是由 64 位程序 qatest 生成的。

然后使用 gdb( 或者 dbx) 对 core 文件进行分析:

bash-2.05$ dbx ./qatest ./core

再使用 where 命令查看 core 的位置:

[email protected] ([email protected]) program terminated by signal BUS(invalid address alignment)
 Current function is MCXML_700::MCSetting::MCSetting
   87       fpValue = s.GetValue()->Clone();
 (dbx) where

从这个 core 文件可以看到,它收到了 BUS 信号,crash 的位置在 = s.GetValue()->Clone() 函数。

更多有关 gdb,dbx 的使用请参考 gdb,dbx 用户手册。

core 文件无法生成常见原因

当程序崩溃时,并不是总会生成 core 文件。经常有下面的情况导致 core 文件没有产生:

1. 对 core 文件大小做了限制,可以通过 ulimit(所有 UNIX 平台适用)的命令进行查看:

bash-3.00$ ulimit -a
 core file size        (blocks, -c) unlimited
 data seg size         (kbytes, -d) unlimited
 file size             (blocks, -f) unlimited
 open files                    (-n) 256
 pipe size          (512 bytes, -p) 10
 stack size            (kbytes, -s) unlimited
 cpu time             (seconds, -t) unlimited
 max user processes            (-u) 29995
 virtual memory        (kbytes, -v) unlimited

建议使用下面的命令将这个限制改为 unlimited

bash-3.00$ ulimit –c unlimited

2. 磁盘空间是否充足,通过 df 命令(所有 UNIX 平台适用)查看 Available 的空间是否充足。

bash-3.00$ df -k
Filesystem           1024-blocks        Used   Available  Capacity  Mounted on
/                              0    40975117    99394509    30%       /
/dev                   140369626    40975117    99394509    30%       /dev

3. 查看信号是否被捕获(例如:Solaris 平台使用 psig 进行查看,见上面的例子,其他平台的命令略有不同,请参考各自平台用户手册)。

如果上面的情况导致 core 文件没有生成,请修改它。

4.没有 core 文件产生,如何分析 crash

有时候经常发现进程 crash 了,但是 core dump 文件没有产生。这时可以使用 dbx,gdb 等调试工具首先 attach 到运行的进程上,然后再执行业务,如果进程 crash,dbx 或者 gdb 将终止在 crash 的位置,我们便可以根据这个堆栈信息对 crash 进行分析,与分析 core 文件相同。

UNIX 下内存泄露问题分析方法

内存泄露简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个 系统崩溃

封装 new 和 delete 对内存泄漏进行分析

通过对 new 和 delete 的封装,将 new 和 delete 的过程通过日志文件的保存记录下来。然后对日志文件进行分析,是否 new 和 delete 是匹配的,有哪些内存申请,但是没有释放。

下面通过一个简单的测试程序(此代码使用 C++ 语言实现,目前没有考虑申请数组的情况)进行演示:

这个测试程序申请了 pTemp1,pTemp2,pTemp3 的三块内存,但是仅仅释放了 pTemp3,存在 pTemp1 和 pTemp2 的内存泄露。

程序解释:

在每次内存申请时,将内存申请的信息注册到 MAP 表中,在每次内存释放时,将对应的内存信息从注册表中删除,这样注册表中将保存未释放的内存信息,按照一定的规则将注册表中的信息输出(定时或者进程退出等)。然后我们从输出信息中便可以分析出内存泄漏点。

通过自定义宏 DEMONEW 和 DEMODELETE 申请内存和释放内存,在这两个宏中,我们将内存的申请和释放做了记录,从而可以得到未释放内存的信息,请参考下面的程序实现流程图:

图 1. 内存申请释放流程:
定位 UNIX 上常见问题的经验总结

图 2.DEMONEW 实现流程:
定位 UNIX 上常见问题的经验总结

图 3.DEMODELETE 实现流程:
定位 UNIX 上常见问题的经验总结

测试程序代码:

#include <map>
 #include <iostream>
 #include <string>
 #include <fstream> 

 // 申请内存时,存储 new 位置的数据结构
 typedef struct {
 std::string filename;
 int line;
 } MEMINFO; 

 // 输出文件
 std::ofstream loginfo("//tmp/memory.log"); 

 typedef std::map<long long, MEMINFO> MemMap; 

 // 存储内存申请记录(在每次内存申请时,将内存申请的地址作为键值,
 // 内存申请操作所在的文件名和行号作为内容,存储到下面的数据结构 memmap 中)
 MemMap memmap; 

 // 注册内存申请信息到上面的 map 容器中,输入的参数分别为内存地址,文件名,行号
 void RegMemInfo(long long addr, const char *fname, long long lnum)
 {
        MEMINFO info; 

        if (fname)
        {
            info.filename = fname;
        }
        info.line = lnum;
        memmap.insert(MemMap::value_type(addr, info));
 }; 

 // 卸载内存申请信息从上面的 map 容器中,输入的参数为内存地址
 void UnRegMemInfo(long long addr)
 {
        if (memmap.end() != memmap.find(addr))
        {
                memmap.erase(addr);
        }
 } 

 // 定义宏 DEMONEW,封装了内存申请的操作,在内存申请成功后,调用 RegMemInfo 功能,
 // 将内存信息注册到 map 容器中
 #define DEMONEW(p, ptype)\
 do \
 {\
        p = new ptype;\
        if (p)\
        {\
            RegMemInfo((long long)p, __FILE__, __LINE__);\
        }\
        else\
    {\
            std::cout<<"NEW failed"<<std::endl;\
    }\
 }\
 while(0) 

 // 定义宏 DEMODELETE,封装了内存释放的操作,在内存释放时,调用 UnRegMemInfo
 // 功能,将内存信息从 map 容器中删除
 #define DEMODELETE(p) \
 do\
 {\
        if (p)\
        {\
                UnRegMemInfo((long long)p);\
                delete p;\
                p = 0;\
        }\
 }while(0) 

 // 写信息流内容到文件
 void WriteString(std::string buf)
 {
        loginfo << buf <<std::endl;
 } 

 // 将整数转换为字符串
 std::string Int2Str(int value)
 {
        char buf[16] = {0};
        sprintf(buf, "%d", value);
        return buf;
 } 

 // 输出 map 容器中存储的内存没有释放的信息
 void Output()
 {
        loginfo.clear(); 

        if (memmap.empty())
        {
            WriteString("No Memory leak.");
            return;
        } 

        MemMap::iterator iter;
        WriteString("The Memory leak is below:");
        for (iter = memmap.begin(); iter != memmap.end(); ++iter)
        {
                std::string buf;
                std::string sAddr = Int2Str(iter->first);
                std::string sLine = Int2Str(iter->second.line);
                buf += "memory Address ";
                buf += sAddr;
                buf += ": FILE ";
                buf += iter->second.filename;
                buf += ", LINE ";
                buf += sLine;
                buf += " no freed";
                WriteString(buf);
        }
 } 

 // 测试程序主入口函数
 int main(int argc,  char* argv[])
 {
        char* pTemp1 = 0;
        DEMONEW(pTemp1, char);
        char* pTemp2 = 0;
        DEMONEW(pTemp2, char);
        char* pTemp3 = 0;
        DEMONEW(pTemp3, char);
        DEMODELETE(pTemp1); 

        Output();
        loginfo.close();
        return 0;
 }

上面测试程序的输出是:

[[email protected] ~]$ vi /tmp/memory.log 

 The Memory leak is below:
 memory Address 280929008: FILE test.cpp, LINE 109 no freed
 memory Address 280929152: FILE test.cpp, LINE 111 no freed

输出分析:

从输出结果我们可以发现,此测试程序在 test.cpp 文件的 109 和 111 行各有一处内存泄漏,查看源代码,它们分别是 pTemp1 和 pTemp2。

使用 Purify(适用所有 UNIX 平台)或者 valgrind(适用 Linux 平台)工具对内存泄漏进行分析

1. 使用 Purify 对内存泄漏进行分析

Purify 是 IBM Rational PurifyPlus 的工具之一, 是一个面向 VC、VB 或者 Java 开发的测试 Visual C/C++ 和 Java 代码中与内存有关的错误的工具,它确保整个应用程序的质量和可靠性。在查找典型的 C/C++ 程序中的传统内存访问错误, Rational Purify 可以大显身手。在 UNIX 系统中,使用 Purify 需要重新编译程序。通常的做法是修改 Makefile 中的编译器变量。

例如定义 CC 变量为 purify gcc

CC=purify gcc

首先运行 Purify 安装目录下的 purifyplus_setup.sh 来设置环境变量,然后运行 make 重新编译程序。需要指出的是,程序必须编译成调试版本。在编译器命令(例如 Solaris 的 CC 编译器,Linux 的 gcc 编译器等)后,也就是必须使用”-g”选项。在重新编译的程序运行结束后,Purify 会打印出一个分析报告。

测试程序(此代码使用 C++ 语言实现):

#include <stdlib.h>
 void func1()
 {
    //char* pBuf = new char;
 } 

 void func2()
 {
    char* pBuf = new char;
 } 

 void func3()
 {
    char* pBuf = new char;
 } 

 int main()
 {
    func1();
    func2();
    func3();
    return 0;
 }

编译程序:

[[email protected] purify]$ purify g++ -g tst.cpp -o tst1

Purify 输出:

[[email protected] purify]$ ./tst1
 16:50:59 (rational) OUT: "PurifyPlusUNIX" [email protected]
 ****  Purify instrumented ./tst1 (pid 530 at Fri Apr  6 16:50:59 2012)
  * Purify 7.0.0.0-014 090319 Linux (64-bit) (C) Copyright IBM Corporation. 1992,
  * 2009 All Rights Reserved.
  * For contact information type: "purify -help"
  * For Purify Viewer output, set the DISPLAY environment variable.
  * License successfully checked out.
  * Command-line: ./tst1
  * Options settings: -g++=yes -purify \
-purify-home=
/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\
purify.i386_linux2.7.0.0.0-014
-process-large-objects=yes -gcc3_path=/usr/bin/g++ \
-cache-dir=
/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\
purify.i386_linux2.7.0.0.0-014\
/cache

 ****  Purify instrumented ./tst1 (pid 530)  ****
 Current file descriptors in use: 5
 FIU: file descriptor 0: <stdin>
 FIU: file descriptor 1: <stdout>
 FIU: file descriptor 2: <stderr>
 FIU: file descriptor 26: <reserved for Purify internal use>
 FIU: file descriptor 27: <reserved for Purify internal use> 

 ****  Purify instrumented ./tst1 (pid 530)  ****
 Purify: Searching for all memory leaks... 

 Memory leaked: 2 bytes (100%); potentially leaked: 0 bytes (0%) 

 MLK: 1 byte leaked at 0xa457098
  * This memory was allocated from:
        malloc         [rtlib.o]
        operator new(unsigned long) [libstdc++.so.6]
        operator new(unsigned long) [rtlib.o]
        func2()        [tst.cpp:9]
        main           [tst.cpp:20]
        __libc_start_main [libc.so.6]
        _start         [crt1.o] 

 MLK: 1 byte leaked at 0xa457138
  * This memory was allocated from:
        malloc         [rtlib.o]
        operator new(unsigned long) [libstdc++.so.6]
        operator new(unsigned long) [rtlib.o]
        func3()        [tst.cpp:14]
        main           [tst.cpp:21]
        __libc_start_main [libc.so.6]
        _start         [crt1.o] 

 Purify Heap Analysis (combining suppressed and unsuppressed blocks)
                         Blocks        Bytes
              Leaked          2            2
  Potentially Leaked          0            0
              In-Use          0            0
  ----------------------------------------
     Total Allocated          2            2

Purify 图形输出:

[[email protected] purify]$ export DISPLAY=9.119.131.33:0

安装 Xmanager 等工具,设置 DISPLAY 为本机 IP,见下图

定位 UNIX 上常见问题的经验总结

输出分析:

从 purify 的输出可以看出,此测试程序存在两处内存泄漏,它分别是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行。

2. 使用 valgrind(现在仅仅支持 Linux 平台)对内存泄漏进行分析

Valgrind 是一套 Linux 下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind 由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架,它模拟了一个 CPU 环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind 在对程序进行侦测的时候,不需要对程序进行重新编译。

下面使用 valgrind 对一个简单的测试程序进行。

测试程序:

同 Purify 的测试程序相同。

编译程序:

[[email protected] purify]$ g++ -g tst.cpp -o tst

valgrind 输出:

[[email protected] purify]$ valgrind --leak-check=full ./tst
 ==25396== Memcheck, a memory error detector
 ==25396== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
 ==25396== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
 ==25396== Command: ./tst
 ==25396==
 ==25396==
 ==25396== HEAP SUMMARY:
 ==25396==     in use at exit: 2 bytes in 2 blocks
 ==25396==   total heap usage: 2 allocs, 0 frees, 2 bytes allocated
 ==25396==
 ==25396== 1 bytes in 1 blocks are definitely lost in loss record 1 of 2
 ==25396==    at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)
 ==25396==    by 0x4005C7: func2() (tst.cpp:9)
 ==25396==    by 0x4005DB: main (tst.cpp:20)
 ==25396==
 ==25396== 1 bytes in 1 blocks are definitely lost in loss record 2 of 2
 ==25396==    at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)
 ==25396==    by 0x4005AF: func3() (tst.cpp:14)
 ==25396==    by 0x4005E0: main (tst.cpp:21)
 ==25396==
 ==25396== LEAK SUMMARY:
 ==25396==    definitely lost: 2 bytes in 2 blocks
 ==25396==    indirectly lost: 0 bytes in 0 blocks
 ==25396==      possibly lost: 0 bytes in 0 blocks
 ==25396==    still reachable: 0 bytes in 0 blocks
 ==25396==         suppressed: 0 bytes in 0 blocks
 ==25396==
 ==25396== For counts of detected and suppressed errors, rerun with: -v
 ==25396== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
 [[email protected] purify]$

输出分析:

从 valgrind 的输出可以看出,此测试程序存在两处内存泄漏,它分别是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行,与 purify 的检测结果相同。

UNIX 下进程性能问题分析方法

● 检查 CPU 占用情况(包含单个线程的 CPU 使用情况)

下面分别对 Solaris 和 Linux 平台做了举例,其他平台的命令略有不同,请参考各自平台用户手册。

例如:在 Solaris 平台

使用 prtdiag 命令查看系统 CPU 信息:

 [/rnd/homes/builder1/purify >prtdiag

输出信息:

SystemConfiguration:SunMicrosystemssun4uSunFireV210
Systemclockfrequency:167MHZ
Memorysize:8GB
==================================== CPUs ====================================
               E$          CPU                    CPU     Temperature
CPU  Freq      Size        Implementation        Mask    Die   Amb.  Status  Location
---  --------  ----------  -------------------- -----   ----  ----  ------  --------
0    1002 MHz  1MB         SUNW,UltraSPARC-IIIi   2.3     -     -    online  MB/P0
1    1002 MHz  1MB         SUNW,UltraSPARC-IIIi   2.3     -     -    online  MB/P1

输出分析:

从上面的输出可以发现,此服务器有两个 CPU,以及各个 CPU 的信息。

使用 prstat 命令进程中每个线程的 CPU 使用情况:

[/rnd/homes/builder1/purify >>prstat -Lu root

输出信息:

   PID USERNAME  SIZE   RSS STATE  PRI NICE      TIME  CPU PROCESS/LWPID
   230 root     3896K 3072K sleep   59    0   0:04:37 0.0% nscd/18
 26229 root      201M  118M sleep   59    0   4:38:06 0.0% java/4

输出分析:

LWPID 虽然不是线程 ID,但是在 Solaris10 版本与线程 ID 是一一对应关系,所以可以通过 LWPID 进行分析。

例如:在 Linux 平台

查看 CPU 个数 ( 使用 top 命令,然后按 1 键可显示 CPU 的个数以及每个 CPU 的负载情况 ):

 [[email protected] purify]$top

输出信息:

Tasks: 205 total,   7 running, 196 sleeping,   1 stopped,   1 zombie
 Cpu0  : 92.7%us,  7.3%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
 Cpu1  : 94.2%us,  5.8%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
 Mem:   2033948k total,  1867908k used,   166040k free,    20088k buffers
 Swap:  4095992k total,   393420k used,  3702572k free,   389476k cached 

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 3088 root      19   0  136m  73m 6960 R 57.2  3.7   0:00.89 cc1plus
 3082 root      21   0 40580  33m 4732 R 52.7  1.7   0:00.75 cc1plus
 3068 root      25   0  232m 163m  10m R 45.2  8.3   0:03.69 cc1plus
 3085 root      19   0 98.8m  33m 5696 R 28.6  1.7   0:00.24 cc1plus
 2901 root      25   0 89732  83m 4508 R 15.1  4.2   0:09.40 cc1plus
 3069 dyu       15   0 10884 1120  768 R  1.5  0.1   0:00.04 top
    1 root      15   0 10372  380  348 S  0.0  0.0   0:03.19 init
    2 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 migration/0
    3 root      34  19     0    0    0 S  0.0  0.0   0:00.15 ksoftirqd/0
    4 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/0

输出分析:

从上面输出可以得到,此服务器有两个 CPU,均为满负荷工作,空闲的 CPU 使用均为 0%。

查看进程中每个线程的 CPU 使用情况:

[[email protected] purify]$ ps -e -o user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu

–sort=%cpu 命令输出要求按 CPU 的使用多少进行排序输出。

输出信息:

[[email protected] ~]$ ps -e -o user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu
 USER       PID  PPID   TID     TIME %CPU CMD
 root         2     1     2 00:00:00  0.0 [migration/0]
 root         3     1     3 00:00:00  0.0 [ksoftirqd/0]
 root         4     1     4 00:00:00  0.0 [watchdog/0]
 root         7     1     7 00:00:00  0.0 [watchdog/1]
 root        10     1    10 00:00:00  0.0 [khelper]
 root        27     1    27 00:00:00  0.0 [kthread]
 root        34    27    34 00:00:00  0.0 [kacpid]

输出分析:

从上面输出可以根据每个线程的 CPU 使用情况分析,性能瓶颈在哪个线程。

检查 IO

使用 iostat 命令可以查看系统 IO 状态等信息。

例如:Solaris 平台 iostat 输出:

/rnd/homes/builder1/purify >>iostat 1
   tty        sd0           sd1           nfs1          nfs2           cpu
 tin tout kps tps serv  kps tps serv  kps tps serv  kps tps serv   us sy wt id
   0    6  12   2    9    0   0   27    0   0    0    4   0    8    2  1  0 97
   0  234   0   0    0    0   0    0    0   0    0    0   0    0    0  1  0 99
   0   80   1   1   11    0   0    0    0   0    0    0   0    0    0  5  0 95
   0   80   0   0    0    0   0    0    0   0    0    0   0    0    0  1  0 99

上面是 iostat 的一个简单输出,可以查看 iostat 的帮助(man iostat)了解更多信息。

使用 Quantify 对程序性能进行分析

Rational Quantify 是 IBM Rational PurifyPlus 的工具之一,可以按多种级别(包括代码行级和函数级)测定性能,并提供分析性能改进所需的准确和详细的信息,使您可以核实代码性能确实有所提高。使用 Rational Quantify,您可以更好地控制数据记录的速度和准确性。您可以按模块调整工具收集信息的级别: 对于应用程序中感兴趣的那部分,可以收集详细信息;而对于不太重要的模块,您可以加快数据记录的速度并简化数据收集。使用“运行控制工具栏”,可以直接、实时地控制性能数据的记录。既可以收集应用程序在整个运行过程中的性能数据,也可以只收集程序执行过程中您最感兴趣的某些阶段的性能数据。

下面是一个使用 Quantify 对程序性能进行分析的例子

测试程序( 此代码使用 C++ 语言实现

#include <stdlib.h>
 #include <iostream>
 void func1()
 {
    for (int i = 0; i < 10000; ++i)
        {
        char* pBuf = new char[1024];
              delete []pBuf;
    }
 } 

 void func2()
 {
    for (int i = 0; i < 10000; ++i)
        {
        char* pBuf = new char[1024];
              delete []pBuf;
    }
 } 

 void func3()
 {
    for (int i = 0; i < 100; ++i)
        {
            std::cout << "Hello World" << std::endl;
        }
 } 

 int main()
 {
    func1();
    func2();
    func3();
    return 0;
 }

编译程序:

[[email protected] purify]$ quantify g++ -g performance.c -o performancetst

Quantify 输出:

****  Quantify instrumented ./performancetst (pid 18503 at 20:12:14)
  Quantify 7.0.0.0-014 090319 Linux (64-bit) (C) Copyright IBM Corporation. 1992,
 2009 All Rights Reserved.
  * For contact information type: "quantify -help"
  * License successfully checked out.
 Quantify: Sending data for 178 of 5713 functions
 from ./performancetst (pid 18772).........done. 

 Quantify: Resource Statistics for ./performancetst (pid 18772)
 *                                            cycles       secs
 * Total counted time:                      56984465      0.031 (100.0%)
 *     Time in your code:                    5599024      0.003 (  9.8%)
 *     Time in system calls:                51252884      0.027 ( 89.9%)
 *     Dynamic library loading:               132557      0.000 (  0.2%)
 *
 * Note: Data collected assuming a machine type of Intel-Core with
 * clock rate of 1.867 GHz.
 * Note: These times exclude Quantify overhead and possible memory effects.
 *
 * Elapsed data collection time:       0.383 secs
 *
 * Note: This measurement includes Quantify overhead.
 *
 To view your saved Quantify data, type:
 quantify -view /home/dyu/purify/performancetst.18772.0.qv

Quantify 的图形输出:

安装 Xmanager 等工具,设置 DISPLAY 为本机 IP,见下图:

[[email protected] purify]$ export DISPLAY=9.119.131.33:0

定位 UNIX 上常见问题的经验总结

定位 UNIX 上常见问题的经验总结

输出分析:

从 Quantify 的输出可以对程序的性能进行初步分析,func1 时间花费为 43.14%,func2 为 42.59%,func3 为 14.27%。

示例演示

通过两个实例去演示多线程性能问题和产品不兼容导致 crash 的问题。

一个多线程互斥导致性能瓶颈问题

1.问题描述:

对某个多线程程序,当单线程的情况下,执行任务 1 花费 70s,但是当配置为 16 个线程时,执行任务 1 仍然花费时间大约 70s。

2.问题分析:

首先查看单个线程或多个线程的 CPU 使用情况

PID USERNAME THR PRI NICE  SIZE   RES STATE    TIME    CPU COMMAND
 11248 czhou      7   0    0  556M  555M cpu/1    0:17 6.25% CFTestApp

 

当多线程情况下,查看每个线程的 CPU 占用情况:
 PID USERNAME  SIZE   RSS STATE  PRI NICE      TIME  CPU PROCESS/LWPID
 11248 czhou    3606M 3605M cpu1     0    0   0:07:11  6.25% CFTestApp/12
 11248 czhou    3606M 3605M cpu1     0    0   0:07:11  0% CFTestApp/1
 11248 czhou    3606M 3605M cpu1     0    0   0:07:11  0% CFTestApp/5
 11248 czhou    3606M 3605M cpu1     0    0   0:07:11  0% CFTestApp/7

从上面可以发现。当多线程的情况下,在 16 个线程中仅仅一个线程的 CPU 占用 6.25%,其他线程占用均为 0%。可以断定大多数的线程被 block 住。然后需要查看这个进程的堆栈信息,得到每个线程的函数调用关系。

Pstack 11248
 -----------------  lwp# 1 / thread# 1  --------------------
 ff11af60 ???(ffbff8e8, ffbff8e0)
 ff15e328 dynamic_cast(258, 7, 10f88, 0, 139c0, 21508) + 58
 0001110c main     (1, ffbff9d4, ffbff9dc, 21400, 0, 0) + fc
 00010b58 _start   (0, 0, 0, 0, 0, 0) + 108
 -----------------  lwp# 7  --------------------------------
 ff11af60 ???(fef7ff30, fef7ff28)
 ff15e328 dynamic_cast(1, ff010224, 0, 0, 0, 0) + 58
 00010fd8 __1cLwork_thread6Fpv_0_ (0, 0, 0, 0, 0, 0) + 8
 ff165e48 _lwp_start (0, 0, 0, 0, 0, 0)
 Then I remove the dynamic_cast, the problem was resolved.

从上面的线程堆栈信息,我们可以看到,大部分的线程几乎都 block 在 dynamic_cast 函数。

3.(3)问题解决:

针对上面的分析对这个性能瓶颈代码进行修正。

一个由于产品不兼容导致 crash 的问题

1. 问题描述:

在 Linux 平台,产品 A 使用编译器 icpc 编译,产品 B 使用编译器 g++ 编译。进程 C 会同时加载产品 A 与产品 B,当进程 C 运行时调用产品 A 中的函数 funcA 时 crash 发生。

2.问题分析:

从 core 文件,我们可以得到下面的信息:

(gdb) where
 #0  std::_Rb_tree<int, std::pair<int const, int>,
 std::_Select1st<std::pair<int const,
 int> >, std::less<int>, std::allocator<std::pair<int const, int> > >
 ::_M_end (this=0x602a20)
at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../include/c++/4.1.2/bits/stl_tree.h:477
#1  0x0000000000400d3c in std::_Rb_tree<int, std::pair<int const, int>,
 std::_Select1st<std::pair<int const, int> >, std::less<int>,
 std::allocator<std::pair<int const, int> > >
 ::lower_bound (this=0x602a20, [email protected]fffe76c)
at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/include/c++/4.1.2/bits/stl_tree.h:1368
#2  0x0000000000400dbd in std::map<int, int, std::less<int>, std::allocator<
 std::pair<int const, int> > >::lower_bound (
    this=0x602a20, [email protected])
    at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/include/c++/4.1.2/bits/stl_map.h:576
 #3  0x0000000000401a0c in std::map<int, int, std::less<int>,
 std::allocator<std::pair<int const, int> > >::operator[] (
    this=0x602a20, [email protected])
    at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/include/c++/4.1.2/bits/stl_map.h:345
 #4  0x0000000000400a1b in funcA () at map.cpp:7

查找产品 A 的依赖库,我们可以得到下面的信息

[email protected] xilinuxbldsrv> ldd libA.so
        linux-vdso.so.1 =>  (0x00007fffa2eb9000)
        libm.so.6 => /lib64/libm.so.6 (0x00002b6783a80000)
        libstdc++.so.5 => /usr/lib64/libstdc++.so.5 (0x00002b6783cd6000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002b6783fb9000)
        libc.so.6 => /lib64/libc.so.6 (0x00002b67841d1000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00002b678452f000)
        /lib64/ld-linux-x86-64.so.2 (0x00002b6783626000)

查找产品 B 的依赖库,我们可以得到下面的信息

[[email protected]]$ ldd libB.so
        linux-vdso.so.1 =>  (0x00007fff02dfc000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003d2c600000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003d1a400000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003d27e00000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003d19c00000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003d19800000)

这个 crash 的位置是在 stl 的 map 数据结构中,从上面的线程栈调用可以发现,4.1.2 为 libstdc++.so.6,但是从 A 的依赖库看,产品 A 依赖 libstdc++.so.5 而不是 libstdc++.so.6,所以 funcA 应该调用 libstdc++.so.5。

从上面我们可以发现 crash 的原因是由于 libstdc++.so 的调用错误导致的。

3.问题解决:

在编译选项中增加 -fvisibility=hidden ,在 API 声明中增加

__attribute__ ((visibility (“default”)))。

UNIX 程序问题分析常用命令

1. ulimit — 设置和查看用户的使用的资源限制情况

2. nm — 显示目标文件的符号表信息

3. ldd –显示动态库的依赖信息

4. pstack(Solaris, Linux), procstack(AIX)– 打印十六进制地址和符号名称

5. pmap(Solaris, Linux), procmap(AIX) –打印地址空间映射

6. pldd(Solaris), procldd(AIX) —列出进程加载的库

7. pfiles(Solaris), procfiles(AIX)– 报告有关的所有文件描述符

8. prstat(Solaris), ps -e -o user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu(Linux)– 检查每个线程的处理器

9. ps –报告进程的状态

10. iostat –报告中央处理单元(中央处理器)统计和输入 / 输出设备和分区统计

11. pwdx(Linux,Solaris)  pid 显示当前工作目录

12. top(Linux,Solaris,HP),topas(AIX)

总结

本文简单介绍了作者在 UNIX 平台的一些经常遇到的问题以及一些基本命令的使用,希望对读者能有帮助。良好的基础知识(C/C++ 语言,操作系统,内核结构等)是分析问题解决问题的基础,同时一些 debug 工具以及一些第三方工具的熟练使用对问题分析也能很好的帮助作用。另外良好的编程习惯(例如申请的变量要初始化等)是避免问题产生的有效手段,在软件开发前期的缺陷控制应该是我们的目标。

相关文章

相关 [unix 常见问题 经验] 推荐:

定位 UNIX 上常见问题的经验总结

- - 博客 - 伯乐在线
简介: 本文主要对 UNIX 平台常见的问题进行了分类,介绍一些常见问题分析时使用的方法和命令,对以下三种常见问题的分析方法做了简单介绍:UNIX 下 Crash 问题的分析方法、UNIX 下内存泄露问题的分析方法和 UNIX 下 performance 问题的分析方法. 同时通过对下面两个例子的介绍,巩固了上面问题分析的介绍:.

HBase工程师线上工作经验总结----HBase常见问题及分析

- - 互联网 - ITeye博客
阅读本文可以带着下面问题:. 1.HBase遇到问题,可以从几方面解决问题. 2.HBase个别请求为什么很慢. 3.客户端读写请求为什么大量出错. 4.大量服务端exception,一般原因是什么. 6.Hbase数据写进去,为什么会没有了,可能的原因是什么. regionserver发生abort,遇到最多是什么情况.

UNIX传奇

- dayu - 桃源
了解过去,我们才能知其然,更知所以然. 总结过去,我们才会知道我们明天该如何去规划,该如何去走. 在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今. 要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史.

linux xampp常见问题

- We_Get - 博客园-首页原创精华区
1.安装xampp4linux后,只能本机(http://localhost)访问,局域网内其他机器无法访问. 解答:在/opt/lampp/etc中修改httpd.conf,将Listen 80修改为Listen 本机ip地址:80 本机ip地址使用ifconfig 查看. 2.我按照1修改之后,局域网内的机器还是无法访问.

storm常见问题解答

- - BlogJava-庄周梦蝶
    最近有朋友给我邮件问一些storm的问题,集中解答在这里. 一、我有一个数据文件,或者我有一个系统里面有数据,怎么导入storm做计算. 你需要实现一个Spout,Spout负责将数据emit到storm系统里,交给bolts计算. 怎么实现spout可以参考官方的kestrel spout实现:.

MariaDB常见问题FAQ

- - OurMySQL
MariaDB常见问题,同样适用于MySQL. 老版本MariaDB服务的相关旧信息. via似乎是个关键字,但是至少在MySQL5.1文档中找不到. 在MySQL5.1中执行成功,但是会出现1064错误 (毫无疑问,用avia替代via就可以). 答           elenst. 这个bug(https://bugs.launchpad.net/maria/+bug/1010351)被修复.

hadoop配置常见问题

- - 企业架构 - ITeye博客
收集记录一些Hadoop配置部署过程中遇到的问题. 这种方法解决了运行中的hadoop的safe mode问题,但是下次重启hadoop,还会出现这个问题. 其实这个问题,我猜测可能是由于目录/app/hadoop/tmp/mapred/system被破坏造成. 永久解决,可以删除掉/app/hadoop/tmp/,重新创建,重新format,重启hadoop——如果条件允许的话.

Zookeeper常见问题整理

- - CSDN博客推荐文章
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态. Zk的选举算法使用ZAB协议:. 选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;.

UNIX系统编程(2)

- - CSDN博客架构设计推荐文章
这回我们来说一下UNIX的文件系统. 由于一般情况下UNIX机的硬盘会很大,所以一般你可以给它分成几个区,而每个分区又都可以有独立的文件系统. 如果你是UNIX系统,你有可能看见 . 如果你是linux你有可能看见 . 这些文件,每一个就可以看成是一个分区. 当计算机启动时,系统先找到/(根目录),然后系统就像挖土豆子一样找到一个带出来一串似的,顺着根目录一带就带出来挂载在根目录下的所有目录(详细的请查一下/etc/fstab这个文件).

UNIX系统编程(1)

- - CSDN博客架构设计推荐文章
注:本文来自“网易”博主,仅阅读,学习. 第一章:什么是系统编程 . UNIX系统编程,简单的说就是“C语言+系统调用(system call)”,学会了C语言再知道一些系统调用的方法,其实就可以进行UNIX系统编程了. 那什么又是系统调用呢,其实初学者就把它看当成是函数用就可以了. 这些“函数”是干什么用的呢,大家知道操作系统内核管理着我们的计算机资源,比如CPU,内存,硬盘等等.