502 lines
18 KiB
C++
502 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreTypes.h"
|
|
#include "Containers/ArrayView.h"
|
|
#include "Containers/StringView.h"
|
|
#include "TraceAnalysisDebug.h"
|
|
#include "Trace/Trace.h"
|
|
|
|
#include <type_traits>
|
|
|
|
namespace UE {
|
|
namespace Trace {
|
|
|
|
/**
|
|
* Interface that users implement to analyze the events in a trace. Analysis
|
|
* works by subscribing to events by name along with a user-provider "route"
|
|
* identifier. The IAnalyzer then receives callbacks when those events are
|
|
* encountered along with an interface to query the value of the event's fields.
|
|
*
|
|
* To analyze a trace, concrete IAnalyzer-derived objects are registered with a
|
|
* FAnalysisContext which is then asked to launch and coordinate the analysis.
|
|
*/
|
|
class TRACEANALYSIS_API IAnalyzer
|
|
{
|
|
public:
|
|
struct FInterfaceBuilder
|
|
{
|
|
/** Subscribe to an event required for analysis.
|
|
* @param RouteId User-provided identifier for this event subscription.
|
|
* @param Logger Name of the logger that emits the event.
|
|
* @param Event Name of the event to subscribe to.
|
|
* @param bScoped Route scoped events. */
|
|
virtual void RouteEvent(uint16 RouteId, const ANSICHAR* Logger, const ANSICHAR* Event, bool bScoped=false) = 0;
|
|
|
|
/** Subscribe to all events from a particular logger.
|
|
* @param RouteId User-provided identifier for this event subscription.
|
|
* @param Logger Name of the logger that emits the event.
|
|
* @param bScoped Route scoped events. */
|
|
virtual void RouteLoggerEvents(uint16 RouteId, const ANSICHAR* Logger, bool bScoped=false) = 0;
|
|
|
|
/** Subscribe to all events in the trace stream being analyzed.
|
|
* @param RouteId User-provided identifier for this event subscription.
|
|
* @param bScoped Route scoped events. */
|
|
virtual void RouteAllEvents(uint16 RouteId, bool bScoped=false) = 0;
|
|
};
|
|
|
|
struct FOnAnalysisContext
|
|
{
|
|
FInterfaceBuilder& InterfaceBuilder;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FEventFieldInfo
|
|
{
|
|
enum class EType { None, Integer, Float, AnsiString, WideString, Reference8, Reference16, Reference32, Reference64 };
|
|
|
|
/** Returns the name of the field. */
|
|
const ANSICHAR* GetName() const;
|
|
|
|
/** What type of field is this? */
|
|
EType GetType() const;
|
|
|
|
/** Is this field an array-type field? */
|
|
bool IsArray() const;
|
|
|
|
/** Is this field signed (only relevant for integer types) */
|
|
bool IsSigned() const;
|
|
|
|
#if UE_TRACE_ANALYSIS_DEBUG_API
|
|
/** Offset from the start of the event to this field's data. */
|
|
uint32 GetOffset() const;
|
|
#endif // UE_TRACE_ANALYSIS_DEBUG_API
|
|
|
|
/** Gets the size in bytes for this field */
|
|
uint8 GetSize() const;
|
|
};
|
|
|
|
struct FEventFieldHandle
|
|
{
|
|
bool IsValid() const { return Detail >= 0; }
|
|
int32 Detail;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FEventTypeInfo
|
|
{
|
|
/** Each event is assigned a unique ID when logged. Note that this is not
|
|
* guaranteed to be the same for the same event from one trace to the next. */
|
|
uint32 GetId() const;
|
|
|
|
#if UE_TRACE_ANALYSIS_DEBUG_API
|
|
/** Returns the event's flags. */
|
|
uint8 GetFlags() const;
|
|
#endif // UE_TRACE_ANALYSIS_DEBUG_API
|
|
|
|
/** The name of the event. */
|
|
const ANSICHAR* GetName() const;
|
|
|
|
/** Returns the logger name the event is associated with. */
|
|
const ANSICHAR* GetLoggerName() const;
|
|
|
|
/** Returns the base size of the event. */
|
|
uint32 GetSize() const;
|
|
|
|
/** The number of member fields this event has. */
|
|
uint32 GetFieldCount() const;
|
|
|
|
/** By-index access to fields' type information. */
|
|
const FEventFieldInfo* GetFieldInfo(uint32 Index) const;
|
|
|
|
/** Returns the field index or -1 (if the event does not contains a field with the specified name). */
|
|
int32 GetFieldIndex(const ANSICHAR* FieldName) const;
|
|
|
|
/** Returns a handle that can used to access events' fields. There is
|
|
* loose validation via ValueType, but one should still exercise caution
|
|
* when reading fields with handles.
|
|
* @param ValueType The intended type that the field will be interpreted as */
|
|
template <typename ValueType>
|
|
FEventFieldHandle GetFieldHandle(const ANSICHAR* FieldName) const;
|
|
|
|
/** Returns a handle without specifying type. This should only be used in circumstances
|
|
* where the field is treated untyped data.
|
|
* @param Index Index of field.
|
|
*/
|
|
FEventFieldHandle GetFieldHandleUnchecked(uint32 Index) const;
|
|
|
|
private:
|
|
FEventFieldHandle GetFieldHandleImpl(const ANSICHAR*, int16&) const;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FArrayReader
|
|
{
|
|
/* Returns the number of elements in the array. */
|
|
uint32 Num() const;
|
|
|
|
#if UE_TRACE_ANALYSIS_DEBUG_API
|
|
/* Returns the pointer to the raw data array. */
|
|
const uint8* GetRawData() const;
|
|
|
|
/* Returns the size in bytes of the raw data array. */
|
|
uint32 GetRawDataSize() const;
|
|
|
|
/* Returns the size and type of an array element. */
|
|
int8 GetSizeAndType() const;
|
|
#endif // UE_TRACE_ANALYSIS_DEBUG_API
|
|
|
|
protected:
|
|
const void* GetImpl(uint32 Index, int8& SizeAndType) const;
|
|
};
|
|
|
|
template <typename ValueType>
|
|
struct TArrayReader
|
|
: public FArrayReader
|
|
{
|
|
/* Returns a element from the array an the given index or zero if Index
|
|
* is out of bounds. */
|
|
ValueType operator [] (uint32 Index) const;
|
|
|
|
/** Get a pointer to the contiguous array data */
|
|
const ValueType* GetData() const;
|
|
};
|
|
|
|
enum class EStyle : uint32
|
|
{
|
|
Normal,
|
|
EnterScope,
|
|
LeaveScope,
|
|
};
|
|
|
|
struct FOnEventContext;
|
|
|
|
struct TRACEANALYSIS_API FEventData
|
|
{
|
|
/** Returns an object describing the underlying event's type. */
|
|
const FEventTypeInfo& GetTypeInfo() const;
|
|
|
|
/** Queries the value of a field of the event. It is not necessary to match
|
|
* ValueType to the type in the event.
|
|
* @param FieldName The name of the event's field to get the value for.
|
|
* @param Default Return this value if the given field was not found.
|
|
* @return Value of the field (coerced to ValueType) if found, otherwise 0. */
|
|
template <typename ValueType> ValueType GetValue(const ANSICHAR* FieldName, ValueType Default=ValueType(0)) const;
|
|
template <typename ValueType> ValueType GetValue(FEventFieldHandle FieldHandle) const;
|
|
|
|
/** Returns an object for reading data from an array-type field. A valid
|
|
* array reader object will always be return even if no field matching the
|
|
* given name was found.
|
|
* @param FieldName The name of the event's field to get the value for. */
|
|
template <typename ValueType> const TArrayReader<ValueType>& GetArray(const ANSICHAR* FieldName) const;
|
|
|
|
/** Returns an array view for reading data from an array-type field. A valid
|
|
* array view will always be returned even if no field matching the
|
|
* given name was found.
|
|
* @param FieldName The name of the event's field to get the value for. */
|
|
template <typename ValueType> TArrayView<const ValueType> GetArrayView(const ANSICHAR* FieldName) const;
|
|
|
|
/** Return the value of a string-type field. The view-type prototypes
|
|
* must match the underlying string type while the FString-variant is
|
|
* agnostic of the field's encoding.
|
|
* @param Out Destination object for the field's value.
|
|
* @return True if the field was found. */
|
|
bool GetString(const ANSICHAR* FieldName, FAnsiStringView& Out) const;
|
|
bool GetString(const ANSICHAR* FieldName, FWideStringView& Out) const;
|
|
bool GetString(const ANSICHAR* FieldName, FString& Out) const;
|
|
|
|
/** Returns a value of a reference field.
|
|
* @param FieldName Name of field
|
|
* @return Reference value
|
|
*/
|
|
template<typename DefinitionType>
|
|
TEventRef<DefinitionType> GetReferenceValue(const ANSICHAR* FieldName) const;
|
|
|
|
template<typename DefinitionType>
|
|
TEventRef<DefinitionType> GetReferenceValue(uint32 FieldIndex) const;
|
|
|
|
/** If this is a spec event, gets the unique Id for this spec.
|
|
* @return A valid spec id if the event is valid, otherwise an empty id.
|
|
*/
|
|
template<typename DefinitionType> TEventRef<DefinitionType> GetDefinitionId() const;
|
|
|
|
/** The size of the event in uncompressed bytes excluding the header */
|
|
uint32 GetSize() const;
|
|
|
|
/** Serializes the event to Cbor object.
|
|
* @param Recipient of the Cbor serialization. Data is appended to Out. */
|
|
void SerializeToCbor(TArray<uint8>& Out) const;
|
|
|
|
/**
|
|
* Returns the raw pointer to a field value
|
|
* @param Handle Handle to field
|
|
* @return Untyped pointer to field value
|
|
*/
|
|
const void* GetValueRaw(FEventFieldHandle Handle) const;
|
|
|
|
/** Returns the event's attachment. Not that this will always return an
|
|
* address but if the event has no attachment then reading from that
|
|
* address if undefined. */
|
|
const uint8* GetAttachment() const;
|
|
|
|
/** Returns the size of the events attachment, or 0 if none. */
|
|
uint32 GetAttachmentSize() const;
|
|
|
|
#if UE_TRACE_ANALYSIS_DEBUG_API
|
|
/** Provides a pointer to the raw event data. */
|
|
const uint8* GetRawPointer() const;
|
|
|
|
/** Returns the size of the raw event data (including attachment). */
|
|
uint32 GetRawSize() const;
|
|
|
|
/** Returns the total uncompressed size of the aux data (including size of aux headers and terminator), in bytes. */
|
|
uint32 GetAuxSize() const;
|
|
|
|
/** Returns the total uncompressed size of the event, in bytes (including headers and aux data). */
|
|
uint32 GetTotalSize(IAnalyzer::EStyle Style, const IAnalyzer::FOnEventContext& Context, uint32 ProtocolVersion = 7) const;
|
|
#endif // UE_TRACE_ANALYSIS_DEBUG_API
|
|
|
|
private:
|
|
bool IsDefinitionImpl(uint32& OutTypeId) const;
|
|
const void* GetReferenceValueImpl(const char* FieldName, uint16& OutSizeType, uint32& OutTypeUid) const;
|
|
const void* GetReferenceValueImpl(uint32 FieldIndex, uint32& OutTypeUid) const;
|
|
const void* GetValueImpl(const ANSICHAR* FieldName, int8& SizeAndType) const;
|
|
const FArrayReader* GetArrayImpl(const ANSICHAR* FieldName) const;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FThreadInfo
|
|
{
|
|
/* Returns the trace-specific id for the thread */
|
|
uint32 GetId() const;
|
|
|
|
/* Returns the system id for the thread. Because this may not be known by
|
|
* trace and because IDs can be reused by the system, relying on the value
|
|
* of this is discouraged. */
|
|
uint32 GetSystemId() const;
|
|
|
|
/* Returns a hint for use when sorting threads. */
|
|
int32 GetSortHint() const;
|
|
|
|
/* Returns the thread's name or an empty string */
|
|
const ANSICHAR* GetName() const;
|
|
|
|
/* Returns the name of the group a thread has been assigned ti, or an empty string */
|
|
const ANSICHAR* GetGroupName() const;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FEventTime
|
|
{
|
|
/** Returns the integer timestamp for the event or zero if there no associated timestamp. */
|
|
uint64 GetTimestamp() const;
|
|
|
|
/** Time of the event in seconds (from the start of the trace). Zero if there is no time for the event. */
|
|
double AsSeconds() const;
|
|
|
|
/** Returns a timestamp for the event compatible with FPlatformTime::Cycle64(), or zero if the event has no timestamp. */
|
|
uint64 AsCycle64() const;
|
|
|
|
/** Returns a FPlatformTime::Cycle64() value as seconds relative to the start of the trace. */
|
|
double AsSeconds(uint64 Cycles64) const;
|
|
|
|
/** As AsSeconds(Cycles64) but absolute. */
|
|
double AsSecondsAbsolute(int64 DurationCycles64) const;
|
|
};
|
|
|
|
struct TRACEANALYSIS_API FOnEventContext
|
|
{
|
|
const FThreadInfo& ThreadInfo;
|
|
const FEventTime& EventTime;
|
|
const FEventData& EventData;
|
|
};
|
|
|
|
virtual ~IAnalyzer() = default;
|
|
|
|
/** Called when analysis of a trace is beginning. Analyzer implementers can
|
|
* subscribe to the events that they are interested in at this point
|
|
* @param Context Contextual information and interface for subscribing to events. */
|
|
virtual void OnAnalysisBegin(const FOnAnalysisContext& Context)
|
|
{
|
|
}
|
|
|
|
/** Indicates that the analysis of a trace log has completed and there are no
|
|
* further events */
|
|
virtual void OnAnalysisEnd()
|
|
{
|
|
}
|
|
|
|
/** Called when information about a thread has been updated. It is entirely
|
|
* possible that this might get called more than once for a particular thread
|
|
* if its details changed.
|
|
* @param ThreadInfo Describes the thread whose information has changed. */
|
|
virtual void OnThreadInfo(const FThreadInfo& ThreadInfo)
|
|
{
|
|
}
|
|
|
|
/** When a new event type appears in the trace stream, this method is called
|
|
* if the event type has been subscribed to.
|
|
* @param RouteId User-provided identifier for this event subscription.
|
|
* @param TypeInfo Object describing the new event's type.
|
|
* @return This analyzer is removed from the analysis session if false is returned. */
|
|
virtual bool OnNewEvent(uint16 RouteId, const FEventTypeInfo& TypeInfo)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** For each event subscribed to in OnAnalysisBegin(), the analysis engine
|
|
* will call this method when those events are encountered in a trace log
|
|
* @param RouteId User-provided identifier given when subscribing to a particular event.
|
|
* @param Style Indicates the style of event. Note that EventData is *undefined* if the style is LeaveScope!
|
|
* @param Context Access to the instance of the subscribed event.
|
|
* @return This analyzer is removed from the analysis session if false is returned. */
|
|
virtual bool OnEvent(uint16 RouteId, EStyle Style, const FOnEventContext& Context)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if UE_TRACE_ANALYSIS_DEBUG_API
|
|
virtual void OnVersion(uint32 TransportVersion, uint32 ProtocolVersion)
|
|
{
|
|
}
|
|
#endif // UE_TRACE_ANALYSIS_DEBUG_API
|
|
|
|
private:
|
|
template <typename ValueType> static ValueType CoerceValue(const void* Addr, int8 SizeAndType);
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
IAnalyzer::FEventFieldHandle IAnalyzer::FEventTypeInfo::GetFieldHandle(const ANSICHAR* FieldName) const
|
|
{
|
|
int16 SizeAndType;
|
|
FEventFieldHandle Handle = GetFieldHandleImpl(FieldName, SizeAndType);
|
|
if (std::is_floating_point<ValueType>::value)
|
|
{
|
|
checkf((SizeAndType < 0), TEXT("Field is not a float-type field"));
|
|
}
|
|
else
|
|
{
|
|
checkf(SizeAndType >= sizeof(ValueType), TEXT("Field is to small to read as hinted type ValueType"));
|
|
}
|
|
return Handle;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
ValueType IAnalyzer::CoerceValue(const void* Addr, int8 SizeAndType)
|
|
{
|
|
using integral8 = std::conditional_t<std::is_signed_v<ValueType>, int8, uint8>;
|
|
using integral16 = std::conditional_t<std::is_signed_v<ValueType>, int16, uint16>;
|
|
using integral32 = std::conditional_t<std::is_signed_v<ValueType>, int32, uint32>;
|
|
using integral64 = std::conditional_t<std::is_signed_v<ValueType>, int64, uint64>;
|
|
switch (SizeAndType)
|
|
{
|
|
case -4: return ValueType(*(const float*)(Addr));
|
|
case -8: return ValueType(*(const double*)(Addr));
|
|
case 1: return ValueType(*(const integral8*)(Addr));
|
|
case 2: return ValueType(*(const integral16*)(Addr));
|
|
case 4: return ValueType(*(const integral32*)(Addr));
|
|
case 8: return ValueType(*(const integral64*)(Addr));
|
|
default: return ValueType(0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
ValueType IAnalyzer::FEventData::GetValue(const ANSICHAR* FieldName, ValueType Default) const
|
|
{
|
|
int8 FieldSizeAndType;
|
|
if (const void* Addr = GetValueImpl(FieldName, FieldSizeAndType))
|
|
{
|
|
return CoerceValue<ValueType>(Addr, FieldSizeAndType);
|
|
}
|
|
return Default;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
ValueType IAnalyzer::FEventData::GetValue(FEventFieldHandle FieldHandle) const
|
|
{
|
|
const uint8* EventDataPtr = *(const uint8**)this;
|
|
return *(ValueType*)(EventDataPtr + FieldHandle.Detail);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
const IAnalyzer::TArrayReader<ValueType>& IAnalyzer::FEventData::GetArray(const ANSICHAR* FieldName) const
|
|
{
|
|
const FArrayReader* Base = GetArrayImpl(FieldName);
|
|
return *(TArrayReader<ValueType>*)(Base);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
TArrayView<const ValueType> IAnalyzer::FEventData::GetArrayView(const ANSICHAR* FieldName) const
|
|
{
|
|
const TArrayReader<ValueType>& ArrayReader = GetArray<ValueType>(FieldName);
|
|
return TArrayView<const ValueType>(ArrayReader.GetData(), ArrayReader.Num());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
ValueType IAnalyzer::TArrayReader<ValueType>::operator [] (uint32 Index) const
|
|
{
|
|
int8 ElementSizeAndType;
|
|
if (const void* Addr = GetImpl(Index, ElementSizeAndType))
|
|
{
|
|
return CoerceValue<ValueType>(Addr, ElementSizeAndType);
|
|
}
|
|
return ValueType(0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename ValueType>
|
|
const ValueType* IAnalyzer::TArrayReader<ValueType>::GetData() const
|
|
{
|
|
int8 ElementSizeAndType;
|
|
const void* Addr = GetImpl(0, ElementSizeAndType);
|
|
|
|
if (Addr == nullptr || sizeof(ValueType) != abs(ElementSizeAndType))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return (const ValueType*)Addr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template<typename DefinitionType>
|
|
TEventRef<DefinitionType> IAnalyzer::FEventData::GetDefinitionId() const
|
|
{
|
|
uint32 TypeUid;
|
|
if (IsDefinitionImpl(TypeUid))
|
|
{
|
|
//todo: Emit warning when trying to access id of incorrect type?
|
|
return MakeEventRef(GetValue<DefinitionType>("DefinitionId", 0), TypeUid);
|
|
}
|
|
return MakeEventRef<DefinitionType>(0,0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template<typename DefinitionType>
|
|
TEventRef<DefinitionType> IAnalyzer::FEventData::GetReferenceValue(const ANSICHAR* FieldName) const
|
|
{
|
|
uint32 TypeUid;
|
|
uint16 SizeAndType;
|
|
const void* Value = GetReferenceValueImpl(FieldName, SizeAndType, TypeUid);
|
|
if (Value)
|
|
{
|
|
return MakeEventRef<DefinitionType>(CoerceValue<DefinitionType>(Value, static_cast<int8>(SizeAndType)), TypeUid);
|
|
}
|
|
return MakeEventRef<DefinitionType>(0,0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename DefinitionType>
|
|
TEventRef<DefinitionType> IAnalyzer::FEventData::GetReferenceValue(uint32 FieldIndex) const
|
|
{
|
|
uint32 RefTypeUid;
|
|
DefinitionType* Id = (DefinitionType*) GetReferenceValueImpl(FieldIndex, RefTypeUid);
|
|
return MakeEventRef<DefinitionType>(*Id, RefTypeUid);
|
|
}
|
|
|
|
} // namespace Trace
|
|
} // namespace UE
|