mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
78 lines
2.9 KiB
Python
Executable File
78 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Print the memory of running Linux processes the way docs/performance.md reports it.
|
|
|
|
scripts/memory-snapshot.py PID [PID ...]
|
|
scripts/memory-snapshot.py --exe-dir /opt/thunderbird # every process of an app
|
|
|
|
private: anonymous resident memory (RssAnon), the kind of figure Windows Task
|
|
Manager shows; it excludes code and libraries mapped from files.
|
|
pss: proportional set size; shared pages are split between their users,
|
|
so it adds up correctly across processes.
|
|
rss: resident set size, as in top/htop; summed over several processes it
|
|
counts shared pages more than once.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def read(pid: str) -> dict[str, int]:
|
|
values: dict[str, int] = {}
|
|
for line in Path(f"/proc/{pid}/smaps_rollup").read_text().splitlines()[1:]:
|
|
key, _, rest = line.partition(":")
|
|
values[key.strip()] = int(rest.split()[0])
|
|
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
|
|
key, _, rest = line.partition(":")
|
|
if key in ("RssAnon", "Threads"):
|
|
values[key] = int(rest.split()[0])
|
|
return values
|
|
|
|
|
|
def processes_under(directory: Path) -> list[str]:
|
|
root = str(directory.resolve()) + os.sep
|
|
found = []
|
|
for entry in Path("/proc").iterdir():
|
|
if entry.name.isdigit():
|
|
try:
|
|
executable = os.path.realpath(os.readlink(entry / "exe"))
|
|
except OSError:
|
|
continue
|
|
if executable.startswith(root):
|
|
found.append(entry.name)
|
|
return sorted(found, key=int)
|
|
|
|
|
|
def mib(kib: int) -> str:
|
|
return f"{kib / 1024:7.1f}"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("pids", nargs="*")
|
|
parser.add_argument("--exe-dir", type=Path, help="measure every process whose executable is in this directory")
|
|
args = parser.parse_args()
|
|
if not sys.platform.startswith("linux"):
|
|
parser.error("reads /proc, so it runs on Linux only")
|
|
pids = list(args.pids) + (processes_under(args.exe_dir) if args.exe_dir else [])
|
|
if not pids:
|
|
parser.error("no processes to measure")
|
|
totals = {"private": 0, "pss": 0, "rss": 0}
|
|
print(" pid private pss rss threads (MiB)")
|
|
for pid in pids:
|
|
values = read(pid)
|
|
row = {"private": values["RssAnon"], "pss": values["Pss"], "rss": values["Rss"]}
|
|
for key, value in row.items():
|
|
totals[key] += value
|
|
print(f"{pid:>7} {mib(row['private'])} {mib(row['pss'])} {mib(row['rss'])} {values['Threads']:7}")
|
|
if len(pids) > 1:
|
|
print(f" total {mib(totals['private'])} {mib(totals['pss'])} {mib(totals['rss'])} (rss total overcounts shared pages)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|