pub mod hot_reload;
pub mod optimizer;
pub use hot_reload::{HotReloadManager, OptimizationCache};
pub use optimizer::{OptimizationBenchmark, OptimizationGenerator};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SequenceStats {
pub sequence: String,
pub count: u64,
pub last_used: SystemTime,
pub avg_processing_ns: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversionProfile {
pub from_script: String,
pub to_script: String,
pub sequences: FxHashMap<String, SequenceStats>,
pub total_conversions: u64,
pub created_at: SystemTime,
pub updated_at: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizedLookupTable {
pub from_script: String,
pub to_script: String,
pub sequence_mappings: FxHashMap<String, String>,
pub word_mappings: FxHashMap<String, String>,
pub metadata: OptimizationMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationMetadata {
pub generated_at: SystemTime,
pub sequence_count: usize,
pub min_frequency: u64,
pub profile_stats: ProfileStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileStats {
pub total_sequences_profiled: u64,
pub unique_sequences: usize,
pub top_sequences: Vec<(String, u64)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfilerConfig {
pub enabled: bool,
pub profile_dir: PathBuf,
pub optimization_dir: PathBuf,
pub min_sequence_frequency: u64,
pub max_sequences_per_table: usize,
pub auto_save_interval: Duration,
pub hot_reload_enabled: bool,
}
impl Default for ProfilerConfig {
fn default() -> Self {
Self {
enabled: true,
profile_dir: PathBuf::from("profiles"),
optimization_dir: PathBuf::from("optimizations"),
min_sequence_frequency: 10,
max_sequences_per_table: 1000,
auto_save_interval: Duration::from_secs(300), hot_reload_enabled: true,
}
}
}
pub struct Profiler {
config: ProfilerConfig,
profiles: Arc<RwLock<FxHashMap<(String, String), ConversionProfile>>>,
optimizations: Arc<RwLock<FxHashMap<(String, String), OptimizedLookupTable>>>,
last_save_time: Arc<Mutex<Instant>>,
}
impl Default for Profiler {
fn default() -> Self {
Self::new()
}
}
impl Profiler {
pub fn new() -> Self {
Self::with_config(ProfilerConfig::default())
}
pub fn with_config(config: ProfilerConfig) -> Self {
if config.enabled {
let _ = fs::create_dir_all(&config.profile_dir);
let _ = fs::create_dir_all(&config.optimization_dir);
}
let profiler = Self {
config,
profiles: Arc::new(RwLock::new(FxHashMap::default())),
optimizations: Arc::new(RwLock::new(FxHashMap::default())),
last_save_time: Arc::new(Mutex::new(Instant::now())),
};
profiler.load_profiles();
profiler.load_optimizations();
profiler
}
pub fn record_sequence(
&self,
from_script: &str,
to_script: &str,
sequence: &str,
processing_time: Duration,
) {
if !self.config.enabled {
return;
}
let key = (from_script.to_string(), to_script.to_string());
let mut profiles = self.profiles.write().unwrap();
let profile = profiles
.entry(key.clone())
.or_insert_with(|| ConversionProfile {
from_script: from_script.to_string(),
to_script: to_script.to_string(),
sequences: FxHashMap::default(),
total_conversions: 0,
created_at: SystemTime::now(),
updated_at: SystemTime::now(),
});
profile.total_conversions += 1;
profile.updated_at = SystemTime::now();
let stats = profile
.sequences
.entry(sequence.to_string())
.or_insert_with(|| SequenceStats {
sequence: sequence.to_string(),
count: 0,
last_used: SystemTime::now(),
avg_processing_ns: 0.0,
});
stats.count += 1;
stats.last_used = SystemTime::now();
let new_time_ns = processing_time.as_nanos() as f64;
if stats.count == 1 {
stats.avg_processing_ns = new_time_ns;
} else {
stats.avg_processing_ns = (stats.avg_processing_ns * (stats.count - 1) as f64
+ new_time_ns)
/ stats.count as f64;
}
drop(profiles); self.maybe_auto_save();
}
pub fn record_conversion(
&self,
from_script: &str,
to_script: &str,
text: &str,
processing_time: Duration,
) {
if !self.config.enabled {
return;
}
let sequences = self.extract_sequences(text);
let time_per_sequence = processing_time / sequences.len() as u32;
for sequence in sequences {
self.record_sequence(from_script, to_script, &sequence, time_per_sequence);
}
}
fn extract_sequences(&self, text: &str) -> Vec<String> {
let mut sequences = Vec::new();
let chars: Vec<char> = text.chars().collect();
for ch in &chars {
if !ch.is_whitespace() && !ch.is_ascii_punctuation() {
sequences.push(ch.to_string());
}
}
for window in chars.windows(2) {
if !window[0].is_whitespace() && !window[1].is_whitespace() {
sequences.push(window.iter().collect());
}
}
for window in chars.windows(3) {
if !window[0].is_whitespace() && !window[2].is_whitespace() {
sequences.push(window.iter().collect());
}
}
for word in text.split_whitespace() {
if word.len() > 1 && word.len() <= 20 {
sequences.push(word.to_string());
}
}
sequences
}
pub fn generate_optimizations(&self) -> Vec<OptimizedLookupTable> {
let profiles = self.profiles.read().unwrap();
let mut optimizations = Vec::new();
for ((from_script, to_script), profile) in profiles.iter() {
let mut sequences: Vec<_> = profile
.sequences
.iter()
.filter(|(_, stats)| stats.count >= self.config.min_sequence_frequency)
.map(|(seq, stats)| (seq.clone(), stats.count))
.collect();
sequences.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
sequences.truncate(self.config.max_sequences_per_table);
if sequences.is_empty() {
continue;
}
let optimization = OptimizedLookupTable {
from_script: from_script.clone(),
to_script: to_script.clone(),
sequence_mappings: FxHashMap::default(), word_mappings: FxHashMap::default(), metadata: OptimizationMetadata {
generated_at: SystemTime::now(),
sequence_count: sequences.len(),
min_frequency: self.config.min_sequence_frequency,
profile_stats: ProfileStats {
total_sequences_profiled: profile.total_conversions,
unique_sequences: profile.sequences.len(),
top_sequences: sequences.clone(),
},
},
};
optimizations.push(optimization);
}
optimizations
}
pub fn get_optimization(
&self,
from_script: &str,
to_script: &str,
) -> Option<OptimizedLookupTable> {
let optimizations = self.optimizations.read().unwrap();
optimizations
.get(&(from_script.to_string(), to_script.to_string()))
.cloned()
}
pub fn load_optimization(&self, table: OptimizedLookupTable) {
if !self.config.hot_reload_enabled {
return;
}
let mut optimizations = self.optimizations.write().unwrap();
let key = (table.from_script.clone(), table.to_script.clone());
optimizations.insert(key, table);
}
pub fn save_profiles(&self) {
let profiles = self.profiles.read().unwrap();
for ((from_script, to_script), profile) in profiles.iter() {
let filename = format!("{from_script}_{to_script}_profile.json");
let path = self.config.profile_dir.join(filename);
if let Ok(json) = serde_json::to_string_pretty(profile) {
let _ = fs::write(path, json);
}
}
*self.last_save_time.lock().unwrap() = Instant::now();
}
fn load_profiles(&self) {
if !self.config.profile_dir.exists() {
return;
}
let mut profiles = self.profiles.write().unwrap();
if let Ok(entries) = fs::read_dir(&self.config.profile_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(profile) = serde_json::from_str::<ConversionProfile>(&content) {
let key = (profile.from_script.clone(), profile.to_script.clone());
profiles.insert(key, profile);
}
}
}
}
}
}
pub fn save_optimizations(&self, optimizations: &[OptimizedLookupTable]) {
for optimization in optimizations {
let filename = format!(
"{}_{}_opt.json",
optimization.from_script, optimization.to_script
);
let path = self.config.optimization_dir.join(filename);
if let Ok(json) = serde_json::to_string_pretty(optimization) {
let _ = fs::write(path, json);
}
}
}
fn load_optimizations(&self) {
if !self.config.optimization_dir.exists() {
return;
}
let mut optimizations = self.optimizations.write().unwrap();
if let Ok(entries) = fs::read_dir(&self.config.optimization_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(opt) = serde_json::from_str::<OptimizedLookupTable>(&content) {
let key = (opt.from_script.clone(), opt.to_script.clone());
optimizations.insert(key, opt);
}
}
}
}
}
}
fn maybe_auto_save(&self) {
let last_save = *self.last_save_time.lock().unwrap();
if last_save.elapsed() >= self.config.auto_save_interval {
self.save_profiles();
}
}
pub fn get_profile_stats(&self) -> FxHashMap<(String, String), ProfileStats> {
let profiles = self.profiles.read().unwrap();
let mut stats = FxHashMap::default();
for (key, profile) in profiles.iter() {
let mut top_sequences: Vec<_> = profile
.sequences
.iter()
.map(|(seq, stats)| (seq.clone(), stats.count))
.collect();
top_sequences.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
top_sequences.truncate(10);
stats.insert(
key.clone(),
ProfileStats {
total_sequences_profiled: profile.total_conversions,
unique_sequences: profile.sequences.len(),
top_sequences,
},
);
}
stats
}
pub fn clear_profiles(&self) {
let mut profiles = self.profiles.write().unwrap();
profiles.clear();
}
pub fn set_enabled(&mut self, enabled: bool) {
self.config.enabled = enabled;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_profiler_creation() {
let profiler = Profiler::new();
assert!(profiler.config.enabled);
}
#[test]
fn test_sequence_recording() {
let profiler = Profiler::new();
profiler.record_sequence("devanagari", "iso15919", "धर्म", Duration::from_nanos(1000));
profiler.record_sequence("devanagari", "iso15919", "धर्म", Duration::from_nanos(1200));
profiler.record_sequence("devanagari", "iso15919", "योग", Duration::from_nanos(800));
let profiles = profiler.profiles.read().unwrap();
let key = ("devanagari".to_string(), "iso15919".to_string());
assert!(profiles.contains_key(&key));
let profile = &profiles[&key];
assert_eq!(profile.sequences.len(), 2);
assert_eq!(profile.sequences["धर्म"].count, 2);
assert_eq!(profile.sequences["योग"].count, 1);
}
#[test]
fn test_sequence_extraction() {
let profiler = Profiler::new();
let sequences = profiler.extract_sequences("धर्म योग");
assert!(sequences.contains(&"ध".to_string()));
assert!(sequences.contains(&"धर".to_string()));
assert!(sequences.contains(&"धर्म".to_string()));
assert!(sequences.contains(&"योग".to_string()));
}
#[test]
fn test_optimization_generation() {
let config = ProfilerConfig {
min_sequence_frequency: 1, ..Default::default()
};
let profiler = Profiler::with_config(config);
for _ in 0..5 {
profiler.record_sequence("devanagari", "iso15919", "धर्म", Duration::from_nanos(1000));
}
for _ in 0..3 {
profiler.record_sequence("devanagari", "iso15919", "योग", Duration::from_nanos(800));
}
let optimizations = profiler.generate_optimizations();
assert_eq!(optimizations.len(), 1);
let opt = &optimizations[0];
assert_eq!(opt.from_script, "devanagari");
assert_eq!(opt.to_script, "iso15919");
assert_eq!(opt.metadata.sequence_count, 2);
}
}