Files
sbox-public/engine/Sandbox.Engine/Game/Services/Benchmark/Benchmark.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

139 lines
4.8 KiB
C#

using Sandbox.Engine;
using System.Threading;
namespace Sandbox.Services;
/// <summary>
/// Allows access to stats for the current game. Stats are defined by the game's author
/// and can be used to track anything from player actions to performance metrics. They are
/// how you submit data to leaderboards.
/// </summary>
public partial class BenchmarkSystem
{
private Dictionary<string, BenchmarkRecord> results { get; } = new();
private List<Sampler> samplers;
private Dictionary<string, double> metrics = new();
private Allocations.Scope allocations = new Allocations.Scope();
string testName;
FastTimer timer;
public BenchmarkSystem()
{
}
/// <summary>
/// Called to start a benchmark
/// </summary>
public void Start( string name )
{
testName = name;
metrics.Clear();
allocations.Clear();
samplers = new()
{
new ("Fps", () => 1.0f / Time.Delta ),
// PerformanceStats
new ("ApproximateProcessMemoryUsage", () => PerformanceStats.ApproximateProcessMemoryUsage / (1024.0f * 1024.0f * 1024.0f) ),
new ("BytesAllocated", () => PerformanceStats.BytesAllocated),
new ("FrameTimeMs", () => PerformanceStats.FrameTime * 1000.0f ),
new ("GpuFrametime", () => PerformanceStats.GpuFrametime, () => PerformanceStats.GpuFrameNumber ),
new ("Gen0Collections", () => PerformanceStats.Gen0Collections ),
new ("Gen1Collections", () => PerformanceStats.Gen1Collections ),
new ("Gen2Collections", () => PerformanceStats.Gen2Collections ),
new ("GcPauseMs", () => TimeSpan.FromTicks( PerformanceStats.GcPause ).Milliseconds ), // Convert to ms so it matches the other timings
new ("Exceptions", () => PerformanceStats.Exceptions ),
// SceneStats
new( "ObjectsRendered", () => FrameStats._current.ObjectsRendered ),
new( "TrianglesRendered", () => FrameStats._current.TrianglesRendered ),
new( "DrawCalls", () => FrameStats._current.DrawCalls ),
new( "MaterialChanges", () => FrameStats._current.MaterialChanges ),
new( "DisplayLists", () => FrameStats._current.DisplayLists ),
new( "SceneViewsRendered", () => FrameStats._current.SceneViewsRendered ),
new( "RenderTargetResolves", () => FrameStats._current.RenderTargetResolves ),
new( "ObjectsCulledByVis", () => FrameStats._current.ObjectsCulledByVis ),
new( "ObjectsCulledByScreenSize", () => FrameStats._current.ObjectsCulledByScreenSize ),
new( "ObjectsCulledByFade", () => FrameStats._current.ObjectsCulledByFade ),
new( "ObjectsFading", () => FrameStats._current.ObjectsFading ),
new( "ShadowedLightsInView", () => FrameStats._current.ShadowedLightsInView ),
new( "UnshadowedLightsInView", () => FrameStats._current.UnshadowedLightsInView ),
new( "ShadowMaps", () => FrameStats._current.ShadowMaps ),
};
foreach ( var e in Sandbox.Diagnostics.PerformanceStats.Timings.GetMain() )
{
samplers.Add( new( e.Name, () => e.AverageMs( 1 ) ) );
}
timer.Start();
allocations.Start();
IGameInstanceDll.Current.ResetSceneListenerMetrics();
}
/// <summary>
/// Set a custom metric, like load time, shutdown time etc
/// </summary>
/// <param name="name"></param>
/// <param name="metric"></param>
public void SetMetric( string name, double metric )
{
metrics[name] = metric;
}
/// <summary>
/// Called to close a benchmark off
/// </summary>
public void Finish()
{
var elapsedSeconds = timer.ElapsedSeconds;
allocations.Stop();
var benchmarkResult = new BenchmarkRecord();
benchmarkResult.Name = testName;
benchmarkResult.Duration = elapsedSeconds;
benchmarkResult.Data = samplers.ToDictionary( x => x.Name, x => (object)x.GetResults() );
benchmarkResult.Data["Alloc"] = allocations.Entries.OrderByDescending( x => x.TotalBytes ).Take( 100 ).ToDictionary( x => x.Name, x => new { x.Count, Size = x.TotalBytes } );
benchmarkResult.Data["Listeners"] = IGameInstanceDll.Current.GetSceneListenerMetrics();
foreach ( var m in metrics )
{
benchmarkResult.Data[m.Key] = m.Value;
}
results[testName] = benchmarkResult;
}
/// <summary>
/// Should be called in update every frame
/// </summary>
public void Sample()
{
if ( samplers is null )
return;
foreach ( var sampler in samplers )
{
sampler.Update();
}
}
/// <summary>
/// Finish this benchmark session and send it off to the backend
/// </summary>
public Task<Guid> SendAsync( CancellationToken token = default )
{
var value = results.Values.ToArray();
results.Clear();
allocations?.Stop();
return Api.Benchmarks.Post( value, token );
}
}