系列文章目录
《ZLToolKit源码学习笔记》(1)VS2019源码编译
《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析
《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析
《ZLToolKit源码学习笔记》(4)工具模块之消息广播器
《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述
《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组
《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器(本文)
《ZLToolKit源码学习笔记》(9)线程模块之任务执行器
《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool
《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述
《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装
《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器
《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller
《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述
《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil
《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存
《ZLToolKit源码学习笔记》(19)网络模块之套接字封装
《ZLToolKit源码学习笔记》(20)网络模块之TcpServer
《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session
《ZLToolKit源码学习笔记》(22)网络模块之UdpServer
前言
线程负载计数器主要是统计一个线程处于休眠期与活跃期时间的占比。
目录
一、ThreadLoadCounter
一个线程,要么处于活跃状态,在执行任务,此时占用CPU,要么处于休眠状态,等待任务的到来,此时不占用CPU,ThreadLoadCounter就是用于统计某一段时间内,线程处于活跃状态所占的比率。也可以说是该线程对CPU的使用率。
比如,上图中,假设我们统计某段时间内,当前线程的CPU使用率,其中线程处于sleep状态的总时间为t1,处于Active状态的总时间为t2,那么CPU使用率为t1/(t1+t2)。
该类提供了以下三个接口:
1.1、epoll网络模型的CPU负载统计
在看该类提供的接口之前,我们先分析下常见的epoll网络模型。使用epoll的伪代码大致如下,线程阻塞在epoll_wait上等待事件到来,当有事件触发时,epoll_wait返回,线程开始处理事件。
要统计该线程的CPU使用率,应该是事件处理占用的时间 / (事件处理占用的时间 + 阻塞在epoll_wait上的时间),即activeTime / (activeTime + sleepTime)。
while循环每执行一次,对应的我们就统计了一次当前CPU负载,仅一次的数据是没有用的,为了能够让统计的数据更准确,我们需要统计多次,以一段时间范围内的n次数据来计算负载率。比如,统计距当前最近的5分钟内,最后的1000次采集的数据,这1000次数据中,totalActiveTime = activeTime1 + activeTime2 + ... + activeTime1000,totalSleepTime = sleepTime1 + sleepTime2 + ... + sleepTime1000,最终的CPU使用率:totalActiveTime / (totalActiveTime+ totalSleepTime).
void runLoop(){
//t1 = t2 = curTime();
while(true){
//t1 = curTime();
//activeTime = t1 - t2;
epoll_wait();
//t2 = curTime();
//sleepTime = t2 - t1;
/*事件处理*/
....
}
}
使用ThreadLoadCounter统计:
void runLoop(){
while(true){
startSleep();
epoll_wait();
sleepWakeUp();
/*事件处理*/
....
}
}
1.2、构造函数
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,那就是500个休眠时间数据+500个活跃时间数据。
1.3、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();
}
}
在阻塞的代码之前调用该接口,计算并记录本轮统计中,线程处于活跃状态的时间,即从线程被唤醒,执行完任务,到再次进入休眠状态的时间。
1.4、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();
}
}
在阻塞的代码之后调用该接口,计算并记录本轮统计中,线程处于休眠状态的时间,即从线程开始休眠,到被唤醒的时间。
1.5、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时间范围内的负载率时,调用该接口。
对统计的所有样本数据,计算总时间(运行时间总和+休眠时间总和),总时间和样本数量应该在指定的范围内,如果超过则删除最早的记录。