-
Notifications
You must be signed in to change notification settings - Fork 7
Add a performance counter that exports all data at the end and avoids… #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mhoffman219
wants to merge
5
commits into
master
Choose a base branch
from
mhoffman/performance_counter
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0d5d6bf
Add a performance counter that exports all data at the end and avoids…
mhoffman219 d12084c
run clang-format (so CI can pass)
pestophagous eced455
libutil: exclude performance_counter from android build
pestophagous 67ccb84
libutil: fix macos build and windows build
pestophagous 3a4d451
app: instantiate our new PerformanceCounter during gui tests
pestophagous File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
#include "performance_counter.h" | ||
|
||
#include <QQuickWindow> | ||
|
||
#include <QQmlContext> | ||
|
||
namespace project | ||
{ | ||
PerformanceCounter::PerformanceCounter( QObject* parent, QQuickWindow* window ) | ||
: QObject( parent ), m_window( window ) | ||
{ | ||
} | ||
|
||
void PerformanceCounter::ExportContextPropertiesToQml( QQmlEngine* engine ) | ||
{ | ||
engine->rootContext()->setContextProperty( "performanceCounter", this ); | ||
} | ||
|
||
void PerformanceCounter::ConnectToWindowIfNecessary() | ||
{ | ||
if( m_connectedToWindow ) | ||
{ | ||
return; | ||
} | ||
connect( m_window, &QQuickWindow::afterAnimating, this, &PerformanceCounter::AfterAnimating ); | ||
connect( m_window, &QQuickWindow::beforeRendering, this, &PerformanceCounter::BeforeRendering ); | ||
connect( m_window, &QQuickWindow::afterRendering, this, &PerformanceCounter::AfterRendering ); | ||
} | ||
|
||
void PerformanceCounter::StartReport( const QString& name ) | ||
{ | ||
for( auto& reportRequest : m_reports ) | ||
{ | ||
if( reportRequest.m_name.isEmpty() ) | ||
{ | ||
reportRequest = ReportRequest( name ); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
void PerformanceCounter::AfterAnimating() | ||
{ | ||
for( auto& reportRequest : m_reports ) | ||
{ | ||
reportRequest.Synchronize(); | ||
if( reportRequest.m_report.has_value() ) | ||
{ | ||
reportRequest.m_report->GetFrameReportToWriteInto().m_afterAnimating = static_cast<qint32>( reportRequest.m_timer.elapsed() ); | ||
} | ||
} | ||
} | ||
|
||
void PerformanceCounter::BeforeRendering() | ||
{ | ||
for( auto& reportRequest : m_reports ) | ||
{ | ||
if( reportRequest.m_report.has_value() ) | ||
{ | ||
reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast<qint32>( reportRequest.m_timer.elapsed() ); | ||
} | ||
} | ||
} | ||
|
||
void PerformanceCounter::AfterRendering() | ||
{ | ||
for( auto& reportRequest : m_reports ) | ||
{ | ||
if( reportRequest.m_report.has_value() ) | ||
{ | ||
reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast<qint32>( reportRequest.m_timer.elapsed() ); | ||
reportRequest.m_report->FinishFrame(); | ||
if( reportRequest.m_requestedToStopRenderThread ) | ||
{ | ||
reportRequest.Finalize(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
ReportRequest::ReportRequest() | ||
: m_name(), m_timer() | ||
{ | ||
} | ||
ReportRequest::ReportRequest( QString name ) | ||
: m_name( name ), m_timer() | ||
{ | ||
m_timer.start(); | ||
} | ||
|
||
// Called automatically from the GUI thread while the render thread is waiting | ||
void ReportRequest::Synchronize() | ||
{ | ||
if( m_finalized ) | ||
{ | ||
*this = ReportRequest(); | ||
} | ||
else | ||
{ | ||
if( ( m_name.data() != nullptr ) && !m_report.has_value() ) | ||
{ | ||
m_report.emplace(); | ||
} | ||
m_requestedToStopRenderThread = m_requestedToStopGUIThread; | ||
} | ||
} | ||
|
||
// Called by users from GUI thread | ||
void ReportRequest::RequestStop() | ||
{ | ||
m_requestedToStopGUIThread = true; | ||
} | ||
|
||
// Called automatically from the render thread | ||
void ReportRequest::Finalize() | ||
{ | ||
ExportReport(); | ||
m_finalized = true; | ||
} | ||
|
||
struct MinMax | ||
{ | ||
qint32 m_min = std::numeric_limits<qint32>::max(); | ||
qint32 m_max = std::numeric_limits<qint32>::min(); | ||
|
||
void Update( qint32 val ) | ||
{ | ||
m_min = std::min( m_min, val ); | ||
m_max = std::max( m_max, val ); | ||
} | ||
}; | ||
|
||
void ReportRequest::ExportReport() | ||
{ | ||
auto log = qDebug(); | ||
log = log << "Exporting report for: " << m_name << Qt::endl; | ||
log = log << Qt::right << qSetFieldWidth( 3 ); | ||
qint32 previousGuiUpdate = 0; | ||
qint32 previousRenderFinish = 0; | ||
|
||
MinMax guiFrameDurationExtents; | ||
MinMax renderFrameDurationExtents; | ||
MinMax synchronizingExtents; | ||
MinMax renderingExtents; | ||
|
||
for( const FrameReport& frame : m_report->m_backloggedFrames ) | ||
{ | ||
qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; | ||
previousGuiUpdate = frame.m_afterAnimating; | ||
qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; | ||
previousRenderFinish = frame.m_afterRendering; | ||
qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; | ||
qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; | ||
log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; | ||
guiFrameDurationExtents.Update( guiFrameDuration ); | ||
renderFrameDurationExtents.Update( renderFrameDuration ); | ||
synchronizingExtents.Update( synchronizing ); | ||
renderingExtents.Update( rendering ); | ||
} | ||
for( size_t i = 0; i < m_report->m_framesFilledCount; i++ ) | ||
{ | ||
const FrameReport& frame = m_report->m_frames[ i ]; | ||
qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; | ||
previousGuiUpdate = frame.m_afterAnimating; | ||
qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; | ||
previousRenderFinish = frame.m_afterRendering; | ||
qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; | ||
qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; | ||
log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; | ||
guiFrameDurationExtents.Update( guiFrameDuration ); | ||
renderFrameDurationExtents.Update( renderFrameDuration ); | ||
synchronizingExtents.Update( synchronizing ); | ||
renderingExtents.Update( rendering ); | ||
} | ||
|
||
log << "GUI Frame Duration Extents " << guiFrameDurationExtents.m_min << ", " << guiFrameDurationExtents.m_max; | ||
log << "Render Frame Duration Extents " << renderFrameDurationExtents.m_min << ", " << renderFrameDurationExtents.m_max; | ||
log << "Synchronizing Extents " << synchronizingExtents.m_min << ", " << synchronizingExtents.m_max; | ||
log << "Rendering Extents " << renderingExtents.m_min << ", " << renderingExtents.m_max; | ||
} | ||
|
||
FrameReport& Report::GetFrameReportToWriteInto() | ||
{ | ||
return m_frames[ m_framesFilledCount ]; | ||
} | ||
|
||
void Report::FinishFrame() | ||
{ | ||
m_framesFilledCount++; | ||
if( m_framesFilledCount == m_frames.size() ) | ||
{ | ||
size_t currentBacklogSize = m_backloggedFrames.size(); | ||
m_backloggedFrames.resize( currentBacklogSize + m_frames.size() ); | ||
memcpy( m_frames.data(), m_backloggedFrames.data() + currentBacklogSize, sizeof( FrameReport ) * m_frames.size() ); | ||
m_framesFilledCount = 0; | ||
} | ||
} | ||
} // namespace project |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
#ifndef PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H | ||
#define PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H | ||
|
||
#include <algorithm> | ||
#include <array> | ||
#include <iterator> | ||
#include <optional> | ||
|
||
#include <QElapsedTimer> | ||
#include <QObject> | ||
#include <QQmlEngine> | ||
#include <QQuickWindow> | ||
|
||
namespace project | ||
{ | ||
struct FrameReport | ||
{ | ||
qint32 m_afterAnimating; | ||
qint32 m_beforeRendering; | ||
qint32 m_afterRendering; | ||
}; | ||
|
||
struct Report | ||
{ | ||
Report() = default; | ||
Report& operator=( const Report& ) = default; | ||
|
||
// Gets the current frame that timing data should be written into. | ||
FrameReport& GetFrameReportToWriteInto(); | ||
// Moves the internal pointer to the next frame to write into, moving the cache into the heap-allocated backlog if space is needed. | ||
void FinishFrame(); | ||
|
||
static constexpr size_t k_maxNumberOfFrames = 15; | ||
|
||
// The number of frame reports that have been fully filled, with all 3 timestamps having been written. | ||
// This means that this is also the index of the FrameReport where new values should be written, | ||
// and that this should be incremented whenever the last timestamp is written. | ||
size_t m_framesFilledCount; | ||
std::array<FrameReport, k_maxNumberOfFrames> m_frames; | ||
std::vector<FrameReport> m_backloggedFrames; | ||
}; | ||
|
||
struct ReportRequest | ||
{ | ||
ReportRequest(); | ||
explicit ReportRequest( QString name ); | ||
|
||
// Called automatically from the GUI thread while the render thread is waiting | ||
void Synchronize(); | ||
|
||
// Called by users from GUI thread | ||
void RequestStop(); | ||
|
||
// Called automatically from the render thread | ||
void Finalize(); | ||
|
||
void ExportReport(); | ||
|
||
// Fully owned by the GUI thread | ||
QString m_name; | ||
// Written by the GUI thread before synchronization, read by the render thread after synchronization. | ||
QElapsedTimer m_timer; | ||
// Written by the GUI thread requesting that the report finish after the next render. | ||
bool m_requestedToStopGUIThread = false; | ||
// Copied based on m_requestedToStopGUIThread during synchronization. | ||
bool m_requestedToStopRenderThread = false; | ||
// Written to on the render thread once the frame that m_requestedToStopRenderThread was set in has finished. | ||
// Once this is true, the report can be cleared out during the next synchronization. | ||
bool m_finalized = false; | ||
std::optional<Report> m_report; | ||
}; | ||
|
||
// This object currently sits at 800 bytes, which means it should fit in a single page of memory. | ||
class PerformanceCounter : public QObject | ||
{ | ||
Q_OBJECT | ||
|
||
public: | ||
PerformanceCounter( QObject* parent, QQuickWindow* window ); | ||
|
||
void ExportContextPropertiesToQml( QQmlEngine* engine ); | ||
|
||
// All public methods must be called by the GUI thread | ||
void StartReport( const QString& reportName ); | ||
void StopReport( const QString& reportName ); | ||
|
||
static constexpr size_t k_maxNumberOfReports = 3; | ||
|
||
private slots: | ||
// This happens on the GUI thread and can be used to synchronize between GUI and rendering thread resources. | ||
void AfterAnimating(); | ||
// This happens on the rendering thread | ||
void BeforeRendering(); | ||
// This happens on the rendering thread | ||
void AfterRendering(); | ||
|
||
private: | ||
// This must be called on the rendering thread | ||
void AddReportToInternalStorage( Report report ); | ||
void ConnectToWindowIfNecessary(); | ||
|
||
std::array<ReportRequest, k_maxNumberOfReports> m_reports; | ||
|
||
// Whether the private slots have been connected to the window's signals. | ||
bool m_connectedToWindow = false; | ||
QQuickWindow* m_window; | ||
}; | ||
|
||
} // namespace project | ||
|
||
#endif // PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhoffman219 Unless we end up discovering some way in which the GuiTest(s) are fundamentally in conflict with how the PerformanceCounter works, then I think this is a good spot to place some sample code (since it won't execute other than during tests).
I ran the gui tests and I observed that this code is executing, but I still didn't manage to get to a point where the PerformanceCounter would dump out its statistics in
ReportRequest::ExportReport()
Hoping you can help me add code here that would result in some printing of stats, even if the numbers are extremely boring when running the GuiTest of this app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. And if you want to launch the app in GuiTest mode:
... and then additionally you might want to hack this
1
to make the app stay open longer so you can press buttons and cause more GUI activity before the app auto-closes. This line is where to hack: