Skip to content

Instantly share code, notes, and snippets.

@aobond2
Last active February 1, 2026 17:23
Show Gist options
  • Select an option

  • Save aobond2/31e852ad046698c3500d6f3a600c2fb7 to your computer and use it in GitHub Desktop.

Select an option

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/
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