前言:最近的工作中涉及到了太多Linux相关调试方法与原理性的问题,但是前期工作太忙一直没来得及好好学习,仔细钻研,最近项目算是告一段落,因此希望能借此机会精进一下技术并好好总结一些方法与原理

CPU相关性能调试基本原理

在Linux系统中,影响性能的指标主要为CPU、内存、IO、网络,每个模块都有其特定的调试方式和工具。与CPU相关的主要调试方向为进程和线程、软硬件的中断、和上下文的切换情况。

进程和线程

进程是资源拥有的基本单位,线程是调度的基本单位。进程与线程在内核中使用的结构体都为task_struct,二者间区别主要是进程的pid=tgid,线程的pid!=tgid。

进程状态:

  1. TASK_RUNNING 并不是说进程正在运行,而是表示进程在时刻准备运行的状态
  2. TASK_INTERRUPTIBLE 因等待事件(比如IO事件)而进入睡眠
  3. TASK_UNINTERRUPTIBLE 因等待事件(比如IO事件)而进入睡眠,不可以被信号唤醒

调度

调度策略

普通调度策略

SCHED_NORMAL:普通进程
SCHED_BATCH:后台进程
SCHED_IDLE:空闲进程

实时调度策略

SCHED_FIFO:高优先级的进程可以抢占低优先级的进程,而相同优先级的进程,先到先得
SCHED_RR:高优先级的进程可以抢占低优先级的进程,而相同优先级的进程,轮换着来
SCHED_DEADLIN7E

调度优先级

实时进程:0 ~ 99

普通进程:100 ~ 139

调度器类

Fair

常用的策略为:SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE

完全公平算法 — CFS

CFS对应的调度策略:SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE。 CFS 会为每一个进程安排一个虚拟运行时间 vruntime。如果一个进程在运行,随着时间的增加,进程的 vruntime 将不断增大。没有得到执行的进程 vruntime 不变。 显然,那些 vruntime 少的,原来受到了不公平的对待,需要给它补上,所以会优先运行这样的进程。 你可能会说,不还有优先级呢?如何给优先级高的进程多分时间呢?按比例!

Real_Time

常用的策略为:SCHEDFIFO 和 SCHEDRR

中断

硬件中断的处理目前都算比较快,所以目前的调试都不会特别关注硬件中断的过程,主要关注的是软件中断的过程,因为会占用比较大量的时间

上下文切换

cpu上下文:包含cpu通用寄存器和PC指针等\

进程上下文切换

1.同一进程的系统调用

两次上下文切换:
用户态 -> 内核态;
内核态 -> 用户态;

2.不同进程的调度切换

进程上下文切换除了cpu通用寄存器、PC指针外,还包含了内核栈、全局变量、虚拟地址空间、TLB缓存切换(失效)等。

线程上下文切换

中断上下文切换

性能指标1——CPU使用率

CPU的使用率,就是一段时间内有进程在CPU上运行的时间占总时间的比例,包含:用户CPU、系统CPU、iowait、硬件中断、软件中断等。

相关工具

top

top工具的输出一般像下面这样:

1
2
3
4
5
6
7
top - 23:50:38 up  5:13,  1 user,  load average: 0.00, 0.00, 0.00
Tasks: 355 total, 1 running, 257 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 16350296 total, 12577212 free, 1452916 used, 2320168 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 14469300 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1477 embedfi+ 20 0 897564 268572 90336 S 0.7 1.6 0:06.02 Xorg

top的输出:
us: un-niced用户进程使用的cpu时间;
sy:系统的内核进程
ni: 被调整过nice值的进程占用的CPU使用率;
wa:就是IO-wait;
hi:hardware Interrupt
si:software interrupt

mpstat

mpstat的输出和top不一样:
%usr: 用户进程使用的cpu时间(包含un-niced和niced);
%nice: niced用户进程使用的cpu时间

显示所有CPU的指标,每秒一次

1
mpstat -P ALL 1

pidstat

pidstat输出:
%user表示用户进程使用的cpu时间(包含un-niced和niced);
%wait表示任务等待运行时所占用的CPU百分比。

