#!/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()