├── .cargo └── config.toml ├── jeprofl-ebpf ├── .helix │ └── config.toml ├── .cargo │ └── config.toml ├── .vim │ └── coc-settings.json ├── .vscode │ └── settings.json ├── rust-toolchain.toml ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── assets ├── bpftop.png └── flamegraph.png ├── Cargo.toml ├── .vim └── coc-settings.json ├── .vscode └── settings.json ├── .dir-locals.el ├── xtask ├── Cargo.toml └── src │ ├── main.rs │ ├── build.rs │ ├── run.rs │ └── build_ebpf.rs ├── .gitignore ├── jeprofl-common ├── Cargo.toml └── src │ └── lib.rs ├── jeprofl ├── src │ ├── snapshots │ │ ├── jeprof__collector__test__tests__print_histogram_empty.snap │ │ ├── jeprof__collector__test__tests__print_histogram_single_allocation.snap │ │ ├── jeprof__collector__test__tests__print_histogram_many_small_allocations.snap │ │ ├── jeprof__collector__test__tests__print_histogram_large_allocations.snap │ │ └── jeprof__collector__test__tests__print_histogram_multiple_sizes.snap │ ├── resolver.rs │ ├── main.rs │ └── collector.rs └── Cargo.toml ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /jeprofl-ebpf/.helix/config.toml: -------------------------------------------------------------------------------- 1 | [editor] 2 | workspace-lsp-roots = [] 3 | -------------------------------------------------------------------------------- /assets/bpftop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdeafbeef/jeprofl/HEAD/assets/bpftop.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["xtask", "jeprofl", "jeprofl-common"] 4 | -------------------------------------------------------------------------------- /assets/flamegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdeafbeef/jeprofl/HEAD/assets/flamegraph.png -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "jeprof-ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "jeprof-ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((prog-mode . ((lsp-rust-analyzer-linked-projects . ["Cargo.toml" "jeprof-ebpf/Cargo.toml"])))) 2 | -------------------------------------------------------------------------------- /jeprofl-ebpf/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "../target" 3 | target = "bpfel-unknown-none" 4 | 5 | [unstable] 6 | build-std = ["core"] 7 | -------------------------------------------------------------------------------- /jeprofl-ebpf/.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "bpfel-unknown-none", 3 | "rust-analyzer.checkOnSave.allTargets": false 4 | } 5 | -------------------------------------------------------------------------------- /jeprofl-ebpf/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "bpfel-unknown-none", 3 | "rust-analyzer.checkOnSave.allTargets": false 4 | } 5 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1" 8 | clap = { version = "4.1", features = ["derive"] } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/master/Rust.gitignore 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | debug/ 6 | target/ 7 | 8 | # These are backup files generated by rustfmt 9 | **/*.rs.bk 10 | /.idea 11 | -------------------------------------------------------------------------------- /jeprofl-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jeprofl-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = [] 8 | user = ["aya"] 9 | 10 | [dependencies] 11 | aya = { optional = true, version = "0.13.0" } 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | -------------------------------------------------------------------------------- /jeprofl/src/snapshots/jeprof__collector__test__tests__print_histogram_empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: jeprof/src/collector.rs 3 | expression: buf 4 | --- 5 | Size | Count | Percentage | Distribution 6 | ----------+-----------+------------+-------------------------------------------------- 7 | Total allocations: 0 B in 0 allocations 8 | -------------------------------------------------------------------------------- /jeprofl-ebpf/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | # The source code of rustc, provided by the rust-src component, is needed for 4 | # building eBPF programs. 5 | components = [ 6 | "cargo", 7 | "clippy", 8 | "rust-docs", 9 | "rust-src", 10 | "rust-std", 11 | "rustc", 12 | "rustfmt", 13 | ] 14 | -------------------------------------------------------------------------------- /jeprofl/src/snapshots/jeprof__collector__test__tests__print_histogram_single_allocation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: jeprof/src/collector.rs 3 | expression: buf 4 | --- 5 | Size | Count | Percentage | Distribution 6 | ----------+-----------+------------+-------------------------------------------------- 7 | 512 B | 1 | 100.00% | ################################################## 8 | Total allocations: 1023 B in 1 allocations 9 | -------------------------------------------------------------------------------- /jeprofl/src/snapshots/jeprof__collector__test__tests__print_histogram_many_small_allocations.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: jeprof/src/collector.rs 3 | expression: buf 4 | --- 5 | Size | Count | Percentage | Distribution 6 | ----------+-----------+------------+-------------------------------------------------- 7 | 1 B | 1000 | 99.90% | ################################################## 8 | 512 B | 1 | 0.10% | 9 | Total allocations: 2.0 kiB in 1001 allocations 10 | -------------------------------------------------------------------------------- /jeprofl/src/snapshots/jeprof__collector__test__tests__print_histogram_large_allocations.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: jeprof/src/collector.rs 3 | expression: buf 4 | --- 5 | Size | Count | Percentage | Distribution 6 | ----------+-----------+------------+-------------------------------------------------- 7 | 1.0 MiB | 1 | 50.00% | ################################################## 8 | 1.0 GiB | 1 | 50.00% | ################################################## 9 | Total allocations: 1.0 GiB in 2 allocations 10 | -------------------------------------------------------------------------------- /jeprofl/src/snapshots/jeprof__collector__test__tests__print_histogram_multiple_sizes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: jeprof/src/collector.rs 3 | expression: buf 4 | --- 5 | Size | Count | Percentage | Distribution 6 | ----------+-----------+------------+-------------------------------------------------- 7 | 1 B | 1 | 33.33% | ################################################## 8 | 512 B | 1 | 33.33% | ################################################## 9 | 1.0 kiB | 1 | 33.33% | ################################################## 10 | Total allocations: 1.5 kiB in 3 allocations 11 | -------------------------------------------------------------------------------- /jeprofl-ebpf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jeprof-ebpf" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | aya-ebpf = "0.1.0" 8 | aya-log-ebpf = "0.1.0" 9 | jeprofl-common = { path = "../jeprofl-common" } 10 | 11 | [[bin]] 12 | name = "jeprofl" 13 | path = "src/main.rs" 14 | 15 | [profile.dev] 16 | opt-level = 3 17 | debug = false 18 | debug-assertions = false 19 | overflow-checks = false 20 | lto = true 21 | panic = "abort" 22 | incremental = false 23 | codegen-units = 1 24 | rpath = false 25 | 26 | [profile.release] 27 | lto = true 28 | panic = "abort" 29 | codegen-units = 1 30 | 31 | [workspace] 32 | members = [] 33 | 34 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod build_ebpf; 2 | mod build; 3 | mod run; 4 | 5 | use std::process::exit; 6 | 7 | use clap::Parser; 8 | 9 | #[derive(Debug, Parser)] 10 | pub struct Options { 11 | #[clap(subcommand)] 12 | command: Command, 13 | } 14 | 15 | #[derive(Debug, Parser)] 16 | enum Command { 17 | BuildEbpf(build_ebpf::Options), 18 | Build(build::Options), 19 | Run(run::Options), 20 | } 21 | 22 | fn main() { 23 | let opts = Options::parse(); 24 | 25 | use Command::*; 26 | let ret = match opts.command { 27 | BuildEbpf(opts) => build_ebpf::build_ebpf(opts), 28 | Run(opts) => run::run(opts), 29 | Build(opts) => build::build(opts), 30 | }; 31 | 32 | if let Err(e) = ret { 33 | eprintln!("{e:#}"); 34 | exit(1); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jeprofl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jeprofl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | aya = "0.13.0" 9 | aya-log = "0.2" 10 | clap = { version = "4.1", features = ["derive"] } 11 | jeprofl-common = { path = "../jeprofl-common", features = ["user"] } 12 | anyhow = "1" 13 | env_logger = "0.11.5" 14 | libc = "0.2" 15 | log = "0.4" 16 | tokio = { version = "1.25", features = ["macros", "rt", "rt-multi-thread", "net", "signal", "time"] } 17 | blazesym = "0.2.0-rc.1" 18 | rustc-hash = "2.0.0" 19 | bytesize = "1.3.0" 20 | derive_more = { version = "1.0.0", features = ["full"] } 21 | minus = { version = "5.6.1", features = ["dynamic_output"] } 22 | insta = "1.40.0" 23 | crossterm = "0.27" 24 | scopeguard = "1.2.0" 25 | csv = "1.3.0" 26 | inferno = "0.11.21" 27 | itertools = "0.13.0" 28 | 29 | [[bin]] 30 | name = "jeprofl" 31 | path = "src/main.rs" 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 The jeprofl Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /xtask/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::Context as _; 4 | use clap::Parser; 5 | 6 | use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions}; 7 | 8 | #[derive(Debug, Parser)] 9 | pub struct Options { 10 | /// Set the endianness of the BPF target 11 | #[clap(default_value = "bpfel-unknown-none", long)] 12 | pub bpf_target: Architecture, 13 | /// Build and run the release target 14 | #[clap(long)] 15 | pub release: bool, 16 | } 17 | 18 | /// Build the project 19 | fn build_project(opts: &Options) -> Result<(), anyhow::Error> { 20 | let mut args = vec!["build"]; 21 | if opts.release { 22 | args.push("--release") 23 | } 24 | let status = Command::new("cargo") 25 | .args(&args) 26 | .env("RUSTFLAGS", "-Cforce-frame-pointers=true") 27 | .status() 28 | .expect("failed to build userspace"); 29 | assert!(status.success()); 30 | Ok(()) 31 | } 32 | 33 | /// Build our ebpf program and the project 34 | pub fn build(opts: Options) -> Result<(), anyhow::Error> { 35 | // build our ebpf program followed by our application 36 | build_ebpf(BuildOptions { 37 | target: opts.bpf_target, 38 | release: opts.release, 39 | }) 40 | .context("Error while building eBPF program")?; 41 | build_project(&opts).context("Error while building userspace application")?; 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /xtask/src/run.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::Context as _; 4 | use clap::Parser; 5 | 6 | use crate::{ 7 | build::{build, Options as BuildOptions}, 8 | build_ebpf::Architecture, 9 | }; 10 | 11 | #[derive(Debug, Parser)] 12 | pub struct Options { 13 | /// Set the endianness of the BPF target 14 | #[clap(default_value = "bpfel-unknown-none", long)] 15 | pub bpf_target: Architecture, 16 | /// Build and run the release target 17 | #[clap(long)] 18 | pub release: bool, 19 | /// The command used to wrap your application 20 | #[clap(short, long, default_value = "sudo -E")] 21 | pub runner: String, 22 | /// Arguments to pass to your application 23 | #[clap(name = "args", last = true)] 24 | pub run_args: Vec, 25 | } 26 | 27 | /// Build and run the project 28 | pub fn run(opts: Options) -> Result<(), anyhow::Error> { 29 | // Build our ebpf program and the project 30 | build(BuildOptions { 31 | bpf_target: opts.bpf_target, 32 | release: opts.release, 33 | }) 34 | .context("Error while building project")?; 35 | 36 | // profile we are building (release or debug) 37 | let profile = if opts.release { "release" } else { "debug" }; 38 | let bin_path = format!("target/{profile}/jeprofl"); 39 | 40 | // arguments to pass to the application 41 | let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); 42 | 43 | // configure args 44 | let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); 45 | args.push(bin_path.as_str()); 46 | args.append(&mut run_args); 47 | 48 | // run the command 49 | let status = Command::new(args.first().expect("No first argument")) 50 | .args(args.iter().skip(1)) 51 | .status() 52 | .expect("failed to run the command"); 53 | 54 | if !status.success() { 55 | anyhow::bail!("Failed to run `{}`", args.join(" ")); 56 | } 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /xtask/src/build_ebpf.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Command}; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum Architecture { 7 | BpfEl, 8 | BpfEb, 9 | } 10 | 11 | impl std::str::FromStr for Architecture { 12 | type Err = String; 13 | 14 | fn from_str(s: &str) -> Result { 15 | Ok(match s { 16 | "bpfel-unknown-none" => Architecture::BpfEl, 17 | "bpfeb-unknown-none" => Architecture::BpfEb, 18 | _ => return Err("invalid target".to_owned()), 19 | }) 20 | } 21 | } 22 | 23 | impl std::fmt::Display for Architecture { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.write_str(match self { 26 | Architecture::BpfEl => "bpfel-unknown-none", 27 | Architecture::BpfEb => "bpfeb-unknown-none", 28 | }) 29 | } 30 | } 31 | 32 | #[derive(Debug, Parser)] 33 | pub struct Options { 34 | /// Set the endianness of the BPF target 35 | #[clap(default_value = "bpfel-unknown-none", long)] 36 | pub target: Architecture, 37 | /// Build the release target 38 | #[clap(long)] 39 | pub release: bool, 40 | } 41 | 42 | pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> { 43 | let dir = PathBuf::from("jeprofl-ebpf"); 44 | let target = format!("--target={}", opts.target); 45 | let mut args = vec!["build", target.as_str(), "-Z", "build-std=core"]; 46 | if opts.release { 47 | args.push("--release") 48 | } 49 | 50 | // Command::new creates a child process which inherits all env variables. This means env 51 | // vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed 52 | // so the rust-toolchain.toml file in the -ebpf folder is honored. 53 | 54 | let status = Command::new("cargo") 55 | .current_dir(dir) 56 | .env_remove("RUSTUP_TOOLCHAIN") 57 | .args(&args) 58 | .status() 59 | .expect("failed to build bpf program"); 60 | assert!(status.success()); 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /jeprofl/src/resolver.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::num::NonZeroU32; 3 | 4 | use aya::maps::stack_trace::StackTrace; 5 | use blazesym::symbolize::{Input, Process, Source, Symbolized}; 6 | use blazesym::Pid; 7 | use itertools::Itertools; 8 | 9 | pub struct Resolver { 10 | symbolizer: blazesym::symbolize::Symbolizer, 11 | } 12 | 13 | impl Resolver { 14 | pub fn new() -> Resolver { 15 | let symbolizer = blazesym::symbolize::Symbolizer::new(); 16 | Resolver { symbolizer } 17 | } 18 | 19 | pub fn resolve_stacktrace( 20 | &self, 21 | stacktrace: &StackTrace, 22 | pid: u32, 23 | ) -> Result { 24 | let pid = Pid::Pid(NonZeroU32::new(pid).unwrap()); 25 | let stacktrace: Vec<_> = stacktrace.frames().iter().map(|x| x.ip).collect(); 26 | let stacktrace = Input::AbsAddr(stacktrace.as_slice()); 27 | let res = self 28 | .symbolizer 29 | .symbolize(&Source::Process(Process::new(pid)), stacktrace)? 30 | .into_iter() 31 | .map(|x| match x { 32 | Symbolized::Sym(s) => OwnedSymbol { 33 | address: s.addr, 34 | symbol: s.name.to_string(), 35 | }, 36 | Symbolized::Unknown(reason) => OwnedSymbol { 37 | address: 0, 38 | symbol: reason.to_string(), 39 | }, 40 | }) 41 | .collect(); 42 | 43 | Ok(ResolvedStackTrace { symbols: res }) 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone)] 48 | pub struct ResolvedStackTrace { 49 | pub symbols: Vec, 50 | } 51 | 52 | impl ResolvedStackTrace { 53 | pub fn as_inferno(&self, calculation: u64) -> String { 54 | let mut symbols: String = self.symbols.iter().map(|x| x.symbol.clone()).join(";"); 55 | symbols.push(' '); 56 | symbols.push_str(&calculation.to_string()); 57 | symbols 58 | } 59 | } 60 | 61 | #[derive(Debug, Clone)] 62 | pub struct OwnedSymbol { 63 | pub address: u64, 64 | pub symbol: String, 65 | } 66 | -------------------------------------------------------------------------------- /jeprofl-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub const MIN_ALLOC_INDEX: u32 = 0; 4 | pub const MAX_ALLOC_INDEX: u32 = 1; 5 | pub const COUNT_INDEX: u32 = 2; 6 | pub const SAMPLE_EVERY_INDEX: u32 = 3; 7 | pub const FUNCTION_INFO_INDEX: u32 = 4; 8 | 9 | const MAX_TRACKED_ALLOCATION_SIZE: usize = const { 10 | const GIB: usize = 1024 * 1024 * 1024; 11 | const MAX: usize = 16 * GIB; 12 | MAX.ilog2() as usize 13 | }; 14 | 15 | #[repr(C)] 16 | #[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)] 17 | pub struct HistogramKey { 18 | pid_stack: u64, 19 | cpu: u64, // for alignment 20 | } 21 | 22 | impl HistogramKey { 23 | pub fn new(pid: u32, stack_id: u32, cpu: u32) -> Self { 24 | Self { 25 | pid_stack: ((pid as u64) << 32 | stack_id as u64), 26 | cpu: cpu as u64, 27 | } 28 | } 29 | 30 | pub fn into_parts(&self) -> UnpackedHistogramKey { 31 | let pid = (self.pid_stack >> 32) as u32; 32 | let stack_id = self.pid_stack as u32; 33 | UnpackedHistogramKey { 34 | pid, 35 | stack_id, 36 | cpu: self.cpu as u32, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Clone, Debug, Copy, Hash, Eq, PartialEq)] 42 | pub struct UnpackedHistogramKey { 43 | pub pid: u32, 44 | pub stack_id: u32, 45 | pub cpu: u32, 46 | } 47 | 48 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 49 | pub struct ReducedEventKey { 50 | pub pid: u32, 51 | pub stack_id: u32, 52 | } 53 | 54 | impl UnpackedHistogramKey { 55 | pub fn as_reduced(&self) -> ReducedEventKey { 56 | ReducedEventKey { 57 | pid: self.pid, 58 | stack_id: self.stack_id, 59 | } 60 | } 61 | } 62 | 63 | #[cfg(feature = "user")] 64 | unsafe impl aya::Pod for HistogramKey {} 65 | 66 | #[repr(C)] 67 | #[derive(Clone, Debug, Copy)] 68 | pub struct Histogram { 69 | pub data: [u64; MAX_TRACKED_ALLOCATION_SIZE], 70 | pub total: u64, 71 | } 72 | 73 | #[cfg(feature = "user")] 74 | unsafe impl aya::Pod for Histogram {} 75 | 76 | impl Histogram { 77 | #[allow(clippy::new_without_default)] 78 | pub const fn new() -> Self { 79 | Self { 80 | data: [0; MAX_TRACKED_ALLOCATION_SIZE], 81 | total: 0, 82 | } 83 | } 84 | 85 | pub fn increment(&mut self, value: u64) { 86 | if value == 0 { 87 | // log(0) is undefined 88 | return; 89 | } 90 | let pow2 = value.ilog2() as usize; 91 | 92 | if let Some(bucket) = self.data.get_mut(pow2) { 93 | *bucket += 1; 94 | } 95 | self.total = self.total.saturating_add(value); 96 | } 97 | 98 | pub fn merge(&mut self, other: &Histogram) { 99 | self.total = self.total.saturating_add(other.total); 100 | for (l, r) in self.data.iter_mut().zip(other.data.iter()) { 101 | *l = l.saturating_add(*r); 102 | } 103 | } 104 | 105 | pub fn total_count(&self) -> u64 { 106 | self.data.iter().sum() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /jeprofl-ebpf/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use aya_ebpf::bindings::BPF_F_USER_STACK; 5 | use aya_ebpf::helpers::bpf_get_smp_processor_id; 6 | use aya_ebpf::macros::map; 7 | use aya_ebpf::maps::{PerCpuArray, PerCpuHashMap, StackTrace}; 8 | use aya_ebpf::{helpers::bpf_get_current_pid_tgid, macros::uprobe, programs::ProbeContext}; 9 | use jeprofl_common::{ 10 | Histogram, HistogramKey, COUNT_INDEX, FUNCTION_INFO_INDEX, MAX_ALLOC_INDEX, MIN_ALLOC_INDEX, 11 | SAMPLE_EVERY_INDEX, 12 | }; 13 | 14 | #[map(name = "CONFIG")] 15 | static STATE: PerCpuArray = PerCpuArray::with_max_entries(5, 0); 16 | 17 | #[map(name = "STACKTRACES")] 18 | static mut STACKTRACES: StackTrace = StackTrace::with_max_entries(1024 * 1024, 0); 19 | 20 | #[map(name = "HISTOGRAMS")] 21 | static mut HISTOGRAMS: PerCpuHashMap = // pid, stack_id to histogram 22 | PerCpuHashMap::with_max_entries(1024 * 1024, 0); 23 | 24 | #[uprobe] 25 | pub fn malloc(ctx: ProbeContext) -> u32 { 26 | try_malloc(ctx).unwrap_or_else(|ret| ret) 27 | } 28 | 29 | fn try_malloc(ctx: ProbeContext) -> Result { 30 | unsafe { 31 | const ARG_INDEX: usize = 0; 32 | if !should_process() { 33 | return Ok(0); 34 | } 35 | // todo: somehow make solver to believe that arg lies in the range [0, 3] to allow dynamic profiling 36 | // let Some(arg_index) = STATE.get(FUNCTION_INFO_INDEX).copied() else { 37 | // return Err(0); 38 | // }; 39 | // 40 | // let Some(arg_index) = STATE.get(FUNCTION_INFO_INDEX).copied() else { 41 | // return Err(0); 42 | // }; 43 | // 44 | // if !check_bounds_unsigned(arg_index as _, 0, 3) { 45 | // return Err(0); 46 | // } 47 | // 48 | // let arg_index = arg_index as usize; 49 | 50 | let size = match ctx.arg::(ARG_INDEX) { 51 | Some(s) => s, 52 | None => return Err(0), 53 | }; 54 | 55 | let min_size = *STATE.get(MIN_ALLOC_INDEX).unwrap_or(&0); 56 | let max_size = *STATE.get(MAX_ALLOC_INDEX).unwrap_or(&u64::MAX); 57 | 58 | if size <= min_size || size >= max_size { 59 | return Ok(0); 60 | } 61 | 62 | let pid = bpf_get_current_pid_tgid() as u32; 63 | let stack_id = match STACKTRACES.get_stackid(&ctx, BPF_F_USER_STACK.into()) { 64 | Ok(stack_id) => stack_id, 65 | Err(_) => return Err(0), 66 | } as u32; // userspace stacks are always 32-bit 67 | 68 | let current_cpu = bpf_get_smp_processor_id(); 69 | update_hist(size, pid, stack_id, current_cpu)?; 70 | } 71 | 72 | Ok(0) 73 | } 74 | 75 | fn should_process() -> bool { 76 | let sample_every = match STATE.get(SAMPLE_EVERY_INDEX) { 77 | None => { 78 | return true; 79 | } 80 | Some(v) if *v == 0 => return true, 81 | Some(v) => *v, 82 | }; 83 | let Some(ctr) = STATE.get_ptr_mut(COUNT_INDEX) else { 84 | return true; 85 | }; 86 | let Some(ctr) = (unsafe { ctr.as_mut() }) else { 87 | return true; 88 | }; 89 | *ctr += 1; 90 | (*ctr % sample_every) == 0 91 | } 92 | 93 | unsafe fn update_hist(size: u64, pid: u32, stack_id: u32, current_cpu: u32) -> Result { 94 | let key = HistogramKey::new(pid, stack_id, current_cpu as _); 95 | match HISTOGRAMS.get_ptr_mut(&key) { 96 | None => { 97 | let mut histogram = Histogram::new(); 98 | histogram.increment(size); 99 | HISTOGRAMS 100 | .insert(&key, &histogram, 0) 101 | .map_err(|e| e as u32)?; //todo use lru? 102 | } 103 | Some(hist) => { 104 | let Some(hist) = hist.as_mut() else { 105 | // should be impossible 106 | return Err(0); 107 | }; 108 | hist.increment(size); 109 | } 110 | } 111 | Ok(0) 112 | } 113 | 114 | // unsafe fn update_counter(pid: u32, stack_id: u32) -> Result { 115 | // match HISTOGRAMS.get_ptr_mut(&(pid, stack_id)) { 116 | // None => { 117 | // let mut counter = Counter::new(); 118 | // HISTOGRAMS 119 | // .insert(&(pid, stack_id), &counter, 0) 120 | // .map_err(|e| e as u32)?; //todo use lru? 121 | // } 122 | // Some(counter) => { 123 | // let Some(counter) = counter.as_mut() else { 124 | // // should be impossible 125 | // return Err(0); 126 | // }; 127 | // counter.increment(); 128 | // } 129 | // } 130 | // Ok(0) 131 | // } 132 | 133 | #[panic_handler] 134 | fn panic(_info: &core::panic::PanicInfo) -> ! { 135 | unsafe { core::hint::unreachable_unchecked() } 136 | } 137 | -------------------------------------------------------------------------------- /jeprofl-ebpf/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aya-ebpf" 7 | version = "0.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b7318de0c49a17873182763831cb22f74fb30d04e2eb7e6d7b7e9b7d86d70ed3" 10 | dependencies = [ 11 | "aya-ebpf-bindings", 12 | "aya-ebpf-cty", 13 | "aya-ebpf-macros", 14 | "rustversion", 15 | ] 16 | 17 | [[package]] 18 | name = "aya-ebpf-bindings" 19 | version = "0.1.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "a8536b7e39b232ecd854e587f473ba15640c09afc3e08408fc28144a7404ae75" 22 | dependencies = [ 23 | "aya-ebpf-cty", 24 | ] 25 | 26 | [[package]] 27 | name = "aya-ebpf-cty" 28 | version = "0.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d5c130d898322b9698937465b3b749095dae85dba0da4ee648235947eb95738d" 31 | 32 | [[package]] 33 | name = "aya-ebpf-macros" 34 | version = "0.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "ce7820cc83547582284a140ffbdd46ab527d7ee2d9d0cfedf3f184fad3f8e15c" 37 | dependencies = [ 38 | "proc-macro-error", 39 | "proc-macro2", 40 | "quote", 41 | "syn", 42 | ] 43 | 44 | [[package]] 45 | name = "aya-log-common" 46 | version = "0.1.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "b6d38a351ee2d5dc24e04cac6184b1b39408642d9a8b585892c99146f8dd4edb" 49 | dependencies = [ 50 | "num_enum", 51 | ] 52 | 53 | [[package]] 54 | name = "aya-log-ebpf" 55 | version = "0.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2a10bbadd0829895a91eb1cd2bb02d7af145704087f03812bed60cb9fe65dbb3" 58 | dependencies = [ 59 | "aya-ebpf", 60 | "aya-log-common", 61 | "aya-log-ebpf-macros", 62 | ] 63 | 64 | [[package]] 65 | name = "aya-log-ebpf-macros" 66 | version = "0.1.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "f6d8251a75f56077db51892041aa6b77c70ef2723845d7a210979700b2f01bc4" 69 | dependencies = [ 70 | "aya-log-common", 71 | "aya-log-parser", 72 | "proc-macro2", 73 | "quote", 74 | "syn", 75 | ] 76 | 77 | [[package]] 78 | name = "aya-log-parser" 79 | version = "0.1.13" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "14b102eb5c88c9aa0b49102d3fbcee08ecb0dfa81014f39b373311de7a7032cb" 82 | dependencies = [ 83 | "aya-log-common", 84 | ] 85 | 86 | [[package]] 87 | name = "jeprof-ebpf" 88 | version = "0.1.0" 89 | dependencies = [ 90 | "aya-ebpf", 91 | "aya-log-ebpf", 92 | "jeprofl-common", 93 | ] 94 | 95 | [[package]] 96 | name = "jeprofl-common" 97 | version = "0.1.0" 98 | 99 | [[package]] 100 | name = "num_enum" 101 | version = "0.7.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 104 | dependencies = [ 105 | "num_enum_derive", 106 | ] 107 | 108 | [[package]] 109 | name = "num_enum_derive" 110 | version = "0.7.3" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 113 | dependencies = [ 114 | "proc-macro2", 115 | "quote", 116 | "syn", 117 | ] 118 | 119 | [[package]] 120 | name = "proc-macro-error" 121 | version = "1.0.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 124 | dependencies = [ 125 | "proc-macro-error-attr", 126 | "proc-macro2", 127 | "quote", 128 | "version_check", 129 | ] 130 | 131 | [[package]] 132 | name = "proc-macro-error-attr" 133 | version = "1.0.4" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 136 | dependencies = [ 137 | "proc-macro2", 138 | "quote", 139 | "version_check", 140 | ] 141 | 142 | [[package]] 143 | name = "proc-macro2" 144 | version = "1.0.86" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 147 | dependencies = [ 148 | "unicode-ident", 149 | ] 150 | 151 | [[package]] 152 | name = "quote" 153 | version = "1.0.37" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 156 | dependencies = [ 157 | "proc-macro2", 158 | ] 159 | 160 | [[package]] 161 | name = "rustversion" 162 | version = "1.0.17" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 165 | 166 | [[package]] 167 | name = "syn" 168 | version = "2.0.77" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 171 | dependencies = [ 172 | "proc-macro2", 173 | "quote", 174 | "unicode-ident", 175 | ] 176 | 177 | [[package]] 178 | name = "unicode-ident" 179 | version = "1.0.13" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 182 | 183 | [[package]] 184 | name = "version_check" 185 | version = "0.9.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jeprofl 2 | 3 | jeprofl is a memory allocation profiling tool that uses eBPF technology to 4 | analyze jemalloc allocations in your program. It may work with other 5 | allocators, but this has not been tested. 6 | 7 | [![colorized flamegraph output](assets/flamegraph.png)](assets/flamegraph.svg) 8 | 9 | It can be used with already running program without recompilation. 10 | Overhead with 1000x sampling is 80 ns per call under 2.5M allocations / sec. 11 | ![bpftop.png](assets/bpftop.png) 12 | 13 | ## Features 14 | 15 | - Attach to a specific process or program 16 | - Support for various jemalloc allocation functions (malloc, calloc, realloc, 17 | etc.) 18 | - Order results by allocation count or total memory traffic 19 | - Set minimum and maximum allocation sizes to track 20 | - Configurable event sampling 21 | - Generate CSV output and flame graphs 22 | - Tracks allocation histograms per stack trace (rounded to power of 2) 23 | 24 | ``` 25 | 6ae5a0 - malloc 26 | 4e54b0 - uu_ls::enter_directory 27 | 4e54b0 - uu_ls::enter_directory 28 | 4e54b0 - uu_ls::enter_directory 29 | 4e54b0 - uu_ls::enter_directory 30 | 4db790 - uu_ls::list 31 | 23b070 - uu_ls::uumain 32 | bb560 - coreutils::main 33 | 1c9e60 - std::sys::backtrace::__rust_begin_short_backtrace 34 | bdf00 - main 35 | 2a010 - __libc_start_call_main 36 | 2a0c0 - __libc_start_main_alias_2 37 | a7f00 - _start 38 | 39 | -----------+-----------+------------+-------------------------------------------------- 40 | Size | Count | Percentage | Distribution 41 | -----------+-----------+------------+-------------------------------------------------- 42 | 1 B | 16870 | 4.23% | ####### 43 | 2 B | 21716 | 5.44% | ######### 44 | 4 B | 38150 | 9.56% | ################ 45 | 8 B | 120776 | 30.27% | ################################################## 46 | 16 B | 103586 | 25.97% | ########################################### 47 | 32 B | 58988 | 14.79% | ######################## 48 | 64 B | 7444 | 1.87% | ### 49 | 128 B | 14768 | 3.70% | ###### 50 | 512 B | 16638 | 4.17% | ####### 51 | Total allocations: 18.4 MiB in 398936 allocations 52 | ``` 53 | 54 | - Minimal overhead 55 | 56 | ## Limitations 57 | 58 | - Only works with statically linked programs(for now). Can work with dynamically 59 | linked programs, but you should provide the path to the dylib. 60 | 61 | ## Prerequisites 62 | 63 | 1. Install bpf-linker: `cargo install bpf-linker` 64 | 2. Linux kernel with eBPF support 65 | 3. Root privileges (for attaching to processes) 66 | 67 | ## Usage 68 | 69 | Options: 70 | 71 | - `--pid `: Attach to a specific process ID 72 | - `--function `: Specify the jemalloc function to trace (default: 73 | malloc) 74 | - `--order-by `: Order results by 'count' or 'traffic' (default: traffic) 75 | Traffic is the total allocated size, count is the number of malloc calls. 76 | - `--max-alloc-size `: Maximum allocation size to track 77 | - `--min-alloc-size `: Minimum allocation size to track 78 | - `--sample-every `: Sample every Nth event 79 | - `--skip-size `: Skip allocations with total allocated < SIZE bytes 80 | - `--skip-count `: Skip stack traces with total allocations count < COUNT 81 | - `--csv `: Generate CSV output: pid, stack_id, total allocations in 82 | bytes, count, histogram 83 | "stacktrace" 84 | - `--flame `: Generate flame graph 85 | 86 | Example: 87 | 88 | ```bash 89 | RUST_LOG=info cargo xtask run --release -- --program ~/dev/oss/coreutils/target/release/coreutils --order-by count --sample-every 100 --skip-count 100 --csv malocs.csv -f malloc --flame malocs.svg 90 | ``` 91 | 92 | This will profile malloc calls in ls program, order results by total allocated 93 | count, generate a CSV output, and create a flame graph. 94 | 95 | # How it works 96 | 97 | Ebpf program is attached to malloc function in the target program. 98 | For every nth call it tracks stacktrace and size of allocation and store it to 99 | cpu-local hashmap. 100 | 101 | Userspace program polls these maps and resolves stacktraces to symbols. 102 | On ctrl+c signal it aggregates all data and prints it. 103 | 104 | ## Todo 105 | 106 | - [x] Aggregate histogram in kernel space. For now, we're just dumping all the 107 | data to userspace, which gives 1us overhead per call which is unacceptable. 108 | Pure uprobe uses 20ns per call. 109 | - [ ] Find which malloc is used (now we assume that target is statically linked) 110 | - [ ] Add ratatui based TUI 111 | - [x] Produce flamegraphs 112 | - [x] Add docs and examples 113 | - [ ] somehow proof to ebpf verifier that number [0,1] is valid index for 114 | function call. For now, it gives amazing errors like: 115 | 116 | ``` 117 | Error: the BPF_PROG_LOAD syscall failed. Verifier output: 0: R1=ctx() R10=fp0 118 | 0: (bf) r6 = r1 ; R1=ctx() R6_w=ctx() 119 | 1: (b7) r1 = 4 ; R1_w=4 120 | 2: (63) *(u32 *)(r10 -280) = r1 ; R1_w=4 R10=fp0 fp-280=????4 121 | 3: (bf) r2 = r10 ; R2_w=fp0 R10=fp0 122 | 4: (07) r2 += -280 ; R2_w=fp-280 123 | 5: (18) r1 = 0xffff9f84a9a0dc00 ; R1_w=map_ptr(map=CONFIG,ks=4,vs=8) 124 | 7: (85) call bpf_map_lookup_elem#1 ; R0_w=map_value_or_null(id=1,map=CONFIG,ks=4,vs=8) 125 | 8: (15) if r0 == 0x0 goto pc+152 ; R0_w=map_value(map=CONFIG,ks=4,vs=8) 126 | 9: (79) r2 = *(u64 *)(r0 +0) ; R0=map_value(map=CONFIG,ks=4,vs=8) R2=scalar() 127 | 10: (65) if r2 s> 0x2 goto pc+7 ; R2=scalar(smax=2) 128 | 11: (b7) r1 = 112 ; R1_w=112 129 | 12: (15) if r2 == 0x0 goto pc+16 ; R2=scalar(smax=2,umin=1) 130 | 13: (15) if r2 == 0x1 goto pc+12 ; R2=scalar(smax=2,umin=2) 131 | 14: (15) if r2 == 0x2 goto pc+1 16: R0=map_value(map=CONFIG,ks=4,vs=8) R1=112 R2=2 R6=ctx() R10=fp0 fp-280=????mmmm 132 | 16: (b7) r1 = 96 ; R1_w=96 133 | 17: (05) goto pc+11 134 | 29: (bf) r2 = r6 ; R2_w=ctx() R6=ctx() 135 | 30: (0f) r2 += r1 ; R1_w=96 R2_w=ctx(off=96) 136 | 31: (79) r8 = *(u64 *)(r2 +0) 137 | dereference of modified ctx ptr R2 off=96 disallowed 138 | verification time 73 usec 139 | stack depth 280+0 140 | processed 22 insns (limit 1000000) max_states_per_insn 0 total_states 2 peak_states 2 mark_read 2 141 | ``` 142 | 143 | ## License 144 | 145 | Apache 2.0 or MIT 146 | 147 | ## Contributing 148 | 149 | Contributions are welcome! Please feel free to submit a Pull Request. -------------------------------------------------------------------------------- /jeprofl/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::collector::spawn_collector; 2 | use aya::maps::{PerCpuArray, PerCpuHashMap, PerCpuValues, StackTraceMap}; 3 | use aya::programs::UProbe; 4 | use aya::util::nr_cpus; 5 | use aya::{include_bytes_aligned, Ebpf}; 6 | 7 | use aya_log::EbpfLogger; 8 | use bytesize::ByteSize; 9 | use clap::Parser; 10 | use jeprofl_common::{ 11 | Histogram, HistogramKey, COUNT_INDEX, FUNCTION_INFO_INDEX, MAX_ALLOC_INDEX, MIN_ALLOC_INDEX, 12 | SAMPLE_EVERY_INDEX, 13 | }; 14 | use log::{debug, info, warn}; 15 | use minus::{ExitStrategy, Pager}; 16 | use std::fmt::Display; 17 | use std::num::NonZeroU32; 18 | use std::path::PathBuf; 19 | use std::str::FromStr; 20 | use std::sync::atomic::AtomicBool; 21 | use std::sync::Arc; 22 | use std::time::Duration; 23 | use tokio::signal; 24 | 25 | mod collector; 26 | mod resolver; 27 | 28 | #[derive(Debug, Parser)] 29 | struct Opt { 30 | #[clap(short, long)] 31 | pid: Option, 32 | 33 | #[clap(long)] 34 | program: PathBuf, 35 | 36 | #[clap(short, long, default_value = "malloc")] 37 | function: JemallocAllocFunctions, 38 | 39 | #[clap(short, long, default_value_t = OrderBy::Count)] 40 | order_by: OrderBy, 41 | 42 | /// Max alloc size to track 43 | #[clap(short, long, default_value_t = u64::MAX)] 44 | max_alloc_size: u64, 45 | /// Min allocation size to track 46 | #[clap(short, long)] 47 | #[clap(default_value_t = 0)] 48 | min_alloc_size: u64, 49 | 50 | /// Specify the sampling interval for events. 51 | /// For example, '1' samples every event, '1000' samples every 1000th event. 52 | #[clap(short, long)] 53 | #[clap(default_value_t = NonZeroU32::new(1).unwrap())] 54 | sample_every: NonZeroU32, 55 | 56 | /// skip allocations with total alocated < `skip_size` bytes 57 | #[clap(short, long, default_value_t = ByteSize(1))] 58 | skip_size: ByteSize, 59 | 60 | /// Skips stack traces with total count < `skip_count` 61 | #[clap(long, default_value_t = 1000)] 62 | skip_count: u64, 63 | 64 | #[clap(long("csv"))] 65 | csv_path: Option, 66 | 67 | /// Writes a flamegraph to the path_by_size.svg and path_by_count.svg 68 | #[clap(long("flame"))] 69 | flame_graph: Option, 70 | } 71 | 72 | #[derive(derive_more::Display, derive_more::FromStr, Debug, Copy, Clone)] 73 | enum OrderBy { 74 | Count, 75 | Traffic, 76 | } 77 | 78 | #[derive(Debug, Clone, Copy)] 79 | enum JemallocAllocFunctions { 80 | Malloc, 81 | Calloc, 82 | Realloc, 83 | Mallocx, 84 | Rallocx, 85 | Xallocx, 86 | } 87 | 88 | impl FromStr for JemallocAllocFunctions { 89 | type Err = anyhow::Error; 90 | 91 | fn from_str(s: &str) -> Result { 92 | match s.to_lowercase().as_str() { 93 | "malloc" => Ok(Self::Malloc), 94 | "calloc" => Ok(Self::Calloc), 95 | "realloc" => Ok(Self::Realloc), 96 | "mallocx" => Ok(Self::Mallocx), 97 | "rallocx" => Ok(Self::Rallocx), 98 | "xallocx" => Ok(Self::Xallocx), 99 | _ => Err(anyhow::anyhow!("Invalid function name {}", s)), 100 | } 101 | } 102 | } 103 | 104 | impl Display for JemallocAllocFunctions { 105 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 106 | match self { 107 | Self::Malloc => write!(f, "malloc"), 108 | Self::Calloc => write!(f, "calloc"), 109 | Self::Realloc => write!(f, "realloc"), 110 | Self::Mallocx => write!(f, "mallocx"), 111 | Self::Rallocx => write!(f, "rallocx"), 112 | Self::Xallocx => write!(f, "xallocx"), 113 | } 114 | } 115 | } 116 | 117 | impl JemallocAllocFunctions { 118 | pub fn allocation_arg_index(&self) -> u64 { 119 | match self { 120 | Self::Malloc => 0, 121 | Self::Calloc => 1, 122 | Self::Realloc => 1, 123 | Self::Mallocx => 1, 124 | Self::Rallocx => 1, 125 | Self::Xallocx => 1, 126 | } 127 | } 128 | } 129 | 130 | #[tokio::main] 131 | async fn main() -> Result<(), anyhow::Error> { 132 | scopeguard::defer! { 133 | crossterm::execute!(std::io::stdout(),crossterm::cursor::Show).ok(); 134 | }; 135 | let opt = Opt::parse(); 136 | 137 | env_logger::init(); 138 | 139 | // Bump the memlock rlimit. This is needed for older kernels that don't use the 140 | // new memcg based accounting, see https://lwn.net/Articles/837122/ 141 | let rlim = libc::rlimit { 142 | rlim_cur: libc::RLIM_INFINITY, 143 | rlim_max: libc::RLIM_INFINITY, 144 | }; 145 | let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlim) }; 146 | if ret != 0 { 147 | debug!("remove limit on locked memory failed, ret is: {}", ret); 148 | } 149 | 150 | // This will include your eBPF object file as raw bytes at compile-time and load it at 151 | // runtime. This approach is recommended for most real-world use cases. If you would 152 | // like to specify the eBPF program at runtime rather than at compile-time, you can 153 | // reach for `Bpf::load_file` instead. 154 | #[cfg(debug_assertions)] 155 | let mut bpf = Ebpf::load(include_bytes_aligned!( 156 | "../../target/bpfel-unknown-none/debug/jeprofl" 157 | ))?; 158 | #[cfg(not(debug_assertions))] 159 | let mut bpf = Ebpf::load(include_bytes_aligned!( 160 | "../../target/bpfel-unknown-none/release/jeprofl" 161 | ))?; 162 | if let Err(e) = EbpfLogger::init(&mut bpf) { 163 | // This can happen if you remove all log statements from your eBPF program. 164 | warn!("failed to initialize eBPF logger: {}", e); 165 | } 166 | 167 | { 168 | let config_map = bpf.map_mut("CONFIG").expect("CONFIG not found"); 169 | let mut config_map = PerCpuArray::try_from(config_map)?; 170 | let num_cpus = nr_cpus().unwrap(); 171 | config_map.set( 172 | MIN_ALLOC_INDEX, 173 | PerCpuValues::try_from(vec![opt.min_alloc_size; num_cpus])?, 174 | 0, 175 | )?; 176 | config_map.set( 177 | MAX_ALLOC_INDEX, 178 | PerCpuValues::try_from(vec![opt.max_alloc_size; num_cpus])?, 179 | 0, 180 | )?; 181 | config_map.set(COUNT_INDEX, PerCpuValues::try_from(vec![0; num_cpus])?, 0)?; 182 | config_map.set( 183 | SAMPLE_EVERY_INDEX, 184 | PerCpuValues::try_from(vec![opt.sample_every.get() as u64; num_cpus])?, 185 | 0, 186 | )?; 187 | config_map.set( 188 | FUNCTION_INFO_INDEX, 189 | PerCpuValues::try_from(vec![opt.function.allocation_arg_index(); num_cpus])?, 190 | 0, 191 | )?; 192 | } 193 | 194 | let program: &mut UProbe = bpf.program_mut("malloc").unwrap().try_into()?; 195 | program.load()?; 196 | 197 | let function = opt.function.to_string(); 198 | log::info!( 199 | "Attaching to function: {}:{}", 200 | opt.program.display(), 201 | function 202 | ); 203 | 204 | program.attach(Some(function.as_str()), 0, &opt.program, opt.pid)?; 205 | 206 | let stack_traces = StackTraceMap::try_from(bpf.take_map("STACKTRACES").unwrap())?; 207 | 208 | let start = std::time::Instant::now(); 209 | let per_cpu_map: PerCpuHashMap<_, HistogramKey, Histogram> = 210 | PerCpuHashMap::try_from(bpf.take_map("HISTOGRAMS").unwrap())?; 211 | log::info!( 212 | "Opened per_cpu_map, took {:?}", 213 | start.elapsed().as_secs_f64() 214 | ); 215 | log::info!( 216 | "Will not save stack traces which has total alocation size < {} or count < {}", 217 | opt.skip_count, 218 | opt.skip_size 219 | ); 220 | 221 | let canceled = Arc::new(AtomicBool::new(false)); 222 | let handle = spawn_collector( 223 | per_cpu_map, 224 | canceled.clone(), 225 | stack_traces, 226 | opt.skip_size.0, 227 | opt.skip_count, 228 | ); 229 | 230 | info!("Waiting for Ctrl-C..."); 231 | signal::ctrl_c().await?; 232 | info!("Exiting..."); 233 | canceled.store(true, std::sync::atomic::Ordering::Release); 234 | // to reduce the probability of installing 2 signal handlers 235 | tokio::time::sleep(Duration::from_secs(1)).await; 236 | 237 | // Initialize the pager 238 | let mut pager = Pager::new(); 239 | pager.set_exit_strategy(ExitStrategy::PagerQuit)?; 240 | // Run the pager in a separate thread 241 | let t = { 242 | let pager = pager.clone(); 243 | std::thread::spawn(move || minus::dynamic_paging(pager)) 244 | }; 245 | 246 | let handle = handle.join().expect("failed to join thread"); 247 | 248 | // let mut str = String::new(); 249 | handle.print_histogram(opt.order_by, &mut pager, opt.csv_path, opt.flame_graph)?; 250 | 251 | t.join().unwrap()?; 252 | 253 | log::info!("Exited"); 254 | Ok(()) 255 | } 256 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /jeprofl/src/collector.rs: -------------------------------------------------------------------------------- 1 | use crate::resolver::{ResolvedStackTrace, Resolver}; 2 | use crate::OrderBy; 3 | use aya::maps::{MapData, PerCpuHashMap, StackTraceMap}; 4 | 5 | use itertools::Itertools; 6 | use jeprofl_common::{Histogram, HistogramKey, ReducedEventKey, UnpackedHistogramKey}; 7 | use rustc_hash::{FxHashMap, FxHashSet}; 8 | use std::collections::hash_map::Entry; 9 | use std::io::BufWriter; 10 | use std::path::PathBuf; 11 | use std::sync::atomic::{AtomicBool, Ordering}; 12 | use std::sync::Arc; 13 | use std::thread; 14 | use std::thread::JoinHandle; 15 | use std::time::Duration; 16 | 17 | pub fn spawn_collector( 18 | mut buf: PerCpuHashMap, 19 | canceled: Arc, 20 | mut stack_trace_map: StackTraceMap, 21 | skip_total_alloc_size_lower_than: u64, 22 | skip_total_count_lower_than: u64, 23 | ) -> JoinHandle { 24 | thread::spawn(move || { 25 | let resolver = Resolver::new(); 26 | let mut processor = EventProcessor::new(); 27 | 28 | let mut keys_to_drop = FxHashSet::default(); 29 | let mut last_clean_up = std::time::Instant::now(); 30 | 31 | loop { 32 | thread::sleep(Duration::from_secs(1)); 33 | if canceled.load(Ordering::Acquire) { 34 | return processor; 35 | } 36 | 37 | let mut was_skiped_on_cpus = true; 38 | for val in buf.iter() { 39 | let (key, per_cpu_histograms) = val.unwrap(); 40 | let unpacked_key = key.into_parts(); 41 | // per cpu histograms 42 | for hist in per_cpu_histograms.iter() { 43 | if hist.total < skip_total_alloc_size_lower_than 44 | && hist.total_count() < skip_total_count_lower_than 45 | { 46 | continue; 47 | } 48 | was_skiped_on_cpus = false; 49 | if canceled.load(Ordering::Acquire) { 50 | return processor; 51 | } 52 | processor.process(unpacked_key, hist, &resolver, &stack_trace_map); 53 | } 54 | 55 | if was_skiped_on_cpus { 56 | keys_to_drop.insert(key); 57 | } else { 58 | keys_to_drop.remove(&key); 59 | } 60 | } 61 | 62 | if last_clean_up.elapsed() > Duration::from_secs(60) { 63 | for key in keys_to_drop.drain() { 64 | let unpacked_key = key.into_parts(); 65 | buf.remove(&key).ok(); // it may be already deleted 66 | stack_trace_map.remove(&unpacked_key.stack_id).ok(); 67 | } 68 | last_clean_up = std::time::Instant::now(); 69 | } 70 | } 71 | }) 72 | } 73 | #[derive(Clone, Debug)] 74 | pub struct EventProcessor { 75 | allocations_stats: FxHashMap, 76 | resolved_traces: FxHashMap, 77 | } 78 | 79 | impl EventProcessor { 80 | pub fn new() -> Self { 81 | Self { 82 | allocations_stats: FxHashMap::with_capacity_and_hasher(1024, Default::default()), 83 | resolved_traces: Default::default(), 84 | } 85 | } 86 | 87 | fn process( 88 | &mut self, 89 | key: UnpackedHistogramKey, 90 | event: &Histogram, 91 | resolver: &Resolver, 92 | stacktrace_map: &StackTraceMap, 93 | ) { 94 | self.allocations_stats.insert(key, *event); // just update with latest snapshot TODO: merge somehow 95 | 96 | match self.resolved_traces.entry(key.stack_id) { 97 | Entry::Occupied(_) => {} 98 | Entry::Vacant(e) => { 99 | let Ok(trace) = stacktrace_map.get(&key.stack_id, 0) else { 100 | return; 101 | }; 102 | let stack_trace = match resolver.resolve_stacktrace(&trace, key.pid) { 103 | Ok(stacktrace) => stacktrace, 104 | Err(e) => { 105 | log::debug!("Failed to resolve {e}"); 106 | return; 107 | } 108 | }; 109 | e.insert(stack_trace); 110 | } 111 | } 112 | } 113 | 114 | fn merge(&self) -> FxHashMap { 115 | let mut allocations_stats: FxHashMap = 116 | FxHashMap::with_capacity_and_hasher(self.allocations_stats.len(), Default::default()); 117 | 118 | for (key, stat) in &self.allocations_stats { 119 | match allocations_stats.entry(key.as_reduced()) { 120 | Entry::Occupied(mut e) => e.get_mut().merge(stat), 121 | Entry::Vacant(e) => { 122 | e.insert(*stat); 123 | } 124 | } 125 | } 126 | allocations_stats 127 | } 128 | 129 | pub fn print_histogram( 130 | &self, 131 | order_by: OrderBy, 132 | mut pager: impl std::fmt::Write, 133 | csv_path: Option, 134 | flame_graph: Option, 135 | ) -> anyhow::Result<()> { 136 | let stats = self.merge(); 137 | writeln!(pager, "total stack traces: {}\n", stats.len())?; 138 | 139 | let mut entries: Vec<(_, _)> = stats.iter().filter(|(_, hist)| hist.total > 0).collect(); 140 | 141 | match order_by { 142 | OrderBy::Count => { 143 | entries.sort_by_key(|(_, hist)| hist.data.iter().sum::()); 144 | } 145 | OrderBy::Traffic => { 146 | entries.sort_by_key(|(_, hist)| hist.total); 147 | } 148 | } 149 | 150 | let mut csv_writer = CsvWriter::new(csv_path)?; 151 | for (key, hist) in &entries { 152 | print_section(&mut pager, '*')?; 153 | 154 | if let Some(resolved_trace) = self.resolved_traces.get(&key.stack_id) { 155 | for fun in resolved_trace.symbols.iter() { 156 | writeln!(pager, "{} - {}", fun.address, fun.symbol)?; 157 | } 158 | } else { 159 | writeln!(pager, "No resolved stacktrace")?; 160 | } 161 | 162 | print_section(&mut pager, '-')?; 163 | 164 | print_histogram(hist, &mut pager)?; 165 | writeln!(&mut pager, "\n")?; 166 | csv_writer.write(key, hist, self)?; 167 | } 168 | csv_writer.finish()?; 169 | 170 | if let Some(path) = flame_graph { 171 | let path_without_extension = match path.file_stem() { 172 | Some(stem) => path.with_file_name(stem), 173 | None => path, 174 | }; 175 | 176 | let result = path_without_extension.to_string_lossy(); 177 | let args = [ 178 | ( 179 | PathBuf::from(format!("{}-by-count.svg", result)), 180 | OrderBy::Count, 181 | ), 182 | ( 183 | PathBuf::from(format!("{}-by-traffic.svg", result)), 184 | OrderBy::Traffic, 185 | ), 186 | ]; 187 | 188 | for (path, order_by) in args { 189 | let file = std::fs::File::create(&path)?; 190 | let file = BufWriter::new(file); 191 | self.write_flame_graph(file, order_by)?; 192 | log::info!("Flamegraph written to {:?}", path); 193 | } 194 | } 195 | 196 | Ok(()) 197 | } 198 | 199 | fn write_flame_graph(&self, writer: impl std::io::Write, mode: OrderBy) -> anyhow::Result<()> { 200 | let traces = self 201 | .allocations_stats 202 | .iter() 203 | .filter_map(|st| { 204 | let symbols = self.resolved_traces.get(&st.0.stack_id)?; 205 | let stat = match mode { 206 | OrderBy::Count => st.1.data.iter().sum(), 207 | OrderBy::Traffic => st.1.total, 208 | }; 209 | Some(symbols.as_inferno(stat)) 210 | }) 211 | .collect_vec(); 212 | 213 | let count_name = match mode { 214 | OrderBy::Count => "count", 215 | OrderBy::Traffic => "total allocated", 216 | }; 217 | 218 | let mut settings = inferno::flamegraph::Options::default(); 219 | settings.count_name = count_name.to_string(); 220 | settings.reverse_stack_order = true; 221 | 222 | let vec_of_strs = traces.iter().map(|x| x.as_str()).collect_vec(); 223 | 224 | inferno::flamegraph::from_lines(&mut settings, vec_of_strs, writer)?; 225 | Ok(()) 226 | } 227 | } 228 | 229 | fn print_section(mut pager: impl std::fmt::Write, char: char) -> anyhow::Result<()> { 230 | let string = (0..80).map(|_| char).collect::(); 231 | pager.write_str(&string)?; 232 | pager.write_char('\n')?; 233 | Ok(()) 234 | } 235 | 236 | pub(crate) fn print_histogram( 237 | hist: &Histogram, 238 | mut pager: impl std::fmt::Write, 239 | ) -> anyhow::Result<()> { 240 | let mut entries: Vec<(usize, u64)> = hist 241 | .data 242 | .iter() 243 | .enumerate() 244 | .filter(|(_, &count)| count > 0) 245 | .map(|(size, &count)| (size, count)) 246 | .collect(); 247 | 248 | entries.sort_by_key(|&(size, _)| size); 249 | 250 | let max_count = entries.iter().map(|&(_, count)| count).max().unwrap_or(1); 251 | let bar_width = 50; // Maximum width of the bar 252 | 253 | writeln!(pager, "Size | Count | Percentage | Distribution")?; 254 | writeln!( 255 | pager, 256 | "----------+-----------+------------+{}", 257 | "-".repeat(bar_width) 258 | )?; 259 | 260 | let total_count: u64 = entries.iter().map(|&(_, count)| count).sum(); 261 | 262 | for (size, count) in entries { 263 | let size_bytes = size_bytes(size); 264 | let percentage = (count as f64 / total_count as f64) * 100.0; 265 | let bar_length = ((count as f64 / max_count as f64) * bar_width as f64).round() as usize; 266 | 267 | writeln!( 268 | pager, 269 | "{:10} | {:9} | {:9.2}% | {}", 270 | bytesize::to_string(size_bytes, true), 271 | count, 272 | percentage, 273 | "#".repeat(bar_length) 274 | )?; 275 | } 276 | 277 | writeln!( 278 | pager, 279 | "Total allocations: {} in {} allocations", 280 | bytesize::to_string(hist.total, true), 281 | total_count 282 | )?; 283 | 284 | Ok(()) 285 | } 286 | 287 | fn size_bytes(size: usize) -> u64 { 288 | 1u64 << size 289 | } 290 | 291 | struct CsvWriter { 292 | writer: Option>>, 293 | } 294 | 295 | impl CsvWriter { 296 | pub fn new(path: Option) -> anyhow::Result { 297 | const HEADERS: [&str; 6] = [ 298 | "pid", 299 | "stack_id", 300 | "total", 301 | "count", 302 | "histogram", 303 | "stacktrace", 304 | ]; 305 | let writer = match path { 306 | Some(path) => { 307 | let mut writer = csv::Writer::from_writer(std::io::BufWriter::new( 308 | std::fs::File::create(&path)?, 309 | )); 310 | writer.write_record(HEADERS)?; 311 | Some(writer) 312 | } 313 | None => None, 314 | }; 315 | 316 | Ok(Self { writer }) 317 | } 318 | 319 | pub fn write( 320 | &mut self, 321 | key: &ReducedEventKey, 322 | hist: &Histogram, 323 | processor: &EventProcessor, 324 | ) -> anyhow::Result<()> { 325 | if let Some(writer) = &mut self.writer { 326 | let stacktrace = processor 327 | .resolved_traces 328 | .get(&key.stack_id) 329 | .map(|trace| { 330 | trace 331 | .symbols 332 | .iter() 333 | .map(|fun| format!("{:x} - {}", fun.address, fun.symbol)) 334 | .collect::>() 335 | .join("\n") 336 | }) 337 | .unwrap_or_else(|| "No resolved stacktrace".to_string()); 338 | let mut histogram = String::new(); 339 | print_histogram(hist, &mut histogram)?; 340 | writer.serialize(( 341 | key.pid, 342 | key.stack_id, 343 | hist.total, 344 | hist.data.iter().sum::(), 345 | histogram, 346 | stacktrace, 347 | ))?; 348 | } 349 | Ok(()) 350 | } 351 | 352 | pub fn finish(self) -> anyhow::Result<()> { 353 | if let Some(mut writer) = self.writer { 354 | writer.flush()?; 355 | } 356 | Ok(()) 357 | } 358 | } 359 | 360 | #[cfg(test)] 361 | mod test { 362 | use crate::collector::print_histogram; 363 | use jeprofl_common::Histogram; 364 | 365 | #[cfg(test)] 366 | mod tests { 367 | use super::*; 368 | 369 | #[test] 370 | fn print_histogram_empty() { 371 | let histogram = Histogram::new(); 372 | let mut buf = String::new(); 373 | print_histogram(&histogram, &mut buf).unwrap(); 374 | insta::assert_snapshot!(buf); 375 | } 376 | 377 | #[test] 378 | fn print_histogram_single_allocation() { 379 | let mut histogram = Histogram::new(); 380 | histogram.increment(1023); 381 | let mut buf = String::new(); 382 | print_histogram(&histogram, &mut buf).unwrap(); 383 | insta::assert_snapshot!(buf); 384 | } 385 | 386 | #[test] 387 | fn print_histogram_multiple_sizes() { 388 | let mut histogram = Histogram::new(); 389 | histogram.increment(1); // 1 B 390 | histogram.increment(512); // 512 B 391 | histogram.increment(1026); // 2 KB 392 | let mut buf = String::new(); 393 | print_histogram(&histogram, &mut buf).unwrap(); 394 | insta::assert_snapshot!(buf); 395 | } 396 | 397 | #[test] 398 | fn print_histogram_large_allocations() { 399 | let mut histogram = Histogram::new(); 400 | histogram.increment(1 << 20); // 1 MB 401 | histogram.increment(1u64 << 30); // 1 GB 402 | let mut buf = String::new(); 403 | print_histogram(&histogram, &mut buf).unwrap(); 404 | insta::assert_snapshot!(buf); 405 | } 406 | 407 | #[test] 408 | fn print_histogram_many_small_allocations() { 409 | let mut histogram = Histogram::new(); 410 | for _ in 0..1000 { 411 | histogram.increment(1); // 1 B 412 | } 413 | histogram.increment(1023); // 1 KB 414 | let mut buf = String::new(); 415 | print_histogram(&histogram, &mut buf).unwrap(); 416 | insta::assert_snapshot!(buf); 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "getrandom", 28 | "once_cell", 29 | "version_check", 30 | "zerocopy", 31 | ] 32 | 33 | [[package]] 34 | name = "aho-corasick" 35 | version = "1.1.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 38 | dependencies = [ 39 | "memchr", 40 | ] 41 | 42 | [[package]] 43 | name = "allocator-api2" 44 | version = "0.2.18" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 47 | 48 | [[package]] 49 | name = "anstream" 50 | version = "0.6.15" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 53 | dependencies = [ 54 | "anstyle", 55 | "anstyle-parse", 56 | "anstyle-query", 57 | "anstyle-wincon", 58 | "colorchoice", 59 | "is_terminal_polyfill", 60 | "utf8parse", 61 | ] 62 | 63 | [[package]] 64 | name = "anstyle" 65 | version = "1.0.8" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 68 | 69 | [[package]] 70 | name = "anstyle-parse" 71 | version = "0.2.5" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 74 | dependencies = [ 75 | "utf8parse", 76 | ] 77 | 78 | [[package]] 79 | name = "anstyle-query" 80 | version = "1.1.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 83 | dependencies = [ 84 | "windows-sys 0.52.0", 85 | ] 86 | 87 | [[package]] 88 | name = "anstyle-wincon" 89 | version = "3.0.4" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 92 | dependencies = [ 93 | "anstyle", 94 | "windows-sys 0.52.0", 95 | ] 96 | 97 | [[package]] 98 | name = "anyhow" 99 | version = "1.0.89" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 102 | 103 | [[package]] 104 | name = "arrayvec" 105 | version = "0.7.6" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 108 | 109 | [[package]] 110 | name = "assert_matches" 111 | version = "1.5.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" 114 | 115 | [[package]] 116 | name = "autocfg" 117 | version = "1.4.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 120 | 121 | [[package]] 122 | name = "aya" 123 | version = "0.13.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "7127cbe933572dfabb7a87d2c740f5b720c3e19b4d2b8c99a262ffa35c8761ff" 126 | dependencies = [ 127 | "assert_matches", 128 | "aya-obj", 129 | "bitflags", 130 | "bytes", 131 | "libc", 132 | "log", 133 | "object", 134 | "once_cell", 135 | "thiserror", 136 | "tokio", 137 | ] 138 | 139 | [[package]] 140 | name = "aya-log" 141 | version = "0.2.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "b600d806c1d07d3b81ab5f4a2a95fd80f479a0d3f1d68f29064d660865f85f02" 144 | dependencies = [ 145 | "aya", 146 | "aya-log-common", 147 | "bytes", 148 | "log", 149 | "thiserror", 150 | "tokio", 151 | ] 152 | 153 | [[package]] 154 | name = "aya-log-common" 155 | version = "0.1.15" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "befef9fe882e63164a2ba0161874e954648a72b0e1c4b361f532d590638c4eec" 158 | dependencies = [ 159 | "num_enum", 160 | ] 161 | 162 | [[package]] 163 | name = "aya-obj" 164 | version = "0.2.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "56e133d505de14d5948a312060b8b12752c22a965632cc57da12f5db653de4c0" 167 | dependencies = [ 168 | "bytes", 169 | "core-error", 170 | "hashbrown 0.15.0", 171 | "log", 172 | "object", 173 | "thiserror", 174 | ] 175 | 176 | [[package]] 177 | name = "backtrace" 178 | version = "0.3.74" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 181 | dependencies = [ 182 | "addr2line", 183 | "cfg-if", 184 | "libc", 185 | "miniz_oxide", 186 | "object", 187 | "rustc-demangle", 188 | "windows-targets 0.52.6", 189 | ] 190 | 191 | [[package]] 192 | name = "bitflags" 193 | version = "2.6.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 196 | 197 | [[package]] 198 | name = "blazesym" 199 | version = "0.2.0-rc.1" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "d750f0052cfee86c14cbc31eda95c14271e16347196fd4902df1a52e4411a090" 202 | dependencies = [ 203 | "cpp_demangle", 204 | "gimli", 205 | "libc", 206 | "memmap2", 207 | "miniz_oxide", 208 | "rustc-demangle", 209 | ] 210 | 211 | [[package]] 212 | name = "bytemuck" 213 | version = "1.19.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" 216 | 217 | [[package]] 218 | name = "bytes" 219 | version = "1.7.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 222 | 223 | [[package]] 224 | name = "bytesize" 225 | version = "1.3.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" 228 | 229 | [[package]] 230 | name = "cfg-if" 231 | version = "1.0.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 234 | 235 | [[package]] 236 | name = "clap" 237 | version = "4.5.20" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 240 | dependencies = [ 241 | "clap_builder", 242 | "clap_derive", 243 | ] 244 | 245 | [[package]] 246 | name = "clap_builder" 247 | version = "4.5.20" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 250 | dependencies = [ 251 | "anstream", 252 | "anstyle", 253 | "clap_lex", 254 | "strsim", 255 | ] 256 | 257 | [[package]] 258 | name = "clap_derive" 259 | version = "4.5.18" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 262 | dependencies = [ 263 | "heck", 264 | "proc-macro2", 265 | "quote", 266 | "syn", 267 | ] 268 | 269 | [[package]] 270 | name = "clap_lex" 271 | version = "0.7.2" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 274 | 275 | [[package]] 276 | name = "colorchoice" 277 | version = "1.0.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 280 | 281 | [[package]] 282 | name = "console" 283 | version = "0.15.8" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 286 | dependencies = [ 287 | "encode_unicode", 288 | "lazy_static", 289 | "libc", 290 | "windows-sys 0.52.0", 291 | ] 292 | 293 | [[package]] 294 | name = "convert_case" 295 | version = "0.6.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 298 | dependencies = [ 299 | "unicode-segmentation", 300 | ] 301 | 302 | [[package]] 303 | name = "core-error" 304 | version = "0.0.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f" 307 | dependencies = [ 308 | "version_check", 309 | ] 310 | 311 | [[package]] 312 | name = "cpp_demangle" 313 | version = "0.4.4" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" 316 | dependencies = [ 317 | "cfg-if", 318 | ] 319 | 320 | [[package]] 321 | name = "crc32fast" 322 | version = "1.4.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 325 | dependencies = [ 326 | "cfg-if", 327 | ] 328 | 329 | [[package]] 330 | name = "crossbeam-channel" 331 | version = "0.5.13" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 334 | dependencies = [ 335 | "crossbeam-utils", 336 | ] 337 | 338 | [[package]] 339 | name = "crossbeam-utils" 340 | version = "0.8.20" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 343 | 344 | [[package]] 345 | name = "crossterm" 346 | version = "0.27.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" 349 | dependencies = [ 350 | "bitflags", 351 | "crossterm_winapi", 352 | "libc", 353 | "mio 0.8.11", 354 | "parking_lot", 355 | "signal-hook", 356 | "signal-hook-mio", 357 | "winapi", 358 | ] 359 | 360 | [[package]] 361 | name = "crossterm_winapi" 362 | version = "0.9.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 365 | dependencies = [ 366 | "winapi", 367 | ] 368 | 369 | [[package]] 370 | name = "csv" 371 | version = "1.3.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" 374 | dependencies = [ 375 | "csv-core", 376 | "itoa", 377 | "ryu", 378 | "serde", 379 | ] 380 | 381 | [[package]] 382 | name = "csv-core" 383 | version = "0.1.11" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" 386 | dependencies = [ 387 | "memchr", 388 | ] 389 | 390 | [[package]] 391 | name = "dashmap" 392 | version = "6.1.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 395 | dependencies = [ 396 | "cfg-if", 397 | "crossbeam-utils", 398 | "hashbrown 0.14.5", 399 | "lock_api", 400 | "once_cell", 401 | "parking_lot_core", 402 | ] 403 | 404 | [[package]] 405 | name = "derive_more" 406 | version = "1.0.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 409 | dependencies = [ 410 | "derive_more-impl", 411 | ] 412 | 413 | [[package]] 414 | name = "derive_more-impl" 415 | version = "1.0.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 418 | dependencies = [ 419 | "convert_case", 420 | "proc-macro2", 421 | "quote", 422 | "syn", 423 | "unicode-xid", 424 | ] 425 | 426 | [[package]] 427 | name = "either" 428 | version = "1.13.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 431 | 432 | [[package]] 433 | name = "encode_unicode" 434 | version = "0.3.6" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 437 | 438 | [[package]] 439 | name = "env_filter" 440 | version = "0.1.2" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 443 | dependencies = [ 444 | "log", 445 | "regex", 446 | ] 447 | 448 | [[package]] 449 | name = "env_logger" 450 | version = "0.11.5" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" 453 | dependencies = [ 454 | "anstream", 455 | "anstyle", 456 | "env_filter", 457 | "humantime", 458 | "log", 459 | ] 460 | 461 | [[package]] 462 | name = "equivalent" 463 | version = "1.0.1" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 466 | 467 | [[package]] 468 | name = "fallible-iterator" 469 | version = "0.3.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 472 | 473 | [[package]] 474 | name = "foldhash" 475 | version = "0.1.3" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" 478 | 479 | [[package]] 480 | name = "getrandom" 481 | version = "0.2.15" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 484 | dependencies = [ 485 | "cfg-if", 486 | "libc", 487 | "wasi", 488 | ] 489 | 490 | [[package]] 491 | name = "gimli" 492 | version = "0.31.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 495 | dependencies = [ 496 | "fallible-iterator", 497 | "indexmap", 498 | "stable_deref_trait", 499 | ] 500 | 501 | [[package]] 502 | name = "hashbrown" 503 | version = "0.14.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 506 | 507 | [[package]] 508 | name = "hashbrown" 509 | version = "0.15.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 512 | dependencies = [ 513 | "allocator-api2", 514 | "equivalent", 515 | "foldhash", 516 | ] 517 | 518 | [[package]] 519 | name = "heck" 520 | version = "0.5.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 523 | 524 | [[package]] 525 | name = "hermit-abi" 526 | version = "0.3.9" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 529 | 530 | [[package]] 531 | name = "hermit-abi" 532 | version = "0.4.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 535 | 536 | [[package]] 537 | name = "humantime" 538 | version = "2.1.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 541 | 542 | [[package]] 543 | name = "indexmap" 544 | version = "2.6.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 547 | dependencies = [ 548 | "equivalent", 549 | "hashbrown 0.15.0", 550 | ] 551 | 552 | [[package]] 553 | name = "inferno" 554 | version = "0.11.21" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" 557 | dependencies = [ 558 | "ahash", 559 | "clap", 560 | "crossbeam-channel", 561 | "crossbeam-utils", 562 | "dashmap", 563 | "env_logger", 564 | "indexmap", 565 | "is-terminal", 566 | "itoa", 567 | "log", 568 | "num-format", 569 | "once_cell", 570 | "quick-xml", 571 | "rgb", 572 | "str_stack", 573 | ] 574 | 575 | [[package]] 576 | name = "insta" 577 | version = "1.40.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" 580 | dependencies = [ 581 | "console", 582 | "lazy_static", 583 | "linked-hash-map", 584 | "similar", 585 | ] 586 | 587 | [[package]] 588 | name = "is-terminal" 589 | version = "0.4.13" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 592 | dependencies = [ 593 | "hermit-abi 0.4.0", 594 | "libc", 595 | "windows-sys 0.52.0", 596 | ] 597 | 598 | [[package]] 599 | name = "is_terminal_polyfill" 600 | version = "1.70.1" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 603 | 604 | [[package]] 605 | name = "itertools" 606 | version = "0.13.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 609 | dependencies = [ 610 | "either", 611 | ] 612 | 613 | [[package]] 614 | name = "itoa" 615 | version = "1.0.11" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 618 | 619 | [[package]] 620 | name = "jeprofl" 621 | version = "0.1.0" 622 | dependencies = [ 623 | "anyhow", 624 | "aya", 625 | "aya-log", 626 | "blazesym", 627 | "bytesize", 628 | "clap", 629 | "crossterm", 630 | "csv", 631 | "derive_more", 632 | "env_logger", 633 | "inferno", 634 | "insta", 635 | "itertools", 636 | "jeprofl-common", 637 | "libc", 638 | "log", 639 | "minus", 640 | "rustc-hash", 641 | "scopeguard", 642 | "tokio", 643 | ] 644 | 645 | [[package]] 646 | name = "jeprofl-common" 647 | version = "0.1.0" 648 | dependencies = [ 649 | "aya", 650 | ] 651 | 652 | [[package]] 653 | name = "lazy_static" 654 | version = "1.5.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 657 | 658 | [[package]] 659 | name = "libc" 660 | version = "0.2.159" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 663 | 664 | [[package]] 665 | name = "linked-hash-map" 666 | version = "0.5.6" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 669 | 670 | [[package]] 671 | name = "lock_api" 672 | version = "0.4.12" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 675 | dependencies = [ 676 | "autocfg", 677 | "scopeguard", 678 | ] 679 | 680 | [[package]] 681 | name = "log" 682 | version = "0.4.22" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 685 | 686 | [[package]] 687 | name = "memchr" 688 | version = "2.7.4" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 691 | 692 | [[package]] 693 | name = "memmap2" 694 | version = "0.9.5" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" 697 | dependencies = [ 698 | "libc", 699 | ] 700 | 701 | [[package]] 702 | name = "miniz_oxide" 703 | version = "0.8.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 706 | dependencies = [ 707 | "adler2", 708 | "simd-adler32", 709 | ] 710 | 711 | [[package]] 712 | name = "minus" 713 | version = "5.6.1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "093bd0520d2a37943566a73750e6d44094dac75d66a978d1f0d97ffc78686832" 716 | dependencies = [ 717 | "crossbeam-channel", 718 | "crossterm", 719 | "once_cell", 720 | "parking_lot", 721 | "textwrap", 722 | "thiserror", 723 | ] 724 | 725 | [[package]] 726 | name = "mio" 727 | version = "0.8.11" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 730 | dependencies = [ 731 | "libc", 732 | "log", 733 | "wasi", 734 | "windows-sys 0.48.0", 735 | ] 736 | 737 | [[package]] 738 | name = "mio" 739 | version = "1.0.2" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 742 | dependencies = [ 743 | "hermit-abi 0.3.9", 744 | "libc", 745 | "wasi", 746 | "windows-sys 0.52.0", 747 | ] 748 | 749 | [[package]] 750 | name = "num-format" 751 | version = "0.4.4" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" 754 | dependencies = [ 755 | "arrayvec", 756 | "itoa", 757 | ] 758 | 759 | [[package]] 760 | name = "num_enum" 761 | version = "0.7.3" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 764 | dependencies = [ 765 | "num_enum_derive", 766 | ] 767 | 768 | [[package]] 769 | name = "num_enum_derive" 770 | version = "0.7.3" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 773 | dependencies = [ 774 | "proc-macro2", 775 | "quote", 776 | "syn", 777 | ] 778 | 779 | [[package]] 780 | name = "object" 781 | version = "0.36.5" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 784 | dependencies = [ 785 | "crc32fast", 786 | "hashbrown 0.15.0", 787 | "indexmap", 788 | "memchr", 789 | ] 790 | 791 | [[package]] 792 | name = "once_cell" 793 | version = "1.20.2" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 796 | dependencies = [ 797 | "parking_lot_core", 798 | ] 799 | 800 | [[package]] 801 | name = "parking_lot" 802 | version = "0.12.3" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 805 | dependencies = [ 806 | "lock_api", 807 | "parking_lot_core", 808 | ] 809 | 810 | [[package]] 811 | name = "parking_lot_core" 812 | version = "0.9.10" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 815 | dependencies = [ 816 | "cfg-if", 817 | "libc", 818 | "redox_syscall", 819 | "smallvec", 820 | "windows-targets 0.52.6", 821 | ] 822 | 823 | [[package]] 824 | name = "pin-project-lite" 825 | version = "0.2.14" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 828 | 829 | [[package]] 830 | name = "proc-macro2" 831 | version = "1.0.87" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 834 | dependencies = [ 835 | "unicode-ident", 836 | ] 837 | 838 | [[package]] 839 | name = "quick-xml" 840 | version = "0.26.0" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" 843 | dependencies = [ 844 | "memchr", 845 | ] 846 | 847 | [[package]] 848 | name = "quote" 849 | version = "1.0.37" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 852 | dependencies = [ 853 | "proc-macro2", 854 | ] 855 | 856 | [[package]] 857 | name = "redox_syscall" 858 | version = "0.5.7" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 861 | dependencies = [ 862 | "bitflags", 863 | ] 864 | 865 | [[package]] 866 | name = "regex" 867 | version = "1.11.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 870 | dependencies = [ 871 | "aho-corasick", 872 | "memchr", 873 | "regex-automata", 874 | "regex-syntax", 875 | ] 876 | 877 | [[package]] 878 | name = "regex-automata" 879 | version = "0.4.8" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 882 | dependencies = [ 883 | "aho-corasick", 884 | "memchr", 885 | "regex-syntax", 886 | ] 887 | 888 | [[package]] 889 | name = "regex-syntax" 890 | version = "0.8.5" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 893 | 894 | [[package]] 895 | name = "rgb" 896 | version = "0.8.50" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" 899 | dependencies = [ 900 | "bytemuck", 901 | ] 902 | 903 | [[package]] 904 | name = "rustc-demangle" 905 | version = "0.1.24" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 908 | 909 | [[package]] 910 | name = "rustc-hash" 911 | version = "2.0.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" 914 | 915 | [[package]] 916 | name = "ryu" 917 | version = "1.0.18" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 920 | 921 | [[package]] 922 | name = "scopeguard" 923 | version = "1.2.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 926 | 927 | [[package]] 928 | name = "serde" 929 | version = "1.0.210" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 932 | dependencies = [ 933 | "serde_derive", 934 | ] 935 | 936 | [[package]] 937 | name = "serde_derive" 938 | version = "1.0.210" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 941 | dependencies = [ 942 | "proc-macro2", 943 | "quote", 944 | "syn", 945 | ] 946 | 947 | [[package]] 948 | name = "signal-hook" 949 | version = "0.3.17" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 952 | dependencies = [ 953 | "libc", 954 | "signal-hook-registry", 955 | ] 956 | 957 | [[package]] 958 | name = "signal-hook-mio" 959 | version = "0.2.4" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 962 | dependencies = [ 963 | "libc", 964 | "mio 0.8.11", 965 | "signal-hook", 966 | ] 967 | 968 | [[package]] 969 | name = "signal-hook-registry" 970 | version = "1.4.2" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 973 | dependencies = [ 974 | "libc", 975 | ] 976 | 977 | [[package]] 978 | name = "simd-adler32" 979 | version = "0.3.7" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 982 | 983 | [[package]] 984 | name = "similar" 985 | version = "2.6.0" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" 988 | 989 | [[package]] 990 | name = "smallvec" 991 | version = "1.13.2" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 994 | 995 | [[package]] 996 | name = "socket2" 997 | version = "0.5.7" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1000 | dependencies = [ 1001 | "libc", 1002 | "windows-sys 0.52.0", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "stable_deref_trait" 1007 | version = "1.2.0" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1010 | 1011 | [[package]] 1012 | name = "str_stack" 1013 | version = "0.1.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" 1016 | 1017 | [[package]] 1018 | name = "strsim" 1019 | version = "0.11.1" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1022 | 1023 | [[package]] 1024 | name = "syn" 1025 | version = "2.0.79" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 1028 | dependencies = [ 1029 | "proc-macro2", 1030 | "quote", 1031 | "unicode-ident", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "textwrap" 1036 | version = "0.16.1" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1039 | dependencies = [ 1040 | "unicode-width", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "thiserror" 1045 | version = "1.0.64" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1048 | dependencies = [ 1049 | "thiserror-impl", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "thiserror-impl" 1054 | version = "1.0.64" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1057 | dependencies = [ 1058 | "proc-macro2", 1059 | "quote", 1060 | "syn", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "tokio" 1065 | version = "1.40.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 1068 | dependencies = [ 1069 | "backtrace", 1070 | "libc", 1071 | "mio 1.0.2", 1072 | "pin-project-lite", 1073 | "signal-hook-registry", 1074 | "socket2", 1075 | "tokio-macros", 1076 | "windows-sys 0.52.0", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "tokio-macros" 1081 | version = "2.4.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1084 | dependencies = [ 1085 | "proc-macro2", 1086 | "quote", 1087 | "syn", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "unicode-ident" 1092 | version = "1.0.13" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1095 | 1096 | [[package]] 1097 | name = "unicode-segmentation" 1098 | version = "1.12.0" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1101 | 1102 | [[package]] 1103 | name = "unicode-width" 1104 | version = "0.1.14" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1107 | 1108 | [[package]] 1109 | name = "unicode-xid" 1110 | version = "0.2.6" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 1113 | 1114 | [[package]] 1115 | name = "utf8parse" 1116 | version = "0.2.2" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1119 | 1120 | [[package]] 1121 | name = "version_check" 1122 | version = "0.9.5" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1125 | 1126 | [[package]] 1127 | name = "wasi" 1128 | version = "0.11.0+wasi-snapshot-preview1" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1131 | 1132 | [[package]] 1133 | name = "winapi" 1134 | version = "0.3.9" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1137 | dependencies = [ 1138 | "winapi-i686-pc-windows-gnu", 1139 | "winapi-x86_64-pc-windows-gnu", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "winapi-i686-pc-windows-gnu" 1144 | version = "0.4.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1147 | 1148 | [[package]] 1149 | name = "winapi-x86_64-pc-windows-gnu" 1150 | version = "0.4.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1153 | 1154 | [[package]] 1155 | name = "windows-sys" 1156 | version = "0.48.0" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1159 | dependencies = [ 1160 | "windows-targets 0.48.5", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "windows-sys" 1165 | version = "0.52.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1168 | dependencies = [ 1169 | "windows-targets 0.52.6", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "windows-targets" 1174 | version = "0.48.5" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1177 | dependencies = [ 1178 | "windows_aarch64_gnullvm 0.48.5", 1179 | "windows_aarch64_msvc 0.48.5", 1180 | "windows_i686_gnu 0.48.5", 1181 | "windows_i686_msvc 0.48.5", 1182 | "windows_x86_64_gnu 0.48.5", 1183 | "windows_x86_64_gnullvm 0.48.5", 1184 | "windows_x86_64_msvc 0.48.5", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "windows-targets" 1189 | version = "0.52.6" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1192 | dependencies = [ 1193 | "windows_aarch64_gnullvm 0.52.6", 1194 | "windows_aarch64_msvc 0.52.6", 1195 | "windows_i686_gnu 0.52.6", 1196 | "windows_i686_gnullvm", 1197 | "windows_i686_msvc 0.52.6", 1198 | "windows_x86_64_gnu 0.52.6", 1199 | "windows_x86_64_gnullvm 0.52.6", 1200 | "windows_x86_64_msvc 0.52.6", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "windows_aarch64_gnullvm" 1205 | version = "0.48.5" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1208 | 1209 | [[package]] 1210 | name = "windows_aarch64_gnullvm" 1211 | version = "0.52.6" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1214 | 1215 | [[package]] 1216 | name = "windows_aarch64_msvc" 1217 | version = "0.48.5" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1220 | 1221 | [[package]] 1222 | name = "windows_aarch64_msvc" 1223 | version = "0.52.6" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1226 | 1227 | [[package]] 1228 | name = "windows_i686_gnu" 1229 | version = "0.48.5" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1232 | 1233 | [[package]] 1234 | name = "windows_i686_gnu" 1235 | version = "0.52.6" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1238 | 1239 | [[package]] 1240 | name = "windows_i686_gnullvm" 1241 | version = "0.52.6" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1244 | 1245 | [[package]] 1246 | name = "windows_i686_msvc" 1247 | version = "0.48.5" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1250 | 1251 | [[package]] 1252 | name = "windows_i686_msvc" 1253 | version = "0.52.6" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1256 | 1257 | [[package]] 1258 | name = "windows_x86_64_gnu" 1259 | version = "0.48.5" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1262 | 1263 | [[package]] 1264 | name = "windows_x86_64_gnu" 1265 | version = "0.52.6" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1268 | 1269 | [[package]] 1270 | name = "windows_x86_64_gnullvm" 1271 | version = "0.48.5" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1274 | 1275 | [[package]] 1276 | name = "windows_x86_64_gnullvm" 1277 | version = "0.52.6" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1280 | 1281 | [[package]] 1282 | name = "windows_x86_64_msvc" 1283 | version = "0.48.5" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1286 | 1287 | [[package]] 1288 | name = "windows_x86_64_msvc" 1289 | version = "0.52.6" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1292 | 1293 | [[package]] 1294 | name = "xtask" 1295 | version = "0.1.0" 1296 | dependencies = [ 1297 | "anyhow", 1298 | "clap", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "zerocopy" 1303 | version = "0.7.35" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1306 | dependencies = [ 1307 | "zerocopy-derive", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "zerocopy-derive" 1312 | version = "0.7.35" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1315 | dependencies = [ 1316 | "proc-macro2", 1317 | "quote", 1318 | "syn", 1319 | ] 1320 | --------------------------------------------------------------------------------