mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-13 16:59:38 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
190 lines
5.0 KiB
C#
190 lines
5.0 KiB
C#
namespace Sandbox.Services;
|
|
|
|
public partial class BenchmarkSystem
|
|
{
|
|
internal class Sampler
|
|
{
|
|
public struct Result
|
|
{
|
|
public string Name { get; set; }
|
|
public double Min { get; set; }
|
|
public double Max { get; set; }
|
|
public double Avg { get; set; }
|
|
public double Sum { get; set; }
|
|
/// <summary>
|
|
/// The value below which 5% of the samples fall (the 5th percentile).
|
|
/// </summary>
|
|
public double P5 { get; set; }
|
|
/// <summary>
|
|
/// The value below which 95% of the samples fall (the 95th percentile).
|
|
/// </summary>
|
|
public double P95 { get; set; }
|
|
/// <summary>
|
|
/// The value below which 99% of the samples fall (the 99th percentile).
|
|
/// </summary>
|
|
public double P99 { get; set; }
|
|
/// <summary>
|
|
/// The value below which 99.9% of the samples fall (the 99.9th percentile).
|
|
/// </summary>
|
|
public double P99_9 { get; set; }
|
|
/// <summary>
|
|
/// This is the sum of all samples that exceeded the stuttering threshold.
|
|
/// May not be very useful for non time based samples.
|
|
/// </summary>
|
|
public double Stuttering { get; set; }
|
|
public double Count { get; set; }
|
|
}
|
|
|
|
public string Name { get; private set; }
|
|
|
|
private List<double> samples;
|
|
|
|
public delegate double GetSampleValueDelegate();
|
|
private GetSampleValueDelegate getSampleValue;
|
|
|
|
public delegate uint GetSampleKeyDelegate();
|
|
private GetSampleKeyDelegate getSampleKey;
|
|
|
|
private uint lastSampleKey;
|
|
|
|
public Sampler( string name, GetSampleValueDelegate getSampleValue, GetSampleKeyDelegate getSampleKey = null )
|
|
{
|
|
//Debug.Assert( getSampleValue != null, "No action provided to sampler" );
|
|
this.getSampleValue = getSampleValue;
|
|
this.getSampleKey = getSampleKey;
|
|
|
|
samples = new List<double>();
|
|
Name = name;
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
// for samplers that are a value tied to a key, skip the sample if the key has not changed
|
|
// (e.g. don't record duplicate GPU frametimes for a frame, where frame # is the key)
|
|
if ( getSampleKey != null )
|
|
{
|
|
uint key = getSampleKey.Invoke();
|
|
uint lastKey = lastSampleKey;
|
|
|
|
lastSampleKey = key;
|
|
if ( key == lastKey )
|
|
return;
|
|
}
|
|
|
|
if ( getSampleValue != null )
|
|
AddSample( getSampleValue.Invoke() );
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
samples.Clear();
|
|
}
|
|
|
|
public void AddSample( double value )
|
|
{
|
|
samples.Add( value );
|
|
}
|
|
|
|
public bool HasValues => samples is not null;
|
|
|
|
public Result GetResults()
|
|
{
|
|
return new Result()
|
|
{
|
|
Name = Name,
|
|
Min = samples.Count() > 0 ? samples.Min() : 0,
|
|
Max = samples.Count() > 0 ? samples.Max() : 0,
|
|
Avg = samples.Count() > 0 ? samples.Average() : 0,
|
|
Sum = samples.Count() > 0 ? samples.Sum() : 0,
|
|
P5 = Percentile( 5 ),
|
|
P95 = Percentile( 95 ),
|
|
P99 = Percentile( 99 ),
|
|
P99_9 = Percentile( 99.9 ),
|
|
Stuttering = Stuttering( stutteringFactor: 2.5 ),
|
|
Count = samples.Count()
|
|
};
|
|
}
|
|
|
|
public string GetDisplayName()
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the specified percentile value from the collected samples.
|
|
/// </summary>
|
|
/// <param name="percentile">
|
|
/// The percentile to compute (a value between 0 and 100).
|
|
/// For example, passing 95 computes the 95th percentile.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The value below which the given percentage of samples fall.
|
|
/// </returns>
|
|
private double Percentile( double percentile )
|
|
{
|
|
var sortedSequence = samples.OrderBy( n => n ).ToList();
|
|
double position = (sortedSequence.Count + 1) * percentile / 100.0;
|
|
int index = (int)position - 1;
|
|
double fraction = position - Math.Floor( position );
|
|
|
|
if ( index + 1 < sortedSequence.Count )
|
|
{
|
|
return sortedSequence[index] + fraction * (sortedSequence[index + 1] - sortedSequence[index]);
|
|
}
|
|
else
|
|
{
|
|
return sortedSequence[index];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the sum of all samples that exceeded the stuttering threshold.
|
|
/// A sample is considered stuttering if it exceeds the moving average multiplied by the stuttering factor.
|
|
/// </summary>
|
|
/// <param name="stutteringFactor">
|
|
/// The multiplier used to determine the stuttering threshold.
|
|
/// A higher factor makes it less sensitive to spikes (default is typically 2.5).
|
|
/// </param>
|
|
private double Stuttering( double stutteringFactor )
|
|
{
|
|
var averageSequence = GetMovingAverage();
|
|
double stuttering = 0.0;
|
|
|
|
for ( int i = 0; i < samples.Count; i++ )
|
|
{
|
|
if ( samples[i] > stutteringFactor * averageSequence[i] )
|
|
{
|
|
stuttering += samples[i];
|
|
}
|
|
}
|
|
|
|
return stuttering;
|
|
}
|
|
|
|
private IList<double> GetMovingAverage()
|
|
{
|
|
int windowSize = Math.Max( 1, (int)(Math.Sqrt( samples.Count ) * 10) );
|
|
var movingAverage = new List<double>( samples.Count );
|
|
|
|
for ( int i = 0; i < samples.Count; i++ )
|
|
{
|
|
int start = Math.Max( 0, i - windowSize / 2 );
|
|
int end = Math.Min( samples.Count - 1, i + windowSize / 2 );
|
|
|
|
double sum = 0.0;
|
|
|
|
for ( int j = start; j <= end; j++ )
|
|
{
|
|
sum += samples[j];
|
|
}
|
|
|
|
double average = sum / (end - start + 1);
|
|
movingAverage.Add( average );
|
|
}
|
|
|
|
return movingAverage;
|
|
}
|
|
}
|
|
}
|
|
|