TestStopwatch.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include <EABase/eabase.h>
  5. #include <EAStdC/EAStopwatch.h>
  6. #include <EAStdC/EASprintf.h>
  7. #include <EAStdC/EADateTime.h>
  8. #include <EATest/EATest.h>
  9. #include <EAStdCTest/EAStdCTest.h>
  10. #include <EAStdC/EAString.h>
  11. #ifdef _MSC_VER
  12. #pragma warning(push, 0)
  13. #endif
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <time.h>
  17. #if defined(EA_PLATFORM_WINDOWS)
  18. #include <Windows.h>
  19. #elif defined(EA_PLATFORM_ANDROID)
  20. #include <sys/system_properties.h>
  21. #endif
  22. #ifdef _MSC_VER
  23. #pragma warning(pop)
  24. #endif
  25. #ifndef EASTDC_SWAPPABLE_PROCESS_PLATFORM
  26. #if defined(EA_PLATFORM_ANDROID)
  27. #define EASTDC_SWAPPABLE_PROCESS_PLATFORM
  28. #endif
  29. #endif
  30. // Tests whether an emulator is being used (for Android devices only)
  31. #ifdef EA_PLATFORM_ANDROID
  32. bool IsEmulator()
  33. {
  34. char buf[PROP_VALUE_MAX];
  35. // "goldfish" and "ranchu" are the possible names of the kernel that run on the Android emulator (as of 2018/02/08)
  36. int propLen = __system_property_get("ro.hardware", buf);
  37. if(propLen != 0 &&
  38. (EA::StdC::Strncmp(buf, "goldfish", propLen) == 0 ||
  39. EA::StdC::Strncmp(buf, "ranchu", propLen) == 0))
  40. return true;
  41. return false;
  42. }
  43. #endif
  44. int TestStopwatch()
  45. {
  46. using namespace EA::StdC;
  47. int nErrorCount(0);
  48. Stopwatch stopwatch(Stopwatch::kUnitsSeconds);
  49. Stopwatch stopwatchHelper1(Stopwatch::kUnitsSeconds);
  50. Stopwatch stopwatchHelper2(Stopwatch::kUnitsSeconds);
  51. uint64_t nElapsedTime;
  52. bool bResult = true;
  53. // Call the stopwatch once to make sure the code is loaded, etc.
  54. stopwatch.Start();
  55. stopwatch.Stop();
  56. ////////////////////////////////////////////////////////////////////////////
  57. // Do a simple test of the CPU cycle counting.
  58. //
  59. // This test will fail on Android when using an Emulator which is what the Build Farm uses
  60. #ifdef EA_PLATFORM_ANDROID
  61. if(!IsEmulator())
  62. {
  63. #endif
  64. bool bDisableTimingTest = false;
  65. #if !EASTDC_VALGRIND_ENABLED
  66. bDisableTimingTest = true;
  67. #endif
  68. uint64_t elapsedTimeLimit = 1000;
  69. EA::UnitTest::SetHighThreadPriority();
  70. int nTookTooLongCount(0);
  71. for(int i(0); i < 20; i++)
  72. {
  73. stopwatch.Reset();
  74. stopwatch.SetUnits(Stopwatch::kUnitsCycles);
  75. stopwatch.Start();
  76. stopwatch.Stop();
  77. nElapsedTime = stopwatch.GetElapsedTime();
  78. if( (nElapsedTime > elapsedTimeLimit) && (bDisableTimingTest == false) )
  79. {
  80. nTookTooLongCount++;
  81. }
  82. }
  83. // Until we can get a way to prevent thread context switches during our tests, we allow as much as one to occur during our tests.
  84. EATEST_VERIFY(nTookTooLongCount <= 1);
  85. EA::UnitTest::SetNormalThreadPriority();
  86. #ifdef EA_PLATFORM_ANDROID
  87. }
  88. #endif
  89. ////////////////////////////////////////////////////////////////////////////
  90. ////////////////////////////////////////////////////////////////////////////
  91. // Do a simple test to verify that Stopwatch::GetStopwatchCycle() always
  92. // generates a value greater than before. There is a problem on some
  93. // multi-processing platforms whereby the processors can have different
  94. // cpu tick values and the OS moves the process between the given processors,
  95. // resulting in one processing giving a different tick count than another.
  96. bool bMultiCPUImprecisionPresent = false;
  97. {
  98. uint64_t previous = Stopwatch::GetStopwatchCycle();
  99. uint64_t current = previous;
  100. LimitStopwatch limitStopwatch(Stopwatch::kUnitsMilliseconds);
  101. limitStopwatch.SetTimeLimit(1000); // 1 second
  102. while(!limitStopwatch.IsTimeUp() && (current >= previous))
  103. {
  104. previous = current;
  105. current = Stopwatch::GetStopwatchCycle();
  106. }
  107. if(current < previous) // If the stopwatch appears to have gone backwards...
  108. {
  109. // We -may- have a multi-processing platform with unsynchronized CPUs.
  110. // Many platforms that use CPU-based cycle counting don't have perfectly synchronized
  111. // multiple CPUs. Stopwatch doesn't try to account for that, as it's not a problem
  112. // that can be solved in a simple fast way. If we aren't dealing with a
  113. // mulitple CPU thing then we may have a true stopwatch rollover situation.
  114. // Some platforms have weaker CPU cycle counting systems which roll over
  115. // quickly. Such an example is the Playbook platform, and there is little we
  116. // can do about it in the raw GetStopwatchCycle function. Our Stopwatch class
  117. // can handle it due to having contextual information.
  118. // Ideally we would have a good way to tell if this is a multi-CPU thing.
  119. // Allow only a tiny amount of imprecision between CPUs.
  120. bMultiCPUImprecisionPresent = (current + (current * 100 / 99)) >= previous; // Note by Paul Pedriana: Isn't the math here wrong and the "* 100" shouldn't be there? And even if it was there we still have the problem of that + operation causing current to roll over UINT64_MAX.
  121. EATEST_VERIFY(bMultiCPUImprecisionPresent);
  122. }
  123. }
  124. ////////////////////////////////////////////////////////////////////////////
  125. // Do a test that verifies that the stopwatch will not report a negative
  126. // time and also that each time GetElapsedTime is called, it will
  127. // be >= to the result of the last call to GetElapsedTime.
  128. uint64_t nCurrentElapsedTime(0), nPreviousElapsedTime(0);
  129. stopwatchHelper1.SetUnits(Stopwatch::kUnitsSeconds);
  130. stopwatchHelper1.Restart();
  131. stopwatch.SetUnits(Stopwatch::kUnitsCycles);
  132. stopwatch.Restart();
  133. bResult = true;
  134. while(bResult && (stopwatchHelper1.GetElapsedTime() < 2))
  135. {
  136. nCurrentElapsedTime = stopwatch.GetElapsedTime();
  137. bResult = (((int64_t)nCurrentElapsedTime >= 0) && (nCurrentElapsedTime >= nPreviousElapsedTime));
  138. EATEST_VERIFY((int64_t)nCurrentElapsedTime >= 0);
  139. EATEST_VERIFY((nCurrentElapsedTime >= nPreviousElapsedTime) || bMultiCPUImprecisionPresent);
  140. nPreviousElapsedTime = nCurrentElapsedTime;
  141. // Just idle away some time.
  142. char buffer[16];
  143. for(int i(0), iEnd(rand() % 10000); i < iEnd; i++)
  144. sprintf(buffer, "%d", i); // Just do a little nothing.
  145. }
  146. ////////////////////////////////////////////////////////////////////////////
  147. ////////////////////////////////////////////////////////////////////////////
  148. // Do a test whereby we use two stopwatchs (one being EA::StdC::Stopwatch and the other
  149. // being a system stopwatch) and see if they yield similar results.
  150. if(EA::StdC::GetTimePrecision() < 10000000) // If the GetTime function has precision of at least 10 milliseconds (10000000 ns)...
  151. {
  152. uint64_t nStopwatchElapsedTime(0);
  153. uint64_t nClockStartTime(0);
  154. uint64_t nClockEndTime(0);
  155. uint64_t nClockElapsedTime(0);
  156. bool bStopwatchComparisonTestSucceeded(true);
  157. //EA::UnitTest::Report("A %u second time comparison test will now be run.\n"
  158. // "Press 'esc' to quit this test early.\n", nSecondsForEachTest);
  159. //EA::UnitTest::Report("The results are printed as percentages.\n"
  160. // "100.0%% is perfect, while 90%% and 110%% are both bad.\n");
  161. stopwatchHelper1.SetUnits(Stopwatch::kUnitsSeconds);
  162. stopwatchHelper1.Restart();
  163. stopwatchHelper2.SetUnits(Stopwatch::kUnitsMilliseconds);
  164. stopwatch.Reset();
  165. stopwatch.SetUnits(Stopwatch::kUnitsMilliseconds);
  166. // Now get the current times. We need to somehow prevent a context switch
  167. // here because a context switch bewteen the two stopwatch elapsed time
  168. // calculations will throw off the precision of the comparison.
  169. EA::UnitTest::SetHighThreadPriority();
  170. stopwatch.Start();
  171. nClockStartTime = EA::StdC::GetTime() / 1000000; // Convert from ns to ms.
  172. EA::UnitTest::SetNormalThreadPriority();
  173. bResult = true;
  174. while(bResult && (stopwatchHelper1.GetElapsedTime() < 5))
  175. {
  176. // Sleep for some small random amount of time.
  177. EA::UnitTest::ThreadSleepRandom(1000, 2000, false);
  178. // Now get the current times. We need to somehow prevent a context switch
  179. // here because a context switch bewteen the two stopwatch elapsed time
  180. // calculations will throw off the precision of the comparison.
  181. EA::UnitTest::SetHighThreadPriority();
  182. nStopwatchElapsedTime = stopwatch.GetElapsedTime();
  183. nClockEndTime = EA::StdC::GetTime() / 1000000; // Convert from ns to ms.
  184. nClockElapsedTime = nClockEndTime - nClockStartTime;
  185. EA::UnitTest::SetNormalThreadPriority();
  186. //Now compare the results.
  187. #if defined(EA_PLATFORM_DESKTOP)
  188. const float kAllowedAccuracyMin = 0.96f; // Windows/Unix do multitasking and can delay threads arbitrarily.
  189. const float kAllowedAccuracyMax = 1.04f;
  190. #else
  191. const float kAllowedAccuracyMin = 0.98f;
  192. const float kAllowedAccuracyMax = 1.02f;
  193. #endif
  194. const float fAccuracy = (float)nStopwatchElapsedTime / (float)nClockElapsedTime;
  195. if((fAccuracy < kAllowedAccuracyMin) || (fAccuracy > kAllowedAccuracyMax))
  196. {
  197. if(bStopwatchComparisonTestSucceeded)
  198. {
  199. bStopwatchComparisonTestSucceeded = false;
  200. bResult = false;
  201. EA::UnitTest::Report("Stopwatch accuracy failure. fAccuracy: %f\n", fAccuracy);
  202. EATEST_VERIFY(false);
  203. }
  204. }
  205. //EA::UnitTest::Report(" Clock: %6d ms, Stopwatch: %6d ms. Accuracy: %5.2f%%\n", nClockElapsedTime, nStopwatchElapsedTime, fAccuracy*100.F);
  206. }
  207. }
  208. ////////////////////////////////////////////////////////////////////////////
  209. ////////////////////////////////////////////////////////////////////////////
  210. // Test SetElapsedTime functionality.
  211. stopwatch.SetUnits(Stopwatch::kUnitsMilliseconds);
  212. stopwatch.Restart();
  213. // <No time passed since the restart>
  214. stopwatch.SetElapsedTime(30000); // 30 seconds
  215. nElapsedTime = stopwatch.GetElapsedTime();
  216. #if defined(EA_PLATFORM_DESKTOP)
  217. const uint64_t kAllowedElapsedTimeMin = 29990;
  218. const uint64_t kAllowedElapsedTimeMax = 31000;
  219. #else
  220. const uint64_t kAllowedElapsedTimeMin = 29990;
  221. const uint64_t kAllowedElapsedTimeMax = 30500;
  222. #endif
  223. EATEST_VERIFY((nElapsedTime >= kAllowedElapsedTimeMin) && (nElapsedTime <= kAllowedElapsedTimeMax));
  224. stopwatch.SetUnits(Stopwatch::kUnitsMilliseconds);
  225. stopwatch.Restart();
  226. while(stopwatch.GetElapsedTime() < 3000)
  227. {
  228. char buffer[32];
  229. EA::StdC::Sprintf(buffer, "%I64u", stopwatch.GetElapsedTime()); // Just spin.
  230. }
  231. stopwatch.SetElapsedTime(30000); // 30 seconds
  232. nElapsedTime = stopwatch.GetElapsedTime();
  233. EATEST_VERIFY((nElapsedTime >= kAllowedElapsedTimeMin) && (nElapsedTime <= kAllowedElapsedTimeMax));
  234. stopwatch.SetUnits(Stopwatch::kUnitsMinutes);
  235. stopwatch.Restart();
  236. stopwatch.SetElapsedTimeFloat(0.5f); // 30 seconds
  237. stopwatch.SetUnits(Stopwatch::kUnitsMilliseconds);
  238. nElapsedTime = stopwatch.GetElapsedTime();
  239. EATEST_VERIFY((nElapsedTime >= kAllowedElapsedTimeMin) && (nElapsedTime <= kAllowedElapsedTimeMax));
  240. ////////////////////////////////////////////////////////////////////////////
  241. // This test has been disabled as it's proving to be unreliable due to generating false failures.
  242. #if 0
  243. ////////////////////////////////////////////////////////////////////////////
  244. // Test SetTimeLimit functionality.
  245. stopwatchHelper1.SetUnits(Stopwatch::kUnitsMilliseconds);
  246. stopwatchHelper1.Start();
  247. LimitStopwatch limitStopwatch(Stopwatch::kUnitsMilliseconds);
  248. limitStopwatch.SetTimeLimit(5000); // 5 seconds
  249. bResult = true;
  250. const float lowLimit = -0.5f; // If the platform is very slow or is pre-empting this thread a lot, we may need to make this value lower,
  251. const float highLimit = 5.0f; // .. and make this value higher.
  252. while(bResult && !limitStopwatch.IsTimeUp())
  253. {
  254. const float fTimeRemaining(limitStopwatch.GetTimeRemainingFloat());
  255. // Verify that when the time is up, the time remaining is close to zero.
  256. if(limitStopwatch.IsTimeUp())
  257. {
  258. if(!((fTimeRemaining >= lowLimit) && (fTimeRemaining < highLimit)))
  259. {
  260. bResult = false;
  261. EA::UnitTest::Report("LimitStopwatch failure. Time remaining: %f\n", fTimeRemaining);
  262. EATEST_VERIFY(((fTimeRemaining >= lowLimit) && (fTimeRemaining < highLimit)));
  263. }
  264. }
  265. }
  266. nElapsedTime = stopwatchHelper1.GetElapsedTime();
  267. EATEST_VERIFY((nElapsedTime >= 5000) || (nElapsedTime <= 5500));
  268. ////////////////////////////////////////////////////////////////////////////
  269. #endif
  270. ////////////////////////////////////////////////////////////////////////////
  271. // Test GetStopwatchCyclesPerUnit / GetCPUCyclesPerUnit
  272. //
  273. Stopwatch stopwatchTemp(Stopwatch::kUnitsMilliseconds, true);
  274. #if !defined(EASTDC_SWAPPABLE_PROCESS_PLATFORM)
  275. const uint64_t nStartStopwatchCycle = Stopwatch::GetStopwatchCycle();
  276. const uint64_t nStartCPUCycle = Stopwatch::GetCPUCycle();
  277. #endif
  278. const uint64_t nTestDuration = 1000; // run test for one second
  279. const int nMaxDrift = 50; // a small amount of drift is just noise
  280. // Loop until the test duration has passed, making sure the float time tracks the integer time.
  281. bool bDriftDetectedPrev = false;
  282. uint64_t elapsedTime_i = 0;
  283. uint64_t elapsedTime_f = 0;
  284. while (elapsedTime_i < nTestDuration && elapsedTime_f < nTestDuration)
  285. {
  286. // measure time and detect drift
  287. elapsedTime_i = stopwatchTemp.GetElapsedTime();
  288. elapsedTime_f = static_cast<uint64_t>(stopwatchTemp.GetElapsedTimeFloat());
  289. int delta = static_cast<int>(elapsedTime_f - elapsedTime_i);
  290. bool bDriftDetected = (delta <= -nMaxDrift || nMaxDrift <= delta);
  291. // fail only once there is significant drift twice in a row.
  292. // This avoids false positives due to pre-emption between measurements
  293. EATEST_VERIFY(bDriftDetected == false || bDriftDetectedPrev == false);
  294. bDriftDetectedPrev = bDriftDetected;
  295. }
  296. stopwatchTemp.Stop();
  297. // For platforms where the main process can be swapped out for extended periods of time,
  298. // we disable the test on the buildfarm (via the manifest.xml)
  299. #if !defined(EASTDC_SWAPPABLE_PROCESS_PLATFORM)
  300. // Make sure the manual GetUnitsPerStopwatchCycle and GetStopwatchCycle yield results that are equivalent to using Start/Stop/GetElapsedTime.
  301. uint64_t nStopwatchCycles = Stopwatch::GetStopwatchCycle() - nStartStopwatchCycle;
  302. const float fUnitsPerStopwatchCycle = Stopwatch::GetUnitsPerStopwatchCycle(Stopwatch::kUnitsSeconds);
  303. const float fStopwatchTime = nStopwatchCycles * fUnitsPerStopwatchCycle;
  304. const uint64_t nStopwatchTime = (uint64_t)(int64_t)(fStopwatchTime + 0.49999f); // Add 0.49999f like GetElapsedTime does for integer rounding
  305. EATEST_VERIFY_F(nStopwatchTime == 1, "TestStopwatch failure: nStopwatchTime: %I64us, expected: 1s", nStopwatchTime);
  306. // Make sure the manual GetUnitsPerCPUCycle and GetCPUCycle yield results that are equivalent to using Start/Stop/GetElapsedTime.
  307. uint64_t nCPUCycles = Stopwatch::GetCPUCycle() - nStartCPUCycle;
  308. const float fUnitsPerCPUCycle = Stopwatch::GetUnitsPerCPUCycle(Stopwatch::kUnitsSeconds);
  309. const float fCPUTime = nCPUCycles * fUnitsPerCPUCycle;
  310. const uint64_t nCPUTime = (uint64_t)(int64_t)(fCPUTime + 0.49999f); // Add 0.49999f like GetElapsedTime does for integer rounding
  311. EATEST_VERIFY_F(nCPUTime == 1, "TestStopwatch failure: nCPUTime: %I64us, expected: 1s", nCPUTime);
  312. #endif
  313. /*{
  314. // Regression to exercize the report that PS4 rdtsc is very slow. Conclusion: it is in fact not intrinsically slow and executes similar to XBox One rdtsc. However, something about the user's code may have been causing usage of it to act slowly for them.
  315. uint64_t i = 0;
  316. uint64_t sum = 0; // We have this only to prevent the GetCPUCycle call from being optimized away.
  317. EA::StdC::Stopwatch stopwatchTime(EA::StdC::Stopwatch::kUnitsMilliseconds, true);
  318. uint64_t start = EA::StdC::Stopwatch::GetCPUCycle();
  319. do
  320. {
  321. sum += EA::StdC::Stopwatch::GetCPUCycle();
  322. sum += EA::StdC::Stopwatch::GetCPUCycle();
  323. sum += EA::StdC::Stopwatch::GetCPUCycle();
  324. sum += EA::StdC::Stopwatch::GetCPUCycle();
  325. sum += EA::StdC::Stopwatch::GetCPUCycle();
  326. sum += EA::StdC::Stopwatch::GetCPUCycle();
  327. sum += EA::StdC::Stopwatch::GetCPUCycle();
  328. i += 8;
  329. } while(stopwatchTime.GetElapsedTime() < 1000);
  330. uint64_t stop = EA::StdC::Stopwatch::GetCPUCycle();
  331. uint64_t cost = ((stop - start) / i);
  332. EA::UnitTest::Report("Stopwatch::GetCPUCycle cost per call: ~%I64u cycles (overestimated due to other stuff we do here). Sum = %I64u (irrelevant).", cost, sum);
  333. }*/
  334. return nErrorCount;
  335. }