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.42"
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.42"
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.42", 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-bindgen-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 timing;
308pub mod types;
309
310// UniFFI integration helpers
311// This module provides template types and conversion traits for UniFFI integration
312pub mod uniffi_types;
313
314// Unified FFI module for UniFFI integration
315pub mod ffi;
316
317// Build automation modules - only with builder/codegen features
318#[cfg(feature = "builders")]
319#[cfg_attr(docsrs, doc(cfg(feature = "builders")))]
320pub mod builders;
321#[cfg(feature = "codegen")]
322#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
323pub mod codegen;
324
325// Registry runtime modules - available without build tooling
326#[cfg(feature = "registry")]
327#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
328pub mod native_c_abi;
329#[cfg(feature = "registry")]
330#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
331pub mod registry;
332#[cfg(feature = "registry")]
333#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
334pub mod runner;
335
336// Re-export the benchmark macro from bench-macros (only with registry feature)
337#[cfg(feature = "registry")]
338#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
339pub use mobench_macros::benchmark;
340
341// Re-export inventory so users don't need to add it as a separate dependency
342#[cfg(feature = "registry")]
343#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
344pub use inventory;
345
346// Re-export key registry types for convenience
347#[cfg(feature = "registry")]
348#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
349pub use native_c_abi::MobenchBuf;
350#[cfg(feature = "registry")]
351#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
352pub use registry::{BenchFunction, discover_benchmarks, find_benchmark, list_benchmark_names};
353#[cfg(feature = "registry")]
354#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
355pub use runner::{BenchmarkBuilder, run_benchmark};
356
357// Re-export types that are always available
358pub use types::{BenchError, BenchSample, BenchSpec, HarnessTimelineSpan, RunnerReport};
359
360// Re-export build/config types. These are plain data types and do not pull in
361// build automation dependencies by themselves.
362pub use types::{
363    BuildConfig, BuildProfile, BuildResult, FfiBackend, InitConfig, NativeLibraryArtifact, Target,
364};
365
366// Re-export timing types at the crate root for convenience
367pub use timing::{BenchSummary, SemanticPhase, TimingError, profile_phase, run_closure};
368
369/// Re-export of [`std::hint::black_box`] for preventing compiler optimizations.
370///
371/// Use this to ensure the compiler doesn't optimize away benchmark computations.
372pub use std::hint::black_box;
373
374/// Library version, matching `Cargo.toml`.
375///
376/// This can be used to verify SDK compatibility:
377///
378/// ```
379/// assert!(!mobench_sdk::VERSION.is_empty());
380/// ```
381pub const VERSION: &str = env!("CARGO_PKG_VERSION");
382
383/// Generates a debug function that prints all discovered benchmarks.
384///
385/// This macro is useful for debugging benchmark registration issues.
386/// It creates a function `_debug_print_benchmarks()` that you can call
387/// to see which benchmarks have been registered via `#[benchmark]`.
388///
389/// # Example
390///
391/// ```ignore
392/// use mobench_sdk::{benchmark, debug_benchmarks};
393///
394/// #[benchmark]
395/// fn my_benchmark() {
396///     std::hint::black_box(42);
397/// }
398///
399/// // Generate the debug function
400/// debug_benchmarks!();
401///
402/// fn main() {
403///     // Print all registered benchmarks
404///     _debug_print_benchmarks();
405///     // Output:
406///     // Discovered benchmarks:
407///     //   - my_crate::my_benchmark
408/// }
409/// ```
410///
411/// # Troubleshooting
412///
413/// If no benchmarks are printed:
414/// 1. Ensure functions are annotated with `#[benchmark]`
415/// 2. Ensure functions are `pub` (public visibility)
416/// 3. Ensure the crate with benchmarks is linked into the binary
417/// 4. Check that `inventory` crate is in your dependencies
418#[cfg(feature = "registry")]
419#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
420#[macro_export]
421macro_rules! debug_benchmarks {
422    () => {
423        /// Prints all discovered benchmark functions to stdout.
424        ///
425        /// This function is generated by the `debug_benchmarks!()` macro
426        /// and is useful for debugging benchmark registration issues.
427        pub fn _debug_print_benchmarks() {
428            println!("Discovered benchmarks:");
429            let names = $crate::list_benchmark_names();
430            if names.is_empty() {
431                println!("  (none found)");
432                println!();
433                println!("Troubleshooting:");
434                println!("  1. Ensure functions are annotated with #[benchmark]");
435                println!("  2. Ensure functions are pub (public visibility)");
436                println!("  3. Ensure the crate with benchmarks is linked into the binary");
437                println!("  4. Check that 'inventory' crate is in your dependencies");
438            } else {
439                for name in names {
440                    println!("  - {}", name);
441                }
442            }
443        }
444    };
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    #[test]
452    fn test_version_is_set() {
453        assert!(!VERSION.is_empty());
454    }
455
456    #[cfg(feature = "registry")]
457    #[test]
458    fn test_discover_benchmarks_compiles() {
459        // This test just ensures the function is accessible
460        let _benchmarks = discover_benchmarks();
461    }
462}