性能文章>浅谈 Linux 高负载的系统化分析>

浅谈 Linux 高负载的系统化分析原创

1年前
7515213

讲解 Linux Load 高如何排查的话题属于老生常谈了,但多数文章只是聚焦了几个点,缺少整体排查思路的介绍。所谓 “授人以鱼不如授人以渔"。本文试图建立一个方法和套路,来帮助读者对 Load 高问题排查有一个更全面的认识。

从消除误解开始

没有基线的 Load,是不靠谱的 Load

从接触 Unix/Linux 系统管理的第一天起,很多人就开始接触 System Load Average 这个监控指标了,然而,并非所有人都知道这个指标的真正含义。一般说来,经常能听到以下误解: - Load 高是 CPU 负载高…

传统 Unix 于 Linux 设计不同。Unix 系统,Load 高就是可运行进程多引发的,但对 Linux 来说不是。对 Linux 来说 Load 高可能有两种情况: - 系统中处于 R 状态的进程数增加引发的 - 系统中处于 D 状态的进程数增加引发的 - Loadavg 数值大于某个值就一定有问题…

Loadavg 的数值是相对值,受到 CPU 和 IO 设备多少的影响,甚至会受到某些软件定义的虚拟资源的影响。Load 高的判断需要基于某个历史基线 (Baseline),不能无原则的跨系统去比较 Load。 - Load 高系统一定很忙…

Load 高系统可以很忙,例如 CPU 负载高,CPU 很忙。但 Load 高,系统不都很忙,如 IO 负载高,磁盘可以很忙,但 CPU 可以比较空闲,如 iowait 高。这里要注意,iowait 本质上是一种特殊的 CPU 空闲状态。另一种 Load 高,可能 CPU 和磁盘外设都很空闲,可能支持锁竞争引起的,这时候 CPU 时间里,iowait 不高,但 idle 高。

如何排查 Load 高的问题

如前所述,由于在 Linux 操作系统里,Load 是一个定义及其含混的指标,排查 loadavg 高就是一个很复杂的过程。其基本思路就是,根据引起 Load 变化的根源是 R 状态任务增多,还是 D 状态任务增多,来进入到不同的流程。

这里给出了 Load 增高的排查的一般套路,仅供参考:

image.png

在 Linux 系统里,读取 /proc/stat 文件,即可获取系统中 R 状态的进程数;但 D 状态的任务数恐怕最直接的方式还是使用 ps 命令比较方便。而/proc/stat 文件里 procs_blocked 则给出的是处于等待磁盘 IO 的进程数:

$cat /proc/stat
.......
processes 50777849
procs_running 1
procs_blocked 0
......

通过简单区分 R 状态任务增多,还是 D 状态任务增多,我们就可以进入到不同的排查流程里。下面,我们就这个大图的排查思路,做一个简单的梳理。

R 状态任务增多

即通常所说的 CPU 负载高。此类问题的排查定位主要思路是系统,容器,进程的运行时间分析上,找到在 CPU 上的热点路径,或者分析 CPU 的运行时间主要是在哪段代码上。

CPU user 和 sys 时间的分布通常能帮助人们快速定位与用户态进程有关,还是与内核有关。另外,CPU 的 run queue 长度和调度等待时间,非主动的上下文切换 (nonvoluntary context switch) 次数都能帮助大致理解问题的场景。

因此,如果要将问题的场景关联到相关的代码,通常需要使用 perf,systemtap, ftrace 这种动态的跟踪工具。

关联到代码路径后,接下来的代码时间分析过程中,代码中的一些无效的运行时间也是分析中首要关注的,例如用户态和内核态中的自旋锁 (Spin Lock)。

当然,如果 CPU 上运行的都是有非常意义,非常有效率的代码,那唯一要考虑的就是,是不是负载真得太大了。

D 状态任务增多

根据 Linux 内核的设计, D 状态任务本质上是 TASK_UNINTERRUPTIBLE 引发的主动睡眠,因此其可能性非常多。但是由于 Linux 内核 CPU 空闲时间上对 IO 栈引发的睡眠做了特殊的定义,即 iowait,因此 iowait 成为 D 状态分类里定位是否 Load 高是由 IO 引发的一个重要参考。

当然,如前所述, /proc/stat 中的 procs_blocked 的变化趋势也可以是一个非常好的判定因 iowait 引发的 Load 高的一个参考。

CPU iowait 高

很多人通常都对 CPU iowait 有一个误解,以为 iowait 高是因为这时的 CPU 正在忙于做 IO 操作。其实恰恰相反, iowait 高的时候,CPU 正处于空闲状态,没有任何任务可以运行。只是因为此时存在已经发出的磁盘 IO,因此这时的空闲状态被标识成了 iowait ,而不是 idle。

但此时,如果用 perf probe 命令,我们可以清楚得看到,在 iowait 状态的 CPU,实际上是运行在 pid 为 0 的 idle 线程上:

$ sudo perf probe -a account_idle_ticks
$sudo perf record -e probe:account_idle_ticks -ag sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.418 MB perf.data (843 samples) ]

$sudo perf script
swapper     0 [013] 5911414.451891: probe:account_idle_ticks: (ffffffff810b6af0)
             2b6af1 account_idle_ticks (/lib/modules/3.10.0/build/vmlinux)
             2d65d9 cpu_startup_entry (/lib/modules/3.10.0/build/vmlinux)
             24840a start_secondary (/lib/modules/3.10.0/build/vmlinux)

相关的 idle 线程的循环如何分别对 CPU iowait 和 idle 计数的代码,如下所示:

/*       

 * Account multiple ticks of idle time.

 * @ticks: number of stolen ticks

 */   

void account_idle_ticks(unsigned long ticks)

{        

    if (sched_clock_irqtime) {

        irqtime_account_idle_ticks(ticks);

        return;

    }   

    account_idle_time(jiffies_to_cputime(ticks)); 

}        

         

/*

 * Account for idle time.

 * @cputime: the cpu time spent in idle wait

 */

void account_idle_time(cputime_t cputime)

{

    u64 *cpustat = kcpustat_this_cpu->cpustat;

    struct rq *rq = this_rq();

 

    if (atomic_read(&rq->nr_iowait) > 0)

        cpustat[CPUTIME_IOWAIT] += (__force u64) cputime;

    else

        cpustat[CPUTIME_IDLE] += (__force u64) cputime;

}

而 Linux IO 栈和文件系统的代码则会调用 io_schedule,等待磁盘 IO 的完成。这时候,对 CPU 时间被记为 iowait 起关键计数的原子变量rq->nr_iowait 则会在睡眠前被增加。注意,io_schedule 在被调用前,通常 caller 会先将任务显式地设置成 TASK_UNINTERRUPTIBLE 状态:

/*           

 * This task is about to go to sleep on IO. Increment rq->nr_iowait so

 * that process accounting knows that this is a task in IO wait state.

 */          

void __sched io_schedule(void)

{

    io_schedule_timeout(MAX_SCHEDULE_TIMEOUT);

}            

EXPORT_SYMBOL(io_schedule);

             

long __sched io_schedule_timeout(long timeout)

{            

    int old_iowait = current->in_iowait;

    struct rq *rq; 

    long ret;

             

    current->in_iowait = 1; 

    if (old_iowait)

        blk_schedule_flush_plug(current);

    else 

        blk_flush_plug(current);



    delayacct_blkio_start();

    rq = raw_rq();

    atomic_inc(&rq->nr_iowait);

    ret = schedule_timeout(timeout);



    current->in_iowait = old_iowait;

    atomic_dec(&rq->nr_iowait);

    delayacct_blkio_end();

             

    return ret; 

}            

EXPORT_SYMBOL(io_schedule_timeout);

CPU idle 高

如前所述,有相当多的内核的阻塞,即 TASK_UNINTERRUPTIBLE 的睡眠,实际上与等待磁盘 IO 无关,如内核中的锁竞争,再如内存直接页回收的睡眠,又如内核中一些代码路径上的主动阻塞,等待资源。

image.png

因此,CPU idle 高的分析,实质上就是分析内核的代码路径引起阻塞的主因是什么。通常,我们可以使用 perf inject 对 perf record 记录的上下文切换的事件进行处理,关联出进程从 CPU 切出 (swtich out) 和再次切入 (switch in) 的内核代码路径,生成一个所谓的 Off CPU 火焰图.

