Skip to content

Commit

Permalink
Puffin profiler (#1)
Browse files Browse the repository at this point in the history
Add a button to profile the viewer. This is done via puffin_viewer
  • Loading branch information
emilk authored Apr 13, 2022
1 parent b46d78a commit df3bf37
Show file tree
Hide file tree
Showing 18 changed files with 206 additions and 51 deletions.
27 changes: 23 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ env:
RUSTDOCFLAGS: -D warnings

jobs:
check:
name: Check
check_all_features:
name: Check --all-features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -20,11 +20,28 @@ jobs:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features

check_no_features:
name: Check --no-default-features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
- uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features --features __ci

check_wasm:
name: Check wasm32
runs-on: ubuntu-latest
Expand All @@ -35,6 +52,7 @@ jobs:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
- run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
Expand All @@ -51,7 +69,7 @@ jobs:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
- run: sudo apt-get install libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
- uses: actions-rs/cargo@v1
with:
command: test
Expand All @@ -67,7 +85,7 @@ jobs:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
- run: sudo apt-get install libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
Expand All @@ -84,6 +102,7 @@ jobs:
profile: minimal
toolchain: 1.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
Expand Down
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ opt-level = 1 # Speed up debug builds

[patch.crates-io]
# we use bleeding edge versions of some crates:
eframe = { git = "https://github.com/emilk/egui", rev = "08b208586a61c86c9968c4a466f17ce6e842389c" } # 2022-04-12
egui_extras = { git = "https://github.com/emilk/egui", rev = "08b208586a61c86c9968c4a466f17ce6e842389c" } # 2022-04-12
egui_glow = { git = "https://github.com/emilk/egui", rev = "08b208586a61c86c9968c4a466f17ce6e842389c" } # 2022-04-12
eframe = { git = "https://github.com/emilk/egui", rev = "170b21b63e461dfeaa7597815b67f1dcd756ac80" } # 2022-04-13
egui = { git = "https://github.com/emilk/egui", rev = "170b21b63e461dfeaa7597815b67f1dcd756ac80" } # 2022-04-13
egui_extras = { git = "https://github.com/emilk/egui", rev = "170b21b63e461dfeaa7597815b67f1dcd756ac80" } # 2022-04-13
egui_glow = { git = "https://github.com/emilk/egui", rev = "170b21b63e461dfeaa7597815b67f1dcd756ac80" } # 2022-04-13
# eframe = { path = "../../egui/eframe" }
# egui = { path = "../../egui/egui" }
# egui_extras = { path = "../../egui/egui_extras" }
Expand Down
2 changes: 2 additions & 0 deletions check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy:
cargo test --workspace --all-targets --all-features
cargo test --workspace --doc --all-features

cargo check --no-default-features

cargo doc --lib --no-deps --all-features
cargo doc --document-private-items --no-deps --all-features

Expand Down
4 changes: 2 additions & 2 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ allow = [
"ISC", # https://tldrlegal.com/license/-isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
# "OpenSSL", # https://www.openssl.org/source/license.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]

[[licenses.clarify]]
Expand Down
7 changes: 7 additions & 0 deletions viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ publish = false
crate-type = ["cdylib", "rlib"]


[features]
default = ["puffin"]
puffin = ["dep:puffin", "dep:puffin_http", "eframe/puffin"]


[dependencies]
clap = { version = "3.1.6", features = ["derive"] }
comms = { path = "../comms", features = ["client"] }
Expand Down Expand Up @@ -43,6 +48,8 @@ vec1 = "1.8"
arboard = { version = "2.1", default-features = false, features = [
"image-data",
] }
puffin = { version = "0.13", optional = true }
puffin_http = { version = "0.10", optional = true }
tracing-subscriber = "0.3"
tokio = { version = "1.16", features = ["macros", "rt-multi-thread"] }
# TODO: consider using mimalloc
Expand Down
19 changes: 19 additions & 0 deletions viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,16 @@ struct AppState {
space_view: crate::space_view::SpaceView,
context_panel: crate::context_panel::ContextPanel,
time_panel: crate::time_panel::TimePanel,

#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
#[serde(skip)]
profiler: crate::misc::profiler::Profiler,
}

impl AppState {
fn show(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame, log_db: &LogDb) {
crate::profile_function!();

egui::TopBottomPanel::top("View").show(egui_ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
Expand All @@ -83,6 +89,17 @@ impl AppState {

ui.close_menu();
}

#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
if ui
.button("Profile viewer")
.on_hover_text(
"Starts a profiler, showing what makes the viewer run slow",
)
.clicked()
{
self.profiler.start();
}
});

if ui.button("Quit").clicked() {
Expand All @@ -108,6 +125,8 @@ impl AppState {
space_view,
context_panel,
time_panel,
#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
profiler: _,
} = self;

egui::TopBottomPanel::bottom("time_panel")
Expand Down
22 changes: 22 additions & 0 deletions viewer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,25 @@ pub use native::run_native_viewer;
mod web;
#[cfg(target_arch = "wasm32")]
pub use web::start;

// ---------------------------------------------------------------------------

/// Profiling macro for feature "puffin"
#[doc(hidden)]
#[macro_export]
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
puffin::profile_function!($($arg)*);
};
}

/// Profiling macro for feature "puffin"
#[doc(hidden)]
#[macro_export]
macro_rules! profile_scope {
($($arg: tt)*) => {
#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
puffin::profile_scope!($($arg)*);
};
}
2 changes: 2 additions & 0 deletions viewer/src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ mod clipboard;
mod image_cache;
pub(crate) mod log_db;
pub(crate) mod mesh_loader;
#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))]
pub(crate) mod profiler;
pub(crate) mod time_axis;
mod time_control;
mod viewer_context;
Expand Down
11 changes: 11 additions & 0 deletions viewer/src/misc/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ impl Clipboard {
}
}

#[cfg(all(feature = "puffin", not(target_arch = "wasm32")))] // only used sometimes
pub fn set_text(&mut self, text: String) {
if let Some(clipboard) = &mut self.arboard {
if let Err(err) = clipboard.set_text(text) {
tracing::error!("Failed to copy image to clipboard: {}", err);
} else {
tracing::info!("Image copied to clipboard");
}
}
}

pub fn set_image(&mut self, size: [usize; 2], rgba_unmultiplied: &[u8]) {
let [width, height] = size;
assert_eq!(width * height * 4, rgba_unmultiplied.len());
Expand Down
19 changes: 19 additions & 0 deletions viewer/src/misc/log_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) struct LogDb {

impl LogDb {
pub fn add(&mut self, msg: LogMsg) {
crate::profile_function!();
santiy_check(&msg);

self.time_points.insert(&msg.time_point);
Expand Down Expand Up @@ -64,6 +65,24 @@ impl LogDb {
self.messages.get(id)
}

/// Grouped by [`ObjectPath`], find the latest [`LogMsg`] that matches
/// the given time source and is not after the given time.
pub fn latest_of_each_object(
&self,
time_source: &str,
no_later_than: TimeValue,
) -> Vec<&LogMsg> {
crate::profile_function!();
self.object_history
.values()
.filter_map(|history| {
history
.latest(time_source, no_later_than)
.and_then(|id| self.get_msg(&id))
})
.collect()
}

pub fn latest(
&self,
time_source: &str,
Expand Down
55 changes: 55 additions & 0 deletions viewer/src/misc/profiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const PORT: u16 = puffin_http::DEFAULT_PORT;

#[derive(Default)]
pub struct Profiler {
server: Option<puffin_http::Server>,
}

impl Profiler {
pub fn start(&mut self) {
puffin::set_scopes_on(true);

if self.server.is_none() {
self.start_server();
}
start_puffin_viewer();
}

fn start_server(&mut self) {
let bind_addr = format!("0.0.0.0:{}", PORT);
self.server = match puffin_http::Server::new(&bind_addr) {
Ok(puffin_server) => {
tracing::info!(
"Started puffin profiling server. View with: cargo install puffin_viewer && puffin_viewer"
);
Some(puffin_server)
}
Err(err) => {
tracing::warn!("Failed to start puffin profiling server: {}", err);
None
}
};
}
}

fn start_puffin_viewer() {
let url = format!("127.0.0.1:{PORT}");
let child = std::process::Command::new("puffin_viewer")
.arg("--url")
.arg(&url)
.spawn();

if let Err(err) = child {
let cmd = format!("cargo install puffin_viewer && puffin_viewer --url {url}",);
crate::Clipboard::with(|cliboard| cliboard.set_text(cmd.clone()));
tracing::warn!(
"Failed to start puffin_viewer: {err}. Try connecting manually with: {cmd}"
);

rfd::MessageDialog::new()
.set_level(rfd::MessageLevel::Info)
.set_title("puffin_viewer required")
.set_description(&format!("To view the profiling data, run the following command:\n\n{cmd}\n\n(it has been copied to your clipboard)"))
.show();
}
}
37 changes: 4 additions & 33 deletions viewer/src/misc/time_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,46 +190,17 @@ impl TimeControl {
pub fn latest_of_each_object<'db>(
&self,
log_db: &'db crate::log_db::LogDb,
) -> BTreeMap<ObjectPath, (TimeValue, &'db LogMsg)> {
) -> Vec<&'db LogMsg> {
crate::profile_function!();

let current_time = if let Some(current_time) = self.time() {
current_time
} else {
return Default::default();
};
let source = self.source();

let mut latest: BTreeMap<ObjectPath, (TimeValue, &LogMsg)> = BTreeMap::new();
for (time_value, msg) in log_db
.messages
.values()
.filter_map(|msg| {
let time_value = *msg.time_point.0.get(source)?;
Some((time_value, msg))
})
.filter(|(time_value, _msg)| time_value <= &current_time)
{
if let Some(existing) = latest.get_mut(&msg.object_path) {
if existing.0 < time_value {
*existing = (time_value, msg);
}
} else {
latest.insert(msg.object_path.clone(), (time_value, msg));
}
}

latest
}

/// Find the latest [`LogMsg`] of each unique [`ObjectPath`] that matches
/// the current time source and is not after the current time.
pub fn latest_of_each_object_vec<'db>(
&self,
log_db: &'db crate::log_db::LogDb,
) -> Vec<&'db LogMsg> {
self.latest_of_each_object(log_db)
.values()
.map(|(_, msg)| *msg)
.collect()
log_db.latest_of_each_object(source, current_time)
}
}

Expand Down
Loading

0 comments on commit df3bf37

Please sign in to comment.