9 个版本 (4 个重大更改)
0.4.2 | 2024年7月8日 |
---|---|
0.4.1 | 2024年7月3日 |
0.3.2 | 2024年7月2日 |
0.2.0 | 2024年6月27日 |
0.0.1 | 2023年12月14日 |
#22 在 分析
每月下载量 43,030
在 2 个库中使用 (通过 dragonfly-client)
69KB
855 行
rust-jemalloc-pprof
一个 Rust 库,用于收集和转换来自 jemalloc 分配器的堆分析数据,并将其转换为 pprof 格式。
要了解如何与 Polar Signals Cloud 结合使用以持续收集分析数据,请参阅 与 Polar Signals Cloud 一起使用 部分。
此代码最初是作为 Materialize 的一部分开发的,然后通过合作提取为独立的库。
要求
目前,此库仅支持 Linux。
此外,您必须能够将您的分配器切换到 jemalloc
。如果您出于任何原因需要继续使用默认的系统分配器,则此库将无用。
使用方法
内部此库使用 tikv-jemalloc-ctl
与 jemalloc 交互,因此要使用它,您必须通过 tikv-jemallocator
库使用 jemalloc 分配器。
当将 tikv-jemallocator
作为依赖项添加时,请确保启用 profiling
功能。
[dependencies]
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { version = "0.5.4", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
注意:我们还建议启用
unprefixed_malloc_on_supported_platforms
功能,这并非绝对必要,但会影响其余的使用。
然后配置全局分配器,并启用分析功能进行配置。
#[cfg(not(target_env = "msvc"))]
#[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
#[allow(non_upper_case_globals)]
#[export_name = "malloc_conf"]
pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0";
如果您不使用
unprefixed_malloc_on_supported_platforms
功能,则必须将其命名为_rjem_malloc_conf
而不是malloc_conf
。
2^19字节(512KiB)是默认的采样周期配置,但我们建议明确指定。要了解更多关于jemalloc采样信息,请查看详细文档。
我们建议在HTTP服务器上提供分析数据,例如axum,其配置可能如下所示,并且我们将故意包含一个4MB的分配来触发采样。
#[tokio::main]
async fn main() {
let mut v = vec![];
for i in 0..1000000 {
v.push(i);
}
let app = axum::Router::new()
.route("/debug/pprof/heap", axum::routing::get(handle_get_heap));
// run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn handle_get_heap() -> Result<impl IntoResponse, (StatusCode, String)> {
let mut prof_ctl = jemalloc_pprof::PROF_CTL.as_ref().unwrap().lock().await;
require_profiling_activated(&prof_ctl)?;
let pprof = prof_ctl
.dump_pprof()
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
Ok(pprof)
}
/// Checks whether jemalloc profiling is activated an returns an error response if not.
fn require_profiling_activated(prof_ctl: &jemalloc_pprof::JemallocProfCtl) -> Result<(), (StatusCode, String)> {
if prof_ctl.activated() {
Ok(())
} else {
Err((axum::http::StatusCode::FORBIDDEN, "heap profiling not activated".into()))
}
}
然后运行应用程序,我们可以使用pprof工具链捕获配置文件并查看。
curl localhost:3000/debug/pprof/heap > heap.pb.gz
pprof -http=:8080 heap.pb.gz
注意:分析数据未符号化,因此需要在路径中可用
addr2line
或llvm-addr2line
,并且pprof需要能够发现相应的调试信息。
可写临时目录
此库的工作方式是创建一个新的临时文件(在平台特定的默认临时目录中),并指示jemalloc将配置文件转储到该文件。因此,相应的平台临时目录必须可由进程写入。在读取并将其转换为pprof后,文件将通过析构函数进行清理。单个配置文件通常只有几KB大,因此不需要大量空间,但它不是零,并且需要可写。
与Polar Signals Cloud一起使用
Polar Signals Cloud允许持续收集堆分析数据,因此您始终有正确的分析数据可用,无需搜索正确数据,您已经拥有了它!
Polar Signals Cloud支持pprof格式的任何内容,因此公开上述pprof端点的进程可以被像抓取文档中详述的那样抓取。
从C或C++使用
将当前jemalloc堆配置文件以pprof格式转储的功能公开给C和C++(或任何其他可以使用jemalloc并通过C ABI链接库的语言)。此功能通过capi
(C API)包公开。
构建
以下先决条件是构建C API包所必需的
- 有效的Rust和C工具链。前者可以通过遵循https://rustup.rs上的说明进行安装。后者可以通过发行版的包管理器进行安装。例如,在Ubuntu上,运行
sudo apt install build-essential
。 jemalloc
及其开发头文件。例如,在Ubuntu上,运行sudo apt install jemalloc-dev
。
一旦安装了先决条件,可以通过运行以下命令构建库:cargo build -p capi --release
。有三个文件值得关注
- 库本身,在
target/release/libjemalloc_pprof.so
生成 - 头文件,位于
capi/include/jemalloc_pprof.h
- 手册页,位于
capi/man/jemalloc_pprof.3
安装和使用这些文件的过程取决于您的发行版和构建系统。
使用
确保您的二进制文件链接到jemalloc和jemalloc_pprof,通过传递链接器标志-ljemalloc -ljemalloc_pprof
。确保传递这些标志的流程取决于您的构建系统,目前超出了本文档的范围。
完成此操作后,可以通过设置MALLOC_CONF
变量或在二进制文件中定义名为malloc_conf
的符号来启用分析。例如
export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:19"
参见jemalloc
手册页面以获取更多详细信息。启用分析后,可以通过dump_jemalloc_pprof
函数将分析以pprof格式导出。
示例
此程序每100毫秒分配1至10 MiB,每2秒将分析导出到文件my_profile
中。
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <jemalloc_pprof.h>
void
a()
{
size_t sz = 1 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
b()
{
size_t sz = 2 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
c()
{
size_t sz = 3 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
d()
{
size_t sz = 4 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
e()
{
size_t sz = 5 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
f()
{
size_t sz = 6 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
g()
{
size_t sz = 7 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
h()
{
size_t sz = 8 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
j()
{
size_t sz = 9 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void
k()
{
size_t sz = 10 * 1024 * 1024;
char *x = malloc(sz);
for (size_t i = 0; i < sz; ++i) {
x[i] = '\0';
}
}
void *
repeatedly_dump(void *ignored)
{
char *buf;
size_t len = 0;
int result;
for (;;) {
sleep(2);
result = dump_jemalloc_pprof(&buf, &len);
if (result != JP_SUCCESS) {
fprintf(stderr, "errno: %d\n", errno);
continue;
}
if (buf) {
FILE *file = fopen("my_profile", "w");
assert(file);
fwrite(buf, sizeof(char), len, file);
fclose(file);
printf("dumped pprof of size %lu\n", len);
free(buf);
}
}
return NULL;
}
int
main()
{
pthread_t tid;
int result;
result = pthread_create(&tid, NULL, repeatedly_dump, NULL);
assert(!result);
for (;;) {
usleep(100000);
switch (rand() % 10) {
case 0:
a();
break;
case 1:
b();
break;
case 2:
c();
break;
case 3:
d();
break;
case 4:
e();
break;
case 5:
f();
break;
case 6:
g();
break;
case 7:
h();
break;
case 8:
j();
break;
case 9:
k();
break;
}
}
}
依赖项
~8–18MB
~273K SLoC