GreptimeDB 内存分析实战:基于 jeprof 的采样、监控与火焰图生成全流程
2026/9/17 11:49:00 网站建设 项目流程

GreptimeDB 内存分析实战:基于 jeprof 的采样、监控与火焰图生成全流程

【免费下载链接】greptimedbThe open-source observability database. One columnar engine for metrics, logs, and traces, on object storage.项目地址: https://gitcode.com/GitHub_Trending/gr/greptimedb

本文是一份针对 GreptimeDB 的完整内存分析操作指南,核心内容来自仓库中 docs/how-to/memory-profile-scripts/scripts/README.md 提供的内存分析流程,并辅以同目录下的现成脚本与 src/common/mem-prof 模块的源码佐证。读者将掌握:如何获取jeprof工具、如何在开启堆采样后运行 GreptimeDB、如何利用dump.sh自动监控内存增长并在超阈值时抓取.gprof采样文件、如何一键生成火焰图(含相邻采样间的差值火焰图),以及如何将火焰图交付给开发者进一步排查内存问题。整套流程无需改动任何代码,全部基于仓库内现成脚本与 HTTP 调试接口即可完成。

内存分析的整体流程

按照 README 的定义,GreptimeDB 的内存分析是一个“采样 → 监控 → 可视化 → 分析”的闭环,共分为 5 个步骤:

  1. 获取jeprof工具脚本(获取方法见下文“获取jeprof工具”小节)。
  2. 以环境变量MALLOC_CONF=prof:true启动greptimedb后,把greptimedb进程的 PID 作为参数执行dump.sh脚本。该脚本会持续监控内存使用,并在超过阈值(例如 10 分钟内增长超过 20MB)时抓取内存画像,输出形如greptime-{timestamp}.gprof的文件。
  3. 积累 2~3 个.gprof文件后,在相同环境下运行gen_flamegraph.sh,生成展示内存分配调用栈的火焰图。
  4. 注意:gen_flamegraph.sh要求当前目录下存在jeprof和可选的flamegraph.pl。如需现场生成火焰图,先运行get_flamegraph_tool.sh,它会将火焰图生成工具flamegraph.pl下载到当前目录。
  5. 将生成的火焰图(即整个<gprof_directory>/flamegraphs目录)发送给开发者,供进一步分析。

获取jeprof工具

jeprof是 jemalloc 自带的内存画像解析工具,负责把 jemalloc 堆采样 dump 文件解析成可读的调用栈数据。README 给出了三种获取方式,从简单到复杂依次排列,任选其一即可,但必须与greptimedb将要运行的环境保持一致

方式一:编译 GreptimeDB 源码后直接复用

如果你是从源码编译 GreptimeDB,那么jeprof已经在编译期间生成。执行cargo build之后,运行仓库提供的 find_compiled_jeprof.sh 即可将其复制到当前目录。

该脚本的逻辑非常简单:在当前目录递归查找名为jeprof的文件,找到后复制到当前目录并赋予可执行权限,找不到则报错退出。其内部实现如下:

JPROF_PATH=$(find . -name 'jeprof' -print -quit) if [ -n "$JPROF_PATH" ]; then echo "Found jeprof at $JPROF_PATH" cp "$JPROF_PATH" . chmod +x jeprof echo "Copied jeprof to current directory and made it executable." else echo "jeprof not found" exit 1 fi

jeprof之所以在编译 GreptimeDB 时自动产出,是因为 GreptimeDB 的 common-mem-prof/Cargo.toml 通过tikv-jemalloc-sys依赖启用了profilingstatsfeature,jemalloc 在构建其 C 库时会顺带生成jeprof工具。其典型路径位于:

./target/${PROFILE}/build/tikv-jemalloc-sys-${HASH}/out/build/bin/jeprof

同时,how-to-profile-memory.md 提醒:如果从系统包管理器安装 jemalloc,需要注意默认版本的jeprof可能没有--collapsed选项,若选择包管理器方式请确保jeprof版本不低于 5.3.0。

方式二:通过 Cargo 新建最小工程快速生成

如果你本地已经安装了 Rust 工具链,可以用一个最小的 Cargo 工程来产出jeprof

cargo new get_jeprof cd get_jeprof

然后在Cargo.toml中追加依赖:

[dependencies] tikv-jemalloc-ctl = { version = "0.6", features = ["use_std", "stats"] }

接着构建:

cargo build

