
点击蓝字关注我们
Lasso 已经提供了从 proc/meminfo 文件中获取的全局内存状态,但在某些情况下,我们需要分析特定进程(例如数据库后端)的内存使用和分配情况。为此,我们需要使用 proc 文件系统中的其他文件。本文意在说明可以使用哪些文件以及如何进行每个进程的内存使用分析。
使用的信息
/proc 文件系统包含有关进程的信息。这些信息存储在 proc/ 目录下,其中 是进程的 ID。在该目录下的文件中,我们可以使用 maps 文件。这是一个文本表示的进程内存分配情况。每一行表示连续的内存块,例如:
00400000-00d68000 r-xp 00000000 fc:00 101577163 /usr/edb/as16/bin/edb-postgres00f67000-00f89000 r--p 00967000 fc:00 101577163 /usr/edb/as16/bin/edb-postgres00f89000-00fa6000 rw-p 00989000 fc:00 101577163 /usr/edb/as16/bin/edb-postgres...(snip)...01225000-012c9000 rw-p 00000000 00:00 0 [heap]...(snip)...7f86327c6000-7f86327c7000 r--p 0002c000 fc:00 1337 usr/lib64/ld-2.28.so7f86327c7000-7f86327c9000 rw-p 0002d000 fc:00 1337 usr/lib64/ld-2.28.so7ffead5b5000-7ffead5d6000 rw-p 00000000 00:00 0 [stack]7ffead5e6000-7ffead5e9000 r--p 00000000 00:00 0 [vvar]7ffead5e9000-7ffead5eb000 r-xp 00000000 00:00 0 [vdso]ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
每一行从左到右的字段描述如下:
分配的地址范围
权限
偏移量
设备
iNode
路径
没有路径的行通常是通过 mmap 分配的内存块。
我们可以使用这些信息来分析每个进程的内存状态。
Linux 和数据库中的内存分配
在 Linux 中,我们通常使用 malloc() 和 free()(glibc 库中的函数)来分配和释放内存。在数据库引擎中,这些函数被包装为 palloc() 和 pfree(),它们提供了 MemoryContext 信息。当不再需要时,我们可以释放 MemoryContext 中分配的所有内存块,这样可以有效避免内存泄漏。
另一方面,glibc 的 malloc() 以两种方式分配内存:
扩展 heap 区域。当请求的大小小于某个阈值时会这样做。在 Ubuntu 22.04 的情况下,阈值为 128 KB。
使用 mmap() 分配。当请求的大小等于或大于上述阈值时会这样做。
free() 的行为取决于内存的分配方式:
如果内存是分配给 heap 的,free() 不会实际将内存返回给操作系统。释放的内存块在 glibc 中被标记为free或available区域,可以用于未来的 malloc() 调用。这个区域会一直保留在 heap 中,直到进程终止。
如果内存是通过 mmap() 分配的,free() 会释放该区域并将其返回给操作系统。
因此,从操作系统角度来看的实际内存使用情况可能与应用程序的角度不同。
收集 maps 文件
当我们需要查看每个进程的内存使用情况时,需要分析 maps 文件。由于 proc 及其底层文件不是真实文件,我们无法直接将它们打包为 tarball。我们需要先将 maps 文件复制到普通文件中,然后再进行打包。
以下是一个示例脚本,用于将 maps 文件作为普通文件收集:
#!/bin/bashDEST=$1pwd=$(pwd)mkdir -p $DESTcd /procfor i in [0-9]*docp $i/maps $pwd/$DEST/maps-$idonecd $pwd
将上述内容保存为文件 backup_maps.sh。然后可以将 $PATH 目录归档为 tarball。
将 maps 加载到表中
单个进程的 maps 文件的总行数可能达到 300,000 行或更多。此外,手动分析如此多的行是不切实际的。以下是将这些行加载到数据库表中进行分析的方法。
表结构可以如下:
CREATE TABLE maps(test_label text, /* 测试用例的标签 */hostname text, /* 主机名 */pid int, /* 进程 ID */chunk_size bigint, /* 从地址范围计算的内存块大小 */start_addr text, /* 内存块的起始地址 */end_addr text, /* 内存块的结束地址 */perms text, /* 权限 */offset_val text, /* 偏移量 */dev text, /* 设备 */inode text, /* iNode */pathname text /* 模块的路径。当通过 mmap() 分配时没有路径。 *//* 堆和栈分别表示为 [Heap] 和 [Stack] */);
前三列定议不在 maps 文件中。我们需要从外部提供这些值。
对于每个 maps 文件,可以使用以下 awk 脚本将内容转换为 SQL 语句,以便将其导入到 maps 数据库表中:
#!/usr/bin/gawk# 调用顺序:## gawk -f maps2table.awk test_label=xxx host_name=yyy pid=ppp <infile>## 此脚本读取 proc/*/maps 文件,并将其转换为 SQL 语句,以导入到 "maps" 数据库表中。#BEGIN {db_conn = "PGPASSWORD='xxx' psql -h myhost -U enterprisedb -d edb -c \"BEGIN;\"";system(db_conn);}{chunk_region = $1;perms = $2;offset = $3;dev = $4;inode = $5;if (NF == 6)pathname = "'" $6 "'";elsepathname = "NULL";split(chunk_region, aa, "-");start_addr = aa[1];end_addr = aa[2];chunk_size = strtonum("0x" end_addr) - strtonum("0x" start_addr);cmd= sprintf("PGPASSWORD='enterprisedb' psql -h myhost -U enterprisedb -d edb -c \"INSERT INTO maps VALUES ('%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %s);\"",test_label, host_name, pid, chunk_size, start_addr, end_addr, perms, offset, dev, inode, pathname) ;system(cmd);}END {system("PGPASSWORD='xxx' psql -h myhost -U enterprisedb -d edb -c \"COMMIT;\"");}
将上述内容保存为文件 maps2table.awk。
然后可以使用以下脚本将 proc 中的所有 maps 文件加载到表中:
#!/bin/bashid=$1host=$2for i in maps-*dogawk -v test_label=$id -v host_name=$host -v pid=${i##maps-} -f "./maps2table.awk" $idone
将上述内容保存为文件 maps2table_all.awk。
这里假设我们已经使用上述 backup_maps.sh 脚本复制了 maps 文件。每个 maps 文件的名称已更改为 maps-,其中 是该 maps 文件所属的进程 ID。
加载到表中
使用由 maps2table.awk 和 maps2table_all.awk 生成的 SQL 命令,可以将 maps 信息加载到数据库表中,如下所示:
./maps2table_all.awk enterprise-db-1 myhost
分析数据
一旦数据加载到表中,我们可以通过各种 SQL 语句以多种方式分析进程的内存使用情况。
以下是一个示例,用于获取特定测量和服务器上运行 EPAS 的每个进程的 heap 和 stack 内存使用情况,并按分配的内存量排序:
SELECTtest_label,hostname,pid,lpad(to_char(sum(chunk_size), 'FM999,999,999,999'), 15) AS total_heap_and_stackFROMpublic.mapswheretest_label = 'edb-1' andpid IN(SELECT pidFROM public.mapsWHEREtest_label = 'edb-1') ANDpathname = '[stack]' OR pathname = '[heap]'GROUP BYtest_label,hostname,pidORDER BYsum(chunk_size) DESC,test_label,hostname,pid
结果如下:
test_label | hostname | pid | total_heap_and_stack-------------------------------------+----------+---------+----------------------edb-1 | myhost | 1466247 | 7,298,633,728edb-1 | myhost | 1466122 | 7,297,798,144edb-1 | myhost | 1465744 | 7,297,544,192edb-1 | myhost | 1463925 | 2,017,427,456edb-1 | myhost | 1463923 | 880,758,784edb-1 | myhost | 1463932 | 792,735,744edb-1 | myhost | 1465797 | 57,286,656edb-1 | myhost | 1464544 | 57,196,544edb-1 | myhost | 1465036 | 56,877,056edb-1 | myhost | 1465908 | 55,054,336edb-1 | myhost | 1465041 | 54,763,520edb-1 | myhost | 1464496 | 54,697,984edb-1 | myhost | 1465827 | 54,673,408...(snip)...edb-1 | myhost | 1463948 | 1,691,648edb-1 | myhost | 1463947 | 1,650,688edb-1 | myhost | 1463949 | 1,650,688edb-1 | myhost | 1463917 | 1,462,272edb-1 | myhost | 1463920 | 1,413,120edb-1 | myhost | 1463921 | 1,413,120edb-1 | myhost | 1463919 | 1,114,112edb-1 | myhost | 225630 | 847,872edb-1 | myhost | 1463916 | 847,872edb-1 | myhost | 1463918 | 847,872edb-1 | myhost | 225633 | 712,704(1521 rows)
将结果与 pg_stat_activity 结合
我们可以将上述信息与实际的后端进程结合使用 Lasso。Lasso tarball 包含 postgresql/running_activity.out 文件,这是 pg_stat_activity 的内容,我们可以通过 PID 将其与内存消耗列表结合。
总结
通过使用 /proc//maps 文件,我们可以分析数据库后端内存使用的各个方面。由于典型生产数据库的数据量很大,因此将这些文件的内容存储在数据库表中以便进行各种分析是非常方便的。
结果可以与 Lasso 输出信息结合,以便进行进一步分析。
关于公司
感谢您关注新智锦绣科技(北京)有限公司!作为 Elastic 的 Elite 合作伙伴及 EnterpriseDB 在国内的唯一代理和服务合作伙伴,我们始终致力于技术创新和优质服务,帮助企业客户实现数据平台的高效构建与智能化管理。无论您是关注 Elastic 生态系统,还是需要 EnterpriseDB 的支持,我们都将为您提供专业的技术支持和量身定制的解决方案。
欢迎关注我们,获取更多技术资讯和数字化转型方案,共创美好未来!
![]() | ![]() |
Elastic 微信群 | EDB 微信群 |

发现“分享”和“赞”了吗,戳我看看吧






