C++ 后台程序實時性能監控


面對的問題:

做后台程序經常會被問一句話,你的程序能撐多少人。一般官方一點的回答是這個得根據實際情況而定。實際上后台程序的性能是可以被量化的。我們開發的每一個服務器程序,對性能都非常有底,以為我們有數據。So,能撐多少人不少隨便猜的,讓數據報表來說話。

另外一種情況經常發生在開發人員之中,甲乙丙一起討論接口實現,經常會說這么實現效率太低,那么實現效率才高等。實際上,效率高低都是相對而言的。一個函數1ms執行完畢夠快嗎?看起來挺快,若某接口需要此函數100次循環,那么情況就不是很樂觀了。但是若此接口又是十天半個月才會被觸發一次,似乎事情又變的不是很嚴重。說到這里想起《unix編程藝術》上關於性能優化的總結:

  • 最有效的優化往往是優化之外的,如清晰干凈的設計
  • 最有效的優化就是不優化,摩爾定律會為你優化
  • 如果確定要優化,必須找到真正的瓶頸

還有一種跟性能有關的情況是,后台程序經常有很多組件組成。比如在運行期發生接口調用性能下降的情況,必須知道是那些組件性能下降引起的。如果可以實時的知道所有接口的性能數據,以上的問題都可迎刃而解。

總結如下原因,必須開啟實時性能監控:

  • 我們需要知道系統的吞吐量,以此參數做部署等。
  • 實時了解各個系統組件的性能,某組件發生故障,可以及時發現
  • 獲得程序接口調用熱點,調用多且慢的接口才需要優化

解決方案:

后台程序開發一個專門統計性能的組件,其需要有如下功能:

  • 可以匯總性能數據,如定時將1小時內說有接口調用開銷、次數等數據匯總到文件
  • 可以非常方便的與邏輯層接口集成,比如在現有接口增加一行代碼即可
  • 直觀的報表,性能數據寫入文件必須按照通用的格式,方便工具分析數據,生成報表

性能監控組件

我實現了一個性能組件performance_daemon_t。接口如下:

//! 性能監控
class performance_daemon_t
{
public:
    struct perf_tool_t
    {
        perf_tool_t(const char* mod_):
            mod(mod_)
        {
            gettimeofday(&tm, NULL);
        }
        ~perf_tool_t()
        {
            struct timeval now;
            gettimeofday(&now, NULL);
            long cost = (now.tv_sec - tm.tv_sec)*1000000 + (now.tv_usec - tm.tv_usec);
            singleton_t<performance_daemon_t>::instance().post(mod, cost);
        }
        const char*    mod;
        struct timeval tm;
    };
public:
    performance_daemon_t();
    ~performance_daemon_t();

    //! 啟動線程,創建文件
    int start(const string& path_, int seconds_);
    //! 關閉線程
    int stop();

    //! 增加性能監控數據
    void post(const string& mod_, long us);

perf_tool_t 是工具類,構造和析構自動調用兩次gettimeofday獲取函數調用開銷,例外有輔助宏定義如下:

#define AUTO_PERF() performance_daemon_t::perf_tool_t __tmp__(__FUNCTION__)

#define PERF(m)     performance_daemon_t::perf_tool_t __tmp__(m)

使用示例:

void foo()
{
    AUTO_PERF();
    //! TODO -----
}

int main(int argc, char* argv[])
{
    
    singleton_t<performance_daemon_t>::instance().start("perf.txt", 5); 
    foo();
}

performance_daemon_t 每隔5秒將性能統計數據輸出到perf.txt, perf.txt的內容是CVS文件格式。

報表工具:

perf.txt 文件內容還不夠直觀,示例內容如下:

time,mod,max_cost[us],min_cost[us],per_cost[us],request_per_second,exe_times
20120606-17:01:41,dumy,515,174,254,3937,390
20120606-17:01:41,foo,5924,4,506,1976,1030
20120606-17:01:41,test,304,8,243,4115,185
time,mod,max_cost[us],min_cost[us],per_cost[us],request_per_second,exe_times
20120606-17:11:41,dumy,1086,222,280,5571,312
20120606-17:11:41,foo,5707,194,503,1988,770
20120606-17:11:41,test,807,8,265,3773,142
time,mod,max_cost[us],min_cost[us],per_cost[us],request_per_second,exe_times
20120606-17:21:41,dumy,1086,222,680,2571,512
20120606-17:21:41,foo,5707,194,403,1388,470
20120606-17:21:41,test,807,8,265,4773,442

為生成足夠友好、直觀的報表,我實現了一個WEB報表頁面,http://ffown.sinaapp.com/perf/, 將perf.txt 內容直接粘貼到web 頁面,點擊轉換輸出如下報表:

各個接口性能監控-折線圖:

此圖顯示了三個接口隨時間順序的走勢,可以非常清楚foo、test、dumy三個接口那個時間性能高,哪個時間性能低,一目了然。

接口熱點分布圖:

顯示三個接口隨時間調用次數走勢,可以很清楚顯示哪個時間段是高峰期。大餅圖顯示了哪個接口是熱點接口,很明顯,foo 接口調用次數最多,優化當優先優化foo。

組件實現淺析:

post 接口:

程序把接口調用開銷投遞到性能組件任務隊列中,保證了對接口性能影響最小。

timer定時回調:

timer_service_t 是我用epoll 實現的定時器,主要實現如下:

void timer_service_t::run()
{
    struct epoll_event ev_set[64];
    //! interupt();

    struct timeval tv;

    do
    {
        ::epoll_wait(m_efd, ev_set, 64, m_min_timeout);

        if (false == m_runing)//! cancel
        {
            break;
        }

        gettimeofday(&tv, NULL);
        long cur_ms = tv.tv_sec*1000 + tv.tv_usec / 1000;

        process_timer_callback(cur_ms);
        
    }while (true) ;
}

process_timer_callback 中檢測鏈表內所有的定時任務,若超時,觸發回調函數。

備注:

有人可能當心AUTO_PERF(); 會影響接口性能,其實其平均開銷大約為1us 

代碼實現:

https://ffown.googlecode.com/svn/trunk/example/ff_performance

WEB 報表生成工具:

http://ffown.sinaapp.com/perf/

 文檔:

http://ffown.sinaapp.com/perf/perf.pdf

更多精彩文章 http://h2cloud.org

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM