notedeck

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

commit 788c4a8e12c437c3c2e0f74f16530f8a45c41999
parent 5d4e795de84dc9f0067a8e28a3c790e19f2b220b
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue, 24 Feb 2026 16:20:46 -0500

feat(perf): add profiling macros

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/account/contacts.rs | 2++
Mcrates/notedeck/src/account/mute.rs | 1+
Mcrates/notedeck/src/app.rs | 16++++++++++------
Mcrates/notedeck/src/jobs/cache.rs | 49+++++++++++++++++++++++++++++--------------------
Mcrates/notedeck/src/jobs/media.rs | 2++
Mcrates/notedeck/src/nip51_set.rs | 2++
Mcrates/notedeck/src/persist/settings_handler.rs | 1+
Mcrates/notedeck/src/unknowns.rs | 1+
Mcrates/notedeck/src/zaps/cache.rs | 1+
Mcrates/notedeck_columns/src/app.rs | 1+
Mcrates/notedeck_columns/src/nav.rs | 2++
Mcrates/notedeck_columns/src/timeline/cache.rs | 3+++
Mcrates/notedeck_columns/src/timeline/mod.rs | 9++++++++-
Mcrates/notedeck_columns/src/timeline/thread.rs | 3+++
Mcrates/notedeck_columns/src/timeline/timeline_units.rs | 1+
15 files changed, 67 insertions(+), 27 deletions(-)

