// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "TraceServices/Model/Threads.h" #include "TraceServices/Model/TimingProfiler.h" namespace TraceServices { class IAnalysisSession; } namespace UE::Trace { class IAnalyzer; } // Defined here to prevent adding logs in the code above, which will likely be moved elsewhere. DEFINE_LOG_CATEGORY_STATIC(LogSummarizeTrace, Log, All); /* * * Helper classes for the SummarizeTrace commandlet. Aggregates statistics about a trace. * Code preceding this comment should eventually be relocated to the Trace/Insights modules. * Everything succeeding this comment should stay in this commandlet. * */ class FIncrementalVariance { public: FIncrementalVariance() : Count(0) , Mean(0.0) , VarianceAccumulator(0.0) { } uint64 GetCount() const; double GetMean() const; /** * Compute the variance given Welford's accumulator and the overall count * * @return The variance in sample units squared */ double Variance() const; /** * Compute the standard deviation given Welford's accumulator and the overall count * * @return The standard deviation in sample units */ double Deviation() const; /** * Perform an increment of work for Welford's variance, from which we can compute variation and standard deviation * * @param InSample The new sample value to operate on */ void Increment(const double InSample); /** * Merge with another IncrementalVariance series in progress * * @param Other The other variance incremented from another mutually exclusive population of analogous data. */ void Merge(const FIncrementalVariance& Other); /** * Reset state back to initialized. */ void Reset(); private: uint64 Count; double Mean; double VarianceAccumulator; }; /** * Base class to extend for CPU scope analysis. Derived class instances are meant to be registered as * delegates to the FSummarizeCpuProfilerProvider to handle scope events and perform meaningful analysis. */ class FSummarizeCpuScopeAnalyzer { public: enum class EScopeEventType : uint32 { Enter, Exit }; struct FScopeEvent { EScopeEventType ScopeEventType; uint32 ScopeId; uint32 ThreadId; double Timestamp; // As Seconds }; struct FScope { uint32 ScopeId; uint32 ThreadId; double EnterTimestamp; // As Seconds double ExitTimestamp; // As Seconds }; public: virtual ~FSummarizeCpuScopeAnalyzer() = default; /** Invoked when a CPU scope is discovered. This function is always invoked first when a CPU scope is encountered for the first time.*/ virtual void OnCpuScopeDiscovered(uint32 ScopeId) {} /** Invoked when CPU scope specification is encountered in the trace stream. */ virtual void OnCpuScopeName(uint32 ScopeId, const FStringView& ScopeName) {}; /** Invoked when a scope is entered. The scope name might not be known yet. */ virtual void OnCpuScopeEnter(const FScopeEvent& ScopeEnter, const FString* ScopeName) {}; /** Invoked when a scope is exited. The scope name might not be known yet. */ virtual void OnCpuScopeExit(const FScope& Scope, const FString* ScopeName) {}; /** Invoked when a root event on the specified thread along with all child events down to the leaves are known. */ virtual void OnCpuScopeTree(uint32 ThreadId, const TArray64& ScopeEvents, const TFunction& ScopeLookupNameFn) {}; /** Invoked when the trace stream has been fully consumed/processed. */ virtual void OnCpuScopeAnalysisEnd() {}; static constexpr uint32 CoroutineSpecId = (1u << 31u) - 1u; static constexpr uint32 CoroutineUnknownSpecId = (1u << 31u) - 2u; }; /** * Decodes, format and routes CPU scope events embedded into a trace stream to specialized CPU scope analyzers. This processor * decodes the low level events, keeps a small state and publishes higher level events to registered analyzers. The purpose * is to decode the stream only once and let several registered analyzers reuse the small state built up by this processor. This * matters when processing very large traces with possibly billions of scope events. */ class FSummarizeCpuProfilerProvider : public TraceServices::IEditableThreadProvider , public TraceServices::IEditableTimingProfilerProvider { public: FSummarizeCpuProfilerProvider(); /** Register an analyzer with this processor. The processor decodes the trace stream and invokes the registered analyzers when a CPU scope event occurs.*/ void AddCpuScopeAnalyzer(TSharedPtr Analyzer); void AnalysisComplete(); private: struct FScopeEnter { uint32 ScopeId; double Timestamp; // As Seconds }; // Contains scope events for a root scope and its children along with extra info to analyze that tree at once. struct FScopeTreeInfo { // Records the current root scope and its children to run analysis that needs to know the parent/child relationship. TArray64 ScopeEvents; // Indicates if one of the scope in the current hierarchy is nameless. (Its names specs hasn't been received yet). bool bHasNamelessScopes = false; void Reset() { ScopeEvents.Reset(); bHasNamelessScopes = false; } }; // For each thread we track what the stack of scopes are, for matching end-to-start struct FThread : public TraceServices::IEditableTimeline { FThread(uint32 InThreadId, FSummarizeCpuProfilerProvider* InProvider) : ThreadId(InThreadId) , Provider(InProvider) { } virtual void AppendBeginEvent(double StartTime, const TraceServices::FTimingProfilerEvent& Event) override; virtual void AppendEndEvent(double EndTime) override; // The ThreadId of this thread uint32 ThreadId; // The provider to forward calls to FSummarizeCpuProfilerProvider* Provider; // The current stack state TArray ScopeStack; // The events recorded for the current root scope and its children to run analysis that needs to know the parent/child relationship, for example to compute time including childreen and time // excluding childreen. FScopeTreeInfo ScopeTreeInfo; // Scope trees for which as least one scope name was unknown. Some analysis need scope names, but scope names/event can be emitted out of order by the engine depending on thread scheduling. // Some scope tree cannot be analyzed right away and need to be delayed until all scope names are discovered. TArray DelayedScopeTreeInfo; }; private: // TraceServices::IEditableThreadProvider virtual void AddThread(uint32 Id, const TCHAR* Name, EThreadPriority Priority) override; // TraceServices::IEditableTimingProfilerProvider virtual uint32 AddCpuTimer(FStringView Name, const TCHAR* File, uint32 Line) override; virtual void SetTimerName(uint32 TimerId, FStringView Name) override; virtual void SetTimerNameAndLocation(uint32 TimerId, FStringView Name, const TCHAR* File, uint32 Line) override; virtual uint32 AddMetadata(uint32 MasterTimerId, TArray&& Metadata) override; virtual TArrayView GetEditableMetadata(uint32 TimerId) override; virtual TraceServices::IEditableTimeline& GetCpuThreadEditableTimeline(uint32 ThreadId) override; // Callbacks from FThread to forward calls to Analyzers virtual void AppendBeginEvent(const FSummarizeCpuScopeAnalyzer::FScopeEvent& ScopeEvent); virtual void AppendEndEvent(const FSummarizeCpuScopeAnalyzer::FScope& ScopeEvent, const FString* ScopeName); // Processing the ScopeTree once it's complete void OnCpuScopeTree(uint32 ThreadId, const FScopeTreeInfo& ScopeTreeInfo); // Resolve a ScopeId to string const FString* LookupScopeName(uint32 ScopeId); private: // The state at any moment of the threads TMap> Threads; // The scope names, the array index correspond to the scope Id. If the optional is not set, the scope hasn't been encountered yet. TArray> ScopeNames; // List of analyzers to invoke when a scope event is decoded. TArray> ScopeAnalyzers; // Scope name lookup function, cached for efficiency. TFunction LookupScopeNameFn; struct FMetadata { TArray Payload; uint32 TimerId; }; TArray Metadatas; }; struct FSummarizeScope { FString Name; FIncrementalVariance DurationVariance; double TotalDurationSeconds = 0.0; double FirstStartSeconds = 0.0; double FirstFinishSeconds = 0.0; double FirstDurationSeconds = 0.0; double LastStartSeconds = 0.0; double LastFinishSeconds = 0.0; double LastDurationSeconds = 0.0; double MinDurationSeconds = TNumericLimits::Max(); double MaxDurationSeconds = TNumericLimits::Min(); TArray EndTimeArray; TArray BeginTimeArray; void AddDuration(double StartSeconds, double FinishSeconds); uint64 GetCount() const; double GetMeanDurationSeconds() const; double GetDeviationDurationSeconds() const; double GetCountPerSecond() const; void Merge(const FSummarizeScope& Other); FString GetValue(const FStringView& Statistic) const; // for deduplication bool operator==(const FSummarizeScope& Scope) const; // for sorting descending bool operator<(const FSummarizeScope& Scope) const; }; static uint32 GetTypeHash(const FSummarizeScope& Scope) { return FCrc::StrCrc32(*Scope.Name); } /** * This analyzer aggregates CPU scopes having the same name together, counting the number of occurrences, total duration, * average occurrence duration, for each scope name. */ class FSummarizeCpuScopeDurationAnalyzer : public FSummarizeCpuScopeAnalyzer { public: FSummarizeCpuScopeDurationAnalyzer(TFunction&)> InPublishFn); protected: virtual void OnCpuScopeDiscovered(uint32 ScopeId) override; virtual void OnCpuScopeName(uint32 ScopeId, const FStringView& ScopeName) override; virtual void OnCpuScopeExit(const FScope& Scope, const FString* ScopeName) override; virtual void OnCpuScopeAnalysisEnd() override; private: TMap Scopes; // Function invoked to publish the list of scopes. TFunction&)> PublishFn; }; // // FSummarizeBookmarksProvider - Tally Bookmarks from bookmark events // struct FSummarizeBookmark { FString Name; uint64 Count = 0; double FirstSeconds = 0.0; double LastSeconds = 0.0; void AddTimestamp(double Seconds); FString GetValue(const FStringView& Statistic) const; // for deduplication bool operator==(const FSummarizeBookmark& Bookmark) const { return Name == Bookmark.Name; } }; static uint32 GetTypeHash(const FSummarizeBookmark& Bookmark) { return FCrc::StrCrc32(*Bookmark.Name); } namespace CsvUtils { /* * Helpers for the csv files */ bool IsCsvSafeString(const FString& String); void WriteAsUTF8String(IFileHandle* Handle, const FString& String); }