diff --git a/benches/perf.rs b/benches/perf.rs index f26edc3..f817085 100644 --- a/benches/perf.rs +++ b/benches/perf.rs @@ -4,9 +4,13 @@ //! First run establishes baseline. Subsequent runs report regressions. use std::fs; +use std::hint::black_box; use std::path::PathBuf; use criterion::{Criterion, criterion_group, criterion_main}; +use swagger_cli::core::indexer::build_index; +use swagger_cli::core::search::{SearchEngine, SearchOptions}; +use swagger_cli::core::spec::SpecIndex; fn fixture_path(name: &str) -> PathBuf { let manifest_dir = env!("CARGO_MANIFEST_DIR"); @@ -22,34 +26,32 @@ fn load_petstore_json() -> serde_json::Value { serde_json::from_slice(&bytes).expect("failed to parse petstore.json") } +fn load_petstore_index() -> SpecIndex { + let raw_json = load_petstore_json(); + build_index(&raw_json, "sha256:bench", 1).expect("failed to build index") +} + fn bench_json_parse(c: &mut Criterion) { let path = fixture_path("petstore.json"); let bytes = fs::read(&path).expect("failed to read petstore.json fixture"); c.bench_function("parse_petstore_json", |b| { b.iter(|| { - let _: serde_json::Value = serde_json::from_slice(&bytes).expect("parse failed"); + let v: serde_json::Value = + serde_json::from_slice(black_box(&bytes)).expect("parse failed"); + black_box(v); }); }); } -fn bench_build_index(c: &mut Criterion) { +fn bench_build_index_real(c: &mut Criterion) { let raw_json = load_petstore_json(); - c.bench_function("build_index_petstore", |b| { + c.bench_function("build_index_real", |b| { b.iter(|| { - // Simulate endpoint extraction (mirrors build_index core logic) - if let Some(paths) = raw_json.get("paths").and_then(|v| v.as_object()) { - let mut endpoints = Vec::new(); - for (path, methods) in paths { - if let Some(methods_map) = methods.as_object() { - for (method, _op) in methods_map { - endpoints.push((path.clone(), method.clone())); - } - } - } - assert!(!endpoints.is_empty()); - } + let index = + build_index(black_box(&raw_json), "sha256:bench", 1).expect("build_index failed"); + black_box(index); }); }); } @@ -62,8 +64,9 @@ fn bench_hash_computation(c: &mut Criterion) { b.iter(|| { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); - hasher.update(&bytes); - let _result = format!("sha256:{:x}", hasher.finalize()); + hasher.update(black_box(&bytes)); + let result = format!("sha256:{:x}", hasher.finalize()); + black_box(result); }); }); } @@ -73,44 +76,131 @@ fn bench_json_pointer_resolution(c: &mut Criterion) { c.bench_function("json_pointer_resolution", |b| { b.iter(|| { - let _ = raw_json + let a = raw_json .pointer("/paths/~1pets/get/summary") .map(|v| v.as_str()); - let _ = raw_json + let b_val = raw_json .pointer("/components/schemas/Pet") .map(|v| v.is_object()); - let _ = raw_json.pointer("/info/title").map(|v| v.as_str()); + let c_val = raw_json.pointer("/info/title").map(|v| v.as_str()); + black_box((a, b_val, c_val)); }); }); } -fn bench_search_scoring(c: &mut Criterion) { - let raw_json = load_petstore_json(); +fn bench_search_real(c: &mut Criterion) { + let index = load_petstore_index(); + let engine = SearchEngine::new(&index); + let opts = SearchOptions::default(); - // Pre-extract summaries for search simulation - let mut summaries: Vec = Vec::new(); - if let Some(paths) = raw_json.get("paths").and_then(|v| v.as_object()) { - for (_path, methods) in paths { - if let Some(methods_map) = methods.as_object() { - for (_method, op) in methods_map { - if let Some(summary) = op.get("summary").and_then(|v| v.as_str()) { - summaries.push(summary.to_lowercase()); - } - } - } - } - } - - c.bench_function("search_scoring_pet", |b| { - let query = "pet"; + c.bench_function("search_real_pet", |b| { b.iter(|| { - let mut matches = 0u32; - for summary in &summaries { - if summary.contains(query) { - matches += 1; - } - } - assert!(matches > 0); + let results = engine.search(black_box("pet"), &opts); + black_box(results); + }); + }); +} + +fn bench_search_multi_term(c: &mut Criterion) { + let index = load_petstore_index(); + let engine = SearchEngine::new(&index); + let opts = SearchOptions::default(); + + c.bench_function("search_real_multi_term", |b| { + b.iter(|| { + let results = engine.search(black_box("list all pets"), &opts); + black_box(results); + }); + }); +} + +fn bench_search_case_insensitive(c: &mut Criterion) { + let index = load_petstore_index(); + let engine = SearchEngine::new(&index); + let opts = SearchOptions { + case_sensitive: false, + ..SearchOptions::default() + }; + + c.bench_function("search_real_case_insensitive", |b| { + b.iter(|| { + let results = engine.search(black_box("PET"), &opts); + black_box(results); + }); + }); +} + +fn bench_normalize_json(c: &mut Criterion) { + let path = fixture_path("petstore.json"); + let bytes = fs::read(&path).expect("failed to read petstore.json fixture"); + + c.bench_function("normalize_json_input", |b| { + b.iter(|| { + let result = swagger_cli::core::indexer::normalize_to_json( + black_box(&bytes), + swagger_cli::core::indexer::Format::Json, + ) + .expect("normalize failed"); + black_box(result); + }); + }); +} + +fn bench_normalize_and_parse_pipeline(c: &mut Criterion) { + let path = fixture_path("petstore.json"); + let bytes = fs::read(&path).expect("failed to read petstore.json fixture"); + + // New way: normalize returns both bytes and value (one parse) + c.bench_function("pipeline_normalize_new", |b| { + b.iter(|| { + let (json_bytes, value) = swagger_cli::core::indexer::normalize_to_json( + black_box(&bytes), + swagger_cli::core::indexer::Format::Json, + ) + .expect("normalize failed"); + black_box((&json_bytes, &value)); + }); + }); + + // Old way simulation: parse to validate + copy, then parse again + c.bench_function("pipeline_normalize_old", |b| { + b.iter(|| { + // Step 1: old normalize_to_json did: parse(validate), copy bytes + let _: serde_json::Value = + serde_json::from_slice(black_box(&bytes)).expect("validate failed"); + let json_bytes = bytes.to_vec(); + // Step 2: caller would parse again + let value: serde_json::Value = + serde_json::from_slice(&json_bytes).expect("parse failed"); + black_box((&json_bytes, &value)); + }); + }); +} + +fn bench_detect_format_json_no_hints(c: &mut Criterion) { + let path = fixture_path("petstore.json"); + let bytes = fs::read(&path).expect("failed to read petstore.json fixture"); + + c.bench_function("detect_format_json_no_hints", |b| { + b.iter(|| { + let fmt = swagger_cli::core::indexer::detect_format(black_box(&bytes), None, None); + black_box(fmt); + }); + }); +} + +fn bench_detect_format_with_hint(c: &mut Criterion) { + let path = fixture_path("petstore.json"); + let bytes = fs::read(&path).expect("failed to read petstore.json fixture"); + + c.bench_function("detect_format_with_content_type", |b| { + b.iter(|| { + let fmt = swagger_cli::core::indexer::detect_format( + black_box(&bytes), + None, + Some("application/json"), + ); + black_box(fmt); }); }); } @@ -118,9 +208,15 @@ fn bench_search_scoring(c: &mut Criterion) { criterion_group!( benches, bench_json_parse, - bench_build_index, + bench_build_index_real, bench_hash_computation, bench_json_pointer_resolution, - bench_search_scoring, + bench_search_real, + bench_search_multi_term, + bench_search_case_insensitive, + bench_normalize_json, + bench_normalize_and_parse_pipeline, + bench_detect_format_json_no_hints, + bench_detect_format_with_hint, ); criterion_main!(benches);