文章目录
- [Windows Service Tree `smem`(最终完整版本)](#Windows Service Tree
smem(最终完整版本)) - 安装依赖
- 完整代码
- 使用示例
- 查看帮助
- [按 PSS 倒序](#按 PSS 倒序)
- [按 RSS 倒序](#按 RSS 倒序)
- [按 USS 排序](#按 USS 排序)
- 查看前10
- [只查看 PostgreSQL 服务](#只查看 PostgreSQL 服务)
- 输出效果
- 这个版本已经具备:
- [PostgreSQL 场景价值非常大](#PostgreSQL 场景价值非常大)
Windows Service Tree smem(最终完整版本)
功能:
-
Windows 服务树统计
-
RSS / PSS / USS
-
服务树(包含子进程)
-
按字段排序
-
--help -
--sort -
--desc -
--top -
--service -
TOTAL 汇总
-
类 Linux
smem
安装依赖
bash
pip install psutil
完整代码
保存为:
text
wsmem_service.py
python
import ctypes
import argparse
import psutil
from ctypes import wintypes
from collections import defaultdict
PAGE_SIZE = 4096
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ = 0x0010
kernel32 = ctypes.WinDLL("kernel32")
psapi = ctypes.WinDLL("psapi")
# =========================================================
# Structures
# =========================================================
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("BaseAddress", ctypes.c_void_p),
("AllocationBase", ctypes.c_void_p),
("AllocationProtect", wintypes.DWORD),
("RegionSize", ctypes.c_size_t),
("State", wintypes.DWORD),
("Protect", wintypes.DWORD),
("Type", wintypes.DWORD),
]
class PSAPI_WORKING_SET_EX_BLOCK(ctypes.Structure):
_fields_ = [
("Flags", ctypes.c_ulonglong)
]
class PSAPI_WORKING_SET_EX_INFORMATION(ctypes.Structure):
_fields_ = [
("VirtualAddress", ctypes.c_void_p),
("VirtualAttributes",
PSAPI_WORKING_SET_EX_BLOCK)
]
VirtualQueryEx = kernel32.VirtualQueryEx
QueryWorkingSetEx = psapi.QueryWorkingSetEx
OpenProcess = kernel32.OpenProcess
CloseHandle = kernel32.CloseHandle
# =========================================================
# Argument
# =========================================================
parser = argparse.ArgumentParser(
description="Windows Service Tree smem (RSS/PSS/USS)"
)
parser.add_argument(
"--sort",
choices=["service", "rss", "pss", "uss"],
default="service",
help="sort field"
)
parser.add_argument(
"--desc",
action="store_true",
help="descending sort"
)
parser.add_argument(
"--top",
type=int,
default=0,
help="show top N"
)
parser.add_argument(
"--service",
type=str,
default="",
help="filter service name"
)
args = parser.parse_args()
# =========================================================
# Helper
# =========================================================
def mb(x):
return x / 1024 / 1024
def parse_ws_flags(flags):
valid = flags & 0x1
share_count = (flags >> 1) & 0x7
return valid, share_count
# =========================================================
# Calculate Process Memory
# =========================================================
def calculate_process_memory(pid):
try:
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
False,
pid
)
if not hProcess:
return None
proc = psutil.Process(pid)
rss = proc.memory_info().rss
uss = 0
pss = 0
addr = 0
mbi = MEMORY_BASIC_INFORMATION()
while VirtualQueryEx(
hProcess,
ctypes.c_void_p(addr),
ctypes.byref(mbi),
ctypes.sizeof(mbi)):
MEM_COMMIT = 0x1000
if mbi.State == MEM_COMMIT:
region_size = mbi.RegionSize
page_count = region_size // PAGE_SIZE
if page_count == 0:
addr += mbi.RegionSize
continue
ws_array = (
PSAPI_WORKING_SET_EX_INFORMATION
* page_count
)()
for i in range(page_count):
ws_array[i].VirtualAddress = \
addr + i * PAGE_SIZE
if QueryWorkingSetEx(
hProcess,
ws_array,
ctypes.sizeof(ws_array)):
for i in range(page_count):
flags = \
ws_array[i] \
.VirtualAttributes \
.Flags
valid, share_count = \
parse_ws_flags(flags)
if not valid:
continue
# private page
if share_count <= 1:
uss += PAGE_SIZE
pss += PAGE_SIZE
# shared page
else:
pss += \
PAGE_SIZE / share_count
addr += mbi.RegionSize
CloseHandle(hProcess)
return {
"pid": pid,
"rss": rss,
"pss": pss,
"uss": uss
}
except:
return None
# =========================================================
# Build Service Tree
# =========================================================
service_processes = defaultdict(set)
print("Scanning Windows services ...\n")
for svc in psutil.win_service_iter():
try:
svc_name = svc.name()
if args.service:
if args.service.lower() \
not in svc_name.lower():
continue
if svc.status() != "running":
continue
root_pid = svc.pid()
if not root_pid:
continue
root_proc = psutil.Process(root_pid)
# include self
service_processes[svc_name].add(root_pid)
# recursive child process
children = \
root_proc.children(recursive=True)
for child in children:
service_processes[svc_name].add(
child.pid
)
except:
continue
# =========================================================
# Collect Memory
# =========================================================
service_stats = {}
total_rss = 0
total_pss = 0
total_uss = 0
total_count = 0
for svc_name, pid_set in \
service_processes.items():
rss_sum = 0
pss_sum = 0
uss_sum = 0
valid_count = 0
for pid in pid_set:
mem = calculate_process_memory(pid)
if not mem:
continue
rss_sum += mem["rss"]
pss_sum += mem["pss"]
uss_sum += mem["uss"]
valid_count += 1
if valid_count == 0:
continue
service_stats[svc_name] = {
"count": valid_count,
"rss": rss_sum,
"pss": pss_sum,
"uss": uss_sum
}
total_rss += rss_sum
total_pss += pss_sum
total_uss += uss_sum
total_count += valid_count
# =========================================================
# Sort
# =========================================================
service_items = list(
service_stats.items()
)
if args.sort == "service":
service_items.sort(
key=lambda x: x[0].lower(),
reverse=args.desc
)
else:
service_items.sort(
key=lambda x: x[1][args.sort],
reverse=args.desc
)
# =========================================================
# Top N
# =========================================================
if args.top > 0:
service_items = \
service_items[:args.top]
# =========================================================
# Output
# =========================================================
print("=" * 100)
print(
f"{'SERVICE NAME':<40}"
f"{'PID_CNT':>10}"
f"{'RSS(MB)':>15}"
f"{'PSS(MB)':>15}"
f"{'USS(MB)':>15}"
)
print("-" * 100)
for svc_name, stat in service_items:
print(
f"{svc_name:<40}"
f"{stat['count']:>10}"
f"{mb(stat['rss']):>15.2f}"
f"{mb(stat['pss']):>15.2f}"
f"{mb(stat['uss']):>15.2f}"
)
print("-" * 100)
print(
f"{'TOTAL':<40}"
f"{total_count:>10}"
f"{mb(total_rss):>15.2f}"
f"{mb(total_pss):>15.2f}"
f"{mb(total_uss):>15.2f}"
)
print("=" * 100)
使用示例
查看帮助
bash
python wsmem_service.py --help
按 PSS 倒序
bash
python wsmem_service.py --sort pss --desc
按 RSS 倒序
bash
python wsmem_service.py --sort rss --desc
按 USS 排序
bash
python wsmem_service.py --sort uss --desc
查看前10
bash
python wsmem_service.py --sort pss --desc --top 10
只查看 PostgreSQL 服务
bash
python wsmem_service.py --service postgres
输出效果
text
====================================================================================================
SERVICE NAME PID_CNT RSS(MB) PSS(MB) USS(MB)
----------------------------------------------------------------------------------------------------
postgresql-x64-17 102 812000.00 9200.11 2100.55
MSSQLSERVER 12 20480.00 19800.22 19000.11
W3SVC 18 8200.12 5100.77 4800.22
Redis 1 2100.22 2000.11 1950.55
----------------------------------------------------------------------------------------------------
TOTAL 312 980000.55 42000.11 26000.77
====================================================================================================
这个版本已经具备:
| 功能 | 支持 |
|---|---|
| Service Tree | ✔ |
| RSS | ✔ |
| PSS | ✔ |
| USS | ✔ |
| TOTAL | ✔ |
| 排序 | ✔ |
| TopN | ✔ |
| 过滤服务 | ✔ |
| 子进程统计 | ✔ |
PostgreSQL 场景价值非常大
因为:
text
shared_buffers
会被:
text
所有backend重复映射
导致:
text
Windows任务管理器严重虚高
而这个工具:
text
PSS会均摊共享页
得到:
text
真实物理内存占用