Files
Oxicloud/examples/bench_propfind_xml.rs
T
Claude 12dc648cff perf: round 4 — one-pass row paths, drive-selector cache, CalDAV single-parse, streamed Azure, batched hydration
Nine benchmark-gated changes (benches/ROUND4.md; every one ships with a
BEFORE/AFTER bench + equivalence gate, rollback rule as ROUND2/3):

- Row→entity path build: one-pass StoragePath::from_folder_and_name /
  from_joined + normalize_storage_name_owned + alloc-free Display —
  743→417 ns/file-row (1.78x), −5 allocs/row on every listing surface.
- WebDAV drive-selector: per-user readable_cache (single-flight, 30 s
  TTL, explicit invalidation incl. membership + group changes) replaces
  the grants join per request — 441 µs → 0.8 µs (~550x), 0 queries warm.
- CalDAV from_ical/update_ical_data: 8 full IcalParser runs per VEVENT
  → 1 (7.1x per PUT, 4.4x on 50-event imports); alloc-free split_vevents,
  chunk scan without the whole-body uppercase copy (1.4x), borrowed-key
  UID grouping (1.3x), REPORT props no longer cloned.
- PROPFIND emit: partition Vecs dropped (single-pass 404 list) + stack
  rendered RFC 3339/2822 dates, sizes, quoted etags (common::fmt,
  chrono-byte-identical, sweep-tested) on both DAV surfaces — 1.22x
  per page, 17.9→12.0 allocs/row.
- Grant-listing hydration: calendars/address books/playlists batch
  hydrate via = ANY($1) — 15 serial queries → 1 (~13x per sync poll).
- user-flags cache: get→insert → try_get_with single-flight (32→1
  queries per cold herd).
- Azure downloads: whole-blob Vec buffering → streamed SDK pages —
  TTFB 349→4 ms (87x), peak heap 480→1.9 MiB (254x) on 256 MiB blobs;
  new OXICLOUD_AZURE_ENDPOINT_URL override (Azurite/bench hook).
- Face indexing: unbounded per-image tokio::spawn → core-count
  semaphore, permit before blob read — peak heap 1175→176 MiB (6.7x).

Checks: cargo fmt, clippy --all-features --all-targets -D warnings,
cargo test --workspace (523 passed) + --features test_utils. hurl API
suite and dockerized integration DB not runnable in this environment —
left to CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-17 13:48:37 +00:00

802 lines
34 KiB
Rust

