Skip to content

Commit 0d5d6bf

Browse files
committed
Add a performance counter that exports all data at the end and avoids mallocs.
1 parent c37d792 commit 0d5d6bf

File tree

5 files changed

+325
-0
lines changed

5 files changed

+325
-0
lines changed

src/app/app.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
QT += core qml quick svg widgets quickcontrols2
44

5+
CONFIG += c++17
6+
57
SOURCES += \
68
event_filter.cc \
79
gui_tests.cc \

src/util/performance_counter.cc

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#include "performance_counter.h"
2+
3+
#include <QQuickWindow>
4+
5+
#include <QQmlContext>
6+
7+
namespace project {
8+
9+
PerformanceCounter::PerformanceCounter(QObject* parent, QQuickWindow* window) : QObject(parent), m_window(window) {
10+
11+
}
12+
13+
void PerformanceCounter::ExportContextPropertiesToQml( QQmlEngine* engine) {
14+
engine->rootContext()->setContextProperty( "performanceCounter", this );
15+
}
16+
17+
void PerformanceCounter::ConnectToWindowIfNecessary() {
18+
if (m_connectedToWindow) {
19+
return;
20+
}
21+
connect(m_window, &QQuickWindow::afterAnimating, this, &PerformanceCounter::AfterAnimating);
22+
connect(m_window, &QQuickWindow::beforeRendering, this, &PerformanceCounter::BeforeRendering);
23+
connect(m_window, &QQuickWindow::afterRendering, this, &PerformanceCounter::AfterRendering);
24+
}
25+
26+
void PerformanceCounter::StartReport(const QString& name) {
27+
for (auto& reportRequest : m_reports) {
28+
if (reportRequest.m_name.data() == nullptr) {
29+
reportRequest = ReportRequest(name);
30+
return;
31+
}
32+
}
33+
}
34+
35+
void PerformanceCounter::AfterAnimating() {
36+
for (auto& reportRequest : m_reports) {
37+
reportRequest.Synchronize();
38+
if (reportRequest.m_report.has_value()) {
39+
reportRequest.m_report->GetFrameReportToWriteInto().m_afterAnimating = static_cast<qint32>(reportRequest.m_timer.elapsed());
40+
}
41+
}
42+
}
43+
44+
void PerformanceCounter::BeforeRendering() {
45+
for (auto& reportRequest : m_reports) {
46+
if (reportRequest.m_report.has_value()) {
47+
reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast<qint32>(reportRequest.m_timer.elapsed());
48+
}
49+
}
50+
}
51+
52+
void PerformanceCounter::AfterRendering() {
53+
for (auto& reportRequest : m_reports) {
54+
if (reportRequest.m_report.has_value()) {
55+
reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast<qint32>(reportRequest.m_timer.elapsed());
56+
reportRequest.m_report->FinishFrame();
57+
if (reportRequest.m_requestedToStopRenderThread) {
58+
reportRequest.Finalize();
59+
}
60+
}
61+
}
62+
}
63+
64+
ReportRequest::ReportRequest() : m_name(), m_timer() {}
65+
ReportRequest::ReportRequest(QString name) : m_name(name), m_timer() {
66+
m_timer.start();
67+
}
68+
69+
// Called automatically from the GUI thread while the render thread is waiting
70+
void ReportRequest::Synchronize() {
71+
if (m_finalized) {
72+
*this = ReportRequest();
73+
} else {
74+
if ((m_name.data() != nullptr) && !m_report.has_value()) {
75+
m_report.emplace();
76+
}
77+
m_requestedToStopRenderThread = m_requestedToStopGUIThread;
78+
}
79+
}
80+
81+
// Called by users from GUI thread
82+
void ReportRequest::RequestStop() {
83+
m_requestedToStopGUIThread = true;
84+
}
85+
86+
// Called automatically from the render thread
87+
void ReportRequest::Finalize() {
88+
ExportReport();
89+
m_finalized = true;
90+
}
91+
92+
struct MinMax {
93+
qint32 m_min = std::numeric_limits < qint32 >::max();
94+
qint32 m_max = std::numeric_limits < qint32 >::min();
95+
96+
void Update(qint32 val) {
97+
m_min = std::min(m_min, val);
98+
m_max = std::max(m_max, val);
99+
}
100+
};
101+
102+
void ReportRequest::ExportReport() {
103+
auto log = qDebug();
104+
log = log << "Exporting report for: " << m_name << Qt::endl;
105+
log = log << Qt::right << qSetFieldWidth(3);
106+
qint32 previousGuiUpdate = 0;
107+
qint32 previousRenderFinish = 0;
108+
109+
MinMax guiFrameDurationExtents;
110+
MinMax renderFrameDurationExtents;
111+
MinMax synchronizingExtents;
112+
MinMax renderingExtents;
113+
114+
for (const FrameReport& frame : m_report->m_backloggedFrames) {
115+
qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate;
116+
previousGuiUpdate = frame.m_afterAnimating;
117+
qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish;
118+
previousRenderFinish = frame.m_afterRendering;
119+
qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating;
120+
qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering;
121+
log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration;
122+
guiFrameDurationExtents.Update(guiFrameDuration);
123+
renderFrameDurationExtents.Update(renderFrameDuration);
124+
synchronizingExtents.Update(synchronizing);
125+
renderingExtents.Update(rendering);
126+
}
127+
for (size_t i = 0; i < m_report->m_framesFilledCount; i++) {
128+
const FrameReport& frame = m_report->m_frames[i];
129+
qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate;
130+
previousGuiUpdate = frame.m_afterAnimating;
131+
qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish;
132+
previousRenderFinish = frame.m_afterRendering;
133+
qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating;
134+
qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering;
135+
log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration;
136+
guiFrameDurationExtents.Update(guiFrameDuration);
137+
renderFrameDurationExtents.Update(renderFrameDuration);
138+
synchronizingExtents.Update(synchronizing);
139+
renderingExtents.Update(rendering);
140+
}
141+
142+
log << "GUI Frame Duration Extents " << guiFrameDurationExtents.m_min << ", " << guiFrameDurationExtents.m_max;
143+
log << "Render Frame Duration Extents " << renderFrameDurationExtents.m_min << ", " << renderFrameDurationExtents.m_max;
144+
log << "Synchronizing Extents " << synchronizingExtents.m_min << ", " << synchronizingExtents.m_max;
145+
log << "Rendering Extents " << renderingExtents.m_min << ", " << renderingExtents.m_max;
146+
}
147+
148+
FrameReport& Report::GetFrameReportToWriteInto() {
149+
return m_frames[m_framesFilledCount];
150+
}
151+
152+
void Report::FinishFrame() {
153+
m_framesFilledCount++;
154+
if (m_framesFilledCount == m_frames.size()) {
155+
size_t currentBacklogSize = m_backloggedFrames.size();
156+
m_backloggedFrames.resize(currentBacklogSize + m_frames.size());
157+
memcpy(m_frames.data(), m_backloggedFrames.data() + currentBacklogSize, sizeof(FrameReport) * m_frames.size());
158+
m_framesFilledCount = 0;
159+
}
160+
}
161+
}

