notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 54deb2dd88d55fc877b13833fd0c5542f922c97b
parent 7b9e6f180caaf63b03a5cacf1de0f54f36aba488
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 23 Mar 2025 10:43:49 -0700

switch to profiling crates

This switches to the profiling crate for compatible
profiling between rust libraries.

To enable:

$ cargo build --release --features puffin

Feel free to experiment with other profiling backends
as well! Would be great to get tracy working.

Diffstat:
M.envrc | 6+++---
M.gitignore | 1+
MCargo.lock | 4++++
MCargo.toml | 3+++
ATODO | 0
Mcrates/notedeck/Cargo.toml | 3++-
Mcrates/notedeck/src/app.rs | 14+++++++-------
Mcrates/notedeck/src/unknowns.rs | 4+---
Mcrates/notedeck_chrome/Cargo.toml | 3++-
Mcrates/notedeck_columns/Cargo.toml | 3++-
Mcrates/notedeck_columns/src/app.rs | 8++------
Mcrates/notedeck_columns/src/images.rs | 18+++++-------------
Mcrates/notedeck_columns/src/ui/mention.rs | 4+---
Mcrates/notedeck_columns/src/ui/note/contents.rs | 14++------------
Mcrates/notedeck_columns/src/ui/note/context.rs | 8++------
Mcrates/notedeck_columns/src/ui/note/mod.rs | 15++++-----------
Mcrates/notedeck_columns/src/ui/note/reply_description.rs | 4+---
Mcrates/notedeck_columns/src/ui/profile/picture.rs | 8++------
Mcrates/notedeck_columns/src/unknowns.rs | 4+---
Mpreview | 2+-
20 files changed, 46 insertions(+), 80 deletions(-)

