简介

在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 文件内容,以进行相应的展示

自定义函数查看OS的file cache_共享内存

查看postgresql 相关进程占用情况

smem -k | grep -E 'User|postgres'

自定义函数查看OS的file cache_物理内存_02

/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);

自定义函数查看OS的file cache_物理内存_03