构建完成后jeprof工具即已产出。此时在当前目录运行find_compiled_jeprof.sh,它会自动定位并把jeprof复制到当前目录。注意这里 Cargo.toml 中声明的是 0.6 版本,而 GreptimeDB 源码内部(见 common-mem-prof/Cargo.toml)使用的是tikv-jemalloc-ctl = "0.7"并同时启用use_stdstatsfeatures,这是为了匹配 GreptimeDB 自身的 jemalloc 构建选项。

方式三:从源码编译 jemalloc

最复杂但最可控的方式是从源码编译 jemalloc。先克隆 tikv/jemalloc 仓库并切换到指定提交:

git clone https://github.com/tikv/jemalloc.git cd jemalloc git checkout e13ca993e8ccb9ba9847cc330696e02839f328f7

然后执行经典的三步构建:

./configure make

构建完成后,jeprof位于.bin/目录中,将其复制到当前目录即可。

采集阶段:用dump.sh自动监控内存并抓取画像

dump.sh是整套流程中的“监控+采集”核心脚本,其完整实现位于 dump.sh。它本质上是一个无限循环守护脚本,每 10 分钟检查一次目标进程的内存占用,当内存增量超过 20MB 时,就通过 HTTP 接口抓取一次内存画像。

关键参数

参数默认值说明
内存增长阈值threshold_kb20MB(20 * 1024KB)当进程内存较上次检查增长超过该值时触发 dump
检查间隔sleep_interval600 秒(10 分钟)每次循环后休眠时长
进程 PID命令行参数$1被监控的greptime进程的 PID
画像输出greptime-{timestamp}.gprof通过curl抓取 HTTP dump 结果落盘

运行方式

./dump.sh <PID>

脚本启动后会先校验 PID:参数缺失、非纯数字、或ps无法读取到该进程的 RSS 时,都会给出明确提示;进程短暂不可读时会跳过本轮检查而不会误触发 dump(保留上一次last_mem_kb以避免误报)。

内部工作机理

每轮循环的执行逻辑为:

  1. 通过ps -o rss= -p "$pid"获取当前常驻内存 RSS(单位 KB);
  2. 计算与上一轮记录的last_mem_kb的差值diff_kb
  3. diff_kb大于threshold_kb(20MB),则生成时间戳并执行:
curl -sf -X POST localhost:4000/debug/prof/mem > "greptime-${timestamp}.gprof"

成功则提示画像已保存,失败则删除可能为空的文件并打印 curl 退出码; 4. 更新last_mem_kb为当前值,休眠 600 秒后进入下一轮。

这里curl -X POST localhost:4000/debug/prof/mem命中的是 GreptimeDB HTTP 服务中的内存画像 dump 接口。从 servers 的 HTTP 路由源码 可以看到,/debug/prof命名空间下注册了一组调试路由:POST /mem(dump 画像)、GET /mem/status(查询状态)、POST /mem/activate/POST /mem/deactivate(启停堆采样)、GET/POST /mem/gdump(gdump 开关与状态查询)、POST /mem/symbol(外部 dump 文件符号化)。

在底层,这些接口由 src/common/mem-prof/src/jemalloc.rs 实现:dump_profile通过tikv_jemalloc_ctl::raw::write(PROF_DUMP, ptr)触发 jemalloc 的prof.dump控制变量,将当前堆采样写入临时文件后读出返回字节流;而curl端收到的正是这份 jemalloc heap dump 的原始字节。因此dump.sh采集出的.gprof文件本质上是 jemalloc 格式的堆画像文件。

注意:只有在启动 GreptimeDB 时开启了prof:true(见下一节),prof.dump才可用。dump_profile内部会先通过opt.prof控制变量校验采样是否已启用,未启用时直接返回ProfilingNotEnabled错误。

使用建议

  • 监控期间保持 GreptimeDB 正常承载业务流量,以捕捉真实的、与业务相关的内存增长模式;
  • 积累 2~3 个.gprof文件即可进入下一阶段——这正是gen_flamegraph.sh生成差值火焰图所需的最少样本数;
  • 若进程内存持续平稳增长,则每个 dump 文件都能反映一段窗口内的分配调用栈,非常适合定位“内存缓慢性增长/泄漏”类问题。

开启内存采样:启动带prof:true的 GreptimeDB

dump.sh能采集到数据的前提是 GreptimeDB 以开启堆采样的方式启动。参考 how-to-profile-memory.md,启动方式如下:

