1 // Copyright 2013 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 "chrome/browser/media/webrtc/webrtc_browsertest_perf.h"
6 
7 #include <stddef.h>
8 
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/values.h"
13 #include "chrome/test/base/in_process_browser_test.h"
14 #include "testing/perf/perf_result_reporter.h"
15 #include "testing/perf/perf_test.h"
16 
Statistic(const std::string & statistic,const std::string & bucket)17 static std::string Statistic(const std::string& statistic,
18                              const std::string& bucket) {
19   // A ssrc stats key will be on the form stats.<bucket>-<key>.values.
20   // This will give a json "path" which will dig into the time series for the
21   // specified statistic. Buckets can be for instance ssrc_1212344, bweforvideo,
22   // and will each contain a bunch of statistics relevant to their nature.
23   // Each peer connection has a number of such buckets.
24   return base::StringPrintf("stats.%s-%s.values", bucket.c_str(),
25                             statistic.c_str());
26 }
27 
MaybePrintResultsForAudioReceive(const std::string & ssrc,const base::DictionaryValue & pc_dict,const std::string & modifier)28 static void MaybePrintResultsForAudioReceive(
29     const std::string& ssrc, const base::DictionaryValue& pc_dict,
30     const std::string& modifier) {
31   std::string value;
32   if (!pc_dict.GetString(Statistic("audioOutputLevel", ssrc), &value)) {
33     // Not an audio receive stream.
34     return;
35   }
36 
37   EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
38   perf_test::PrintResult(
39       "audio_misc", modifier, "packets_lost", value, "frames", false);
40   EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
41   perf_test::PrintResult(
42       "audio_rx", modifier, "goog_jitter_recv", value, "ms", false);
43 
44   EXPECT_TRUE(pc_dict.GetString(Statistic("googExpandRate", ssrc), &value));
45   perf_test::PrintResult(
46       "audio_rates", modifier, "goog_expand_rate", value, "%", false);
47   EXPECT_TRUE(
48       pc_dict.GetString(Statistic("googSpeechExpandRate", ssrc), &value));
49   perf_test::PrintResult(
50       "audio_rates", modifier, "goog_speech_expand_rate", value, "%", false);
51   EXPECT_TRUE(
52       pc_dict.GetString(Statistic("googSecondaryDecodedRate", ssrc), &value));
53   perf_test::PrintResult(
54       "audio_rates", modifier, "goog_secondary_decoded_rate", value, "%",
55       false);
56 }
57 
MaybePrintResultsForAudioSend(const std::string & ssrc,const base::DictionaryValue & pc_dict,const std::string & modifier)58 static void MaybePrintResultsForAudioSend(
59     const std::string& ssrc, const base::DictionaryValue& pc_dict,
60     const std::string& modifier) {
61   std::string value;
62   if (!pc_dict.GetString(Statistic("audioInputLevel", ssrc), &value)) {
63     // Not an audio send stream.
64     return;
65   }
66 
67   EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterReceived", ssrc), &value));
68   perf_test::PrintResult(
69       "audio_tx", modifier, "goog_jitter_recv", value, "ms", false);
70   EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
71   perf_test::PrintResult(
72       "audio_tx", modifier, "goog_rtt", value, "ms", false);
73   EXPECT_TRUE(
74       pc_dict.GetString(Statistic("packetsSentPerSecond", ssrc), &value));
75   perf_test::PrintResult("audio_tx", modifier, "packets_sent_per_second", value,
76                          "packets", false);
77 }
78 
MaybePrintResultsForVideoSend(const std::string & ssrc,const base::DictionaryValue & pc_dict,const std::string & modifier)79 static void MaybePrintResultsForVideoSend(
80     const std::string& ssrc, const base::DictionaryValue& pc_dict,
81     const std::string& modifier) {
82   std::string value;
83   if (!pc_dict.GetString(Statistic("googFrameRateSent", ssrc), &value)) {
84     // Not a video send stream.
85     return;
86   }
87 
88   // Graph these by unit: the dashboard expects all stats in one graph to have
89   // the same unit (e.g. ms, fps, etc). Most graphs, like video_fps, will also
90   // be populated by the counterparts on the video receiving side.
91   perf_test::PrintResult(
92       "video_fps", modifier, "goog_frame_rate_sent", value, "fps", false);
93   EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameRateInput", ssrc), &value));
94   perf_test::PrintResult(
95       "video_fps", modifier, "goog_frame_rate_input", value, "fps", false);
96 
97   EXPECT_TRUE(pc_dict.GetString(Statistic("googFirsReceived", ssrc), &value));
98   perf_test::PrintResult(
99       "video_misc", modifier, "goog_firs_recv", value, "", false);
100   EXPECT_TRUE(pc_dict.GetString(Statistic("googNacksReceived", ssrc), &value));
101   perf_test::PrintResult(
102       "video_misc", modifier, "goog_nacks_recv", value, "", false);
103 
104   EXPECT_TRUE(pc_dict.GetString(Statistic("googFrameWidthSent", ssrc), &value));
105   perf_test::PrintResult("video_resolution", modifier, "goog_frame_width_sent",
106                          value, "pixels", false);
107   EXPECT_TRUE(
108       pc_dict.GetString(Statistic("googFrameHeightSent", ssrc), &value));
109   perf_test::PrintResult("video_resolution", modifier, "goog_frame_height_sent",
110                          value, "pixels", false);
111 
112   EXPECT_TRUE(pc_dict.GetString(Statistic("googAvgEncodeMs", ssrc), &value));
113   perf_test::PrintResult(
114       "video_tx", modifier, "goog_avg_encode_ms", value, "ms", false);
115   EXPECT_TRUE(pc_dict.GetString(Statistic("googRtt", ssrc), &value));
116   perf_test::PrintResult("video_tx", modifier, "goog_rtt", value, "ms", false);
117 
118   EXPECT_TRUE(pc_dict.GetString(
119       Statistic("googEncodeUsagePercent", ssrc), &value));
120   perf_test::PrintResult("video_cpu_usage", modifier,
121                          "goog_encode_usage_percent", value, "%", false);
122 }
123 
MaybePrintResultsForVideoReceive(const std::string & ssrc,const base::DictionaryValue & pc_dict,const std::string & modifier)124 static void MaybePrintResultsForVideoReceive(
125     const std::string& ssrc, const base::DictionaryValue& pc_dict,
126     const std::string& modifier) {
127   std::string value;
128   if (!pc_dict.GetString(Statistic("googFrameRateReceived", ssrc), &value)) {
129     // Not a video receive stream.
130     return;
131   }
132 
133   perf_test::PrintResult(
134       "video_fps", modifier, "goog_frame_rate_recv", value, "fps", false);
135   EXPECT_TRUE(
136       pc_dict.GetString(Statistic("googFrameRateOutput", ssrc), &value));
137   perf_test::PrintResult(
138       "video_fps", modifier, "goog_frame_rate_output", value, "fps", false);
139 
140   EXPECT_TRUE(pc_dict.GetString(Statistic("packetsLost", ssrc), &value));
141   perf_test::PrintResult("video_misc", modifier, "packets_lost", value,
142                          "frames", false);
143 
144   EXPECT_TRUE(
145       pc_dict.GetString(Statistic("googFrameWidthReceived", ssrc), &value));
146   perf_test::PrintResult("video_resolution", modifier, "goog_frame_width_recv",
147                          value, "pixels", false);
148   EXPECT_TRUE(
149       pc_dict.GetString(Statistic("googFrameHeightReceived", ssrc), &value));
150   perf_test::PrintResult("video_resolution", modifier, "goog_frame_height_recv",
151                          value, "pixels", false);
152 
153   EXPECT_TRUE(pc_dict.GetString(Statistic("googCurrentDelayMs", ssrc), &value));
154   perf_test::PrintResult(
155       "video_rx", modifier, "goog_current_delay_ms", value, "ms", false);
156   EXPECT_TRUE(pc_dict.GetString(Statistic("googTargetDelayMs", ssrc), &value));
157   perf_test::PrintResult(
158       "video_rx", modifier, "goog_target_delay_ms", value, "ms", false);
159   EXPECT_TRUE(pc_dict.GetString(Statistic("googDecodeMs", ssrc), &value));
160   perf_test::PrintResult("video_rx", modifier, "goog_decode_ms", value, "ms",
161                          false);
162   EXPECT_TRUE(pc_dict.GetString(Statistic("googMaxDecodeMs", ssrc), &value));
163   perf_test::PrintResult(
164       "video_rx", modifier, "goog_max_decode_ms", value, "ms", false);
165   EXPECT_TRUE(pc_dict.GetString(Statistic("googJitterBufferMs", ssrc), &value));
166   perf_test::PrintResult(
167       "video_rx", modifier, "goog_jitter_buffer_ms", value, "ms", false);
168   EXPECT_TRUE(pc_dict.GetString(Statistic("googRenderDelayMs", ssrc), &value));
169   perf_test::PrintResult(
170       "video_rx", modifier, "goog_render_delay_ms", value, "ms", false);
171 }
172 
ExtractSsrcIdentifier(const std::string & key)173 static std::string ExtractSsrcIdentifier(const std::string& key) {
174   // Example key: ssrc_1234-someStatName. Grab the part before the dash.
175   size_t key_start_pos = 0;
176   size_t key_end_pos = key.find("-");
177   CHECK(key_end_pos != std::string::npos) << "Could not parse key " << key;
178   return key.substr(key_start_pos, key_end_pos - key_start_pos);
179 }
180 
181 // Returns the set of unique ssrc identifiers in the call (e.g. ssrc_1234,
182 // ssrc_12356, etc). |stats_dict| is the .stats dict from one peer connection.
FindAllSsrcIdentifiers(const base::DictionaryValue & stats_dict)183 static std::set<std::string> FindAllSsrcIdentifiers(
184     const base::DictionaryValue& stats_dict) {
185   std::set<std::string> result;
186   base::DictionaryValue::Iterator stats_iterator(stats_dict);
187 
188   while (!stats_iterator.IsAtEnd()) {
189     if (stats_iterator.key().find("ssrc_") != std::string::npos)
190       result.insert(ExtractSsrcIdentifier(stats_iterator.key()));
191     stats_iterator.Advance();
192   }
193   return result;
194 }
195 
196 namespace test {
197 
PrintBweForVideoMetrics(const base::DictionaryValue & pc_dict,const std::string & modifier,const std::string & video_codec)198 void PrintBweForVideoMetrics(const base::DictionaryValue& pc_dict,
199                              const std::string& modifier,
200                              const std::string& video_codec) {
201   std::string video_modifier =
202       video_codec.empty() ? modifier : modifier + "_" + video_codec;
203   const std::string kBweStatsKey = "bweforvideo";
204   std::string value;
205   ASSERT_TRUE(pc_dict.GetString(
206       Statistic("googAvailableSendBandwidth", kBweStatsKey), &value));
207   perf_test::PrintResult("bwe_stats", video_modifier, "available_send_bw",
208                          value, "bit/s", false);
209   ASSERT_TRUE(pc_dict.GetString(
210       Statistic("googAvailableReceiveBandwidth", kBweStatsKey), &value));
211   perf_test::PrintResult("bwe_stats", video_modifier, "available_recv_bw",
212                          value, "bit/s", false);
213   ASSERT_TRUE(pc_dict.GetString(
214       Statistic("googTargetEncBitrate", kBweStatsKey), &value));
215   perf_test::PrintResult("bwe_stats", video_modifier, "target_enc_bitrate",
216                          value, "bit/s", false);
217   ASSERT_TRUE(pc_dict.GetString(
218       Statistic("googActualEncBitrate", kBweStatsKey), &value));
219   perf_test::PrintResult("bwe_stats", video_modifier, "actual_enc_bitrate",
220                          value, "bit/s", false);
221   ASSERT_TRUE(pc_dict.GetString(
222       Statistic("googTransmitBitrate", kBweStatsKey), &value));
223   perf_test::PrintResult("bwe_stats", video_modifier, "transmit_bitrate", value,
224                          "bit/s", false);
225 }
226 
PrintMetricsForAllStreams(const base::DictionaryValue & pc_dict,const std::string & modifier,const std::string & video_codec)227 void PrintMetricsForAllStreams(const base::DictionaryValue& pc_dict,
228                                const std::string& modifier,
229                                const std::string& video_codec) {
230   PrintMetricsForSendStreams(pc_dict, modifier, video_codec);
231   PrintMetricsForRecvStreams(pc_dict, modifier, video_codec);
232 }
233 
PrintMetricsForSendStreams(const base::DictionaryValue & pc_dict,const std::string & modifier,const std::string & video_codec)234 void PrintMetricsForSendStreams(const base::DictionaryValue& pc_dict,
235                                 const std::string& modifier,
236                                 const std::string& video_codec) {
237   std::string video_modifier =
238       video_codec.empty() ? modifier : modifier + "_" + video_codec;
239   const base::DictionaryValue* stats_dict;
240   ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
241   std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
242 
243   auto ssrc_iterator = ssrc_identifiers.begin();
244   for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
245     const std::string& ssrc = *ssrc_iterator;
246     MaybePrintResultsForAudioSend(ssrc, pc_dict, modifier);
247     MaybePrintResultsForVideoSend(ssrc, pc_dict, video_modifier);
248   }
249 }
250 
PrintMetricsForRecvStreams(const base::DictionaryValue & pc_dict,const std::string & modifier,const std::string & video_codec)251 void PrintMetricsForRecvStreams(const base::DictionaryValue& pc_dict,
252                                 const std::string& modifier,
253                                 const std::string& video_codec) {
254   std::string video_modifier =
255       video_codec.empty() ? modifier : modifier + "_" + video_codec;
256   const base::DictionaryValue* stats_dict;
257   ASSERT_TRUE(pc_dict.GetDictionary("stats", &stats_dict));
258   std::set<std::string> ssrc_identifiers = FindAllSsrcIdentifiers(*stats_dict);
259 
260   auto ssrc_iterator = ssrc_identifiers.begin();
261   for (; ssrc_iterator != ssrc_identifiers.end(); ++ssrc_iterator) {
262     const std::string& ssrc = *ssrc_iterator;
263     MaybePrintResultsForAudioReceive(ssrc, pc_dict, modifier);
264     MaybePrintResultsForVideoReceive(ssrc, pc_dict, video_modifier);
265   }
266 }
267 
268 constexpr char kMetricPrefixVideoQualityTest[] =
269     "WebRtcVideoQualityBrowserTest.";
270 constexpr char kMetricUniqueFramesCount[] = "Unique_frames_count";
271 constexpr char kMetricPsnrUnitless[] = "PSNR";
272 constexpr char kMetricSsimUnitless[] = "SSIM";
273 constexpr char kMetricMaxRepeatedCount[] = "Max_repeated";
274 constexpr char kMetricMaxSkippedCount[] = "Max_skipped";
275 constexpr char kMetricTotalSkippedCount[] = "Total_skipped";
276 constexpr char kMetricDecodeErrorsReferenceCount[] = "Decode_errors_reference";
277 constexpr char kMetricDecodeErrorsTestCount[] = "Decode_errors_test";
278 
ParseDoubleFromOutput(const std::string & line,const char * expected_label,double * value)279 bool ParseDoubleFromOutput(const std::string& line,
280                            const char* expected_label,
281                            double* value) {
282   auto fields = base::SplitString(line, " ", base::TRIM_WHITESPACE,
283                                   base::SPLIT_WANT_NONEMPTY);
284   auto actual_label = std::string(expected_label) + ":";
285   if (fields.size() < 4 || fields[1] != actual_label) {
286     LOG(ERROR) << "Expected line with " << actual_label
287                << " and four or five space-separated fields, got " << line;
288     return false;
289   }
290   if (!base::StringToDouble(fields[3], value)) {
291     LOG(ERROR) << "Expected " << fields[3] << " to be an int";
292     return false;
293   }
294   return true;
295 }
296 
ParseListFromOutput(const std::string & line,const char * expected_label,std::string * value)297 bool ParseListFromOutput(const std::string& line,
298                          const char* expected_label,
299                          std::string* value) {
300   auto fields = base::SplitString(line, " ", base::TRIM_WHITESPACE,
301                                   base::SPLIT_WANT_NONEMPTY);
302   auto actual_label = std::string(expected_label) + ":";
303   if (fields.size() < 4 || fields[1] != actual_label) {
304     LOG(ERROR) << "Expected line with " << actual_label
305                << " and four or five space-separated fields, got " << line;
306     return false;
307   }
308 
309   // Strip out [].
310   std::string values = fields[3];
311   if (values.length() < 2) {
312     LOG(ERROR) << "Malformed values, expected [val1, val2], got " << values;
313     return false;
314   }
315   *value = values.substr(1, values.length() - 2);
316   return true;
317 }
318 
WriteCompareVideosOutputAsHistogram(const std::string & test_label,const std::string & output)319 bool WriteCompareVideosOutputAsHistogram(const std::string& test_label,
320                                          const std::string& output) {
321   perf_test::PerfResultReporter reporter(kMetricPrefixVideoQualityTest,
322                                          test_label);
323 
324   reporter.RegisterFyiMetric(kMetricUniqueFramesCount, "count");
325   reporter.RegisterFyiMetric(kMetricPsnrUnitless, "unitless");
326   reporter.RegisterFyiMetric(kMetricSsimUnitless, "unitless");
327   reporter.RegisterFyiMetric(kMetricMaxRepeatedCount, "count");
328   reporter.RegisterFyiMetric(kMetricMaxSkippedCount, "count");
329   reporter.RegisterFyiMetric(kMetricTotalSkippedCount, "count");
330   reporter.RegisterFyiMetric(kMetricDecodeErrorsReferenceCount, "count");
331   reporter.RegisterFyiMetric(kMetricDecodeErrorsTestCount, "count");
332   auto lines = base::SplitString(output, "\n", base::TRIM_WHITESPACE,
333                                  base::SPLIT_WANT_NONEMPTY);
334 
335   if (lines.size() == 12) {
336     // Remove warning about colorspace conversion.
337     lines.erase(lines.begin(), lines.begin() + 4);
338   }
339 
340   if (lines.size() != 8) {
341     LOG(ERROR) << "Expected 8 lines, got " << lines.size()
342                << " lines. Output:\n\n"
343                << output;
344     return false;
345   }
346 
347   double value;
348   if (!ParseDoubleFromOutput(lines[0], kMetricUniqueFramesCount, &value))
349     return false;
350   reporter.AddResult(kMetricUniqueFramesCount, value);
351   std::string value_list;
352   if (!ParseListFromOutput(lines[1], kMetricPsnrUnitless, &value_list))
353     return false;
354   reporter.AddResultList(kMetricPsnrUnitless, value_list);
355   if (!ParseListFromOutput(lines[2], kMetricSsimUnitless, &value_list))
356     return false;
357   reporter.AddResultList(kMetricSsimUnitless, value_list);
358   if (!ParseDoubleFromOutput(lines[3], kMetricMaxRepeatedCount, &value))
359     return false;
360   reporter.AddResult(kMetricMaxRepeatedCount, value);
361   if (!ParseDoubleFromOutput(lines[4], kMetricMaxSkippedCount, &value))
362     return false;
363   reporter.AddResult(kMetricMaxSkippedCount, value);
364   if (!ParseDoubleFromOutput(lines[5], kMetricTotalSkippedCount, &value))
365     return false;
366   reporter.AddResult(kMetricTotalSkippedCount, value);
367   if (!ParseDoubleFromOutput(lines[6], kMetricDecodeErrorsReferenceCount,
368                              &value))
369     return false;
370   reporter.AddResult(kMetricDecodeErrorsReferenceCount, value);
371   if (!ParseDoubleFromOutput(lines[7], kMetricDecodeErrorsTestCount, &value))
372     return false;
373   reporter.AddResult(kMetricDecodeErrorsTestCount, value);
374   return true;
375 }
376 
377 }  // namespace test
378