Contents

LinuxSys:System load averages

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

System load averages

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

System load averages

什么是平均负载

man uptime

System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.

系统负载平均值是处于可运行或不可中断状态的平均进程数。 处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。 处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。 取三个时间间隔的平均值。 负载平均值并未针对系统中的 CPU 数量进行标准化,因此负载平均值为 1 意味着单个 CPU 系统始终处于加载状态,而在 4 个 CPU 系统上则意味着它有 75% 的时间处于空闲状态。

  • 可运行状态:

    • 指正在使用CPU或者正在等待CPU的进程,我们使用ps命令查看处于R状态的进程
  • 不可中断状态:

    • 进程则是正处于内核态关键流程中的进程,并且这些流程是不可中断的。例如:常见的等待硬件设备I/O的响应,也就是我们在ps命令查看处于D状态的进程

比如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程中断或者打断的,这个时候的进程处于不可中断状态,如果此时的进程被打断了,就容易出现磁盘数据和进程数据不一致的问题。

所以,不可中断状态实际上是系统进程和硬件设备的一种保护机制。

因此,你可以简单理解为,平均负载就是平均活跃进程数。平均活跃进程数,直观上的理解就是单位时间内的活跃进程数,但它实际上是活跃进程数的指数衰减平均值。既然是平均活跃进程数,那么理想状态,就是每个CPU上都刚好运行着一个进程,这样每个CPU都会得到充分的利用。例如平均负载为2时,意味着什么呢?

  • 在只有2个CPU的系统上,意味着所有的CPU刚好被完全占用
  • 在4个CPU的系统上,意味着CPU有50%的空闲
  • 而在只有1个CPU的系统上,则意味着有一半的进程竞争不到CPU

在了解平均负载之前,先了解下Linux中进程的几种状态:

  • TASK_RUNNINT: 简称R,可执行状态
  • TASK_INTERRUPTIBLE:简称S,可中断的睡眠状态,能够响应信号。
  • TASK_UNINTERRUPTIBLE:简称D,不可中断的睡眠状态。该状态主要是显示内核在处理一些流程时,是不可中断的,不可中断状态可以认为是一种保护机制,来保证系统对进程和设备之间的一致性。
  • TASK_STOPPED || TASK_TRACED:简称T,暂停状态或跟踪状态,
  • TASK_DEAD - EXIT_ZOMBIE :简称Z,退出状态,进程为僵尸进程,该进程不可被kill,不响应任务信号。
  • TASK_DEAD-EXIT_DEAD: 简称X,退出状态,进程即将被销毁。

平均负载呢,可以简单地理解为在一定时间内,系统处于可运行状态不可中断状态的平均进程数。

  1. 可运行状态就是我们上面所说的TASK_RUNNING,可运行状态,该状态包含正在使用CPU或者等待CPU的进程。
  2. 不可中断状态的进程:是我们上面所说的TASK_UNINTERRUPTIBLE,简称D的进程。

所以我们可以看到平均负载并不只是可运行状态的进程,还包含着不可中断的进程。

平均负载查看过程

top 查看平均负载

./images/image-20230112110402613.png

Linux 是计算了过去一段时间内的平均值,这三个数分别代表的是过去 1 分钟、过去 5 分钟和过去 15 分钟的平均负载值。

事实上,top 命令里的负载值是从 /proc/loadavg 这个伪文件里来的。通过 strace 命令跟踪 top 命令的系统调用可以看的到这个过程。

1
strace top

./images/image-20230112111522614.png

内核中定义了 loadavg 这个伪文件的 open 函数。当用户态访问 /proc/loadavg 会触发内核定义的函数,在这里会读取内核中的平均负载变量,简单计算后便可展示出来。整体流程如下图所示。

./images/loadavg1.png

我们根据上述流程图再展开了看下。伪文件 /proc/loadavg 在 kernel 中定义是在 /fs/proc/loadavg.c 中。在该文件中会创建 /proc/loadavg,并为其指定操作方法 loadavg_proc_fops。

1
2
3
4
5
6
//file: fs/proc/loadavg.c
static int __init proc_loadavg_init(void)
{
 proc_create("loadavg", 0, NULL, &loadavg_proc_fops);
 return 0;
}

在 loadavg_proc_fops 中包含了打开该文件时对应的操作方法。

