123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include <Jolt/Jolt.h>
- #include <Jolt/Core/Profiler.h>
- #include <Jolt/Core/Color.h>
- #include <Jolt/Core/StringTools.h>
- #include <Jolt/Core/QuickSort.h>
- JPH_SUPPRESS_WARNINGS_STD_BEGIN
- #include <fstream>
- JPH_SUPPRESS_WARNINGS_STD_END
- #ifdef JPH_PROFILE_ENABLED
- JPH_NAMESPACE_BEGIN
- //////////////////////////////////////////////////////////////////////////////////////////
- // Profiler
- //////////////////////////////////////////////////////////////////////////////////////////
- Profiler *Profiler::sInstance = nullptr;
- thread_local ProfileThread *ProfileThread::sInstance = nullptr;
- bool ProfileMeasurement::sOutOfSamplesReported = false;
- void Profiler::NextFrame()
- {
- std::lock_guard lock(mLock);
- if (mDump)
- {
- DumpInternal();
- mDump = false;
- }
- for (ProfileThread *t : mThreads)
- t->mCurrentSample = 0;
- }
- void Profiler::Dump(const string_view &inTag)
- {
- mDump = true;
- mDumpTag = inTag;
- }
- void Profiler::AddThread(ProfileThread *inThread)
- {
- std::lock_guard lock(mLock);
- mThreads.push_back(inThread);
- }
- void Profiler::RemoveThread(ProfileThread *inThread)
- {
- std::lock_guard lock(mLock);
-
- Array<ProfileThread *>::iterator i = find(mThreads.begin(), mThreads.end(), inThread);
- JPH_ASSERT(i != mThreads.end());
- mThreads.erase(i);
- }
- void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator)
- {
- // Store depth
- ioSample->mDepth = uint8(min(255, inDepth));
- // Update color
- if (ioSample->mColor == 0)
- ioSample->mColor = inColor;
- else
- inColor = ioSample->mColor;
- // Start accumulating totals
- uint64 cycles_this_with_children = ioSample->mEndCycle - ioSample->mStartCycle;
- uint64 cycles_in_children = 0;
- // Loop over following samples until we find a sample that starts on or after our end
- ProfileSample *sample;
- for (sample = ioSample + 1; sample < inEnd && sample->mStartCycle < ioSample->mEndCycle; ++sample)
- {
- JPH_ASSERT(sample[-1].mStartCycle <= sample->mStartCycle);
- JPH_ASSERT(sample->mStartCycle >= ioSample->mStartCycle);
- JPH_ASSERT(sample->mEndCycle <= ioSample->mEndCycle);
- // This is a direct child of us, accumulate time
- cycles_in_children += sample->mEndCycle - sample->mStartCycle;
- // Recurse and skip over the children of this child
- sAggregate(inDepth + 1, inColor, sample, inEnd, ioAggregators, ioKeyToAggregator);
- }
- // Find the aggregator for this name / filename pair
- Aggregator *aggregator;
- KeyToAggregator::iterator aggregator_idx = ioKeyToAggregator.find(ioSample->mName);
- if (aggregator_idx == ioKeyToAggregator.end())
- {
- // Not found, add to map and insert in array
- ioKeyToAggregator.try_emplace(ioSample->mName, ioAggregators.size());
- ioAggregators.emplace_back(ioSample->mName);
- aggregator = &ioAggregators.back();
- }
- else
- {
- // Found
- aggregator = &ioAggregators[aggregator_idx->second];
- }
- // Add the measurement to the aggregator
- aggregator->AccumulateMeasurement(cycles_this_with_children, cycles_in_children);
- // Update ioSample to the last child of ioSample
- JPH_ASSERT(sample[-1].mStartCycle <= ioSample->mEndCycle);
- JPH_ASSERT(sample >= inEnd || sample->mStartCycle >= ioSample->mEndCycle);
- ioSample = sample - 1;
- }
- void Profiler::DumpInternal()
- {
- // Freeze data from threads
- // Note that this is not completely thread safe: As a profile sample is added mCurrentSample is incremented
- // but the data is not written until the sample finishes. So if we dump the profile information while
- // some other thread is running, we may get some garbage information from the previous frame
- Threads threads;
- for (ProfileThread *t : mThreads)
- threads.push_back({ t->mThreadName, t->mSamples, t->mSamples + t->mCurrentSample });
-
- // Shift all samples so that the first sample is at zero
- uint64 min_cycle = 0xffffffffffffffffUL;
- for (const ThreadSamples &t : threads)
- if (t.mSamplesBegin < t.mSamplesEnd)
- min_cycle = min(min_cycle, t.mSamplesBegin[0].mStartCycle);
- for (const ThreadSamples &t : threads)
- for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- s->mStartCycle -= min_cycle;
- s->mEndCycle -= min_cycle;
- }
- // Determine tag of this profile
- String tag;
- if (mDumpTag.empty())
- {
- // Next sequence number
- static int number = 0;
- ++number;
- tag = ConvertToString(number);
- }
- else
- {
- // Take provided tag
- tag = mDumpTag;
- mDumpTag.clear();
- }
- // Aggregate data across threads
- Aggregators aggregators;
- KeyToAggregator key_to_aggregators;
- for (const ThreadSamples &t : threads)
- for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- sAggregate(0, Color::sGetDistinctColor(0).GetUInt32(), s, end, aggregators, key_to_aggregators);
- // Dump as list
- DumpList(tag.c_str(), aggregators);
- // Dump as chart
- DumpChart(tag.c_str(), threads, key_to_aggregators, aggregators);
- }
- static String sHTMLEncode(const char *inString)
- {
- String str(inString);
- StringReplace(str, "<", "<");
- StringReplace(str, ">", ">");
- return str;
- }
- void Profiler::DumpList(const char *inTag, const Aggregators &inAggregators)
- {
- // Open file
- std::ofstream f;
- f.open(StringFormat("profile_list_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
- if (!f.is_open())
- return;
- // Write header
- f << R"(<!DOCTYPE html>
- <html>
- <head>
- <title>Profile List</title>
- <link rel="stylesheet" href="WebIncludes/semantic.min.css">
- <script type="text/javascript" src="WebIncludes/jquery-3.2.1.min.js"></script>
- <script type="text/javascript" src="WebIncludes/semantic.min.js"></script>
- <script type="text/javascript" src="WebIncludes/tablesort.js"></script>
- <script type="text/javascript">$(document).ready(function() { $('table').tablesort({ compare: function(a, b) { return isNaN(a) || isNaN(b)? a.localeCompare(b) : Number(a) - Number(b); } }); });</script>
- </head>
- <body class="minimal pushable">
- <table id="profile" class="ui sortable celled striped table">
- <thead>
- <tr>
- <th>Description</th>
- <th class="sorted descending">Total time with children (%)</th>
- <th>Total time (%)</th>
- <th>Calls</th>
- <th>µs / call with children</th>
- <th>µs / call</th>
- <th>Min. µs / call</th>
- <th>Max. µs / call</th>
- </tr>
- </thead>
- <tbody style="text-align: right;">
- )";
- // Get total time
- uint64 total_time = 0;
- for (const Aggregator &item : inAggregators)
- total_time += item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren;
- // Get cycles per second
- uint64 cycles_per_second = GetProcessorTicksPerSecond();
-
- // Sort the list
- Aggregators aggregators = inAggregators;
- QuickSort(aggregators.begin(), aggregators.end());
-
- // Write all aggregators
- for (const Aggregator &item : aggregators)
- {
- uint64 cycles_in_call_no_children = item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren;
- char str[2048];
- snprintf(str, sizeof(str), R"(<tr>
- <td style="text-align: left;">%s</td>
- <td>%.1f</td>
- <td>%.1f</td>
- <td>%u</td>
- <td>%.2f</td>
- <td>%.2f</td>
- <td>%.2f</td>
- <td>%.2f</td>
- </tr>)",
- sHTMLEncode(item.mName).c_str(), // Description
- 100.0 * item.mTotalCyclesInCallWithChildren / total_time, // Total time with children
- 100.0 * cycles_in_call_no_children / total_time, // Total time no children
- item.mCallCounter, // Calls
- 1000000.0 * item.mTotalCyclesInCallWithChildren / cycles_per_second / item.mCallCounter, // us / call with children
- 1000000.0 * cycles_in_call_no_children / cycles_per_second / item.mCallCounter, // us / call no children
- 1000000.0 * item.mMinCyclesInCallWithChildren / cycles_per_second, // Min. us / call with children
- 1000000.0 * item.mMaxCyclesInCallWithChildren / cycles_per_second); // Max. us / call with children
- f << str;
- }
- // End table
- f << R"(</tbody></table></body></html>)";
- }
- void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators)
- {
- // Open file
- std::ofstream f;
- f.open(StringFormat("profile_chart_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
- if (!f.is_open())
- return;
- // Write header
- f << R"(<!DOCTYPE html>
- <html>
- <head>
- <title>Profile Chart</title>
- <link rel="stylesheet" href="WebIncludes/profile_chart.css">
- <script type="text/javascript" src="WebIncludes/profile_chart.js"></script>
- </head>
- <body onload="startChart();">
- <script type="text/javascript">
- )";
- // Get cycles per second
- uint64 cycles_per_second = GetProcessorTicksPerSecond();
- f << "var cycles_per_second = " << cycles_per_second << ";\n";
- // Dump samples
- f << "var threads = [\n";
- bool first_thread = true;
- for (const ThreadSamples &t : inThreads)
- {
- if (!first_thread)
- f << ",\n";
- first_thread = false;
- f << "{\nthread_name: \"" << t.mThreadName << "\",\naggregator: [";
- bool first = true;
- for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- if (!first)
- f << ",";
- first = false;
- f << inKeyToAggregators.find(s->mName)->second;
- }
- f << "],\ncolor: [";
- first = true;
- for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- if (!first)
- f << ",";
- first = false;
- Color c(s->mColor);
- f << StringFormat("\"#%02x%02x%02x\"", c.r, c.g, c.b);
- }
- f << "],\nstart: [";
- first = true;
- for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- if (!first)
- f << ",";
- first = false;
- f << s->mStartCycle;
- }
- f << "],\ncycles: [";
- first = true;
- for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- if (!first)
- f << ",";
- first = false;
- f << s->mEndCycle - s->mStartCycle;
- }
- f << "],\ndepth: [";
- first = true;
- for (const ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
- {
- if (!first)
- f << ",";
- first = false;
- f << int(s->mDepth);
- }
- f << "]\n}";
- }
- // Dump aggregated data
- f << "];\nvar aggregated = {\nname: [";
- bool first = true;
- for (const Aggregator &a : inAggregators)
- {
- if (!first)
- f << ",";
- first = false;
- String name = "\"" + sHTMLEncode(a.mName) + "\"";
- f << name;
- }
- f << "],\ncalls: [";
- first = true;
- for (const Aggregator &a : inAggregators)
- {
- if (!first)
- f << ",";
- first = false;
- f << a.mCallCounter;
- }
- f << "],\nmin_cycles: [";
- first = true;
- for (const Aggregator &a : inAggregators)
- {
- if (!first)
- f << ",";
- first = false;
- f << a.mMinCyclesInCallWithChildren;
- }
- f << "],\nmax_cycles: [";
- first = true;
- for (const Aggregator &a : inAggregators)
- {
- if (!first)
- f << ",";
- first = false;
- f << a.mMaxCyclesInCallWithChildren;
- }
- f << "],\ncycles_per_frame: [";
- first = true;
- for (const Aggregator &a : inAggregators)
- {
- if (!first)
- f << ",";
- first = false;
- f << a.mTotalCyclesInCallWithChildren;
- }
- // Write footer
- f << R"(]};
- </script>
- <canvas id="canvas"></canvas>
- <div id="tooltip"></div>
- </tbody></table></body></html>)";
- }
- JPH_NAMESPACE_END
- #endif // JPH_PROFILE_ENABLED
|