当然,类似于锁竞争这样的比较简单的问题,Off CPU 火焰图足以一步定位出问题。但是对于更加复杂的因 D 状态而阻塞的延迟问题,可能 Off CPU 火焰图只能给我们一个调查的起点。

例如,当我们看到,Off CPU 火焰图的主要睡眠时间是因为 epoll_wait 等待引发的。那么,我们继续要排查的应该是网络栈的延迟,即本文大图中的 Net Delay 这部分。

至此,你也许会发现,CPU iowait 和 idle 高的性能分析的实质就是 延迟分析。这就是大图按照内核中资源管理的大方向,将延迟分析细化成了六大延迟分析:

  • CPU 延迟

  • 内存延迟

  • 文件系统延迟

  • IO 栈延迟

  • 网络栈延迟

  • 锁及同步原语竞争

任何上述代码路径引发的 TASK_UNINTERRUPTIBLE 的睡眠,都是我们要分析的对象!

以问题结束

限于篇幅,本文很难将其所涉及的细节一一展开,因为读到这里,你也许会发现,原来 Load 高的分析,实际上就是对系统的全面负载分析。怪不得叫 System Load 呢。这也是 Load 分析为什么很难在一篇文章里去全面覆盖。

本文转自公众号:Linux阅码场,作者:杨勇 (Oliver Yang),Linux 内核工程师,来自阿里云系统组。

分类:
标签:
请先登录,再评论

那个排查思路图看不清楚😂

1年前
回复 小破鸟儿:

可以添加小马微信(perfma)小马分享原图给大大

1年前回复

赞干货,加油👍

1年前

为你推荐

在调试器里看LINUX内核态栈溢出
图灵最先发明了栈,但没有给它取名字。德国人鲍尔也“发明”了栈,取名叫酒窖。澳大利亚人汉布林也“发明”了栈,取名叫弹夹。1959年,戴克斯特拉在度假时想到了Stack这个名字,后来被广泛使用。
LONG究竟有多长,从皇帝的新衣到海康SDK
转眼之间初中毕业30年了,但我仍清楚的记得初中英语的一篇课文,题目叫《皇帝的新装》(“The king’s new clothes”)。这篇课文的前两句话是:”Long long ago, there
雕刻在LINUX内核中的LINUS故事
因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人。虽然大家可能都听过钱钟书先生的名言:“假如你吃个鸡蛋觉得味道不错,又何必认识那个下蛋的母鸡呢?” 但是如果真是遇到一个“特别显赫”
如何使用Linux内核中没有被导出的变量或函数?
本文详细介绍了使用EXPORT_SYMBOL宏导出函数或变量、使用kallsyms_lookup_name()查找函数或变量的虚拟地址以及内核模块中直接使用内核函数的虚拟地址等3种方案解决没有被EXPORT_SYMBOL 相关的宏导出的变量或函数不能直接使用的问题
LINUX网络子系统中DMA机制的实现
我们先从计算机组成原理的层面介绍DMA,再简单介绍Linux网络子系统的DMA机制是如何的实现的。 计算机组成原理中的DMA 以往的I/O设备和主存交换信息都要经过CPU的操作。不论是最早的轮询方式,
内存泄漏(增长)火焰图
本文总结了在分析内存增长和内存泄漏问题用到的4种追踪方法得到有关内存使用情况的代码路径,使用栈追踪技术对代码路径进行检查,并且会以火焰图的形式把它们可视化输出,在Linux上演示分析过程,随后概述其它系统的情况。
为什么容器内存占用居高不下,频频 OOM(续)
在之前的文章《[为什么容器内存占用居高不下,频频 OOM](https://heapdump.cn/article/1589003)》 中,我根据现状进行了分析和说明,收到了很多读者的建议和疑
通过生产者与消费者模型感受死锁
一. 实验目的及实验环境 1.实验目的通过观察、分析实验现象,深入理解产生死锁的原因,学会分析死锁的方法, 并利用 pstack、 gdb 或 core 文件分析( valgrind (DRD+Hel