1
2
3
4
5
//file: fs/proc/loadavg.c
static const struct file_operations loadavg_proc_fops = {
 .open  = loadavg_proc_open,
 ......
};

当在用户态打开 /proc/loadavg 文件时,都会调用 loadavg_proc_fops 中的 open 函数指针 - loadavg_proc_open。loadavg_proc_open 接下来会调用 loadavg_proc_show 进行处理,核心的计算是在这里完成的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//file: fs/proc/loadavg.c
static int loadavg_proc_show(struct seq_file *m, void *v)
{
 unsigned long avnrun[3];

 //获取平均负载值
 get_avenrun(avnrun, FIXED_1/200, 0);

 //打印输出平均负载
 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,
  task_active_pid_ns(current)->last_pid);
 return 0;
}

在 loadavg_proc_show 函数中做了两件事。

  • 调用 get_avenrun 读取当前负载值
  • 将平均负载值按照一定的格式打印输出

在上面的源码中,大家看到了 FIXED_1/200、LOAD_INT、LOAD_FRAC 等奇奇怪怪的定义,代码写的这么猥琐是因为内核中并没有 float、double 等浮点数类型,而是用整数来模拟的。这些代码都是为了在整数和小数之间转化使的。知道这个背景就行了,不用过度展开剖析。

这样用户通过访问 /proc/loadavg 文件就可以读取到内核计算的负载数据了。其中获取 get_avenrun 只是在访问 avenrun 这个全局数组而已。

1
2
3
4
5
6
7
//file:kernel/sched/core.c
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;
}

现在可以总结一下我们开篇中的一个问题: 内核是如何暴露负载数据给应用层的

内核定义了一个伪文件 /proc/loadavg,每当用户打开这个文件的时候,内核中的 loadavg_proc_show 函数就会被调用到,接着访问 avenrun 全局数组变量 并将平均负载从整数转化为小数,并打印出来。

内核中负载的计算过程

继续查看 avenrun 全局数组变量的数据来源。这个数组的计算过程分为如下两步:

1.PerCPU 定期汇总瞬时负载:定时刷新每个 CPU 当前任务数到 calc_load_tasks,将每个 CPU 的负载数据汇总起来,得到系统当前的瞬时负载。 2.定时计算系统平均负载:定时器根据当前系统整体瞬时负载,使用指数加权移动平均法(一种高效计算平均数的算法)计算过去 1 分钟、过去 5 分钟、过去 15 分钟的平均负载。

PerCPU 定期汇总负载

在 Linux 内核中,有一个子系统叫做时间子系统。在时间子系统里,初始化了一个叫高分辨率的定时器。在该定时器中会定时将每个 CPU 上的负载数据(running 进程数 + uninterruptible 进程数)汇总到系统全局的瞬时负载变量 calc_load_tasks 中。整体流程如下图所示.

./images/loadavg2.png

我们把上述流程图展开看一下,我们找到了高分辨率定时器的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//file:kernel/time/tick-sched.c
void tick_setup_sched_timer(void)
{
 //初始化高分辨率定时器 sched_timer
 hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);

 //将定时器的到期函数设置成 tick_sched_timer
 ts->sched_timer.function = tick_sched_timer;
 ...
}

在高分辨率初始化的时候,将到期函数设置成了 tick_sched_timer。通过这个函数让每个 CPU 都会周期性地执行一些任务。其中刷新当前系统负载就是在这个时机进行的。这里有一点要注意一个前提是每个 CPU 都有自己独立的运行队列,。

我们根据 tick_sched_timer 的源码进行追踪,它依次通过调用 tick_sched_handle => update_process_times => scheduler_tick。最终在 scheduler_tick 中会刷新当前 CPU 上的负载值到 calc_load_tasks 上。因为每个 CPU 都在定时刷,所以 calc_load_tasks 上记录的就是整个系统的瞬时负载值。

我们来看下负责刷新的 scheduler_tick 这个核心函数:

1
2
3
4
5
6
7
8
//file:kernel/sched/core.c
void scheduler_tick(void)
{
 int cpu = smp_processor_id();
 struct rq *rq = cpu_rq(cpu);
 update_cpu_load_active(rq);
 ...
}

