Editor/Output/ArchBuildProfile.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Sunless.Architecture;
// Where one rebuild's time actually went. Ambient the way ArchBuildMemo is, because the phases worth naming are
// spread across the scene reconcile, the builder and the tool, and none of them could be handed a profile without
// threading one through every generator standing between them.
public sealed class ArchBuildProfile : IDisposable {
public static ArchBuildProfile Current { get; private set; }
readonly ArchBuildProfile held;
readonly long opened = Stopwatch.GetTimestamp();
readonly Dictionary<string, double> phases = new();
readonly List<string> order = new();
readonly Dictionary<string, double> roles = new();
readonly Dictionary<string, int> counts = new();
ArchBuildProfile() {
held = Current;
Current = this;
}
public static ArchBuildProfile Begin() => new();
public void Dispose() {
Total = Since( opened );
Current = held;
}
public double Total { get; private set; }
public IEnumerable<KeyValuePair<string, double>> Phases => order.Select( name => new KeyValuePair<string, double>( name, phases[name] ) );
// The generators, heaviest first - which one to attack is the whole question the generate phase raises and
// cannot answer.
public IEnumerable<KeyValuePair<string, double>> Roles => roles.OrderByDescending( entry => entry.Value );
public int Drawn( string role ) => counts.TryGetValue( role, out var held ) ? held : 0;
// Nothing open means nothing measured, so every call site reads the same whether anybody is profiling or not -
// `using` over a null disposable is a no-op.
public static IDisposable Stage( string name ) {
return Current is { } profile ? new Phase( profile, name ) : null;
}
public static long Now => Current is null ? 0L : Stopwatch.GetTimestamp();
public static void Ran( string role, long from ) {
if ( Current is not { } profile || from == 0L ) {
return;
}
profile.roles[role] = profile.roles.TryGetValue( role, out var held ) ? held + Since( from ) : Since( from );
profile.counts[role] = profile.Drawn( role ) + 1;
}
static double Since( long from ) => (Stopwatch.GetTimestamp() - from) * 1000d / Stopwatch.Frequency;
void Took( string name, long from ) {
if ( !phases.ContainsKey( name ) ) {
order.Add( name );
phases[name] = 0d;
}
phases[name] += Since( from );
}
readonly struct Phase : IDisposable {
readonly ArchBuildProfile profile;
readonly string name;
readonly long from;
public Phase( ArchBuildProfile profile, string name ) {
this.profile = profile;
this.name = name;
from = Stopwatch.GetTimestamp();
}
public void Dispose() => profile.Took( name, from );
}
}