Home | History | Annotate | Download | only in performance_InboxInputLatency
      1 # Copyright 2017 The Chromium OS 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 import logging
      6 import numpy
      7 import os
      8 import tempfile
      9 import time
     10 
     11 from autotest_lib.client.bin import test
     12 from autotest_lib.client.bin.input import input_device
     13 from autotest_lib.client.bin.input.input_event_player import InputEventPlayer
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib import file_utils
     16 from autotest_lib.client.common_lib.cros import chrome
     17 from telemetry.timeline import model as model_module
     18 from telemetry.timeline import tracing_config
     19 
     20 
     21 _COMPOSE_BUTTON_CLASS = 'y hC'
     22 _DISCARD_BUTTON_CLASS = 'ezvJwb bG AK ew IB'
     23 _IDLE_FOR_INBOX_STABLIZED = 30
     24 _INBOX_URL = 'https://inbox.google.com'
     25 _KEYIN_TEST_DATA = 'input_test_data'
     26 # In order to have focus switched from url bar to body text area of the compose
     27 # frame, 18 times of tab switch are performed accodingly which includes one
     28 # hidden iframe then the whole inbox frame, 8 items in title bar of inbox frame,
     29 # and 'Compose' button, 3 items of top-level compose frame:close, maximize and
     30 # minimize, then 'To' text area, To's drop-down,'Subject' text area, and the
     31 # final 'Body' text area.
     32 _NUMBER_OF_TABS_TO_COMPOSER_FRAME = 18
     33 _PRESS_TAB_KEY = 'key_event_tab'
     34 _SCRIPT_TIMEOUT = 5
     35 _TARGET_EVENT = 'InputLatency::Char'
     36 _TARGET_TRACING_CATEGORIES = 'input, latencyInfo'
     37 _TRACING_TIMEOUT = 60
     38 
     39 
     40 class performance_InboxInputLatency(test.test):
     41     """Invoke Inbox composer, inject key events then measure latency."""
     42     version = 1
     43 
     44     def initialize(self):
     45         # Create a virtual keyboard device for key event playback.
     46         device_node = input_device.get_device_node(input_device.KEYBOARD_TYPES)
     47         if not device_node:
     48             raise error.TestFail('Could not find keyboard device node')
     49         self.keyboard = input_device.InputDevice(device_node)
     50 
     51         # Instantiate Chrome browser.
     52         with tempfile.NamedTemporaryFile() as cap:
     53             file_utils.download_file(chrome.CAP_URL, cap.name)
     54             password = cap.read().rstrip()
     55 
     56         self.browser = chrome.Chrome(gaia_login=True,
     57                                      username=chrome.CAP_USERNAME,
     58                                      password=password)
     59         self.tab = self.browser.browser.tabs[0]
     60 
     61         # Setup Chrome Tracing.
     62         config = tracing_config.TracingConfig()
     63         category_filter = config.chrome_trace_config.category_filter
     64         category_filter.AddFilterString(_TARGET_TRACING_CATEGORIES)
     65         config.enable_chrome_trace = True
     66         self.target_tracing_config = config
     67 
     68     def cleanup(self):
     69         if self.browser:
     70             self.browser.close()
     71 
     72     def inject_key_events(self, event_file):
     73         """
     74         Replay key events from file.
     75 
     76         @param event_file: the file with key events recorded.
     77 
     78         """
     79         current_dir = os.path.dirname(__file__)
     80         event_file_path = os.path.join(current_dir, event_file)
     81         InputEventPlayer().playback(self.keyboard, event_file_path)
     82 
     83     def click_button_by_class_name(self, class_name):
     84         """
     85         Locate a button by its class name and click it.
     86 
     87         @param class_name: the class name of the button.
     88 
     89         """
     90         button_query = 'document.getElementsByClassName("' + class_name +'")'
     91         # Make sure the target button is available
     92         self.tab.WaitForJavaScriptCondition(button_query + '.length == 1',
     93                                             timeout=_SCRIPT_TIMEOUT)
     94         # Perform click action
     95         self.tab.ExecuteJavaScript(button_query + '[0].click();')
     96 
     97     def setup_inbox_composer(self):
     98         """Navigate to Inbox, and click the compose button."""
     99         self.tab.Navigate(_INBOX_URL)
    100         tracing_controller = self.tab.browser.platform.tracing_controller
    101         if not tracing_controller.IsChromeTracingSupported():
    102             raise Exception('Chrome tracing not supported')
    103 
    104         # Idle for making inbox tab stablized, i.e. not busy in syncing with
    105         # backend inbox server.
    106         time.sleep(_IDLE_FOR_INBOX_STABLIZED)
    107 
    108         # Pressing tabs to jump into composer frame as a workaround.
    109         self.click_button_by_class_name(_COMPOSE_BUTTON_CLASS)
    110         for _ in range(_NUMBER_OF_TABS_TO_COMPOSER_FRAME):
    111             self.inject_key_events(_PRESS_TAB_KEY)
    112 
    113     def teardown_inbox_composer(self):
    114         """Discards the draft mail."""
    115         self.click_button_by_class_name(_DISCARD_BUTTON_CLASS)
    116 
    117     def measure_input_latency(self):
    118         """Injects key events then measure and report the latency."""
    119         tracing_controller = self.tab.browser.platform.tracing_controller
    120         tracing_controller.StartTracing(self.target_tracing_config,
    121                                         timeout=_TRACING_TIMEOUT)
    122         # Inject pre-recorded test key events
    123         self.inject_key_events(_KEYIN_TEST_DATA)
    124         results = tracing_controller.StopTracing()
    125 
    126         # Iterate recorded events and output target latency events
    127         timeline_model = model_module.TimelineModel(results)
    128         event_iter = timeline_model.IterAllEvents(
    129                 event_type_predicate=model_module.IsSliceOrAsyncSlice)
    130 
    131         # Extract and report the latency information
    132         latency_data = []
    133         previous_start = 0.0
    134         for event in event_iter:
    135             if event.name == _TARGET_EVENT and event.start != previous_start:
    136                 logging.info('input char latency = %f ms', event.duration)
    137                 latency_data.append(event.duration)
    138                 previous_start = event.start
    139         operators = ['mean', 'std', 'max', 'min']
    140         for operator in operators:
    141             description = 'input_char_latency_' + operator
    142             value = getattr(numpy, operator)(latency_data)
    143             logging.info('%s = %f', description, value)
    144             self.output_perf_value(description=description,
    145                                    value=value,
    146                                    units='ms',
    147                                    higher_is_better=False)
    148 
    149     def run_once(self):
    150         self.setup_inbox_composer()
    151         self.measure_input_latency()
    152         self.teardown_inbox_composer()
    153