perf: round 5 — CalDAV cursor streaming, SPA interning gaps, NC href prefix, per-request micro-allocs

Seven benchmark-gated changes (benches/ROUND5.md; BEFORE/AFTER bench +
equivalence gate each, rollback rule as ROUND2-4 — two intermediate
CalDAV shapes measured worse and were themselves rolled back before
shipping):

- CalDAV whole-calendar responses (REPORT no-range/sync-collection,
  depth-1 collection PROPFIND, .ics GET): buffered double-residency →
  ONE window-ordered scan (MIN(start_time) OVER (PARTITION BY ical_uid))
  streamed through a PG cursor, pages cut at UID boundaries. TTFB
  23.3→11.0 ms (2.1x), peak heap 14.2→8.0 MiB at 4k events / 45→24 MiB
  at 12k, wall +9-15% (documented trade, ZIP-streaming class); both
  multistatus and ICS byte-identical to the buffered output. Rejected
  shapes kept in the doc: per-page GROUP-BY keyset (3-4x wall) and
  per-uid ANY hydration (~20 µs/index descent).
- SPA listing interning gaps: folder/recent/favorites resources handlers
  (and the WebDAV pseudo-root) called raw Arc::from per row for the
  closed display set ROUND3 interned — now intern_display/intern_mime,
  4→0 allocs/row, byte-identical Arc contents.
- NC PROPFIND child hrefs: username + parent path encoded once per
  request instead of per child (543→165 ns/row, 13→4 allocs); native
  WebDAV href drops its intermediate encode String.
- suggest enrichment: entity clone + field re-clones per keystroke row →
  consume + move (166.5→126.8 µs/200 rows, 20→7 allocs/row).
- list_readable_by returns the cache's Arc (246→128 ns warm hit, 4→0
  allocs) — deep Vec clone per DAV-selector request removed.
- CardDAV REPORT: borrowed props, reused href buffer, exact-size etag
  quoting (3.04→2.34 ms per 5k-contact getetag poll).
- Auth span records: user_id.to_string() per request ×3 →
  tracing::field::display.

