Files
MSCO/PerfStats.cs
T

82 lines
3.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Laufzeit-Statistik: wie viele ms pro Bild jeder MSCO-Teil braucht, FPS und Garbage-Collections. Konsole (F7):
/// "perf" bzw. "perf reset". Mit der Startoption -msco-perf wird alle 30 s eine Zeile in MSCO\MSCO_Loader.txt
/// geschrieben (zum Messen ohne Konsole).
/// </summary>
public static class PerfStats
{
static readonly Dictionary<string, double> totals = new Dictionary<string, double>();
/// <summary>Aufrufe der Harmony-Haken (Fsm.SwitchState, Fsm.Event, cInput) - zum Abschätzen ihrer Kosten.</summary>
public static int SwitchStateCalls, EventCalls, InputCalls;
static int frames, gcStart = -1;
static double frameSeconds, worstFrame;
static float started = -1f;
static readonly bool autoLog = Array.IndexOf(Environment.GetCommandLineArgs(), "-msco-perf") >= 0;
public static void Add(string part, double ms)
{
double t;
totals.TryGetValue(part, out t);
totals[part] = t + ms;
}
/// <summary>Einmal pro Bild (Update).</summary>
public static void Frame(float dt)
{
if (started < 0f) Reset();
frames++;
frameSeconds += dt;
if (dt > worstFrame) worstFrame = dt;
// Mit -msco-perf alle 30 s, sonst im Spiel alle 5 Minuten ein Bericht in MSCO_Loader.txt - so lässt sich bei
// "wenig FPS" nachsehen, ob MSCO, eine Mod oder das Spiel selbst die Zeit braucht
float every = autoLog ? 30f : 300f;
if ((autoLog || MenuHelper.InGame) && Time.realtimeSinceStartup - started >= every)
{
Bootstrap.Log(Report());
Reset();
}
}
public static void Reset()
{
totals.Clear();
frames = 0;
frameSeconds = 0;
worstFrame = 0;
started = Time.realtimeSinceStartup;
gcStart = GC.CollectionCount(0);
SwitchStateCalls = EventCalls = InputCalls = 0;
}
public static string Report()
{
if (frames == 0) return "PERF: no frames measured yet";
double seconds = Math.Max(0.001, Time.realtimeSinceStartup - started);
double msco = 0;
foreach (double v in totals.Values) msco += v;
var parts = new List<KeyValuePair<string, double>>(totals);
parts.Sort((a, b) => b.Value.CompareTo(a.Value));
var sb = new StringBuilder();
sb.Append("PERF ").Append(seconds.ToString("0")).Append(" s (").Append(MenuHelper.Scene).Append("): ")
.Append((frames / frameSeconds).ToString("0")).Append(" fps, ")
.Append((frameSeconds * 1000.0 / frames).ToString("0.0")).Append(" ms/frame (worst ")
.Append((worstFrame * 1000.0).ToString("0")).Append(" ms), MSCO ")
.Append((msco / frames).ToString("0.00")).Append(" ms/frame, GC ")
.Append(((GC.CollectionCount(0) - gcStart) * 60.0 / seconds).ToString("0.0")).Append("/min")
.Append(", hooks/frame: state ").Append((SwitchStateCalls / (double)frames).ToString("0"))
.Append(" event ").Append((EventCalls / (double)frames).ToString("0"))
.Append(" input ").Append((InputCalls / (double)frames).ToString("0"));
for (int i = 0; i < parts.Count && i < 8; i++)
sb.Append(i == 0 ? " | " : ", ").Append(parts[i].Key).Append(' ').Append((parts[i].Value / frames).ToString("0.000"));
return sb.ToString();
}
}
}