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