mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 14:43:07 +02:00
569 lines
24 KiB
Python
Executable File
569 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Nsight Profiling Results Analyzer
|
|
=================================
|
|
Analyzes NVIDIA Nsight Systems profiling data and generates LaTeX output
|
|
for the master's thesis comparing Unity and Unreal Engine performance.
|
|
|
|
Usage:
|
|
python analyze_nsight.py [--output OUTPUT_TEX] [--data-dir DATA_DIR]
|
|
|
|
Example:
|
|
python analyze_nsight.py --output latex/tex/wyniki-nsight.tex
|
|
"""
|
|
|
|
import argparse
|
|
import csv
|
|
import os
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class ProfilingResult:
|
|
"""Container for profiling results from a single run."""
|
|
name: str
|
|
engine: str
|
|
duration_seconds: float
|
|
total_frames: int
|
|
avg_fps: float
|
|
report_path: str
|
|
sqlite_path: Optional[str]
|
|
vulkan_csv_path: Optional[str]
|
|
osrt_csv_path: Optional[str]
|
|
|
|
|
|
@dataclass
|
|
class VulkanApiCall:
|
|
"""Single Vulkan API call statistics."""
|
|
name: str
|
|
time_percent: float
|
|
total_time_ns: int
|
|
num_calls: int
|
|
avg_time_ns: float
|
|
min_time_ns: int
|
|
max_time_ns: int
|
|
|
|
|
|
@dataclass
|
|
class OsRuntimeCall:
|
|
"""Single OS runtime call statistics."""
|
|
name: str
|
|
time_percent: float
|
|
total_time_ns: int
|
|
num_calls: int
|
|
avg_time_ns: float
|
|
min_time_ns: int
|
|
max_time_ns: int
|
|
|
|
|
|
class NsightAnalyzer:
|
|
"""Analyzes Nsight profiling data and generates LaTeX output."""
|
|
|
|
def __init__(self, data_dir: str):
|
|
self.data_dir = Path(data_dir)
|
|
self.results: list[ProfilingResult] = []
|
|
self.vulkan_calls: dict[str, list[VulkanApiCall]] = {}
|
|
self.osrt_calls: dict[str, list[OsRuntimeCall]] = {}
|
|
|
|
def discover_reports(self) -> list[str]:
|
|
"""Find all .nsys-rep files in the data directory."""
|
|
reports = []
|
|
for f in self.data_dir.glob("**/*.nsys-rep"):
|
|
reports.append(str(f))
|
|
return sorted(reports)
|
|
|
|
def parse_vulkan_csv(self, csv_path: str) -> list[VulkanApiCall]:
|
|
"""Parse Vulkan API summary CSV file."""
|
|
calls = []
|
|
try:
|
|
with open(csv_path, 'r') as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
try:
|
|
calls.append(VulkanApiCall(
|
|
name=row.get('Name', row.get('name', '')),
|
|
time_percent=float(row.get('Time (%)', row.get('time_percent', 0))),
|
|
total_time_ns=int(row.get('Total Time (ns)', row.get('total_time', 0))),
|
|
num_calls=int(row.get('Num Calls', row.get('num_calls', 0))),
|
|
avg_time_ns=float(row.get('Avg (ns)', row.get('avg_time', 0))),
|
|
min_time_ns=int(row.get('Min (ns)', row.get('min_time', 0))),
|
|
max_time_ns=int(row.get('Max (ns)', row.get('max_time', 0))),
|
|
))
|
|
except (ValueError, KeyError) as e:
|
|
continue
|
|
except FileNotFoundError:
|
|
print(f"Warning: CSV file not found: {csv_path}")
|
|
return calls
|
|
|
|
def parse_osrt_csv(self, csv_path: str) -> list[OsRuntimeCall]:
|
|
"""Parse OS Runtime summary CSV file."""
|
|
calls = []
|
|
try:
|
|
with open(csv_path, 'r') as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
try:
|
|
calls.append(OsRuntimeCall(
|
|
name=row.get('Name', row.get('name', '')),
|
|
time_percent=float(row.get('Time (%)', row.get('time_percent', 0))),
|
|
total_time_ns=int(row.get('Total Time (ns)', row.get('total_time', 0))),
|
|
num_calls=int(row.get('Num Calls', row.get('num_calls', 0))),
|
|
avg_time_ns=float(row.get('Avg (ns)', row.get('avg_time', 0))),
|
|
min_time_ns=int(row.get('Min (ns)', row.get('min_time', 0))),
|
|
max_time_ns=int(row.get('Max (ns)', row.get('max_time', 0))),
|
|
))
|
|
except (ValueError, KeyError):
|
|
continue
|
|
except FileNotFoundError:
|
|
print(f"Warning: CSV file not found: {csv_path}")
|
|
return calls
|
|
|
|
def get_frame_count_from_sqlite(self, sqlite_path: str) -> int:
|
|
"""Extract frame count from SQLite database."""
|
|
try:
|
|
conn = sqlite3.connect(sqlite_path)
|
|
cursor = conn.cursor()
|
|
# Count vkQueuePresentKHR calls (each = 1 frame)
|
|
cursor.execute("""
|
|
SELECT COUNT(*) FROM VULKAN_API
|
|
WHERE nameId IN (
|
|
SELECT id FROM StringIds WHERE value='vkQueuePresentKHR'
|
|
)
|
|
""")
|
|
result = cursor.fetchone()
|
|
conn.close()
|
|
return result[0] if result else 0
|
|
except Exception as e:
|
|
print(f"Warning: Could not read SQLite: {e}")
|
|
return 0
|
|
|
|
def get_frame_times_from_sqlite(self, sqlite_path: str) -> list[float]:
|
|
"""Extract individual frame times from SQLite database."""
|
|
frame_times = []
|
|
try:
|
|
conn = sqlite3.connect(sqlite_path)
|
|
cursor = conn.cursor()
|
|
# Get timestamps of vkQueuePresentKHR calls
|
|
cursor.execute("""
|
|
SELECT start FROM VULKAN_API
|
|
WHERE nameId IN (
|
|
SELECT id FROM StringIds WHERE value='vkQueuePresentKHR'
|
|
)
|
|
ORDER BY start
|
|
""")
|
|
timestamps = [row[0] for row in cursor.fetchall()]
|
|
conn.close()
|
|
|
|
# Calculate frame times (differences between presents)
|
|
for i in range(1, len(timestamps)):
|
|
frame_time_ns = timestamps[i] - timestamps[i-1]
|
|
frame_times.append(frame_time_ns / 1_000_000) # Convert to ms
|
|
except Exception as e:
|
|
print(f"Warning: Could not extract frame times: {e}")
|
|
return frame_times
|
|
|
|
def analyze_report(self, report_path: str) -> Optional[ProfilingResult]:
|
|
"""Analyze a single Nsight report file."""
|
|
report_path = Path(report_path)
|
|
base_name = report_path.stem
|
|
|
|
# Determine engine from filename
|
|
if 'unity' in base_name.lower():
|
|
engine = 'Unity'
|
|
elif 'unreal' in base_name.lower():
|
|
engine = 'Unreal Engine'
|
|
else:
|
|
engine = 'Unknown'
|
|
|
|
# Find associated files
|
|
sqlite_path = report_path.with_suffix('.sqlite')
|
|
|
|
# Look for CSV files with various naming patterns
|
|
vulkan_csv = None
|
|
osrt_csv = None
|
|
|
|
for pattern in [f"{base_name}_vulkan*sum*.csv", f"{base_name}*vulkan*.csv"]:
|
|
matches = list(self.data_dir.glob(pattern))
|
|
if matches:
|
|
vulkan_csv = str(matches[0])
|
|
break
|
|
|
|
for pattern in [f"{base_name}_osrt*sum*.csv", f"{base_name}*osrt*.csv"]:
|
|
matches = list(self.data_dir.glob(pattern))
|
|
if matches:
|
|
osrt_csv = str(matches[0])
|
|
break
|
|
|
|
# Extract duration from filename if present (e.g., unity_full_95s)
|
|
duration = 95 # default
|
|
import re
|
|
match = re.search(r'(\d+)s', base_name)
|
|
if match:
|
|
duration = int(match.group(1))
|
|
|
|
# Get frame count
|
|
frames = 0
|
|
if sqlite_path.exists():
|
|
frames = self.get_frame_count_from_sqlite(str(sqlite_path))
|
|
|
|
fps = frames / duration if duration > 0 else 0
|
|
|
|
result = ProfilingResult(
|
|
name=base_name,
|
|
engine=engine,
|
|
duration_seconds=duration,
|
|
total_frames=frames,
|
|
avg_fps=fps,
|
|
report_path=str(report_path),
|
|
sqlite_path=str(sqlite_path) if sqlite_path.exists() else None,
|
|
vulkan_csv_path=vulkan_csv,
|
|
osrt_csv_path=osrt_csv,
|
|
)
|
|
|
|
# Parse CSV files
|
|
if vulkan_csv:
|
|
self.vulkan_calls[base_name] = self.parse_vulkan_csv(vulkan_csv)
|
|
if osrt_csv:
|
|
self.osrt_calls[base_name] = self.parse_osrt_csv(osrt_csv)
|
|
|
|
return result
|
|
|
|
def analyze_all(self):
|
|
"""Analyze all discovered reports."""
|
|
reports = self.discover_reports()
|
|
print(f"Found {len(reports)} Nsight report(s)")
|
|
|
|
for report in reports:
|
|
print(f" Analyzing: {os.path.basename(report)}")
|
|
result = self.analyze_report(report)
|
|
if result:
|
|
self.results.append(result)
|
|
|
|
def format_time_ns(self, ns: float) -> str:
|
|
"""Format nanoseconds to human readable string."""
|
|
if ns >= 1_000_000_000:
|
|
return f"{ns / 1_000_000_000:.2f} s"
|
|
elif ns >= 1_000_000:
|
|
return f"{ns / 1_000_000:.2f} ms"
|
|
elif ns >= 1_000:
|
|
return f"{ns / 1_000:.2f} μs"
|
|
else:
|
|
return f"{ns:.0f} ns"
|
|
|
|
def generate_latex(self) -> str:
|
|
"""Generate LaTeX document with analysis results."""
|
|
lines = []
|
|
|
|
# Header
|
|
lines.append("% =============================================================================")
|
|
lines.append("% Wyniki profilowania NVIDIA Nsight Systems")
|
|
lines.append(f"% Wygenerowano automatycznie: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
lines.append("% =============================================================================")
|
|
lines.append("")
|
|
|
|
# Section header
|
|
lines.append("\\section{Wyniki profilowania NVIDIA Nsight Systems}")
|
|
lines.append("\\label{sec:wyniki-nsight}")
|
|
lines.append("")
|
|
|
|
lines.append("W niniejszej sekcji przedstawiono wyniki profilowania wydajności")
|
|
lines.append("przeprowadzonego przy użyciu narzędzia NVIDIA Nsight Systems.")
|
|
lines.append("Profilowanie obejmowało analizę wywołań API Vulkan oraz funkcji")
|
|
lines.append("systemowych (OS Runtime).")
|
|
lines.append("")
|
|
|
|
# Performance summary table
|
|
if self.results:
|
|
lines.append("\\subsection{Podsumowanie wydajności}")
|
|
lines.append("\\label{subsec:nsight-podsumowanie}")
|
|
lines.append("")
|
|
lines.append("Tabela~\\ref{tab:nsight-summary} przedstawia podsumowanie")
|
|
lines.append("wyników profilowania dla poszczególnych testów.")
|
|
lines.append("")
|
|
lines.append("\\begin{table}[htbp]")
|
|
lines.append("\\centering")
|
|
lines.append("\\caption{Podsumowanie wyników profilowania Nsight}")
|
|
lines.append("\\label{tab:nsight-summary}")
|
|
lines.append("\\begin{tabular}{|l|c|c|c|c|}")
|
|
lines.append("\\hline")
|
|
lines.append("\\textbf{Test} & \\textbf{Silnik} & \\textbf{Czas [s]} & \\textbf{Klatki} & \\textbf{Śr. FPS} \\\\")
|
|
lines.append("\\hline")
|
|
|
|
for r in self.results:
|
|
name_escaped = r.name.replace('_', '\\_')
|
|
lines.append(f"{name_escaped} & {r.engine} & {r.duration_seconds:.0f} & {r.total_frames} & {r.avg_fps:.2f} \\\\")
|
|
|
|
lines.append("\\hline")
|
|
lines.append("\\end{tabular}")
|
|
lines.append("\\end{table}")
|
|
lines.append("")
|
|
|
|
# Vulkan API analysis
|
|
for result in self.results:
|
|
if result.name in self.vulkan_calls and self.vulkan_calls[result.name]:
|
|
calls = self.vulkan_calls[result.name]
|
|
|
|
lines.append(f"\\subsection{{Analiza wywołań Vulkan API -- {result.engine}}}")
|
|
lines.append(f"\\label{{subsec:vulkan-{result.name.replace('_', '-')}}}")
|
|
lines.append("")
|
|
|
|
lines.append(f"Podczas {result.duration_seconds:.0f}-sekundowego testu zarejestrowano")
|
|
total_calls = sum(c.num_calls for c in calls)
|
|
lines.append(f"łącznie {total_calls:,} wywołań API Vulkan.".replace(',', '\\,'))
|
|
lines.append("")
|
|
|
|
# Top 10 by time
|
|
top_by_time = sorted(calls, key=lambda x: x.time_percent, reverse=True)[:10]
|
|
|
|
lines.append("\\begin{table}[htbp]")
|
|
lines.append("\\centering")
|
|
lines.append("\\caption{Najczęstsze wywołania Vulkan API według czasu wykonania}")
|
|
lines.append(f"\\label{{tab:vulkan-time-{result.name.replace('_', '-')}}}")
|
|
lines.append("\\begin{tabular}{|l|r|r|r|r|}")
|
|
lines.append("\\hline")
|
|
lines.append("\\textbf{Funkcja} & \\textbf{Czas [\\%]} & \\textbf{Wywołania} & \\textbf{Śr. czas} & \\textbf{Maks. czas} \\\\")
|
|
lines.append("\\hline")
|
|
|
|
for call in top_by_time:
|
|
name = call.name.replace('_', '\\_')
|
|
avg_time = self.format_time_ns(call.avg_time_ns)
|
|
max_time = self.format_time_ns(call.max_time_ns)
|
|
num_calls = f"{call.num_calls:,}".replace(',', '\\,')
|
|
lines.append(f"{name} & {call.time_percent:.1f} & {num_calls} & {avg_time} & {max_time} \\\\")
|
|
|
|
lines.append("\\hline")
|
|
lines.append("\\end{tabular}")
|
|
lines.append("\\end{table}")
|
|
lines.append("")
|
|
|
|
# Analysis text
|
|
if top_by_time:
|
|
top_call = top_by_time[0]
|
|
lines.append(f"Dominującą funkcją pod względem czasu wykonania jest")
|
|
lines.append(f"\\texttt{{{top_call.name.replace('_', '\\_')}}}, która zajmuje")
|
|
lines.append(f"{top_call.time_percent:.1f}\\% całkowitego czasu profilowania.")
|
|
|
|
if 'WaitForFences' in top_call.name:
|
|
lines.append("Funkcja ta odpowiada za synchronizację CPU z GPU,")
|
|
lines.append("co wskazuje na to, że aplikacja jest ograniczona przez GPU")
|
|
lines.append("(ang. \\textit{GPU-bound}).")
|
|
elif 'QueuePresent' in top_call.name:
|
|
lines.append("Funkcja ta odpowiada za prezentację wyrenderowanej klatki,")
|
|
lines.append("co jest oczekiwane w aplikacji graficznej.")
|
|
lines.append("")
|
|
|
|
# OS Runtime analysis
|
|
for result in self.results:
|
|
if result.name in self.osrt_calls and self.osrt_calls[result.name]:
|
|
calls = self.osrt_calls[result.name]
|
|
|
|
lines.append(f"\\subsection{{Analiza wywołań systemowych -- {result.engine}}}")
|
|
lines.append(f"\\label{{subsec:osrt-{result.name.replace('_', '-')}}}")
|
|
lines.append("")
|
|
|
|
total_calls = sum(c.num_calls for c in calls)
|
|
lines.append(f"Zarejestrowano {total_calls:,} wywołań funkcji systemowych.".replace(',', '\\,'))
|
|
lines.append("")
|
|
|
|
# Top 10 by time
|
|
top_by_time = sorted(calls, key=lambda x: x.time_percent, reverse=True)[:10]
|
|
|
|
lines.append("\\begin{table}[htbp]")
|
|
lines.append("\\centering")
|
|
lines.append("\\caption{Najczęstsze wywołania systemowe według czasu wykonania}")
|
|
lines.append(f"\\label{{tab:osrt-time-{result.name.replace('_', '-')}}}")
|
|
lines.append("\\begin{tabular}{|l|r|r|r|r|}")
|
|
lines.append("\\hline")
|
|
lines.append("\\textbf{Funkcja} & \\textbf{Czas [\\%]} & \\textbf{Wywołania} & \\textbf{Śr. czas} & \\textbf{Maks. czas} \\\\")
|
|
lines.append("\\hline")
|
|
|
|
for call in top_by_time:
|
|
name = call.name.replace('_', '\\_')
|
|
avg_time = self.format_time_ns(call.avg_time_ns)
|
|
max_time = self.format_time_ns(call.max_time_ns)
|
|
num_calls = f"{call.num_calls:,}".replace(',', '\\,')
|
|
lines.append(f"{name} & {call.time_percent:.1f} & {num_calls} & {avg_time} & {max_time} \\\\")
|
|
|
|
lines.append("\\hline")
|
|
lines.append("\\end{tabular}")
|
|
lines.append("\\end{table}")
|
|
lines.append("")
|
|
|
|
# Analysis text
|
|
if top_by_time:
|
|
top_call = top_by_time[0]
|
|
lines.append(f"Najczęściej wywoływaną funkcją systemową jest")
|
|
lines.append(f"\\texttt{{{top_call.name.replace('_', '\\_')}}}, zajmująca")
|
|
lines.append(f"{top_call.time_percent:.1f}\\% czasu.")
|
|
|
|
if 'futex' in top_call.name.lower():
|
|
lines.append("Jest to mechanizm synchronizacji wątków w systemie Linux,")
|
|
lines.append("co wskazuje na intensywne wykorzystanie wielowątkowości")
|
|
lines.append("przez silnik gry.")
|
|
elif 'pthread' in top_call.name.lower():
|
|
lines.append("Funkcje pthread odpowiadają za zarządzanie wątkami POSIX,")
|
|
lines.append("co potwierdza wielowątkową architekturę silnika.")
|
|
lines.append("")
|
|
|
|
# Frame time analysis (if SQLite available)
|
|
for result in self.results:
|
|
if result.sqlite_path and os.path.exists(result.sqlite_path):
|
|
frame_times = self.get_frame_times_from_sqlite(result.sqlite_path)
|
|
|
|
if frame_times:
|
|
lines.append(f"\\subsection{{Analiza czasów klatek -- {result.engine}}}")
|
|
lines.append(f"\\label{{subsec:frametime-{result.name.replace('_', '-')}}}")
|
|
lines.append("")
|
|
|
|
import statistics
|
|
avg_ft = statistics.mean(frame_times)
|
|
min_ft = min(frame_times)
|
|
max_ft = max(frame_times)
|
|
std_ft = statistics.stdev(frame_times) if len(frame_times) > 1 else 0
|
|
|
|
# Calculate percentiles
|
|
sorted_ft = sorted(frame_times)
|
|
p1 = sorted_ft[int(len(sorted_ft) * 0.01)] if len(sorted_ft) > 100 else min_ft
|
|
p99 = sorted_ft[int(len(sorted_ft) * 0.99)] if len(sorted_ft) > 100 else max_ft
|
|
|
|
lines.append("\\begin{table}[htbp]")
|
|
lines.append("\\centering")
|
|
lines.append("\\caption{Statystyki czasów klatek}")
|
|
lines.append(f"\\label{{tab:frametime-stats-{result.name.replace('_', '-')}}}")
|
|
lines.append("\\begin{tabular}{|l|r|}")
|
|
lines.append("\\hline")
|
|
lines.append("\\textbf{Metryka} & \\textbf{Wartość} \\\\")
|
|
lines.append("\\hline")
|
|
lines.append(f"Liczba klatek & {len(frame_times)} \\\\")
|
|
lines.append(f"Średni czas klatki & {avg_ft:.2f} ms \\\\")
|
|
lines.append(f"Minimalny czas klatki & {min_ft:.2f} ms \\\\")
|
|
lines.append(f"Maksymalny czas klatki & {max_ft:.2f} ms \\\\")
|
|
lines.append(f"Odchylenie standardowe & {std_ft:.2f} ms \\\\")
|
|
lines.append(f"1. percentyl & {p1:.2f} ms \\\\")
|
|
lines.append(f"99. percentyl & {p99:.2f} ms \\\\")
|
|
lines.append(f"Średnia liczba FPS & {1000/avg_ft:.2f} \\\\")
|
|
lines.append("\\hline")
|
|
lines.append("\\end{tabular}")
|
|
lines.append("\\end{table}")
|
|
lines.append("")
|
|
|
|
# Stability analysis
|
|
cv = (std_ft / avg_ft) * 100 if avg_ft > 0 else 0
|
|
lines.append(f"Współczynnik zmienności czasów klatek wynosi {cv:.1f}\\%,")
|
|
if cv < 5:
|
|
lines.append("co wskazuje na bardzo stabilną wydajność renderowania.")
|
|
elif cv < 15:
|
|
lines.append("co wskazuje na stabilną wydajność z niewielkimi wahaniami.")
|
|
else:
|
|
lines.append("co wskazuje na znaczne wahania wydajności.")
|
|
lines.append("")
|
|
|
|
# Summary section
|
|
lines.append("\\subsection{Podsumowanie analizy profilowania}")
|
|
lines.append("\\label{subsec:nsight-wnioski}")
|
|
lines.append("")
|
|
|
|
if self.results:
|
|
unity_results = [r for r in self.results if r.engine == 'Unity']
|
|
unreal_results = [r for r in self.results if r.engine == 'Unreal Engine']
|
|
|
|
if unity_results:
|
|
avg_unity_fps = sum(r.avg_fps for r in unity_results) / len(unity_results)
|
|
lines.append(f"Silnik Unity osiągnął średnią wydajność {avg_unity_fps:.2f} FPS")
|
|
lines.append(f"w przeprowadzonych testach.")
|
|
lines.append("")
|
|
|
|
if unreal_results:
|
|
avg_unreal_fps = sum(r.avg_fps for r in unreal_results) / len(unreal_results)
|
|
lines.append(f"Silnik Unreal Engine osiągnął średnią wydajność {avg_unreal_fps:.2f} FPS")
|
|
lines.append(f"w przeprowadzonych testach.")
|
|
lines.append("")
|
|
|
|
if unity_results and unreal_results:
|
|
diff = avg_unity_fps - avg_unreal_fps
|
|
if abs(diff) > 5:
|
|
better = "Unity" if diff > 0 else "Unreal Engine"
|
|
lines.append(f"Silnik {better} wykazał lepszą wydajność w przeprowadzonych testach,")
|
|
lines.append(f"osiągając o {abs(diff):.1f} FPS więcej.")
|
|
else:
|
|
lines.append("Oba silniki wykazały zbliżoną wydajność w przeprowadzonych testach.")
|
|
lines.append("")
|
|
|
|
lines.append("% Koniec sekcji wyników Nsight")
|
|
lines.append("")
|
|
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Analyze Nsight profiling data and generate LaTeX output'
|
|
)
|
|
parser.add_argument(
|
|
'--data-dir', '-d',
|
|
default='data/nsight',
|
|
help='Directory containing Nsight data files (default: data/nsight)'
|
|
)
|
|
parser.add_argument(
|
|
'--output', '-o',
|
|
default='latex/tex/wyniki-nsight.tex',
|
|
help='Output LaTeX file path (default: latex/tex/wyniki-nsight.tex)'
|
|
)
|
|
parser.add_argument(
|
|
'--print', '-p',
|
|
action='store_true',
|
|
help='Print LaTeX to stdout instead of writing to file'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Find project root
|
|
script_dir = Path(__file__).parent
|
|
project_dir = script_dir.parent
|
|
|
|
data_dir = project_dir / args.data_dir
|
|
output_path = project_dir / args.output
|
|
|
|
if not data_dir.exists():
|
|
print(f"Error: Data directory not found: {data_dir}")
|
|
sys.exit(1)
|
|
|
|
print(f"Nsight Profiling Analyzer")
|
|
print(f"=" * 50)
|
|
print(f"Data directory: {data_dir}")
|
|
print(f"Output file: {output_path}")
|
|
print()
|
|
|
|
analyzer = NsightAnalyzer(str(data_dir))
|
|
analyzer.analyze_all()
|
|
|
|
if not analyzer.results:
|
|
print("Warning: No profiling results found!")
|
|
sys.exit(1)
|
|
|
|
print()
|
|
print("Generating LaTeX output...")
|
|
latex_content = analyzer.generate_latex()
|
|
|
|
if args.print:
|
|
print()
|
|
print(latex_content)
|
|
else:
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write(latex_content)
|
|
print(f"LaTeX output written to: {output_path}")
|
|
|
|
print()
|
|
print("Done!")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|