Mir

Merge lp:~vanvugt/mir/compositor-report into lp:mir

Proposed by Daniel van Vugt
Status: Merged
Approved by: Daniel van Vugt
Approved revision: no longer in the source branch.
Merged at revision: 1308
Proposed branch: lp:~vanvugt/mir/compositor-report
Merge into: lp:mir
Diff against target: 923 lines (+617/-11)
16 files modified
include/server/mir/compositor/compositor_report.h (+58/-0)
include/server/mir/default_configuration_options.h (+1/-0)
include/server/mir/default_server_configuration.h (+3/-0)
src/server/compositor/CMakeLists.txt (+1/-0)
src/server/compositor/default_configuration.cpp (+2/-1)
src/server/compositor/multi_threaded_compositor.cpp (+26/-5)
src/server/compositor/multi_threaded_compositor.h (+4/-1)
src/server/compositor/null_compositor_report.cpp (+45/-0)
src/server/default_configuration_options.cpp (+3/-0)
src/server/logging/CMakeLists.txt (+1/-0)
src/server/logging/compositor_report.cpp (+161/-0)
src/server/logging/compositor_report.h (+81/-0)
src/server/logging/default_configuration.cpp (+20/-0)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+68/-4)
tests/unit-tests/logging/CMakeLists.txt (+1/-0)
tests/unit-tests/logging/test_compositor_report.cpp (+142/-0)
To merge this branch: bzr merge lp:~vanvugt/mir/compositor-report
Reviewer Review Type Date Requested Status
Robert Carr (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Andreas Pokorny (community) Approve
Review via email: mp+200001@code.launchpad.net

Commit message

Introducing CompositorReport. In its present form it just logs useful
information like composition frame rate, frame time (render + post) and
pause/resume.

Basic usage:
   mir_demo_server_shell --compositor-report=log

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

The finished_frame method took me some time to parse - please extract some parts.
Could that be reused in a lttng implementation - then we it might make even more sense to have those calculations not directly in that method.

What is dn?

review: Needs Fixing
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

dn is a delta of n as in nframes:
   auto dn = i.nframes - i.last_reported_nframes;
I think the "dX" syntax is acceptable shorthand for any delta value calculus.

I disagree with breaking up finished_frame. In its current form it is highly cohesive.

As for different reporting outputs... The best report design is one with a minimal interface, like you see in CompositorReport. While it's possible LTTNG might use some of the same logic, I think it's unlikely. Because most of the current logic is about doing accurate reports of averages on 1-second intervals. That's not something LTTNG would reuse.

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> I disagree with breaking up finished_frame. In its current form it is highly
> cohesive.

It does two things. it updates one "Instance" and if timeout is big enough it creates reports for each instance and logs them. So the second thing is two things again, calculating and resetting the Riemann sums and formatting and printing the log string.

Why are the "last_reported_*" time values updated even when no report took place?

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

Ah that check is like first frame vs. any further frame measured.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Robert Carr (robertcarr) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/server/mir/compositor/compositor_report.h'
2--- include/server/mir/compositor/compositor_report.h 1970-01-01 00:00:00 +0000
3+++ include/server/mir/compositor/compositor_report.h 2014-01-02 09:11:20 +0000
4@@ -0,0 +1,58 @@
5+/*
6+ * Copyright © 2013 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
21+ */
22+
23+#ifndef MIR_COMPOSITOR_COMPOSITOR_REPORT_H_
24+#define MIR_COMPOSITOR_COMPOSITOR_REPORT_H_
25+
26+namespace mir
27+{
28+namespace compositor
29+{
30+
31+class CompositorReport
32+{
33+public:
34+ typedef const void* SubCompositorId; // e.g. thread/display buffer ID
35+ virtual void added_display(int width, int height, int x, int y, SubCompositorId id = 0) = 0;
36+ virtual void began_frame(SubCompositorId id = 0) = 0;
37+ virtual void finished_frame(SubCompositorId id = 0) = 0;
38+ virtual void started() = 0;
39+ virtual void stopped() = 0;
40+ virtual void scheduled() = 0;
41+protected:
42+ CompositorReport() = default;
43+ virtual ~CompositorReport() = default;
44+ CompositorReport(CompositorReport const&) = delete;
45+ CompositorReport& operator=(CompositorReport const&) = delete;
46+};
47+
48+class NullCompositorReport : public CompositorReport
49+{
50+public:
51+ void added_display(int width, int height, int x, int y, SubCompositorId id);
52+ void began_frame(SubCompositorId id);
53+ void finished_frame(SubCompositorId id);
54+ void started();
55+ void stopped();
56+ void scheduled();
57+};
58+
59+} // namespace compositor
60+} // namespace mir
61+
62+#endif // MIR_COMPOSITOR_COMPOSITOR_REPORT_H_
63
64=== modified file 'include/server/mir/default_configuration_options.h'
65--- include/server/mir/default_configuration_options.h 2013-12-18 02:19:19 +0000
66+++ include/server/mir/default_configuration_options.h 2014-01-02 09:11:20 +0000
67@@ -32,6 +32,7 @@
68 static char const* const no_server_socket_opt;
69 static char const* const session_mediator_report_opt;
70 static char const* const msg_processor_report_opt;
71+ static char const* const compositor_report_opt;
72 static char const* const display_report_opt;
73 static char const* const legacy_input_report_opt;
74 static char const* const connector_report_opt;
75
76=== modified file 'include/server/mir/default_server_configuration.h'
77--- include/server/mir/default_server_configuration.h 2013-12-18 02:19:19 +0000
78+++ include/server/mir/default_server_configuration.h 2014-01-02 09:11:20 +0000
79@@ -36,6 +36,7 @@
80 class DisplayBufferCompositorFactory;
81 class Compositor;
82 class RendererFactory;
83+class CompositorReport;
84 }
85 namespace frontend
86 {
87@@ -150,6 +151,7 @@
88 /** @name compositor configuration - customization
89 * configurable interfaces for modifying compositor
90 * @{ */
91+ virtual std::shared_ptr<compositor::CompositorReport> the_compositor_report();
92 virtual std::shared_ptr<compositor::DisplayBufferCompositorFactory> the_display_buffer_compositor_factory();
93 /** @} */
94
95@@ -278,6 +280,7 @@
96 CachedPtr<shell::SurfaceConfigurator> shell_surface_configurator;
97 CachedPtr<compositor::DisplayBufferCompositorFactory> display_buffer_compositor_factory;
98 CachedPtr<compositor::Compositor> compositor;
99+ CachedPtr<compositor::CompositorReport> compositor_report;
100 CachedPtr<logging::Logger> logger;
101 CachedPtr<graphics::DisplayReport> display_report;
102 CachedPtr<time::TimeSource> time_source;
103
104=== modified file 'src/server/compositor/CMakeLists.txt'
105--- src/server/compositor/CMakeLists.txt 2013-12-18 02:19:19 +0000
106+++ src/server/compositor/CMakeLists.txt 2014-01-02 09:11:20 +0000
107@@ -14,6 +14,7 @@
108 bypass.cpp
109 occlusion.cpp
110 default_configuration.cpp
111+ null_compositor_report.cpp
112 )
113
114 ADD_LIBRARY(
115
116=== modified file 'src/server/compositor/default_configuration.cpp'
117--- src/server/compositor/default_configuration.cpp 2013-12-18 02:19:19 +0000
118+++ src/server/compositor/default_configuration.cpp 2014-01-02 09:11:20 +0000
119@@ -54,7 +54,8 @@
120 {
121 return std::make_shared<mc::MultiThreadedCompositor>(the_display(),
122 the_scene(),
123- the_display_buffer_compositor_factory());
124+ the_display_buffer_compositor_factory(),
125+ the_compositor_report());
126 });
127 }
128
129
130=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
131--- src/server/compositor/multi_threaded_compositor.cpp 2013-12-18 02:19:19 +0000
132+++ src/server/compositor/multi_threaded_compositor.cpp 2014-01-02 09:11:20 +0000
133@@ -22,6 +22,7 @@
134 #include "mir/compositor/display_buffer_compositor.h"
135 #include "mir/compositor/display_buffer_compositor_factory.h"
136 #include "mir/compositor/scene.h"
137+#include "mir/compositor/compositor_report.h"
138
139 #include <thread>
140 #include <condition_variable>
141@@ -56,11 +57,13 @@
142 {
143 public:
144 CompositingFunctor(std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
145- mg::DisplayBuffer& buffer)
146+ mg::DisplayBuffer& buffer,
147+ std::shared_ptr<CompositorReport> const& report)
148 : display_buffer_compositor_factory{db_compositor_factory},
149 buffer(buffer),
150 running{true},
151- frames_scheduled{0}
152+ frames_scheduled{0},
153+ report{report}
154 {
155 }
156
157@@ -76,6 +79,14 @@
158
159 auto display_buffer_compositor = display_buffer_compositor_factory->create_compositor_for(buffer);
160
161+ CompositorReport::SubCompositorId report_id =
162+ display_buffer_compositor.get();
163+
164+ const auto& r = buffer.view_area();
165+ report->added_display(r.size.width.as_int(), r.size.height.as_int(),
166+ r.top_left.x.as_int(), r.top_left.y.as_int(),
167+ report_id);
168+
169 while (running)
170 {
171 /* Wait until compositing has been scheduled or we are stopped */
172@@ -91,7 +102,9 @@
173 if (running)
174 {
175 lock.unlock();
176+ report->began_frame(report_id);
177 display_buffer_compositor->composite();
178+ report->finished_frame(report_id);
179 lock.lock();
180 }
181 }
182@@ -127,6 +140,7 @@
183 int frames_scheduled;
184 std::mutex run_mutex;
185 std::condition_variable run_cv;
186+ std::shared_ptr<CompositorReport> const report;
187 };
188
189 }
190@@ -135,10 +149,12 @@
191 mc::MultiThreadedCompositor::MultiThreadedCompositor(
192 std::shared_ptr<mg::Display> const& display,
193 std::shared_ptr<mc::Scene> const& scene,
194- std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory)
195+ std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,
196+ std::shared_ptr<CompositorReport> const& compositor_report)
197 : display{display},
198 scene{scene},
199- display_buffer_compositor_factory{db_compositor_factory}
200+ display_buffer_compositor_factory{db_compositor_factory},
201+ report{compositor_report}
202 {
203 }
204
205@@ -149,10 +165,12 @@
206
207 void mc::MultiThreadedCompositor::start()
208 {
209+ report->started();
210+
211 /* Start the compositing threads */
212 display->for_each_display_buffer([this](mg::DisplayBuffer& buffer)
213 {
214- auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer};
215+ auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report};
216 auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw);
217
218 threads.push_back(std::thread{std::ref(*thread_functor)});
219@@ -162,6 +180,7 @@
220 /* Recomposite whenever the scene changes */
221 scene->set_change_callback([this]()
222 {
223+ report->scheduled();
224 for (auto& f : thread_functors)
225 f->schedule_compositing();
226 });
227@@ -183,4 +202,6 @@
228
229 thread_functors.clear();
230 threads.clear();
231+
232+ report->stopped();
233 }
234
235=== modified file 'src/server/compositor/multi_threaded_compositor.h'
236--- src/server/compositor/multi_threaded_compositor.h 2013-12-18 02:19:19 +0000
237+++ src/server/compositor/multi_threaded_compositor.h 2014-01-02 09:11:20 +0000
238@@ -39,13 +39,15 @@
239 class DisplayBufferCompositorFactory;
240 class CompositingFunctor;
241 class Scene;
242+class CompositorReport;
243
244 class MultiThreadedCompositor : public Compositor
245 {
246 public:
247 MultiThreadedCompositor(std::shared_ptr<graphics::Display> const& display,
248 std::shared_ptr<Scene> const& scene,
249- std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory);
250+ std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,
251+ std::shared_ptr<CompositorReport> const& compositor_report);
252 ~MultiThreadedCompositor();
253
254 void start();
255@@ -55,6 +57,7 @@
256 std::shared_ptr<graphics::Display> const display;
257 std::shared_ptr<Scene> const scene;
258 std::shared_ptr<DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
259+ std::shared_ptr<CompositorReport> const report;
260
261 std::vector<std::unique_ptr<CompositingFunctor>> thread_functors;
262 std::vector<std::thread> threads;
263
264=== added file 'src/server/compositor/null_compositor_report.cpp'
265--- src/server/compositor/null_compositor_report.cpp 1970-01-01 00:00:00 +0000
266+++ src/server/compositor/null_compositor_report.cpp 2014-01-02 09:11:20 +0000
267@@ -0,0 +1,45 @@
268+/*
269+ * Copyright © 2013 Canonical Ltd.
270+ *
271+ * This program is free software: you can redistribute it and/or modify it
272+ * under the terms of the GNU General Public License version 3,
273+ * as published by the Free Software Foundation.
274+ *
275+ * This program is distributed in the hope that it will be useful,
276+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
277+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
278+ * GNU General Public License for more details.
279+ *
280+ * You should have received a copy of the GNU General Public License
281+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
282+ *
283+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
284+ */
285+
286+#include "mir/compositor/compositor_report.h"
287+
288+using namespace mir::compositor;
289+
290+void NullCompositorReport::added_display(int, int, int, int, SubCompositorId)
291+{
292+}
293+
294+void NullCompositorReport::began_frame(SubCompositorId)
295+{
296+}
297+
298+void NullCompositorReport::finished_frame(SubCompositorId)
299+{
300+}
301+
302+void NullCompositorReport::started()
303+{
304+}
305+
306+void NullCompositorReport::stopped()
307+{
308+}
309+
310+void NullCompositorReport::scheduled()
311+{
312+}
313
314=== modified file 'src/server/default_configuration_options.cpp'
315--- src/server/default_configuration_options.cpp 2013-12-18 02:19:19 +0000
316+++ src/server/default_configuration_options.cpp 2014-01-02 09:11:20 +0000
317@@ -70,6 +70,7 @@
318 char const* const mir::ConfigurationOptions::no_server_socket_opt = "no-file";
319 char const* const mir::ConfigurationOptions::session_mediator_report_opt = "session-mediator-report";
320 char const* const mir::ConfigurationOptions::msg_processor_report_opt = "msg-processor-report";
321+char const* const mir::ConfigurationOptions::compositor_report_opt = "compositor-report";
322 char const* const mir::ConfigurationOptions::display_report_opt = "display-report";
323 char const* const mir::ConfigurationOptions::legacy_input_report_opt = "legacy-input-report";
324 char const* const mir::ConfigurationOptions::connector_report_opt = "connector-report";
325@@ -120,6 +121,8 @@
326 "Library to use for platform graphics support [default=libmirplatformgraphics.so]")
327 ("enable-input,i", po::value<bool>(),
328 "Enable input. [bool:default=true]")
329+ (compositor_report_opt, po::value<std::string>(),
330+ "Compositor reporting [{log,off}:default=off]")
331 (connector_report_opt, po::value<std::string>(),
332 "How to handle the Connector report. [{log,off}:default=off]")
333 (display_report_opt, po::value<std::string>(),
334
335=== modified file 'src/server/logging/CMakeLists.txt'
336--- src/server/logging/CMakeLists.txt 2013-12-18 02:19:19 +0000
337+++ src/server/logging/CMakeLists.txt 2014-01-02 09:11:20 +0000
338@@ -8,6 +8,7 @@
339 message_processor_report.cpp
340 display_report.cpp
341 input_report.cpp
342+ compositor_report.cpp
343 )
344
345 add_library(
346
347=== added file 'src/server/logging/compositor_report.cpp'
348--- src/server/logging/compositor_report.cpp 1970-01-01 00:00:00 +0000
349+++ src/server/logging/compositor_report.cpp 2014-01-02 09:11:20 +0000
350@@ -0,0 +1,161 @@
351+/*
352+ * Copyright © 2013-2014 Canonical Ltd.
353+ *
354+ * This program is free software: you can redistribute it and/or modify it
355+ * under the terms of the GNU General Public License version 3,
356+ * as published by the Free Software Foundation.
357+ *
358+ * This program is distributed in the hope that it will be useful,
359+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
360+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
361+ * GNU General Public License for more details.
362+ *
363+ * You should have received a copy of the GNU General Public License
364+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
365+ *
366+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
367+ */
368+
369+#include "compositor_report.h"
370+#include "mir/logging/logger.h"
371+
372+using namespace mir;
373+using namespace mir::compositor;
374+using namespace mir::time;
375+
376+namespace
377+{
378+ const char * const component = "compositor";
379+ const auto min_report_interval = std::chrono::seconds(1);
380+}
381+
382+logging::CompositorReport::CompositorReport(
383+ std::shared_ptr<Logger> const& logger,
384+ std::shared_ptr<TimeSource> const& clock)
385+ : logger(logger),
386+ clock(clock),
387+ last_report(now())
388+{
389+}
390+
391+logging::CompositorReport::TimePoint logging::CompositorReport::now() const
392+{
393+ return clock->sample();
394+}
395+
396+void logging::CompositorReport::added_display(int width, int height,
397+ int x, int y,
398+ SubCompositorId id)
399+{
400+ char msg[128];
401+ snprintf(msg, sizeof msg, "Added display %p: %dx%d %+d%+d",
402+ id, width, height, x, y);
403+ logger->log(Logger::informational, msg, component);
404+}
405+
406+void logging::CompositorReport::began_frame(SubCompositorId id)
407+{
408+ std::lock_guard<std::mutex> lock(mutex);
409+ auto& inst = instance[id];
410+
411+ auto t = now();
412+ inst.start_of_frame = t;
413+ inst.latency_sum += t - last_scheduled;
414+}
415+
416+void logging::CompositorReport::Instance::log(
417+ Logger& logger, SubCompositorId id)
418+{
419+ // The first report is a valid sample, but don't log anything because
420+ // we need at least two samples for valid deltas.
421+ if (last_reported_total_time_sum > TimePoint())
422+ {
423+ long long dt =
424+ std::chrono::duration_cast<std::chrono::microseconds>(
425+ total_time_sum - last_reported_total_time_sum
426+ ).count();
427+ auto dn = nframes - last_reported_nframes;
428+ long long df =
429+ std::chrono::duration_cast<std::chrono::microseconds>(
430+ frame_time_sum - last_reported_frame_time_sum
431+ ).count();
432+ long long dl =
433+ std::chrono::duration_cast<std::chrono::microseconds>(
434+ latency_sum - last_reported_latency_sum
435+ ).count();
436+
437+ // Keep everything premultiplied by 1000 to guarantee accuracy
438+ // and avoid floating point.
439+ long frames_per_1000sec = dt ? dn * 1000000000LL / dt : 0;
440+ long avg_frame_time_usec = dn ? df / dn : 0;
441+ long avg_latency_usec = dn ? dl / dn : 0;
442+ long dt_msec = dt / 1000L;
443+
444+ char msg[128];
445+ snprintf(msg, sizeof msg, "Display %p averaged %ld.%03ld FPS, "
446+ "%ld.%03ld ms/frame, "
447+ "latency %ld.%03ld ms, "
448+ "%ld frames over %ld.%03ld sec",
449+ id,
450+ frames_per_1000sec / 1000,
451+ frames_per_1000sec % 1000,
452+ avg_frame_time_usec / 1000,
453+ avg_frame_time_usec % 1000,
454+ avg_latency_usec / 1000,
455+ avg_latency_usec % 1000,
456+ dn,
457+ dt_msec / 1000,
458+ dt_msec % 1000
459+ );
460+
461+ logger.log(Logger::informational, msg, component);
462+ }
463+
464+ last_reported_total_time_sum = total_time_sum;
465+ last_reported_frame_time_sum = frame_time_sum;
466+ last_reported_latency_sum = latency_sum;
467+ last_reported_nframes = nframes;
468+}
469+
470+void logging::CompositorReport::finished_frame(SubCompositorId id)
471+{
472+ std::lock_guard<std::mutex> lock(mutex);
473+ auto& inst = instance[id];
474+
475+ auto t = now();
476+ inst.total_time_sum += t - inst.end_of_frame;
477+ inst.frame_time_sum += t - inst.start_of_frame;
478+ inst.end_of_frame = t;
479+ inst.nframes++;
480+
481+ /*
482+ * The exact reporting interval doesn't matter because we count everything
483+ * as a Reimann sum. Results will simply be the average over the interval.
484+ */
485+ if ((t - last_report) >= min_report_interval)
486+ {
487+ last_report = t;
488+
489+ for (auto& i : instance)
490+ i.second.log(*logger, i.first);
491+ }
492+}
493+
494+void logging::CompositorReport::started()
495+{
496+ logger->log(Logger::informational, "Started", component);
497+}
498+
499+void logging::CompositorReport::stopped()
500+{
501+ logger->log(Logger::informational, "Stopped", component);
502+
503+ std::lock_guard<std::mutex> lock(mutex);
504+ instance.clear();
505+}
506+
507+void logging::CompositorReport::scheduled()
508+{
509+ std::lock_guard<std::mutex> lock(mutex);
510+ last_scheduled = now();
511+}
512
513=== added file 'src/server/logging/compositor_report.h'
514--- src/server/logging/compositor_report.h 1970-01-01 00:00:00 +0000
515+++ src/server/logging/compositor_report.h 2014-01-02 09:11:20 +0000
516@@ -0,0 +1,81 @@
517+/*
518+ * Copyright © 2013 Canonical Ltd.
519+ *
520+ * This program is free software: you can redistribute it and/or modify it
521+ * under the terms of the GNU General Public License version 3,
522+ * as published by the Free Software Foundation.
523+ *
524+ * This program is distributed in the hope that it will be useful,
525+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
526+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
527+ * GNU General Public License for more details.
528+ *
529+ * You should have received a copy of the GNU General Public License
530+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
531+ *
532+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
533+ */
534+
535+#ifndef MIR_LOGGING_COMPOSITOR_REPORT_H_
536+#define MIR_LOGGING_COMPOSITOR_REPORT_H_
537+
538+#include "mir/compositor/compositor_report.h"
539+#include "mir/time/time_source.h"
540+#include <memory>
541+#include <mutex>
542+#include <unordered_map>
543+#include <chrono>
544+
545+namespace mir
546+{
547+namespace logging
548+{
549+
550+class Logger;
551+
552+class CompositorReport : public mir::compositor::CompositorReport
553+{
554+public:
555+ CompositorReport(std::shared_ptr<Logger> const& logger,
556+ std::shared_ptr<time::TimeSource> const& clock);
557+ void added_display(int width, int height, int x, int y, SubCompositorId id);
558+ void began_frame(SubCompositorId id);
559+ void finished_frame(SubCompositorId id);
560+ void started();
561+ void stopped();
562+ void scheduled();
563+
564+private:
565+ std::shared_ptr<Logger> const logger;
566+ std::shared_ptr<time::TimeSource> const clock;
567+
568+ typedef time::Timestamp TimePoint;
569+ TimePoint now() const;
570+
571+ struct Instance
572+ {
573+ TimePoint start_of_frame;
574+ TimePoint end_of_frame;
575+ TimePoint total_time_sum;
576+ TimePoint frame_time_sum;
577+ TimePoint latency_sum;
578+ long nframes = 0;
579+
580+ TimePoint last_reported_total_time_sum;
581+ TimePoint last_reported_frame_time_sum;
582+ TimePoint last_reported_latency_sum;
583+ long last_reported_nframes = 0;
584+
585+ void log(Logger& logger, SubCompositorId id);
586+ };
587+
588+ std::mutex mutex; // Protects the following...
589+ std::unordered_map<SubCompositorId, Instance> instance;
590+ TimePoint last_scheduled;
591+ TimePoint last_report;
592+};
593+
594+} // namespace logging
595+} // namespace mir
596+
597+#endif // MIR_LOGGING_COMPOSITOR_REPORT_H_
598
599=== modified file 'src/server/logging/default_configuration.cpp'
600--- src/server/logging/default_configuration.cpp 2013-12-18 02:19:19 +0000
601+++ src/server/logging/default_configuration.cpp 2014-01-02 09:11:20 +0000
602@@ -24,7 +24,9 @@
603 #include "session_mediator_report.h"
604
605 #include "mir/graphics/null_display_report.h"
606+#include "compositor_report.h"
607
608+using namespace mir;
609 namespace mf = mir::frontend;
610 namespace mg = mir::graphics;
611 namespace ml = mir::logging;
612@@ -70,6 +72,24 @@
613 });
614 }
615
616+std::shared_ptr<compositor::CompositorReport>
617+DefaultServerConfiguration::the_compositor_report()
618+{
619+ return compositor_report(
620+ [this]() -> std::shared_ptr<compositor::CompositorReport>
621+ {
622+ if (the_options()->get(compositor_report_opt, off_opt_value) == log_opt_value)
623+ {
624+ return std::make_shared<ml::CompositorReport>(
625+ the_logger(), the_time_source());
626+ }
627+ else
628+ {
629+ return std::make_shared<compositor::NullCompositorReport>();
630+ }
631+ });
632+}
633+
634 std::shared_ptr<mf::SessionMediatorReport>
635 mir::DefaultServerConfiguration::the_session_mediator_report()
636 {
637
638=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
639--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2013-12-18 02:19:19 +0000
640+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-01-02 09:11:20 +0000
641@@ -20,6 +20,7 @@
642 #include "mir/compositor/display_buffer_compositor.h"
643 #include "mir/compositor/scene.h"
644 #include "mir/compositor/display_buffer_compositor_factory.h"
645+#include "mir/compositor/compositor_report.h"
646 #include "mir_test_doubles/null_display.h"
647 #include "mir_test_doubles/null_display_buffer.h"
648 #include "mir_test_doubles/mock_display_buffer.h"
649@@ -296,6 +297,20 @@
650 }
651 };
652
653+class MockCompositorReport : public mc::CompositorReport
654+{
655+public:
656+ MOCK_METHOD5(added_display,
657+ void(int,int,int,int,mc::CompositorReport::SubCompositorId));
658+ MOCK_METHOD1(began_frame, void(mc::CompositorReport::SubCompositorId));
659+ MOCK_METHOD1(finished_frame, void(mc::CompositorReport::SubCompositorId));
660+ MOCK_METHOD0(started, void());
661+ MOCK_METHOD0(stopped, void());
662+ MOCK_METHOD0(scheduled, void());
663+};
664+
665+auto const null_report = std::make_shared<mc::NullCompositorReport>();
666+
667 }
668
669 TEST(MultiThreadedCompositor, compositing_happens_in_different_threads)
670@@ -307,7 +322,7 @@
671 auto display = std::make_shared<StubDisplay>(nbuffers);
672 auto scene = std::make_shared<StubScene>();
673 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
674- mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
675+ mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report};
676
677 compositor.start();
678
679@@ -320,6 +335,53 @@
680 EXPECT_TRUE(db_compositor_factory->buffers_rendered_in_different_threads());
681 }
682
683+TEST(MultiThreadedCompositor, reports_in_the_right_places)
684+{
685+ using namespace testing;
686+
687+ auto display = std::make_shared<StubDisplayWithMockBuffers>(1);
688+ auto scene = std::make_shared<StubScene>();
689+ auto db_compositor_factory =
690+ std::make_shared<RecordingDisplayBufferCompositorFactory>();
691+ auto mock_report = std::make_shared<MockCompositorReport>();
692+ mc::MultiThreadedCompositor compositor{display, scene,
693+ db_compositor_factory,
694+ mock_report};
695+
696+ EXPECT_CALL(*mock_report, started())
697+ .Times(1);
698+
699+ display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
700+ {
701+ EXPECT_CALL(mock_buf, make_current()).Times(1);
702+ EXPECT_CALL(mock_buf, view_area())
703+ .WillOnce(Return(geom::Rectangle()));
704+ });
705+
706+ EXPECT_CALL(*mock_report, added_display(_,_,_,_,_))
707+ .Times(1);
708+ EXPECT_CALL(*mock_report, scheduled())
709+ .Times(1);
710+ EXPECT_CALL(*mock_report, began_frame(_))
711+ .Times(mc::max_client_buffers);
712+ EXPECT_CALL(*mock_report, finished_frame(_))
713+ .Times(mc::max_client_buffers);
714+
715+ display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
716+ {
717+ EXPECT_CALL(mock_buf, release_current()).Times(1);
718+ });
719+
720+ EXPECT_CALL(*mock_report, stopped())
721+ .Times(AtLeast(1));
722+
723+ compositor.start();
724+ scene->emit_change_event();
725+ while (!db_compositor_factory->check_record_count_for_each_buffer(1, mc::max_client_buffers))
726+ std::this_thread::yield();
727+ compositor.stop();
728+}
729+
730 /*
731 * It's difficult to test that a render won't happen, without some further
732 * introspective capabilities that would complicate the code. This test will
733@@ -338,7 +400,7 @@
734 auto display = std::make_shared<StubDisplay>(nbuffers);
735 auto scene = std::make_shared<StubScene>();
736 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
737- mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
738+ mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report};
739
740 // Verify we're actually starting at zero frames
741 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));
742@@ -398,7 +460,7 @@
743 auto display = std::make_shared<StubDisplay>(nbuffers);
744 auto scene = std::make_shared<StubScene>();
745 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
746- mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
747+ mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report};
748
749 compositor.start();
750
751@@ -417,10 +479,12 @@
752 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
753 auto scene = std::make_shared<StubScene>();
754 auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
755- mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
756+ mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report};
757
758 display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
759 {
760+ EXPECT_CALL(mock_buf, view_area())
761+ .WillOnce(Return(geom::Rectangle()));
762 EXPECT_CALL(mock_buf, make_current()).Times(1);
763 EXPECT_CALL(mock_buf, release_current()).Times(1);
764 });
765
766=== modified file 'tests/unit-tests/logging/CMakeLists.txt'
767--- tests/unit-tests/logging/CMakeLists.txt 2013-05-28 17:55:26 +0000
768+++ tests/unit-tests/logging/CMakeLists.txt 2014-01-02 09:11:20 +0000
769@@ -2,6 +2,7 @@
770 ${CMAKE_CURRENT_SOURCE_DIR}/message_processor_report.cpp
771 ${CMAKE_CURRENT_SOURCE_DIR}/test_legacy_input_report.cpp
772 ${CMAKE_CURRENT_SOURCE_DIR}/test_display_report.cpp
773+ ${CMAKE_CURRENT_SOURCE_DIR}/test_compositor_report.cpp
774 )
775
776 set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE)
777
778=== added file 'tests/unit-tests/logging/test_compositor_report.cpp'
779--- tests/unit-tests/logging/test_compositor_report.cpp 1970-01-01 00:00:00 +0000
780+++ tests/unit-tests/logging/test_compositor_report.cpp 2014-01-02 09:11:20 +0000
781@@ -0,0 +1,142 @@
782+/*
783+ * Copyright © 2013 Canonical Ltd.
784+ *
785+ * This program is free software: you can redistribute it and/or modify it
786+ * under the terms of the GNU General Public License version 3,
787+ * as published by the Free Software Foundation.
788+ *
789+ * This program is distributed in the hope that it will be useful,
790+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
791+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
792+ * GNU General Public License for more details.
793+ *
794+ * You should have received a copy of the GNU General Public License
795+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
796+ *
797+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
798+ */
799+
800+#include "src/server/logging/compositor_report.h"
801+#include "mir/logging/logger.h"
802+#include <gtest/gtest.h>
803+#include <string>
804+#include <cstdio>
805+
806+using namespace mir;
807+using namespace std;
808+
809+namespace
810+{
811+
812+class FakeClock : public time::TimeSource
813+{
814+public:
815+ void elapse(chrono::microseconds delta)
816+ {
817+ now += delta;
818+ }
819+ time::Timestamp sample() const
820+ {
821+ return now;
822+ }
823+private:
824+ time::Timestamp now;
825+};
826+
827+class Recorder : public logging::Logger
828+{
829+public:
830+ void log(Severity, string const& message, string const&)
831+ {
832+ last = message;
833+ }
834+ string const& last_message() const
835+ {
836+ return last;
837+ }
838+ bool scrape(float& fps, float& frame_time) const
839+ {
840+ return sscanf(last.c_str(), "Display %*s averaged %f FPS, %f ms/frame",
841+ &fps, &frame_time) == 2;
842+ }
843+private:
844+ string last;
845+};
846+
847+} // namespace
848+
849+TEST(LoggingCompositorReport, calculates_accurate_stats)
850+{
851+ /*
852+ * This test just verifies the important stats; FPS and frame time.
853+ * We don't need to validate all the numbers because maintaining low
854+ * coupling to log formats is more important.
855+ */
856+ auto clock = make_shared<FakeClock>();
857+ auto recorder = make_shared<Recorder>();
858+ const void* const display_id = nullptr;
859+
860+ logging::CompositorReport report(recorder, clock);
861+
862+ int target_fps = 60;
863+ for (int frame = 0; frame < target_fps*3; frame++)
864+ {
865+ report.began_frame(display_id);
866+ clock->elapse(chrono::microseconds(1000000 / target_fps));
867+ report.finished_frame(display_id);
868+ }
869+
870+ float measured_fps, measured_frame_time;
871+ ASSERT_TRUE(recorder->scrape(measured_fps, measured_frame_time))
872+ << recorder->last_message();
873+ EXPECT_LE(59.9f, measured_fps);
874+ EXPECT_GE(60.1f, measured_fps);
875+ EXPECT_LE(16.5f, measured_frame_time);
876+ EXPECT_GE(16.7f, measured_frame_time);
877+
878+ clock->elapse(chrono::microseconds(5000000));
879+
880+ target_fps = 100;
881+ for (int frame = 0; frame < target_fps*3; frame++)
882+ {
883+ report.began_frame(display_id);
884+ clock->elapse(chrono::microseconds(1000000 / target_fps));
885+ report.finished_frame(display_id);
886+ }
887+ ASSERT_TRUE(recorder->scrape(measured_fps, measured_frame_time))
888+ << recorder->last_message();
889+ EXPECT_FLOAT_EQ(100.0f, measured_fps);
890+ EXPECT_FLOAT_EQ(10.0f, measured_frame_time);
891+}
892+
893+TEST(LoggingCompositorReport, survives_pause_resume)
894+{
895+ auto clock = make_shared<FakeClock>();
896+ auto logger = make_shared<Recorder>();
897+ const void* const before = "before";
898+ const void* const after = "after";
899+
900+ logging::CompositorReport report(logger, clock);
901+
902+ report.started();
903+
904+ report.began_frame(before);
905+ clock->elapse(chrono::microseconds(12345));
906+ report.finished_frame(before);
907+
908+ report.stopped();
909+ clock->elapse(chrono::microseconds(12345678));
910+ report.started();
911+
912+ report.began_frame(after);
913+ clock->elapse(chrono::microseconds(12345));
914+ report.finished_frame(after);
915+
916+ clock->elapse(chrono::microseconds(12345678));
917+
918+ report.began_frame(after);
919+ clock->elapse(chrono::microseconds(12345));
920+ report.finished_frame(after);
921+
922+ report.stopped();
923+}

Subscribers

People subscribed via source and target branches