diff --git a/.envrc b/.envrc @@ -13,6 +13,6 @@ source scripts/macos_build_secrets.sh || : export PATH=$PATH:$HOME/.cargo/bin export JB55=32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245 -export JACK=npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m -export VROD=bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91 -export JEFFG=npub1zuuajd7u3sx8xu92yav9jwxpr839cs0kc3q6t56vd5u9q033xmhsk6c2uc +# simple todo reminders +export TODO_FILE=TODO +2>/dev/null todo.sh ls || : diff --git a/.gitignore b/.gitignore @@ -1,4 +1,5 @@ .buildcmd +TODO.bak android-config.json build.log perf.data diff --git a/Cargo.lock b/Cargo.lock @@ -2740,6 +2740,7 @@ dependencies = [ "nostr", "nostrdb", "poll-promise", + "profiling", "puffin", "puffin_egui", "serde", @@ -2765,6 +2766,7 @@ dependencies = [ "egui_extras", "notedeck", "notedeck_columns", + "profiling", "puffin", "puffin_egui", "serde", @@ -2803,6 +2805,7 @@ dependencies = [ "open", "poll-promise", "pretty_assertions", + "profiling", "puffin", "puffin_egui", "rfd", @@ -3487,6 +3490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", + "puffin", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml @@ -62,6 +62,7 @@ bincode = "1.3.3" mime_guess = "2.0.5" pretty_assertions = "1.4.1" jni = "0.21.1" +profiling = "1.0" [profile.small] inherits = 'release' @@ -84,6 +85,8 @@ eframe = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055 egui-winit = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" } egui_extras = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" } epaint = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" } +puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" } +puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" } #winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" } #winit = { path = "/home/jb55/dev/github/rust-windowing/winit" } #android-activity = { git = "https://github.com/damus-io/android-activity", rev = "da17773852312a58c3445422dfe477162f2f1265" } diff --git a/TODO b/TODO diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml @@ -32,6 +32,7 @@ ehttp = {workspace = true } mime_guess = { workspace = true } egui-winit = { workspace = true } tokenator = { workspace = true } +profiling = { workspace = true } [dev-dependencies] tempfile = { workspace = true } @@ -40,4 +41,4 @@ tempfile = { workspace = true } jni = { workspace = true } [features] -profiling = ["puffin", "puffin_egui"] +puffin = ["puffin_egui", "dep:puffin"] diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs @@ -76,8 +76,7 @@ fn render_notedeck(notedeck: &mut Notedeck, ctx: &egui::Context) { impl eframe::App for Notedeck { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - #[cfg(feature = "profiling")] - puffin::GlobalProfiler::lock().new_frame(); + profiling::finish_frame!(); // handle account updates self.accounts.update(&mut self.ndb, &mut self.pool, ctx); @@ -97,7 +96,7 @@ impl eframe::App for Notedeck { } } - #[cfg(feature = "profiling")] + #[cfg(feature = "puffin")] puffin_egui::profiler_window(ctx); } @@ -107,15 +106,16 @@ impl eframe::App for Notedeck { } } -#[cfg(feature = "profiling")] -fn setup_profiling() { +#[cfg(feature = "puffin")] +fn setup_puffin() { + info!("setting up puffin"); puffin::set_scopes_on(true); // tell puffin to collect data } impl Notedeck { pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { - #[cfg(feature = "profiling")] - setup_profiling(); + #[cfg(feature = "puffin")] + setup_puffin(); // Skip the first argument, which is the program name. let (parsed_args, unrecognized_args) = Args::parse(&args[1..]); diff --git a/crates/notedeck/src/unknowns.rs b/crates/notedeck/src/unknowns.rs @@ -246,6 +246,7 @@ impl UnknownId { /// We return all of this in a HashSet so that we can fetch these from /// remote relays. /// +#[profiling::function] pub fn get_unknown_note_ids<'a>( ndb: &Ndb, cached_note: &CachedNote, @@ -253,9 +254,6 @@ pub fn get_unknown_note_ids<'a>( note: &Note<'a>, ids: &mut HashMap<UnknownId, HashSet<RelayUrl>>, ) -> Result<()> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - // the author pubkey if ndb.get_profile_by_pubkey(txn, note.pubkey()).is_err() { ids.entry(UnknownId::Pubkey(Pubkey::new(*note.pubkey()))) diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -23,6 +23,7 @@ tokio = { workspace = true } tracing-appender = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } +profiling = { workspace = true } [dev-dependencies] tempfile = { workspace = true } @@ -40,7 +41,7 @@ path = "src/preview.rs" [features] default = [] -profiling = ["notedeck_columns/puffin", "puffin", "puffin_egui"] +puffin = ["profiling/profile-with-puffin", "dep:puffin"] debug-widget-callstack = ["egui/callstack"] debug-interactive-widgets = [] diff --git a/crates/notedeck_columns/Cargo.toml b/crates/notedeck_columns/Cargo.toml @@ -49,6 +49,7 @@ uuid = { workspace = true } sha2 = { workspace = true } base64 = { workspace = true } egui-winit = { workspace = true } +profiling = { workspace = true } [target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] rfd = "0.15" @@ -62,5 +63,5 @@ security-framework = "2.11.0" [features] default = [] -profiling = ["puffin", "puffin_egui"] +puffin = ["dep:puffin", "profiling/profile-with-puffin"] diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -507,10 +507,8 @@ fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) { } */ +#[profiling::function] fn render_damus_mobile(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::Ui) { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - //let routes = app.timelines[0].routes.clone(); if !app.columns(app_ctx.accounts).columns().is_empty() @@ -522,10 +520,8 @@ fn render_damus_mobile(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut e } } +#[profiling::function] fn render_damus_desktop(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::Ui) { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let screen_size = ui.ctx().screen_rect().width(); let calc_panel_width = (screen_size / get_active_columns(app_ctx.accounts, &app.decks_cache).num_columns() as f32) diff --git a/crates/notedeck_columns/src/images.rs b/crates/notedeck_columns/src/images.rs @@ -67,10 +67,8 @@ pub fn aspect_fill( response } +#[profiling::function] pub fn round_image(image: &mut ColorImage) { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - // The radius to the edge of of the avatar circle let edge_radius = image.size[0] as f32 / 2.0; let edge_radius_squared = edge_radius * edge_radius; @@ -114,10 +112,8 @@ pub fn round_image(image: &mut ColorImage) { } } +#[profiling::function] fn process_pfp_bitmap(imgtyp: ImageType, mut image: image::DynamicImage) -> ColorImage { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - match imgtyp { ImageType::Content => { let image_buffer = image.clone().into_rgba8(); @@ -156,10 +152,8 @@ fn process_pfp_bitmap(imgtyp: ImageType, mut image: image::DynamicImage) -> Colo } } +#[profiling::function] fn parse_img_response(response: ehttp::Response, imgtyp: ImageType) -> Result<ColorImage> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let content_type = response.content_type().unwrap_or_default(); let size_hint = match imgtyp { ImageType::Profile(size) => SizeHint::Size(size, size), @@ -167,16 +161,14 @@ fn parse_img_response(response: ehttp::Response, imgtyp: ImageType) -> Result<Co }; if content_type.starts_with("image/svg") { - #[cfg(feature = "profiling")] - puffin::profile_scope!("load_svg"); + profiling::scope!("load_svg"); let mut color_image = egui_extras::image::load_svg_bytes_with_size(&response.bytes, Some(size_hint))?; round_image(&mut color_image); Ok(color_image) } else if content_type.starts_with("image/") { - #[cfg(feature = "profiling")] - puffin::profile_scope!("load_from_memory"); + profiling::scope!("load_from_memory"); let dyn_image = image::load_from_memory(&response.bytes)?; Ok(process_pfp_bitmap(imgtyp, dyn_image)) } else { diff --git a/crates/notedeck_columns/src/ui/mention.rs b/crates/notedeck_columns/src/ui/mention.rs @@ -63,6 +63,7 @@ impl egui::Widget for Mention<'_> { } #[allow(clippy::too_many_arguments)] +#[profiling::function] fn mention_ui( ndb: &Ndb, img_cache: &mut Images, @@ -72,9 +73,6 @@ fn mention_ui( size: f32, selectable: bool, ) -> egui::InnerResponse<Option<NoteAction>> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let link_color = ui.visuals().hyperlink_color; ui.horizontal(|ui| { diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs @@ -60,6 +60,7 @@ impl egui::Widget for &mut NoteContents<'_, '_> { /// Render an inline note preview with a border. These are used when /// notes are references within a note #[allow(clippy::too_many_arguments)] +#[profiling::function] pub fn render_note_preview( ui: &mut egui::Ui, note_context: &mut NoteContext, @@ -68,9 +69,6 @@ pub fn render_note_preview( parent: NoteKey, note_options: NoteOptions, ) -> NoteResponse { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let note = if let Ok(note) = note_context.ndb.get_note_by_id(txn, id) { // TODO: support other preview kinds if note.kind() == 1 { @@ -118,6 +116,7 @@ pub fn render_note_preview( } #[allow(clippy::too_many_arguments)] +#[profiling::function] fn render_note_contents( ui: &mut egui::Ui, note_context: &mut NoteContext, @@ -125,9 +124,6 @@ fn render_note_contents( note: &Note, options: NoteOptions, ) -> NoteResponse { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let note_key = note.key().expect("todo: implement non-db notes"); let selectable = options.has_selectable_text(); let mut images: Vec<(String, MediaCacheType)> = vec![]; @@ -197,8 +193,6 @@ fn render_note_contents( }, BlockType::Hashtag => { - #[cfg(feature = "profiling")] - puffin::profile_scope!("hashtag contents"); let resp = ui.colored_label(link_color, format!("#{}", block.as_str())); if resp.clicked() { @@ -223,8 +217,6 @@ fn render_note_contents( } }; if hide_media || !found_supported() { - #[cfg(feature = "profiling")] - puffin::profile_scope!("url contents"); ui.add(Hyperlink::from_label_and_url( RichText::new(block.as_str()).color(link_color), block.as_str(), @@ -233,8 +225,6 @@ fn render_note_contents( } BlockType::Text => { - #[cfg(feature = "profiling")] - puffin::profile_scope!("text contents"); if options.has_scramble_text() { ui.add(egui::Label::new(rot13(block.as_str())).selectable(selectable)); } else { diff --git a/crates/notedeck_columns/src/ui/note/context.rs b/crates/notedeck_columns/src/ui/note/context.rs @@ -97,10 +97,8 @@ impl NoteContextButton { Self::max_distance_between_circles() / Self::expansion_multiple() } + #[profiling::function] pub fn show(ui: &mut egui::Ui, note_key: NoteKey, put_at: Rect) -> egui::Response { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let id = ui.id().with(("more_options_anim", note_key)); let min_radius = Self::min_radius(); @@ -138,13 +136,11 @@ impl NoteContextButton { response } + #[profiling::function] pub fn menu( ui: &mut egui::Ui, button_response: egui::Response, ) -> Option<NoteContextSelection> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let mut context_selection: Option<NoteContextSelection> = None; stationary_arbitrary_menu_button(ui, button_response, |ui| { diff --git a/crates/notedeck_columns/src/ui/note/mod.rs b/crates/notedeck_columns/src/ui/note/mod.rs @@ -307,15 +307,13 @@ impl<'a, 'd> NoteView<'a, 'd> { } } + #[profiling::function] fn note_header( ui: &mut egui::Ui, note_cache: &mut NoteCache, note: &Note, profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>, ) { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let note_key = note.key().unwrap(); ui.horizontal(|ui| { @@ -327,9 +325,8 @@ impl<'a, 'd> NoteView<'a, 'd> { }); } + #[profiling::function] fn show_standard(&mut self, ui: &mut egui::Ui) -> NoteResponse { - #[cfg(feature = "profiling")] - puffin::profile_function!(); let note_key = self.note.key().expect("todo: support non-db notes"); let txn = self.note.txn().expect("todo: support non-db notes"); @@ -563,14 +560,12 @@ fn note_hitbox_clicked( } } +#[profiling::function] fn render_note_actionbar( ui: &mut egui::Ui, note_id: &[u8; 32], note_key: NoteKey, ) -> egui::InnerResponse<Option<NoteAction>> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - ui.horizontal(|ui| { let reply_resp = reply_button(ui, note_key); let quote_resp = quote_repost_button(ui, note_key); @@ -590,14 +585,12 @@ fn secondary_label(ui: &mut egui::Ui, s: impl Into<String>) { ui.add(Label::new(RichText::new(s).size(10.0).color(color))); } +#[profiling::function] fn render_reltime( ui: &mut egui::Ui, note_cache: &mut CachedNote, before: bool, ) -> egui::InnerResponse<()> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - ui.horizontal(|ui| { if before { secondary_label(ui, "⋅"); diff --git a/crates/notedeck_columns/src/ui/note/reply_description.rs b/crates/notedeck_columns/src/ui/note/reply_description.rs @@ -8,6 +8,7 @@ use nostrdb::{Note, NoteReply, Transaction}; use super::{contents::NoteContext, NoteOptions}; #[must_use = "Please handle the resulting note action"] +#[profiling::function] pub fn reply_desc( ui: &mut egui::Ui, txn: &Transaction, @@ -15,9 +16,6 @@ pub fn reply_desc( note_context: &mut NoteContext, note_options: NoteOptions, ) -> Option<NoteAction> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let mut note_action: Option<NoteAction> = None; let size = 10.0; let selectable = false; diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -80,6 +80,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { } } +#[profiling::function] fn render_pfp( ui: &mut egui::Ui, img_cache: &mut Images, @@ -87,9 +88,6 @@ fn render_pfp( ui_size: f32, border: Option<Stroke>, ) -> egui::Response { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - // We will want to downsample these so it's not blurry on hi res displays let img_size = 128u32; @@ -116,15 +114,13 @@ fn render_pfp( ) } +#[profiling::function] fn pfp_image( ui: &mut egui::Ui, img: &TextureHandle, size: f32, border: Option<Stroke>, ) -> egui::Response { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let (rect, response) = ui.allocate_at_least(vec2(size, size), Sense::hover()); if let Some(stroke) = border { draw_bg_border(ui, rect.center(), size, stroke); diff --git a/crates/notedeck_columns/src/unknowns.rs b/crates/notedeck_columns/src/unknowns.rs @@ -25,6 +25,7 @@ pub fn update_from_columns( } } +#[profiling::function] pub fn get_unknown_ids( txn: &Transaction, unknown_ids: &mut UnknownIds, @@ -32,9 +33,6 @@ pub fn get_unknown_ids( ndb: &Ndb, note_cache: &mut NoteCache, ) -> Result<()> { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - let mut new_cached_notes: Vec<(NoteKey, CachedNote)> = vec![]; for (_kind, timeline) in timeline_cache.timelines.iter() { diff --git a/preview b/preview @@ -1,5 +1,5 @@ #!/usr/bin/env bash # pass --mobile for mobile previews -#RUST_LOG=info cargo run --bin ui_preview --features profiling --release -- "$@" +#RUST_LOG=info cargo run --bin ui_preview --features puffin --release -- "$@" cargo run --bin ui_preview --release -- "$@"