diff --git a/wlauto/common/android/ApplaunchInterface.class b/wlauto/common/android/ApplaunchInterface.class new file mode 100644 index 00000000..0bd8e053 Binary files /dev/null and b/wlauto/common/android/ApplaunchInterface.class differ diff --git a/wlauto/common/android/BaseUiAutomation$1.class b/wlauto/common/android/BaseUiAutomation$1.class index 5a216cc3..8298936c 100644 Binary files a/wlauto/common/android/BaseUiAutomation$1.class and b/wlauto/common/android/BaseUiAutomation$1.class differ diff --git a/wlauto/common/android/BaseUiAutomation.class b/wlauto/common/android/BaseUiAutomation.class index 2188bd5f..b20d2376 100644 Binary files a/wlauto/common/android/BaseUiAutomation.class and b/wlauto/common/android/BaseUiAutomation.class differ diff --git a/wlauto/common/android/UiAutoUtils.class b/wlauto/common/android/UiAutoUtils.class new file mode 100644 index 00000000..fb1da9af Binary files /dev/null and b/wlauto/common/android/UiAutoUtils.class differ diff --git a/wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class b/wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class index d793d013..a62db9e1 100644 Binary files a/wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class and b/wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class differ diff --git a/wlauto/common/android/UxPerfUiAutomation$GestureType.class b/wlauto/common/android/UxPerfUiAutomation$GestureType.class index 8cc30f68..fc2cf4d2 100644 Binary files a/wlauto/common/android/UxPerfUiAutomation$GestureType.class and b/wlauto/common/android/UxPerfUiAutomation$GestureType.class differ diff --git a/wlauto/common/android/UxPerfUiAutomation.class b/wlauto/common/android/UxPerfUiAutomation.class index 1be53c1f..016b784f 100644 Binary files a/wlauto/common/android/UxPerfUiAutomation.class and b/wlauto/common/android/UxPerfUiAutomation.class differ diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/ApplaunchInterface.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/ApplaunchInterface.java new file mode 100644 index 00000000..b53c7bb4 --- /dev/null +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/ApplaunchInterface.java @@ -0,0 +1,54 @@ + +/* Copyright 2013-2016 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wlauto.uiauto; + +import android.os.Bundle; +import android.util.Log; + +// Import the uiautomator libraries +import com.android.uiautomator.core.UiObject; + +/** + * ApplaunchInterface.java + * Interface used for enabling uxperfapplaunch workload. + * This interface gets implemented by all workloads that support application launch + * instrumentation. + */ + +public interface ApplaunchInterface { + + /** + * Sets the launchEndObject of a workload, which is a UiObject that marks + * the end of the application launch. + */ + public UiObject getLaunchEndObject(); + + /** + * Runs the Uiautomation methods for clearing the initial run + * dialogues on the first time installation of an application package. + */ + public void runApplicationInitialization() throws Exception; + + /** + * Provides the application launch command of the application which is + * constructed as a string from the workload. + */ + public String getLaunchCommand(); + + /** Passes the workload parameters. */ + public void setWorkloadParameters(Bundle parameters); +} diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java index eb1e583b..1a234d52 100755 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java @@ -228,6 +228,9 @@ public class BaseUiAutomation extends UiAutomatorTestCase { UiDevice.getInstance().pressEnter(); } + public void pressHome() { + UiDevice.getInstance().pressHome(); + } public void pressBack() { UiDevice.getInstance().pressBack(); diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UiAutoUtils.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UiAutoUtils.java new file mode 100644 index 00000000..35bebd03 --- /dev/null +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UiAutoUtils.java @@ -0,0 +1,36 @@ +/* Copyright 2013-2016 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wlauto.uiauto; + +import android.os.Bundle; +import java.util.logging.Logger; + +public final class UiAutoUtils { + + /** Construct launch command of an application. */ + public static String createLaunchCommand(Bundle parameters) { + String launchCommand; + String activityName = parameters.getString("launch_activity"); + String packageName = parameters.getString("package"); + if (activityName.equals("None")) { + launchCommand = String.format("am start %s", packageName); + } + else { + launchCommand = String.format("am start -n %s/%s", packageName, activityName); + } + return launchCommand; + } +} diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java index 10367015..68ec38fc 100644 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java @@ -16,8 +16,19 @@ package com.arm.wlauto.uiauto; import java.util.logging.Logger; +import android.os.Bundle; public class UxPerfUiAutomation extends BaseUiAutomation { + + protected Bundle parameters; + protected String packageName; + protected String packageID; + + //Get application package parameters and create package ID + public void getPackageParameters() { + packageName = parameters.getString("package"); + packageID = packageName + ":id/"; + } private Logger logger = Logger.getLogger(UxPerfUiAutomation.class.getName()); diff --git a/wlauto/instrumentation/fps/__init__.py b/wlauto/instrumentation/fps/__init__.py index 54fb353d..9cadeebd 100755 --- a/wlauto/instrumentation/fps/__init__.py +++ b/wlauto/instrumentation/fps/__init__.py @@ -39,10 +39,9 @@ from wlauto.instrumentation import instrument_is_installed from wlauto.exceptions import (InstrumentError, WorkerThreadError, ConfigError, DeviceNotRespondingError, TimeoutError) from wlauto.utils.types import boolean, numeric -from wlauto.utils.fps import FpsProcessor, SurfaceFlingerFrame, GfxInfoFrame, GFXINFO_EXEMPT +from wlauto.utils.fps import (FpsProcessor, SurfaceFlingerFrame, GfxInfoFrame, GFXINFO_EXEMPT, + VSYNC_INTERVAL) - -VSYNC_INTERVAL = 16666667 PAUSE_LATENCY = 20 EPSYLON = 0.0001 diff --git a/wlauto/result_processors/uxperf.py b/wlauto/result_processors/uxperf.py index ff9027f8..6221afde 100755 --- a/wlauto/result_processors/uxperf.py +++ b/wlauto/result_processors/uxperf.py @@ -14,17 +14,13 @@ # import os -import re -import logging - -from collections import defaultdict from distutils.version import LooseVersion + from wlauto import ResultProcessor, Parameter from wlauto.instrumentation import instrument_is_enabled -from wlauto.instrumentation.fps import VSYNC_INTERVAL from wlauto.exceptions import ResultProcessorError, ConfigError -from wlauto.utils.fps import FpsProcessor, SurfaceFlingerFrame, GfxInfoFrame from wlauto.utils.types import numeric, boolean +from wlauto.utils.uxperf import UxPerfParser try: import pandas as pd @@ -77,6 +73,7 @@ class UxPerfResultProcessor(ResultProcessor): ] def initialize(self, context): + # needed for uxperf parser if not pd or LooseVersion(pd.__version__) < LooseVersion('0.13.1'): message = ('uxperf result processor requires pandas Python package ' '(version 0.13.1 or higher) to be installed.\n' @@ -101,161 +98,3 @@ class UxPerfResultProcessor(ResultProcessor): if self.add_frames: self.logger.debug('Adding per-action frame metrics') parser.add_action_frames(framelog, self.drop_threshold, self.generate_csv) - - -class UxPerfParser(object): - ''' - Parses logcat messages for UX Performance markers. - - UX Performance markers are output from logcat under a debug priority. The - logcat tag for the marker messages is UX_PERF. The messages associated with - this tag consist of a name for the action to be recorded and a timestamp. - These fields are delimited by a single space. e.g. - - : - UX_PERF : gestures_swipe_left_start 861975087367 - ... - ... - UX_PERF : gestures_swipe_left_end 862132085804 - - Timestamps are produced using the running Java Virtual Machine's - high-resolution time source, in nanoseconds. - ''' - def __init__(self, context): - self.context = context - self.actions = defaultdict(list) - self.logger = logging.getLogger('UxPerfParser') - # regex for matching logcat message format: - self.regex = re.compile(r'UX_PERF.*?:\s*(?P.*\d+$)') - - def parse(self, log): - ''' - Opens log file and parses UX_PERF markers. - - Actions delimited by markers are captured in a dictionary with - actions mapped to timestamps. - ''' - loglines = self._read(log) - self._gen_action_timestamps(loglines) - - def add_action_frames(self, frames, drop_threshold, generate_csv): # pylint: disable=too-many-locals - ''' - Uses FpsProcessor to parse frame.csv extracting fps, frame count, jank - and vsync metrics on a per action basis. Adds results to metrics. - ''' - refresh_period = self._parse_refresh_peroid() - - for action in self.actions: - # default values - fps, frame_count, janks, not_at_vsync = float('nan'), 0, 0, 0 - p90, p95, p99 = [float('nan')] * 3 - metrics = (fps, frame_count, janks, not_at_vsync) - - df = self._create_sub_df(self.actions[action], frames) - if not df.empty: # pylint: disable=maybe-no-member - fp = FpsProcessor(df, action=action) - try: - per_frame_fps, metrics = fp.process(refresh_period, drop_threshold) - fps, frame_count, janks, not_at_vsync = metrics - - if generate_csv: - name = action + '_fps' - filename = name + '.csv' - fps_outfile = os.path.join(self.context.output_directory, filename) - per_frame_fps.to_csv(fps_outfile, index=False, header=True) - self.context.add_artifact(name, path=filename, kind='data') - - p90, p95, p99 = fp.percentiles() - except AttributeError: - self.logger.warning('Non-matched timestamps in dumpsys output: action={}' - .format(action)) - - self.context.result.add_metric(action + '_FPS', fps) - self.context.result.add_metric(action + '_frame_count', frame_count) - self.context.result.add_metric(action + '_janks', janks, lower_is_better=True) - self.context.result.add_metric(action + '_not_at_vsync', not_at_vsync, lower_is_better=True) - self.context.result.add_metric(action + '_frame_time_90percentile', p90, 'ms', lower_is_better=True) - self.context.result.add_metric(action + '_frame_time_95percentile', p95, 'ms', lower_is_better=True) - self.context.result.add_metric(action + '_frame_time_99percentile', p99, 'ms', lower_is_better=True) - - def add_action_timings(self): - ''' - Add simple action timings in millisecond resolution to metrics - ''' - for action, timestamps in self.actions.iteritems(): - # nanosecond precision, but not necessarily nanosecond resolution - # truncate to guarantee millisecond precision - ts_ms = tuple(int(ts) for ts in timestamps) - if len(ts_ms) == 2: - start, finish = ts_ms - duration = finish - start - result = self.context.result - - result.add_metric(action + "_start", start, units='ms') - result.add_metric(action + "_finish", finish, units='ms') - result.add_metric(action + "_duration", duration, units='ms', lower_is_better=True) - else: - self.logger.warning('Expected two timestamps. Received {}'.format(ts_ms)) - - def _gen_action_timestamps(self, lines): - ''' - Parses lines and matches against logcat tag. - Groups timestamps by action name. - Creates a dictionary of lists with actions mapped to timestamps. - ''' - for line in lines: - match = self.regex.search(line) - - if match: - message = match.group('message') - action_with_suffix, timestamp = message.rsplit(' ', 1) - action, _ = action_with_suffix.rsplit('_', 1) - self.actions[action].append(timestamp) - - def _parse_refresh_peroid(self): - ''' - Reads the first line of the raw dumpsys output for the refresh period. - ''' - raw_path = os.path.join(self.context.output_directory, 'surfaceflinger.raw') - if os.path.isfile(raw_path): - raw_lines = self._read(raw_path) - refresh_period = int(raw_lines.next()) - else: - refresh_period = VSYNC_INTERVAL - - return refresh_period - - def _create_sub_df(self, action, frames): - ''' - Creates a data frame containing fps metrics for a captured action. - ''' - if len(action) == 2: - start, end = map(int, action) - df = pd.read_csv(frames) - # SurfaceFlinger Algorithm - if df.columns.tolist() == list(SurfaceFlingerFrame._fields): # pylint: disable=maybe-no-member - field = 'actual_present_time' - # GfxInfo Algorithm - elif df.columns.tolist() == list(GfxInfoFrame._fields): # pylint: disable=maybe-no-member - field = 'FrameCompleted' - else: - field = '' - self.logger.error('frames.csv not in a recognised format. Cannot parse.') - if field: - df = df[start < df[field]] - df = df[df[field] <= end] - else: - self.logger.warning('Discarding action. Expected 2 timestamps, got {}!'.format(len(action))) - df = pd.DataFrame() - return df - - def _read(self, log): - ''' - Opens a file a yields the lines with whitespace stripped. - ''' - try: - with open(log, 'r') as rfh: - for line in rfh: - yield line.strip() - except IOError: - self.logger.error('Could not open {}'.format(log)) diff --git a/wlauto/utils/fps.py b/wlauto/utils/fps.py index 84016622..735be824 100755 --- a/wlauto/utils/fps.py +++ b/wlauto/utils/fps.py @@ -29,6 +29,8 @@ GfxInfoFrame = collections.namedtuple('GfxInfoFrame', 'Flags IntendedVsync Vsync # Android M: WindowLayoutChanged | SurfaceCanvas GFXINFO_EXEMPT = 1 | 4 +VSYNC_INTERVAL = 16666667 + class FpsProcessor(object): """ diff --git a/wlauto/utils/uxperf.py b/wlauto/utils/uxperf.py new file mode 100644 index 00000000..36b9ee06 --- /dev/null +++ b/wlauto/utils/uxperf.py @@ -0,0 +1,170 @@ +import os +import re +import logging +from collections import defaultdict + +from wlauto.utils.fps import FpsProcessor, SurfaceFlingerFrame, GfxInfoFrame, VSYNC_INTERVAL + +try: + import pandas as pd +except ImportError: + pd = None + + +class UxPerfParser(object): + ''' + Parses logcat messages for UX Performance markers. + + UX Performance markers are output from logcat under a debug priority. The + logcat tag for the marker messages is UX_PERF. The messages associated with + this tag consist of a name for the action to be recorded and a timestamp. + These fields are delimited by a single space. e.g. + + : + UX_PERF : gestures_swipe_left_start 861975087367 + ... + ... + UX_PERF : gestures_swipe_left_end 862132085804 + + Timestamps are produced using the running Java Virtual Machine's + high-resolution time source, in nanoseconds. + ''' + def __init__(self, context, prefix=''): + self.context = context + self.prefix = prefix + self.actions = defaultdict(list) + self.logger = logging.getLogger('UxPerfParser') + # regex for matching logcat message format: + self.regex = re.compile(r'UX_PERF.*?:\s*(?P.*\d+$)') + + def parse(self, log): + ''' + Opens log file and parses UX_PERF markers. + + Actions delimited by markers are captured in a dictionary with + actions mapped to timestamps. + ''' + loglines = self._read(log) + self._gen_action_timestamps(loglines) + + def add_action_frames(self, frames, drop_threshold, generate_csv): # pylint: disable=too-many-locals + ''' + Uses FpsProcessor to parse frame.csv extracting fps, frame count, jank + and vsync metrics on a per action basis. Adds results to metrics. + ''' + refresh_period = self._parse_refresh_peroid() + + for action in self.actions: + # default values + fps, frame_count, janks, not_at_vsync = float('nan'), 0, 0, 0 + p90, p95, p99 = [float('nan')] * 3 + metrics = (fps, frame_count, janks, not_at_vsync) + + df = self._create_sub_df(self.actions[action], frames) + if not df.empty: # pylint: disable=maybe-no-member + fp = FpsProcessor(df, action=action) + try: + per_frame_fps, metrics = fp.process(refresh_period, drop_threshold) + fps, frame_count, janks, not_at_vsync = metrics + + if generate_csv: + name = action + '_fps' + filename = name + '.csv' + fps_outfile = os.path.join(self.context.output_directory, filename) + per_frame_fps.to_csv(fps_outfile, index=False, header=True) + self.context.add_artifact(name, path=filename, kind='data') + + p90, p95, p99 = fp.percentiles() + except AttributeError: + self.logger.warning('Non-matched timestamps in dumpsys output: action={}' + .format(action)) + + self.context.result.add_metric(self.prefix + action + '_FPS', fps) + self.context.result.add_metric(self.prefix + action + '_frame_count', frame_count) + self.context.result.add_metric(self.prefix + action + '_janks', janks, lower_is_better=True) + self.context.result.add_metric(self.prefix + action + '_not_at_vsync', not_at_vsync, lower_is_better=True) + self.context.result.add_metric(self.prefix + action + '_frame_time_90percentile', p90, 'ms', lower_is_better=True) + self.context.result.add_metric(self.prefix + action + '_frame_time_95percentile', p95, 'ms', lower_is_better=True) + self.context.result.add_metric(self.prefix + action + '_frame_time_99percentile', p99, 'ms', lower_is_better=True) + + def add_action_timings(self): + ''' + Add simple action timings in millisecond resolution to metrics + ''' + for action, timestamps in self.actions.iteritems(): + # nanosecond precision, but not necessarily nanosecond resolution + # truncate to guarantee millisecond precision + ts_ms = tuple(int(ts) for ts in timestamps) + if len(ts_ms) == 2: + start, finish = ts_ms + duration = finish - start + result = self.context.result + + result.add_metric(self.prefix + action + "_start", start, units='ms') + result.add_metric(self.prefix + action + "_finish", finish, units='ms') + result.add_metric(self.prefix + action + "_duration", duration, units='ms', lower_is_better=True) + else: + self.logger.warning('Expected two timestamps. Received {}'.format(ts_ms)) + + def _gen_action_timestamps(self, lines): + ''' + Parses lines and matches against logcat tag. + Groups timestamps by action name. + Creates a dictionary of lists with actions mapped to timestamps. + ''' + for line in lines: + match = self.regex.search(line) + + if match: + message = match.group('message') + action_with_suffix, timestamp = message.rsplit(' ', 1) + action, _ = action_with_suffix.rsplit('_', 1) + self.actions[action].append(timestamp) + + def _parse_refresh_peroid(self): + ''' + Reads the first line of the raw dumpsys output for the refresh period. + ''' + raw_path = os.path.join(self.context.output_directory, 'surfaceflinger.raw') + if os.path.isfile(raw_path): + raw_lines = self._read(raw_path) + refresh_period = int(raw_lines.next()) + else: + refresh_period = VSYNC_INTERVAL + + return refresh_period + + def _create_sub_df(self, action, frames): + ''' + Creates a data frame containing fps metrics for a captured action. + ''' + if len(action) == 2: + start, end = map(int, action) + df = pd.read_csv(frames) + # SurfaceFlinger Algorithm + if df.columns.tolist() == list(SurfaceFlingerFrame._fields): # pylint: disable=maybe-no-member + field = 'actual_present_time' + # GfxInfo Algorithm + elif df.columns.tolist() == list(GfxInfoFrame._fields): # pylint: disable=maybe-no-member + field = 'FrameCompleted' + else: + field = '' + self.logger.error('frames.csv not in a recognised format. Cannot parse.') + if field: + df = df[start < df[field]] + df = df[df[field] <= end] + else: + self.logger.warning('Discarding action. Expected 2 timestamps, got {}!'.format(len(action))) + df = pd.DataFrame() + return df + + def _read(self, log): + ''' + Opens a file a yields the lines with whitespace stripped. + ''' + try: + with open(log, 'r') as rfh: + for line in rfh: + yield line.strip() + except IOError: + self.logger.error('Could not open {}'.format(log)) diff --git a/wlauto/workloads/adobereader/com.arm.wlauto.uiauto.adobereader.jar b/wlauto/workloads/adobereader/com.arm.wlauto.uiauto.adobereader.jar index a99b0d70..a49643bf 100644 Binary files a/wlauto/workloads/adobereader/com.arm.wlauto.uiauto.adobereader.jar and b/wlauto/workloads/adobereader/com.arm.wlauto.uiauto.adobereader.jar differ diff --git a/wlauto/workloads/adobereader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/adobereader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index 4fff0d41..9f8af678 100755 --- a/wlauto/workloads/adobereader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/adobereader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -16,6 +16,7 @@ package com.arm.wlauto.uiauto.adobereader; import android.os.Bundle; +import android.util.Log; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; @@ -23,6 +24,8 @@ import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiSelector; import com.arm.wlauto.uiauto.UxPerfUiAutomation; +import com.arm.wlauto.uiauto.ApplaunchInterface; +import com.arm.wlauto.uiauto.UiAutoUtils; import static com.arm.wlauto.uiauto.BaseUiAutomation.FindByCriteria.BY_ID; import static com.arm.wlauto.uiauto.BaseUiAutomation.FindByCriteria.BY_TEXT; @@ -34,27 +37,21 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -public class UiAutomation extends UxPerfUiAutomation { - - protected Bundle parameters; - protected String packageName; - protected String packageID; +public class UiAutomation extends UxPerfUiAutomation implements ApplaunchInterface { private long networkTimeout = TimeUnit.SECONDS.toMillis(20); private long searchTimeout = TimeUnit.SECONDS.toMillis(20); public void runUiAutomation() throws Exception { parameters = getParams(); - packageName = parameters.getString("package"); - packageID = packageName + ":id/"; String filename = parameters.getString("filename").replace("0space0", " "); String[] searchStrings = parameters.getString("search_string_list").replace("0space0", " ").split("0newline0"); setScreenOrientation(ScreenOrientation.NATURAL); + runApplicationInitialization(); - dismissWelcomeView(); openFile(filename); gesturesTest(); searchPdfTest(searchStrings); @@ -62,6 +59,31 @@ public class UiAutomation extends UxPerfUiAutomation { unsetScreenOrientation(); } + + // Get application parameters and clear the initial run dialogues of the application launch. + public void runApplicationInitialization() throws Exception { + getPackageParameters(); + dismissWelcomeView(); + } + + // Sets the UiObject that marks the end of the application launch. + public UiObject getLaunchEndObject() { + UiObject launchEndObject = new UiObject(new UiSelector().textContains("RECENT") + .className("android.widget.TextView")); + return launchEndObject; + } + + // Returns the launch command for the application. + public String getLaunchCommand() { + String launch_command; + launch_command = UiAutoUtils.createLaunchCommand(parameters); + return launch_command; + } + + // Pass the workload parameters, used for applaunch + public void setWorkloadParameters(Bundle workload_parameters) { + parameters = workload_parameters; + } private void dismissWelcomeView() throws Exception { UiObject welcomeView = getUiObjectByResourceId("android:id/content", diff --git a/wlauto/workloads/applaunch/__init__.py b/wlauto/workloads/applaunch/__init__.py index 40dcb7b7..19fcac96 100644 --- a/wlauto/workloads/applaunch/__init__.py +++ b/wlauto/workloads/applaunch/__init__.py @@ -1,189 +1,169 @@ -# Copyright 2013-2015 ARM Limited +# Copyright 2015 ARM Limited # -# Licensed under the Apache License, Version 2.0 (the "License"); +# Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, +# distributed under the License is distributed on an 'AS IS' BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # - -# pylint: disable=E1101 - -from __future__ import division +# pylint: disable=attribute-defined-outside-init import os -try: - import jinja2 -except ImportError: - jinja2 = None +from time import sleep -from wlauto import Workload, settings, Parameter -from wlauto.exceptions import WorkloadError -from wlauto.utils.hwmon import discover_sensors -from wlauto.utils.misc import get_meansd -from wlauto.utils.types import boolean, identifier, list_of_strs +from wlauto import Workload, AndroidBenchmark, AndroidUxPerfWorkload, UiAutomatorWorkload +from wlauto import Parameter +from wlauto import ExtensionLoader +from wlauto import File +from wlauto import settings +from wlauto.exceptions import ConfigError +from wlauto.exceptions import ResourceError +from wlauto.utils.android import ApkInfo +from wlauto.utils.uxperf import UxPerfParser + +import wlauto.common.android.resources -THIS_DIR = os.path.dirname(__file__) -TEMPLATE_NAME = 'device_script.template' -SCRIPT_TEMPLATE = os.path.join(THIS_DIR, TEMPLATE_NAME) - -APP_CONFIG = { - 'browser': { - 'package': 'com.android.browser', - 'activity': '.BrowserActivity', - 'options': '-d about:blank', - }, - 'calculator': { - 'package': 'com.android.calculator2', - 'activity': '.Calculator', - 'options': '', - }, - 'calendar': { - 'package': 'com.android.calendar', - 'activity': '.LaunchActivity', - 'options': '', - }, -} - - -class ApplaunchWorkload(Workload): +class Applaunch(AndroidUxPerfWorkload): name = 'applaunch' - description = """ - Measures the time and energy used in launching an application. + description = ''' + This workload launches and measures the launch time of applications for supporting workloads. + + Currently supported workloads are the ones that implement ``ApplaunchInterface``. For any + workload to support this workload, it should implement the ``ApplaunchInterface``. + The corresponding java file of the workload associated with the application being measured + is executed during the run. The application that needs to be + measured is passed as a parametre ``workload_name``. The parameters required for that workload + have to be passed as a dictionary which is captured by the parametre ``workload_params``. + This information can be obtained by inspecting the workload details of the specific workload. - """ + The workload allows to run multiple iterations of an application + launch in two modes: + + 1. Launch from background + 2. Launch from long-idle + + These modes are captured as a parameter applaunch_type. + + ``launch_from_background`` + Launches an application after the application is sent to background by + pressing Home button. + + ``launch_from_long-idle`` + Launches an application after killing an application process and + clearing all the caches. + + **Test Description:** + + - During the initialization and setup, the application being launched is launched + for the first time. The jar file of the workload of the application + is moved to device at the location ``workdir`` which further implements the methods + needed to measure the application launch time. + + - Run phase calls the UiAutomator of the applaunch which runs in two subphases. + A. Applaunch Setup Run: + During this phase, welcome screens and dialogues during the first launch + of the instrumented application are cleared. + B. Applaunch Metric Run: + During this phase, the application is launched multiple times determined by + the iteration number specified by the parametre ``applaunch_iterations``. + Each of these iterations are instrumented to capture the launch time taken + and the values are recorded as UXPERF marker values in logfile. + ''' supported_platforms = ['android'] parameters = [ - Parameter('app', default='browser', allowed_values=['calculator', 'browser', 'calendar'], - description='The name of the application to measure.'), - Parameter('set_launcher_affinity', kind=bool, default=True, - description=('If ``True``, this will explicitly set the affinity of the launcher ' - 'process to the A15 cluster.')), - Parameter('times', kind=int, default=8, - description='Number of app launches to do on the device.'), - Parameter('measure_energy', kind=boolean, default=False, + Parameter('workload_name', kind=str, + description='Name of the uxperf workload to launch', + default='gmail'), + Parameter('workload_params', kind=dict, default={}, description=""" - Specfies wether energy measurments should be taken during the run. - - .. note:: This depends on appropriate sensors to be exposed through HWMON. - + parameters of the uxperf workload whose application launch + time is measured + """), + Parameter('applaunch_type', kind=str, default='launch_from_background', + allowed_values=['launch_from_background', 'launch_from_long-idle'], + description=""" + Choose launch_from_long-idle for measuring launch time + from long-idle. These two types are described in the class + description. + """), + Parameter('applaunch_iterations', kind=int, default=1, + description=""" + Number of iterations of the application launch + """), + Parameter('report_results', kind=bool, default=True, + description=""" + Choose to report results of the application launch time. """), - Parameter('io_stress', kind=boolean, default=False, - description='Specifies whether to stress IO during App launch.'), - Parameter('io_scheduler', allowed_values=['noop', 'deadline', 'row', 'cfq', 'bfq'], - description='Set the IO scheduler to test on the device.'), - Parameter('cleanup', kind=boolean, default=True, - description='Specifies whether to clean up temporary files on the device.'), ] def __init__(self, device, **kwargs): - super(ApplaunchWorkload, self).__init__(device, **kwargs) - if not jinja2: - raise WorkloadError('Please install jinja2 Python package: "sudo pip install jinja2"') - filename = '{}-{}.sh'.format(self.name, self.app) - self.host_script_file = os.path.join(settings.meta_directory, filename) - self.device_script_file = os.path.join(self.device.working_directory, filename) - self._launcher_pid = None - self._old_launcher_affinity = None - self.sensors = [] + super(Applaunch, self).__init__(device, **kwargs) - def on_run_init(self, context): # pylint: disable=W0613 - if self.measure_energy: - self.sensors = discover_sensors(self.device, ['energy']) - for sensor in self.sensors: - sensor.label = identifier(sensor.label).upper() + def init_resources(self, context): + super(Applaunch, self).init_resources(context) + loader = ExtensionLoader(packages=settings.extension_packages, paths=settings.extension_paths) + self.workload_params['markers_enabled'] = True + self.workload = loader.get_workload(self.workload_name, self.device, + **self.workload_params) + self.init_workload_resources(context) + + def init_workload_resources(self, context): + self.workload.uiauto_file = context.resolver.get(wlauto.common.android.resources.JarFile(self.workload)) + if not self.workload.uiauto_file: + raise ResourceError('No UI automation JAR file found for workload {}.'.format(self.workload.name)) + self.workload.device_uiauto_file = self.device.path.join(self.device.working_directory, os.path.basename(self.workload.uiauto_file)) + if not self.workload.uiauto_package: + self.workload.uiauto_package = os.path.splitext(os.path.basename(self.workload.uiauto_file))[0] + + def validate(self): + super(Applaunch, self).validate() + self.workload.validate() + self.pass_parameters() + + def pass_parameters(self): + self.uiauto_params['workload'] = self.workload.name + self.uiauto_params['package'] = self.workload.package + self.uiauto_params['binaries_directory'] = self.device.binaries_directory + self.uiauto_params.update(self.workload.uiauto_params) + if self.workload.activity: + self.uiauto_params['launch_activity'] = self.workload.activity + else: + self.uiauto_params['launch_activity'] = "None" + self.uiauto_params['applaunch_type'] = self.applaunch_type + self.uiauto_params['applaunch_iterations'] = self.applaunch_iterations def setup(self, context): - self.logger.debug('Creating script {}'.format(self.host_script_file)) - with open(self.host_script_file, 'w') as wfh: - env = jinja2.Environment(loader=jinja2.FileSystemLoader(THIS_DIR)) - template = env.get_template(TEMPLATE_NAME) - wfh.write(template.render(device=self.device, # pylint: disable=maybe-no-member - sensors=self.sensors, - iterations=self.times, - io_stress=self.io_stress, - io_scheduler=self.io_scheduler, - cleanup=self.cleanup, - package=APP_CONFIG[self.app]['package'], - activity=APP_CONFIG[self.app]['activity'], - options=APP_CONFIG[self.app]['options'], - busybox=self.device.busybox, - )) - self.device_script_file = self.device.install(self.host_script_file) - if self.set_launcher_affinity: - self._set_launcher_affinity() - self.device.clear_logcat() + AndroidBenchmark.setup(self.workload, context) + if not self.workload.launch_main: + self.workload.launch_app() + UiAutomatorWorkload.setup(self, context) + self.workload.device.push_file(self.workload.uiauto_file, self.workload.device_uiauto_file) def run(self, context): - self.device.execute('sh {}'.format(self.device_script_file), timeout=300, as_root=self.io_stress) + UiAutomatorWorkload.run(self, context) - def update_result(self, context): # pylint: disable=too-many-locals - result_files = ['time.result'] - result_files += ['{}.result'.format(sensor.label) for sensor in self.sensors] - metric_suffix = '' - if self.io_stress: - host_scheduler_file = os.path.join(context.output_directory, 'scheduler') - device_scheduler_file = '/sys/block/mmcblk0/queue/scheduler' - self.device.pull_file(device_scheduler_file, host_scheduler_file) - with open(host_scheduler_file) as fh: - scheduler = fh.read() - scheduler_used = scheduler[scheduler.index("[") + 1:scheduler.index("]")] - metric_suffix = '_' + scheduler_used - for filename in result_files: - self._extract_results_from_file(context, filename, metric_suffix) + def update_result(self, context): + super(Applaunch, self).update_result(context) + if self.report_results: + parser = UxPerfParser(context, prefix='applaunch_') + logfile = os.path.join(context.output_directory, 'logcat.log') + parser.parse(logfile) + parser.add_action_timings() def teardown(self, context): - if self.set_launcher_affinity: - self._reset_launcher_affinity() - if self.cleanup: - self.device.delete_file(self.device_script_file) - - def _set_launcher_affinity(self): - try: - self._launcher_pid = self.device.get_pids_of('com.android.launcher')[0] - result = self.device.execute('taskset -p {}'.format(self._launcher_pid), busybox=True, as_root=True) - self._old_launcher_affinity = int(result.split(':')[1].strip(), 16) - - cpu_ids = [i for i, x in enumerate(self.device.core_names) if x == 'a15'] - if not cpu_ids or len(cpu_ids) == len(self.device.core_names): - self.logger.debug('Cannot set affinity.') - return - - new_mask = reduce(lambda x, y: x | y, cpu_ids, 0x0) - self.device.execute('taskset -p 0x{:X} {}'.format(new_mask, self._launcher_pid), busybox=True, as_root=True) - except IndexError: - raise WorkloadError('Could not set affinity of launcher: PID not found.') - - def _reset_launcher_affinity(self): - command = 'taskset -p 0x{:X} {}'.format(self._old_launcher_affinity, self._launcher_pid) - self.device.execute(command, busybox=True, as_root=True) - - def _extract_results_from_file(self, context, filename, metric_suffix): - host_result_file = os.path.join(context.output_directory, filename) - device_result_file = self.device.path.join(self.device.working_directory, filename) - self.device.pull_file(device_result_file, host_result_file) - - with open(host_result_file) as fh: - if filename == 'time.result': - values = [v / 1000 for v in map(int, fh.read().split())] - _add_metric(context, 'time' + metric_suffix, values, 'Seconds') - else: - metric = filename.replace('.result', '').lower() - numbers = iter(map(int, fh.read().split())) - deltas = [(after - before) / 1000000 for before, after in zip(numbers, numbers)] - _add_metric(context, metric, deltas, 'Joules') - - -def _add_metric(context, metric, values, units): - mean, sd = get_meansd(values) - context.result.add_metric(metric, mean, units) - context.result.add_metric(metric + ' sd', sd, units, lower_is_better=True) + super(Applaunch, self).teardown(context) + AndroidBenchmark.teardown(self.workload, context) + UiAutomatorWorkload.teardown(self.workload, context) + #Workload uses Dexclass loader while loading the jar file of the instrumented workload. + #Dexclassloader unzips and generates .dex file in the .jar directory during the run. + device_uiauto_dex_file = self.workload.device_uiauto_file.replace(".jar", ".dex") + self.workload.device.delete_file(self.device.path.join(self.device.binaries_directory, device_uiauto_dex_file)) diff --git a/wlauto/workloads/applaunch/com.arm.wlauto.uiauto.applaunch.jar b/wlauto/workloads/applaunch/com.arm.wlauto.uiauto.applaunch.jar new file mode 100644 index 00000000..c10edd6f Binary files /dev/null and b/wlauto/workloads/applaunch/com.arm.wlauto.uiauto.applaunch.jar differ diff --git a/wlauto/workloads/applaunch/device_script.template b/wlauto/workloads/applaunch/device_script.template deleted file mode 100644 index 1e940d70..00000000 --- a/wlauto/workloads/applaunch/device_script.template +++ /dev/null @@ -1,88 +0,0 @@ -#!{{ device.binaries_directory.rstrip('/') }}/sh - - -{% for sensor in sensors %} -GET_{{ sensor.label }}="cat {{ sensor.filepath }}" -{% endfor %} - -LAUNCH_COMMAND="am start -W -n {{ package }}/{{ activity }} {{ options }}" -STOP_COMMAND="am force-stop {{ package }}" -TEMP_FILE=tmp.txt - -TIME_RESULT="" -{% for sensor in sensors %} -{{ sensor.label }}="" -{% endfor %} - -cd {{ device.working_directory }} - -# esc esc down down down ENTER (this should bring up the apps menu) -input keyevent 111 -sleep 1 -input keyevent 111 -sleep 1 -input keyevent 20 -sleep 1 -input keyevent 20 -sleep 1 -input keyevent 20 -sleep 1 -input keyevent 66 -sleep 1 - -# Warm up caches. -$LAUNCH_COMMAND -$STOP_COMMAND -$LAUNCH_COMMAND -$STOP_COMMAND -$LAUNCH_COMMAND -$STOP_COMMAND - -{% if io_scheduler != None %} -echo {{ io_scheduler }} > /sys/block/mmcblk0/queue/scheduler -{% endif %} - -for i in $({{ busybox }} seq 1 {{ iterations }}) -do - {% for sensor in sensors %} - {{ sensor.label }}="${{ sensor.label }} `$GET_{{ sensor.label }}`" - {% endfor %} - - {% if io_stress %} - # Drop caches to get a cold start. - sync; echo 3 > /proc/sys/vm/drop_caches - # Run IO stress during App launch. - {{ busybox }} dd if=/dev/zero of=write.img bs=1048576 count=2000 conv=fsync > dd_write.txt 2>&1 & - io_write=$! - {{ busybox }} dd if=/dev/block/mmcblk0 of=/dev/null bs=1048576 > dd_read.txt 2>&1 & - io_read=$! - {% endif %} - - $LAUNCH_COMMAND > $TEMP_FILE - - {% for sensor in sensors %} - {{ sensor.label }}="${{ sensor.label }} `$GET_{{ sensor.label }}`" - {% endfor %} - - TIME=`{{ busybox }} awk '{if($1~"TotalTime") print $2}' $TEMP_FILE` - TIME_RESULT="$TIME_RESULT $TIME" - {% if cleanup %} - rm $TEMP_FILE - {% if io_stress %} - kill $io_write - kill $io_read - rm -f write.img - {% endif %} - {% endif %} - - $STOP_COMMAND - sleep 2 -done - -{% for sensor in sensors %} -echo ${{ sensor.label }} > {{ sensor.label }}.result -{% endfor %} -echo $TIME_RESULT > time.result -# esc esc down down down ENTER (this should bring up the apps menu) -input keyevent 111 -sleep 1 diff --git a/wlauto/workloads/applaunch/uiauto/build.sh b/wlauto/workloads/applaunch/uiauto/build.sh new file mode 100755 index 00000000..1c5233c5 --- /dev/null +++ b/wlauto/workloads/applaunch/uiauto/build.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# CD into build dir if possible - allows building from any directory +script_path='.' +if `readlink -f $0 &>/dev/null`; then + script_path=`readlink -f $0 2>/dev/null` +fi +script_dir=`dirname $script_path` +cd $script_dir + +# Ensure build.xml exists before starting +if [[ ! -f build.xml ]]; then + echo 'Ant build.xml file not found! Check that you are in the right directory.' + exit 9 +fi + +# Copy base classes from wlauto dist +class_dir=bin/classes/com/arm/wlauto/uiauto +base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"` +mkdir -p $class_dir +cp $base_classes $class_dir + +# Build and return appropriate exit code if failed +ant build +exit_code=$? +if [[ $exit_code -ne 0 ]]; then + echo "ERROR: 'ant build' exited with code $exit_code" + exit $exit_code +fi + +# If successful move JAR file to workload folder (overwrite previous) +package=com.arm.wlauto.uiauto.applaunch.jar +rm -f ../$package +if [[ -f bin/$package ]]; then + cp bin/$package .. +else + echo 'ERROR: UiAutomator JAR could not be found!' + exit 9 +fi diff --git a/wlauto/workloads/applaunch/uiauto/build.xml b/wlauto/workloads/applaunch/uiauto/build.xml new file mode 100644 index 00000000..b4e76972 --- /dev/null +++ b/wlauto/workloads/applaunch/uiauto/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wlauto/workloads/applaunch/uiauto/project.properties b/wlauto/workloads/applaunch/uiauto/project.properties new file mode 100644 index 00000000..ce39f2d0 --- /dev/null +++ b/wlauto/workloads/applaunch/uiauto/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-18 diff --git a/wlauto/workloads/applaunch/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/applaunch/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java new file mode 100755 index 00000000..b0e9f80f --- /dev/null +++ b/wlauto/workloads/applaunch/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -0,0 +1,220 @@ +/* Copyright 2014-2016 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arm.wlauto.uiauto.applaunch; + +import android.os.Bundle; +import android.util.Log; + +// Import the uiautomator libraries +import com.android.uiautomator.core.UiObject; +import com.android.uiautomator.core.UiObjectNotFoundException; +import com.android.uiautomator.core.UiSelector; + +import com.arm.wlauto.uiauto.ApplaunchInterface; +import com.arm.wlauto.uiauto.UxPerfUiAutomation; + +import static com.arm.wlauto.uiauto.BaseUiAutomation.FindByCriteria.BY_ID; +import static com.arm.wlauto.uiauto.BaseUiAutomation.FindByCriteria.BY_TEXT; +import static com.arm.wlauto.uiauto.BaseUiAutomation.FindByCriteria.BY_DESC; + +import java.util.concurrent.TimeUnit; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Map.Entry; +import dalvik.system.DexClassLoader; +import java.lang.reflect.Method; + + +public class UiAutomation extends UxPerfUiAutomation { + + /** + * Uiobject that marks the end of launch of an application, which is workload + * specific and added in the workload Java file by a method called getLaunchEndObject(). + */ + public UiObject launchEndObject; + /** Timeout to wait for application launch to finish. */ + private Integer launch_timeout = 10; + public String applaunchType; + public String applaunchIterations; + public String activityName; + public ApplaunchInterface launch_workload; + + /** Uiautomator function called by the applaunch workload. */ + public void runUiAutomation() throws Exception{ + parameters = getParams(); + + // Get workload jar file parameters + String workload = parameters.getString("workload"); + String binariesDirectory = parameters.getString("binaries_directory"); + String workloadJarPath = parameters.getString("workdir"); + String workloadJarName = String.format("com.arm.wlauto.uiauto.%1s.jar",workload); + String workloadJarFile = String.format("%1s/%2s",workloadJarPath, workloadJarName); + + // Load the jar file + File jarFile = new File(workloadJarFile); + if(!jarFile.exists()) { + throw new Exception(String.format("Jar file not found: %s", workloadJarFile)); + } + DexClassLoader classloader = new DexClassLoader(jarFile.toURI().toURL().toString(), + binariesDirectory, null, ClassLoader.getSystemClassLoader()); + Class uiautomation = null; + Object uiautomation_interface = null; + String workloadClass = String.format("com.arm.wlauto.uiauto.%1s.UiAutomation",workload); + try { + uiautomation = classloader.loadClass(workloadClass); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + Log.d("Class loaded:", uiautomation.getCanonicalName()); + uiautomation_interface = uiautomation.newInstance(); + + // Create an Application Interface object from the workload + launch_workload = ((ApplaunchInterface)uiautomation_interface); + + // Get parameters for application launch + getPackageParameters(); + applaunchType = parameters.getString("applaunch_type"); + applaunchIterations = parameters.getString("applaunch_iterations"); + activityName = parameters.getString("launch_activity"); + + // Run the workload for application launch initialization + runApplaunchSetup(); + + // Run the workload for application launch measurement + for (int iteration = 0; iteration < Integer.parseInt(applaunchIterations); iteration++) { + Log.d("Applaunch iteration number: ", applaunchIterations); + sleep(20);//sleep for a while before next iteration + killBackground(); + runApplaunchIteration(iteration); + closeApplication(); + } + } + + /** + * Setup run for applaunch workload that clears the initial + * run dialogues on launching an application package. + */ + public void runApplaunchSetup() throws Exception{ + setScreenOrientation(ScreenOrientation.NATURAL); + launch_workload.setWorkloadParameters(parameters); + launch_workload.runApplicationInitialization(); + launchEndObject = launch_workload.getLaunchEndObject(); + unsetScreenOrientation(); + closeApplication(); + } + + /** + * This method performs multiple iterations of application launch and + * records the time taken for each iteration. + */ + public void runApplaunchIteration(Integer iteration_count) throws Exception{ + String testTag = "applaunch" + iteration_count; + String launchCommand = launch_workload.getLaunchCommand(); + AppLaunch applaunch = new AppLaunch(testTag, launchCommand); + applaunch.startLaunch();//Launch the application and start timer + applaunch.endLaunch();//marks the end of launch and stops timer + } + + /* + * AppLaunch class implements methods that facilitates launching applications + * from the uiautomator. It has methods that are used for one complete iteration of application + * launch instrumentation. + * ActionLogger class is instantiated within the class for measuring applaunch time. + * startLaunch(): Marks the beginning of the application launch, starts Timer + * endLaunch(): Marks the end of application, ends Timer + * launchMain(): Starts the application launch process and validates the finish of launch. + */ + private class AppLaunch { + + private String testTag; + private String launchCommand; + private ActionLogger logger; + Process launch_p; + + public AppLaunch(String testTag, String launchCommand) { + this.testTag = testTag; + this.launchCommand = launchCommand; + this.logger = new ActionLogger(testTag, parameters); + } + + // Called by launchMain() to check if app launch is successful + public void launchValidate(Process launch_p) throws Exception { + launch_p.waitFor(); + Integer exit_val = launch_p.exitValue(); + if (exit_val != 0) { + throw new Exception("Application could not be launched"); + } + } + + // Marks the end of application launch of the workload. + public void endLaunch() throws Exception{ + waitObject(launchEndObject, launch_timeout); + logger.stop(); + launch_p.destroy(); + } + + // Launches the application. + public void launchMain() throws Exception{ + launch_p = Runtime.getRuntime().exec(launchCommand); + + launchValidate(launch_p); + } + + // Beginning of application launch + public void startLaunch() throws Exception{ + logger.start(); + launchMain(); + } + } + + // Exits the application according to application launch type. + public void closeApplication() throws Exception{ + if(applaunchType.equals("launch_from_background")) { + pressHome(); + } + else if(applaunchType.equals("launch_from_long-idle")) { + killApplication(); + dropCaches(); + } + } + + // Kills the application process + public void killApplication() throws Exception{ + Process kill_p; + kill_p = Runtime.getRuntime().exec(String.format("am force-stop %s", packageName)); + kill_p.waitFor(); + kill_p.destroy(); + } + + // Kills the background processes + public void killBackground() throws Exception{ + Process kill_p; + kill_p = Runtime.getRuntime().exec("am kill-all"); + kill_p.waitFor(); + kill_p.destroy(); + } + + // Drop the caches + public void dropCaches() throws Exception{ + Process drop_cache; + drop_cache = Runtime.getRuntime().exec("su sync; su echo 3 > /proc/sys/vm/drop_caches"); + drop_cache.waitFor(); + drop_cache.destroy(); + } +} diff --git a/wlauto/workloads/gmail/com.arm.wlauto.uiauto.gmail.jar b/wlauto/workloads/gmail/com.arm.wlauto.uiauto.gmail.jar index 12e12841..4511eb4c 100644 Binary files a/wlauto/workloads/gmail/com.arm.wlauto.uiauto.gmail.jar and b/wlauto/workloads/gmail/com.arm.wlauto.uiauto.gmail.jar differ diff --git a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index d24f2757..edae608a 100755 --- a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -23,28 +23,24 @@ import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiSelector; import com.arm.wlauto.uiauto.UxPerfUiAutomation; +import com.arm.wlauto.uiauto.ApplaunchInterface; +import com.arm.wlauto.uiauto.UiAutoUtils; import java.util.concurrent.TimeUnit; -public class UiAutomation extends UxPerfUiAutomation { - - public Bundle parameters; - public String packageName; - public String packageID; +public class UiAutomation extends UxPerfUiAutomation implements ApplaunchInterface{ private int networkTimeoutSecs = 30; private long networkTimeout = TimeUnit.SECONDS.toMillis(networkTimeoutSecs); public void runUiAutomation() throws Exception { parameters = getParams(); - packageName = parameters.getString("package"); - packageID = packageName + ":id/"; String recipient = parameters.getString("recipient"); setScreenOrientation(ScreenOrientation.NATURAL); + runApplicationInitialization(); - clearFirstRunDialogues(); clickNewMail(); attachImage(); setToField(recipient); @@ -54,6 +50,31 @@ public class UiAutomation extends UxPerfUiAutomation { unsetScreenOrientation(); } + + // Get application parameters and clear the initial run dialogues of the application launch. + public void runApplicationInitialization() throws Exception { + getPackageParameters(); + clearFirstRunDialogues(); + } + + // Sets the UiObject that marks the end of the application launch. + public UiObject getLaunchEndObject() { + UiObject launchEndObject = + new UiObject(new UiSelector().className("android.widget.ImageButton")); + return launchEndObject; + } + + // Returns the launch command for the application. + public String getLaunchCommand() { + String launch_command; + launch_command = UiAutoUtils.createLaunchCommand(parameters); + return launch_command; + } + + // Pass the workload parameters, used for applaunch + public void setWorkloadParameters(Bundle workload_parameters) { + parameters = workload_parameters; + } public void clearFirstRunDialogues() throws Exception { // The first run dialogues vary on different devices so check if they are there and dismiss