Profiler.cpp 8.8 KB

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