文章目录
- 前情提要
- 效果展示
- pidstat 简介
- matplotlib 简介
- 认识 figure 和 axes
- 绘制曲线图
- 绘制柱形图
- 创建两个轴,将上面两种图形放到一个 figure 中
- Backends of matplotlib
- 如何使用 WebAgg
- 注意事项
前情提要
这段时间在忙服务器压测的工作,虽然我们程序里面有统计 cpu 消耗的日志,但是有两点不足。一是该统计信息只统计了整个进程的总的 cpu 消耗,没有细致到每个线程的 cpu 消耗。二是该统计信息只包含了 cpu 的开销,没有其他诸如 io 消耗等数据。在 github 上以性能统计为关键字找到了 sysstat 库,这是一个 linux 系统性能分析的工具集。里面大多数工具是以整个系统为对象进行数据采样和统计的。而我主要是想统计指定进程的信息,我在这个工具集中找到一个符合的工具 pidstat,“pidstat reports statistics for Linux tasks (processes) : I/O, CPU, memory, etc.”。本文介绍 pidstat 的基础使用,以及将 pidstat 采样的数据以图表的方式输出出来,再依靠 matplotlib 的 backend 机制创建一个临时的 web 服务器,我们可以通过访问该临时站点查看和下载图表的 png 文件。
效果展示
这是执行脚本分析 pidstat 采样到的数据,将生成的图表展示到一个临时的 web 服务器上
访问上面提示的站点,有如下效果
在网页的左下角有下载按钮
pidstat 简介
pidstat 是 sysstat 工具集的一员。专门用于统计进程的各项指标。要在服务器上使用 pidstat,需要先安装 sysstat 工具集,用 yum 管理软件包可以使用以下命令安装:
yum install -y sysstat # 安装
systemctl enable --now sysstat # 启用
下面介绍一下 pidstat 的常用命令:
- -C name 表示采样进程的 Command 包含 name 字段(name 支持正则表达式),Command 为进程名字:
- -p pid 表示指定采样进程的 pid。使用该选项可以更精准的指定采样目标。
- -u 采样 cpu 使用率,包含了 cpu 百分比,分为内核占用,用户占用,总占用。例如:
- -d 采样 IO 消耗,该选项只支持内核版本 2.6.20 以后的机器,可以用 uname -r 查看内核版本。主要关注每秒读写信息:
- -r 采样页中断次数和内存消耗,页中断次数可以反应程序申请和释放内存的频率,一些不好的代码逻辑也可能造成页中断次数增高。主要关注 rss(resident size),驻留物理内存。
- -t 同时展示归属该进程的线程信息,他们的关系会用树的形式展现,使用该选项的前提是用 -p 指定pid,我用 -C name 试了下没有捕获到具体的线程信息。如下:
- -h 该选项在你使用了上诉的多个采样选项之后可以加上,它可以让所有采样信息在水平一行一起输出,否则就是一个或者某几个天然可以搭配的采样内容一起输出,另外的单独输出,这样不方便将数据汇总起来二次处理。
- 另外,在所有选项最后接上一个数字表示采样间隔时间(单位秒),如果你还想控制更精确点,后面还可以跟上一个数字表示在前一个数字指定的间隔时间内的采样次数。(采样间隔时间是可选项,默认值为 10 分钟,采样次数也是可选项,默认值为 1 次,我一般是设置为采样间隔 1 秒,采样次数就用默认值,即 1 秒 1 次)
这是我使用的采样命令:
nohup pidstat -p $pid -udrth 1 > $stat_cpu_log 2>/dev/null &
- pidstat -p $pid -udrth 1 这部分是表示统计指定 pid 的进程的所有线程的 cpu 消耗,io 消耗,内存消耗,并且将每次的采样信息输出到一排,采样频率为 1 秒 1 次。
- nohup 使得我关掉终端并不影响 pidstat 的运行。
- > $stat_cpu_log 将 pidstat 输出的内容重定向到文件,方便我后续做二次处理。
- 2>/dev/null 将 pidstat 产生的错误信息重定向到空文件,就是丢弃。
- & 后台运行。
matplotlib 简介
Matplotlib是一个Python数据可视化库,提供了丰富的绘图工具,包括线图、散点图、条形图、等高线图、图像等。它的设计灵活,可以用于各种不同的绘图需求,从简单的图形到复杂的动态图形。Matplotlib的优点是它易于使用,具有广泛的文档和社区支持,可以与NumPy、SciPy等Python科学计算库无缝集成。同时,Matplotlib也是许多其他Python数据可视化库的基础,如Seaborn、ggplot等。
认识 figure 和 axes
figure 可以理解为一个图表的画布,这个画布上可以有多个 axes(轴,包含 x轴 和 y轴 组成的坐标轴),以下这张图是官网提供的 figure 介绍图,内容量很大。其中的蓝色圆圈表示圈中的内容可以由圈下面的函数调用绘制而成。
figure 和 axes 可以由 plt.subplots() 函数创建出来:
fig, ax = plt.subplots() # 创建包含了一个坐标轴的 figure
# figure 为一个 matplotlib.figure 对象
# ax 为一个 matplotlib.axes.Axes 对象
fig, axs = plt.subplots(rows, cols) # 创建包含了 rows * cols 个坐标轴的 figure
# axs 为 matplotlib.axes.Axes 对象的二维数组
# 形如 axs = matplotlib.axes.Axes[rows][cols]
我们的曲线或者其他图形都是基于 axes 来绘制的,figure 可以不要,例如用 plt.subplot 接口只创建坐标轴来绘制。但是基于 figure 来创建 axes ,可以用 figure 的一些属性来提供对图表的总体调控。
绘制曲线图
# -*- coding: utf-8 -*-
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# 0. 创建坐标轴,可以根据自己实际需要选择创建一个轴还是多个轴,只是要注意下多个轴是返回的数组
fig, ax = plt.subplots()
x_list = [1,2,3] # x 轴数据
y_list = [3,6,5]
# 1. 基本调用
ax.plot(x_list, y_list) # x 和 y 轴数据都由用户传入
# 2. 省略 x 数组
ax.plot(y_list) # x 轴数据自动生成,系统会根据 y 轴数组长度自动填充 1~n 的 x 轴数据
# 3. 给曲线命名
ax.plot(y_list, label="name1") # label 属性可以指定该条曲线的名称
ax.legend() # 显示每条线的名字列表
# 4. 给坐标轴命名
ax.set_xlabel("我是x轴")
ax.set_ylabel("我是y轴")
# 5. 绘制
plt.show()
绘制柱形图
# -*- coding: utf-8 -*-
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# 0. 创建坐标轴,可以根据自己实际需要选择创建一个轴还是多个轴,只是要注意下多个轴是返回的数组
fig, ax = plt.subplots()
categories = ["name1", "name2", "name3"] # 柱形图 x 轴命名
values = [10, 35.5, 99] # 数据
# 1. 绘制到坐标轴上
rects = ax.bar(categories, values) # 返回值可以不关心,这里是用于设置柱形图顶上的数据显示需要用到下一个函数
# 2. 将数据标注到柱形图上方,方便观察
ax.bar_label(rects, padding=3, rotation=-55) #
# 3. 将 x 轴的名字按 -55 度进行旋转,即是 顺时针旋转 55 度
ax.tick_params(axis='x', rotation=-55) # 这是为了避免名字过长,左右两个名字出现重叠
# 4. 绘制
plt.show()
创建两个轴,将上面两种图形放到一个 figure 中
# -*- coding: utf-8 -*-
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# 0. 创建两个坐标轴,用 figsize 设置图表的总大小
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
ax = axs[0] # 曲线图用第一个
ax_bar = axs[1] # 柱形图用第二个
x_list = [1,2,3] # x 轴数据
y_list = [3,6,5]
# 1. 基本调用
ax.plot(x_list, y_list) # x 和 y 轴数据都由用户传入
# 2. 省略 x 数组
ax.plot(y_list) # x 轴数据自动生成,系统会根据 y 轴数组长度自动填充 1~n 的 x 轴数据
# 3. 给曲线命名
ax.plot(y_list, label="name1") # label 属性可以指定该条曲线的名称
ax.set_xlabel("IamX")
ax.set_ylabel("IamY")
ax.legend() # 显示每条线的名字列表
categories = ["name1", "name2", "name3"] # 柱形图 x 轴命名
values = [10, 35.5, 99] # 数据
# 1. 绘制到坐标轴上
rects = ax_bar.bar(categories, values) # 返回值可以不关心,这里是用于设置柱形图顶上的数据显示需要用到下一个函数
# 2. 将数据标注到柱形图上方,方便观察
ax_bar.bar_label(rects, padding=3, rotation=-55)
# 3. 将 x 轴的名字按 -55 度进行旋转,即是 顺时针旋转 55 度
ax_bar.tick_params(axis='x', rotation=-55) # 这是为了避免名字过长,左右两个名字出现重叠
plt.show()
Backends of matplotlib
我在 matplotlib 的文档中发现一个 backends 机制,简单来说,matplotlib 可以让用户选择用什么后端技术来最终渲染输出绘制的内容,比如在 windows 平台,可以直接在控制台窗口执行脚本,弹出绘图窗口;或者将 matplotlib 嵌入到 PyQt 或 PyGObject 等图形用户界面中构建应用;还有一些人运行 web 应用程序服务器来动态展示图表。
由于我是在 linux 服务器上绘图,所以打开一个 web 服务器的 backend 特别合适,这样我每次有新的图表绘制后,只需要刷新一下网页即可,比生成图片,然后用 sz 发送到本地(sz 还和 tmux 冲突),再保存到文件夹,再打开来说方便太多了。
以下是 matplotlib 目前支持的 backend 模式,我要介绍的是 WebAgg 模式,其他模式大家可以按自己的需求来选择:
如何使用 WebAgg
-
由于 matplotlib 是根据环境变量 MPLBACKEND 的值来指定对应 backend 的, 所以要么预先设置服务器上的该环境变量为 MPLBACKEND=WebAgg,要么就是调用脚本时先指定环境变量,可以像这样调用:
MPLBACKEND=WebAgg python3 test.py
-
WebAgg 是通过调用 Tornado 这个库来生成 web 服务器的,所以需要用 pip 安装 Tornado 库。后面会说到版本的问题。
-
我自己测试发现,默认启动的 web 服务器的访问站点是 http://127.0.0.1:8898,如果你是在自己的电脑上执行脚本的话那么还好,你可以在网页中访问该站点。但是我是在内网的某个服务器上执行脚本,我从本地电脑是无法访问到服务器的这个站点的。官方有提供 WebAgg 的源码,通过源码发现可以像这样修改 web 服务器的 IP:
import matplotlib as mpl mpl.rcParams['webagg.address'] = ip
这里的 ip 你可以写死成你的服务器地址,但是这样肯定是不好的。这里提供一个获取 ip 的函数。
# -*- coding: utf-8 -*- import socket import matplotlib as mpl # 获取本机IP地址 def get_local_ip(): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('8.8.8.8', 80)) ip = s.getsockname()[0] except: ip = '127.0.0.1' finally: s.close() return ip mpl.rcParams['webagg.address'] = get_local_ip()
注意事项
- matplotlib 版本差异,python 版本差异,依赖库的差异都可能引发各种问题,下面列一下我使用的版本情况
- 如果有 python 安装模块不全的问题可以参考 python3.8 安装 和 python3.10.3 安装。