// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
namespace UnrealBuildTool
{
///
/// Public interface for a timeline scope. Should be disposed to exit the scope.
///
interface ITimelineEvent : IDisposable
{
void Finish();
}
///
/// Tracks simple high-level timing data
///
static class Timeline
{
///
/// A marker in the timeline
///
[DebuggerDisplay("{Name}")]
class Event : ITimelineEvent
{
///
/// Name of the marker
///
public string Name;
///
/// Time at which the event ocurred
///
public TimeSpan StartTime;
///
/// Time at which the event ended
///
public TimeSpan? FinishTime;
///
/// The trace span for external tracing
///
public ITraceSpan Span;
///
/// Constructor
///
/// Event name
/// Time of the event
/// Finish time for the event. May be null.
public Event(string Name, TimeSpan StartTime, TimeSpan? FinishTime)
{
this.Name = Name;
this.StartTime = StartTime;
this.FinishTime = FinishTime;
this.Span = TraceSpan.Create(Name);
}
///
/// Finishes the current event
///
public void Finish()
{
if(!FinishTime.HasValue)
{
FinishTime = Stopwatch.Elapsed;
}
Span.Dispose();
}
///
/// Disposes of the current event
///
public void Dispose()
{
Finish();
}
}
///
/// The stopwatch used for timing
///
static Stopwatch Stopwatch = new Stopwatch();
///
/// The recorded events
///
static List Events = new List();
///
/// Property for the total time elapsed
///
public static TimeSpan Elapsed
{
get { return Stopwatch.Elapsed; }
}
///
/// Start the stopwatch
///
public static void Start()
{
Stopwatch.Restart();
}
///
/// Records a timeline marker with the given name
///
/// The marker name
public static void AddEvent(string Name)
{
TimeSpan Time = Stopwatch.Elapsed;
Events.Add(new Event(Name, Time, Time));
}
///
/// Enters a scope event with the given name. Should be disposed to terminate the scope.
///
/// Name of the event
/// Event to track the length of the event
public static ITimelineEvent ScopeEvent(string Name)
{
Event Event = new Event(Name, Stopwatch.Elapsed, null);
Events.Add(Event);
return Event;
}
///
/// Prints this information to the log
///
public static void Print(TimeSpan MaxUnknownTime, LogLevel Verbosity, ILogger Logger)
{
// Print the start time
Logger.Log(Verbosity, "Timeline:");
Logger.Log(Verbosity, "");
Logger.Log(Verbosity, "[{Time,6}]", FormatTime(TimeSpan.Zero));
// Create the root event
TimeSpan FinishTime = Stopwatch.Elapsed;
List OuterEvents = new List();
OuterEvents.Add(new Event("", TimeSpan.Zero, FinishTime));
// Print out all the child events
TimeSpan LastTime = TimeSpan.Zero;
for(int EventIdx = 0; EventIdx < Events.Count; EventIdx++)
{
Event Event = Events[EventIdx];
// Pop events off the stack
for (; OuterEvents.Count > 1; OuterEvents.RemoveAt(OuterEvents.Count - 1))
{
Event OuterEvent = OuterEvents.Last();
if (Event.StartTime < OuterEvent.FinishTime!.Value)
{
break;
}
UpdateLastEventTime(ref LastTime, OuterEvent.FinishTime.Value, MaxUnknownTime, OuterEvents, Verbosity, Logger);
}
// If there's a gap since the last event, print an unknown marker
UpdateLastEventTime(ref LastTime, Event.StartTime, MaxUnknownTime, OuterEvents, Verbosity, Logger);
// Print this event
Print(Event.StartTime, Event.FinishTime, Event.Name, OuterEvents, Verbosity, Logger);
// Push it onto the stack
if(Event.FinishTime.HasValue)
{
if(EventIdx + 1 < Events.Count && Events[EventIdx + 1].StartTime < Event.FinishTime.Value)
{
OuterEvents.Add(Event);
}
else
{
LastTime = Event.FinishTime.Value;
}
}
}
// Remove everything from the stack
for(; OuterEvents.Count > 0; OuterEvents.RemoveAt(OuterEvents.Count - 1))
{
UpdateLastEventTime(ref LastTime, OuterEvents.Last().FinishTime!.Value, MaxUnknownTime, OuterEvents, Verbosity, Logger);
}
// Print the finish time
Logger.Log(Verbosity, "[{Time,6}]", FormatTime(FinishTime));
}
///
/// Updates the last event time
///
///
///
///
///
///
///
static void UpdateLastEventTime(ref TimeSpan LastTime, TimeSpan NewTime, TimeSpan MaxUnknownTime, List OuterEvents, LogLevel Verbosity, ILogger Logger)
{
const string UnknownEvent = "";
if (NewTime - LastTime > MaxUnknownTime)
{
Print(LastTime, NewTime, UnknownEvent, OuterEvents, Verbosity, Logger);
}
LastTime = NewTime;
}
///
/// Prints an individual event to the log
///
/// Start time for the event
/// Finish time for the event. May be null.
/// Event name
/// List of all the start times for parent events
/// Verbosity for the output
/// Logger for output
static void Print(TimeSpan StartTime, TimeSpan? FinishTime, string Label, List OuterEvents, LogLevel Verbosity, ILogger Logger)
{
StringBuilder Prefix = new StringBuilder();
for(int Idx = 0; Idx < OuterEvents.Count - 1; Idx++)
{
Prefix.AppendFormat(" {0,6} ", FormatTime(StartTime - OuterEvents[Idx].StartTime));
}
Prefix.AppendFormat("[{0,6}]", FormatTime(StartTime - OuterEvents[OuterEvents.Count - 1].StartTime));
if (!FinishTime.HasValue)
{
Prefix.AppendFormat("({0,6})", "???");
}
else if(FinishTime.Value == StartTime)
{
Prefix.Append(" ------ ");
}
else
{
Prefix.AppendFormat("({0,6})", "+" + FormatTime(FinishTime.Value - StartTime));
}
Logger.Log(Verbosity, "{Prefix} {Label}", Prefix.ToString(), Label);
}
///
/// Formats a timespan in milliseconds
///
/// The time to format
/// Formatted timespan
static string FormatTime(TimeSpan Time)
{
int TotalMilliseconds = (int)Time.TotalMilliseconds;
return String.Format("{0}.{1:000}", TotalMilliseconds / 1000, TotalMilliseconds % 1000);
}
}
}