# Linux MALLOC_CONF=prof:true ./target/debug/greptime standalone start # macOS _RJEM_MALLOC_CONF=prof:true ./target/debug/greptime standalone start

其中MALLOC_CONF=prof:true是 jemalloc 的标准堆采样开关(macOS 下 tikv-jemalloc 使用_RJEM_MALLOC_CONF前缀)。不开启该开关时,/debug/prof/mem接口会直接报错。

此外,官方 Docker 镜像默认已启用并激活内存采样。这一行为由enable_heap_profiling配置项控制:

[memory] # Whether to enable heap profiling activation during startup. # Default is true. enable_heap_profiling = true

将其设为false即可在镜像中关闭内存采样。

运行时控制接口(可选)

如果不想重启进程,也可以借助 HTTP 接口在运行时启停采样与查询状态:

# 查询当前采样状态 curl -X GET localhost:4000/debug/prof/mem/status # 激活堆采样(若尚未激活) curl -X POST localhost:4000/debug/prof/mem/activate # 停用堆采样 curl -X POST localhost:4000/debug/prof/mem/deactivate # 激活 gdump:每当虚拟内存使用超过历史峰值时自动 dump 画像 curl -X POST localhost:4000/debug/prof/mem/gdump -d 'activate=true' # 停用 gdump curl -X POST localhost:4000/debug/prof/mem/gdump -d 'activate=false' # 查询 gdump 当前状态 curl -X GET localhost:4000/debug/prof/mem/gdump

这些接口对应 jemalloc.rs 中的activate_heap_profile/deactivate_heap_profile/set_gdump_active/is_gdump_active等函数,它们分别读写 jemalloc 的prof.activeprof.gdump控制变量。

手动 dump(可选)

dump.sh之外,也可以随时手动抓取一份画像,并直接输出为不同格式:

# 原始 jemalloc heap dump curl -X POST localhost:4000/debug/prof/mem > greptime.hprof # 直接输出火焰图 SVG curl -X POST "localhost:4000/debug/prof/mem?output=flamegraph" > greptime.svg # 输出 pprof 格式 curl -X POST "localhost:4000/debug/prof/mem?output=proto" > greptime.pprof

从源码看,dump_pprofdump_flamegraph会先调用dump_profile拿到 jemalloc dump,再借助jemalloc-pprof-utils(pprof_util)解析为StackProfile,进而转换为 pprof 或火焰图。这也解释了为什么同一个接口能输出三种格式。

可视化阶段:用gen_flamegraph.sh生成火焰图

火焰图是分析内存分配调用栈最直观的形式:横轴表示采样到的分配,纵轴表示调用栈层级,色块宽度代表该路径上的分配占比,能一眼定位“内存被谁占走”。

前置条件

gen_flamegraph.sh要求当前目录下同时存在两个工具:

  • ./jeprof:解析.gprof文件;
  • ./flamegraph.pl:把 collapsed 格式的栈数据渲染为 SVG 火焰图。

若缺少flamegraph.pl,先运行 get_flamegraph_tool.sh 下载:

./get_flamegraph_tool.sh

该脚本本质就是一行 curl 加授权:

curl https://raw.githubusercontent.com/brendangregg/FlameGraph/master/flamegraph.pl > ./flamegraph.pl chmod +x ./flamegraph.pl

脚本启动时会分别校验./jeprof./flamegraph.pl是否存在,任一缺失都会报错退出(set -e保证任何一步失败即终止)。

使用方式

Usage: ./gen_flamegraph.sh <binary_path> <gprof_directory>
  • <binary_path>greptimedb可执行文件的路径(解析采样时需要符号信息,因此必须是同一份二进制);
  • <gprof_directory>dump.sh输出.gprof文件的目录。

示例调用:

./gen_flamegraph.sh ./greptime .

生成火焰图可能需要几分钟时间。产物位于<gprof_directory>/flamegraphs目录;如果当前目录没有flamegraph.pl,则该目录下只有.collapse文件(collapsed 格式的栈计数文本),同样可用于后续分析。

脚本做了什么

从 gen_flamegraph.sh 的实现看,它对目录内所有.gprof文件(按文件名自然排序)逐一执行两件事:

  1. 单文件火焰图——等价于:
./jeprof <binary> <gprof> --collapse | ./flamegraph.pl > <output>

即先用jeprof ... --collapse把堆画像折叠成栈计数文本,再交给flamegraph.pl渲染成<filename>.svg

  1. 相邻差值火焰图(diff 分析)——从第二个文件起,对每一对相邻画像执行:
./jeprof <binary> --base <gprof1> <gprof2> --collapse | ./flamegraph.pl > <output_diff>

--base模式计算两次采样之间的增量分配,产物命名为<prev>_vs_<next>_diff.svg。这正是 README 建议“积累 2~3 个 gprof 文件”再运行脚本的原因:差值火焰图能精确揭示两个时间点之间新增的内存分配来自哪些调用路径,是定位内存泄漏的利器。

最终flamegraphs目录中每个样本都会产出对应的.collapse.svg,相邻样本之间还会产出_diff.collapse_diff.svg

从 .collapse 单独生成火焰图

如果手头只有.collapse文件(例如在无flamegraph.pl的环境中先完成了 collapse 折叠),可以使用 gen_from_collapse.sh 补出 SVG:

Usage: ./gen_from_collapse.sh <collapse_directory>

它会扫描指定目录下所有.collapse文件,逐个用./flamegraph.pl渲染为同名.svg

交付与分析:将 flamegraphs 目录交给开发者

README 明确建议:将生成的火焰图(整个<gprof_directory>/flamegraphs文件夹)发送给开发者做进一步分析。这样做的原因在于:

  • 火焰图 SVG 携带了完整的调用栈语义,开发者可以快速比对不同时间点的快照以及相邻差值图,判断内存增长是否集中在某条特定调用路径;
  • flamegraphs目录中还保留了.collapse中间产物,便于在本地用其他工具(如 Brendan Gregg 的 FlameGraph 系列脚本)做二次加工;
  • 如需复现或深挖,配合对应的greptimedb二进制与.gprof原始文件,开发者可以在自己环境中重新生成任意火焰图,甚至在支持符号化的环境中获取更精确的函数名。

进阶:外部 dump 文件的符号化

除了解析 GreptimeDB 自身 HTTP 接口产出的画像,how-to-profile-memory.md 还介绍了另一条实用路径:外部生成、内部符号化

如果堆 dump 文件是在外部环境生成的(例如通过MALLOC_CONF="prof:true,prof_prefix:jeprof.out,lg_prof_interval:26"prof_gdump机制自动落盘),可以把它上传到运行中的 GreptimeDB 实例做符号化并直接得到火焰图:

curl -X POST --data-binary @/path/to/jeprof.out.12345.0.i0.heap \ localhost:4000/debug/prof/mem/symbol > flamegraph.svg

该接口对应 mem_prof.rs 中的symbolicate_handler,底层调用 jemalloc.rs 的symbolicate_jeheap:利用当前进程的内存映射(jemalloc_pprof_mappings::MAPPINGS)解析 jeheap 格式文件,再渲染为火焰图 SVG。这在以下场景尤其有用:

  • 在无调试符号的生产环境采集到了堆 dump,希望拿到带符号的火焰图;
  • 希望用 jemalloc 自带的自动 dump 机制(prof_prefix+lg_prof_intervalgdump)批量采集,再统一上传符号化;
  • 需要分析非 GreptimeDB HTTP 接口产出的历史 dump 文件。

排查思路小结

结合上述全部工具,一条完整的 GreptimeDB 内存问题排查路径可以总结为:

  1. MALLOC_CONF=prof:true启动 GreptimeDB(或直接使用官方 Docker 镜像,其默认已启用采样);
  2. find_compiled_jeprof.sh取出jeprof,必要时用get_flamegraph_tool.sh补上flamegraph.pl
  3. dump.sh <PID>在业务运行期间自动监控并抓取多份greptime-{timestamp}.gprof
  4. 积累 2~3 份画像后运行./gen_flamegraph.sh <binary> <gprof_dir>,产出单文件火焰图与相邻差值火焰图;
  5. 将整个flamegraphs目录交给开发者,结合差值火焰图定位新增内存分配的调用栈,最终收敛到具体模块;
  6. 若只有外部 jemalloc dump,可利用/debug/prof/mem/symbol接口符号化。

整套方案零代码侵入、全部依赖仓库内现成脚本(dump.sh、gen_flamegraph.sh、find_compiled_jeprof.sh、get_flamegraph_tool.sh、gen_from_collapse.sh)与 HTTP 调试接口,既可用于开发环境的问题复现,也适合生产环境的低侵入式排查。

【免费下载链接】greptimedbThe open-source observability database. One columnar engine for metrics, logs, and traces, on object storage.项目地址: https://gitcode.com/GitHub_Trending/gr/greptimedb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询