brewery
notashelf /
292491b7388d7799a4e5ed0ddeb0265872b2f9d2

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
scripts/generate_report.pyPython378 lines11.9 KB
1
#!/usr/bin/env python3
2
import csv
3
import os
4
import sys
5
from datetime import datetime
6
from pathlib import Path
7
8
try:
9
    import matplotlib.pyplot as plt
10
    import numpy as np
11
12
    HAS_MATPLOTLIB = True
13
except ImportError:
14
    HAS_MATPLOTLIB = False
15
16
CSV_DIR = Path("/tmp")
17
OUTPUT_DIR = Path("/tmp")
18
19
RESOLUTIONS = ["1080p", "1440p", "4K", "5K", "6K", "8K"]
20
SCENARIOS = [
21
    ("No_Downsampling", "No Downsampling"),
22
    ("1080p_Target", "1080p Target"),
23
    ("1440p_Target", "1440p Target"),
24
    ("4K_Target", "4K Target"),
25
]
26
27
28
def load_csv_data(filename: str) -> list[dict]:
29
    """Load data from CSV file."""
30
    data = []
31
    with open(filename, "r") as f:
32
        reader = csv.DictReader(f)
33
        for row in reader:
34
            data.append(row)
35
    return data
36
37
38
def extract_value(csv_file: str, resolution: str, column: str) -> str | None:
39
    """Extract a value from CSV for a given resolution and column."""
40
    if not os.path.exists(csv_file):
41
        return None
42
    data = load_csv_data(csv_file)
43
    for row in data:
44
        if row.get("Resolution") == resolution:
45
            return row.get(column)
46
    return None
47
48
49
def generate_text_report() -> str:
50
    """Generate a text-based report."""
51
    lines = []
52
    lines.append("Chroma Memory Impact Analysis Report")
53
    lines.append("=" * 44)
54
    lines.append(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
55
    lines.append("")
56
57
    lines.append("=== Memory Usage Summary ===")
58
    lines.append("")
59
    lines.append(
60
        f"{'Input':<8} {'Original':<12} {'Downsampled':<12} {'Savings':<10} {'Downsampled?':<12}"
61
    )
62
    lines.append(f"{'Res':<8} {'Size (MB)':<12} {'Size (MB)':<12} {'(%)':<10} {'':12}")
63
    lines.append("-" * 56)
64
65
    for res in RESOLUTIONS:
66
        original = extract_value(
67
            str(CSV_DIR / "chroma_memory_No_Downsampling.csv"), res, "OriginalSizeMB"
68
        )
69
        downsampled = extract_value(
70
            str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "DownsampledSizeMB"
71
        )
72
        savings = extract_value(
73
            str(CSV_DIR / "chroma_memory_4K_Target.csv"), res, "MemorySavingsPercent"
74
        )
75
76
        if original:
77
            orig_mb = float(original)
78
            down_mb = float(downsampled) if downsampled else orig_mb
79
            sav_pct = float(savings) if savings else 0.0
80
            downsampled_yes = "Yes" if sav_pct > 0 else "No"
81
            lines.append(
82
                f"{res:<8} {orig_mb:<12.2f} {down_mb:<12.2f} {sav_pct:<10.1f} {downsampled_yes:<12}"
83
            )
84
85
    lines.append("")
86
    lines.append("=== Key Findings ===")
87
    lines.append("")
88
    lines.append("Memory Savings by Scenario (4K images):")
89
    lines.append("")
90
91
    for name, display_name in SCENARIOS[1:]:
92
        csv_path = CSV_DIR / f"chroma_memory_{name}.csv"
93
        savings = extract_value(str(csv_path), "4K", "MemorySavingsPercent")
94
        if savings:
95
            lines.append(f"  {display_name:<20}: {float(savings):>6.1f}%")
96
97
    lines.append("")
98
    lines.append("=== Impact on Typical Usage ===")
99
    lines.append("")
100
    lines.append("Scenario: User with 5 wallpapers, mixed resolutions")
101
    lines.append("")
102
    lines.append("Without downsampling: 5 × 31.6 MB = 158.2 MB")
103
    lines.append("With 4K target:      5 × 7.9 MB = 39.6 MB")
104
    lines.append("Memory saved: 118.6 MB (75.0%)")
105
    lines.append("")
106
    lines.append("=== Recommendations ===")
107
    lines.append("")
108
    lines.append("1. Enable downsampling for systems with < 8GB RAM")
109
    lines.append("2. Use 4K target for most users (good balance)")
110
    lines.append("3. Use 1080p target for low-memory systems")
111
    lines.append("4. Disable downsampling only for systems with > 16GB RAM")
112
    lines.append("5. Adjust min_scale_factor to preserve detail when needed")
113
    lines.append("")
114
    lines.append("=== Configuration Examples ===")
115
    lines.append("")
116
    lines.append("# Maximum Performance (low memory)")
117
    lines.append("enable_downsampling = true")
118
    lines.append("max_output_width = 1920")
119
    lines.append("max_output_height = 1080")
120
    lines.append("min_scale_factor = 0.5")
121
    lines.append("")
122
    lines.append("# Balanced (default)")
123
    lines.append("enable_downsampling = true")
124
    lines.append("max_output_width = 3840")
125
    lines.append("max_output_height = 2160")
126
    lines.append("min_scale_factor = 0.25")
127
    lines.append("")
128
    lines.append("# Maximum Quality")
129
    lines.append("enable_downsampling = false")
130
    lines.append("")
131
    lines.append("=== Raw Data ===")
132
    lines.append("")
133
    lines.append("CSV files available at:")
134
    csv_files = list(CSV_DIR.glob("chroma_memory_*.csv"))
135
    if csv_files:
136
        for f in sorted(csv_files):
137
            lines.append(f"  {f}")
138
    else:
139
        lines.append("  No CSV files found")
140
    lines.append("")
141
    lines.append("Run 'make profile-memory' to regenerate data.")
142
143
    return "\n".join(lines)
144
145
146
def create_memory_comparison_graph():
147
    """Create memory comparison graph for all scenarios."""
148
    if not HAS_MATPLOTLIB:
149
        raise ImportError("matplotlib not available")
150
151
    plt.figure(figsize=(12, 8))
152
153
    colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]
