ProfilerSystemComponent.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. #include <ProfilerSystemComponent.h>
  9. #include <AzCore/RTTI/BehaviorContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/EditContextConstants.inl>
  12. #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
  13. #include <AzCore/Serialization/Json/JsonUtils.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. namespace Profiler
  16. {
  17. static constexpr AZ::Crc32 profilerServiceCrc = AZ_CRC_CE("ProfilerService");
  18. struct DelayedFunction
  19. {
  20. using func_type = AZStd::function<void()>;
  21. DelayedFunction(int framesToDelay, func_type&& function)
  22. : m_function(AZStd::move(function))
  23. , m_framesLeft(framesToDelay)
  24. {
  25. }
  26. void Run()
  27. {
  28. if (--m_framesLeft <= 0)
  29. {
  30. m_function();
  31. }
  32. else
  33. {
  34. AZ::SystemTickBus::QueueFunction(
  35. [](DelayedFunction&& delayedFunc)
  36. {
  37. delayedFunc.Run();
  38. },
  39. AZStd::move(*this)
  40. );
  41. }
  42. }
  43. func_type m_function;
  44. int m_framesLeft{ 0 };
  45. };
  46. bool SerializeCpuProfilingData(const AZStd::ring_buffer<TimeRegionMap>& data, AZStd::string outputFilePath, bool wasEnabled)
  47. {
  48. AZ_TracePrintf("ProfilerSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size());
  49. AZ::JsonSerializerSettings serializationSettings;
  50. serializationSettings.m_keepDefaults = true;
  51. CpuProfilingStatisticsSerializer serializer(data);
  52. const auto saveResult = AZ::JsonSerializationUtils::SaveObjectToFile(&serializer,
  53. outputFilePath, (CpuProfilingStatisticsSerializer*)nullptr, &serializationSettings);
  54. AZStd::string captureInfo = outputFilePath;
  55. if (!saveResult.IsSuccess())
  56. {
  57. captureInfo = AZStd::string::format("Failed to save Cpu Profiling Statistics data to file '%s'. Error: %s",
  58. outputFilePath.c_str(),
  59. saveResult.GetError().c_str());
  60. AZ_Warning("ProfilerSystemComponent", false, captureInfo.c_str());
  61. }
  62. else
  63. {
  64. AZ_Printf("ProfilerSystemComponent", "Cpu profiling statistics was saved to file [%s]\n", outputFilePath.c_str());
  65. }
  66. // Disable the profiler again
  67. if (!wasEnabled)
  68. {
  69. AZ::Debug::ProfilerSystemInterface::Get()->SetActive(false);
  70. }
  71. // Notify listeners that the profiler capture has finished.
  72. AZ::Debug::ProfilerNotificationBus::Broadcast(&AZ::Debug::ProfilerNotificationBus::Events::OnCaptureFinished,
  73. saveResult.IsSuccess(),
  74. captureInfo);
  75. return saveResult.IsSuccess();
  76. }
  77. void ProfilerSystemComponent::Reflect(AZ::ReflectContext* context)
  78. {
  79. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  80. {
  81. serialize->Class<ProfilerSystemComponent, AZ::Component>()
  82. ->Version(0);
  83. if (AZ::EditContext* ec = serialize->GetEditContext())
  84. {
  85. ec->Class<ProfilerSystemComponent>("Profiler", "Provides a custom implementation of the AZ::Debug::Profiler interface for capturing performance data")
  86. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  87. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  88. }
  89. }
  90. CpuProfilingStatisticsSerializer::Reflect(context);
  91. }
  92. void ProfilerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  93. {
  94. provided.push_back(profilerServiceCrc);
  95. }
  96. void ProfilerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  97. {
  98. incompatible.push_back(profilerServiceCrc);
  99. }
  100. void ProfilerSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
  101. {
  102. }
  103. void ProfilerSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
  104. {
  105. }
  106. ProfilerSystemComponent::ProfilerSystemComponent()
  107. {
  108. if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
  109. {
  110. AZ::Debug::ProfilerSystemInterface::Register(this);
  111. }
  112. }
  113. ProfilerSystemComponent::~ProfilerSystemComponent()
  114. {
  115. if (AZ::Debug::ProfilerSystemInterface::Get() == this)
  116. {
  117. AZ::Debug::ProfilerSystemInterface::Unregister(this);
  118. }
  119. }
  120. void ProfilerSystemComponent::Activate()
  121. {
  122. m_cpuProfiler.Init();
  123. }
  124. void ProfilerSystemComponent::Deactivate()
  125. {
  126. m_cpuProfiler.Shutdown();
  127. // Block deactivation until the IO thread has finished serializing the CPU data
  128. if (m_cpuDataSerializationThread.joinable())
  129. {
  130. m_cpuDataSerializationThread.join();
  131. }
  132. }
  133. bool ProfilerSystemComponent::IsActive() const
  134. {
  135. return m_cpuProfiler.IsProfilerEnabled();
  136. }
  137. void ProfilerSystemComponent::SetActive(bool enabled)
  138. {
  139. m_cpuProfiler.SetProfilerEnabled(enabled);
  140. }
  141. bool ProfilerSystemComponent::CaptureFrame(const AZStd::string& outputFilePath)
  142. {
  143. bool expected = false;
  144. if (!m_cpuCaptureInProgress.compare_exchange_strong(expected, true))
  145. {
  146. return false;
  147. }
  148. // Start the cpu profiling
  149. bool wasEnabled = m_cpuProfiler.IsProfilerEnabled();
  150. if (!wasEnabled)
  151. {
  152. m_cpuProfiler.SetProfilerEnabled(true);
  153. }
  154. const int frameDelay = 5; // arbitrary number
  155. DelayedFunction delayedFunc(frameDelay,
  156. [this, outputFilePath, wasEnabled]()
  157. {
  158. // Blocking call for a single frame of data, avoid thread overhead
  159. AZStd::ring_buffer<TimeRegionMap> singleFrameData(1);
  160. singleFrameData.push_back(m_cpuProfiler.GetTimeRegionMap());
  161. SerializeCpuProfilingData(singleFrameData, outputFilePath, wasEnabled);
  162. m_cpuCaptureInProgress.store(false);
  163. }
  164. );
  165. delayedFunc.Run();
  166. return true;
  167. }
  168. bool ProfilerSystemComponent::StartCapture(AZStd::string outputFilePath)
  169. {
  170. m_captureFile = AZStd::move(outputFilePath);
  171. return m_cpuProfiler.BeginContinuousCapture();
  172. }
  173. bool ProfilerSystemComponent::EndCapture()
  174. {
  175. bool expected = false;
  176. if (!m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true))
  177. {
  178. AZ_TracePrintf(
  179. "ProfilerSystemComponent",
  180. "Cannot end a continuous capture - another serialization is currently in progress\n");
  181. return false;
  182. }
  183. AZStd::ring_buffer<TimeRegionMap> captureResult;
  184. const bool captureEnded = m_cpuProfiler.EndContinuousCapture(captureResult);
  185. if (!captureEnded)
  186. {
  187. AZ_TracePrintf("ProfilerSystemComponent", "Could not end the continuous capture, is one in progress?\n");
  188. m_cpuDataSerializationInProgress.store(false);
  189. return false;
  190. }
  191. // cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk.
  192. auto threadIoFunction =
  193. [data = AZStd::move(captureResult), filePath = m_captureFile, &flag = m_cpuDataSerializationInProgress]()
  194. {
  195. SerializeCpuProfilingData(data, filePath, true);
  196. flag.store(false);
  197. };
  198. // If the thread object already exists (ex. we have already serialized data), join. This will not block since
  199. // m_cpuDataSerializationInProgress was false, meaning the IO thread has already completed execution.
  200. if (m_cpuDataSerializationThread.joinable())
  201. {
  202. m_cpuDataSerializationThread.join();
  203. }
  204. auto thread = AZStd::thread(threadIoFunction);
  205. m_cpuDataSerializationThread = AZStd::move(thread);
  206. return true;
  207. }
  208. bool ProfilerSystemComponent::IsCaptureInProgress() const
  209. {
  210. return m_cpuProfiler.IsContinuousCaptureInProgress();
  211. }
  212. } // namespace Profiler