Skip to main content

mobench_sdk/
lib.rs

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