在这个函数中,获取当前 cpu 以及其对应的运行队列 rq(run queue),调用 update_cpu_load_active 刷新当前 CPU 的负载数据到全局数组中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//file:kernel/sched/core.c
static void update_cpu_load_active(struct rq *this_rq)
{
 ...
 calc_load_account_active(this_rq);
}

//file:kernel/sched/core.c
static void calc_load_account_active(struct rq *this_rq)
{
 //获取当前运行队列的负载相对值
 delta  = calc_load_fold_active(this_rq);
 if (delta)
  //添加到全局瞬时负载值
  atomic_long_add(delta, &calc_load_tasks);
 ...
}

在 calc_load_account_active 中看到,通过 calc_load_fold_active 获取当前运行队列的负载相对值,并把它加到全局瞬时负载值 calc_load_tasks 上。至此,calc_load_tasks 上就有了当前系统当前时间下的整体瞬时负载总数了

我们再展开看看是如何根据运行队列计算负载值的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//file:kernel/sched/core.c
static long calc_load_fold_active(struct rq *this_rq)
{
 long nr_active, delta = 0;

 // R 和 D 状态的用户 task
 nr_active = this_rq->nr_running;
 nr_active += (long) this_rq->nr_uninterruptible;

 // 只返回变化的量
 if (nr_active != this_rq->calc_load_active) {
  delta = nr_active - this_rq->calc_load_active;
  this_rq->calc_load_active = nr_active;
 }

 return delta;
}

哦,原来是同时计算了 nr_running 和 nr_uninterruptible 两种状态的进程的数量。对应于用户空间中的 R 和 D 两种状态的 task 数(进程 OR 线程)。

由于 calc_load_tasks 是一个长期存在的数据。所以在刷新 rq 里的进程数到其上的时候,只需要刷变化的量就行,不用全部重算。因此上述函数返回的是一个 delta。

定时计算系统平均负载

我们找到了系统当前瞬时负载 calc_load_tasks 变量的更新过程。现在我们还缺一个计算过去 1 分钟、过去 5 分钟、过去 15 分钟平均负载的机制。

传统意义上,我们在计算平均数的时候采取的方法都是把过去一段时间的数字都加起来然后平均一下。把过去 N 个时间点的所有瞬时负载都加起来取一个平均数不完事了。这其实是我们传统意义上理解的平均数,假如有 n 个数字,分别是 x1, x2, …, xn。那么这个数据集合的平均数就是 (x1 + x2 + … + xn) / N。

但是如果用这种简单的算法来计算平均负载的话,存在以下几个问题:

1.需要存储过去每一个采样周期的数据 假设我们每 10 毫秒都采集一次,那么就需要使用一个比较大的数组将每一次采样的数据全部都存起来,那么统计过去 15 分钟的平均数就得存 1500 个数据(15 分钟 * 每分钟 100 次) 。而且每出现一个新的观察值,就要从移动平均中减去一个最早的观察值,再加上一个最新的观察值,内存数组会频繁地修改和更新。

2.计算过程较为复杂 计算的时候再把整个数组全加起来,再除以样本总数。虽然加法很简单,但是成百上千个数字的累加仍然很是繁琐。

3.不能准确表示当前变化趋势传统的平均数计算过程中,所有数字的权重是一样的。但对于平均负载这种实时应用来说,其实越靠近当前时刻的数值权重应该越要大一些才好。因为这样能更好反应近期变化的趋势。

所以,在 Linux 里使用的并不是我们所以为的传统的平均数的计算方法,而是采用的一种**指数加权移动平均(Exponential Weighted Moving Average,EMWA)**的平均数计算法。

这种指数加权移动平均数计算法在深度学习中有很广泛的应用。另外股票市场里的 EMA 均线也是使用的是类似的方法求均值的方法。该算法的数学表达式是:a1 = a0 * factor + a * (1 - factor)。这个算法想理解起来有点小复杂,感兴趣的同学可以 Google 自行搜索。

我们只需要知道这种方法在实际计算的时候只需要上一个时间的平均数即可,不需要保存所有瞬时负载值。另外就是越靠近现在的时间点权重越高,能够很好地表示近期变化趋势。

这其实也是在时间子系统中定时完成的,通过一种叫做指数加权移动平均计算的方法,计算这三个平均数。

./images/loadavg3.png

