|
| 1 | +//! Benchmark suite for SerpAPI search queries |
| 2 | +//! |
| 3 | +//! This benchmark executes the same search query 10 times and measures: |
| 4 | +//! - Runtime performance |
| 5 | +//! - Memory usage |
| 6 | +//! - CPU performance (JSON parsing) |
| 7 | +//! |
| 8 | +//! Usage: |
| 9 | +//! export API_KEY=your_api_key |
| 10 | +//! cargo bench --bench search_query |
| 11 | +
|
| 12 | +use criterion::{black_box, criterion_group, criterion_main, Criterion}; |
| 13 | +use serpapi::serpapi::Client; |
| 14 | +use std::collections::HashMap; |
| 15 | +use std::sync::{Arc, Mutex}; |
| 16 | +use tokio::runtime::Runtime; |
| 17 | + |
| 18 | +fn api_key() -> String { |
| 19 | + std::env::var("SERPAPI_KEY").unwrap_or_else(|_| { |
| 20 | + eprintln!("Warning: SERPAPI_KEY not set. Benchmarks may fail."); |
| 21 | + "".to_string() |
| 22 | + }) |
| 23 | +} |
| 24 | + |
| 25 | +fn setup_client() -> Client { |
| 26 | + let mut default = HashMap::<String, String>::new(); |
| 27 | + default.insert("engine".to_string(), "google".to_string()); |
| 28 | + default.insert("api_key".to_string(), api_key()); |
| 29 | + Client::new(default) |
| 30 | +} |
| 31 | + |
| 32 | +fn setup_search_params() -> HashMap<String, String> { |
| 33 | + let mut parameter = HashMap::<String, String>::new(); |
| 34 | + parameter.insert("q".to_string(), "coffee".to_string()); |
| 35 | + parameter.insert( |
| 36 | + "location".to_string(), |
| 37 | + "Austin, TX, Texas, United States".to_string(), |
| 38 | + ); |
| 39 | + parameter |
| 40 | +} |
| 41 | + |
| 42 | +fn benchmark_search_query(c: &mut Criterion) { |
| 43 | + let rt = Runtime::new().unwrap(); |
| 44 | + let client = Arc::new(setup_client()); |
| 45 | + let params = setup_search_params(); |
| 46 | + |
| 47 | + // Collect memory statistics separately using Arc<Mutex<>> |
| 48 | + let memory_deltas: Arc<Mutex<Vec<usize>>> = Arc::new(Mutex::new(Vec::new())); |
| 49 | + let peak_memory: Arc<Mutex<usize>> = Arc::new(Mutex::new(0)); |
| 50 | + |
| 51 | + let mut group = c.benchmark_group("search_query"); |
| 52 | + group.sample_size(10); // Execute 10 times |
| 53 | + group.measurement_time(std::time::Duration::from_secs(120)); // Allow up to 120s for API calls |
| 54 | + |
| 55 | + let client_clone = Arc::clone(&client); |
| 56 | + let params_clone = params.clone(); |
| 57 | + let memory_deltas_clone = Arc::clone(&memory_deltas); |
| 58 | + let peak_memory_clone = Arc::clone(&peak_memory); |
| 59 | + |
| 60 | + group.bench_function("search_with_parsing", |b| { |
| 61 | + let client = Arc::clone(&client_clone); |
| 62 | + let params = params_clone.clone(); |
| 63 | + let memory_deltas = Arc::clone(&memory_deltas_clone); |
| 64 | + let peak_memory = Arc::clone(&peak_memory_clone); |
| 65 | + |
| 66 | + b.to_async(&rt).iter(|| { |
| 67 | + let client = Arc::clone(&client); |
| 68 | + let params = params.clone(); |
| 69 | + let memory_deltas = Arc::clone(&memory_deltas); |
| 70 | + let peak_memory = Arc::clone(&peak_memory); |
| 71 | + |
| 72 | + async move { |
| 73 | + // Track memory before |
| 74 | + let mem_before = memory_stats::memory_stats() |
| 75 | + .map(|m| m.physical_mem) |
| 76 | + .unwrap_or(0); |
| 77 | + |
| 78 | + // Perform search using the shared client |
| 79 | + let result = client.search(black_box(params)).await; |
| 80 | + |
| 81 | + // CPU-intensive operation: parse and access JSON |
| 82 | + if let Ok(json) = result { |
| 83 | + let _organic_results = json["organic_results"].as_array(); |
| 84 | + let _local_results = json["local_results"]["places"].as_array(); |
| 85 | + let _search_metadata = json["search_metadata"].as_object(); |
| 86 | + |
| 87 | + // Track memory after |
| 88 | + let mem_after = memory_stats::memory_stats() |
| 89 | + .map(|m| m.physical_mem) |
| 90 | + .unwrap_or(0); |
| 91 | + |
| 92 | + let mem_delta = mem_after.saturating_sub(mem_before); |
| 93 | + |
| 94 | + // Store memory statistics |
| 95 | + if let Ok(mut deltas) = memory_deltas.lock() { |
| 96 | + deltas.push(mem_delta); |
| 97 | + } |
| 98 | + if let Ok(mut peak) = peak_memory.lock() { |
| 99 | + *peak = (*peak).max(mem_after); |
| 100 | + } |
| 101 | + |
| 102 | + black_box((json, mem_delta)) |
| 103 | + } else { |
| 104 | + black_box((serde_json::Value::Null, 0usize)) |
| 105 | + } |
| 106 | + } |
| 107 | + }); |
| 108 | + }); |
| 109 | + |
| 110 | + group.finish(); |
| 111 | + |
| 112 | + // Report memory statistics |
| 113 | + let deltas = memory_deltas.lock().unwrap(); |
| 114 | + let peak = peak_memory.lock().unwrap(); |
| 115 | + |
| 116 | + if !deltas.is_empty() { |
| 117 | + let mean_delta: f64 = deltas.iter().sum::<usize>() as f64 / deltas.len() as f64; |
| 118 | + let variance: f64 = deltas.iter() |
| 119 | + .map(|&x| { |
| 120 | + let diff = x as f64 - mean_delta; |
| 121 | + diff * diff |
| 122 | + }) |
| 123 | + .sum::<f64>() / deltas.len() as f64; |
| 124 | + let std_dev = variance.sqrt(); |
| 125 | + |
| 126 | + println!("\n=== Memory Usage Statistics ==="); |
| 127 | + println!("Memory Delta (mean): {:.2} KB ({:.2} MB)", mean_delta / 1024.0, mean_delta / (1024.0 * 1024.0)); |
| 128 | + println!("Memory Delta (std dev): {:.2} KB ({:.2} MB)", std_dev / 1024.0, std_dev / (1024.0 * 1024.0)); |
| 129 | + println!("Peak Memory: {:.2} KB ({:.2} MB)", *peak as f64 / 1024.0, *peak as f64 / (1024.0 * 1024.0)); |
| 130 | + println!("Samples: {}", deltas.len()); |
| 131 | + println!("===============================\n"); |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +criterion_group! { |
| 136 | + name = benches; |
| 137 | + config = Criterion::default() |
| 138 | + .with_plots() |
| 139 | + .with_output_color(true); |
| 140 | + targets = benchmark_search_query |
| 141 | +} |
| 142 | + |
| 143 | +criterion_main!(benches); |
0 commit comments