1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-03-24 03:29:02 +00:00

Merge pull request #342 from jimboatarm/applaunch-interface-v2

Applaunch Workload
This commit is contained in:
marcbonnici 2017-02-03 15:25:16 +00:00 committed by GitHub
commit ae0c9fa60b
26 changed files with 839 additions and 425 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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);
}

View File

@ -228,6 +228,9 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
UiDevice.getInstance().pressEnter();
}
public void pressHome() {
UiDevice.getInstance().pressHome();
}
public void pressBack() {
UiDevice.getInstance().pressBack();

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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

View File

@ -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.
<TAG> : <MESSAGE>
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<message>.*\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))

View File

@ -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):
"""

170
wlauto/utils/uxperf.py Normal file
View File

@ -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.
<TAG> : <MESSAGE>
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<message>.*\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))

View File

@ -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",

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="com.arm.wlauto.uiauto.applaunch" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: VERSION_TAG -->
<import file="${sdk.dir}/tools/ant/uibuild.xml" />
</project>

View File

@ -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

View File

@ -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();
}
}

View File

@ -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