// SPDX-FileCopyrightText: 2021 Jorrit Rouwe // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #if defined(JPH_EXTERNAL_PROFILE) namespace JPH { /// Create this class on the stack to start sampling timing information of a particular scope. /// /// Left unimplemented intentionally. Needs to be implemented by the user of the library. /// On construction a measurement should start, on destruction it should be stopped. class alignas(16) ExternalProfileMeasurement : public NonCopyable { public: /// Constructor ExternalProfileMeasurement(const char *inName, uint32 inColor = 0); ~ExternalProfileMeasurement(); private: uint8 mUserData[64]; }; } // JPH ////////////////////////////////////////////////////////////////////////////////////////// // Macros to do the actual profiling ////////////////////////////////////////////////////////////////////////////////////////// // Dummy implementations #define JPH_PROFILE_THREAD_START(name) #define JPH_PROFILE_THREAD_END() #define JPH_PROFILE_NEXTFRAME() #define JPH_PROFILE_DUMP(...) // Scope profiling measurement #define JPH_PROFILE_TAG2(line) profile##line #define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line) /// Macro to collect profiling information. /// /// Usage: /// /// { /// JPH_PROFILE("Operation"); /// do operation; /// } /// #define JPH_PROFILE(...) ExternalProfileMeasurement JPH_PROFILE_TAG(__LINE__)(__VA_ARGS__) // Scope profiling for function #define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME) #elif defined(JPH_PROFILE_ENABLED) namespace JPH { class ProfileSample; class ProfileThread; /// Singleton class for managing profiling information class Profiler : public NonCopyable { public: /// Increments the frame counter to provide statistics per frame void NextFrame(); /// Dump profiling statistics at the start of the next frame /// @param inTag If not empty, this overrides the auto incrementing number in the filename of the dump file void Dump(string inTag = string()); /// Add a thread to be instrumented void AddThread(ProfileThread *inThread); /// Remove a thread from being instrumented void RemoveThread(ProfileThread *inThread); /// Singleton instance static Profiler sInstance; private: /// Helper class to freeze ProfileSamples per thread while processing them struct ThreadSamples { string mThreadName; ProfileSample * mSamplesBegin; ProfileSample * mSamplesEnd; }; /// Helper class to aggregate ProfileSamples class Aggregator { public: /// Constructor Aggregator(const char *inName) : mName(inName) { } /// Accumulate results for a measurement void AccumulateMeasurement(uint64 inCyclesInCallWithChildren, uint64 inCyclesInChildren) { mCallCounter++; mTotalCyclesInCallWithChildren += inCyclesInCallWithChildren; mTotalCyclesInChildren += inCyclesInChildren; mMinCyclesInCallWithChildren = min(inCyclesInCallWithChildren, mMinCyclesInCallWithChildren); mMaxCyclesInCallWithChildren = max(inCyclesInCallWithChildren, mMaxCyclesInCallWithChildren); } /// Sort descending by total cycles bool operator < (const Aggregator &inRHS) const { return mTotalCyclesInCallWithChildren > inRHS.mTotalCyclesInCallWithChildren; } /// Identification const char * mName; ///< User defined name of this item /// Statistics uint32 mCallCounter = 0; ///< Number of times AccumulateMeasurement was called uint64 mTotalCyclesInCallWithChildren = 0; ///< Total amount of cycles spent in this scope uint64 mTotalCyclesInChildren = 0; ///< Total amount of cycles spent in children of this scope uint64 mMinCyclesInCallWithChildren = 0xffffffffffffffffUL; ///< Minimum amount of cycles spent per call uint64 mMaxCyclesInCallWithChildren = 0; ///< Maximum amount of cycles spent per call }; using Threads = vector; using Aggregators = vector; using KeyToAggregator = unordered_map; /// Helper function to aggregate profile sample data static void sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator); /// Dump profiling statistics void DumpInternal(); void DumpList(string inTag, const Aggregators &inAggregators); void DumpChart(string inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators); mutex mLock; ///< Lock that protects mThreads vector mThreads; ///< List of all active threads bool mDump = false; ///< When true, the samples are dumped next frame string mDumpTag; ///< When not empty, this overrides the auto incrementing number of the dump filename }; // Class that contains the information of a single scoped measurement class alignas(16) ProfileSample : public NonCopyable { public: const char * mName; ///< User defined name of this item uint32 mColor; ///< Color to use for this sample uint8 mDepth; ///< Calculated depth uint8 mUnused[3]; uint64 mStartCycle; ///< Cycle counter at start of measurement uint64 mEndCycle; ///< Cycle counter at end of measurement }; /// Collects all samples of a single thread class ProfileThread : public NonCopyable { public: /// Constructor inline ProfileThread(const string &inThreadName); inline ~ProfileThread(); static const uint cMaxSamples = 65536; string mThreadName; ///< Name of the thread that we're collecting information for ProfileSample mSamples[cMaxSamples]; ///< Buffer of samples uint mCurrentSample = 0; ///< Next position to write a sample to static thread_local ProfileThread *sInstance; }; /// Create this class on the stack to start sampling timing information of a particular scope class ProfileMeasurement : public NonCopyable { public: /// Constructor inline ProfileMeasurement(const char *inName, uint32 inColor = 0); inline ~ProfileMeasurement(); private: ProfileSample * mSample; ProfileSample mTemp; static bool sOutOfSamplesReported; }; } // JPH #include "Profiler.inl" ////////////////////////////////////////////////////////////////////////////////////////// // Macros to do the actual profiling ////////////////////////////////////////////////////////////////////////////////////////// /// Start instrumenting a thread #define JPH_PROFILE_THREAD_START(name) ProfileThread::sInstance = new ProfileThread(name) /// End instrumenting a thread #define JPH_PROFILE_THREAD_END() do { delete ProfileThread::sInstance; ProfileThread::sInstance = nullptr; } while (false) /// Scope profiling measurement #define JPH_PROFILE_TAG2(line) profile##line #define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line) #define JPH_PROFILE(...) ProfileMeasurement JPH_PROFILE_TAG(__LINE__)(__VA_ARGS__) /// Scope profiling for function #define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME) /// Update frame counter #define JPH_PROFILE_NEXTFRAME() Profiler::sInstance.NextFrame() /// Dump profiling info #define JPH_PROFILE_DUMP(...) Profiler::sInstance.Dump(__VA_ARGS__) #else ////////////////////////////////////////////////////////////////////////////////////////// // Dummy profiling instructions ////////////////////////////////////////////////////////////////////////////////////////// #define JPH_PROFILE_THREAD_START(name) #define JPH_PROFILE_THREAD_END() #define JPH_PROFILE(...) #define JPH_PROFILE_FUNCTION() #define JPH_PROFILE_NEXTFRAME() #define JPH_PROFILE_DUMP(...) #endif