// Copyright Epic Games, Inc. All Rights Reserved. #include "NetProfilerProvider.h" #include "AnalysisServicePrivate.h" #include "Containers/ArrayView.h" #include "Common/StringStore.h" namespace TraceServices { const TCHAR* LexToString(const ENetProfilerChannelCloseReason Value) { switch (Value) { case ENetProfilerChannelCloseReason::Destroyed: return TEXT("Destroyed"); case ENetProfilerChannelCloseReason::Dormancy: return TEXT("Dormancy"); case ENetProfilerChannelCloseReason::LevelUnloaded: return TEXT("LevelUnloaded"); case ENetProfilerChannelCloseReason::Relevancy: return TEXT("Relevancy"); case ENetProfilerChannelCloseReason::TearOff: return TEXT("TearOff"); } return TEXT("Unknown"); } const TCHAR* LexToString(const ENetProfilerConnectionState Value) { switch (Value) { case ENetProfilerConnectionState::USOCK_Closed: return TEXT("Closed"); case ENetProfilerConnectionState::USOCK_Open: return TEXT("Open"); case ENetProfilerConnectionState::USOCK_Pending: return TEXT("Pending"); case ENetProfilerConnectionState::USOCK_Invalid: default: return TEXT("Invalid"); } } FNetProfilerProvider::FNetProfilerProvider(IAnalysisSession& InSession) : Session(InSession) , Connections(InSession.GetLinearAllocator(), 4096) , NetTraceVersion(0U) , ConnectionChangeCount(0u) , GameInstanceChangeCount(0u) { // Use name index 0 to indicate that we do not know the name AddNetProfilerName(TEXT("N/A")); AggregatedStatsTableLayout. AddColumn(&FNetProfilerAggregatedStats::EventTypeIndex, TEXT("EventTypeIndex")). AddColumn(&FNetProfilerAggregatedStats::InstanceCount, TEXT("Count")). AddColumn(&FNetProfilerAggregatedStats::TotalInclusive, TEXT("Incl")). AddColumn(&FNetProfilerAggregatedStats::MaxInclusive, TEXT("I.Max")). AddColumn(&FNetProfilerAggregatedStats::AverageInclusive, TEXT("I.Avg")). AddColumn(&FNetProfilerAggregatedStats::TotalExclusive, TEXT("Excl")). AddColumn(&FNetProfilerAggregatedStats::MaxExclusive, TEXT("E.Max")); } FNetProfilerProvider::~FNetProfilerProvider() { } void FNetProfilerProvider::SetNetTraceVersion(uint32 Version) { Session.WriteAccessCheck(); NetTraceVersion = Version; } uint32 FNetProfilerProvider::GetNetTraceVersion() const { return NetTraceVersion; } FNetProfilerNameIndexType FNetProfilerProvider::AddNetProfilerName(const TCHAR* Name) { Session.WriteAccessCheck(); FNetProfilerName& NewName = Names.AddDefaulted_GetRef(); NewName.NameIndex = Names.Num() - 1; NewName.Name = Session.StoreString(Name); return NewName.NameIndex; } const FNetProfilerName* FNetProfilerProvider::GetNetProfilerName(FNetProfilerNameIndexType NameIndex) const { return NameIndex < (uint32)Names.Num() ? &Names[NameIndex] : nullptr; } uint32 FNetProfilerProvider::AddNetProfilerEventType(FNetProfilerNameIndexType NameIndex, uint32 Level) { Session.WriteAccessCheck(); FNetProfilerEventType& NewEventType = EventTypes.AddDefaulted_GetRef(); NewEventType.EventTypeIndex = EventTypes.Num() - 1; NewEventType.NameIndex = NameIndex; NewEventType.Name = GetNetProfilerName(NameIndex)->Name; NewEventType.Level = IntCastChecked(Level); return NewEventType.EventTypeIndex; } const FNetProfilerEventType* FNetProfilerProvider::GetNetProfilerEventType(uint32 EventTypeIndex) const { return EventTypeIndex < (uint32)EventTypes.Num() ? &EventTypes[EventTypeIndex] : nullptr; } uint32 FNetProfilerProvider::AddNetProfilerStatsCounterType(FNetProfilerNameIndexType NameIndex, ENetProfilerStatsCounterType Type) { Session.WriteAccessCheck(); FNetProfilerStatsCounterType& NewStatsCounterType = StatsCounterTypes.AddDefaulted_GetRef(); NewStatsCounterType.StatsCounterTypeIndex = StatsCounterTypes.Num() - 1; NewStatsCounterType.NameIndex = NameIndex; NewStatsCounterType.Type = Type; return NewStatsCounterType.StatsCounterTypeIndex; } const FNetProfilerStatsCounterType* FNetProfilerProvider::GetNetProfilerStatsCounterType(uint32 StatsCounterTypeIndex) const { return StatsCounterTypeIndex < (uint32)StatsCounterTypes.Num() ? &StatsCounterTypes[StatsCounterTypeIndex] : nullptr; } FNetProfilerGameInstanceInternal& FNetProfilerProvider::CreateGameInstance() { Session.WriteAccessCheck(); FNetProfilerGameInstanceInternal& GameInstance = GameInstances.AddDefaulted_GetRef(); GameInstance.Instance.GameInstanceIndex = GameInstances.Num() - 1; GameInstance.Objects = (TPagedArray*)Session.GetLinearAllocator().Allocate(sizeof(TPagedArray)); new (GameInstance.Objects) TPagedArray(Session.GetLinearAllocator(), 4096); GameInstance.ObjectsChangeCount = 0u; GameInstance.Frames = (TPagedArray*)Session.GetLinearAllocator().Allocate(sizeof(TPagedArray)); new (GameInstance.Frames) TPagedArray(Session.GetLinearAllocator(), 4096); GameInstance.FramesChangeCount = 0u; GameInstance.FrameStats = (TPagedArray*)Session.GetLinearAllocator().Allocate(sizeof(TPagedArray)); new (GameInstance.FrameStats) TPagedArray(Session.GetLinearAllocator(), 4096); // We reserve object index 0 as an invalid object CreateObject(GameInstance.Instance.GameInstanceIndex); MarkGameInstancesDirty(); return GameInstance; } FNetProfilerGameInstanceInternal* FNetProfilerProvider::EditGameInstance(uint32 GameInstanceIndex) { Session.WriteAccessCheck(); if (ensure(GameInstanceIndex < (uint32)GameInstances.Num())) { return &GameInstances[GameInstanceIndex]; } else { return nullptr; } } void FNetProfilerProvider::MarkGameInstancesDirty() { Session.WriteAccessCheck(); ++GameInstanceChangeCount; } FNetProfilerConnectionInternal& FNetProfilerProvider::CreateConnection(uint32 GameInstanceIndex) { Session.WriteAccessCheck(); FNetProfilerGameInstanceInternal* GameInstance = EditGameInstance(GameInstanceIndex); check(GameInstance); // Create new connection uint32 ConnectionIndex = static_cast(Connections.Num()); FNetProfilerConnectionInternal& Connection = Connections.PushBack(); Connection.Connection.ConnectionIndex = ConnectionIndex; Connection.Connection.GameInstanceIndex = GameInstanceIndex; Connection.Connection.bHasIncomingData = false; Connection.Connection.bHasOutgoingData = false; GameInstance->Connections.Push(ConnectionIndex); // Allocate storage for packets and events Connection.Data[ENetProfilerConnectionMode::Outgoing] = (FNetProfilerConnectionData*)Session.GetLinearAllocator().Allocate(sizeof(FNetProfilerConnectionData)); new (Connection.Data[ENetProfilerConnectionMode::Outgoing]) FNetProfilerConnectionData(Session.GetLinearAllocator()); Connection.Data[ENetProfilerConnectionMode::Incoming] = (FNetProfilerConnectionData*)Session.GetLinearAllocator().Allocate(sizeof(FNetProfilerConnectionData)); new (Connection.Data[ENetProfilerConnectionMode::Incoming]) FNetProfilerConnectionData(Session.GetLinearAllocator()); ++ConnectionChangeCount; return Connection; } FNetProfilerObjectInstance& FNetProfilerProvider::CreateObject(uint32 GameInstanceIndex) { Session.WriteAccessCheck(); FNetProfilerGameInstanceInternal* GameInstance = EditGameInstance(GameInstanceIndex); check(GameInstance); FNetProfilerObjectInstance& Object = GameInstance->Objects->PushBack(); Object.ObjectIndex = static_cast(GameInstance->Objects->Num()) - 1; ++GameInstance->ObjectsChangeCount; return Object; } FNetProfilerObjectInstance* FNetProfilerProvider::EditObject(uint32 GameInstanceIndex, uint32 ObjectIndex) { Session.WriteAccessCheck(); FNetProfilerGameInstanceInternal* GameInstance = EditGameInstance(GameInstanceIndex); check(GameInstance); if (ensure(ObjectIndex < (uint32)GameInstance->Objects->Num())) { ++GameInstance->ObjectsChangeCount; return &(*GameInstance->Objects)[ObjectIndex]; } else { return nullptr; } } FNetProfilerConnectionInternal* FNetProfilerProvider::EditConnection(uint32 ConnectionIndex) { Session.WriteAccessCheck(); if (ensure(ConnectionIndex < (uint32)Connections.Num())) { ++ConnectionChangeCount; return &Connections[ConnectionIndex]; } else { return nullptr; } } void FNetProfilerProvider::EditPacketDeliveryStatus(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 SequenceNumber, ENetProfilerDeliveryStatus DeliveryStatus) { check(ConnectionIndex < (uint32)Connections.Num()); FNetProfilerConnectionInternal& Connection = Connections[ConnectionIndex]; FNetProfilerConnectionData& Data = *Connections[ConnectionIndex].Data[Mode]; // try to locate packet uint32 PacketCount = static_cast(Data.Packets.Num()); for (uint32 It = 0; It < PacketCount; ++It) { const uint32 PacketIndex = PacketCount - It - 1u; if (Data.Packets[PacketIndex].SequenceNumber == SequenceNumber) { Data.Packets[PacketIndex].DeliveryStatus = DeliveryStatus; ++Data.PacketChangeCount; return; } } } FNetProfilerConnectionData& FNetProfilerProvider::EditConnectionData(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode) { check(ConnectionIndex < (uint32)Connections.Num()); FNetProfilerConnectionInternal& Connection = Connections[ConnectionIndex]; if (Mode == ENetProfilerConnectionMode::Incoming && !Connection.Connection.bHasIncomingData) { Connection.Connection.bHasIncomingData = true; ++ConnectionChangeCount; } if (Mode == ENetProfilerConnectionMode::Outgoing && !Connection.Connection.bHasOutgoingData) { Connection.Connection.bHasOutgoingData = true; ++ConnectionChangeCount; } return *Connections[ConnectionIndex].Data[Mode]; } void FNetProfilerProvider::ReadNames(TFunctionRef Callback) const { Session.ReadAccessCheck(); Callback(Names.GetData(), Names.Num()); } void FNetProfilerProvider::ReadName(uint32 NameIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(NameIndex < (uint32)Names.Num()); Callback(*GetNetProfilerName(NameIndex)); } void FNetProfilerProvider::ReadEventTypes(TFunctionRef Callback) const { Session.ReadAccessCheck(); Callback(EventTypes.GetData(), EventTypes.Num()); } void FNetProfilerProvider::ReadEventType(uint32 EventTypeIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(EventTypeIndex < (uint32)EventTypes.Num()); Callback(*GetNetProfilerEventType(EventTypeIndex)); } void FNetProfilerProvider::ReadNetStatsCounterTypes(TFunctionRef Callback) const { Session.ReadAccessCheck(); Callback(StatsCounterTypes.GetData(), StatsCounterTypes.Num()); } void FNetProfilerProvider::ReadNetStatsCounterType(uint32 TypeIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(TypeIndex < (uint32)StatsCounterTypes.Num()); Callback(*GetNetProfilerStatsCounterType(TypeIndex)); } void FNetProfilerProvider::ReadGameInstances(TFunctionRef Callback) const { Session.ReadAccessCheck(); for (const FNetProfilerGameInstanceInternal& Instance : GameInstances) { Callback(Instance.Instance); } } uint32 FNetProfilerProvider::GetConnectionCount(uint32 GameInstanceIndex) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; return GameInstance.Connections.Num(); } void FNetProfilerProvider::ReadConnections(uint32 GameInstanceIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; for (uint32 ConnectionIndex : MakeArrayView(GameInstance.Connections.GetData(), GameInstance.Connections.Num())) { Callback(Connections[ConnectionIndex].Connection); } } void FNetProfilerProvider::ReadConnection(uint32 ConnectionIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); Callback(Connections[ConnectionIndex].Connection); } uint32 FNetProfilerProvider::GetObjectCount(uint32 GameInstanceIndex) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; return static_cast(GameInstance.Objects->Num()); } void FNetProfilerProvider::ReadObjects(uint32 GameInstanceIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; const auto& Objects = *GameInstance.Objects; const uint32 ObjectsEndIt = static_cast(GameInstance.Objects->Num()); for (uint32 ObjectsIt = 0; ObjectsIt < ObjectsEndIt; ++ObjectsIt) { Callback(Objects[ObjectsIt]); } } void FNetProfilerProvider::ReadObject(uint32 GameInstanceIndex, uint32 ObjectIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; check(ObjectIndex < GameInstance.Objects->Num()); Callback((*GameInstance.Objects)[ObjectIndex]); } uint32 FNetProfilerProvider::GetObjectsChangeCount(uint32 GameInstanceIndex) const { Session.ReadAccessCheck(); check(GameInstanceIndex < (uint32)GameInstances.Num()); const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[GameInstanceIndex]; return GameInstance.ObjectsChangeCount; } int32 FNetProfilerProvider::FindPacketIndexFromPacketSequence(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 SequenceNumber) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); const auto& Packets = Connections[ConnectionIndex].Data[Mode]->Packets; const int32 PacketCount = (int32)Packets.Num(); if (PacketCount == 0) { return -1; } if (SequenceNumber < Packets[0].SequenceNumber) { return -1; } if (SequenceNumber > Packets[PacketCount - 1].SequenceNumber) { return -1; } // Brute force it, we can cache some data to speed this up if necessary for (int32 PacketIt = 0, PacketEndIt = PacketCount - 1; PacketIt <= PacketEndIt; ++PacketIt) { if (Packets[PacketIt].SequenceNumber == SequenceNumber) { return PacketIt; } } return -1; } uint32 FNetProfilerProvider::GetPacketCount(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); const auto& Packets = Connections[ConnectionIndex].Data[Mode]->Packets; return static_cast(Packets.Num()); } void FNetProfilerProvider::EnumeratePackets(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 PacketIndexIntervalStart, uint32 PacketIndexIntervalEnd, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); const auto& Packets = Connections[ConnectionIndex].Data[Mode]->Packets; const uint32 PacketCount = static_cast(Packets.Num()); // [PacketIndexIntervalStart, PacketIndexIntervalEnd] is an inclusive interval. if (PacketCount == 0 || PacketIndexIntervalStart > PacketIndexIntervalEnd) { return; } for (uint32 PacketIt = PacketIndexIntervalStart, PacketEndIt = FMath::Min(PacketIndexIntervalEnd, PacketCount - 1u); PacketIt <= PacketEndIt; ++PacketIt) { Callback(Packets[PacketIt]); } } // Enumerate packet content events by range void FNetProfilerProvider::EnumeratePacketContentEventsByIndex(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 StartEventIndex, uint32 EndEventIndex, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); const auto& ContentEvents = Connections[ConnectionIndex].Data[Mode]->ContentEvents; const uint32 EventCount = static_cast(ContentEvents.Num()); // [StartEventIndex, EndEventIndex] is an inclusive interval. if (EventCount == 0 || StartEventIndex > EndEventIndex) { return; } for (uint32 EventIt = StartEventIndex, EventEndIt = FMath::Min(EndEventIndex, EventCount - 1u); EventIt <= EventEndIt; ++EventIt) { Callback(ContentEvents[EventIt]); } } void FNetProfilerProvider::EnumeratePacketContentEventsByPosition(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 PacketIndex, uint32 StartPos, uint32 EndPos, TFunctionRef Callback) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); const FNetProfilerConnectionData& ConnectionData = * Connections[ConnectionIndex].Data[Mode]; const FNetProfilerPacket& Packet = ConnectionData.Packets[PacketIndex]; if (Packet.EventCount == 0u) { return; } const uint32 StartEventIndex = Packet.StartEventIndex; const uint32 EndEventIndex = StartEventIndex + Packet.EventCount - 1u; const auto& ContentEvents = ConnectionData.ContentEvents; // The input [StartPos, EndPos) is an exclusive bit range. // Also, the [StartPos, EndPos) for ContentEvents[EventIt] is an exclusive bit range. uint32 EventIt = StartEventIndex; // Skip all Events outside of the scope while (EventIt <= EndEventIndex && ContentEvents[EventIt].EndPos <= StartPos) { ++EventIt; } // Execute callback for all found events while (EventIt <= EndEventIndex && ContentEvents[EventIt].StartPos < EndPos) { Callback(ContentEvents[EventIt]); EndPos = FMath::Max(EndPos, (uint32)ContentEvents[EventIt].EndPos); ++EventIt; } } uint32 FNetProfilerProvider::GetPacketChangeCount(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); return Connections[ConnectionIndex].Data[Mode]->PacketChangeCount; } uint32 FNetProfilerProvider::GetPacketContentEventChangeCount(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode) const { Session.ReadAccessCheck(); check(ConnectionIndex < Connections.Num()); return Connections[ConnectionIndex].Data[Mode]->ContentEventChangeCount; } ITable* FNetProfilerProvider::CreateAggregation(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 PacketIndexIntervalStart, uint32 PacketIndexIntervalEnd, uint32 StartPosition, uint32 EndPosition) const { Session.ReadAccessCheck(); if (!ensure(ConnectionIndex < Connections.Num())) { return nullptr; } // [PacketIndexIntervalStart, PacketIndexIntervalEnd] is an inclusive interval. if (!ensure(PacketIndexIntervalStart <= PacketIndexIntervalEnd)) { return nullptr; } const auto& Packets = Connections[ConnectionIndex].Data[Mode]->Packets; const uint32 PacketCount = static_cast(Packets.Num()); if (!ensure(PacketCount > 0)) { return nullptr; } struct FStackEntry { uint32 EventTypeIndex = 0U; uint32 StartPos = 0U; uint32 EndPos = 0U; uint32 ExclusiveAccumulator = 0U; }; struct FStatsHelper { TMap AggregatedStats; uint64 StackSize; TArray Stack; }; FStatsHelper Helper; Helper.AggregatedStats.Reserve(EventTypes.Num()); Helper.StackSize = 0; // stack is empty Helper.Stack.SetNum(256); auto GetStatsFunction = [&Helper, this](const FNetProfilerContentEvent& ContentEvent) { FNetProfilerAggregatedStats* StatsEntry = Helper.AggregatedStats.Find(ContentEvent.EventTypeIndex); if (!StatsEntry) { StatsEntry = &Helper.AggregatedStats.Add(ContentEvent.EventTypeIndex); StatsEntry->EventTypeIndex = ContentEvent.EventTypeIndex; } // Fill in basics. const uint32 InclusiveSize = static_cast(ContentEvent.EndPos - ContentEvent.StartPos); ++StatsEntry->InstanceCount; StatsEntry->TotalInclusive += InclusiveSize; StatsEntry->MaxInclusive = FMath::Max(InclusiveSize, StatsEntry->MaxInclusive); // Pops events from the stack. Keeps only the parent hierarchy of the current event. while (Helper.StackSize > ContentEvent.Level) { --Helper.StackSize; FStackEntry& StackEntry = Helper.Stack[static_cast(Helper.StackSize)]; // Finalize exclusive for each poped event (all its children were already processed). FNetProfilerAggregatedStats& Stats = Helper.AggregatedStats.FindChecked(StackEntry.EventTypeIndex); const uint32 ExclusiveSize = (StackEntry.EndPos - StackEntry.StartPos) - StackEntry.ExclusiveAccumulator; Stats.TotalExclusive += ExclusiveSize; Stats.MaxExclusive = FMath::Max(Stats.MaxExclusive, ExclusiveSize); } // Pushes the new event on the stack. ensure(ContentEvent.Level == Helper.StackSize); Helper.StackSize = ContentEvent.Level + 1; // push FStackEntry& CurrentEventLevelStackEntry = Helper.Stack[ContentEvent.Level]; // We track what we have visited to be able to update exclusive bits for our parent. // We accumulate during the first pass and finalize the values during the second. CurrentEventLevelStackEntry.EventTypeIndex = ContentEvent.EventTypeIndex; CurrentEventLevelStackEntry.StartPos = ContentEvent.StartPos; CurrentEventLevelStackEntry.EndPos = ContentEvent.EndPos; CurrentEventLevelStackEntry.ExclusiveAccumulator = 0U; if (ContentEvent.Level > 0U) { // Update parent event with the contribution from current event. FStackEntry& ParentStackEntry = Helper.Stack[static_cast(ContentEvent.Level) - 1]; ParentStackEntry.ExclusiveAccumulator += InclusiveSize; } }; // Iterate over content events if (PacketIndexIntervalStart == PacketIndexIntervalEnd) { EnumeratePacketContentEventsByPosition(ConnectionIndex, Mode, PacketIndexIntervalStart, StartPosition, EndPosition, GetStatsFunction); // Pops the remaining events from the stack. while (Helper.StackSize > 0) { --Helper.StackSize; FStackEntry& StackEntry = Helper.Stack[static_cast(Helper.StackSize)]; // Finalize exclusive for each poped event (all its children were already processed). FNetProfilerAggregatedStats& Stats = Helper.AggregatedStats.FindChecked(StackEntry.EventTypeIndex); const uint32 ExclusiveSize = (StackEntry.EndPos - StackEntry.StartPos) - StackEntry.ExclusiveAccumulator; Stats.TotalExclusive += ExclusiveSize; Stats.MaxExclusive = FMath::Max(Stats.MaxExclusive, ExclusiveSize); } } else { // Iterate over packets for (uint32 PacketIt = PacketIndexIntervalStart, PacketEndIt = FMath::Min(PacketIndexIntervalEnd, PacketCount - 1u); PacketIt <= PacketEndIt; ++PacketIt) { const auto& ContentEvents = Connections[ConnectionIndex].Data[Mode]->ContentEvents; const uint32 EventCount = static_cast(ContentEvents.Num()); const FNetProfilerPacket& Packet = Packets[PacketIt]; if (Packet.EventCount > 0U) { ensure(Helper.StackSize == 0); // stack should be empty (before each packet) const uint32 StartEventIndex = Packet.StartEventIndex; for (uint32 It = 0U; It < Packet.EventCount; ++It) { GetStatsFunction(ContentEvents[StartEventIndex + It]); } // Pops the remaining events from the stack, for each packet. while (Helper.StackSize > 0) { --Helper.StackSize; FStackEntry& StackEntry = Helper.Stack[static_cast(Helper.StackSize)]; // Finalize exclusive for each poped event (all its children were already processed). FNetProfilerAggregatedStats& Stats = Helper.AggregatedStats.FindChecked(StackEntry.EventTypeIndex); const uint32 ExclusiveSize = (StackEntry.EndPos - StackEntry.StartPos) - StackEntry.ExclusiveAccumulator; Stats.TotalExclusive += ExclusiveSize; Stats.MaxExclusive = FMath::Max(Stats.MaxExclusive, ExclusiveSize); } } } } // Calculate averages and populate table TTable* Table = new TTable(AggregatedStatsTableLayout); for (const auto& KV : Helper.AggregatedStats) { FNetProfilerAggregatedStats& Row = Table->AddRow(); Row = KV.Value; // Finalize StatsEntry Row.AverageInclusive = (uint64)((double)KV.Value.TotalInclusive / KV.Value.InstanceCount); } return Table; } ITable* FNetProfilerProvider::CreateStatsCountersAggregation(uint32 ConnectionIndex, ENetProfilerConnectionMode Mode, uint32 PacketIndexIntervalStart, uint32 PacketIndexIntervalEnd) const { Session.ReadAccessCheck(); if (!ensure(ConnectionIndex < Connections.Num())) { return nullptr; } // [PacketIndexIntervalStart, PacketIndexIntervalEnd] is an inclusive interval. if (!ensure(PacketIndexIntervalStart <= PacketIndexIntervalEnd)) { return nullptr; } const FNetProfilerConnectionData* ConnectionData = Connections[ConnectionIndex].Data[Mode]; const auto& Packets = ConnectionData->Packets; const uint32 PacketCount = static_cast(Packets.Num()); if (!ensure(PacketCount > 0)) { return nullptr; } TMap AggregatedStatsMap; AggregatedStatsMap.Reserve(StatsCounterTypes.Num()); auto AccumulateStatsFunction = [&AggregatedStatsMap, this](const FNetProfilerStats& StatsCounter) { FNetProfilerAggregatedStatsCounterStats* StatsEntry = AggregatedStatsMap.Find(StatsCounter.StatsCounterTypeIndex); if (!StatsEntry) { StatsEntry = &AggregatedStatsMap.Add(StatsCounter.StatsCounterTypeIndex); StatsEntry->StatsCounterTypeIndex = StatsCounter.StatsCounterTypeIndex; } const uint32 StatsValue = StatsCounter.StatsValue; StatsEntry->Sum += StatsValue; StatsEntry->Max = FMath::Max(StatsValue, StatsEntry->Max); ++StatsEntry->Count; }; const FNetProfilerGameInstanceInternal& GameInstance = GameInstances[Connections[ConnectionIndex].Connection.GameInstanceIndex]; const auto& PacketStatsCounters = ConnectionData->PacketStats; const auto& FrameStatsCounters = *GameInstance.FrameStats; const auto& Frames = *GameInstance.Frames; // Iterate over packets for (uint32 PacketIt = PacketIndexIntervalStart, PacketEndIt = FMath::Min(PacketIndexIntervalEnd, PacketCount - 1u); PacketIt <= PacketEndIt; ++PacketIt) { const FNetProfilerPacket& Packet = Packets[PacketIt]; const uint32 StatsCounterCount = static_cast(PacketStatsCounters.Num()); // Iterate over all StatsCounters stored for the Packet for (uint32 StatsCounterIt = 0; StatsCounterIt < Packet.StatsCount; ++StatsCounterIt) { AccumulateStatsFunction(PacketStatsCounters[Packet.StartStatsIndex + StatsCounterIt]); } // Include frame stats as well const uint32 NetProfilerFrameIndex = Packet.NetProfilerFrameIndex; if (NetProfilerFrameIndex < Frames.Num()) { const FNetProfilerFrame& Frame = Frames[Packet.NetProfilerFrameIndex]; for (uint32 StatsCounterIt = 0; StatsCounterIt < Frame.StatsCount; ++StatsCounterIt) { AccumulateStatsFunction(FrameStatsCounters[Frame.StartStatsIndex + StatsCounterIt]); } } } // Calculate averages and populate table TTable* Table = new TTable(AggregatedStatsCounterStatsTableLayout); for (const auto& KV : AggregatedStatsMap) { FNetProfilerAggregatedStatsCounterStats& Row = Table->AddRow(); Row = KV.Value; // Finalize StatsEntry Row.Average = (uint64)((double)KV.Value.Sum / KV.Value.Count); } return Table; } FName GetNetProfilerProviderName() { static const FName Name("NetProfilerProvider"); return Name; } const INetProfilerProvider* ReadNetProfilerProvider(const IAnalysisSession& Session) { return Session.ReadProvider(GetNetProfilerProviderName()); } } // namespace TraceServices