Initial profiling support for Iced.

This commit is contained in:
Bingus 2022-11-29 19:50:58 -08:00 committed by Héctor Ramón Jiménez
parent ba20ac8e49
commit c5cd236b73
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
20 changed files with 357 additions and 35 deletions

15
profiling/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "iced_profiling"
authors = ["Bingus <shankern@protonmail.com>"]
version = "0.1.0"
edition = "2021"
description = "Profiling backend implementations for Iced"
[dependencies]
tracing = { version = "0.1.37", default-features = false, features = ["std"] }
tracing-core = "0.1.30"
tracing-subscriber = { version = "0.3.16", features = ["registry", "env-filter"] }
[dependencies.tracing-chrome]
version = "0.7.0"
optional = true

61
profiling/README.md Normal file
View file

@ -0,0 +1,61 @@
# `iced_profiling`
[![Documentation](https://docs.rs/iced_profiling/badge.svg)]
[![Crates.io](https://img.shields.io/crates/v/iced_profiling.svg)](https://crates.io/crates/iced_profiling)
[![License](https://img.shields.io/crates/l/iced_profiling.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd)
`iced_profiling` is a crate which implements various tracing backends for Iced.
It relies on the [tracing](https://crates.io/crates/tracing) crate to collect diagnostics. We currently only support
tracing with `tracing`'s `info_span!` macro, but will consider different logging levels in the future. PRs welcome!
## Trace backends
We currently support only Chrome JSON traces using the [tracing-chrome](https://crates.io/crates/tracing-chrome) crate.
There are plans to add support for [Tracy](https://github.com/wolfpld/tracy) in the near future!
## Generating a trace file
### Using Iced's `Application`
Simply enable your tracing backend of choice (e.g. `trace_chrome`) feature in Iced & run your project.
```shell
cargo run --features iced/trace_chrome
```
### Standalone dependency
You can enable tracing by enabling your tracing backend of choice as a feature of `iced_profiling`.
```toml
iced_profiling = { version = "0.1.0", features = ["tracing-chrome"]}
```
Doing so will require you to initialize the profiler manually like so:
```rust
let _guard = iced_profiling::init();
```
This reference must be kept alive for the entire duration of your application that you wish to profile.
## Chrome
By default, Chrome trace files will be generated in the current working directory:
```shell
path/to/your/project/project_trace_{timestamp}.json
```
You also set a specific path by setting the `CHROME_TRACE_FILE` env variable:
```shell
CHROME_TRACE_FILE = ~/Desktop/trace.json cargo run
```
If you cannot find your trace file, there may have been a permission issue when trying to generate your file. Be sure to check your cargo manifest directory!
Once your file is generated, you can view it in Google Chrome at either [ui.perfetto.dev](ui.perfetto.dev) (new) or [chrome://trace](chrome://trace) (old).
<p align="center">
<img alt="The native target" src="../docs/images/perfetto.png" width="80%">
</p>

100
profiling/src/lib.rs Normal file
View file

@ -0,0 +1,100 @@
use std::time::Duration;
use tracing_subscriber::prelude::*;
use tracing_subscriber::Registry;
#[cfg(feature = "tracing-chrome")]
use {
tracing_chrome::FlushGuard,
tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},
};
pub use tracing::{info_span, instrument};
/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends.
pub struct Profiler {
#[cfg(feature = "tracing-chrome")]
/// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing.
_guard: FlushGuard,
}
pub fn init() -> Profiler {
// Registry stores the spans & generates unique span IDs
let subscriber = Registry::default();
#[cfg(feature = "tracing-chrome")]
let (chrome_layer, guard) = {
let mut layer = tracing_chrome::ChromeLayerBuilder::new();
// Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json,
// for uploading to chrome://tracing (old) or ui.perfetto.dev (new).
if let Ok(path) = std::env::var("CHROME_TRACE_FILE") {
layer = layer.file(path);
} else if let Ok(current_dir) = std::env::current_dir() {
let time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(Duration::from_millis(0))
.as_millis();
let trace_file_name = current_dir
.file_name()
.map(|file_dir| {
format!(
"{}_trace_{}.json",
file_dir.to_str().unwrap_or("trace"),
time
)
})
.unwrap_or_else(|| "trace.json".to_string());
let path = format!(
"{}/{}",
current_dir.to_str().expect("Invalid path"),
trace_file_name
);
layer = layer.file(path);
} else {
layer = layer.file(env!("CARGO_MANIFEST_DIR"))
}
let (chrome_layer, guard) = layer
.name_fn(Box::new(|event_or_span| match event_or_span {
tracing_chrome::EventOrSpan::Event(event) => {
event.metadata().name().into()
}
tracing_chrome::EventOrSpan::Span(span) => {
if let Some(fields) = span
.extensions()
.get::<FormattedFields<DefaultFields>>()
{
format!(
"{}: {}",
span.metadata().name(),
fields.fields.as_str()
)
} else {
span.metadata().name().into()
}
}
}))
.build();
(chrome_layer, guard)
};
let fmt_layer = tracing_subscriber::fmt::Layer::default();
let subscriber = subscriber.with(fmt_layer);
#[cfg(feature = "tracing-chrome")]
let subscriber = subscriber.with(chrome_layer);
// create dispatcher which will forward span events to the subscriber
// this can only be set once or will panic
tracing::subscriber::set_global_default(subscriber)
.expect("Profiler could not set the global default subscriber.");
Profiler {
#[cfg(feature = "tracing-chrome")]
_guard: guard,
}
}