1. 程式人生 > 其它 >easylogging++的那些事(四)原始碼分析(七)效能跟蹤

easylogging++的那些事(四)原始碼分析(七)效能跟蹤

目錄

在上一篇我們介紹了

VERBOSE 日誌資訊管理,今天我們開始介紹效能跟蹤的實現。
easylogging++的效能跟蹤可以分為兩種: 程式碼塊整體的跟蹤 以及基於程式碼塊整體的跟蹤基礎上的 程式碼塊內的某個標記點的跟蹤

在 easylogging++的 功能介紹 中我們介紹過效能跟蹤巨集的使用:
1、程式碼塊整體的跟蹤對應的巨集: TIMED_SCOPE_IF 巨集、TIMED_SCOPE 巨集、TIMED_FUNC_IF 巨集、TIMED_FUNC 巨集、TIMED_BLOCK 巨集。
2、程式碼塊內的某個標記點的跟蹤對應的巨集:PERFORMANCE_CHECKPOINT 巨集、PERFORMANCE_CHECKPOINT_WITH_ID

巨集。

我們就從效能跟蹤巨集開始今天的分析。

巨集展開

TIMED_SCOPE_IF 巨集

    TIMED_SCOPE_IF 巨集定義如下:

#define TIMED_SCOPE_IF(obj, blockname, condition) el::base::type::PerformanceTrackerPtr obj( condition ? \
    new el::base::PerformanceTracker(blockname, ELPP_MIN_UNIT) : nullptr )

    用個具體的例子就一目瞭然了:

TIMED_SCOPE_IF(timerObj, "TIMED_SCOPE_IF", true);

    展開後為:

el::base::type::PerformanceTrackerPtr timerObj( true ? new el::base::PerformanceTracker("TIMED_SCOPE_IF", ELPP_MIN_UNIT) : nullptr );

    PerformanceTrackerPtr 定義如下:

typedef std::unique_ptr<el::base::PerformanceTracker> PerformanceTrackerPtr;

    最終展開後的結果就相當於:建立了一個 el::base::PerformanceTracker 型別的獨佔型智慧指標(std::unique_ptr<el::base::PerformanceTracker>)物件。當這個智慧指標物件離開其作用域後,自身就會被銷燬,進而自動刪除其管理的物件,最終呼叫 el::base::PerformanceTracker 類的解構函式,而這個解構函式執行了真正的效能跟蹤統計
    el::base::PerformanceTracker 類後面原始碼分析的時候回詳細說明。

TIMED_SCOPE 巨集

    TIMED_SCOPE 巨集定義如下:

#define TIMED_SCOPE(obj, blockname) TIMED_SCOPE_IF(obj, blockname, true)

    TIMED_SCOPE_IF 巨集前面已經介紹了,這裡就不多說了。

TIMED_FUNC_IF 巨集

    TIMED_FUNC_IF 巨集定義如下:

#define TIMED_FUNC(obj) TIMED_SCOPE(obj, ELPP_FUNC)

    TIMED_SCOPE 巨集前面已經介紹了,這裡就不多說了。

TIMED_FUNC 巨集

    TIMED_FUNC 巨集定義如下:

#define TIMED_FUNC(obj) TIMED_SCOPE(obj, ELPP_FUNC)

    TIMED_SCOPE 巨集前面已經介紹了,這裡就不多說了。

TIMED_BLOCK 巨集

    TIMED_BLOCK 巨集定義如下:

#define TIMED_BLOCK(obj, blockName) for (struct { int i; el::base::type::PerformanceTrackerPtr timer; } obj = { 0, \
    el::base::type::PerformanceTrackerPtr(new el::base::PerformanceTracker(blockName, ELPP_MIN_UNIT)) }; obj.i < 1; ++obj.i)

    從 TIMED_BLOCK 巨集定義上來看,這個巨集只是定義了 for 迴圈語句的開頭部分,迴圈體空著,真正使用時需要加上迴圈體才能正常使用。
    用個具體的例子就一目瞭然了:

TIMED_BLOCK(timerBlkObj, "TIMED_BLOCK")
{
    //實際要效能測試的程式碼
}

    展開後為:

for (struct { int i; el::base::type::PerformanceTrackerPtr timer; } timerBlkObj = {0, ::base::type::PerformanceTrackerPtr(new el::base::PerformanceTracker("TIMED_BLOCK", ELPP_MIN_UNIT))};
     timerBlkObj.i < 1; ++timerBlkObj.i)
{
}

    最終展開後的結果就相當於建立了僅僅執行一次的 for 迴圈語句,而這個迴圈語句的迴圈體內部,建立了一個 el::base::PerformanceTracker 型別的獨佔型智慧指標物件。和 TIMED_SCOPE_IF 巨集一樣,這裡就不多說了。