diff --git a/crates/notedeck/src/account/contacts.rs b/crates/notedeck/src/account/contacts.rs @@ -69,6 +69,7 @@ impl Contacts { } } + #[profiling::function] pub(super) fn poll_for_updates(&mut self, ndb: &Ndb, txn: &Transaction, sub: Subscription) { let nks = ndb.poll_for_notes(sub, 1); @@ -104,6 +105,7 @@ impl Contacts { } } +#[profiling::function] fn update_state(state: &mut ContactState, note: &Note, key: NoteKey) { match state { ContactState::Unreceived => { diff --git a/crates/notedeck/src/account/mute.rs b/crates/notedeck/src/account/mute.rs @@ -94,6 +94,7 @@ impl AccountMutedData { muted } + #[profiling::function] pub(super) fn poll_for_updates(&mut self, ndb: &Ndb, txn: &Transaction, sub: Subscription) { let nks = ndb.poll_for_notes(sub, 1); diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs @@ -121,17 +121,21 @@ fn render_notedeck(notedeck: &mut Notedeck, ctx: &egui::Context) { } impl eframe::App for Notedeck { + #[profiling::function] fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { profiling::finish_frame!(); self.frame_history .on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage); - self.media_jobs.run_received(&mut self.job_pool, |id| { - crate::run_media_job_pre_action(id, &mut self.img_cache.textures); - }); - self.media_jobs.deliver_all_completed(|completed| { - crate::deliver_completed_media_job(completed, &mut self.img_cache.textures) - }); + { + profiling::scope!("media jobs"); + self.media_jobs.run_received(&mut self.job_pool, |id| { + crate::run_media_job_pre_action(id, &mut self.img_cache.textures); + }); + self.media_jobs.deliver_all_completed(|completed| { + crate::deliver_completed_media_job(completed, &mut self.img_cache.textures) + }); + } self.nip05_cache.poll(); diff --git a/crates/notedeck/src/jobs/cache.rs b/crates/notedeck/src/jobs/cache.rs @@ -39,6 +39,7 @@ where } } + #[profiling::function] pub fn run_received(&mut self, pool: &mut JobPool, mut pre_action: impl FnMut(&JobId<K>)) { for pkg in self.receive_new_jobs.try_iter() { let id = &pkg.id; @@ -68,6 +69,7 @@ where } } + #[profiling::function] pub fn deliver_all_completed(&mut self, mut deliver_complete: impl FnMut(JobComplete<K, T>)) { while let Some(res) = self.completed.pop() { tracing::trace!("Got completed: {:?}", res.job_id); @@ -82,6 +84,7 @@ where } } +#[profiling::function] fn run_received_job<K, T>( job_run: JobRun<T>, pool: &mut JobPool, @@ -102,6 +105,7 @@ fn run_received_job<K, T>( } } +#[profiling::function] fn run_sync<F, K, T>( job_pool: &mut JobPool, send_new_jobs: Sender<JobPackage<K, T>>, @@ -114,38 +118,42 @@ fn run_sync<F, K, T>( T: Send + 'static, { let id_c = id.clone(); - let wrapped: Box<dyn FnOnce() + Send + 'static> = Box::new(move || { - let res = run_job(); - match res { - JobOutput::Complete(complete_response) => { - completion_queue.push(JobComplete { - job_id: id.job_id.clone(), - response: complete_response.response, - }); - if let Some(run) = complete_response.run_no_output { + let wrapped: Box<dyn FnOnce() + Send + 'static> = { + profiling::scope!("box gen"); + Box::new(move || { + let res = run_job(); + match res { + JobOutput::Complete(complete_response) => { + completion_queue.push(JobComplete { + job_id: id.job_id.clone(), + response: complete_response.response, + }); + if let Some(run) = complete_response.run_no_output { + if let Err(e) = send_new_jobs.send(JobPackage { + id: id.into_internal(), + run: RunType::NoOutput(run), + }) { + tracing::error!("{e}"); + } + } + } + JobOutput::Next(job_run) => { if let Err(e) = send_new_jobs.send(JobPackage { id: id.into_internal(), - run: RunType::NoOutput(run), + run: RunType::Output(job_run), }) { tracing::error!("{e}"); } } } - JobOutput::Next(job_run) => { - if let Err(e) = send_new_jobs.send(JobPackage { - id: id.into_internal(), - run: RunType::Output(job_run), - }) { - tracing::error!("{e}"); - } - } - } - }); + }) + }; tracing::trace!("Spawning sync job: {id_c:?}"); job_pool.schedule_no_output(wrapped); } +#[profiling::function] fn run_async<K, T>( send_new_jobs: Sender<JobPackage<K, T>>, completion_queue: CompletionQueue<K, T>, @@ -187,6 +195,7 @@ fn run_async<K, T>( }); } +#[profiling::function] fn no_output_run(pool: &mut JobPool, run: NoOutputRun) { match run { NoOutputRun::Sync(c) => { diff --git a/crates/notedeck/src/jobs/media.rs b/crates/notedeck/src/jobs/media.rs @@ -23,6 +23,7 @@ pub enum MediaJobResult { Animation(Result<Animation, Error>), } +#[profiling::function] pub fn deliver_completed_media_job( completed: JobComplete<MediaJobKind, MediaJobResult>, tex_cache: &mut TexturesCache, @@ -56,6 +57,7 @@ pub fn deliver_completed_media_job( tracing::trace!("Delivered job for {id_c}"); } +#[profiling::function] pub fn run_media_job_pre_action(job_id: &JobId<MediaJobKind>, tex_cache: &mut TexturesCache) { let id = job_id.id.clone(); match job_id.job_kind { diff --git a/crates/notedeck/src/nip51_set.rs b/crates/notedeck/src/nip51_set.rs @@ -53,6 +53,7 @@ impl Nip51SetCache { }) } + #[profiling::function] pub fn poll_for_notes(&mut self, ndb: &Ndb, unknown_ids: &mut UnknownIds) { let new_notes = ndb.poll_for_notes(self.sub.local, 5); @@ -86,6 +87,7 @@ impl Nip51SetCache { } } +#[profiling::function] fn add( notes: Vec<Note>, cache: &mut IndexMap<PackId, Nip51Set>, diff --git a/crates/notedeck/src/persist/settings_handler.rs b/crates/notedeck/src/persist/settings_handler.rs @@ -231,6 +231,7 @@ impl SettingsHandler { self.try_save_settings(); } + #[profiling::function] pub fn update_batch<F>(&mut self, update_fn: F) where F: FnOnce(&mut Settings), diff --git a/crates/notedeck/src/unknowns.rs b/crates/notedeck/src/unknowns.rs @@ -164,6 +164,7 @@ impl UnknownIds { } } + #[profiling::function] pub fn update_from_note( txn: &Transaction, ndb: &Ndb, diff --git a/crates/notedeck/src/zaps/cache.rs b/crates/notedeck/src/zaps/cache.rs @@ -276,6 +276,7 @@ impl Zaps { states.push(*id); } + #[profiling::function] pub fn process( &mut self, accounts: &mut Accounts, diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -77,6 +77,7 @@ pub struct Damus { hovered_column: Option<usize>, } +#[profiling::function] fn handle_egui_events( input: &egui::InputState, columns: &mut Columns, diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -259,6 +259,7 @@ fn process_popup_resp( process_result } +#[profiling::function] fn process_nav_resp( app: &mut Damus, ctx: &mut AppContext<'_>, @@ -512,6 +513,7 @@ impl RouterAction { } } +#[profiling::function] fn process_render_nav_action( app: &mut Damus, ctx: &mut AppContext<'_>, diff --git a/crates/notedeck_columns/src/timeline/cache.rs b/crates/notedeck_columns/src/timeline/cache.rs @@ -115,6 +115,7 @@ impl TimelineCache { } /// Get and/or update the notes associated with this timeline + #[profiling::function] fn notes<'a>( &'a mut self, ndb: &Ndb, @@ -137,6 +138,7 @@ impl TimelineCache { let mut notes = Vec::new(); for package in filters.local().packages { + profiling::scope!("ndb query"); if let Ok(results) = ndb.query(txn, package.filters, 1000) { let cur_notes: Vec<NoteRef> = results .into_iter() @@ -174,6 +176,7 @@ impl TimelineCache { /// When `load_local` is false, the timeline is created and subscribed /// without running a blocking local query. Use this for startup paths /// where initial notes are loaded asynchronously. + #[profiling::function] pub fn open( &mut self, ndb: &Ndb, diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -164,6 +164,7 @@ impl TimelineTab { self.list.borrow_mut().reset(); } + #[profiling::function] fn insert<'a>( &mut self, payloads: Vec<&'a NotePayload>, @@ -409,6 +410,7 @@ impl Timeline { let now = unix_time_secs(); let mut unknown_pks = HashSet::new(); for note_ref in notes { + profiling::scope!("inserting notes"); if is_future_timestamp(note_ref.created_at, now) { continue; } @@ -450,6 +452,7 @@ impl Timeline { /// The main function used for inserting notes into timelines. Handles /// inserting into multiple views if we have them. All timeline note /// insertions should use this function. + #[profiling::function] pub fn insert( &mut self, new_note_ids: &[NoteKey], @@ -528,7 +531,10 @@ impl Timeline { .get_local() .ok_or(Error::App(notedeck::Error::no_active_sub()))?; - let new_note_ids = ndb.poll_for_notes(sub, 500); + let new_note_ids = { + profiling::scope!("big ndb poll"); + ndb.poll_for_notes(sub, 500) + }; if new_note_ids.is_empty() { return Ok(()); } else { @@ -889,6 +895,7 @@ fn setup_timeline_nostrdb_sub( /// Our timelines may require additional data before it is functional. For /// example, when we have to fetch a contact list before we do the actual /// following list query. +#[profiling::function] pub fn is_timeline_ready( ndb: &Ndb, pool: &mut RelayPool, diff --git a/crates/notedeck_columns/src/timeline/thread.rs b/crates/notedeck_columns/src/timeline/thread.rs @@ -61,6 +61,7 @@ impl Threads { /// Opening a thread. /// Similar to [[super::cache::TimelineCache::open]] #[allow(clippy::too_many_arguments)] + #[profiling::function] pub fn open( &mut self, ndb: &mut Ndb, @@ -136,6 +137,8 @@ impl Threads { } /// Responsible for making sure the chain and the direct replies are up to date + #[allow(clippy::too_many_arguments)] + #[profiling::function] pub fn update( &mut self, selected: &Note<'_>, diff --git a/crates/notedeck_columns/src/timeline/timeline_units.rs b/crates/notedeck_columns/src/timeline/timeline_units.rs @@ -39,6 +39,7 @@ impl TimelineUnits { } /// returns number of new entries merged + #[profiling::function] pub fn merge_new_notes<'a>( &mut self, payloads: Vec<&'a NotePayload>,