Skip to content

Commit

Permalink
Merge pull request #9 from flavio/multiple-fixes
Browse files Browse the repository at this point in the history
multiple fixes
  • Loading branch information
umanwizard authored Aug 7, 2024
2 parents e57facc + c8d7b38 commit 6b380b9
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 50 deletions.
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
16 changes: 16 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: test suite
on: [push, pull_request]

jobs:
test:
name: cargo test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: |
sudo apt update
sudo apt install -y build-essential libjemalloc-dev
- run: |
make test
make capi
42 changes: 27 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,33 @@ edition = "2021"
publish = true
license = "Apache-2.0"
authors = [
"Frederic Branczyk <[email protected]>",
"Brennan Vincent <[email protected]>",
"Frederic Branczyk <[email protected]>",
"Brennan Vincent <[email protected]>",
]
repository = "https://github.com/polarsignals/rust-jemalloc-pprof"
keywords = ["jemalloc", "pprof", "memory", "profiling", "observability"]
categories = ["development-tools", "development-tools::profiling", "development-tools::debugging", "memory-management"]
categories = [
"development-tools",
"development-tools::profiling",
"development-tools::debugging",
"memory-management",
]
documentation = "https://docs.rs/jemalloc_pprof/latest/jemalloc_pprof/"
homepage = "https://crates.io/crates/jemalloc_pprof"

[workspace.dependencies]
anyhow = "1.0.66"
flate2 = "1.0.24"
libc = "0.2.138"
once_cell = "1.16.0"
prost = { version = "0.13.1", features = ["no-recursion-limit"] }
tempfile = "3.2.0"
tikv-jemalloc-ctl = { version = "0.5.0", features = ["use_std"] }
tracing = "0.1.37"
tokio = { version = "1.32.0", features = ["time", "sync"] }
paste = "1.0.11"
num = "0.4.0"
errno = "0.3.8"
anyhow = "1"
flate2 = "1.0"
libc = "0.2"
once_cell = "1.19"
prost = { version = "0.13", features = ["no-recursion-limit"] }
tempfile = "3.11"
tikv-jemalloc-ctl = { version = "0.6", features = ["use_std"] }
tracing = "0.1"
tokio = { version = "1", features = ["time", "sync"] }
paste = "1.0"
num = "0.4"
errno = "0.3"
util = { path = "./util", version = "0.4", package = "pprof_util" }
mappings = { path = "./mappings", version = "0.5" }

Expand All @@ -44,3 +49,10 @@ once_cell.workspace = true
tracing.workspace = true
tempfile.workspace = true
tokio.workspace = true

[dev-dependencies]
tikv-jemallocator = "0.6"
axum = "0.7"
# re-import tokio to enable all its features. This is required to
# successfully compile the test snipptes that are part of the documentation
tokio = { version = "1", features = ["full"] }
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.PHONY: capi
capi:
cargo build -p capi --release

.PHONY: fmt
fmt:
cargo fmt --all -- --check

.PHONY: lint
lint:
cargo clippy --workspace -- -D warnings

.PHONY: doc
doc:
RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo doc --all-features --no-deps

.PHONY: test
test: fmt lint doc
cargo test --workspace

