StatsCaptureTest.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 <native/tests/AssetProcessorTest.h>
  9. #include <native/tests/MockAssetDatabaseRequestsHandler.h>
  10. #include <native/utilities/StatsCapture.h>
  11. #include <AzCore/std/containers/vector.h>
  12. #include <AzCore/std/string/string.h>
  13. #include <AzCore/Settings/SettingsRegistry.h>
  14. #include <AzCore/Debug/TraceMessageBus.h>
  15. #include <AzCore/StringFunc/StringFunc.h>
  16. #include <QDir>
  17. #include <QString>
  18. // the simple stats capture system has a trivial interface and only writes to printf.
  19. // So the simplest tests we can do is make sure it only asserts when it should
  20. // and doesn't assert in cases when it shouldn't, and that the stats are reasonable
  21. // in printf format.
  22. namespace AssetProcessor
  23. {
  24. class StatsCaptureTest
  25. : public AssetProcessorTest
  26. {
  27. private:
  28. MockAssetDatabaseRequestsHandler m_databaseLocationListener;
  29. };
  30. // Its okay to talk to this system when unintialized, you can gain some perf
  31. // by not intializing it at all
  32. TEST_F(StatsCaptureTest, StatsCaptureTest_UninitializedSystemDoesNotAssert)
  33. {
  34. AssetProcessor::StatsCapture::BeginCaptureStat("Test");
  35. AssetProcessor::StatsCapture::EndCaptureStat("Test");
  36. AssetProcessor::StatsCapture::Dump();
  37. AssetProcessor::StatsCapture::Shutdown();
  38. }
  39. // Double-intiailize is an error
  40. TEST_F(StatsCaptureTest, StatsCaptureTest_DoubleInitializeIsAnAssert)
  41. {
  42. m_errorAbsorber->Clear();
  43. AssetProcessor::StatsCapture::Initialize();
  44. AssetProcessor::StatsCapture::Initialize();
  45. EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
  46. EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1); // not allowed to assert on this
  47. AssetProcessor::StatsCapture::BeginCaptureStat("Test");
  48. AssetProcessor::StatsCapture::Shutdown();
  49. }
  50. class StatsCaptureOutputTest
  51. : public StatsCaptureTest
  52. , public AZ::Debug::TraceMessageBus::Handler
  53. {
  54. private:
  55. //! These private members establish the connection to a temporary asset database, which StatsCapture may persist stat entries to.
  56. AssetDatabaseConnection m_dbConnection;
  57. friend class GTEST_TEST_CLASS_NAME_(StatsCaptureOutputTest, StatsCaptureTest_PersistToDb);
  58. public:
  59. void SetUp() override
  60. {
  61. StatsCaptureTest::SetUp();
  62. m_dbConnection.OpenDatabase();
  63. AssetProcessor::StatsCapture::Initialize();
  64. }
  65. // dump but also capture the dump as a vector of lines:
  66. void Dump()
  67. {
  68. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  69. AssetProcessor::StatsCapture::Dump();
  70. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  71. }
  72. virtual bool OnPrintf(const char* /*window*/, const char* message)
  73. {
  74. m_gatheredMessages.emplace_back(message);
  75. AZ::StringFunc::TrimWhiteSpace(m_gatheredMessages.back(), true, true);
  76. return false;
  77. }
  78. void TearDown() override
  79. {
  80. m_gatheredMessages = {};
  81. AssetProcessor::StatsCapture::Shutdown();
  82. StatsCaptureTest::TearDown();
  83. }
  84. AZStd::vector<AZStd::string> m_gatheredMessages;
  85. };
  86. // turning off machine and human readable mode, should not dump anything.
  87. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_DisabledByRegset_DumpsNothing)
  88. {
  89. auto registry = AZ::SettingsRegistry::Get();
  90. ASSERT_NE(registry, nullptr);
  91. registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
  92. registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", false);
  93. AssetProcessor::StatsCapture::BeginCaptureStat("Test");
  94. AssetProcessor::StatsCapture::EndCaptureStat("Test");
  95. Dump();
  96. EXPECT_EQ(m_gatheredMessages.size(), 0);
  97. }
  98. // turning on Human Readable, turn off Machine Readable, should not output any machine readable stats.
  99. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_HumanReadableOnly_DumpsNoMachineReadable)
  100. {
  101. auto registry = AZ::SettingsRegistry::Get();
  102. ASSERT_NE(registry, nullptr);
  103. registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", true);
  104. registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", false);
  105. AssetProcessor::StatsCapture::BeginCaptureStat("Test");
  106. AssetProcessor::StatsCapture::EndCaptureStat("Test");
  107. Dump();
  108. EXPECT_GT(m_gatheredMessages.size(), 0);
  109. for (const auto& message : m_gatheredMessages)
  110. {
  111. // we expect to see ZERO "Machine Readable" lines
  112. EXPECT_FALSE(message.contains("MachineReadableStat:")) << "Found unexpected line in output: " << message.c_str();
  113. }
  114. }
  115. // Turn on Machine Readable, Turn off Human Readable, ensure only Machine Readable stats emitted.
  116. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_MachineReadableOnly_DumpsNoHumanReadable)
  117. {
  118. auto registry = AZ::SettingsRegistry::Get();
  119. ASSERT_NE(registry, nullptr);
  120. registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
  121. registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", true);
  122. AssetProcessor::StatsCapture::BeginCaptureStat("Test");
  123. AssetProcessor::StatsCapture::EndCaptureStat("Test");
  124. Dump();
  125. for (const auto& message : m_gatheredMessages)
  126. {
  127. // we expect to see ONLY "Machine Readable" lines
  128. EXPECT_TRUE(message.contains("MachineReadableStat:")) << "Found unexpected line in output: " << message.c_str();
  129. }
  130. EXPECT_GT(m_gatheredMessages.size(), 0);
  131. }
  132. // The interface for StatsCapture just captures and then dumps.
  133. // For us to test this, we thus have to capture and parse the dump output.
  134. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_Sanity)
  135. {
  136. auto registry = AZ::SettingsRegistry::Get();
  137. ASSERT_NE(registry, nullptr);
  138. // Make it output in "machine raadable" format so that it is simpler to parse.
  139. registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
  140. registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", true);
  141. AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo,mybuilder");
  142. AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo,mybuilder");
  143. // Intentionally not using sleeps in this test. It means that the
  144. // captured duration will be likely 0 but its not worth it to slow down tests.
  145. // If the durations end up 0 its going to be extremely noticable in day-to-day use.
  146. AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo,mybuilder");
  147. AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo,mybuilder");
  148. // for the second stat, we'll double capture and double end, in order to test debounce
  149. AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo2,mybuilder");
  150. AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo2,mybuilder");
  151. AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo2,mybuilder2");
  152. AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo2,mybuilder2");
  153. m_gatheredMessages.clear();
  154. Dump();
  155. EXPECT_GT(m_gatheredMessages.size(), 0);
  156. // We'll parse the machine readable stat lines here and make sure that the following is true
  157. // mybuilder appears
  158. // mybuilder appears only once but count is 2
  159. bool foundFoo = false;
  160. bool foundFoo2 = false;
  161. for (const auto& stat : m_gatheredMessages)
  162. {
  163. if (stat.contains("MachineReadableStat:"))
  164. {
  165. AZStd::vector<AZStd::string> tokens;
  166. AZ::StringFunc::Tokenize(stat, tokens, ":", false, false);
  167. ASSERT_EQ(tokens.size(), 5); // should be "MachineReadableStat:time:count:average:name)
  168. const auto& countData = tokens[2];
  169. const auto& nameData = tokens[4];
  170. if (AZ::StringFunc::Equal(nameData, "CreateJobs,foo,mybuilder"))
  171. {
  172. EXPECT_FALSE(foundFoo); // should only find one of these
  173. foundFoo = true;
  174. EXPECT_STREQ(countData.c_str(), "2");
  175. }
  176. if (AZ::StringFunc::Equal(nameData, "CreateJobs,foo2,mybuilder2"))
  177. {
  178. EXPECT_FALSE(foundFoo2); // should only find one of these
  179. foundFoo2 = true;
  180. EXPECT_STREQ(countData.c_str(), "1");
  181. }
  182. }
  183. }
  184. EXPECT_TRUE(foundFoo) << "The expected token CreateJobs,foo,mybuilder did not appear in the output.";
  185. EXPECT_TRUE(foundFoo2) << "The expected CreateJobs.foo2.mybuilder2 did not appear in the output";
  186. }
  187. // If BeginCaptureStat was called for a certain statName, EndCaptureStat returns with a AZStd::optional containing just-measured duration as
  188. // its value. If BeginCaptureStat was not called for a certain statName, EndCaptureStat returns with a AZStd::optional without a value.
  189. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_ReturnsLastDuration)
  190. {
  191. AssetProcessor::StatsCapture::BeginCaptureStat("O3");
  192. auto o3Result = AssetProcessor::StatsCapture::EndCaptureStat("O3");
  193. auto deResult = AssetProcessor::StatsCapture::EndCaptureStat("DE");
  194. EXPECT_TRUE(o3Result.has_value());
  195. EXPECT_FALSE(deResult.has_value());
  196. }
  197. // Stat does not exist in asset database if EndCaptureStat's persistToDb argument is not specified or is false, and exist in asset database if
  198. // persistToDb is true.
  199. TEST_F(StatsCaptureOutputTest, StatsCaptureTest_PersistToDb)
  200. {
  201. AZ::u32 statEntryCount{ 0 };
  202. auto countQueriedStatEntry = [&statEntryCount]([[maybe_unused]] AzToolsFramework::AssetDatabase::StatDatabaseEntry& entry)
  203. {
  204. ++statEntryCount;
  205. return true;
  206. };
  207. AZStd::vector<AzToolsFramework::AssetDatabase::StatDatabaseEntry> statEntryContainer;
  208. auto getQueriedStatEntry = [&statEntryContainer]([[maybe_unused]] AzToolsFramework::AssetDatabase::StatDatabaseEntry& entry)
  209. {
  210. statEntryContainer.emplace_back() = AZStd::move(entry);
  211. return true;
  212. };
  213. // persistToDb argument is not specified
  214. AssetProcessor::StatsCapture::BeginCaptureStat("Open");
  215. AssetProcessor::StatsCapture::EndCaptureStat("Open");
  216. statEntryCount = 0;
  217. m_dbConnection.QueryStatsTable(countQueriedStatEntry);
  218. EXPECT_EQ(statEntryCount, 0);
  219. // persistToDb argument is false
  220. AssetProcessor::StatsCapture::BeginCaptureStat("3D");
  221. AssetProcessor::StatsCapture::EndCaptureStat("3D", false);
  222. statEntryCount = 0;
  223. m_dbConnection.QueryStatsTable(countQueriedStatEntry);
  224. EXPECT_EQ(statEntryCount, 0);
  225. // persistToDb argument is true
  226. AssetProcessor::StatsCapture::BeginCaptureStat("Engine");
  227. auto statResult = AssetProcessor::StatsCapture::EndCaptureStat("Engine", true);
  228. ASSERT_TRUE(statResult.has_value());
  229. statEntryContainer.clear();
  230. m_dbConnection.QueryStatsTable(getQueriedStatEntry);
  231. ASSERT_EQ(statEntryContainer.size(), 1);
  232. EXPECT_EQ(statEntryContainer.at(0).m_statValue, aznumeric_cast<AZ::s64>(statResult.value()));
  233. }
  234. } // namespace AssetProcessor