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 enables running performance benchmarks
8//! on real Android and iOS devices via BrowserStack App Automate.
9//!
10//! ## Overview
11//!
12//! `mobench-sdk` provides a simple, declarative API for defining benchmarks that can
13//! run on mobile devices. It handles the complexity of cross-compilation, FFI bindings,
14//! and mobile app packaging automatically.
15//!
16//! ## Quick Start
17//!
18//! ### 1. Add Dependencies
19//!
20//! ```toml
21//! [dependencies]
22//! mobench-sdk = "0.1"
23//! inventory = "0.3"  # Required for benchmark registration
24//! ```
25//!
26//! ### 2. Define Benchmarks
27//!
28//! Use the [`#[benchmark]`](macro@benchmark) attribute to mark functions for benchmarking:
29//!
30//! ```ignore
31//! use mobench_sdk::benchmark;
32//!
33//! #[benchmark]
34//! fn my_expensive_operation() {
35//!     let result = expensive_computation();
36//!     std::hint::black_box(result);  // Prevent optimization
37//! }
38//!
39//! #[benchmark]
40//! fn another_benchmark() {
41//!     for i in 0..1000 {
42//!         std::hint::black_box(i * i);
43//!     }
44//! }
45//! ```
46//!
47//! ### 3. Build and Run
48//!
49//! Use the `mobench` CLI to build and run benchmarks:
50//!
51//! ```bash
52//! # Install the CLI
53//! cargo install mobench
54//!
55//! # Build for Android (outputs to target/mobench/)
56//! cargo mobench build --target android
57//!
58//! # Build for iOS
59//! cargo mobench build --target ios
60//!
61//! # Run on BrowserStack
62//! cargo mobench run --target android --function my_expensive_operation \
63//!     --iterations 100 --warmup 10 --devices "Google Pixel 7-13.0"
64//! ```
65//!
66//! ## Architecture
67//!
68//! The SDK consists of several components:
69//!
70//! | Module | Description |
71//! |--------|-------------|
72//! | [`timing`] | Core timing infrastructure (always available) |
73//! | [`registry`] | Runtime discovery of `#[benchmark]` functions (requires `full` feature) |
74//! | [`runner`] | Benchmark execution engine (requires `full` feature) |
75//! | [`builders`] | Android and iOS build automation (requires `full` feature) |
76//! | [`codegen`] | Mobile app template generation (requires `full` feature) |
77//! | [`types`] | Common types and error definitions |
78//!
79//! ## Crate Ecosystem
80//!
81//! The mobench ecosystem consists of three published crates:
82//!
83//! - **`mobench-sdk`** (this crate) - Core SDK library with timing harness and build automation
84//! - **[`mobench`](https://crates.io/crates/mobench)** - CLI tool for building and running benchmarks
85//! - **[`mobench-macros`](https://crates.io/crates/mobench-macros)** - `#[benchmark]` proc macro
86//!
87//! Note: The `mobench-runner` crate has been consolidated into this crate as the [`timing`] module.
88//!
89//! ## Feature Flags
90//!
91//! | Feature | Default | Description |
92//! |---------|---------|-------------|
93//! | `full` | Yes | Full SDK with build automation, templates, and registry |
94//! | `runner-only` | No | Minimal timing-only mode for mobile binaries |
95//!
96//! For mobile binaries where binary size matters, use `runner-only`:
97//!
98//! ```toml
99//! [dependencies]
100//! mobench-sdk = { version = "0.1", default-features = false, features = ["runner-only"] }
101//! ```
102//!
103//! ## Programmatic Usage
104//!
105//! You can also use the SDK programmatically:
106//!
107//! ### Using the Builder Pattern
108//!
109//! ```ignore
110//! use mobench_sdk::BenchmarkBuilder;
111//!
112//! let report = BenchmarkBuilder::new("my_benchmark")
113//!     .iterations(100)
114//!     .warmup(10)
115//!     .run()?;
116//!
117//! println!("Mean: {} ns", report.samples.iter()
118//!     .map(|s| s.duration_ns)
119//!     .sum::<u64>() / report.samples.len() as u64);
120//! ```
121//!
122//! ### Using BenchSpec Directly
123//!
124//! ```ignore
125//! use mobench_sdk::{BenchSpec, run_benchmark};
126//!
127//! let spec = BenchSpec {
128//!     name: "my_benchmark".to_string(),
129//!     iterations: 50,
130//!     warmup: 5,
131//! };
132//!
133//! let report = run_benchmark(spec)?;
134//! println!("Collected {} samples", report.samples.len());
135//! ```
136//!
137//! ### Discovering Benchmarks
138//!
139//! ```ignore
140//! use mobench_sdk::{discover_benchmarks, list_benchmark_names};
141//!
142//! // Get all registered benchmark names
143//! let names = list_benchmark_names();
144//! for name in names {
145//!     println!("Found benchmark: {}", name);
146//! }
147//!
148//! // Get full benchmark function info
149//! let benchmarks = discover_benchmarks();
150//! for bench in benchmarks {
151//!     println!("Benchmark: {}", bench.name);
152//! }
153//! ```
154//!
155//! ## Building Mobile Apps
156//!
157//! The SDK includes builders for automating mobile app creation:
158//!
159//! ### Android Builder
160//!
161//! ```ignore
162//! use mobench_sdk::builders::AndroidBuilder;
163//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
164//!
165//! let builder = AndroidBuilder::new(".", "my-bench-crate")
166//!     .verbose(true)
167//!     .output_dir("target/mobench");  // Default
168//!
169//! let config = BuildConfig {
170//!     target: Target::Android,
171//!     profile: BuildProfile::Release,
172//!     incremental: true,
173//! };
174//!
175//! let result = builder.build(&config)?;
176//! println!("APK built at: {:?}", result.app_path);
177//! ```
178//!
179//! ### iOS Builder
180//!
181//! ```ignore
182//! use mobench_sdk::builders::{IosBuilder, SigningMethod};
183//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
184//!
185//! let builder = IosBuilder::new(".", "my-bench-crate")
186//!     .verbose(true);
187//!
188//! let config = BuildConfig {
189//!     target: Target::Ios,
190//!     profile: BuildProfile::Release,
191//!     incremental: true,
192//! };
193//!
194//! let result = builder.build(&config)?;
195//! println!("xcframework built at: {:?}", result.app_path);
196//!
197//! // Package IPA for distribution
198//! let ipa_path = builder.package_ipa("BenchRunner", SigningMethod::AdHoc)?;
199//! ```
200//!
201//! ## Output Directory
202//!
203//! By default, all mobile artifacts are written to `target/mobench/`:
204//!
205//! ```text
206//! target/mobench/
207//! ├── android/
208//! │   ├── app/
209//! │   │   ├── src/main/jniLibs/     # Native .so libraries
210//! │   │   └── build/outputs/apk/    # Built APK
211//! │   └── ...
212//! └── ios/
213//!     ├── sample_fns.xcframework/   # Built xcframework
214//!     ├── BenchRunner/              # Xcode project
215//!     └── BenchRunner.ipa           # Packaged IPA
216//! ```
217//!
218//! This keeps generated files inside `target/`, following Rust conventions
219//! and preventing accidental commits of mobile project files.
220//!
221//! ## Platform Requirements
222//!
223//! ### Android
224//!
225//! - Android NDK (set `ANDROID_NDK_HOME` environment variable)
226//! - `cargo-ndk` (`cargo install cargo-ndk`)
227//! - Rust targets: `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android`
228//!
229//! ### iOS
230//!
231//! - Xcode with command line tools
232//! - `uniffi-bindgen` (`cargo install uniffi-bindgen`)
233//! - `xcodegen` (optional, `brew install xcodegen`)
234//! - Rust targets: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim`
235//!
236//! ## Best Practices
237//!
238//! ### Use `black_box` to Prevent Optimization
239//!
240//! Always wrap benchmark results with [`std::hint::black_box`] to prevent the
241//! compiler from optimizing away the computation:
242//!
243//! ```ignore
244//! #[benchmark]
245//! fn correct_benchmark() {
246//!     let result = expensive_computation();
247//!     std::hint::black_box(result);  // Result is "used"
248//! }
249//! ```
250//!
251//! ### Avoid Side Effects
252//!
253//! Benchmarks should be deterministic and avoid I/O operations:
254//!
255//! ```ignore
256//! // Good: Pure computation
257//! #[benchmark]
258//! fn good_benchmark() {
259//!     let data = vec![1, 2, 3, 4, 5];
260//!     let sum: i32 = data.iter().sum();
261//!     std::hint::black_box(sum);
262//! }
263//!
264//! // Avoid: File I/O adds noise
265//! #[benchmark]
266//! fn noisy_benchmark() {
267//!     let data = std::fs::read_to_string("data.txt").unwrap();  // Don't do this
268//!     std::hint::black_box(data);
269//! }
270//! ```
271//!
272//! ### Choose Appropriate Iteration Counts
273//!
274//! - **Warmup**: 5-10 iterations to warm CPU caches and JIT
275//! - **Iterations**: 50-100 for stable statistics
276//! - Mobile devices may have more variance than desktop
277//!
278//! ## License
279//!
280//! MIT License - see repository for details.
281
282#![cfg_attr(docsrs, feature(doc_cfg))]
283
284// Core timing module - always available
285pub mod timing;
286pub mod types;
287
288// Full SDK modules - only with "full" feature
289#[cfg(feature = "full")]
290#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
291pub mod builders;
292#[cfg(feature = "full")]
293#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
294pub mod codegen;
295#[cfg(feature = "full")]
296#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
297pub mod registry;
298#[cfg(feature = "full")]
299#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
300pub mod runner;
301
302// Re-export the benchmark macro from bench-macros (only with full feature)
303#[cfg(feature = "full")]
304#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
305pub use mobench_macros::benchmark;
306
307// Re-export key types for convenience (full feature)
308#[cfg(feature = "full")]
309#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
310pub use registry::{BenchFunction, discover_benchmarks, find_benchmark, list_benchmark_names};
311#[cfg(feature = "full")]
312#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
313pub use runner::{BenchmarkBuilder, run_benchmark};
314
315// Re-export types that are always available
316pub use types::{BenchError, BenchSample, BenchSpec, RunnerReport};
317
318// Re-export types that require full feature
319#[cfg(feature = "full")]
320#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
321pub use types::{BuildConfig, BuildProfile, BuildResult, InitConfig, Target};
322
323// Re-export timing types at the crate root for convenience
324pub use timing::{run_closure, TimingError};
325
326/// Library version, matching `Cargo.toml`.
327///
328/// This can be used to verify SDK compatibility:
329///
330/// ```
331/// assert!(!mobench_sdk::VERSION.is_empty());
332/// ```
333pub const VERSION: &str = env!("CARGO_PKG_VERSION");
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_version_is_set() {
341        assert!(!VERSION.is_empty());
342    }
343
344    #[cfg(feature = "full")]
345    #[test]
346    fn test_discover_benchmarks_compiles() {
347        // This test just ensures the function is accessible
348        let _benchmarks = discover_benchmarks();
349    }
350}