PERFORMANCE_CHECKPOINT 巨集

    PERFORMANCE_CHECKPOINT 巨集定義如下:

#define PERFORMANCE_CHECKPOINT(obj) obj->checkpoint(std::string(), __FILE__, __LINE__, ELPP_FUNC)

    用個具體的例子就一目瞭然了:

PERFORMANCE_CHECKPOINT(timerObj);

    展開後為:

timerObj->checkpoint(std::string(), __FILE__, __LINE__, ELPP_FUNC);

    最後展開後的結果相當於呼叫了某個物件的 checkpoint 介面。

PERFORMANCE_CHECKPOINT_WITH_ID 巨集

    PERFORMANCE_CHECKPOINT_WITH_ID 巨集定義如下:

#define PERFORMANCE_CHECKPOINT_WITH_ID(obj, id) obj->checkpoint(id, __FILE__, __LINE__, ELPP_FUNC)

    用個具體的例子就一目瞭然了:

PERFORMANCE_CHECKPOINT_WITH_ID(timerObj, "PERFORMANCE_CHECKPOINT");

    展開後為:

timerObj->checkpoint("PERFORMANCE_CHECKPOINT", __FILE__, __LINE__, ELPP_FUNC);

    與 PERFORMANCE_CHECKPOINT 巨集一樣,最後展開後的結果相當於呼叫了某個物件的 checkpoint 介面。
    而這個物件的型別實際上就是 el::base::PerformanceTracker 類。後面原始碼分析時我們會清楚的看到這一點。

原始碼剖析

    從上面效能跟蹤巨集的最終展開結果可以看出來,最終都與 el::base::PerformanceTracker 這個類有關。下面我們就來分析下這個類的實現。

成員變數

std::string m_blockName;             // 效能跟蹤的程式碼塊的自定義名稱
base::TimestampUnit m_timestampUnit; // 效能跟蹤的時間精度
std::string m_loggerId;              // 效能跟蹤對應的日誌記錄器的ID
bool m_scopedLog;                    // 開啟效能跟蹤巨集後,預設進行程式碼塊的效能跟蹤
Level m_level;                       // 日誌級別
bool m_hasChecked;                   // 預設為false,進行一次跟蹤點統計後置為true
std::string m_lastCheckpointId;      // 最近完成效能跟蹤點對應的ID
bool m_enabled;                      // 對應日誌記錄器的指定日誌級別是否啟用效能跟蹤
struct timeval m_startTime;          // 效能跟蹤的開始時間
struct timeval m_endTime;            // 效能跟蹤的結束時間
struct timeval m_lastCheckpointTime; // 最近完成效能跟蹤點的時間

成員函式

建構函式

PerformanceTracker::PerformanceTracker(const std::string &blockName, base::TimestampUnit timestampUnit, const std::string &loggerId, bool scopedLog, Level level)
    : m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog), m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false)
{
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
    // We store it locally so that if user happen to change configuration by the end of scope
    // or before calling checkpoint, we still depend on state of configuration at time of construction
    // 獲取日誌記錄器物件
    el::Logger *loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
    // 對應日誌記錄器的指定日誌級別是否啟用效能跟蹤
    m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
    if (m_enabled)
    {
        // 啟用效能跟蹤則記錄當前時間為效能跟蹤的開始時間
        base::utils::DateTime::gettimeofday(&m_startTime);
    }
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}

解構函式

    解構函式用於執行真正的效能跟蹤統計

