十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python和psutil打造轻量级进程内存监控工具

用Python和psutil打造轻量级进程内存监控工具 家里那台服役多年的“沧桑电脑系统”平时开机两分钟风扇转得比吹风机还响。任务管理器一打开内存稳坐 90%但到底是谁在吃内存往往只能看到几个熟悉的名字更多时候是一堆不知道什么进程在后台悄悄“啃”资源。更尴尬的是想去看某个进程的详细内存占用任务管理器给的信息总感觉不够用——工作集、内存(私有工作集)、共享内存看得人头大也看不出来哪个进程才是真正的内存大户。这篇文章就围绕我自己在旧电脑上动手写的进程内存监控工具来聊讲清楚怎么读懂进程的内存数据怎么用 Python 在几分钟内搭一个轻量级的内存统计工具既能单次统计也能持续监控还能把历史数据记录下来排查内存泄漏。文章会从概念、环境、核心代码到常见坑位一步步展开不管是刚接触系统监控的小白还是想拿现成脚本改造的开发者都可以照着操作。1. 内存统计工具到底在解决什么问题1.1 老电脑的内存焦虑很多旧电脑性能下降最明显的瓶颈不是 CPU而是内存。机械硬盘时代的机器物理内存本身就不算大再加上现在的浏览器、微信、办公软件都是吃内存大户随便开几个程序物理内存就被挤满了。内存一满系统就会把一部分数据换到磁盘上的虚拟内存交换分区/页面文件里于是一边用磁盘模拟内存一边频繁读写交换文件电脑就会变得越来越卡。这种情况下我们最需要的就是一个能清晰列出“谁在占用内存”的工具。光知道系统总内存不够用没有意义关键在于定位是浏览器开了几百个标签页是某个后台服务发生内存泄漏还是一个可能已经失去响应、但还在慢慢增长的进程如果没有一个顺手的内存统计工具只能靠猜。1.2 任务管理器不够用我们需要进程内存监控Windows 的任务管理器、Linux 的 top 命令、macOS 的活动监视器都能看到进程的内存占用但它们在“统计维度”上并不完全一致而且信息比较零散。比如 Windows 任务管理器默认显示的是“内存(活动私有工作集)”它会排除已修改但未写回磁盘的共享页面实际值和任务管理器里的“进程”标签页、其中的工作集含义不同。又比如 Linux 的 top 显示 RES常驻内存但 RES 中包含共享库很多进程会共享同一个动态库如果把每个进程的 RES 加起来会超过系统总内存。我们需要的是一个更通用的进程内存监控方案既能查看系统整体内存水位又能查看单进程内存细节最好还能持续采样、输出日志方便事后分析。自带监控工具做不到但我们可以自己写一个。1.3 几个必须先分清的进程内存概念在写工具之前先把几个最常见的进程内存概念分清楚否则后面统计出来的数字自己都解释不了。概念英文缩写含义虚拟内存大小VSZ / Virtual Memory Size进程可访问的虚拟地址空间大小包含尚未分配物理页面的部分通常比实际占用大很多常驻内存集RSS / Resident Set Size进程当前驻留在物理内存中的页面大小实际上包含共享页面多个进程可能重复计算比例集大小PSS / Proportional Set SizeRSS 中按进程数均摊共享页面后的值能更合理地估算单一进程“分摊后”的内存占用独占集大小USS / Unique Set Size进程独享的内存页面大小不包含任何共享部分是判断进程私有占用的最准确指标工作集Working SetWindows 系统上的概念指进程当前在物理内存中的页面集合可以简单理解为该进程最近访问的内存页面页面文件Pagefile / Swap内存不够时用来暂存数据磁盘空间被换出的进程页面在物理内存外对于 Windows 上的内存监控我们经常会看到“工作集”“私有工作集”“共享工作集”三种说法工作集 私有工作集 共享工作集私有工作集是进程独占、其他进程引用不到的部分共享工作集是和其他进程共享的部分比如多个程序加载同一个 DLL在最后下结论时优先看“私有内存”和“PSS”这类排除了共享干扰的数据再看“工作集”作为实际物理内存压力参考。2. 技术选型与环境准备2.1 为什么选择 Python psutil内存统计工具并不是只能自己从头解析系统文件常规的做法有两个直接解析系统接口比如在 Linux 上读/proc/[pid]/status或/proc/[pid]/statm在 Windows 上调用GlobalMemoryStatusEx、QueryWorkingSet等 Native API。这种做法性能好但代码平台相关性强而且细节很多写起来容易踩坑。使用跨平台系统监控库psutil。它把不同操作系统的进程信息做了一个统一抽象提供Process.memory_info()、virtual_memory()、swap_memory()等接口几行代码就能获取原本需要调用一堆系统 API 才能得到的数据。在“沧桑电脑系统”这种资源有限的环境下用 Python 写工具可能看起来会有点“奢侈”但只要不高频采集普通脚本的内存开销也只有十几 MB 到几十 MB完全可接受。更重要的是这个方案的开销来源于 Python 解释器本身和 psutil 库的文件扫描代码逻辑本身很轻量适合快速开发。2.2 环境准备下面是示例环境假设操作系统Windows 10 / Windows 11 或 Linux 均可Python3.7示例代码兼容常见特性psutil5.8.x 或 5.9.x推荐使用虚拟环境安装依赖如果你手头是老电脑、老系统Python 版本可能是 2.7 或者很老的 Python 3.4那就建议先升级到 Python 3.7 以上老版本不仅 psutil 兼容性变差一些语法写起来也束手束脚。如果你的系统包管理器非常老也可以考虑直接用pip install --user psutil安装到当前用户目录避免系统路径权限问题。不同系统的 Python 安装方式# Ubuntu / Debian sudo apt update sudo apt install -y python3 python3-pip python3-venv # CentOS / RHEL sudo yum install -y python3 python3-pip # Windows # 前往 python.org 下载安装包安装时勾选 Add Python to PATH2.3 安装 psutil 并验证建议先创建项目目录和虚拟环境mkdir mem-monitor cd mem-monitor # Linux/macOS python3 -m venv venv source venv/bin/activate # Windows python -m venv venv venv\Scripts\activate然后安装依赖pip install psutil验证是否安装成功直接在命令行进入 Python 交互环境import psutil print(psutil.__version__)如果能看到类似5.9.8的版本号说明环境已经就绪。注意不同系统下 psutil 读取进程信息时可能受权限影响部分高权限进程的数据读不到这个后面会专门讲。3. 核心原理怎么读取进程内存3.1 psutil 中与内存相关的方法psutil 提供的核心内存接口有两类系统内存统计接口psutil.virtual_memory() # 内存整体统计 psutil.swap_memory() # 交换分区 / 页面文件统计进程内存统计接口psutil.Process(pid).memory_info() # 进程内存详情返回包含不同指标的命名元组 psutil.Process(pid).memory_percent() # 进程内存占用占系统内存的百分比 psutil.Process(pid).memory_maps() # 进程详细内存映射Linux 上很强大这里memory_info()是重点不同平台上字段不完全一致。在 Linux 上它通常包含字段含义rss常驻内存大小vms虚拟内存大小shared共享内存大小text代码段大小lib共享库大小data数据段大小dirty脏页大小在 Windows 上它通常包含字段含义rss工作集大小Working Setvms虚拟内存分页文件提交量Pagefile Usagenum_page_faults页面错误次数peak_wset工作集峰值wset当前工作集peak_paged_pool分页池峰值paged_pool分页池大小peak_nonpaged_pool非分页池峰值nonpaged_pool非分页池大小pagefile页面文件使用量peak_pagefile页面文件使用量峰值private私有内存大小可以看到Windows 上的rss其实对应的是“工作集”它不是私有内存。如果我们要统计一个 Windows 进程真正独有的内存应该优先看private或pagefile字段。3.2 先来看一个最简单的最小示例import psutil pid 1 # 在 Linux 上是 init 或 systemd在 Windows 上可能不存在 try: proc psutil.Process(pid) mem proc.memory_info() print(fPID: {pid}) print(fRSS: {mem.rss / 1024 / 1024:.2f} MB) print(fVMS: {mem.vms / 1024 / 1024:.2f} MB) if hasattr(mem, private): print(fPrivate: {mem.private / 1024 / 1024:.2f} MB) except psutil.NoSuchProcess: print(f进程 {pid} 不存在)这个示例只做了一件事根据 PID 获取进程对象然后通过memory_info()拿到内存数据。注意mem.rss返回的是字节通常需要除以 1024 两次转成 MB。3.3 获取系统整体内存监控单个进程还不够工具需要同时展示系统的整体内存水位。virtual_memory()返回的字段中最常用的是total物理内存总量available可用内存大小注意它不是简单的total - used而是系统估算的可以分配给新程序的内存percent内存使用百分比used/free不同系统统计口径略有不同在 Linux 上下文中available比free更有参考价值示例import psutil vm psutil.virtual_memory() print(f总内存: {vm.total / 1024 / 1024:.2f} MB) print(f可用内存: {vm.available / 1024 / 1024:.2f} MB) print(f内存使用率: {vm.percent}%)这种“系统总览 进程明细”的组合正好可以在同一个工具里展示。3.4 进程内存统计的两种口径当我们要判断“哪个进程最吃内存”时不同口径得到的结果可能会差异很大。比如有两个进程共同加载了一个 100 MB 的共享库用 RSS 统计时两个进程都会把这个 100 MB 算进去总和虚高如果改用 PSS则各算 50 MB更接近真实物理占用分摊。psutil 没有直接提供一个跨平台的pss方法在 Linux 上我们可以通过memory_maps()来汇总 PSSimport psutil proc psutil.Process(pid) pss 0 uss 0 try: for mmap in proc.memory_maps(groupedTrue): pss mmap.pss uss mmap.private_clean mmap.private_dirty except (psutil.AccessDenied, psutil.NoSuchProcess): pass print(fPSS: {pss / 1024 / 1024:.2f} MB) print(fUSS: {uss / 1024 / 1024:.2f} MB)但在 Windows 上memory_maps()并不像 Linux 那样有完整的 pss/uss 字段此时应该用private字段作为“进程独占内存”的近似指标。在设计工具时最实用的做法是同时输出 RSS、Private、PSS如果可用并在排序时优先按 RSS 排序同时在表格里标注 Private 列给使用者一个更谨慎的判断依据。4. 实战构建一个可用的进程内存监控神器4.1 工具功能设计现在我们把零散的思路整合成一个相对完整的工具memstat.py功能如下单次快照列出当前系统内存概况和内存占用 Top N 的进程。可参数化排序支持按 RSS、Private、内存占用百分比排序。可指定进程名过滤比如只看chrome、python相关进程。持续监控每隔一段时间刷新一次像top一样在终端里动态刷新。导出 CSV把采样历史写入文件方便后续用 Excel 或 pandas 分析内存增长趋势。整个工具设计成命令行风格用argparse解析参数保持代码清晰直接。4.2 创建项目结构建议结构如下mem-monitor/ ├── venv/ ├── memstat.py ├── requirements.txt └── logs/ # 运行时生成的采样日志目录requirements.txt内容psutil5.8.04.3 核心代码实现这一步是整个工具的核心我拆成几段来写并解释每段的作用。第一段导入库、定义字节转换函数和取进程信息的函数。import argparse import csv import os import sys import time from datetime import datetime import psutil def format_bytes(size_bytes): 把字节数转成可读的 MB / GB 字符串 if size_bytes is None: return N/A if size_bytes 0: return N/A value float(size_bytes) units [B, KB, MB, GB, TB] idx 0 while value 1024 and idx len(units) - 1: value / 1024 idx 1 return f{value:.2f} {units[idx]}第二段定义获取单个进程内存信息的方法。def get_process_memory_info(proc): 从 psutil.Process 对象中提取内存信息。 返回统一格式的字典兼容 Windows 和 Linux。 try: mem proc.memory_info() except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): return None info { pid: proc.pid, name: proc.name(), rss: getattr(mem, rss, 0), vms: getattr(mem, vms, 0), private: getattr(mem, private, 0), pagefile: getattr(mem, pagefile, 0), percent: proc.memory_percent(), } # 如果上面某些字段不存在取一个安全的默认值 if info[private] 0 and info[pagefile] 0: # Windows 平台上 pagefile 字段近似私有提交量 info[private] info[pagefile] return info这里的关键点在于private字段只在 Windows 上有Linux 上没有而为了统计跨平台通用在 Linux 上我们可以用 0 表示“未知”在后续显示时根据平台决定展示哪一列。第三段过滤并排序进程列表。def collect_processes(name_filterNone, sort_byrss, top10): 遍历当前系统所有进程提取内存信息。 返回排序后的进程列表。 results [] for proc in psutil.process_iter([pid, name]): try: if name_filter and name_filter.lower() not in proc.info[name].lower(): continue info get_process_memory_info(proc) if info is None: continue results.append(info) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue if sort_by rss: results.sort(keylambda x: x[rss], reverseTrue) elif sort_by percent: results.sort(keylambda x: x[percent], reverseTrue) elif sort_by private: results.sort(keylambda x: x[private], reverseTrue) return results[:top]这段代码使用psutil.process_iter()而不是psutil.pids()因为它内部做了缓存和异常处理遍历效率更高还能避免进程在列表读取中途退出导致的NoSuchProcess。process_iter和proc.info的组合是遍历进程时最推荐的写法。第四段在终端打印表格。def print_table(rows, sort_by): 打印进程内存表格 os.system(cls if os.name nt else clear) vm psutil.virtual_memory() print(f系统内存总量: {format_bytes(vm.total)}) print(f可用内存: {format_bytes(vm.available)}) print(f内存使用率: {vm.percent}%) print(f采样时间: {datetime.now().strftime(%Y-%m-%d %H:%M:%S)}) print(- * 70) if sort_by private: header f{PID:8}{进程名:20}{RSS:12}{Private:12}{占用%:8} else: header f{PID:8}{进程名:20}{RSS:12}{VMS:12}{占用%:8} print(header) print(- * 70) for row in rows: if sort_by private: print(f{row[pid]:8}{row[name][:18]:20} f{format_bytes(row[rss]):12}{format_bytes(row[private]):12} f{row[percent]:8.2f}) else: print(f{row[pid]:8}{row[name][:18]:20} f{format_bytes(row[rss]):12}{format_bytes(row[vms]):12} f{row[percent]:8.2f}) print(- * 70)第五段写 CSV 日志。def write_csv_row(csv_writer, rows): 将一次采样写入 CSV vm psutil.virtual_memory() timestamp datetime.now().strftime(%Y-%m-%d %H:%M:%S) for row in rows: csv_writer.writerow([ timestamp, row[pid], row[name], row[rss], row[vms], row[private], row[percent], vm.percent, ])第六段持续监控主循环。def monitor_loop(args): 持续采样每 interval 秒刷新一次 log_file None if args.output: log_file open(args.output, a, newline, encodingutf-8) csv_writer csv.writer(log_file) if os.path.getsize(args.output) 0: csv_writer.writerow([ timestamp, pid, name, rss, vms, private, process_percent, system_percent, ]) print(开始持续监控按 CtrlC 停止...) try: while True: rows collect_processes( name_filterargs.name, sort_byargs.sort, topargs.top, ) print_table(rows, args.sort) if csv_writer: write_csv_row(csv_writer, rows) log_file.flush() time.sleep(args.interval) except KeyboardInterrupt: print(\n监控已停止) finally: if log_file: log_file.close()第七段解析参数并启动单次或持续模式。def main(): parser argparse.ArgumentParser(description轻量级进程内存监控工具) parser.add_argument(--top, typeint, default10, help显示内存占用前 N 个进程默认 10) parser.add_argument(--sort, choices[rss, private, percent], defaultrss, help排序字段默认按 RSS) parser.add_argument(--name, typestr, defaultNone, help按进程名过滤不区分大小写) parser.add_argument(--interval, typefloat, default2.0, help持续监控刷新间隔秒默认 2 秒) parser.add_argument(--output, typestr, defaultNone, help输出 CSV 文件路径例如 logs/mem.csv) parser.add_argument(--watch, actionstore_true, help启用持续监控模式) args parser.parse_args() if not os.path.exists(logs): os.makedirs(logs, exist_okTrue) if args.watch: monitor_loop(args) else: rows collect_processes( name_filterargs.name, sort_byargs.sort, topargs.top, ) print_table(rows, args.sort) if __name__ __main__: main()到这里memstat.py的完整代码就写完了。为了方便复制我把完整代码合并成一份展示在下方你直接保存为memstat.py就能运行。import argparse import csv import os import time from datetime import datetime import psutil def format_bytes(size_bytes): if size_bytes is None or size_bytes 0: return N/A value float(size_bytes) units [B, KB, MB, GB, TB] idx 0 while value 1024 and idx len(units) - 1: value / 1024 idx 1 return f{value:.2f} {units[idx]} def get_process_memory_info(proc): try: mem proc.memory_info() except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): return None info { pid: proc.pid, name: proc.name(), rss: getattr(mem, rss, 0), vms: getattr(mem, vms, 0), private: getattr(mem, private, 0), pagefile: getattr(mem, pagefile, 0), percent: proc.memory_percent(), } if info[private] 0 and info[pagefile] 0: info[private] info[pagefile] return info def collect_processes(name_filterNone, sort_byrss, top10): results [] for proc in psutil.process_iter([pid, name]): try: if name_filter and name_filter.lower() not in proc.info[name].lower(): continue info get_process_memory_info(proc) if info is None: continue results.append(info) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue if sort_by rss: results.sort(keylambda x: x[rss], reverseTrue) elif sort_by percent: results.sort(keylambda x: x[percent], reverseTrue) elif sort_by private: results.sort(keylambda x: x[private], reverseTrue) return results[:top] def print_table(rows, sort_by): os.system(cls if os.name nt else clear) vm psutil.virtual_memory() print(f系统内存总量: {format_bytes(vm.total)}) print(f可用内存: {format_bytes(vm.available)}) print(f内存使用率: {vm.percent}%) print(f采样时间: {datetime.now().strftime(%Y-%m-%d %H:%M:%S)}) print(- * 70) if sort_by private: header f{PID:8}{进程名:20}{RSS:12}{Private:12}{占用%:8} else: header f{PID:8}{进程名:20}{RSS:12}{VMS:12}{占用%:8} print(header) print(- * 70) for row in rows: if sort_by private: print(f{row[pid]:8}{row[name][:18]:20} f{format_bytes(row[rss]):12}{format_bytes(row[private]):12} f{row[percent]:8.2f}) else: print(f{row[pid]:8}{row[name][:18]:20} f{format_bytes(row[rss]):12}{format_bytes(row[vms]):12} f{row[percent]:8.2f}) print(- * 70) def write_csv_row(csv_writer, rows): vm psutil.virtual_memory() timestamp datetime.now().strftime(%Y-%m-%d %H:%M:%S) for row in rows: csv_writer.writerow([ timestamp, row[pid], row[name], row[rss], row[vms], row[private], row[percent], vm.percent, ]) def monitor_loop(args): log_file None if args.output: log_file open(args.output, a, newline, encodingutf-8) csv_writer csv.writer(log_file) if os.path.getsize(args.output) 0: csv_writer.writerow([ timestamp, pid, name, rss, vms, private, process_percent, system_percent, ]) print(开始持续监控按 CtrlC 停止...) try: while True: rows collect_processes( name_filterargs.name, sort_byargs.sort, topargs.top, ) print_table(rows, args.sort) if csv_writer: write_csv_row(csv_writer, rows) log_file.flush() time.sleep(args.interval) except KeyboardInterrupt: print(\n监控已停止) finally: if log_file: log_file.close() def main(): parser argparse.ArgumentParser(description轻量级进程内存监控工具) parser.add_argument(--top, typeint, default10, help显示内存占用前 N 个进程默认 10) parser.add_argument(--sort, choices[rss, private, percent], defaultrss, help排序字段默认按 RSS) parser.add_argument(--name, typestr, defaultNone, help按进程名过滤不区分大小写) parser.add_argument(--interval, typefloat, default2.0, help持续监控刷新间隔秒默认 2 秒) parser.add_argument(--output, typestr, defaultNone, help输出 CSV 文件路径例如 logs/mem.csv) parser.add_argument(--watch, actionstore_true, help启用持续监控模式) args parser.parse_args() if not os.path.exists(logs): os.makedirs(logs, exist_okTrue) if args.watch: monitor_loop(args) else: rows collect_processes( name_filterargs.name, sort_byargs.sort, topargs.top, ) print_table(rows, args.sort) if __name__ __main__: main()4.4 运行与验证先运行单次统计python memstat.py --top 15预期输出类似系统内存总量: 15.86 GB 可用内存: 6.23 GB 内存使用率: 61.7% 采样时间: 2025-01-15 22:08:31 ---------------------------------------------------------------------- PID 进程名 RSS VMS 占用% ---------------------------------------------------------------------- 21528 chrome.exe 1412.36 MB 2498.52 MB 3.20 11144 wechat.exe 812.56 MB 3560.22 MB 1.84 ... ----------------------------------------------------------------------再运行持续监控每 5 秒刷新一次python memstat.py --watch --interval 5 --top 10按CtrlC就能退出。如果想查看某个具体进程比如排查python.exe的内存占用python memstat.py --name python --sort private --top 5这个命令会把所有名字里包含“python”的进程列出来并按私有内存排序适合快速找出哪个 Python 脚本在偷吃内存。4.5 结果说明与参数注意事项从工具输出中我们需要关注这样几类信息系统可用内存变化如果可用内存持续下降并且关闭几个大进程后仍然回不到正常水平说明可能有进程在慢慢增长也就是典型的内存泄漏特征。RSS 最大的进程RSS 大不代表它一定有问题因为共享库会被重复计算。所以接着看 Private 列如果 Private 也很大说明它确实是在独占物理内存。内存占用百分比这个值是单进程占系统总内存的比例用于横向对比。注意在 Windows 上Process.memory_percent()默认按总物理内存计算并未扣除系统保留部分数值会略低于任务管理器中的“内存”列显示这是统计口径不同导致的正常现象。参数使用上--top决定输出行数不要一次输出几千行终端会刷屏。默认 10 就够。--sort private只有在 Windows 上才最有意义Linux 上 private 字段可能为 0。如果你在 Linux 上更想排掉共享库影响建议另外用 PSS 版本脚本。--interval不要小于 1 秒除非你有特殊需求。因为遍历所有进程本身也有开销间隔太短会加重系统负担反而干扰采样结果。4.6 扩展一导入到 CSV 做内存泄漏趋势分析内存监控中最有价值的场景其实是持续采样然后画出趋势图。实现也比较简单python memstat.py --watch --interval 10 --top 20 --output logs/mem.csv让它后台跑一个小时期间正常使用电脑。结束后用 Excel 打开logs/mem.csv能看到每次采样的时间戳、每个进程的 RSS、Private 和系统内存使用率。如果某个进程的 RSS 曲线持续上升而不是呈波动态就要重点怀疑它存在内存泄漏。如果需要用 pandas 快速分析可以这样读 CSVimport pandas as pd import matplotlib.pyplot as plt df pd.read_csv(logs/mem.csv) # 只看 chrome.exe 的内存变化 df_c df[df[name].str.contains(chrome, caseFalse, naFalse)] grouped df_c.groupby(timestamp).agg({rss: sum}).reset_index() grouped[timestamp] pd.to_datetime(grouped[timestamp]) grouped.plot(xtimestamp, yrss, figsize(10, 4)) plt.title(Chrome 进程总 RSS 变化趋势) plt.ylabel(RSS (bytes)) plt.show()4.7 扩展二简单 GUI 面板可选如果觉得命令行不够友好也可以基于 Tkinter 做一个极简面板。Tkinter 是 Python 标准库不需要额外安装。下面是一个最小可运行版本展示系统内存使用率和 Top 5 进程。import tkinter as tk from tkinter import ttk import psutil class MemPanel: def __init__(self, root): self.root root self.root.title(进程内存监控 - 简易版) self.root.geometry(520x380) self.label ttk.Label(root, text系统内存监控) self.label.pack(pady10) self.mem_label ttk.Label(root, text) self.mem_label.pack() self.tree ttk.Treeview(root, columns(pid, name, rss, percent), showheadings) self.tree.heading(pid, textPID) self.tree.heading(name, text进程名) self.tree.heading(rss, textRSS (MB)) self.tree.heading(percent, text占用%) self.tree.column(pid, width80) self.tree.column(name, width180) self.tree.column(rss, width120) self.tree.column(percent, width80) self.tree.pack(filltk.BOTH, expandTrue, padx10, pady10) self.btn ttk.Button(root, text刷新, commandself.refresh) self.btn.pack(pady5) self.refresh() def refresh(self): vm psutil.virtual_memory() self.mem_label.config( textf总内存: {vm.total / 1024 / 1024:.0f} MB | f可用: {vm.available / 1024 / 1024:.0f} MB | f使用率: {vm.percent}% ) for item in self.tree.get_children(): self.tree.delete(item) processes [] for proc in psutil.process_iter([pid, name]): try: mem proc.memory_info() processes.append((proc.pid, proc.info[name], mem.rss, proc.memory_percent())) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue processes.sort(keylambda x: x[2], reverseTrue) for pid, name, rss, percent in processes[:5]: self.tree.insert(, tk.END, values(pid, name, rss / 1024 / 1024, f{percent:.2f})) if __name__ __main__: root tk.Tk() MemPanel(root) root.mainloop()这个 GUI 面板适合在 Windows 上双击运行对不想敲命令的同事很友好。不过要注意Tkinter 的刷新频率不建议太快手动点按钮刷新即可如果要做自动刷新可以设置after(5000, self.refresh)也就是 5 秒刷新一次。5. 常见问题与排查思路过程中最容易遇到的问题我整理成了一个表格遇到报错时可以先对照检查。问题现象常见原因解决思路导入 psutil 时报 ModuleNotFoundError当前 Python 环境未安装 psutil或者安装到了另一个解释器在项目虚拟环境中执行pip install psutil并确认pip指向的解释器与运行脚本的解释器一致运行脚本报 AccessDenied当前用户权限不足无法读取某些系统进程或受保护进程的内存信息Windows 下以管理员身份运行 CMD/PowerShellLinux 下用sudo运行但要注意最小权限原则某个进程 PID 变化快统计结果有时报 NoSuchProcess进程已经退出或 PID 被回收使用process_iter()时已经做了大部分异常处理显式访问单个 PID 时必须加 try-except统计到的内存总和比系统已用内存小很多RSS 共享部分被重复计算或者排除了某些内核进程、缓存看单进程趋势而不是总和统计系统缓存占用可以用psutil.virtual_memory().cached等字段Windows 上 Private 字段为 0某些系统进程不在当前权限域或 psutil 未返回该字段检查是否使用管理员权限运行也可以用pagefile字段近似代替私有提交量老电脑运行脚本时系统更卡了遍历进程太频繁Python 进程本身占用 CPU/内存调大--interval降低采样频率把--top限制在 10 到 20 个进程考虑使用更轻量的定时任务方案GUI 界面中文显示乱码Tkinter 默认字体/编码问题在脚本开头设置字体比如(微软雅黑, 10)文件保存为 UTF-8 编码最常见的两个坑再单独说一下坑一误以为 psutil 就是“实时且零开销”的。psutil 在 Linux 上通过读取/proc文件系统获取进程信息在 Windows 上通过系统 API 获取这个过程本身有开销。如果对一个几千进程的服务器频繁调用psutil.process_iter()加memory_info()CPU 占用可能会上升到 5% 甚至更多。所以监控频率要克制默认 2 秒到 10 秒比较合理。坑二把 RSS 加起来当作“真实内存占用”。RSS 包含共享库其实质是每个进程视角下的物理内存映射大小不是系统物理内存的唯一消耗。尤其多个 Java、Python、Node 进程都加载同一个动态库时RSS 总和会明显超过系统实际的内存压力。专业分析时Linux 上要结合 PSS/USSWindows 上要结合私有工作集或页面文件提交量。6. 在真实电脑上使用的最佳实践6.1 监控策略上如何设定采样频率对“沧桑电脑系统”这类性能紧张的老机器一个额外的监控进程本身也可能成为压垮内存的最后一根稻草。建议这样控制消耗手动排查时用单次快照模式即可不需要常驻。怀疑内存泄漏时用--watch --interval 10 --output logs/mem.csv后台跑 30 到 60 分钟采样间隔尽量保持在 10 秒以上。需要定时报告时不要在脚本内部做复杂分析和图表让脚本只负责“记录原始数据”分析交给 Excel、pandas 或 Nightingale、Prometheus 等监控平台。日志文件要设置大小上限。一个简单的做法是每天切换日志文件或者在文件超过 50 MB 后轮转。避免日志文件无限增长吃掉磁盘空间。6.2 定位内存泄漏的思路拿到 CSV 趋势数据后排查思路可以按以下顺序先看系统可用内存曲线如果一路下滑到很低水平再进入进程级排查。对每个 Top 进程画出rss和private两条曲线。如果某个进程的private曲线稳定上升、没有回落迹象而其他进程总量不变基本可以确定该进程存在内存泄漏或缓存管理不当。重新复现业务操作观察内存是否随操作次数线性增长。例如浏览器每打开一个标签页、服务每处理一次请求内存都上涨且不回收就有问题了。确认后可尝试kill或重启该进程观察系统可用内存是否回到正常区间同时做好数据备份和业务侧确认。6.3 老电脑选型与轻量化建议如果 Python 在你的老电脑上跑起来都吃力可以尝试以下轻量化替代方案在 Windows 上写一个基于tasklist的批处理脚本echo off tasklist /FO CSV /NH | sort /R /65 mem_snapshot.csv pause虽然信息没有 psutil 丰富但零依赖适合极低配环境。在 Linux 上用/proc/meminfo加ps aux --sort-%mem可以快速查看cat /proc/meminfo | head -n 5 ps aux --sort-%mem | head -n 15这些都是系统自带命令基本不增加额外负载。如果你的环境能装 Python建议优先用 psutil因为可编程性和跨平台一致性更好。6.4 权限、安全与生产注意事项内存监控在个人电脑上使用没有太多安全风险但如果要部署到服务器上请记住下面几点坚持最小权限原则。监控只需要“读”进程信息和内存统计不需要以 root/Administrator 身份长期运行只有在确实需要读取其他受保护进程时才提升权限。不要在日志中记录敏感参数、命令行参数或文件路径中的密码信息。本项目只采集 PID、进程名、内存数据不采集命令行参数这是一种好的安全习惯。如果要把监控集成到生产环境建议先在公司内部测试环境验证脚本兼容性再逐步灰度到生产节点。不要在线上数据库服务器或核心业务服务器上以sudo长跑一个未经评审的监控脚本除非你已经确认过脚本不会干扰业务进程。定期清理日志避免日志堆积造成磁盘占用过高。6.5 输出结果的可读性优化在实际使用中如果准备长期保留这个脚本可以顺手优化三个地方支持按 PSS 排序。在 Linux 上memory_maps()可以拿到 pss 和 uss把它封装到get_process_memory_info()中排序字段再多一个pss会更有说服力。支持输出 JSON。把脚本输出格式改成 JSON 后可以被 Grafana、Prometheus 的 pushgateway 或其他监控平台直接消费。支持多平台字段差异的自动处理。比如在 Windows 上优先显示 Private在 Linux 上优先显示 USS/PSS这样脚本换到哪台机器都能用。7. 总结与下一步学习路线这次动手实践其实只做了一件事把一个看似复杂的“进程内存监控”拆成了“系统内存总览 单进程内存详情 持续采样记录”三个层次并用 Python 和 psutil 快速实现出来。通过这个项目你可以掌握 Windows/Linux 进程内存统计的核心概念学会用psutil遍历进程、提取 RSS/Private/PSS 等关键字段也能写出一个带命令行参数、支持 CSV 导出的实用工具。对于想继续深入的同学后续可以按这几条线往下走研究psutil的更多能力net_io_counters()、disk_io_counters()、sensors_temperatures()、boot_time()把一个内存监控工具扩展成系统健康检查脚本。学习 Linux/proc文件系统的细节比如/proc/meminfo、/proc/[pid]/smaps中 VmRSS、Pss、Private_Clean、Private_Dirty 之间的关系。了解现代可观测性方案比如 Prometheus 的node_exporter会采集进程内存Grafana 可以做可视化面板Python 的prometheus_client也可以把进程级指标暴露出来。如果你用的是 Java 服务可以进一步学习 JVM 内存模型、各代内存参数和jcmd、jmap、jstat等工具。整个过程最关键的一点不是背住某个 API 或者某个命令参数而是建立“用数据定位问题”的思路。下次电脑突然卡顿、内存飙高时不用再凭感觉猜进程了跑一下这个工具30 秒内就能锁定嫌疑进程。如果这个工具对你有帮助建议先收藏备用也欢迎在评论区聊聊你在这台“沧桑电脑系统”上遇到过哪些内存难题。
返回列表