use crate::config::ShardexConfig;
use crate::error::ShardexError;
use crate::monitoring::{DetailedIndexStats, PerformanceMonitor as MonitoringPerformanceMonitor};
use crate::shardex::ShardexImpl;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Default)]
pub struct PerformanceTracker {
pub is_active: bool,
pub start_time: Option<Instant>,
pub total_operations: u64,
pub operation_timings: HashMap<String, Vec<Duration>>,
pub cumulative_timings: HashMap<String, Duration>,
}
impl PerformanceTracker {
pub fn new() -> Self {
Self::default()
}
pub fn start(&mut self) {
self.is_active = true;
self.start_time = Some(Instant::now());
}
pub fn stop(&mut self) {
self.is_active = false;
self.start_time = None;
}
pub fn record_operation(&mut self, operation_type: &str, duration: Duration) {
if !self.is_active {
return;
}
self.total_operations += 1;
self.operation_timings
.entry(operation_type.to_string())
.or_default()
.push(duration);
*self
.cumulative_timings
.entry(operation_type.to_string())
.or_insert(Duration::ZERO) += duration;
}
pub fn average_latency(&self, operation_type: &str) -> Option<Duration> {
self.operation_timings.get(operation_type).map(|timings| {
if timings.is_empty() {
Duration::ZERO
} else {
let total: Duration = timings.iter().sum();
total / timings.len() as u32
}
})
}
pub fn overall_average_latency(&self) -> Duration {
if self.total_operations == 0 {
return Duration::ZERO;
}
let total_time: Duration = self.cumulative_timings.values().sum();
total_time / self.total_operations as u32
}
pub fn throughput(&self) -> f64 {
if let Some(start_time) = self.start_time {
let elapsed = start_time.elapsed().as_secs_f64();
if elapsed > 0.0 {
self.total_operations as f64 / elapsed
} else {
0.0
}
} else {
0.0
}
}
pub fn reset(&mut self) {
self.total_operations = 0;
self.operation_timings.clear();
self.cumulative_timings.clear();
if self.is_active {
self.start_time = Some(Instant::now());
}
}
}
pub struct ShardexContext {
index: Option<ShardexImpl>,
config: ShardexConfig,
stats: Option<DetailedIndexStats>,
monitor: Option<MonitoringPerformanceMonitor>,
directory_path: Option<PathBuf>,
performance_tracker: PerformanceTracker,
text_storage_enabled: bool,
max_document_text_size: Option<usize>,
}
impl Default for ShardexContext {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ShardexContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShardexContext")
.field("index", &self.index.is_some())
.field("config", &self.config)
.field("stats", &self.stats)
.field("monitor", &self.monitor.is_some())
.field("directory_path", &self.directory_path)
.field("performance_tracker", &self.performance_tracker)
.field("text_storage_enabled", &self.text_storage_enabled)
.field("max_document_text_size", &self.max_document_text_size)
.finish()
}
}
impl ShardexContext {
pub fn new() -> Self {
Self {
index: None,
config: ShardexConfig::default(),
stats: None,
monitor: None,
directory_path: None,
performance_tracker: PerformanceTracker::new(),
text_storage_enabled: false,
max_document_text_size: None,
}
}
pub fn with_config(config: ShardexConfig) -> Self {
let text_storage_enabled = config.max_document_text_size > 0;
let max_document_text_size = if text_storage_enabled {
Some(config.max_document_text_size)
} else {
None
};
Self {
index: None,
config,
stats: None,
monitor: None,
directory_path: None,
performance_tracker: PerformanceTracker::new(),
text_storage_enabled,
max_document_text_size,
}
}
pub fn is_initialized(&self) -> bool {
self.index.is_some()
}
pub fn get_config(&self) -> &ShardexConfig {
&self.config
}
pub fn get_stats(&self) -> Option<&DetailedIndexStats> {
self.stats.as_ref()
}
pub fn get_directory_path(&self) -> Option<&PathBuf> {
self.directory_path.as_ref()
}
pub fn effective_directory_path(&self) -> &PathBuf {
self.directory_path
.as_ref()
.unwrap_or(&self.config.directory_path)
}
pub fn set_directory_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.directory_path = Some(path.into());
self
}
pub fn update_config(&mut self, config: ShardexConfig) -> Result<(), ShardexError> {
config.validate()?;
self.text_storage_enabled = config.max_document_text_size > 0;
self.max_document_text_size = if self.text_storage_enabled {
Some(config.max_document_text_size)
} else {
None
};
self.config = config;
Ok(())
}
pub fn clear_directory_path(&mut self) {
self.directory_path = None;
}
pub fn has_monitor(&self) -> bool {
self.monitor.is_some()
}
pub fn get_monitor(&self) -> Option<&MonitoringPerformanceMonitor> {
self.monitor.as_ref()
}
pub fn start_performance_tracking(&mut self) {
self.performance_tracker.start();
}
pub fn stop_performance_tracking(&mut self) {
self.performance_tracker.stop();
}
pub fn record_operation(&mut self, operation: &str, duration: Duration) {
self.performance_tracker
.record_operation(operation, duration);
}
pub fn get_total_operations(&self) -> u64 {
self.performance_tracker.total_operations
}
pub fn get_average_latency(&self) -> Duration {
self.performance_tracker.overall_average_latency()
}
pub fn get_throughput(&self) -> f64 {
self.performance_tracker.throughput()
}
pub fn get_operation_average_latency(&self, operation_type: &str) -> Option<Duration> {
self.performance_tracker.average_latency(operation_type)
}
pub fn is_performance_tracking_active(&self) -> bool {
self.performance_tracker.is_active
}
pub fn reset_performance_tracking(&mut self) {
self.performance_tracker.reset();
}
pub fn get_performance_tracker(&self) -> &PerformanceTracker {
&self.performance_tracker
}
pub fn is_text_storage_enabled(&self) -> bool {
self.text_storage_enabled
}
pub fn get_max_document_text_size(&self) -> Option<usize> {
self.max_document_text_size
}
pub fn validate_text_storage(&self, text_size: usize) -> Result<(), ShardexError> {
if !self.text_storage_enabled {
return Err(ShardexError::config_error(
"text_storage",
"text storage is not enabled",
"Enable text storage by setting max_document_text_size > 0 in the configuration",
));
}
if let Some(max_size) = self.max_document_text_size {
if text_size > max_size {
return Err(ShardexError::config_error(
"text_size",
format!("text size {} bytes exceeds maximum {}", text_size, max_size),
format!(
"Reduce document size or increase max_document_text_size to at least {} bytes",
text_size
),
));
}
}
Ok(())
}
pub fn validate_batch_text_storage<I>(&self, text_sizes: I) -> Result<(), ShardexError>
where
I: IntoIterator<Item = usize>,
{
if !self.text_storage_enabled {
return Err(ShardexError::config_error(
"text_storage",
"text storage is not enabled",
"Enable text storage by setting max_document_text_size > 0 in the configuration",
));
}
if let Some(max_size) = self.max_document_text_size {
for (i, text_size) in text_sizes.into_iter().enumerate() {
if text_size > max_size {
return Err(ShardexError::config_error(
format!("text_sizes[{}]", i),
format!("text size {} bytes exceeds maximum {}", text_size, max_size),
format!(
"Reduce document sizes or increase max_document_text_size to handle {} bytes",
text_size
),
));
}
}
}
Ok(())
}
pub(crate) fn set_index(&mut self, index: ShardexImpl) {
self.index = Some(index);
}
pub(crate) fn get_index(&self) -> Option<&ShardexImpl> {
self.index.as_ref()
}
pub(crate) fn get_index_mut(&mut self) -> Option<&mut ShardexImpl> {
self.index.as_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_context() {
let context = ShardexContext::new();
assert!(!context.is_initialized());
assert!(context.get_stats().is_none());
assert!(context.get_directory_path().is_none());
assert!(!context.has_monitor());
}
#[test]
fn test_default_context() {
let context = ShardexContext::default();
assert_eq!(context.get_config().vector_size, 384); assert!(!context.is_initialized());
}
#[test]
fn test_with_config() {
let config = ShardexConfig::new().vector_size(768).shard_size(50000);
let context = ShardexContext::with_config(config);
assert_eq!(context.get_config().vector_size, 768);
assert_eq!(context.get_config().shard_size, 50000);
assert!(!context.is_initialized());
}
#[test]
fn test_set_directory_path() {
let context = ShardexContext::new().set_directory_path("./test_index");
assert_eq!(context.get_directory_path().unwrap().to_str().unwrap(), "./test_index");
assert_eq!(context.effective_directory_path().to_str().unwrap(), "./test_index");
}
#[test]
fn test_effective_directory_path_fallback() {
let config = ShardexConfig::new().directory_path("./config_path");
let context = ShardexContext::with_config(config);
assert_eq!(context.effective_directory_path().to_str().unwrap(), "./config_path");
}
#[test]
fn test_update_config_valid() {
let mut context = ShardexContext::new();
assert_eq!(context.get_config().vector_size, 384);
let new_config = ShardexConfig::new().vector_size(768);
let result = context.update_config(new_config);
assert!(result.is_ok());
assert_eq!(context.get_config().vector_size, 768);
}
#[test]
fn test_update_config_invalid() {
let mut context = ShardexContext::new();
let invalid_config = ShardexConfig::new().vector_size(0); let result = context.update_config(invalid_config);
assert!(result.is_err());
assert_eq!(context.get_config().vector_size, 384);
}
#[test]
fn test_clear_directory_path() {
let mut context = ShardexContext::new().set_directory_path("./test");
assert!(context.get_directory_path().is_some());
context.clear_directory_path();
assert!(context.get_directory_path().is_none());
}
#[test]
fn test_context_creation_equality() {
let config = ShardexConfig::new().vector_size(512);
let context1 = ShardexContext::with_config(config.clone()).set_directory_path("./test");
let context2 = ShardexContext::with_config(config).set_directory_path("./test");
assert_eq!(context1.get_config().vector_size, context2.get_config().vector_size);
assert_eq!(context1.get_directory_path(), context2.get_directory_path());
assert_eq!(context1.is_initialized(), context2.is_initialized());
}
#[test]
fn test_context_debug() {
let context = ShardexContext::new();
let debug_str = format!("{:?}", context);
assert!(debug_str.contains("ShardexContext"));
assert!(debug_str.contains("performance_tracker"));
}
#[test]
fn test_performance_tracking() {
let mut context = ShardexContext::new();
assert!(!context.is_performance_tracking_active());
assert_eq!(context.get_total_operations(), 0);
context.start_performance_tracking();
assert!(context.is_performance_tracking_active());
context.record_operation("test_op", Duration::from_millis(100));
context.record_operation("test_op", Duration::from_millis(200));
context.record_operation("other_op", Duration::from_millis(50));
assert_eq!(context.get_total_operations(), 3);
assert!(context.get_average_latency().as_millis() > 0);
assert!(context.get_throughput() >= 0.0);
let test_op_latency = context.get_operation_average_latency("test_op");
assert!(test_op_latency.is_some());
assert_eq!(test_op_latency.unwrap(), Duration::from_millis(150));
let other_op_latency = context.get_operation_average_latency("other_op");
assert!(other_op_latency.is_some());
assert_eq!(other_op_latency.unwrap(), Duration::from_millis(50));
context.stop_performance_tracking();
assert!(!context.is_performance_tracking_active());
assert_eq!(context.get_total_operations(), 3);
}
#[test]
fn test_performance_tracking_reset() {
let mut context = ShardexContext::new();
context.start_performance_tracking();
context.record_operation("test", Duration::from_millis(100));
assert_eq!(context.get_total_operations(), 1);
context.reset_performance_tracking();
assert_eq!(context.get_total_operations(), 0);
assert!(context.is_performance_tracking_active()); }
#[test]
fn test_performance_tracker_standalone() {
let mut tracker = PerformanceTracker::new();
assert!(!tracker.is_active);
assert_eq!(tracker.total_operations, 0);
tracker.start();
assert!(tracker.is_active);
tracker.record_operation("test", Duration::from_millis(100));
tracker.record_operation("test", Duration::from_millis(200));
assert_eq!(tracker.total_operations, 2);
assert_eq!(tracker.average_latency("test"), Some(Duration::from_millis(150)));
assert_eq!(tracker.overall_average_latency(), Duration::from_millis(150));
tracker.stop();
assert!(!tracker.is_active);
}
#[test]
fn test_performance_tracker_throughput() {
let mut tracker = PerformanceTracker::new();
tracker.start();
tracker.record_operation("test", Duration::from_millis(10));
let throughput = tracker.throughput();
assert!(throughput >= 0.0);
}
#[test]
fn test_text_storage_configuration() {
let context = ShardexContext::new();
assert!(!context.is_text_storage_enabled());
assert!(context.get_max_document_text_size().is_none());
let config = ShardexConfig::new().max_document_text_size(1024 * 1024); let context = ShardexContext::with_config(config);
assert!(context.is_text_storage_enabled());
assert_eq!(context.get_max_document_text_size(), Some(1024 * 1024));
}
#[test]
fn test_text_storage_validation() {
let context = ShardexContext::new();
let result = context.validate_text_storage(1000);
assert!(result.is_err());
let config = ShardexConfig::new().max_document_text_size(2048); let context = ShardexContext::with_config(config);
let result = context.validate_text_storage(1000);
assert!(result.is_ok());
let result = context.validate_text_storage(3000);
assert!(result.is_err());
let result = context.validate_text_storage(2048);
assert!(result.is_ok());
}
#[test]
fn test_batch_text_storage_validation() {
let config = ShardexConfig::new().max_document_text_size(1000);
let context = ShardexContext::with_config(config);
let sizes = vec![500, 800, 1000];
let result = context.validate_batch_text_storage(sizes);
assert!(result.is_ok());
let sizes = vec![500, 1200, 800];
let result = context.validate_batch_text_storage(sizes);
assert!(result.is_err());
let sizes: Vec<usize> = vec![];
let result = context.validate_batch_text_storage(sizes);
assert!(result.is_ok());
}
#[test]
fn test_config_update_with_text_storage() {
let mut context = ShardexContext::new();
assert!(!context.is_text_storage_enabled());
let new_config = ShardexConfig::new().max_document_text_size(5 * 1024 * 1024);
context.update_config(new_config).unwrap();
assert!(context.is_text_storage_enabled());
assert_eq!(context.get_max_document_text_size(), Some(5 * 1024 * 1024));
let disable_config = ShardexConfig::new().max_document_text_size(0);
context.update_config(disable_config).unwrap();
assert!(!context.is_text_storage_enabled());
assert!(context.get_max_document_text_size().is_none());
}
#[test]
fn test_context_debug_includes_text_storage() {
let config = ShardexConfig::new().max_document_text_size(1024);
let context = ShardexContext::with_config(config);
let debug_str = format!("{:?}", context);
assert!(debug_str.contains("text_storage_enabled: true"));
assert!(debug_str.contains("max_document_text_size: Some(1024)"));
}
}