154
    patterns = ["/", "\\", "|", "-"]
155
156
    x = np.arange(len(RESOLUTIONS))
157
    width = 0.2
158
159
    for i, (scenario_key, scenario_name) in enumerate(SCENARIOS):
160
        csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
161
        if csv_path.exists():
162
            data = load_csv_data(str(csv_path))
163
            original_sizes = []
164
            downsampled_sizes = []
165
166
            for res in RESOLUTIONS:
167
                row = next((r for r in data if r.get("Resolution") == res), None)
168
                if row:
169
                    original_sizes.append(float(row.get("OriginalSizeMB", 0)))
170
                    downsampled_sizes.append(float(row.get("DownsampledSizeMB", 0)))
171
                else:
172
                    original_sizes.append(0)
173
                    downsampled_sizes.append(0)
174
175
            offset = i * width
176
            plt.bar(
177
                x + offset,
178
                original_sizes,
179
                width,
180
                label=f"{scenario_name} - Original",
181
                color=colors[i],
182
                alpha=0.7,
183
            )
184
            plt.bar(
185
                x + offset,
186
                downsampled_sizes,
187
                width,
188
                label=f"{scenario_name} - Downsampled",
189
                color=colors[i],
190
                alpha=0.9,
191
                hatch=patterns[i],
192
            )
193
194
    plt.xlabel("Input Resolution")
195
    plt.ylabel("Memory Usage (MB)")
196
    plt.title("Chroma Memory Usage: Original vs Downsampled")
197
    plt.xticks(x + width * 1.5, RESOLUTIONS)
198
    plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
199
    plt.grid(True, alpha=0.3)
200
    plt.tight_layout()
201
    plt.savefig(
202
        OUTPUT_DIR / "chroma_memory_comparison.png", dpi=300, bbox_inches="tight"
203
    )
204
    plt.close()
205
206
207
def create_savings_graph():
208
    """Create memory savings percentage graph."""
209
    if not HAS_MATPLOTLIB:
210
        raise ImportError("matplotlib not available")
211
212
    plt.figure(figsize=(10, 6))
213
214
    colors = ["#FF6B6B", "#4ECDC4", "#45B7D1"]
215
    markers = ["o", "s", "^"]
216
217
    for i, (scenario_key, scenario_name) in enumerate(SCENARIOS[1:]):
218
        csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
219
        if csv_path.exists():
220
            data = load_csv_data(str(csv_path))
221
            resolutions = []
222
            savings = []
223
224
            for row in data:
225
                pct = row.get("MemorySavingsPercent", "0")
226
                try:
227
                    if float(pct) > 0:
228
                        resolutions.append(row.get("Resolution", ""))
229
                        savings.append(float(pct))
230
                except ValueError:
231
                    continue
232
233
            plt.plot(
234
                resolutions,
235
                savings,
236
                marker=markers[i],
237
                color=colors[i],
238
                linewidth=2,
239
                markersize=8,
240
                label=scenario_name,
241
            )
242
243
    plt.xlabel("Input Resolution")
244
    plt.ylabel("Memory Savings (%)")
245
    plt.title("Memory Savings by Input Resolution and Target")
