1use scirs2_core::numeric::Float;
8use std::collections::{HashMap, VecDeque};
9use std::fmt::Debug;
10use std::time::{Duration, Instant};
11
12use crate::advanced_coordinator_modules::types::{
13 DataProfile, InterpolationMethodType, PerformanceMetrics,
14};
15use crate::error::{InterpolateError, InterpolateResult};
16
17#[derive(Debug)]
19pub struct InterpolationMemoryManager {
20 memory_tracker: MemoryTracker,
22 cache_manager: CacheManager,
24 allocation_strategy: MemoryAllocationStrategy,
26}
27
28impl InterpolationMemoryManager {
29 pub fn new() -> InterpolateResult<Self> {
31 Ok(Self {
32 memory_tracker: MemoryTracker::default(),
33 cache_manager: CacheManager::new()?,
34 allocation_strategy: MemoryAllocationStrategy::Adaptive,
35 })
36 }
37
38 pub fn track_memory_usage(&mut self, usage: usize, operation: String) -> InterpolateResult<()> {
40 self.memory_tracker.record_usage(usage, operation)?;
41
42 if self.memory_tracker.should_adjust_strategy() {
44 self.adjust_allocation_strategy()?;
45 }
46
47 Ok(())
48 }
49
50 pub fn get_current_usage(&self) -> usize {
52 self.memory_tracker.current_usage
53 }
54
55 pub fn get_peak_usage(&self) -> usize {
57 self.memory_tracker.peak_usage
58 }
59
60 pub fn get_memory_statistics(&self) -> MemoryStatistics {
62 MemoryStatistics {
63 current_usage: self.memory_tracker.current_usage,
64 peak_usage: self.memory_tracker.peak_usage,
65 average_usage: self.memory_tracker.calculate_average_usage(),
66 usage_trend: self.memory_tracker.calculate_usage_trend(),
67 allocation_strategy: self.allocation_strategy.clone(),
68 }
69 }
70
71 pub fn optimize_memory_usage(&mut self) -> InterpolateResult<MemoryOptimizationResult> {
73 let initial_usage = self.memory_tracker.current_usage;
74
75 let cache_freed = self.cache_manager.cleanup_cache()?;
77
78 self.adjust_allocation_strategy()?;
80
81 let final_usage = self.memory_tracker.current_usage;
82 let memory_freed = initial_usage.saturating_sub(final_usage);
83
84 Ok(MemoryOptimizationResult {
85 memory_freed: memory_freed + cache_freed,
86 cache_freed,
87 new_allocation_strategy: self.allocation_strategy.clone(),
88 optimization_effectiveness: if initial_usage > 0 {
89 memory_freed as f64 / initial_usage as f64
90 } else {
91 0.0
92 },
93 })
94 }
95
96 pub fn set_allocation_strategy(&mut self, strategy: MemoryAllocationStrategy) {
98 self.allocation_strategy = strategy;
99 }
100
101 fn adjust_allocation_strategy(&mut self) -> InterpolateResult<()> {
103 let avg_usage = self.memory_tracker.calculate_average_usage();
104 let peak_usage = self.memory_tracker.peak_usage;
105
106 if peak_usage > 0 {
107 let usage_ratio = avg_usage as f64 / peak_usage as f64;
108
109 self.allocation_strategy = if usage_ratio > 0.8 {
110 MemoryAllocationStrategy::Aggressive } else if usage_ratio < 0.3 {
112 MemoryAllocationStrategy::Conservative } else {
114 MemoryAllocationStrategy::Adaptive };
116 }
117
118 Ok(())
119 }
120}
121
122impl Default for InterpolationMemoryManager {
123 fn default() -> Self {
124 Self::new().unwrap_or_else(|_| Self {
125 memory_tracker: MemoryTracker::default(),
126 cache_manager: CacheManager::default(),
127 allocation_strategy: MemoryAllocationStrategy::Adaptive,
128 })
129 }
130}
131
132#[derive(Debug, Default)]
134pub struct MemoryTracker {
135 pub current_usage: usize,
137 pub peak_usage: usize,
139 pub usage_history: VecDeque<MemoryUsageRecord>,
141}
142
143impl MemoryTracker {
144 pub fn record_usage(&mut self, usage: usize, operation: String) -> InterpolateResult<()> {
146 self.current_usage = usage;
147 if usage > self.peak_usage {
148 self.peak_usage = usage;
149 }
150
151 let record = MemoryUsageRecord {
152 usage,
153 timestamp: Instant::now(),
154 operation,
155 };
156
157 self.usage_history.push_back(record);
158
159 if self.usage_history.len() > 1000 {
161 self.usage_history.pop_front();
162 }
163
164 Ok(())
165 }
166
167 pub fn calculate_average_usage(&self) -> usize {
169 if self.usage_history.is_empty() {
170 return 0;
171 }
172
173 let sum: usize = self.usage_history.iter().map(|record| record.usage).sum();
174 sum / self.usage_history.len()
175 }
176
177 pub fn calculate_usage_trend(&self) -> f64 {
179 if self.usage_history.len() < 2 {
180 return 0.0;
181 }
182
183 let recent_count = 10.min(self.usage_history.len());
184 let recent_usage: Vec<usize> = self
185 .usage_history
186 .iter()
187 .rev()
188 .take(recent_count)
189 .map(|record| record.usage)
190 .collect();
191
192 if recent_usage.len() < 2 {
193 return 0.0;
194 }
195
196 let first_half = &recent_usage[recent_usage.len() / 2..];
197 let second_half = &recent_usage[..recent_usage.len() / 2];
198
199 let first_avg = first_half.iter().sum::<usize>() as f64 / first_half.len() as f64;
200 let second_avg = second_half.iter().sum::<usize>() as f64 / second_half.len() as f64;
201
202 if first_avg > 0.0 {
203 (second_avg - first_avg) / first_avg
204 } else {
205 0.0
206 }
207 }
208
209 pub fn should_adjust_strategy(&self) -> bool {
211 self.usage_history.len() >= 10 && self.usage_history.len().is_multiple_of(50)
212 }
214}
215
216#[derive(Debug, Clone)]
218pub struct MemoryUsageRecord {
219 pub usage: usize,
221 pub timestamp: Instant,
223 pub operation: String,
225}
226
227#[derive(Debug)]
229pub struct CacheManager {
230 hit_ratio: f64,
232 cache_size: usize,
234 eviction_policy: CacheEvictionPolicy,
236 statistics: CacheStatistics,
238}
239
240impl CacheManager {
241 pub fn new() -> InterpolateResult<Self> {
243 Ok(Self {
244 hit_ratio: 0.0,
245 cache_size: 0,
246 eviction_policy: CacheEvictionPolicy::Adaptive,
247 statistics: CacheStatistics::default(),
248 })
249 }
250
251 pub fn record_hit(&mut self) {
253 self.statistics.hit_count += 1;
254 self.update_hit_ratio();
255 }
256
257 pub fn record_miss(&mut self) {
259 self.statistics.miss_count += 1;
260 self.update_hit_ratio();
261 }
262
263 pub fn record_eviction(&mut self, freed_bytes: usize) {
265 self.statistics.eviction_count += 1;
266 self.cache_size = self.cache_size.saturating_sub(freed_bytes);
267 self.statistics.total_cache_size = self.cache_size;
268 }
269
270 pub fn update_cache_size(&mut self, new_size: usize) {
272 self.cache_size = new_size;
273 self.statistics.total_cache_size = new_size;
274 }
275
276 pub fn get_statistics(&self) -> &CacheStatistics {
278 &self.statistics
279 }
280
281 pub fn get_hit_ratio(&self) -> f64 {
283 self.hit_ratio
284 }
285
286 pub fn cleanup_cache(&mut self) -> InterpolateResult<usize> {
288 let initial_size = self.cache_size;
289
290 let cleanup_percentage = match self.eviction_policy {
292 CacheEvictionPolicy::LRU => 0.3,
293 CacheEvictionPolicy::LFU => 0.25,
294 CacheEvictionPolicy::TimeBasedExpiration { .. } => 0.4,
295 CacheEvictionPolicy::SizeBasedEviction { .. } => 0.5,
296 CacheEvictionPolicy::Adaptive => {
297 if self.hit_ratio < 0.3 {
298 0.6 } else if self.hit_ratio < 0.7 {
300 0.3 } else {
302 0.1 }
304 }
305 };
306
307 let bytes_freed = (self.cache_size as f64 * cleanup_percentage) as usize;
308 self.cache_size = self.cache_size.saturating_sub(bytes_freed);
309 self.statistics.total_cache_size = self.cache_size;
310 self.statistics.eviction_count += 1;
311
312 Ok(bytes_freed)
313 }
314
315 fn update_hit_ratio(&mut self) {
317 let total_requests = self.statistics.hit_count + self.statistics.miss_count;
318 if total_requests > 0 {
319 self.hit_ratio = self.statistics.hit_count as f64 / total_requests as f64;
320 }
321 }
322}
323
324impl Default for CacheManager {
325 fn default() -> Self {
326 Self::new().unwrap_or_else(|_| Self {
327 hit_ratio: 0.0,
328 cache_size: 0,
329 eviction_policy: CacheEvictionPolicy::Adaptive,
330 statistics: CacheStatistics::default(),
331 })
332 }
333}
334
335#[derive(Debug, Clone)]
337pub enum CacheEvictionPolicy {
338 LRU,
340 LFU,
342 TimeBasedExpiration { ttl: Duration },
344 SizeBasedEviction { max_size: usize },
346 Adaptive,
348}
349
350#[derive(Debug, Clone)]
352pub enum MemoryAllocationStrategy {
353 Conservative,
355 Aggressive,
357 Adaptive,
359 Custom { strategy: String },
361}
362
363#[derive(Debug, Default)]
365pub struct InterpolationPerformanceTracker {
366 pub execution_times: VecDeque<f64>,
368 pub memory_usage: VecDeque<usize>,
370 pub accuracy_measurements: VecDeque<f64>,
372 pub method_usage: HashMap<InterpolationMethodType, MethodStats>,
374 pub performance_trends: PerformanceTrends,
376}
377
378impl InterpolationPerformanceTracker {
379 pub fn track_performance(
381 &mut self,
382 method: InterpolationMethodType,
383 performance: &PerformanceMetrics,
384 ) -> InterpolateResult<()> {
385 self.execution_times
387 .push_back(performance.execution_time_ms);
388 if self.execution_times.len() > 1000 {
389 self.execution_times.pop_front();
390 }
391
392 self.memory_usage.push_back(performance.memory_usage_bytes);
394 if self.memory_usage.len() > 1000 {
395 self.memory_usage.pop_front();
396 }
397
398 self.accuracy_measurements.push_back(performance.accuracy);
400 if self.accuracy_measurements.len() > 1000 {
401 self.accuracy_measurements.pop_front();
402 }
403
404 let stats = self.method_usage.entry(method).or_default();
406 stats.update_with_performance(performance);
407
408 self.update_performance_trends()?;
410
411 Ok(())
412 }
413
414 pub fn get_performance_summary(&self) -> PerformanceSummary {
416 PerformanceSummary {
417 average_execution_time: self.calculate_average_execution_time(),
418 average_memory_usage: self.calculate_average_memory_usage(),
419 average_accuracy: self.calculate_average_accuracy(),
420 best_performing_method: self.get_best_performing_method(),
421 performance_trends: self.performance_trends.clone(),
422 }
423 }
424
425 fn calculate_average_execution_time(&self) -> f64 {
427 if self.execution_times.is_empty() {
428 return 0.0;
429 }
430 self.execution_times.iter().sum::<f64>() / self.execution_times.len() as f64
431 }
432
433 fn calculate_average_memory_usage(&self) -> usize {
435 if self.memory_usage.is_empty() {
436 return 0;
437 }
438 self.memory_usage.iter().sum::<usize>() / self.memory_usage.len()
439 }
440
441 fn calculate_average_accuracy(&self) -> f64 {
443 if self.accuracy_measurements.is_empty() {
444 return 0.0;
445 }
446 self.accuracy_measurements.iter().sum::<f64>() / self.accuracy_measurements.len() as f64
447 }
448
449 fn get_best_performing_method(&self) -> Option<InterpolationMethodType> {
451 self.method_usage
452 .iter()
453 .max_by(|a, b| {
454 let score_a = a.1.calculate_performance_score();
455 let score_b = b.1.calculate_performance_score();
456 score_a
457 .partial_cmp(&score_b)
458 .unwrap_or(std::cmp::Ordering::Equal)
459 })
460 .map(|(method, _)| *method)
461 }
462
463 fn update_performance_trends(&mut self) -> InterpolateResult<()> {
465 self.performance_trends.execution_time_trend = self.calculate_trend(&self.execution_times);
466 self.performance_trends.memory_usage_trend = self.calculate_usage_trend(&self.memory_usage);
467 self.performance_trends.accuracy_trend = self.calculate_trend(&self.accuracy_measurements);
468
469 self.performance_trends.overall_performance_score =
471 (self.performance_trends.accuracy_trend * 0.5)
472 + (-self.performance_trends.execution_time_trend * 0.3)
473 + (-self.performance_trends.memory_usage_trend * 0.2);
474
475 Ok(())
476 }
477
478 fn calculate_trend(&self, values: &VecDeque<f64>) -> f64 {
480 if values.len() < 10 {
481 return 0.0;
482 }
483
484 let recent_count = 10.min(values.len());
485 let recent_values: Vec<f64> = values.iter().rev().take(recent_count).copied().collect();
486
487 let mid_point = recent_values.len() / 2;
488 let first_half = &recent_values[mid_point..];
489 let second_half = &recent_values[..mid_point];
490
491 let first_avg = first_half.iter().sum::<f64>() / first_half.len() as f64;
492 let second_avg = second_half.iter().sum::<f64>() / second_half.len() as f64;
493
494 if first_avg != 0.0 {
495 (second_avg - first_avg) / first_avg
496 } else {
497 0.0
498 }
499 }
500
501 fn calculate_usage_trend(&self, values: &VecDeque<usize>) -> f64 {
503 if values.len() < 10 {
504 return 0.0;
505 }
506
507 let recent_count = 10.min(values.len());
508 let recent_values: Vec<usize> = values.iter().rev().take(recent_count).copied().collect();
509
510 let mid_point = recent_values.len() / 2;
511 let first_half = &recent_values[mid_point..];
512 let second_half = &recent_values[..mid_point];
513
514 let first_avg = first_half.iter().sum::<usize>() as f64 / first_half.len() as f64;
515 let second_avg = second_half.iter().sum::<usize>() as f64 / second_half.len() as f64;
516
517 if first_avg != 0.0 {
518 (second_avg - first_avg) / first_avg
519 } else {
520 0.0
521 }
522 }
523}
524
525#[derive(Debug, Clone, Default)]
527pub struct MethodStats {
528 pub usage_count: usize,
530 pub avg_execution_time: f64,
532 pub avg_memory_usage: usize,
534 pub avg_accuracy: f64,
536 pub success_rate: f64,
538 total_executions: usize,
540 successful_executions: usize,
542}
543
544impl MethodStats {
545 pub fn update_with_performance(&mut self, performance: &PerformanceMetrics) {
547 self.usage_count += 1;
548 self.total_executions += 1;
549
550 if performance.accuracy > 0.7 {
551 self.successful_executions += 1;
552 }
553
554 let count = self.usage_count as f64;
556 self.avg_execution_time =
557 (self.avg_execution_time * (count - 1.0) + performance.execution_time_ms) / count;
558 self.avg_memory_usage = ((self.avg_memory_usage as f64 * (count - 1.0))
559 + performance.memory_usage_bytes as f64) as usize
560 / self.usage_count;
561 self.avg_accuracy = (self.avg_accuracy * (count - 1.0) + performance.accuracy) / count;
562
563 self.success_rate = self.successful_executions as f64 / self.total_executions as f64;
565 }
566
567 pub fn calculate_performance_score(&self) -> f64 {
569 if self.usage_count == 0 {
570 return 0.0;
571 }
572
573 let efficiency_score = if self.avg_execution_time > 0.0 {
575 1.0 / (1.0 + self.avg_execution_time / 1000.0) } else {
577 1.0
578 };
579
580 self.avg_accuracy * 0.5 + self.success_rate * 0.3 + efficiency_score * 0.2
581 }
582}
583
584#[derive(Debug, Default, Clone)]
586pub struct PerformanceTrends {
587 pub execution_time_trend: f64,
589 pub memory_usage_trend: f64,
591 pub accuracy_trend: f64,
593 pub overall_performance_score: f64,
595}
596
597#[derive(Debug)]
599pub struct AdaptiveInterpolationCache<F: Float + Debug> {
600 interpolant_cache: HashMap<InterpolantCacheKey, CachedInterpolant<F>>,
602 cache_stats: CacheStatistics,
604 cache_policy: AdaptiveCachePolicy,
606}
607
608impl<F: Float + Debug> AdaptiveInterpolationCache<F> {
609 pub fn new() -> InterpolateResult<Self> {
611 Ok(Self {
612 interpolant_cache: HashMap::new(),
613 cache_stats: CacheStatistics::default(),
614 cache_policy: AdaptiveCachePolicy::new(),
615 })
616 }
617
618 pub fn get(&mut self, key: &InterpolantCacheKey) -> Option<&CachedInterpolant<F>> {
620 if let Some(interpolant) = self.interpolant_cache.get_mut(key) {
621 interpolant.access_count += 1;
622 interpolant.last_access = Instant::now();
623 self.cache_stats.hit_count += 1;
624 Some(interpolant)
625 } else {
626 self.cache_stats.miss_count += 1;
627 None
628 }
629 }
630
631 pub fn store(
633 &mut self,
634 key: InterpolantCacheKey,
635 interpolant: CachedInterpolant<F>,
636 ) -> InterpolateResult<()> {
637 if self.should_evict() {
639 self.evict_entries()?;
640 }
641
642 let interpolant_size = interpolant.interpolant_data.len();
643 self.interpolant_cache.insert(key, interpolant);
644 self.cache_stats.total_cache_size += interpolant_size;
645
646 Ok(())
647 }
648
649 pub fn clear(&mut self) {
651 self.interpolant_cache.clear();
652 self.cache_stats.total_cache_size = 0;
653 self.cache_stats.eviction_count += 1;
654 }
655
656 pub fn get_statistics(&self) -> &CacheStatistics {
658 &self.cache_stats
659 }
660
661 fn should_evict(&self) -> bool {
663 match &self.cache_policy.base_policy {
664 CacheEvictionPolicy::SizeBasedEviction { max_size } => {
665 self.cache_stats.total_cache_size > *max_size
666 }
667 _ => self.interpolant_cache.len() > 1000, }
669 }
670
671 fn evict_entries(&mut self) -> InterpolateResult<()> {
673 match &self.cache_policy.base_policy {
674 CacheEvictionPolicy::LRU => {
675 self.evict_lru();
676 }
677 CacheEvictionPolicy::LFU => {
678 self.evict_lfu();
679 }
680 CacheEvictionPolicy::TimeBasedExpiration { ttl } => {
681 self.evict_expired(*ttl);
682 }
683 _ => {
684 self.evict_lru(); }
686 }
687
688 self.cache_stats.eviction_count += 1;
689 Ok(())
690 }
691
692 fn evict_lru(&mut self) {
694 if let Some((key_to_remove, size)) = self
695 .interpolant_cache
696 .iter()
697 .min_by_key(|(_, interpolant)| interpolant.last_access)
698 .map(|(key, interpolant)| (key.clone(), interpolant.interpolant_data.len()))
699 {
700 self.interpolant_cache.remove(&key_to_remove);
701 self.cache_stats.total_cache_size =
702 self.cache_stats.total_cache_size.saturating_sub(size);
703 }
704 }
705
706 fn evict_lfu(&mut self) {
708 if let Some((key_to_remove, size)) = self
709 .interpolant_cache
710 .iter()
711 .min_by_key(|(_, interpolant)| interpolant.access_count)
712 .map(|(key, interpolant)| (key.clone(), interpolant.interpolant_data.len()))
713 {
714 self.interpolant_cache.remove(&key_to_remove);
715 self.cache_stats.total_cache_size =
716 self.cache_stats.total_cache_size.saturating_sub(size);
717 }
718 }
719
720 fn evict_expired(&mut self, ttl: Duration) {
722 let now = Instant::now();
723 let mut keys_to_remove = Vec::new();
724 let mut total_size_removed = 0;
725
726 for (key, interpolant) in &self.interpolant_cache {
727 if now.duration_since(interpolant.creation_time) > ttl {
728 keys_to_remove.push(key.clone());
729 total_size_removed += interpolant.interpolant_data.len();
730 }
731 }
732
733 for key in keys_to_remove {
734 self.interpolant_cache.remove(&key);
735 }
736
737 self.cache_stats.total_cache_size = self
738 .cache_stats
739 .total_cache_size
740 .saturating_sub(total_size_removed);
741 }
742}
743
744impl<F: Float + Debug> Default for AdaptiveInterpolationCache<F> {
745 fn default() -> Self {
746 Self::new().unwrap_or_else(|_| Self {
747 interpolant_cache: HashMap::new(),
748 cache_stats: CacheStatistics::default(),
749 cache_policy: AdaptiveCachePolicy::default(),
750 })
751 }
752}
753
754#[derive(Debug, Clone, Hash, Eq, PartialEq)]
756pub struct InterpolantCacheKey {
757 pub data_signature: String,
759 pub method: InterpolationMethodType,
761 pub parameters: String, }
764
765#[derive(Debug, Clone)]
767pub struct CachedInterpolant<F: Float> {
768 pub interpolant_data: Vec<u8>,
770 pub creation_time: Instant,
772 pub access_count: usize,
774 pub last_access: Instant,
776 pub performance_metrics: CachedInterpolantMetrics<F>,
778}
779
780#[derive(Debug, Clone)]
782pub struct CachedInterpolantMetrics<F: Float> {
783 pub creation_time: F,
785 pub memory_usage: usize,
787 pub accuracy_score: F,
789}
790
791#[derive(Debug, Default)]
793pub struct CacheStatistics {
794 pub hit_count: usize,
796 pub miss_count: usize,
798 pub eviction_count: usize,
800 pub total_cache_size: usize,
802}
803
804impl CacheStatistics {
805 pub fn hit_ratio(&self) -> f64 {
807 let total = self.hit_count + self.miss_count;
808 if total > 0 {
809 self.hit_count as f64 / total as f64
810 } else {
811 0.0
812 }
813 }
814}
815
816#[derive(Debug)]
818pub struct AdaptiveCachePolicy {
819 base_policy: CacheEvictionPolicy,
821 adaptive_params: CacheAdaptiveParams,
823}
824
825impl AdaptiveCachePolicy {
826 pub fn new() -> Self {
828 Self {
829 base_policy: CacheEvictionPolicy::LRU,
830 adaptive_params: CacheAdaptiveParams::default(),
831 }
832 }
833
834 pub fn adapt_policy(&mut self, stats: &CacheStatistics) {
836 let hit_ratio = stats.hit_ratio();
837
838 if hit_ratio < self.adaptive_params.hit_ratio_threshold {
839 self.base_policy = match &self.base_policy {
841 CacheEvictionPolicy::LRU => CacheEvictionPolicy::LFU,
842 CacheEvictionPolicy::LFU => CacheEvictionPolicy::TimeBasedExpiration {
843 ttl: Duration::from_secs(300),
844 },
845 _ => CacheEvictionPolicy::LRU,
846 };
847 }
848 }
849}
850
851impl Default for AdaptiveCachePolicy {
852 fn default() -> Self {
853 Self::new()
854 }
855}
856
857#[derive(Debug, Clone)]
859pub struct CacheAdaptiveParams {
860 pub hit_ratio_threshold: f64,
862 pub memory_pressure_threshold: f64,
864 pub access_pattern_weight: f64,
866 pub temporal_locality_weight: f64,
868}
869
870impl Default for CacheAdaptiveParams {
871 fn default() -> Self {
872 Self {
873 hit_ratio_threshold: 0.7,
874 memory_pressure_threshold: 0.8,
875 access_pattern_weight: 0.6,
876 temporal_locality_weight: 0.4,
877 }
878 }
879}
880
881#[derive(Debug, Clone)]
883pub struct MemoryStatistics {
884 pub current_usage: usize,
886 pub peak_usage: usize,
888 pub average_usage: usize,
890 pub usage_trend: f64,
892 pub allocation_strategy: MemoryAllocationStrategy,
894}
895
896#[derive(Debug, Clone)]
898pub struct MemoryOptimizationResult {
899 pub memory_freed: usize,
901 pub cache_freed: usize,
903 pub new_allocation_strategy: MemoryAllocationStrategy,
905 pub optimization_effectiveness: f64,
907}
908
909#[derive(Debug, Clone)]
911pub struct PerformanceSummary {
912 pub average_execution_time: f64,
914 pub average_memory_usage: usize,
916 pub average_accuracy: f64,
918 pub best_performing_method: Option<InterpolationMethodType>,
920 pub performance_trends: PerformanceTrends,
922}