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