chromium/tools/perf/page_sets/webrtc_cases.py

# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from page_sets import press_story
from telemetry import story


class WebrtcPage(press_story.PressStory):

  def __init__(self, url, page_set, name, tags, extra_browser_args=None):
    assert url.startswith('file://webrtc_cases/')
    self.URL = url
    self.NAME = name
    super(WebrtcPage, self).__init__(page_set,
                                     tags=tags,
                                     extra_browser_args=extra_browser_args)


class GetUserMedia(WebrtcPage):
  """Why: Acquires a high definition (720p) local stream."""

  def __init__(self, page_set, tags):
    super(GetUserMedia, self).__init__(
        url='file://webrtc_cases/resolution.html',
        name='hd_local_stream_10s',
        page_set=page_set, tags=tags)

  def ExecuteTest(self, action_runner):
    action_runner.ClickElement('button[id="hd"]')
    action_runner.Wait(10)


class DataChannel(WebrtcPage):
  """Why: Transfer as much data as possible through a data channel in 10s."""

  def __init__(self, page_set, tags):
    super(DataChannel, self).__init__(
        url='file://webrtc_cases/datatransfer.html',
        name='10s_datachannel_transfer',
        page_set=page_set, tags=tags)

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript('megsToSend.value = 100;')
    action_runner.ClickElement('button[id="sendTheData"]')
    action_runner.Wait(10)

  def ParseTestResults(self, action_runner):
    self.AddJavaScriptMeasurement(
        'data_transferred',
        'sizeInBytes_biggerIsBetter',
        'receiveProgress.value',
        description='Amount of data transferred by data channel in 10 seconds')
    self.AddJavaScriptMeasurement(
        'data_throughput',
        'bytesPerSecond_biggerIsBetter',
        'currentThroughput',
        description='Throughput of the data transfer.')


class CanvasCapturePeerConnection(WebrtcPage):
  """Why: Sets up a canvas capture stream connection to a peer connection."""

  def __init__(self, page_set, tags):
    super(CanvasCapturePeerConnection, self).__init__(
        url='file://webrtc_cases/canvas-capture.html',
        name='canvas_capture_peer_connection',
        page_set=page_set, tags=tags)

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction('Action_Canvas_PeerConnection',
                                         repeatable=False):
      action_runner.ClickElement('button[id="startButton"]')
      action_runner.Wait(10)


class VideoCodecConstraints(WebrtcPage):
  """Why: Sets up a video codec to a peer connection."""

  def __init__(self, page_set, video_codec, tags):
    super(VideoCodecConstraints, self).__init__(
        url='file://webrtc_cases/codec_constraints.html',
        name='codec_constraints_%s' % video_codec.lower(),
        page_set=page_set, tags=tags)
    self.video_codec = video_codec

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction('Action_Codec_Constraints',
                                         repeatable=False):
      action_runner.ClickElement('input[id="%s"]' % self.video_codec)
      action_runner.ClickElement('button[id="startButton"]')
      action_runner.WaitForElement('button[id="callButton"]:enabled')
      action_runner.ClickElement('button[id="callButton"]')
      action_runner.Wait(20)
      action_runner.ClickElement('button[id="hangupButton"]')


class MultiplePeerConnections(WebrtcPage):
  """Why: Sets up several peer connections in the same page."""

  def __init__(self, page_set, tags):
    super(MultiplePeerConnections, self).__init__(
        url='file://webrtc_cases/multiple-peerconnections.html',
        name='multiple_peerconnections',
        page_set=page_set, tags=tags)

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction('Action_Create_PeerConnection',
                                         repeatable=False):
      # Set the number of peer connections to create to 10.
      action_runner.ExecuteJavaScript(
          'document.getElementById("num-peerconnections").value=10')
      action_runner.ExecuteJavaScript(
          'document.getElementById("cpuoveruse-detection").checked=false')
      action_runner.ClickElement('button[id="start-test"]')
      action_runner.Wait(20)


class PausePlayPeerConnections(WebrtcPage):
  """Why: Ensures frequent pause and plays of peer connection streams work."""

  def __init__(self, page_set, tags):
    super(PausePlayPeerConnections, self).__init__(
        url='file://webrtc_cases/pause-play.html',
        name='pause_play_peerconnections',
        page_set=page_set, tags=tags)

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript(
        'startTest({test_runtime_s}, {num_peerconnections},'
        '{iteration_delay_ms}, "video");'.format(
            test_runtime_s=20, num_peerconnections=10, iteration_delay_ms=20))
    action_runner.Wait(20)


