mobench_sdk/lib.rs
1//! # mobench-sdk
2//!
3//! [](https://crates.io/crates/mobench-sdk)
4//! [](https://docs.rs/mobench-sdk)
5//! [](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//! In repository CI, the SDK is typically exercised through the `mobench` CLI's
125//! hybrid GitHub integration: compile-gated stateless controller workflows can
126//! start benchmark runs, while `mobench-webhook` owns history ingest and GitHub
127//! Check Runs when deployed.
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//! | `runner-only` | No | Minimal timing-only mode for mobile binaries |
145//!
146//! For mobile binaries where binary size matters, use `runner-only`:
147//!
148//! ```toml
149//! [dependencies]
150//! mobench-sdk = { version = "0.1", default-features = false, features = ["runner-only"] }
151//! ```
152//!
153//! ## Programmatic Usage
154//!
155//! You can also use the SDK programmatically:
156//!
157//! ### Using the Builder Pattern
158//!
159//! ```ignore
160//! use mobench_sdk::BenchmarkBuilder;
161//!
162//! let report = BenchmarkBuilder::new("my_benchmark")
163//! .iterations(100)
164//! .warmup(10)
165//! .run()?;
166//!
167//! println!("Mean: {} ns", report.samples.iter()
168//! .map(|s| s.duration_ns)
169//! .sum::<u64>() / report.samples.len() as u64);
170//! ```
171//!
172//! ### Using BenchSpec Directly
173//!
174//! ```ignore
175//! use mobench_sdk::{BenchSpec, run_benchmark};
176//!
177//! let spec = BenchSpec {
178//! name: "my_benchmark".to_string(),
179//! iterations: 50,
180//! warmup: 5,
181//! };
182//!
183//! let report = run_benchmark(spec)?;
184//! println!("Collected {} samples", report.samples.len());
185//! ```
186//!
187//! ### Discovering Benchmarks
188//!
189//! ```ignore
190//! use mobench_sdk::{discover_benchmarks, list_benchmark_names};
191//!
192//! // Get all registered benchmark names
193//! let names = list_benchmark_names();
194//! for name in names {
195//! println!("Found benchmark: {}", name);
196//! }
197//!
198//! // Get full benchmark function info
199//! let benchmarks = discover_benchmarks();
200//! for bench in benchmarks {
201//! println!("Benchmark: {}", bench.name);
202//! }
203//! ```
204//!
205//! ## Building Mobile Apps
206//!
207//! The SDK includes builders for automating mobile app creation:
208//!
209//! ### Android Builder
210//!
211//! ```ignore
212//! use mobench_sdk::builders::AndroidBuilder;
213//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
214//!
215//! let builder = AndroidBuilder::new(".", "my-bench-crate")
216//! .verbose(true)
217//! .output_dir("target/mobench"); // Default
218//!
219//! let config = BuildConfig {
220//! target: Target::Android,
221//! profile: BuildProfile::Release,
222//! incremental: true,
223//! };
224//!
225//! let result = builder.build(&config)?;
226//! println!("APK built at: {:?}", result.app_path);
227//! ```
228//!
229//! ### iOS Builder
230//!
231//! ```ignore
232//! use mobench_sdk::builders::{IosBuilder, SigningMethod};
233//! use mobench_sdk::{BuildConfig, BuildProfile, Target};
234//!
235//! let builder = IosBuilder::new(".", "my-bench-crate")
236//! .verbose(true);
237//!
238//! let config = BuildConfig {
239//! target: Target::Ios,
240//! profile: BuildProfile::Release,
241//! incremental: true,
242//! };
243//!
244//! let result = builder.build(&config)?;
245//! println!("xcframework built at: {:?}", result.app_path);
246//!
247//! // Package IPA for distribution
248//! let ipa_path = builder.package_ipa("BenchRunner", SigningMethod::AdHoc)?;
249//! ```
250//!
251//! ## Output Directory
252//!
253//! By default, all mobile artifacts are written to `target/mobench/`:
254//!
255//! ```text
256//! target/mobench/
257//! ├── android/
258//! │ ├── app/
259//! │ │ ├── src/main/jniLibs/ # Native .so libraries
260//! │ │ └── build/outputs/apk/ # Built APK
261//! │ └── ...
262//! └── ios/
263//! ├── sample_fns.xcframework/ # Built xcframework
264//! ├── BenchRunner/ # Xcode project
265//! └── BenchRunner.ipa # Packaged IPA
266//! ```
267//!
268//! This keeps generated files inside `target/`, following Rust conventions
269//! and preventing accidental commits of mobile project files.
270//!
271//! ## Platform Requirements
272//!
273//! ### Android
274//!
275//! - Android NDK (set `ANDROID_NDK_HOME` environment variable)
276//! - `cargo-ndk` (`cargo install cargo-ndk`)
277//! - Rust targets: `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android`
278//!
279//! ### iOS
280//!
281//! - Xcode with command line tools
282//! - `uniffi-bindgen` (`cargo install uniffi-bindgen`)
283//! - `xcodegen` (optional, `brew install xcodegen`)
284//! - Rust targets: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios`
285//!
286//! ## Best Practices
287//!
288//! ### Use `black_box` to Prevent Optimization
289//!
290//! Always wrap benchmark results with [`std::hint::black_box`] to prevent the
291//! compiler from optimizing away the computation:
292//!
293//! ```ignore
294//! #[benchmark]
295//! fn correct_benchmark() {
296//! let result = expensive_computation();
297//! std::hint::black_box(result); // Result is "used"
298//! }
299//! ```
300//!
301//! ### Avoid Side Effects
302//!
303//! Benchmarks should be deterministic and avoid I/O operations:
304//!
305//! ```ignore
306//! // Good: Pure computation
307//! #[benchmark]
308//! fn good_benchmark() {
309//! let data = vec![1, 2, 3, 4, 5];
310//! let sum: i32 = data.iter().sum();
311//! std::hint::black_box(sum);
312//! }
313//!
314//! // Avoid: File I/O adds noise
315//! #[benchmark]
316//! fn noisy_benchmark() {
317//! let data = std::fs::read_to_string("data.txt").unwrap(); // Don't do this
318//! std::hint::black_box(data);
319//! }
320//! ```
321//!
322//! ### Choose Appropriate Iteration Counts
323//!
324//! - **Warmup**: 5-10 iterations to warm CPU caches and JIT
325//! - **Iterations**: 50-100 for stable statistics
326//! - Mobile devices may have more variance than desktop
327//!
328//! ## License
329//!
330//! MIT License - see repository for details.
331
332#![cfg_attr(docsrs, feature(doc_cfg))]
333
334// Core timing module - always available
335pub mod timing;
336pub mod types;
337
338// UniFFI integration helpers
339// This module provides template types and conversion traits for UniFFI integration
340pub mod uniffi_types;
341
342// Unified FFI module for UniFFI integration
343pub mod ffi;
344
345// Full SDK modules - only with "full" feature
346#[cfg(feature = "full")]
347#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
348pub mod builders;
349#[cfg(feature = "full")]
350#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
351pub mod codegen;
352#[cfg(feature = "full")]
353#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
354pub mod registry;
355#[cfg(feature = "full")]
356#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
357pub mod runner;
358
359// Re-export the benchmark macro from bench-macros (only with full feature)
360#[cfg(feature = "full")]
361#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
362pub use mobench_macros::benchmark;
363
364// Re-export inventory so users don't need to add it as a separate dependency
365#[cfg(feature = "full")]
366#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
367pub use inventory;
368
369// Re-export key types for convenience (full feature)
370#[cfg(feature = "full")]
371#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
372pub use registry::{BenchFunction, discover_benchmarks, find_benchmark, list_benchmark_names};
373#[cfg(feature = "full")]
374#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
375pub use runner::{BenchmarkBuilder, run_benchmark};
376
377// Re-export types that are always available
378pub use types::{BenchError, BenchSample, BenchSpec, RunnerReport};
379
380// Re-export types that require full feature
381#[cfg(feature = "full")]
382#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
383pub use types::{BuildConfig, BuildProfile, BuildResult, InitConfig, Target};
384
385// Re-export timing types at the crate root for convenience
386pub use timing::{BenchSummary, TimingError, run_closure};
387
388/// Re-export of [`std::hint::black_box`] for preventing compiler optimizations.
389///
390/// Use this to ensure the compiler doesn't optimize away benchmark computations.
391pub use std::hint::black_box;
392
393/// Library version, matching `Cargo.toml`.
394///
395/// This can be used to verify SDK compatibility:
396///
397/// ```
398/// assert!(!mobench_sdk::VERSION.is_empty());
399/// ```
400pub const VERSION: &str = env!("CARGO_PKG_VERSION");
401
402/// Generates a debug function that prints all discovered benchmarks.
403///
404/// This macro is useful for debugging benchmark registration issues.
405/// It creates a function `_debug_print_benchmarks()` that you can call
406/// to see which benchmarks have been registered via `#[benchmark]`.
407///
408/// # Example
409///
410/// ```ignore
411/// use mobench_sdk::{benchmark, debug_benchmarks};
412///
413/// #[benchmark]
414/// fn my_benchmark() {
415/// std::hint::black_box(42);
416/// }
417///
418/// // Generate the debug function
419/// debug_benchmarks!();
420///
421/// fn main() {
422/// // Print all registered benchmarks
423/// _debug_print_benchmarks();
424/// // Output:
425/// // Discovered benchmarks:
426/// // - my_crate::my_benchmark
427/// }
428/// ```
429///
430/// # Troubleshooting
431///
432/// If no benchmarks are printed:
433/// 1. Ensure functions are annotated with `#[benchmark]`
434/// 2. Ensure functions are `pub` (public visibility)
435/// 3. Ensure the crate with benchmarks is linked into the binary
436/// 4. Check that `inventory` crate is in your dependencies
437#[cfg(feature = "full")]
438#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
439#[macro_export]
440macro_rules! debug_benchmarks {
441 () => {
442 /// Prints all discovered benchmark functions to stdout.
443 ///
444 /// This function is generated by the `debug_benchmarks!()` macro
445 /// and is useful for debugging benchmark registration issues.
446 pub fn _debug_print_benchmarks() {
447 println!("Discovered benchmarks:");
448 let names = $crate::list_benchmark_names();
449 if names.is_empty() {
450 println!(" (none found)");
451 println!();
452 println!("Troubleshooting:");
453 println!(" 1. Ensure functions are annotated with #[benchmark]");
454 println!(" 2. Ensure functions are pub (public visibility)");
455 println!(" 3. Ensure the crate with benchmarks is linked into the binary");
456 println!(" 4. Check that 'inventory' crate is in your dependencies");
457 } else {
458 for name in names {
459 println!(" - {}", name);
460 }
461 }
462 }
463 };
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn test_version_is_set() {
472 assert!(!VERSION.is_empty());
473 }
474
475 #[cfg(feature = "full")]
476 #[test]
477 fn test_discover_benchmarks_compiles() {
478 // This test just ensures the function is accessible
479 let _benchmarks = discover_benchmarks();
480 }
481}