Merge lp:~vanvugt/mir/compositor-report into lp:mir
- compositor-report
- Merge into development-branch
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 |
Related bugs: | |
Related blueprints: |
Mir related performance items
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Carr (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Andreas Pokorny (community) | Approve | ||
Review via email:
|
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_
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1328
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
dn is a delta of n as in nframes:
auto dn = i.nframes - i.last_
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andreas Pokorny (andreas-pokorny) wrote : | # |
Ah that check is like first frame vs. any further frame measured.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1330
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
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 | +} |
FAILED: Continuous integration, rev:1326 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/576/ jenkins. qa.ubuntu. com/job/ mir-android- trusty- i386-build/ 511 jenkins. qa.ubuntu. com/job/ mir-clang- trusty- amd64-build/ 507 jenkins. qa.ubuntu. com/job/ mir-mediumtests -trusty- touch/115 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- amd64-ci/ 302/console jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 305 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 305/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/115 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/115/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/143 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 2612
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/576/ rebuild
http://