easylogging++的那些事(四)原始碼分析(七)效能跟蹤
在上一篇我們介紹了
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 物件的建立以及初始化、日誌輸出、日誌資訊儲存 已經仔細介紹過了,這裡就不多說了。
至此,效能跟蹤的實現就介紹完了,下一篇我們開始分析崩潰處理的實現。