#![allow(dead_code)]
use embedded_charts::{
chart::traits::{Chart, ChartConfig},
data::{point::Point2D, series::StaticDataSeries},
error::ChartResult,
};
use embedded_graphics::{pixelcolor::Rgb565, prelude::*, primitives::Rectangle};
use super::{MemoryMetrics, PerformanceMetrics, TEST_VIEWPORT};
pub struct PerformanceBenchmark;
impl PerformanceBenchmark {
pub fn benchmark_data_scaling<T>(chart: &T) -> ChartResult<Vec<PerformanceMetrics>>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let data_sizes = [1, 5, 10, 25, 50, 100, 256];
let mut results = Vec::new();
for &size in &data_sizes {
let mut data = StaticDataSeries::new();
for i in 0..size {
data.push(Point2D::new(i as f32, (i % 50) as f32)).ok();
}
let metrics = Self::measure_single_render(chart, &data)?;
results.push(metrics);
}
Ok(results)
}
pub fn benchmark_viewport_scaling<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
) -> ChartResult<Vec<PerformanceMetrics>>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let viewport_sizes = [
Size::new(64, 48), Size::new(128, 96), Size::new(320, 240), Size::new(640, 480), Size::new(800, 600), ];
let mut results = Vec::new();
for &size in &viewport_sizes {
let viewport = Rectangle::new(Point::zero(), size);
let metrics = Self::measure_single_render_with_viewport(chart, data, viewport)?;
results.push(metrics);
}
Ok(results)
}
pub fn measure_single_render<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
) -> ChartResult<PerformanceMetrics>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
Self::measure_single_render_with_viewport(chart, data, TEST_VIEWPORT)
}
pub fn measure_single_render_with_viewport<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
viewport: Rectangle,
) -> ChartResult<PerformanceMetrics>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let config = super::create_test_config();
let mut display = super::create_test_display();
let start_time = 0;
chart.draw(data, &config, viewport, &mut display)?;
let end_time = 1000;
let mut metrics = PerformanceMetrics::new();
metrics.render_time_ns = end_time - start_time;
metrics.draw_calls = 1;
metrics.memory_usage = MemoryMetrics {
heap_used: 0, stack_used: core::mem::size_of_val(data), static_memory: core::mem::size_of_val(chart),
};
Ok(metrics)
}
pub fn validate_performance_scaling<T>(chart: &T) -> ChartResult<()>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let results = Self::benchmark_data_scaling(chart)?;
if results.is_empty() {
return Ok(());
}
let first_time = results[0].render_time_ns;
let last_time = results[results.len() - 1].render_time_ns;
let max_acceptable_ratio = 10;
let actual_ratio = if first_time > 0 {
last_time / first_time
} else {
1
};
if actual_ratio > max_acceptable_ratio {
return Err(embedded_charts::error::ChartError::InvalidRange);
}
Ok(())
}
pub fn validate_memory_constraints<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
max_bytes: usize,
) -> ChartResult<()>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let metrics = Self::measure_single_render(chart, data)?;
let total_memory = metrics.memory_usage.heap_used
+ metrics.memory_usage.stack_used
+ metrics.memory_usage.static_memory;
if total_memory > max_bytes {
return Err(embedded_charts::error::ChartError::MemoryFull);
}
Ok(())
}
pub fn stress_test_rapid_renders<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
iterations: usize,
) -> ChartResult<PerformanceMetrics>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let config = super::create_test_config();
let mut display = super::create_test_display();
let start_time = 0;
for _ in 0..iterations {
chart.draw(data, &config, TEST_VIEWPORT, &mut display)?;
}
let end_time = (iterations * 1000) as u64;
let mut metrics = PerformanceMetrics::new();
metrics.render_time_ns = end_time - start_time as u64;
metrics.draw_calls = iterations;
Ok(metrics)
}
}
pub struct MemoryProfiler;
impl MemoryProfiler {
pub fn profile_chart_memory<T>(
chart: &T,
data: &StaticDataSeries<Point2D, 256>,
) -> ChartResult<MemoryProfile>
where
T: Chart<Rgb565, Data = StaticDataSeries<Point2D, 256>, Config = ChartConfig<Rgb565>>,
{
let config = super::create_test_config();
let mut display = super::create_test_display();
let before = Self::measure_current_memory();
chart.draw(data, &config, TEST_VIEWPORT, &mut display)?;
let after = Self::measure_current_memory();
Ok(MemoryProfile {
before,
after,
peak_usage: after, chart_size: core::mem::size_of_val(chart),
data_size: core::mem::size_of_val(data),
})
}
fn measure_current_memory() -> MemoryMetrics {
MemoryMetrics {
heap_used: 0, stack_used: 1024, static_memory: 512, }
}
pub fn validate_embedded_memory_usage(profile: &MemoryProfile) -> ChartResult<()> {
let total_peak = profile.peak_usage.heap_used
+ profile.peak_usage.stack_used
+ profile.peak_usage.static_memory;
let constraints = [
("ultra_constrained", 1024), ("small_embedded", 4096), ("medium_embedded", 16384), ("large_embedded", 65536), ];
if total_peak > constraints[2].1 {
return Err(embedded_charts::error::ChartError::MemoryFull);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MemoryProfile {
pub before: MemoryMetrics,
pub after: MemoryMetrics,
pub peak_usage: MemoryMetrics,
pub chart_size: usize,
pub data_size: usize,
}
impl MemoryProfile {
pub fn memory_delta(&self) -> i32 {
let before_total =
self.before.heap_used + self.before.stack_used + self.before.static_memory;
let after_total = self.after.heap_used + self.after.stack_used + self.after.static_memory;
after_total as i32 - before_total as i32
}
pub fn total_footprint(&self) -> usize {
self.peak_usage.heap_used + self.peak_usage.stack_used + self.peak_usage.static_memory
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "line")]
use super::super::data_generators;
#[test]
#[cfg(feature = "line")]
#[ignore = "MockDisplay has limitations with pixel overlap detection"]
fn test_performance_benchmark() {
use embedded_charts::chart::line::LineChart;
let chart = LineChart::new();
let data = data_generators::generate_test_data(super::super::TestDataPattern::Linear, 10);
let result = PerformanceBenchmark::measure_single_render(&chart, &data);
assert!(result.is_ok());
let metrics = result.unwrap();
assert!(metrics.render_time_ns > 0);
}
#[test]
fn test_memory_profiler() {
let metrics = MemoryMetrics::new();
assert_eq!(metrics.heap_used, 0);
}
#[test]
fn test_memory_profile_delta() {
let profile = MemoryProfile {
before: MemoryMetrics {
heap_used: 100,
stack_used: 200,
static_memory: 300,
},
after: MemoryMetrics {
heap_used: 120,
stack_used: 220,
static_memory: 300,
},
peak_usage: MemoryMetrics {
heap_used: 120,
stack_used: 220,
static_memory: 300,
},
chart_size: 64,
data_size: 128,
};
assert_eq!(profile.memory_delta(), 40);
assert_eq!(profile.total_footprint(), 640);
}
}