我们来详细看下上图中的执行过程。时间子系统将在时钟中断中会注册时钟中断的处理函数为 timer_interrupt 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//file:arch/ia64/kernel/time.c
void __init
time_init (void)
{
 register_percpu_irq(IA64_TIMER_VECTOR, &timer_irqaction);
 ia64_init_itm();
}

static struct irqaction timer_irqaction = {
 .handler = timer_interrupt,
 .flags = IRQF_DISABLED | IRQF_IRQPOLL,
 .name =  "timer"
};

当每次时钟节拍到来时会调用到 timer_interrupt,依次会调用到 do_timer 函数。

1
2
3
4
5
6
//file:kernel/time/timekeeping.c
void do_timer(unsigned long ticks)
{ 
 ...
 calc_global_load(ticks);
}

其中 calc_global_load 是平均负载计算的核心。它会获取系统当前瞬时负载值 calc_load_tasks,然后来计算过去 1 分钟、过去 5 分钟、过去 15 分钟的平均负载,并保存到 avenrun 中,供用户进程读取。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//file:kernel/sched/core.c
void calc_global_load(unsigned long ticks)
{
 ...
 // 1.获取当前瞬时负载值
 active = atomic_long_read(&calc_load_tasks);

 // 2.平均负载的计算
 avenrun[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);
 ...
}

获取瞬时负载比较简单,就是读取一个内存变量而已。在 calc_load 中就是采用了我们前面说的指数加权移动平均法来计算过去 1 分钟、过去 5 分钟、过去 15 分钟的平均负载的。具体实现的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//file:kernel/sched/core.c
/*
 * a1 = a0 * e + a * (1 - e)
 */
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
 load *= exp;
 load += active * (FIXED_1 - exp);
 load += 1UL << (FSHIFT - 1);
 return load >> FSHIFT;
}

虽然这个算法理解起来挺复杂,但是代码看起来确实要简单不少,计算量看起来很少。而且看不懂也没有关系,只需要知道内核并不是采用的原始的平均数计算方法,而是采用了一种计算快,且能更好表达变化趋势的算法就行。

至此,我们开篇提到的**“负载是如何计算出来的?”**这个问题也有结论了。

Linux 定时将每个 CPU 上的运行队列中 running 和 uninterruptible 的状态的进程数量汇总到一个全局系统瞬时负载值中,然后再定时使用指数加权移动平均法来统计过去 1 分钟、过去 5 分钟、过去 15 分钟的平均负载。

平均负载和CPU使用率

现实工作中,我们经常容易把平均负载和CPU使用率混淆,所以在这里,我也做一个分区。

可能你会疑惑,既然平均负载代表的是活跃进程数,那平均负载高了,不就意味着CPU使用率高吗?

在很老的 Linux 的版本里,统计负载的时候确实是只计算了 runnable 的任务数量,这些进程只对 CPU 有需求。在那个年代里,负载和 CPU 消耗量确实是正相关的。负载越高就表示正在 CPU 上运行,或等待 CPU 执行的进程越多,CPU 消耗量也会越高。

但是前面我们看到了,本文使用的 3.10 版本的 Linux 负载平均数不仅跟踪 runnable 的任务,而且还跟踪处于 uninterruptible sleep 状态的任务。而 uninterruptible 状态的进程其实是不占 CPU 的。

./images/loadavg4.png

所以说,负载高并一定是 CPU 处理不过来,也有可能会是因为磁盘等其他资源调度不过来而使得进程进入 uninterruptible 状态的进程导致的!

为什么要这么修改。我从网上搜到了远在 1993 年的一封邮件里找到了原因,以下是邮件原文。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
From: Matthias Urlichs <urlichs@smurf.sub.org>
Subject: Load average broken ?
Date: Fri, 29 Oct 1993 11:37:23 +0200
 
 
The kernel only counts "runnable" processes when computing the load average.
I don't like that; the problem is that processes which are swapping or
waiting on "fast", i.e. noninterruptible, I/O, also consume resources.
 
It seems somewhat nonintuitive that the load average goes down when you
replace your fast swap disk with a slow swap disk...
 
Anyway, the following patch seems to make the load average much more
consistent WRT the subjective speed of the system. And, most important, the
load is still zero when nobody is doing anything. ;-)


--- kernel/sched.c.orig Fri Oct 29 10:31:11 1993
+++ kernel/sched.c  Fri Oct 29 10:32:51 1993
@@ -414,7 +414,9 @@
    unsigned long nr = 0;
 
    for(p = &LAST_TASK; p > &FIRST_TASK; --p)
