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}