scirs2_core/memory/metrics/
reporter.rs1#[cfg(feature = "memory_metrics")]
6use serde_json::{json, Value as JsonValue};
7use std::time::Duration;
8
9use crate::memory::metrics::collector::MemoryReport;
10
11#[allow(dead_code)]
13pub fn format_bytes(bytes: usize) -> String {
14 const KB: usize = 1024;
15 const MB: usize = KB * 1024;
16 const GB: usize = MB * 1024;
17
18 if bytes >= GB {
19 format!("{:.2} GB", bytes as f64 / GB as f64)
20 } else if bytes >= MB {
21 format!("{:.2} MB", bytes as f64 / MB as f64)
22 } else if bytes >= KB {
23 format!("{:.2} KB", bytes as f64 / KB as f64)
24 } else {
25 format!("{bytes} bytes")
26 }
27}
28
29#[allow(dead_code)]
31pub fn format_duration(duration: Duration) -> String {
32 let total_secs = duration.as_secs();
33
34 if total_secs < 60 {
35 return format!("{total_secs}s");
36 }
37
38 let mins = total_secs / 60;
39 let secs = total_secs % 60;
40
41 if mins < 60 {
42 return format!("{mins}m {secs}s");
43 }
44
45 let hours = mins / 60;
46 let mins = mins % 60;
47
48 format!("{hours}h {mins}m {secs}s")
49}
50
51impl MemoryReport {
52 pub fn format(&self) -> String {
54 let mut output = String::new();
55
56 output.push_str(&format!(
57 "Memory Report (duration: {})\n",
58 format_duration(Duration::from_secs(1))
59 ));
60 output.push_str(&format!(
61 "Total Current Usage: {}\n",
62 format_bytes(self.total_current_usage)
63 ));
64 output.push_str(&format!(
65 "Total Peak Usage: {}\n",
66 format_bytes(self.total_peak_usage)
67 ));
68 output.push_str(&format!(
69 "Total Allocations: {}\n",
70 self.total_allocation_count
71 ));
72 output.push_str(&format!(
73 "Total Allocated Bytes: {}\n",
74 format_bytes(self.total_allocated_bytes)
75 ));
76
77 output.push_str("\nComponent Statistics:\n");
78
79 let mut components: Vec<_> = self.component_stats.iter().collect();
81 components.sort_by(|a, b| b.1.peak_usage.cmp(&a.1.peak_usage));
82
83 for (component, stats) in components {
84 output.push_str(&format!("\n {component}\n"));
85 output.push_str(&format!(
86 " Current Usage: {}\n",
87 format_bytes(stats.current_usage)
88 ));
89 output.push_str(&format!(
90 " Peak Usage: {}\n",
91 format_bytes(stats.peak_usage)
92 ));
93 output.push_str(&format!(
94 " Allocation Count: {}\n",
95 stats.allocation_count
96 ));
97 output.push_str(&format!(
98 " Total Allocated: {}\n",
99 format_bytes(stats.total_allocated)
100 ));
101 output.push_str(&format!(
102 " Avg Allocation Size: {}\n",
103 format_bytes(stats.avg_allocation_size as usize)
104 ));
105
106 if stats.peak_usage > 0 {
108 let reuse_ratio = stats.total_allocated as f64 / stats.peak_usage as f64;
109 output.push_str(&format!(" Memory Reuse Ratio: {reuse_ratio:.2}\n"));
110 }
111
112 if self.duration.as_secs_f64() > 0.0 {
114 let alloc_per_sec = stats.allocation_count as f64 / self.duration.as_secs_f64();
115 output.push_str(&format!(" Allocations/sec: {alloc_per_sec:.2}\n"));
116 }
117 }
118
119 output
120 }
121
122 #[cfg(feature = "memory_metrics")]
124 pub fn to_json(&self) -> JsonValue {
125 let mut component_stats = serde_json::Map::new();
126
127 for (component, stats) in &self.component_stats {
128 let reuse_ratio = if stats.peak_usage > 0 {
129 stats.total_allocated as f64 / stats.peak_usage as f64
130 } else {
131 0.0
132 };
133
134 let alloc_per_sec = if self.duration.as_secs_f64() > 0.0 {
135 stats.allocation_count as f64 / self.duration.as_secs_f64()
136 } else {
137 0.0
138 };
139
140 let stats_obj = json!({
141 "current_usage": stats.current_usage,
142 "current_usage_formatted": format_bytes(stats.current_usage),
143 "peak_usage": stats.peak_usage,
144 "peak_usage_formatted": format_bytes(stats.peak_usage),
145 "allocation_count": stats.allocation_count,
146 "total_allocated": stats.total_allocated,
147 "total_allocated_formatted": format_bytes(stats.total_allocated),
148 "avg_allocation_size": stats.avg_allocation_size,
149 "avg_allocation_size_formatted": format_bytes(stats.avg_allocation_size as usize),
150 "reuse_ratio": reuse_ratio,
151 "alloc_per_sec": alloc_per_sec,
152 });
153
154 component_stats.insert(component.clone(), stats_obj);
155 }
156
157 json!({
158 "total_current_usage": self.total_current_usage,
159 "total_current_usage_formatted": format_bytes(self.total_current_usage),
160 "total_peak_usage": self.total_peak_usage,
161 "total_peak_usage_formatted": format_bytes(self.total_peak_usage),
162 "total_allocation_count": self.total_allocation_count,
163 "total_allocated_bytes": self.total_allocated_bytes,
164 "total_allocated_bytes_formatted": format_bytes(self.total_allocated_bytes),
165 "duration_seconds": self.duration.as_secs_f64(),
166 "duration_formatted": format_duration(self.duration),
167 "components": component_stats,
168 })
169 }
170
171 #[cfg(not(feature = "memory_metrics"))]
173 pub fn to_json_2(&self) -> String {
174 "{}".to_string()
175 }
176
177 #[cfg(feature = "memory_visualization")]
179 pub fn generate_chart(&self, format: ChartFormat) -> String {
180 match format {
181 ChartFormat::Ascii => self.generate_ascii_chart(),
182 ChartFormat::Svg => self.generate_svg_chart(),
183 }
184 }
185
186 #[cfg(feature = "memory_visualization")]
188 fn generate_ascii_chart(&self) -> String {
189 let mut output = String::new();
190
191 output.push_str("Memory Usage by Component (ASCII Chart)\n\n");
192
193 let mut components: Vec<_> = self.component_stats.iter().collect();
195 components.sort_by(|a, b| b.1.peak_usage.cmp(&a.1.peak_usage));
196
197 let max_usage = components
199 .first()
200 .map(|(_, stats)| stats.peak_usage)
201 .unwrap_or(0);
202
203 if max_usage == 0 {
204 return "No memory usage data available.".to_string();
205 }
206
207 const CHART_WIDTH: usize = 50;
209
210 for (component, stats) in components {
211 let peak_width =
212 (stats.peak_usage as f64 / max_usage as f64 * CHART_WIDTH as f64) as usize;
213 let current_width =
214 (stats.current_usage as f64 / max_usage as f64 * CHART_WIDTH as f64) as usize;
215
216 let peak_bar = "#".repeat(peak_width);
217 let current_bar = if current_width > 0 {
218 "|".repeat(current_width)
219 } else {
220 String::new()
221 };
222
223 output.push_str(&format!(
224 "{:<20} [{:<50}] {}\n",
225 component,
226 format!("{current_bar}{peak_bar}"),
227 format_bytes(stats.peak_usage)
228 ));
229 }
230
231 output.push_str("\nLegend: | = Current Usage, # = Peak Usage\n");
232
233 output
234 }
235
236 #[cfg(feature = "memory_visualization")]
238 fn generate_svg_chart(&self) -> String {
239 "SVG chart generation not implemented".to_string()
242 }
243}
244
245#[cfg(feature = "memory_visualization")]
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ChartFormat {
249 Ascii,
251 Svg,
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::memory::metrics::collector::ComponentMemoryStats;
259 use std::collections::HashMap;
260 use std::time::Duration;
261
262 #[test]
263 fn test_format_bytes() {
264 assert_eq!(format_bytes(512), "512 bytes");
265 assert_eq!(format_bytes(1024), "1.00 KB");
266 assert_eq!(format_bytes(1536), "1.50 KB");
267 assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
268 assert_eq!(format_bytes(1536 * 1024), "1.50 MB");
269 assert_eq!(format_bytes(1024 * 1024 * 1024), "1.00 GB");
270 }
271
272 #[test]
273 fn test_format_duration() {
274 assert_eq!(format_duration(Duration::from_secs(30)), "30s");
275 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
276 assert_eq!(format_duration(Duration::from_secs(3600 + 90)), "1h 1m 30s");
277 }
278
279 #[test]
280 fn test_memory_report_format() {
281 let mut component_stats = HashMap::new();
282
283 component_stats.insert(
284 "Component1".to_string(),
285 ComponentMemoryStats {
286 current_usage: 1024,
287 peak_usage: 2048,
288 allocation_count: 10,
289 total_allocated: 4096,
290 avg_allocation_size: 409.6,
291 },
292 );
293
294 component_stats.insert(
295 "Component2".to_string(),
296 ComponentMemoryStats {
297 current_usage: 512,
298 peak_usage: 1024,
299 allocation_count: 5,
300 total_allocated: 2048,
301 avg_allocation_size: 409.6,
302 },
303 );
304
305 let report = MemoryReport {
306 total_current_usage: 1536,
307 total_peak_usage: 3072,
308 total_allocation_count: 15,
309 total_allocated_bytes: 6144,
310 component_stats,
311 duration: Duration::from_secs(60),
312 };
313
314 let formatted = report.format();
315
316 assert!(formatted.contains("Total Current Usage: 1.50 KB"));
318 assert!(formatted.contains("Total Peak Usage: 3.00 KB"));
319 assert!(formatted.contains("Total Allocations: 15"));
320 assert!(formatted.contains("Component1"));
321 assert!(formatted.contains("Component2"));
322 }
323
324 #[test]
325 fn test_memory_report_to_json() {
326 let mut component_stats = HashMap::new();
327
328 component_stats.insert(
329 "Component1".to_string(),
330 ComponentMemoryStats {
331 current_usage: 1024,
332 peak_usage: 2048,
333 allocation_count: 10,
334 total_allocated: 4096,
335 avg_allocation_size: 409.6,
336 },
337 );
338
339 let report = MemoryReport {
340 total_current_usage: 1024,
341 total_peak_usage: 2048,
342 total_allocation_count: 10,
343 total_allocated_bytes: 4096,
344 component_stats,
345 duration: Duration::from_secs(30),
346 };
347
348 #[cfg(feature = "memory_metrics")]
349 {
350 let json = report.to_json();
351 assert_eq!(json["total_current_usage"], 1024);
352 assert_eq!(json["total_peak_usage"], 2048);
353 assert_eq!(json["total_allocation_count"], 10);
354 assert_eq!(json["components"]["Component1"]["current_usage"], 1024);
355 }
356
357 #[cfg(not(feature = "memory_metrics"))]
358 {
359 let _ = report;
361 }
362 }
363}