Files
2025-05-18 13:04:45 +08:00

194 lines
5.3 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace EpicGames.Tracing.UnrealInsights.Events
{
public class CpuProfilerEventSpecEvent : ITraceEvent
{
public static readonly EventType EventType = new EventType(0, "CpuProfiler", "EventSpec", EventType.FlagImportant | EventType.FlagMaybeHasAux | EventType.FlagNoSync,
new List<EventTypeField>() {
new EventTypeField(0, 4, EventTypeField.TypeInt32, "Id"),
new EventTypeField(4, 0, EventTypeField.TypeAnsiString, "Name")
});
public ushort Size => (ushort) (_genericEvent.Size + TraceImportantEventHeader.HeaderSize);
public EventType Type => EventType;
public uint Id { get; }
readonly string _name;
private readonly GenericEvent _genericEvent;
public CpuProfilerEventSpecEvent(uint id, string name)
{
Id = id;
_name = name;
Field[] fields =
{
Field.FromInt((int) Id),
Field.FromString(_name),
};
_genericEvent = new GenericEvent(0, fields, EventType);
}
public void Serialize(ushort uid, BinaryWriter writer)
{
new TraceImportantEventHeader(uid, _genericEvent.Size).Serialize(writer);
_genericEvent.Serialize(uid, writer);
}
}
public class CpuProfilerEventBatchEvent : ITraceEvent
{
public static readonly EventType EventType = new EventType(0, "CpuProfiler", "EventBatch", EventType.FlagMaybeHasAux | EventType.FlagNoSync,
new List<EventTypeField>() { new EventTypeField(0, 0, EventTypeField.TypeArray, "Data") });
public ushort Size => (ushort) (_genericEvent.Size + sizeof(ushort));
public EventType Type => EventType;
readonly GenericEvent _genericEvent;
public CpuProfilerEventBatchEvent(byte[] data)
{
Field[] fields = { Field.FromArray(data) };
_genericEvent = new GenericEvent(0, fields, EventType);
}
public void Serialize(ushort uid, BinaryWriter writer)
{
writer.WritePackedUid(uid);
_genericEvent.Serialize(uid, writer);
}
}
public class CpuProfilerScopeEvent
{
public ulong Timestamp { get; }
public bool IsEnterScope { get; }
public uint? SpecId { get; }
public CpuProfilerScopeEvent(ulong timestamp, bool isEnterScope, uint? specId)
{
Timestamp = timestamp;
IsEnterScope = isEnterScope;
SpecId = specId;
}
public override string ToString()
{
string name = IsEnterScope ? "EnterScope" : "ExitScope";
return $"{nameof(CpuProfilerScopeEvent)}({name} {nameof(Timestamp)}={Timestamp} {nameof(SpecId)}={SpecId})";
}
}
/// <summary>
/// Stateful serializer that can serialize/deserialize the data argument in CpuProfiler.EventBatch events
/// </summary>
public class CpuProfilerSerializer
{
private readonly ulong _cycleFrequency;
public List<CpuProfilerScopeEvent> ScopeEvents { get; } = new List<CpuProfilerScopeEvent>();
public CpuProfilerSerializer(ulong cycleFrequency)
{
_cycleFrequency = cycleFrequency;
}
public void Read(byte[] eventBatchData)
{
using MemoryStream ms = new MemoryStream(eventBatchData);
using BinaryReader reader = new BinaryReader(ms);
ulong absTimestamp = 0;
ulong lastTimestamp = 0;
while (reader.BaseStream.Position != reader.BaseStream.Length)
{
ulong value = TraceUtils.Read7BitUint(reader);
(ulong timestamp, bool isEnterScope) = DecodeTimestamp(value);
if (absTimestamp == 0)
{
absTimestamp = timestamp; // First timestamp is always absolute
}
else
{
timestamp = lastTimestamp + timestamp * _cycleFrequency; // Make timestamp absolute
}
lastTimestamp = timestamp;
if (isEnterScope)
{
ulong specId = TraceUtils.Read7BitUint(reader);
CpuProfilerScopeEvent scopeEvent = new CpuProfilerScopeEvent(timestamp, true, (uint?)specId);
ScopeEvents.Add(scopeEvent);
}
else
{
CpuProfilerScopeEvent scopeEvent = new CpuProfilerScopeEvent(timestamp, false, null);
ScopeEvents.Add(scopeEvent);
}
}
}
public List<byte[]> Write()
{
List<MemoryStream> streams = new List<MemoryStream>();
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
streams.Add(stream);
ulong lastAbsTimestamp = 0;
foreach (CpuProfilerScopeEvent scope in ScopeEvents)
{
ulong timestamp;
if (lastAbsTimestamp == 0)
{
timestamp = scope.Timestamp;
}
else
{
timestamp = (scope.Timestamp - lastAbsTimestamp) / _cycleFrequency;
}
lastAbsTimestamp = scope.Timestamp;
TraceUtils.Write7BitUint(writer, EncodeTimestamp(timestamp, scope.IsEnterScope));
if (scope.IsEnterScope)
{
TraceUtils.Write7BitUint(writer, (ulong) scope.SpecId!);
}
// Split buffers after 250 bytes
if (stream.Position > 250)
{
writer.Close();
stream = new MemoryStream();
writer = new BinaryWriter(stream);
lastAbsTimestamp = 0;
}
}
writer.Close();
return streams.Select(x => x.ToArray()).ToList();
}
internal static ulong EncodeTimestamp(ulong timestamp, bool isScopeEnter)
{
ulong value = (timestamp << 1) | (uint)(isScopeEnter ? 1 : 0);
return value;
}
internal static (ulong Timestamp, bool IsScopeEnter) DecodeTimestamp(ulong value)
{
bool isScopeEnter = (value & 1) != 0;
ulong timestamp = value >> 1; // Strip the IsScopeEnter bit
return (timestamp, isScopeEnter);
}
}
}