CpuProfiler.h 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #pragma once
  9. #include <AzCore/Component/TickBus.h>
  10. #include <AzCore/Debug/Profiler.h>
  11. #include <AzCore/Memory/SystemAllocator.h>
  12. #include <AzCore/Name/Name.h>
  13. #include <AzCore/RTTI/RTTI.h>
  14. #include <AzCore/std/containers/map.h>
  15. #include <AzCore/std/containers/ring_buffer.h>
  16. #include <AzCore/std/containers/unordered_map.h>
  17. #include <AzCore/std/parallel/mutex.h>
  18. #include <AzCore/std/parallel/shared_mutex.h>
  19. #include <AzCore/std/smart_ptr/intrusive_refcount.h>
  20. #include <AzCore/std/string/string.h>
  21. namespace Profiler
  22. {
  23. //! Structure that is used to cache a timed region into the thread's local storage.
  24. struct CachedTimeRegion
  25. {
  26. //! Structure used internally for caching assumed global string pointers (ideally literals) to the marker group/region
  27. //! NOTE: When used in a separate shared library, the library mustn't be unloaded before the CpuProfiler is shutdown.
  28. struct GroupRegionName
  29. {
  30. GroupRegionName() = delete;
  31. GroupRegionName(const char* const group, const char* const region);
  32. const char* m_groupName = nullptr;
  33. AZ::Name m_regionName;
  34. struct Hash
  35. {
  36. AZStd::size_t operator()(const GroupRegionName& name) const;
  37. };
  38. bool operator==(const GroupRegionName& other) const;
  39. };
  40. CachedTimeRegion() = default;
  41. explicit CachedTimeRegion(const GroupRegionName& groupRegionName);
  42. CachedTimeRegion(const GroupRegionName& groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick);
  43. GroupRegionName m_groupRegionName{nullptr, ""};
  44. uint16_t m_stackDepth = 0u;
  45. AZStd::sys_time_t m_startTick = 0;
  46. AZStd::sys_time_t m_endTick = 0;
  47. };
  48. using ThreadTimeRegionMap = AZStd::unordered_map<AZStd::string, AZStd::vector<CachedTimeRegion>>;
  49. using TimeRegionMap = AZStd::unordered_map<AZStd::thread_id, ThreadTimeRegionMap>;
  50. //! Thread local class to keep track of the thread's cached time regions.
  51. //! Each thread keeps track of its own time regions, which is communicated from the CpuProfiler.
  52. //! The CpuProfiler is able to request the cached time regions from the CpuTimingLocalStorage.
  53. class CpuTimingLocalStorage
  54. : public AZStd::intrusive_refcount<AZStd::atomic_uint>
  55. {
  56. friend class CpuProfiler;
  57. public:
  58. AZ_CLASS_ALLOCATOR(CpuTimingLocalStorage, AZ::SystemAllocator);
  59. CpuTimingLocalStorage();
  60. ~CpuTimingLocalStorage();
  61. private:
  62. // Maximum stack size
  63. static constexpr uint32_t TimeRegionStackSize = 2048u;
  64. // Adds a region to the stack, gets called each time a region begins
  65. void RegionStackPushBack(CachedTimeRegion& timeRegion);
  66. // Pops a region from the stack, gets called each time a region ends
  67. void RegionStackPopBack();
  68. // Add a new cached time region. If the stack is empty, flush all entries to the cached map
  69. void AddCachedRegion(const CachedTimeRegion& timeRegionCached);
  70. // Tries to flush the map to the passed parameter, only if the thread's mutex is unlocked
  71. void TryFlushCachedMap(ThreadTimeRegionMap& cachedRegionMap);
  72. // Clears m_cachedTimeRegions and resets m_cachedDataLimitReached flag.
  73. void ResetCachedData();
  74. AZStd::thread_id m_executingThreadId;
  75. // Keeps track of the current thread's stack depth
  76. uint32_t m_stackLevel = 0u;
  77. // Cached region map, will be flushed to the system's map when the system requests it
  78. ThreadTimeRegionMap m_cachedTimeRegionMap;
  79. // Use fixed vectors to avoid re-allocating new elements
  80. // Keeps track of the regions that added and removed using the macro
  81. AZStd::fixed_vector<CachedTimeRegion, TimeRegionStackSize> m_timeRegionStack;
  82. // Keeps track of regions that completed (i.e regions that was pushed and popped from the stack)
  83. // Intermediate storage point for the CachedTimeRegions, when the stack is empty, all entries will be
  84. // copied to the map.
  85. AZStd::fixed_vector<CachedTimeRegion, TimeRegionStackSize> m_cachedTimeRegions;
  86. AZStd::mutex m_cachedTimeRegionMutex;
  87. // Dirty flag which is set when the CpuProfiler's enabled state is set from false to true
  88. AZStd::atomic_bool m_clearContainers = false;
  89. // When the thread is terminated, it will flag itself for deletion
  90. AZStd::atomic_bool m_deleteFlag = false;
  91. // Keep track of the regions that have hit the size limit so we don't have to lock to check
  92. AZStd::map<AZStd::string, bool> m_hitSizeLimitMap;
  93. // Keeps track of the first time cached data limit was reached.
  94. bool m_cachedDataLimitReached = false;
  95. };
  96. //! CpuProfiler will keep track of the registered threads, and
  97. //! forwards the request to profile a region to the appropriate thread. The user is able to request all
  98. //! cached regions, which are stored on a per thread frequency.
  99. class CpuProfiler final
  100. : public AZ::Debug::Profiler
  101. , public AZ::SystemTickBus::Handler
  102. {
  103. friend class CpuTimingLocalStorage;
  104. public:
  105. AZ_RTTI(CpuProfiler, "{10E9D394-FC83-4B45-B2B8-807C6BF07BF0}", AZ::Debug::Profiler);
  106. AZ_CLASS_ALLOCATOR(CpuProfiler, AZ::SystemAllocator);
  107. CpuProfiler() = default;
  108. ~CpuProfiler() = default;
  109. //! Registers/un-registers the AZ::Debug::Profiler instance to the interface
  110. void Init();
  111. void Shutdown();
  112. //! AZ::Debug::Profiler overrides...
  113. void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, size_t eventNameArgCount, ...) final override;
  114. void EndRegion(const AZ::Debug::Budget* budget) final override;
  115. //! Get the last frame's TimeRegionMap
  116. const TimeRegionMap& GetTimeRegionMap() const;
  117. //! Starting/ending a multi-frame capture of profiling data
  118. bool BeginContinuousCapture();
  119. bool EndContinuousCapture(AZStd::ring_buffer<TimeRegionMap>& flushTarget);
  120. //! Check to see if a programmatic capture is currently in progress, implies
  121. //! that the profiler is active if returns True.
  122. bool IsContinuousCaptureInProgress() const;
  123. //! Getter/setter for the profiler active state
  124. void SetProfilerEnabled(bool enabled);
  125. bool IsProfilerEnabled() const;
  126. //! AZ::SystemTickBus::Handler overrides
  127. //! When fired, the profiler collects all profiling data from registered threads and updates
  128. //! m_timeRegionMap so that the next frame has up-to-date profiling data.
  129. void OnSystemTick() final override;
  130. private:
  131. static constexpr AZStd::size_t MaxFramesToSave = 2 * 60 * 120; // 2 minutes of 120fps
  132. static constexpr AZStd::size_t MaxRegionStringPoolSize = 16384; // Max amount of unique strings to save in the pool before throwing warnings.
  133. // Lazily create and register the local thread data
  134. void RegisterThreadStorage();
  135. // ThreadId -> ThreadTimeRegionMap
  136. // On the start of each frame, this map will be updated with the last frame's profiling data.
  137. TimeRegionMap m_timeRegionMap;
  138. // Set of registered threads when created
  139. AZStd::vector<AZStd::intrusive_ptr<CpuTimingLocalStorage>, AZ::OSStdAllocator> m_registeredThreads;
  140. AZStd::mutex m_threadRegisterMutex;
  141. // Thread local storage, gets lazily allocated when a thread is created
  142. static thread_local CpuTimingLocalStorage* ms_threadLocalStorage;
  143. // Enable/Disables the threads from profiling
  144. AZStd::atomic_bool m_enabled = false;
  145. // This lock will only be contested when the CpuProfiler's Shutdown() method has been called
  146. AZStd::shared_mutex m_shutdownMutex;
  147. bool m_initialized = false;
  148. AZStd::mutex m_continuousCaptureEndingMutex;
  149. AZStd::atomic_bool m_continuousCaptureInProgress = false;
  150. // Stores multiple frames of profiling data, size is controlled by MaxFramesToSave. Flushed when EndContinuousCapture is called.
  151. // Ring buffer so that we can have fast append of new data + removal of old profiling data with good cache locality.
  152. AZStd::ring_buffer<TimeRegionMap> m_continuousCaptureData;
  153. };
  154. // Intermediate class to serialize Cpu TimedRegion data.
  155. class CpuProfilingStatisticsSerializer
  156. {
  157. public:
  158. class CpuProfilingStatisticsSerializerEntry
  159. {
  160. public:
  161. AZ_TYPE_INFO(CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry, "{26B78F65-EB96-46E2-BE7E-A1233880B225}");
  162. static void Reflect(AZ::ReflectContext* context);
  163. CpuProfilingStatisticsSerializerEntry() = default;
  164. CpuProfilingStatisticsSerializerEntry(const CachedTimeRegion& cachedTimeRegion, AZStd::thread_id threadId);
  165. AZ::Name m_groupName;
  166. AZ::Name m_regionName;
  167. uint16_t m_stackDepth;
  168. AZStd::sys_time_t m_startTick;
  169. AZStd::sys_time_t m_endTick;
  170. size_t m_threadId;
  171. };
  172. AZ_TYPE_INFO(CpuProfilingStatisticsSerializer, "{D5B02946-0D27-474F-9A44-364C2706DD41}");
  173. static void Reflect(AZ::ReflectContext* context);
  174. CpuProfilingStatisticsSerializer() = default;
  175. CpuProfilingStatisticsSerializer(const AZStd::ring_buffer<TimeRegionMap>& continuousData);
  176. AZStd::vector<CpuProfilingStatisticsSerializerEntry> m_cpuProfilingStatisticsSerializerEntries;
  177. AZStd::sys_time_t m_timeTicksPerSecond = 0;
  178. };
  179. } // namespace Profiler