flycast/core/profiler/fc_profiler.cpp

162 lines
5.6 KiB
C++

#include "fc_profiler.h"
#include "log/LogManager.h"
#include "cfg/option.h"
#include "imgui.h"
#include "implot.h"
#include <cassert>
namespace fc_profiler
{
thread_local ProfileThread* ProfileScope::s_thread = nullptr;
std::vector<ProfileThread*> ProfileThread::s_allThreads;
std::recursive_mutex ProfileThread::s_allThreadsLock;
void startThread(const std::string& threadName)
{
if (config::ProfilerEnabled)
{
if (!ProfileScope::s_thread)
{
std::unique_lock<std::recursive_mutex> lock(ProfileThread::s_allThreadsLock);
ProfileScope::s_thread = new ProfileThread();
ProfileThread::s_allThreads.push_back(ProfileScope::s_thread);
}
ProfileThread& profileThread = *ProfileScope::s_thread;
profileThread.scopes.clear();
profileThread.scopes.reserve(FC_PROFILE_SCOPE_RESERVE_SIZE);
profileThread.level = 0;
profileThread.startTicks = std::chrono::high_resolution_clock::now();
profileThread.threadName = threadName;
}
}
void endThread(double warningTime)
{
if (config::ProfilerEnabled)
{
std::unique_lock<std::recursive_mutex> lock(ProfileThread::s_allThreadsLock);
if (!ProfileScope::s_thread)
return;
ProfileThread& profileThread = *ProfileScope::s_thread;
std::chrono::high_resolution_clock::time_point endTicks = std::chrono::high_resolution_clock::now();
std::chrono::microseconds durationMicro = std::chrono::duration_cast<std::chrono::microseconds>(endTicks - profileThread.startTicks);
profileThread.cachedTime = (double)durationMicro.count() / 1000000;
if (profileThread.scopes.size() > 0)
{
profileThread.cachedResultTree.clear();
profileThread.cachedResultTree.resize(1);
ProfileThread::ResultNode* node = &profileThread.cachedResultTree.back();
ProfileThread::ResultNode* parent = node;
u32 currScope = 0;
for (const ProfileSection& section : profileThread.scopes)
{
if (section.scope == currScope)
{
parent->children.push_back(ProfileThread::ResultNode());
node = &parent->children.back();
node->parent = parent;
}
else if (section.scope > currScope)
{
assert(node);
node->children.push_back(ProfileThread::ResultNode());
parent = node;
node = &node->children.back();
node->parent = parent;
}
else if (section.scope < currScope)
{
assert(parent);
parent->children.push_back(ProfileThread::ResultNode());
node = &parent->children.back();
parent = node->parent;
if (!parent)
parent = node;
}
currScope = section.scope;
node->section = section;
}
}
if (config::ProfilerOutputTTY && warningTime > 0.0f && profileThread.cachedTime > warningTime)
{
WARN_LOG(PROFILER, "=== Profiler =======================================================================================");
WARN_LOG(PROFILER, "Frame profile on thread \'%s\' exceeded warning time (duration = %.4fs, limit = %.4fs)", profileThread.threadName.c_str(), profileThread.cachedTime, warningTime);
WARN_LOG(PROFILER, "====================================================================================================");
outputTTY(profileThread.cachedResultTree);
WARN_LOG(PROFILER, "====================================================================================================\n");
}
profileThread.history[profileThread.historyIdx] = profileThread.cachedTime;
profileThread.historyIdx = (profileThread.historyIdx + 1) % FC_PROFILE_HISTORY_MAX_SIZE;
}
}
void drawGUI(const std::vector<ProfileThread::ResultNode>& results)
{
std::unique_lock<std::recursive_mutex> lock(ProfileThread::s_allThreadsLock);
for (const ProfileThread::ResultNode& node : results)
{
if (node.section.function)
{
std::chrono::microseconds scopeTimeMicro = std::chrono::duration_cast<std::chrono::microseconds>(node.section.end - node.section.start);
double scopeTimeS = (double)scopeTimeMicro.count() / 1000000;
char text[256];
std::snprintf(text, 256, "%.3f : %s (%s, %i)", (float)scopeTimeS, node.section.function, node.section.file, node.section.line);
ImGui::TreeNode(text);
}
ImGui::Indent();
drawGUI(node.children);
ImGui::Unindent();
}
}
void outputTTY(const std::vector<ProfileThread::ResultNode>& results)
{
std::unique_lock<std::recursive_mutex> lock(ProfileThread::s_allThreadsLock);
for (const ProfileThread::ResultNode& node : results)
{
std::chrono::microseconds scopeTimeMicro = std::chrono::duration_cast<std::chrono::microseconds>(node.section.end - node.section.start);
double scopeTimeS = (double)scopeTimeMicro.count() / 1000000;
WARN_LOG(PROFILER, "%.4f %*s%s (%s, %i)", scopeTimeS, node.section.scope, "", node.section.function, node.section.file, node.section.line);
outputTTY(node.children);
}
}
void drawGraph(const ProfileThread& profileThread)
{
char threadName[256];
std::snprintf(threadName, 256, "Thread %s", profileThread.threadName.c_str());
if (ImPlot::BeginPlot(threadName, ImVec2(-1, 0), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText))
{
float values[FC_PROFILE_HISTORY_MAX_SIZE];
float max = FLT_MIN;
for (int i = 0; i < FC_PROFILE_HISTORY_MAX_SIZE; i++)
{
values[i] = profileThread.history[i] * 1000.0f;
if (values[i] > max)
max = values[i];
}
ImPlot::SetupAxis(ImAxis_X1, "Frame");
ImPlot::SetupAxis(ImAxis_Y1, "Time (ms)");
ImPlot::SetupAxesLimits(0, FC_PROFILE_HISTORY_MAX_SIZE, 0.0f, max, ImGuiCond_Always);
ImPlot::PlotLine(threadName, values, FC_PROFILE_HISTORY_MAX_SIZE, 1.0f, 0.0f, ImPlotLineFlags_Shaded, profileThread.historyIdx);
ImPlot::EndPlot();
}
}
}