Skip to main content

mobench_sdk/
lib.rs

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