|
| 1 | +# Profiling Rust Memory Usage with Jemalloc |
| 2 | + |
| 3 | +Our jemalloc integration makes it possible to profile heap usage of |
| 4 | +Rust programs, as long as you are willing to use the jemalloc |
| 5 | +allocator. |
| 6 | + |
| 7 | +# Setup Instructions |
| 8 | + |
| 9 | +## Using jemalloc and rust-jemalloc-pprof |
| 10 | + |
| 11 | +Add the |
| 12 | +[jemalloc_pprof](https://crates.io/crates/jemalloc-pprof) |
| 13 | +and [tikv-jemallocator](https://crates.io/crates/tikv-jemallocator) |
| 14 | +packages to your project. Make sure the latter has the `profiling` and |
| 15 | +`unprefixed_malloc_on_supported_platforms` features: |
| 16 | + |
| 17 | +``` bash |
| 18 | +cargo add jemalloc_pprof |
| 19 | +cargo add tikv-jemallocator --features profiling,unprefixed_malloc_on_supported_platforms |
| 20 | +``` |
| 21 | + |
| 22 | +Then, in your program's `main.rs` set your global allocator to |
| 23 | +jemalloc and configure it with the special `malloc_conf` symbol: |
| 24 | + |
| 25 | +``` rust |
| 26 | +#[global_allocator] |
| 27 | +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; |
| 28 | + |
| 29 | +#[unsafe(export_name = "malloc_conf")] |
| 30 | +#[allow(non_upper_case_globals)] |
| 31 | +pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0"; |
| 32 | +``` |
| 33 | + |
| 34 | +## Exposing pprof profiles with http: |
| 35 | + |
| 36 | +Call the `dump_pprof` method to dump profiles to memory. We |
| 37 | +recommending exposing an HTTP interface for these profiles that can be |
| 38 | +scraped by Parca. |
| 39 | + |
| 40 | +Here is how to do so using Axum: |
| 41 | + |
| 42 | +``` rust |
| 43 | +async fn handle_get_heap() -> Result<impl IntoResponse, (StatusCode, String)> { |
| 44 | + let mut prof_ctl = jemalloc_pprof::PROF_CTL |
| 45 | + .as_ref() |
| 46 | + .ok_or(( |
| 47 | + StatusCode::INTERNAL_SERVER_ERROR, |
| 48 | + "Profiling not available".to_string(), |
| 49 | + ))? |
| 50 | + .lock() |
| 51 | + .await; |
| 52 | + |
| 53 | + let pprof = prof_ctl |
| 54 | + .dump_pprof() |
| 55 | + .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?; |
| 56 | + |
| 57 | + Ok(pprof) |
| 58 | +} |
| 59 | + |
| 60 | +fn main() { |
| 61 | + let app = Router::new().route("/debug/pprof/heap", get(handle_get_heap)); |
| 62 | + |
| 63 | + let rt = Runtime::new().unwrap(); |
| 64 | + |
| 65 | + rt.spawn(async { |
| 66 | + let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") |
| 67 | + .await |
| 68 | + .expect("Failed to bind to port 3000"); |
| 69 | + axum::serve(listener, app).await.expect("Server failed"); |
| 70 | + }); |
| 71 | +} |
| 72 | + |
| 73 | +``` |
| 74 | + |
| 75 | +## Uploading symbols with `parca-debuginfo` (only if not using |
| 76 | +`parca-agent`). |
| 77 | + |
| 78 | +If you are already using `parca-agent`, all relevant symbols will be |
| 79 | +found and uploaded to the backend automatically. Otherwise, you will |
| 80 | +need to manually upload them using the `parca-debuginfo` CLI. For example, |
| 81 | +assuming Parca is running on localhost: |
| 82 | + |
| 83 | +``` bash |
| 84 | +parca-debuginfo upload --store-address=localhost:7070 --insecure path/to/your/binary |
| 85 | +``` |
| 86 | + |
| 87 | +## Scraping with Parca |
| 88 | + |
| 89 | +In order to continually scrape the endpoint, add a stanza like the |
| 90 | +following to your `parca.yaml`, assuming (as in the example above) the |
| 91 | +profiles are being served via HTTP on `127.0.0.1:3000`: |
| 92 | + |
| 93 | +``` yaml |
| 94 | +scrape_configs: |
| 95 | + - job_name: "rjemp" |
| 96 | + scrape_interval: "10s" |
| 97 | + static_configs: |
| 98 | + - targets: [ '127.0.0.1:3000' ] |
| 99 | + profiling_config: |
| 100 | + pprof_config: |
| 101 | + heap: |
| 102 | + enabled: true |
| 103 | + path: /debug/pprof/heap |
| 104 | +``` |
| 105 | +
|
| 106 | +This should cause profiles to appear in the Parca UI. |
0 commit comments