线程负载计数器主要是统计一个线程处于休眠期与活跃期时间的占比。
前置
一个线程,要么处于活跃状态,在执行任务,此时占用CPU,要么处于休眠状态,等待任务的到来,此时不占用CPU,ThreadLoadCounter就是用于统计某一段时间内,线程处于活跃状态所占的比率。也可以说是该线程对CPU的使用率。
比如,上图中,假设我们统计某段时间内,当前线程的CPU使用率,其中线程处于sleep状态的总时间为t1,处于Active状态的总时间为t2,那么CPU使用率为t1/(t1+t2)。
该类提供了以下三个接口:
在看该类提供的接口之前,我们先分析下常见的epoll网络模型。使用epoll的伪代码大致如下,线程阻塞在epoll_wait上等待事件到来,当有事件触发时,epoll_wait返回,线程开始处理事件。
void runLoop(){
//t1 = t2 = curTime();
while(true){
//t1 = curTime();
//activeTime = t1 - t2;
epoll_wait();
//t2 = curTime();
//sleepTime = t2 - t1;
/*事件处理*/
....
}
}
要统计该线程的CPU使用率,应该是事件处理占用的时间 / (事件处理占用的时间 + 阻塞在epoll_wait上的时间),即activeTime / (activeTime + sleepTime)。
while循环每执行一次,对应的我们就统计了一次当前CPU负载,仅一次的数据是没有用的,为了能够让统计的数据更准确,我们需要统计多次,以一段时间范围内的n次数据来计算负载率。比如,统计距当前最近的5分钟内,最后的1000次采集的数据,这1000次数据中,totalActiveTime = activeTime1 + activeTime2 + … + activeTime1000,totalSleepTime = sleepTime1 + sleepTime2 + … + sleepTime1000,最终的CPU使用率:totalActiveTime / (totalActiveTime+ totalSleepTime).
使用ThreadLoadCounter统计:
void runLoop(){
while(true){
startSleep();
epoll_wait();
sleepWakeUp();
/*事件处理*/
....
}
}
源码分析
构造&&析构
class ThreadLoadCounter {
public:
/**
* 构造函数
* @param max_size 统计样本数量
* @param max_usec 统计时间窗口,亦即最近{max_usec}的cpu负载率
*/
ThreadLoadCounter(uint64_t max_size, uint64_t max_usec);
~ThreadLoadCounter() = default;
实现如下:
ThreadLoadCounter::ThreadLoadCounter(uint64_t max_size, uint64_t max_usec) {
_last_sleep_time = _last_wake_time = getCurrentMicrosecond();
_max_size = max_size;
_max_usec = max_usec;
}
可以看出,它指定了统计的样本数量以及时间范围。
样本数量是统计休眠时间的次数与活跃时间的次数之和。比如max_size = 1000,可能是300个休眠时间数据+700个活跃时间数据。
startSleep
void ThreadLoadCounter::startSleep() {
lock_guard<mutex> lck(_mtx);
_sleeping = true;
auto current_time = getCurrentMicrosecond();
auto run_time = current_time - _last_wake_time;
_last_sleep_time = current_time;
_time_list.emplace_back(run_time, false);
if (_time_list.size() > _max_size) {
_time_list.pop_front();
}
}
在阻塞的代码之前调用该接口,计算并记录本轮统计中,线程处于活跃状态的时间,即从线程被唤醒,执行完任务,到再次进入休眠状态的时间。
sleepWakeUp
void ThreadLoadCounter::sleepWakeUp() {
lock_guard<mutex> lck(_mtx);
_sleeping = false;
auto current_time = getCurrentMicrosecond();
auto sleep_time = current_time - _last_sleep_time;
_last_wake_time = current_time;
_time_list.emplace_back(sleep_time, true);
if (_time_list.size() > _max_size) {
_time_list.pop_front();
}
}
在阻塞的代码之后调用该接口,计算并记录本轮统计中,线程处于休眠状态的时间,即从线程开始休眠,到被唤醒的时间。
load
int ThreadLoadCounter::load() {
lock_guard<mutex> lck(_mtx);
uint64_t totalSleepTime = 0;
uint64_t totalRunTime = 0;
_time_list.for_each([&](const TimeRecord &rcd) {
if (rcd._sleep) {
totalSleepTime += rcd._time;
} else {
totalRunTime += rcd._time;
}
});
if (_sleeping) {
totalSleepTime += (getCurrentMicrosecond() - _last_sleep_time);
} else {
totalRunTime += (getCurrentMicrosecond() - _last_wake_time);
}
uint64_t totalTime = totalRunTime + totalSleepTime;
while ((_time_list.size() != 0) && (totalTime > _max_usec || _time_list.size() > _max_size)) {
TimeRecord &rcd = _time_list.front();
if (rcd._sleep) {
totalSleepTime -= rcd._time;
} else {
totalRunTime -= rcd._time;
}
totalTime -= rcd._time;
_time_list.pop_front();
}
if (totalTime == 0) {
return 0;
}
return (int) (totalRunTime * 100 / totalTime);
}
需要获取当前线程最近max_usec时间范围内的负载率时,调用该接口。
对统计的所有样本数据,计算总时间(运行时间总和+休眠时间总和),总时间和样本数量应该在指定的范围内,如果超过则删除最早的记录。