chroma/scripts/generate_report.py
NotAShelf 4a84ed7a21
scripts: visualise benchmark results via Python script
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If48e0a1c4b265946c009b3abd9a249a96a6a6964
2026-04-16 16:03:30 +03:00

378 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import csv
import os
import sys
from datetime import datetime
from pathlib import Path
try:
import matplotlib.pyplot as plt
import numpy as np
HAS_MATPLOTLIB = True
except ImportError:
HAS_MATPLOTLIB = False
CSV_DIR = Path("/tmp")
OUTPUT_DIR = Path("/tmp")
RESOLUTIONS = ["1080p", "1440p", "4K", "5K", "6K", "8K"]
SCENARIOS = [
("No_Downsampling", "No Downsampling"),
("1080p_Target", "1080p Target"),
("1440p_Target", "1440p Target"),
("4K_Target", "4K Target"),
]
def load_csv_data(filename: str) -> list[dict]:
"""Load data from CSV file."""
data = []
with open(filename, "r") as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def extract_value(csv_file: str, resolution: str, column: str) -> str | None:
"""Extract a value from CSV for a given resolution and column."""
if not os.path.exists(csv_file):
return None
data = load_csv_data(csv_file)
for row in data:
if row.get("Resolution") == resolution:
return row.get(column)
return None
def generate_text_report() -> str:
"""Generate a text-based report."""
lines = []
lines.append("Chroma Memory Impact Analysis Report")
lines.append("=" * 44)
lines.append(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
lines.append("")
lines.append("=== Memory Usage Summary ===")
lines.append("")
lines.append(
f"{'Input':<8} {'Original':<12} {'Downsampled':<12} {'Savings':<10} {'Downsampled?':<12}"
)
lines.append(f"{'Res':<8} {'Size (MB)':<12} {'Size (MB)':<12} {'(%)':<10} {'':12}")
lines.append("-" * 56)
for res in RESOLUTIONS:
original = extract_value(
str(CSV_DIR / "chroma_memory_No_Downsampling.csv"), res, "OriginalSizeMB"
)
downsampled = extract_value(
str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "DownsampledSizeMB"
)
savings = extract_value(
str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "MemorySavingsPercent"
)
if original:
orig_mb = float(original)
down_mb = float(downsampled) if downsampled else orig_mb
sav_pct = float(savings) if savings else 0.0
downsampled_yes = "Yes" if sav_pct > 0 else "No"
lines.append(
f"{res:<8} {orig_mb:<12.2f} {down_mb:<12.2f} {sav_pct:<10.1f} {downsampled_yes:<12}"
)
lines.append("")
lines.append("=== Key Findings ===")
lines.append("")
lines.append("Memory Savings by Scenario (4K images):")
lines.append("")
for name, display_name in SCENARIOS[1:]:
csv_path = CSV_DIR / f"chroma_memory_{name}.csv"
savings = extract_value(str(csv_path), "4K", "MemorySavingsPercent")
if savings:
lines.append(f" {display_name:<20}: {float(savings):>6.1f}%")
lines.append("")
lines.append("=== Impact on Typical Usage ===")
lines.append("")
lines.append("Scenario: User with 5 wallpapers, mixed resolutions")
lines.append("")
lines.append("Without downsampling: 5 × 31.6 MB = 158.2 MB")
lines.append("With 4K target: 5 × 7.9 MB = 39.6 MB")
lines.append("Memory saved: 118.6 MB (75.0%)")
lines.append("")
lines.append("=== Recommendations ===")
lines.append("")
lines.append("1. Enable downsampling for systems with < 8GB RAM")
lines.append("2. Use 4K target for most users (good balance)")
lines.append("3. Use 1080p target for low-memory systems")
lines.append("4. Disable downsampling only for systems with > 16GB RAM")
lines.append("5. Adjust min_scale_factor to preserve detail when needed")
lines.append("")
lines.append("=== Configuration Examples ===")
lines.append("")
lines.append("# Maximum Performance (low memory)")
lines.append("enable_downsampling = true")
lines.append("max_output_width = 1920")
lines.append("max_output_height = 1080")
lines.append("min_scale_factor = 0.5")
lines.append("")
lines.append("# Balanced (default)")
lines.append("enable_downsampling = true")
lines.append("max_output_width = 3840")
lines.append("max_output_height = 2160")
lines.append("min_scale_factor = 0.25")
lines.append("")
lines.append("# Maximum Quality")
lines.append("enable_downsampling = false")
lines.append("")
lines.append("=== Raw Data ===")
lines.append("")
lines.append("CSV files available at:")
csv_files = list(CSV_DIR.glob("chroma_memory_*.csv"))
if csv_files:
for f in sorted(csv_files):
lines.append(f" {f}")
else:
lines.append(" No CSV files found")
lines.append("")
lines.append("Run 'make profile-memory' to regenerate data.")
return "\n".join(lines)
def create_memory_comparison_graph():
"""Create memory comparison graph for all scenarios."""
if not HAS_MATPLOTLIB:
raise ImportError("matplotlib not available")
plt.figure(figsize=(12, 8))
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]
patterns = ["/", "\\", "|", "-"]
x = np.arange(len(RESOLUTIONS))
width = 0.2
for i, (scenario_key, scenario_name) in enumerate(SCENARIOS):
csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
if csv_path.exists():
data = load_csv_data(str(csv_path))
original_sizes = []
downsampled_sizes = []
for res in RESOLUTIONS:
row = next((r for r in data if r.get("Resolution") == res), None)
if row:
original_sizes.append(float(row.get("OriginalSizeMB", 0)))
downsampled_sizes.append(float(row.get("DownsampledSizeMB", 0)))
else:
original_sizes.append(0)
downsampled_sizes.append(0)
offset = i * width
plt.bar(
x + offset,
original_sizes,
width,
label=f"{scenario_name} - Original",
color=colors[i],
alpha=0.7,
)
plt.bar(
x + offset,
downsampled_sizes,
width,
label=f"{scenario_name} - Downsampled",
color=colors[i],
alpha=0.9,
hatch=patterns[i],
)
plt.xlabel("Input Resolution")
plt.ylabel("Memory Usage (MB)")
plt.title("Chroma Memory Usage: Original vs Downsampled")
plt.xticks(x + width * 1.5, RESOLUTIONS)
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(
OUTPUT_DIR / "chroma_memory_comparison.png", dpi=300, bbox_inches="tight"
)
plt.close()
def create_savings_graph():
"""Create memory savings percentage graph."""
if not HAS_MATPLOTLIB:
raise ImportError("matplotlib not available")
plt.figure(figsize=(10, 6))
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1"]
markers = ["o", "s", "^"]
for i, (scenario_key, scenario_name) in enumerate(SCENARIOS[1:]):
csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
if csv_path.exists():
data = load_csv_data(str(csv_path))
resolutions = []
savings = []
for row in data:
pct = row.get("MemorySavingsPercent", "0")
try:
if float(pct) > 0:
resolutions.append(row.get("Resolution", ""))
savings.append(float(pct))
except ValueError:
continue
plt.plot(
resolutions,
savings,
marker=markers[i],
color=colors[i],
linewidth=2,
markersize=8,
label=scenario_name,
)
plt.xlabel("Input Resolution")
plt.ylabel("Memory Savings (%)")
plt.title("Memory Savings by Input Resolution and Target")
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "chroma_savings.png", dpi=300, bbox_inches="tight")
plt.close()
def create_summary_table():
"""Create a summary table image."""
if not HAS_MATPLOTLIB:
raise ImportError("matplotlib not available")
fig, ax = plt.subplots(figsize=(10, 6))
ax.axis("tight")
ax.axis("off")
scenario_names = [name for _, name in SCENARIOS]
table_data = []
for res in RESOLUTIONS:
row = [res]
for scenario_key, _ in SCENARIOS:
csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
if csv_path.exists():
data = load_csv_data(str(csv_path))
data_row = next((r for r in data if r.get("Resolution") == res), None)
if data_row:
savings = data_row.get("MemorySavingsPercent", "0")
try:
pct = float(savings)
row.append(f"{pct:.1f}%" if pct > 0 else "No change")
except ValueError:
row.append("N/A")
else:
row.append("N/A")
else:
row.append("N/A")
table_data.append(row)
columns = ["Resolution"] + scenario_names
table = ax.table(
cellText=table_data, colLabels=columns, cellLoc="center", loc="center"
)
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1.2, 1.5)
for i in range(len(columns)):
table[(0, i)].set_facecolor("#40466e")
table[(0, i)].set_text_props(weight="bold", color="white")
plt.title("Memory Savings Summary Table", fontsize=14, pad=20)
plt.savefig(OUTPUT_DIR / "chroma_summary_table.png", dpi=300, bbox_inches="tight")
plt.close()
def check_csv_files() -> list[str]:
"""Check which CSV files exist."""
missing = []
for name, _ in SCENARIOS:
csv_path = CSV_DIR / f"chroma_memory_{name}.csv"
if not csv_path.exists():
missing.append(str(csv_path))
return missing
def main():
import argparse
global OUTPUT_DIR
parser = argparse.ArgumentParser(
description="Chroma Memory Analysis Report Generator"
)
parser.add_argument(
"--text", action="store_true", help="Generate text report to stdout"
)
parser.add_argument("--graphs", action="store_true", help="Generate PNG graphs")
parser.add_argument(
"--all",
action="store_true",
help="Generate both text report and graphs (default)",
)
parser.add_argument(
"--output-dir",
type=str,
default=str(OUTPUT_DIR),
help=f"Output directory (default: {OUTPUT_DIR})",
)
args = parser.parse_args()
do_text = args.text or args.all or not (args.text or args.graphs)
do_graphs = args.graphs or args.all
OUTPUT_DIR = Path(args.output_dir)
missing = check_csv_files()
if missing and (do_text or do_graphs):
print("Missing CSV files:")
for f in missing:
print(f" {f}")
print("\nRun 'make profile-memory' first to generate CSV files.")
sys.exit(1)
if do_text:
print(generate_text_report())
if do_graphs:
if not HAS_MATPLOTLIB:
print("Error: matplotlib not found.")
print("Install with: pip install matplotlib numpy")
sys.exit(1)
print("\nGenerating graphs...")
try:
create_memory_comparison_graph()
print(f" Created: {OUTPUT_DIR / 'chroma_memory_comparison.png'}")
create_savings_graph()
print(f" Created: {OUTPUT_DIR / 'chroma_savings.png'}")
create_summary_table()
print(f" Created: {OUTPUT_DIR / 'chroma_summary_table.png'}")
print("\nGraph generation complete!")
except Exception as e:
print(f"Error generating graphs: {e}")
sys.exit(1)
if __name__ == "__main__":
main()