Files
Oxicloud/examples/bench_log_writer.rs
Claude 221c1f31b0 perf: round 11 — StoragePath joined-only, classifier fusion, memoized bodies, query-shape pack, SPA fine-grained stars
Backend (each change benchmark-gated with BEFORE replicas + equivalence
gates; see examples/bench_round11_micro.rs, bench_round11_queries.rs,
bench_log_writer.rs and benches/ROUND11.md — final numbers land in the
follow-up doc commit):

- StoragePath re-representation: single canonical joined String, segments
  derived on demand; File/Folder drop the duplicated path_string field
  (4000→1000 allocs per 500-row listing page)
- Display classifier fusion: classify_display shares one stack-lowered
  extension across the three decision trees; call sites in FileDto,
  folder/favorites/recent handlers, trash, path-resolver (+ interning
  where Arc::from was still used)
- /status.php and /openapi.json memoized into OnceLock<Bytes> (openapi
  rebuilt a 171 KiB spec per request: 2.8 ms → 18 ns)
- NC upload-session PROPFIND: write! + pre-sized body + stack RFC2822
  dates (2.3-2.6x, 2582→772 allocs at 256 chunks)
- REST download: dead FileDto clone removed (capture mime/size + move)
- CalendarEventDto/TrashedItem into_parts moves (11 KiB ical_data memcpy
  gone per CalDAV row); CardDAV getlastmodified stack render
- 4xx path: borrowed ErrorResponse serialize, ErrorKind::as_str,
  not_found/already_exists clone kill
- vCard emit via write!; search page moved out with into_iter skip/take;
  content-hit UUIDs parsed once; group last-user check via HashSet
- RateLimiter: lock-free get + insert (and_upsert_with variant REJECTED
  by benchmark); CSRF token borrow-compare + borrowed cookie extraction
- Thumbnail/preview ETags built from as_str (Debug-identical bytes)
- Encrypted backend: encrypt_in_place_detached single-buffer write path,
  chunk-sized reserve in collect_stream; retry labels made lazy
- PG: deferred upload registration 3→1 round-trips (persist_file CTE
  template); direct_grant_cache for Calendar/AddressBook/Playlist authz
  (single-flight + set_role/clear_role invalidation); expand_user
  tokio::join!; geo clusters min(uuid)::text; recluster face assignment
  batched into one UNNEST update
- People recluster cosine: norms precomputed once (bit-identical gate)
- NC capabilities poll logs demoted to debug; tracing-appender dep added
  for the log-writer benchmark

Frontend:
- ResourceList.selectedEntries O(N)-per-toggle → id-index projection
  O(k log k); favorites/recent consume the batchToolbar snippet param and
  drop their duplicate filter + dead selectedIds mirror
- Recent: star state via new favoriteIds prop — a star click no longer
  rebuilds all N entries
- admin timeAgo >30d fallback uses the cached Intl.DateTimeFormat
- vitest gates in src/lib/components/round11.bench.test.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABhTEHuGujvwoodh67Kga7
2026-07-18 22:02:00 +00:00

