1#![allow(dead_code)]
16
17use anyhow::{Context, Result};
18use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
19use rayon::prelude::*;
20use std::collections::HashMap;
21use std::path::{Path, PathBuf};
22use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
23use std::sync::{Arc, Mutex, RwLock};
24use std::time::{Duration, Instant};
25
26#[derive(Debug, Clone)]
32pub struct ThreadPoolConfig {
33 pub num_threads: Option<usize>,
35 pub stack_size: Option<usize>,
37 pub thread_name_prefix: String,
39 pub pin_threads: bool,
41 pub priority: ThreadPriority,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub enum ThreadPriority {
48 Low,
50 #[default]
52 Normal,
53 High,
55}
56
57impl Default for ThreadPoolConfig {
58 fn default() -> Self {
59 Self {
60 num_threads: None,
61 stack_size: Some(8 * 1024 * 1024), thread_name_prefix: "oxigeo-worker".to_string(),
63 pin_threads: false,
64 priority: ThreadPriority::Normal,
65 }
66 }
67}
68
69impl ThreadPoolConfig {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_num_threads(mut self, num: usize) -> Self {
77 self.num_threads = Some(num);
78 self
79 }
80
81 pub fn with_stack_size(mut self, size: usize) -> Self {
83 self.stack_size = Some(size);
84 self
85 }
86
87 pub fn with_name_prefix(mut self, prefix: impl Into<String>) -> Self {
89 self.thread_name_prefix = prefix.into();
90 self
91 }
92
93 pub fn with_pin_threads(mut self, pin: bool) -> Self {
95 self.pin_threads = pin;
96 self
97 }
98
99 pub fn with_priority(mut self, priority: ThreadPriority) -> Self {
101 self.priority = priority;
102 self
103 }
104}
105
106pub fn init_thread_pool(config: ThreadPoolConfig) -> Result<()> {
108 let mut builder = rayon::ThreadPoolBuilder::new();
109
110 if let Some(num_threads) = config.num_threads {
111 builder = builder.num_threads(num_threads);
112 }
113
114 if let Some(stack_size) = config.stack_size {
115 builder = builder.stack_size(stack_size);
116 }
117
118 let prefix = config.thread_name_prefix;
119 builder = builder.thread_name(move |idx| format!("{}-{}", prefix, idx));
120
121 builder
122 .build_global()
123 .context("Failed to initialize thread pool")?;
124
125 Ok(())
126}
127
128pub fn optimal_thread_count() -> usize {
130 num_cpus::get()
131}
132
133pub fn physical_core_count() -> usize {
135 num_cpus::get_physical()
136}
137
138#[derive(Debug, Clone)]
144pub struct WorkItem<T> {
145 pub data: T,
147 pub priority: i32,
149 pub estimated_cost: u64,
151 pub group_id: Option<String>,
153}
154
155impl<T> WorkItem<T> {
156 pub fn new(data: T) -> Self {
158 Self {
159 data,
160 priority: 0,
161 estimated_cost: 1,
162 group_id: None,
163 }
164 }
165
166 pub fn with_priority(mut self, priority: i32) -> Self {
168 self.priority = priority;
169 self
170 }
171
172 pub fn with_cost(mut self, cost: u64) -> Self {
174 self.estimated_cost = cost;
175 self
176 }
177
178 pub fn with_group(mut self, group: impl Into<String>) -> Self {
180 self.group_id = Some(group.into());
181 self
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187pub enum DistributionStrategy {
188 #[default]
190 RoundRobin,
191 WorkStealing,
193 LoadBalanced,
195 PriorityBased,
197}
198
199pub struct WorkDistributor<T> {
201 items: Vec<WorkItem<T>>,
202 strategy: DistributionStrategy,
203 chunk_size: Option<usize>,
204}
205
206impl<T: Send + Sync> WorkDistributor<T> {
207 pub fn new(items: Vec<WorkItem<T>>) -> Self {
209 Self {
210 items,
211 strategy: DistributionStrategy::default(),
212 chunk_size: None,
213 }
214 }
215
216 pub fn from_items(items: impl IntoIterator<Item = T>) -> Self {
218 let work_items: Vec<WorkItem<T>> = items.into_iter().map(WorkItem::new).collect();
219 Self::new(work_items)
220 }
221
222 pub fn with_strategy(mut self, strategy: DistributionStrategy) -> Self {
224 self.strategy = strategy;
225 self
226 }
227
228 pub fn with_chunk_size(mut self, size: usize) -> Self {
230 self.chunk_size = Some(size);
231 self
232 }
233
234 pub fn process<R, F>(&mut self, processor: F) -> Result<Vec<R>>
236 where
237 R: Send,
238 F: Fn(&T) -> Result<R> + Send + Sync,
239 {
240 match self.strategy {
242 DistributionStrategy::PriorityBased => {
243 self.items
244 .sort_by_key(|item| std::cmp::Reverse(item.priority));
245 }
246 DistributionStrategy::LoadBalanced => {
247 self.items
249 .sort_by_key(|item| std::cmp::Reverse(item.estimated_cost));
250 }
251 _ => {}
252 }
253
254 let results: Result<Vec<R>> = self
256 .items
257 .par_iter()
258 .map(|item| processor(&item.data))
259 .collect();
260
261 results
262 }
263
264 pub fn process_with_progress<R, F>(
266 &mut self,
267 processor: F,
268 progress_message: &str,
269 ) -> Result<Vec<R>>
270 where
271 R: Send,
272 F: Fn(&T) -> Result<R> + Send + Sync,
273 {
274 let pb = ProgressBar::new(self.items.len() as u64);
275 pb.set_style(
276 ProgressStyle::default_bar()
277 .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
278 .unwrap_or_else(|_| ProgressStyle::default_bar())
279 .progress_chars("=>-"),
280 );
281 pb.set_message(progress_message.to_string());
282
283 let results: Result<Vec<R>> = self
284 .items
285 .par_iter()
286 .map(|item| {
287 let result = processor(&item.data);
288 pb.inc(1);
289 result
290 })
291 .collect();
292
293 pb.finish_with_message(format!("{}: complete", progress_message));
294
295 results
296 }
297}
298
299#[derive(Debug, Clone)]
305pub struct ProgressStats {
306 pub total: u64,
308 pub completed: u64,
310 pub failed: u64,
312 pub in_progress: u64,
314 pub start_time: Instant,
316 pub bytes_processed: u64,
318}
319
320impl Default for ProgressStats {
321 fn default() -> Self {
322 Self {
323 total: 0,
324 completed: 0,
325 failed: 0,
326 in_progress: 0,
327 start_time: Instant::now(),
328 bytes_processed: 0,
329 }
330 }
331}
332
333impl ProgressStats {
334 pub fn items_per_second(&self) -> f64 {
336 let elapsed = self.start_time.elapsed().as_secs_f64();
337 if elapsed > 0.0 {
338 self.completed as f64 / elapsed
339 } else {
340 0.0
341 }
342 }
343
344 pub fn bytes_per_second(&self) -> f64 {
346 let elapsed = self.start_time.elapsed().as_secs_f64();
347 if elapsed > 0.0 {
348 self.bytes_processed as f64 / elapsed
349 } else {
350 0.0
351 }
352 }
353
354 pub fn estimated_remaining(&self) -> Duration {
356 let remaining = self.total.saturating_sub(self.completed + self.failed);
357 let rate = self.items_per_second();
358 if rate > 0.0 {
359 Duration::from_secs_f64(remaining as f64 / rate)
360 } else {
361 Duration::MAX
362 }
363 }
364
365 pub fn percent_complete(&self) -> f64 {
367 if self.total > 0 {
368 ((self.completed + self.failed) as f64 / self.total as f64) * 100.0
369 } else {
370 0.0
371 }
372 }
373}
374
375pub struct ProgressAggregator {
377 operations: Arc<RwLock<HashMap<String, ProgressStats>>>,
378 multi_progress: Arc<MultiProgress>,
379 progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>>,
380}
381
382impl ProgressAggregator {
383 pub fn new() -> Self {
385 Self {
386 operations: Arc::new(RwLock::new(HashMap::new())),
387 multi_progress: Arc::new(MultiProgress::new()),
388 progress_bars: Arc::new(Mutex::new(HashMap::new())),
389 }
390 }
391
392 pub fn register_operation(&self, name: &str, total: u64) -> Result<()> {
394 let stats = ProgressStats {
395 total,
396 ..Default::default()
397 };
398
399 let mut ops = self
400 .operations
401 .write()
402 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
403 ops.insert(name.to_string(), stats);
404
405 let pb = self.multi_progress.add(ProgressBar::new(total));
407 pb.set_style(
408 ProgressStyle::default_bar()
409 .template("{spinner:.green} {msg}: [{bar:40.cyan/blue}] {pos}/{len}")
410 .unwrap_or_else(|_| ProgressStyle::default_bar())
411 .progress_chars("=>-"),
412 );
413 pb.set_message(name.to_string());
414
415 let mut pbs = self
416 .progress_bars
417 .lock()
418 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
419 pbs.insert(name.to_string(), pb);
420
421 Ok(())
422 }
423
424 pub fn update(&self, name: &str, completed_delta: u64, failed_delta: u64) -> Result<()> {
426 let mut ops = self
427 .operations
428 .write()
429 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
430
431 if let Some(stats) = ops.get_mut(name) {
432 stats.completed += completed_delta;
433 stats.failed += failed_delta;
434 }
435
436 let pbs = self
437 .progress_bars
438 .lock()
439 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
440 if let Some(pb) = pbs.get(name) {
441 pb.inc(completed_delta + failed_delta);
442 }
443
444 Ok(())
445 }
446
447 pub fn update_bytes(&self, name: &str, bytes: u64) -> Result<()> {
449 let mut ops = self
450 .operations
451 .write()
452 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
453
454 if let Some(stats) = ops.get_mut(name) {
455 stats.bytes_processed += bytes;
456 }
457
458 Ok(())
459 }
460
461 pub fn get_aggregate_stats(&self) -> Result<ProgressStats> {
463 let ops = self
464 .operations
465 .read()
466 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
467
468 let mut aggregate = ProgressStats::default();
469 for stats in ops.values() {
470 aggregate.total += stats.total;
471 aggregate.completed += stats.completed;
472 aggregate.failed += stats.failed;
473 aggregate.in_progress += stats.in_progress;
474 aggregate.bytes_processed += stats.bytes_processed;
475 }
476
477 Ok(aggregate)
478 }
479
480 pub fn finish_operation(&self, name: &str, message: &str) -> Result<()> {
482 let pbs = self
483 .progress_bars
484 .lock()
485 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
486 if let Some(pb) = pbs.get(name) {
487 pb.finish_with_message(message.to_string());
488 }
489 Ok(())
490 }
491}
492
493impl Default for ProgressAggregator {
494 fn default() -> Self {
495 Self::new()
496 }
497}
498
499#[derive(Debug, Clone)]
505pub struct ErrorInfo {
506 pub message: String,
508 pub source: Option<String>,
510 pub timestamp: Instant,
512 pub recoverable: bool,
514 pub context: HashMap<String, String>,
516}
517
518impl ErrorInfo {
519 pub fn new(message: impl Into<String>) -> Self {
521 Self {
522 message: message.into(),
523 source: None,
524 timestamp: Instant::now(),
525 recoverable: true,
526 context: HashMap::new(),
527 }
528 }
529
530 pub fn with_source(mut self, source: impl Into<String>) -> Self {
532 self.source = Some(source.into());
533 self
534 }
535
536 pub fn with_recoverable(mut self, recoverable: bool) -> Self {
538 self.recoverable = recoverable;
539 self
540 }
541
542 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
544 self.context.insert(key.into(), value.into());
545 self
546 }
547}
548
549pub struct ErrorCollector {
551 errors: Arc<Mutex<Vec<ErrorInfo>>>,
552 max_errors: usize,
553 stop_on_error: Arc<AtomicBool>,
554 error_count: Arc<AtomicUsize>,
555}
556
557impl ErrorCollector {
558 pub fn new() -> Self {
560 Self {
561 errors: Arc::new(Mutex::new(Vec::new())),
562 max_errors: 1000,
563 stop_on_error: Arc::new(AtomicBool::new(false)),
564 error_count: Arc::new(AtomicUsize::new(0)),
565 }
566 }
567
568 pub fn with_max_errors(mut self, max: usize) -> Self {
570 self.max_errors = max;
571 self
572 }
573
574 pub fn with_stop_on_error(self, stop: bool) -> Self {
576 self.stop_on_error.store(stop, Ordering::SeqCst);
577 self
578 }
579
580 pub fn should_stop(&self) -> bool {
582 self.stop_on_error.load(Ordering::SeqCst) && self.error_count.load(Ordering::SeqCst) > 0
583 }
584
585 pub fn add_error(&self, error: ErrorInfo) -> Result<()> {
587 let count = self.error_count.fetch_add(1, Ordering::SeqCst);
588 if count < self.max_errors {
589 let mut errors = self
590 .errors
591 .lock()
592 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
593 errors.push(error);
594 }
595 Ok(())
596 }
597
598 pub fn collect<T>(&self, result: Result<T>, source: Option<&str>) -> Option<T> {
600 match result {
601 Ok(value) => Some(value),
602 Err(e) => {
603 let mut error_info = ErrorInfo::new(format!("{:?}", e));
604 if let Some(src) = source {
605 error_info = error_info.with_source(src);
606 }
607 let _ = self.add_error(error_info);
609 None
610 }
611 }
612 }
613
614 pub fn get_errors(&self) -> Result<Vec<ErrorInfo>> {
616 let errors = self
617 .errors
618 .lock()
619 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
620 Ok(errors.clone())
621 }
622
623 pub fn error_count(&self) -> usize {
625 self.error_count.load(Ordering::SeqCst)
626 }
627
628 pub fn has_errors(&self) -> bool {
630 self.error_count.load(Ordering::SeqCst) > 0
631 }
632
633 pub fn summary(&self) -> Result<String> {
635 let errors = self.get_errors()?;
636 let total = self.error_count();
637
638 if errors.is_empty() {
639 return Ok("No errors".to_string());
640 }
641
642 let mut summary = format!("Total errors: {}\n", total);
643 for (i, error) in errors.iter().enumerate().take(10) {
644 summary.push_str(&format!(
645 " {}. {} (source: {})\n",
646 i + 1,
647 error.message,
648 error.source.as_deref().unwrap_or("unknown")
649 ));
650 }
651
652 if total > 10 {
653 summary.push_str(&format!(" ... and {} more errors\n", total - 10));
654 }
655
656 Ok(summary)
657 }
658}
659
660impl Default for ErrorCollector {
661 fn default() -> Self {
662 Self::new()
663 }
664}
665
666pub struct ParallelFileProcessor {
672 multi_progress: Arc<MultiProgress>,
673 file_count: Arc<AtomicUsize>,
674 error_count: Arc<AtomicUsize>,
675 bytes_processed: Arc<AtomicU64>,
676 error_collector: ErrorCollector,
677}
678
679impl ParallelFileProcessor {
680 pub fn new() -> Self {
682 Self {
683 multi_progress: Arc::new(MultiProgress::new()),
684 file_count: Arc::new(AtomicUsize::new(0)),
685 error_count: Arc::new(AtomicUsize::new(0)),
686 bytes_processed: Arc::new(AtomicU64::new(0)),
687 error_collector: ErrorCollector::new(),
688 }
689 }
690
691 pub fn process_files<P, F>(
693 &self,
694 files: Vec<P>,
695 processor: F,
696 progress_message: &str,
697 ) -> Result<Vec<Result<()>>>
698 where
699 P: AsRef<Path> + Send + Sync,
700 F: Fn(&Path) -> Result<()> + Send + Sync,
701 {
702 let pb = self
703 .multi_progress
704 .add(ProgressBar::new(files.len() as u64));
705 pb.set_style(
706 ProgressStyle::default_bar()
707 .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
708 .unwrap_or_else(|_| ProgressStyle::default_bar())
709 .progress_chars("=>-"),
710 );
711 pb.set_message(progress_message.to_string());
712
713 let results: Vec<Result<()>> = files
714 .par_iter()
715 .map(|file| {
716 let file_path = file.as_ref();
717 let result = processor(file_path);
718
719 if result.is_ok() {
720 self.file_count.fetch_add(1, Ordering::SeqCst);
721 } else {
722 self.error_count.fetch_add(1, Ordering::SeqCst);
723 }
724
725 pb.inc(1);
726 result
727 })
728 .collect();
729
730 pb.finish_with_message(format!(
731 "{}: {} succeeded, {} failed",
732 progress_message,
733 self.file_count.load(Ordering::SeqCst),
734 self.error_count.load(Ordering::SeqCst)
735 ));
736
737 Ok(results)
738 }
739
740 pub fn process_files_with_results<P, T, F>(
742 &self,
743 files: Vec<P>,
744 processor: F,
745 progress_message: &str,
746 ) -> Result<Vec<(PathBuf, Result<T>)>>
747 where
748 P: AsRef<Path> + Send + Sync,
749 T: Send,
750 F: Fn(&Path) -> Result<T> + Send + Sync,
751 {
752 let pb = self
753 .multi_progress
754 .add(ProgressBar::new(files.len() as u64));
755 pb.set_style(
756 ProgressStyle::default_bar()
757 .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
758 .unwrap_or_else(|_| ProgressStyle::default_bar())
759 .progress_chars("=>-"),
760 );
761 pb.set_message(progress_message.to_string());
762
763 let results: Vec<(PathBuf, Result<T>)> = files
764 .par_iter()
765 .map(|file| {
766 let file_path = file.as_ref();
767 let result = processor(file_path);
768
769 if result.is_ok() {
770 self.file_count.fetch_add(1, Ordering::SeqCst);
771 } else {
772 self.error_count.fetch_add(1, Ordering::SeqCst);
773 }
774
775 pb.inc(1);
776 (file_path.to_path_buf(), result)
777 })
778 .collect();
779
780 pb.finish_with_message(format!(
781 "{}: {} succeeded, {} failed",
782 progress_message,
783 self.file_count.load(Ordering::SeqCst),
784 self.error_count.load(Ordering::SeqCst)
785 ));
786
787 Ok(results)
788 }
789
790 pub fn add_bytes(&self, bytes: u64) {
792 self.bytes_processed.fetch_add(bytes, Ordering::SeqCst);
793 }
794
795 pub fn stats(&self) -> (usize, usize, u64) {
797 (
798 self.file_count.load(Ordering::SeqCst),
799 self.error_count.load(Ordering::SeqCst),
800 self.bytes_processed.load(Ordering::SeqCst),
801 )
802 }
803
804 pub fn error_collector(&self) -> &ErrorCollector {
806 &self.error_collector
807 }
808}
809
810impl Default for ParallelFileProcessor {
811 fn default() -> Self {
812 Self::new()
813 }
814}
815
816pub struct ParallelBandProcessor {
822 multi_progress: Arc<MultiProgress>,
823}
824
825impl ParallelBandProcessor {
826 pub fn new() -> Self {
828 Self {
829 multi_progress: Arc::new(MultiProgress::new()),
830 }
831 }
832
833 pub fn process_bands<T, F>(
835 &self,
836 band_indices: Vec<usize>,
837 processor: F,
838 progress_message: &str,
839 ) -> Result<Vec<T>>
840 where
841 T: Send,
842 F: Fn(usize) -> Result<T> + Send + Sync,
843 {
844 let pb = self
845 .multi_progress
846 .add(ProgressBar::new(band_indices.len() as u64));
847 pb.set_style(
848 ProgressStyle::default_bar()
849 .template("{msg} [{bar:40.green/blue}] {pos}/{len}")
850 .unwrap_or_else(|_| ProgressStyle::default_bar())
851 .progress_chars("=>-"),
852 );
853 pb.set_message(progress_message.to_string());
854
855 let results: Result<Vec<T>> = band_indices
856 .par_iter()
857 .map(|&idx| {
858 let result = processor(idx);
859 pb.inc(1);
860 result
861 })
862 .collect();
863
864 pb.finish_with_message(format!("{}: complete", progress_message));
865
866 results
867 }
868}
869
870impl Default for ParallelBandProcessor {
871 fn default() -> Self {
872 Self::new()
873 }
874}
875
876pub struct ParallelTileProcessor {
882 tile_size: (usize, usize),
883 multi_progress: Arc<MultiProgress>,
884}
885
886impl ParallelTileProcessor {
887 pub fn new(tile_width: usize, tile_height: usize) -> Self {
889 Self {
890 tile_size: (tile_width, tile_height),
891 multi_progress: Arc::new(MultiProgress::new()),
892 }
893 }
894
895 pub fn tile_size(&self) -> (usize, usize) {
897 self.tile_size
898 }
899
900 pub fn process_tiles<T, F>(
902 &self,
903 raster_width: usize,
904 raster_height: usize,
905 overlap: usize,
906 processor: F,
907 progress_message: &str,
908 ) -> Result<Vec<T>>
909 where
910 T: Send,
911 F: Fn(usize, usize, usize, usize) -> Result<T> + Send + Sync,
912 {
913 let (tile_w, tile_h) = self.tile_size;
914
915 let tiles_x = raster_width.div_ceil(tile_w);
917 let tiles_y = raster_height.div_ceil(tile_h);
918 let total_tiles = tiles_x * tiles_y;
919
920 let pb = self
921 .multi_progress
922 .add(ProgressBar::new(total_tiles as u64));
923 pb.set_style(
924 ProgressStyle::default_bar()
925 .template("{msg} [{bar:40.yellow/blue}] {pos}/{len} tiles")
926 .unwrap_or_else(|_| ProgressStyle::default_bar())
927 .progress_chars("=>-"),
928 );
929 pb.set_message(progress_message.to_string());
930
931 let mut tile_specs = Vec::with_capacity(total_tiles);
932 for ty in 0..tiles_y {
933 for tx in 0..tiles_x {
934 let x_start = tx * tile_w;
935 let y_start = ty * tile_h;
936 let x_end = (x_start + tile_w + overlap).min(raster_width);
937 let y_end = (y_start + tile_h + overlap).min(raster_height);
938
939 tile_specs.push((x_start, y_start, x_end - x_start, y_end - y_start));
940 }
941 }
942
943 let results: Result<Vec<T>> = tile_specs
944 .par_iter()
945 .map(|&(x, y, w, h)| {
946 let result = processor(x, y, w, h);
947 pb.inc(1);
948 result
949 })
950 .collect();
951
952 pb.finish_with_message(format!("{}: complete", progress_message));
953
954 results
955 }
956}
957
958pub struct BatchManager {
964 batch_size: usize,
965 multi_progress: Arc<MultiProgress>,
966}
967
968impl BatchManager {
969 pub fn new(batch_size: usize) -> Self {
971 Self {
972 batch_size: batch_size.max(1),
973 multi_progress: Arc::new(MultiProgress::new()),
974 }
975 }
976
977 pub fn batch_size(&self) -> usize {
979 self.batch_size
980 }
981
982 pub fn process_batches<T, R, F>(
984 &self,
985 items: Vec<T>,
986 processor: F,
987 progress_message: &str,
988 ) -> Result<Vec<R>>
989 where
990 T: Send + Clone,
991 R: Send,
992 F: Fn(Vec<T>) -> Result<Vec<R>> + Send + Sync,
993 {
994 let batches: Vec<Vec<T>> = items
995 .chunks(self.batch_size)
996 .map(|chunk| chunk.to_vec())
997 .collect();
998
999 let pb = self
1000 .multi_progress
1001 .add(ProgressBar::new(batches.len() as u64));
1002 pb.set_style(
1003 ProgressStyle::default_bar()
1004 .template("{msg} [{bar:40.magenta/blue}] {pos}/{len} batches")
1005 .unwrap_or_else(|_| ProgressStyle::default_bar())
1006 .progress_chars("=>-"),
1007 );
1008 pb.set_message(progress_message.to_string());
1009
1010 let results: Result<Vec<Vec<R>>> = batches
1011 .into_par_iter()
1012 .map(|batch| {
1013 let result = processor(batch);
1014 pb.inc(1);
1015 result
1016 })
1017 .collect();
1018
1019 pb.finish_with_message(format!("{}: complete", progress_message));
1020
1021 results.map(|batches| batches.into_iter().flatten().collect())
1022 }
1023}
1024
1025pub struct ResourceManager {
1031 max_memory_bytes: Arc<Mutex<usize>>,
1032 current_memory_bytes: Arc<Mutex<usize>>,
1033 max_threads: usize,
1034 active_threads: Arc<AtomicUsize>,
1035}
1036
1037impl ResourceManager {
1038 pub fn new(max_memory_mb: usize, max_threads: usize) -> Self {
1040 Self {
1041 max_memory_bytes: Arc::new(Mutex::new(max_memory_mb * 1024 * 1024)),
1042 current_memory_bytes: Arc::new(Mutex::new(0)),
1043 max_threads: max_threads.max(1),
1044 active_threads: Arc::new(AtomicUsize::new(0)),
1045 }
1046 }
1047
1048 pub fn try_allocate(&self, bytes: usize) -> Result<bool> {
1050 let mut current = self
1051 .current_memory_bytes
1052 .lock()
1053 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1054 let max = self
1055 .max_memory_bytes
1056 .lock()
1057 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1058
1059 if *current + bytes <= *max {
1060 *current += bytes;
1061 Ok(true)
1062 } else {
1063 Ok(false)
1064 }
1065 }
1066
1067 pub fn release(&self, bytes: usize) -> Result<()> {
1069 let mut current = self
1070 .current_memory_bytes
1071 .lock()
1072 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1073
1074 *current = current.saturating_sub(bytes);
1075
1076 Ok(())
1077 }
1078
1079 pub fn current_usage_mb(&self) -> Result<f64> {
1081 let current = self
1082 .current_memory_bytes
1083 .lock()
1084 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1085 Ok(*current as f64 / (1024.0 * 1024.0))
1086 }
1087
1088 pub fn max_memory_mb(&self) -> Result<f64> {
1090 let max = self
1091 .max_memory_bytes
1092 .lock()
1093 .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1094 Ok(*max as f64 / (1024.0 * 1024.0))
1095 }
1096
1097 pub fn max_threads(&self) -> usize {
1099 self.max_threads
1100 }
1101
1102 pub fn acquire_thread(&self) -> bool {
1104 let current = self.active_threads.fetch_add(1, Ordering::SeqCst);
1105 if current >= self.max_threads {
1106 self.active_threads.fetch_sub(1, Ordering::SeqCst);
1107 false
1108 } else {
1109 true
1110 }
1111 }
1112
1113 pub fn release_thread(&self) {
1115 self.active_threads.fetch_sub(1, Ordering::SeqCst);
1116 }
1117
1118 pub fn active_threads(&self) -> usize {
1120 self.active_threads.load(Ordering::SeqCst)
1121 }
1122}
1123
1124pub trait PipelineStage<I, O>: Send + Sync {
1130 fn process(&self, input: I) -> Result<O>;
1132
1133 fn name(&self) -> &str;
1135}
1136
1137pub struct FnStage<I, O, F>
1139where
1140 I: Send + Sync,
1141 O: Send + Sync,
1142 F: Fn(I) -> Result<O> + Send + Sync,
1143{
1144 name: String,
1145 func: F,
1146 _phantom: std::marker::PhantomData<(I, O)>,
1147}
1148
1149impl<I, O, F> FnStage<I, O, F>
1150where
1151 I: Send + Sync,
1152 O: Send + Sync,
1153 F: Fn(I) -> Result<O> + Send + Sync,
1154{
1155 pub fn new(name: impl Into<String>, func: F) -> Self {
1157 Self {
1158 name: name.into(),
1159 func,
1160 _phantom: std::marker::PhantomData,
1161 }
1162 }
1163}
1164
1165impl<I, O, F> PipelineStage<I, O> for FnStage<I, O, F>
1166where
1167 I: Send + Sync,
1168 O: Send + Sync,
1169 F: Fn(I) -> Result<O> + Send + Sync,
1170{
1171 fn process(&self, input: I) -> Result<O> {
1172 (self.func)(input)
1173 }
1174
1175 fn name(&self) -> &str {
1176 &self.name
1177 }
1178}
1179
1180#[derive(Debug)]
1182pub struct PipelineResult<T> {
1183 pub successes: Vec<T>,
1185 pub failures: Vec<ErrorInfo>,
1187 pub duration: Duration,
1189 pub throughput: f64,
1191}
1192
1193pub struct PipelineExecutor {
1195 multi_progress: Arc<MultiProgress>,
1196 error_collector: ErrorCollector,
1197}
1198
1199impl PipelineExecutor {
1200 pub fn new() -> Self {
1202 Self {
1203 multi_progress: Arc::new(MultiProgress::new()),
1204 error_collector: ErrorCollector::new(),
1205 }
1206 }
1207
1208 pub fn execute_two_stage<I, M, O, S1, S2>(
1210 &self,
1211 inputs: Vec<I>,
1212 stage1: &S1,
1213 stage2: &S2,
1214 progress_message: &str,
1215 ) -> Result<PipelineResult<O>>
1216 where
1217 I: Send + Sync + Clone,
1218 M: Send + Sync,
1219 O: Send,
1220 S1: PipelineStage<I, M>,
1221 S2: PipelineStage<M, O>,
1222 {
1223 let start = Instant::now();
1224 let total = inputs.len() as u64;
1225
1226 let pb = self.multi_progress.add(ProgressBar::new(total));
1227 pb.set_style(
1228 ProgressStyle::default_bar()
1229 .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec})")
1230 .unwrap_or_else(|_| ProgressStyle::default_bar())
1231 .progress_chars("=>-"),
1232 );
1233 pb.set_message(progress_message.to_string());
1234
1235 let results: Vec<Result<O>> = inputs
1236 .par_iter()
1237 .map(|input| {
1238 let mid = stage1.process(input.clone())?;
1239 let output = stage2.process(mid)?;
1240 pb.inc(1);
1241 Ok(output)
1242 })
1243 .collect();
1244
1245 pb.finish_with_message(format!("{}: complete", progress_message));
1246
1247 let duration = start.elapsed();
1248 let mut successes = Vec::new();
1249 let mut failures = Vec::new();
1250
1251 for (i, result) in results.into_iter().enumerate() {
1252 match result {
1253 Ok(output) => successes.push(output),
1254 Err(e) => {
1255 failures.push(
1256 ErrorInfo::new(format!("{:?}", e)).with_source(format!("item_{}", i)),
1257 );
1258 }
1259 }
1260 }
1261
1262 let throughput = if duration.as_secs_f64() > 0.0 {
1263 total as f64 / duration.as_secs_f64()
1264 } else {
1265 0.0
1266 };
1267
1268 Ok(PipelineResult {
1269 successes,
1270 failures,
1271 duration,
1272 throughput,
1273 })
1274 }
1275
1276 pub fn execute_three_stage<I, M1, M2, O, S1, S2, S3>(
1278 &self,
1279 inputs: Vec<I>,
1280 stage1: &S1,
1281 stage2: &S2,
1282 stage3: &S3,
1283 progress_message: &str,
1284 ) -> Result<PipelineResult<O>>
1285 where
1286 I: Send + Sync + Clone,
1287 M1: Send + Sync,
1288 M2: Send + Sync,
1289 O: Send,
1290 S1: PipelineStage<I, M1>,
1291 S2: PipelineStage<M1, M2>,
1292 S3: PipelineStage<M2, O>,
1293 {
1294 let start = Instant::now();
1295 let total = inputs.len() as u64;
1296
1297 let pb = self.multi_progress.add(ProgressBar::new(total));
1298 pb.set_style(
1299 ProgressStyle::default_bar()
1300 .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec})")
1301 .unwrap_or_else(|_| ProgressStyle::default_bar())
1302 .progress_chars("=>-"),
1303 );
1304 pb.set_message(progress_message.to_string());
1305
1306 let results: Vec<Result<O>> = inputs
1307 .par_iter()
1308 .map(|input| {
1309 let m1 = stage1.process(input.clone())?;
1310 let m2 = stage2.process(m1)?;
1311 let output = stage3.process(m2)?;
1312 pb.inc(1);
1313 Ok(output)
1314 })
1315 .collect();
1316
1317 pb.finish_with_message(format!("{}: complete", progress_message));
1318
1319 let duration = start.elapsed();
1320 let mut successes = Vec::new();
1321 let mut failures = Vec::new();
1322
1323 for (i, result) in results.into_iter().enumerate() {
1324 match result {
1325 Ok(output) => successes.push(output),
1326 Err(e) => {
1327 failures.push(
1328 ErrorInfo::new(format!("{:?}", e)).with_source(format!("item_{}", i)),
1329 );
1330 }
1331 }
1332 }
1333
1334 let throughput = if duration.as_secs_f64() > 0.0 {
1335 total as f64 / duration.as_secs_f64()
1336 } else {
1337 0.0
1338 };
1339
1340 Ok(PipelineResult {
1341 successes,
1342 failures,
1343 duration,
1344 throughput,
1345 })
1346 }
1347
1348 pub fn error_collector(&self) -> &ErrorCollector {
1350 &self.error_collector
1351 }
1352}
1353
1354impl Default for PipelineExecutor {
1355 fn default() -> Self {
1356 Self::new()
1357 }
1358}
1359
1360#[cfg(test)]
1365mod tests {
1366 use super::*;
1367
1368 #[test]
1369 fn test_optimal_thread_count() {
1370 let count = optimal_thread_count();
1371 assert!(count > 0);
1372 }
1373
1374 #[test]
1375 fn test_physical_core_count() {
1376 let count = physical_core_count();
1377 assert!(count > 0);
1378 assert!(
1381 count <= 1024,
1382 "Physical core count seems unreasonable: {}",
1383 count
1384 );
1385 }
1386
1387 #[test]
1388 fn test_thread_pool_config() {
1389 let config = ThreadPoolConfig::new()
1390 .with_num_threads(4)
1391 .with_stack_size(4 * 1024 * 1024)
1392 .with_name_prefix("test-worker")
1393 .with_priority(ThreadPriority::High);
1394
1395 assert_eq!(config.num_threads, Some(4));
1396 assert_eq!(config.stack_size, Some(4 * 1024 * 1024));
1397 assert_eq!(config.thread_name_prefix, "test-worker");
1398 assert_eq!(config.priority, ThreadPriority::High);
1399 }
1400
1401 #[test]
1402 fn test_work_item() {
1403 let item = WorkItem::new(42)
1404 .with_priority(10)
1405 .with_cost(100)
1406 .with_group("test-group");
1407
1408 assert_eq!(item.data, 42);
1409 assert_eq!(item.priority, 10);
1410 assert_eq!(item.estimated_cost, 100);
1411 assert_eq!(item.group_id, Some("test-group".to_string()));
1412 }
1413
1414 #[test]
1415 fn test_work_distributor() {
1416 let items: Vec<i32> = (0..100).collect();
1417 let mut distributor =
1418 WorkDistributor::from_items(items).with_strategy(DistributionStrategy::WorkStealing);
1419
1420 let results = distributor
1421 .process(|&x| Ok(x * 2))
1422 .expect("Processing failed");
1423
1424 assert_eq!(results.len(), 100);
1425 }
1426
1427 #[test]
1428 fn test_progress_stats() {
1429 let stats = ProgressStats {
1430 total: 100,
1431 completed: 50,
1432 failed: 5,
1433 ..Default::default()
1434 };
1435
1436 assert!((stats.percent_complete() - 55.0).abs() < 0.001);
1437 }
1438
1439 #[test]
1440 fn test_error_collector() {
1441 let collector = ErrorCollector::new();
1442
1443 let error = ErrorInfo::new("Test error")
1444 .with_source("test.txt")
1445 .with_recoverable(true);
1446
1447 collector.add_error(error).expect("Failed to add error");
1448
1449 assert!(collector.has_errors());
1450 assert_eq!(collector.error_count(), 1);
1451
1452 let errors = collector.get_errors().expect("Failed to get errors");
1453 assert_eq!(errors.len(), 1);
1454 assert_eq!(errors[0].message, "Test error");
1455 }
1456
1457 #[test]
1458 fn test_error_collector_collect() {
1459 let collector = ErrorCollector::new();
1460
1461 let ok_result: Result<i32> = Ok(42);
1462 let err_result: Result<i32> = Err(anyhow::anyhow!("Test error"));
1463
1464 let ok_value = collector.collect(ok_result, Some("source1"));
1465 let err_value = collector.collect(err_result, Some("source2"));
1466
1467 assert_eq!(ok_value, Some(42));
1468 assert_eq!(err_value, None);
1469 assert_eq!(collector.error_count(), 1);
1470 }
1471
1472 #[test]
1473 fn test_resource_manager() {
1474 let rm = ResourceManager::new(100, 4); assert!(
1477 rm.try_allocate(50 * 1024 * 1024)
1478 .expect("Allocation failed")
1479 );
1480 assert_eq!(
1481 rm.current_usage_mb().expect("Usage check failed").round() as u64,
1482 50
1483 );
1484
1485 assert!(
1486 rm.try_allocate(50 * 1024 * 1024)
1487 .expect("Allocation failed")
1488 );
1489 assert!(
1490 !rm.try_allocate(1024 * 1024)
1491 .expect("Allocation check failed")
1492 );
1493
1494 rm.release(50 * 1024 * 1024).expect("Release failed");
1495 assert_eq!(
1496 rm.current_usage_mb().expect("Usage check failed").round() as u64,
1497 50
1498 );
1499 }
1500
1501 #[test]
1502 fn test_resource_manager_threads() {
1503 let rm = ResourceManager::new(100, 2);
1504
1505 assert!(rm.acquire_thread());
1506 assert!(rm.acquire_thread());
1507 assert!(!rm.acquire_thread()); rm.release_thread();
1510 assert!(rm.acquire_thread()); }
1512
1513 #[test]
1514 fn test_batch_manager() {
1515 let bm = BatchManager::new(10);
1516 let items: Vec<i32> = (0..100).collect();
1517
1518 let result = bm
1519 .process_batches(items, Ok, "Test batch processing")
1520 .expect("Batch processing failed");
1521
1522 assert_eq!(result.len(), 100);
1523 }
1524
1525 #[test]
1526 fn test_tile_processor() {
1527 let processor = ParallelTileProcessor::new(256, 256);
1528
1529 let results = processor
1530 .process_tiles(1024, 1024, 0, |x, y, w, h| Ok((x, y, w, h)), "Test tiles")
1531 .expect("Tile processing failed");
1532
1533 assert_eq!(results.len(), 16); }
1535
1536 #[test]
1537 fn test_pipeline_stage() {
1538 let stage = FnStage::new("double", |x: i32| Ok(x * 2));
1539
1540 let result = stage.process(21).expect("Processing failed");
1541 assert_eq!(result, 42);
1542 assert_eq!(stage.name(), "double");
1543 }
1544
1545 #[test]
1546 fn test_pipeline_executor() {
1547 let executor = PipelineExecutor::new();
1548
1549 let stage1 = FnStage::new("add_one", |x: i32| Ok(x + 1));
1550 let stage2 = FnStage::new("double", |x: i32| Ok(x * 2));
1551
1552 let inputs: Vec<i32> = (0..10).collect();
1553 let result = executor
1554 .execute_two_stage(inputs, &stage1, &stage2, "Test pipeline")
1555 .expect("Pipeline failed");
1556
1557 assert_eq!(result.successes.len(), 10);
1558 assert!(result.failures.is_empty());
1559 assert_eq!(result.successes[0], 2); assert_eq!(result.successes[1], 4); }
1562
1563 #[test]
1564 fn test_three_stage_pipeline() {
1565 let executor = PipelineExecutor::new();
1566
1567 let stage1 = FnStage::new("add_one", |x: i32| Ok(x + 1));
1568 let stage2 = FnStage::new("double", |x: i32| Ok(x * 2));
1569 let stage3 = FnStage::new("to_string", |x: i32| Ok(x.to_string()));
1570
1571 let inputs: Vec<i32> = (0..5).collect();
1572 let result = executor
1573 .execute_three_stage(inputs, &stage1, &stage2, &stage3, "Test 3-stage pipeline")
1574 .expect("Pipeline failed");
1575
1576 assert_eq!(result.successes.len(), 5);
1577 assert_eq!(result.successes[0], "2");
1578 assert_eq!(result.successes[1], "4");
1579 }
1580
1581 #[test]
1582 fn test_progress_aggregator() {
1583 let agg = ProgressAggregator::new();
1584
1585 agg.register_operation("op1", 100)
1586 .expect("Registration failed");
1587 agg.register_operation("op2", 50)
1588 .expect("Registration failed");
1589
1590 agg.update("op1", 10, 0).expect("Update failed");
1591 agg.update("op2", 5, 1).expect("Update failed");
1592
1593 let stats = agg.get_aggregate_stats().expect("Stats failed");
1594 assert_eq!(stats.total, 150);
1595 assert_eq!(stats.completed, 15);
1596 assert_eq!(stats.failed, 1);
1597 }
1598}