ppt_rs/generator/
memory_profile.rs1use std::time::{Duration, Instant};
4
5use super::slide_content::SlideContent;
6use super::{create_pptx_lazy_to_writer, create_pptx_with_content, LazySlideSource};
7use std::io::Cursor;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct GenerationMetrics {
12 pub slide_count: usize,
13 pub output_bytes: usize,
14 pub slide_payload_bytes: usize,
15 pub duration: Duration,
16}
17
18impl GenerationMetrics {
19 pub fn bytes_per_slide(&self) -> usize {
21 if self.slide_count == 0 {
22 0
23 } else {
24 self.output_bytes / self.slide_count
25 }
26 }
27}
28
29pub fn estimate_slide_payload(slides: &[SlideContent]) -> usize {
31 slides.iter().fold(0, |total, slide| {
32 let title_bytes = slide.title.len();
33 let bullet_bytes: usize = slide.bullets.iter().map(|b| b.text.len()).sum();
34 let notes_bytes = slide.notes.as_ref().map(|n| n.len()).unwrap_or(0);
35 total + title_bytes + bullet_bytes + notes_bytes
36 })
37}
38
39pub fn estimate_output_capacity(slide_count: usize, slides: Option<&[SlideContent]>) -> usize {
41 const BASE_BYTES: usize = 32_768;
42 const PER_SLIDE_BYTES: usize = 4_096;
43
44 let mut cap = BASE_BYTES + slide_count.saturating_mul(PER_SLIDE_BYTES);
45
46 if let Some(slides) = slides {
47 cap += estimate_slide_payload(slides);
48 for slide in slides {
49 cap += slide.charts.len().saturating_mul(8_192);
50 for image in &slide.images {
51 if let Some(bytes) = image.get_bytes() {
52 cap += bytes.len();
53 }
54 }
55 }
56 }
57
58 cap.max(8_192)
59}
60
61pub fn profile_eager_generation(title: &str, slides: Vec<SlideContent>) -> GenerationMetrics {
63 let slide_count = slides.len();
64 let slide_payload_bytes = estimate_slide_payload(&slides);
65 let start = Instant::now();
66 let output = create_pptx_with_content(title, slides).expect("pptx generation");
67 let duration = start.elapsed();
68
69 GenerationMetrics {
70 slide_count,
71 output_bytes: output.len(),
72 slide_payload_bytes,
73 duration,
74 }
75}
76
77struct IndexedSlideSource {
78 slides: Vec<SlideContent>,
79}
80
81impl LazySlideSource for IndexedSlideSource {
82 fn slide_count(&self) -> usize {
83 self.slides.len()
84 }
85
86 fn generate_slide(&self, index: usize) -> Option<SlideContent> {
87 self.slides.get(index).cloned()
88 }
89}
90
91pub fn profile_lazy_generation(title: &str, slides: Vec<SlideContent>) -> GenerationMetrics {
93 let slide_count = slides.len();
94 let slide_payload_bytes = estimate_slide_payload(&slides);
95 let start = Instant::now();
96 let cursor = Cursor::new(Vec::new());
97 let source = Box::new(IndexedSlideSource { slides });
98 let output = create_pptx_lazy_to_writer(cursor, title, source, None)
99 .expect("lazy pptx generation")
100 .into_inner();
101 let duration = start.elapsed();
102
103 GenerationMetrics {
104 slide_count,
105 output_bytes: output.len(),
106 slide_payload_bytes,
107 duration,
108 }
109}
110
111pub fn sample_slides(count: usize) -> Vec<SlideContent> {
113 (0..count)
114 .map(|index| {
115 let title = format!("Slide {index}");
116 let bullet_a = format!("Bullet A on slide {index}");
117 let bullet_b = format!("Bullet B on slide {index}");
118 SlideContent::new(&title)
119 .add_bullet(&bullet_a)
120 .add_bullet(&bullet_b)
121 })
122 .collect()
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_estimate_slide_payload() {
131 let slides = sample_slides(2);
132 let bytes = estimate_slide_payload(&slides);
133 assert!(bytes > 0);
134 }
135
136 #[test]
137 fn test_profile_eager_10_slides() {
138 let metrics = profile_eager_generation("Profile", sample_slides(10));
139 assert_eq!(metrics.slide_count, 10);
140 assert!(metrics.output_bytes > 1_000);
141 assert!(metrics.duration < Duration::from_secs(5));
142 }
143}