/** * IndexBenchmark - Benchmarks for Index entity operations * * Measures performance of text index creation and search operations * using n-gram based full-text search. * * @version 0.1 * @since 0.1 */ namespace Implexus.Tools.Perf { /** * Benchmark for Index entity operations. * * Tests: * - create_index: Creating text indices * - search_contains: Searching with contains pattern * - search_prefix: Prefix-based searches * - search_suffix: Suffix-based searches * - search_exact: Exact match searches * - search_rare_term: Searching for rare terms * - create_index_title: Creating indices on title field * - search_title: Searching on title index */ public class IndexBenchmark : Benchmark { /** * {@inheritDoc} */ public override string name { get { return "Index"; } } /** * Creates a new IndexBenchmark. * * @param engine The engine to benchmark * @param config The benchmark configuration */ public IndexBenchmark(Core.Engine engine, BenchmarkConfig config) { base(engine, config); } /** * {@inheritDoc} */ public override async void run_async(Results results) throws Error { // Setup: Create test documents with text content var parent_path = new Core.EntityPath("/perf-test/indices"); yield ensure_container_async(new Core.EntityPath("/perf-test"), "perf-test"); _cleanup_tracker.add_container(new Core.EntityPath("/perf-test").to_string()); yield cleanup_path_async(parent_path); var root = yield _engine.get_root_async(); var perf_test = yield root.get_child_async("perf-test"); var parent_container = yield perf_test.create_container_async("indices"); _cleanup_tracker.add_container(parent_path.to_string()); int test_doc_count = _config.test_doc_count; int quick_iterations = _config.quick_iterations; int iterations = _config.iterations; // Sample texts for indexing string[] sample_texts = { "The quick brown fox jumps over the lazy dog", "A journey of a thousand miles begins with a single step", "To be or not to be that is the question", "All that glitters is not gold", "The only thing we have to fear is fear itself", "Ask not what your country can do for you", "I have a dream that one day this nation will rise up", "In the beginning was the Word and the Word was with God", "It was the best of times it was the worst of times", "Call me Ishmael Some years ago never mind how long precisely" }; // Create test documents with text content print(" Setting up test documents (%d documents)...\n", test_doc_count); for (int i = 0; i < test_doc_count; i++) { var doc_name = "doc-%d".printf(i); var doc_path = parent_path.append_child(doc_name); yield cleanup_path_async(doc_path); var doc = yield parent_container.create_document_async(doc_name, "TextDoc"); yield doc.set_entity_property_async( "content", new Invercargill.NativeElement(sample_texts[i % sample_texts.length]) ); yield doc.set_entity_property_async( "title", new Invercargill.NativeElement("Document %d".printf(i)) ); // Track for deferred cleanup _cleanup_tracker.add_document(doc_path.to_string()); } // Test 1: Create index on content field var index_path = parent_path.append_child("content-index"); print(" Testing create_index (%d iterations)...\n", quick_iterations); var create_op = new CreateIndexOp(this, parent_container, index_path, "content-index", "TextDoc", "content"); var create_result = yield measure_async("create_index", quick_iterations, create_op); results.add(name, create_result); // Get the index entity for search tests Core.Entity? idx_entity = yield _engine.get_entity_or_null_async(index_path); var index_entity = idx_entity as Entities.Index; // Test 2: Search with single term (contains) print(" Testing search_contains (%d iterations)...\n", iterations); var contains_op = new SearchOp(index_entity, "*the*"); var contains_result = yield measure_async("search_contains", iterations, contains_op); results.add(name, contains_result); // Test 3: Search with prefix pattern print(" Testing search_prefix (%d iterations)...\n", iterations); var prefix_op = new SearchOp(index_entity, "the*"); var prefix_result = yield measure_async("search_prefix", iterations, prefix_op); results.add(name, prefix_result); // Test 4: Search with suffix pattern print(" Testing search_suffix (%d iterations)...\n", iterations); var suffix_op = new SearchOp(index_entity, "*ing"); var suffix_result = yield measure_async("search_suffix", iterations, suffix_op); results.add(name, suffix_result); // Test 5: Exact match search print(" Testing search_exact (%d iterations)...\n", iterations); var exact_op = new SearchOp(index_entity, "dog"); var exact_result = yield measure_async("search_exact", iterations, exact_op); results.add(name, exact_result); // Test 6: Search for rare term print(" Testing search_rare_term (%d iterations)...\n", iterations); var rare_op = new SearchOp(index_entity, "*Ishmael*"); var rare_result = yield measure_async("search_rare_term", iterations, rare_op); results.add(name, rare_result); // Test 7: Create index on title field var title_index_path = parent_path.append_child("title-index"); print(" Testing create_index_title (%d iterations)...\n", quick_iterations); var title_create_op = new CreateIndexOp(this, parent_container, title_index_path, "title-index", "TextDoc", "title"); var title_create_result = yield measure_async("create_index_title", quick_iterations, title_create_op); results.add(name, title_create_result); // Test 8: Search on title index Core.Entity? title_idx_entity = yield _engine.get_entity_or_null_async(title_index_path); var title_index = title_idx_entity as Entities.Index; print(" Testing search_title (%d iterations)...\n", iterations); var title_search_op = new SearchOp(title_index, "*Document*"); var title_search_result = yield measure_async("search_title", iterations, title_search_op); results.add(name, title_search_result); } } // Operation classes for IndexBenchmark private class CreateIndexOp : AsyncOperation { private IndexBenchmark _benchmark; private Core.Entity _parent_container; private Core.EntityPath _index_path; private string _name; private string _entity_type; private string _field_name; public CreateIndexOp(IndexBenchmark benchmark, Core.Entity parent_container, Core.EntityPath index_path, string name, string entity_type, string field_name) { _benchmark = benchmark; _parent_container = parent_container; _index_path = index_path; _name = name; _entity_type = entity_type; _field_name = field_name; } public override async void execute_async() throws Error { yield _benchmark.cleanup_path_async(_index_path); yield _parent_container.create_index_async(_name, _entity_type, _field_name); // Track for deferred cleanup _benchmark._cleanup_tracker.add_index(_index_path.to_string()); } } private class SearchOp : AsyncOperation { private Entities.Index? _index_entity; private string _pattern; public SearchOp(Entities.Index? index_entity, string pattern) { _index_entity = index_entity; _pattern = pattern; } public override async void execute_async() throws Error { if (_index_entity != null) { // search is synchronous (runs in DBM thread context) var search_result = ((!) _index_entity).search(_pattern); if (search_result != null) { var children = yield ((!) search_result).get_children_async(); int count = 0; foreach (var doc in children) { count++; } } } } } }