codesearch 0.1.15

A fast, intelligent CLI tool with multiple search modes (regex, fuzzy, semantic), code analysis, and dead code detection for popular programming languages
Documentation
//! Benchmarks for symbol operations
//!
//! Run with: cargo bench --bench symbol_benchmarks

use codesearch::symbols::{
    Symbol, SymbolIndex, SymbolKind, SymbolVisibility,
    extractor::{SymbolExtractor, extract_symbols_from_file},
    relationships::RelationshipGraph,
};
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use std::collections::HashMap;
use std::fs;
use tempfile::tempdir;

/// Create a sample Rust file with multiple symbols
const SAMPLE_RUST: &str = r#"
pub struct Config {
    name: String,
    value: i32,
}

impl Config {
    pub fn new(name: &str) -> Self {
        Config {
            name: name.to_string(),
            value: 0,
        }
    }

    pub fn get_name(&self) -> &str {
        &self.name
    }

    pub fn set_value(&mut self, val: i32) {
        self.value = val;
    }
}

pub fn process_config(config: &Config) -> String {
    format!("Config: {}", config.get_name())
}

pub fn main() {
    let config = Config::new("test");
    println!("{}", process_config(&config));
}
"#;

/// Create a sample Python file with multiple symbols
const SAMPLE_PYTHON: &str = r#"
class DataProcessor:
    def __init__(self, name):
        self.name = name
        self.data = []

    def process(self, items):
        result = []
        for item in items:
            result.append(item * 2)
        return result

    def get_summary(self):
        return f"Processor: {self.name}"

def main():
    processor = DataProcessor("test")
    result = processor.process([1, 2, 3])
    print(result)

if __name__ == "__main__":
    main()
"#;

/// Create a sample JavaScript file with multiple symbols
const SAMPLE_JS: &str = r#"
class ApiHandler {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async get(endpoint) {
        return fetch(`${this.baseUrl}${endpoint}`);
    }

