Profiler.h 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #pragma once
  4. #include <mutex>
  5. #include <unordered_map>
  6. #include <Core/NonCopyable.h>
  7. #include <Core/TickCounter.h>
  8. #if defined(JPH_EXTERNAL_PROFILE)
  9. namespace JPH {
  10. /// Create this class on the stack to start sampling timing information of a particular scope.
  11. ///
  12. /// Left unimplemented intentionally. Needs to be implemented by the user of the library.
  13. /// On construction a measurement should start, on destruction it should be stopped.
  14. class alignas(16) ExternalProfileMeasurement : public NonCopyable
  15. {
  16. public:
  17. /// Constructor
  18. ExternalProfileMeasurement(const char *inName, uint32 inColor = 0);
  19. ~ExternalProfileMeasurement();
  20. private:
  21. uint8 mUserData[64];
  22. };
  23. } // JPH
  24. //////////////////////////////////////////////////////////////////////////////////////////
  25. // Macros to do the actual profiling
  26. //////////////////////////////////////////////////////////////////////////////////////////
  27. // Dummy implementations
  28. #define JPH_PROFILE_THREAD_START(name)
  29. #define JPH_PROFILE_THREAD_END()
  30. #define JPH_PROFILE_NEXTFRAME()
  31. #define JPH_PROFILE_DUMP(...)
  32. // Scope profiling measurement
  33. #define JPH_PROFILE_TAG2(line) profile##line
  34. #define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
  35. /// Macro to collect profiling information.
  36. ///
  37. /// Usage:
  38. ///
  39. /// {
  40. /// JPH_PROFILE("Operation");
  41. /// do operation;
  42. /// }
  43. ///
  44. #define JPH_PROFILE(...) ExternalProfileMeasurement JPH_PROFILE_TAG(__LINE__)(__VA_ARGS__)
  45. // Scope profiling for function
  46. #define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME)
  47. #elif defined(JPH_PROFILE_ENABLED)
  48. namespace JPH {
  49. class ProfileSample;
  50. class ProfileThread;
  51. /// Singleton class for managing profiling information
  52. class Profiler : public NonCopyable
  53. {
  54. public:
  55. /// Increments the frame counter to provide statistics per frame
  56. void NextFrame();
  57. /// Dump profiling statistics at the start of the next frame
  58. /// @param inTag If not empty, this overrides the auto incrementing number in the filename of the dump file
  59. void Dump(string inTag = string());
  60. /// Add a thread to be instrumented
  61. void AddThread(ProfileThread *inThread);
  62. /// Remove a thread from being instrumented
  63. void RemoveThread(ProfileThread *inThread);
  64. /// Singleton instance
  65. static Profiler sInstance;
  66. private:
  67. /// Helper class to freeze ProfileSamples per thread while processing them
  68. struct ThreadSamples
  69. {
  70. string mThreadName;
  71. ProfileSample * mSamplesBegin;
  72. ProfileSample * mSamplesEnd;
  73. };
  74. /// Helper class to aggregate ProfileSamples
  75. class Aggregator
  76. {
  77. public:
  78. /// Constructor
  79. Aggregator(const char *inName) : mName(inName) { }
  80. /// Accumulate results for a measurement
  81. void AccumulateMeasurement(uint64 inCyclesInCallWithChildren, uint64 inCyclesInChildren)
  82. {
  83. mCallCounter++;
  84. mTotalCyclesInCallWithChildren += inCyclesInCallWithChildren;
  85. mTotalCyclesInChildren += inCyclesInChildren;
  86. mMinCyclesInCallWithChildren = min(inCyclesInCallWithChildren, mMinCyclesInCallWithChildren);
  87. mMaxCyclesInCallWithChildren = max(inCyclesInCallWithChildren, mMaxCyclesInCallWithChildren);
  88. }
  89. /// Sort descending by total cycles
  90. bool operator < (const Aggregator &inRHS) const
  91. {
  92. return mTotalCyclesInCallWithChildren > inRHS.mTotalCyclesInCallWithChildren;
  93. }
  94. /// Identification
  95. const char * mName; ///< User defined name of this item
  96. /// Statistics
  97. uint32 mCallCounter = 0; ///< Number of times AccumulateMeasurement was called
  98. uint64 mTotalCyclesInCallWithChildren = 0; ///< Total amount of cycles spent in this scope
  99. uint64 mTotalCyclesInChildren = 0; ///< Total amount of cycles spent in children of this scope
  100. uint64 mMinCyclesInCallWithChildren = 0xffffffffffffffffUL; ///< Minimum amount of cycles spent per call
  101. uint64 mMaxCyclesInCallWithChildren = 0; ///< Maximum amount of cycles spent per call
  102. };
  103. using Threads = vector<ThreadSamples>;
  104. using Aggregators = vector<Aggregator>;
  105. using KeyToAggregator = unordered_map<const char *, size_t>;
  106. /// Helper function to aggregate profile sample data
  107. static void sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator);
  108. /// Dump profiling statistics
  109. void DumpInternal();
  110. void DumpList(string inTag, const Aggregators &inAggregators);
  111. void DumpChart(string inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators);
  112. mutex mLock; ///< Lock that protects mThreads
  113. vector<ProfileThread *> mThreads; ///< List of all active threads
  114. bool mDump = false; ///< When true, the samples are dumped next frame
  115. string mDumpTag; ///< When not empty, this overrides the auto incrementing number of the dump filename
  116. };
  117. // Class that contains the information of a single scoped measurement
  118. class alignas(16) ProfileSample : public NonCopyable
  119. {
  120. public:
  121. const char * mName; ///< User defined name of this item
  122. uint32 mColor; ///< Color to use for this sample
  123. uint8 mDepth; ///< Calculated depth
  124. uint8 mUnused[3];
  125. uint64 mStartCycle; ///< Cycle counter at start of measurement
  126. uint64 mEndCycle; ///< Cycle counter at end of measurement
  127. };
  128. /// Collects all samples of a single thread
  129. class ProfileThread : public NonCopyable
  130. {
  131. public:
  132. /// Constructor
  133. inline ProfileThread(const string &inThreadName);
  134. inline ~ProfileThread();
  135. static const uint cMaxSamples = 65536;
  136. string mThreadName; ///< Name of the thread that we're collecting information for
  137. ProfileSample mSamples[cMaxSamples]; ///< Buffer of samples
  138. uint mCurrentSample = 0; ///< Next position to write a sample to
  139. static thread_local ProfileThread *sInstance;
  140. };
  141. /// Create this class on the stack to start sampling timing information of a particular scope
  142. class ProfileMeasurement : public NonCopyable
  143. {
  144. public:
  145. /// Constructor
  146. inline ProfileMeasurement(const char *inName, uint32 inColor = 0);
  147. inline ~ProfileMeasurement();
  148. private:
  149. ProfileSample * mSample;
  150. ProfileSample mTemp;
  151. static bool sOutOfSamplesReported;
  152. };
  153. } // JPH
  154. #include "Profiler.inl"
  155. //////////////////////////////////////////////////////////////////////////////////////////
  156. // Macros to do the actual profiling
  157. //////////////////////////////////////////////////////////////////////////////////////////
  158. /// Start instrumenting a thread
  159. #define JPH_PROFILE_THREAD_START(name) ProfileThread::sInstance = new ProfileThread(name)
  160. /// End instrumenting a thread
  161. #define JPH_PROFILE_THREAD_END() do { delete ProfileThread::sInstance; ProfileThread::sInstance = nullptr; } while (false)
  162. /// Scope profiling measurement
  163. #define JPH_PROFILE_TAG2(line) profile##line
  164. #define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
  165. #define JPH_PROFILE(...) ProfileMeasurement JPH_PROFILE_TAG(__LINE__)(__VA_ARGS__)
  166. /// Scope profiling for function
  167. #define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME)
  168. /// Update frame counter
  169. #define JPH_PROFILE_NEXTFRAME() Profiler::sInstance.NextFrame()
  170. /// Dump profiling info
  171. #define JPH_PROFILE_DUMP(...) Profiler::sInstance.Dump(__VA_ARGS__)
  172. #else
  173. //////////////////////////////////////////////////////////////////////////////////////////
  174. // Dummy profiling instructions
  175. //////////////////////////////////////////////////////////////////////////////////////////
  176. #define JPH_PROFILE_THREAD_START(name)
  177. #define JPH_PROFILE_THREAD_END()
  178. #define JPH_PROFILE(...)
  179. #define JPH_PROFILE_FUNCTION()
  180. #define JPH_PROFILE_NEXTFRAME()
  181. #define JPH_PROFILE_DUMP(...)
  182. #endif