显示所有进程的CPU指标,每秒一次:

1
pidstat -u 1

gdb

perf

实时显示CPU时钟占用最多的函数或指令

平均负载最理想的指标值是等于CPU的个数。

调试技巧

  1. us cpu 占用高,说明用户态进程占用了较多cpu,着重排查应用程序本身的代码逻辑问题;
  2. sy cpu占用高,说明内核态代码占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题;
  3. wa cpu占用高,说明等待 I/O 完成所花的时间比较长,所以应该着重排查linux系统是不是存在IO相关的性能瓶颈;
  4. hi和si占用高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序,一般是网络;
  5. 系统整体cpu使用较高,而实际的单个进程的cpu使用都不高,要考虑短时进程是否被频繁创建和销毁;

性能指标2——平均负载

平均负载是指单位时间内,系统处于可运行状态(Running)和不可中断等待状态(uninterruptible)的平均进程数

平均负载为3,意味着:
1、在只有3个CPU 的系统上,意味着所有的CPU都刚好被进程完全占用;
2、在6个CPU的系统上,意味着CPU有50% 的空闲;
3、在只有1个CPU 的系统中,则意味着2/3的进程竞争不到CPU;

平均负载是一个综合性的指标,需要通过整体变化趋势来看系统是否有压力

相关工具

uptime

top

dstat -y

调优技巧

  1. 平均负载高可能是cpu密集型进程导致的;
  2. 平均负载高并不一定代表 CPU 使用率高,还有可能是等待I/O的进程变多了;
  3. 平均负载高的时候,需要辅助其他的工具来做进一步的分析;

性能指标3——上下文切换

如果系统的上下文切换次数比较稳定,那么理想数据是1万以内

相关工具

vmstat

cs列:系统每秒上下文切换的次数。
r列:处于可运行态的进程数量
b列:处于不可中断睡眠状态的进程数量

pidstat

显示进程的每秒自愿和非自愿上下文切换次数:

1
pidstat -w

cswch/s:进程每秒自愿上下文切换的总数
nvcswch/s:进程每秒非自愿上下文切换的总数

/proc/interrupts

/proc/softirqs

调优技巧

如果进程的自愿上下文切换多了,表示进程在等待资源;
如果进程的非自愿上下文切换多了,说明进程在被强制调度(被实时性更高的进程抢占);
如果中断次数多了,说明中断处理程序在占用大量的cpu;
如果软中断次数多了,说明下半部处理程序在占用大量的cpu,一般是网络;

调优工具

top

数据及指标

us: 代表用户态cpu时间,不包含被调 整过nice值的进程所占的cpu时间;
ni: 代表被调整过nice值的进程占用的cpu时间; 
sy: 代表内核态cpu时间
id: 空闲时间,注意,它不包括等待 I/O 的时间(iowait)
wa: 代表等待I/O的cpu时间
hi: 代表硬件中断占据的cpu时间
si: 代表软件中断占据的cpu时间

[例子]

1、stress命令起1个进程:
stress -c 1
2、top查看
3、renice
renice -n 5 -p 26205
4、top查看可见进程26205的cpu用量由原来的统计到us变成了统计到ni上

可添加的参数

-p {pid}:只显示某个进程的状态

以下内容中【交互】表示使用top命令实时显示数据时输入对应的选项会将实时显示的数据进行对应的变化

【交互】h: 显示帮助
【交互】c: 切换显示完整的命令行
【交互】M:根据常驻内存(RES)用量进行排序
【交互】P: 根据CPU使用百分比大小进行排序
【交互】S:切换到累加模式
【交互】T:根据时间或者累计时间进行排序(TIME+列)
【交互】s:改变两次刷新的延迟时间,默认是3s
【交互】r:修改某个进程的nice值(对应top的NI列)

ps

显示所有进程:

1
ps -ef

显示所有线程:

1
ps -eLf或ps -eTf

ps统计的是进程的整个生命周期,top是实时的消耗,默认是三秒内

ps的可选项:

-e:选择所有进程