src/util/performance_counter.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#ifndef PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H
2+
#define PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H
3+
4+
#include <optional>
5+
#include <algorithm>
6+
7+
#include <QObject>
8+
#include <QElapsedTimer>
9+
#include <QQuickWindow>
10+
#include <QQmlEngine>
11+
12+
namespace project
13+
{
14+
15+
struct FrameReport {
16+
qint32 m_afterAnimating;
17+
qint32 m_beforeRendering;
18+
qint32 m_afterRendering;
19+
};
20+
21+
struct Report {
22+
Report() = default;
23+
Report& operator=(const Report&) = default;
24+
25+
// Gets the current frame that timing data should be written into.
26+
FrameReport& GetFrameReportToWriteInto();
27+
// Moves the internal pointer to the next frame to write into, moving the cache into the heap-allocated backlog if space is needed.
28+
void FinishFrame();
29+
30+
static constexpr size_t k_maxNumberOfFrames = 15;
31+
32+
// The number of frame reports that have been fully filled, with all 3 timestamps having been written.
33+
// This means that this is also the index of the FrameReport where new values should be written,
34+
// and that this should be incremented whenever the last timestamp is written.
35+
size_t m_framesFilledCount;
36+
std::array<FrameReport, k_maxNumberOfFrames> m_frames;
37+
std::vector<FrameReport> m_backloggedFrames;
38+
};
39+
40+
struct ReportRequest {
41+
ReportRequest();
42+
explicit ReportRequest(QString name);
43+
44+
// Called automatically from the GUI thread while the render thread is waiting
45+
void Synchronize();
46+
47+
// Called by users from GUI thread
48+
void RequestStop();
49+
50+
// Called automatically from the render thread
51+
void Finalize();
52+
53+
void ExportReport();
54+
55+
// Fully owned by the GUI thread
56+
QString m_name;
57+
// Written by the GUI thread before synchronization, read by the render thread after synchronization.
58+
QElapsedTimer m_timer;
59+
// Written by the GUI thread requesting that the report finish after the next render.
60+
bool m_requestedToStopGUIThread = false;
61+
// Copied based on m_requestedToStopGUIThread during synchronization.
62+
bool m_requestedToStopRenderThread = false;
63+
// Written to on the render thread once the frame that m_requestedToStopRenderThread was set in has finished.
64+
// Once this is true, the report can be cleared out during the next synchronization.
65+
bool m_finalized = false;
66+
std::optional<Report> m_report;
67+
};
68+
69+
// This object currently sits at 800 bytes, which means it should fit in a single page of memory.
70+
class PerformanceCounter : public QObject
71+
{
72+
Q_OBJECT
73+
74+
public:
75+
PerformanceCounter(QObject* parent, QQuickWindow* window);
76+
77+
void ExportContextPropertiesToQml( QQmlEngine* engine );
78+
79+
// All public methods must be called by the GUI thread
80+
void StartReport(const QString& reportName);
81+
void StopReport(const QString& reportName);
82+
83+
static constexpr size_t k_maxNumberOfReports = 3;
84+
85+
private slots:
86+
// This happens on the GUI thread and can be used to synchronize between GUI and rendering thread resources.
87+
void AfterAnimating();
88+
// This happens on the rendering thread
89+
void BeforeRendering();
90+
// This happens on the rendering thread
91+
void AfterRendering();
92+
93+
private:
94+
// This must be called on the rendering thread
95+
void AddReportToInternalStorage(Report report);
96+
void ConnectToWindowIfNecessary();
97+
98+
std::array<ReportRequest, k_maxNumberOfReports> m_reports;
99+
100+
// Whether the private slots have been connected to the window's signals.
101+
bool m_connectedToWindow = false;
102+
QQuickWindow* m_window;
103+
};
104+
105+
} // namespace project
106+
107+
#endif // PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H

