diff --git a/Cargo.lock b/Cargo.lock index 1372095e..40ad53f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.13" @@ -536,6 +542,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "castaway" version = "0.2.2" @@ -581,6 +593,33 @@ dependencies = [ "chrono", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.8" @@ -709,6 +748,44 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "futures", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "tokio", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -769,6 +846,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1331,6 +1414,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -1747,6 +1840,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is-wsl" version = "0.4.0" @@ -1763,6 +1867,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -2232,6 +2345,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "open" version = "5.2.0" @@ -2587,6 +2706,34 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3081,6 +3228,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scc" version = "2.1.1" @@ -3685,6 +3841,7 @@ dependencies = [ "chrono", "chrono-humanize", "clap", + "criterion", "crossterm", "directories", "edit", @@ -3876,6 +4033,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.1" @@ -4440,6 +4607,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4597,6 +4774,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index f407ac62..c3080245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ axum-server = { version = "0.6.0", features = ["tls-rustls"] } bitflags = { version = "2.6.0", default-features = false } chrono = { version = "0.4.38", default-features = false } clap = { version = "4.5", default-features = false } +criterion = { version = "0.5.1", features = ["async_tokio"] } either = { version = "1.13.0" } fake = { version = "2.9.2", features = ["derive", "chrono"] } fdlimit = { version = "0.3.0", default-features = false } diff --git a/crates/synd_term/Cargo.toml b/crates/synd_term/Cargo.toml index d2a31071..8c0e974b 100644 --- a/crates/synd_term/Cargo.toml +++ b/crates/synd_term/Cargo.toml @@ -47,6 +47,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = "1.0.120" thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio-stream = { version = "0.1.15", optional = true } toml = { version = "0.8.14", default-features = true } tracing = { workspace = true } tracing-appender = "0.2.3" @@ -60,22 +61,26 @@ url = { workspace = true } [features] # Integration test -integration = [] +integration = ["dep:tokio-stream"] [dev-dependencies] synd-api = { path = "../synd_api" } synd-test = { path = "../synd_test" } -assert_cmd = { workspace = true } -axum-server = { workspace = true } -fake = { workspace = true } -insta = { workspace = true } -kvsd = { workspace = true } -proptest = { workspace = true } -serial_test = { version = "3.1.1", default-features = false, features = ["async", "file_locks"] } -tempfile = { workspace = true } -tokio-stream = "0.1.15" -tokio-util = { workspace = true } +assert_cmd = { workspace = true } +axum-server = { workspace = true } +criterion = { workspace = true } +fake = { workspace = true } +insta = { workspace = true } +kvsd = { workspace = true } +proptest = { workspace = true } +serial_test = { version = "3.1.1", default-features = false, features = ["async", "file_locks"] } +tempfile = { workspace = true } +tokio-util = { workspace = true } + +[[bench]] +harness = false +name = "render" [lints] workspace = true diff --git a/crates/synd_term/benches/bench/helper.rs b/crates/synd_term/benches/bench/helper.rs new file mode 100644 index 00000000..a202e8fd --- /dev/null +++ b/crates/synd_term/benches/bench/helper.rs @@ -0,0 +1,40 @@ +use std::time::Duration; + +use ratatui::backend::TestBackend; +use synd_term::{ + application::{Application, Cache, Config}, + client::Client, + config::Categories, + terminal::Terminal, + ui::theme::Theme, +}; +use url::Url; + +pub fn init_app() -> Application { + let terminal = { + let backend = TestBackend::new(120, 40); + let terminal = ratatui::Terminal::new(backend).unwrap(); + Terminal::with(terminal) + }; + + let client = { + Client::new( + Url::parse("http://dummy.ymgyt.io").unwrap(), + Duration::from_secs(10), + ) + .unwrap() + }; + + let config = { Config::default().with_idle_timer_interval(Duration::from_micros(1)) }; + + let cache = { Cache::new(tempfile::TempDir::new().unwrap().into_path()) }; + + Application::builder() + .terminal(terminal) + .client(client) + .categories(Categories::default_toml()) + .config(config) + .cache(cache) + .theme(Theme::default()) + .build() +} diff --git a/crates/synd_term/benches/render.rs b/crates/synd_term/benches/render.rs new file mode 100644 index 00000000..91b7ed1d --- /dev/null +++ b/crates/synd_term/benches/render.rs @@ -0,0 +1,48 @@ +mod bench { + use criterion::Criterion; + use synd_term::{integration, key}; + + mod helper; + + pub(super) fn render(c: &mut Criterion) { + c.bench_function("render", move |b| { + b.to_async(runtime()).iter_batched( + || { + let app = helper::init_app(); + let (tx, event_stream) = integration::event_stream(); + for _ in 0..100 { + tx.send(key!('j')); + } + (app, event_stream) + }, + |(mut app, mut event_stream)| async move { + app.wait_until_jobs_completed(&mut event_stream).await; + }, + criterion::BatchSize::SmallInput, + ); + }); + } + + fn runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed building the Runtime") + } +} + +// Explicitly using the expanded code of the following lines. +// criterion::criterion_group!(benches, bench::render); +// criterion::criterion_main!(benches); +pub fn benches() { + let mut criterion: criterion::Criterion<_> = + criterion::Criterion::default().configure_from_args(); + bench::render(&mut criterion); +} + +fn main() { + benches(); + criterion::Criterion::default() + .configure_from_args() + .final_summary(); +} diff --git a/crates/synd_term/src/integration.rs b/crates/synd_term/src/integration.rs new file mode 100644 index 00000000..554fcc79 --- /dev/null +++ b/crates/synd_term/src/integration.rs @@ -0,0 +1,33 @@ +use std::io; + +use tokio::sync::mpsc::UnboundedSender; +use tokio_stream::wrappers::UnboundedReceiverStream; + +pub struct UnboundedSenderWrapper { + inner: UnboundedSender>, +} + +impl UnboundedSenderWrapper { + pub fn send(&self, event: crossterm::event::Event) { + self.inner.send(Ok(event)).unwrap(); + } + + pub fn send_multi(&self, events: T) + where + T: IntoIterator, + { + events.into_iter().for_each(|event| { + self.send(event); + }); + } +} + +pub fn event_stream() -> ( + UnboundedSenderWrapper, + UnboundedReceiverStream>, +) { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let tx = UnboundedSenderWrapper { inner: tx }; + let event_stream = UnboundedReceiverStream::new(rx); + (tx, event_stream) +} diff --git a/crates/synd_term/src/lib.rs b/crates/synd_term/src/lib.rs index d827da39..a172e719 100644 --- a/crates/synd_term/src/lib.rs +++ b/crates/synd_term/src/lib.rs @@ -14,3 +14,6 @@ pub mod matcher; pub mod terminal; pub mod types; pub mod ui; + +#[cfg(feature = "integration")] +pub mod integration; diff --git a/crates/synd_term/tests/test/helper.rs b/crates/synd_term/tests/test/helper.rs index 229b8ba2..1971a91d 100644 --- a/crates/synd_term/tests/test/helper.rs +++ b/crates/synd_term/tests/test/helper.rs @@ -1,4 +1,4 @@ -use std::{io, path::PathBuf, sync::Once, time::Duration}; +use std::{path::PathBuf, sync::Once, time::Duration}; use chrono::{DateTime, Utc}; use futures_util::future; @@ -15,6 +15,7 @@ use synd_auth::{ device_flow::{provider, DeviceFlow}, jwt, }; +pub use synd_term::integration::event_stream; use synd_term::{ application::{ Application, Authenticator, Cache, Clock, Config, DeviceFlows, JwtService, SystemClock, @@ -28,8 +29,7 @@ use synd_term::{ ui::theme::Theme, }; use synd_test::temp_dir; -use tokio::{net::TcpListener, sync::mpsc::UnboundedSender}; -use tokio_stream::wrappers::UnboundedReceiverStream; +use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; use tracing_subscriber::EnvFilter; use url::Url; @@ -332,35 +332,6 @@ pub async fn serve_api( Ok(()) } -pub fn event_stream() -> ( - UnboundedSenderWrapper, - UnboundedReceiverStream>, -) { - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - let tx = UnboundedSenderWrapper { inner: tx }; - let event_stream = UnboundedReceiverStream::new(rx); - (tx, event_stream) -} - -pub struct UnboundedSenderWrapper { - inner: UnboundedSender>, -} - -impl UnboundedSenderWrapper { - pub fn send(&self, event: crossterm::event::Event) { - self.inner.send(Ok(event)).unwrap(); - } - - pub fn send_multi(&self, events: T) - where - T: IntoIterator, - { - events.into_iter().for_each(|event| { - self.send(event); - }); - } -} - pub fn resize_event(columns: u16, rows: u16) -> crossterm::event::Event { crossterm::event::Event::Resize(columns, rows) } diff --git a/flake.nix b/flake.nix index ea083792..b5c396d4 100644 --- a/flake.nix +++ b/flake.nix @@ -70,6 +70,7 @@ cargo-machete cargo-insta oranda + gnuplot # for rendering with criterion ] ++ ci_packages ## For cargo-release build ++ pkgs.lib.optionals pkgs.stdenv.isDarwin synd.darwinDeps; diff --git a/justfile b/justfile index 7382cb28..baaea0ba 100644 --- a/justfile +++ b/justfile @@ -31,7 +31,7 @@ check *flags: # Run cargo check c: - cargo check --all-features --tests + cargo check --all-features --tests --benches # Run spell checker typo: @@ -91,6 +91,11 @@ coverage: cargo llvm-cov nextest --all-features --open \ --ignore-filename-regex '(integration_backend.rs|client/generated/.*.rs)' +# Run benchmark +bench: + cargo bench --package synd-term --bench render --features integration -- --verbose + @start ./target/criterion/report/index.html + # Update synd_api graphql schema update-gql-schema: @graphql-client introspect-schema https://localhost:5959/graphql --no-ssl \