Add a statistical profiler. Also applied a bugfix b3Profiler.

- This is a compact hierarchical profiler which also stores node statistics
- Might need to use a hash-table to lookup statistic for node because each frame trees are build
This commit is contained in:
Irlan 2019-04-06 18:06:20 -03:00
parent ff535f9f7b
commit 1ef6d46d33
13 changed files with 381 additions and 205 deletions

View File

@ -104,9 +104,13 @@ static void Run()
while (glfwWindowShouldClose(g_window) == 0)
{
g_profiler->Begin();
g_profilerSt->Begin();
g_profiler->BeginScope("Frame");
g_profilerSt->BeginScope("Frame");
g_view->BeginInterface();
if (g_model->IsPaused())
@ -122,6 +126,8 @@ static void Run()
g_model->Update();
g_profilerSt->EndScope();
g_profiler->EndScope();
if (g_settings->drawProfileTree)
@ -129,20 +135,13 @@ static void Run()
g_view->InterfaceProfileTree();
}
g_profilerRecorder->BuildRecords();
if (g_settings->drawProfile)
if (g_settings->drawProfileTreeStats)
{
b3StackArray<ProfilerRecord*, 256> records;
g_profilerRecorder->BuildSortedRecords(records);
for (u32 i = 0; i < records.Count(); ++i)
{
ProfilerRecord* r = records[i];
g_draw->DrawString(b3Color_white, "%s %.4f (min = %.4f) (max = %.4f) (calls = %d) [ms]", r->name, r->elapsed, r->minElapsed, r->maxElapsed, r->callCount);
}
g_view->InterfaceProfileTreeStats();
}
g_profilerSt->End();
g_profiler->End();
g_view->EndInterface();

View File

@ -26,7 +26,7 @@ Model::Model()
g_draw = &m_draw;
g_camera = &m_camera;
g_profiler = &m_profiler;
g_profilerRecorder = &m_profilerRecorder;
g_profilerSt = &m_profilerSt;
#if (PROFILE_JSON == 1)
g_profilerListener = &m_jsonListener;
@ -56,7 +56,7 @@ Model::~Model()
g_draw = nullptr;
g_camera = nullptr;
g_profiler = nullptr;
g_profilerRecorder = nullptr;
g_profilerSt = nullptr;
#if (PROFILE_JSON == 1)
g_profilerListener = nullptr;

View File

@ -21,7 +21,7 @@
#include <testbed/framework/draw.h>
#include <testbed/framework/profiler.h>
#include <testbed/framework/profiler_recorder.h>
#include <testbed/framework/profiler_st.h>
// Set to 1 to write profile events into a .json file. Set to 0 otherwise.
#define PROFILE_JSON 0
@ -71,7 +71,7 @@ private:
Draw m_draw;
Camera m_camera;
Profiler m_profiler;
ProfilerRecorder m_profilerRecorder;
ProfilerSt m_profilerSt;
#if (PROFILE_JSON == 1)
JsonProfiler m_jsonListener;

View File

