【轉載】定位 UNIX 上常見問題的經驗總結

原文地址: https://www.ibm.com/developerworks/cn/aix/library/1206_yudh_unixproblemsolve/index.html


同時通過對下面兩個例子的介紹,鞏固了上面問題分析的介紹:

  • 一個多線程應用的性能問題的分析
  • 一個 crash 問題的分析

UNIX 程序常見問題分類

UNIX 下運行程序,經常會遇到以下幾類問題 :

  1. Crash
  2. 內存泄露
  3. 句柄泄露
  4. 進程不響應
  5. 性能不滿足預期
  6. 邏輯錯誤

UNIX 程序常見問題的分析方法

UNIX 下 Crash 問題的分析方法

crash 原理和 core 文件生成原因 ( 信號的介紹 )

Crash 是進程崩潰,是由於應用進程做了錯誤的操作 ( 例如,數組拷貝越界導致對系統內存進行了寫操作,使用了錯誤的指針地址 ), 操作系統嚮應用進程發送了信號,如果應用進程沒有做特殊處理,應用進程將 core dump 在進程當前的工作目錄下生成一個 core 文件,core 文件複製了該進程的存儲圖像,是一個內存映像。

不是所有的信號默認行爲都是 crash, 常見默認 crash 信號主要有:

1
2
3
4
5
SIGABRT
SIGBUS
SIGSEGV
SIGILL
SIGPIPE

可以通過 kill –l (適用所有 UNIX 平臺)查看信號的信息。

查看針對某個進程的所有信號的默認行爲(例如:在 Solaris 平臺使用 psig pid 命令查看,其他平臺的命令略有不同,請參考各自平臺用戶手冊).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[[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 文件生成的源程序

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

從以上結果可以看出,該 core 文件是由 64 位程序 qatest 生成的。

然後使用 gdb( 或者 dbx) 對 core 文件進行分析:

1
bash-2.05$ dbx ./qatest ./core

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

1
2
3
4
[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 平臺適用)的命令進行查看:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    1
    bash-3.00$ ulimit –c unlimited
  2. 磁盤空間是否充足,通過 df 命令(所有 UNIX 平臺適用)查看 Available 的空間是否充足。
    1
    2
    3
    4
    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. 內存申請釋放流程:
圖 1. 內存申請釋放流程:
圖 2.DEMONEW 實現流程:
圖 2.DEMONEW 實現流程:
圖 3.DEMODELETE 實現流程:
圖 3.DEMODELETE 實現流程:

測試程序代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#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;
}

上面測試程序的輸出是:

1
2
3
4
5
[[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

    1
    CC=purify gcc

    首先運行 Purify 安裝目錄下的 purifyplus_setup.sh 來設置環境變量,然後運行 make 重新編譯程序。需要指出的是,程序必須編譯成調試版本。在編譯器命令(例如 Solaris 的 CC 編譯器,Linux 的 gcc 編譯器等)後,也就是必須使用"-g"選項。在重新編譯的程序運行結束後,Purify 會打印出一個分析報告。

    測試程序(此代碼使用 C++ 語言實現):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #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;
    }

    編譯程序:

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

    Purify 輸出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    [[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 圖形輸出:

    安裝 Xmanager 等工具,設置 DISPLAY 爲本機 IP,見下圖:

    1
    [[email protected] purify]$ export DISPLAY=9.119.131.33:0
    Figure xxx. Requires a heading

    輸出分析:

    從 purify 的輸出可以看出,此測試程序存在兩處內存泄漏,它分別是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行。

  2. 使用 valgrind(現在僅僅支持 Linux 平臺)對內存泄漏進行分析

    Valgrind 是一套 Linux 下,開放源代碼(GPL V2)的仿真調試工具的集合。Valgrind 由內核(core)以及基於內核的其他調試工具組成。內核類似於一個框架,它模擬了一個 CPU 環境,並提供服務給其他工具;而其他工具則類似於插件 (plug-in),利用內核提供的服務完成各種特定的內存調試任務。Valgrind 在對程序進行偵測的時候,不需要對程序進行重新編譯。

    下面使用 valgrind 對一個簡單的測試程序進行。

    測試程序:

    同 Purify 的測試程序相同。

    編譯程序:

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

    valgrind 輸出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [[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 信息:

    1
    [/rnd/homes/builder1/purify >prtdiag

    輸出信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 使用情況:

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

    輸出信息:

    1
    2
    3
       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 的負載情況 ):

    1
    [[email protected] purify]$top

    輸出信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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 使用情況:

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

    --sort=%cpu 命令輸出要求按 CPU 的使用多少進行排序輸出。

    輸出信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [[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 輸出:

    1
    2
    3
    4
    5
    6
    7
    /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++ 語言實現):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #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;
    }

    編譯程序:

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

    Quantify 輸出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    ****  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,見下圖:

    1
    [[email protected] purify]$ export DISPLAY=9.119.131.33:0
    Figure xxx. Requires a heading
    Figure xxx. Requires a heading

    輸出分析:

    從 Quantify 的輸出可以對程序的性能進行初步分析,func1 時間花費爲 43.14%,func2 爲 42.59%,func3 爲 14.27%。

示例演示

通過兩個實例去演示多線程性能問題和產品不兼容導致 crash 的問題。

一個多線程互斥導致性能瓶頸問題

  1. 問題描述:

    對某個多線程程序,當單線程的情況下,執行任務 1 花費 70s,但是當配置爲 16 個線程時,執行任務 1 仍然花費時間大約 70s。

  2. 問題分析:

    首先查看單個線程或多個線程的 CPU 使用情況:

    1
    2
    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
    1
    2
    3
    4
    5
    6
    當多線程情況下,查看每個線程的 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 住。然後需要查看這個進程的堆棧信息,得到每個線程的函數調用關係。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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 文件,我們可以得到下面的信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    (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
相關文章
相關標籤/搜索