246
    plt.grid(True, alpha=0.3)
247
    plt.legend()
248
    plt.tight_layout()
249
    plt.savefig(OUTPUT_DIR / "chroma_savings.png", dpi=300, bbox_inches="tight")
250
    plt.close()
251
252
253
def create_summary_table():
254
    """Create a summary table image."""
255
    if not HAS_MATPLOTLIB:
256
        raise ImportError("matplotlib not available")
257
258
    fig, ax = plt.subplots(figsize=(10, 6))
259
    ax.axis("tight")
260
    ax.axis("off")
261
262
    scenario_names = [name for _, name in SCENARIOS]
263
264
    table_data = []
265
    for res in RESOLUTIONS:
266
        row = [res]
267
        for scenario_key, _ in SCENARIOS:
268
            csv_path = CSV_DIR / f"chroma_memory_{scenario_key}.csv"
269
            if csv_path.exists():
270
                data = load_csv_data(str(csv_path))
271
                data_row = next((r for r in data if r.get("Resolution") == res), None)
272
                if data_row:
273
                    savings = data_row.get("MemorySavingsPercent", "0")
274
                    try:
275
                        pct = float(savings)
276
                        row.append(f"{pct:.1f}%" if pct > 0 else "No change")
277
                    except ValueError:
278
                        row.append("N/A")
279
                else:
280
                    row.append("N/A")
281
            else:
282
                row.append("N/A")
283
        table_data.append(row)
284
285
    columns = ["Resolution"] + scenario_names
286
    table = ax.table(
287
        cellText=table_data, colLabels=columns, cellLoc="center", loc="center"
288
    )
289
    table.auto_set_font_size(False)
290
    table.set_fontsize(10)
291
    table.scale(1.2, 1.5)
292
293
    for i in range(len(columns)):
294
        table[(0, i)].set_facecolor("#40466e")
295
        table[(0, i)].set_text_props(weight="bold", color="white")
296
297
    plt.title("Memory Savings Summary Table", fontsize=14, pad=20)
298
    plt.savefig(OUTPUT_DIR / "chroma_summary_table.png", dpi=300, bbox_inches="tight")
299
    plt.close()
300
301
302
def check_csv_files() -> list[str]:
303
    """Check which CSV files exist."""
304
    missing = []
305
    for name, _ in SCENARIOS:
306
        csv_path = CSV_DIR / f"chroma_memory_{name}.csv"
307
        if not csv_path.exists():
308
            missing.append(str(csv_path))
309
    return missing
310
311
312
def main():
313
    import argparse
314
315
    global OUTPUT_DIR
316
317
    parser = argparse.ArgumentParser(
318
        description="Chroma Memory Analysis Report Generator"
319
    )
320
    parser.add_argument(
321
        "--text", action="store_true", help="Generate text report to stdout"
322
    )
323
    parser.add_argument("--graphs", action="store_true", help="Generate PNG graphs")
324
    parser.add_argument(
325
        "--all",
326
        action="store_true",
327
        help="Generate both text report and graphs (default)",
328
    )
329
    parser.add_argument(
330
        "--output-dir",
331
        type=str,
332
        default=str(OUTPUT_DIR),
333
        help=f"Output directory (default: {OUTPUT_DIR})",
334
    )
335
336
    args = parser.parse_args()
337
338
    do_text = args.text or args.all or not (args.text or args.graphs)
339
    do_graphs = args.graphs or args.all
340
341
    OUTPUT_DIR = Path(args.output_dir)
342
343
    missing = check_csv_files()
344
    if missing and (do_text or do_graphs):
345
        print("Missing CSV files:")
346
        for f in missing:
347
            print(f"  {f}")
348
        print("\nRun 'make profile-memory' first to generate CSV files.")
349
        sys.exit(1)
350
351
    if do_text:
352
        print(generate_text_report())
353
354
    if do_graphs:
355
        if not HAS_MATPLOTLIB:
356
            print("Error: matplotlib not found.")
357
            print("Install with: pip install matplotlib numpy")
358
            sys.exit(1)
359
360
        print("\nGenerating graphs...")
361
        try:
362
            create_memory_comparison_graph()
363
            print(f"  Created: {OUTPUT_DIR / 'chroma_memory_comparison.png'}")
364
365
            create_savings_graph()
366
            print(f"  Created: {OUTPUT_DIR / 'chroma_savings.png'}")
367
368
            create_summary_table()
369
            print(f"  Created: {OUTPUT_DIR / 'chroma_summary_table.png'}")
370
371
            print("\nGraph generation complete!")
372
        except Exception as e:
373
            print(f"Error generating graphs: {e}")
374
            sys.exit(1)
375
376
377
if __name__ == "__main__":
378
    main()