Last active
February 1, 2026 17:23
-
-
Save aobond2/31e852ad046698c3500d6f3a600c2fb7 to your computer and use it in GitHub Desktop.
Script to summarize from csv data here into html report, attach utrace as well. More detail here https://www.bondhan.com/2026/02/01/automated-vfx-audit/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import csv | |
| import os | |
| import shutil | |
| import webbrowser | |
| from datetime import datetime | |
| # USE RAW STRINGS (r"") TO AVOID BACKSLASH ERRORS | |
| # TODO: Set this to not be local | |
| csv_folder = r"C:\Users\aobon_ktnb1ku\Documents\Unreal Projects\AutoTest\Saved\Profiling\CSV" | |
| profiling_root = r"C:\Users\aobon_ktnb1ku\Documents\Unreal Projects\AutoTest\Saved\Profiling" | |
| GPU_BUDGET = 0.5 | |
| def run_vfx_gauntlet_report(): | |
| # 1. Validation | |
| if not os.path.exists(csv_folder): | |
| print(f"ERROR: Folder does not exist: {csv_folder}") | |
| return | |
| # 2. Identify Files | |
| all_files = os.listdir(csv_folder) | |
| csv_files = [f for f in all_files if f.endswith(".csv") and "VFX_Performance_Report" not in f] | |
| print(f"Files found in folder: {len(all_files)}") | |
| print(f"CSV files to process: {len(csv_files)}") | |
| if not csv_files: | |
| print("No CSV files to process. Report will not be generated.") | |
| return | |
| # 3. Setup Output | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| output_folder = os.path.join(csv_folder, f"VFX_Results_{timestamp}") | |
| os.makedirs(output_folder, exist_ok=True) | |
| summary_rows = [] | |
| html_table_rows = "" | |
| # 4. Process Loop | |
| for filename in csv_files: | |
| full_path = os.path.join(csv_folder, filename) | |
| asset_name = filename.replace(".csv", "") | |
| rt_values = [] | |
| try: | |
| with open(full_path, mode='r', encoding='utf-8', errors='ignore') as f: | |
| reader = csv.DictReader(f) | |
| for row in reader: | |
| # Metadata Extraction | |
| if row.get('EVENTS') and "VFXAsset" in str(row['EVENTS']): | |
| asset_name = row['EVENTS'].split('VFXAsset ')[-1].split(';')[0].strip() | |
| # Performance Data Extraction | |
| val = row.get('Exclusive/RenderThread/Niagara') | |
| if val: | |
| try: | |
| rt_values.append(float(val)) | |
| except: | |
| continue | |
| if rt_values: | |
| avg_rt = round(sum(rt_values) / len(rt_values), 4) | |
| max_rt = round(max(rt_values), 4) | |
| status = "PASS" if max_rt < GPU_BUDGET else "FAIL" | |
| # Trace Linking - Search Profiling Root | |
| trace_link_html = "N/A" | |
| for t_file in os.listdir(profiling_root): | |
| # Match .utrace files that contain the asset name | |
| if t_file.endswith(".utrace") and asset_name in t_file: | |
| source_trace = os.path.join(profiling_root, t_file) | |
| dest_trace = os.path.join(output_folder, t_file) | |
| shutil.move(source_trace, dest_trace) | |
| # Link specifically to the moved file | |
| trace_link_html = f'<a href="{t_file}" style="color: #2196f3; text-decoration: none;">Download Trace</a>' | |
| break | |
| summary_rows.append([asset_name, avg_rt, max_rt, filename]) | |
| color = "#4caf50" if status == "PASS" else "#f44336" | |
| html_table_rows += f""" | |
| <tr style="border-bottom: 1px solid #444;"> | |
| <td style="padding: 12px;">{asset_name}</td> | |
| <td style="padding: 12px;">{avg_rt} ms</td> | |
| <td style="padding: 12px;">{max_rt} ms</td> | |
| <td style="padding: 12px; color: {color}; font-weight: bold;">{status}</td> | |
| <td style="padding: 12px;">{trace_link_html}</td> | |
| </tr>""" | |
| # Move CSV after processing | |
| shutil.move(full_path, os.path.join(output_folder, filename)) | |
| except Exception as e: | |
| print(f"Failed to process {filename}: {e}") | |
| # 5. Write HTML (Added explicit encoding='utf-8') | |
| html_content = f""" | |
| <html><head><style> | |
| body {{ font-family: sans-serif; background: #121212; color: #eee; padding: 40px; }} | |
| table {{ width: 100%; border-collapse: collapse; background: #1e1e1e; }} | |
| th {{ text-align: left; padding: 15px; background: #333; border-bottom: 2px solid #444; }} | |
| </style></head><body> | |
| <h2>VFX Performance Audit - {timestamp}</h2> | |
| <p>Budget Threshold: {GPU_BUDGET} ms</p> | |
| <table> | |
| <tr><th>Asset</th><th>Avg RT</th><th>Max Peak</th><th>Status</th><th>Trace File</th></tr> | |
| {html_table_rows} | |
| </table> | |
| </body></html>""" | |
| report_path = os.path.join(output_folder, "VFX_Report.html") | |
| # CRITICAL: Added encoding='utf-8' here | |
| with open(report_path, "w", encoding='utf-8') as f: | |
| f.write(html_content) | |
| print(f"Report successfully generated: {report_path}") | |
| if __name__ == "__main__": | |
| run_vfx_gauntlet_report() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment