An intrusive flamegraph profiling tool for rust.

Related tags

Profiling flame
Overview

FLAME

A cool flamegraph library for rust

Flamegraphs are a great way to view profiling information. At a glance, they give you information about how much time your program spends in critical sections of your code giving you some much-needed insight into where optimizations may be needed.

Unlike tools like perf which have the OS interrupt your running program repeatedly and reports on every function in your callstack, FLAME lets you choose what you want to see in the graph by adding performance instrumentation to your own code.

Simply use any of FLAMEs APIs to annotate the start and end of a block code that you want timing information from, and FLAME will organize these timings hierarchically.

Docs

Here's an example of how to use some of FLAMEs APIs:

extern crate flame;

use std::fs::File;

fn main() {
    // Manual `start` and `end`
    flame::start("read file");
    let x = read_a_file();
    flame::end("read file");

    // Time the execution of a closure.  (the result of the closure is returned)
    let y = flame::span_of("database query", || query_database());

    // Time the execution of a block by creating a guard.
    let z = {
        let _guard = flame::start_guard("cpu-heavy calculation");
        cpu_heavy_operations_1();
        // Notes can be used to annotate a particular instant in time.
        flame::note("something interesting happened", None);
        cpu_heavy_operations_2()
    };

    // Dump the report to disk
    flame::dump_html(&mut File::create("flame-graph.html").unwrap()).unwrap();
    
    // Or read and process the data yourself!
    let spans = flame::spans();
    
    println!("{} {} {}", x, y, z);
}

And here's a screenshot of a flamegraph produced by dump_html (from a different project):

flamegraph

Full Example

use std::fs::File;

use flame;

fn make_vec(size: usize) -> Vec<u32> {
    // start_guard needs to drop to calculate duration.
    let _fg = ::flame::start_guard("make_vec");

    let mut res = flame::span_of("vec init", || vec![0_u32; size]);
    for x in 0..size {
        res[x] = ((x + 10)/3) as u32;
    }
    let mut waste_time = 0;
    for i in 0..size*10 {
        waste_time += i
    }
    res
}

fn more_computing(i: usize) {
    let _fg = ::flame::start_guard("more_computation");

    for x in 0..(i * 100) {
        let mut v = make_vec(x);
        let x = Vec::from(&v[..]);
        for i in 0..v.len() {
            let flip = (v.len() - 1) - i as usize;
            v[i] = x[flip];
        }
    }
}

fn some_computation() {
    let _fg = ::flame::start_guard("some_computation");

    for i in 0..15 {
        more_computing(i);
    }
}


fn main() {
    let _fg = ::flame::start_guard("main");
    
    some_computation();
    // in order to create the flamegraph you must call one of the
    // flame::dump_* functions.
    flame::dump_html(File::create("flamegraph.html").unwrap()).unwrap();
}

Below is the resulting flamegraph.

flamegraph

llogiq has created flamer, a compiler plugin that automatically inserts FLAME instrumentation into annotated functions allowing you to write code like

