diff --git a/src/file_indexer.rs b/src/file_indexer.rs index d4126b1..adad118 100644 --- a/src/file_indexer.rs +++ b/src/file_indexer.rs @@ -390,13 +390,18 @@ impl FileIndexManager { // Fetch content for matching files and calculate relevance scores let mut files_with_content = Vec::new(); - // Limit the number of files to fetch - for file_path in matching_files.iter().take(5) { - match self - .gitlab_client - .get_file_content(project_id, file_path, None) - .await - { + // Limit the number of files to fetch and run requests concurrently + let files_to_fetch: Vec<_> = matching_files.iter().take(5).collect(); + + let fetch_futures = files_to_fetch.iter().map(|file_path| { + let client = self.gitlab_client.clone(); + async move { client.get_file_content(project_id, file_path, None).await } + }); + + let results = futures::future::join_all(fetch_futures).await; + + for (result, file_path) in results.into_iter().zip(files_to_fetch) { + match result { Ok(mut file) => { // Calculate relevance score based on content let content_score = if let Some(content) = &file.content { diff --git a/src/tests/file_indexer_perf_test.rs b/src/tests/file_indexer_perf_test.rs new file mode 100644 index 0000000..cf0ab7a --- /dev/null +++ b/src/tests/file_indexer_perf_test.rs @@ -0,0 +1,78 @@ +use crate::config::AppSettings; +use crate::file_indexer::FileIndexManager; +use crate::gitlab::GitlabApiClient; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use urlencoding::encode; +use wiremock::matchers::{method, path, query_param}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +#[tokio::test] +async fn test_search_files_performance() { + let mock_server = MockServer::start().await; + + let mut settings = AppSettings::default(); + settings.gitlab_url = mock_server.uri(); + settings.gitlab_token = "token".to_string(); + settings.default_branch = "main".to_string(); + + let client = Arc::new(GitlabApiClient::new(Arc::new(settings)).unwrap()); + let manager = Arc::new(FileIndexManager::new(client.clone(), 60)); + + let project_id = 1; + let index = manager.get_or_create_index(project_id); + + // Populate index with 5 files that match the keyword "test" + for i in 0..5 { + let file_path = format!("src/file_{}.rs", i); + index.add_file(&file_path, "fn test() {}"); + + let encoded_path = encode(&file_path); + let endpoint_path = format!( + "/api/v4/projects/{}/repository/files/{}", + project_id, encoded_path + ); + + let content = base64::encode("fn test() {}"); + let response_body = serde_json::json!({ + "file_name": format!("file_{}.rs", i), + "file_path": file_path, + "size": 100, + "encoding": "base64", + "content": content, + "ref": "main", + "blob_id": "123", + "commit_id": "456", + "last_commit_id": "789" + }); + + Mock::given(method("GET")) + .and(path(endpoint_path)) + .and(query_param("ref", "main")) + .respond_with( + ResponseTemplate::new(200) + .set_body_json(response_body) + .set_delay(Duration::from_millis(100)), + ) + .mount(&mock_server) + .await; + } + + let start = Instant::now(); + let results = manager + .search_files(project_id, &["test".to_string()]) + .await + .unwrap(); + let duration = start.elapsed(); + + println!("Search took {:?}", duration); + assert_eq!(results.len(), 5); + + // In concurrent mode, it should be close to the max delay of a single request (100ms) + // We assert < 250ms to allow for some overhead (was ~500ms sequentially) + assert!( + duration.as_millis() < 250, + "Expected duration < 250ms (concurrent), got {:?}", + duration + ); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 53cd5d5..4d2b0f9 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -8,6 +8,8 @@ pub mod agents_md_perf_test; #[cfg(test)] pub mod config_tests; #[cfg(test)] +pub mod file_indexer_perf_test; +#[cfg(test)] pub mod file_indexer_search_test; #[cfg(test)] pub mod file_indexer_simple_test;