PerformanceTracker::~PerformanceTracker(void)
{
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
    if (m_enabled)
    {
        base::threading::ScopedLock scopedLock(lock());
        // PerformanceTracker僅僅提供了一個建構函式的實現,其中提供了預設值true,效能跟蹤巨集使用的是預設值
        // 開啟效能跟蹤巨集後,預設進行程式碼塊的效能跟蹤
        if (m_scopedLog)
        {
            // 記錄當前時間為效能跟蹤的結束時間
            base::utils::DateTime::gettimeofday(&m_endTime);
            // 將時間根據需要調整為合適的格式後,以字串形式返回
            base::type::string_t formattedTime = getFormattedTimeTaken();
            // 效能跟蹤回撥資料物件的初始化,交給效能跟蹤回撥處理(比如預設DefaultPerformanceTrackingCallback進行效能跟蹤的時長統計)
            // PerformanceTrackingData::DataType::Complete表示當前效能跟蹤完成
            PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
            data.init(this);
            data.m_formattedTimeTaken = formattedTime;
            PerformanceTrackingCallback *callback = nullptr;
            // 遍歷註冊的所有效能跟蹤回撥
            for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr> &h : ELPP->m_performanceTrackingCallbacks)
            {
                callback = h.second.get();
                if (callback != nullptr && callback->enabled())
                {
                    callback->handle(&data);
                }
            }
        }
    }
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}

    在 easylogging++的 主流程 中我們介紹過,日誌庫初始化的時候,預設會註冊 DefaultPerformanceTrackingCallback。其主要職責就是執行 easylogging++預設的效能跟蹤統計。DefaultPerformanceTrackingCallback 的實現稍後會詳細分析。

checkpoint 介面

    checkpoint 介面主要用於跟蹤點的效能統計

void PerformanceTracker::checkpoint(const std::string &id, const char *file, base::type::LineNumber line, const char *func)
{
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
    // 是否啟用效能跟蹤
    if (m_enabled)
    {
        base::threading::ScopedLock scopedLock(lock());
        // 記錄當前時間為效能跟蹤點的結束時間
        base::utils::DateTime::gettimeofday(&m_endTime);
        // 將時間根據需要調整為合適的格式後,以字串形式返回
        base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL("");
        // 效能跟蹤回撥資料物件的初始化,交給效能跟蹤回撥處理(比如預設DefaultPerformanceTrackingCallback進行效能跟蹤的時長統計)
        // PerformanceTrackingData::DataType::Checkpoint表示單個跟蹤點的效能跟蹤
        PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint);
        data.init(this);
        data.m_checkpointId = id;
        data.m_file = file;
        data.m_line = line;
        data.m_func = func;
        // 經過調整後的時間戳的字串形式。
        data.m_formattedTimeTaken = formattedTime;
        PerformanceTrackingCallback *callback = nullptr;
        // 遍歷註冊的所有效能跟蹤回撥
        for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr> &h : ELPP->m_performanceTrackingCallbacks)
        {
            callback = h.second.get();
            if (callback != nullptr && callback->enabled())
            {
                callback->handle(&data);
            }
        }
        // 更新最後的效能跟蹤點的時間為當前跟蹤點的結束時間
        base::utils::DateTime::gettimeofday(&m_lastCheckpointTime);
        // 預設為false,進行一次跟蹤點統計後置為true
        m_hasChecked = true;
        m_lastCheckpointId = id;
    }
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
    ELPP_UNUSED(id);
    ELPP_UNUSED(file);
    ELPP_UNUSED(line);
    ELPP_UNUSED(func);
}

getFormattedTimeTaken 介面

    getFormattedTimeTaken 介面用於獲取經過調整後的時間戳的字串形式。

const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const
{
    // 保留時間格式,不將其轉換為秒、小時等(僅限效能跟蹤)
    if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat))
    {
        base::type::stringstream_t ss;
        // 求timeval結構體之間的時間差(以毫秒或者微秒為單位),以m_timestampUnit為統計單位,預設效能跟蹤單位為毫秒
        ss << base::utils::DateTime::getTimeDifference(m_endTime, startTime, m_timestampUnit)
           << " "
           << base::consts::kTimeFormats[static_cast<base::type::EnumType>(m_timestampUnit)].unit;
        return ss.str();
    }
    // 將指定的時間戳轉化為指定時間單位的字串形式,以m_timestampUnit為統計單位
    return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime, startTime, m_timestampUnit), m_timestampUnit);
}

相關類介紹

PerformanceTrackingData 類

    PerformanceTrackingData 類主要作為效能跟蹤回撥的引數,傳遞效能跟蹤統計需要的一些資訊。