#[flame]
fn this_function_is_profiled() {
    ...
}
Comments
  • Side-effect on `std::convert::Into<_>` introduced in 0.1.12

    Side-effect on `std::convert::Into<_>` introduced in 0.1.12

    As the 0.1.12 update yesterday, I noticed that building lib unit tests is failing in unicode-bidi with flame_it feature. I went back and minimized the changes for the break, which actually reduced to only extern crate flame;, and only happening for =0.1.12.

    Here's the commit causing error on a stable unicode-bidi build: https://github.com/behnam/rust-unicode-bidi/commit/6e6fc7890bca1e1fe952e69508dba9369e1d37b1

    Here's the error:

    error[E0283]: type annotations required: cannot resolve `level::Level: std::convert::Into<_>`
       --> src/level.rs:331:31
        |
    331 |         assert_eq!(1u8, level.into());
        |                               ^^^^
    
    error: aborting due to previous error
    

    from https://travis-ci.org/behnam/rust-unicode-bidi/jobs/243806493#L259

    I have no idea how flame can cause this in a module that doesn't use any of the traits. What do you think, @TyOverby ?

    opened by behnam 10
  • Implement Stack Merging

    Implement Stack Merging

    As noted on http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html#description

    The x-axis spans the sample population. It does not show the passing of time from left to right, as most graphs do. The left to right ordering has no meaning (it's sorted alphabetically to maximize frame merging).

    Looks like rust flame is not doing any merging, which results in:

    • technically not being a CPU Flame Graph;
    • hard to see where the actual CPU usage is, as where the number of calls are high, the names don't fit in the small lines, and even the lines become invisible.
    • too many SVG objects on the screen that slow down the rendering and digging in.
    opened by behnam 6
  • ' panicked" on unit tests">

    "thread '' panicked" on unit tests

    Following up the problem we faced in https://github.com/servo/unicode-bidi/pull/40, I'm able to repro the problem consistently on Ubuntu 12.04.5 with latest nightly rust.

    Here's the repro:

    ~/code/rust-unicode-bidi$ RUST_BACKTRACE=1 RUST_TEST_THREADS=1 cargo +nightly test --verbose --lib --features flame_it -- --nocapture >/dev/null
           Fresh matches v0.1.6
           Fresh serde v1.0.8
           Fresh libc v0.2.23
           Fresh winapi-build v0.1.1
           Fresh winapi v0.2.8
           Fresh lazy_static v0.2.8
           Fresh serde_test v1.0.8
           Fresh kernel32-sys v0.2.2
           Fresh thread-id v2.0.0
           Fresh flame v0.1.11
           Fresh flamer v0.1.4
           Fresh unicode-bidi v0.3.3 (file:///home/behnam/code/rust-unicode-bidi)
        Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
         Running `/home/behnam/code/rust-unicode-bidi/target/debug/deps/unicode_bidi-f20b46f3613e37fb --nocapture`
    thread '<unnamed>' panicked at 'use of std::thread::current() is not possible after the thread's local data has been destroyed', /checkout/src/libcore/option.rs:823
    stack backtrace:
       0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
    

    The failure is caused by the std::thread::current() expecting thread dat to not be destroyed: https://doc.rust-lang.org/1.3.0/src/std/thread/mod.rs.html#409

    Basically, when flame (and flamer) are enabled, a thread panics at the ends of the test. (The tests themselves all pass with ok message.) Without flame/flamer crates loaded, there's no panic.

    Crate dependency tree is:

    ├── flame v0.1.11
    │   ├── lazy_static v0.2.8
    │   └── thread-id v2.0.0
    │       ├── kernel32-sys v0.2.2
    │       │   └── winapi v0.2.8
    │       └── libc v0.2.23
    

    I'm guessing that the bug is somewhere under thread-id. (cc @ruuda)

    I haven't been able to repro it in any later Ubuntu version. I'll report if and when I can.

    opened by behnam 6
  • Documentation / repo / crates.io mismatch

    Documentation / repo / crates.io mismatch

    The published documentation lists methods which do not exist in the latest version on Crates.io.

    I also can’t find code in the repo which matches the documentation which is currently published.

    Could you clarify what’s gone on here?

    opened by ticky 5
  • thread test is failing

    thread test is failing

    #25 was closed, but workaround mentioned there didn't work...

    + /usr/bin/cargo test --release -j1
           Fresh serde v1.0.19
           Fresh num-traits v0.1.40
           Fresh dtoa v0.4.2
           Fresh lazy_static v0.2.9
           Fresh itoa v0.3.4
           Fresh unicode-xid v0.1.0
           Fresh libc v0.2.33
           Fresh quote v0.3.15
           Fresh serde_json v1.0.6
           Fresh synom v0.11.3
           Fresh thread-id v3.2.0
           Fresh syn v0.11.11
           Fresh serde_derive_internals v0.17.0
           Fresh serde_derive v1.0.19
       Compiling flame v0.2.0 (file:///builddir/build/BUILD/flame-0.2.0)
         Running `/usr/bin/rustc --crate-name flame src/lib.rs --emit=dep-info,link -C opt-level=3 --test --cfg 'feature="default"' --cfg 'feature="json"' --cfg 'feature="serde"' --cfg 'feature="serde_derive"' --cfg 'feature="serde_json"' -C metadata=55a8dd78fadf7079 -C extra-filename=-55a8dd78fadf7079 --out-dir /builddir/build/BUILD/flame-0.2.0/target/release/deps -L dependency=/builddir/build/BUILD/flame-0.2.0/target/release/deps --extern serde=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde-9be79e3bf217c101.rlib --extern serde_json=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_json-c4e6d1533382957b.rlib --extern serde_derive=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_derive-b8e3961dc7f06b8e.so --extern lazy_static=/builddir/build/BUILD/flame-0.2.0/target/release/deps/liblazy_static-81a739ef1bc96bbc.rlib --extern thread_id=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libthread_id-4879e83dca0b7ebf.rlib -Copt-level=3 -Cdebuginfo=2 -Clink-arg=-Wl,-z,relro,-z,now`
         Running `/usr/bin/rustc --crate-name demo examples/demo.rs --crate-type bin --emit=dep-info,link -C opt-level=3 --cfg 'feature="default"' --cfg 'feature="json"' --cfg 'feature="serde"' --cfg 'feature="serde_derive"' --cfg 'feature="serde_json"' -C metadata=1a0c0dff5daa6f29 -C extra-filename=-1a0c0dff5daa6f29 --out-dir /builddir/build/BUILD/flame-0.2.0/target/release/examples -L dependency=/builddir/build/BUILD/flame-0.2.0/target/release/deps --extern serde=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde-9be79e3bf217c101.rlib --extern serde_json=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_json-c4e6d1533382957b.rlib --extern serde_derive=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_derive-b8e3961dc7f06b8e.so --extern lazy_static=/builddir/build/BUILD/flame-0.2.0/target/release/deps/liblazy_static-81a739ef1bc96bbc.rlib --extern thread_id=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libthread_id-4879e83dca0b7ebf.rlib --extern flame=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libflame-1016207c5c092409.rlib -Copt-level=3 -Cdebuginfo=2 -Clink-arg=-Wl,-z,relro,-z,now`
         Running `/usr/bin/rustc --crate-name tests tests/tests.rs --emit=dep-info,link -C opt-level=3 --test --cfg 'feature="default"' --cfg 'feature="json"' --cfg 'feature="serde"' --cfg 'feature="serde_derive"' --cfg 'feature="serde_json"' -C metadata=8b7dc1aeb3c717dd -C extra-filename=-8b7dc1aeb3c717dd --out-dir /builddir/build/BUILD/flame-0.2.0/target/release/deps -L dependency=/builddir/build/BUILD/flame-0.2.0/target/release/deps --extern serde=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde-9be79e3bf217c101.rlib --extern serde_json=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_json-c4e6d1533382957b.rlib --extern serde_derive=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libserde_derive-b8e3961dc7f06b8e.so --extern lazy_static=/builddir/build/BUILD/flame-0.2.0/target/release/deps/liblazy_static-81a739ef1bc96bbc.rlib --extern thread_id=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libthread_id-4879e83dca0b7ebf.rlib --extern flame=/builddir/build/BUILD/flame-0.2.0/target/release/deps/libflame-1016207c5c092409.rlib -Copt-level=3 -Cdebuginfo=2 -Clink-arg=-Wl,-z,relro,-z,now`
        Finished release [optimized] target(s) in 5.23 secs
         Running `/builddir/build/BUILD/flame-0.2.0/target/release/deps/flame-55a8dd78fadf7079`
    running 0 tests
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
         Running `/builddir/build/BUILD/flame-0.2.0/target/release/deps/tests-8b7dc1aeb3c717dd`
    running 11 tests
    test dropped_guarded_event ... ok
    test double_nested ... ok
    test cant_note ... ok
    test implicit_guarded_event ... ok
    test end_with ... ok
    test named_guarded_event ... ok
    test single_event ... ok
    test single_nested ... ok
    test multiple_guard_early_return ... ok
    test wrong_name ... ok
    test threads ... FAILED
    failures:
    ---- threads stdout ----
    	thread 'threads' panicked at 'assertion failed: `(left == right)`
      left: `2`,
     right: `6`', tests/tests.rs:111:4
    failures:
        threads
    test result: FAILED. 10 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
    
    opened by ignatenkobrain 5
  • Replace clock_ticks with std::time

    Replace clock_ticks with std::time

    Instant is currently grabbed in thread_local init of Library. If it's desirable to share Instant then a write-once read-many global could be used for the epoch

    Developing through github, feel free to squash

    opened by serprex 3
  • add an `end_with(_, _)` function

    add an `end_with(_, _)` function

    Rationale: We got some problems reported on flamer that the drop impls of span guards are sometimes optimized out. Also we probably don't need to care about panics (or want to record an unclosed span in that case which would not happen with start_guard(_)).

    This fixes #8.

    opened by llogiq 3
  • flame::end(repl) attempted to end end

    flame::end(repl) attempted to end end

    I'm running into an issue where when I call flame::start("repl") and then later flame::end("repl") from one of my functions, the end call results in this panic:

    error: thread 'main' panicked at 'flame::end(repl) attempted to end end'
    

    I don't ever call start/end with "end" as an argument that I know of, unless flamer is doing that for some reason. When I have a few minutes to kill I'm gonna go through my project and comment out all of the start/end calls until I find the minimal repro case, but I figured I'd post here in the meantime in case anyone else has any intuition about what would cause this.

    opened by MasonRemaley 2
  • threads test is failing

    threads test is failing

    	thread 'threads' panicked at 'assertion failed: `(left == right)` (left: `5`, right: `6`)', tests/tests.rs:111
    

    Not sure which information to provide.

    opened by ignatenkobrain 2
  • how to get data from multiple threads?

    how to get data from multiple threads?

    Currently, a thread-local library is used to collect frames. However, this fails when using multiple threads; the report will only ever contain frames for the thread that started the reporting. So how to report for multiple threads?

    I think using a thread local library for collection is the only viable choice (otherwise we'd synchronize cores and thus destroy the performance we intend to measure). Using this for multiple threads would probably require some kind of global library registry (where each thread local would need to be registered on construction, and would also require moving out of the thread on drop). Also we'd need a way to display data from multiple threads without thoroughly confusing users.

    opened by llogiq 2
  • Remove pub qualifier from local function which causes error E0447

    Remove pub qualifier from local function which causes error E0447

    Running the example code generated the following error:

    $ cargo run --example demo
        Updating registry `https://github.com/rust-lang/crates.io-index`
       Compiling libc v0.1.12
    /Users/pastoraleman/.cargo/registry/src/github.com-88ac128001ac3a9a/libc-0.1.12/rust/src/liblibc/lib.rs:81:21: 81:39 warning: lint raw_pointer_derive has been removed: using derive with raw pointers is ok
    /Users/pastoraleman/.cargo/registry/src/github.com-88ac128001ac3a9a/libc-0.1.12/rust/src/liblibc/lib.rs:81 #![allow(bad_style, raw_pointer_derive)]
                                                                                                                            ^~~~~~~~~~~~~~~~~~
       Compiling clock_ticks v0.1.0
       Compiling flame v0.1.4 (file:///Users/pastoraleman/dev_sandbox/flame)
    src/lib.rs:368:5: 379:6 error: visibility has no effect inside functions or block expressions [E0447]
    src/lib.rs:368     pub fn print_span(span: &Span) {
    src/lib.rs:369         let mut buf = String::new();
    src/lib.rs:370         for _ in 0 .. span.depth {
    src/lib.rs:371             buf.push_str("  ");
    src/lib.rs:372         }
    src/lib.rs:373         buf.push_str("| ");
                   ...
    src/lib.rs:368:5: 379:6 help: run `rustc --explain E0447` to see a detailed explanation
    error: aborting due to previous error
    Could not compile `flame`.
    
    To learn more, run the command again with --verbose.
    

    Removing the "pub" qualifier from the local function allows the example to run correctly.

    opened by pwrdwnsys 2
  • WIP: fix build without json feature

    WIP: fix build without json feature

    I've put the json dump from the example behind a #[cfg(..)] so the build works. However, the threads test fails without the feature. I'll need to look deeper into this.

    opened by llogiq 0
  • Crate doesn't compile without `json` feature

    Crate doesn't compile without `json` feature

    The crate's code uses extern crate serde_derive without #[cfg(feature = "json")], meaning that it doesn't compile if the json feature is disabled.

    opened by mbartlett21 1
  • Missing escping in dump_html

    Missing escping in dump_html

    It appears like flame::dump_html writes json data to a script tag in the generated html file, without doing json or html escaping. That means that the generated page could be unable to load due to syntax errors or get incorrect values.

    Trying out the flamegraph in https://github.com/RustPython/RustPython, I got syntax errors in the generated data such as:

    {
    name: "init VirtualMachine",
    value: 307664657,
    start: 80780829,
    end: 388445486,
    children: [
    {
    name: "call_method("__setattr__")",
    value: 195873,
    start: 80813067,
    end: 81008940,
    children: [
    {
    

    The flamegraph hade spans named call_method("__setattr__"), where the quotes would have to be escaped in order to be put in a javascript/json string.

    opened by alvinlindstam 1
  • Suiggestion: use inferno for flamegraphs

    Suiggestion: use inferno for flamegraphs

    Now that inferno is relatively stable, and exposes separate APIs for both stack folding and producing flamegraphs, I wonder if it'd be interesting to see if we could make flame use inferno to generate the flamegraphs. That way you wouldn't need to vendor all the JS code and such into this project. You'd also get stack folding for free if you wished!

    opened by jonhoo 0
  • Doesn't seem to work with multiple threads

    Doesn't seem to work with multiple threads

    Minimal test case:

    use rayon::prelude::*;
    use flame;
    use std:: fs::File;
    
    fn something(i: usize) -> f64 {
        flame::start("Something");
        std::thread::sleep(std::time::Duration::from_millis(10));
        flame::end("Something");
        0.56
    }
    fn main() {
        let a = (0..200).collect::<Vec<_>>()
            .par_iter()
            .map(|i| something(*i))
            .collect::<Vec<_>>();
    
        flame::dump_html(&mut File::create("flames.html").unwrap()).unwrap();
        println!("{:?}", flame::threads());
    }
    

    Dependencies:

    rayon = "1.0.3"
    flame = "0.2.2"
    

    The written file has no graphics. The output is: [Thread { id: 140470522639296, name: Some("main"), spans: [], _priv: () }], so no data is available.

    opened by Ploppz 5
Owner
null
A http server benchmark tool written in rust 🦀

rsb - rust benchmark rsb is a http server benchmark tool written in rust. The development of this tool is mainly inspired by the bombardier project, a

Michael 45 Apr 10, 2023
A command-line benchmarking tool

hyperfine 中文 A command-line benchmarking tool. Demo: Benchmarking fd and find: Features Statistical analysis across multiple runs. Support for arbitra

David Peter 14.1k Jan 6, 2023
Benchmark tool for comparing with other runtimes.

Monoio Benchmark TCP ping-pong(not echo) is a common benchmark for network applications. We will use 1K ping-pong to test performance of different run

null 6 Oct 10, 2022
It is not about Keanu Reeves but a benchmark tool.

benchman Features Focus on one-shot benchmark RAII-style Statistics (Average, Median, 95% and 99% percentile) Colored output Tagging Nesting Motivatio

Akira Hayakawa 4 Feb 12, 2022
A unix "time" like benchmarking tool on steroids

benchie Usage Binary Once Rust is installed (see step 1 in "Toolchain Setup"), you can easily install the latest version of benchie with: $ cargo inst

benchie 3 May 6, 2022
Statistics-driven benchmarking library for Rust

Criterion.rs Statistics-driven Microbenchmarking in Rust Getting Started | User Guide | Master API Docs | Released API Docs | Changelog | | Criterion.

Brook Heisler 3.1k Jan 8, 2023
A Rust implementation of the PCP instrumentation API

hornet hornet is a Performance Co-Pilot (PCP) Memory Mapped Values (MMV) instrumentation library written in Rust. Contents What is PCP MMV instrumenta

Performance Co-Pilot 33 Sep 15, 2022
A stopwatch library for Rust. Used to time things.

rust-stopwatch This is a simple module used to time things in Rust. Usage To use, add the following line to Cargo.toml under [dependencies]: stopwatch

Chucky Ellison 77 Dec 11, 2022
Benchmark for Rust and humans

bma-benchmark Benchmark for Rust and humans What is this for I like testing different libraries, crates and algorithms. I do benchmarks on prototypes

Altertech 11 Jan 17, 2022
Rust wrapper for COCO benchmark functions.

Coco Rust bindings for the COCO Numerical Black-Box Optimization Benchmarking Framework. See https://github.com/numbbo/coco and https://numbbo.github.

Leopold Luley 1 Nov 15, 2022
Easy c̵̰͠r̵̛̠ö̴̪s̶̩̒s̵̭̀-t̶̲͝h̶̯̚r̵̺͐e̷̖̽ḁ̴̍d̶̖̔ ȓ̵͙ė̶͎ḟ̴͙e̸̖͛r̶̖͗ë̶̱́ṉ̵̒ĉ̷̥e̷͚̍ s̷̹͌h̷̲̉a̵̭͋r̷̫̊ḭ̵̊n̷̬͂g̵̦̃ f̶̻̊ơ̵̜ṟ̸̈́ R̵̞̋ù̵̺s̷̖̅ţ̸͗!̸̼͋

Rust S̵̓i̸̓n̵̉ I̴n̴f̶e̸r̵n̷a̴l mutability! Howdy, friendly Rust developer! Ever had a value get m̵̯̅ð̶͊v̴̮̾ê̴̼͘d away right under your nose just when

null 294 Dec 23, 2022
Show puffin profiler flamegraph in-game using egui

Show puffin profiler flamegraph in-game using egui puffin is an instrumentation profiler where you opt-in to profile parts of your code: fn my_functio

Emil Ernerfeldt 44 Jun 3, 2022
A vim profiling tool

vim-profiler ?? vim-profiler is a wrapper around the (n)vim --startuptime command, written in Rust. The binary is called vp and has only been tested o

Liam 35 Dec 14, 2022
Cloud-Based Microservice Performance Profiling Tool

Revelio Systems Revelio Systems is a student startup sponsored by UT Austin's Inventors Program in partnership with Trend Micro. Team: Tejas Saboo, So

Tejas Saboo 1 Feb 24, 2022
Experimental one-shot benchmarking/profiling harness for Rust

Iai Experimental One-shot Benchmark Framework in Rust Getting Started | User Guide | Released API Docs | Changelog Iai is an experimental benchmarking

Brook Heisler 409 Dec 25, 2022
A safe `Pin`-based intrusive doubly-linked list in Rust

pin-list This crate provides PinList, a safe Pin-based intrusive doubly linked list. Example A thread-safe unfair async mutex. use pin_project_lite::p

Sabrina Jewson 7 Oct 26, 2022
A Rust doubly-linked intrusive list with Miri tests

Intrusive stack-allocated doubly-linked list example Quickstart cargo test Or to run the tests under Miri: rustup toolchain install nightly # if y

Cliff L. Biffle 9 Dec 4, 2022
The most fundamental type for async synchronization: an intrusive linked list of futures

wait-list This crate provides WaitList, the most fundamental type for async synchronization. WaitList is implemented as an intrusive linked list of fu

Sabrina Jewson 7 Oct 26, 2022
A command line tool written in Rust and designed to be a modern build tool + package manager for C/C++ projects.

CCake CCake is a command line tool written in Rust and designed to be a modern build tool + package manager for C/C++ projects. Goals To be easily und

Boston Vanseghi 4 Oct 24, 2022
Ethereum key tool - Lightweight CLI tool to deal with ETH keys written in rust

ekt - Etherum Key Tool ekt is a lightweight tool to generate ethereum keys and addresses. Installation Either clone it and run it with cargo or instal

null 5 May 8, 2023