158 lines
5.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Round-11 log-writer benchmark — synchronous fmt layer (stdout under a
//! global lock, on the async workers) vs `tracing_appender::non_blocking`
//! with `lossy(false)` (audit lines must never drop; the emitting thread
//! blocks only if the 128k-line channel fills).
//!
//! Two writer profiles:
//! - fast: stdout redirected to /dev/null (best case for the sync arm)
//! - slow: a writer that burns ~20 µs per line under the same lock,
//! modelling a laggy pipe / journald / TTY consumer
//!
//! The global subscriber can only be installed once per process, so the
//! arm is chosen via env and the harness runs the binary once per arm:
//!
//! BENCH_LOG_ARM=sync cargo run --release --features bench --example bench_log_writer >/dev/null
//! BENCH_LOG_ARM=nonblocking cargo run --release --features bench --example bench_log_writer >/dev/null
//! BENCH_LOG_WRITER=slow BENCH_LOG_ARM=... (slow-writer profile)
//!
//! Measurements print to stderr. Emits 4 workers × 25k events; reports
//! total wall, per-event p50/p99/p999 emit latency, and (for the
//! non-blocking arm) confirms zero dropped lines via a line count gate
//! (lossy(false) + guard flush).
use std::io::Write;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
static LINES: AtomicU64 = AtomicU64::new(0);
/// Counts lines then forwards to stdout (which the run command redirects
/// to /dev/null). The `slow` profile burns ~20 µs per write while holding
/// the caller's lock, modelling a slow consumer.
struct CountingWriter {
slow: bool,
}
impl Write for CountingWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
LINES.fetch_add(1, Ordering::Relaxed);
if self.slow {
let t = Instant::now();
while t.elapsed().as_micros() < 20 {
std::hint::spin_loop();
}
}
std::io::stdout().write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
std::io::stdout().flush()
}
}
#[derive(Clone)]
struct MakeCounting {
slow: bool,
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for MakeCounting {
type Writer = CountingWriter;
fn make_writer(&'a self) -> Self::Writer {
CountingWriter { slow: self.slow }
}
}
fn main() {
let arm = std::env::var("BENCH_LOG_ARM").unwrap_or_else(|_| "sync".into());
let slow = std::env::var("BENCH_LOG_WRITER").as_deref() == Ok("slow");
let workers = 4usize;
let per_worker = 25_000u64;
// Same filter shape as main.rs.
let filter = tracing_subscriber::EnvFilter::new("info,http=warn,http::web=error");
// Keep the non-blocking guard alive for the whole run.
let _guard: Option<tracing_appender::non_blocking::WorkerGuard> = match arm.as_str() {
"nonblocking" => {
let (nb, guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
.lossy(false)
.finish(CountingWriter { slow });
tracing_subscriber::registry()
.with(filter)
.with(tracing_subscriber::fmt::layer().with_writer(nb))
.init();
Some(guard)
}
_ => {
tracing_subscriber::registry()
.with(filter)
.with(tracing_subscriber::fmt::layer().with_writer(MakeCounting { slow }))
.init();
None
}
};
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(workers)
.enable_all()
.build()
.unwrap();
let (wall, mut lat_us): (f64, Vec<f64>) = rt.block_on(async {
let t0 = Instant::now();
let mut handles = Vec::new();
for w in 0..workers {
handles.push(tokio::spawn(async move {
let mut lats = Vec::with_capacity(per_worker as usize);
for i in 0..per_worker {
let t = Instant::now();
tracing::info!(worker = w, seq = i, "bench log line with a few fields");
lats.push(t.elapsed().as_secs_f64() * 1e6);
if i % 512 == 0 {
tokio::task::yield_now().await;
}
}
lats
}));
}
let mut all = Vec::new();
for h in handles {
all.extend(h.await.unwrap());
}
(t0.elapsed().as_secs_f64(), all)
});
// Flush (drop guard for non-blocking) before counting lines.
drop(_guard);
std::thread::sleep(std::time::Duration::from_millis(200));
lat_us.sort_by(|a, b| a.partial_cmp(b).unwrap());
let pct = |p: f64| lat_us[((lat_us.len() as f64 * p) as usize).min(lat_us.len() - 1)];
let total = workers as u64 * per_worker;
let emitted = LINES.load(Ordering::Relaxed);
eprintln!(
"arm={arm} writer={} events={total} wall={:.3}s ({:.0} ev/s)",
if slow {
"slow(20µs)"
} else {
"fast(/dev/null)"
},
wall,
total as f64 / wall
);
eprintln!(
" emit latency µs: p50={:.1} p99={:.1} p999={:.1} max={:.1}",
pct(0.50),
pct(0.99),
pct(0.999),
lat_us[lat_us.len() - 1]
);
eprintln!(
" gate[no lines dropped]: {}",
if emitted >= total { "OK" } else { "FAILED" }
);
}