Files
UnrealEngine/Engine/Source/Developer/TraceServices/Private/Analyzers/NetTraceAnalyzer.cpp
2025-05-18 13:04:45 +08:00

1007 lines
39 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NetTraceAnalyzer.h"
#include "AnalysisServicePrivate.h"
#include "Common/Utils.h"
#include "TraceServices/Model/Frames.h"
#include "HAL/LowLevelMemTracker.h"
#include "Logging/LogMacros.h"
#include "TraceServices/Model/Threads.h"
DEFINE_LOG_CATEGORY_STATIC(LogNetTrace, Log, All);
namespace TraceServices
{
enum ENetTraceAnalyzerVersion
{
ENetTraceAnalyzerVersion_Initial = 1,
ENetTraceAnalyzerVersion_BunchChannelIndex = 2,
ENetTraceAnalyzerVersion_BunchChannelInfo = 3,
ENetTraceAnalyzerVersion_FixedBunchSizeEncoding = 4,
ENetTraceAnalyzerVersion_DebugNameIndexIs32Bits = 5,
};
FNetTraceAnalyzer::FNetTraceAnalyzer(IAnalysisSession& InSession, FNetProfilerProvider& InNetProfilerProvider)
: Session(InSession)
, NetProfilerProvider(InNetProfilerProvider)
, FrameProvider(ReadFrameProvider(InSession))
, NetTraceVersion(0)
, NetTraceReporterVersion(0)
{
}
void FNetTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context)
{
auto& Builder = Context.InterfaceBuilder;
Builder.RouteEvent(RouteId_InitEvent, "NetTrace", "InitEvent");
Builder.RouteEvent(RouteId_InstanceDestroyedEvent, "NetTrace", "InstanceDestroyedEvent");
Builder.RouteEvent(RouteId_NameEvent, "NetTrace", "NameEvent");
Builder.RouteEvent(RouteId_PacketContentEvent, "NetTrace", "PacketContentEvent");
Builder.RouteEvent(RouteId_PacketEvent, "NetTrace", "PacketEvent");
Builder.RouteEvent(RouteId_PacketDroppedEvent, "NetTrace", "PacketDroppedEvent");
Builder.RouteEvent(RouteId_ConnectionCreatedEvent, "NetTrace", "ConnectionCreatedEvent");
Builder.RouteEvent(RouteId_ConnectionUpdatedEvent, "NetTrace", "ConnectionUpdatedEvent");
// Add some default event types that we use for generic type events to make it easier to extend
// ConnectionAdded/Removed connections state /name etc?
Builder.RouteEvent(RouteId_ConnectionClosedEvent, "NetTrace", "ConnectionClosedEvent");
Builder.RouteEvent(RouteId_PacketStatsCounterEvent, "NetTrace", "PacketStatsCounterEvent");
Builder.RouteEvent(RouteId_FrameStatsCounterEvent, "NetTrace", "FrameStatsCounterEvent");
Builder.RouteEvent(RouteId_ObjectCreatedEvent, "NetTrace", "ObjectCreatedEvent");
Builder.RouteEvent(RouteId_ObjectExistsEvent, "NetTrace", "ObjectExistsEvent");
Builder.RouteEvent(RouteId_ObjectDestroyedEvent, "NetTrace", "ObjectDestroyedEvent");
Builder.RouteEvent(RouteId_ConnectionStateUpdatedEvent, "NetTrace", "ConnectionStateUpdatedEvent");
Builder.RouteEvent(RouteId_InstanceUpdatedEvent, "NetTrace", "InstanceUpdatedEvent");
// Default names
{
FAnalysisSessionEditScope _(Session);
BunchHeaderNameIndex = NetProfilerProvider.AddNetProfilerName(TEXT("BunchHeader"));
PendingNameIndex = NetProfilerProvider.AddNetProfilerName(TEXT("Pending"));
}
}
void FNetTraceAnalyzer::OnAnalysisEnd()
{
}
uint32 FNetTraceAnalyzer::GetTracedEventTypeIndex(FNetProfilerNameIndexType NameIndex, uint8 Level)
{
const uint64 TracedEventTypeKey = (((uint64)NameIndex) << 8U) | (uint64)Level;
if (const uint32* NetProfilerEventTypeIndex = TraceEventTypeToNetProfilerEventTypeIndexMap.Find(TracedEventTypeKey))
{
return *NetProfilerEventTypeIndex;
}
else
{
// Add new EventType
uint32 NewEventTypeIndex = NetProfilerProvider.AddNetProfilerEventType(NameIndex, Level);
TraceEventTypeToNetProfilerEventTypeIndexMap.Add(TracedEventTypeKey, NewEventTypeIndex);
return NewEventTypeIndex;
}
}
bool FNetTraceAnalyzer::OnEvent(uint16 RouteId, EStyle Style, const FOnEventContext& Context)
{
LLM_SCOPE_BYNAME(TEXT("Insights/FNetTraceAnalyzer"));
FAnalysisSessionEditScope _(Session);
// sometimes when a trace starts, the cache buffer contains some events which get written before the init event can be written,
// so we need to discard those
if ((NetTraceVersion == 0) && (RouteId != RouteId_InitEvent))
{
return true;
}
const auto& EventData = Context.EventData;
switch (RouteId)
{
case RouteId_InitEvent:
{
const uint64 TimestampCycles = EventData.GetValue<uint64>("Timestamp");
StartTimeStamp = Context.EventTime.AsSeconds(TimestampCycles);
LastTimeStamp = StartTimeStamp;
// we always trace the version so that we make sure that we are backwards compatible with older trace stream
NetTraceVersion = EventData.GetValue<uint32>("NetTraceVersion");
NetTraceReporterVersion = EventData.GetValue<uint32>("NetTraceReporterVersion");
NetProfilerProvider.SetNetTraceVersion(NetTraceVersion);
}
break;
case RouteId_InstanceDestroyedEvent:
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
DestroyActiveGameInstanceState(GameInstanceId);
}
break;
case RouteId_NameEvent:
{
FNetProfilerNameIndexType TraceNameId = EventData.GetValue<FNetProfilerNameIndexType>("NameId");
if (TracedNameIdToNetProfilerNameIdMap.Contains(TraceNameId))
{
// need to update the name
check(false);
}
else
{
FString Name = FTraceAnalyzerUtils::LegacyAttachmentString<UTF8CHAR>("Name", Context);
TracedNameIdToNetProfilerNameIdMap.Add(TraceNameId, NetProfilerProvider.AddNetProfilerName(*Name));
}
}
break;
case RouteId_PacketContentEvent:
{
HandlePacketContentEvent(Context, EventData);
}
break;
case RouteId_PacketEvent:
{
HandlePacketEvent(Context, EventData);
}
break;
case RouteId_PacketDroppedEvent:
{
HandlePacketDroppedEvent(Context, EventData);
}
break;
case RouteId_ConnectionCreatedEvent:
{
HandleConnectionCreatedEvent(Context, EventData);
}
break;
case RouteId_PacketStatsCounterEvent:
{
HandlePacketStatsCounterEvent(Context, EventData);
}
break;
case RouteId_FrameStatsCounterEvent:
{
HandleFrameStatsCounterEvent(Context, EventData);
}
break;
case RouteId_ConnectionStateUpdatedEvent:
{
HandleConnectionStateUpdatedEvent(Context, EventData);
}
break;
case RouteId_ConnectionUpdatedEvent:
{
HandleConnectionUpdatedEvent(Context, EventData);
}
break;
case RouteId_ConnectionClosedEvent:
{
HandleConnectionClosedEvent(Context, EventData);
}
break;
case RouteId_ObjectCreatedEvent:
{
HandleObjectCreatedEvent(Context, EventData);
}
break;
case RouteId_ObjectExistsEvent:
{
HandleObjectExistsEvent(Context, EventData);
}
break;
case RouteId_ObjectDestroyedEvent:
{
HandleObjectDestroyedEvent(Context, EventData);
}
break;
case RouteId_InstanceUpdatedEvent:
{
HandleGameInstanceUpdatedEvent(Context, EventData);
}
break;
}
return true;
}
void FNetTraceAnalyzer::HandlePacketContentEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint8 PacketType = EventData.GetValue<uint8>("PacketType");
//UE_LOG(LogNetTrace, Display, TEXT("FNetTraceAnalyzer::HandlePacketContentEvent: GameInstanceId: %u, ConnectionId: %u, %s"), (uint32)GameInstanceId, (uint32)ConnectionId, PacketType ? TEXT("Incoming") : TEXT("Outgoing"));
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
FNetTraceConnectionState* ConnectionState = GetActiveConnectionState(GameInstanceId, ConnectionId);
if (!ConnectionState)
{
return;
}
const ENetProfilerConnectionMode ConnectionMode = ENetProfilerConnectionMode(PacketType);
TArray<FNetProfilerContentEvent>& Events = (ConnectionState->BunchEvents)[ConnectionMode];
TArray<FBunchInfo>& BunchInfos = (ConnectionState->BunchInfos)[ConnectionMode];
// Decode batched events
TArrayView<const uint8> DataView = FTraceAnalyzerUtils::LegacyAttachmentArray("Data", Context);
uint64 BufferSize = DataView.Num();
const uint8* BufferPtr = DataView.GetData();
const uint8* BufferEnd = BufferPtr + BufferSize;
uint64 LastOffset = 0;
uint64 CurrentBunchOffset = 0U;
while (BufferPtr < BufferEnd)
{
// Decode data
const EContentEventType DecodedEventType = EContentEventType(*BufferPtr++);
switch (DecodedEventType)
{
case EContentEventType::Object:
case EContentEventType::NameId:
{
FNetProfilerContentEvent& Event = Events.Emplace_GetRef();
const uint8 DecodedNestingLevel = *BufferPtr++;
const uint64 DecodedNameOrObjectId = FTraceAnalyzerUtils::Decode7bit(BufferPtr);
const uint64 DecodedEventStartPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr) + LastOffset;
LastOffset = DecodedEventStartPos;
const uint64 DecodedEventEndPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr) + DecodedEventStartPos;
// Fill in event data
Event.StartPos = DecodedEventStartPos + CurrentBunchOffset;
Event.EndPos = DecodedEventEndPos + CurrentBunchOffset;
Event.Level = DecodedNestingLevel;
Event.ObjectInstanceIndex = 0;
Event.NameIndex = 0;
Event.BunchInfo.Value = 0;
checkSlow(Event.EndPos > Event.StartPos);
if (DecodedEventType == EContentEventType::Object)
{
// Object index, need to lookup name indirectly
if (const FNetTraceActiveObjectState* ActiveObjectState = GameInstanceState->ActiveObjects.Find(DecodedNameOrObjectId))
{
Event.NameIndex = ActiveObjectState->NameIndex;
Event.ObjectInstanceIndex = ActiveObjectState->ObjectIndex;
}
else if (DecodedNameOrObjectId != 0)
{
// Sometime we report data for objects that are still pending creation, which we will update as soon as we have more data.
FNetProfilerObjectInstance& ObjectInstance = NetProfilerProvider.CreateObject(GameInstanceState->GameInstanceIndex);
// Fill in the object data we currently have
ObjectInstance.LifeTime.Begin = GetLastTimestamp();
ObjectInstance.NameIndex = PendingNameIndex;
ObjectInstance.NetObjectId = DecodedNameOrObjectId;
ObjectInstance.TypeId = 0;
// Add to active objects
GameInstanceState->ActiveObjects.Add(DecodedNameOrObjectId, { ObjectInstance.ObjectIndex, ObjectInstance.NameIndex });
Event.NameIndex = ObjectInstance.NameIndex;
Event.ObjectInstanceIndex = ObjectInstance.ObjectIndex;
}
}
else if (DecodedEventType == EContentEventType::NameId)
{
if (const FNetProfilerNameIndexType* NetProfilerNameIndex = TracedNameIdToNetProfilerNameIdMap.Find(IntCastChecked<FNetProfilerNameIndexType>(DecodedNameOrObjectId)))
{
Event.NameIndex = *NetProfilerNameIndex;
}
else
{
UE_LOG(LogNetTrace, Warning, TEXT("PacketContentEvent GameInstanceId: %u, ConnectionId: %u %s, Missing NameIndex: %llu"), (uint32)GameInstanceId, (uint32)ConnectionId, ConnectionMode ? TEXT("Incoming") : TEXT("Outgoing"), DecodedNameOrObjectId);
}
}
// EventTypeIndex does not match NameIndex as we might see the same name on different levels
Event.EventTypeIndex = GetTracedEventTypeIndex(Event.NameIndex, static_cast<uint8>(Event.Level));
}
break;
case EContentEventType::BunchEvent:
{
const FNetProfilerNameIndexType DecodedNameId = IntCastChecked<FNetProfilerNameIndexType>(FTraceAnalyzerUtils::Decode7bit(BufferPtr));
uint32 DecodedBunchBits = 0U;
if (NetTraceVersion >= ENetTraceAnalyzerVersion_FixedBunchSizeEncoding)
{
DecodedBunchBits = IntCastChecked<uint32>(FTraceAnalyzerUtils::Decode7bit(BufferPtr));
}
else
{
const uint64 DecodedEventStartPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr);
const uint64 DecodedEventEndPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr);
DecodedBunchBits = IntCastChecked<uint32>((uint32)DecodedEventEndPos + (uint32)DecodedEventStartPos);
}
const FNetProfilerNameIndexType* NetProfilerNameIndex = DecodedNameId ? TracedNameIdToNetProfilerNameIdMap.Find(DecodedNameId) : nullptr;
FBunchInfo BunchInfo;
BunchInfo.BunchInfo.Value = 0;
BunchInfo.HeaderBits = 0U;
BunchInfo.BunchBits = DecodedBunchBits;
BunchInfo.FirstBunchEventIndex = Events.Num();
BunchInfo.NameIndex = NetProfilerNameIndex ? *NetProfilerNameIndex : 0U;
BunchInfos.Add(BunchInfo);
// Must reset LastOffset after reading bunch data
LastOffset = 0U;
}
break;
case EContentEventType::BunchHeaderEvent:
{
const uint32 DecodedEventCount = IntCastChecked<uint32>(FTraceAnalyzerUtils::Decode7bit(BufferPtr));
const uint32 DecodedHeaderBits = IntCastChecked<uint32>(FTraceAnalyzerUtils::Decode7bit(BufferPtr));
FBunchInfo& BunchInfo = BunchInfos.Last();
BunchInfo.EventCount = DecodedEventCount;
BunchInfo.FirstBunchEventIndex = Events.Num() - DecodedEventCount;
// A bunch with header bits set is an actual bunch
if (DecodedHeaderBits)
{
if (NetTraceVersion >= ENetTraceAnalyzerVersion_BunchChannelIndex)
{
const uint64 DecodedBunchInfo = FTraceAnalyzerUtils::Decode7bit(BufferPtr);
if (NetTraceVersion >= ENetTraceAnalyzerVersion_BunchChannelInfo)
{
BunchInfo.BunchInfo.Value = DecodedBunchInfo;
}
else
{
BunchInfo.BunchInfo.Value = uint64(0);
BunchInfo.BunchInfo.ChannelIndex = DecodedBunchInfo;
}
if (BunchInfo.NameIndex)
{
GameInstanceState->ChannelNames.FindOrAdd(BunchInfo.BunchInfo.ChannelIndex) = BunchInfo.NameIndex;
}
else
{
const FNetProfilerNameIndexType* ExistingChannelNameIndex = GameInstanceState->ChannelNames.Find(BunchInfo.BunchInfo.ChannelIndex);
BunchInfo.NameIndex = ExistingChannelNameIndex ? *ExistingChannelNameIndex : 0U;
}
BunchInfo.BunchInfo.bIsValid = 1U;
}
BunchInfo.HeaderBits = DecodedHeaderBits;
CurrentBunchOffset = 0U;
}
else
{
// Merged bunch, set offset for events
CurrentBunchOffset = BunchInfo.BunchBits;
}
}
break;
};
}
check(BufferPtr == BufferEnd);
}
void FNetTraceAnalyzer::AddEvent(TPagedArray<FNetProfilerContentEvent>& Events, const FNetProfilerContentEvent& InEvent, uint32 Offset, uint32 LevelOffset)
{
FNetProfilerContentEvent& Event = Events.PushBack();
Event.EventTypeIndex = GetTracedEventTypeIndex(InEvent.NameIndex, IntCastChecked<uint8>(InEvent.Level + LevelOffset));
Event.NameIndex = InEvent.NameIndex;
Event.ObjectInstanceIndex = InEvent.ObjectInstanceIndex;
Event.StartPos = InEvent.StartPos + Offset;
Event.EndPos = InEvent.EndPos + Offset;
Event.Level = InEvent.Level + LevelOffset;
Event.BunchInfo = InEvent.BunchInfo;
}
void FNetTraceAnalyzer::AddEvent(TPagedArray<FNetProfilerContentEvent>& Events, uint32 StartPos, uint32 EndPos, uint32 Level, FNetProfilerNameIndexType NameIndex, FNetProfilerBunchInfo BunchInfo)
{
FNetProfilerContentEvent& Event = Events.PushBack();
Event.EventTypeIndex = GetTracedEventTypeIndex(NameIndex, IntCastChecked<uint8>(Level));
Event.NameIndex = NameIndex;
Event.ObjectInstanceIndex = 0;
Event.StartPos = StartPos;
Event.EndPos = EndPos;
Event.Level = Level;
Event.BunchInfo = BunchInfo;
}
void FNetTraceAnalyzer::FlushPacketEvents(FNetTraceConnectionState& ConnectionState, FNetProfilerConnectionData& ConnectionData, const ENetProfilerConnectionMode ConnectionMode)
{
TPagedArray<FNetProfilerContentEvent>& Events = ConnectionData.ContentEvents;
TArray<FNetProfilerContentEvent>& BunchEvents = ConnectionState.BunchEvents[ConnectionMode];
int32 CurrentBunchEventIndex = 0;
// Track bunch offsets
uint32 NextBunchOffset = 0U;
int32 NonBunchEventCount = ConnectionState.BunchInfos[ConnectionMode].Num() ? ConnectionState.BunchInfos[ConnectionMode][0].FirstBunchEventIndex : BunchEvents.Num();
// Inject any events reported before the first bunch
while (CurrentBunchEventIndex < NonBunchEventCount)
{
const FNetProfilerContentEvent& BunchEvent = BunchEvents[CurrentBunchEventIndex];
AddEvent(Events, BunchEvent, 0U, 0U);
NextBunchOffset = FMath::Max(static_cast<uint32>(BunchEvent.EndPos), NextBunchOffset);
++CurrentBunchEventIndex;
++ConnectionData.ContentEventChangeCount;
}
uint32 EventsToAdd = 0U;
for (const FBunchInfo& Bunch : ConnectionState.BunchInfos[ConnectionMode])
{
uint32 BunchOffset = NextBunchOffset + Bunch.HeaderBits;
EventsToAdd += Bunch.EventCount;
// Report events for committed bunches
if (Bunch.HeaderBits)
{
// Bunch event
AddEvent(Events, NextBunchOffset, NextBunchOffset + Bunch.HeaderBits + Bunch.BunchBits, 0, Bunch.NameIndex, Bunch.BunchInfo);
// Bunch header event
AddEvent(Events, NextBunchOffset, NextBunchOffset + Bunch.HeaderBits, 1, BunchHeaderNameIndex, FNetProfilerBunchInfo::MakeBunchInfo(0));
// Add events belonging to bunch, including the ones from merged bunches
for (uint32 EventIt = 0; EventIt < EventsToAdd; ++EventIt)
{
const FNetProfilerContentEvent& BunchEvent = BunchEvents[CurrentBunchEventIndex];
AddEvent(Events, BunchEvent, BunchOffset, 1U);
++CurrentBunchEventIndex;
}
// Accumulate offset
NextBunchOffset += Bunch.BunchBits + Bunch.HeaderBits;
// Reset event count
EventsToAdd = 0U;
}
++ConnectionData.ContentEventChangeCount;
}
ConnectionState.BunchEvents[ConnectionMode].Reset();
ConnectionState.BunchInfos[ConnectionMode].Reset();
}
void FNetTraceAnalyzer::HandlePacketEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint64 TimestampCycles = EventData.GetValue<uint64>("Timestamp");
const uint32 PacketBits = EventData.GetValue<uint32>("PacketBits");
const uint32 SequenceNumber = EventData.GetValue<uint32>("SequenceNumber");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
const uint8 PacketType = EventData.GetValue<uint8>("PacketType");
const ENetProfilerConnectionMode ConnectionMode = ENetProfilerConnectionMode(PacketType);
// Update LastTimestamp, later on we will be able to get timestamps piggybacked from other analyzers
LastTimeStamp = Context.EventTime.AsSeconds(TimestampCycles);
// Get the NetProfilerFrameIndex for the current engine frame/timestamp
const uint32 NetProfilerFrameIndex = GetCurrentNetProfilerFrameIndexAndFlushFrameStatsCountersIfNeeded(
GameInstanceId,
FrameProvider.GetFrameNumberForTimestamp(ETraceFrameType::TraceFrameType_Game, LastTimeStamp)
);
FNetTraceConnectionState* ConnectionState = GetActiveConnectionState(GameInstanceId, ConnectionId);
if (!ConnectionState)
{
return;
}
// Add the packet
FNetProfilerConnectionData& ConnectionData = NetProfilerProvider.EditConnectionData(ConnectionState->ConnectionIndex, ConnectionMode);
if (ConnectionMode == ENetProfilerConnectionMode::Incoming)
{
if (ConnectionData.Packets.Num() > 0)
{
uint32 ExpectedSequenceNumber = ConnectionData.Packets.Last().SequenceNumber + 1U;
while (ExpectedSequenceNumber < SequenceNumber)
{
// Inject packets to visualize missing packets
FNetProfilerPacket& Packet = ConnectionData.Packets.PushBack();
Packet.SequenceNumber = ExpectedSequenceNumber;
// Fake it
Packet.StartEventIndex = ConnectionState->CurrentPacketStartIndex[ConnectionMode];
Packet.EventCount = 0;
Packet.TimeStamp = GetLastTimestamp();
Packet.DeliveryStatus = ENetProfilerDeliveryStatus::Dropped;
Packet.ConnectionState = ConnectionState->ConnectionState;
Packet.ContentSizeInBits = 0;
Packet.TotalPacketSizeInBytes = (Packet.ContentSizeInBits + 7u) >> 3u;
++ExpectedSequenceNumber;
++ConnectionData.PacketChangeCount;
}
}
}
FNetProfilerPacket& Packet = ConnectionData.Packets.PushBack();
++ConnectionData.PacketChangeCount;
// Flush packet events
FlushPacketEvents(*ConnectionState, ConnectionData, ConnectionMode);
Packet.NetProfilerFrameIndex = NetProfilerFrameIndex;
// Fill in packet data a packet must have at least 1 event?
Packet.StartEventIndex = ConnectionState->CurrentPacketStartIndex[ConnectionMode];
Packet.EventCount = static_cast<uint32>(ConnectionData.ContentEvents.Num()) - Packet.StartEventIndex;
Packet.TimeStamp = GetLastTimestamp();
Packet.SequenceNumber = SequenceNumber;
Packet.DeliveryStatus = ENetProfilerDeliveryStatus::Unknown;
Packet.ConnectionState = ConnectionState->ConnectionState;
Packet.ContentSizeInBits = PacketBits;
Packet.TotalPacketSizeInBytes = (Packet.ContentSizeInBits + 7u) >> 3u;
Packet.DeliveryStatus = ENetProfilerDeliveryStatus::Delivered;
// Flush PacketStats
Packet.StartStatsIndex = static_cast<uint32>(ConnectionData.PacketStats.Num());
Packet.StatsCount = ConnectionState->PacketStats.Num();
for (const FNetProfilerStats& Stat : ConnectionState->PacketStats)
{
ConnectionData.PacketStats.EmplaceBack(Stat);
}
ConnectionState->PacketStats.Reset();
++ConnectionData.PacketStatsChangeCount;
// Mark the beginning of a new packet
ConnectionState->CurrentPacketStartIndex[ConnectionMode] = static_cast<uint32>(ConnectionData.ContentEvents.Num());
ConnectionState->CurrentPacketBitOffset[ConnectionMode] = 0U;
ConnectionState->CurrentPacketStatsStartIndex[ConnectionMode] = static_cast<uint32>(ConnectionData.PacketStats.Num());
//UE_LOG(LogNetTrace, Log, TEXT("PacketEvent GameInstanceId: %u, ConnectionId: %u, %s, Seq: %u PacketBits: %u"), (uint32)GameInstanceId, (uint32)ConnectionId, ConnectionMode ? TEXT("Incoming") : TEXT("Outgoing"), SequenceNumber, Packet.ContentSizeInBits);
}
void FNetTraceAnalyzer::FlushFrameStatsCounters(FNetTraceAnalyzer::FNetTraceGameInstanceState& GameInstanceState)
{
if (FNetProfilerGameInstanceInternal* GameInstance = NetProfilerProvider.EditGameInstance(GameInstanceState.GameInstanceIndex))
{
const bool bIsNewFrame = (GameInstance->Frames->Num() == 0) || (GameInstance->Frames->Last().EngineFrameNumber != GameInstanceState.CurrentEngineFrameIndex);
if (bIsNewFrame)
{
FNetProfilerFrame& Frame = GameInstance->Frames->EmplaceBack();
Frame.EngineFrameNumber = GameInstanceState.CurrentEngineFrameIndex;
Frame.StartStatsIndex = static_cast<uint32>(GameInstance->FrameStats->Num());
Frame.StatsCount = GameInstanceState.FrameStatsCounters.Num();
Frame.TimeStamp = LastTimeStamp;
}
else
{
FNetProfilerFrame& Frame = GameInstance->Frames->Last();
Frame.StatsCount += GameInstanceState.FrameStatsCounters.Num();
}
for (const FNetProfilerStats& Stat : GameInstanceState.FrameStatsCounters)
{
GameInstance->FrameStats->EmplaceBack(Stat);
}
GameInstanceState.FrameStatsCounters.Reset();
GameInstanceState.CurrentNetProfilerFrameIndex = static_cast<uint32>(GameInstance->Frames->Num());
// Mark frames dirty
++GameInstance->FramesChangeCount;
}
}
uint32 FNetTraceAnalyzer::GetCurrentNetProfilerFrameIndexAndFlushFrameStatsCountersIfNeeded(uint32 GameInstanceId, uint32 EngineFrameIndex)
{
TSharedRef<FNetTraceAnalyzer::FNetTraceGameInstanceState> GameInstanceStateRef = GetOrCreateActiveGameInstanceState(GameInstanceId);
FNetTraceAnalyzer::FNetTraceGameInstanceState& GameInstanceState = GameInstanceStateRef.Get();
if (EngineFrameIndex > GameInstanceState.CurrentEngineFrameIndex)
{
FlushFrameStatsCounters(GameInstanceState);
GameInstanceState.CurrentEngineFrameIndex = EngineFrameIndex;
}
return GameInstanceState.CurrentNetProfilerFrameIndex;
}
void FNetTraceAnalyzer::HandlePacketDroppedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint64 TimestampCycles = EventData.GetValue<uint64>("Timestamp");
const uint32 SequenceNumber = EventData.GetValue<uint32>("SequenceNumber");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
const uint8 PacketType = EventData.GetValue<uint8>("PacketType");
// Update LastTimestamp, later on we will be able to get timestamps piggybacked from other analyzers
LastTimeStamp = Context.EventTime.AsSeconds(TimestampCycles);
FNetTraceConnectionState* ConnectionState = GetActiveConnectionState(GameInstanceId, ConnectionId);
if (!ConnectionState)
{
return;
}
FNetProfilerConnectionData& ConnectionData = NetProfilerProvider.EditConnectionData(ConnectionState->ConnectionIndex, ENetProfilerConnectionMode(PacketType));
// Update packet delivery status
NetProfilerProvider.EditPacketDeliveryStatus(ConnectionState->ConnectionIndex, ENetProfilerConnectionMode(PacketType), SequenceNumber, ENetProfilerDeliveryStatus::Dropped);
}
void FNetTraceAnalyzer::HandleConnectionCreatedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
ensureAlwaysMsgf(!GameInstanceState->ActiveConnections.Contains(ConnectionId), TEXT("Got ConnectionCreatedEvent for already existing connection GameInstanceId: %u ConnectionId: %u"), GameInstanceId, ConnectionId);
// Add to both active connections and to persistent connections
FNetProfilerConnectionInternal& Connection = NetProfilerProvider.CreateConnection(GameInstanceState->GameInstanceIndex);
TSharedRef<FNetTraceConnectionState> ConnectionState = MakeShared<FNetTraceConnectionState>();
GameInstanceState->ActiveConnections.Add(ConnectionId, ConnectionState);
// Fill in Connection data
Connection.Connection.ConnectionId = ConnectionId;
Connection.Connection.LifeTime.Begin = GetLastTimestamp();
ConnectionState->ConnectionIndex = Connection.Connection.ConnectionIndex;
ConnectionState->CurrentPacketStartIndex[ENetProfilerConnectionMode::Outgoing] = 0U;
ConnectionState->CurrentPacketStartIndex[ENetProfilerConnectionMode::Incoming] = 0U;
ConnectionState->CurrentPacketBitOffset[ENetProfilerConnectionMode::Outgoing] = 0U;
ConnectionState->CurrentPacketBitOffset[ENetProfilerConnectionMode::Incoming] = 0U;
ConnectionState->CurrentPacketStatsStartIndex[ENetProfilerConnectionMode::Outgoing] = 0U;
ConnectionState->CurrentPacketStatsStartIndex[ENetProfilerConnectionMode::Incoming] = 0U;
}
uint32 FNetTraceAnalyzer::GetOrCreateNetProfilerStatsCounterTypeIndex(FNetProfilerNameIndexType NameId, ENetProfilerStatsCounterType StatsType)
{
if (const uint32* ExistingNetProfilerStatsCounterTypeIndex = TraceNetStatsCounterIdToNetProfilerStatsCounterTypeIndexMap.Find(NameId))
{
return *ExistingNetProfilerStatsCounterTypeIndex;
}
else
{
// Add new counter type
const FNetProfilerNameIndexType* NetProfilerNameIndex = TracedNameIdToNetProfilerNameIdMap.Find(NameId);
const FNetProfilerNameIndexType NameIndex = NetProfilerNameIndex ? *NetProfilerNameIndex : 0u;
uint32 NetProfilerStatsCounterTypeIndex = NetProfilerProvider.AddNetProfilerStatsCounterType(NameIndex, StatsType);
TraceNetStatsCounterIdToNetProfilerStatsCounterTypeIndexMap.Add(NameId, NetProfilerStatsCounterTypeIndex);
return NetProfilerStatsCounterTypeIndex;
}
}
void FNetTraceAnalyzer::HandlePacketStatsCounterEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint32 StatsCounterValue = EventData.GetValue<uint32>("StatsValue");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
const FNetProfilerNameIndexType NameId = EventData.GetValue<FNetProfilerNameIndexType>("NameId");
FNetProfilerStats Stats;
Stats.StatsCounterTypeIndex = GetOrCreateNetProfilerStatsCounterTypeIndex(NameId, ENetProfilerStatsCounterType::Packet);
Stats.StatsValue = StatsCounterValue;
// Accumulate stats for current packet
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
if (TSharedRef<FNetTraceConnectionState>* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId))
{
(*ConnectionState)->PacketStats.Emplace(Stats);
}
}
void FNetTraceAnalyzer::HandleFrameStatsCounterEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint64 TimestampCycles = EventData.GetValue<uint64>("Timestamp");
const uint32 StatsCounterValue = EventData.GetValue<uint32>("StatsValue");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const FNetProfilerNameIndexType NameId = EventData.GetValue<FNetProfilerNameIndexType>("NameId");
// Update LastTimestamp, later on we will be able to get timestamps piggybacked from other analyzers
LastTimeStamp = Context.EventTime.AsSeconds(TimestampCycles);
// Get the NetProfilerFrameIndex for the current engine frame/timestamp
const uint32 NetProfilerFrameIndex = GetCurrentNetProfilerFrameIndexAndFlushFrameStatsCountersIfNeeded(
GameInstanceId,
FrameProvider.GetFrameNumberForTimestamp(ETraceFrameType::TraceFrameType_Game, LastTimeStamp)
);
FNetProfilerStats Stats;
Stats.StatsCounterTypeIndex = GetOrCreateNetProfilerStatsCounterTypeIndex(NameId, ENetProfilerStatsCounterType::Frame);
Stats.StatsValue = StatsCounterValue;
// Accumulate stats for current frame
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
GameInstanceState->FrameStatsCounters.Emplace(Stats);
}
void FNetTraceAnalyzer::HandleConnectionStateUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
const uint8 ConnectionStateValue = EventData.GetValue<uint8>("ConnectionStateValue");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
if (TSharedRef<FNetTraceConnectionState>* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId))
{
(*ConnectionState)->ConnectionState = ENetProfilerConnectionState(ConnectionStateValue);
}
}
void FNetTraceAnalyzer::HandleConnectionUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
FString Name;
EventData.GetString("Name", Name);
FString AddressString;
EventData.GetString("Address", AddressString);
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
if (TSharedRef<FNetTraceConnectionState>* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId))
{
if (FNetProfilerConnectionInternal* Connection = NetProfilerProvider.EditConnection((*ConnectionState)->ConnectionIndex))
{
Connection->Connection.Name = Session.StoreString(Name);
Connection->Connection.AddressString = Session.StoreString(AddressString);
}
}
else
{
// Incomplete trace? Ignore?
UE_LOG(LogNetTrace, Warning, TEXT("Connection %d is missing"), ConnectionId);
}
}
void FNetTraceAnalyzer::HandleConnectionClosedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint16 ConnectionId = EventData.GetValue<uint16>("ConnectionId");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
if (TSharedRef<FNetTraceConnectionState>* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId))
{
if (FNetProfilerConnectionInternal* Connection = NetProfilerProvider.EditConnection((*ConnectionState)->ConnectionIndex))
{
// Update connection state
Connection->Connection.LifeTime.End = GetLastTimestamp();
}
GameInstanceState->ActiveConnections.Remove(ConnectionId);
}
else
{
// Incomplete trace? Ignore?
UE_LOG(LogNetTrace, Warning, TEXT("Connection %d is missing"), ConnectionId);
}
}
void FNetTraceAnalyzer::HandleObjectCreatedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint64 TypeId = EventData.GetValue<uint64>("TypeId");
const uint64 ObjectId = EventData.GetValue<uint64>("ObjectId");
//const uint32 OwnerId = EventData.GetValue<uint32>("OwnerId");
const FNetProfilerNameIndexType NameId = EventData.GetValue<FNetProfilerNameIndexType>("NameId");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
const FNetProfilerNameIndexType* NetProfilerNameIndex = TracedNameIdToNetProfilerNameIdMap.Find(NameId);
const FNetProfilerNameIndexType NameIndex = NetProfilerNameIndex ? *NetProfilerNameIndex : 0u;
if (FNetTraceActiveObjectState* ActiveObjectInstance = GameInstanceState->ActiveObjects.Find(ObjectId))
{
if (FNetProfilerObjectInstance* ExistingInstance = NetProfilerProvider.EditObject(GameInstanceState->GameInstanceIndex, ActiveObjectInstance->ObjectIndex))
{
if (ExistingInstance->NameIndex == NameIndex || ExistingInstance->NameIndex == PendingNameIndex)
{
// Update existing object instance
ExistingInstance->LifeTime.Begin = GetLastTimestamp();
ExistingInstance->NetObjectId = ObjectId;
ExistingInstance->TypeId = TypeId;
// Update name in both the persistent instance and the active one
ExistingInstance->NameIndex = NameIndex;
ActiveObjectInstance->NameIndex = NameIndex;
return;
}
// End instance and remove it from ActiveObjects
ExistingInstance->LifeTime.End = GetLastTimestamp();
GameInstanceState->ActiveObjects.Remove(ObjectId);
}
}
// Add persistent object representation
FNetProfilerObjectInstance& ObjectInstance = NetProfilerProvider.CreateObject(GameInstanceState->GameInstanceIndex);
// Fill in object data
ObjectInstance.LifeTime.Begin = GetLastTimestamp();
ObjectInstance.NameIndex = NameIndex;
ObjectInstance.NetObjectId = ObjectId;
ObjectInstance.TypeId = TypeId;
// Add to active objects
GameInstanceState->ActiveObjects.Add(ObjectId, { ObjectInstance.ObjectIndex, ObjectInstance.NameIndex });
}
void FNetTraceAnalyzer::HandleObjectExistsEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint64 TypeId = EventData.GetValue<uint64>("TypeId");
const uint64 ObjectId = EventData.GetValue<uint64>("ObjectId");
const FNetProfilerNameIndexType NameId = EventData.GetValue<FNetProfilerNameIndexType>("NameId");
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
const FNetProfilerNameIndexType* NetProfilerNameIndex = TracedNameIdToNetProfilerNameIdMap.Find(NameId);
const FNetProfilerNameIndexType NameIndex = NetProfilerNameIndex ? *NetProfilerNameIndex : 0u;
if (FNetTraceActiveObjectState* ActiveObjectInstance = GameInstanceState->ActiveObjects.Find(ObjectId))
{
if (FNetProfilerObjectInstance* ExistingInstance = NetProfilerProvider.EditObject(GameInstanceState->GameInstanceIndex, ActiveObjectInstance->ObjectIndex))
{
if (ExistingInstance->NameIndex == NameIndex || ExistingInstance->NameIndex == PendingNameIndex)
{
// Update existing object instance
ExistingInstance->LifeTime.Begin = StartTimeStamp;
ExistingInstance->NetObjectId = ObjectId;
ExistingInstance->TypeId = TypeId;
// Update name in both the persistent instance and the active one
ExistingInstance->NameIndex = NameIndex;
ActiveObjectInstance->NameIndex = NameIndex;
return;
}
// End instance and remove it from ActiveObjects
ExistingInstance->LifeTime.End = GetLastTimestamp();
GameInstanceState->ActiveObjects.Remove(ObjectId);
}
}
// Add persistent object representation
FNetProfilerObjectInstance& ObjectInstance = NetProfilerProvider.CreateObject(GameInstanceState->GameInstanceIndex);
// Fill in object data
ObjectInstance.LifeTime.Begin = StartTimeStamp;
ObjectInstance.NameIndex = NameIndex;
ObjectInstance.NetObjectId = ObjectId;
ObjectInstance.TypeId = TypeId;
// Add to active objects
GameInstanceState->ActiveObjects.Add(ObjectId, { ObjectInstance.ObjectIndex, ObjectInstance.NameIndex });
}
void FNetTraceAnalyzer::HandleObjectDestroyedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
// Remove from active instances and mark the end timestamp in the persistent instance list
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const uint64 ObjectId = EventData.GetValue<uint64>("ObjectId");
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
FNetTraceActiveObjectState DestroyedObjectState;
if (GameInstanceState->ActiveObjects.RemoveAndCopyValue(ObjectId, DestroyedObjectState))
{
if (FNetProfilerObjectInstance* ObjectInstance = NetProfilerProvider.EditObject(GameInstanceState->GameInstanceIndex, DestroyedObjectState.ObjectIndex))
{
// Update object data
ObjectInstance->LifeTime.End = GetLastTimestamp();
}
}
}
TSharedRef<FNetTraceAnalyzer::FNetTraceGameInstanceState> FNetTraceAnalyzer::GetOrCreateActiveGameInstanceState(uint32 GameInstanceId)
{
if (TSharedRef<FNetTraceAnalyzer::FNetTraceGameInstanceState>* FoundState = ActiveGameInstances.Find(GameInstanceId))
{
return *FoundState;
}
else
{
// Persistent GameInstance
FNetProfilerGameInstanceInternal& GameInstance = NetProfilerProvider.CreateGameInstance();
GameInstance.Instance.GameInstanceId = GameInstanceId;
GameInstance.Instance.LifeTime.Begin = GetLastTimestamp();
// Active GameInstanceState
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = MakeShared<FNetTraceGameInstanceState>();
ActiveGameInstances.Add(GameInstanceId, GameInstanceState);
GameInstanceState->GameInstanceIndex = GameInstance.Instance.GameInstanceIndex;
const uint32 FrameCount = static_cast<uint32>(FrameProvider.GetFrameCount(TraceFrameType_Game));
GameInstanceState->CurrentEngineFrameIndex = (FrameCount > 0) ? FrameCount - 1 : 0;
return GameInstanceState;
}
}
void FNetTraceAnalyzer::DestroyActiveGameInstanceState(uint32 GameInstanceId)
{
if (TSharedRef<FNetTraceAnalyzer::FNetTraceGameInstanceState>* FoundState = ActiveGameInstances.Find(GameInstanceId))
{
// Mark as closed
if (FNetProfilerGameInstanceInternal* GameInstance = NetProfilerProvider.EditGameInstance((*FoundState)->GameInstanceIndex))
{
GameInstance->Instance.LifeTime.End = GetLastTimestamp();
NetProfilerProvider.MarkGameInstancesDirty();
}
ActiveGameInstances.Remove(GameInstanceId);
}
}
FNetTraceAnalyzer::FNetTraceConnectionState* FNetTraceAnalyzer::GetActiveConnectionState(uint32 GameInstanceId, uint32 ConnectionId)
{
if (TSharedRef<FNetTraceAnalyzer::FNetTraceGameInstanceState>* FoundState = ActiveGameInstances.Find(GameInstanceId))
{
if (TSharedRef<FNetTraceConnectionState>* ConnectionState = (*FoundState)->ActiveConnections.Find(ConnectionId))
{
return &(*ConnectionState).Get();
}
}
return nullptr;
}
void FNetTraceAnalyzer::HandleGameInstanceUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData)
{
const uint8 GameInstanceId = EventData.GetValue<uint8>("GameInstanceId");
const bool bIsServer = EventData.GetValue<bool>("bIsServer");
FString InstanceName;
EventData.GetString("Name", InstanceName);
TSharedRef<FNetTraceGameInstanceState> GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId);
if (FNetProfilerGameInstanceInternal* InternalGameInstance = NetProfilerProvider.EditGameInstance(GameInstanceState->GameInstanceIndex))
{
InternalGameInstance->Instance.bIsServer = bIsServer;
InternalGameInstance->Instance.InstanceName = Session.StoreString(InstanceName);
NetProfilerProvider.MarkGameInstancesDirty();
}
}
} // namespace TraceServices