class InsertableStreamsAudioProcessing(WebrtcPage):
  """Why: processes/transforms audio using insertable streams."""

  def __init__(self, page_set, tags):
    super(InsertableStreamsAudioProcessing, self).__init__(
        url='file://webrtc_cases/audio-processing.html',
        name='insertable_streams_audio_processing',
        page_set=page_set,
        tags=tags,
        extra_browser_args=(
            '--enable-blink-features=WebCodecs,MediaStreamInsertableStreams'))
    self.supported = None

  def RunNavigateSteps(self, action_runner):
    self.supported = action_runner.EvaluateJavaScript('''(function () {
  try {
    new MediaStreamTrackGenerator('audio');
    return true;
  } catch (e) {
    return false;
  }
})()''')
    if self.supported:
      super(InsertableStreamsAudioProcessing,
            self).RunNavigateSteps(action_runner)

  def ExecuteTest(self, action_runner):
    self.AddMeasurement(
        'supported', 'count_biggerIsBetter', 1 if self.supported else 0,
        'Boolean flag indicating if this benchmark is supported by the browser.'
    )
    if not self.supported:
      return
    action_runner.WaitForJavaScriptCondition('!!audio')
    action_runner.ExecuteJavaScript('start()')
    action_runner.Wait(10)


class InsertableStreamsVideoProcessing(WebrtcPage):
  """Why: processes/transforms video in various ways."""

  def __init__(self, page_set, source, transform, sink, tags):
    super(InsertableStreamsVideoProcessing, self).__init__(
        url='file://webrtc_cases/video-processing.html',
        name=('insertable_streams_video_processing_%s_%s_%s' %
              (source, transform, sink)),
        page_set=page_set,
        tags=tags,
        extra_browser_args=(
            '--enable-blink-features=WebCodecs,MediaStreamInsertableStreams'))
    self.source = source
    self.transform = transform
    self.sink = sink
    self.supported = None

  def RunNavigateSteps(self, action_runner):
    self.supported = action_runner.EvaluateJavaScript(
        "typeof MediaStreamTrackProcessor !== 'undefined' &&"
        "typeof MediaStreamTrackGenerator !== 'undefined'")
    if self.supported:
      super(InsertableStreamsVideoProcessing,
            self).RunNavigateSteps(action_runner)

  def ExecuteTest(self, action_runner):
    self.AddMeasurement(
        'supported', 'count_biggerIsBetter', 1 if self.supported else 0,
        'Boolean flag indicating if this benchmark is supported by the browser.'
    )
    if not self.supported:
      return
    with action_runner.CreateInteraction('Start_Pipeline', repeatable=True):
      action_runner.WaitForElement('select[id="sourceSelector"]:enabled')
      action_runner.ExecuteJavaScript(
          'document.getElementById("sourceSelector").value="%s";' % self.source)
      action_runner.WaitForElement('select[id="transformSelector"]:enabled')
      action_runner.ExecuteJavaScript(
          'document.getElementById("transformSelector").value="%s";' %
          self.transform)
      action_runner.WaitForElement('select[id="sinkSelector"]:enabled')
      action_runner.ExecuteJavaScript(
          'document.getElementById("sinkSelector").value="%s";' % self.sink)
      action_runner.ExecuteJavaScript(
          'document.getElementById("sourceSelector").dispatchEvent('
          '  new InputEvent("input", {}));')
      action_runner.WaitForElement('.sinkVideo')
      action_runner.Wait(10)
    self.AddJavaScriptMeasurement(
        'sink_decoded_frames',
        'count_biggerIsBetter',
        'document.querySelector(".sinkVideo").webkitDecodedFrameCount',
        description='Number of frames received at the sink video.')