Checks: cargo fmt, clippy --all-features --all-targets -D warnings,
cargo test --workspace (523 passed). Follow-ups (CardDAV streaming,
&[&str] id batches, ::text UUID casts A/B, share-landing join) recorded
in benches/ROUND5.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
This commit is contained in:
Claude
2026-07-17 15:19:00 +00:00
parent 12dc648cff
commit 63cf6646d0
24 changed files with 2008 additions and 261 deletions
+534
View File
@@ -0,0 +1,534 @@
//! CalDAV whole-calendar response benchmark — buffered vs streamed (ROUND5).
//!
//! The REPORT path (no-range calendar-query, sync-collection) and the
//! collection `.ics` GET used to (a) materialise EVERY event DTO of the
//! calendar in one Vec (owned `ical_data` per row), then (b) render the
//! complete multistatus / VCALENDAR into a second in-RAM buffer — the
//! calendar resident twice, TTFB = full generation. AFTER streams ONE
//! window-ordered scan (`MIN(start_time) OVER (PARTITION BY ical_uid)`)
//! through a PG cursor and cuts pages at UID boundaries — same-UID rows
//! never split, bundle order equals the buffered first-appearance
//! order, and only a page of rows is resident. (A first keyset-paged
//! shape re-aggregated per page — 3-4x wall — and a per-uid ANY
//! hydration paid ~20 µs per index descent — both measured and
//! discarded; see ROUND5.md.)
//!
//! This bench drives the REAL repository methods + adapter writers both
//! ways at the repo layer (authz gates are identical constants on both
//! sides and excluded). BEFORE uses the surviving buffered generator
//! (byte-stable refactor of the old monolith) + a verbatim copy of the
//! removed `generate_full_calendar_ical`. Gates: streamed concatenation
//! byte-identical to the buffered output for BOTH the multistatus and
//! the ICS body (seeded with strictly distinct start times so ordering
//! is deterministic).
//!
//! Run (needs Postgres up; reads DATABASE_URL from .env):
//! cargo run --release --features bench --example bench_caldav_stream
//! Tunables (env): BENCH_EVENTS (4000), BENCH_PAGE (500), BENCH_PASSES (9).
use std::alloc::{GlobalAlloc, Layout, System};
use std::env;
use std::fmt::Write as _;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use chrono::{DateTime, TimeZone, Utc};
use oxicloud::application::adapters::caldav_adapter::{
CalDavAdapter, CalDavReportType, bench as caldav_bench,
};
use oxicloud::application::dtos::calendar_dto::CalendarEventDto;
use oxicloud::domain::repositories::calendar_event_repository::CalendarEventRepository;
use oxicloud::infrastructure::repositories::pg::CalendarEventPgRepository;
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use uuid::Uuid;
// ─── Peak-live-heap tracking allocator ──────────────────────────────────────
static LIVE: AtomicU64 = AtomicU64::new(0);
static PEAK: AtomicU64 = AtomicU64::new(0);
struct PeakAlloc;
fn bump(sz: u64) {
let live = LIVE.fetch_add(sz, Ordering::Relaxed) + sz;
PEAK.fetch_max(live, Ordering::Relaxed);
}
unsafe impl GlobalAlloc for PeakAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
bump(layout.size() as u64);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
LIVE.fetch_sub(layout.size() as u64, Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
bump((new_size - layout.size()) as u64);
} else {
LIVE.fetch_sub((layout.size() - new_size) as u64, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
bump(layout.size() as u64);
unsafe { System.alloc_zeroed(layout) }
}
}
#[global_allocator]
static GLOBAL: PeakAlloc = PeakAlloc;
// ─── BEFORE: verbatim copy of the removed whole-calendar ICS builder ────────
#[allow(clippy::all)]
mod before {
use super::*;
/// Verbatim copy of the removed `generate_full_calendar_ical`.
pub fn generate_full_calendar_ical(calendar_name: &str, events: &[CalendarEventDto]) -> String {
let mut buf = String::with_capacity(256 + events.len() * 320);
let _ = write!(
buf,
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//OxiCloud//NONSGML Calendar//EN\r\nX-WR-CALNAME:{}\r\n",
calendar_name
);
for group in caldav_bench::group_events_by_uid(events) {
for event in group {
if let Some(chunk) = caldav_bench::extract_vevent_chunk(&event.ical_data) {
buf.push_str(chunk);
if !buf.ends_with('\n') {
buf.push_str("\r\n");
}
}
}
}
buf.push_str("END:VCALENDAR\r\n");
buf
}
}
// ─── Seed ───────────────────────────────────────────────────────────────────
fn vevent_body(uid: &str, start: DateTime<Utc>, exception: bool) -> String {
let dt = start.format("%Y%m%dT%H%M%SZ");
let dtend = (start + chrono::Duration::minutes(45)).format("%Y%m%dT%H%M%SZ");
let mut v = String::with_capacity(640);
v.push_str("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//OxiCloud//NONSGML Calendar//EN\r\n");
v.push_str("BEGIN:VEVENT\r\n");
let _ = write!(v, "UID:{uid}\r\nDTSTAMP:20260701T120000Z\r\n");
let _ = write!(v, "DTSTART:{dt}\r\nDTEND:{dtend}\r\n");
if exception {
let _ = write!(v, "RECURRENCE-ID:{dt}\r\n");
} else {
v.push_str("RRULE:FREQ=WEEKLY;BYDAY=WE\r\n");
}
let _ = write!(v, "SUMMARY:Reunión {uid}\r\n");
v.push_str("LOCATION:Sala 3\r\nSTATUS:CONFIRMED\r\n");
v.push_str("BEGIN:VALARM\r\nACTION:DISPLAY\r\nTRIGGER:-PT10M\r\nEND:VALARM\r\n");
v.push_str("END:VEVENT\r\nEND:VCALENDAR\r\n");
v
}
struct Seeded {
calendar_id: Uuid,
owner_id: Uuid,
}
async fn seed(pool: &PgPool, n: usize) -> Seeded {
let owner_id: Uuid = sqlx::query_scalar(
"INSERT INTO auth.users (username, email, role)
VALUES ('bench_calstream', 'bench_calstream@bench.invalid', 'user') RETURNING id",
)
.fetch_one(pool)
.await
.expect("seed user");
let calendar_id: Uuid = sqlx::query_scalar(
"INSERT INTO caldav.calendars (id, name, owner_id)
VALUES (gen_random_uuid(), 'Agenda grande', $1) RETURNING id",
)
.bind(owner_id)
.fetch_one(pool)
.await
.expect("seed calendar");
let base = Utc.with_ymd_and_hms(2026, 1, 5, 8, 0, 0).unwrap();
let mut tx = pool.begin().await.expect("begin");
for i in 0..n {
// 20% of rows are exception overrides sharing the previous
// master's UID; every start_time is strictly distinct so the
// response ordering is deterministic (byte-identity gate).
let exception = i % 5 == 4;
let master = if exception { i - 1 } else { i };
let uid = format!("evt-{master:06}@oxicloud.bench");
let start = base + chrono::Duration::seconds((i as i64) * 137);
let recurrence: Option<DateTime<Utc>> = exception.then_some(start);
sqlx::query(
"INSERT INTO caldav.calendar_events
(id, calendar_id, summary, start_time, end_time, all_day,
rrule, ical_uid, ical_data, recurrence_id)
VALUES (gen_random_uuid(), $1, $2, $3, $4, false, $5, $6, $7, $8)",
)
.bind(calendar_id)
.bind(format!("Reunión {i}"))
.bind(start)
.bind(start + chrono::Duration::minutes(45))
.bind((!exception).then_some("FREQ=WEEKLY;BYDAY=WE"))
.bind(&uid)
.bind(vevent_body(&uid, start, exception))
.bind(recurrence)
.execute(&mut *tx)
.await
.expect("seed event");
}
tx.commit().await.expect("commit");
Seeded {
calendar_id,
owner_id,
}
}
async fn cleanup(pool: &PgPool, s: &Seeded) {
let _ = sqlx::query("DELETE FROM caldav.calendar_events WHERE calendar_id = $1")
.bind(s.calendar_id)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM caldav.calendars WHERE id = $1")
.bind(s.calendar_id)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM auth.users WHERE id = $1")
.bind(s.owner_id)
.execute(pool)
.await;
}
// ─── Pipelines ──────────────────────────────────────────────────────────────
fn report_shape() -> CalDavReportType {
CalDavReportType::CalendarQuery {
props: vec![],
time_range: None,
}
}
/// BEFORE: the buffered pipeline — full entity fetch → full DTO Vec →
/// one whole-response buffer. Returns (ttfb_ms, wall_ms, bytes).
async fn buffered_report(
repo: &CalendarEventPgRepository,
calendar_id: &Uuid,
base_href: &str,
) -> (f64, f64, Vec<u8>) {
let t0 = Instant::now();
let events: Vec<CalendarEventDto> = repo
.list_events_by_calendar(calendar_id)
.await
.expect("list events")
.into_iter()
.map(CalendarEventDto::from)
.collect();
let mut out = Vec::with_capacity(events.len() * 1024);
CalDavAdapter::generate_calendar_events_response(&mut out, &events, &report_shape(), base_href)
.expect("generate");
let wall = t0.elapsed().as_secs_f64() * 1e3;
// Buffered: the first byte is only available when everything is.
(wall, wall, out)
}
/// AFTER: the streaming pipeline — uid-keyset pages, per-page hydration,
/// header/page/footer chunks (the handler's loop over the same public
/// pieces). Returns (ttfb_ms, wall_ms, concatenated bytes).
async fn streamed_report(
repo: &CalendarEventPgRepository,
calendar_id: &Uuid,
base_href: &str,
page_uids: usize,
) -> (f64, f64, Vec<u8>) {
let t0 = Instant::now();
let mut ttfb = None;
let mut all = Vec::new();
let report = report_shape();
let mut chunk = Vec::with_capacity(256);
{
let mut w = quick_xml::Writer::new(&mut chunk);
CalDavAdapter::write_caldav_multistatus_start(&mut w).expect("start");
}
all.extend_from_slice(&chunk);
{
use futures::TryStreamExt;
let mut rows = repo.stream_events_uid_order(*calendar_id);
let mut page: Vec<CalendarEventDto> = Vec::with_capacity(page_uids + 32);
loop {
let next = rows
.try_next()
.await
.expect("stream row")
.map(CalendarEventDto::from);
let flush = match &next {
Some(ev) => {
page.len() >= page_uids
&& page.last().is_some_and(|p| p.ical_uid != ev.ical_uid)
}
None => !page.is_empty(),
};
if flush {
let mut chunk = Vec::with_capacity(page.len() * 1024 + 128);
{
let mut w = quick_xml::Writer::new(&mut chunk);
CalDavAdapter::write_report_page(&mut w, &page, &report, base_href)
.expect("page");
}
if ttfb.is_none() && !all.is_empty() {
// header already emitted; first data page complete
}
page.clear();
all.extend_from_slice(&chunk);
ttfb.get_or_insert_with(|| t0.elapsed().as_secs_f64() * 1e3);
}
match next {
Some(ev) => page.push(ev),
None => break,
}
}
}
let mut chunk = Vec::with_capacity(32);
{
let mut w = quick_xml::Writer::new(&mut chunk);
CalDavAdapter::write_caldav_multistatus_end(&mut w).expect("end");
}
all.extend_from_slice(&chunk);
(
ttfb.unwrap_or(f64::NAN),
t0.elapsed().as_secs_f64() * 1e3,
all,
)
}
/// TTFB for the streaming path measured honestly: time until the FIRST
/// PAGE chunk (header + one hydrated page) exists — the moment real
/// bytes could hit the socket.
async fn streamed_report_ttfb(
repo: &CalendarEventPgRepository,
calendar_id: &Uuid,
base_href: &str,
page_uids: usize,
) -> f64 {
use futures::TryStreamExt;
let t0 = Instant::now();
let mut rows = repo.stream_events_uid_order(*calendar_id);
let mut page: Vec<CalendarEventDto> = Vec::with_capacity(page_uids + 32);
while let Some(ev) = rows.try_next().await.expect("stream row") {
let ev = CalendarEventDto::from(ev);
if page.len() >= page_uids && page.last().is_some_and(|p| p.ical_uid != ev.ical_uid) {
break;
}
page.push(ev);
}
let mut chunk = Vec::with_capacity(page.len() * 1024 + 256);
{
let mut w = quick_xml::Writer::new(&mut chunk);
CalDavAdapter::write_caldav_multistatus_start(&mut w).expect("start");
CalDavAdapter::write_report_page(&mut w, &page, &report_shape(), base_href).expect("page");
}
std::hint::black_box(&chunk);
t0.elapsed().as_secs_f64() * 1e3
}
fn p50(mut xs: Vec<f64>) -> f64 {
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
xs[xs.len() / 2]
}
fn reset_peak() {
PEAK.store(LIVE.load(Ordering::Relaxed), Ordering::Relaxed);
}
fn peak_mib() -> f64 {
PEAK.load(Ordering::Relaxed) as f64 / (1024.0 * 1024.0)
}
#[tokio::main(flavor = "multi_thread")]
async fn main() {
dotenvy::dotenv().ok();
let url = env::var("DATABASE_URL")
.or_else(|_| env::var("OXICLOUD_DB_CONNECTION_STRING"))
.expect("set DATABASE_URL — the dev Postgres URL");
let n: usize = env::var("BENCH_EVENTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(4000);
let page_uids: usize = env::var("BENCH_PAGE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(500);
let passes: usize = env::var("BENCH_PASSES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(9);
let pool = Arc::new(
PgPoolOptions::new()
.max_connections(10)
.min_connections(10)
.acquire_timeout(Duration::from_secs(10))
.connect(&url)
.await
.expect("connect Postgres"),
);
let seeded = seed(&pool, n).await;
let repo = CalendarEventPgRepository::new(pool.clone());
let base_href = format!("/caldav/{}/", seeded.calendar_id);
println!(
"bench_caldav_stream — {n} events (20% exceptions), page={page_uids} uids, {passes} passes\n"
);
// ── [1] REPORT (multistatus) ────────────────────────────────────────────
// Warm-up + equivalence gate first.
let (_, _, before_bytes) = buffered_report(&repo, &seeded.calendar_id, &base_href).await;
let (_, _, after_bytes) =
streamed_report(&repo, &seeded.calendar_id, &base_href, page_uids).await;
let gate_report = before_bytes == after_bytes;
let mut b_wall = Vec::new();
let mut a_wall = Vec::new();
let mut a_ttfb = Vec::new();
for _ in 0..passes {
let (_, w, out) = buffered_report(&repo, &seeded.calendar_id, &base_href).await;
std::hint::black_box(out);
b_wall.push(w);
let (_, w, out) = streamed_report(&repo, &seeded.calendar_id, &base_href, page_uids).await;
std::hint::black_box(out);
a_wall.push(w);
a_ttfb.push(streamed_report_ttfb(&repo, &seeded.calendar_id, &base_href, page_uids).await);
}
// Peak-heap arms, measured in isolation.
reset_peak();
let (_, _, out) = buffered_report(&repo, &seeded.calendar_id, &base_href).await;
drop(out);
let peak_before = peak_mib();
reset_peak();
// Streamed peak: emulate the socket by dropping each chunk — reuse
// the pipeline but without accumulating (accumulation would charge
// the response size to the streaming arm).
{
use futures::TryStreamExt;
let t0 = Instant::now();
let report = report_shape();
let mut rows = repo.stream_events_uid_order(seeded.calendar_id);
let mut page: Vec<CalendarEventDto> = Vec::with_capacity(page_uids + 32);
loop {
let next = rows
.try_next()
.await
.expect("stream row")
.map(CalendarEventDto::from);
let flush = match &next {
Some(ev) => {
page.len() >= page_uids
&& page.last().is_some_and(|p| p.ical_uid != ev.ical_uid)
}
None => !page.is_empty(),
};
if flush {
let mut chunk = Vec::with_capacity(page.len() * 1024 + 128);
{
let mut w = quick_xml::Writer::new(&mut chunk);
CalDavAdapter::write_report_page(&mut w, &page, &report, &base_href)
.expect("page");
}
std::hint::black_box(&chunk);
page.clear();
}
match next {
Some(ev) => page.push(ev),
None => break,
}
}
std::hint::black_box(t0.elapsed());
}
let peak_after = peak_mib();
let bw = p50(b_wall);
let aw = p50(a_wall);
let at = p50(a_ttfb);
println!("[1] REPORT calendar-query (no range) TTFB ms wall ms peak heap MiB");
println!(" BEFORE (buffered) {bw:8.1} {bw:8.1} {peak_before:10.1}");
println!(
" AFTER (streamed) {at:8.1} {aw:8.1} {peak_after:10.1} TTFB {:.1}x, heap {:.1}x lower",
bw / at,
peak_before / peak_after
);
// ── [2] Collection GET (.ics) ───────────────────────────────────────────
let events_all: Vec<CalendarEventDto> = repo
.list_events_by_calendar(&seeded.calendar_id)
.await
.expect("list")
.into_iter()
.map(CalendarEventDto::from)
.collect();
let before_ics = before::generate_full_calendar_ical("Agenda grande", &events_all);
drop(events_all);
// Streamed ICS: header + per-page chunks + footer (the handler loop).
let mut after_ics = String::from(
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//OxiCloud//NONSGML Calendar//EN\r\nX-WR-CALNAME:Agenda grande\r\n",
);
let ics_pages: Vec<Vec<CalendarEventDto>> = {
use futures::TryStreamExt;
let mut rows = repo.stream_events_uid_order(seeded.calendar_id);
let mut pages = Vec::new();
let mut page: Vec<CalendarEventDto> = Vec::with_capacity(page_uids + 32);
while let Some(ev) = rows.try_next().await.expect("stream row") {
let ev = CalendarEventDto::from(ev);
if page.len() >= page_uids && page.last().is_some_and(|p| p.ical_uid != ev.ical_uid) {
pages.push(std::mem::take(&mut page));
}
page.push(ev);
}
if !page.is_empty() {
pages.push(page);
}
pages
};
for events in &ics_pages {
let events = &events[..];
let mut chunk = String::with_capacity(events.len() * 384);
for group in caldav_bench::group_events_by_uid(events) {
for event in group {
if let Some(vevent) = caldav_bench::extract_vevent_chunk(&event.ical_data) {
chunk.push_str(vevent);
if !chunk.ends_with('\n') {
chunk.push_str("\r\n");
}
}
}
}
after_ics.push_str(&chunk);
}
after_ics.push_str("END:VCALENDAR\r\n");
let gate_ics = before_ics == after_ics;
println!(
"[2] collection GET .ics: {} bytes, streamed == buffered: {}",
before_ics.len(),
if gate_ics { "OK" } else { "MISMATCH" }
);
cleanup(&pool, &seeded).await;
println!(
"\n[gate] multistatus byte-identical: {} · ICS byte-identical: {}",
if gate_report { "OK" } else { "FAILED" },
if gate_ics { "OK" } else { "FAILED" }
);
if !gate_report || !gate_ics {
std::process::exit(1);
}
}
+4 -4
View File
@@ -245,15 +245,15 @@ async fn main() {
.list_readable_by(user_id)
.await
.expect("repo list")
.into_iter()
.map(|d| (d.drive.id, d.root_folder_name))
.iter()
.map(|d| (d.drive.id, d.root_folder_name.clone()))
.collect();
let warm: Vec<(Uuid, String)> = repo
.list_readable_by(user_id)
.await
.expect("repo list warm")
.into_iter()
.map(|d| (d.drive.id, d.root_folder_name))
.iter()
.map(|d| (d.drive.id, d.root_folder_name.clone()))
.collect();
if before_rows != cold || cold != warm {
eprintln!(
+570
View File
@@ -0,0 +1,570 @@
//! Round-5 micro-allocation pack — per-request/per-row churn removed
//! from five hot paths. Each section is BEFORE (verbatim old shape) vs
//! AFTER (the shipped code or its exact pattern), with byte/structure
//! equality gates. No Postgres.
//!
//! [1] search suggest enrichment: entity clone + 3 field re-clones per
//! row → consume + move.
//! [2] `list_readable_by` warm hit: deep `Vec<DriveWithRootName>`
//! clone per request → `Arc` refcount bump.
//! [3] SPA listing rows (folder/recent/favorites handlers): raw
//! `Arc::from` per closed-set display field → `intern_display` /
//! `intern_mime` lookups.
//! [4] NC PROPFIND child hrefs: per-row re-encode of username + parent
//! path (`nc_href`) → prefix precomputed once + name-only encode.
//! [5] CardDAV REPORT (getetag poll): per-REPORT props clone +
//! per-contact href String + etag `format!` → borrowed props,
//! reused href buffer, exact-size quoting.
//!
//! Run (no Postgres needed):
//! cargo run --release --features bench --example bench_micro_allocs
//! Tunables (env): BENCH_ROWS (5000), BENCH_PASSES (60).
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 chrono::{TimeZone, Utc};
use oxicloud::application::adapters::carddav_adapter::{CardDavAdapter, CardDavReportType};
use oxicloud::application::adapters::webdav_adapter::QualifiedName;
use oxicloud::application::dtos::contact_dto::ContactDto;
use oxicloud::application::dtos::display_helpers::{
category_for, icon_class_for, icon_special_class_for, intern_display, intern_mime,
};
use oxicloud::application::dtos::file_dto::FileDto;
use oxicloud::application::dtos::search_dto::SearchSuggestionItem;
use oxicloud::domain::entities::drive::{Drive, DriveKind};
use oxicloud::domain::entities::file::File;
use oxicloud::domain::repositories::drive_repository::DriveWithRootName;
use oxicloud::interfaces::nextcloud::webdav_handler::nc_href;
use uuid::Uuid;
// ─── 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 p50(mut xs: Vec<f64>) -> f64 {
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
xs[xs.len() / 2]
}
fn time_passes<T>(passes: usize, mut f: impl FnMut() -> T) -> f64 {
let mut per = Vec::with_capacity(passes);
for _ in 0..passes {
let t0 = Instant::now();
black_box(f());
per.push(t0.elapsed().as_secs_f64() * 1e6);
}
p50(per)
}
fn allocs_of<T>(mut f: impl FnMut() -> T) -> u64 {
let s0 = ALLOC_CALLS.load(Ordering::Relaxed);
black_box(f());
ALLOC_CALLS.load(Ordering::Relaxed) - s0
}
// ─── Corpus builders ────────────────────────────────────────────────────────
fn make_files(n: usize) -> Vec<File> {
(0..n)
.map(|i| {
File::from_materialized_row(
Uuid::from_u128(i as u128).to_string(),
format!("documento-{i}.pdf"),
Some("/Personal/Proyectos/2026"),
1024 + i as u64,
"application/pdf".to_string(),
None,
1_700_000_000,
1_750_000_000,
format!("{:032x}", i),
None,
None,
)
.expect("file")
})
.collect()
}
fn compute_relevance(name: &str, q: &str) -> u32 {
if name.to_lowercase().contains(q) {
100
} else {
50
}
}
/// The suggest enrichment loop — BEFORE: per-row entity clone + field
/// re-clones (verbatim old shape, icon helper substituted identically
/// on both arms).
fn suggest_before(files: &[File], q: &str) -> Vec<SearchSuggestionItem> {
let mut out = Vec::new();
let query_lower = q.to_lowercase();
for file in files {
let file_dto = FileDto::from(file.clone());
let score = compute_relevance(&file_dto.name, &query_lower);
out.push(SearchSuggestionItem {
name: file_dto.name.clone(),
item_type: "file".to_string(),
id: file_dto.id.clone(),
path: file_dto.path.clone(),
icon_class: icon_class_for(&file_dto.name, &file_dto.mime_type).to_string(),
icon_special_class: icon_special_class_for(&file_dto.name, &file_dto.mime_type)
.to_string(),
relevance_score: score,
});
}
out
}
/// AFTER: consume + move (the shipped shape).
fn suggest_after(files: Vec<File>, q: &str) -> Vec<SearchSuggestionItem> {
let mut out = Vec::new();
let query_lower = q.to_lowercase();
for file in files {
let file_dto = FileDto::from(file);
let score = compute_relevance(&file_dto.name, &query_lower);
let icon_class = icon_class_for(&file_dto.name, &file_dto.mime_type).to_string();
let icon_special_class =
icon_special_class_for(&file_dto.name, &file_dto.mime_type).to_string();
out.push(SearchSuggestionItem {
name: file_dto.name,
item_type: "file".to_string(),
id: file_dto.id,
path: file_dto.path,
icon_class,
icon_special_class,
relevance_score: score,
});
}
out
}
fn make_drives(n: usize) -> Vec<DriveWithRootName> {
(0..n)
.map(|i| DriveWithRootName {
drive: Drive {
id: Uuid::from_u128(i as u128),
kind: if i == 0 {
DriveKind::Personal
} else {
DriveKind::Shared
},
default_for_user: (i == 0).then(|| Uuid::from_u128(999)),
root_folder_id: Uuid::from_u128(1000 + i as u128),
quota_bytes: Some(10_737_418_240),
used_bytes: 123_456_789,
policies: serde_json::json!({}),
created_at: Utc.with_ymd_and_hms(2026, 1, 1, 0, 0, 0).unwrap(),
updated_at: Utc.with_ymd_and_hms(2026, 7, 1, 0, 0, 0).unwrap(),
},
root_folder_name: format!("Drive número {i}"),
caller_role: None,
})
.collect()
}
fn make_contacts(n: usize) -> Vec<ContactDto> {
(0..n)
.map(|i| ContactDto {
id: Uuid::from_u128(i as u128).to_string(),
uid: format!("contact-{i:05}"),
etag: format!("{:016x}", i * 2_654_435_761u64 as usize),
full_name: Some(format!("Persona {i}")),
..ContactDto::default()
})
.collect()
}
// BEFORE replica of the CardDAV REPORT emitter (props.clone + per-row
// href String + etag format!) for the getetag poll shape — the
// address-data branch is never hit with this prop set, so the replica
// stays self-contained.
mod before_carddav {
use super::*;
use quick_xml::Writer;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
pub fn generate_contacts_response(
out: &mut Vec<u8>,
contacts: &[ContactDto],
report: &CardDavReportType,
base_href: &str,
) {
let mut xml_writer = Writer::new(out);
xml_writer
.write_event(Event::Start(
BytesStart::new("D:multistatus").with_attributes([
("xmlns:D", "DAV:"),
("xmlns:CR", "urn:ietf:params:xml:ns:carddav"),
]),
))
.unwrap();
let props = match report {
CardDavReportType::AddressbookQuery { props } => props.clone(),
CardDavReportType::AddressbookMultiget { props, .. } => props.clone(),
CardDavReportType::SyncCollection { props, .. } => props.clone(),
};
for contact in contacts {
let href = format!("{}{}.vcf", base_href, contact.uid);
xml_writer
.write_event(Event::Start(BytesStart::new("D:response")))
.unwrap();
xml_writer
.write_event(Event::Start(BytesStart::new("D:href")))
.unwrap();
xml_writer
.write_event(Event::Text(BytesText::new(&href)))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:href")))
.unwrap();
xml_writer
.write_event(Event::Start(BytesStart::new("D:propstat")))
.unwrap();
xml_writer
.write_event(Event::Start(BytesStart::new("D:prop")))
.unwrap();
for prop in &props {
match (prop.namespace.as_str(), prop.name.as_str()) {
("DAV:", "resourcetype") => {
xml_writer
.write_event(Event::Empty(BytesStart::new("D:resourcetype")))
.unwrap();
}
("DAV:", "getetag") => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getetag")))
.unwrap();
xml_writer
.write_event(Event::Text(BytesText::new(&format!(
"\"{}\"",
contact.etag
))))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:getetag")))
.unwrap();
}
("DAV:", "getcontenttype") => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getcontenttype")))
.unwrap();
xml_writer
.write_event(Event::Text(BytesText::new("text/vcard; charset=utf-8")))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:getcontenttype")))
.unwrap();
}
_ => {}
}
}
xml_writer
.write_event(Event::End(BytesEnd::new("D:prop")))
.unwrap();
xml_writer
.write_event(Event::Start(BytesStart::new("D:status")))
.unwrap();
xml_writer
.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:status")))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:propstat")))
.unwrap();
xml_writer
.write_event(Event::End(BytesEnd::new("D:response")))
.unwrap();
}
xml_writer
.write_event(Event::End(BytesEnd::new("D:multistatus")))
.unwrap();
}
}
fn main() {
let rows: usize = env::var("BENCH_ROWS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(5000);
let passes: usize = env::var("BENCH_PASSES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60);
let mut ok = true;
println!("bench_micro_allocs — {rows} rows, {passes} passes\n");
// ── [1] suggest enrichment ──────────────────────────────────────────────
{
let files = make_files(200); // suggest is limit-bounded (~10-200)
let t_b = time_passes(passes, || suggest_before(&files, "doc"));
// Production AFTER consumes the caller's Vec — no clone exists.
// The replay clone happens OUTSIDE the timed window.
let t_a = {
let mut per = Vec::with_capacity(passes);
for _ in 0..passes {
let corpus = files.clone();
let t0 = Instant::now();
black_box(suggest_after(corpus, "doc"));
per.push(t0.elapsed().as_secs_f64() * 1e6);
}
p50(per)
};
// Alloc parity: charge the corpus clone to neither arm by
// measuring BEFORE with its borrow (clones inside) and AFTER
// seeded from a pre-cloned Vec outside the counter window.
let a_b = allocs_of(|| suggest_before(&files, "doc")) as f64 / files.len() as f64;
let mut pre = Some(files.clone());
let a_a =
allocs_of(|| suggest_after(pre.take().unwrap(), "doc")) as f64 / files.len() as f64;
let g_b = suggest_before(&files, "doc");
let g_a = suggest_after(files.clone(), "doc");
let same = g_b.len() == g_a.len()
&& g_b.iter().zip(&g_a).all(|(x, y)| {
x.name == y.name && x.id == y.id && x.path == y.path && x.icon_class == y.icon_class
});
if !same {
eprintln!("GATE FAIL suggest");
ok = false;
}
println!("[1] suggest enrichment (200 rows) µs/pass allocs/row");
println!(" BEFORE (clone per row) {t_b:8.1} {a_b:7.2}");
println!(
" AFTER (consume + move) {t_a:8.1} {a_a:7.2} {:.2}x",
t_b / t_a
);
}
// ── [2] readable-drives warm hit ────────────────────────────────────────
{
let value = Arc::new(make_drives(3));
let cache: moka::sync::Cache<Uuid, Arc<Vec<DriveWithRootName>>> =
moka::sync::Cache::new(100);
let user = Uuid::from_u128(42);
cache.insert(user, value);
let hit_before = || {
let arc = cache.get(&user).expect("warm");
let v: Vec<DriveWithRootName> = (*arc).clone(); // old: deep clone out
v
};
let hit_after = || cache.get(&user).expect("warm"); // new: Arc bump
let n_iters = 10_000u32;
let t_b = time_passes(passes, || {
for _ in 0..n_iters {
black_box(hit_before());
}
}) / n_iters as f64
* 1000.0;
let t_a = time_passes(passes, || {
for _ in 0..n_iters {
black_box(hit_after());
}
}) / n_iters as f64
* 1000.0;
let a_b = allocs_of(hit_before);
let a_a = allocs_of(hit_after);
let g = hit_before();
let ga = hit_after();
if g.len() != ga.len() || g[0].root_folder_name != ga[0].root_folder_name {
eprintln!("GATE FAIL readable hit");
ok = false;
}
println!("[2] list_readable_by warm hit (3 drives) ns/hit allocs/hit");
println!(" BEFORE (deep Vec clone) {t_b:8.1} {a_b:7}");
println!(
" AFTER (Arc refcount bump) {t_a:8.1} {a_a:7} {:.1}x",
t_b / t_a
);
}
// ── [3] SPA listing closed-set fields ───────────────────────────────────
{
let names: Vec<String> = (0..rows).map(|i| format!("informe-{i}.pdf")).collect();
let mime = "application/pdf";
let row_before = |name: &str| {
(
Arc::<str>::from(mime),
Arc::<str>::from(icon_class_for(name, mime)),
Arc::<str>::from(icon_special_class_for(name, mime)),
Arc::<str>::from(category_for(name, mime)),
)
};
let row_after = |name: &str| {
(
intern_mime(mime),
intern_display(icon_class_for(name, mime)),
intern_display(icon_special_class_for(name, mime)),
intern_display(category_for(name, mime)),
)
};
let t_b = time_passes(passes, || {
for n in &names {
black_box(row_before(n));
}
}) / rows as f64
* 1000.0;
let t_a = time_passes(passes, || {
for n in &names {
black_box(row_after(n));
}
}) / rows as f64
* 1000.0;
let a_b = allocs_of(|| row_before(&names[0]));
let a_a = allocs_of(|| row_after(&names[0]));
let (bm, bi, bs, bc) = row_before(&names[0]);
let (am, ai, as_, ac) = row_after(&names[0]);
if *bm != *am || *bi != *ai || *bs != *as_ || *bc != *ac {
eprintln!("GATE FAIL interning content");
ok = false;
}
println!("[3] listing closed-set fields ns/row allocs/row");
println!(" BEFORE (Arc::from ×4) {t_b:8.1} {a_b:7}");
println!(
" AFTER (intern lookups ×4) {t_a:8.1} {a_a:7} {:.1}x",
t_b / t_a
);
}
// ── [4] NC PROPFIND child hrefs ─────────────────────────────────────────
{
let username = "ana.garcia";
let subpath = "Personal/Proyectos 2026/Diseño";
let names: Vec<String> = (0..rows)
.map(|i| format!("archivo con espacios {i}.png"))
.collect();
// Verbatim replica of the production shape — `subpath` is a
// const here, so the emptiness test is statically known.
#[allow(clippy::const_is_empty)]
let href_before = |name: &str| {
let child_sub = if subpath.is_empty() {
name.to_string()
} else {
format!("{}/{}", subpath.trim_end_matches('/'), name)
};
nc_href(username, &child_sub)
};
let prefix = {
let base = nc_href(username, subpath);
if base.ends_with('/') {
base
} else {
format!("{base}/")
}
};
let href_after = |name: &str| format!("{}{}", prefix, urlencoding::encode(name));
let t_b = time_passes(passes, || {
for n in &names {
black_box(href_before(n));
}
}) / rows as f64
* 1000.0;
let t_a = time_passes(passes, || {
for n in &names {
black_box(href_after(n));
}
}) / rows as f64
* 1000.0;
let a_b = allocs_of(|| href_before(&names[0]));
let a_a = allocs_of(|| href_after(&names[0]));
for n in names.iter().take(50) {
if href_before(n) != href_after(n) {
eprintln!("GATE FAIL href: {} != {}", href_before(n), href_after(n));
ok = false;
break;
}
}
println!("[4] NC child hrefs (depth-3 parent) ns/row allocs/row");
println!(" BEFORE (nc_href per row) {t_b:8.1} {a_b:7}");
println!(
" AFTER (prefix + name encode) {t_a:8.1} {a_a:7} {:.1}x",
t_b / t_a
);
}
// ── [5] CardDAV REPORT getetag poll ─────────────────────────────────────
{
let contacts = make_contacts(rows);
let report = CardDavReportType::AddressbookQuery {
props: vec![
QualifiedName::new("DAV:", "getetag"),
QualifiedName::new("DAV:", "getcontenttype"),
],
};
let base = "/carddav/libreta/";
let run_before = || {
let mut out = Vec::with_capacity(contacts.len() * 256);
before_carddav::generate_contacts_response(&mut out, &contacts, &report, base);
out
};
let run_after = || {
let mut out = Vec::with_capacity(contacts.len() * 256);
CardDavAdapter::generate_contacts_response(&mut out, &contacts, &report, base)
.expect("generate");
out
};
let t_b = time_passes(passes.min(30), run_before);
let t_a = time_passes(passes.min(30), run_after);
let xb = run_before();
let xa = run_after();
if xb != xa {
let at = xb.iter().zip(&xa).position(|(a, b)| a != b).unwrap_or(0);
eprintln!(
"GATE FAIL carddav at byte {at}: …{}… vs …{}…",
String::from_utf8_lossy(&xb[at.saturating_sub(60)..(at + 60).min(xb.len())]),
String::from_utf8_lossy(&xa[at.saturating_sub(60)..(at + 60).min(xa.len())]),
);
ok = false;
}
println!("[5] CardDAV REPORT getetag ({rows} contacts) µs/report");
println!(" BEFORE (clone + format! churn) {t_b:8.1}");
println!(
" AFTER (borrow + reuse + exact-size) {t_a:8.1} {:.2}x",
t_b / t_a
);
}
println!(
"\n[gate] {}",
if ok {
"OK (identical outputs)"
} else {
"FAILED"
}
);
if !ok {
std::process::exit(1);
}
}