1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6
7 #include "base/json/json_reader.h"
8 #include "base/strings/string_tokenizer.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/test/trace_event_analyzer.h"
11 #include "build/build_config.h"
12 #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
13 #include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_tabstrip.h"
16 #include "chrome/test/base/tracing.h"
17 #include "chrome/test/base/ui_test_utils.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/render_view_host.h"
20 #include "content/public/common/content_switches.h"
21 #include "content/public/test/browser_test.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "media/base/media_switches.h"
24 #include "net/test/embedded_test_server/embedded_test_server.h"
25 #include "testing/perf/perf_test.h"
26 #include "third_party/blink/public/common/features.h"
27 #include "ui/gl/gl_switches.h"
28
29 using trace_analyzer::TraceEvent;
30 using trace_analyzer::TraceEventVector;
31 using trace_analyzer::Query;
32
33 namespace {
34
35 // Trace events.
36 static const char kStartRenderEventName[] =
37 "RemoteVideoSourceDelegate::RenderFrame";
38 static const char kEnqueueFrameEventName[] =
39 "WebMediaPlayerMSCompositor::EnqueueFrame";
40 static const char kSetFrameEventName[] =
41 "WebMediaPlayerMSCompositor::SetCurrentFrame";
42 static const char kGetFrameEventName[] =
43 "WebMediaPlayerMSCompositor::GetCurrentFrame";
44 static const char kVideoResourceEventName[] =
45 "VideoResourceUpdater::ObtainFrameResources";
46 static const char kVsyncEventName[] = "Display::DrawAndSwap";
47
48 // VideoFrameSubmitter dumps the delay from the handover of a decoded remote
49 // VideoFrame from webrtc to the moment the OS acknowledges the swap buffers.
50 static const char kVideoFrameSubmitterEventName[] = "VideoFrameSubmitter";
51
52 static const char kEventMatchKey[] = "Timestamp";
53 static const char kTestResultString[] = "TestVideoDisplayPerf";
54 static const char kMainWebrtcTestHtmlPage[] =
55 "/webrtc/webrtc_video_display_perf_test.html";
56
57 struct VideoDisplayPerfTestConfig {
58 int width;
59 int height;
60 int fps;
61 bool disable_render_smoothness_algorithm;
62 };
63
CalculateMeanAndMax(const std::vector<double> & inputs,double * mean,double * std_dev,double * max)64 void CalculateMeanAndMax(const std::vector<double>& inputs,
65 double* mean,
66 double* std_dev,
67 double* max) {
68 double sum = 0.0;
69 double sqr_sum = 0.0;
70 double max_so_far = 0.0;
71 size_t count = inputs.size();
72 for (const auto& input : inputs) {
73 sum += input;
74 sqr_sum += input * input;
75 max_so_far = std::max(input, max_so_far);
76 }
77 *max = max_so_far;
78 *mean = sum / count;
79 *std_dev = sqrt(std::max(0.0, count * sqr_sum - sum * sum)) / count;
80 }
81
PrintMeanAndMax(const std::string & var_name,const std::string & name_modifier,const std::vector<double> & vars)82 void PrintMeanAndMax(const std::string& var_name,
83 const std::string& name_modifier,
84 const std::vector<double>& vars) {
85 double mean = 0.0;
86 double std_dev = 0.0;
87 double max = 0.0;
88 CalculateMeanAndMax(vars, &mean, &std_dev, &max);
89 perf_test::PrintResultMeanAndError(
90 kTestResultString, name_modifier, var_name + " Mean",
91 base::StringPrintf("%.0lf,%.0lf", mean, std_dev), "μs", true);
92 perf_test::PrintResult(kTestResultString, name_modifier, var_name + " Max",
93 base::StringPrintf("%.0lf", max), "μs", true);
94 }
95
FindEvents(trace_analyzer::TraceAnalyzer * analyzer,const std::string & event_name,const Query & base_query,TraceEventVector * events)96 void FindEvents(trace_analyzer::TraceAnalyzer* analyzer,
97 const std::string& event_name,
98 const Query& base_query,
99 TraceEventVector* events) {
100 Query query = Query::EventNameIs(event_name) && base_query;
101 analyzer->FindEvents(query, events);
102 }
103
AssociateEvents(trace_analyzer::TraceAnalyzer * analyzer,const std::vector<std::string> & event_names,const std::string & match_string,const Query & base_query)104 void AssociateEvents(trace_analyzer::TraceAnalyzer* analyzer,
105 const std::vector<std::string>& event_names,
106 const std::string& match_string,
107 const Query& base_query) {
108 for (size_t i = 0; i < event_names.size() - 1; ++i) {
109 Query begin = Query::EventNameIs(event_names[i]);
110 Query end = Query::EventNameIs(event_names[i + 1]);
111 Query match(Query::EventArg(match_string) == Query::OtherArg(match_string));
112 analyzer->AssociateEvents(begin, end, base_query && match);
113 }
114 }
115
OpenWebrtcInternalsTab(Browser * browser)116 content::WebContents* OpenWebrtcInternalsTab(Browser* browser) {
117 chrome::AddTabAt(browser, GURL(url::kAboutBlankURL), -1, true);
118 ui_test_utils::NavigateToURL(browser, GURL("chrome://webrtc-internals"));
119 return browser->tab_strip_model()->GetActiveWebContents();
120 }
121
ParseGoogMaxDecodeFromWebrtcInternalsTab(const std::string & webrtc_internals_stats_json)122 std::vector<double> ParseGoogMaxDecodeFromWebrtcInternalsTab(
123 const std::string& webrtc_internals_stats_json) {
124 std::vector<double> goog_decode_ms;
125
126 std::unique_ptr<base::Value> parsed_json =
127 base::JSONReader::ReadDeprecated(webrtc_internals_stats_json);
128 base::DictionaryValue* dictionary = nullptr;
129 if (!parsed_json.get() || !parsed_json->GetAsDictionary(&dictionary))
130 return goog_decode_ms;
131 ignore_result(parsed_json.release());
132
133 // |dictionary| should have exactly two entries, one per ssrc.
134 if (!dictionary || dictionary->size() != 2u)
135 return goog_decode_ms;
136
137 // Only a given |dictionary| entry will have a "stats" entry that has a key
138 // that ends with "recv-googMaxDecodeMs" inside (it will start with the ssrc
139 // id, but we don't care about that). Then collect the string of "values" out
140 // of that key and convert those into the |goog_decode_ms| vector of doubles.
141 for (const auto& dictionary_entry : *dictionary) {
142 for (const auto& ssrc_entry : dictionary_entry.second->DictItems()) {
143 if (ssrc_entry.first != "stats")
144 continue;
145
146 for (const auto& stat_entry : ssrc_entry.second.DictItems()) {
147 if (!base::EndsWith(stat_entry.first, "recv-googMaxDecodeMs",
148 base::CompareCase::SENSITIVE)) {
149 continue;
150 }
151 base::Value* values_entry = stat_entry.second.FindKey({"values"});
152 if (!values_entry)
153 continue;
154 base::StringTokenizer values_tokenizer(values_entry->GetString(),
155 "[,]");
156 while (values_tokenizer.GetNext()) {
157 if (values_tokenizer.token_is_delim())
158 continue;
159 goog_decode_ms.push_back(atof(values_tokenizer.token().c_str()) *
160 base::Time::kMicrosecondsPerMillisecond);
161 }
162 }
163 }
164 }
165 return goog_decode_ms;
166 }
167
168 } // anonymous namespace
169
170 // Tests the performance of Chrome displaying remote video.
171 //
172 // This test creates a WebRTC peer connection between two tabs and measures the
173 // trace events listed in the beginning of this file on the tab receiving
174 // remote video. In order to cut down from the encode cost, the tab receiving
175 // remote video does not send any video to its peer.
176 //
177 // This test traces certain categories for a period of time. It follows the
178 // lifetime of a single video frame by synchronizing on the timestamps values
179 // attached to trace events. Then, it calculates the duration and related stats.
180
181 class WebRtcVideoDisplayPerfBrowserTest
182 : public WebRtcTestBase,
183 public testing::WithParamInterface<
184 std::tuple<gfx::Size /* resolution */,
185 int /* fps */,
186 bool /* disable_render_smoothness_algorithm */>> {
187 public:
WebRtcVideoDisplayPerfBrowserTest()188 WebRtcVideoDisplayPerfBrowserTest() {
189 const auto& params = GetParam();
190 const gfx::Size& resolution = std::get<0>(params);
191 test_config_ = {resolution.width(), resolution.height(),
192 std::get<1>(params), std::get<2>(params)};
193 }
194
SetUpInProcessBrowserTestFixture()195 void SetUpInProcessBrowserTestFixture() override {
196 DetectErrorsInJavaScript();
197 }
198
SetUpCommandLine(base::CommandLine * command_line)199 void SetUpCommandLine(base::CommandLine* command_line) override {
200 command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
201 command_line->RemoveSwitch(switches::kUseFakeDeviceForMediaStream);
202 command_line->AppendSwitchASCII(
203 switches::kUseFakeDeviceForMediaStream,
204 base::StringPrintf("fps=%d", test_config_.fps));
205 if (test_config_.disable_render_smoothness_algorithm)
206 command_line->AppendSwitch(switches::kDisableRTCSmoothnessAlgorithm);
207 command_line->AppendSwitch(switches::kUseGpuInTests);
208 }
209
TestVideoDisplayPerf(const std::string & video_codec)210 void TestVideoDisplayPerf(const std::string& video_codec) {
211 ASSERT_TRUE(embedded_test_server()->Start());
212 // chrome:webrtc-internals doesn't start tracing anything until the
213 // connection(s) are up.
214 content::WebContents* webrtc_internals_tab =
215 OpenWebrtcInternalsTab(browser());
216 EXPECT_TRUE(content::ExecuteScript(
217 webrtc_internals_tab,
218 "currentGetStatsMethod = OPTION_GETSTATS_LEGACY"));
219
220 content::WebContents* left_tab =
221 OpenPageAndGetUserMediaInNewTabWithConstraints(
222 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
223 base::StringPrintf(
224 "{audio: true, video: {mandatory: {minWidth: %d, maxWidth: %d, "
225 "minHeight: %d, maxHeight: %d}}}",
226 test_config_.width, test_config_.width, test_config_.height,
227 test_config_.height));
228 content::WebContents* right_tab =
229 OpenPageAndGetUserMediaInNewTabWithConstraints(
230 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
231 "{audio: true, video: false}");
232 const int process_id = right_tab->GetMainFrame()
233 ->GetRenderViewHost()
234 ->GetProcess()
235 ->GetProcess()
236 .Pid();
237
238 const std::string disable_cpu_adaptation_constraint(
239 "{'optional': [{'googCpuOveruseDetection': false}]}");
240 SetupPeerconnectionWithConstraintsAndLocalStream(
241 left_tab, disable_cpu_adaptation_constraint);
242 SetupPeerconnectionWithConstraintsAndLocalStream(
243 right_tab, disable_cpu_adaptation_constraint);
244
245 if (!video_codec.empty()) {
246 constexpr bool kPreferHwVideoCodec = true;
247 SetDefaultVideoCodec(left_tab, video_codec, kPreferHwVideoCodec);
248 SetDefaultVideoCodec(right_tab, video_codec, kPreferHwVideoCodec);
249 }
250 NegotiateCall(left_tab, right_tab);
251
252 StartDetectingVideo(right_tab, "remote-view");
253 WaitForVideoToPlay(right_tab);
254 // Run the connection a bit to ramp up.
255 test::SleepInJavascript(left_tab, 10000);
256
257 ASSERT_TRUE(tracing::BeginTracing("media,viz,webrtc"));
258 // Run the connection for 5 seconds to collect metrics.
259 test::SleepInJavascript(left_tab, 5000);
260
261 const std::string webrtc_internals_stats_json = ExecuteJavascript(
262 "window.domAutomationController.send("
263 " JSON.stringify(peerConnectionDataStore));",
264 webrtc_internals_tab);
265 webrtc_decode_latencies_ =
266 ParseGoogMaxDecodeFromWebrtcInternalsTab(webrtc_internals_stats_json);
267 chrome::CloseWebContents(browser(), webrtc_internals_tab, false);
268
269 std::string json_events;
270 ASSERT_TRUE(tracing::EndTracing(&json_events));
271 std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
272 trace_analyzer::TraceAnalyzer::Create(json_events));
273 analyzer->AssociateAsyncBeginEndEvents();
274
275 HangUp(left_tab);
276 HangUp(right_tab);
277 chrome::CloseWebContents(browser(), left_tab, false);
278 chrome::CloseWebContents(browser(), right_tab, false);
279
280 ASSERT_TRUE(CalculatePerfResults(analyzer.get(), process_id));
281 PrintResults(video_codec);
282 }
283
284 private:
CalculatePerfResults(trace_analyzer::TraceAnalyzer * analyzer,int render_process_id)285 bool CalculatePerfResults(trace_analyzer::TraceAnalyzer* analyzer,
286 int render_process_id) {
287 Query match_process_id = Query::EventPidIs(render_process_id);
288 const std::vector<std::string> chain_of_events = {
289 kStartRenderEventName, kEnqueueFrameEventName, kSetFrameEventName,
290 kGetFrameEventName, kVideoResourceEventName};
291 AssociateEvents(analyzer, chain_of_events,
292 kEventMatchKey, match_process_id);
293
294 TraceEventVector start_render_events;
295 FindEvents(analyzer, kStartRenderEventName, match_process_id,
296 &start_render_events);
297 if (start_render_events.empty())
298 return false;
299
300 // We are only interested in vsync events coming after the first render
301 // event. Earlier ones are already missed.
302 Query after_first_render_event =
303 Query::EventTime() >
304 Query::Double(start_render_events.front()->timestamp);
305 TraceEventVector vsync_events;
306 FindEvents(analyzer, kVsyncEventName, after_first_render_event,
307 &vsync_events);
308 if (vsync_events.empty())
309 return false;
310
311 size_t found_vsync_index = 0;
312 size_t skipped_frame_count = 0;
313 for (const auto* event : start_render_events) {
314 const double start = event->timestamp;
315
316 const TraceEvent* enqueue_frame_event = event->other_event;
317 if (!enqueue_frame_event) {
318 skipped_frame_count++;
319 continue;
320 }
321 const double enqueue_frame_duration =
322 enqueue_frame_event->timestamp - start;
323
324 const TraceEvent* set_frame_event = enqueue_frame_event->other_event;
325 if (!set_frame_event) {
326 skipped_frame_count++;
327 continue;
328 }
329 const double set_frame_duration =
330 set_frame_event->timestamp - enqueue_frame_event->timestamp;
331
332 const TraceEvent* get_frame_event = set_frame_event->other_event;
333 if (!get_frame_event) {
334 skipped_frame_count++;
335 continue;
336 }
337 const double get_frame_duration =
338 get_frame_event->timestamp - set_frame_event->timestamp;
339
340 const TraceEvent* video_resource_event = get_frame_event->other_event;
341 if (!video_resource_event) {
342 skipped_frame_count++;
343 continue;
344 }
345 const double resource_ready_duration =
346 video_resource_event->timestamp - get_frame_event->timestamp;
347
348 // We try to find the closest vsync event after video resource is ready.
349 const bool found_vsync = FindFirstOf(
350 vsync_events,
351 Query::EventTime() > Query::Double(video_resource_event->timestamp +
352 video_resource_event->duration),
353 found_vsync_index, &found_vsync_index);
354 if (!found_vsync) {
355 skipped_frame_count++;
356 continue;
357 }
358 const double vsync_duration = vsync_events[found_vsync_index]->timestamp -
359 video_resource_event->timestamp;
360 const double total_duration =
361 vsync_events[found_vsync_index]->timestamp - start;
362
363 enqueue_frame_durations_.push_back(enqueue_frame_duration);
364 set_frame_durations_.push_back(set_frame_duration);
365 get_frame_durations_.push_back(get_frame_duration);
366 resource_ready_durations_.push_back(resource_ready_duration);
367 vsync_durations_.push_back(vsync_duration);
368 total_controlled_durations_.push_back(total_duration -
369 set_frame_duration);
370 total_durations_.push_back(total_duration);
371 }
372
373 if (start_render_events.size() == skipped_frame_count)
374 return false;
375
376 // Calculate the percentage by dividing by the number of frames received.
377 skipped_frame_percentage_ =
378 100.0 * skipped_frame_count / start_render_events.size();
379
380 // |kVideoFrameSubmitterEventName| is in itself an ASYNC latency measurement
381 // from the point where the remote video decode is available (i.e.
382 // kStartRenderEventName) until the platform-dependent swap buffers, so by
383 // definition is larger than the |total_duration|.
384 TraceEventVector video_frame_submitter_events;
385 analyzer->FindEvents(Query::MatchAsyncBeginWithNext() &&
386 Query::EventNameIs(kVideoFrameSubmitterEventName),
387 &video_frame_submitter_events);
388 for (const auto* event : video_frame_submitter_events) {
389 // kVideoFrameSubmitterEventName is divided into a BEGIN, a PAST and an
390 // END steps. AssociateAsyncBeginEndEvents paired BEGIN with PAST, but we
391 // have to get to the END. Note that if there's no intermediate PAST, it
392 // means this wasn't a remote feed VideoFrame, we should not have those in
393 // this test. If there's no END, then tracing was cut short.
394 if (!event->has_other_event() ||
395 event->other_event->phase != TRACE_EVENT_PHASE_ASYNC_STEP_PAST ||
396 !event->other_event->has_other_event()) {
397 continue;
398 }
399 const auto begin = event->timestamp;
400 const auto end = event->other_event->other_event->timestamp;
401 video_frame_submmitter_latencies_.push_back(end - begin);
402 }
403
404 return true;
405 }
406
PrintResults(const std::string & video_codec)407 void PrintResults(const std::string& video_codec) {
408 std::string smoothness_indicator =
409 test_config_.disable_render_smoothness_algorithm ? "_DisableSmoothness"
410 : "";
411 std::string name_modifier = base::StringPrintf(
412 "%s_%dp%df%s", video_codec.c_str(), test_config_.height,
413 test_config_.fps, smoothness_indicator.c_str());
414 perf_test::PrintResult(
415 kTestResultString, name_modifier, "Skipped frames",
416 base::StringPrintf("%.2lf", skipped_frame_percentage_), "percent",
417 true);
418 // We identify intervals in a way that can help us easily bisect the source
419 // of added latency in case of a regression. From these intervals, "Render
420 // Algorithm" can take random amount of times based on the vsync cycle it is
421 // closest to. Therefore, "Total Controlled Latency" refers to the total
422 // times without that section for semi-consistent results.
423 PrintMeanAndMax("Passing to Render Algorithm Latency", name_modifier,
424 enqueue_frame_durations_);
425 PrintMeanAndMax("Render Algorithm Latency", name_modifier,
426 set_frame_durations_);
427 PrintMeanAndMax("Compositor Picking Frame Latency", name_modifier,
428 get_frame_durations_);
429 PrintMeanAndMax("Compositor Resource Preparation Latency", name_modifier,
430 resource_ready_durations_);
431 PrintMeanAndMax("Vsync Latency", name_modifier, vsync_durations_);
432 PrintMeanAndMax("Total Controlled Latency", name_modifier,
433 total_controlled_durations_);
434 PrintMeanAndMax("Total Latency", name_modifier, total_durations_);
435
436 PrintMeanAndMax("Post-decode-to-raster latency", name_modifier,
437 video_frame_submmitter_latencies_);
438 PrintMeanAndMax("WebRTC decode latency", name_modifier,
439 webrtc_decode_latencies_);
440 }
441
442 VideoDisplayPerfTestConfig test_config_;
443 // Containers for test results.
444 double skipped_frame_percentage_ = 0;
445 std::vector<double> enqueue_frame_durations_;
446 std::vector<double> set_frame_durations_;
447 std::vector<double> get_frame_durations_;
448 std::vector<double> resource_ready_durations_;
449 std::vector<double> vsync_durations_;
450 std::vector<double> total_controlled_durations_;
451 std::vector<double> total_durations_;
452
453 // These two put together represent the whole delay from encoded video frames
454 // to OS swap buffers call (or callback, depending on the platform).
455 std::vector<double> video_frame_submmitter_latencies_;
456 std::vector<double> webrtc_decode_latencies_;
457 };
458
459 INSTANTIATE_TEST_SUITE_P(WebRtcVideoDisplayPerfBrowserTests,
460 WebRtcVideoDisplayPerfBrowserTest,
461 testing::Combine(testing::Values(gfx::Size(1280, 720),
462 gfx::Size(1920,
463 1080)),
464 testing::Values(30, 60),
465 testing::Bool()));
466
IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,MANUAL_TestVideoDisplayPerfVP9)467 IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
468 MANUAL_TestVideoDisplayPerfVP9) {
469 TestVideoDisplayPerf("VP9");
470 }
471
472 #if BUILDFLAG(RTC_USE_H264)
IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,MANUAL_TestVideoDisplayPerfH264)473 IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
474 MANUAL_TestVideoDisplayPerfH264) {
475 if (!base::FeatureList::IsEnabled(
476 blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
477 LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
478 "Skipping WebRtcVideoDisplayPerfBrowserTest.MANUAL_"
479 "TestVideoDisplayPerfH264 "
480 "(test \"OK\")";
481 return;
482 }
483 TestVideoDisplayPerf("H264");
484 }
485 #endif // BUILDFLAG(RTC_USE_H264)
486