class PerformanceTrackingData
{
public:
    enum class DataType : base::type::EnumType
    {
        Checkpoint = 1,  //效能跟蹤點的統計
        Complete = 2     //整個程式碼塊的效能跟蹤統計
    };
    // Do not use constructor, will run into multiple definition error, use init(PerformanceTracker*)
    explicit PerformanceTrackingData(DataType dataType)
        : m_performanceTracker(nullptr), m_dataType(dataType), m_firstCheckpoint(false), m_file(""), m_line(0), m_func("")
    {
    }
    inline const std::string *blockName(void) const;
    inline const struct timeval *startTime(void) const;
    inline const struct timeval *endTime(void) const;
    inline const struct timeval *lastCheckpointTime(void) const;
    inline const base::PerformanceTracker *performanceTracker(void) const
    {
        return m_performanceTracker;
    }
    inline PerformanceTrackingData::DataType dataType(void) const
    {
        return m_dataType;
    }
    inline bool firstCheckpoint(void) const
    {
        return m_firstCheckpoint;
    }
    inline std::string checkpointId(void) const
    {
        return m_checkpointId;
    }
    inline const char *file(void) const
    {
        return m_file;
    }
    inline base::type::LineNumber line(void) const
    {
        return m_line;
    }
    inline const char *func(void) const
    {
        return m_func;
    }
    inline const base::type::string_t *formattedTimeTaken() const
    {
        return &m_formattedTimeTaken;
    }
    inline const std::string &loggerId(void) const;

private:
    base::PerformanceTracker *m_performanceTracker; // 效能跟蹤統計類
    base::type::string_t m_formattedTimeTaken;      // 經過調整後的時間戳的字串形式。
    PerformanceTrackingData::DataType m_dataType;   // 效能跟蹤統計的型別
    bool m_firstCheckpoint;                         // 暫時未用上
    std::string m_checkpointId;                     // 當前進行的效能跟蹤點的ID
    const char *m_file;                             // 進行效能跟蹤的原始檔
    base::type::LineNumber m_line;                  // 進行效能跟蹤的行號
    const char *m_func;                             // 進行效能跟蹤的函式
    inline void init(base::PerformanceTracker *performanceTracker, bool firstCheckpoint = false)
    {
        m_performanceTracker = performanceTracker;
        m_firstCheckpoint = firstCheckpoint;
    }

    friend class el::base::PerformanceTracker;
};

DefaultPerformanceTrackingCallback 類

    DefaultPerformanceTrackingCallback 類是預設效能跟蹤回撥類,其主要職責就是執行 easylogging++預設的效能跟蹤統計。其實現如下:

class DefaultPerformanceTrackingCallback : public PerformanceTrackingCallback
{
protected:
    void handle(const PerformanceTrackingData *data)
    {
        m_data = data;
        base::type::stringstream_t ss;
        // 程式碼塊效能跟蹤結束
        if (m_data->dataType() == PerformanceTrackingData::DataType::Complete)
        {
            ss << ELPP_LITERAL("Executed [") << m_data->blockName()->c_str() << ELPP_LITERAL("] in [") << *m_data->formattedTimeTaken() << ELPP_LITERAL("]");
        }
        else
        {
            // 效能跟蹤點
            ss << ELPP_LITERAL("Performance checkpoint");
            if (!m_data->checkpointId().empty())
            {
                ss << ELPP_LITERAL(" [") << m_data->checkpointId().c_str() << ELPP_LITERAL("]");
            }
            ss << ELPP_LITERAL(" for block [") << m_data->blockName()->c_str() << ELPP_LITERAL("] : [") << *m_data->performanceTracker();
            // 未禁用效能跟蹤點的對比統計並且已經進行過跟蹤點統計
            if (!ELPP->hasFlag(LoggingFlag::DisablePerformanceTrackingCheckpointComparison) && m_data->performanceTracker()->m_hasChecked)
            {
                // 輸出上一次跟蹤點統計結束的時間
                ss << ELPP_LITERAL(" ([") << *m_data->formattedTimeTaken() << ELPP_LITERAL("] from ");
                if (m_data->performanceTracker()->m_lastCheckpointId.empty())
                {
                    ss << ELPP_LITERAL("last checkpoint");
                }
                else
                {
                    ss << ELPP_LITERAL("checkpoint '") << m_data->performanceTracker()->m_lastCheckpointId.c_str() << ELPP_LITERAL("'");
                }
                ss << ELPP_LITERAL(")]");
            }
            else
            {
                ss << ELPP_LITERAL("]");
            }
        }
        // 要輸出的效能跟蹤資訊彙總後,使用el::base::Writer最終進行日誌輸出。
        el::base::Writer(m_data->performanceTracker()->level(), m_data->file(), m_data->line(), m_data->func()).construct(1, m_data->loggerId().c_str()) << ss.str();
    }

private:
    const PerformanceTrackingData *m_data;
};

    el::base::Writer 類我們在 CLOG 巨集 Writer 物件的建立以及初始化日誌輸出日誌資訊儲存 已經仔細介紹過了,這裡就不多說了。

至此,效能跟蹤的實現就介紹完了,下一篇我們開始分析崩潰處理的實現。