rocksdb源码——性能诊断
该文前三部份介绍 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。