src/util/register_type.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <QMetaType>
2+
#include <QQmlEngine>
3+
4+
// Internal helper function that registers a type with modifier (like being wrapped in a vector).
5+
// aliases is the list of aliased names the underlying class uses, and aliasModifierOrNone is optionally a function that will apply the modifier to the alias.
6+
template<typename ModifiedType>
7+
void registerModifiedType(const std::vector<std::string>& aliases, std::function<std::string(const std::string&)> aliasModifier = [](const std::string& s) {return s;}) {
8+
if (aliases.size() == 0) {
9+
qRegisterMetaType<ModifiedType>();
10+
} else {
11+
for (const std::string& alias : aliases) {
12+
qRegisterMetaType<ModifiedType>(aliasModifier(alias).c_str());
13+
}
14+
}
15+
}
16+
17+
// Aliases is all aliases that will be registered for the type (such as both type name with and without the namespace).
18+
// If the type is not anonymous, the first alias will be used as the name.
19+
// It is valid to register a type without aliases, but then it must be anonymous.
20+
// Template arguments control which variants on a type (pointer, Qt smart pointer, etc.) are created, as well as const versions (with const-ness binding most tightly to the main type).
21+
template<typename TypeWithoutQualifiers, bool makeAnonymous, bool registerConstVersions, bool registerValueType, bool registerPointer, bool registerSharedPointer, bool registerVectorOfSharedPointers>
22+
void registerType(const std::vector<std::string>& aliases) {
23+
if constexpr (makeAnonymous) {
24+
qmlRegisterAnonymousType<TypeWithoutQualifiers>("App", 1);
25+
} else {
26+
qmlRegisterType<TypeWithoutQualifiers>("App", 1, 0, aliases[0].c_str());
27+
}
28+
29+
if constexpr (registerValueType) {
30+
registerModifiedType<TypeWithoutQualifiers>(aliases);
31+
if constexpr (registerConstVersions) {
32+
registerModifiedType<const TypeWithoutQualifiers>(aliases, [](const std::string& s){ return "const " + s;});
33+
}
34+
}
35+
if constexpr (registerPointer) {
36+
registerModifiedType<TypeWithoutQualifiers*>(aliases, [](const std::string& s){ return s + "*"; });
37+
if constexpr (registerConstVersions) {
38+
registerModifiedType<const TypeWithoutQualifiers*>(aliases, [](const std::string& s){ return "const " + s + "*"; });
39+
}
40+
}
41+
if constexpr (registerSharedPointer) {
42+
registerModifiedType<QSharedPointer<TypeWithoutQualifiers>>(aliases, [](const std::string& s){ return "QSharedPointer<" + s + ">"; });
43+
if constexpr (registerConstVersions) {
44+
registerModifiedType<QSharedPointer<const TypeWithoutQualifiers>>(aliases, [](const std::string& s){ return "QSharedPointer<const " + s + ">"; });
45+
}
46+
}
47+
if constexpr (registerVectorOfSharedPointers) {
48+
registerModifiedType<QVector<QSharedPointer<TypeWithoutQualifiers>>>(aliases, [](const std::string& s){ return "QVector<QSharedPointer<" + s + ">>"; });
49+
if constexpr (registerConstVersions) {
50+
registerModifiedType<QVector<QSharedPointer<const TypeWithoutQualifiers>>>(aliases, [](const std::string& s){ return "QVector<QSharedPointer<const " + s + ">>"; });
51+
}
52+
}
53+
}

src/util/util.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ SOURCES += \
2020
am_i_inside_debugger.cc \
2121
deleter_with_qt_deferred_deletion.cc \
2222
every_so_often.cc \
23+
performance_counter.cc \
2324
qml_message_interceptor.cc
2425

2526
HEADERS += \
2627
am_i_inside_debugger.h \
2728
deleter_with_qt_deferred_deletion.h \
2829
every_so_often.h \
30+
performance_counter.h \
2931
qml_list_property_helper.h \
3032
qml_message_interceptor.h \
3133
usage_log_t.hpp

0 commit comments

Comments
 (0)