Files
Oxicloud/examples/bench_round10_micro.rs
T

587 lines
21 KiB
Rust
Raw Normal View History

//! Round-10 CPU/alloc micro-pack — BEFORE replicas vs the shipped code.
//!
//! Sections (all pure CPU, no Postgres):
//! 1. Authenticated-request identity build (Bearer/cookie hit path):
//! BEFORE `String` claims clone ×2 + live-role `to_string` + `Arc::new`
//! vs AFTER `Arc<str>` refcount bumps + inline `SmolStr` + `Arc::new`.
//! 2. Basic-auth cache hit: BEFORE `CachedBasicAuthResult{String}` moka
//! value clone vs AFTER `Arc<str>`/`SmolStr` bumps.
//! 3. NC PROPFIND per-row integer props (`oc:fileid`, `nc:creation_time`,
//! `nc:upload_time`): BEFORE `to_string()` per field vs AFTER
//! `common::fmt` stack render. Gate: byte-identical XML.
//! 4. NC trashbin date/int props: BEFORE `to_rfc2822()` + `to_string()`
//! vs AFTER stack render. Gate: byte-identical XML.
//! 5. Native-WebDAV scope prefix test: BEFORE `format!("{prefix}/")` per
//! request vs AFTER borrow-only check. Gate: identical routing.
//! 6. Share listing base-url: BEFORE `env::var("OXICLOUD_BASE_URL")` +
//! rebuild per row vs AFTER the construction-time snapshot.
//! 7. JWT verify miss: BEFORE fresh `Validation` + `DecodingKey` per
//! decode vs AFTER pre-built fields. Gate: identical claims.
//! 8. AES-GCM cipher hand-off: BEFORE key-schedule memcpy clone vs AFTER
//! `Arc` bump.
//! 9. Request-id header: BEFORE `Uuid::to_string` + `HeaderValue::from_str`
//! vs AFTER stack-encode. Gate: identical header bytes.
//!
//! Run: cargo run --release --features bench --example bench_round10_micro
//! Tunables (env): BENCH_ITERS (100000)
use std::alloc::{GlobalAlloc, Layout, System};
use std::env;
use std::hint::black_box;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use smol_str::SmolStr;
// ─── Counting allocator ─────────────────────────────────────────────────────
static ALLOC_CALLS: AtomicU64 = AtomicU64::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc_zeroed(layout) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn measure<R>(label: &str, iters: u64, mut f: impl FnMut() -> R) -> (f64, f64) {
// Warmup
for _ in 0..1000 {
black_box(f());
}
let a0 = ALLOC_CALLS.load(Ordering::Relaxed);
let t0 = Instant::now();
for _ in 0..iters {
black_box(f());
}
let wall = t0.elapsed().as_secs_f64();
let allocs = (ALLOC_CALLS.load(Ordering::Relaxed) - a0) as f64 / iters as f64;
let ns = wall * 1e9 / iters as f64;
println!(" {label:<44} {ns:>9.1} ns/op {allocs:>7.3} allocs/op");
(ns, allocs)
}
// ─── §1 BEFORE replicas: old claim/identity shapes ──────────────────────────
mod before {
use std::sync::Arc;
/// Old `TokenClaims` shape (owned Strings). The unread fields keep
/// the replica byte-faithful to the historical struct layout.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct OldTokenClaims {
pub sub: String,
pub exp: i64,
pub iat: i64,
pub jti: String,
pub username: String,
pub email: String,
pub role: String,
}
/// Old `CurrentUser` shape.
#[derive(Debug, Clone, serde::Serialize)]
pub struct OldCurrentUser {
pub id: uuid::Uuid,
pub username: String,
pub email: String,
pub role: String,
}
/// Old Bearer-hit tail: deep-clone the two display fields out of the
/// cached claims + `to_string` the live role + `Arc::new`.
pub fn bearer_identity(claims: &Arc<OldTokenClaims>, live_role: &str) -> Arc<OldCurrentUser> {
let role = live_role.to_string(); // decide_live_role's `flags.role.to_string()`
Arc::new(OldCurrentUser {
id: uuid::Uuid::nil(),
username: claims.username.clone(),
email: claims.email.clone(),
role,
})
}
/// Old Basic-auth cached value (owned Strings — moka clones on get).
#[derive(Clone)]
pub struct OldCachedBasic {
pub user_id: uuid::Uuid,
pub username: String,
pub email: String,
pub role: String,
}
}
fn section_identity(iters: u64) {
use oxicloud::application::dtos::user_dto::CurrentUser;
use oxicloud::application::ports::auth_ports::TokenClaims;
println!("[1] authenticated-request identity build (per request)");
let old_claims = Arc::new(before::OldTokenClaims {
sub: "6a11f8a2-14a5-4f8a-9d55-3e3c8a2b9a01".into(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: "alice.longname".to_string(),
email: "alice.longname@example.com".to_string(),
role: "user".to_string(),
});
let new_claims = Arc::new(TokenClaims {
sub: "6a11f8a2-14a5-4f8a-9d55-3e3c8a2b9a01".into(),
sub_id: uuid::Uuid::parse_str("6a11f8a2-14a5-4f8a-9d55-3e3c8a2b9a01").unwrap(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: Arc::from("alice.longname"),
email: Arc::from("alice.longname@example.com"),
role: "user".to_string(),
2026-08-09 02:03:54 +02:00
dpop_jkt: None,
2026-08-19 22:58:09 +02:00
sid: None,
});
let (bn, ba) = measure("BEFORE String clones + role to_string", iters, || {
before::bearer_identity(black_box(&old_claims), black_box("user"))
});
let (an, aa) = measure("AFTER Arc bumps + inline SmolStr", iters, || {
// The shipped middleware tail: LiveRole render + CurrentUser build.
let role = SmolStr::new_static("user");
Arc::new(CurrentUser {
id: uuid::Uuid::nil(),
username: Arc::clone(&black_box(&new_claims).username),
email: Arc::clone(&new_claims.email),
role,
2026-08-09 02:03:54 +02:00
dpop_jkt: None,
})
});
// Gate: identical field values + identical JSON wire shape.
let old = before::bearer_identity(&old_claims, "user");
let new = Arc::new(CurrentUser {
id: uuid::Uuid::nil(),
username: Arc::clone(&new_claims.username),
email: Arc::clone(&new_claims.email),
role: SmolStr::new_static("user"),
2026-08-09 02:03:54 +02:00
dpop_jkt: None,
});
assert_eq!(old.username, *new.username);
assert_eq!(old.email, *new.email);
assert_eq!(old.role, new.role.as_str());
let json_old = serde_json::to_string(&*old).unwrap();
let json_new = serde_json::to_string(&*new).unwrap();
assert_eq!(json_old, json_new, "CurrentUser JSON shape must not change");
println!(
" gate: fields + JSON byte-identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
fn section_basic_hit(iters: u64) {
println!("[2] basic-auth cache hit → tuple hand-off (per DAV request)");
let old_val = before::OldCachedBasic {
user_id: uuid::Uuid::nil(),
username: "dav.client.user".to_string(),
email: "dav.client.user@example.com".to_string(),
role: "user".to_string(),
};
struct NewCachedBasic {
user_id: uuid::Uuid,
username: Arc<str>,
email: Arc<str>,
role: SmolStr,
}
impl Clone for NewCachedBasic {
fn clone(&self) -> Self {
Self {
user_id: self.user_id,
username: Arc::clone(&self.username),
email: Arc::clone(&self.email),
role: self.role.clone(),
}
}
}
let new_val = NewCachedBasic {
user_id: uuid::Uuid::nil(),
username: Arc::from("dav.client.user"),
email: Arc::from("dav.client.user@example.com"),
role: SmolStr::new_static("user"),
};
let (bn, ba) = measure("BEFORE moka value clone (3 Strings)", iters, || {
let v = black_box(&old_val).clone(); // what moka's get does
(v.user_id, v.username, v.email, v.role)
});
let (an, aa) = measure("AFTER moka value clone (bumps)", iters, || {
let v = black_box(&new_val).clone();
(v.user_id, v.username, v.email, v.role)
});
let o = old_val.clone();
let n = new_val.clone();
assert_eq!(o.username, *n.username);
assert_eq!(o.email, *n.email);
assert_eq!(o.role, n.role.as_str());
println!(
" gate: identity fields identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
// ─── §3/§4 XML emit ─────────────────────────────────────────────────────────
fn write_text_element(
xml: &mut quick_xml::Writer<&mut Vec<u8>>,
tag: &str,
value: &str,
) -> Result<(), String> {
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
xml.write_event(Event::Start(BytesStart::new(tag)))
.map_err(|e| e.to_string())?;
xml.write_event(Event::Text(BytesText::new(value)))
.map_err(|e| e.to_string())?;
xml.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| e.to_string())?;
Ok(())
}
fn section_propfind_ints(iters: u64) {
println!("[3] NC PROPFIND per-row integer props (500-row page)");
let rows: Vec<(i64, u64, u64)> = (0..500)
.map(|i| (912_345_678 + i as i64, 1_700_000_000 + i, 1_700_000_100 + i))
.collect();
let emit_before = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &(fid, created, modified) in &rows {
write_text_element(&mut xml, "oc:fileid", &fid.to_string()).unwrap();
write_text_element(&mut xml, "nc:creation_time", &created.to_string()).unwrap();
write_text_element(&mut xml, "nc:upload_time", &modified.to_string()).unwrap();
}
};
let emit_after = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &(fid, created, modified) in &rows {
let mut ibuf = [0u8; 21];
write_text_element(
&mut xml,
"oc:fileid",
oxicloud::common::fmt::i64_str(&mut ibuf, fid),
)
.unwrap();
let mut ubuf = [0u8; 20];
write_text_element(
&mut xml,
"nc:creation_time",
oxicloud::common::fmt::u64_str(&mut ubuf, created),
)
.unwrap();
write_text_element(
&mut xml,
"nc:upload_time",
oxicloud::common::fmt::u64_str(&mut ubuf, modified),
)
.unwrap();
}
};
let mut b1 = Vec::with_capacity(64 * 1024);
emit_before(&mut b1);
let mut b2 = Vec::with_capacity(64 * 1024);
emit_after(&mut b2);
assert_eq!(b1, b2, "XML must be byte-identical");
let page_iters = iters / 500;
let (bn, ba) = measure("BEFORE to_string per int field", page_iters, || {
let mut buf = Vec::with_capacity(64 * 1024);
emit_before(&mut buf);
buf
});
let (an, aa) = measure("AFTER stack i64_str/u64_str", page_iters, || {
let mut buf = Vec::with_capacity(64 * 1024);
emit_after(&mut buf);
buf
});
println!(
" gate: 500-row page byte-identical ✓ page: {:.1}→{:.1} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
fn section_trashbin(iters: u64) {
println!("[4] NC trashbin per-item date/int props (2000-item bin)");
let items: Vec<i64> = (0..2000).map(|i| 1_700_000_000 + i * 37).collect();
let emit_before = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &ts in &items {
let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(ts, 0).unwrap();
write_text_element(&mut xml, "d:getlastmodified", &dt.to_rfc2822()).unwrap();
write_text_element(&mut xml, "nc:trashbin-deletion-time", &ts.to_string()).unwrap();
}
};
let emit_after = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &ts in &items {
let mut dbuf = [0u8; 31];
// The shipped path goes through write_date_element → rfc2822_utc
// with a chrono fallback; in-range timestamps take the stack path.
let s = oxicloud::common::fmt::rfc2822_utc(&mut dbuf, ts).unwrap();
write_text_element(&mut xml, "d:getlastmodified", s).unwrap();
let mut ibuf = [0u8; 21];
write_text_element(
&mut xml,
"nc:trashbin-deletion-time",
oxicloud::common::fmt::i64_str(&mut ibuf, ts),
)
.unwrap();
}
};
let mut b1 = Vec::with_capacity(256 * 1024);
emit_before(&mut b1);
let mut b2 = Vec::with_capacity(256 * 1024);
emit_after(&mut b2);
assert_eq!(b1, b2, "trashbin XML must be byte-identical");
let bin_iters = (iters / 2000).max(20);
let (bn, ba) = measure("BEFORE chrono to_rfc2822 + to_string", bin_iters, || {
let mut buf = Vec::with_capacity(256 * 1024);
emit_before(&mut buf);
buf
});
let (an, aa) = measure("AFTER stack rfc2822_utc + i64_str", bin_iters, || {
let mut buf = Vec::with_capacity(256 * 1024);
emit_after(&mut buf);
buf
});
println!(
" gate: 2000-item bin byte-identical ✓ bin: {:.1}→{:.1} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
// ─── §5 webdav scope prefix ─────────────────────────────────────────────────
fn section_scope_prefix(iters: u64) {
println!("[5] native-WebDAV scope prefix test (per request)");
// 1:1 replicas of the two shapes (the production fn is handler-private).
fn before_route(normalized: &str, marker: &str) -> Option<usize> {
let with_slash = format!("{}/", marker);
normalized.strip_prefix(&with_slash).map(|r| r.len())
}
fn strip_prefix_slash<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
s.strip_prefix(prefix)?.strip_prefix('/')
}
fn after_route(normalized: &str, marker: &str) -> Option<usize> {
strip_prefix_slash(normalized, marker).map(|r| r.len())
}
let cases = [
("@drive/Personal/Photos/2026/img.jpg", "@drive"),
("Personal/Documents/report.pdf", "@drive"),
("@drive", "@drive"),
("@driveX/nope", "@drive"),
];
for (path, marker) in cases {
assert_eq!(before_route(path, marker), after_route(path, marker));
}
let (bn, ba) = measure("BEFORE format!(\"{prefix}/\") probe", iters, || {
before_route(
black_box("@drive/Personal/Photos/2026/img.jpg"),
black_box("@drive"),
)
});
let (an, aa) = measure("AFTER borrow-only probe", iters, || {
after_route(
black_box("@drive/Personal/Photos/2026/img.jpg"),
black_box("@drive"),
)
});
println!(
" gate: routing identical on all shapes ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
// ─── §6 base_url ────────────────────────────────────────────────────────────
fn section_base_url(iters: u64) {
println!("[6] share-listing base_url (per 500-row listing)");
unsafe {
env::set_var("OXICLOUD_BASE_URL", "https://cloud.example.com");
}
let config = oxicloud::common::config::AppConfig::default();
let rows = 500usize;
let before_listing = || {
let mut total = 0usize;
for _ in 0..rows {
total += config.base_url().len(); // env read + String per row
}
total
};
let snapshot = config.base_url();
let after_listing = || {
let mut total = 0usize;
for _ in 0..rows {
total += snapshot.len(); // field read
}
total
};
assert_eq!(before_listing(), after_listing());
let listing_iters = (iters / rows as u64).max(50);
let (bn, ba) = measure("BEFORE env::var + rebuild per row", listing_iters, || {
before_listing()
});
let (an, aa) = measure("AFTER construction-time snapshot", listing_iters, || {
after_listing()
});
println!(
" gate: identical URLs ✓ listing: {:.1}→{:.3} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
// ─── §7 JWT verify miss ─────────────────────────────────────────────────────
fn section_jwt(iters: u64) {
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
println!("[7] JWT verify (validation-cache miss path)");
#[derive(serde::Serialize, serde::Deserialize)]
struct C {
sub: String,
exp: i64,
iat: i64,
jti: String,
username: String,
email: String,
role: String,
}
let secret = "bench_secret_key_at_least_32_bytes_long!";
let claims = C {
sub: uuid::Uuid::nil().to_string(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: "alice".into(),
email: "alice@example.com".into(),
role: "user".into(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.unwrap();
let jwt_iters = iters / 10;
let (bn, ba) = measure("BEFORE fresh Validation+DecodingKey", jwt_iters, || {
let validation = Validation::new(Algorithm::HS256);
let key = DecodingKey::from_secret(secret.as_bytes());
decode::<C>(black_box(&token), &key, &validation)
.unwrap()
.claims
.exp
});
let key = DecodingKey::from_secret(secret.as_bytes());
let validation = Validation::new(Algorithm::HS256);
let (an, aa) = measure("AFTER pre-built service fields", jwt_iters, || {
decode::<C>(black_box(&token), &key, &validation)
.unwrap()
.claims
.exp
});
println!(" gate: same decode result ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)");
}
// ─── §8 cipher clone ────────────────────────────────────────────────────────
fn section_cipher(iters: u64) {
use aes_gcm::{Aes256Gcm, KeyInit};
println!("[8] AES-GCM cipher hand-off (per blob op)");
let cipher = Aes256Gcm::new_from_slice(&[7u8; 32]).unwrap();
let arc_cipher = Arc::new(Aes256Gcm::new_from_slice(&[7u8; 32]).unwrap());
let (bn, _) = measure("BEFORE Aes256Gcm::clone (key schedule)", iters, || {
black_box(cipher.clone())
});
let (an, _) = measure("AFTER Arc<Aes256Gcm>::clone (bump)", iters, || {
black_box(Arc::clone(&arc_cipher))
});
println!(" gate: n/a (same cipher key, encryption unchanged) ({bn:.1}→{an:.1} ns)");
}
// ─── §9 request-id ──────────────────────────────────────────────────────────
fn section_request_id(iters: u64) {
println!("[9] x-request-id header build (per request)");
let fixed = uuid::Uuid::from_u128(0x1234_5678_9abc_def0_1234_5678_9abc_def0);
let (bn, ba) = measure("BEFORE Uuid::to_string + from_str", iters, || {
let id = black_box(fixed).to_string();
axum::http::HeaderValue::from_str(&id).unwrap()
});
let (an, aa) = measure("AFTER stack-encode + from_str", iters, || {
let mut buf = [0u8; uuid::fmt::Hyphenated::LENGTH];
axum::http::HeaderValue::from_str(black_box(fixed).hyphenated().encode_lower(&mut buf))
.unwrap()
});
let a = {
let id = fixed.to_string();
axum::http::HeaderValue::from_str(&id).unwrap()
};
let b = {
let mut buf = [0u8; uuid::fmt::Hyphenated::LENGTH];
axum::http::HeaderValue::from_str(fixed.hyphenated().encode_lower(&mut buf)).unwrap()
};
assert_eq!(a, b, "header bytes must be identical");
println!(" gate: header bytes identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)");
}
fn main() {
let iters: u64 = env_or("BENCH_ITERS", 100_000);
println!("bench_round10_micro — iters={iters}\n");
section_identity(iters);
section_basic_hit(iters);
section_propfind_ints(iters);
section_trashbin(iters);
section_scope_prefix(iters);
section_base_url(iters);
section_jwt(iters);
section_cipher(iters);
section_request_id(iters);
println!("\nall gates passed");
}