Reland "Adds more performance stats collection to scenario tests."

This is a reland of 63b0b2cf307b47bae3c10b295ece9a5f6d9bd7a4

Original change's description:
> Adds more performance stats collection to scenario tests.
> 
> Bug: webrtc:10365
> Change-Id: I66dce6ff21242c30af674f89fc9fd19172d4a3af
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/131948
> Commit-Queue: Sebastian Jansson <srte@webrtc.org>
> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#27585}

Bug: webrtc:10365
Change-Id: Id7ddb64ac17ecbb4de223dec497bc562040ba7c4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/132711
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27618}
diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn
index eba344b..1918a2b 100644
--- a/test/scenario/BUILD.gn
+++ b/test/scenario/BUILD.gn
@@ -59,6 +59,7 @@
       "hardware_codecs.h",
       "network_node.cc",
       "network_node.h",
+      "performance_stats.cc",
       "performance_stats.h",
       "scenario.cc",
       "scenario.h",
diff --git a/test/scenario/performance_stats.cc b/test/scenario/performance_stats.cc
new file mode 100644
index 0000000..3ff66cd
--- /dev/null
+++ b/test/scenario/performance_stats.cc
@@ -0,0 +1,193 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "test/scenario/performance_stats.h"
+
+#include <algorithm>
+
+namespace webrtc {
+namespace test {
+void EventRateCounter::AddEvent(Timestamp event_time) {
+  if (first_time_.IsInfinite()) {
+    first_time_ = event_time;
+  } else {
+    RTC_DCHECK(event_time >= last_time_);
+    interval_.AddSample(event_time - last_time_);
+  }
+  last_time_ = event_time;
+  event_count_++;
+}
+
+void EventRateCounter::AddEvents(EventRateCounter other) {
+  first_time_ = std::min(first_time_, other.first_time_);
+  last_time_ = std::max(last_time_, other.last_time_);
+  event_count_ += other.event_count_;
+  interval_.AddSamples(other.interval_);
+}
+
+bool EventRateCounter::IsEmpty() const {
+  return first_time_ == last_time_;
+}
+
+double EventRateCounter::Rate() const {
+  if (event_count_ == 0)
+    return 0;
+  if (event_count_ == 1)
+    return NAN;
+  return (event_count_ - 1) / (last_time_ - first_time_).seconds<double>();
+}
+
+double SampleStats<double>::Max() {
+  if (IsEmpty())
+    return INFINITY;
+  return GetMax();
+}
+
+double SampleStats<double>::Mean() {
+  if (IsEmpty())
+    return 0;
+  return GetAverage();
+}
+
+double SampleStats<double>::Median() {
+  return Quantile(0.5);
+}
+
+double SampleStats<double>::Quantile(double quantile) {
+  if (IsEmpty())
+    return 0;
+  return GetPercentile(quantile);
+}
+
+double SampleStats<double>::Min() {
+  if (IsEmpty())
+    return -INFINITY;
+  return GetMin();
+}
+
+double SampleStats<double>::Variance() {
+  if (IsEmpty())
+    return 0;
+  return GetVariance();
+}
+
+double SampleStats<double>::StandardDeviation() {
+  return sqrt(Variance());
+}
+
+void SampleStats<TimeDelta>::AddSample(TimeDelta delta) {
+  RTC_DCHECK(delta.IsFinite());
+  stats_.AddSample(delta.seconds<double>());
+}
+
+void SampleStats<TimeDelta>::AddSampleMs(double delta_ms) {
+  AddSample(TimeDelta::ms(delta_ms));
+}
+void SampleStats<TimeDelta>::AddSamples(const SampleStats<TimeDelta>& other) {
+  stats_.AddSamples(other.stats_);
+}
+
+TimeDelta SampleStats<TimeDelta>::Max() {
+  return TimeDelta::seconds(stats_.Max());
+}
+
+TimeDelta SampleStats<TimeDelta>::Mean() {
+  return TimeDelta::seconds(stats_.Mean());
+}
+
+TimeDelta SampleStats<TimeDelta>::Median() {
+  return Quantile(0.5);
+}
+
+TimeDelta SampleStats<TimeDelta>::Quantile(double quantile) {
+  return TimeDelta::seconds(stats_.Quantile(quantile));
+}
+
+TimeDelta SampleStats<TimeDelta>::Min() {
+  return TimeDelta::seconds(stats_.Min());
+}
+
+TimeDelta SampleStats<TimeDelta>::Variance() {
+  return TimeDelta::seconds(stats_.Variance());
+}
+
+TimeDelta SampleStats<TimeDelta>::StandardDeviation() {
+  return TimeDelta::seconds(stats_.StandardDeviation());
+}
+
+void SampleStats<DataRate>::AddSample(DataRate sample) {
+  stats_.AddSample(sample.bps<double>());
+}
+
+void SampleStats<DataRate>::AddSampleBps(double rate_bps) {
+  stats_.AddSample(rate_bps);
+}
+
+void SampleStats<DataRate>::AddSamples(const SampleStats<DataRate>& other) {
+  stats_.AddSamples(other.stats_);
+}
+
+DataRate SampleStats<DataRate>::Max() {
+  return DataRate::bps(stats_.Max());
+}
+
+DataRate SampleStats<DataRate>::Mean() {
+  return DataRate::bps(stats_.Mean());
+}
+
+DataRate SampleStats<DataRate>::Median() {
+  return Quantile(0.5);
+}
+
+DataRate SampleStats<DataRate>::Quantile(double quantile) {
+  return DataRate::bps(stats_.Quantile(quantile));
+}
+
+DataRate SampleStats<DataRate>::Min() {
+  return DataRate::bps(stats_.Min());
+}
+
+DataRate SampleStats<DataRate>::Variance() {
+  return DataRate::bps(stats_.Variance());
+}
+
+DataRate SampleStats<DataRate>::StandardDeviation() {
+  return DataRate::bps(stats_.StandardDeviation());
+}
+
+void VideoFramesStats::AddFrameInfo(const VideoFrameBuffer& frame,
+                                    Timestamp at_time) {
+  ++count;
+  RTC_DCHECK(at_time.IsFinite());
+  pixels.AddSample(frame.width() * frame.height());
+  resolution.AddSample(std::max(frame.width(), frame.height()));
+  frames.AddEvent(at_time);
+}
+
+void VideoFramesStats::AddStats(const VideoFramesStats& other) {
+  count += other.count;
+  pixels.AddSamples(other.pixels);
+  resolution.AddSamples(other.resolution);
+  frames.AddEvents(other.frames);
+}
+
+void VideoQualityStats::AddStats(const VideoQualityStats& other) {
+  capture.AddStats(other.capture);
+  render.AddStats(other.render);
+  lost_count += other.lost_count;
+  freeze_count += other.freeze_count;
+  end_to_end_delay.AddSamples(other.end_to_end_delay);
+  psnr.AddSamples(other.psnr);
+  skipped_between_rendered.AddSamples(other.skipped_between_rendered);
+  freeze_duration.AddSamples(other.freeze_duration);
+  time_between_freezes.AddSamples(other.time_between_freezes);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/scenario/performance_stats.h b/test/scenario/performance_stats.h
index b00c62c..1979876 100644
--- a/test/scenario/performance_stats.h
+++ b/test/scenario/performance_stats.h
@@ -14,7 +14,7 @@
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
 #include "api/video/video_frame_buffer.h"
-#include "rtc_base/numerics/running_statistics.h"
+#include "rtc_base/numerics/samples_stats_counter.h"
 
 namespace webrtc {
 namespace test {
@@ -35,13 +35,125 @@
   int repeated = 0;
 };
 
+template <typename T>
+class SampleStats;
+
+template <>
+class SampleStats<double> : public SamplesStatsCounter {
+ public:
+  double Max();
+  double Mean();
+  double Median();
+  double Quantile(double quantile);
+  double Min();
+  double Variance();
+  double StandardDeviation();
+};
+
+template <>
+class SampleStats<TimeDelta> {
+ public:
+  void AddSample(TimeDelta delta);
+  void AddSampleMs(double delta_ms);
+  void AddSamples(const SampleStats<TimeDelta>& other);
+  TimeDelta Max();
+  TimeDelta Mean();
+  TimeDelta Median();
+  TimeDelta Quantile(double quantile);
+  TimeDelta Min();
+  TimeDelta Variance();
+  TimeDelta StandardDeviation();
+
+ private:
+  SampleStats<double> stats_;
+};
+
+template <>
+class SampleStats<DataRate> {
+ public:
+  void AddSample(DataRate rate);
+  void AddSampleBps(double rate_bps);
+  void AddSamples(const SampleStats<DataRate>& other);
+  DataRate Max();
+  DataRate Mean();
+  DataRate Median();
+  DataRate Quantile(double quantile);
+  DataRate Min();
+  DataRate Variance();
+  DataRate StandardDeviation();
+
+ private:
+  SampleStats<double> stats_;
+};
+
+class EventRateCounter {
+ public:
+  void AddEvent(Timestamp event_time);
+  void AddEvents(EventRateCounter other);
+  bool IsEmpty() const;
+  double Rate() const;
+  SampleStats<TimeDelta>& interval() { return interval_; }
+
+ private:
+  Timestamp first_time_ = Timestamp::PlusInfinity();
+  Timestamp last_time_ = Timestamp::MinusInfinity();
+  int64_t event_count_ = 0;
+  SampleStats<TimeDelta> interval_;
+};
+
+struct VideoFramesStats {
+  int count = 0;
+  SampleStats<double> pixels;
+  SampleStats<double> resolution;
+  EventRateCounter frames;
+  void AddFrameInfo(const VideoFrameBuffer& frame, Timestamp at_time);
+  void AddStats(const VideoFramesStats& other);
+};
+
 struct VideoQualityStats {
-  int captures_count = 0;
-  int valid_count = 0;
   int lost_count = 0;
-  RunningStatistics<double> end_to_end_seconds;
-  RunningStatistics<int> frame_size;
-  RunningStatistics<double> psnr;
+  int freeze_count = 0;
+  VideoFramesStats capture;
+  VideoFramesStats render;
+  // Time from frame was captured on device to time frame was displayed on
+  // device.
+  SampleStats<TimeDelta> end_to_end_delay;
+  SampleStats<double> psnr;
+  // Frames skipped between two nearest.
+  SampleStats<double> skipped_between_rendered;
+  // In the next 2 metrics freeze is a pause that is longer, than maximum:
+  //  1. 150ms
+  //  2. 3 * average time between two sequential frames.
+  // Item 1 will cover high fps video and is a duration, that is noticeable by
+  // human eye. Item 2 will cover low fps video like screen sharing.
+  SampleStats<TimeDelta> freeze_duration;
+  // Mean time between one freeze end and next freeze start.
+  SampleStats<TimeDelta> time_between_freezes;
+  void AddStats(const VideoQualityStats& other);
+};
+
+struct CollectedCallStats {
+  SampleStats<DataRate> target_rate;
+  SampleStats<double> memory_usage;
+};
+
+struct CollectedAudioReceiveStats {
+  SampleStats<double> expand_rate;
+  SampleStats<double> accelerate_rate;
+  SampleStats<TimeDelta> jitter_buffer;
+};
+struct CollectedVideoSendStats {
+  SampleStats<double> encode_frame_rate;
+  SampleStats<TimeDelta> encode_time;
+  SampleStats<double> encode_usage;
+  SampleStats<DataRate> media_bitrate;
+  SampleStats<DataRate> fec_bitrate;
+};
+struct CollectedVideoReceiveStats {
+  SampleStats<TimeDelta> decode_time;
+  SampleStats<TimeDelta> decode_time_max;
+  SampleStats<double> decode_pixels;
+  SampleStats<double> resolution;
 };
 
 }  // namespace test
diff --git a/test/scenario/scenario_unittest.cc b/test/scenario/scenario_unittest.cc
index aa65a12..c0f8508 100644
--- a/test/scenario/scenario_unittest.cc
+++ b/test/scenario/scenario_unittest.cc
@@ -102,8 +102,7 @@
   }
   // Regression tests based on previous runs.
   EXPECT_EQ(analyzer.stats().lost_count, 0);
-  ASSERT_TRUE(analyzer.stats().psnr.GetMean());
-  EXPECT_NEAR(*analyzer.stats().psnr.GetMean(), 38, 2);
+  EXPECT_NEAR(analyzer.stats().psnr.Mean(), 38, 2);
 }
 
 // TODO(bugs.webrtc.org/10515): Remove this when performance has been improved.
@@ -123,8 +122,7 @@
   }
   // Regression tests based on previous runs.
   EXPECT_LT(analyzer.stats().lost_count, 2);
-  ASSERT_TRUE(analyzer.stats().psnr.GetMean());
-  EXPECT_NEAR(*analyzer.stats().psnr.GetMean(), 38, 10);
+  EXPECT_NEAR(analyzer.stats().psnr.Mean(), 38, 10);
 }
 
 TEST(ScenarioTest, SimTimeFakeing) {
diff --git a/test/scenario/stats_collection.cc b/test/scenario/stats_collection.cc
index 77b2ae0..104c824 100644
--- a/test/scenario/stats_collection.cc
+++ b/test/scenario/stats_collection.cc
@@ -9,10 +9,8 @@
  */
 
 #include "test/scenario/stats_collection.h"
-
-#include <utility>
-
 #include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/memory_usage.h"
 
 namespace webrtc {
 namespace test {
@@ -39,20 +37,47 @@
 }
 
 void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample) {
+  layer_analyzers_[sample.layer_id].HandleFramePair(sample, writer_.get());
+  cached_.reset();
+}
+
+std::vector<VideoQualityStats> VideoQualityAnalyzer::layer_stats() const {
+  std::vector<VideoQualityStats> res;
+  for (auto& layer : layer_analyzers_)
+    res.push_back(layer.second.stats_);
+  return res;
+}
+
+VideoQualityStats& VideoQualityAnalyzer::stats() {
+  if (!cached_) {
+    cached_ = VideoQualityStats();
+    for (auto& layer : layer_analyzers_)
+      cached_->AddStats(layer.second.stats_);
+  }
+  return *cached_;
+}
+
+void VideoLayerAnalyzer::HandleFramePair(VideoFramePair sample,
+                                         RtcEventLogOutput* writer) {
   double psnr = NAN;
   RTC_CHECK(sample.captured);
-  ++stats_.captures_count;
+  HandleCapturedFrame(sample);
   if (!sample.decoded) {
     ++stats_.lost_count;
+    ++skip_count_;
   } else {
     psnr = I420PSNR(*sample.captured->ToI420(), *sample.decoded->ToI420());
-    ++stats_.valid_count;
-    stats_.end_to_end_seconds.AddSample(
-        (sample.render_time - sample.capture_time).seconds<double>());
+    stats_.end_to_end_delay.AddSample(sample.render_time - sample.capture_time);
     stats_.psnr.AddSample(psnr);
+    if (sample.repeated) {
+      ++stats_.freeze_count;
+      ++skip_count_;
+    } else {
+      HandleRenderedFrame(sample);
+    }
   }
-  if (writer_) {
-    LogWriteFormat(writer_.get(), "%.3f %.3f %.3f %i %i %i %i %.3f\n",
+  if (writer) {
+    LogWriteFormat(writer, "%.3f %.3f %.3f %i %i %i %i %.3f\n",
                    sample.capture_time.seconds<double>(),
                    sample.render_time.seconds<double>(),
                    sample.captured->width(), sample.captured->height(),
@@ -60,9 +85,78 @@
   }
 }
 
-VideoQualityStats VideoQualityAnalyzer::stats() const {
-  return stats_;
+void VideoLayerAnalyzer::HandleCapturedFrame(const VideoFramePair& sample) {
+  stats_.capture.AddFrameInfo(*sample.captured, sample.capture_time);
+  if (last_freeze_time_.IsInfinite())
+    last_freeze_time_ = sample.capture_time;
 }
 
+void VideoLayerAnalyzer::HandleRenderedFrame(const VideoFramePair& sample) {
+  stats_.render.AddFrameInfo(*sample.decoded, sample.render_time);
+  stats_.skipped_between_rendered.AddSample(skip_count_);
+  skip_count_ = 0;
+
+  if (last_render_time_.IsFinite()) {
+    RTC_DCHECK(sample.render_time.IsFinite());
+    TimeDelta render_interval = sample.render_time - last_render_time_;
+    TimeDelta mean_interval = stats_.render.frames.interval().Mean();
+    if (render_interval > TimeDelta::ms(150) + mean_interval ||
+        render_interval > 3 * mean_interval) {
+      stats_.freeze_duration.AddSample(render_interval);
+      stats_.time_between_freezes.AddSample(last_render_time_ -
+                                            last_freeze_time_);
+      last_freeze_time_ = sample.render_time;
+    }
+  }
+  last_render_time_ = sample.render_time;
+}
+
+void CallStatsCollector::AddStats(Call::Stats sample) {
+  stats_.target_rate.AddSampleBps(sample.send_bandwidth_bps);
+  stats_.memory_usage.AddSample(rtc::GetProcessResidentSizeBytes());
+}
+
+void AudioReceiveStatsCollector::AddStats(AudioReceiveStream::Stats sample) {
+  stats_.expand_rate.AddSample(sample.expand_rate);
+  stats_.accelerate_rate.AddSample(sample.accelerate_rate);
+  stats_.jitter_buffer.AddSampleMs(sample.jitter_buffer_ms);
+}
+
+void VideoSendStatsCollector::AddStats(VideoSendStream::Stats sample,
+                                       Timestamp at_time) {
+  // It's not certain that we yet have estimates for any of these stats.
+  // Check that they are positive before mixing them in.
+  if (sample.encode_frame_rate <= 0)
+    return;
+
+  stats_.encode_frame_rate.AddSample(sample.encode_frame_rate);
+  stats_.encode_time.AddSampleMs(sample.avg_encode_time_ms);
+  stats_.encode_usage.AddSample(sample.encode_usage_percent / 100.0);
+  stats_.media_bitrate.AddSampleBps(sample.media_bitrate_bps);
+
+  size_t fec_bytes = 0;
+  for (const auto& kv : sample.substreams) {
+    fec_bytes += kv.second.rtp_stats.fec.payload_bytes +
+                 kv.second.rtp_stats.fec.padding_bytes;
+  }
+  if (last_update_.IsFinite()) {
+    auto fec_delta = DataSize::bytes(fec_bytes - last_fec_bytes_);
+    auto time_delta = at_time - last_update_;
+    stats_.fec_bitrate.AddSample(fec_delta / time_delta);
+  }
+  last_fec_bytes_ = fec_bytes;
+  last_update_ = at_time;
+}
+
+void VideoReceiveStatsCollector::AddStats(VideoReceiveStream::Stats sample) {
+  if (sample.decode_ms > 0)
+    stats_.decode_time.AddSampleMs(sample.decode_ms);
+  if (sample.max_decode_ms > 0)
+    stats_.decode_time_max.AddSampleMs(sample.max_decode_ms);
+  if (sample.width > 0 && sample.height > 0) {
+    stats_.decode_pixels.AddSample(sample.width * sample.height);
+    stats_.resolution.AddSample(sample.height);
+  }
+}
 }  // namespace test
 }  // namespace webrtc
diff --git a/test/scenario/stats_collection.h b/test/scenario/stats_collection.h
index d1b46e4..0b8b4a3 100644
--- a/test/scenario/stats_collection.h
+++ b/test/scenario/stats_collection.h
@@ -10,8 +10,11 @@
 #ifndef TEST_SCENARIO_STATS_COLLECTION_H_
 #define TEST_SCENARIO_STATS_COLLECTION_H_
 
+#include <map>
 #include <memory>
 
+#include "absl/types/optional.h"
+#include "call/call.h"
 #include "test/logging/log_writer.h"
 #include "test/scenario/performance_stats.h"
 
@@ -22,6 +25,18 @@
   double psnr_coverage = 1;
 };
 
+class VideoLayerAnalyzer {
+ public:
+  void HandleCapturedFrame(const VideoFramePair& sample);
+  void HandleRenderedFrame(const VideoFramePair& sample);
+  void HandleFramePair(VideoFramePair sample, RtcEventLogOutput* writer);
+  VideoQualityStats stats_;
+  Timestamp last_capture_time_ = Timestamp::MinusInfinity();
+  Timestamp last_render_time_ = Timestamp::MinusInfinity();
+  Timestamp last_freeze_time_ = Timestamp::MinusInfinity();
+  int skip_count_ = 0;
+};
+
 class VideoQualityAnalyzer {
  public:
   explicit VideoQualityAnalyzer(
@@ -29,15 +44,59 @@
       std::unique_ptr<RtcEventLogOutput> writer = nullptr);
   ~VideoQualityAnalyzer();
   void HandleFramePair(VideoFramePair sample);
-  VideoQualityStats stats() const;
+  std::vector<VideoQualityStats> layer_stats() const;
+  VideoQualityStats& stats();
   void PrintHeaders();
   void PrintFrameInfo(const VideoFramePair& sample);
   std::function<void(const VideoFramePair&)> Handler();
 
  private:
   const VideoQualityAnalyzerConfig config_;
-  VideoQualityStats stats_;
+  std::map<int, VideoLayerAnalyzer> layer_analyzers_;
   const std::unique_ptr<RtcEventLogOutput> writer_;
+  absl::optional<VideoQualityStats> cached_;
+};
+
+class CallStatsCollector {
+ public:
+  void AddStats(Call::Stats sample);
+  CollectedCallStats& stats() { return stats_; }
+
+ private:
+  CollectedCallStats stats_;
+};
+class AudioReceiveStatsCollector {
+ public:
+  void AddStats(AudioReceiveStream::Stats sample);
+  CollectedAudioReceiveStats& stats() { return stats_; }
+
+ private:
+  CollectedAudioReceiveStats stats_;
+};
+class VideoSendStatsCollector {
+ public:
+  void AddStats(VideoSendStream::Stats sample, Timestamp at_time);
+  CollectedVideoSendStats& stats() { return stats_; }
+
+ private:
+  CollectedVideoSendStats stats_;
+  Timestamp last_update_ = Timestamp::MinusInfinity();
+  size_t last_fec_bytes_ = 0;
+};
+class VideoReceiveStatsCollector {
+ public:
+  void AddStats(VideoReceiveStream::Stats sample);
+  CollectedVideoReceiveStats& stats() { return stats_; }
+
+ private:
+  CollectedVideoReceiveStats stats_;
+};
+
+struct CallStatsCollectors {
+  CallStatsCollector call;
+  AudioReceiveStatsCollector audio_receive;
+  VideoSendStatsCollector video_send;
+  VideoReceiveStatsCollector video_receive;
 };
 
 }  // namespace test
diff --git a/test/scenario/stats_collection_unittest.cc b/test/scenario/stats_collection_unittest.cc
index 7e65ec1..48a4bc3 100644
--- a/test/scenario/stats_collection_unittest.cc
+++ b/test/scenario/stats_collection_unittest.cc
@@ -16,49 +16,71 @@
 namespace {
 void CreateAnalyzedStream(Scenario* s,
                           NetworkNodeConfig network_config,
-                          VideoQualityAnalyzer* analyzer) {
+                          VideoQualityAnalyzer* analyzer,
+                          CallStatsCollectors* collectors) {
   VideoStreamConfig config;
   config.encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8;
   config.encoder.implementation =
       VideoStreamConfig::Encoder::Implementation::kSoftware;
   config.hooks.frame_pair_handlers = {analyzer->Handler()};
-  auto route = s->CreateRoutes(s->CreateClient("caller", CallClientConfig()),
-                               {s->CreateSimulationNode(network_config)},
-                               s->CreateClient("callee", CallClientConfig()),
-                               {s->CreateSimulationNode(NetworkNodeConfig())});
-  s->CreateVideoStream(route->forward(), config);
+  auto* caller = s->CreateClient("caller", CallClientConfig());
+  auto route =
+      s->CreateRoutes(caller, {s->CreateSimulationNode(network_config)},
+                      s->CreateClient("callee", CallClientConfig()),
+                      {s->CreateSimulationNode(NetworkNodeConfig())});
+  auto* video = s->CreateVideoStream(route->forward(), config);
+  auto* audio = s->CreateAudioStream(route->forward(), AudioStreamConfig());
+  if (collectors) {
+    s->Every(TimeDelta::seconds(1), [=] {
+      collectors->call.AddStats(caller->GetStats());
+      collectors->audio_receive.AddStats(audio->receive()->GetStats());
+      collectors->video_send.AddStats(video->send()->GetStats(), s->Now());
+      collectors->video_receive.AddStats(video->receive()->GetStats());
+    });
+  }
 }
 }  // namespace
 
 TEST(ScenarioAnalyzerTest, PsnrIsHighWhenNetworkIsGood) {
   VideoQualityAnalyzer analyzer;
+  CallStatsCollectors stats;
   {
-    Scenario s("", /*real_time*/ false);
+    Scenario s;
     NetworkNodeConfig good_network;
     good_network.simulation.bandwidth = DataRate::kbps(1000);
-    CreateAnalyzedStream(&s, good_network, &analyzer);
-    s.RunFor(TimeDelta::seconds(1));
+    CreateAnalyzedStream(&s, good_network, &analyzer, &stats);
+    s.RunFor(TimeDelta::seconds(3));
   }
-  // This is mainty a regression test, the target is based on previous runs and
-  // might change due to changes in configuration and encoder etc.
-  ASSERT_TRUE(analyzer.stats().psnr.GetMean());
-  EXPECT_GT(*analyzer.stats().psnr.GetMean(), 40);
+  // This is a change detecting test, the targets are based on previous runs and
+  // might change due to changes in configuration and encoder etc. The main
+  // purpose is to show how the stats can be used. To avoid being overly
+  // sensistive to change, the ranges are chosen to be quite large.
+  EXPECT_NEAR(analyzer.stats().psnr.Mean(), 43, 10);
+  EXPECT_NEAR(stats.call.stats().target_rate.Mean().kbps(), 700, 300);
+  EXPECT_NEAR(stats.video_send.stats().media_bitrate.Mean().kbps(), 500, 200);
+  EXPECT_NEAR(stats.video_receive.stats().resolution.Mean(), 180, 10);
+  EXPECT_NEAR(stats.audio_receive.stats().jitter_buffer.Mean().ms(), 40, 20);
 }
 
 TEST(ScenarioAnalyzerTest, PsnrIsLowWhenNetworkIsBad) {
   VideoQualityAnalyzer analyzer;
+  CallStatsCollectors stats;
   {
-    Scenario s("", /*real_time*/ false);
+    Scenario s;
     NetworkNodeConfig bad_network;
     bad_network.simulation.bandwidth = DataRate::kbps(100);
     bad_network.simulation.loss_rate = 0.02;
-    CreateAnalyzedStream(&s, bad_network, &analyzer);
-    s.RunFor(TimeDelta::seconds(1));
+    CreateAnalyzedStream(&s, bad_network, &analyzer, &stats);
+    s.RunFor(TimeDelta::seconds(3));
   }
-  // This is mainty a regression test, the target is based on previous runs and
+  // This is a change detecting test, the targets are based on previous runs and
   // might change due to changes in configuration and encoder etc.
-  ASSERT_TRUE(analyzer.stats().psnr.GetMean());
-  EXPECT_LT(*analyzer.stats().psnr.GetMean(), 30);
+  EXPECT_NEAR(analyzer.stats().psnr.Mean(), 16, 10);
+  EXPECT_NEAR(stats.call.stats().target_rate.Mean().kbps(), 75, 50);
+  EXPECT_NEAR(stats.video_send.stats().media_bitrate.Mean().kbps(), 100, 50);
+  EXPECT_NEAR(stats.video_receive.stats().resolution.Mean(), 180, 10);
+  EXPECT_NEAR(stats.audio_receive.stats().jitter_buffer.Mean().ms(), 45, 20);
 }
+
 }  // namespace test
 }  // namespace webrtc