linux 工作中常用的性能检查 - 结果导出 - 文本处理

emmm。。。我这 18 线城市的运维,经常得对服务器进行性能检查,并把结果进行导出。大多数时候服务器都是没啥问题的,所以时间都费在复制粘贴上了,如果把那些持续运行的命令结果直接导出,然后全部合并在一起以格式化输出不就省事了?

cpu 性能检查——top 与 ps 之争!

top 可以看 cpu 占用率这个大家应该都知道。但 ps 其实也可以看。因为当我们运行 ps aux 的时候:


这一排 cpu 占用率实际上是可以利用起来的:

1
ps aux|sed 1d|awk '{a+=$3}END{print a"%"}'

这一行命令下去,就可以直接得出当时的 cpu 占用率了,相比从茫茫的 top 运行屏幕中把结果摘出来,是不是方便了不少?

且慢!

实际上,当你用多了这个命令,不难发现几个奇怪的地方:

  1. 当负载起来以后,如果恰好你的机器是多核心的话。。。就会发现,出来的数字怎么就大于 100% 了呢?
  2. 负载下去以后,发现出来的数字并没有马上降下去,但是多敲几次命令的话,会发现越到后面数字越低,但很长一段时间都不会是低负载时的哪个占用率,奇怪,此时 cpu 占用率应该已经接近 0 了呀?
  3. 发现 root 下运行出来的结果和非 root 下运行的结果居然差了 10 个百分点?

实际上,这几个问题的答案,就能充分反映出 ps 和 top 两个命令之间的机制差异:

  • top 的 cpu 最大值是 100,而 ps 统计出来的 cpu 最大值上限是 100xCPU 核心总数的值。假设一台机器有两个 cpu,每个 cpu 有四个核心,那么 ps aux 统计出的 cpu 最大值上限就是 100*2*4=800;

  • 参考linux - ps and top give different CPU usage - Super User

    from man top:

    1. %CPU – CPU Usage The task’s share of the elapsed CPU time since the last screen update, expressed as a percentage of total CPU time.

    from man ps:

    CPU usage is currently expressed as the percentage of time spent running during the entire lifetime of a process.

    翻译成人话就是:top 的结果更即时,严格反映出当前时间点的 cpu 占用;而 ps 中进程的 cpu 占用率是当前进程全生命时间占用率的平均值。

  • 在非 root 账户下 ps 输出的结果不包含 root 状态下运行进程的 cpu 占用。

所以嘛,平时对服务器的检查究竟用哪个比较合适呢?

思来想去,我觉得其实都可以。虽然 ps 不包含 root 下的进程,但企业用的服务器为了安全着想,本就不会在 root 下运行高占用的进程;而平时 cpu 占用率检查的意义,一个是查看当前时间点的 cpu 占用率是否过高(突发负载),更重要的目的,或许还是判断当前 cpu 是否能胜任所负担的工作(平均负载)。这么说,两个命令应该配合着使用才对呀,不过,检查要填的单子里面只用填一个 cpu 占用率数据,所以嘛。。。

对于 ps,只要把上面的命令处理一下,除一下核心数就好了:

1
ps aux|sed 1d|awk -v cpu=$(cat /proc/cpuinfo| grep "processor"| wc -l) '{a+=$3/cpu}END{print a"%"}'

对于 top,也可以通过 -n1 只运行 1s,然后通过 sed 和 awk 摘出里面的 cpu 占用数据:

1
top -n1|sed -n '3p'|awk '{print $2}'

拼接结果:大显神通的 paste 和拦路虎 ansi 字符

几个命令之间当然也可以通过 && 来拼接起来,只不过输出不在同一行;而用 paste 的话,不仅输出在同一行,而且命令与命令之间的输出中间会自动插入一个制表符,粘贴到表格里面刚好就会变成两格,简直就是懒人的福音!

但是嘛。。。我这边还有一个需求,那就是把 top 第一行后面这些带时间的内容输出出来,也就是这串:

按理来说,配合 sed 就可以了,顺便剪掉前面的 top:

1
top -n1|sed -n '1p'|sed 's/top - //g'  

然后我发现,运行完以后会莫名其妙的做一次清屏。这样复制粘贴就不方便了。

原因也挺简单的。里面混有 ansi 字符,输出出来:

1
top -n1|sed -n '1p'|sed 's/top - //g'  > ansi.txt

用 vim 看一眼:

拖下来再用 vscode 看一眼:

好家伙显示出来的效果都不同。。。

