// Copyright Epic Games, Inc. All Rights Reserved. #include "Trace/Config.h" #if TRACE_PRIVATE_MINIMAL_ENABLED #include "Platform.h" #include "Trace/Detail/Atomic.h" #include "Trace/Detail/Protocol.h" #include "Trace/Detail/Transport.h" #include #include #include namespace UE { namespace Trace { namespace Private { // This here to aid future maintenance of a trace's transport packets. static_assert(ETransport::Active == ETransport::TidPacketSync, "Tail-tracing is transport aware"); //////////////////////////////////////////////////////////////////////////////// uint32 GetEncodeMaxSize(uint32); int32 Encode(const void*, int32, void*, int32); void* Writer_MemoryAllocate(SIZE_T, uint32); void Writer_MemoryFree(void*, uint32); void Writer_SendData(uint32, uint8* __restrict, uint32); void Writer_SendDataRaw(const void*, uint32); extern FStatistics GTraceStatistics; #if UE_TRACE_PACKET_VERIFICATION extern uint64 GPacketSerial; #endif //////////////////////////////////////////////////////////////////////////////// // ** See the bottom of this file for an explanation of FPacketRing class FPacketRing { public: struct FRange { const void* Data; uint32 Size; }; void Initialize(uint32 InSize); void Shutdown(); void Reset(); uint32 GetSize() const; bool IsActive() const; FRange GetBackPackets() const; FRange GetFrontPackets() const; template void IterateRanges(CallbackType&& Callback); template PacketType* Append(uint32 InSize); template void BackUp(uint32 InSize); void BackUp(uint32 InSize); private: FTidPacketBase* AppendImpl(uint32 InSize); uint8* Data; uint32 Size; uint32 Cursor; uint32 Left; uint32 Right; }; // TraceLog must be ready after value-init so that it can be used before // dynamic-init. Thus statically-scoped objects cannot have [con|des]tructors. static_assert(std::is_trivial(), "FPacketRing must be trivial"); //////////////////////////////////////////////////////////////////////////////// void FPacketRing::Initialize(uint32 InSize) { Data = (uint8*)Writer_MemoryAllocate(InSize, 16); #if TRACE_PRIVATE_STATISTICS AtomicAddRelaxed(>raceStatistics.FixedBufferAllocated, uint32(InSize)); #endif Size = InSize; Reset(); } //////////////////////////////////////////////////////////////////////////////// void FPacketRing::Shutdown() { Writer_MemoryFree(Data, Size); #if TRACE_PRIVATE_STATISTICS AtomicSubRelaxed(>raceStatistics.FixedBufferAllocated, uint32(Size)); #endif Data = nullptr; } //////////////////////////////////////////////////////////////////////////////// void FPacketRing::Reset() { Cursor = 0; Left = Right = Size; } //////////////////////////////////////////////////////////////////////////////// uint32 FPacketRing::GetSize() const { return Size; } //////////////////////////////////////////////////////////////////////////////// bool FPacketRing::IsActive() const { return Data != nullptr; } //////////////////////////////////////////////////////////////////////////////// FPacketRing::FRange FPacketRing::GetBackPackets() const { return { Data + Left, Right - Left }; } //////////////////////////////////////////////////////////////////////////////// FPacketRing::FRange FPacketRing::GetFrontPackets() const { return { Data, Cursor }; } //////////////////////////////////////////////////////////////////////////////// template void FPacketRing::IterateRanges(CallbackType&& Callback) { FPacketRing::FRange Ranges[] = { GetBackPackets(), GetFrontPackets() }; // Send out the ranges. for (const auto& Range : Ranges) { if (Range.Size == 0) { continue; } Callback(Range); } } //////////////////////////////////////////////////////////////////////////////// template PacketType* FPacketRing::Append(uint32 InSize) { FTidPacketBase* Ptr = AppendImpl(InSize + sizeof(PacketType)); return static_cast(Ptr); } //////////////////////////////////////////////////////////////////////////////// template void FPacketRing::BackUp(uint32 InSize) { Cursor -= (InSize + sizeof(PacketType)); } //////////////////////////////////////////////////////////////////////////////// void FPacketRing::BackUp(uint32 InSize) { Cursor -= InSize; } //////////////////////////////////////////////////////////////////////////////// FTidPacketBase* FPacketRing::AppendImpl(uint32 InSize) { // ** See the bottom of this file for an explanation of the logic here. // Too big to fit in the buffer? It is not possible to maintain consisntency // past this point as some of the data would be truncated. So we drop all // known data and start afresh. if (UNLIKELY(InSize > Size)) { Reset(); return nullptr; } uint32 NextCursor = Cursor + InSize; // Run off the end of the buffer? if (UNLIKELY(NextCursor > Size)) { Left = 0; Right = Cursor; Cursor = 0; NextCursor = InSize; } // Discard old packets until there is space for the new one while (true) { // Does [Cursor, NextCursor) no longer overlap [Left, Right)? if (LIKELY(Left >= NextCursor)) { break; } // Is [Left, Right) now empty? if (UNLIKELY(Left >= Right)) { break; } const auto* TidPacket = (const FTidPacketBase*)(Data + Left); Left += TidPacket->PacketSize; } // Drop a packet from left. auto* TidPacket = (FTidPacketBase*)(Data + Cursor); TidPacket->PacketSize = uint16(InSize); Cursor = NextCursor; return TidPacket; } //////////////////////////////////////////////////////////////////////////////// static FPacketRing GPacketRing; // = {}; //////////////////////////////////////////////////////////////////////////////// void Writer_TailAppend(uint32 ThreadId, uint8* __restrict Data, uint32 Size) { FProfilerScope _(__func__); // Perhaps tail tracing is disabled? if (!GPacketRing.IsActive()) { return Writer_SendData(ThreadId, Data, Size); } // If the packet is going to be too big (discounting compression ratio as // that's unknown) then we'll drop the history and this packet. if (uint32(Size + sizeof(FTidPacketEncoded)) > GPacketRing.GetSize()) { GPacketRing.Reset(); return Writer_SendData(ThreadId, Data, Size); } ThreadId &= FTidPacketBase::ThreadIdMask; // Smaller buffers usually aren't redundant enough to benefit from being // compressed. They often end up being larger. if (Size > 384) { uint32 EncodeMaxSize = GetEncodeMaxSize(Size); auto* Packet = GPacketRing.Append(EncodeMaxSize); Packet->ThreadId = uint16(ThreadId); Packet->ThreadId |= FTidPacketBase::EncodedMarker; #if UE_TRACE_PACKET_VERIFICATION Packet->ThreadId |= FTidPacketBase::Verification; #endif Packet->DecodedSize = uint16(Size); int32 EncodedSize = Encode(Data, Size, Packet->Data, EncodeMaxSize); if (EncodedSize > 0) { // Reclaim buffer space (worst case size - actual size) uint32 BackUp = EncodeMaxSize - EncodedSize; GPacketRing.BackUp(BackUp); Packet->PacketSize -= uint16(BackUp); Writer_SendDataRaw(Packet, Packet->PacketSize); #if UE_TRACE_PACKET_VERIFICATION const uint64 Serial = GPacketSerial++; Writer_SendDataRaw(&Serial, sizeof(uint64)); #endif return; } else { // Compression failed, back out of entire packet GPacketRing.BackUp(EncodeMaxSize); } } auto* Packet = GPacketRing.Append(Size); Packet->ThreadId = uint16(ThreadId); #if UE_TRACE_PACKET_VERIFICATION Packet->ThreadId |= FTidPacketBase::Verification; #endif ::memcpy(Packet->Data, Data, Size); Writer_SendDataRaw(Packet, Packet->PacketSize); #if UE_TRACE_PACKET_VERIFICATION const uint64 Serial = GPacketSerial++; Writer_SendDataRaw(&Serial, sizeof(uint64)); #endif } //////////////////////////////////////////////////////////////////////////////// void Writer_TailOnConnect() { FProfilerScope _(__func__); // If there's no tail being maintained then there is nothing to send. if (!GPacketRing.IsActive()) { return; } GPacketRing.IterateRanges([] (const FPacketRing::FRange& Range) { Writer_SendDataRaw(Range.Data, Range.Size); }); } //////////////////////////////////////////////////////////////////////////////// void Writer_InitializeTail(int32 BufferSize) { #if defined(STRESS_PACKET_RING) static void StressRingPacket(); StressRingPacket(); #endif if (BufferSize <= 0) { return; } // Round up to 1K and clamp the size. There has to be a sensible amount of // buffer size for tail tracing to work or be useful. uint32 Rounding = (1 << 10) - 1; BufferSize = (BufferSize + Rounding) & ~Rounding; if (BufferSize < (128 << 10)) { BufferSize = 128 << 10; } GPacketRing.Initialize(BufferSize); } //////////////////////////////////////////////////////////////////////////////// bool Writer_IsTailing() { return GPacketRing.IsActive(); } //////////////////////////////////////////////////////////////////////////////// void Writer_ShutdownTail() { GPacketRing.Shutdown(); } //////////////////////////////////////////////////////////////////////////////// #if defined(STRESS_PACKET_RING) static void StressRingPacket() { FPacketRing Ring; Ring.Initialize(300); uint32 Bits = 0x0493'0493; for (int32 i = 0; i < 1024; ++i) { FTidPacket* Packet = Ring.Append((Bits & 0x1f) + 6); Packet->ThreadId = i; Ring.IterateRanges([] (const FPacketRing::FRange&) { /* nop */ }); Bits = (Bits ^ 0xa93a'93a9) * 0x0493; } for (int32 i = 7; i < 448; i += 67) { if (auto* Packet = Ring.Append(i)) { Packet->ThreadId = 0; } } } #endif // STRESS_PACKET_RING } // namespace Private } // namespace Trace } // namespace UE #endif // TRACE_PRIVATE_MINIMAL_ENABLED /* FPacketRing ring-buffers packets. Internally the buffer is divided up into two ranges; [0-Cursor) and [Left-Right) which are initially empty; 0 L C----------------------------------------------------------------------R A packet consists of a size and a opaque blob of data. Reading the sizes allows one to stride through the packets. L 0[SZ]==============>[SZ]=============>[SZ]=======>C--------------------R Eventually the next packet will not fit in the buffer because the next cursor (N) is off the buffer's end; L 0[SZ]==============>[SZ]=============>[SZ]=======>[SZ]==========>C-----R [SZ]========>N When this happens the 0-Cursor range is transferred to Left-Right and the 0-Cursor range is set such that it can contain the new packet being added. L[SZ]==============>[SZ]=============>[SZ]=======>[SZ]==========>R-----| 0[SZ]========>C The two ranges now overlap so packets are then removed from Left until there is enough space for the new packet. 0[SZ]========>C-----L[SZ]============>[SZ]=======>[SZ]==========>R-----| The Left-Right range has the oldest packets. Left will eventually advance to meet Right at which point the Left-Right range becomes empty The process above repeats as if the buffer was being filled for the first time. */