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