TestStopwatch.cpp 16 KB

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