//! PROPFIND per-row XML emit benchmark — Vec churn + format-interpreter
//! dates (ROUND4).
//!
//! For EVERY file/folder row of every PROPFIND page the old writers paid:
//! • a `partition` into two throwaway `Vec<&QualifiedName>`s (+ a third
//! for the 404 list) — even though the requested-props writer already
//! skips unknown names itself;
//! • `to_rfc3339()` + `to_rfc2822()` — chrono's format-spec interpreter
//! plus a heap String each;
//! • `size.to_string()` and a `format!("\"{etag}\"")`.
//!
//! AFTER: single-pass 404 computation (usually-empty Vec), stack-rendered
//! dates/sizes (`common::fmt`, byte-identical, chrono fallback for
//! out-of-range), exactly-sized etag quoting.
//!
//! The OLD writers are copied verbatim into `mod before`; the gate
//! asserts byte-identical multistatus XML for named-prop (typical sync
//! client set + unknown props), AllProp (with quota), and dead-prop
//! carrying rows. Exit 1 on any diff.
//!
//! Run (no Postgres needed):
//! cargo run --release --features bench --example bench_propfind_xml
//! Tunables (env): BENCH_ROWS (1000), BENCH_PASSES (200)
use std::alloc::{GlobalAlloc, Layout, System};
use std::env;
use std::hint::black_box;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use oxicloud::application::adapters::webdav_adapter::{
PropFindRequest, PropFindType, QualifiedName, bench as dav_bench,
};
use oxicloud::application::dtos::file_dto::FileDto;
use oxicloud::application::dtos::folder_dto::FolderDto;
use oxicloud::domain::entities::file::File;
use oxicloud::domain::entities::folder::Folder;
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;
// ─── BEFORE: verbatim copy of the old per-row writers ───────────────────────
#[allow(clippy::all)]
mod before {
use chrono::Utc;
use oxicloud::application::adapters::webdav_adapter::{
PropFindRequest, PropFindType, QualifiedName,
};
use oxicloud::application::dtos::file_dto::FileDto;
use oxicloud::application::dtos::folder_dto::FolderDto;
use quick_xml::Writer;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use std::io::Write;
type Result<T> = std::result::Result<T, quick_xml::Error>;
fn folder_prop_is_known(prop: &QualifiedName, quota: Option<(i64, Option<i64>)>) -> bool {
if prop.namespace != "DAV:" {
return false;
}
match prop.name.as_str() {
"resourcetype" | "displayname" | "creationdate" | "getlastmodified" | "getetag"
| "getcontentlength" | "getcontenttype" => true,
"quota-used-bytes" => quota.is_some(),
"quota-available-bytes" => quota.is_some_and(|(_, available)| available.is_some()),
_ => false,
}
}
fn file_prop_is_known(prop: &QualifiedName) -> bool {
prop.namespace == "DAV:"
&& matches!(
prop.name.as_str(),
"resourcetype"
| "displayname"
| "getcontenttype"
| "getcontentlength"
| "creationdate"
| "getlastmodified"
| "getetag"
)
}
fn write_qname_empty<W: Write>(xml_writer: &mut Writer<W>, prop: &QualifiedName) -> Result<()> {
if prop.namespace.is_empty() {
xml_writer.write_event(Event::Empty(BytesStart::new(prop.name.as_str())))?;
} else if prop.namespace == "DAV:" {
xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?;
} else {
let tag = format!("X:{}", prop.name);
let mut start = BytesStart::new(tag.as_str());
start.push_attribute(("xmlns:X", prop.namespace.as_str()));
xml_writer.write_event(Event::Empty(start))?;
}
Ok(())
}
fn write_unknown_props_404<W: Write>(
xml_writer: &mut Writer<W>,
unknown: &[&QualifiedName],
) -> Result<()> {
if unknown.is_empty() {
return Ok(());
}
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
for prop in unknown {
write_qname_empty(xml_writer, prop)?;
}
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 404 Not Found")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
Ok(())
}
fn write_dead_props_propstat<W: Write>(
xml_writer: &mut Writer<W>,
dead_props: &[(QualifiedName, Option<String>)],
) -> Result<()> {
if dead_props.is_empty() {
return Ok(());
}
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
for (name, value) in dead_props {
let tag = if name.namespace.is_empty() {
name.name.clone()
} else {
format!("X:{}", name.name)
};
let mut start = BytesStart::new(tag.as_str());
if !name.namespace.is_empty() {
start.push_attribute(("xmlns:X", name.namespace.as_str()));
}
match value {
Some(v) if !v.is_empty() => {
xml_writer.write_event(Event::Start(start))?;
xml_writer.write_event(Event::Text(BytesText::new(v)))?;
xml_writer.write_event(Event::End(BytesEnd::new(tag.as_str())))?;
}
_ => {
xml_writer.write_event(Event::Empty(start))?;
}
}
}
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
Ok(())
}
fn write_quota_props<W: Write>(
xml_writer: &mut Writer<W>,
used_bytes: i64,
available_bytes: Option<i64>,
) -> Result<()> {
xml_writer.write_event(Event::Start(BytesStart::new("D:quota-used-bytes")))?;
xml_writer.write_event(Event::Text(BytesText::new(&used_bytes.to_string())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:quota-used-bytes")))?;
if let Some(available_bytes) = available_bytes {
xml_writer.write_event(Event::Start(BytesStart::new("D:quota-available-bytes")))?;
xml_writer.write_event(Event::Text(BytesText::new(&available_bytes.to_string())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:quota-available-bytes")))?;
}
Ok(())
}
fn write_folder_standard_props<W: Write>(
xml_writer: &mut Writer<W>,
folder: &FolderDto,
quota: Option<(i64, Option<i64>)>,
) -> Result<()> {
xml_writer.write_event(Event::Start(BytesStart::new("D:resourcetype")))?;
xml_writer.write_event(Event::Empty(BytesStart::new("D:collection")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:resourcetype")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
xml_writer.write_event(Event::Text(BytesText::new(&folder.name)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
let created_at = chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
let modified_at = chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer.write_event(Event::Text(BytesText::new(&format!("\"{}\"", folder.etag))))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
xml_writer.write_event(Event::Text(BytesText::new("0")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
xml_writer.write_event(Event::Text(BytesText::new("httpd/unix-directory")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
if let Some((used, available)) = quota {
write_quota_props(xml_writer, used, available)?;
}
Ok(())
}
fn write_file_standard_props<W: Write>(
xml_writer: &mut Writer<W>,
file: &FileDto,
) -> Result<()> {
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
xml_writer.write_event(Event::Text(BytesText::new(&file.name)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
xml_writer.write_event(Event::Text(BytesText::new(&file.mime_type)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
xml_writer.write_event(Event::Text(BytesText::new(&file.size.to_string())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
let created_at = chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
let modified_at = chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer.write_event(Event::Text(BytesText::new(&format!("\"{}\"", file.etag))))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
Ok(())
}
fn write_folder_requested_props<W: Write>(
xml_writer: &mut Writer<W>,
folder: &FolderDto,
props: &[&QualifiedName],
quota: Option<(i64, Option<i64>)>,
) -> Result<()> {
for prop in props {
if prop.namespace == "DAV:" {
match prop.name.as_str() {
"resourcetype" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:resourcetype")))?;
xml_writer.write_event(Event::Empty(BytesStart::new("D:collection")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:resourcetype")))?;
}
"displayname" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
xml_writer.write_event(Event::Text(BytesText::new(&folder.name)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
}
"creationdate" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
let created_at =
chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer
.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
}
"getlastmodified" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
let modified_at =
chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer
.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
}
"getetag" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer.write_event(Event::Text(BytesText::new(&format!(
"\"{}\"",
folder.etag
))))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
}
"getcontentlength" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
xml_writer.write_event(Event::Text(BytesText::new("0")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
}
"getcontenttype" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
xml_writer
.write_event(Event::Text(BytesText::new("httpd/unix-directory")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
}
"quota-used-bytes" => {
if let Some((used, _)) = quota {
xml_writer
.write_event(Event::Start(BytesStart::new("D:quota-used-bytes")))?;
xml_writer
.write_event(Event::Text(BytesText::new(&used.to_string())))?;
xml_writer
.write_event(Event::End(BytesEnd::new("D:quota-used-bytes")))?;
}
}
"quota-available-bytes" => {
if let Some((_, Some(available))) = quota {
xml_writer.write_event(Event::Start(BytesStart::new(
"D:quota-available-bytes",
)))?;
xml_writer
.write_event(Event::Text(BytesText::new(&available.to_string())))?;
xml_writer.write_event(Event::End(BytesEnd::new(
"D:quota-available-bytes",
)))?;
}
}
_ => {}
}
}
}
Ok(())
}
fn write_file_requested_props<W: Write>(
xml_writer: &mut Writer<W>,
file: &FileDto,
props: &[&QualifiedName],
) -> Result<()> {
for prop in props {
if prop.namespace == "DAV:" {
match prop.name.as_str() {
"resourcetype" => {
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
}
"displayname" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
xml_writer.write_event(Event::Text(BytesText::new(&file.name)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
}
"getcontenttype" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
xml_writer.write_event(Event::Text(BytesText::new(&file.mime_type)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
}
"getcontentlength" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
xml_writer
.write_event(Event::Text(BytesText::new(&file.size.to_string())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
}
"creationdate" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
let created_at =
chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer
.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
}
"getlastmodified" => {
xml_writer
.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
let modified_at =
chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
.unwrap_or_else(Utc::now);
xml_writer
.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
}
"getetag" => {
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer.write_event(Event::Text(BytesText::new(&format!(
"\"{}\"",
file.etag
))))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
}
_ => {}
}
}
}
Ok(())
}
pub fn write_file_response_with_dead_props<W: Write>(
xml_writer: &mut Writer<W>,
file: &FileDto,
request: &PropFindRequest,
href: &str,
dead_props: &[(QualifiedName, Option<String>)],
) -> Result<()> {
xml_writer.write_event(Event::Start(BytesStart::new("D:response")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
let relevant_dead: Vec<_> = match &request.prop_find_type {
PropFindType::Prop(requested) => dead_props
.iter()
.filter(|(name, _)| requested.iter().any(|r| r == name))
.cloned()
.collect(),
PropFindType::AllProp => dead_props.to_vec(),
PropFindType::PropName => vec![],
};
let dead_name_set: std::collections::HashSet<&QualifiedName> =
relevant_dead.iter().map(|(n, _)| n).collect();
match &request.prop_find_type {
PropFindType::Prop(props) => {
let (known, unknown): (Vec<_>, Vec<_>) =
props.iter().partition(|p| file_prop_is_known(p));
let truly_unknown: Vec<_> = unknown
.into_iter()
.filter(|p| !dead_name_set.contains(*p))
.collect();
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
write_file_requested_props(xml_writer, file, &known)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
write_unknown_props_404(xml_writer, &truly_unknown)?;
}
other => {
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
match other {
PropFindType::AllProp => {
write_file_standard_props(xml_writer, file)?;
}
PropFindType::PropName => {
// not exercised in this bench
}
PropFindType::Prop(_) => unreachable!(),
}
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
}
}
write_dead_props_propstat(xml_writer, &relevant_dead)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
Ok(())
}
pub fn write_folder_response_with_dead_props<W: Write>(
xml_writer: &mut Writer<W>,
folder: &FolderDto,
request: &PropFindRequest,
href: &str,
dead_props: &[(QualifiedName, Option<String>)],
quota: Option<(i64, Option<i64>)>,
) -> Result<()> {
xml_writer.write_event(Event::Start(BytesStart::new("D:response")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
let relevant_dead: Vec<_> = match &request.prop_find_type {
PropFindType::Prop(requested) => dead_props
.iter()
.filter(|(name, _)| requested.iter().any(|r| r == name))
.cloned()
.collect(),
PropFindType::AllProp => dead_props.to_vec(),
PropFindType::PropName => vec![],
};
let dead_name_set: std::collections::HashSet<&QualifiedName> =
relevant_dead.iter().map(|(n, _)| n).collect();
match &request.prop_find_type {
PropFindType::Prop(props) => {
let (known, unknown): (Vec<_>, Vec<_>) =
props.iter().partition(|p| folder_prop_is_known(p, quota));
let truly_unknown: Vec<_> = unknown
.into_iter()
.filter(|p| !dead_name_set.contains(*p))
.collect();
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
write_folder_requested_props(xml_writer, folder, &known, quota)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
write_unknown_props_404(xml_writer, &truly_unknown)?;
}
other => {
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
match other {
PropFindType::AllProp => {
write_folder_standard_props(xml_writer, folder, quota)?;
}
PropFindType::PropName => {}
PropFindType::Prop(_) => unreachable!(),
}
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
}
}
write_dead_props_propstat(xml_writer, &relevant_dead)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
Ok(())
}
}
// ─── Corpus ─────────────────────────────────────────────────────────────────
fn build_files(rows: usize) -> Vec<FileDto> {
(0..rows)
.map(|i| {
// Timestamp mix: epoch edge, padded-day dates, recent, far future.
let created = [0u64, 1_120_176_000, 1_700_000_000, 4_102_444_799][i % 4];
let f = File::from_materialized_row(
Uuid::from_u128(i as u128).to_string(),
format!("informe-{i}.pdf"),
Some("/Personal/Projects/2026"),
(i as u64) * 3_517 + 42,
"application/pdf".to_string(),
Some(Uuid::nil().to_string()),
created,
created + 86_400 * (i as u64 % 300),
format!("{:032x}", i * 2_654_435_761),
None,
None,
)
.expect("valid file");
FileDto::from(f)
})
.collect()
}
fn build_folders(rows: usize) -> Vec<FolderDto> {
(0..rows)
.map(|i| {
let created = [0u64, 1_120_176_000, 1_700_000_000, 4_102_444_799][i % 4];
let f = Folder::from_materialized_row(
Uuid::from_u128((1_000_000 + i) as u128).to_string(),
format!("Carpeta {i}"),
format!("/Personal/Carpeta {i}"),
None,
Uuid::nil(),
created,
created + 3_600,
created + 7_200,
None,
None,
)
.expect("valid folder");
FolderDto::from(f)
})
.collect()
}
/// The prop set DAVx⁵/rclone-style clients poll with, plus two unknown
/// names so the 404 path is exercised.
fn sync_request() -> PropFindRequest {
PropFindRequest {
prop_find_type: PropFindType::Prop(vec![
QualifiedName::new("DAV:", "resourcetype"),
QualifiedName::new("DAV:", "displayname"),
QualifiedName::new("DAV:", "getcontenttype"),
QualifiedName::new("DAV:", "getcontentlength"),
QualifiedName::new("DAV:", "getlastmodified"),
QualifiedName::new("DAV:", "getetag"),
QualifiedName::new("DAV:", "lockdiscovery"),
QualifiedName::new("http://owncloud.org/ns", "fileid"),
]),
}
}
fn allprop_request() -> PropFindRequest {
PropFindRequest {
prop_find_type: PropFindType::AllProp,
}
}
fn p50(mut xs: Vec<f64>) -> f64 {
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
xs[xs.len() / 2]
}
const QUOTA: Option<(i64, Option<i64>)> = Some((123_456_789, Some(9_876_543_210)));
fn render_before(
files: &[FileDto],
folders: &[FolderDto],
request: &PropFindRequest,
dead: &[(QualifiedName, Option<String>)],
) -> Vec<u8> {
let mut out = Vec::with_capacity(1 << 20);
let mut w = quick_xml::Writer::new(&mut out);
for (i, folder) in folders.iter().enumerate() {
let dead = if i % 7 == 0 { dead } else { &[] };
before::write_folder_response_with_dead_props(
&mut w,
folder,
request,
"/webdav/Personal/",
dead,
QUOTA,
)
.expect("before folder row");
}
for (i, file) in files.iter().enumerate() {
let dead = if i % 7 == 0 { dead } else { &[] };
before::write_file_response_with_dead_props(
&mut w,
file,
request,
"/webdav/Personal/informe.pdf",
dead,
)
.expect("before file row");
}
out
}
fn render_after(
files: &[FileDto],
folders: &[FolderDto],
request: &PropFindRequest,
dead: &[(QualifiedName, Option<String>)],
) -> Vec<u8> {
let mut out = Vec::with_capacity(1 << 20);
let mut w = quick_xml::Writer::new(&mut out);
for (i, folder) in folders.iter().enumerate() {
let dead = if i % 7 == 0 { dead } else { &[] };
dav_bench::write_folder_propfind_row(
&mut w,
folder,
request,
"/webdav/Personal/",
dead,
QUOTA,
)
.expect("after folder row");
}
for (i, file) in files.iter().enumerate() {
let dead = if i % 7 == 0 { dead } else { &[] };
dav_bench::write_file_propfind_row(
&mut w,
file,
request,
"/webdav/Personal/informe.pdf",
dead,
)
.expect("after file row");
}
out
}
fn main() {
let rows: usize = env::var("BENCH_ROWS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1000);
let passes: usize = env::var("BENCH_PASSES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(200);
let files = build_files(rows);
let folders = build_folders(rows / 10);
let total_rows = files.len() + folders.len();
let dead: Vec<(QualifiedName, Option<String>)> = vec![(
QualifiedName::new("http://example.com/ns", "color"),
Some("azul".to_string()),
)];
let sync_req = sync_request();
let all_req = allprop_request();
println!(
"bench_propfind_xml — {} files + {} folders/page, {passes} passes\n",
files.len(),
folders.len()
);
for (label, req) in [("named-prop (sync set)", &sync_req), ("allprop", &all_req)] {
let mut lat_before = Vec::with_capacity(passes);
let mut lat_after = Vec::with_capacity(passes);
for _ in 0..passes {
let t0 = Instant::now();
black_box(render_before(&files, &folders, req, &dead));
lat_before.push(t0.elapsed().as_secs_f64() * 1e6);
let t0 = Instant::now();
black_box(render_after(&files, &folders, req, &dead));
lat_after.push(t0.elapsed().as_secs_f64() * 1e6);
}
let b = p50(lat_before);
let a = p50(lat_after);
let s0 = ALLOC_CALLS.load(Ordering::Relaxed);
black_box(render_before(&files, &folders, req, &dead));
let ab = (ALLOC_CALLS.load(Ordering::Relaxed) - s0) as f64 / total_rows as f64;
let s0 = ALLOC_CALLS.load(Ordering::Relaxed);
black_box(render_after(&files, &folders, req, &dead));
let aa = (ALLOC_CALLS.load(Ordering::Relaxed) - s0) as f64 / total_rows as f64;
println!("[{label}] µs/page (p50) + allocs/row");
println!(" BEFORE {b:9.1} µs {ab:6.2} allocs/row");
println!(
" AFTER {a:9.1} µs {aa:6.2} allocs/row {:.2}x",
b / a
);
}
// ── Equivalence gate: byte-identical multistatus XML ────────────────────
let mut ok = true;
for req in [&sync_req, &all_req] {
let xb = render_before(&files, &folders, req, &dead);
let xa = render_after(&files, &folders, req, &dead);
if xb != xa {
ok = false;
let diff_at = xb.iter().zip(&xa).position(|(a, b)| a != b).unwrap_or(0);
let lo = diff_at.saturating_sub(120);
eprintln!(
"GATE FAIL ({:?}): first diff at byte {diff_at}\n BEFORE: …{}…\n AFTER: …{}…",
match req.prop_find_type {
PropFindType::Prop(_) => "prop",
PropFindType::AllProp => "allprop",
PropFindType::PropName => "propname",
},
String::from_utf8_lossy(&xb[lo..(diff_at + 120).min(xb.len())]),
String::from_utf8_lossy(&xa[lo..(diff_at + 120).min(xa.len())]),
);
}
}
println!(
"\n[gate] multistatus XML: {}",
if ok { "OK (byte-identical)" } else { "FAILED" }
);
if !ok {
std::process::exit(1);
}
}