@ -127,12 +127,13 @@ void Profiler::End()
listener->BeginEvents();
}
RecurseEvents(m_root);
RecurseDestroyNode(m_root);
m_root = nullptr;
if (m_root)
{
RecurseEvents(m_root);
assert(m_root == nullptr);
RecurseDestroyNode(m_root);
m_root = nullptr;
}
if (listener)
{

View File

@ -62,8 +62,6 @@ public:
// Get the root profiler node.
ProfilerNode* GetRoot() { return m_root; }
private:
friend class ProfilerRecorder;
ProfilerNode* CreateNode();
void DestroyNode(ProfilerNode* node);

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include <testbed/framework/profiler_recorder.h>
#include <testbed/framework/profiler.h>
ProfilerRecorder* g_profilerRecorder = nullptr;
ProfilerRecord* ProfilerRecorder::FindRecord(const char* name)
{
for (u32 i = 0; i < m_records.Count(); ++i)
{
ProfilerRecord& r = m_records[i];
if (r.name == name)
{
return &r;
}
}
return nullptr;
}
void ProfilerRecorder::RecurseBuildRecords(ProfilerNode* node)
{
ProfilerRecord* fr = FindRecord(node->name);
if (fr)
{
float64 elapsedTime = node->t1 - node->t0;
fr->elapsed += elapsedTime;
fr->minElapsed = b3Min(fr->minElapsed, elapsedTime);
fr->maxElapsed = b3Max(fr->maxElapsed, elapsedTime);
++fr->callCount;
}
else
{
float64 elapsedTime = node->t1 - node->t0;
ProfilerRecord r;
r.name = node->name;
r.elapsed = elapsedTime;
r.minElapsed = elapsedTime;
r.maxElapsed = elapsedTime;
r.callCount = 1;
m_records.PushBack(r);
}
for (u32 i = 0; i < node->children.Count(); ++i)
{
RecurseBuildRecords(node->children[i]);
}
}
void ProfilerRecorder::BuildRecords()
{
for (u32 i = 0; i < m_records.Count(); ++i)
{
m_records[i].elapsed = 0.0;
m_records[i].callCount = 0;
}
RecurseBuildRecords(g_profiler->m_root);
}
static ProfilerRecord* FindSortedRecord(b3Array<ProfilerRecord*>& records, const char* name)
{
for (u32 i = 0; i < records.Count(); ++i)
{
ProfilerRecord* r = records[i];
if (r->name == name)
{
return r;
}
}
return nullptr;
}
void ProfilerRecorder::RecurseBuildSortedRecords(ProfilerNode* node, b3Array<ProfilerRecord*>& output)
{
ProfilerRecord* fsr = FindSortedRecord(output, node->name);
if (fsr == nullptr)
{
ProfilerRecord* fr = FindRecord(node->name);
assert(fr != nullptr);
// Push back the first ocurrence of call in calling order
output.PushBack(fr);
}
for (u32 i = 0; i < node->children.Count(); ++i)
{
RecurseBuildSortedRecords(node->children[i], output);
}
}
void ProfilerRecorder::BuildSortedRecords(b3Array<ProfilerRecord*>& output)
{
assert(output.Count() == 0);
output.Reserve(m_records.Count());
RecurseBuildSortedRecords(g_profiler->m_root, output);
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef PROFILER_RECORDER_H
#define PROFILER_RECORDER_H
#include <bounce/common/math/math.h>
#include <bounce/common/template/array.h>
struct ProfilerNode;
// A persistent profiler record
struct ProfilerRecord
{
const char* name;
float64 elapsed;
float64 minElapsed;
float64 maxElapsed;
u32 callCount;
};
// This class maintains persistent profiler records
class ProfilerRecorder
{
public:
void BuildRecords();
void BuildSortedRecords(b3Array<ProfilerRecord*>& output);
const b3Array<ProfilerRecord>& GetRecords() const { return m_records; }
private:
void RecurseBuildRecords(ProfilerNode* node);
void RecurseBuildSortedRecords(ProfilerNode* node, b3Array<ProfilerRecord*>& output);
ProfilerRecord* FindRecord(const char* name);
b3StackArray<ProfilerRecord, 256> m_records; // persistent profiler records
};
extern ProfilerRecorder* g_profilerRecorder;
#endif

View File

@ -0,0 +1,199 @@
/*
* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include <testbed/framework/profiler_st.h>
ProfilerSt* g_profilerSt = nullptr;
ProfilerSt::ProfilerSt() : m_pool(sizeof(ProfilerStNode))
{
m_root = nullptr;
m_top = nullptr;
}
ProfilerSt::~ProfilerSt()
{
if (m_root)
{
RecurseDestroyNode(m_root);
}
}
ProfilerStNodeStat* ProfilerSt::FindStat(const char* name)
{
for (u32 i = 0; i < m_stats.Count(); ++i)
{
if (m_stats[i].name == name)
{
return &m_stats[i];
}
}
return nullptr;
}
ProfilerStNodeStat* ProfilerSt::CreateStat()
{
m_stats.PushBack(ProfilerStNodeStat());
return &m_stats.Back();
}
ProfilerStNode* ProfilerSt::CreateNode()
{
void* block = m_pool.Allocate();
return new (block) ProfilerStNode();
}
void ProfilerSt::DestroyNode(ProfilerStNode* node)
{
node->~ProfilerStNode();
m_pool.Free(node);
}
void ProfilerSt::RecurseDestroyNode(ProfilerStNode* node)
{
for (u32 i = 0; i < node->children.Count(); ++i)
{
return RecurseDestroyNode(node->children[i]);
}
DestroyNode(node);
}
static ProfilerStNode* RecurseFindNode(ProfilerStNode* node, const char* name)
{
if (node->name == name)
{
return node;
}
ProfilerStNode* result = nullptr;
for (u32 i = 0; result == nullptr && i < node->children.Count(); ++i)
{
result = RecurseFindNode(node->children[i], name);
}
return result;
}
ProfilerStNode* ProfilerSt::FindNode(const char* name)
{
if (m_top)
{
return RecurseFindNode(m_top, name);
}
if (m_root)
{
return RecurseFindNode(m_root, name);
}
return nullptr;
}
void ProfilerSt::BeginScope(const char* name)
{
ProfilerStNode* fn = FindNode(name);
if (fn)
{
m_time.Update();
fn->t0 = m_time.GetCurrentMilis();
++fn->callCount;
m_top = fn;
return;
}
m_time.Update();
ProfilerStNode* n = CreateNode();
n->name = name;
n->t0 = m_time.GetCurrentMilis();
n->elapsed = 0.0f;
n->callCount = 1;
n->parent = m_top;
n->stat = nullptr;
if (m_root == nullptr)
{
m_root = n;
m_top = n;
return;
}
if (m_top)
{
m_top->children.PushBack(n);
}
m_top = n;
}
void ProfilerSt::EndScope()
{
assert(m_top != nullptr);
m_time.Update();
m_top->t1 = m_time.GetCurrentMilis();
float64 elapsedTime = m_top->t1 - m_top->t0;
m_top->elapsed += elapsedTime;
ProfilerStNodeStat* stat = FindStat(m_top->name);
if (stat == nullptr)
{
stat = CreateStat();
stat->name = m_top->name;
stat->minElapsed = elapsedTime;
stat->maxElapsed = elapsedTime;
}
else
{
stat->minElapsed = b3Min(stat->minElapsed, elapsedTime);
stat->maxElapsed = b3Max(stat->maxElapsed, elapsedTime);
}
if (m_top->stat == nullptr)
{
m_top->stat = stat;
}
assert(m_top->stat == stat);
m_top = m_top->parent;
}
void ProfilerSt::Begin()
{
assert(m_top == nullptr);
}
void ProfilerSt::End()
{
assert(m_top == nullptr);
if (m_root)
{
RecurseDestroyNode(m_root);
m_root = nullptr;
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef PROFILER_ST_H
#define PROFILER_ST_H
#include <bounce/common/math/math.h>
#include <bounce/common/memory/block_pool.h>
#include <bounce/common/template/array.h>
#include <bounce/common/time.h>
// Profiler tree node statistics
struct ProfilerStNodeStat
{
const char* name;
float64 minElapsed;
float64 maxElapsed;
};
// A profiler tree node
struct ProfilerStNode
{
const char* name;
float64 t0;
float64 t1;
float64 elapsed;
u32 callCount;
ProfilerStNode* parent;
b3StackArray<ProfilerStNode*, 32> children;
ProfilerStNodeStat* stat;
};
// A profiler tree
class ProfilerSt
{
public:
ProfilerSt();
~ProfilerSt();
// Must be called before profiling.
void Begin();
// Must be called after profiling.
void End();
// Begin a new scope.
void BeginScope(const char* name);
// End the top scope.
void EndScope();
ProfilerStNode* GetRoot() { return m_root; }
private:
ProfilerStNode* CreateNode();
void DestroyNode(ProfilerStNode* node);
void RecurseDestroyNode(ProfilerStNode* node);
ProfilerStNode* FindNode(const char* name);
ProfilerStNodeStat* CreateStat();
ProfilerStNodeStat* FindStat(const char* name);
b3BlockPool m_pool; // pool of nodes
b3Time m_time; // timer
ProfilerStNode* m_root; // tree root node
ProfilerStNode* m_top; // top node
b3StackArray<ProfilerStNodeStat, 256> m_stats; // node statistics
};
extern ProfilerSt* g_profilerSt;
#endif

View File

@ -18,6 +18,7 @@
#include <testbed/framework/test.h>
#include <testbed/framework/profiler.h>
#include <testbed/framework/profiler_st.h>
extern u32 b3_allocCalls, b3_maxAllocCalls;
extern u32 b3_convexCalls, b3_convexCacheHits;
@ -27,11 +28,13 @@ extern bool b3_convexCache;
void b3BeginProfileScope(const char* name)
{
g_profiler->BeginScope(name);
g_profilerSt->BeginScope(name);
}
void b3EndProfileScope()
{
g_profiler->EndScope();
g_profilerSt->EndScope();
}
Test::Test() :

View File

@ -20,6 +20,7 @@
#include <testbed/framework/view_model.h>
#include <testbed/framework/test.h>
#include <testbed/framework/profiler.h>
#include <testbed/framework/profiler_st.h>
#include <imgui/imgui.h>
#if defined (U_OPENGL_2)
@ -218,8 +219,8 @@ void View::Interface()
if (ImGui::BeginMenu("View"))
{
ImGui::MenuItem("Profile", "", &settings.drawProfile);
ImGui::MenuItem("Profile Tree", "", &settings.drawProfileTree);
ImGui::MenuItem("Profile Tree Statistics", "", &settings.drawProfileTreeStats);
ImGui::MenuItem("Statistics", "", &settings.drawStats);
ImGui::Separator();
@ -435,7 +436,7 @@ void View::InterfaceProfileTree()
ImGui::SetNextWindowPos(ImVec2(0.0f, wp.y + ws.y));
ImGui::SetNextWindowSize(ImVec2(g_camera->m_width - 250.0f, 0.0f));
ImGui::Begin("##ProfileTree", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Begin("Profile Tree", NULL, ImGuiWindowFlags_AlwaysAutoResize);
ProfilerNode* root = g_profiler->GetRoot();
if (root)
@ -449,6 +450,64 @@ void View::InterfaceProfileTree()
ImGui::PopStyleVar();
}
static void TreeNode(ProfilerStNode* node, u32& index)
{
ImGui::PushID(index);
++index;
if (ImGui::TreeNode(node->name))
{
ImGui::Text("%.4f (min = %.4f) (max = %.4f) (calls = %d) [ms]", node->elapsed, node->stat->minElapsed, node->stat->maxElapsed, node->callCount);
for (u32 i = 0; i < node->children.Count(); ++i)
{
TreeNode(node->children[i], index);
}
ImGui::TreePop();
}
ImGui::PopID();
}
void View::InterfaceProfileTreeStats()
{
ImGui::Begin("Overlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
ImVec2 wp = ImGui::GetWindowPos();
ImVec2 ws = ImGui::GetWindowSize();
ImGui::End();
wp.y = wp.y + ws.y;
if (g_settings->drawProfileTree)
{
ImGui::Begin("Profile Tree", NULL, ImGuiWindowFlags_AlwaysAutoResize);
ImVec2 ptwp = ImGui::GetWindowPos();
ImVec2 ptws = ImGui::GetWindowSize();
ImGui::End();
wp.y = ptwp.y + ptws.y;
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::SetNextWindowPos(ImVec2(0.0f, wp.y));
ImGui::SetNextWindowSize(ImVec2(g_camera->m_width - 250.0f, 0.0f));
ImGui::Begin("Profile Tree Statistics", NULL, ImGuiWindowFlags_AlwaysAutoResize);
ProfilerStNode* root = g_profilerSt->GetRoot();
if (root)
{
u32 index = 0;
TreeNode(root, index);
}
ImGui::End();
ImGui::PopStyleVar();
}
void View::EndInterface()
{
ImGui::PopStyleVar();

View File

@ -42,6 +42,7 @@ public:
void BeginInterface();
void Interface();
void InterfaceProfileTree();
void InterfaceProfileTreeStats();
void EndInterface();
private:
friend class ViewModel;

View File

@ -32,8 +32,8 @@ struct Settings
drawLines = true;
drawTriangles = true;
drawGrid = true;
drawProfile = false;
drawProfileTree = false;
drawProfileTreeStats = false;
drawStats = false;
}
@ -43,8 +43,8 @@ struct Settings
bool drawLines;
bool drawTriangles;
bool drawGrid;
bool drawProfile;
bool drawProfileTree;
bool drawProfileTreeStats;
bool drawStats;
};