#[cfg(feature = "std")]
use std::time::{Duration, Instant};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use alloc::string::String;
use crate::nodes::node::Node;
use crate::parser::utils::visit::visit_with_depth;
#[derive(Clone, Debug, Default)]
pub struct DocumentStats {
pub total_nodes: usize,
pub max_depth: usize,
pub string_count: usize,
pub number_count: usize,
pub boolean_count: usize,
pub array_count: usize,
pub mapping_count: usize,
pub set_count: usize,
pub anchor_count: usize,
pub alias_count: usize,
pub tagged_count: usize,
pub total_string_bytes: usize,
pub largest_array: usize,
pub largest_mapping: usize,
}
impl DocumentStats {
pub fn new() -> Self {
Self::default()
}
pub fn from_node(node: &Node) -> Self {
let mut stats = Self::new();
visit_with_depth(node, 0, &mut |node, depth| {
stats.total_nodes += 1;
if depth > stats.max_depth {
stats.max_depth = depth;
}
match node {
Node::Str(s, _, _) => {
stats.string_count += 1;
stats.total_string_bytes += s.len();
}
Node::Number(_) => {
stats.number_count += 1;
}
Node::Boolean(_) => {
stats.boolean_count += 1;
}
Node::Array(items) => {
stats.array_count += 1;
if items.len() > stats.largest_array {
stats.largest_array = items.len();
}
}
Node::Mapping(pairs) => {
stats.mapping_count += 1;
if pairs.len() > stats.largest_mapping {
stats.largest_mapping = pairs.len();
}
}
Node::Set(_) => {
stats.set_count += 1;
}
Node::Document(_) | Node::Documents(_) => {}
Node::Anchored(_, _) => {
stats.anchor_count += 1;
}
Node::Tagged(_, _) => {
stats.tagged_count += 1;
}
Node::Alias(_) => {
stats.alias_count += 1;
}
Node::Comment(_) | Node::None => {}
}
});
stats
}
pub fn estimated_memory_bytes(&self) -> usize {
let node_overhead = self.total_nodes * 64; let string_data = self.total_string_bytes;
let collection_overhead = (self.array_count + self.mapping_count + self.set_count) * 24;
node_overhead + string_data + collection_overhead
}
#[cfg(feature = "alloc")]
pub fn summary(&self) -> String {
alloc::format!(
"Document Statistics:\n\
- Total nodes: {}\n\
- Max depth: {}\n\
- Strings: {} ({} bytes)\n\
- Numbers: {}\n\
- Booleans: {}\n\
- Arrays: {} (largest: {})\n\
- Mappings: {} (largest: {})\n\
- Sets: {}\n\
- Anchors: {}\n\
- Aliases: {}\n\
- Tagged: {}\n\
- Est. memory: {} bytes",
self.total_nodes,
self.max_depth,
self.string_count,
self.total_string_bytes,
self.number_count,
self.boolean_count,
self.array_count,
self.largest_array,
self.mapping_count,
self.largest_mapping,
self.set_count,
self.anchor_count,
self.alias_count,
self.tagged_count,
self.estimated_memory_bytes()
)
}
}
#[cfg(feature = "std")]
#[derive(Debug)]
pub struct Timer {
start: Instant,
#[allow(dead_code)]
label: String,
}
#[cfg(feature = "std")]
impl Timer {
pub fn new<S: Into<String>>(label: S) -> Self {
Self {
start: Instant::now(),
label: label.into(),
}
}
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
pub fn stop(self) -> Duration {
self.elapsed()
}
pub fn stop_and_print(self) {
#[cfg(feature = "debug-trace")]
{
let elapsed = self.elapsed();
println!("{}: {:?}", self.label, elapsed);
}
}
}
#[cfg(all(feature = "std", feature = "alloc"))]
#[derive(Debug, Default)]
pub struct Profiler {
measurements: Vec<(String, Duration)>,
}
#[cfg(all(feature = "std", feature = "alloc"))]
impl Profiler {
pub fn new() -> Self {
Self {
measurements: Vec::new(),
}
}
pub fn time<F, R>(&mut self, label: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
let elapsed = start.elapsed();
self.measurements.push((label.to_string(), elapsed));
result
}
pub fn measurements(&self) -> &[(String, Duration)] {
&self.measurements
}
pub fn total_time(&self) -> Duration {
self.measurements.iter().map(|(_, d)| *d).sum()
}
pub fn print_results(&self) {
#[cfg(feature = "debug-trace")]
{
println!("Performance Profile:");
println!("{:-<60}", "");
for (label, duration) in &self.measurements {
println!("{:<40} {:>15?}", label, duration);
}
println!("{:-<60}", "");
println!("{:<40} {:>15?}", "Total", self.total_time());
}
}
pub fn clear(&mut self) {
self.measurements.clear();
}
}
#[cfg(feature = "std")]
pub fn compare_performance<F1, F2>(label1: &str, f1: F1, label2: &str, f2: F2)
where
F1: FnOnce(),
F2: FnOnce(),
{
let timer1 = Timer::new(label1);
f1();
let time1 = timer1.stop();
let timer2 = Timer::new(label2);
f2();
let time2 = timer2.stop();
#[cfg(feature = "debug-trace")]
println!("Performance Comparison:");
#[cfg(feature = "debug-trace")]
println!(" {}: {:?}", label1, time1);
#[cfg(feature = "debug-trace")]
println!(" {}: {:?}", label2, time2);
if time1 < time2 {
let _ratio = time2.as_secs_f64() / time1.as_secs_f64();
#[cfg(feature = "debug-trace")]
println!(" {} is {:.2}x faster", label1, _ratio);
} else if time2 < time1 {
let _ratio = time1.as_secs_f64() / time2.as_secs_f64();
#[cfg(feature = "debug-trace")]
println!(" {} is {:.2}x faster", label2, _ratio);
} else {
#[cfg(feature = "debug-trace")]
println!(" Both approaches have similar performance");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_stats_estimated_memory_bytes() {
let mut stats = DocumentStats::new();
stats.total_nodes = 3;
stats.total_string_bytes = 10;
stats.array_count = 1;
stats.mapping_count = 1;
stats.set_count = 1;
let mem = stats.estimated_memory_bytes();
assert!(mem >= 3 * 64 + 10 + 3 * 24);
}
#[test]
#[cfg(feature = "alloc")]
fn test_document_stats_summary() {
let stats = DocumentStats {
total_nodes: 2,
max_depth: 1,
string_count: 1,
number_count: 1,
boolean_count: 0,
array_count: 0,
mapping_count: 0,
set_count: 0,
anchor_count: 0,
alias_count: 0,
tagged_count: 0,
total_string_bytes: 5,
largest_array: 0,
largest_mapping: 0,
};
let summary = stats.summary();
assert!(summary.contains("Total nodes: 2"));
assert!(summary.contains("Strings: 1 (5 bytes)"));
}
#[test]
fn test_document_stats_from_node_edge_cases() {
use crate::nodes::node::Node;
let node = Node::None;
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.total_nodes, 1);
let node = Node::Array(vec![]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.array_count, 1);
let node = Node::Mapping(vec![]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.mapping_count, 1);
}
#[test]
#[cfg(feature = "std")]
fn test_compare_performance_runs() {
compare_performance("a", || {}, "b", || {});
}
#[test]
fn test_document_stats_empty() {
let stats = DocumentStats::new();
assert_eq!(stats.total_nodes, 0);
assert_eq!(stats.max_depth, 0);
}
#[test]
fn test_document_stats_simple() {
let node = Node::from(42);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.total_nodes, 1);
assert_eq!(stats.number_count, 1);
assert_eq!(stats.max_depth, 0);
}
#[test]
fn test_document_stats_array() {
let node = Node::Array(vec![Node::from(1), Node::from(2), Node::from(3)]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.total_nodes, 4); assert_eq!(stats.array_count, 1);
assert_eq!(stats.number_count, 3);
assert_eq!(stats.largest_array, 3);
assert_eq!(stats.max_depth, 1);
}
#[test]
fn test_document_stats_nested() {
let node = Node::Array(vec![
Node::from(1),
Node::Array(vec![Node::from(2), Node::from(3)]),
]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.total_nodes, 5);
assert_eq!(stats.array_count, 2);
assert_eq!(stats.number_count, 3);
assert_eq!(stats.max_depth, 2);
}
#[test]
fn test_document_stats_mapping() {
let node = Node::Mapping(vec![
(Node::from("key1"), Node::from("value1")),
(Node::from("key2"), Node::from("value2")),
]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.total_nodes, 5); assert_eq!(stats.mapping_count, 1);
assert_eq!(stats.string_count, 4);
assert_eq!(stats.largest_mapping, 2);
assert_eq!(stats.total_string_bytes, 20); }
#[test]
fn test_document_stats_mixed() {
let node = Node::Mapping(vec![
(
Node::from("numbers"),
Node::Array(vec![Node::from(1), Node::from(2)]),
),
(Node::from("text"), Node::from("hello")),
(Node::from("flag"), Node::from(true)),
]);
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.mapping_count, 1);
assert_eq!(stats.array_count, 1);
assert_eq!(stats.string_count, 4); assert_eq!(stats.number_count, 2);
assert_eq!(stats.boolean_count, 1);
}
#[test]
fn test_document_stats_anchors() {
use alloc::boxed::Box;
let node = Node::Anchored(Box::new(Node::from(42)), "anchor".to_string());
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.anchor_count, 1);
assert_eq!(stats.number_count, 1);
}
#[test]
fn test_document_stats_alias() {
let node = Node::Alias("ref".to_string());
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.alias_count, 1);
assert_eq!(stats.total_nodes, 1);
}
#[test]
fn test_document_stats_tagged() {
use alloc::boxed::Box;
let node = Node::Tagged(Box::new(Node::from("value")), "!custom".to_string());
let stats = DocumentStats::from_node(&node);
assert_eq!(stats.tagged_count, 1);
assert_eq!(stats.string_count, 1);
}
#[test]
fn test_estimated_memory() {
let node = Node::Array(vec![Node::from(1), Node::from(2)]);
let stats = DocumentStats::from_node(&node);
let mem = stats.estimated_memory_bytes();
assert!(mem > 0);
assert!(mem >= stats.total_nodes * 64);
}
#[cfg(feature = "alloc")]
#[test]
fn test_summary_format() {
let node = Node::Array(vec![Node::from(1), Node::from(2)]);
let stats = DocumentStats::from_node(&node);
let summary = stats.summary();
assert!(summary.contains("Total nodes: 3"));
assert!(summary.contains("Arrays: 1"));
assert!(summary.contains("Numbers: 2"));
}
#[cfg(feature = "std")]
#[test]
fn test_timer_creation() {
let timer = Timer::new("test");
assert_eq!(timer.label, "test");
}
#[cfg(feature = "std")]
#[test]
fn test_timer_elapsed() {
let timer = Timer::new("test");
std::thread::sleep(std::time::Duration::from_millis(10));
let elapsed = timer.elapsed();
assert!(elapsed.as_millis() >= 10);
}
#[cfg(feature = "std")]
#[test]
fn test_timer_stop() {
let timer = Timer::new("test");
std::thread::sleep(std::time::Duration::from_millis(10));
let duration = timer.stop();
assert!(duration.as_millis() >= 10);
}
#[cfg(all(feature = "std", feature = "alloc"))]
#[test]
fn test_profiler_creation() {
let profiler = Profiler::new();
assert_eq!(profiler.measurements.len(), 0);
}
#[cfg(all(feature = "std", feature = "alloc"))]
#[test]
fn test_profiler_time() {
let mut profiler = Profiler::new();
let result = profiler.time("test_op", || {
std::thread::sleep(std::time::Duration::from_millis(10));
42
});
assert_eq!(result, 42);
assert_eq!(profiler.measurements().len(), 1);
assert_eq!(profiler.measurements()[0].0, "test_op");
assert!(profiler.measurements()[0].1.as_millis() >= 10);
}
#[cfg(all(feature = "std", feature = "alloc"))]
#[test]
fn test_profiler_multiple_measurements() {
let mut profiler = Profiler::new();
profiler.time("op1", || {
std::thread::sleep(std::time::Duration::from_millis(10))
});
profiler.time("op2", || {
std::thread::sleep(std::time::Duration::from_millis(20))
});
assert_eq!(profiler.measurements().len(), 2);
let total = profiler.total_time();
assert!(total.as_millis() >= 30);
}
#[cfg(all(feature = "std", feature = "alloc"))]
#[test]
fn test_profiler_clear() {
let mut profiler = Profiler::new();
profiler.time("op", || {});
assert_eq!(profiler.measurements().len(), 1);
profiler.clear();
assert_eq!(profiler.measurements().len(), 0);
}
}