class NegotiateTiming(WebrtcPage):
  """Why: Measure how long renegotiation takes with large SDP blobs."""

  def __init__(self, page_set, tags):
    super(NegotiateTiming,
          self).__init__(url='file://webrtc_cases/negotiate-timing.html',
                         name='negotiate-timing',
                         page_set=page_set,
                         tags=tags)

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript('start()')
    action_runner.WaitForJavaScriptCondition('!callButton.disabled')
    action_runner.ExecuteJavaScript('call()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    # Due to suspicion of renegotiate activating too early:
    action_runner.Wait(1)
    # Negotiate 50 transceivers, then negotiate back to 1, simulating Meet "pin"
    action_runner.ExecuteJavaScript('videoSectionsField.value = 50')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    action_runner.ExecuteJavaScript('videoSectionsField.value = 1')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    # Negotiate back up to 50, simulating Meet "unpin". This is what gets measured.
    action_runner.ExecuteJavaScript('videoSectionsField.value = 50')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    result = action_runner.EvaluateJavaScript('result')

    self.AddMeasurement('callerSetLocalDescription',
                        'ms',
                        result['callerSetLocalDescription'],
                        description='Time for caller SetLocalDescription')
    self.AddMeasurement('calleeSetLocalDescription',
                        'ms',
                        result['calleeSetLocalDescription'],
                        description='Time for callee SetLocalDescription')
    self.AddMeasurement('callerSetRemoteDescription',
                        'ms',
                        result['callerSetRemoteDescription'],
                        description='Time for caller SetRemoteDescription')
    self.AddMeasurement('calleeSetRemoteDescription',
                        'ms',
                        result['calleeSetRemoteDescription'],
                        description='Time for callee SetRemoteDescription')
    self.AddMeasurement('callerCreateOffer',
                        'ms',
                        result['callerCreateOffer'],
                        description='Time for overall offer/answer handshake')
    self.AddMeasurement('calleeCreateAnswer',
                        'ms',
                        result['calleeCreateAnswer'],
                        description='Time for overall offer/answer handshake')
    self.AddMeasurement('elapsedTime',
                        'ms',
                        result['elapsedTime'],
                        description='Time for overall offer/answer handshake')
    self.AddMeasurement(
        'audioImpairment',
        'count',
        result['audioImpairment'],
        description='Number of late audio samples concealed during negotiation')


class EncodedInsertableStreams(WebrtcPage):
  """Why: Performs encoded insertable streams."""
  def __init__(self, page_set, tags):
    super(EncodedInsertableStreams, self).__init__(
        url='file://webrtc_cases/encoded-insertable-streams.html',
        name='encoded_insertable_streams',
        page_set=page_set,
        tags=tags)

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction('Action_Create_PeerConnection',
                                         repeatable=False):
      # Set the number of peer connections to create to 10.
      action_runner.ExecuteJavaScript(
          'document.getElementById("num-peerconnections").value=10')
      action_runner.ClickElement('button[id="start-test"]')
      action_runner.Wait(20)


class WebrtcPageSet(story.StorySet):
  def __init__(self):
    super(WebrtcPageSet, self).__init__(
        cloud_storage_bucket=story.PUBLIC_BUCKET)

    self.AddStory(PausePlayPeerConnections(self, tags=['pauseplay']))
    self.AddStory(MultiplePeerConnections(self, tags=['stress']))
    self.AddStory(DataChannel(self, tags=['datachannel']))
    self.AddStory(GetUserMedia(self, tags=['getusermedia']))
    self.AddStory(CanvasCapturePeerConnection(self, tags=['smoothness']))
    self.AddStory(VideoCodecConstraints(self, 'H264', tags=['videoConstraints']))
    self.AddStory(VideoCodecConstraints(self, 'VP8', tags=['videoConstraints']))
    self.AddStory(VideoCodecConstraints(self, 'VP9', tags=['videoConstraints']))
    self.AddStory(
        InsertableStreamsAudioProcessing(self, tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'camera',
                                         'webgl',
                                         'video',
                                         tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'video',
                                         'webgl',
                                         'video',
                                         tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'pc',
                                         'webgl',
                                         'video',
                                         tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'camera',
                                         'canvas2d',
                                         'video',
                                         tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'camera',
                                         'noop',
                                         'video',
                                         tags=['insertableStreams']))
    self.AddStory(
        InsertableStreamsVideoProcessing(self,
                                         'camera',
                                         'webgl',
                                         'pc',
                                         tags=['insertableStreams']))
    self.AddStory(NegotiateTiming(self, tags=['sdp']))
    self.AddStory(EncodedInsertableStreams(self, tags=['stress']))