-o:用于设定输出格式
例如: -o stat,ppid,pid,cmd 表示只输出进程的stat(状态信息)、ppid(父进程pid)、pid(当前进程的pid),cmd(即进程的可执行文件)
-L Show threads, possibly with LWP and NLWP columns
-T Show threads, possibly with SPID column
-m Show threads after processes.
-f 全格式输出
-a 选择所有进程,除了session leader(见getsid(2))和与terminal不相关的进程。
-A, 选择所有进程(同-e)

mpstat

用法:

mpstat -P ALL 1

sar

用法

前提条件: 1. sudo vi /etc/default/sysstat  //把false修改为true 2. sudo service sysstat restart  //重启sysstat服务

pidstat

%usr: 进程在用户态执行的cpu时间 %system: 进程在内核态执行的cpu时间 %wait: 进程等待运行时所花费的CPU时间

用法

每隔1秒输出一组进程的cpu数据: $ pidstat -u 1

-p {pid} 指定查看某个进程的信息
-U {usrname} 显示属于这个用户的进程
-r:内存

-d: IO
kB_rd/s:该进程每秒从磁盘读取的数据大小
kB_wr/s:该进程每秒写入磁盘的数据大小
kB_ccwr/s:每秒取消的写请求数据大小

iodelay:块 I/O 延迟,包括等待同步块 I/O 和换入块 I/O 结束的时间,单位是时钟周期。

-u: cpu(默认)

-R: 进程的realtime priority and scheduling policy

-w:进程的上下文切换信息

cswch/s:每秒自愿进行上下文切换的次数

1、所谓自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换; 2、而非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。
nvcswch/s:表示每秒非自愿上下文切换的次数

-v:进程相关的线程数和文件描述符数量

-s:进程stack所用内存信息

perf

用法

perf list:
列出所有能够触发perf采样点的事件,类似/sys/kernel/debug/tracing/available_events的输出
实测发现,perf 支持的事件要比ftrace多一倍左右。

perf probe:
定义新的动态tracepoint

—add:添加一个probe event

例如:perf probe —add do_sys_open
—del:删除probe event
例如:perf probe —del probe:do_sys_open
例子:perf record -e probe:do_sys_open -aR sleep 10

perf trace:
类似strace,不过性能更佳,例如:perf trace ls

perf stat:
运行命令并收集性能统计信息

perf top:
可以实时查看当前系统进程函数占用率情况

perf record:
运行命令并保存profile到perf.data

-p {pid} 记录进程的events
-a:从所有cpu上进行采集
-e {event}:指定PMU(处理器监控单元) event ,默认是cycles:ppp(CPU周期数)
-g:启用调用图(堆栈链/回溯)记录
-F {freq}:采样频率

例如:
perf record -p 12069 -a -g -F 99 — sleep 10
perf record -p 12069 -a -g -F 999 — sleep 10
perf record -g -e cpu-clock ./perftest

perf report:
从perf.data读取并显示profile

—no-children:不统计Children开销

Self:Self 记录的是最后一列的符号(可以理解为函数)本身的采样数占总采样数的百分比
目的:找到最底层的热点函数

Children:记录的是这个符号调用的其他符号(理解为子函数,包括直接调用和间接调用)的采样数之和占总采样数的百分比
目的:找到较高层的热点函数

perf script:
从perf.data读取并显示详细的采样数据

perf kmem:
跟踪/测量内核内存属性

record:记录kmem events

—slab:记录slab申请器的events

—page:记录page 申请器的events

stat:报告内核内存统计信息

—slab:统计slab申请器的events

—page:统计page 申请器的events

perf mem:
分析内存访问

perf lock:
分析锁性能

perf kvm:
针对kvm虚拟化分析

perf sched:
分析内核调度器性能

record:采集和记录scheduling events

例如(全局):perf sched record — sleep 10
例如(进程):perf sched record -p 752 — sleep 10

script:报告采集到的事件

latency:报告每个任务的调度延迟和进程的其他调度属性

timehist:提供调度事件的分析报告

火焰图

