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