不过原理都差不多,vscode 这里的 ESC 就等效于上面 vim 的 ^[,同时还等效于\x1B。但使用 sed 命令去处理的话,只有 \x1B 能成功匹配到这些个 ensi 符号。所以我们可以先把这些符号给转成可见性比较好的英文字符,参考:用 sed 去除文件中的 ASCII 控制字符乱码_Fu_Tianshu的博客-CSDN博客

1
top -n1|sed -n '1p'|sed 's/top - //g'|sed -r  -e 's/\x00/[NUL]/g' -e 's/\x01/[SOH]/g' -e 's/\x02/[STX]/g' -e 's/\x03/[ETX]/g' -e 's/\x04/[EOT]/g' -e 's/\x05/[ENQ]/g' -e 's/\x06/[ACK]/g' -e 's/\x07/[BEL]/g' -e 's/\x08/[BS]/g' -e 's/\x0A/[LF]/g' -e 's/\x0B/[VT]/g' -e 's/\x0C/[FF]/g' -e 's/\x0D/[CR]/g' -e 's/\x0E/[SO]/g' -e 's/\x0F/[SI]/g' -e 's/\x10/[DLE]/g' -e 's/\x11/[DC1]/g' -e 's/\x12/[DC2]/g' -e 's/\x13/[DC3]/g' -e 's/\x14/[DC4]/g' -e 's/\x15/[NAK]/g' -e 's/\x16/[SYN]/g' -e 's/\x17/[ETB]/g' -e 's/\x18/[CAN]/g' -e 's/\x19/[EM]/g' -e 's/\x1A/[SUB]/g' -e 's/\x1B/[ESC]/g' -e 's/\x1C/[FS]/g' -e 's/\x1D/[GS]/g' -e 's/\x1E/[RS]/g' -e 's/\x1F/[US]/g' -e 's/\x7F/[DEL]/g'|

认真去看的话,要提取的字符串前后都有乱七八糟的 ansi 字符和搭配生效的 ansi 命令:

1
^[[?25l^[[?1c^[[H^[[J^[[m^Otop - 02:37:10 up 132 days, 19:05,  1 user,  load average: 0.00, 0.03, 0.00^[[m^O^[[39;49m^[[m^O^[[39;49m^[[K

我稍微了解了一下,上面这些 ansi 命令的意思:

1
2
3
4
5
6
^[[?25l光标不可见
^[[?1c 在^[[?25l后执行确保在执行序列时不会有光标闪烁Linux 控制台
^[[H回到页首
^[[J从光标擦除到页尾
^[[m^O重置字体颜色
^[[39;49m使用默认颜色

其中其决定作用的自然是那个^[[H^[[J了,就是这两个联合起来才让 top 命令达成了清屏的效果。现在就把他们都干掉!前面,用长长的那一串语句做转换后,要提取的字符串前后的 ansi 字符组变成了下面这样的两串东西:

1
2
[ESC][?25l[ESC][?1c[ESC][H[ESC][J[ESC][m[SI]
[ESC][m[SI][ESC][39;49m[ESC][m[SI][ESC][39;49m[ESC][K

复制到 vscode 后,按 ctrl+H,vscode 就能自动的把这两串东西转义成正则表达式形式,真方便。整理一下,放到上面用过的表达式中做处理:

1
top -n1|sed -n '1p'|sed 's/top - //g'|sed -r  -e 's/\x00/[NUL]/g' -e 's/\x01/[SOH]/g' -e 's/\x02/[STX]/g' -e 's/\x03/[ETX]/g' -e 's/\x04/[EOT]/g' -e 's/\x05/[ENQ]/g' -e 's/\x06/[ACK]/g' -e 's/\x07/[BEL]/g' -e 's/\x08/[BS]/g' -e 's/\x0A/[LF]/g' -e 's/\x0B/[VT]/g' -e 's/\x0C/[FF]/g' -e 's/\x0D/[CR]/g' -e 's/\x0E/[SO]/g' -e 's/\x0F/[SI]/g' -e 's/\x10/[DLE]/g' -e 's/\x11/[DC1]/g' -e 's/\x12/[DC2]/g' -e 's/\x13/[DC3]/g' -e 's/\x14/[DC4]/g' -e 's/\x15/[NAK]/g' -e 's/\x16/[SYN]/g' -e 's/\x17/[ETB]/g' -e 's/\x18/[CAN]/g' -e 's/\x19/[EM]/g' -e 's/\x1A/[SUB]/g' -e 's/\x1B/[ESC]/g' -e 's/\x1C/[FS]/g' -e 's/\x1D/[GS]/g' -e 's/\x1E/[RS]/g' -e 's/\x1F/[US]/g' -e 's/\x7F/[DEL]/g'|sed -r -e 's/\[ESC\]\[\?25l\[ESC\]\[\?1c\[ESC\]\[H\[ESC\]\[J\[ESC\]\[m\[SI\]//g' -e 's/\[ESC\]\[m\[SI\]\[ESC\]\[39;49m\[ESC\]\[m\[[SI\]\[ESC\]\[39;49m\[ESC\]\[K//g'  

这样出来的结果就不会有问题了。。。


但是,但是!用 paste 去把这个语句和上面测量 cpu 占用率的两个语句组起来,就会莫名其妙的卡住,原因不明。。。

但解决方法也很简单,这时我才发现 top 里有个参数叫 -b,可以自动把这些 ansi 字符干掉。。。

所以我绕了一大圈是为了什么呢?????

1
paste <(top -b -n1|sed -n '3p'|awk '{print $2}'|sed 's/us//g') <(top -b -n1|sed -n '1p'|sed 's/top - //g')