Skip to main content

mobench_sdk/
lib.rs

1//! # mobench-sdk
2//!
3//! Mobile benchmarking SDK for Rust. It provides runtime timing, benchmark
4//! registration, Android/iOS builders, generated runner templates, UniFFI
5//! compatibility, native JSON C ABI exports, and local profiling helpers.
6//!
7//! ## Install
8//!
9//! ```toml
10//! [dependencies]
11//! mobench-sdk = "0.1.49"
12//! inventory = "0.3"
13//!
14//! [lib]
15//! crate-type = ["cdylib", "staticlib", "lib"]
16//! ```
17//!
18//! Generated runners use `ffi_backend = "uniffi"` by default. Set
19//! `ffi_backend = "native-c-abi"` in `mobench.toml` to use the direct
20//! mobench JSON C ABI path and export it from the benchmark crate with
21//! `mobench_sdk::export_native_c_abi!()`.
22//!
23//! For complete integration instructions, see
24//! <https://github.com/worldcoin/mobile-bench-rs/blob/main/docs/guides/sdk-integration.md>.
25//! ```toml
26//! [dependencies]
27//! mobench-sdk = "0.1.49"
28//! inventory = "0.3"  # Required for benchmark registration
29//! ```
30//!
31//! ### 2. Define Benchmarks
32//!
33//! Use the [`#[benchmark]`](macro@benchmark) attribute to mark functions for benchmarking:
34//!
35//! ```ignore
36//! use mobench_sdk::benchmark;
37//!
38//! #[benchmark]
39//! fn my_expensive_operation() {
40//!     let result = expensive_computation();
41//!     std::hint::black_box(result);  // Prevent optimization
42//! }
43//!
44//! #[benchmark]
45//! fn another_benchmark() {
46//!     for i in 0..1000 {
47//!         std::hint::black_box(i * i);
48//!     }
49//! }
50//! ```
51//!
52//! ### 3. Build and Run
53//!
54//! Use the `mobench` CLI to build and run benchmarks:
55//!
56//! ```bash
57//! # Install the CLI
58//! cargo install mobench
59//!
60//! # Build for Android (outputs to target/mobench/)
61//! cargo mobench build --target android
62//!
63//! # Build for iOS
64//! cargo mobench build --target ios
65//!
66//! # Run on BrowserStack (use --release for smaller APK uploads)
67//! cargo mobench run --target android --function my_expensive_operation \
68//!     --iterations 100 --warmup 10 --devices "Google Pixel 7-13.0" --release
69//!
70//! # Or capture a local native profile
71//! cargo mobench profile run --target android --provider local \
72//!     --backend android-native --function my_expensive_operation
73//! ```
74//!
75//! ## Architecture
76//!
77//! The SDK consists of several components:
78//!
79//! | Module | Description |
80//! |--------|-------------|
81//! | [`timing`] | Core timing infrastructure (always available) |
82//! | [`registry`] | Runtime discovery of `#[benchmark]` functions (requires `registry` or `full` feature) |
83//! | [`runner`] | Benchmark execution engine (requires `registry` or `full` feature) |
84//! | [`builders`] | Android and iOS build automation (requires `builders` or `full` feature) |
85//! | [`codegen`] | Mobile app template generation (requires `codegen`, `builders`, or `full` feature) |
86//! | [`types`] | Common types and error definitions |
87//!
88//! ## Crate Ecosystem
89//!
90//! The mobench ecosystem consists of three published crates:
91//!
92//! - **`mobench-sdk`** (this crate) - Core SDK library with timing harness and build automation
93//! - **[`mobench`](https://crates.io/crates/mobench)** - CLI tool for building and running benchmarks
94//! - **[`mobench-macros`](https://crates.io/crates/mobench-macros)** - `#[benchmark]` proc macro
95//!
96//! Note: The `mobench-runner` crate has been consolidated into this crate as the [`timing`] module.
97//!
98//! ## Feature Flags
99//!
100//! | Feature | Default | Description |
101//! |---------|---------|-------------|
102//! | `full` | Yes | Full SDK with build automation, templates, and registry |
103//! | `registry` | No | Benchmark macro, inventory registry, and runtime execution without build tooling |
104//! | `builders` | No | Android/iOS build automation; enables `codegen` |
105//! | `codegen` | No | Project and mobile app template generation |
106//! | `runner-only` | No | Minimal timing-only mode for mobile binaries |
107//!
108//! For mobile binaries where binary size matters, use `runner-only`:
109//!
110//! ```toml
111//! [dependencies]
112//! mobench-sdk = { version = "0.1.49", default-features = false, features = ["runner-only"] }
113//! ```
114//!
115//! ## Programmatic Usage
116//!
117//! You can also use the SDK programmatically:
118//!
119//! ### Using the Benchmark Builder Pattern
120//!
121//! Requires the `registry` or `full` feature.
122//!
123//! ```ignore
124//! use mobench_sdk::BenchmarkBuilder;
125//!
126//! fn main() -> Result<(), Box<dyn std::error::Error>> {
127//!     let report = BenchmarkBuilder::new("my_benchmark")
128//!         .iterations(100)
129//!         .warmup(10)
130//!         .run()?;
131//!
132//!     println!("Mean: {} ns", report.mean_ns());
133//!     Ok(())
134//! }
135//! ```
136//!
137//! ### Using BenchSpec With Registry Dispatch
138//!
139//! Requires the `registry` or `full` feature. With `runner-only`, use
140//! [`run_closure`] or [`timing::run_closure`] for manual dispatch instead.
141//!
142//! ```ignore
143//! use mobench_sdk::{BenchSpec, run_benchmark};
144//!
145//! fn main() -> Result<(), Box<dyn std::error::Error>> {
146//!     let spec = BenchSpec::new("my_benchmark", 50, 5)?;
147//!
148//!     let report = run_benchmark(spec)?;
149//!     println!("Collected {} samples", report.samples.len());
150//!     Ok(())
151//! }
152//! ```
153//!
154//! ### Discovering Benchmarks
155//!
156//! Requires the `registry` or `full` feature.
157//!
158//! ```ignore
159//! use mobench_sdk::{discover_benchmarks, list_benchmark_names};
160//!
161//! fn main() {
162//!     // Get all registered benchmark names
163//!     let names = list_benchmark_names();
164//!     for name in names {
165//!         println!("Found benchmark: {}", name);
166//!     }
167//!
168//!     // Get full benchmark function info
169//!     let benchmarks = discover_benchmarks();
170//!     for bench in benchmarks {
171//!         println!("Benchmark: {}", bench.name);
172//!     }
173//! }
174//! ```
175//!
176//! ## Building Mobile Apps
177//!
178//! The SDK includes builders for automating mobile app creation:
179//!
180//! ### Android Builder
181//!
182//! ```ignore
183//! use mobench_sdk::builders::AndroidBuilder;
184//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
185//!
186//! let builder = AndroidBuilder::new(".", "my-bench-crate")
187//!     .verbose(true)
188//!     .output_dir("target/mobench");  // Default
189//!
190//! let config = BuildConfig {
191//!     target: Target::Android,
192//!     profile: BuildProfile::Release,
193//!     incremental: true,
194//! };
195//!
196//! let result = builder.build(&config)?;
197//! println!("APK built at: {:?}", result.app_path);
198//! ```
199//!
200//! ### iOS Builder
201//!
202//! ```ignore
203//! use mobench_sdk::builders::{IosBuilder, SigningMethod};
204//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
205//!
206//! let builder = IosBuilder::new(".", "my-bench-crate")
207//!     .verbose(true);
208//!
209//! let config = BuildConfig {
210//!     target: Target::Ios,
211//!     profile: BuildProfile::Release,
212//!     incremental: true,
213//! };
214//!
215//! let result = builder.build(&config)?;
216//! println!("xcframework built at: {:?}", result.app_path);
217//!
218//! // Package IPA for distribution
219//! let ipa_path = builder.package_ipa("BenchRunner", SigningMethod::AdHoc)?;
220//! ```
221//!
222//! ## Output Directory
223//!
224//! By default, all mobile artifacts are written to `target/mobench/`:
225//!
226//! ```text
227//! target/mobench/
228//! ├── android/
229//! │   ├── app/
230//! │   │   ├── src/main/jniLibs/     # Native .so libraries
231//! │   │   └── build/outputs/apk/    # Built APK
232//! │   └── ...
233//! └── ios/
234//!     ├── sample_fns.xcframework/   # Built xcframework
235//!     ├── BenchRunner/              # Xcode project
236//!     └── BenchRunner.ipa           # Packaged IPA
237//! ```
238//!
239//! This keeps generated files inside `target/`, following Rust conventions
240//! and preventing accidental commits of mobile project files.
241//!
242//! ## Platform Requirements
243//!
244//! ### Android
245//!
246//! - Android NDK (set `ANDROID_NDK_HOME` environment variable)
247//! - `cargo-ndk` (`cargo install cargo-ndk`)
248//! - Rust targets: `rustup target add aarch64-linux-android`
249//! - Optional extra ABI targets only when configured explicitly
250//!
251//! ### iOS
252//!
253//! - Xcode with command line tools
254//! - `uniffi-bindgen` (`cargo install --git https://github.com/mozilla/uniffi-rs --tag <uniffi-tag> uniffi --features cli --bin uniffi-bindgen`)
255//! - `xcodegen` (optional, `brew install xcodegen`)
256//! - Rust targets: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios`
257//!
258//! ## Best Practices
259//!
260//! ### Use `black_box` to Prevent Optimization
261//!
262//! Always wrap benchmark results with [`std::hint::black_box`] to prevent the
263//! compiler from optimizing away the computation:
264//!
265//! ```ignore
266//! #[benchmark]
267//! fn correct_benchmark() {
268//!     let result = expensive_computation();
269//!     std::hint::black_box(result);  // Result is "used"
270//! }
271//! ```
272//!
273//! ### Avoid Side Effects
274//!
275//! Benchmarks should be deterministic and avoid I/O operations:
276//!
277//! ```ignore
278//! // Good: Pure computation
279//! #[benchmark]
280//! fn good_benchmark() {
281//!     let data = vec![1, 2, 3, 4, 5];
282//!     let sum: i32 = data.iter().sum();
283//!     std::hint::black_box(sum);
284//! }
285//!
286//! // Avoid: File I/O adds noise
287//! #[benchmark]
288//! fn noisy_benchmark() {
289//!     let data = std::fs::read_to_string("data.txt").unwrap();  // Don't do this
290//!     std::hint::black_box(data);
291//! }
292//! ```
293//!
294//! ### Choose Appropriate Iteration Counts
295//!
296//! - **Warmup**: 5-10 iterations to warm CPU caches and JIT
297//! - **Iterations**: 50-100 for stable statistics
298//! - Mobile devices may have more variance than desktop
299//!
300//! ## License
301//!
302//! MIT License - see repository for details.
303
304#![cfg_attr(docsrs, feature(doc_cfg))]
305
306// Core timing module - always available
307pub mod metrics;
308pub mod timing;
309pub mod types;
310
311// UniFFI integration helpers
312// This module provides template types and conversion traits for UniFFI integration
313pub mod uniffi_types;
314
315// Unified FFI module for UniFFI integration
316pub mod ffi;
317
318// Build automation modules - only with builder/codegen features
319#[cfg(feature = "builders")]
320#[cfg_attr(docsrs, doc(cfg(feature = "builders")))]
321pub mod builders;
322#[cfg(feature = "codegen")]
323#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
324pub mod codegen;
325
326// Registry runtime modules - available without build tooling
327#[cfg(feature = "registry")]
328#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
329pub mod native_c_abi;
330#[cfg(feature = "registry")]
331#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
332pub mod registry;
333#[cfg(feature = "registry")]
334#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
335pub mod runner;
336#[cfg(feature = "registry")]
337#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
338pub mod web;
339
340// Re-export the benchmark macro from bench-macros (only with registry feature)
341#[cfg(feature = "registry")]
342#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
343pub use mobench_macros::benchmark;
344
345// Re-export inventory so users don't need to add it as a separate dependency
346#[cfg(feature = "registry")]
347#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
348pub use inventory;
349
350// Re-export key registry types for convenience
351pub use metrics::{record_run_u64, record_sample_u64};
352#[cfg(feature = "registry")]
353#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
354pub use native_c_abi::MobenchBuf;
355#[cfg(feature = "registry")]
356#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
357pub use registry::{BenchFunction, discover_benchmarks, find_benchmark, list_benchmark_names};
358#[cfg(feature = "registry")]
359#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
360pub use runner::{BenchmarkBuilder, run_benchmark};
361#[cfg(feature = "registry")]
362#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
363pub use web::{BrowserRunnerError, run_benchmark_json};
364
365// Re-export types that are always available
366pub use types::{BenchError, BenchSample, BenchSpec, HarnessTimelineSpan, RunnerReport};
367
368// Re-export build/config types. These are plain data types and do not pull in
369// build automation dependencies by themselves.
370pub use types::{
371    BuildConfig, BuildProfile, BuildResult, FfiBackend, InitConfig, NativeLibraryArtifact, Target,
372};
373
374// Re-export timing types at the crate root for convenience
375pub use timing::{BenchSummary, SemanticPhase, TimingError, profile_phase, run_closure};
376
377/// Re-export of [`std::hint::black_box`] for preventing compiler optimizations.
378///
379/// Use this to ensure the compiler doesn't optimize away benchmark computations.
380pub use std::hint::black_box;
381
382/// Library version, matching `Cargo.toml`.
383///
384/// This can be used to verify SDK compatibility:
385///
386/// ```
387/// assert!(!mobench_sdk::VERSION.is_empty());
388/// ```
389pub const VERSION: &str = env!("CARGO_PKG_VERSION");
390
391/// Generates a debug function that prints all discovered benchmarks.
392///
393/// This macro is useful for debugging benchmark registration issues.
394/// It creates a function `_debug_print_benchmarks()` that you can call
395/// to see which benchmarks have been registered via `#[benchmark]`.
396///
397/// # Example
398///
399/// ```ignore
400/// use mobench_sdk::{benchmark, debug_benchmarks};
401///
402/// #[benchmark]
403/// fn my_benchmark() {
404///     std::hint::black_box(42);
405/// }
406///
407/// // Generate the debug function
408/// debug_benchmarks!();
409///
410/// fn main() {
411///     // Print all registered benchmarks
412///     _debug_print_benchmarks();
413///     // Output:
414///     // Discovered benchmarks:
415///     //   - my_crate::my_benchmark
416/// }
417/// ```
418///
419/// # Troubleshooting
420///
421/// If no benchmarks are printed:
422/// 1. Ensure functions are annotated with `#[benchmark]`
423/// 2. Ensure functions are `pub` (public visibility)
424/// 3. Ensure the crate with benchmarks is linked into the binary
425/// 4. Check that `inventory` crate is in your dependencies
426#[cfg(feature = "registry")]
427#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
428#[macro_export]
429macro_rules! debug_benchmarks {
430    () => {
431        /// Prints all discovered benchmark functions to stdout.
432        ///
433        /// This function is generated by the `debug_benchmarks!()` macro
434        /// and is useful for debugging benchmark registration issues.
435        pub fn _debug_print_benchmarks() {
436            println!("Discovered benchmarks:");
437            let names = $crate::list_benchmark_names();
438            if names.is_empty() {
439                println!("  (none found)");
440                println!();
441                println!("Troubleshooting:");
442                println!("  1. Ensure functions are annotated with #[benchmark]");
443                println!("  2. Ensure functions are pub (public visibility)");
444                println!("  3. Ensure the crate with benchmarks is linked into the binary");
445                println!("  4. Check that 'inventory' crate is in your dependencies");
446            } else {
447                for name in names {
448                    println!("  - {}", name);
449                }
450            }
451        }
452    };
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_version_is_set() {
461        assert!(!VERSION.is_empty());
462    }
463
464    #[cfg(feature = "registry")]
465    #[test]
466    fn test_discover_benchmarks_compiles() {
467        // This test just ensures the function is accessible
468        let _benchmarks = discover_benchmarks();
469    }
470}