简介
在OS中使用cache机制,主要为了提高磁盘的读取效率,避免高频的IO交换。将频繁访问的数据存放在file cache中,下一次在获取的时候就可以直接读取,缓存高命中率对于数据高速检索十分有利。
smem
smem 是一个可以显示 Linux 系统中进程内存使用情况的工具,它可以显示包括进程共享的内存部分,如文件缓存。你可以使用它来查看每个进程的内存使用情况(包括共享内存)。
smem -k
会有以下字段
PID:监控的进程号
User:监控的进程owner
Command:监控的允许指令
Swap:监控的进程在交换分区(swap)中使用的内存量
USS:监控的进程独占集大小,即该进程唯一占用的内存量,不包括与其他进程共享的内存。这个值表示进程专有的物理内存部分
PSS:监控的进程比例集大小,即按比例分摊的共享内存部分。如果两个或多个进程共享同一块内存区域,那么每个进程的 PSS 会分摊这块共享内存的大小。PSS 值为 共享内存的大小 / 共享进程的数量,并加上独占内存。
RSS:监控的进程驻留集大小,即进程实际占用的物理内存总量,包括了进程独占的内存和与其他进程共享的内存,但不考虑共享内存的实际使用情况。
smem工具主要是通过解析/proc/{pid}/smaps 文件内容,以进行相应的展示
查看postgresql 相关进程占用情况
smem -k | grep -E 'User|postgres'
/proc/{pid}/smaps
系统中每个进程的详细内存映射文件。拥有比smaps 更为详细的信息内容。
以下是postgresql 主进程的
00400000-00c10000 r-xp 00000000 fd:00 1715955 /home/postgres/pg/bin/postgres
Size: 8256 kB
Rss: 1540 kB
Pss: 995 kB
Shared_Clean: 756 kB
Shared_Dirty: 0 kB
Private_Clean: 784 kB
Private_Dirty: 0 kB
Referenced: 1540 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
ProtectionKey: 0
VmFlags: rd ex mr mp me dw sd
00400000-00c10000 r-xp 00000000 fd:00 1715955 /home/postgres/pg/bin/postgres:
00400000-00c10000: 虚拟内存的地址范围,表示此内存段的起始地址(00400000)到结束地址(00c10000)。
r-xp: 这个内存段的权限标识。
r: 可读(Read)。
x: 可执行(Execute)。
p: 私有(Private),表示写入的更改不会共享给其他进程。
00000000: 文件偏移量,表示如果该段映射到文件,文件从哪个偏移开始映射。此处为 00000000,表示从文件开头映射。
fd:00: 设备 ID,指示该文件所在的设备号。
1715955: 该文件的 inode 编号,用于唯一标识文件。
/home/postgres/pg/bin/postgres: 映射的文件路径,表示这个内存段映射的是 Postgres 可执行文件的部分。
Size:
表示这个段在虚拟内存中的总大小。
Rss:
表示已经实际驻留在物理内存中的部分大小,也就是这个段的 1540 KB 已经加载到了 RAM 中。
Pss:
表示按比例分摊的物理内存使用量。如果多个进程共享该内存段,PSS 会根据共享进程数分摊计算。
Shared_Clean:
表示该段有 756 KB 的干净(未修改)内存页被多个进程共享。
Shared_Dirty:
该段的 Shared_Dirty 是 0 KB,表示该段没有被共享的修改页(即未被其他进程修改)。
Private_Clean:
该段的 Private_Clean 是 784 KB,表示该段有 784 KB 的干净(未修改)内存页,仅供当前进程使用,不会与其他进程共享。
Private_Dirty:
该段的 Private_Dirty 是 0 KB,表示该段没有任何私有的修改页。
Referenced:
Referenced 是 1540 KB,表示该段内存自从加载后,已经有 1540 KB 被访问过。
Anonymous:
该段的 Anonymous 是 0 KB,表示该段没有任何匿名内存。匿名内存通常是没有文件映射的内存,通常用于堆和栈空间。
AnonHugePages:
AnonHugePages 是 0 KB,表示该段没有使用巨大页(HugePages),巨大页通常用于提升内存管理效率。
Swap:
Swap 是 0 KB,表示该段内存没有被交换到硬盘(即没有被放入 swap 空间)。
KernelPageSize:
KernelPageSize 是 4 KB,表示内核的页面大小为 4 KB,这是常见的内存页大小。
MMUPageSize:
MMUPageSize 是 4 KB,表示内存管理单元(MMU)的页面大小为 4 KB。通常,KernelPageSize 和 MMUPageSize 是相同的。
Locked:
Locked 是 0 KB,表示这个段内存没有被锁定在物理内存中。被锁定的内存无法被交换到磁盘上。
ProtectionKey:
ProtectionKey 是 0,表示该段没有使用内存保护键。这是与现代 CPU 的内存保护机制相关的字段。
VmFlags: rd ex
VmFlags 表示该段内存的各种标志:
rd: 可读(Readable)。
ex: 可执行(Executable)。
mr: 可被内存管理单元(MMU)读取。
mp: 使用了巨页(HugePage)的页中介表(Page Middle Directory)。
me: 支持内存扩展(Memory Expansion)。
dw: 延迟写入(Delayed Write),通常用于文件系统缓存。
sd: 支持软脏页追踪(Soft Dirty),用于记录内存写入状态。
自定义函数
那就数据库端,写个脚本定时查看其file cache情况
CREATE OR REPLACE FUNCTION compare_data(pid integer)
RETURNS SETOF record AS $$
import re
def parse_smaps(pid):
smaps_file = f"/proc/{pid}/smaps"
rows = []
try:
with open(smaps_file, 'r') as file:
current_entry = {}
for line in file:
if re.match(r'^[0-9a-f]+-[0-9a-f]+', line):
if current_entry:
rows.append(generate_row(pid, current_entry))
current_entry = {}
# Extract start_addr, end_addr, offset, device, inode, and path
parts = line.split()
current_entry['start_addr'], current_entry['end_addr'] = parts[0].split('-')
current_entry['offset'] = parts[2]
current_entry['device'] = parts[3]
current_entry['inode'] = parts[4]
if len(parts) > 5:
current_entry['path'] = parts[5]
else:
current_entry['path'] = None
elif line.startswith('Size:'):
current_entry['Size'] = line.split()[1]
elif line.startswith('Rss:'):
current_entry['Rss'] = line.split()[1]
elif line.startswith('Pss:'):
current_entry['Pss'] = line.split()[1]
elif line.startswith('Shared_Clean:'):
current_entry['Shared_Clean'] = line.split()[1]
elif line.startswith('Shared_Dirty:'):
current_entry['Shared_Dirty'] = line.split()[1]
elif line.startswith('Private_Clean:'):
current_entry['Private_Clean'] = line.split()[1]
elif line.startswith('Private_Dirty:'):
current_entry['Private_Dirty'] = line.split()[1]
elif line.startswith('Referenced:'):
current_entry['Referenced'] = line.split()[1]
elif line.startswith('Anonymous:'):
current_entry['Anonymous'] = line.split()[1]
elif line.startswith('AnonHugePages:'):
current_entry['AnonHugePages'] = line.split()[1]
elif line.startswith('Swap:'):
current_entry['Swap'] = line.split()[1]
elif line.startswith('KernelPageSize:'):
current_entry['KernelPageSize'] = line.split()[1]
elif line.startswith('MMUPageSize:'):
current_entry['MMUPageSize'] = line.split()[1]
elif line.startswith('Locked:'):
current_entry['Locked'] = line.split()[1]
elif line.startswith('ProtectionKey:'):
current_entry['ProtectionKey'] = line.split()[1]
elif line.startswith('VmFlags:'):
current_entry['VmFlags'] = line.split()[1:]
if current_entry:
rows.append(generate_row(pid, current_entry))
except FileNotFoundError:
return []
return rows
def generate_row(pid, entry):
# Prepare the row as a tuple to return
return (
pid, entry.get('start_addr'), entry.get('end_addr'), entry.get('offset'), entry.get('device'),
entry.get('inode'), entry.get('path'), entry.get('Size'), entry.get('Rss'), entry.get('Pss'),
entry.get('Shared_Clean'), entry.get('Shared_Dirty'), entry.get('Private_Clean'), entry.get('Private_Dirty'),
entry.get('Referenced'), entry.get('Anonymous'), entry.get('AnonHugePages'), entry.get('Swap'),
entry.get('KernelPageSize'), entry.get('MMUPageSize'), entry.get('Locked'), entry.get('ProtectionKey'),
' '.join(entry.get('VmFlags', []))
)
rows = parse_smaps(pid)
# PL/Python expects a return statement with a list of rows to return them as records
return rows
$$ LANGUAGE plpython3u;
执行函数
SELECT * FROM compare_data(39469) AS t(pid integer, start_addr text, end_addr text, "offset" text, device text, inode text,
path text, size text, rss text, pss text, shared_clean text, shared_dirty text,
private_clean text, private_dirty text, referenced text, anonymous text,
anonhugepages text, swap text, kernelpagesize text, mmupagesize text, "locked" text,
protectionkey text, vmflags text);