Profiler.h 11 KB

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