JsonTraceEventLoggerTests.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  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 <AzCore/Metrics/JsonTraceEventLogger.h>
  9. #include <AzCore/IO/ByteContainerStream.h>
  10. #include <AzCore/IO/Path/Path.h>
  11. #include <AzCore/JSON/document.h>
  12. #include <AzCore/JSON/error/en.h>
  13. #include <AzCore/Settings/SettingsRegistryImpl.h>
  14. #include <AzCore/std/string/conversions.h>
  15. #include <AzCore/std/smart_ptr/unique_ptr.h>
  16. #include <AzCore/std/ranges/zip_view.h>
  17. #include <AzCore/UnitTest/TestTypes.h>
  18. namespace UnitTest
  19. {
  20. class JsonTraceEventLoggerTest
  21. : public UnitTest::LeakDetectionFixture
  22. {
  23. };
  24. bool JsonStringContains(AZStd::string_view lhs, AZStd::string_view rhs)
  25. {
  26. auto TrimWhitespace = [](char element) -> bool
  27. {
  28. return !::isspace(element);
  29. };
  30. AZStd::string sourceString;
  31. for (char elem : lhs)
  32. {
  33. if (TrimWhitespace(elem))
  34. {
  35. sourceString += elem;
  36. }
  37. }
  38. AZStd::string containsString;
  39. for (char elem : rhs)
  40. {
  41. if (TrimWhitespace(elem))
  42. {
  43. containsString += elem;
  44. }
  45. }
  46. return sourceString.contains(containsString);
  47. }
  48. size_t JsonStringCountAll(AZStd::string_view sourceView, AZStd::string_view containsView)
  49. {
  50. if (containsView.empty())
  51. {
  52. return 0;
  53. }
  54. auto TrimWhitespace = [](char element) -> bool
  55. {
  56. return !::isspace(element);
  57. };
  58. // Need persistent storage in this case to count all occurrences of JSON string inside of source string
  59. // Remove all whitespace from both strings
  60. AZStd::string sourceString;
  61. for (char elem : sourceView)
  62. {
  63. if (TrimWhitespace(elem))
  64. {
  65. sourceString += elem;
  66. }
  67. }
  68. AZStd::string containsString;
  69. for (char elem : containsView)
  70. {
  71. if (TrimWhitespace(elem))
  72. {
  73. containsString += elem;
  74. }
  75. }
  76. size_t count{};
  77. AZStd::string_view remainingRange(sourceString);
  78. for (;;)
  79. {
  80. auto foundFirst = remainingRange.find(containsString);
  81. if (foundFirst == AZStd::string_view::npos)
  82. {
  83. break;
  84. }
  85. AZStd::string_view foundRange = remainingRange.substr(foundFirst, containsString.size());
  86. // If the contains string has been found reduce the
  87. // remaining search range to be the end of the found string until the end of the source string
  88. remainingRange = { foundRange.end(), sourceString.end() };
  89. ++count;
  90. }
  91. return count;
  92. }
  93. TEST_F(JsonTraceEventLoggerTest, RecordDurationEvent_ProperJsonOutput_ToStream)
  94. {
  95. constexpr AZStd::string_view eventBeginMessage = "Hello world";
  96. AZStd::string metricsOutput;
  97. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  98. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream));
  99. // Provides storage for the arguments supplied to the "args" structure
  100. using EventFieldStorage = AZStd::fixed_vector<AZ::Metrics::EventField, 8>;
  101. EventFieldStorage argContainer{ {"Field1", eventBeginMessage} };
  102. AZ::Metrics::DurationArgs testArgs;
  103. testArgs.m_name = "StringEvent";
  104. testArgs.m_cat = "Test";
  105. testArgs.m_args = argContainer;
  106. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  107. ResultOutcome resultOutcome = googleTraceLogger.RecordDurationEventBegin(testArgs);
  108. EXPECT_TRUE(resultOutcome);
  109. // Update test args
  110. constexpr AZStd::string_view eventEndMessage = "Goodbye World";
  111. argContainer = EventFieldStorage{ { "Field1", eventEndMessage } };
  112. resultOutcome = googleTraceLogger.RecordDurationEventEnd(testArgs);
  113. EXPECT_TRUE(resultOutcome);
  114. // Flush and closes the event stream
  115. // This completes the json array
  116. googleTraceLogger.ResetStream(nullptr);
  117. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("name": "StringEvent")"));
  118. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("cat": "Test")"));
  119. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("ph": "B")"));
  120. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("name": "StringEvent")"));
  121. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("cat": "Test")"));
  122. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("ph": "B")"));
  123. // Validate that the output is a valid json doucment
  124. rapidjson::Document validateDoc;
  125. rapidjson::ParseResult parseResult = validateDoc.Parse(metricsOutput.c_str());
  126. EXPECT_TRUE(parseResult) << R"(JSON parse error ")" << rapidjson::GetParseError_En(parseResult.Code())
  127. << R"(" at offset (%u))";
  128. }
  129. TEST_F(JsonTraceEventLoggerTest, RecordAllEvents_StringsFromMultipleThreads_WrittenToStream)
  130. {
  131. constexpr AZStd::string_view eventString = "Hello world";
  132. constexpr AZ::s64 eventInt64 = -2;
  133. constexpr AZ::u64 eventUint64 = 0xFFFF'0000'FFFF'FFFF;
  134. constexpr bool eventBool = true;
  135. constexpr double eventDouble = 64.0;
  136. constexpr auto objectFieldNames = AZStd::to_array<AZStd::string_view>({"Field1", "Field2", "Field3", "Field4", "Field5"});
  137. using EventArgsType = AZ::Metrics::EventValue::ArgsVariant;
  138. const auto objectFieldValues = AZStd::to_array<EventArgsType>({ eventString, eventInt64, eventUint64, eventBool, eventDouble });
  139. // Provides storage for the arguments supplied to the "args" structure
  140. using EventArrayStorage = AZStd::fixed_vector<AZ::Metrics::EventValue, 8>;
  141. using EventObjectStorage = AZStd::fixed_vector<AZ::Metrics::EventField, 8>;
  142. EventArrayStorage eventArray;
  143. EventObjectStorage eventObject;
  144. // Fill out the child array and child object fields
  145. for (auto [fieldName, fieldValue] : AZStd::views::zip(objectFieldNames, objectFieldValues))
  146. {
  147. auto AppendArgs = [name = fieldName, &eventArray, &eventObject](auto&& value)
  148. {
  149. eventArray.push_back(value);
  150. eventObject.emplace_back(name, value);
  151. };
  152. AZStd::visit(AppendArgs, fieldValue);
  153. }
  154. // Populate the "args" container to associate with each events "args" field
  155. EventObjectStorage argsContainer;
  156. argsContainer.emplace_back("string", eventString);
  157. argsContainer.emplace_back("int64_t", eventInt64);
  158. argsContainer.emplace_back("uint64_t", eventUint64);
  159. argsContainer.emplace_back("bool", eventBool);
  160. argsContainer.emplace_back("double", eventDouble);
  161. argsContainer.emplace_back("array", AZ::Metrics::EventArray(eventArray));
  162. argsContainer.emplace_back("object", AZ::Metrics::EventObject(eventObject));
  163. // Create an byte container stream that allows event logger output to be logged in-memory
  164. AZStd::string metricsOutput;
  165. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  166. // Create the trace event logger that logs to the Google Trace Event format
  167. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream));
  168. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  169. // Defer logging until after all threads have been started
  170. AZStd::atomic_bool startLogging{};
  171. auto LogAllEvents = [&startLogging, &googleTraceLogger, &argsContainer](int threadIndex)
  172. {
  173. // Fake timestamp to use for complete event
  174. AZStd::chrono::utc_clock::time_point startThreadTime = AZStd::chrono::utc_clock::now();
  175. while (!startLogging)
  176. {
  177. AZStd::this_thread::yield();
  178. }
  179. // Convert the threadIndex to a string and use that as the "id" value
  180. AZStd::fixed_string<32> idString;
  181. AZStd::to_string(idString, threadIndex);
  182. ResultOutcome resultOutcome(AZStd::unexpect, AZ::Metrics::IEventLogger::ErrorString("Uninitialized"));
  183. {
  184. // Record Duration Begin and End Events
  185. AZ::Metrics::DurationArgs durationArgs;
  186. durationArgs.m_name = "Duration Event";
  187. durationArgs.m_cat = "Test";
  188. durationArgs.m_args = argsContainer;
  189. durationArgs.m_id = idString;
  190. resultOutcome = googleTraceLogger.RecordDurationEventBegin(durationArgs);
  191. EXPECT_TRUE(resultOutcome);
  192. resultOutcome = googleTraceLogger.RecordDurationEventEnd(durationArgs);
  193. EXPECT_TRUE(resultOutcome);
  194. }
  195. {
  196. // Record Complete Event
  197. auto duration = AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(AZStd::chrono::utc_clock::now() - startThreadTime);
  198. AZ::Metrics::CompleteArgs completeArgs;
  199. completeArgs.m_name = "Complete Event";
  200. completeArgs.m_cat = "Test";
  201. completeArgs.m_dur = duration;
  202. completeArgs.m_args = argsContainer;
  203. completeArgs.m_id = idString;
  204. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  205. EXPECT_TRUE(resultOutcome);
  206. }
  207. {
  208. // Record Instant Event
  209. AZ::Metrics::InstantArgs instantArgs;
  210. instantArgs.m_name = "Instant Event";
  211. instantArgs.m_cat = "Test";
  212. instantArgs.m_args = argsContainer;
  213. instantArgs.m_id = idString;
  214. instantArgs.m_scope = AZ::Metrics::InstantEventScope::Thread;
  215. resultOutcome = googleTraceLogger.RecordInstantEvent(instantArgs);
  216. EXPECT_TRUE(resultOutcome);
  217. }
  218. {
  219. // Record Instant Event
  220. // Add an extra object field for a count by making a copy of the argsContainer
  221. auto extendedArgs = argsContainer;
  222. extendedArgs.emplace_back("frameTime", AZ::Metrics::EventValue{ AZStd::in_place_type<AZ::s64>, 16 + threadIndex });
  223. AZ::Metrics::CounterArgs counterArgs;
  224. counterArgs.m_name = "Counter Event";
  225. counterArgs.m_cat = "Test";
  226. counterArgs.m_args = extendedArgs;
  227. counterArgs.m_id = idString;
  228. resultOutcome = googleTraceLogger.RecordCounterEvent(counterArgs);
  229. EXPECT_TRUE(resultOutcome);
  230. }
  231. {
  232. // Record Async Start and End Events
  233. // Also records the Async Instant event
  234. constexpr AZStd::string_view asyncOuterEventName = "Async Event";
  235. AZ::Metrics::AsyncArgs asyncArgs;
  236. asyncArgs.m_name = asyncOuterEventName;
  237. asyncArgs.m_cat = "Test";
  238. asyncArgs.m_args = argsContainer;
  239. asyncArgs.m_id = idString;
  240. asyncArgs.m_scope = "Distinguishing Scope";
  241. resultOutcome = googleTraceLogger.RecordAsyncEventStart(asyncArgs);
  242. EXPECT_TRUE(resultOutcome);
  243. // Change the name of the event
  244. asyncArgs.m_name = "Async Instant Event";
  245. resultOutcome = googleTraceLogger.RecordAsyncEventInstant(asyncArgs);
  246. EXPECT_TRUE(resultOutcome);
  247. // "Async Event" is the logical name of the being and end event being recorded
  248. // So make sure the end event matches
  249. asyncArgs.m_name = asyncOuterEventName;
  250. resultOutcome = googleTraceLogger.RecordAsyncEventEnd(asyncArgs);
  251. EXPECT_TRUE(resultOutcome);
  252. }
  253. };
  254. // Spin up totalThreads for testing logging from multiple threads
  255. constexpr size_t totalThreads = 4;
  256. // Used for an id value for an event
  257. int32_t currentThreadIndex{};
  258. AZStd::array<AZStd::thread, totalThreads> threads;
  259. for (AZStd::thread& threadRef : threads)
  260. {
  261. threadRef = AZStd::thread(LogAllEvents, currentThreadIndex++);
  262. }
  263. // Start logging of events
  264. startLogging = true;
  265. // Join the threads. This should complete the event recording
  266. for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
  267. {
  268. threads[threadIndex].join();
  269. }
  270. // Flush and closes the event stream
  271. // This completes the json array
  272. googleTraceLogger.ResetStream(nullptr);
  273. // Count should be 32.
  274. // There are 8 variations of events (1. Duration Begin, 2. Duration End, 3. Complete, 4. Instant,
  275. // 5. Counter, 6. Async Start, 7. Async End, 8. Async Instant
  276. // Times 4 threads being run
  277. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("string": "Hello world")"));
  278. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("int64_t": -2)"));
  279. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("uint64_t": 18446462603027808255)"));
  280. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("bool": true)"));
  281. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("double": 64.0)"));
  282. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("array": [)"));
  283. EXPECT_EQ(32, JsonStringCountAll(metricsOutput, R"("object": {)"));
  284. // Validate that the output is a valid json doucment
  285. rapidjson::Document validateDoc;
  286. rapidjson::ParseResult parseResult = validateDoc.Parse(metricsOutput.c_str());
  287. EXPECT_TRUE(parseResult) << R"(JSON parse error ")" << rapidjson::GetParseError_En(parseResult.Code())
  288. << R"(" at offset (%u))";
  289. }
  290. TEST_F(JsonTraceEventLoggerTest, TogglingActiveSetting_CanTurnOrOffEventRecording_Succeeds)
  291. {
  292. // local Settings Registry used for validating changes to the event logger active state
  293. AZ::SettingsRegistryImpl settingsRegistry;
  294. // Event Logger name used to read settings and set settings associated with the Event Logger
  295. constexpr AZStd::string_view EventLoggerName = "JsonTraceEventTest";
  296. AZStd::string metricsOutput;
  297. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  298. {
  299. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream), { EventLoggerName, &settingsRegistry });
  300. // Event logger is be active by default if there is no `/O3DE/Metrics/<Name>/Active` in the settings registry
  301. AZStd::string eventMessage = "Hello world";
  302. AZ::Metrics::EventObjectStorage argContainer{ {"Field1", eventMessage} };
  303. AZ::Metrics::CompleteArgs completeArgs;
  304. completeArgs.m_name = "StringEvent";
  305. completeArgs.m_cat = "Test";
  306. completeArgs.m_args = argContainer;
  307. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  308. ResultOutcome resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  309. EXPECT_TRUE(resultOutcome);
  310. // As the event logger is active, the event data should have been recorded to the stream
  311. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("Field1":"Hello world")"));
  312. // Set any event loggers with a name that matches EventLoggerName to inactive
  313. settingsRegistry.Set(AZ::Metrics::SettingsKey(EventLoggerName) + "/Active", false);
  314. // Update the event string for the complete event and attempt to record it again
  315. eventMessage = "Unrecorded world";
  316. argContainer[0].m_value = eventMessage;
  317. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  318. EXPECT_TRUE(resultOutcome);
  319. // No recording of the complete event to the stream should have occured
  320. EXPECT_FALSE(JsonStringContains(metricsOutput, R"("Field1":"Unrecorded world")"));
  321. // Set the active state for event loggers with the name matching the EventLoggerName constant
  322. settingsRegistry.Set(AZ::Metrics::SettingsKey(EventLoggerName) + "/Active", true);
  323. // Change the event string to 3rd different string and attempt to record again
  324. eventMessage = "Re-recorded world";
  325. argContainer[0].m_value = eventMessage;
  326. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  327. EXPECT_TRUE(resultOutcome);
  328. // The recording of the complete event should now have occured
  329. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("Field1":"Re-recorded world")"));
  330. }
  331. }
  332. TEST_F(JsonTraceEventLoggerTest, InitialActiveSetting_SetToFalse_DoesNotRecord_Succeeds)
  333. {
  334. // Event Logger name used to read settings and set settings associated with the Event Logger
  335. constexpr AZStd::string_view EventLoggerName = "JsonTraceEventTest";
  336. // local Settings Registry used for validating changes to the event logger active state
  337. AZ::SettingsRegistryImpl settingsRegistry;
  338. // Default to the inactive state for event loggers matching the name of the EventLoggerName constant
  339. settingsRegistry.Set(AZ::Metrics::SettingsKey(EventLoggerName) + "/Active", false);
  340. AZStd::string metricsOutput;
  341. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  342. {
  343. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream), { EventLoggerName, &settingsRegistry });
  344. AZStd::string eventMessage = "Hello world";
  345. AZ::Metrics::EventObjectStorage argContainer{ {"Field1", eventMessage} };
  346. AZ::Metrics::CompleteArgs completeArgs;
  347. completeArgs.m_name = "StringEvent";
  348. completeArgs.m_cat = "Test";
  349. completeArgs.m_args = argContainer;
  350. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  351. ResultOutcome resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  352. EXPECT_TRUE(resultOutcome);
  353. // The event logger should be inactive, so recording to the output stream should not occur
  354. EXPECT_FALSE(JsonStringContains(metricsOutput, R"("Field1":"Hello world")"));
  355. // Set the active state for event loggers with the name matching the EventLoggerName constant
  356. settingsRegistry.Set(AZ::Metrics::SettingsKey(EventLoggerName) + "/Active", true);
  357. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  358. EXPECT_TRUE(resultOutcome);
  359. // The recording to the event logger stream should have succeeded this time
  360. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("Field1":"Hello world")"));
  361. }
  362. }
  363. TEST_F(JsonTraceEventLoggerTest, ChangingEventLoggerName_ResetsRecordingState_Succeeds)
  364. {
  365. // Event Logger name used to read settings and set settings associated with the Event Logger
  366. constexpr AZStd::string_view EventLoggerName = "JsonTraceEventTest";
  367. // name to use to reading a different section of the "/O3DE/Metrics/<Name>" setting
  368. constexpr AZStd::string_view NewLoggerName = "NewName";
  369. // local Settings Registry used for validating changes to the event logger active state
  370. AZ::SettingsRegistryImpl settingsRegistry;
  371. // Default to the inactive state
  372. settingsRegistry.Set(AZ::Metrics::SettingsKey(EventLoggerName) + "/Active", false);
  373. AZStd::string metricsOutput;
  374. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  375. {
  376. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream), { EventLoggerName, &settingsRegistry });
  377. AZStd::string eventMessage = "Hello world";
  378. AZ::Metrics::EventObjectStorage argContainer{ {"Field1", eventMessage} };
  379. AZ::Metrics::CompleteArgs completeArgs;
  380. completeArgs.m_name = "StringEvent";
  381. completeArgs.m_cat = "Test";
  382. completeArgs.m_args = argContainer;
  383. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  384. ResultOutcome resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  385. EXPECT_TRUE(resultOutcome);
  386. // The "JsonTraceEventTest" event logger should be inactive
  387. EXPECT_FALSE(JsonStringContains(metricsOutput, R"("Field1":"Hello world")"));
  388. // Change the name of the event loger to "NewName", it should be active by default
  389. googleTraceLogger.SetName(NewLoggerName);
  390. EXPECT_EQ(NewLoggerName, googleTraceLogger.GetName());
  391. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  392. EXPECT_TRUE(resultOutcome);
  393. // Recording to the event logger with "NewName" should have succeeded
  394. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("Field1":"Hello world")"));
  395. // Set the "NewName" event logger to inactive
  396. settingsRegistry.Set(AZ::Metrics::SettingsKey(NewLoggerName) + "/Active", false);
  397. // Change event string value to validate that recording is not occuring
  398. eventMessage = "Goodbye world";
  399. argContainer[0].m_value = eventMessage;
  400. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  401. EXPECT_TRUE(resultOutcome);
  402. // Recording to the "NewName" event logger should not occur
  403. EXPECT_FALSE(JsonStringContains(metricsOutput, R"("Field1":"Goodbye world")"));
  404. // Reset the "NewName" event logger to active again by deleting 'Active' settings key
  405. // (Setting the 'Active' setting to true also works
  406. settingsRegistry.Remove(AZ::Metrics::SettingsKey(NewLoggerName) + "/Active");
  407. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  408. EXPECT_TRUE(resultOutcome);
  409. // Recording of the complete event should be succeeding again
  410. EXPECT_TRUE(JsonStringContains(metricsOutput, R"("Field1":"Goodbye world")"));
  411. // Finally change the name back to the original
  412. // This will reset the active state back to the value associated with the "JsonTraceEventTest"
  413. // name.
  414. googleTraceLogger.SetName(EventLoggerName);
  415. // As the original event logger had a setting with the active setting to false
  416. // recording should not occur
  417. eventMessage = "Metrics Elided";
  418. argContainer[0].m_value = eventMessage;
  419. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  420. EXPECT_TRUE(resultOutcome);
  421. EXPECT_FALSE(JsonStringContains(metricsOutput, R"("Field1":"Metrics Elided")"));
  422. }
  423. }
  424. } // namespace UnitTest
  425. #if defined(HAVE_BENCHMARK)
  426. namespace Benchmark
  427. {
  428. class JsonTraceEventLoggerBenchmarkFixture
  429. : public ::UnitTest::AllocatorsBenchmarkFixture
  430. {
  431. protected:
  432. void RecordMetricsToStream(AZStd::unique_ptr<AZ::IO::GenericStream>);
  433. protected:
  434. AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
  435. };
  436. void JsonTraceEventLoggerBenchmarkFixture::RecordMetricsToStream(AZStd::unique_ptr<AZ::IO::GenericStream>)
  437. {
  438. constexpr AZStd::string_view eventString = "Hello world";
  439. constexpr AZ::s64 eventInt64 = -2;
  440. constexpr AZ::u64 eventUint64 = 0xFFFF'0000'FFFF'FFFF;
  441. constexpr bool eventBool = true;
  442. constexpr double eventDouble = 64.0;
  443. constexpr auto objectFieldNames = AZStd::to_array<AZStd::string_view>({ "Field1", "Field2", "Field3", "Field4", "Field5" });
  444. using EventArgsType = AZ::Metrics::EventValue::ArgsVariant;
  445. const auto objectFieldValues = AZStd::to_array<EventArgsType>({ eventString, eventInt64, eventUint64, eventBool, eventDouble });
  446. // Provides storage for the arguments supplied to the "args" structure
  447. using EventArrayStorage = AZStd::fixed_vector<AZ::Metrics::EventValue, 8>;
  448. using EventObjectStorage = AZStd::fixed_vector<AZ::Metrics::EventField, 8>;
  449. EventArrayStorage eventArray;
  450. EventObjectStorage eventObject;
  451. // Fill out the child array and child object fields
  452. for (auto [fieldName, fieldValue] : AZStd::views::zip(objectFieldNames, objectFieldValues))
  453. {
  454. auto AppendArgs = [name = fieldName, &eventArray, &eventObject](auto&& value)
  455. {
  456. eventArray.push_back(value);
  457. eventObject.emplace_back(name, value);
  458. };
  459. AZStd::visit(AppendArgs, fieldValue);
  460. }
  461. // Populate the "args" container to associate with each events "args" field
  462. EventObjectStorage argsContainer;
  463. argsContainer.emplace_back("string", eventString);
  464. argsContainer.emplace_back("int64_t", eventInt64);
  465. argsContainer.emplace_back("uint64_t", eventUint64);
  466. argsContainer.emplace_back("bool", eventBool);
  467. argsContainer.emplace_back("double", eventDouble);
  468. argsContainer.emplace_back("array", AZ::Metrics::EventArray(eventArray));
  469. argsContainer.emplace_back("object", AZ::Metrics::EventObject(eventObject));
  470. // Create an byte container stream that allows event logger output to be logged in-memory
  471. AZStd::string metricsOutput;
  472. auto metricsStream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsOutput);
  473. // Create the trace event logger that logs to the Google Trace Event format
  474. AZ::Metrics::JsonTraceEventLogger googleTraceLogger(AZStd::move(metricsStream));
  475. auto LogAllEvents = [&googleTraceLogger, &argsContainer](int idValue)
  476. {
  477. // Fake timestamp to use for complete event
  478. AZStd::chrono::utc_clock::time_point startThreadTime = AZStd::chrono::utc_clock::now();
  479. // Convert the threadIndex to a string and use that as the "id" value
  480. AZStd::fixed_string<32> idString;
  481. AZStd::to_string(idString, idValue);
  482. using ResultOutcome = AZ::Metrics::IEventLogger::ResultOutcome;
  483. [[maybe_unused]] ResultOutcome resultOutcome(AZStd::unexpect, AZ::Metrics::IEventLogger::ErrorString("Uninitialized"));
  484. {
  485. // Record Duration Begin and End Events
  486. AZ::Metrics::DurationArgs durationArgs;
  487. durationArgs.m_name = "Duration Event";
  488. durationArgs.m_cat = "Test";
  489. durationArgs.m_args = argsContainer;
  490. durationArgs.m_id = idString;
  491. resultOutcome = googleTraceLogger.RecordDurationEventBegin(durationArgs);
  492. resultOutcome = googleTraceLogger.RecordDurationEventEnd(durationArgs);
  493. }
  494. {
  495. // Record Complete Event
  496. auto duration = AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(AZStd::chrono::utc_clock::now() - startThreadTime);
  497. AZ::Metrics::CompleteArgs completeArgs;
  498. completeArgs.m_name = "Complete Event";
  499. completeArgs.m_cat = "Test";
  500. completeArgs.m_dur = duration;
  501. completeArgs.m_args = argsContainer;
  502. completeArgs.m_id = idString;
  503. resultOutcome = googleTraceLogger.RecordCompleteEvent(completeArgs);
  504. }
  505. {
  506. // Record Instant Event
  507. AZ::Metrics::InstantArgs instantArgs;
  508. instantArgs.m_name = "Instant Event";
  509. instantArgs.m_cat = "Test";
  510. instantArgs.m_args = argsContainer;
  511. instantArgs.m_id = idString;
  512. instantArgs.m_scope = AZ::Metrics::InstantEventScope::Thread;
  513. resultOutcome = googleTraceLogger.RecordInstantEvent(instantArgs);
  514. }
  515. {
  516. // Record Complete Event
  517. // Add an extra object field for a count by making a copy of the argsContainer
  518. auto extendedArgs = argsContainer;
  519. extendedArgs.emplace_back("frameTime", AZ::Metrics::EventValue{ AZStd::in_place_type<AZ::s64>, 20 });
  520. AZ::Metrics::CounterArgs counterArgs;
  521. counterArgs.m_name = "Counter Event";
  522. counterArgs.m_cat = "Test";
  523. counterArgs.m_args = extendedArgs;
  524. counterArgs.m_id = idString;
  525. resultOutcome = googleTraceLogger.RecordCounterEvent(counterArgs);
  526. }
  527. {
  528. // Record Async Start and End Events
  529. // Also records the Async Instant event
  530. constexpr AZStd::string_view asyncOuterEventName = "Async Event";
  531. AZ::Metrics::AsyncArgs asyncArgs;
  532. asyncArgs.m_name = asyncOuterEventName;
  533. asyncArgs.m_cat = "Test";
  534. asyncArgs.m_args = argsContainer;
  535. asyncArgs.m_id = idString;
  536. asyncArgs.m_scope = "Distinguishing Scope";
  537. resultOutcome = googleTraceLogger.RecordAsyncEventStart(asyncArgs);
  538. // Change the name of the event
  539. asyncArgs.m_name = "Async Instant Event";
  540. resultOutcome = googleTraceLogger.RecordAsyncEventInstant(asyncArgs);
  541. // "Async Event" is the logical name of the being and end event being recorded
  542. // So make sure the end event matches
  543. asyncArgs.m_name = asyncOuterEventName;
  544. resultOutcome = googleTraceLogger.RecordAsyncEventEnd(asyncArgs);
  545. }
  546. };
  547. LogAllEvents(0);
  548. // Flush and closes the event stream
  549. // This completes the json array
  550. googleTraceLogger.ResetStream(nullptr);
  551. }
  552. BENCHMARK_F(JsonTraceEventLoggerBenchmarkFixture, BM_JsonTraceEventLogger_RecordEventsToInMemoryStream)(benchmark::State& state)
  553. {
  554. constexpr size_t eventOutputReserveSize = 4096;
  555. AZStd::string metricsPayload;
  556. // Reserve memory to prevent allocation of string data skewing results
  557. // for byte container stream
  558. metricsPayload.reserve(eventOutputReserveSize);
  559. for ([[maybe_unused]] auto _ : state)
  560. {
  561. state.PauseTiming();
  562. // Do not time the creation of the stream
  563. auto stream = AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::string>>(&metricsPayload);
  564. state.ResumeTiming();
  565. RecordMetricsToStream(AZStd::move(stream));
  566. metricsPayload.clear();
  567. }
  568. }
  569. BENCHMARK_F(JsonTraceEventLoggerBenchmarkFixture, BM_JsonTraceEventLogger_RecordEventsToTemporaryDirUnbufferedFileStream)(benchmark::State& state)
  570. {
  571. // Open the file and truncate the file
  572. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite;
  573. const AZ::IO::FixedMaxPath metricsFilepath = m_tempDirectory.GetDirectoryAsFixedMaxPath() / "metrics.json";
  574. for ([[maybe_unused]] auto _ : state)
  575. {
  576. state.PauseTiming();
  577. auto stream = AZStd::make_unique<AZ::IO::SystemFileStream>(metricsFilepath.c_str(), openMode);
  578. state.ResumeTiming();
  579. RecordMetricsToStream(AZStd::move(stream));
  580. }
  581. }
  582. } // Benchmark
  583. #endif