火焰图的横轴和纵轴的含义: - 横轴表示采样数和采样比例。一个函数占用的横轴越宽,就代表它的执行时间越长。同一层的多个函数,则是按照字母来排序。 - 纵轴表示调用栈,由下往上根据调用关系逐个展开。换句话说,上下相邻的两个函数中,下面的函数,是上面函数的父函数。这样,调用栈越深,纵轴就越高。 火焰图不包含任何时间的因素,所以并不能看出横向各个函数的执行次序。

场景

寻找热点函数,定位性能瓶颈

具体实现是对事件进行采样,然后再根据采样数,评估各个函数的调用频率,

perf 可以用来分析 CPU cache、CPU 迁移、分支预测、指令周期等各种硬件事件

perf 也可以只对感兴趣的事件进行动态追踪

实践过程

寻找热点函数,定位性能瓶颈

自定义追踪函数

1、添加 do_sys_open 探针 $ perf probe —add do_sys_open 2、采样和追踪 $ perf record -e probe:do_sys_open -aR sleep 1 3、查看采样结果 $ perf script 4、删除探针 $ perf probe —del probe:do_sys_open

pstree

用法

经典用法: $ pstree -p 5638 显示5638这个进程的进程树(包含线程) $ pstree -T -p 5638 显示5638这个进程的进程树(不包含线程)

-a 显示命令行参数 If the command line of a process is swapped out,则该进程将显示在括号中,例如类似这样: -{kubeensaas}(8)

-c 禁止压缩子树(压缩后不显示子树信息)

-n 通过pid而不是name对相同祖先的进程排序

-g 显示PGIDs

-p 显示某个进程的进程树(包含线程)

-T 隐藏线程、只显示进程

taskset

用法

-pc 0x3 {pid}:绑定cpu0和cpu1到进程

-pc {pid}:查看进程绑定的cpu(输出为3,也就是011,表示第0,1个cpu)

cpulimit

用法

-p {pid} -l {percent}:进程允许的cpu用量为percent%

-k:如果进程cpu超量,直接杀掉进程而不是限制cpu使用(默认);

-m:输出统计信息;

pstack

$ pstack 11613 11613: ./jin pstack: Input/output error failed to read target. 【解决】 参考此处: https://blog.csdn.net/u010164190/article/details/111059283

用法

pstack{pid}对指定PID的进程输出函数调用栈

场景

应用并未崩溃,如何查看stack trace信息?

strace

用法

-p {pid}
-f 跟踪子进程
-t 在输出中的每一行前加上时间信息
-T 显示每一个系统调用所耗的时间
-c 统计每一个系统调用的调用次数、错误次数、执行时间和执行时间占比

场景

正在运行的程序实际读取的是哪个配置文件?

程序好像hang住了,具体是什么情况,为什么hang住?hang在了哪里?

进程运行很慢,但是没有源代码,想看看时间都花在了哪里?

容器环境下,如何对应用程序的网络行为进行调试和追踪?

stap

用法

stap —all-modules dropwatch.stp

/proc

用法

通过子进程的Pid得到父进程的Pid:cat /proc/{pid}/status | grep PPid

调试方法

用户cpu使用率较高 checklist

分析过程

  1. 通过top命令查看系统整体的cpu使用率和平均负载
  2. pidstat -u 1| more 查看进程的cpu使用率,找到可疑进程
  3. pstree -p {pid}查看进程的进程结构(继承关系)
  4. strace -f -p {pid} 追踪进程的系统调用情况,是否存在频繁的系统调用?
  5. pstack {pid}找到代码瓶颈点

软中断cpu使用率较高,Checklist

分析过程

  1. 通过top命令查看系统整体的cpu使用率和平均负载
  2. watch -d cat /proc/softirqs 找到瓶颈所在的软件中断
  3. perf record -g 采集内核事件
  4. perf report分析事件,找到瓶颈所在的内核函数代码

cpu使用率较高,Checklist

分析过程

  1. 通过top命令查看系统整体的cpu使用率和平均负载
  2. pidstat -u 1| more
    查看进程的cpu使用率,找到可疑进程
  3. iostat查看系统整体的I/O情况
  4. iotop查看进程的I/O压力情况
  5. strace -f -p {pid} 追踪进程的系统调用情况,是否存在频繁的系统调用?