.PHONY: clean
clean:
cargo clean
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ When adding `tikv-jemallocator` as a dependency, make sure to enable the `profil
```toml
[dependencies]
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { version = "0.5.4", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
tikv-jemallocator = { version = "0.6.0", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
```

> Note: We also recommend enabling the `unprefixed_malloc_on_supported_platforms` feature, not strictly necessary, but will influence the rest of the usage.
Then configure the global allocator and configure it with profiling enabled.

```rust
```rust,no_run
#[cfg(not(target_env = "msvc"))]
#[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
Expand All @@ -48,7 +48,7 @@ pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0

We recommend serving the profiling data on an HTTP server such as [axum](https://github.com/tokio-rs/axum), that could look like this, and we'll intentionally include a 4mb allocation to trigger sampling.

```rust
```rust,no_run
#[tokio::main]
async fn main() {
let mut v = vec![];
Expand Down Expand Up @@ -88,7 +88,7 @@ fn require_profiling_activated(prof_ctl: &jemalloc_pprof::JemallocProfCtl) -> Re

Then running the application, we can capture a profile and view it the pprof toolchain.

```
```shell
curl localhost:3000/debug/pprof/heap > heap.pb.gz
pprof -http=:8080 heap.pb.gz
```
Expand Down Expand Up @@ -116,21 +116,21 @@ functionality is exposed via the `capi` (C API) package.

The following prerequisites are necessary to build the C API package:

* Working Rust and C toolchains. The former can be installed by
following the instructions at https://rustup.rs . The latter can be
- Working Rust and C toolchains. The former can be installed by
following the instructions at <https://rustup.rs> . The latter can be
installed via the distribution's package manager. For example, on
Ubuntu, run `sudo apt install build-essential`.
* `jemalloc` and its development headers. For example, on Ubuntu, run
`sudo apt install jemalloc-dev`.
- `jemalloc` and its development headers. For example, on Ubuntu, run
`sudo apt install libjemalloc-dev`.

Once the prerequisites are installed, the library can be built by
running `cargo build -p capi --release`. There are three files of
running `make capi`. There are three files of
interest:

* The library itself, produced at
- The library itself, produced at
`target/release/libjemalloc_pprof.so`
* A header file, at `capi/include/jemalloc_pprof.h`
* A manual page, at `capi/man/jemalloc_pprof.3`.
- A header file, at `capi/include/jemalloc_pprof.h`
- A manual page, at `capi/man/jemalloc_pprof.3`.

The procedure for installing and using these files depends on your
distribution and build system.
Expand All @@ -147,7 +147,7 @@ Once that is done, profiling can be enabled either by setting the
`MALLOC_CONF` variable or by defining a symbol called `malloc_conf` in
the binary. For example:

``` shell
```shell
export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:19"
```

Expand All @@ -160,7 +160,7 @@ enabled, a profile may be dumped in pprof format via the
This program allocates between 1 and 10 MiB every 100 milliseconds,
and dumps a profile to the file `my_profile` every 2 seconds.

``` c
```c
#include <assert.h>
#include <errno.h>
#include <unistd.h>
Expand Down Expand Up @@ -283,11 +283,11 @@ repeatedly_dump(void *ignored)
fprintf(stderr, "errno: %d\n", errno);
continue;
}
if (buf) {
if (buf) {
FILE *file = fopen("my_profile", "w");
assert(file);

fwrite(buf, sizeof(char), len, file);
fwrite(buf, sizeof(char), len, file);
fclose(file);
printf("dumped pprof of size %lu\n", len);
free(buf);
Expand Down
25 changes: 15 additions & 10 deletions capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use std::ptr::null_mut;

use errno::{set_errno, Errno};
use libc::{c_char, c_int, c_void, size_t};
use mappings::MAPPINGS;
use tempfile::NamedTempFile;
use util::{parse_jeheap, MAPPINGS};
use util::parse_jeheap;

pub const JP_SUCCESS: c_int = 0;
pub const JP_FAILURE: c_int = -1;
Expand All @@ -27,7 +28,7 @@ extern "C" {
enum Error {
Io(std::io::Error),
Mallctl(c_int),
Anyhow(anyhow::Error),
ParseProfile(),
}

impl From<std::io::Error> for Error {
Expand All @@ -36,12 +37,6 @@ impl From<std::io::Error> for Error {
}
}

impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Self::Anyhow(e)
}
}

fn dump_pprof_inner() -> Result<Vec<u8>, Error> {
let f = NamedTempFile::new()?;
let path = CString::new(f.path().as_os_str().as_bytes().to_vec()).unwrap();
Expand All @@ -62,7 +57,8 @@ fn dump_pprof_inner() -> Result<Vec<u8>, Error> {
}

let dump_reader = BufReader::new(f);
let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
let profile =
parse_jeheap(dump_reader, MAPPINGS.as_deref()).map_err(|_| Error::ParseProfile())?;
let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
Ok(pprof)
}
Expand All @@ -78,7 +74,9 @@ fn dump_pprof_inner() -> Result<Vec<u8>, Error> {
/// If `JP_FAILURE` is returned, the values pointed to by `buf_out` and `n_out`
/// are unspecified.
///
/// SAFETY: You probably don't want to call this from Rust.
/// # Safety
///
/// You probably don't want to call this from Rust.
/// Use the Rust API instead.
#[no_mangle]
pub unsafe extern "C" fn dump_jemalloc_pprof(buf_out: *mut *mut u8, n_out: *mut size_t) -> c_int {
Expand All @@ -97,6 +95,13 @@ pub unsafe extern "C" fn dump_jemalloc_pprof(buf_out: *mut *mut u8, n_out: *mut
return JP_FAILURE;
}
};

// Disable clippy warning.
// usize is defined to be the same as uintptr_t (AKA have the same representation as a pointer),
// which is different from size_t, which is the maximum size of an array.
// This is not usually an issue, except on some platforms like CHERI which store extra information in the pointer.
// On those platforms, usize will be 128 bits, while size_t is 64 bit.
#[allow(clippy::useless_conversion)]
let len: size_t = buf.len().try_into().expect("absurd length");
let p = if len > 0 {
// leak is ok, consumer is responsible for freeing
Expand Down
9 changes: 2 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,8 @@ pub async fn deactivate_jemalloc_profiling() {
}

/// Per-process singleton for controlling jemalloc profiling.
pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> = Lazy::new(|| {
if let Some(ctl) = JemallocProfCtl::get() {
Some(Arc::new(Mutex::new(ctl)))
} else {
None
}
});
pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> =
Lazy::new(|| JemallocProfCtl::get().map(|ctl| Arc::new(Mutex::new(ctl))));

/// Metadata about a jemalloc heap profiler.
#[derive(Copy, Clone, Debug)]
Expand Down
4 changes: 2 additions & 2 deletions util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,12 @@ pub fn parse_jeheap<R: BufRead>(
// number is the inverse probability of a byte being sampled.
let sampling_rate: f64 = str::parse(first_line.trim_start_matches("heap_v2/"))?;

while let Some(line) = lines.next() {
for line in &mut lines {
let line = line?;
let line = line.trim();

let words: Vec<_> = line.split_ascii_whitespace().collect();
if words.len() > 0 && words[0] == "@" {
if !words.is_empty() && words[0] == "@" {
if cur_stack.is_some() {
bail!("Stack without corresponding weight!")
}
Expand Down

0 comments on commit 6b380b9

Please sign in to comment.