CPU负载均衡之loadavg计算
CPU负载均衡之loadavg计算
前言
本文分析关于loadavg 使用、计算相关内容(基于kernel 4.9),如下图:
其中重点介绍CPU负载计算过程相关内容(源码网站参考:opengrok 和 bootlin);
文章目录
- CPU负载均衡之loadavg计算
- 前言
- 1. CPU负载
- 1.1 一个味道很重的比喻
- 1.2 核心概念说明
- 2. loadavg使用
- 2.1 查看CPU 负载
- 2.2 CPU负载数据的分析说明
- 3. 负载计算
- 3.1 流程示意
- 3.2 code跟踪
- 3.2.1 文件目录:
- 3.2.2 loadavg 显示流程
- 3.2.3 avenrun 计算
- 3.2.4 计算active
- 3.2.4 这里有个很好玩的计算
- 问题记录
1. CPU负载
负载:当前队列中排队的Task+正在running的Task数量之和与CPU能力的比值
1.1 一个味道很重的比喻
这个概念又一个很形象的比喻:
- 游乐场很好玩,人超多,对应Task;
- 只有一个厕所(也可能有2、3、4个),对应CPU的核数;
- 一旦节假日去到游乐场,就会发现厕所排了很长的队;
这种情况就是满负载了,嗯,有内个味儿了!!!
1.2 核心概念说明
name | 说明 |
---|---|
active task | 对应每个CPU上nr_running和nr_uninterruptible的task,在runqueue结构体中维护 |
clac_load_tasks | 对应所有CPU中active的task之和 |
avnrun | sched中提供数组,用于记录和计算1、5、15分钟时的平均负载值,与直观的load显示比值为2048 |
NO_HZ | kernel中定义的系统频率,倒数即1s的节拍数,每个节拍提供中断用于各类task使用,NO_HZ即在CPU空闲时关闭周期性的中断 |
2. loadavg使用
2.1 查看CPU 负载
- Linux、android的debug系统已经很好的集成了loadavg功能,再出现ANR、SWT等异常时,抓取的log信息中均会有当时loadavg情况;
- 可以通过系统提供命令查看
- cat /proc/loadavg
- uptime
- dumpsys cpuinfo
- top (top中没有显示loadavg)
2.2 CPU负载数据的分析说明
- 三个数值分别代表:1分钟、5分钟、15分钟内的平均负载;
- 一个核满负载为1,一般的4核CPU满负载就是4,也存在只有一个核但是负载为3的情况,这说明CPU成为了block点;
- 可以看到的是统计的最小粒度为1min,其实这个统计主要代表一个趋势性的说明,即偶尔出现满负载并不可怕,如果一直长时间持续,则说明或者系统出现问题或者系统与HW资源不匹配了;
- 按照以往经验,如果负载一直维持在70%以内,那说明系统整体状况还是比较健康的,但是并不说明系统没有问题(可能这70%是异常task造成的)
- DVFS就是根据这个负载情况调节频率和电压的;
3. 负载计算
核心计算步骤如下三步:
计算每个CPU当前活跃task数量:active task = nr_running和nr_uninterruptible
在update的周期到来时,将nr_active更新到全局变量calc_load_tasks中;
根据oldload和当前活跃task计算新负载:newload = load * exp + active * (FIXED_1 - exp);
3.1 流程示意
上述图示展示了整个数据更新的过程,如下详细解释说明下:
- 涉及的数据:
active:每个CPU上的运行Task
calc_load_idle:在cpu进入idle时更新此值,避免进入idle后没有中断,在update周期到来时无法更新
calc_load_tasks:全局变量,记录所有Task sum,实际做指数平均的active;
avenrun:每个period计算出来的最终load,/proc/loadavg即读取这个值;
- 更新时机
每次shcedule_tick调用calc_load_fold_active,在到达更新时机(即5s)时获取每个CPU的running和uninterruptible数值,更新每个CPU的active;
每次tick_nohz_stop_sched_tick()进入Idle时调用calc_load_enter_idle获取当前CPU的active更新到calc_load_idle,提供给到之后使用;
每次do_timer调用在到达更新时机(即5s+10tick)时根据calc_load_tasks、EXP(权重)、当前load计算新的load值;
所谓更新时机,可参照这个图:
3.2 code跟踪
3.2.1 文件目录:
id | 路径 |
---|---|
1 | /kernel/fs/proc/loadavg.c |
2 | /kernel/kernel/shcedule/loadavg.c |
3 | /kernel/sched/sched.h |
4 | /kernel/sched/core.c |
5 | /kernel/timer/timekeeping.c |
3.2.2 loadavg 显示流程
所有loadavg的使用其实都是基于/proc/loadavg,首先将这里做为入口:
//ps:这里应该是基于5.72的,核心步骤不影响
static int loadavg_proc_show(struct seq_file *m, void *v){unsigned long avnrun[3];get_avenrun(avnrun, FIXED_1/200, 0);//获取avnrun//写入loadavg,分别计算整数部分和小数部分 seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]),nr_running(), nr_threads,idr_get_cursor(&task_active_pid_ns(current)->idr) - 1);//这里就是最后last_pidreturn 0;}static int __init proc_loadavg_init(void){proc_create_single("loadavg", 0, NULL, loadavg_proc_show);return 0;}//创建节点,显示内容为loadavg_proc_show
fs_initcall(proc_loadavg_init);//系统启动时fs_initcall中注册init函数
- 在系统启动的时候将proc_loadavg_init启动起来;
- 核心函数为获取avenrun,然后分别计算整数部分和小数部分;
这里需要理解的几个概念:
avnrun,这个是从sched中直接获取过来的,中间经过一个简单转换(添加了10.24)
nr_running 理解为number of runnable task
LOAD_IN 右移11位,即取高位
LOAD_FRAC 取低11位,运算乘100
定义如下:
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */#define LOAD_INT(x) ((x) >> FSHIFT) //计算整数
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)//计算小数
3.2.3 avenrun 计算
- proc下的avenrun数组是从sched下的avenrun经过运算得到的:
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{loads[0] = (avenrun[0] + offset) << shift;loads[1] = (avenrun[1] + offset) << shift;loads[2] = (avenrun[2] + offset) << shift;//则对于运算来说就是添加了一个offset,即2048 / 200 / 2048=0.005 , 负载只取小数点后2位,所以添加了一个类似于四舍五入操作;
}
- 在code中搜索,avenrun数组的计算赋值操作,即每个周期的计算load操作:
- loadavg.c中calc_global_load函数中,完整计算;
则可以很直观的看到avenrun(load average array)是通过calc_load相关函数更新的,此部分为详细计算过程:
- loadavg.c中calc_global_load函数中,完整计算;
/** calc_load - update the avenrun load estimates 10 ticks after the* CPUs have updated calc_load_tasks.** Called from the global timer code.*/
void calc_global_load(unsigned long ticks)
{unsigned long sample_window;long active, delta;sample_window = READ_ONCE(calc_load_update);
//没到更新时间5s + 10tick 则退出 if (time_before(jiffies, sample_window + 10))return;/** Fold the 'old' NO_HZ-delta to include all NO_HZ CPUs.*/
//这里首先会去读取load idle 的值,并将其更新到calc_load_tasks; delta = calc_load_nohz_read();if (delta)atomic_long_add(delta, &calc_load_tasks);
//获取active,实质就是读取calc_load_tasksactive = atomic_long_read(&calc_load_tasks);active = active > 0 ? active * FIXED_1 : 0;
//根据权重运算avenrunavenrun[0] = calc_load(avenrun[0], EXP_1, active);avenrun[1] = calc_load(avenrun[1], EXP_5, active);avenrun[2] = calc_load(avenrun[2], EXP_15, active);
//周期更新;WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);/** In case we went to NO_HZ for multiple LOAD_FREQ intervals* catch up in bulk.*/
//这里是用来cover一种情况,存在多个周期(n * 5s)在Idle,则一直没有更新;calc_global_nohz();
}/** a1 = a0 * e + a * (1 - e)*/
static inline unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{unsigned long newload;
//核心计算公式:newload = load * exp + active * (FIXED_1 - exp);
//如果新增active比之前的完整还多,则newload + 1,体现趋势 if (active >= load)newload += FIXED_1-1;return newload / FIXED_1;
}
这里的核心内容就是计算avenrun,即获取到active的和,使用指数平均的公式计算最终的load:
- 确认已经达到了统计时间,默认为5*HZ+10tick
- 获取active数量,直接读取calc_load_tasks(所有增加的和);
- 更新数组内容,通过公式计算:a1 = a0 * e + a * (1 - e) ,则这个公式:
- 分别在1、5、15分钟的条件下历史load的权重是不断增加的,也就是说新增加的active数量对于越久时间的统计影响越小(1884、2014、2037)
- 新增加的active task的影响是慢慢变缓的,即时间越久,整体计算出来的数据越趋向于active_task
3.2.4 计算active
在code中搜索,active的计算赋值操作为:
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
是直接读取的calc_load_tasks,更新这个变量的位置:
- atomic_long_add(delta, &calc_load_tasks),每次scheduler_tick时检测到达更新周期时add;
- delta = calc_load_fold_idle(),进入idle更新到数组中,在do_timer进入global计算时更新;
/*
* Called from scheduler_tick() to periodically update this CPU's
* active count.
*/
void calc_global_load_tick(struct rq *this_rq)
{long delta;
// now is befor update tick, do nothingif (time_before(jiffies, this_rq->calc_load_update))return;
// now is in update tick ,read the per-cpu nr_activedelta = calc_load_fold_active(this_rq, 0);if (delta)atomic_long_add(delta, &calc_load_tasks);
//normal update, is rqthis_rq->calc_load_update += LOAD_FREQ;
}
上述函数是per-cpu更新的位置;
//进入idle的时候将数据更新到calc_load_idle数组中,需要根据idx和时间判断具体添加位置
void calc_load_enter_idle(void)
{struct rq *this_rq = this_rq();long delta;// when the cpu is enter idle, update the load to calc_load_idle;
// we will use idx to choose if this load is used in this update tick;delta = calc_load_fold_active(this_rq, 0);if (delta) {int idx = calc_load_write_idx();atomic_long_add(delta, &calc_load_idle[idx]);}
}
//决策添加的数组,这部分内容看下图理解会更方便
static inline int calc_load_write_idx(void)
{int idx = calc_load_idx;/** See calc_global_nohz(), if we observe the new index, we also* need to observe the new update time.*/smp_rmb();/** If the folding window started, make sure we start writing in the* next idle-delta.*/
//qyp :core, if per-cpu is calc load and then this perid idle update is used in next freqif (!time_before(jiffies, calc_load_update))idx++;return idx & 1;
}//在global更新的时候读取对应的数组内容;
static long calc_load_fold_idle(void)
{int idx = calc_load_read_idx();long delta = 0;
// use idx to choose the calc_load_idle, and return itif (atomic_long_read(&calc_load_idle[idx]))delta = atomic_long_xchg(&calc_load_idle[idx], 0);return delta;
}
//读取idx
static inline int calc_load_read_idx(void)
{return calc_load_idx & 1;
}
//退出idle的时候判断当前jiffies,即如果已经进入下个周期,则更新时间点
void calc_load_exit_idle(void)
{struct rq *this_rq = this_rq();
// use the globle calc_load_update to forbidden the rq update tick is multi period no update this_rq->calc_load_update = calc_load_update;
//if now is before per-cpu update tick : return if (time_before(jiffies, this_rq->calc_load_update))return;
//if now is before avnrun update tick : update the update tick,because we can not enter the tick to update it; if (time_before(jiffies, this_rq->calc_load_update + 10))this_rq->calc_load_update += LOAD_FREQ;
}
这个过程比较比较复杂,首先查看比较古老的版本,学习思想,再回来查看其设计方法:
- 实质:计算每个CPU上的nr_running和uninterrupt_sleep的数量,并返回;
- 过程中添加了一个记录idle状态的全局变量,用于更新当前的active数值,这里搞这么复杂实际上是为了解决进入IDLE后没有中断更新load的情况:
- 在进入Idle时,将load 数量更新到idle数组中;
- 到达更新点的时候,统一计算完整的idle数组中值,通过数组以及idx自增的方式判断实际的进入Idle时间以及是否需要计算;
- 退出idle时需要判断此时是否已经到达cpu计算load的时间点,如果已经到达,则更新update时间,避免重复计算 ;
3.2.4 这里有个很好玩的计算
对于可能存在的多个周期的idle情况,会调用到这里,这里就是会计算N个周期的load情况
通过数学公式计算可以发现其实N个周期后的计算公式为:
an = a0 * e^n + a * (1 - e) * (1 + e + … + e^n-1) [1]
= a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e)
= a0 * e^n + a * (1 - e^n)
所以这里会直接实现对应en的效果:
static void calc_global_nohz(void)
{long delta, active, n;
// in globle update tickif (!time_before(jiffies, calc_load_update + 10)) {/** Catch-up, fold however many we are behind still*/
//calc the perioddelta = jiffies - calc_load_update - 10;n = 1 + (delta / LOAD_FREQ);active = atomic_long_read(&calc_load_tasks);active = active > 0 ? active * FIXED_1 : 0;
//calc the n avenrunavenrun[0] = calc_load_n(avenrun[0], EXP_1, active, n);avenrun[1] = calc_load_n(avenrun[1], EXP_5, active, n);avenrun[2] = calc_load_n(avenrun[2], EXP_15, active, n);
//update globle tickcalc_load_update += n * LOAD_FREQ;}/** Flip the idle index...** Make sure we first write the new time then flip the index, so that* calc_load_write_idx() will see the new time when it reads the new* index, this avoids a double flip messing things up.*/smp_wmb();calc_load_idx++;
}
上述函数是计算周期N的值,然后调用calc_load_n来计算上述公式:
static unsigned long
calc_load_n(unsigned long load, unsigned long exp,unsigned long active, unsigned int n)
{
// math calc: n period = load * exp ^ n + active * (1-exp^n)return calc_load(load, fixed_power_int(exp, FSHIFT, n), active);
}
那么en是怎么计算的呢?就在下边(通过右移减少计算次数):
static unsigned long
fixed_power_int(unsigned long x, unsigned int frac_bits, unsigned int n)
{unsigned long result = 1UL << frac_bits;// * 2048
//use bit to calc e^n = e^1 * e^2 * e^4if (n) {for (;;) {if (n & 1) {//对应bit有则*xresult *= x;result += 1UL << (frac_bits - 1);result >>= frac_bits;}n >>= 1;if (!n)break;//更新x x^2 x^4 ..... x *= x;x += 1UL << (frac_bits - 1);x >>= frac_bits;}}return result;
}
问题记录
- 为什么avenrun计算的三个权重分别为1884、2014、2037,这个是如何决定的?
这里的系数是按照上述公式决定的即:2048 * (1 / e ^ (5s / period) ) - 为什么在5s一次的统计过程中要+10 tick,而且核心计算公式其实很简单,为什么流程要搞这么复杂?
A:- 由于存在多核情况,在计算各个核的load时和整体load更新时存在竞争,所以修改为在5HZ时各个CPU计算,而在+10tick之后更新给到AVGRUN;
- 由于存在nohz的情况,即IDLE时不会有中断更新计算load,所以要引入新的计算补足这一部分;
- 关于do_timer和scheduler_tick相关调度的整体逻辑是怎样的?
A: 实质就是在周期性中断到来的时候,调度对应函数检测是否到达更新周期进行计算;
CPU负载均衡之loadavg计算相关推荐
- 查看linux cpu负载均衡,关于linux内核cpu进程的负载均衡
2.6内核中进程调度模块的负载均衡行为分为"拉"和"推",推这里不考虑,关于拉均衡有一篇文章特别好,具体出处就不记得了,我当时用的百度快照,那篇文章我认为最精彩 ...
- 一文读懂 | CPU负载均衡实现
在<一文读懂 | 进程怎么绑定 CPU>这篇文章中介绍过,在 Linux 内核中会为每个 CPU 创建一个可运行进程队列,由于每个 CPU 都拥有一个可运行进程队列,那么就有可能会出现每个 ...
- linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan
还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是lo ...
- linux进程网络均衡,linux多CPU进程负载均衡解析
在linux中,支持对称smp的处理器模型,在多处理器的情况下,每个处理器都有自己的一个运行队列,这样就存在着分配不均的情况,有的cpu运行队列很多进程,导致一直很忙,有的cpu运行队列可能很少的进程 ...
- (2)Linux进程调度器-CPU负载
目录 背景 1. 概述 2. 全局CPU平均负载 2.1 基础概念 2.2 流程 2.3 计算方法 3. 运行队列CPU负载 4. PELT 4.1 PELT计算方法 4.2 PELT计算调用 背景 ...
- linux在多核处理器上的负载均衡原理
原文出处:http://donghao.org/uii/ [原理] 现在互联网公司使用的都是多CPU(多核)的服务器了,Linux操作系统会自动把任务分配到不同的处理器上,并尽可能的保持负载均衡.那L ...
- 分布式基础-负载均衡
前言 我理解要分布式系统,无非两个原因数据和计算,单机系统无法保存这么大的数据量,所以要分布式系统来保存; 计算嘛,就是说单机计算无法达到性能要求,比如双十一一台机器肯定处理不过来,所以需要后台一系列 ...
- Linux系统cpu负载浅析
cpu负载就是cpu在一段时间内正在处理以及等待cpu处理的进程数之和的统计信息,也就是cpu使用队列的长度统计信息,这个数字越小越好. 维基百科关于系统负载的解释: the system Load ...
- 现代网络负载均衡和代理技术
作者:Matt Klein 译者:杨泽 原题:Introduction to modern network load balancing and proxying 关于现代网络负载均衡和代理(prox ...
- CPU使用率和CPU负载的区别
1.CPU使用率 CPU使用率指的是程序在运行期间实时占用的CPU百分比,这是对一个时间段内CPU使用状况的统计.通过这个指标可以看出在某一个时间段内CPU被占用的情况. 2.cpu负载 2.1 lo ...
最新文章
- Odoo进销存业务学习笔记
- 未来教育python视频百度云-2019年计算机二级Python语言程序设计考试大纲
- matlab0-255的颜色映射到0-1,python中的matlab颜色映射
- javascript控制html高,Javascript可以控制css吗?
- Linux:-bash: ***: command not found
- Android异常总结---W/System.err(661): java.lang.IllegalStateException: End of document.
- POJ2987 Firing 最大权闭合图
- python整型为空的情况_深度剖析凭什么python中整型不会溢出
- 【RDMA】技术详解(四):RDMA之Verbs和编程步骤
- 【数据结构与算法】之深入解析“香槟塔”的求解思路与算法示例
- 关于cocoapods遇到xxxx file not found和No such module 'XXX'的问题
- w7提示无法关闭计算机,番茄花园windows7关闭电脑关机时提示强制关闭程序怎么办...
- Excel导入CSV文件(解决数值转换文本问题)
- 值得珍藏的免费观影网站
- python 两个word文档之间的相似度_如何用 word2vec 计算两个句子之间的相似度?
- 计算机组成原理—储存器的层次结构
- java程序员自己的图片转文字OCR识图工具
- mysql握手_详细介绍mysql 协议的服务端握手包及对其解析
- 随机算法 之随机数的产生
- 各种饼的做法,先存起来,老了慢慢学着做