Profiler.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Core/Profiler.h>
  6. #include <Jolt/Core/Color.h>
  7. #include <Jolt/Core/StringTools.h>
  8. #include <Jolt/Core/QuickSort.h>
  9. JPH_SUPPRESS_WARNINGS_STD_BEGIN
  10. #include <fstream>
  11. JPH_SUPPRESS_WARNINGS_STD_END
  12. JPH_NAMESPACE_BEGIN
  13. #if defined(JPH_EXTERNAL_PROFILE) && defined(JPH_SHARED_LIBRARY)
  14. ProfileStartMeasurementFunction ProfileStartMeasurement = [](const char *, uint32, uint8 *) { };
  15. ProfileEndMeasurementFunction ProfileEndMeasurement = [](uint8 *) { };
  16. #elif defined(JPH_PROFILE_ENABLED)
  17. //////////////////////////////////////////////////////////////////////////////////////////
  18. // Profiler
  19. //////////////////////////////////////////////////////////////////////////////////////////
  20. Profiler *Profiler::sInstance = nullptr;
  21. #ifdef JPH_SHARED_LIBRARY
  22. static thread_local ProfileThread *sInstance = nullptr;
  23. ProfileThread *ProfileThread::sGetInstance()
  24. {
  25. return sInstance;
  26. }
  27. void ProfileThread::sSetInstance(ProfileThread *inInstance)
  28. {
  29. sInstance = inInstance;
  30. }
  31. #else
  32. thread_local ProfileThread *ProfileThread::sInstance = nullptr;
  33. #endif
  34. bool ProfileMeasurement::sOutOfSamplesReported = false;
  35. void Profiler::UpdateReferenceTime()
  36. {
  37. mReferenceTick = GetProcessorTickCount();
  38. mReferenceTime = std::chrono::high_resolution_clock::now();
  39. }
  40. uint64 Profiler::GetProcessorTicksPerSecond() const
  41. {
  42. uint64 ticks = GetProcessorTickCount();
  43. std::chrono::high_resolution_clock::time_point time = std::chrono::high_resolution_clock::now();
  44. return (ticks - mReferenceTick) * 1000000000ULL / std::chrono::duration_cast<std::chrono::nanoseconds>(time - mReferenceTime).count();
  45. }
  46. // This function assumes that none of the threads are active while we're dumping the profile,
  47. // otherwise there will be a race condition on mCurrentSample and the profile data.
  48. JPH_TSAN_NO_SANITIZE
  49. void Profiler::NextFrame()
  50. {
  51. std::lock_guard lock(mLock);
  52. if (mDump)
  53. {
  54. DumpInternal();
  55. mDump = false;
  56. }
  57. for (ProfileThread *t : mThreads)
  58. t->mCurrentSample = 0;
  59. UpdateReferenceTime();
  60. }
  61. void Profiler::Dump(const string_view &inTag)
  62. {
  63. mDump = true;
  64. mDumpTag = inTag;
  65. }
  66. void Profiler::AddThread(ProfileThread *inThread)
  67. {
  68. std::lock_guard lock(mLock);
  69. mThreads.push_back(inThread);
  70. }
  71. void Profiler::RemoveThread(ProfileThread *inThread)
  72. {
  73. std::lock_guard lock(mLock);
  74. Array<ProfileThread *>::iterator i = std::find(mThreads.begin(), mThreads.end(), inThread);
  75. JPH_ASSERT(i != mThreads.end());
  76. mThreads.erase(i);
  77. }
  78. void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator)
  79. {
  80. // Store depth
  81. ioSample->mDepth = uint8(min(255, inDepth));
  82. // Update color
  83. if (ioSample->mColor == 0)
  84. ioSample->mColor = inColor;
  85. else
  86. inColor = ioSample->mColor;
  87. // Start accumulating totals
  88. uint64 cycles_this_with_children = ioSample->mEndCycle - ioSample->mStartCycle;
  89. // Loop over following samples until we find a sample that starts on or after our end
  90. ProfileSample *sample;
  91. for (sample = ioSample + 1; sample < inEnd && sample->mStartCycle < ioSample->mEndCycle; ++sample)
  92. {
  93. JPH_ASSERT(sample[-1].mStartCycle <= sample->mStartCycle);
  94. JPH_ASSERT(sample->mStartCycle >= ioSample->mStartCycle);
  95. JPH_ASSERT(sample->mEndCycle <= ioSample->mEndCycle);
  96. // Recurse and skip over the children of this child
  97. sAggregate(inDepth + 1, inColor, sample, inEnd, ioAggregators, ioKeyToAggregator);
  98. }
  99. // Find the aggregator for this name / filename pair
  100. Aggregator *aggregator;
  101. KeyToAggregator::iterator aggregator_idx = ioKeyToAggregator.find(ioSample->mName);
  102. if (aggregator_idx == ioKeyToAggregator.end())
  103. {
  104. // Not found, add to map and insert in array
  105. ioKeyToAggregator.try_emplace(ioSample->mName, ioAggregators.size());
  106. ioAggregators.emplace_back(ioSample->mName);
  107. aggregator = &ioAggregators.back();
  108. }
  109. else
  110. {
  111. // Found
  112. aggregator = &ioAggregators[aggregator_idx->second];
  113. }
  114. // Add the measurement to the aggregator
  115. aggregator->AccumulateMeasurement(cycles_this_with_children);
  116. // Update ioSample to the last child of ioSample
  117. JPH_ASSERT(sample[-1].mStartCycle <= ioSample->mEndCycle);
  118. JPH_ASSERT(sample >= inEnd || sample->mStartCycle >= ioSample->mEndCycle);
  119. ioSample = sample - 1;
  120. }
  121. void Profiler::DumpInternal()
  122. {
  123. // Freeze data from threads
  124. // Note that this is not completely thread safe: As a profile sample is added mCurrentSample is incremented
  125. // but the data is not written until the sample finishes. So if we dump the profile information while
  126. // some other thread is running, we may get some garbage information from the previous frame
  127. Threads threads;
  128. for (ProfileThread *t : mThreads)
  129. threads.push_back({ t->mThreadName, t->mSamples, t->mSamples + t->mCurrentSample });
  130. // Shift all samples so that the first sample is at zero
  131. uint64 min_cycle = 0xffffffffffffffffUL;
  132. for (const ThreadSamples &t : threads)
  133. if (t.mSamplesBegin < t.mSamplesEnd)
  134. min_cycle = min(min_cycle, t.mSamplesBegin[0].mStartCycle);
  135. for (const ThreadSamples &t : threads)
  136. for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  137. {
  138. s->mStartCycle -= min_cycle;
  139. s->mEndCycle -= min_cycle;
  140. }
  141. // Determine tag of this profile
  142. String tag;
  143. if (mDumpTag.empty())
  144. {
  145. // Next sequence number
  146. static int number = 0;
  147. ++number;
  148. tag = ConvertToString(number);
  149. }
  150. else
  151. {
  152. // Take provided tag
  153. tag = mDumpTag;
  154. mDumpTag.clear();
  155. }
  156. // Aggregate data across threads
  157. Aggregators aggregators;
  158. KeyToAggregator key_to_aggregators;
  159. for (const ThreadSamples &t : threads)
  160. for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  161. sAggregate(0, Color::sGetDistinctColor(0).GetUInt32(), s, end, aggregators, key_to_aggregators);
  162. // Dump as chart
  163. DumpChart(tag.c_str(), threads, key_to_aggregators, aggregators);
  164. }
  165. static String sHTMLEncode(const char *inString)
  166. {
  167. String str(inString);
  168. StringReplace(str, "<", "&lt;");
  169. StringReplace(str, ">", "&gt;");
  170. return str;
  171. }
  172. void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators)
  173. {
  174. // Open file
  175. std::ofstream f;
  176. f.open(StringFormat("profile_chart_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
  177. if (!f.is_open())
  178. return;
  179. // Write header
  180. f << R"(<!DOCTYPE html>
  181. <html>
  182. <head>
  183. <title>Profile Chart</title>
  184. <link rel="stylesheet" href="WebIncludes/profile_chart.css">
  185. <script type="text/javascript" src="WebIncludes/profile_chart.js"></script>
  186. </head>
  187. <body onload="startChart();">
  188. <script type="text/javascript">
  189. )";
  190. // Get cycles per second
  191. uint64 cycles_per_second = GetProcessorTicksPerSecond();
  192. f << "var cycles_per_second = " << cycles_per_second << ";\n";
  193. // Dump samples
  194. f << "var threads = [\n";
  195. bool first_thread = true;
  196. for (const ThreadSamples &t : inThreads)
  197. {
  198. if (!first_thread)
  199. f << ",\n";
  200. first_thread = false;
  201. f << "{\nthread_name: \"" << t.mThreadName << "\",\naggregator: [";
  202. bool first = true;
  203. for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  204. {
  205. if (!first)
  206. f << ",";
  207. first = false;
  208. f << inKeyToAggregators.find(s->mName)->second;
  209. }
  210. f << "],\ncolor: [";
  211. first = true;
  212. for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  213. {
  214. if (!first)
  215. f << ",";
  216. first = false;
  217. Color c(s->mColor);
  218. f << StringFormat("\"#%02x%02x%02x\"", c.r, c.g, c.b);
  219. }
  220. f << "],\nstart: [";
  221. first = true;
  222. for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  223. {
  224. if (!first)
  225. f << ",";
  226. first = false;
  227. f << s->mStartCycle;
  228. }
  229. f << "],\ncycles: [";
  230. first = true;
  231. for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  232. {
  233. if (!first)
  234. f << ",";
  235. first = false;
  236. f << s->mEndCycle - s->mStartCycle;
  237. }
  238. f << "],\ndepth: [";
  239. first = true;
  240. for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
  241. {
  242. if (!first)
  243. f << ",";
  244. first = false;
  245. f << int(s->mDepth);
  246. }
  247. f << "]\n}";
  248. }
  249. // Dump aggregated data
  250. f << "];\nvar aggregated = {\nname: [";
  251. bool first = true;
  252. for (const Aggregator &a : inAggregators)
  253. {
  254. if (!first)
  255. f << ",";
  256. first = false;
  257. String name = "\"" + sHTMLEncode(a.mName) + "\"";
  258. f << name;
  259. }
  260. f << "],\ncalls: [";
  261. first = true;
  262. for (const Aggregator &a : inAggregators)
  263. {
  264. if (!first)
  265. f << ",";
  266. first = false;
  267. f << a.mCallCounter;
  268. }
  269. f << "],\nmin_cycles: [";
  270. first = true;
  271. for (const Aggregator &a : inAggregators)
  272. {
  273. if (!first)
  274. f << ",";
  275. first = false;
  276. f << a.mMinCyclesInCallWithChildren;
  277. }
  278. f << "],\nmax_cycles: [";
  279. first = true;
  280. for (const Aggregator &a : inAggregators)
  281. {
  282. if (!first)
  283. f << ",";
  284. first = false;
  285. f << a.mMaxCyclesInCallWithChildren;
  286. }
  287. f << "],\ncycles_per_frame: [";
  288. first = true;
  289. for (const Aggregator &a : inAggregators)
  290. {
  291. if (!first)
  292. f << ",";
  293. first = false;
  294. f << a.mTotalCyclesInCallWithChildren;
  295. }
  296. // Write footer
  297. f << R"(]};
  298. </script>
  299. <canvas id="canvas"></canvas>
  300. <div id="tooltip"></div>
  301. </tbody></table></body></html>)";
  302. }
  303. #endif // JPH_PROFILE_ENABLED
  304. JPH_NAMESPACE_END