Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If48e0a1c4b265946c009b3abd9a249a96a6a6964
378 lines
12 KiB
Python
378 lines
12 KiB
Python
#!/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()
|