psutil是python进程指标采集的事实标准,避免手动解析/proc或wmi;需注意cpu_percent首次调用为0、内存用rss而非vms、容器中指标受限、定时采集应基于monotonic时间对齐、上报用线程池、快照用字典而非threading.local。

用 psutil 读进程指标最稳,别自己去扒 /proc
Python 做运行时指标采集,psutil 是事实标准。它封装了跨平台的底层调用,避免你手动解析 /proc/pid/stat 或 Windows WMI,省掉权限、路径、字段偏移、字符串切分等一堆易错环节。
常见错误是看到“Linux 下能读 /proc”,就直接上 open('/proc/...') —— 实际会遇到:PID 已消亡导致文件不存在、权限被容器限制、字段顺序在不同内核版本里有微小变动、没处理符号链接跳转(比如 /proc/self)。
-
psutil.Process().cpu_percent(interval=0.1)的interval小于 0.1 秒时可能返回0.0,因为需要两次采样差值;首次调用必为0.0,得调两次才有效 - 内存指标优先用
memory_info().rss,不是vms(后者含 swap 和未分配页,对 OOM 判断无意义) - 容器环境里
psutil.sensors_temperatures()通常不可用,别默认开启
定时采集别用 time.sleep() 循环
用阻塞式休眠做周期采集,在信号中断、GC 暂停、系统负载高时,实际间隔会漂移甚至堆积。尤其当采集逻辑里包含网络请求或磁盘 I/O,误差可能达秒级。
更可靠的做法是基于绝对时间对齐:记录下一次该触发的时间点,用 time.monotonic() 判断是否到达,再执行采集。这样即使单次耗时长,下次也会“追赶”而非“跳过”。
立即学习“Python免费学习笔记(深入)”;
- 别写
while True: collect(); time.sleep(5)—— 这会让周期变成5 + collect耗时 - 改用
next_run = time.monotonic() + 5; while True: if time.monotonic() >= next_run: collect(); next_run += 5 - 如果采集要发 HTTP 上报,建议用独立线程或
concurrent.futures.ThreadPoolExecutor提交,避免阻塞主采集循环
threading.local 不适合存指标快照
有人想在线程本地存储每次采集的 cpu_percent 或内存值,方便后续聚合,结果发现数据错乱或丢失。这是因为 threading.local 绑定的是 OS 线程,而 Python 的 concurrent.futures 或异步任务可能复用线程,且主线程和采集线程不是同一个上下文。
真正轻量又安全的方式是每次采集生成一个字典或 dataclass 实例,显式传给上报函数。若需跨阶段共享,用全局 dict 加 threading.RLock,但多数场景没必要——指标本来就是离散快照。
- 错误示范:
local_store = threading.local(); local_store.cpu = psutil.cpu_percent() - 正确做法:
snapshot = {'ts': time.time(), 'cpu': psutil.cpu_percent(), 'mem_rss': p.memory_info().rss} - 如果用
asyncio,更别碰threading.local—— 它对协程完全无效
容器里 psutil 看不到宿主机指标,也别硬绕
在 Docker 或 Kubernetes Pod 里,默认 cgroups v2 + pid namespace 隔离后,psutil 只能看到本容器内进程,psutil.disk_usage('/') 返回的是容器 rootfs 而非宿主机磁盘,psutil.net_if_addrs() 也可能只暴露虚拟网卡。
强行挂载 /proc 或 /sys 宿主机路径进来,虽然能读到数据,但会破坏容器边界,带来权限提升风险,且在 OpenShift 等强化环境中直接被拒绝。真要宿主机级指标,应该由专门的 DaemonSet agent(如 Prometheus Node Exporter)统一采集并暴露 HTTP 接口,你的 Python 服务只消费它。
- 容器内调用
psutil.sensors_battery()总是空,因为没设备节点映射 -
psutil.boot_time()在容器里返回的是宿主机启动时间,不是容器启动时间 —— 如需后者,读/proc/1/comm或环境变量START_TIME更靠谱 - 别在容器里尝试
psutil.pids()后遍历所有 PID 做全量分析 —— 大部分 PID 你根本没权限读,会抛psutil.AccessDenied










