Profiler.h 9.6 KB

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