該文前三部份介紹 statistics、perf context和iostat context和thread status相關內容。最后介紹ThreadLocalPtr實現的原理。
0. 性能診斷類型
-
statistics:所有線程的所有操作的count/time的累加。
-
perf context和iostat context: 單個操作(比如get和put)的count/time。
-
thread status: 用於監視線程的運行時狀態
1. statistics
開銷:
增加5%-10%
頭文件:
include/rocksdb/statistics.h
monitoring/statistics.h
使用方法:
Options options;
options.statistics = rocksdb::CreateDBStatistics();
可選統計級別:
- kExceptDetailedTimers: 除去mutex等待和壓縮的計時
- kExceptTimeForMutex: 除去mutex等待的計時
- kAll: 所有
數據統計類型:
-
ticker:類型是64位無符號整型。用於度量counters (e.g. “rocksdb.block.cache.hit”), cumulative bytes (e.g. “rocksdb.bytes.written”) 或者 time (e.g. “rocksdb.l0.slowdown.micros”)。
-
histogram:統計數據的統計分布,包括最大值、最小值、平均值、中位數、標准差。
統計函數的接口:
-
MeasureTime:函數名有歧義。實際上是把value記錄到histogram中。
-
RecordTick:累加ticker。
獲取結果的接口:
-
Statistics::getTickerCount:指定ticker type獲得count。
-
Statistics::histogramData:指定Histograms type,返回一個HistogramData結構體,成員是統計值,包括最大值、最小值、平均值、中位數、標准差。
-
Statistics::getHistogramString:指定Histograms type,返回直方圖可讀的字符串。
-
Statistics::ToString():返回可讀的字符串,包括所有的ticker和histogram。
1.1 statistics實現
1.1.1 函數與成員變量
實現了StatisticsImpl類,繼承了Statistics的接口。
主要接口:
- getTickerCount
- histogramData
- getHistogramString
- getAndResetTickerCount
- recordTick
- measureTime
- ToString
成員變量:
- TickerInfo tickers_[INTERNAL_TICKER_ENUM_MAX];
- HistogramInfo histograms_[INTERNAL_HISTOGRAM_ENUM_MAX];
這里的TickerInfo和HistogramInfo類型的數據結構是相似的:一個線程局部的counter或者time;加上一個非線程局部的統計值用來累加counter或者time。
TickerInfo類型包含兩個參數:
- ThreadLocalPtr類型(真實類型ThreadTickerInfo)的thread_value,包含:
- 整型類型的value
- 指向merged_sum的指針
- 整型類型的merged_sum
HistogreamInfo類型包含兩個參數:
- ThreadLocalPtr類型(真實類型ThreadHistogramInfo)的thread_value,包含:
- HistogramImpl類型的value
- 指向merged_hist的指針
- 指向merge_lock的指針
- HistogramImpl類型的merged_hist
- Mutex類型的merge_lock
merged_sum和merged_hist初始化時都是空的,而且當且僅當線程退出時,才調用mergeThreadValue函數將TickerInfo和HistogreamInfo中的線程局部變量累加到merged_sum和merged_hist。這個實現與ThreadLocalPtr
密切相關。
1.1.2 關鍵函數實現
getTickerCount:
- 用處:指定ticker type獲得count
- 實現思路:遍歷對應的ticker type的TickerInfo所有線程的線程局部變量的值,累加得到thread_local_sum,再加上當前merged_sum得到最終結果。
- 加鎖情況:首先加StatisticsImpl::aggregated_lock鎖;調用TickInfo里的ThreadLocalPtr的Fold函數會再加一個保護ThreadData的鏈表的鎖。
histogramData/getHistogramString:
- 實現思路:遍歷對應的Histograms type的HistogramInfo所有線程的線程局部變量的值,累加得到類型為HistogramImpl的res_hist,再加上當前merged_hist得到最終結果。histogramData將結果轉化成HistogramData的結構體。而getHistogramString將結果轉化成可讀的string。
- 加鎖情況:首先加StatisticsImpl::merge_lock鎖;調用HistogramInfo里的ThreadLocalPtr的Fold函數會再加一個保護ThreadData的鏈表的鎖。
ToString:
- 用處:返回可讀的字符串,包括所有的ticker和histogram。
- 實現思路:依次對TickerInfo數組調用getTickerCount,打印結果;依次對HistogramInfo數組調用histogramData,打印結果。
2. perf context和iostat context
頭文件
include/rocksdb/perf_level.h
include/rocksdb/perf_context.h
include/rocksdb/iostats_context.h
使用方法:
rocksdb::SetPerfLevel(rocksdb::PerfLevel::kEnableTimeExceptForMutex);
rocksdb::perf_context.Reset();
rocksdb::iostats_context.Reset();
... // run your query
rocksdb::SetPerfLevel(rocksdb::PerfLevel::kDisable);
可選統計級別:
- kDisable: 關閉統計
- kEnableCount: 統計count
- kEnableTimeExceptForMutex: 統計count和與mutex無關的time
- kEnableTime: 統計count和time
統計變量:
extern __thread PerfContext perf_context;
extern __thread IOStatsContext iostats_context;
計數函數(宏):
#define PERF_COUNTER_ADD(metric, value) \
perf_context.metric += value;
#define IOSTATS_ADD(metric, value) \ (iostats_context.metric += value)
通過簡單調用上述兩個宏即可在對應的counter統計值上累加。
而計數相對復雜一點點,使用了一個PerfStepTimer類,實現了start、measure、stop三個函數,類在析構時調用stop函數。
perf context計時宏:
- PERF_TIMER_GUARD(metric):實例化一個PerfStepTimer變量,將perf_context對應的metric指針寫到PerfStepTimer中。調用start函數。
- PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(metric, condition) :增加一個開始調用函數的條件。
- PERF_TIMER_START(metric):調用start函數。
- PERF_TIMER_MEASURE(metric):調用measure函數,將計時累加perf_context的metric中,重置start時間。
- PERF_TIMER_STOP(metric) :調用measure函數,將計時累加perf_context的metric中
iostat context計時宏:
- IOSTATS_TIMER_GUARD(metric):實例化一個PerfStepTimer變量,將iostats_context對應的metric指針寫到PerfStepTimer中。調用start函數。
3. thread status
參考文檔:
docs/_posts/2015-10-27-getthreadlist.markdown
頭文件:
include/rocksdb/env.h
include/rocksdb/thread_status.h
util/thread_operation.h
monitoring/thread_status_updater.h
monitoring/thread_status_util.h
使用方法:
-
將該線程的統計加入ThreadStatusUpdater:
調用ThreadStatusUtil::RegisterThread -
將該線程的統計從ThreadStatusUpdater刪除:
ThreadStatusUtil::UnregisterThread -
其他修改thread status的函數:
見monitoring/thread_status_util.h -
通過調用env的GetThreadList()函數可以獲得當前后台線程的狀態,狀態的狀態值存放於一個vector中。將其中的內容展現出來,類似於下圖:
第一列是統計的一些參數。第二列和第三列分別是對應的兩個線程的統計值。
3.1 實現
關鍵類:
- ThreadStatusUpdater:
存儲了各自后台線程的狀態和所有后台線程狀態的指針。 - ThreadStatusUtil:
該類只有靜態變量和靜態方法。
源碼注釋推薦通過該類的方法去更新ThreadStatusUpdater中的狀態。
他們之間的關系如下:
{{thread status.png(uploading...)}}
番外: 4. ThreadLocalPtr實現
StatisticsImpl類使用了ThreadLocalPtr的原因:
使用__thread關鍵字修飾的變量,能做到線程間的隔離,但是並不能做到實例間的隔離。舉個例子:
class A{
static __thread int a_;
};
A a1, a2;
雖然不同的線程里,a_
是線程局部的。在同一個線程里這兩者使用的是卻是同一個a_
。
而使用ThreadLocalPtr能使得線程局部變量既做到線程間隔離,又做到實例間隔離。
如何做到線程間隔離,也能做到實例間隔離?
-
線程間隔離:
ThreadLocalPtr之間共享一個線程局部變量tls_,tls_是指向ThreadData類型的指針。不同的線程通過不同的tls_地址,指向的是不同的ThreadData。 -
實例間隔離:
ThreadData使用vector存放不同實例之間的value。在ThreadLocalPtr實例化時會獲得一個id,id標示它的值存放在vector的位置。這個id能夠區分開不同實例對應的不同的線程局部變量。
基本原理如下:
線程的tls初始值為空。在第一次使用時,通過new實例化ThreadData,並且根據id,對ThreadData中的vector進行resize。
ThreadData的數據實際存放在堆上,ThreadLocalPtr如何管理ThreadData的數據?
-
線程退出:
利用pthread_key的機制,設置線程退出函數OnThreadExit,在線程退出時刪除對應的ThreadData。 -
ThreadLocalPtr的實例生命周期結束:
在所有ThreadData的vector中刪除對應id的數據,回收id。