-       if (*p && (*p)->state == TASK_RUNNING)
+       if (*p && ((*p)->state == TASK_RUNNING) ||
+                  (*p)->state == TASK_UNINTERRUPTIBLE) ||
+                  (*p)->state == TASK_SWAPPING))
            nr += FIXED_1;
    return nr;
 }

可见这个修改是在 1993 年就引入了。在这封邮件所示的 Linux 源码变化中可以看到,负载正式把 TASK_UNINTERRUPTIBLE 和 TASK_SWAPPING 状态(交换状态后来从 Linux 中删除)的进程也给添加了进来。在这封邮件中的正文中,作者也清楚地表达了为什么要把 TASK_UNINTERRUPTIBLE 状态的进程添加进来的原因。我把他的说明翻译一下,如下:

“内核在计算平均负载时只计算“可运行”进程。我不喜欢那样;问题是正在“快速”交换或等待的进程,即不可中断的 I/O,也会消耗资源。当您用慢速交换磁盘替换快速交换磁盘时,平均负载下降似乎有点不直观…… 无论如何,下面的补丁似乎使负载平均值更加一致 WRT 系统的主观速度。而且,最重要的是,当没有人做任何事情时,负载仍然为零。;-)”

这一补丁提交者的主要思想是平均负载应该表现对系统所有资源的需求情况,而不应该只表现对 CPU 资源的需求

假设某个 TASK_UNINTERRUPTIBLE 状态的进程因为等待磁盘 IO 而排队的话,此时它并不消耗 CPU,但是正在等磁盘等硬件资源。那么它是应该体现在平均负载的计算里的。所以作者把 TASK_UNINTERRUPTIBLE 状态的进程都表现到平均负载里了。

所以,负载高低表明的是当前系统上对系统资源整体需求更情况。如果负载变高,可能是 CPU 资源不够了,也可能是磁盘 IO 资源不够了,所以还需要配合其它观测命令具体分情况分析。

回到平均负载的含义上来,平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数,所以,它不仅包括了正常使用CPU的进程,还包括了等待CPU和等待I/O的进程。

而CPU使用率,是单位时间内CPU的繁忙情况的统计,跟平均负载并不一定完全对应,例如:

  • CPU密集型进程,使用大量CPU会导致平均负载升高,此时这两者是一致的
  • I/O密集型进程,等待I/O也会导致平均负载升高,但CPU使用率不一定很高
  • 大量等待CPU的进程调度也会导致平均负载升高,此时的CPU使用率会很高

总结

./images/loadavg5.png

我把负载工作原理分成了如下三步。

  • 1.内核定时汇总每 CPU 负载到系统瞬时负载
  • 2.内核使用指数加权移动平均快速计算过去1、5、15分钟的平均数
  • 3.用户进程通过打开 loadavg 读取内核中的平均负载

我们再回头来总结一下开篇提到的几个问题。

1.负载是如何计算出来的? 是定时将每个 CPU 上的运行队列中 running 和 uninterruptible 的状态的进程数量汇总到一个全局系统瞬时负载值中,然后再定时使用指数加权移动平均法来统计过去 1 分钟、过去 5 分钟、过去 15 分钟的平均负载。

2.负载高低和 CPU 消耗正相关吗? 负载高低表明的是当前系统上对系统资源整体需求更情况。如果负载变高,可能是 CPU 资源不够了,也可能是磁盘 IO 资源不够了。所以不能说看着负载变高,就觉得是 CPU 资源不够用了。

3.内核是如何暴露负载数据给应用层的? 内核定义了一个伪文件 /proc/loadavg,每当用户打开这个文件的时候,内核中的 loadavg_proc_show 函数就会被调用到,该函数中访问 avenrun 全局数组变量,并将平均负载从整数转化为小数,然后打印出来。

平均负载高分析

  • CPU密集型

  • I/O密集型进程

  • 大量进程的场景

实时监控工具使用

systat

  • mpstat

    mpstat是Multiprocessor Statistics的缩写,是实时监控工具,报告与cpu的一些统计信息这些信息都存在/proc/stat文件中,在多CPU系统里,其不但能查看所有的CPU的平均状况的信息,而且能够有查看特定的cpu信息,mpstat最大的特点是:可以查看多核心的cpu中每个计算核心的统计数据;而且类似工具vmstat只能查看系统的整体cpu情况。