    async post(endpoint, data) {
        return fetch(`${this.baseUrl}${endpoint}`, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
}

function createHandler(url) {
    return new ApiHandler(url);
}

module.exports = { ApiHandler, createHandler };
"#;

fn benchmark_symbol_extraction_rust(c: &mut Criterion) {
    let dir = tempdir().unwrap();
    let file_path = dir.path().join("test.rs");
    fs::write(&file_path, SAMPLE_RUST).unwrap();

    c.bench_function("symbol_extract_rust", |b| {
        b.iter(|| black_box(extract_symbols_from_file(black_box(&file_path)).unwrap()))
    });
}

fn benchmark_symbol_extraction_python(c: &mut Criterion) {
    let dir = tempdir().unwrap();
    let file_path = dir.path().join("test.py");
    fs::write(&file_path, SAMPLE_PYTHON).unwrap();

    c.bench_function("symbol_extract_python", |b| {
        b.iter(|| black_box(extract_symbols_from_file(black_box(&file_path)).unwrap()))
    });
}

fn benchmark_symbol_extraction_js(c: &mut Criterion) {
    let dir = tempdir().unwrap();
    let file_path = dir.path().join("test.js");
    fs::write(&file_path, SAMPLE_JS).unwrap();

    c.bench_function("symbol_extract_js", |b| {
        b.iter(|| black_box(extract_symbols_from_file(black_box(&file_path)).unwrap()))
    });
}

fn benchmark_symbol_index_add(c: &mut Criterion) {
    let index = SymbolIndex::new();
    let symbols: Vec<Symbol> = (0..100)
        .map(|i| Symbol {
            id: format!("symbol_{}", i),
            name: format!("function_{}", i),
            kind: SymbolKind::Function,
            file_path: "src/main.rs".to_string(),
            line: i,
            column: 0,
            end_line: i + 5,
            signature: "()".to_string(),
            documentation: None,
            visibility: SymbolVisibility::Public,
            parent: None,
            type_info: None,
            generics: vec![],
            annotations: vec![],
            attributes: vec![],
            metadata: HashMap::new(),
        })
        .collect();

    c.bench_function("symbol_index_add_100", |b| {
        b.iter(|| {
            let idx = SymbolIndex::new();
            for symbol in &symbols {
                idx.add_symbol(black_box(symbol.clone()));
            }
            black_box(idx);
        })
    });
}

fn benchmark_symbol_index_search(c: &mut Criterion) {
    let index = SymbolIndex::new();

    // Populate with 1000 symbols
    for i in 0..1000 {
        let symbol = Symbol {
            id: format!("symbol_{}", i),
            name: format!("func_{}", i),
            kind: if i % 5 == 0 {
                SymbolKind::Class
            } else if i % 3 == 0 {
                SymbolKind::Method
            } else {
                SymbolKind::Function
            },
            file_path: format!("src/file_{}.rs", i % 10),
            line: i,
            column: 0,
            end_line: i + 5,
            signature: "()".to_string(),
            documentation: None,
            visibility: if i % 2 == 0 {
                SymbolVisibility::Public
            } else {
                SymbolVisibility::Private
            },
            parent: if i % 3 == 0 {
                Some("ParentClass".to_string())
            } else {
                None
            },
            type_info: None,
            generics: vec![],
            annotations: vec![],
            attributes: vec![],
            metadata: HashMap::new(),
        };
        index.add_symbol(symbol);
    }

    let mut group = c.benchmark_group("symbol_index_search");

    group.bench_function("find_by_name_exact", |b| {
        b.iter(|| black_box(index.find_by_name("func_500")))
    });

    group.bench_function("find_by_name_pattern", |b| {
        b.iter(|| black_box(index.find_by_name_pattern("func_5.*").unwrap()))
    });

    group.bench_function("get_symbols_by_kind", |b| {
        b.iter(|| black_box(index.get_symbols_by_kind(&SymbolKind::Function)))
    });

    group.bench_function("search_symbols_multi", |b| {
        b.iter(|| {
            black_box(index.search_symbols(
                Some("func_"),
                Some(&SymbolKind::Function),
                None,
                Some(&SymbolVisibility::Public),
            ))
        })
    });

    group.finish();
}

fn benchmark_relationship_graph(c: &mut Criterion) {
    let graph = RelationshipGraph::new();

    // Build a graph with 500 symbols
    let mut symbols = Vec::new();
    for i in 0..500 {
        let symbol = Symbol {
            id: format!("sym_{}", i),
            name: format!("symbol_{}", i),
            kind: SymbolKind::Function,
            file_path: "test.rs".to_string(),
            line: i,
            column: 0,
            end_line: i + 5,
            signature: "()".to_string(),
            documentation: None,
            visibility: SymbolVisibility::Public,
            parent: None,
            type_info: None,
            generics: vec![],
            annotations: vec![],
            attributes: vec![],
            metadata: HashMap::new(),
        };
        graph.add_symbol(symbol.clone());
        symbols.push(symbol);
    }

    // Add relationships
    for i in 0..symbols.len() {
        if i > 0 {
            graph.add_relationship(
                &symbols[i].id,
                &symbols[i - 1].id,
                codesearch::symbols::SymbolRelationType::Calls,
                1.0,
            );
        }
        if i > 10 {
            graph.add_relationship(
                &symbols[i].id,
                &symbols[i - 10].id,
                codesearch::symbols::SymbolRelationType::References,
                0.8,
            );
        }
    }

    let mut group = c.benchmark_group("relationship_graph");

    group.bench_function("get_relationships", |b| {
        b.iter(|| black_box(graph.get_relationships("sym_250")))
    });

    group.bench_function("find_callers", |b| {
        b.iter(|| black_box(graph.find_callers("sym_100")))
    });

    group.bench_function("find_callees", |b| {
        b.iter(|| black_box(graph.find_callees("sym_200")))
    });

    group.bench_function("find_hierarchy", |b| {
        b.iter(|| black_box(graph.find_hierarchy("sym_50")))
    });

    group.finish();
}

fn benchmark_symbol_extractor_creation(c: &mut Criterion) {
    c.bench_function("symbol_extractor_new", |b| {
        b.iter(|| black_box(SymbolExtractor::new()))
    });
}

fn benchmark_multi_language_comparison(c: &mut Criterion) {
    let dir = tempdir().unwrap();

    let rust_path = dir.path().join("sample.rs");
    let py_path = dir.path().join("sample.py");
    let js_path = dir.path().join("sample.js");

    fs::write(&rust_path, SAMPLE_RUST).unwrap();
    fs::write(&py_path, SAMPLE_PYTHON).unwrap();
    fs::write(&js_path, SAMPLE_JS).unwrap();

    let mut group = c.benchmark_group("symbol_extract_comparison");

    group.bench_with_input(
        BenchmarkId::new("language", "rust"),
        &rust_path,
        |b, path| b.iter(|| black_box(extract_symbols_from_file(path).unwrap())),
    );

    group.bench_with_input(
        BenchmarkId::new("language", "python"),
        &py_path,
        |b, path| b.iter(|| black_box(extract_symbols_from_file(path).unwrap())),
    );

    group.bench_with_input(BenchmarkId::new("language", "js"), &js_path, |b, path| {
        b.iter(|| black_box(extract_symbols_from_file(path).unwrap()))
    });

    group.finish();
}

criterion_group!(
    benches,
    benchmark_symbol_extraction_rust,
    benchmark_symbol_extraction_python,
    benchmark_symbol_extraction_js,
    benchmark_symbol_index_add,
    benchmark_symbol_index_search,
    benchmark_relationship_graph,
    benchmark_symbol_extractor_creation,
    benchmark_multi_language_comparison,
);
criterion_main!(benches);