// 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("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("NetTraceVersion"); NetTraceReporterVersion = EventData.GetValue("NetTraceReporterVersion"); NetProfilerProvider.SetNetTraceVersion(NetTraceVersion); } break; case RouteId_InstanceDestroyedEvent: { const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); DestroyActiveGameInstanceState(GameInstanceId); } break; case RouteId_NameEvent: { FNetProfilerNameIndexType TraceNameId = EventData.GetValue("NameId"); if (TracedNameIdToNetProfilerNameIdMap.Contains(TraceNameId)) { // need to update the name check(false); } else { FString Name = FTraceAnalyzerUtils::LegacyAttachmentString("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("ConnectionId"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint8 PacketType = EventData.GetValue("PacketType"); //UE_LOG(LogNetTrace, Display, TEXT("FNetTraceAnalyzer::HandlePacketContentEvent: GameInstanceId: %u, ConnectionId: %u, %s"), (uint32)GameInstanceId, (uint32)ConnectionId, PacketType ? TEXT("Incoming") : TEXT("Outgoing")); TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); FNetTraceConnectionState* ConnectionState = GetActiveConnectionState(GameInstanceId, ConnectionId); if (!ConnectionState) { return; } const ENetProfilerConnectionMode ConnectionMode = ENetProfilerConnectionMode(PacketType); TArray& Events = (ConnectionState->BunchEvents)[ConnectionMode]; TArray& BunchInfos = (ConnectionState->BunchInfos)[ConnectionMode]; // Decode batched events TArrayView 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(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(Event.Level)); } break; case EContentEventType::BunchEvent: { const FNetProfilerNameIndexType DecodedNameId = IntCastChecked(FTraceAnalyzerUtils::Decode7bit(BufferPtr)); uint32 DecodedBunchBits = 0U; if (NetTraceVersion >= ENetTraceAnalyzerVersion_FixedBunchSizeEncoding) { DecodedBunchBits = IntCastChecked(FTraceAnalyzerUtils::Decode7bit(BufferPtr)); } else { const uint64 DecodedEventStartPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr); const uint64 DecodedEventEndPos = FTraceAnalyzerUtils::Decode7bit(BufferPtr); DecodedBunchBits = IntCastChecked((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(FTraceAnalyzerUtils::Decode7bit(BufferPtr)); const uint32 DecodedHeaderBits = IntCastChecked(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& Events, const FNetProfilerContentEvent& InEvent, uint32 Offset, uint32 LevelOffset) { FNetProfilerContentEvent& Event = Events.PushBack(); Event.EventTypeIndex = GetTracedEventTypeIndex(InEvent.NameIndex, IntCastChecked(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& Events, uint32 StartPos, uint32 EndPos, uint32 Level, FNetProfilerNameIndexType NameIndex, FNetProfilerBunchInfo BunchInfo) { FNetProfilerContentEvent& Event = Events.PushBack(); Event.EventTypeIndex = GetTracedEventTypeIndex(NameIndex, IntCastChecked(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& Events = ConnectionData.ContentEvents; TArray& 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(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("Timestamp"); const uint32 PacketBits = EventData.GetValue("PacketBits"); const uint32 SequenceNumber = EventData.GetValue("SequenceNumber"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); const uint8 PacketType = EventData.GetValue("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(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(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(ConnectionData.ContentEvents.Num()); ConnectionState->CurrentPacketBitOffset[ConnectionMode] = 0U; ConnectionState->CurrentPacketStatsStartIndex[ConnectionMode] = static_cast(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(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(GameInstance->Frames->Num()); // Mark frames dirty ++GameInstance->FramesChangeCount; } } uint32 FNetTraceAnalyzer::GetCurrentNetProfilerFrameIndexAndFlushFrameStatsCountersIfNeeded(uint32 GameInstanceId, uint32 EngineFrameIndex) { TSharedRef 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("Timestamp"); const uint32 SequenceNumber = EventData.GetValue("SequenceNumber"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); const uint8 PacketType = EventData.GetValue("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("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); TSharedRef 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 ConnectionState = MakeShared(); 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("StatsValue"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); const FNetProfilerNameIndexType NameId = EventData.GetValue("NameId"); FNetProfilerStats Stats; Stats.StatsCounterTypeIndex = GetOrCreateNetProfilerStatsCounterTypeIndex(NameId, ENetProfilerStatsCounterType::Packet); Stats.StatsValue = StatsCounterValue; // Accumulate stats for current packet TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); if (TSharedRef* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId)) { (*ConnectionState)->PacketStats.Emplace(Stats); } } void FNetTraceAnalyzer::HandleFrameStatsCounterEvent(const FOnEventContext& Context, const FEventData& EventData) { const uint64 TimestampCycles = EventData.GetValue("Timestamp"); const uint32 StatsCounterValue = EventData.GetValue("StatsValue"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const FNetProfilerNameIndexType NameId = EventData.GetValue("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 GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); GameInstanceState->FrameStatsCounters.Emplace(Stats); } void FNetTraceAnalyzer::HandleConnectionStateUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData) { const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); const uint8 ConnectionStateValue = EventData.GetValue("ConnectionStateValue"); TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); if (TSharedRef* ConnectionState = GameInstanceState->ActiveConnections.Find(ConnectionId)) { (*ConnectionState)->ConnectionState = ENetProfilerConnectionState(ConnectionStateValue); } } void FNetTraceAnalyzer::HandleConnectionUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData) { const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); FString Name; EventData.GetString("Name", Name); FString AddressString; EventData.GetString("Address", AddressString); TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); if (TSharedRef* 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("GameInstanceId"); const uint16 ConnectionId = EventData.GetValue("ConnectionId"); TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); if (TSharedRef* 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("TypeId"); const uint64 ObjectId = EventData.GetValue("ObjectId"); //const uint32 OwnerId = EventData.GetValue("OwnerId"); const FNetProfilerNameIndexType NameId = EventData.GetValue("NameId"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); TSharedRef 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("TypeId"); const uint64 ObjectId = EventData.GetValue("ObjectId"); const FNetProfilerNameIndexType NameId = EventData.GetValue("NameId"); const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); TSharedRef 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("GameInstanceId"); const uint64 ObjectId = EventData.GetValue("ObjectId"); TSharedRef 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::GetOrCreateActiveGameInstanceState(uint32 GameInstanceId) { if (TSharedRef* 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 GameInstanceState = MakeShared(); ActiveGameInstances.Add(GameInstanceId, GameInstanceState); GameInstanceState->GameInstanceIndex = GameInstance.Instance.GameInstanceIndex; const uint32 FrameCount = static_cast(FrameProvider.GetFrameCount(TraceFrameType_Game)); GameInstanceState->CurrentEngineFrameIndex = (FrameCount > 0) ? FrameCount - 1 : 0; return GameInstanceState; } } void FNetTraceAnalyzer::DestroyActiveGameInstanceState(uint32 GameInstanceId) { if (TSharedRef* 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* FoundState = ActiveGameInstances.Find(GameInstanceId)) { if (TSharedRef* ConnectionState = (*FoundState)->ActiveConnections.Find(ConnectionId)) { return &(*ConnectionState).Get(); } } return nullptr; } void FNetTraceAnalyzer::HandleGameInstanceUpdatedEvent(const FOnEventContext& Context, const FEventData& EventData) { const uint8 GameInstanceId = EventData.GetValue("GameInstanceId"); const bool bIsServer = EventData.GetValue("bIsServer"); FString InstanceName; EventData.GetString("Name", InstanceName); TSharedRef GameInstanceState = GetOrCreateActiveGameInstanceState(GameInstanceId); if (FNetProfilerGameInstanceInternal* InternalGameInstance = NetProfilerProvider.EditGameInstance(GameInstanceState->GameInstanceIndex)) { InternalGameInstance->Instance.bIsServer = bIsServer; InternalGameInstance->Instance.InstanceName = Session.StoreString(InstanceName); NetProfilerProvider.MarkGameInstancesDirty(); } } } // namespace TraceServices