StreamerProfilerSystemComponent.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  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 <StreamerProfilerSystemComponent.h>
  9. #include <AzCore/Casting/lossy_cast.h>
  10. #include <AzCore/Casting/numeric_cast.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Serialization/EditContext.h>
  13. #include <AzCore/Serialization/EditContextConstants.inl>
  14. #include <AzCore/IO/IStreamer.h>
  15. #include <AzCore/IO/Path/Path.h>
  16. #include <AzCore/IO/Streamer/FileRequest.h>
  17. #if defined(IMGUI_ENABLED)
  18. #include <imgui/imgui.h>
  19. #endif
  20. namespace Streamer
  21. {
  22. StreamerProfilerSystemComponent::GraphStore::GraphStore()
  23. {
  24. memset(m_values.data(), 0, sizeof(float) * GraphStoreElementCount);
  25. }
  26. StreamerProfilerSystemComponent::GraphStore::GraphStore(float minValue, float maxValue)
  27. : m_minValue(minValue)
  28. , m_maxValue(maxValue)
  29. {
  30. AZ_Assert(
  31. minValue <= maxValue,
  32. "A GraphStore object in the Streamer Profiler received a min value (%f) that's not smaller or equal to the max value (%f).",
  33. minValue, maxValue);
  34. memset(m_values.data(), 0, sizeof(float) * GraphStoreElementCount);
  35. }
  36. void StreamerProfilerSystemComponent::GraphStore::AddValue(float value)
  37. {
  38. m_minValue = AZStd::min(value, m_minValue);
  39. m_maxValue = AZStd::max(value, m_maxValue);
  40. m_values[m_front] = value;
  41. m_front = (m_front + 1) & (GraphStoreElementCount - 1);
  42. }
  43. float StreamerProfilerSystemComponent::GraphStore::operator[](size_t index)
  44. {
  45. return m_values[(index + m_front) & (GraphStoreElementCount - 1)];
  46. }
  47. float StreamerProfilerSystemComponent::GraphStore::GetMin() const
  48. {
  49. return m_minValue;
  50. }
  51. float StreamerProfilerSystemComponent::GraphStore::GetMax() const
  52. {
  53. return m_maxValue;
  54. }
  55. void StreamerProfilerSystemComponent::Reflect(AZ::ReflectContext* context)
  56. {
  57. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  58. {
  59. serialize->Class<StreamerProfilerSystemComponent, AZ::Component>()
  60. ->Version(0)
  61. ;
  62. if (AZ::EditContext* ec = serialize->GetEditContext())
  63. {
  64. ec->Class<StreamerProfilerSystemComponent>("Streamer Profiler", "Provides profiling visualization for AZ::IO::Streamer.")
  65. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  66. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  67. ;
  68. }
  69. }
  70. }
  71. void StreamerProfilerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  72. {
  73. provided.push_back(AZ_CRC_CE("StreamerProfilerService"));
  74. }
  75. void StreamerProfilerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  76. {
  77. incompatible.push_back(AZ_CRC_CE("StreamerProfilerService"));
  78. }
  79. void StreamerProfilerSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
  80. {
  81. }
  82. void StreamerProfilerSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
  83. {
  84. }
  85. StreamerProfilerSystemComponent::StreamerProfilerSystemComponent()
  86. {
  87. if (AZ::IO::StreamerProfiler::Get() == nullptr)
  88. {
  89. AZ::IO::StreamerProfiler::Register(this);
  90. }
  91. }
  92. StreamerProfilerSystemComponent::~StreamerProfilerSystemComponent()
  93. {
  94. if (AZ::IO::StreamerProfiler::Get() == this)
  95. {
  96. AZ::IO::StreamerProfiler::Unregister(this);
  97. }
  98. }
  99. void StreamerProfilerSystemComponent::Init()
  100. {
  101. }
  102. void StreamerProfilerSystemComponent::Activate()
  103. {
  104. }
  105. void StreamerProfilerSystemComponent::Deactivate()
  106. {
  107. }
  108. void StreamerProfilerSystemComponent::DrawStatistics([[maybe_unused]] bool& keepDrawing)
  109. {
  110. #if defined(IMGUI_ENABLED)
  111. ImGui::SetNextWindowSize({1024.0f, 800.0f}, ImGuiCond_Once);
  112. if (ImGui::Begin("File IO Profiler", &keepDrawing, ImGuiWindowFlags_None))
  113. {
  114. if (auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get(); streamer)
  115. {
  116. if (ImGui::CollapsingHeader("Hardware Info"))
  117. {
  118. DrawHardwareInfo(*streamer);
  119. }
  120. else
  121. {
  122. DrawToolTip("Lists the lowest hardware specs across all used hardware. The presented information are recommendations "
  123. "to consider for various use cases. The information in the live stats will refer to these values.");
  124. }
  125. if (ImGui::CollapsingHeader("Stack configuration"))
  126. {
  127. DrawStackConfiguration(*streamer);
  128. }
  129. else
  130. {
  131. DrawToolTip("The configuration of the Streamer stack. These are the nodes that process requests or provide information "
  132. "to the scheduler to schedule requests. Requests are added to the top of the stack and the move down the "
  133. "stack or are completed early if possible.");
  134. }
  135. if (ImGui::CollapsingHeader("Live stats", ImGuiTreeNodeFlags_DefaultOpen))
  136. {
  137. DrawLiveStats(*streamer);
  138. }
  139. else
  140. {
  141. DrawToolTip("The live metrics retrieved from all parts of Streamer. These contain all information that can be "
  142. "retrieved without issuing requests to Streamer. As such these values should be viewed as coming from a "
  143. "sampling profiler, which means not all changes in the values are captured. Note though that a large "
  144. "number of values are recorded inside Streamer using a sliding window so this limitation typically doesn't"
  145. "impact the ability to retrieve meaningful information.");
  146. }
  147. if (ImGui::CollapsingHeader("File locks"))
  148. {
  149. DrawFileLocks(*streamer);
  150. }
  151. else
  152. {
  153. DrawToolTip("A list of all the files that are locked by Streamer and by what node. Retrieving this information "
  154. "requires repeatedly issuing requests with Streamer, which will show up in the live stats.");
  155. }
  156. }
  157. }
  158. #endif // #if defined(IMGUI_ENABLED)
  159. }
  160. void StreamerProfilerSystemComponent::DrawLiveStats([[maybe_unused]] AZ::IO::IStreamer& streamer)
  161. {
  162. #if defined(IMGUI_ENABLED)
  163. if (ImGui::Button("Reset graphs"))
  164. {
  165. m_graphInfo.clear();
  166. }
  167. ImGui::SameLine();
  168. bool suspend = streamer.IsSuspended();
  169. if (ImGui::Checkbox("Suspend", &suspend))
  170. {
  171. if (suspend)
  172. {
  173. streamer.SuspendProcessing();
  174. }
  175. else
  176. {
  177. streamer.ResumeProcessing();
  178. }
  179. }
  180. if (ImGui::BeginTable("Stats", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable))
  181. {
  182. streamer.CollectStatistics(m_stats);
  183. ImGui::TableSetupColumn("Owner", ImGuiTableColumnFlags_WidthStretch, 0.15f);
  184. ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.15f);
  185. ImGui::TableSetupColumn("Graph", ImGuiTableColumnFlags_WidthFixed, 0.50f);
  186. ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.20f);
  187. ImGui::TableHeadersRow();
  188. for (AZ::IO::Statistic& stat : m_stats)
  189. {
  190. ImGui::TableNextRow();
  191. ImGui::TableNextColumn();
  192. ImGui::Text("%.*s", AZ_STRING_ARG(stat.GetOwner()));
  193. ImGui::TableNextColumn();
  194. ImGui::Text("%.*s", AZ_STRING_ARG(stat.GetName()));
  195. if (!stat.GetDescription().empty())
  196. {
  197. DrawToolTip(stat.GetDescription());
  198. }
  199. float minValue = AZStd::numeric_limits<float>::max();
  200. float maxValue = AZStd::numeric_limits<float>::min();
  201. ImGui::TableNextColumn();
  202. if (stat.GetGraphType() != AZ::IO::Statistic::GraphType::None)
  203. {
  204. FullStatName fullStatName;
  205. fullStatName += stat.GetOwner();
  206. fullStatName += '.';
  207. fullStatName += stat.GetName();
  208. auto graphIt = m_graphInfo.find(fullStatName);
  209. if (graphIt == m_graphInfo.end())
  210. {
  211. graphIt = m_graphInfo.emplace(AZStd::move(fullStatName), CreateGraph(stat.GetValue())).first;
  212. }
  213. else
  214. {
  215. minValue = graphIt->second.GetMin();
  216. maxValue = graphIt->second.GetMax();
  217. }
  218. DrawGraph(stat.GetValue(), graphIt->second, stat.GetGraphType() == AZ::IO::Statistic::GraphType::Histogram);
  219. }
  220. ImGui::TableNextColumn();
  221. DrawStatisticValue(stat.GetValue(), minValue, maxValue);
  222. }
  223. ImGui::EndTable();
  224. m_stats.clear();
  225. }
  226. #endif
  227. }
  228. void StreamerProfilerSystemComponent::DrawHardwareInfo([[maybe_unused]] AZ::IO::IStreamer& streamer)
  229. {
  230. #if defined(IMGUI_ENABLED)
  231. if (ImGui::BeginTable("Hardware", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable))
  232. {
  233. const AZ::IO::IStreamerTypes::Recommendations& recommendations = streamer.GetRecommendations();
  234. ImGui::TableNextColumn();
  235. ImGui::Text("Memory alignment");
  236. DrawToolTip("The minimal memory alignment that's required to avoid intermediate buffers. If the memory provided "
  237. "to the read request isn't aligned to this size it may require a temporary or cached buffer to "
  238. "first read to and copy the result from to the provided memory.");
  239. ImGui::TableNextColumn();
  240. ImGui::Text("%llu bytes", recommendations.m_memoryAlignment);
  241. ImGui::TableNextRow();
  242. ImGui::TableNextColumn();
  243. ImGui::Text("Size alignment");
  244. DrawToolTip("The minimal size alignment that's required to avoid intermediate buffers. If the size and/or "
  245. "offset provided to the read request isn't aligned to this size it may require a temporary or "
  246. "cached buffer to first read to and copy the result from to the provided memory.");
  247. ImGui::TableNextColumn();
  248. ImGui::Text("%llu bytes", recommendations.m_sizeAlignment);
  249. ImGui::TableNextRow();
  250. ImGui::TableNextColumn();
  251. ImGui::Text("Granularity");
  252. DrawToolTip("The recommended size for partial reads. It's recommended to read entire files at once, but for "
  253. "streaming systems such as video and audio this is not always practical. The granularity will give "
  254. "the most optimal size for partial file reads. Note for partial reads it's also recommended to "
  255. "store the data uncompressed and to align the offset of the rest to the granularity.");
  256. ImGui::TableNextColumn();
  257. ImGui::Text("%.2f kilobytes (%llu bytes)", (recommendations.m_granularity / 1024.0f), recommendations.m_granularity);
  258. ImGui::TableNextRow();
  259. ImGui::TableNextColumn();
  260. ImGui::Text("Max concurrent reads");
  261. DrawToolTip(
  262. "The number of requests that the scheduler will try to keep active in the stack. Additional requests are considered "
  263. "pending and are subject to scheduling. There are no restrictions on the number of requests that can be send and "
  264. "generally there is no need to throttle the number of requests. The exception is for streaming systems such as video "
  265. "and audio that could flood the scheduler with requests in a short amount of time if not capped. For those systems "
  266. "it's recommended that no more than the provided number of requests are issued.");
  267. ImGui::TableNextColumn();
  268. ImGui::Text("%d", recommendations.m_maxConcurrentRequests);
  269. ImGui::TableNextRow();
  270. ImGui::EndTable();
  271. }
  272. #endif //#if defined(IMGUI_ENABLED)
  273. }
  274. void StreamerProfilerSystemComponent::DrawStackConfiguration([[maybe_unused]] AZ::IO::IStreamer& streamer)
  275. {
  276. #if defined(IMGUI_ENABLED)
  277. if (!m_stackConfigurationAvailable)
  278. {
  279. AZ::IO::FileRequestPtr request = streamer.Report(m_stackConfiguration, AZ::IO::IStreamerTypes::ReportType::Config);
  280. auto callback = [this](AZ::IO::FileRequestHandle)
  281. {
  282. m_stackConfigurationAvailable = true;
  283. };
  284. streamer.SetRequestCompleteCallback(request, AZStd::move(callback));
  285. streamer.QueueRequest(request);
  286. }
  287. else
  288. {
  289. if (ImGui::BeginTable("Stack configuration", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable))
  290. {
  291. ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_WidthStretch);
  292. ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
  293. ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
  294. ImGui::TableHeadersRow();
  295. AZStd::string_view currentNode = "";
  296. for (AZ::IO::Statistic& stat : m_stackConfiguration)
  297. {
  298. ImGui::TableNextRow();
  299. ImGui::TableNextColumn();
  300. if (currentNode != stat.GetOwner())
  301. {
  302. ImGui::Text("%.*s", AZ_STRING_ARG(stat.GetOwner()));
  303. currentNode = stat.GetOwner();
  304. ImGui::TableNextRow();
  305. ImGui::TableNextColumn();
  306. }
  307. ImGui::TableNextColumn();
  308. ImGui::Text("%.*s", AZ_STRING_ARG(stat.GetName()));
  309. if (!stat.GetDescription().empty())
  310. {
  311. DrawToolTip(stat.GetDescription());
  312. }
  313. ImGui::TableNextColumn();
  314. DrawStatisticValue(stat.GetValue());
  315. }
  316. ImGui::EndTable();
  317. }
  318. }
  319. #endif // #if defined(IMGUI_ENABLED)
  320. }
  321. void StreamerProfilerSystemComponent::DrawFileLocks([[maybe_unused]] AZ::IO::IStreamer& streamer)
  322. {
  323. #if defined(IMGUI_ENABLED)
  324. // Queue next request for update if needed.
  325. if (m_readingFileLocks)
  326. {
  327. AZ::IO::FileRequestPtr request = streamer.Report(*m_readingFileLocks, AZ::IO::IStreamerTypes::ReportType::FileLocks);
  328. auto callback = [this, readingFileLocks = m_readingFileLocks](AZ::IO::FileRequestHandle)
  329. {
  330. // Once the request has been completed, enable the stats to draw.
  331. StatsContainer* displayingLocks = nullptr;
  332. while (!m_transferFileLocks.compare_exchange_strong(displayingLocks, readingFileLocks))
  333. {
  334. displayingLocks = nullptr;
  335. }
  336. };
  337. streamer.SetRequestCompleteCallback(request, AZStd::move(callback));
  338. streamer.QueueRequest(request);
  339. m_readingFileLocks = nullptr;
  340. }
  341. // Try to get a handle to the fresh stats.
  342. StatsContainer* transferFileLocks = m_transferFileLocks;
  343. while (!m_transferFileLocks.compare_exchange_strong(transferFileLocks, nullptr))
  344. {
  345. transferFileLocks = m_transferFileLocks;
  346. }
  347. if (transferFileLocks)
  348. {
  349. // Rotate the buffers
  350. m_displayingFileLocks->clear();
  351. m_readingFileLocks = m_displayingFileLocks;
  352. m_displayingFileLocks = transferFileLocks;
  353. }
  354. // Draw the display list. One should always be assigned.
  355. AZ_Assert(m_displayingFileLocks, "Expected to always have a valid list of file locks, even if it's empty.");
  356. ImGui::Text("Total file lock count: %zu", m_displayingFileLocks->size());
  357. if (ImGui::Button("Flush all"))
  358. {
  359. streamer.QueueRequest(streamer.FlushCaches());
  360. }
  361. if (ImGui::BeginTable("File Locks", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable))
  362. {
  363. ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_WidthStretch, 0.20f);
  364. ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch, 0.70f);
  365. ImGui::TableSetupColumn("Flush", ImGuiTableColumnFlags_WidthStretch, 0.10f);
  366. ImGui::TableHeadersRow();
  367. for (AZ::IO::Statistic& stat : *m_displayingFileLocks)
  368. {
  369. ImGui::TableNextRow();
  370. ImGui::TableNextColumn();
  371. ImGui::Text("%.*s", AZ_STRING_ARG(stat.GetOwner()));
  372. ImGui::TableNextColumn();
  373. DrawStatisticValue(stat.GetValue());
  374. const AZStd::string* path = AZStd::get_if<AZStd::string>(&stat.GetValue());
  375. if (path)
  376. {
  377. constexpr static size_t StringCacheSize = AZ::IO::MaxPathLength + 7; // +7 for the characters in "Flush##".
  378. ImGui::TableNextColumn();
  379. AZStd::fixed_string<StringCacheSize> name = "Flush##";
  380. size_t remainingStringSpace = StringCacheSize - name.length() - 1;
  381. if (path->length() < remainingStringSpace)
  382. {
  383. name += *path;
  384. }
  385. else
  386. {
  387. name += AZStd::string_view(*path).substr(path->length() - remainingStringSpace);
  388. }
  389. if (ImGui::Button(name.c_str()))
  390. {
  391. streamer.QueueRequest(streamer.FlushCache(*path));
  392. }
  393. }
  394. }
  395. ImGui::EndTable();
  396. }
  397. #endif // #if defined(IMGUI_ENABLED)
  398. }
  399. void StreamerProfilerSystemComponent::DrawGraph(
  400. [[maybe_unused]] const AZ::IO::Statistic::Value& value, [[maybe_unused]] GraphStore& values, [[maybe_unused]] bool useHistogram)
  401. {
  402. #if defined(IMGUI_ENABLED)
  403. auto visitor = [&values](auto&& value)
  404. {
  405. using Type = AZStd::decay_t<decltype(value)>;
  406. if constexpr (AZStd::is_same_v<Type, bool>)
  407. {
  408. values.AddValue(value ? 1.0f : 0.0f);
  409. }
  410. else if constexpr (AZStd::is_same_v<Type, double> || AZStd::is_same_v<Type, AZ::s64>)
  411. {
  412. values.AddValue(azlossy_cast<float>(value));
  413. }
  414. else if constexpr (
  415. AZStd::is_same_v<Type, AZ::IO::Statistic::FloatRange> || AZStd::is_same_v<Type, AZ::IO::Statistic::IntegerRange> ||
  416. AZStd::is_same_v<Type, AZ::IO::Statistic::Percentage> || AZStd::is_same_v<Type, AZ::IO::Statistic::PercentageRange> ||
  417. AZStd::is_same_v<Type, AZ::IO::Statistic::ByteSize> || AZStd::is_same_v<Type, AZ::IO::Statistic::ByteSizeRange> ||
  418. AZStd::is_same_v<Type, AZ::IO::Statistic::BytesPerSecond>)
  419. {
  420. values.AddValue(azlossy_cast<float>(value.m_value));
  421. }
  422. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::Time> || AZStd::is_same_v<Type, AZ::IO::Statistic::TimeRange>)
  423. {
  424. values.AddValue(azlossy_cast<float>(value.m_value.count()));
  425. }
  426. };
  427. AZStd::visit(visitor, value);
  428. auto valueRetriever = [](void* data, int idx) -> float
  429. {
  430. return (*reinterpret_cast<GraphStore*>(data))[idx];
  431. };
  432. if (useHistogram)
  433. {
  434. ImGui::PlotHistogram("", valueRetriever, &values, GraphStoreElementCount, 0, nullptr, values.GetMin(), values.GetMax());
  435. }
  436. else
  437. {
  438. ImGui::PlotLines("", valueRetriever, &values, GraphStoreElementCount, 0, nullptr, values.GetMin(), values.GetMax());
  439. }
  440. #endif // #if defined(IMGUI_ENABLED)
  441. }
  442. void StreamerProfilerSystemComponent::DrawStatisticValue(
  443. [[maybe_unused]] const AZ::IO::Statistic::Value& value, [[maybe_unused]] float capturedMin, [[maybe_unused]] float capturedMax)
  444. {
  445. #if defined(IMGUI_ENABLED)
  446. auto visitor = [capturedMin, capturedMax](auto&& value)
  447. {
  448. using Type = AZStd::decay_t<decltype(value)>;
  449. if constexpr (AZStd::is_same_v<Type, bool>)
  450. {
  451. static ImColor colorEnabled = ImColor(0.0f, 1.0f, 0.0f, 0.75f);
  452. static ImColor colorDisabled = ImColor(1.0f, 0.0f, 0.0f, 0.75f);
  453. ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, value ? colorEnabled : colorDisabled);
  454. ImGui::Text(value ? "True" : "False");
  455. }
  456. else if constexpr (AZStd::is_same_v<Type, double>)
  457. {
  458. ImGui::Text("%.2f", value);
  459. DrawToolTipV("Min: %.2f\nMax: %.2f", capturedMin, capturedMax);
  460. }
  461. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::FloatRange>)
  462. {
  463. if (value.m_min != AZStd::numeric_limits<decltype(value.m_min)>::max() &&
  464. value.m_max != AZStd::numeric_limits<decltype(value.m_max)>::min())
  465. {
  466. ImGui::Text("%.2f", value.m_value);
  467. DrawToolTipV("Min: %.2f\nMax: %.2f", value.m_min, value.m_max);
  468. }
  469. else
  470. {
  471. ImGui::Text("Unused");
  472. }
  473. }
  474. else if constexpr (AZStd::is_same_v<Type, AZ::s64>)
  475. {
  476. ImGui::Text("%lld", value);
  477. DrawToolTipV("Min: %i\nMax: %i", azlossy_cast<int>(capturedMin), azlossy_cast<int>(capturedMax));
  478. }
  479. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::IntegerRange>)
  480. {
  481. if (value.m_min != AZStd::numeric_limits<decltype(value.m_min)>::max() &&
  482. value.m_max != AZStd::numeric_limits<decltype(value.m_max)>::min())
  483. {
  484. ImGui::Text("%lld", value.m_value);
  485. DrawToolTipV("Min: %lld\nMax: %lld", value.m_min, value.m_max);
  486. }
  487. else
  488. {
  489. ImGui::Text("Unused");
  490. }
  491. }
  492. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::Percentage>)
  493. {
  494. ImGui::ProgressBar(aznumeric_cast<float>(value.m_value));
  495. }
  496. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::PercentageRange>)
  497. {
  498. ImGui::ProgressBar(aznumeric_cast<float>(value.m_value));
  499. DrawToolTipV("Min: %.2f%%\nMax: %.2f%%", value.m_min * 100.0, value.m_max * 100.0);
  500. }
  501. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::ByteSize>)
  502. {
  503. AZStd::fixed_string<256> text;
  504. AppendByteSize(text, value.m_value);
  505. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  506. text.clear();
  507. text += "Min: ";
  508. AppendByteSize(text, azlossy_cast<AZ::u64>(capturedMin));
  509. text += "\nMax: ";
  510. AppendByteSize(text, azlossy_cast<AZ::u64>(capturedMax));
  511. DrawToolTip(text);
  512. }
  513. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::ByteSizeRange>)
  514. {
  515. if (value.m_min != AZStd::numeric_limits<decltype(value.m_min)>::max() &&
  516. value.m_max != AZStd::numeric_limits<decltype(value.m_max)>::min())
  517. {
  518. AZStd::fixed_string<256> text;
  519. AppendByteSize(text, value.m_value);
  520. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  521. text.clear();
  522. text += "Min: ";
  523. AppendByteSize(text, value.m_min);
  524. text += "\nMax: ";
  525. AppendByteSize(text, value.m_max);
  526. DrawToolTip(text);
  527. }
  528. else
  529. {
  530. ImGui::Text("Unused");
  531. }
  532. }
  533. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::Time>)
  534. {
  535. AZStd::fixed_string<256> text;
  536. AppendTime(text, value.m_value);
  537. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  538. text.clear();
  539. text += "Min: ";
  540. AppendTime(text, AZ::IO::Statistic::TimeValue{ azlossy_cast<AZ::s64>(capturedMin) });
  541. text += "\nMax: ";
  542. AppendTime(text, AZ::IO::Statistic::TimeValue{ azlossy_cast<AZ::s64>(capturedMax) });
  543. DrawToolTip(text);
  544. }
  545. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::TimeRange>)
  546. {
  547. if (value.m_min != AZ::IO::Statistic::TimeValue::max() && value.m_max != AZ::IO::Statistic::TimeValue::min())
  548. {
  549. AZStd::fixed_string<256> text;
  550. AppendTime(text, value.m_value);
  551. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  552. text.clear();
  553. text += "Min: ";
  554. AppendTime(text, value.m_min);
  555. text += "\nMax: ";
  556. AppendTime(text, value.m_max);
  557. DrawToolTip(text);
  558. }
  559. else
  560. {
  561. ImGui::Text("Unused");
  562. }
  563. }
  564. else if constexpr (AZStd::is_same_v<Type, AZ::IO::Statistic::BytesPerSecond>)
  565. {
  566. AZStd::fixed_string<256> text;
  567. AppendBytesPerSecond(text, value.m_value);
  568. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  569. text.clear();
  570. text += "Min: ";
  571. AppendBytesPerSecond(text, capturedMin);
  572. text += "\nMax: ";
  573. AppendBytesPerSecond(text, capturedMax);
  574. DrawToolTip(text);
  575. }
  576. else if constexpr (AZStd::is_same_v<Type, AZStd::string> || AZStd::is_same_v<Type, AZStd::string_view>)
  577. {
  578. ImGui::TextUnformatted(value.begin(), value.end());
  579. }
  580. };
  581. AZStd::visit(visitor, value);
  582. #endif // #if defined(IMGUI_ENABLED)
  583. }
  584. void StreamerProfilerSystemComponent::AppendByteSize(AZStd::fixed_string<256>& text, AZ::u64 value)
  585. {
  586. static constexpr AZ::u64 kilobyte = 1024;
  587. static constexpr AZ::u64 megabyte = 1024 * kilobyte;
  588. static constexpr AZ::u64 gigabyte = 1024 * megabyte;
  589. static constexpr AZ::u64 terabyte = 1024 * gigabyte;
  590. if (value > terabyte)
  591. {
  592. text += AZStd::fixed_string<64>::format("%.2f terabytes (%llu bytes)", value * (1.0f / terabyte), value);
  593. }
  594. else if (value > gigabyte)
  595. {
  596. text += AZStd::fixed_string<64>::format("%.2f gigabytes (%llu bytes)", value * (1.0f / gigabyte), value);
  597. }
  598. else if (value > megabyte)
  599. {
  600. text += AZStd::fixed_string<64>::format("%.2f megabytes (%llu bytes)", value * (1.0f / megabyte), value);
  601. }
  602. else if (value > kilobyte)
  603. {
  604. text += AZStd::fixed_string<32>::format("%.2f kilobytes (%llu bytes)", value * (1.0f / kilobyte), value);
  605. }
  606. else
  607. {
  608. text += AZStd::fixed_string<16>::format("%llu bytes", value);
  609. }
  610. }
  611. void StreamerProfilerSystemComponent::AppendTime(AZStd::fixed_string<256>& text, AZ::IO::Statistic::TimeValue value)
  612. {
  613. static constexpr AZ::s64 microseconds = 1000;
  614. static constexpr AZ::s64 milliseconds = microseconds * 1000;
  615. static constexpr AZ::s64 seconds = milliseconds * 1000;
  616. static constexpr AZ::s64 minutes = 60 * seconds;
  617. static constexpr AZ::s64 hours = 60 * minutes;
  618. auto count = value.count();
  619. if (count > hours)
  620. {
  621. text += AZStd::fixed_string<32>::format("%.2f hours", count * (1.0 / hours));
  622. }
  623. else if (count > minutes)
  624. {
  625. text += AZStd::fixed_string<32>::format("%.2f minutes", count * (1.0 / minutes));
  626. }
  627. else if (count > seconds)
  628. {
  629. text += AZStd::fixed_string<32>::format("%.2f seconds", count * (1.0 / seconds));
  630. }
  631. else if (count > milliseconds)
  632. {
  633. text += AZStd::fixed_string<32>::format("%.2f milliseconds", count * (1.0 / milliseconds));
  634. }
  635. else if (count > microseconds)
  636. {
  637. text += AZStd::fixed_string<32>::format("%.2f microseconds", count * (1.0 / microseconds));
  638. }
  639. else
  640. {
  641. text += AZStd::fixed_string<32>::format("%lld nanoseconds", static_cast<long long>(count));
  642. }
  643. }
  644. void StreamerProfilerSystemComponent::AppendBytesPerSecond(AZStd::fixed_string<256>& text, double value)
  645. {
  646. static constexpr AZ::u64 kilobyte = 1024;
  647. static constexpr AZ::u64 megabyte = 1024 * kilobyte;
  648. static constexpr AZ::u64 gigabyte = 1024 * megabyte;
  649. static constexpr AZ::u64 terabyte = 1024 * gigabyte;
  650. if (value > terabyte)
  651. {
  652. text += AZStd::fixed_string<64>::format("%.2f terabytes per second", value * (1.0 / terabyte));
  653. }
  654. else if (value > gigabyte)
  655. {
  656. text += AZStd::fixed_string<64>::format("%.2f gigabytes per second", value * (1.0 / gigabyte));
  657. }
  658. else if (value > megabyte)
  659. {
  660. text += AZStd::fixed_string<32>::format("%.2f megabytes per second", value * (1.0 / megabyte));
  661. }
  662. else if (value > kilobyte)
  663. {
  664. text += AZStd::fixed_string<32>::format("%.2f kilobytes per second", value * (1.0 / kilobyte));
  665. }
  666. else
  667. {
  668. text += AZStd::fixed_string<32>::format("%.2f bytes per second", value);
  669. }
  670. }
  671. auto StreamerProfilerSystemComponent::CreateGraph(const AZ::IO::Statistic::Value& value) -> GraphStore
  672. {
  673. auto visitor = [](auto&& value) -> GraphStore
  674. {
  675. using Type = AZStd::decay_t<decltype(value)>;
  676. if constexpr (AZStd::is_same_v<Type, bool> || AZStd::is_same_v<Type, AZ::IO::Statistic::Percentage>)
  677. {
  678. return GraphStore(0.0f, 1.0f);
  679. }
  680. else if constexpr (
  681. AZStd::is_same_v<Type, AZ::IO::Statistic::FloatRange> ||
  682. AZStd::is_same_v<Type, AZ::IO::Statistic::IntegerRange> ||
  683. AZStd::is_same_v<Type, AZ::IO::Statistic::PercentageRange>)
  684. {
  685. return GraphStore(azlossy_cast<float>(value.m_min), azlossy_cast<float>(value.m_max));
  686. }
  687. else
  688. {
  689. return GraphStore{};
  690. }
  691. };
  692. return AZStd::visit(visitor, value);
  693. }
  694. void StreamerProfilerSystemComponent::DrawToolTip([[maybe_unused]] AZStd::string_view text)
  695. {
  696. #if defined(IMGUI_ENABLED)
  697. if (ImGui::IsItemHovered())
  698. {
  699. ImGui::BeginTooltip();
  700. ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
  701. ImGui::TextUnformatted(text.data(), text.data() + text.size());
  702. ImGui::PopTextWrapPos();
  703. ImGui::EndTooltip();
  704. }
  705. #endif // #if defined(IMGUI_ENABLED)
  706. }
  707. void StreamerProfilerSystemComponent::DrawToolTipV([[maybe_unused]] const char* text, ...)
  708. {
  709. #if defined(IMGUI_ENABLED)
  710. if (ImGui::IsItemHovered())
  711. {
  712. ImGui::BeginTooltip();
  713. ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
  714. va_list args;
  715. va_start(args, text);
  716. ImGui::TextV(text, args);
  717. va_end(args);
  718. ImGui::PopTextWrapPos();
  719. ImGui::EndTooltip();
  720. }
  721. #endif // #if defined(IMGUI_ENABLED)
  722. }
  723. } // namespace Streamer