mirror of
https://github.com/HayatoG/iss_pc.git
synced 2026-11-08 17:14:36 -10:00
session_chart.py takes the frame cap as an optional 4th argument (30 by default, 0 for games without a cap: the axis follows the session, 30 and 60 FPS references, median and time above 30/60 FPS). inFAMOUS, current main on an RTX 2060: median 27 FPS. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JyWhQUvUQf2YSf8HJWnAtd
72 lines
3.6 KiB
Python
72 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Desempenho de uma sessão a partir de um log do port (TLG_FRAME_STATS=1, uma amostra a cada 5 s):
|
|
FPS, pior quadro e draws por quadro ao longo do tempo.
|
|
Uso: session_chart.py <log> <saída.png> [nome do jogo] [trava de FPS: 30 (padrão), 60 ou 0 = sem trava]"""
|
|
import re
|
|
import statistics
|
|
import sys
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.pyplot as plt
|
|
|
|
STAT = re.compile(r'Frame stats: ([\d.]+) FPS, worst frame ([\d.]+) ms.*?(\d+) draws/frame')
|
|
PACE = re.compile(r'Frame pacing: median ([\d.]+) ms, stddev ([\d.]+) ms, p99 ([\d.]+) ms')
|
|
|
|
text = open(sys.argv[1], encoding='utf-8', errors='replace').read()
|
|
stats = [tuple(map(float, m.groups())) for m in STAT.finditer(text)]
|
|
pacing = [tuple(map(float, m.groups())) for m in PACE.finditer(text)]
|
|
# The first samples cover boot and shader warm-up; the chart shows the whole session anyway.
|
|
t = [i * 5 / 60 for i in range(len(stats))]
|
|
fps = [s[0] for s in stats]
|
|
worst = [s[1] for s in stats]
|
|
draws = [s[2] for s in stats]
|
|
|
|
ink, muted, accent, warn = '#1f2937', '#6b7280', '#2563eb', '#dc2626'
|
|
plt.rcParams.update({'font.size': 11, 'axes.edgecolor': '#d1d5db', 'axes.labelcolor': ink,
|
|
'xtick.color': muted, 'ytick.color': muted, 'axes.spines.top': False,
|
|
'axes.spines.right': False})
|
|
fig, (a, b, c) = plt.subplots(3, 1, figsize=(10, 6.4), sharex=True,
|
|
gridspec_kw={'height_ratios': [1.1, 1, 0.8]})
|
|
fig.patch.set_facecolor('white')
|
|
a.plot(t, fps, color=accent, lw=1.8)
|
|
cap = float(sys.argv[4]) if len(sys.argv) > 4 else 30.0
|
|
if cap:
|
|
a.axhline(cap, color=muted, lw=0.8, ls='--')
|
|
a.set_ylim(0, cap * 1.2)
|
|
a.text(t[-1], cap * 1.04, 'trava do jogo: %.0f FPS' % cap, ha='right', color=muted, fontsize=9)
|
|
else:
|
|
# Uncapped game (inFAMOUS): the axis follows the session, with 30 and 60 FPS for reference.
|
|
a.set_ylim(0, max(66, max(fps) * 1.1))
|
|
for ref in (30, 60):
|
|
a.axhline(ref, color=muted, lw=0.8, ls='--')
|
|
a.text(t[-1], ref + 1, '%d FPS' % ref, ha='right', color=muted, fontsize=9)
|
|
a.set_ylabel('FPS')
|
|
ideal = 1000.0 / (cap or 30.0)
|
|
b.plot(t, worst, color=warn, lw=1.4)
|
|
b.axhline(ideal, color=muted, lw=0.8, ls='--')
|
|
b.set_ylim(0, max(100, max(worst) * 1.1))
|
|
b.set_ylabel('pior quadro (ms)')
|
|
b.text(t[-1], ideal + 3, 'quadro ideal: %.1f ms' % ideal, ha='right', va='bottom', color=muted, fontsize=9)
|
|
c.plot(t, draws, color='#059669', lw=1.4)
|
|
c.set_ylabel('draws/quadro')
|
|
c.set_xlabel('minutos de sessão')
|
|
steady = [f for f in fps[6:]] or fps
|
|
target = cap or 30.0
|
|
at30 = sum(1 for f in steady if f >= target - 0.5) / len(steady) * 100
|
|
at60 = sum(1 for f in steady if f >= 59.5) / len(steady) * 100
|
|
p99 = statistics.median(p[2] for p in pacing) if pacing else float('nan')
|
|
game = sys.argv[3] if len(sys.argv) > 3 else 'The Last Guardian'
|
|
fig.suptitle('%s no PC — sessão de %.0f min (Linux, Vulkan, RTX 2060 6 GB)' % (game, t[-1]),
|
|
x=0.07, ha='left', color=ink, fontsize=13, fontweight='bold')
|
|
if cap:
|
|
summary = '%.0f%% das janelas de 5 s a %.0f FPS · p99 do quadro %.1f ms · %d a %d draws por quadro' % (
|
|
at30, cap, p99, min(draws), max(draws))
|
|
else:
|
|
summary = ('sem trava de FPS · mediana %.0f FPS · %.0f%% das janelas a 30+ FPS, %.0f%% a 60 · '
|
|
'%d a %d draws por quadro') % (statistics.median(steady), at30, at60, min(draws), max(draws))
|
|
fig.text(0.07, 0.915, summary, color=muted, fontsize=10)
|
|
fig.tight_layout(rect=(0, 0, 1, 0.92))
|
|
fig.savefig(sys.argv[2], dpi=110)
|
|
print(f'{len(stats)} amostras, mediana {statistics.median(steady):.1f} FPS, {at30:.0f}% a {target:.0f}+ FPS, '
|
|
f'p99 {p99:.2f} ms, draws {min(draws):.0f}-{max(draws):.0f}')
|