text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <stddef.h>
  #include <stdint.h>
  
  #include <algorithm>
  #include <cmath>
  #include <cstring>
  #include <iterator>
  #include <map>
  #include <memory>
  #include <vector>
  
  #include "base/base64.h"
  #include "base/command_line.h"
  #include "base/files/file_path.h"
  #include "base/files/file_util.h"
  #include "base/strings/strcat.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/stringprintf.h"
  #include "base/task/thread_pool/thread_pool_instance.h"
  #include "base/test/trace_event_analyzer.h"
  #include "base/time/default_tick_clock.h"
  #include "base/values.h"
  #include "build/build_config.h"
  #include "build/chromeos_buildflags.h"
  #include "chrome/browser/media/cast_mirroring_service_host.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/test/base/in_process_browser_test.h"
  #include "chrome/test/base/tracing.h"
  #include "chrome/test/base/ui_test_utils.h"
  #include "components/cast_channel/cast_message_handler.h"
  #include "components/mirroring/mojom/cast_message_channel.mojom.h"
  #include "components/mirroring/mojom/mirroring_service_host.mojom.h"
  #include "components/mirroring/mojom/session_observer.mojom.h"
  #include "components/mirroring/mojom/session_parameters.mojom.h"
  #include "components/sessions/content/session_tab_helper.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/common/content_features.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/test/browser_test.h"
  #include "content/public/test/browser_test_utils.h"
  #include "media/base/audio_bus.h"
  #include "media/base/video_frame.h"
  #include "media/cast/test/skewed_tick_clock.h"
  #include "media/cast/test/utility/audio_utility.h"
  #include "media/cast/test/utility/barcode.h"
  #include "media/cast/test/utility/default_config.h"
  #include "media/cast/test/utility/in_process_receiver.h"
  #include "media/cast/test/utility/net_utility.h"
  #include "media/cast/test/utility/standalone_cast_environment.h"
  #include "media/cast/test/utility/udp_proxy.h"
  #include "mojo/public/cpp/bindings/pending_receiver.h"
  #include "mojo/public/cpp/bindings/pending_remote.h"
  #include "mojo/public/cpp/bindings/receiver.h"
  #include "mojo/public/cpp/bindings/remote.h"
  #include "net/base/ip_address.h"
  #include "net/base/ip_endpoint.h"
  #include "net/base/net_errors.h"
  #include "net/base/rand_callback.h"
  #include "net/dns/mock_host_resolver.h"
  #include "net/log/net_log_source.h"
  #include "net/socket/udp_server_socket.h"
  #include "sandbox/policy/features.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "testing/perf/perf_result_reporter.h"
  #include "third_party/jsoncpp/source/include/json/reader.h"
  #include "third_party/jsoncpp/source/include/json/value.h"
  #include "third_party/jsoncpp/source/include/json/writer.h"
  #include "third_party/openscreen/src/cast/streaming/answer_messages.h"
  #include "third_party/openscreen/src/cast/streaming/offer_messages.h"
  #include "third_party/openscreen/src/cast/streaming/ssrc.h"
  #include "third_party/zlib/google/compression_utils.h"
  #include "ui/gl/gl_switches.h"
  
  namespace {
  
  // Number of events to trim from the beginning and end. These events don't
  // contribute anything toward stable measurements: A brief moment of startup
  // "jank" is acceptable, and shutdown may result in missing events (e.g., if
  // streaming stops a few frames before capture stops).
  constexpr int kTrimEvents = 24;  // 1 sec at 24fps, or 0.4 sec at 60 fps.
  
  // Minimum number of events required for a reasonable analysis.
  constexpr int kMinDataPointsForFullRun = 100;  // 1s of audio, ~5s at 24fps.
  
  // Minimum number of events required for data analysis in a non-performance run.
  constexpr int kMinDataPointsForQuickRun = 3;
  
  // These are how long the browser is run with trace event recording taking
  // place.
  constexpr base::TimeDelta kFullRunObservationPeriod =
      base::TimeDelta::FromSeconds(15);
  constexpr base::TimeDelta kQuickRunObservationPeriod =
      base::TimeDelta::FromSeconds(4);
  
  constexpr char kMetricPrefixCastV2[] = "CastV2.";
  constexpr char kMetricTimeBetweenCapturesMs[] = "time_between_captures";
  constexpr char kMetricAvSyncMs[] = "av_sync";
  constexpr char kMetricAbsAvSyncMs[] = "abs_av_sync";
  constexpr char kMetricAudioJitterMs[] = "audio_jitter";
  constexpr char kMetricVideoJitterMs[] = "video_jitter";
  constexpr char kMetricPlayoutResolutionLines[] = "playout_resolution";
  constexpr char kMetricResolutionChangesCount[] = "resolution_changes";
  constexpr char kMetricFrameDropRatePercent[] = "frame_drop_rate";
  constexpr char kMetricTotalLatencyMs[] = "total_latency";
  constexpr char kMetricCaptureDurationMs[] = "capture_duration";
  constexpr char kMetricSendToRendererMs[] = "send_to_renderer";
  constexpr char kMetricEncodeMs[] = "encode";
  constexpr char kMetricTransmitMs[] = "transmit";
  constexpr char kMetricDecodeMs[] = "decode";
  constexpr char kMetricCastLatencyMs[] = "cast_latency";
  
  constexpr char kTestPageLocation[] =
      "/cast/cast_mirroring_performance_browsertest.html";
  
  constexpr base::StringPiece kFullPerformanceRunSwitch = "full-performance-run";
  
  // The test receiver and senders should share the target playout delay.
  constexpr int kTargetPlayoutDelayMs = 400;  // milliseconds
  
  perf_test::PerfResultReporter SetUpCastV2Reporter(const std::string& story) {
    perf_test::PerfResultReporter reporter(kMetricPrefixCastV2, story);
    reporter.RegisterImportantMetric(kMetricTimeBetweenCapturesMs, "ms");
    reporter.RegisterImportantMetric(kMetricAvSyncMs, "ms");
    reporter.RegisterImportantMetric(kMetricAbsAvSyncMs, "ms");
    reporter.RegisterImportantMetric(kMetricAudioJitterMs, "ms");
    reporter.RegisterImportantMetric(kMetricVideoJitterMs, "ms");
    reporter.RegisterImportantMetric(kMetricPlayoutResolutionLines, "lines");
    reporter.RegisterImportantMetric(kMetricResolutionChangesCount, "count");
    reporter.RegisterImportantMetric(kMetricFrameDropRatePercent, "percent");
    reporter.RegisterImportantMetric(kMetricTotalLatencyMs, "ms");
    reporter.RegisterImportantMetric(kMetricCaptureDurationMs, "ms");
    reporter.RegisterImportantMetric(kMetricSendToRendererMs, "ms");
    reporter.RegisterImportantMetric(kMetricEncodeMs, "ms");
    reporter.RegisterImportantMetric(kMetricTransmitMs, "ms");
    reporter.RegisterImportantMetric(kMetricDecodeMs, "ms");
    reporter.RegisterImportantMetric(kMetricCastLatencyMs, "ms");
    return reporter;
  }
  
  std::string VectorToString(const std::vector<double>& values) {
    CHECK(values.size());
    std::string csv;
    for (const auto& val : values) {
      csv += base::NumberToString(val) + ",";
    }
    // Strip off trailing comma.
    csv.pop_back();
    return csv;
  }
  
  void MaybeAddResultList(const perf_test::PerfResultReporter& reporter,
                          const std::string& metric,
                          const std::vector<double>& values) {
    if (values.size() == 0) {
      LOG(ERROR) << "No events for " << metric;
      return;
    }
    reporter.AddResultList(metric, VectorToString(values));
  }
  
  // A convenience macro to run a gtest expectation in the "full performance run"
  // setting, or else a warning that something is not being entirely tested in the
  // "CQ run" setting. This is required because the test runs in the CQ may not be
  // long enough to collect sufficient tracing data; and, unfortunately, there's
  // nothing we can do about that.
  #define EXPECT_FOR_PERFORMANCE_RUN(expr)           \
    if (!(expr)) {                                   \
      const char* out = #expr;                       \
      if (is_full_performance_run_) {                \
        LOG(ERROR) << "Failure: " << out;            \
      } else {                                       \
        LOG(WARNING) << "Allowing failure: " << out; \
      }                                              \
    }
  
  enum TestFlags {
    kSmallWindow = 1 << 2,  // Window size: 1 = 800x600, 0 = 2000x1000
    kProxyWifi = 1 << 6,    // Run UDP through UDPProxy wifi profile.
    kProxySlow = 1 << 7,    // Run UDP through UDPProxy slow profile.
    kProxyBad = 1 << 8,     // Run UDP through UDPProxy bad profile.
    kSlowClock = 1 << 9,    // Receiver clock is 10 seconds slow.
    kFastClock = 1 << 10,   // Receiver clock is 10 seconds fast.
  };
  
  struct SharedSenderReceiverConfig {
    std::string aes_key;
    std::string aes_iv_mask;
    openscreen::cast::Ssrc receiver_ssrc;
    openscreen::cast::Ssrc sender_ssrc;
  };
  
  struct SharedSenderReceiverConfigs {
    SharedSenderReceiverConfig audio;
    SharedSenderReceiverConfig video;
  };
  
  media::cast::FrameReceiverConfig WithSharedConfig(
      const media::cast::FrameReceiverConfig& config,
      const SharedSenderReceiverConfig& shared) {
    media::cast::FrameReceiverConfig result = config;
    result.aes_key = shared.aes_key;
    result.aes_iv_mask = shared.aes_iv_mask;
    result.receiver_ssrc = shared.receiver_ssrc;
    result.sender_ssrc = shared.sender_ssrc;
    result.rtp_max_delay_ms = kTargetPlayoutDelayMs;
    return result;
  }
  
  void ContinueBrowserFor(base::TimeDelta duration) {
    base::RunLoop run_loop;
    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), duration);
    run_loop.Run();
  }
  
  using TraceAnalyzerUniquePtr = std::unique_ptr<trace_analyzer::TraceAnalyzer>;
  
  void QueryTraceEvents(trace_analyzer::TraceAnalyzer* analyzer,
                        base::StringPiece event_name,
                        trace_analyzer::TraceEventVector* events) {
    const trace_analyzer::Query kQuery =
        trace_analyzer::Query::EventNameIs(event_name.as_string()) &&
        (trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN) ||
         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) ||
         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_FLOW_BEGIN) ||
         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_INSTANT) ||
         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_COMPLETE));
    analyzer->FindEvents(kQuery, events);
  }
  
  std::string MakeBase64EncodedGZippedString(const std::string& input) {
    std::string gzipped_input;
    compression::GzipCompress(input, &gzipped_input);
    std::string result;
    base::Base64Encode(gzipped_input, &result);
  
    // Break up the string with newlines to make it easier to handle in the
    // console logs.
    constexpr size_t kMaxLineWidth = 80;
    std::string formatted_result;
    formatted_result.reserve(result.size() + 1 + (result.size() / kMaxLineWidth));
    for (std::string::size_type src_pos = 0; src_pos < result.size();
         src_pos += kMaxLineWidth) {
      formatted_result.append(result, src_pos, kMaxLineWidth);
      formatted_result.append(1, '\n');
    }
    return formatted_result;
  }
  
  TraceAnalyzerUniquePtr TraceAndObserve(
      bool is_full_performance_run,
      const std::string& category_patterns,
      const std::vector<base::StringPiece>& event_names,
      int required_event_count) {
    const base::TimeDelta observation_period = is_full_performance_run
                                                   ? kFullRunObservationPeriod
                                                   : kQuickRunObservationPeriod;
  
    VLOG(1) << "Starting tracing...";
    {
      // Wait until all child processes have ACK'ed that they are now tracing.
      base::trace_event::TraceConfig trace_config(
          category_patterns, base::trace_event::RECORD_CONTINUOUSLY);
      base::RunLoop run_loop;
      const bool did_begin_tracing = tracing::BeginTracingWithTraceConfig(
          trace_config, run_loop.QuitClosure());
      CHECK(did_begin_tracing);
      run_loop.Run();
    }
  
    VLOG(1) << "Running browser for " << observation_period.InSecondsF()
            << " sec...";
    ContinueBrowserFor(observation_period);
  
    VLOG(1) << "Observation period has completed. Ending tracing...";
    std::string json_events;
    const bool success = tracing::EndTracing(&json_events);
    CHECK(success);
  
    std::unique_ptr<trace_analyzer::TraceAnalyzer> result(
        trace_analyzer::TraceAnalyzer::Create(json_events));
    result->AssociateAsyncBeginEndEvents();
    bool have_enough_events = true;
    for (const auto& event_name : event_names) {
      trace_analyzer::TraceEventVector events;
      QueryTraceEvents(result.get(), event_name, &events);
      VLOG(1) << "Collected " << events.size() << " events ("
              << required_event_count << " required) for: " << event_name;
      if (static_cast<int>(events.size()) < required_event_count) {
        have_enough_events = false;
      }
    }
    LOG_IF(WARNING, !have_enough_events) << "Insufficient data collected.";
  
    VLOG_IF(2, result) << "Dump of trace events (trace_events.json.gz.b64):\n"
                       << MakeBase64EncodedGZippedString(json_events);
    return result;
  }
  
  class SkewedCastEnvironment : public media::cast::StandaloneCastEnvironment {
   public:
    explicit SkewedCastEnvironment(const base::TimeDelta& delta)
        : StandaloneCastEnvironment(),
          skewed_clock_(base::DefaultTickClock::GetInstance()) {
      // If testing with a receiver clock that is ahead or behind the sender
      // clock, fake a clock that is offset and also ticks at a rate of 50 parts
      // per million faster or slower than the local sender's clock. This is the
      // worst-case scenario for skew in-the-wild.
      if (!delta.is_zero()) {
        const double skew = delta < base::TimeDelta() ? 0.999950 : 1.000050;
        skewed_clock_.SetSkew(skew, delta);
      }
      clock_ = &skewed_clock_;
    }
  
   protected:
    ~SkewedCastEnvironment() override {}
  
   private:
    media::cast::test::SkewedTickClock skewed_clock_;
  };
  
  // We log one of these for each call to OnAudioFrame/OnVideoFrame.
  struct TimeData {
    TimeData(uint16_t frame_no_, base::TimeTicks playout_time_)
        : frame_no(frame_no_), playout_time(playout_time_) {}
    // The unit here is video frames, for audio data there can be duplicates.
    // This was decoded from the actual audio/video data.
    uint16_t frame_no;
    // This is when we should play this data, according to the sender.
    base::TimeTicks playout_time;
  };
  
  // TODO(hubbe): Move to media/cast to use for offline log analysis.
  class MeanAndError {
   public:
    explicit MeanAndError(const std::vector<double>& values) {
      double sum = 0.0;
      double sqr_sum = 0.0;
      num_values_ = values.size();
      if (num_values_ > 0) {
        for (size_t i = 0; i < num_values_; i++) {
          sum += values[i];
          sqr_sum += values[i] * values[i];
        }
        mean_ = sum / num_values_;
        std_dev_ =
            sqrt(std::max(0.0, num_values_ * sqr_sum - sum * sum)) / num_values_;
      } else {
        mean_ = NAN;
        std_dev_ = NAN;
      }
    }
  
    void SetMeanAsAbsoluteValue() { mean_ = std::abs(mean_); }
  
    std::string AsString() const {
      return base::StringPrintf("%f,%f", mean_, std_dev_);
    }
  
   private:
    size_t num_values_;
    double mean_;
    double std_dev_;
  };
  
  // This function checks how smooth the data in |data| is.
  // It computes the average error of deltas and the average delta.
  // If data[x] == x * A + B, then this function returns zero.
  // The unit is milliseconds.
  static std::vector<double> AnalyzeJitter(const std::vector<TimeData>& data) {
    VLOG(0) << "Jitter analysis on " << data.size() << " values.";
    std::vector<double> deltas;
    double sum = 0.0;
    for (size_t i = 1; i < data.size(); i++) {
      double delta =
          (data[i].playout_time - data[i - 1].playout_time).InMillisecondsF();
      deltas.push_back(delta);
      sum += delta;
    }
    if (deltas.empty()) {
      // Not enough data. Don't do the following calculation, to avoid a
      // divide-by-zero.
    } else {
      double mean = sum / deltas.size();
      for (size_t i = 0; i < deltas.size(); i++) {
        deltas[i] = fabs(mean - deltas[i]);
      }
    }
  
    return deltas;
  }
  
  // An in-process Cast receiver that examines the audio/video frames being
  // received and logs some data about each received audio/video frame.
  class TestPatternReceiver : public media::cast::InProcessReceiver {
   public:
    explicit TestPatternReceiver(
        const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
        const net::IPEndPoint& local_end_point,
        bool is_full_performance_run,
        const SharedSenderReceiverConfigs& configs)
        : InProcessReceiver(
              cast_environment,
              local_end_point,
              net::IPEndPoint(),
              WithSharedConfig(media::cast::GetDefaultAudioReceiverConfig(),
                               configs.audio),
              WithSharedConfig(media::cast::GetDefaultVideoReceiverConfig(),
                               configs.video)),
          is_full_performance_run_(is_full_performance_run) {}
  
    typedef std::map<uint16_t, base::TimeTicks> TimeMap;
  
    // Build a map from frame ID (as encoded in the audio and video data)
    // to the rtp timestamp for that frame. Note that there will be multiple
    // audio frames which all have the same frame ID. When that happens we
    // want the minimum rtp timestamp, because that audio frame is supposed
    // to play at the same time that the corresponding image is presented.
    void MapFrameTimes(const std::vector<TimeData>& events, TimeMap* map) {
      const int trim_count = is_full_performance_run_ ? kTrimEvents : 0;
      for (int i = trim_count; i < static_cast<int>(events.size()) - trim_count;
           i++) {
        base::TimeTicks& frame_tick = (*map)[events[i].frame_no];
        if (frame_tick.is_null()) {
          frame_tick = events[i].playout_time;
        } else {
          frame_tick = std::min(events[i].playout_time, frame_tick);
        }
      }
    }
  
    void Analyze(const std::string& story) {
      // First, find the minimum rtp timestamp for each audio and video frame.
      // Note that the data encoded in the audio stream contains video frame
      // numbers. So in a 30-fps video stream, there will be 1/30s of "1", then
      // 1/30s of "2", etc.
      TimeMap audio_frame_times, video_frame_times;
      MapFrameTimes(audio_events_, &audio_frame_times);
      const int min_data_points = is_full_performance_run_
                                      ? kMinDataPointsForFullRun
                                      : kMinDataPointsForQuickRun;
      EXPECT_FOR_PERFORMANCE_RUN(min_data_points <=
                                 static_cast<int>(audio_frame_times.size()));
      MapFrameTimes(video_events_, &video_frame_times);
  
      EXPECT_FOR_PERFORMANCE_RUN(min_data_points <=
                                 static_cast<int>(video_frame_times.size()));
      std::vector<double> deltas;
      for (TimeMap::const_iterator i = audio_frame_times.begin();
           i != audio_frame_times.end(); ++i) {
        TimeMap::const_iterator j = video_frame_times.find(i->first);
        if (j != video_frame_times.end()) {
          deltas.push_back((i->second - j->second).InMillisecondsF());
        }
      }
  
      EXPECT_FOR_PERFORMANCE_RUN(min_data_points <=
                                 static_cast<int>(deltas.size()));
  
      auto reporter = SetUpCastV2Reporter(story);
      MaybeAddResultList(reporter, kMetricAvSyncMs, deltas);
      // Close to zero is better (av_sync can be negative).
      if (deltas.size()) {
        MeanAndError av_sync(deltas);
        av_sync.SetMeanAsAbsoluteValue();
        reporter.AddResultMeanAndError(kMetricAbsAvSyncMs, av_sync.AsString());
      }
      // lower is better.
      MaybeAddResultList(reporter, kMetricAudioJitterMs,
                         AnalyzeJitter(audio_events_));
      // lower is better.
      MaybeAddResultList(reporter, kMetricVideoJitterMs,
                         AnalyzeJitter(video_events_));
  
      // Mean resolution of video at receiver. Lower stddev is better, while the
      // mean should be something reasonable given the network constraints
      // (usually 480 lines or more). Note that this is the video resolution at
      // the receiver, but changes originate on the sender side.
      std::vector<double> slice_for_analysis;
      const int trim_count = is_full_performance_run_ ? kTrimEvents : 0;
      if (static_cast<int>(video_frame_lines_.size()) > trim_count * 2) {
        slice_for_analysis.reserve(video_frame_lines_.size() - trim_count * 2);
        EXPECT_FOR_PERFORMANCE_RUN(
            min_data_points <= static_cast<int>(slice_for_analysis.capacity()));
        std::transform(video_frame_lines_.begin() + trim_count,
                       video_frame_lines_.end() - trim_count,
                       std::back_inserter(slice_for_analysis),
                       [](int lines) { return static_cast<double>(lines); });
      }
      MaybeAddResultList(reporter, kMetricPlayoutResolutionLines,
                         slice_for_analysis);
  
      // Number of resolution changes. Lower is better (and 1 is ideal). Zero
      // indicates a lack of data.
      int last_lines = -1;
      int change_count = 0;
      for (int i = trim_count;
           i < static_cast<int>(video_frame_lines_.size()) - trim_count; ++i) {
        if (video_frame_lines_[i] != last_lines) {
          ++change_count;
          last_lines = video_frame_lines_[i];
        }
      }
      EXPECT_FOR_PERFORMANCE_RUN(change_count > 0);
      reporter.AddResult(kMetricResolutionChangesCount,
                         static_cast<size_t>(change_count));
    }
  
   private:
    // Invoked by InProcessReceiver for each received audio frame.
    void OnAudioFrame(std::unique_ptr<media::AudioBus> audio_frame,
                      base::TimeTicks playout_time,
                      bool is_continuous) override {
      CHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
  
      if (audio_frame->frames() <= 0) {
        NOTREACHED() << "OnAudioFrame called with no samples?!?";
        return;
      }
  
      TRACE_EVENT_INSTANT1("cast_perf_test", "AudioFramePlayout",
                           TRACE_EVENT_SCOPE_THREAD, "playout_time",
                           (playout_time - base::TimeTicks()).InMicroseconds());
  
      // Note: This is the number of the video frame that this audio belongs to.
      uint16_t frame_no;
      if (media::cast::DecodeTimestamp(audio_frame->channel(0),
                                       audio_frame->frames(), &frame_no)) {
        audio_events_.push_back(TimeData(frame_no, playout_time));
      } else {
        DVLOG(2) << "Failed to decode audio timestamp!";
      }
    }
  
    void OnVideoFrame(scoped_refptr<media::VideoFrame> video_frame,
                      base::TimeTicks playout_time,
                      bool is_continuous) override {
      CHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
  
      TRACE_EVENT_INSTANT1("cast_perf_test", "VideoFramePlayout",
                           TRACE_EVENT_SCOPE_THREAD, "playout_time",
                           (playout_time - base::TimeTicks()).InMicroseconds());
  
      uint16_t frame_no;
      if (media::cast::test::DecodeBarcode(*video_frame, &frame_no)) {
        video_events_.push_back(TimeData(frame_no, playout_time));
      } else {
        DVLOG(2) << "Failed to decode barcode!";
      }
  
      video_frame_lines_.push_back(video_frame->visible_rect().height());
    }
  
    const bool is_full_performance_run_;
  
    std::vector<TimeData> audio_events_;
    std::vector<TimeData> video_events_;
  
    // The height (number of lines) of each video frame received.
    std::vector<int> video_frame_lines_;
  
    DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
  };
  
  class TestCompleteObserver {
   public:
    MOCK_METHOD(void, TestComplete, ());
  };
  
  class CastV2PerformanceTest : public InProcessBrowserTest,
                                public testing::WithParamInterface<int> {
   public:
    CastV2PerformanceTest() = default;
    ~CastV2PerformanceTest() override = default;
  
    bool HasFlag(TestFlags flag) const { return (GetParam() & flag) == flag; }
  
    std::string GetSuffixForTestFlags() const {
      std::string suffix;
      // Note: Add "_gpu" tag for backwards-compatibility with existing
      // Performance Dashboard timeseries data.
      suffix += "gpu";
      if (HasFlag(kSmallWindow))
        suffix += "_small";
      if (HasFlag(kProxyWifi))
        suffix += "_wifi";
      if (HasFlag(kProxySlow))
        suffix += "_slowwifi";
      if (HasFlag(kProxyBad))
        suffix += "_bad";
      if (HasFlag(kSlowClock))
        suffix += "_slow";
      if (HasFlag(kFastClock))
        suffix += "_fast";
      return suffix;
    }
  
    void SetUp() override {
      // Necessary for screen capture.
      EnablePixelOutput();
  
      feature_list_.InitWithFeatures(
          {
              features::kAudioServiceSandbox,
              features::kAudioServiceLaunchOnStartup,
              features::kAudioServiceOutOfProcess,
          },
          {});
      InProcessBrowserTest::SetUp();
    }
  
    void SetUpOnMainThread() override {
      InProcessBrowserTest::SetUpOnMainThread();
  
      best_effort_fence_.emplace();
      host_resolver()->AddRule("*", "127.0.0.1");
      https_server_ = std::make_unique<net::EmbeddedTestServer>(
          net::EmbeddedTestServer::TYPE_HTTPS);
      https_server_->AddDefaultHandlers(GetChromeTestDataDir());
      ASSERT_TRUE(https_server_->Start());
    }
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      is_full_performance_run_ =
          command_line->HasSwitch(kFullPerformanceRunSwitch);
  
      if (HasFlag(kSmallWindow)) {
        command_line->AppendSwitchASCII(switches::kWindowSize, "800,600");
      } else {
        command_line->AppendSwitchASCII(switches::kWindowSize, "2000,1500");
      }
  
      InProcessBrowserTest::SetUpCommandLine(command_line);
    }
  
    // The key contains the name of the argument and the argument.
    typedef std::pair<std::string, double> EventMapKey;
    typedef std::map<EventMapKey, const trace_analyzer::TraceEvent*> EventMap;
  
    // Make events findable by their arguments, for instance, if an
    // event has a "timestamp": 238724 argument, the map will contain
    // pair<"timestamp", 238724> -> &event.  All arguments are indexed.
    void IndexEvents(trace_analyzer::TraceAnalyzer* analyzer,
                     const std::string& event_name,
                     EventMap* event_map) {
      trace_analyzer::TraceEventVector events;
      QueryTraceEvents(analyzer, event_name, &events);
      for (size_t i = 0; i < events.size(); i++) {
        std::map<std::string, double>::const_iterator j;
        for (j = events[i]->arg_numbers.begin();
             j != events[i]->arg_numbers.end(); ++j) {
          (*event_map)[*j] = events[i];
        }
      }
    }
  
    // Look up an event in |event_map|. The return event will have the same
    // value for the argument |key_name| as |prev_event|.
    const trace_analyzer::TraceEvent* FindNextEvent(
        const EventMap& event_map,
        const trace_analyzer::TraceEvent* prev_event,
        std::string key_name) {
      const auto arg_it = prev_event->arg_numbers.find(key_name);
      if (arg_it == prev_event->arg_numbers.end())
        return nullptr;
      const EventMapKey& key = *arg_it;
      const auto event_it = event_map.find(key);
      if (event_it == event_map.end())
        return nullptr;
      return event_it->second;
    }
  
    void NavigateToTestPagePath(const std::string& path) const {
      ui_test_utils::NavigateToURL(browser(), https_server_->GetURL(path));
    }
  
    // Given a vector of vector of data, extract the difference between
    // two columns (|col_a| and |col_b|) and output the result as a
    // performance metric.
    void OutputMeasurement(const std::vector<std::vector<double>>& data,
                           const std::string& metric,
                           int col_a,
                           int col_b) {
      std::vector<double> tmp;
      for (size_t i = 0; i < data.size(); i++) {
        tmp.push_back((data[i][col_b] - data[i][col_a]) / 1000.0);
      }
      auto reporter = SetUpCastV2Reporter(GetSuffixForTestFlags());
      MaybeAddResultList(reporter, metric, tmp);
    }
  
    // Analyze the latency of each frame as it goes from capture to playout. The
    // event tracing system is used to track the frames.
    void AnalyzeLatency(trace_analyzer::TraceAnalyzer* analyzer) {
      // Retrieve and index all "checkpoint" events related to frames progressing
      // from start to finish.
      trace_analyzer::TraceEventVector capture_events;
      // Sender side:
      QueryTraceEvents(analyzer, "Capture", &capture_events);
      EventMap onbuffer, sink, inserted, encoded, transmitted, decoded, done;
      IndexEvents(analyzer, "OnBufferReceived", &onbuffer);
      IndexEvents(analyzer, "ConsumeVideoFrame", &sink);
      IndexEvents(analyzer, "InsertRawVideoFrame", &inserted);
      IndexEvents(analyzer, "VideoFrameEncoded", &encoded);
      // Receiver side:
      IndexEvents(analyzer, "PullEncodedVideoFrame", &transmitted);
      IndexEvents(analyzer, "VideoFrameDecoded", &decoded);
      IndexEvents(analyzer, "VideoFramePlayout", &done);
  
      // Analyzing latency is non-trivial, because only the frame timestamps
      // uniquely identify frames AND the timestamps take varying forms throughout
      // the pipeline (TimeTicks, TimeDelta, RtpTimestamp, etc.). Luckily, each
      // neighboring stage in the pipeline knows about the timestamp from the
      // prior stage, in whatever form it had, and so it's possible to track
      // specific frames all the way from capture until playout at the receiver.
      std::vector<std::pair<EventMap*, std::string>> event_maps;
      event_maps.push_back(std::make_pair(&onbuffer, "time_delta"));
      event_maps.push_back(std::make_pair(&sink, "time_delta"));
      event_maps.push_back(std::make_pair(&inserted, "timestamp"));
      event_maps.push_back(std::make_pair(&encoded, "rtp_timestamp"));
      event_maps.push_back(std::make_pair(&transmitted, "rtp_timestamp"));
      event_maps.push_back(std::make_pair(&decoded, "rtp_timestamp"));
      event_maps.push_back(std::make_pair(&done, "playout_time"));
  
      // For each "begin capture" event, search for all the events following it,
      // producing a matrix of when each frame reached each pipeline checkpoint.
      // See the "cheat sheet" below for a description of each pipeline
      // checkpoint.
      const int trim_count = is_full_performance_run_ ? kTrimEvents : 0;
      EXPECT_FOR_PERFORMANCE_RUN((trim_count * 2) <=
                                 static_cast<int>(capture_events.size()));
      std::vector<std::vector<double>> traced_frames;
      for (int i = trim_count;
           i < static_cast<int>(capture_events.size()) - trim_count; i++) {
        std::vector<double> times;
        const trace_analyzer::TraceEvent* event = capture_events[i];
        if (!event->other_event)
          continue;  // Begin capture event without a corresponding end event.
        times.push_back(event->timestamp);  // begin capture
        event = event->other_event;
        times.push_back(event->timestamp);  // end capture
        const trace_analyzer::TraceEvent* prev_event = event;
        for (size_t j = 0; j < event_maps.size(); j++) {
          event = FindNextEvent(*event_maps[j].first, prev_event,
                                event_maps[j].second);
          if (!event)
            break;  // Missing an event: The frame was dropped along the way.
          prev_event = event;
          times.push_back(event->timestamp);
        }
        if (event) {
          // Successfully traced frame from beginning to end.
          traced_frames.push_back(std::move(times));
        }
      }
  
      // Report the fraction of captured frames that were dropped somewhere along
      // the way (i.e., before playout at the receiver).
      const int capture_event_count = capture_events.size() - 2 * trim_count;
      EXPECT_FOR_PERFORMANCE_RUN((is_full_performance_run_
                                      ? kMinDataPointsForFullRun
                                      : kMinDataPointsForQuickRun) <=
                                 capture_event_count);
      const double success_percent =
          (capture_event_count == 0)
              ? NAN
              : (100.0 * traced_frames.size() / capture_event_count);
      auto reporter = SetUpCastV2Reporter(GetSuffixForTestFlags());
      reporter.AddResult(kMetricFrameDropRatePercent, 100 - success_percent);
  
      // Report the latency between various pairs of checkpoints in the pipeline.
      // Lower latency is better for all of these measurements.
      //
      // Cheat sheet:
      //   0 = Sender: capture begin
      //   1 = Sender: capture end
      //   2 = Sender: memory buffer reached the render process
      //   3 = Sender: frame routed to Cast RTP consumer
      //   4 = Sender: frame reached VideoSender::InsertRawVideoFrame()
      //   5 = Sender: frame encoding complete, queueing for transmission
      //   6 = Receiver: frame fully received from network
      //   7 = Receiver: frame decoded
      //   8 = Receiver: frame played out
      OutputMeasurement(traced_frames, kMetricTotalLatencyMs, 0, 8);
      OutputMeasurement(traced_frames, kMetricCaptureDurationMs, 0, 1);
      OutputMeasurement(traced_frames, kMetricSendToRendererMs, 1, 3);
      OutputMeasurement(traced_frames, kMetricEncodeMs, 3, 5);
      OutputMeasurement(traced_frames, kMetricTransmitMs, 5, 6);
      OutputMeasurement(traced_frames, kMetricDecodeMs, 6, 7);
      OutputMeasurement(traced_frames, kMetricCastLatencyMs, 3, 8);
    }
  
    std::vector<double> AnalyzeTraceDistance(
        trace_analyzer::TraceAnalyzer* analyzer,
        const std::string& event_name) {
      trace_analyzer::TraceEventVector events;
      QueryTraceEvents(analyzer, event_name, &events);
  
      const int trim_count = is_full_performance_run_ ? kTrimEvents : 0;
      std::vector<double> deltas;
      for (int i = trim_count + 1;
           i < static_cast<int>(events.size()) - trim_count; ++i) {
        double delta_micros = events[i]->timestamp - events[i - 1]->timestamp;
        deltas.push_back(delta_micros / 1000.0);
      }
      return deltas;
    }
  
    void StartReceiver(const net::IPEndPoint& endpoint,
                       const SharedSenderReceiverConfigs& shared_configs) {
      // Start the in-process receiver that examines audio/video for the expected
      // test patterns.
      base::TimeDelta delta = base::TimeDelta::FromSeconds(0);
      if (HasFlag(kFastClock)) {
        delta = base::TimeDelta::FromSeconds(10);
      }
      if (HasFlag(kSlowClock)) {
        delta = base::TimeDelta::FromSeconds(-10);
      }
  
      cast_environment_ = base::MakeRefCounted<SkewedCastEnvironment>(delta);
      receiver_ = std::make_unique<TestPatternReceiver>(
          cast_environment_, endpoint, is_full_performance_run_, shared_configs);
      receiver_->Start();
    }
  
   protected:
    // Ensure best effort tasks are not required for this test to pass.
    base::Optional<base::ThreadPoolInstance::ScopedBestEffortExecutionFence>
        best_effort_fence_;
  
    // HTTPS server for loading pages from the test data dir.
    std::unique_ptr<net::EmbeddedTestServer> https_server_;
  
    // Cast environment used for the Receiver.
    scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment_;
  
    // Test receiver.
    std::unique_ptr<TestPatternReceiver> receiver_;
  
    // Whether this is a full performance run, or if we just want to do a sanity
    // check.
    bool is_full_performance_run_ = true;
  
    // Manages the audio service feature set.
    base::test::ScopedFeatureList feature_list_;
  };
  
  class TestTabMirroringSession : public mirroring::mojom::SessionObserver,
                                  public mirroring::mojom::CastMessageChannel {
   public:
    explicit TestTabMirroringSession(CastV2PerformanceTest* parent)
        : parent_(parent) {}
  
    // Start the mirroring session. Port is specified as well as endpoint, in
    // case the sender needs to connect to a proxy.
    void Start(content::WebContents* contents,
               const net::IPEndPoint& endpoint,
               int port) {
      ASSERT_TRUE(contents);
  
      receiver_endpoint_ = endpoint;
      udp_port_ = port;
      mirroring::CastMirroringServiceHost::GetForTab(
          contents, host_.BindNewPipeAndPassReceiver());
  
      mojo::PendingRemote<mirroring::mojom::SessionObserver> observer_remote;
      observer_receiver_.Bind(observer_remote.InitWithNewPipeAndPassReceiver());
      mojo::PendingRemote<mirroring::mojom::CastMessageChannel> channel_remote;
      channel_receiver_.Bind(channel_remote.InitWithNewPipeAndPassReceiver());
  
      const std::string receiver_model_name{};
      auto session_params = mirroring::mojom::SessionParameters::New(
          mirroring::mojom::SessionType::AUDIO_AND_VIDEO, endpoint.address(),
          receiver_model_name,
          base::TimeDelta::FromMilliseconds(kTargetPlayoutDelayMs));
  
      host_->Start(std::move(session_params), std::move(observer_remote),
                   std::move(channel_remote),
                   channel_to_service_.BindNewPipeAndPassReceiver());
    }
  
    // SessionObserver implementation
    void OnError(mirroring::mojom::SessionError error) override {
      FAIL() << "Encountered session error: " << error;
    }
  
    void DidStart() override {}
    void DidStop() override {}
    void LogInfoMessage(const std::string& message) override {}
    void LogErrorMessage(const std::string& message) override {}
  
    // CastMessageChannel implementation
    void Send(mirroring::mojom::CastMessagePtr message) override {
      Json::CharReaderBuilder rb;
      auto reader = std::unique_ptr<Json::CharReader>(rb.newCharReader());
      Json::Value root;
  
      const std::string& d = message->json_format_data;
      ASSERT_TRUE(reader->parse(d.data(), d.data() + d.length(), &root, nullptr));
  
      // Currently we only handle OFFER messages.
      if (root["type"].asString() != "OFFER") {
        return;
      }
  
      auto streams = GetStreamsFromOffer(root);
      SetSharedConfigs(streams);
      SendAnswer(root, std::move(message), streams);
      // The shared configs have been set as part of building the answer,
      // so we start the receiver now before sending the ANSWER message.
      parent_->StartReceiver(receiver_endpoint_, shared_configs_);
    }
  
    const net::IPEndPoint& receiver_endpoint() const {
      return receiver_endpoint_;
    }
  
   private:
    std::string ToString(const std::array<uint8_t, 16>& key) {
      return std::string(reinterpret_cast<const char*>(key.data()), key.size());
    }
  
    // Returns AUDIO + VIDEO
    std::pair<openscreen::cast::Stream, openscreen::cast::Stream>
    GetStreamsFromOffer(const Json::Value& offer_message_body) {
      auto offer = openscreen::cast::Offer::Parse(offer_message_body["offer"]);
      EXPECT_TRUE(offer);
      EXPECT_LT(0u, offer.value().audio_streams.size());
      EXPECT_LT(0u, offer.value().video_streams.size());
      return std::make_pair(offer.value().audio_streams[0].stream,
                            offer.value().video_streams[0].stream);
    }
  
    void SetSharedConfigs(const std::pair<openscreen::cast::Stream,
                                          openscreen::cast::Stream>& streams) {
      const auto& audio = streams.first;
      const auto& video = streams.second;
      shared_configs_.audio.aes_key = ToString(audio.aes_key);
      shared_configs_.audio.aes_iv_mask = ToString(audio.aes_iv_mask);
      shared_configs_.audio.sender_ssrc = audio.ssrc;
      shared_configs_.audio.receiver_ssrc = audio.ssrc + 1;
      shared_configs_.video.aes_key = ToString(video.aes_key);
      shared_configs_.video.aes_iv_mask = ToString(video.aes_iv_mask);
      shared_configs_.video.sender_ssrc = video.ssrc;
      shared_configs_.video.receiver_ssrc = video.ssrc + 1;
    }
  
    void SendAnswer(const Json::Value& offer_message_body,
                    mirroring::mojom::CastMessagePtr offer_message,
                    const std::pair<openscreen::cast::Stream,
                                    openscreen::cast::Stream>& streams) {
      openscreen::cast::Answer answer;
      answer.udp_port = udp_port_;
  
      answer.ssrcs.push_back(streams.first.ssrc + 1);
      answer.send_indexes.push_back(streams.first.index);
      answer.ssrcs.push_back(streams.second.ssrc + 1);
      answer.send_indexes.push_back(streams.second.index);
  
      ASSERT_TRUE(answer.IsValid());
  
      Json::StreamWriterBuilder wb;
      wb["indentation"] = "";
      std::unique_ptr<Json::StreamWriter> writer(wb.newStreamWriter());
  
      Json::Value answer_message_body;
      answer_message_body["type"] = "ANSWER";
      answer_message_body["result"] = "ok";
      answer_message_body["answer"] = answer.ToJson();
      answer_message_body["seqNum"] = offer_message_body["seqNum"];
      std::ostringstream ssb;
      writer->write(answer_message_body, &ssb);
  
      VLOG(1) << "Sending ANSWER";
      offer_message->json_format_data = ssb.str();
      channel_to_service_->Send(std::move(offer_message));
    }
  
    mojo::Remote<mirroring::mojom::MirroringServiceHost> host_;
    mojo::Remote<mirroring::mojom::CastMessageChannel> channel_to_service_;
    mojo::Receiver<mirroring::mojom::SessionObserver> observer_receiver_{this};
    mojo::Receiver<mirroring::mojom::CastMessageChannel> channel_receiver_{this};
  
    net::IPEndPoint receiver_endpoint_;
    int udp_port_ = -1;
    SharedSenderReceiverConfigs shared_configs_;
  
    CastV2PerformanceTest* const parent_;
    scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment_;
  };
  }  // namespace
  
  IN_PROC_BROWSER_TEST_P(CastV2PerformanceTest, Performance) {
    net::IPEndPoint receiver_end_point = media::cast::test::GetFreeLocalPort();
    VLOG(1) << "Got local UDP endpoint for testing: "
            << receiver_end_point.ToString();
  
    // Create a proxy for the UDP packets that simulates certain network
    // environments.
    std::unique_ptr<media::cast::test::UDPProxy> udp_proxy;
    int udp_proxy_port = receiver_end_point.port();
    if (HasFlag(kProxyWifi) || HasFlag(kProxySlow) || HasFlag(kProxyBad)) {
      net::IPEndPoint proxy_end_point = media::cast::test::GetFreeLocalPort();
      if (HasFlag(kProxyWifi)) {
        udp_proxy = media::cast::test::UDPProxy::Create(
            proxy_end_point, receiver_end_point, media::cast::test::WifiNetwork(),
            media::cast::test::WifiNetwork(), nullptr);
      } else if (HasFlag(kProxySlow)) {
        udp_proxy = media::cast::test::UDPProxy::Create(
            proxy_end_point, receiver_end_point, media::cast::test::SlowNetwork(),
            media::cast::test::SlowNetwork(), nullptr);
      } else if (HasFlag(kProxyBad)) {
        udp_proxy = media::cast::test::UDPProxy::Create(
            proxy_end_point, receiver_end_point, media::cast::test::BadNetwork(),
            media::cast::test::BadNetwork(), nullptr);
      }
  
      VLOG(1) << "Setting UDP proxy port: " << udp_proxy_port;
      udp_proxy_port = proxy_end_point.port();
    }
  
    NavigateToTestPagePath(kTestPageLocation);
  
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
  
    TestTabMirroringSession session(this);
    session.Start(web_contents, receiver_end_point, udp_proxy_port);
  
    // Now that capture has started, start playing the barcode video in the test
    // page.
    content::SimulateMouseClick(web_contents, 0,
                                blink::WebMouseEvent::Button::kLeft);
  
    // Observe the running browser for a while, collecting a trace.
    TraceAnalyzerUniquePtr analyzer = TraceAndObserve(
        is_full_performance_run_, "gpu.capture,cast_perf_test",
        std::vector<base::StringPiece>{
            // From the Compositor/Capture pipeline...
            "Capture", "OnBufferReceived", "ConsumeVideoFrame",
            // From the Cast Sender's pipeline...
            "InsertRawVideoFrame", "VideoFrameEncoded",
            // From the Cast Receiver's pipeline...
            "PullEncodedVideoFrame", "VideoFrameDecoded",
            // From the TestPatternReceiver (see class above!)...
            "VideoFramePlayout", "AudioFramePlayout"},
        // In a full performance run, events will be trimmed from both ends of
        // trace. Otherwise, just require the bare-minimum to verify the stats
        // calculations will work.
        is_full_performance_run_ ? (2 * kTrimEvents + kMinDataPointsForFullRun)
                                 : kMinDataPointsForQuickRun);
  
    // Shut down the receiver and all the CastEnvironment threads.
    VLOG(1) << "Shutting-down receiver and CastEnvironment...";
    receiver_->Stop();
    cast_environment_->Shutdown();
  
    VLOG(1) << "Analyzing trace events...";
    // This prints out the average time between capture events.
    // Depending on the test, the capture frame rate is capped (e.g., at 30fps,
    // this score cannot get any better than 33.33 ms). However, the measurement
    // is important since it provides a valuable check that capture can keep up
    // with the content's framerate.
    // Lower is better.
    auto reporter = SetUpCastV2Reporter(GetSuffixForTestFlags());
    MaybeAddResultList(reporter, kMetricTimeBetweenCapturesMs,
                       AnalyzeTraceDistance(analyzer.get(), "Capture"));
  
    receiver_->Analyze(GetSuffixForTestFlags());
  
    AnalyzeLatency(analyzer.get());
  }
  
  #if !BUILDFLAG(IS_CHROMEOS_ASH) || !defined(MEMORY_SANITIZER)
  // TODO(b/161545049): reenable FPS value features.
  INSTANTIATE_TEST_SUITE_P(All,
                           CastV2PerformanceTest,
                           testing::Values(kSmallWindow,
                                           kProxyWifi,
                                           kProxyBad,
                                           kSlowClock,
                                           kFastClock,
                                           kProxySlow));
  #endif
  