| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import numpy |
| import os |
| import time |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.cros.input_playback import keyboard |
| from autotest_lib.client.cros.input_playback import stylus |
| from autotest_lib.client.common_lib.cros import arc_util |
| from autotest_lib.client.common_lib.cros import chrome |
| from telemetry.timeline import model as model_module |
| from telemetry.timeline import tracing_config |
| |
| |
| _BODY_EDITABLE_CLASS = 'aR CoXcW editable' |
| _COMPOSE_BUTTON_CLASS = 'y hC' |
| _DISCARD_BUTTON_CLASS = 'ezvJwb bG AK ew IB' |
| _IDLE_FOR_INBOX_STABLIZED = 30 |
| _INBOX_URL = 'https://inbox.google.com' |
| _KEYIN_TEST_DATA = 'input_test_data' |
| _SCRIPT_TIMEOUT = 5 |
| _TARGET_EVENT = 'InputLatency::Char' |
| _TARGET_TRACING_CATEGORIES = 'input, latencyInfo' |
| _TRACING_TIMEOUT = 60 |
| |
| |
| class performance_InboxInputLatency(test.test): |
| """Invoke Inbox composer, inject key events then measure latency.""" |
| version = 1 |
| |
| def initialize(self): |
| """Setup the test environment.""" |
| # Create a virtual keyboard device for key event playback. |
| self.keyboard = keyboard.Keyboard() |
| |
| # Create a virtual stylus |
| self.stylus = stylus.Stylus() |
| # Instantiate Chrome browser. |
| username, password = arc_util.get_test_account_info() |
| self.browser = chrome.Chrome(gaia_login=True, |
| username=username, |
| password=password) |
| self.tab = self.browser.browser.tabs[0] |
| |
| # Setup Chrome Tracing. |
| config = tracing_config.TracingConfig() |
| category_filter = config.chrome_trace_config.category_filter |
| category_filter.AddFilterString(_TARGET_TRACING_CATEGORIES) |
| config.enable_chrome_trace = True |
| self.target_tracing_config = config |
| |
| def cleanup(self): |
| """Cleanup the test environment.""" |
| if hasattr(self, 'browser'): |
| self.browser.close() |
| if self.keyboard: |
| self.keyboard.close() |
| if self.stylus: |
| self.stylus.close() |
| |
| def click_button_by_class_name(self, class_name): |
| """ |
| Locate a button by its class name and click it. |
| |
| @param class_name: the class name of the button. |
| |
| """ |
| button_query = 'document.getElementsByClassName("' + class_name +'")' |
| # Make sure the target button is available |
| self.tab.WaitForJavaScriptCondition(button_query + '.length == 1', |
| timeout=_SCRIPT_TIMEOUT) |
| |
| query = ('(function(element) {' |
| ' var rect = element.getBoundingClientRect();' |
| ' return {left: rect.left, top: rect.top,' |
| ' width: rect.width, height: rect.height};' |
| ' })(%s[0]);' % button_query) |
| target = self.tab.EvaluateJavaScript(query) |
| logging.info(target) |
| |
| query = ('(function(element) {' |
| ' var rect = element.getBoundingClientRect();' |
| ' return {width: rect.width,' |
| ' height: rect.height};' |
| ' })(document.body);') |
| window = self.tab.EvaluateJavaScript(query) |
| logging.info(window) |
| |
| # Click on the center of the target component in the window |
| percent_x = (target['left'] + target['width'] / 2.0) / window['width'] |
| percent_y = (target['top'] + target['height'] / 2.0) / window['height'] |
| self.stylus.click_with_percentage(percent_x, percent_y) |
| |
| def setup_inbox_composer(self): |
| """Navigate to Inbox, and click the compose button.""" |
| self.tab.Navigate(_INBOX_URL) |
| tracing_controller = self.tab.browser.platform.tracing_controller |
| if not tracing_controller.IsChromeTracingSupported(): |
| raise Exception('Chrome tracing not supported') |
| |
| # Idle for making inbox tab stablized, i.e. not busy in syncing with |
| # backend inbox server. |
| time.sleep(_IDLE_FOR_INBOX_STABLIZED) |
| |
| # Bring back the focus to browser window |
| self.stylus.click(1, 1) |
| |
| # Make inbox tab fullscreen by pressing F4 key |
| fullscreen = self.tab.EvaluateJavaScript('document.webkitIsFullScreen') |
| if not fullscreen: |
| self.keyboard.press_key('f4') |
| |
| # Click Compose button to instantiate a draft mail. |
| self.click_button_by_class_name(_COMPOSE_BUTTON_CLASS) |
| |
| # Click the mail body area for focus |
| self.click_button_by_class_name(_BODY_EDITABLE_CLASS) |
| |
| def teardown_inbox_composer(self): |
| """Discards the draft mail.""" |
| self.click_button_by_class_name(_DISCARD_BUTTON_CLASS) |
| # Cancel Fullscreen |
| query = ('if (document.webkitIsFullScreen)' |
| ' document.webkitCancelFullScreen();') |
| self.tab.EvaluateJavaScript(query) |
| |
| def measure_input_latency(self): |
| """Injects key events then measure and report the latency.""" |
| tracing_controller = self.tab.browser.platform.tracing_controller |
| tracing_controller.StartTracing(self.target_tracing_config, |
| timeout=_TRACING_TIMEOUT) |
| # Inject pre-recorded test key events |
| current_dir = os.path.dirname(os.path.realpath(__file__)) |
| data_file = os.path.join(current_dir, _KEYIN_TEST_DATA) |
| self.keyboard.playback(data_file) |
| |
| # The result is the first element in the tuple. |
| results = tracing_controller.StopTracing()[0] |
| |
| # Iterate recorded events and output target latency events |
| timeline_model = model_module.TimelineModel(results) |
| event_iter = timeline_model.IterAllEvents( |
| event_type_predicate=timeline_model.IsSliceOrAsyncSlice) |
| |
| # Extract and report the latency information |
| latency_data = [] |
| previous_start = 0.0 |
| for event in event_iter: |
| if event.name == _TARGET_EVENT and event.start != previous_start: |
| logging.info('input char latency = %f ms', event.duration) |
| latency_data.append(event.duration) |
| previous_start = event.start |
| operators = ['mean', 'std', 'max', 'min'] |
| for operator in operators: |
| description = 'input_char_latency_' + operator |
| value = getattr(numpy, operator)(latency_data) |
| logging.info('%s = %f', description, value) |
| self.output_perf_value(description=description, |
| value=value, |
| units='ms', |
| higher_is_better=False) |
| |
| def run_once(self): |
| """Test main function.""" |
| self.setup_inbox_composer() |
| self.measure_input_latency() |
| self.teardown_inbox_composer() |