[development] added option to control update frequency in profiler visualizer (#7705)

resolves #4365

Increased the initial profiler ImGui window size
Updated thread render order so the main thread is always on top

Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
Scott Romero
2022-02-17 11:05:45 -08:00
committed by GitHub
parent ac79420da7
commit 18fcdcd4f4
2 changed files with 63 additions and 15 deletions
+55 -13
View File
@@ -24,13 +24,17 @@
#include <AzCore/std/sort.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/time.h>
#include <AzCore/Time/ITime.h>
namespace Profiler
{
constexpr AZStd::sys_time_t ProfilerViewEdgePadding = 5000;
constexpr size_t InitialCpuTimingStatsAllocation = 8;
constexpr int MinSavableFrameCount = 30; // 1 second @ 30 fps
constexpr int MaxSavableFrameCount = 2000;
constexpr int MaxUpdateFrequencyMs = 2000; // 2 seconds
namespace CpuProfilerImGuiHelper
{
float TicksToMs(double ticks)
@@ -116,12 +120,18 @@ namespace Profiler
}
} // namespace CpuProfilerImGuiHelper
ImGuiCpuProfiler::ImGuiCpuProfiler()
{
// thread IDs are hashed internally to unify display across platforms
m_mainThreadId = AZStd::hash<AZStd::thread_id>{}(AZStd::this_thread::get_id());
}
void ImGuiCpuProfiler::Draw(bool& keepDrawing)
{
// Cache the value to detect if it was changed by ImGui(user pressed 'x')
const bool cachedShowCpuProfiler = keepDrawing;
const ImVec2 windowSize(900.0f, 600.0f);
const ImVec2 windowSize(1280.0f, 720.0f);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_Once);
if (ImGui::Begin("CPU Profiler", &keepDrawing, ImGuiWindowFlags_None))
{
@@ -491,6 +501,7 @@ namespace Profiler
}
// -- CPU Visualizer --
void ImGuiCpuProfiler::DrawVisualizer()
{
DrawCommonHeader();
@@ -499,9 +510,19 @@ namespace Profiler
if (ImGui::BeginChild("Options and Statistics", { 0, 0 }, true))
{
ImGui::Columns(3, "Options", true);
ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 20000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic);
ImGui::SliderInt("Update Freq. (ms)", &m_updateFrequencyMs, 0, MaxUpdateFrequencyMs, "%d", ImGuiSliderFlags_AlwaysClamp);
ImGui::SliderInt("Saved Frames", &m_framesToCollect, MinSavableFrameCount, MaxSavableFrameCount, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic);
m_visualizerHighlightFilter.Draw("Find Region");
// estimate the number of frames required to fulfill the update frequency
const AZ::TimeMs deltaMs = AZ::TimeUsToMs(AZ::GetRealTickDeltaTimeUs());
const int estimatedFrameCountPadding = 5; // padding is necessary to prevent flashes of blank frames
const int estimatedFrameCount = aznumeric_cast<int >(AZ::TimeMs{ m_updateFrequencyMs } / deltaMs) + estimatedFrameCountPadding;
// bump the number of saved frames to the update frequency estimate to prevent periods of empty data
m_framesToCollect = AZStd::max(m_framesToCollect, estimatedFrameCount);
ImGui::NextColumn();
ImGui::Text("Viewport width: %.3f ms", CpuProfilerImGuiHelper::TicksToMs(GetViewportTickWidth()));
@@ -555,24 +576,25 @@ namespace Profiler
// Main draw loop
AZ::u64 baseRow = 0;
for (const auto& [currentThreadId, singleThreadData] : m_savedData)
auto drawThreadDataFunc = [&](size_t threadId, const AZStd::vector<TimeRegion>& threadData)
{
// Find the first TimeRegion that we should draw
auto regionItr = AZStd::lower_bound(
singleThreadData.begin(), singleThreadData.end(), *startTickItr,
threadData.begin(), threadData.end(), *startTickItr,
[](const TimeRegion& wrapper, AZStd::sys_time_t target)
{
return wrapper.m_startTick < target;
});
if (regionItr == singleThreadData.end())
if (regionItr == threadData.end())
{
continue;
return;
}
// Draw all of the blocks for a given thread/row
AZ::u64 maxDepth = 0;
while (regionItr != singleThreadData.end())
while (regionItr != threadData.end())
{
const TimeRegion& region = *regionItr;
@@ -590,10 +612,20 @@ namespace Profiler
}
// Draw UI details
DrawThreadLabel(baseRow, currentThreadId);
DrawThreadLabel(baseRow, threadId);
DrawThreadSeparator(baseRow, maxDepth);
baseRow += maxDepth + 1; // Next draw loop should start one row down
};
// keep the main thread at the top
drawThreadDataFunc(m_mainThreadId, m_savedData[m_mainThreadId]);
for (const auto& [threadId, threadData] : m_savedData)
{
if (threadId != m_mainThreadId)
{
drawThreadDataFunc(threadId, threadData);
}
}
DrawFrameBoundaries();
@@ -678,8 +710,8 @@ namespace Profiler
// Get the latest TimeRegionMap
const CpuProfiler::TimeRegionMap& timeRegionMap = CpuProfiler::Get()->GetTimeRegionMap();
m_viewportStartTick = AZStd::numeric_limits<AZ::s64>::max();
m_viewportEndTick = AZStd::numeric_limits<AZ::s64>::lowest();
AZ::s64 viewportStartTick = AZStd::numeric_limits<AZ::s64>::max();
AZ::s64 viewportEndTick = AZStd::numeric_limits<AZ::s64>::lowest();
// Iterate through the entire TimeRegionMap and copy the data since it will get deleted on the next frame
for (const auto& [threadId, singleThreadRegionMap] : timeRegionMap)
@@ -724,8 +756,8 @@ namespace Profiler
});
// Use the latest frame's data as the new bounds of the viewport
m_viewportStartTick = AZStd::min(newVisualizerData.front().m_startTick, m_viewportStartTick);
m_viewportEndTick = AZStd::max(newVisualizerData.back().m_endTick, m_viewportEndTick);
viewportStartTick = AZStd::min(newVisualizerData.front().m_startTick, viewportStartTick);
viewportEndTick = AZStd::max(newVisualizerData.back().m_endTick, viewportEndTick);
m_savedRegionCount += newVisualizerData.size();
@@ -734,6 +766,16 @@ namespace Profiler
savedDataVec.insert(
savedDataVec.end(), AZStd::make_move_iterator(newVisualizerData.begin()), AZStd::make_move_iterator(newVisualizerData.end()));
}
// only update the viewport bounds at the specified frequency
m_currentUpdateTimeMs += AZ::TimeUsToMs(AZ::GetRealTickDeltaTimeUs());
if (m_currentUpdateTimeMs >= static_cast<AZ::TimeMs>(m_updateFrequencyMs))
{
m_currentUpdateTimeMs = AZ::TimeMs{ 0 };
m_viewportStartTick = viewportStartTick;
m_viewportEndTick = viewportEndTick;
}
}
void ImGuiCpuProfiler::CullFrameData()
+8 -2
View File
@@ -18,6 +18,7 @@
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/Time/ITime.h>
#include <imgui/imgui.h>
@@ -92,7 +93,7 @@ namespace Profiler
double m_executeDuration = 0;
};
ImGuiCpuProfiler() = default;
ImGuiCpuProfiler();
~ImGuiCpuProfiler() = default;
//! Draws the overall CPU profiling window, defaults to the statistical view
@@ -100,7 +101,8 @@ namespace Profiler
private:
static constexpr float RowHeight = 35.0f;
static constexpr int DefaultFramesToCollect = 50;
static constexpr int DefaultFramesToCollect = 60; // 1 second @ 60 fps
static constexpr int DefaultUpdateFrequencyMs = 1000; // 1 second
static constexpr float MediumFrameTimeLimit = 16.6f; // 60 fps
static constexpr float HighFrameTimeLimit = 33.3f; // 30 fps
@@ -169,6 +171,9 @@ namespace Profiler
// --- Visualizer Members ---
int m_updateFrequencyMs = DefaultUpdateFrequencyMs;
AZ::TimeMs m_currentUpdateTimeMs = AZ::TimeMs{ 0 };
int m_framesToCollect = DefaultFramesToCollect;
// Tally of the number of saved profiling events so far
@@ -182,6 +187,7 @@ namespace Profiler
// note: we use size_t as a proxy for thread_id because native_thread_id_type differs differs from
// platform to platform, which causes problems when deserializing saved captures.
AZStd::unordered_map<size_t, AZStd::vector<TimeRegion>> m_savedData;
size_t m_mainThreadId = 0;
// Region color cache
AZStd::unordered_map<GroupRegionName, ImVec4, CachedTimeRegion::GroupRegionName::Hash> m_regionColorMap;