Skip to content

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
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
# following the format reported by `git shortlog -sen | cut -f 2`.

Sean Moore <sean@219design.com>

mhoffman <mhoffman@219design.com>
pestophagous <pestophagous@gmail.com>
2 changes: 2 additions & 0 deletions src/app/app.pro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

QT += core qml quick svg widgets quickcontrols2

CONFIG += c++17

SOURCES += \
event_filter.cc \
gui_tests.cc \
Expand Down
15 changes: 15 additions & 0 deletions src/app/view_model_collection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
#include "src/libstyles/resource_helper.h"
#include "src/util/qml_message_interceptor.h"
#include "src/util/usage_log_t.hpp"
#include "util-assert.h"

#if !defined( Q_OS_ANDROID )
# include "src/util/performance_counter.h"
#endif // !defined(Q_OS_ANDROID)

namespace project
{
Expand Down Expand Up @@ -66,5 +71,15 @@ void ViewModelCollection::ExportContextPropertiesToQml( QQmlEngine* engine )
void ViewModelCollection::SetRootObject( QObject* object )
{
m_eventFilter->FilterEventsDirectedAtThisObject( object );

#if !defined( Q_OS_ANDROID )
if( m_opts->RunningGuiTests() )
Copy link
Member

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.

Copy link
Member

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:

build/src/app/app -g

... 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:

{
auto rootWin = qobject_cast<QQuickWindow*>( object );
FASSERT( rootWin, "the casting must succeed so we get a QQuickWindow" );
m_perfCounter = std::make_unique<PerformanceCounter>( nullptr, rootWin );
m_perfCounter->StartReport( "GuiTests" );
}
#endif // !defined(Q_OS_ANDROID)
}
} // namespace project
5 changes: 5 additions & 0 deletions src/app/view_model_collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CliOptions;
class EventFilter;
class GuiTests;
class LoggingTags;
class PerformanceCounter;
class QmlMessageInterceptor;

class ViewModelCollection
Expand All @@ -43,6 +44,10 @@ class ViewModelCollection
std::unique_ptr<QmlMessageInterceptor> m_qmlLogger;
std::unique_ptr<LoggingTags> m_logging;

#if !defined( Q_OS_ANDROID )
std::unique_ptr<PerformanceCounter> m_perfCounter;
#endif // !defined(Q_OS_ANDROID)

std::unique_ptr<GuiTests> m_guiTests;
};
} // namespace project
Expand Down
198 changes: 198 additions & 0 deletions src/util/performance_counter.cc
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
111 changes: 111 additions & 0 deletions src/util/performance_counter.h
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
Loading