1
2
 mpstat -P ALL 5 20
 # -P ALL 表示监控所有CPU,后面数字5 表示间隔5秒输出一次数据 输出20次

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Linux 4.4.189 (localhost.localdomain) 	07/05/21 	_aarch64_	(6 CPU)

15:35:42     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
15:35:47     all   27.52    0.00   10.54    0.00    0.00    0.52    0.00    0.00    0.00   61.41
15:35:47       0   17.94    0.00   18.92    0.00    0.00    3.44    0.00    0.00    0.00   59.71
15:35:47       1   15.08    0.00   14.15    0.00    0.00    0.00    0.00    0.00    0.00   70.77
15:35:47       2   15.44    0.00   11.98    0.00    0.00    0.00    0.00    0.00    0.00   72.58
15:35:47       3   14.67    0.00    9.78    0.00    0.00    0.00    0.00    0.00    0.00   75.56
15:35:47       4   42.42    0.00    5.12    0.00    0.00    0.00    0.00    0.00    0.00   52.46
15:35:47       5   53.96    0.00    5.27    0.00    0.00    0.00    0.00    0.00    0.00   40.77

字段的含义

%user 在internal时间段里,用户态的CPU时间(%),不包含nice值为负进程 (usr/total)*100 %nice 在internal时间段里,nice值为负进程的CPU时间(%) (nice/total)*100 %sys 在internal时间段里,内核时间(%) (system/total)*100 %iowait 在internal时间段里,硬盘IO等待时间(%) (iowait/total)*100 %irq 在internal时间段里,硬中断时间(%) (irq/total)*100 %soft 在internal时间段里,软中断时间(%) (softirq/total)*100 %idle 在internal时间段里,CPU除去等待磁盘IO操作外的因为任何原因而空闲的时间闲置时间(%) (idle/total)*100

  • pidstat
1
./pidstat -r

字段的含义

PID:进程标识符

Minflt/s:任务每秒发生的次要错误,不需要从磁盘中加载页

Majflt/s:任务每秒发生的主要错误,需要从磁盘中加载页

VSZ:虚拟地址大小,虚拟内存的使用KB

RSS:常驻集合大小,非交换区五里内存使用KB

Command:task命令名

1
./pidstat -w | grep -E "Command|libra*|DEH5*"

字段的含义

PID:进程id

Cswch/s:每秒主动任务上下文切换数量

Nvcswch/s:每秒被动任务上下文切换数量

Command:命令名

  • iostat

iostat主要用于建库系统设备的io负载情况,sostat首次运行时会显示自系统启动开始的各项统计信息,之后UI女性ipstat将显示自上次运行该命令以后的统计信息,用户可以通过指定统计的次数和时间来获得所需的统计信息

语法:iostat [ -c ] [ -d ] [ -h ] [ -N ] [ -k | -m ] [ -t ] [ -V ] [ -x ] [ -z ] [ device […] | ALL ] [ -p [ device [,…] | ALL ] ] [ interval [ count ] ]

命令使用 [root@yankerp ~]# iostat -d -k 2 6

1
2
3
4
./iostat -d -k 2 6
# 参数-d表示显示设备磁盘的使用状态;-k表示某些使用block为单位的列强制使用kilobytes为单位,2表示数据每隔2秒刷新一次 6表示一共刷新6次
./iostat -d -k 2 6
# 把K单位换成M

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Linux 4.4.189 (localhost.localdomain) 	07/05/21 	_aarch64_	(6 CPU)

Device             tps    kB_read/s    kB_wrtn/s    kB_dscd/s    kB_read    kB_wrtn    kB_dscd
loop0             0.05         2.50         0.00         0.00      44150          0          0
loop1             0.01         0.23         0.00         0.00       4135          0          0
loop10            0.01         0.24         0.00         0.00       4245          0          0
loop11            0.05         3.42         0.00         0.00      60297          0          0
loop12            0.00         0.07         0.00         0.00       1233          0          0
loop13            0.02         0.60         0.00         0.00      10576          0          0
loop14            0.01         0.13         0.00         0.00       2312          0          0
loop15            0.01         0.25         0.00         0.00       4350          0          0
loop16            0.01         0.35         0.00         0.00       6164          0          0
loop17            0.02         1.19         0.00         0.00      21004          0          0
loop18            0.01         0.14         0.00         0.00       2500          0          0
loop2             0.01         0.23         0.00         0.00       4129          0          0
loop3             0.00         0.02         0.00         0.00        291          0          0
loop4             0.00         0.02         0.00         0.00        371          0          0
loop5             0.00         0.00         0.00         0.00          8          0          0
loop6             0.00         0.01         0.00         0.00        216          0          0
loop7             0.00         0.04         0.00         0.00        656          0          0
loop8             0.01         0.12         0.00         0.00       2162          0          0
loop9             0.01         0.10         0.00         0.00       1848          0          0
mmcblk1           1.74        14.94        23.95         0.00     263683     422668          0
mmcblk1boot0      0.00         0.01         0.00         0.00        108          0          0
mmcblk1boot1      0.00         0.01         0.00         0.00        108          0          0

输出信息意义 tps:该设备每秒的传输次数(Indicate the number of transfers per second that were issued to the device.)。“一次传输"意思是"一次I/O请求”。多个逻辑请求可能会被合并为"一次I/O请求"。“一次传输"请求的大小是未知的。 kB_read/s:每秒从设备(drive expressed)读取的数据量; kB_wrtn/s:每秒向设备(drive expressed)写入的数据量; kB_read:读取的总数据量; kB_wrtn:写入的总数量数据量;这些单位都为Kilobytes。

使用iostat查看cpu统计信息使用-C参数

top

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
top - 19:20:09 up 2 days,  8:36,  1 user,  load average: 9.88, 9.21, 9.03
Tasks: 204 total,   2 running, 202 sleeping,   0 stopped,   0 zombie
%Cpu(s): 30.7 us, 12.4 sy,  0.0 ni, 56.3 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
KiB Mem :  2005872 total,   340276 free,   687916 used,   977680 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   705768 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND     
 2650 root      20   0 3905652 268060  18504 S  93.9 13.4   3149:02 libra       
 3989 root      20   0 4354532 286032 118244 R  80.1 14.3 295:06.63 DEH5Bin     
 2070 root      20   0  693096  16804   1408 S  16.1  0.8 462:05.73 npu_transf+ 
  896 root      20   0  357220   8812    800 S  10.6  0.4 333:11.49 npu_transf+ 
 1680 root      20   0  115300  14456   7072 S  10.3  0.7 339:48.94 bumble.arm+ 
 2118 root      20   0  190408  80972      8 S   7.1  4.0 257:56.98 flowservice 
    1 root      20   0  161340   5924   3276 S   1.6  0.3  60:14.68 systemd

top 输出界面的顶端,也显示了系统整体的内存使用情况,这些数据跟 free 类似,我就不再重复解释。我们接着看下面的内容,跟内存相关的几列数据,比如 VIRT、RES、SHR 以及 %MEM 等。

这些数据,包含了进程最重要的几个内存使用情况。

  • VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
  • RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
  • SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
  • %MEM 是进程使用物理内存占系统总内存的百分比。

除了要认识这些基本信息,在查看 top 输出时,你还要注意两点。

第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。

第二,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。

free

1
2
3
4
root@localhost:~# free
              total        used        free      shared  buff/cache   available
Mem:        2005872      687308      339996      590336      978568      706268
Swap:             0           0           0

free 输出的是一个表格,其中的数值都默认以字节为单位。表格总共有两行六列,这两行分别是物理内存 Mem 和交换分区 Swap 的使用情况,而六列中,每列数据的含义分别为:

  • 第一列,total 是总内存大小;
  • 第二列,used 是已使用内存的大小,包含了共享内存;
  • 第三列,free 是未使用内存的大小;
  • 第四列,shared 是共享内存的大小;
  • 第五列,buff/cache 是缓存和缓冲区的大小;
  • 最后一列,available 是新进程可用内存的大小。

最后一列的可用内存 available 。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。

参考

别再纠结线程池大小 + 线程数量了,没有固定公式的!

一文理解 Linux 平均负载,附排查工具

Linux性能分析工具汇总合集

详解mpstat、iostat、sar、vmstat命令的使用

一篇文章告诉你,平均负载的来龙去脉

Linux kworker 占用CPU过高