//! Deterministic benchmarks for the pure payment domain (review item 7.7). //! //! These use the stable-Rust harness rather than `#![feature(test)]` or an //! extra dependency, so they run under the repository's pinned toolchain and //! in CI without a nightly compiler. //! //! They measure only pure, allocation-bounded work: no clock skew from I/O, no //! gateway, no database. Run with: //! //! ```bash //! cargo bench -p nigig-pay-domain //! ``` use std::time::{Duration, Instant}; use nigig_pay_domain::evidence::{ body_digest, EvidenceLog, EvidenceSentiment, EvidenceSource, ObservedEvidence, }; use nigig_pay_domain::fee::compute_adjusted_amount; use nigig_pay_domain::money::Money; use nigig_pay_domain::payment_intent::{PaymentIntent, PaymentState}; use nigig_pay_domain::validation::validate_phone; /// Run `iterations` of `body` and report the total and per-operation cost. fn bench(name: &str, iterations: u32, mut body: impl FnMut()) -> Duration { // One warm-up pass so first-call allocation does not dominate. body(); let start = Instant::now(); for _ in 0..iterations { body(); } let elapsed = start.elapsed(); let per_op = elapsed / iterations; println!("{name:<44} {iterations:>7} iters {elapsed:>12.3?} total {per_op:>10.3?}/op"); elapsed } fn bench_money_formatting() { bench("money_format_ksh", 100_000, || { let amount = Money::from_major(1_234_567); std::hint::black_box(amount.format_ksh()); }); } fn bench_phone_validation() { let inputs = [ "0712345678", "+254712345678", "254112345678", "0712 345 678", "not-a-phone", ]; bench("validate_100_phone_numbers", 1_000, || { for _ in 0..20 { for input in inputs.iter() { std::hint::black_box(validate_phone(input).ok()); } } }); } fn bench_fee_quotes() { bench("compute_100_fee_quotes", 1_000, || { for index in 0..100u64 { let quote = compute_adjusted_amount( Money::from_major(1_000 + index), Ok(Money::from_major(23)), Some(Ok(Money::from_major(34))), index % 2 == 0, index % 3 == 0, ); std::hint::black_box(quote.ok()); } }); } fn bench_state_machine() { bench("drive_1000_intents_through_lifecycle", 100, || { for _ in 0..1_000 { let mut intent = PaymentIntent::new( Money::from_major(1_000), Money::from_major(23), "254712345678".into(), "Send Money".into(), ); let _ = intent.transition(PaymentState::Dispatching); let _ = intent.transition(PaymentState::Submitted); let _ = intent.transition(PaymentState::Confirmed); std::hint::black_box(&intent); } }); } fn bench_evidence_digest() { let body = "QWE123 Confirmed. Ksh1,000.00 sent to ADA LOVELACE 254712345678 \ on 27/7/26 at 2:15 PM. New M-PESA balance is Ksh4,321.00."; bench("digest_1000_sms_bodies", 100, || { for index in 0..1_000 { std::hint::black_box(body_digest(&format!("{body}{index}"))); } }); } fn bench_evidence_dedup() { // The de-duplication scan is linear in log length, so measure the cost of // a realistically noisy log rather than a single insert. bench("record_200_evidence_entries", 500, || { let mut log = EvidenceLog::new(); let amount = Money::from_major(1_000); for index in 0..200 { let evidence = ObservedEvidence::new( EvidenceSource::Sms { sender: "MPESA".into(), }, EvidenceSentiment::Indeterminate, index as i64, body_digest(&format!("message-{index}")), ); std::hint::black_box(log.record(evidence, amount)); } }); } fn main() { println!("nigig-pay-domain benchmarks (pure domain, no I/O)"); println!("{}", "-".repeat(96)); bench_money_formatting(); bench_phone_validation(); bench_fee_quotes(); bench_state_machine(); bench_evidence_digest(); bench_evidence_dedup(); println!("{}", "-".repeat(96)); println!("done"); }