mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-04 20:32:36 +01:00
Compare commits
300 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e1e5d466a2 | ||
|
3a863fa143 | ||
|
e548e75017 | ||
|
1b0799bcc5 | ||
|
ea22bc062e | ||
|
ba6889ea7f | ||
|
9a9e8395cf | ||
|
0995e57689 | ||
|
7c920f953c | ||
|
e5417e5b7f | ||
|
d295b7d92d | ||
|
e33245ac72 | ||
|
57a8e62be9 | ||
|
7ce1044eff | ||
|
5a70ab8534 | ||
|
486c0c6887 | ||
|
9e498a911e | ||
|
3d80e4ef34 | ||
|
713fcc44d9 | ||
|
39bc8eae20 | ||
|
adf13feb81 | ||
|
b312bf317d | ||
|
d8a5d2fa37 | ||
|
a3d042e054 | ||
|
afe3c16908 | ||
|
5eee03c689 | ||
|
6266edad6f | ||
|
64426601fe | ||
|
cea39a6193 | ||
|
c952b2f0d4 | ||
|
ea05022f5f | ||
|
cdc7c96cdf | ||
|
2f683e59d2 | ||
|
6915de98f1 | ||
|
27fd2a81d3 | ||
|
a1c19b55b8 | ||
|
890f4309d4 | ||
|
c8cb153f8e | ||
|
10c1f64216 | ||
|
f194ef281b | ||
|
4d607b46c5 | ||
|
cdd0834447 | ||
|
4c0d3f8d20 | ||
|
8dcf1ba6a5 | ||
|
081358769d | ||
|
10a614ff04 | ||
|
58a8ea9051 | ||
|
335fff2a6f | ||
|
cd0863d7fa | ||
|
5cd9bc756c | ||
|
362e93c4cb | ||
|
3f7d44de28 | ||
|
f35c444ddb | ||
|
e35d0f2959 | ||
|
6d9a03ad8f | ||
|
c6cdd8c0e6 | ||
|
8cf4c259c0 | ||
|
77bb14331f | ||
|
dd8843d24a | ||
|
16b302679a | ||
|
f9db0a3798 | ||
|
548eb99bdc | ||
|
f4669e2b20 | ||
|
0578f26bc7 | ||
|
12230da959 | ||
|
afa2e307cc | ||
|
d662498ea8 | ||
|
9e934c6733 | ||
|
596595698d | ||
|
220e0962e1 | ||
|
be3c91b131 | ||
|
efad084b24 | ||
|
6da0550b98 | ||
|
9500bcd914 | ||
|
7f8e7fed4b | ||
|
87972e2605 | ||
|
33874a8f71 | ||
|
93dba8b35d | ||
|
6f629601be | ||
|
565f352114 | ||
|
920d80ad6e | ||
|
9962b413d7 | ||
|
fdff80a72b | ||
|
6cc4626f50 | ||
|
665d76456b | ||
|
f4b91b6c9e | ||
|
4730ac2989 | ||
|
6737fda24e | ||
|
f72bf95612 | ||
|
2a07ed92fe | ||
|
a4c7340746 | ||
|
6eb4e59129 | ||
|
d65561e3e9 | ||
|
64d3ddc8e6 | ||
|
579b940d75 | ||
|
0656e61bc2 | ||
|
13e787b5d0 | ||
|
4d7ef408c9 | ||
|
a533680e49 | ||
|
4ddb6f44fc | ||
|
1238cb71f0 | ||
|
53203bfb50 | ||
|
11859af894 | ||
|
1f5f9cf478 | ||
|
52a07160b9 | ||
|
0d8204121d | ||
|
a47d6c6521 | ||
|
d391b28d2e | ||
|
e42099851b | ||
|
0c0ccb10d9 | ||
|
486494f1f2 | ||
|
919a44baec | ||
|
4f09c3118a | ||
|
b6b1b259dc | ||
|
f1b63b239c | ||
|
797cb3c9ea | ||
|
9bd745b347 | ||
|
9f3ecd3715 | ||
|
1a177dd52f | ||
|
4010618631 | ||
|
1168c1ada8 | ||
|
8b7db92ab8 | ||
|
088102a81d | ||
|
87ce1fd0ae | ||
|
4fac39ada9 | ||
|
81fa5fdf81 | ||
|
2702731532 | ||
|
b95ea60213 | ||
|
8acebe12bd | ||
|
2172db5833 | ||
|
ff67ed1697 | ||
|
ae1a5019dc | ||
|
51db53d979 | ||
|
42a4831092 | ||
|
8e94b79ed8 | ||
|
37d99e4a5d | ||
|
0683427acd | ||
|
64574b1d4c | ||
|
3653e1b507 | ||
|
e62262dbb3 | ||
|
18e47c89c5 | ||
|
389a1ecf76 | ||
|
260e9563b3 | ||
|
48d0e1196c | ||
|
b64c615e8e | ||
|
a4e27128e5 | ||
|
242cf7e33a | ||
|
cc641e8dac | ||
|
bb17590689 | ||
|
4ce38fcd55 | ||
|
bf694ffdf1 | ||
|
b36e0061e1 | ||
|
526ad15c01 | ||
|
0694ab6792 | ||
|
2fd7614d97 | ||
|
c75c102cd2 | ||
|
c86ce64408 | ||
|
d6909c5e6a | ||
|
55e140f20a | ||
|
0c7a7e4dca | ||
|
a7ed00d9ed | ||
|
89aa3e1208 | ||
|
f33fd97705 | ||
|
bf598d1873 | ||
|
c094a47f12 | ||
|
6dab2b48ab | ||
|
1809def83f | ||
|
1158c62c55 | ||
|
d13defe242 | ||
|
a9c5ab9bc8 | ||
|
27af97b795 | ||
|
a0a189044e | ||
|
eca6c10a75 | ||
|
01efbe1807 | ||
|
81f7f92b84 | ||
|
95c98fd458 | ||
|
428abfb6a6 | ||
|
a8bf8458ec | ||
|
862eeadb39 | ||
|
ec66e1d166 | ||
|
3e4ffa8cd5 | ||
|
dc14e6ed11 | ||
|
47ccae1a6b | ||
|
a2eef179ef | ||
|
7eb36b959b | ||
|
b5563113b1 | ||
|
5720d3f214 | ||
|
5b82b90939 | ||
|
bfcb829ab0 | ||
|
70f646f87d | ||
|
3f03dec7af | ||
|
076772da4d | ||
|
138c95745e | ||
|
f517e6c4da | ||
|
8125c3e0a2 | ||
|
81bdc7251b | ||
|
b948d28c62 | ||
|
bc6af25366 | ||
|
5d8305cfdc | ||
|
e038fb924a | ||
|
51c92cb2f5 | ||
|
e98b653b3e | ||
|
b1da0fe958 | ||
|
b10b5970c3 | ||
|
e866cfed12 | ||
|
f5b40e3d64 | ||
|
cc3a815cb7 | ||
|
9ef8bddc4b | ||
|
a3011accf8 | ||
|
317da0a1c4 | ||
|
7c79c24865 | ||
|
6c5e52ad56 | ||
|
1019b30174 | ||
|
50236b0661 | ||
|
6cfcb89827 | ||
|
cf0a4d3ff3 | ||
|
06764dff00 | ||
|
8f2ff4d2f8 | ||
|
9a9de99c5f | ||
|
f52aa0f7e9 | ||
|
935edcdd01 | ||
|
869c0bc6bd | ||
|
57ab97df89 | ||
|
773bf26ff2 | ||
|
2822949517 | ||
|
4dc0aaa724 | ||
|
35df2fff30 | ||
|
4abbe7602a | ||
|
55e40ded1c | ||
|
441ecfa98c | ||
|
7130b3a4ab | ||
|
510abf4666 | ||
|
70b265dd34 | ||
|
ddbd94b2a8 | ||
|
c4025cad66 | ||
|
38b368eec2 | ||
|
2e97bcce70 | ||
|
9b13f65895 | ||
|
78c1a80a51 | ||
|
7f85da6633 | ||
|
f76d9f9a1f | ||
|
6ffed382cf | ||
|
6e7087ee88 | ||
|
96c6f010f8 | ||
|
8f1206678a | ||
|
d10e51e30b | ||
|
bd0a6ac3c8 | ||
|
9ba30c27df | ||
|
8f40e6aa40 | ||
|
8c9ae6db53 | ||
|
6ac0c20619 | ||
|
cfa1827445 | ||
|
821cbaff4e | ||
|
c7db9a4543 | ||
|
6f67f00834 | ||
|
40db6a9933 | ||
|
543228f7c3 | ||
|
58b97305d5 | ||
|
0246302814 | ||
|
0aee99a79c | ||
|
5012ab5f1b | ||
|
b7b2406a25 | ||
|
cd05c36250 | ||
|
b169b45e57 | ||
|
40cc830de1 | ||
|
1ebe56ca76 | ||
|
79f58e74b9 | ||
|
7fe699eb41 | ||
|
2bd77f0833 | ||
|
7b7eb05e7f | ||
|
ae0c9fa60b | ||
|
75b56e462c | ||
|
d8438c5ae8 | ||
|
ca7a1abe78 | ||
|
bd37973442 | ||
|
c38dc287ab | ||
|
3feb702898 | ||
|
0f57dee6bf | ||
|
99b46b4630 | ||
|
4ce20e2d3f | ||
|
65aa00483b | ||
|
da7a3276ed | ||
|
b89d9bbc4b | ||
|
54bffb45ab | ||
|
e7a47f602d | ||
|
490dd404ae | ||
|
60f52c2187 | ||
|
fbb9908298 | ||
|
01fa0a3571 | ||
|
be2b14a575 | ||
|
dab2bbb1c7 | ||
|
340ae72ca2 | ||
|
e64dc8bc26 | ||
|
cb48ece77f | ||
|
8f67b7f94b | ||
|
fa553ee430 | ||
|
01c9c88e79 | ||
|
0c32e39ce0 | ||
|
51f07c4473 | ||
|
ffde7401ef |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
*.bak
|
||||
*.o
|
||||
*.cmd
|
||||
*.iml
|
||||
Module.symvers
|
||||
modules.order
|
||||
*~
|
||||
@@ -14,9 +15,12 @@ wa_output/
|
||||
doc/source/api/
|
||||
doc/source/extensions/
|
||||
MANIFEST
|
||||
wlauto/external/uiauto/bin/
|
||||
wlauto/external/uiauto/**/build/
|
||||
wlauto/external/uiauto/*.properties
|
||||
wlauto/external/uiauto/build.xml
|
||||
wlauto/external/uiauto/app/libs/
|
||||
wlauto/external/uiauto/app/proguard-rules.pro
|
||||
wlauto/external/uiauto/**/.gradle
|
||||
wlauto/external/uiauto/**/.idea
|
||||
*.orig
|
||||
local.properties
|
||||
wlauto/external/revent/libs/
|
||||
@@ -27,4 +31,9 @@ pmu_logger.mod.c
|
||||
.tmp_versions
|
||||
obj/
|
||||
libs/armeabi
|
||||
wlauto/workloads/*/uiauto/bin/
|
||||
wlauto/workloads/*/uiauto/**/build/
|
||||
wlauto/workloads/*/uiauto/*.properties
|
||||
wlauto/workloads/*/uiauto/app/libs/
|
||||
wlauto/workloads/*/uiauto/app/proguard-rules.pro
|
||||
wlauto/workloads/*/uiauto/**/.gradle
|
||||
wlauto/workloads/*/uiauto/**/.idea
|
||||
|
16
README.rst
16
README.rst
@@ -1,6 +1,19 @@
|
||||
Workload Automation
|
||||
+++++++++++++++++++
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<span style="border: solid red 3px">
|
||||
<p align="center">
|
||||
<font color="red">DO NOT USE "MASTER" BRANCH</font>. WA2 has been
|
||||
deprecated in favor of WA3, which is currently on "next" branch.
|
||||
</p>
|
||||
<p align="center">
|
||||
The master branch will be aligned with next shortly. In the mean time,
|
||||
please <font style="bold">USE "NEXT" BRANCH</font> instead.
|
||||
</p>
|
||||
</span>
|
||||
|
||||
Workload Automation (WA) is a framework for executing workloads and collecting
|
||||
measurements on Android and Linux devices. WA includes automation for nearly 50
|
||||
workloads (mostly Android), some common instrumentation (ftrace, ARM
|
||||
@@ -46,7 +59,8 @@ documentation.
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can view pre-built HTML documentation `here <http://pythonhosted.org/wlauto/>`_.
|
||||
You can view pre-built HTML documentation
|
||||
`here <http://workload-automation.readthedocs.io/en/latest/>`_.
|
||||
|
||||
Documentation in reStructuredText format may be found under ``doc/source``. To
|
||||
compile it into cross-linked HTML, make sure you have `Sphinx
|
||||
|
@@ -10,22 +10,29 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from wlauto.exceptions import WAError, ToolError
|
||||
from wlauto.utils.doc import format_simple_table
|
||||
from wlauto.utils.misc import check_output, get_null, which
|
||||
|
||||
|
||||
def get_aapt_path():
|
||||
"""Return the full path to aapt tool."""
|
||||
sdk_path = os.getenv('ANDROID_HOME')
|
||||
if not sdk_path:
|
||||
raise ToolError('Please make sure you have Android SDK installed and have ANDROID_HOME set.')
|
||||
build_tools_directory = os.path.join(sdk_path, 'build-tools')
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logging.debug('Found aapt for version {}'.format(version))
|
||||
return aapt_path
|
||||
if sdk_path:
|
||||
build_tools_directory = os.path.join(sdk_path, 'build-tools')
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logging.debug('Found aapt for version {}'.format(version))
|
||||
return aapt_path
|
||||
else:
|
||||
raise ToolError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
else:
|
||||
raise ToolError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
logging.debug("ANDROID_HOME is not set, try to find in $PATH.")
|
||||
aapt_path = which('aapt')
|
||||
if aapt_path:
|
||||
logging.debug('Using aapt from {}'.format(aapt_path))
|
||||
return aapt_path
|
||||
raise ToolError('Please make sure you have Android SDK installed and have ANDROID_HOME set.')
|
||||
|
||||
|
||||
def get_apks(path):
|
||||
|
59
doc/Makefile
59
doc/Makefile
@@ -7,18 +7,11 @@ SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
SPHINXAPI = sphinx-apidoc
|
||||
SPHINXAPIOPTS =
|
||||
|
||||
WAEXT = ./build_extension_docs.py
|
||||
WAEXTOPTS = source/extensions ../wlauto ../wlauto/external ../wlauto/tests
|
||||
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
ALLSPHINXAPIOPTS = -f $(SPHINXAPIOPTS) -o source/api ../wlauto
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
@@ -58,52 +51,38 @@ coverage:
|
||||
@echo
|
||||
@echo "Build finished. The coverage reports are in $(BUILDDIR)/coverage."
|
||||
|
||||
api: ../wlauto
|
||||
rm -rf source/api/*
|
||||
$(SPHINXAPI) $(ALLSPHINXAPIOPTS)
|
||||
|
||||
waext: ../wlauto
|
||||
rm -rf source/extensions
|
||||
mkdir -p source/extensions
|
||||
$(WAEXT) $(WAEXTOPTS)
|
||||
|
||||
|
||||
sigtab: ../wlauto/core/instrumentation.py source/instrumentation_method_map.template
|
||||
rm -rf source/instrumentation_method_map.rst
|
||||
./build_instrumentation_method_map.py source/instrumentation_method_map.rst
|
||||
|
||||
html: api waext sigtab
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml: api waext sigtab
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml: api waext sigtab
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle: api waext sigtab
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json: api waext sigtab
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp: api waext sigtab
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp: api waext sigtab
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
@@ -112,7 +91,7 @@ qthelp: api waext sigtab
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WorkloadAutomation2.qhc"
|
||||
|
||||
devhelp: api
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@@ -121,64 +100,64 @@ devhelp: api
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WorkloadAutomation2"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub: api waext sigtab
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex: api waext sigtab
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf: api waext sigtab
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text: api waext sigtab
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man: api waext sigtab
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo: api waext sigtab
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info: api waext sigtab
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext: api waext sigtab
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes: api waext sigtab
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck: api waext sigtab
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest: api waext sigtab
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
@@ -17,6 +17,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
from wlauto import ExtensionLoader
|
||||
from wlauto.utils.doc import get_rst_from_extension, underline
|
||||
@@ -30,6 +31,9 @@ def generate_extension_documentation(source_dir, outdir, ignore_paths):
|
||||
loader = ExtensionLoader(keep_going=True)
|
||||
loader.clear()
|
||||
loader.update(paths=[source_dir], ignore_paths=ignore_paths)
|
||||
if os.path.exists(outdir):
|
||||
shutil.rmtree(outdir)
|
||||
os.makedirs(outdir)
|
||||
for ext_type in loader.extension_kinds:
|
||||
if not ext_type in GENERATE_FOR:
|
||||
continue
|
||||
|
@@ -16,7 +16,6 @@
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
from copy import copy
|
||||
|
||||
from wlauto.core.instrumentation import SIGNAL_MAP, PRIORITY_MAP
|
||||
from wlauto.utils.doc import format_simple_table
|
||||
@@ -25,7 +24,7 @@ from wlauto.utils.doc import format_simple_table
|
||||
CONVINIENCE_ALIASES = ['initialize', 'setup', 'start', 'stop', 'process_workload_result',
|
||||
'update_result', 'teardown', 'finalize']
|
||||
|
||||
OUTPUT_TEMPLATE_FILE = os.path.join(os.path.dirname(__file__), 'source', 'instrumentation_method_map.template')
|
||||
OUTPUT_TEMPLATE_FILE = os.path.join(os.path.dirname(__file__), 'source', 'instrumentation_method_map.template')
|
||||
|
||||
|
||||
def escape_trailing_underscore(value):
|
||||
@@ -37,7 +36,9 @@ def generate_instrumentation_method_map(outfile):
|
||||
signal_table = format_simple_table([(k, v) for k, v in SIGNAL_MAP.iteritems()],
|
||||
headers=['method name', 'signal'], align='<<')
|
||||
priority_table = format_simple_table([(escape_trailing_underscore(k), v) for k, v in PRIORITY_MAP.iteritems()],
|
||||
headers=['prefix', 'priority'], align='<>')
|
||||
headers=['prefix', 'priority'], align='<>')
|
||||
if os.path.isfile(outfile):
|
||||
os.unlink(outfile)
|
||||
with open(OUTPUT_TEMPLATE_FILE) as fh:
|
||||
template = string.Template(fh.read())
|
||||
with open(outfile, 'w') as wfh:
|
||||
|
2
doc/requirements.txt
Normal file
2
doc/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
nose
|
||||
sphinx==1.6.5
|
@@ -147,7 +147,7 @@ to 15 million. You can specify this using dhrystone's parameters:
|
||||
|
||||
wa show dhrystone
|
||||
|
||||
see the :ref:`Invocation` section for details.
|
||||
see the :ref:`Invocation <invocation>` section for details.
|
||||
|
||||
In addition to configuring the workload itself, we can also specify
|
||||
configuration for the underlying device. This can be done by setting runtime
|
||||
|
@@ -7,8 +7,8 @@ APK resolution
|
||||
--------------
|
||||
|
||||
WA has various resource getters that can be configured to locate APK files but for most people APK files
|
||||
should be kept in the ``$WA_HOME/dependencies/SOME_WORKLOAD/`` directory. (by default
|
||||
``~/.workload_automation/dependencies/SOME_WORKLOAD/``). The ``WA_HOME`` enviroment variable can be used
|
||||
should be kept in the ``$WA_USER_DIRECTORY/dependencies/SOME_WORKLOAD/`` directory. (by default
|
||||
``~/.workload_automation/dependencies/SOME_WORKLOAD/``). The ``WA_USER_DIRECTORY`` enviroment variable can be used
|
||||
to chnage the location of this folder. The APK files need to be put into the corresponding directories
|
||||
for the workload they belong to. The name of the file can be anything but as explained below may need
|
||||
to contain certain peices of information.
|
||||
|
@@ -28,12 +28,16 @@
|
||||
|
||||
import sys, os
|
||||
import warnings
|
||||
from sphinx.apidoc import main
|
||||
|
||||
warnings.filterwarnings('ignore', "Module louie was already imported")
|
||||
|
||||
this_dir = os.path.dirname(__file__)
|
||||
sys.path.insert(0, os.path.join(this_dir, '..'))
|
||||
sys.path.insert(0, os.path.join(this_dir, '../..'))
|
||||
import wlauto
|
||||
from build_extension_docs import generate_extension_documentation
|
||||
from build_instrumentation_method_map import generate_instrumentation_method_map
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -264,7 +268,21 @@ texinfo_documents = [
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
def run_apidoc(_):
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
cur_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
api_output = os.path.join(cur_dir, 'api')
|
||||
module = os.path.join(cur_dir, '..', '..', 'wlauto')
|
||||
main(['-f', '-o', api_output, module, '--force'])
|
||||
|
||||
def setup(app):
|
||||
module_dir = os.path.join('..', '..', 'wlauto')
|
||||
excluded_extensions = [os.path.join(module_dir, 'external'),
|
||||
os.path.join(module_dir, 'tests')]
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
app.connect('builder-inited', run_apidoc)
|
||||
generate_instrumentation_method_map('instrumentation_method_map.rst')
|
||||
generate_extension_documentation(module_dir, 'extensions', excluded_extensions)
|
||||
app.add_object_type('confval', 'confval',
|
||||
objname='configuration value',
|
||||
indextemplate='pair: %s; configuration value')
|
||||
|
@@ -964,7 +964,7 @@ that accompanies them, in addition to what has been outlined here, should
|
||||
provide enough guidance.
|
||||
|
||||
:commands: This allows extending WA with additional sub-commands (to supplement
|
||||
exiting ones outlined in the :ref:`invocation` section).
|
||||
exiting ones outlined in the :ref:`invocation <invocation>` section).
|
||||
:modules: Modules are "extensions for extensions". They can be loaded by other
|
||||
extensions to expand their functionality (for example, a flashing
|
||||
module maybe loaded by a device in order to support flashing).
|
||||
|
@@ -22,7 +22,6 @@ import textwrap
|
||||
import argparse
|
||||
import shutil
|
||||
import getpass
|
||||
import subprocess
|
||||
from collections import OrderedDict
|
||||
|
||||
import yaml
|
||||
@@ -30,8 +29,9 @@ import yaml
|
||||
from wlauto import ExtensionLoader, Command, settings
|
||||
from wlauto.exceptions import CommandError, ConfigError
|
||||
from wlauto.utils.cli import init_argument_parser
|
||||
from wlauto.utils.misc import (capitalize, check_output,
|
||||
ensure_file_directory_exists as _f, ensure_directory_exists as _d)
|
||||
from wlauto.utils.misc import (capitalize,
|
||||
ensure_file_directory_exists as _f,
|
||||
ensure_directory_exists as _d)
|
||||
from wlauto.utils.types import identifier
|
||||
from wlauto.utils.doc import format_body
|
||||
|
||||
@@ -41,20 +41,6 @@ __all__ = ['create_workload']
|
||||
|
||||
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
|
||||
UIAUTO_BUILD_SCRIPT = """#!/bin/bash
|
||||
|
||||
class_dir=bin/classes/com/arm/wlauto/uiauto
|
||||
base_class=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', 'BaseUiAutomation.class')"`
|
||||
mkdir -p $$class_dir
|
||||
cp $$base_class $$class_dir
|
||||
|
||||
ant build
|
||||
|
||||
if [[ -f bin/${package_name}.jar ]]; then
|
||||
cp bin/${package_name}.jar ..
|
||||
fi
|
||||
"""
|
||||
|
||||
|
||||
class CreateSubcommand(object):
|
||||
|
||||
@@ -321,7 +307,7 @@ def create_basic_workload(path, name, class_name):
|
||||
|
||||
|
||||
def create_uiautomator_workload(path, name, class_name):
|
||||
uiauto_path = _d(os.path.join(path, 'uiauto'))
|
||||
uiauto_path = os.path.join(path, 'uiauto')
|
||||
create_uiauto_project(uiauto_path, name)
|
||||
source_file = os.path.join(path, '__init__.py')
|
||||
with open(source_file, 'w') as wfh:
|
||||
@@ -335,37 +321,34 @@ def create_android_benchmark(path, name, class_name):
|
||||
|
||||
|
||||
def create_android_uiauto_benchmark(path, name, class_name):
|
||||
uiauto_path = _d(os.path.join(path, 'uiauto'))
|
||||
uiauto_path = os.path.join(path, 'uiauto')
|
||||
create_uiauto_project(uiauto_path, name)
|
||||
source_file = os.path.join(path, '__init__.py')
|
||||
with open(source_file, 'w') as wfh:
|
||||
wfh.write(render_template('android_uiauto_benchmark', {'name': name, 'class_name': class_name}))
|
||||
|
||||
|
||||
def create_uiauto_project(path, name, target='1'):
|
||||
sdk_path = get_sdk_path()
|
||||
android_path = os.path.join(sdk_path, 'tools', 'android')
|
||||
def create_uiauto_project(path, name):
|
||||
package_name = 'com.arm.wlauto.uiauto.' + name.lower()
|
||||
|
||||
# ${ANDROID_HOME}/tools/android create uitest-project -n com.arm.wlauto.uiauto.linpack -t 1 -p ../test2
|
||||
command = '{} create uitest-project --name {} --target {} --path {}'.format(android_path,
|
||||
package_name,
|
||||
target,
|
||||
path)
|
||||
try:
|
||||
check_output(command, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if 'is is not valid' in e.output:
|
||||
message = 'No Android SDK target found; have you run "{} update sdk" and download a platform?'
|
||||
raise CommandError(message.format(android_path))
|
||||
shutil.copytree(os.path.join(TEMPLATES_DIR, 'uiauto_template'), path)
|
||||
|
||||
manifest_path = os.path.join(path, 'app', 'src', 'main')
|
||||
mainifest = os.path.join(_d(manifest_path), 'AndroidManifest.xml')
|
||||
with open(mainifest, 'w') as wfh:
|
||||
wfh.write(render_template('uiauto_AndroidManifest.xml', {'package_name': package_name}))
|
||||
|
||||
build_gradle_path = os.path.join(path, 'app')
|
||||
build_gradle = os.path.join(_d(build_gradle_path), 'build.gradle')
|
||||
with open(build_gradle, 'w') as wfh:
|
||||
wfh.write(render_template('uiauto_build.gradle', {'package_name': package_name}))
|
||||
|
||||
build_script = os.path.join(path, 'build.sh')
|
||||
with open(build_script, 'w') as wfh:
|
||||
template = string.Template(UIAUTO_BUILD_SCRIPT)
|
||||
wfh.write(template.substitute({'package_name': package_name}))
|
||||
wfh.write(render_template('uiauto_build_script', {'package_name': package_name}))
|
||||
os.chmod(build_script, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
|
||||
source_file = _f(os.path.join(path, 'src',
|
||||
source_file = _f(os.path.join(path, 'app', 'src', 'main', 'java',
|
||||
os.sep.join(package_name.split('.')[:-1]),
|
||||
'UiAutomation.java'))
|
||||
with open(source_file, 'w') as wfh:
|
||||
|
@@ -74,7 +74,7 @@ class RecordCommand(ReventCommand):
|
||||
name = 'record'
|
||||
description = '''Performs a revent recording
|
||||
|
||||
This command helps making revent recordings. It will automatically
|
||||
This command helps create revent recordings. It will automatically
|
||||
deploy revent and even has the option of automatically opening apps.
|
||||
|
||||
Revent allows you to record raw inputs such as screen swipes or button presses.
|
||||
@@ -82,14 +82,14 @@ class RecordCommand(ReventCommand):
|
||||
have XML UI layouts that can be used with UIAutomator. As a drawback from this,
|
||||
revent recordings are specific to the device type they were recorded on.
|
||||
|
||||
WA uses two parts to the names of revent recordings in the format,
|
||||
{device_name}.{suffix}.revent.
|
||||
WA uses two parts to form the names of revent recordings in the format,
|
||||
{device_name}.{suffix}.revent
|
||||
|
||||
- device_name can either be specified manually with the ``-d`` argument or
|
||||
it can be automatically determined. On Android device it will be obtained
|
||||
else the name of the device will be automatically determined. On an Android device it is obtained
|
||||
from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``.
|
||||
- suffix is used by WA to determine which part of the app execution the
|
||||
recording is for, currently these are either ``setup`` or ``run``. This
|
||||
recording is for, currently either ``setup``, ``run`` or ``teardown``. This
|
||||
should be specified with the ``-s`` argument.
|
||||
|
||||
|
||||
|
@@ -2,23 +2,30 @@ package ${package_name};
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
// Import the uiautomator libraries
|
||||
import com.android.uiautomator.core.UiObject;
|
||||
import com.android.uiautomator.core.UiObjectNotFoundException;
|
||||
import com.android.uiautomator.core.UiScrollable;
|
||||
import com.android.uiautomator.core.UiSelector;
|
||||
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||
import android.support.test.uiautomator.UiScrollable;
|
||||
import android.support.test.uiautomator.UiSelector;
|
||||
|
||||
|
||||
import com.arm.wlauto.uiauto.BaseUiAutomation;
|
||||
|
||||
public class UiAutomation extends BaseUiAutomation {
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UiAutomation extends BaseUiAutomation {
|
||||
|
||||
public static String TAG = "${name}";
|
||||
|
||||
public void runUiAutomation() throws Exception {
|
||||
@Test
|
||||
public void runUiAutomation() throws Exception {
|
||||
initialize_instrumentation();
|
||||
// UI Automation code goes here
|
||||
}
|
||||
|
||||
|
12
wlauto/commands/templates/uiauto_AndroidManifest.xml
Normal file
12
wlauto/commands/templates/uiauto_AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="${package_name}"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
|
||||
<instrumentation
|
||||
android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="${package_name}"/>
|
||||
|
||||
</manifest>
|
33
wlauto/commands/templates/uiauto_build.gradle
Normal file
33
wlauto/commands/templates/uiauto_build.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 18
|
||||
buildToolsVersion '25.0.0'
|
||||
defaultConfig {
|
||||
applicationId "${package_name}"
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 25
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.outputFile = file("$$project.buildDir/apk/${package_name}.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
compile 'com.android.support.test:runner:0.5'
|
||||
compile 'com.android.support.test:rules:0.5'
|
||||
compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
compile(name: 'uiauto', ext: 'aar')
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
39
wlauto/commands/templates/uiauto_build_script
Normal file
39
wlauto/commands/templates/uiauto_build_script
Normal 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 gradelw exists before starting
|
||||
if [[ ! -f gradlew ]]; then
|
||||
echo 'gradlew file not found! Check that you are in the right directory.'
|
||||
exit 9
|
||||
fi
|
||||
|
||||
# Copy base class library from wlauto dist
|
||||
libs_dir=app/libs
|
||||
base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.aar')"`
|
||||
mkdir -p $$libs_dir
|
||||
cp $$base_classes $$libs_dir
|
||||
|
||||
# Build and return appropriate exit code if failed
|
||||
# gradle build
|
||||
./gradlew clean :app:assembleDebug
|
||||
exit_code=$$?
|
||||
if [[ $$exit_code -ne 0 ]]; then
|
||||
echo "ERROR: 'gradle build' exited with code $$exit_code"
|
||||
exit $$exit_code
|
||||
fi
|
||||
|
||||
# If successful move APK file to workload folder (overwrite previous)
|
||||
rm -f ../$package_name
|
||||
if [[ -f app/build/apk/$package_name.apk ]]; then
|
||||
cp app/build/apk/$package_name.apk ../$package_name.apk
|
||||
else
|
||||
echo 'ERROR: UiAutomator apk could not be found!'
|
||||
exit 9
|
||||
fi
|
23
wlauto/commands/templates/uiauto_template/build.gradle
Normal file
23
wlauto/commands/templates/uiauto_template/build.gradle
Normal file
@@ -0,0 +1,23 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
BIN
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed May 03 15:42:44 BST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
160
wlauto/commands/templates/uiauto_template/gradlew
vendored
Executable file
160
wlauto/commands/templates/uiauto_template/gradlew
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
wlauto/commands/templates/uiauto_template/gradlew.bat
vendored
Normal file
90
wlauto/commands/templates/uiauto_template/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@@ -0,0 +1 @@
|
||||
include ':app'
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -30,7 +30,7 @@ from wlauto.common.resources import Executable
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.linux.device import BaseLinuxDevice, PsEntry
|
||||
from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError
|
||||
from wlauto.utils.misc import convert_new_lines, ABI_MAP
|
||||
from wlauto.utils.misc import convert_new_lines, ABI_MAP, commonprefix
|
||||
from wlauto.utils.types import boolean, regex
|
||||
from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices,
|
||||
adb_command, AndroidProperties, ANDROID_VERSION_MAP)
|
||||
@@ -390,7 +390,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
else:
|
||||
return self.install_executable(filepath, with_name)
|
||||
|
||||
def install_apk(self, filepath, timeout=default_timeout, replace=False, allow_downgrade=False): # pylint: disable=W0221
|
||||
def install_apk(self, filepath, timeout=300, replace=False, allow_downgrade=False): # pylint: disable=W0221
|
||||
self._check_ready()
|
||||
ext = os.path.splitext(filepath)[1].lower()
|
||||
if ext == '.apk':
|
||||
@@ -550,11 +550,10 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
props['android_id'] = self.get_android_id()
|
||||
self._update_build_properties(props)
|
||||
|
||||
dumpsys_target_file = self.path.join(self.working_directory, 'window.dumpsys')
|
||||
dumpsys_host_file = os.path.join(context.host_working_directory, 'window.dumpsys')
|
||||
self.execute('{} > {}'.format('dumpsys window', dumpsys_target_file))
|
||||
self.pull_file(dumpsys_target_file, dumpsys_host_file)
|
||||
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
||||
with open(dumpsys_host_file, 'w') as wfh:
|
||||
wfh.write(self.execute('dumpsys window'))
|
||||
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
||||
|
||||
prop_file = os.path.join(context.host_working_directory, 'android-props.json')
|
||||
with open(prop_file, 'w') as wfh:
|
||||
@@ -717,23 +716,40 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def broadcast_media_mounted(self, dirpath):
|
||||
def refresh_device_files(self, file_list):
|
||||
"""
|
||||
Depending on the devices android version and root status, determine the
|
||||
appropriate method of forcing a re-index of the mediaserver cache for a given
|
||||
list of files.
|
||||
"""
|
||||
if self.device.is_rooted or self.device.get_sdk_version() < 24: # MM and below
|
||||
common_path = commonprefix(file_list, sep=self.device.path.sep)
|
||||
self.broadcast_media_mounted(common_path, self.device.is_rooted)
|
||||
else:
|
||||
for f in file_list:
|
||||
self.broadcast_media_scan_file(f)
|
||||
|
||||
def broadcast_media_scan_file(self, filepath):
|
||||
"""
|
||||
Force a re-index of the mediaserver cache for the specified file.
|
||||
"""
|
||||
command = 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://'
|
||||
self.execute(command + filepath)
|
||||
|
||||
def broadcast_media_mounted(self, dirpath, as_root=False):
|
||||
"""
|
||||
Force a re-index of the mediaserver cache for the specified directory.
|
||||
"""
|
||||
command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://'
|
||||
self.execute(command + dirpath)
|
||||
command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://'
|
||||
self.execute(command + dirpath, as_root=as_root)
|
||||
|
||||
# Internal methods: do not use outside of the class.
|
||||
|
||||
def _update_build_properties(self, props):
|
||||
try:
|
||||
def strip(somestring):
|
||||
return somestring.strip().replace('[', '').replace(']', '')
|
||||
for line in self.execute("getprop").splitlines():
|
||||
key, value = line.split(':', 1)
|
||||
key = strip(key)
|
||||
value = strip(value)
|
||||
regex = re.compile(r'\[([^\]]+)\]\s*:\s*\[([^\]]+)\]')
|
||||
for match in regex.finditer(self.execute("getprop")):
|
||||
key = match.group(1).strip()
|
||||
value = match.group(2).strip()
|
||||
props[key] = value
|
||||
except ValueError:
|
||||
self.logger.warning('Could not parse build.prop.')
|
||||
|
@@ -35,9 +35,12 @@ class ApkFile(FileResource):
|
||||
|
||||
name = 'apk'
|
||||
|
||||
def __init__(self, owner, platform=None):
|
||||
def __init__(self, owner, platform=None, uiauto=False, package=None):
|
||||
super(ApkFile, self).__init__(owner)
|
||||
self.platform = platform
|
||||
self.uiauto = uiauto
|
||||
self.package = package
|
||||
|
||||
def __str__(self):
|
||||
return '<{}\'s {} APK>'.format(self.owner, self.platform)
|
||||
apk_type = 'uiautomator ' if self.uiauto else ''
|
||||
return '<{}\'s {} {}APK>'.format(self.owner, self.platform, apk_type)
|
||||
|
BIN
wlauto/common/android/uiauto.aar
Normal file
BIN
wlauto/common/android/uiauto.aar
Normal file
Binary file not shown.
@@ -16,25 +16,24 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from math import ceil
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from wlauto.core.extension import Parameter, ExtensionMeta, ListCollection
|
||||
from wlauto.core.workload import Workload
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.android.resources import ApkFile, ReventFile
|
||||
from wlauto.common.resources import ExtensionAsset, Executable, File
|
||||
from wlauto.common.android.resources import ApkFile
|
||||
from wlauto.common.resources import ExtensionAsset, File
|
||||
from wlauto.exceptions import WorkloadError, ResourceError, DeviceError
|
||||
from wlauto.utils.android import ApkInfo, ANDROID_NORMAL_PERMISSIONS, UNSUPPORTED_PACKAGES
|
||||
from wlauto.utils.types import boolean
|
||||
from wlauto.utils.revent import ReventRecording
|
||||
from wlauto.utils.android import (ApkInfo, ANDROID_NORMAL_PERMISSIONS,
|
||||
ANDROID_UNCHANGEABLE_PERMISSIONS, UNSUPPORTED_PACKAGES)
|
||||
from wlauto.utils.types import boolean, ParameterDict
|
||||
import wlauto.utils.statedetect as state_detector
|
||||
import wlauto.common.android.resources
|
||||
from wlauto.common.linux.workload import ReventWorkload
|
||||
|
||||
|
||||
DELAY = 5
|
||||
|
||||
|
||||
# Due to the way `super` works you have to call it at every level but WA executes some
|
||||
# methods conditionally and so has to call them directly via the class, this breaks super
|
||||
# and causes it to run things mutiple times ect. As a work around for this untill workloads
|
||||
@@ -43,12 +42,12 @@ DELAY = 5
|
||||
|
||||
class UiAutomatorWorkload(Workload):
|
||||
"""
|
||||
Base class for all workloads that rely on a UI Automator JAR file.
|
||||
Base class for all workloads that rely on a UI Automator APK file.
|
||||
|
||||
This class should be subclassed by workloads that rely on android UiAutomator
|
||||
to work. This class handles transferring the UI Automator JAR file to the device
|
||||
and invoking it to run the workload. By default, it will look for the JAR file in
|
||||
the same directory as the .py file for the workload (this can be changed by overriding
|
||||
to work. This class handles installing the UI Automator APK to the device
|
||||
and invoking it to run the workload. By default, it will look for the ``*.apk`` file
|
||||
in the same directory as the .py file for the workload (this can be changed by overriding
|
||||
the ``uiauto_file`` property in the subclassing workload).
|
||||
|
||||
To inintiate UI Automation, the fully-qualified name of the Java class and the
|
||||
@@ -60,7 +59,7 @@ class UiAutomatorWorkload(Workload):
|
||||
match what is expected, or you could override ``uiauto_package``, ``uiauto_class`` and
|
||||
``uiauto_method`` class attributes with the value that match your Java code.
|
||||
|
||||
You can also pass parameters to the JAR file. To do this add the parameters to
|
||||
You can also pass parameters to the APK file. To do this add the parameters to
|
||||
``self.uiauto_params`` dict inside your class's ``__init__`` or ``setup`` methods.
|
||||
|
||||
"""
|
||||
@@ -69,39 +68,43 @@ class UiAutomatorWorkload(Workload):
|
||||
|
||||
uiauto_package = ''
|
||||
uiauto_class = 'UiAutomation'
|
||||
uiauto_method = 'runUiAutomation'
|
||||
|
||||
uiauto_method = 'android.support.test.runner.AndroidJUnitRunner'
|
||||
# Can be overidden by subclasses to adjust to run time of specific
|
||||
# benchmarks.
|
||||
run_timeout = 4 * 60 # seconds
|
||||
run_timeout = 10 * 60 # seconds
|
||||
uninstall_uiauto_apk = True
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs): # pylint: disable=W0613
|
||||
if _call_super:
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
self.uiauto_file = None
|
||||
self.device_uiauto_file = None
|
||||
self.command = None
|
||||
self.uiauto_params = {}
|
||||
self.uiauto_params = ParameterDict()
|
||||
|
||||
def init_resources(self, context):
|
||||
self.uiauto_file = context.resolver.get(wlauto.common.android.resources.JarFile(self))
|
||||
self.uiauto_file = context.resolver.get(ApkFile(self, uiauto=True))
|
||||
if not self.uiauto_file:
|
||||
raise ResourceError('No UI automation JAR file found for workload {}.'.format(self.name))
|
||||
self.device_uiauto_file = self.device.path.join(self.device.working_directory,
|
||||
os.path.basename(self.uiauto_file))
|
||||
raise ResourceError('No UI automation APK file found for workload {}.'.format(self.name))
|
||||
|
||||
if not self.uiauto_package:
|
||||
self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0]
|
||||
|
||||
def setup(self, context):
|
||||
Workload.setup(self, context)
|
||||
method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method)
|
||||
params_dict = self.uiauto_params
|
||||
params_dict['workdir'] = self.device.working_directory
|
||||
params = ''
|
||||
for k, v in self.uiauto_params.iteritems():
|
||||
for k, v in self.uiauto_params.iter_encoded_items():
|
||||
params += ' -e {} "{}"'.format(k, v)
|
||||
self.command = 'uiautomator runtest {}{} -c {}'.format(self.device_uiauto_file, params, method_string)
|
||||
self.device.push_file(self.uiauto_file, self.device_uiauto_file)
|
||||
|
||||
if self.device.package_is_installed(self.uiauto_package):
|
||||
self.device.uninstall(self.uiauto_package)
|
||||
self.device.install_apk(self.uiauto_file)
|
||||
|
||||
instrumention_string = 'am instrument -w -r {} -e class {}.{} {}/{}'
|
||||
self.command = instrumention_string.format(params, self.uiauto_package,
|
||||
self.uiauto_class, self.uiauto_package,
|
||||
self.uiauto_method)
|
||||
self.device.killall('uiautomator')
|
||||
|
||||
def run(self, context):
|
||||
@@ -116,11 +119,12 @@ class UiAutomatorWorkload(Workload):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
self.device.delete_file(self.device_uiauto_file)
|
||||
if self.uninstall_uiauto_apk:
|
||||
self.device.uninstall(self.uiauto_package)
|
||||
|
||||
def validate(self):
|
||||
if not self.uiauto_file:
|
||||
raise WorkloadError('No UI automation JAR file found for workload {}.'.format(self.name))
|
||||
raise WorkloadError('No UI automation APK file found for workload {}.'.format(self.name))
|
||||
if not self.uiauto_package:
|
||||
raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name))
|
||||
|
||||
@@ -191,6 +195,11 @@ class ApkWorkload(Workload):
|
||||
If ``True``, workload will check that the APK matches the target
|
||||
device ABI, otherwise any APK found will be used.
|
||||
'''),
|
||||
Parameter('clear_data_on_reset', kind=bool, default=True,
|
||||
description="""
|
||||
If set to ``False``, this will prevent WA from clearing package
|
||||
data for this workload prior to running it.
|
||||
"""),
|
||||
]
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
@@ -200,11 +209,15 @@ class ApkWorkload(Workload):
|
||||
self.apk_version = None
|
||||
self.logcat_log = None
|
||||
self.exact_apk_version = None
|
||||
self.exact_abi = kwargs.get('exact_abi')
|
||||
|
||||
def setup(self, context): # pylint: disable=too-many-branches
|
||||
Workload.setup(self, context)
|
||||
self.setup_workload_apk(context)
|
||||
self.launch_application()
|
||||
self.kill_background()
|
||||
self.device.clear_logcat()
|
||||
|
||||
def setup_workload_apk(self, context):
|
||||
# Get target version
|
||||
target_version = self.device.get_installed_package_version(self.package)
|
||||
if target_version:
|
||||
@@ -212,7 +225,8 @@ class ApkWorkload(Workload):
|
||||
self.logger.debug("Found version '{}' on target device".format(target_version))
|
||||
|
||||
# Get host version
|
||||
self.apk_file = context.resolver.get(ApkFile(self, self.device.abi),
|
||||
self.apk_file = context.resolver.get(ApkFile(self, self.device.abi,
|
||||
package=getattr(self, 'package', None)),
|
||||
version=getattr(self, 'version', None),
|
||||
variant_name=getattr(self, 'variant_name', None),
|
||||
strict=False)
|
||||
@@ -224,7 +238,8 @@ class ApkWorkload(Workload):
|
||||
|
||||
# Get host version, primary abi is first, and then try to find supported.
|
||||
for abi in self.device.supported_abi:
|
||||
self.apk_file = context.resolver.get(ApkFile(self, abi),
|
||||
self.apk_file = context.resolver.get(ApkFile(self, abi,
|
||||
package=getattr(self, 'package', None)),
|
||||
version=getattr(self, 'version', None),
|
||||
variant_name=getattr(self, 'variant_name', None),
|
||||
strict=False)
|
||||
@@ -233,13 +248,30 @@ class ApkWorkload(Workload):
|
||||
if self.apk_file or self.exact_abi:
|
||||
break
|
||||
|
||||
host_version = self.check_host_version()
|
||||
self.verify_apk_version(target_version, target_abi, host_version)
|
||||
|
||||
if self.force_install:
|
||||
self.force_install_apk(context, host_version)
|
||||
elif self.check_apk:
|
||||
self.prefer_host_apk(context, host_version, target_version)
|
||||
else:
|
||||
self.prefer_target_apk(context, host_version, target_version)
|
||||
|
||||
self.reset(context)
|
||||
self.apk_version = self.device.get_installed_package_version(self.package)
|
||||
context.add_classifiers(apk_version=self.apk_version)
|
||||
|
||||
def check_host_version(self):
|
||||
host_version = None
|
||||
if self.apk_file is not None:
|
||||
host_version = ApkInfo(self.apk_file).version_name
|
||||
if host_version:
|
||||
host_version = LooseVersion(host_version)
|
||||
self.logger.debug("Found version '{}' on host".format(host_version))
|
||||
return host_version
|
||||
|
||||
def verify_apk_version(self, target_version, target_abi, host_version):
|
||||
# Error if apk was not found anywhere
|
||||
if target_version is None and host_version is None:
|
||||
msg = "Could not find APK for '{}' on the host or target device"
|
||||
@@ -256,22 +288,12 @@ class ApkWorkload(Workload):
|
||||
msg = "APK abi '{}' not found on the host and target is '{}'"
|
||||
raise ResourceError(msg.format(self.device.abi, target_abi))
|
||||
|
||||
# Ensure the apk is setup on the device
|
||||
if self.force_install:
|
||||
self.force_install_apk(context, host_version)
|
||||
elif self.check_apk:
|
||||
self.prefer_host_apk(context, host_version, target_version)
|
||||
else:
|
||||
self.prefer_target_apk(context, host_version, target_version)
|
||||
|
||||
self.reset(context)
|
||||
self.apk_version = self.device.get_installed_package_version(self.package)
|
||||
context.add_classifiers(apk_version=self.apk_version)
|
||||
|
||||
def launch_application(self):
|
||||
if self.launch_main:
|
||||
self.launch_package() # launch default activity without intent data
|
||||
|
||||
def kill_background(self):
|
||||
self.device.execute('am kill-all') # kill all *background* activities
|
||||
self.device.clear_logcat()
|
||||
|
||||
def force_install_apk(self, context, host_version):
|
||||
if host_version is None:
|
||||
@@ -387,7 +409,8 @@ class ApkWorkload(Workload):
|
||||
|
||||
def reset(self, context): # pylint: disable=W0613
|
||||
self.device.execute('am force-stop {}'.format(self.package))
|
||||
self.device.execute('pm clear {}'.format(self.package))
|
||||
if self.clear_data_on_reset:
|
||||
self.device.execute('pm clear {}'.format(self.package))
|
||||
|
||||
# As of android API level 23, apps can request permissions at runtime,
|
||||
# this will grant all of them so requests do not pop up when running the app
|
||||
@@ -397,7 +420,7 @@ class ApkWorkload(Workload):
|
||||
|
||||
def install_apk(self, context, replace=False):
|
||||
success = False
|
||||
if replace:
|
||||
if replace and self.device.package_is_installed(self.package):
|
||||
self.device.uninstall(self.package)
|
||||
output = self.device.install_apk(self.apk_file, timeout=self.install_timeout,
|
||||
replace=replace, allow_downgrade=True)
|
||||
@@ -432,16 +455,18 @@ class ApkWorkload(Workload):
|
||||
# "Normal" Permisions are automatically granted and cannot be changed
|
||||
permission_name = permission.rsplit('.', 1)[1]
|
||||
if permission_name not in ANDROID_NORMAL_PERMISSIONS:
|
||||
# On some API 23+ devices, this may fail with a SecurityException
|
||||
# on previously granted permissions. In that case, just skip as it
|
||||
# is not fatal to the workload execution
|
||||
try:
|
||||
self.device.execute("pm grant {} {}".format(self.package, permission))
|
||||
except DeviceError as e:
|
||||
if "changeable permission" in e.message or "Unknown permission" in e.message:
|
||||
self.logger.debug(e)
|
||||
else:
|
||||
raise e
|
||||
# Some permissions are not allowed to be "changed"
|
||||
if permission_name not in ANDROID_UNCHANGEABLE_PERMISSIONS:
|
||||
# On some API 23+ devices, this may fail with a SecurityException
|
||||
# on previously granted permissions. In that case, just skip as it
|
||||
# is not fatal to the workload execution
|
||||
try:
|
||||
self.device.execute("pm grant {} {}".format(self.package, permission))
|
||||
except DeviceError as e:
|
||||
if "changeable permission" in e.message or "Unknown permission" in e.message:
|
||||
self.logger.debug(e)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def do_post_install(self, context):
|
||||
""" May be overwritten by derived classes."""
|
||||
@@ -463,102 +488,8 @@ class ApkWorkload(Workload):
|
||||
if self.uninstall_apk:
|
||||
self.device.uninstall(self.package)
|
||||
|
||||
|
||||
AndroidBenchmark = ApkWorkload # backward compatibility
|
||||
|
||||
|
||||
class ReventWorkload(Workload):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
if _call_super:
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
devpath = self.device.path
|
||||
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
|
||||
self.setup_timeout = kwargs.get('setup_timeout', None)
|
||||
self.run_timeout = kwargs.get('run_timeout', None)
|
||||
self.revent_setup_file = None
|
||||
self.revent_run_file = None
|
||||
self.on_device_setup_revent = None
|
||||
self.on_device_run_revent = None
|
||||
self.statedefs_dir = None
|
||||
|
||||
if self.check_states:
|
||||
state_detector.check_match_state_dependencies()
|
||||
|
||||
def setup(self, context):
|
||||
self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup'))
|
||||
self.revent_run_file = context.resolver.get(ReventFile(self, 'run'))
|
||||
devpath = self.device.path
|
||||
self.on_device_setup_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_setup_file)[-1])
|
||||
self.on_device_run_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_run_file)[-1])
|
||||
self._check_revent_files(context)
|
||||
default_setup_timeout = ceil(ReventRecording(self.revent_setup_file).duration) + 30
|
||||
default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30
|
||||
self.setup_timeout = self.setup_timeout or default_setup_timeout
|
||||
self.run_timeout = self.run_timeout or default_run_timeout
|
||||
|
||||
Workload.setup(self, context)
|
||||
self.device.killall('revent')
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent)
|
||||
self.device.execute(command, timeout=self.setup_timeout)
|
||||
|
||||
def run(self, context):
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent)
|
||||
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
||||
self.device.execute(command, timeout=self.run_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
|
||||
def update_result(self, context):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
self.device.killall('revent')
|
||||
self.device.delete_file(self.on_device_setup_revent)
|
||||
self.device.delete_file(self.on_device_run_revent)
|
||||
|
||||
def _check_revent_files(self, context):
|
||||
# check the revent binary
|
||||
revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||
if not os.path.isfile(revent_binary):
|
||||
message = '{} does not exist. '.format(revent_binary)
|
||||
message += 'Please build revent for your system and place it in that location'
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_setup_file:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = '{0}.setup.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name)
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_run_file:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = '{0}.run.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name)
|
||||
raise WorkloadError(message)
|
||||
|
||||
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
||||
self.device.push_file(self.revent_run_file, self.on_device_run_revent)
|
||||
self.device.push_file(self.revent_setup_file, self.on_device_setup_revent)
|
||||
|
||||
def _check_statedetection_files(self, context):
|
||||
try:
|
||||
self.statedefs_dir = context.resolver.get(File(self, 'state_definitions'))
|
||||
except ResourceError:
|
||||
self.logger.warning("State definitions directory not found. Disabling state detection.")
|
||||
self.check_states = False
|
||||
|
||||
def check_state(self, context, phase):
|
||||
try:
|
||||
self.logger.info("\tChecking workload state...")
|
||||
screenshotPath = os.path.join(context.output_directory, "screen.png")
|
||||
self.device.capture_screen(screenshotPath)
|
||||
stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase)
|
||||
if not stateCheck:
|
||||
raise WorkloadError("Unexpected state after setup")
|
||||
except state_detector.StateDefinitionError as e:
|
||||
msg = "State definitions or template files missing or invalid ({}). Skipping state detection."
|
||||
self.logger.warning(msg.format(e.message))
|
||||
|
||||
|
||||
class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||
|
||||
supported_platforms = ['android']
|
||||
@@ -639,17 +570,27 @@ class AndroidUxPerfWorkload(AndroidUiAutoBenchmark):
|
||||
return self.device.path.join(dirname, fname)
|
||||
|
||||
def push_assets(self, context):
|
||||
pushed = False
|
||||
file_list = []
|
||||
for f in self.deployable_assets:
|
||||
fpath = context.resolver.get(File(self, f))
|
||||
device_path = self._path_on_device(fpath)
|
||||
if self.force_push_assets or not self.device.file_exists(device_path):
|
||||
self.device.push_file(fpath, device_path, timeout=300)
|
||||
self.device.broadcast_media_mounted(self.device.working_directory)
|
||||
file_list.append(device_path)
|
||||
pushed = True
|
||||
if pushed:
|
||||
self.device.refresh_device_files(file_list)
|
||||
|
||||
def delete_assets(self):
|
||||
for f in self.deployable_assets:
|
||||
self.device.delete_file(self._path_on_device(f))
|
||||
self.device.broadcast_media_mounted(self.device.working_directory)
|
||||
if self.deployable_assets:
|
||||
file_list = []
|
||||
for f in self.deployable_assets:
|
||||
f = self._path_on_device(f)
|
||||
self.device.delete_file(f)
|
||||
file_list.append(f)
|
||||
self.device.delete_file(f)
|
||||
self.device.refresh_device_files(file_list)
|
||||
|
||||
def __init__(self, device, **kwargs):
|
||||
super(AndroidUxPerfWorkload, self).__init__(device, **kwargs)
|
||||
@@ -658,7 +599,7 @@ class AndroidUxPerfWorkload(AndroidUiAutoBenchmark):
|
||||
|
||||
def validate(self):
|
||||
super(AndroidUxPerfWorkload, self).validate()
|
||||
self.uiauto_params['package'] = self.package
|
||||
self.uiauto_params['package_name'] = self.package
|
||||
self.uiauto_params['markers_enabled'] = self.markers_enabled
|
||||
|
||||
def setup(self, context):
|
||||
@@ -703,6 +644,7 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
view = 'SurfaceView'
|
||||
loading_time = 10
|
||||
supported_platforms = ['android']
|
||||
setup_required = True
|
||||
|
||||
parameters = [
|
||||
Parameter('install_timeout', default=500, override=True),
|
||||
@@ -711,16 +653,13 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
after setup and run"""),
|
||||
Parameter('assets_push_timeout', kind=int, default=500,
|
||||
description='Timeout used during deployment of the assets package (if there is one).'),
|
||||
Parameter('clear_data_on_reset', kind=bool, default=True,
|
||||
description="""
|
||||
If set to ``False``, this will prevent WA from clearing package
|
||||
data for this workload prior to running it.
|
||||
"""),
|
||||
]
|
||||
|
||||
def __init__(self, device, **kwargs): # pylint: disable=W0613
|
||||
ApkWorkload.__init__(self, device, **kwargs)
|
||||
ReventWorkload.__init__(self, device, _call_super=False, **kwargs)
|
||||
if self.check_states:
|
||||
state_detector.check_match_state_dependencies()
|
||||
self.logcat_process = None
|
||||
self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__)
|
||||
self.revent_dir = os.path.join(self.module_dir, 'revent_files')
|
||||
@@ -795,3 +734,22 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
self.device.busybox,
|
||||
ondevice_cache)
|
||||
self.device.execute(deploy_command, timeout=timeout, as_root=True)
|
||||
|
||||
def _check_statedetection_files(self, context):
|
||||
try:
|
||||
self.statedefs_dir = context.resolver.get(File(self, 'state_definitions'))
|
||||
except ResourceError:
|
||||
self.logger.warning("State definitions directory not found. Disabling state detection.")
|
||||
self.check_states = False # pylint: disable=W0201
|
||||
|
||||
def check_state(self, context, phase):
|
||||
try:
|
||||
self.logger.info("\tChecking workload state...")
|
||||
screenshotPath = os.path.join(context.output_directory, "screen.png")
|
||||
self.device.capture_screen(screenshotPath)
|
||||
stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase)
|
||||
if not stateCheck:
|
||||
raise WorkloadError("Unexpected state after setup")
|
||||
except state_detector.StateDefinitionError as e:
|
||||
msg = "State definitions or template files missing or invalid ({}). Skipping state detection."
|
||||
self.logger.warning(msg.format(e.message))
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -462,7 +462,7 @@ class BaseGem5Device(object):
|
||||
# gem5 might be slow. Hence, we need to make the ping timeout very long.
|
||||
def ping(self):
|
||||
self.logger.debug("Pinging gem5 to see if it is still alive")
|
||||
self.gem5_shell('ls /', timeout=self.longdelay)
|
||||
self.gem5_shell('ls /', timeout=self.long_delay)
|
||||
|
||||
# Additional Android-specific methods.
|
||||
def forward_port(self, _): # pylint: disable=R0201
|
||||
|
@@ -17,6 +17,7 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import base64
|
||||
import socket
|
||||
from collections import namedtuple
|
||||
from subprocess import CalledProcessError
|
||||
@@ -105,7 +106,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
runtime_parameters = [
|
||||
RuntimeParameter('sysfile_values', 'get_sysfile_values', 'set_sysfile_values', value_name='params'),
|
||||
CoreParameter('${core}_cores', 'get_number_of_online_cpus', 'set_number_of_online_cpus',
|
||||
CoreParameter('${core}_cores', 'get_number_of_online_cores', 'set_number_of_online_cores',
|
||||
value_name='number'),
|
||||
CoreParameter('${core}_min_frequency', 'get_core_min_frequency', 'set_core_min_frequency',
|
||||
value_name='freq'),
|
||||
@@ -241,7 +242,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
self.logger.debug('Could not pull property file "{}"'.format(propfile))
|
||||
return {}
|
||||
|
||||
def get_sysfile_value(self, sysfile, kind=None):
|
||||
def get_sysfile_value(self, sysfile, kind=None, binary=False):
|
||||
"""
|
||||
Get the contents of the specified sysfile.
|
||||
|
||||
@@ -251,28 +252,49 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
be any Python callable that takes a single str argument.
|
||||
If not specified or is None, the contents will be returned
|
||||
as a string.
|
||||
:param binary: Whether the value should be encoded into base64 for reading
|
||||
to deal with binary format.
|
||||
|
||||
"""
|
||||
output = self.execute('cat \'{}\''.format(sysfile), as_root=self.is_rooted).strip() # pylint: disable=E1103
|
||||
if binary:
|
||||
output = self.execute('{} base64 {}'.format(self.busybox, sysfile), as_root=self.is_rooted).strip()
|
||||
output = output.decode('base64')
|
||||
else:
|
||||
output = self.execute('cat \'{}\''.format(sysfile), as_root=self.is_rooted).strip() # pylint: disable=E1103
|
||||
if kind:
|
||||
return kind(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
def set_sysfile_value(self, sysfile, value, verify=True):
|
||||
def set_sysfile_value(self, sysfile, value, verify=True, binary=False):
|
||||
"""
|
||||
Set the value of the specified sysfile. By default, the value will be checked afterwards.
|
||||
Can be overridden by setting ``verify`` parameter to ``False``.
|
||||
Can be overridden by setting ``verify`` parameter to ``False``. By default binary values
|
||||
will not be written correctly this can be changed by setting the ``binary`` parameter to
|
||||
``True``.
|
||||
|
||||
"""
|
||||
value = str(value)
|
||||
self.execute('echo {} > \'{}\''.format(value, sysfile), check_exit_code=False, as_root=True)
|
||||
if binary:
|
||||
# Value is already string encoded, so need to decode before encoding in base64
|
||||
try:
|
||||
value = str(value.decode('string_escape'))
|
||||
except ValueError as e:
|
||||
msg = 'Can not interpret value "{}" for "{}": {}'
|
||||
raise ValueError(msg.format(value, sysfile, e.message))
|
||||
|
||||
encoded_value = base64.b64encode(value)
|
||||
cmd = 'echo {} | {} base64 -d > \'{}\''.format(encoded_value, self.busybox, sysfile)
|
||||
else:
|
||||
cmd = 'echo {} > \'{}\''.format(value, sysfile)
|
||||
self.execute(cmd, check_exit_code=False, as_root=True)
|
||||
|
||||
if verify:
|
||||
output = self.get_sysfile_value(sysfile)
|
||||
output = self.get_sysfile_value(sysfile, binary=binary)
|
||||
if output.strip() != value: # pylint: disable=E1103
|
||||
message = 'Could not set the value of {} to {}'.format(sysfile, value)
|
||||
raise DeviceError(message)
|
||||
self._written_sysfiles.append(sysfile)
|
||||
self._written_sysfiles.append((sysfile, binary))
|
||||
|
||||
def get_sysfile_values(self):
|
||||
"""
|
||||
@@ -281,21 +303,24 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
"""
|
||||
values = {}
|
||||
for sysfile in self._written_sysfiles:
|
||||
values[sysfile] = self.get_sysfile_value(sysfile)
|
||||
for sysfile, binary in self._written_sysfiles:
|
||||
values[sysfile] = self.get_sysfile_value(sysfile, binary=binary)
|
||||
return values
|
||||
|
||||
def set_sysfile_values(self, params):
|
||||
"""
|
||||
The plural version of ``set_sysfile_value``. Takes a single parameter which is a mapping of
|
||||
file paths to values to be set. By default, every value written will be verified. The can
|
||||
be disabled for individual paths by appending ``'!'`` to them.
|
||||
file paths to values to be set. By default, every value written will be verified. This can
|
||||
be disabled for individual paths by appending ``'!'`` to them. To enable values being
|
||||
written as binary data, a ``'^'`` can be prefixed to the path.
|
||||
|
||||
"""
|
||||
for sysfile, value in params.iteritems():
|
||||
verify = not sysfile.endswith('!')
|
||||
sysfile = sysfile.rstrip('!')
|
||||
self.set_sysfile_value(sysfile, value, verify=verify)
|
||||
binary = sysfile.startswith('^')
|
||||
sysfile = sysfile.lstrip('^')
|
||||
self.set_sysfile_value(sysfile, value, verify=verify, binary=binary)
|
||||
|
||||
def deploy_busybox(self, context, force=False):
|
||||
"""
|
||||
@@ -452,20 +477,6 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
else:
|
||||
raise ValueError(c)
|
||||
|
||||
def get_number_of_online_cpus(self, c):
|
||||
return len(self.get_online_cpus(c))
|
||||
|
||||
def set_number_of_online_cpus(self, core, number):
|
||||
core_ids = [i for i, c in enumerate(self.core_names) if c == core]
|
||||
max_cores = len(core_ids)
|
||||
if number > max_cores:
|
||||
message = 'Attempting to set the number of active {} to {}; maximum is {}'
|
||||
raise ValueError(message.format(core, number, max_cores))
|
||||
for i in xrange(0, number):
|
||||
self.enable_cpu(core_ids[i])
|
||||
for i in xrange(number, max_cores):
|
||||
self.disable_cpu(core_ids[i])
|
||||
|
||||
# hotplug
|
||||
|
||||
def enable_cpu(self, cpu):
|
||||
@@ -504,17 +515,17 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
sysfile = '/sys/devices/system/cpu/{}/online'.format(cpu)
|
||||
self.set_sysfile_value(sysfile, status)
|
||||
|
||||
def get_number_of_active_cores(self, core):
|
||||
def get_number_of_online_cores(self, core):
|
||||
if core not in self.core_names:
|
||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names))))
|
||||
active_cpus = self.active_cpus
|
||||
online_cpus = self.online_cpus
|
||||
num_active_cores = 0
|
||||
for i, c in enumerate(self.core_names):
|
||||
if c == core and i in active_cpus:
|
||||
if c == core and i in online_cpus:
|
||||
num_active_cores += 1
|
||||
return num_active_cores
|
||||
|
||||
def set_number_of_active_cores(self, core, number): # NOQA
|
||||
def set_number_of_online_cores(self, core, number): # NOQA
|
||||
if core not in self.core_names:
|
||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names))))
|
||||
core_ids = [i for i, c in enumerate(self.core_names) if c == core]
|
||||
@@ -527,8 +538,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
# make sure at least one other core is enabled to avoid trying to
|
||||
# hotplug everything.
|
||||
for i, c in enumerate(self.core_names):
|
||||
if c != core:
|
||||
self.enable_cpu(i)
|
||||
if c != core and i in self.online_cpus:
|
||||
break
|
||||
else: # did not find one
|
||||
raise ValueError('Cannot hotplug all cpus on the device!')
|
||||
@@ -580,7 +590,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
def get_device_model(self):
|
||||
if self.file_exists("/proc/device-tree/model"):
|
||||
raw_model = self.execute("cat /proc/device-tree/model")
|
||||
return '_'.join(raw_model.split()[:2])
|
||||
device_model_to_return = '_'.join(raw_model.split()[:2])
|
||||
return device_model_to_return.rstrip(' \t\r\n\0')
|
||||
# Right now we don't know any other way to get device model
|
||||
# info in linux on arm platforms
|
||||
return None
|
||||
@@ -589,7 +600,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
def _check_ready(self):
|
||||
if not self._is_ready:
|
||||
raise AttributeError('Device not ready.')
|
||||
raise RuntimeError('Device not ready (has connect() been called?)')
|
||||
|
||||
def _get_core_cluster(self, core):
|
||||
"""Returns the first cluster that has cores of the specified type. Raises
|
||||
|
165
wlauto/common/linux/workload.py
Normal file
165
wlauto/common/linux/workload.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# Copyright 2017 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.
|
||||
#
|
||||
|
||||
import os
|
||||
from math import ceil
|
||||
|
||||
from wlauto.core.extension import Parameter
|
||||
from wlauto.core.workload import Workload
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.resources import Executable
|
||||
from wlauto.common.android.resources import ReventFile
|
||||
from wlauto.exceptions import WorkloadError
|
||||
from wlauto.utils.revent import ReventRecording
|
||||
|
||||
class ReventWorkload(Workload):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
|
||||
description = """
|
||||
A workload for playing back revent recordings. You can supply three
|
||||
different files:
|
||||
|
||||
1. {device_model}.setup.revent
|
||||
2. {device_model}.run.revent
|
||||
3. {device_model}.teardown.revent
|
||||
|
||||
You may generate these files using the wa record command using the -s flag
|
||||
to specify the stage (``setup``, ``run``, ``teardown``)
|
||||
|
||||
You may also supply an 'idle_time' in seconds in place of the run file.
|
||||
The ``run`` file may only be omitted if you choose to run this way, but
|
||||
while running idle may supply ``setup`` and ``teardown`` files.
|
||||
|
||||
To use a ``setup`` or ``teardown`` file set the setup_required and/or
|
||||
teardown_required class attributes to True (default: False).
|
||||
|
||||
N.B. This is the default description. You may overwrite this for your
|
||||
workload to include more specific information.
|
||||
|
||||
"""
|
||||
|
||||
setup_required = False
|
||||
teardown_required = False
|
||||
|
||||
parameters = [
|
||||
Parameter(
|
||||
'idle_time', kind=int, default=None,
|
||||
description='''
|
||||
The time you wish the device to remain idle for (if a value is
|
||||
given then this overrides any .run revent file).
|
||||
'''),
|
||||
]
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
if _call_super:
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
self.setup_timeout = kwargs.get('setup_timeout', None)
|
||||
self.run_timeout = kwargs.get('run_timeout', None)
|
||||
self.teardown_timeout = kwargs.get('teardown_timeout', None)
|
||||
self.revent_setup_file = None
|
||||
self.revent_run_file = None
|
||||
self.revent_teardown_file = None
|
||||
self.on_device_setup_revent = None
|
||||
self.on_device_run_revent = None
|
||||
self.on_device_teardown_revent = None
|
||||
self.statedefs_dir = None
|
||||
|
||||
def initialize(self, context):
|
||||
devpath = self.device.path
|
||||
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
|
||||
|
||||
def setup(self, context):
|
||||
devpath = self.device.path
|
||||
if self.setup_required:
|
||||
self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup'))
|
||||
if self.revent_setup_file:
|
||||
self.on_device_setup_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_setup_file)[-1])
|
||||
duration = ReventRecording(self.revent_setup_file).duration
|
||||
self.default_setup_timeout = ceil(duration) + 30
|
||||
if not self.idle_time:
|
||||
self.revent_run_file = context.resolver.get(ReventFile(self, 'run'))
|
||||
if self.revent_run_file:
|
||||
self.on_device_run_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_run_file)[-1])
|
||||
self.default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30
|
||||
if self.teardown_required:
|
||||
self.revent_teardown_file = context.resolver.get(ReventFile(self, 'teardown'))
|
||||
if self.revent_teardown_file:
|
||||
self.on_device_teardown_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_teardown_file)[-1])
|
||||
duration = ReventRecording(self.revent_teardown_file).duration
|
||||
self.default_teardown_timeout = ceil(duration) + 30
|
||||
self._check_revent_files(context)
|
||||
|
||||
Workload.setup(self, context)
|
||||
|
||||
if self.revent_setup_file is not None:
|
||||
self.setup_timeout = self.setup_timeout or self.default_setup_timeout
|
||||
self.device.killall('revent')
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent)
|
||||
self.device.execute(command, timeout=self.setup_timeout)
|
||||
self.logger.debug('Revent setup completed.')
|
||||
|
||||
def run(self, context):
|
||||
if not self.idle_time:
|
||||
self.run_timeout = self.run_timeout or self.default_run_timeout
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent)
|
||||
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
||||
self.device.execute(command, timeout=self.run_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
else:
|
||||
self.logger.info('Idling for ' + str(self.idle_time) + ' seconds.')
|
||||
self.device.sleep(self.idle_time)
|
||||
self.logger.info('Successfully did nothing for ' + str(self.idle_time) + ' seconds!')
|
||||
|
||||
def update_result(self, context):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
if self.revent_teardown_file is not None:
|
||||
self.teardown_timeout = self.teardown_timeout or self.default_teardown_timeout
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary,
|
||||
self.on_device_teardown_revent)
|
||||
self.device.execute(command, timeout=self.teardown_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
self.device.killall('revent')
|
||||
if self.revent_setup_file is not None:
|
||||
self.device.delete_file(self.on_device_setup_revent)
|
||||
if not self.idle_time:
|
||||
self.device.delete_file(self.on_device_run_revent)
|
||||
if self.revent_teardown_file is not None:
|
||||
self.device.delete_file(self.on_device_teardown_revent)
|
||||
|
||||
def _check_revent_files(self, context):
|
||||
# check the revent binary
|
||||
revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||
if not os.path.isfile(revent_binary):
|
||||
message = '{} does not exist. '.format(revent_binary)
|
||||
message += 'Please build revent for your system and place it in that location'
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_run_file and not self.idle_time:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = 'It seems a {0}.run.revent file does not exist, '\
|
||||
'Please provide one for your device: {0}.'
|
||||
raise WorkloadError(message.format(self.device.name))
|
||||
|
||||
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
||||
if self.revent_setup_file is not None:
|
||||
self.device.push_file(self.revent_setup_file, self.on_device_setup_revent)
|
||||
if not self.idle_time:
|
||||
self.device.push_file(self.revent_run_file, self.on_device_run_revent)
|
||||
if self.revent_teardown_file is not None:
|
||||
self.device.push_file(self.revent_teardown_file, self.on_device_teardown_revent)
|
@@ -61,7 +61,8 @@ clean_up = False
|
||||
######################################### Device Settings ##########################################
|
||||
####################################################################################################
|
||||
# Specify the device you want to run workload automation on. This must be a #
|
||||
# string with the ID of the device. At the moment, only 'TC2' is supported. #
|
||||
# string with the ID of the device. Common options are 'generic_android' and #
|
||||
# 'generic_linux'. Run ``wa list devices`` to see all available options. #
|
||||
# #
|
||||
device = 'generic_android'
|
||||
|
||||
@@ -87,7 +88,7 @@ device_config = dict(
|
||||
|
||||
|
||||
####################################################################################################
|
||||
################################### Instrumention Configuration ####################################
|
||||
################################### Instrumentation Configuration ####################################
|
||||
####################################################################################################
|
||||
# This defines the additionnal instrumentation that will be enabled during workload execution, #
|
||||
# which in turn determines what additional data (such as /proc/interrupts content or Streamline #
|
||||
@@ -192,7 +193,7 @@ logging = {
|
||||
####################################################################################################
|
||||
#################################### Instruments Configuration #####################################
|
||||
####################################################################################################
|
||||
# Instrumention Configuration is related to specific instrument's settings. Some of the #
|
||||
# Instrumentation Configuration is related to specific instrument's settings. Some of the #
|
||||
# instrumentations require specific settings in order for them to work. These settings are #
|
||||
# specified here. #
|
||||
# Note that these settings only take effect if the corresponding instrument is
|
||||
|
@@ -152,7 +152,8 @@ def init_environment(env_root, dep_dir, extension_paths, overwrite_existing=Fals
|
||||
os.makedirs(env_root)
|
||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as rf:
|
||||
text = re.sub(r'""".*?"""', '', rf.read(), 1, re.DOTALL)
|
||||
with open(os.path.join(_env_root, 'config.py'), 'w') as wf:
|
||||
config_path = os.path.join(env_root, 'config.py')
|
||||
with open(config_path, 'w') as wf:
|
||||
wf.write(text)
|
||||
|
||||
os.makedirs(dep_dir)
|
||||
@@ -173,9 +174,11 @@ def init_environment(env_root, dep_dir, extension_paths, overwrite_existing=Fals
|
||||
os.chown(os.path.join(root, d), uid, gid)
|
||||
for f in files: # pylint: disable=W0621
|
||||
os.chown(os.path.join(root, f), uid, gid)
|
||||
return config_path
|
||||
|
||||
|
||||
_env_root = os.getenv('WA_USER_DIRECTORY', os.path.join(_user_home, '.workload_automation'))
|
||||
_env_root = os.path.abspath(_env_root)
|
||||
_dep_dir = os.path.join(_env_root, 'dependencies')
|
||||
_extension_paths = [os.path.join(_env_root, ext.default_path) for ext in _extensions]
|
||||
_env_var_paths = os.getenv('WA_EXTENSION_PATHS', '')
|
||||
@@ -189,7 +192,8 @@ for filename in ['config.py', 'config.yaml']:
|
||||
_env_configs.append(filepath)
|
||||
|
||||
if not os.path.isdir(_env_root):
|
||||
init_environment(_env_root, _dep_dir, _extension_paths)
|
||||
cfg_path = init_environment(_env_root, _dep_dir, _extension_paths)
|
||||
_env_configs.append(cfg_path)
|
||||
elif not _env_configs:
|
||||
filepath = os.path.join(_env_root, 'config.py')
|
||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as f:
|
||||
|
@@ -381,7 +381,20 @@ class Device(Extension):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_sysfile_value(self, filepath, value, verify=True):
|
||||
def sleep(self, seconds):
|
||||
"""Sleep for the specified time on the target device.
|
||||
|
||||
:param seconds: Time in seconds to sleep on the device
|
||||
|
||||
The sleep is executed on the device using self.execute(). We
|
||||
set the timeout for this command to be 10 seconds longer than
|
||||
the sleep itself to make sure the command has time to complete
|
||||
before we timeout.
|
||||
|
||||
"""
|
||||
self.execute("sleep {}".format(seconds), timeout=seconds + 10)
|
||||
|
||||
def set_sysfile_value(self, filepath, value, verify=True, binary=False):
|
||||
"""
|
||||
Write the specified value to the specified file on the device
|
||||
and verify that the value has actually been written.
|
||||
@@ -390,13 +403,14 @@ class Device(Extension):
|
||||
:param value: The value to be written to the file. Must be
|
||||
an int or a string convertable to an int.
|
||||
:param verify: Specifies whether the value should be verified, once written.
|
||||
:param binary: Specifies whether the value should be written as binary data.
|
||||
|
||||
Should raise DeviceError if could write value.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_sysfile_value(self, sysfile, kind=None):
|
||||
def get_sysfile_value(self, sysfile, kind=None, binary=False):
|
||||
"""
|
||||
Get the contents of the specified sysfile.
|
||||
|
||||
@@ -406,6 +420,8 @@ class Device(Extension):
|
||||
be any Python callable that takes a single str argument.
|
||||
If not specified or is None, the contents will be returned
|
||||
as a string.
|
||||
:param binary: Whether the value should be encoded into base64 for reading
|
||||
to deal with binary format.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
@@ -14,10 +14,12 @@
|
||||
#
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
@@ -41,6 +43,9 @@ def load_commands(subparsers):
|
||||
for command in ext_loader.list_commands():
|
||||
settings.commands[command.name] = ext_loader.get_command(command.name, subparsers=subparsers)
|
||||
|
||||
def convert_TERM_into_INT_handler(signal, frame):
|
||||
logger.critical("TERM received, aborting")
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
def main():
|
||||
try:
|
||||
@@ -62,6 +67,7 @@ def main():
|
||||
settings.update(args.config)
|
||||
init_logging(settings.verbosity)
|
||||
|
||||
signal.signal(signal.SIGTERM, convert_TERM_into_INT_handler)
|
||||
command = settings.commands[args.command]
|
||||
sys.exit(command.execute(args))
|
||||
|
||||
|
@@ -642,7 +642,7 @@ class Runner(object):
|
||||
job.iteration = self.context.current_iteration
|
||||
if job.result.status in self.config.retry_on_status:
|
||||
if job.retry >= self.config.max_retries:
|
||||
self.logger.error('Exceeded maxium number of retries. Abandoning job.')
|
||||
self.logger.error('Exceeded maximum number of retries. Abandoning job.')
|
||||
else:
|
||||
self.logger.info('Job status was {}. Retrying...'.format(job.result.status))
|
||||
retry_job = RunnerJob(job.spec, job.retry + 1)
|
||||
|
@@ -38,8 +38,8 @@ class GetterPriority(object):
|
||||
"""
|
||||
cached = 20
|
||||
preferred = 10
|
||||
remote = 5
|
||||
environment = 0
|
||||
remote = -4
|
||||
external_package = -5
|
||||
package = -10
|
||||
|
||||
|
@@ -18,7 +18,7 @@ from collections import namedtuple
|
||||
|
||||
VersionTuple = namedtuple('Version', ['major', 'minor', 'revision'])
|
||||
|
||||
version = VersionTuple(2, 6, 0)
|
||||
version = VersionTuple(2, 7, 0)
|
||||
|
||||
|
||||
def get_wa_version():
|
||||
|
14
wlauto/devices/android/meizumx6/__init__.py
Normal file
14
wlauto/devices/android/meizumx6/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from wlauto import AndroidDevice
|
||||
|
||||
|
||||
class MeizuMX6(AndroidDevice):
|
||||
|
||||
name = 'meizumx6'
|
||||
|
||||
@property
|
||||
def is_rooted(self):
|
||||
# "su" executable on a rooted Meizu MX6 is targeted
|
||||
# specifically towards Android application and cannot
|
||||
# be used to execute a command line shell. This makes it
|
||||
# "broken" from WA prespective.
|
||||
return False
|
30
wlauto/external/revent/revent.c
vendored
30
wlauto/external/revent/revent.c
vendored
@@ -183,7 +183,7 @@ off_t get_file_size(const char *filename) {
|
||||
}
|
||||
|
||||
int get_device_info(int fd, device_info_t *info) {
|
||||
bzero(info, sizeof(device_info_t));
|
||||
memset(info, 0, sizeof(device_info_t));
|
||||
|
||||
if (ioctl(fd, EVIOCGID, &info->id) < 0)
|
||||
return errno;
|
||||
@@ -226,31 +226,31 @@ void destroy_replay_device(int fd)
|
||||
die("Could not destroy replay device");
|
||||
}
|
||||
|
||||
inline void set_evbit(int fd, int bit)
|
||||
static inline void set_evbit(int fd, int bit)
|
||||
{
|
||||
if(ioctl(fd, UI_SET_EVBIT, bit) < 0)
|
||||
die("Could not set EVBIT %i", bit);
|
||||
}
|
||||
|
||||
inline void set_keybit(int fd, int bit)
|
||||
static inline void set_keybit(int fd, int bit)
|
||||
{
|
||||
if(ioctl(fd, UI_SET_KEYBIT, bit) < 0)
|
||||
die("Could not set KEYBIT %i", bit);
|
||||
}
|
||||
|
||||
inline void set_absbit(int fd, int bit)
|
||||
static inline void set_absbit(int fd, int bit)
|
||||
{
|
||||
if(ioctl(fd, UI_SET_ABSBIT, bit) < 0)
|
||||
die("Could not set ABSBIT %i", bit);
|
||||
}
|
||||
|
||||
inline void set_relbit(int fd, int bit)
|
||||
static inline void set_relbit(int fd, int bit)
|
||||
{
|
||||
if(ioctl(fd, UI_SET_RELBIT, bit) < 0)
|
||||
die("Could not set RELBIT %i", bit);
|
||||
}
|
||||
|
||||
inline void block_sigterm(sigset_t *oldset)
|
||||
static inline void block_sigterm(sigset_t *oldset)
|
||||
{
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset);
|
||||
@@ -295,7 +295,7 @@ int write_record_header(int fd, const revent_record_desc_t *desc)
|
||||
if (ret < sizeof(uint16_t))
|
||||
return errno;
|
||||
|
||||
bzero(padding, HEADER_PADDING_SIZE);
|
||||
memset(padding, 0, HEADER_PADDING_SIZE);
|
||||
ret = write(fd, padding, HEADER_PADDING_SIZE);
|
||||
if (ret < HEADER_PADDING_SIZE)
|
||||
return errno;
|
||||
@@ -711,7 +711,7 @@ int init_general_input_devices(input_devices_t *devices)
|
||||
uint32_t num, i, path_len;
|
||||
char paths[INPDEV_MAX_DEVICES][INPDEV_MAX_PATH];
|
||||
int fds[INPDEV_MAX_DEVICES];
|
||||
int max_fd;
|
||||
int max_fd = 0;
|
||||
|
||||
num = 0;
|
||||
for(i = 0; i < INPDEV_MAX_DEVICES; ++i) {
|
||||
@@ -941,7 +941,7 @@ int create_replay_device_or_die(const device_info_t *info)
|
||||
return fd;
|
||||
}
|
||||
|
||||
inline void read_revent_recording_or_die(const char *filepath, revent_recording_t *recording)
|
||||
static inline void read_revent_recording_or_die(const char *filepath, revent_recording_t *recording)
|
||||
{
|
||||
int ret;
|
||||
FILE *fin;
|
||||
@@ -1083,7 +1083,7 @@ void record(const char *filepath, int delay, recording_mode_t mode)
|
||||
die("Could not initialise event count: %s", strerror(errno));
|
||||
|
||||
char padding[EVENT_PADDING_SIZE];
|
||||
bzero(padding, EVENT_PADDING_SIZE);
|
||||
memset(padding, 0, EVENT_PADDING_SIZE);
|
||||
|
||||
fd_set readfds;
|
||||
struct timespec tout;
|
||||
@@ -1099,8 +1099,7 @@ void record(const char *filepath, int delay, recording_mode_t mode)
|
||||
while(1)
|
||||
{
|
||||
FD_ZERO(&readfds);
|
||||
if (wait_for_stdin)
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
for (i=0; i < devices.num; i++)
|
||||
FD_SET(devices.fds[i], &readfds);
|
||||
|
||||
@@ -1241,7 +1240,7 @@ void replay(const char *filepath)
|
||||
adjust_event_times(&recording);
|
||||
|
||||
struct timeval start_time, now, desired_time, last_event_delta, delta;
|
||||
bzero(&last_event_delta, sizeof(struct timeval));
|
||||
memset(&last_event_delta, 0, sizeof(struct timeval));
|
||||
gettimeofday(&start_time, NULL);
|
||||
|
||||
int ret;
|
||||
@@ -1265,13 +1264,16 @@ void replay(const char *filepath)
|
||||
|
||||
int32_t idx = (recording.events[i]).dev_idx;
|
||||
struct input_event ev = (recording.events[i]).event;
|
||||
while((i < recording.num_events) && !timercmp(&ev.time, &last_event_delta, !=)) {
|
||||
while(!timercmp(&ev.time, &last_event_delta, !=)) {
|
||||
ret = write(recording.devices.fds[idx], &ev, sizeof(ev));
|
||||
if (ret != sizeof(ev))
|
||||
die("Could not replay event");
|
||||
dprintf("replayed event: type %d code %d value %d\n", ev.type, ev.code, ev.value);
|
||||
|
||||
i++;
|
||||
if (i >= recording.num_events) {
|
||||
break;
|
||||
}
|
||||
idx = recording.events[i].dev_idx;
|
||||
ev = recording.events[i].event;
|
||||
}
|
||||
|
18
wlauto/external/uiauto/app/build.gradle
vendored
Normal file
18
wlauto/external/uiauto/app/build.gradle
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '25.0.3'
|
||||
defaultConfig {
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 25
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
compile 'com.android.support.test:runner:0.5'
|
||||
compile 'com.android.support.test:rules:0.5'
|
||||
compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
}
|
9
wlauto/external/uiauto/app/src/main/AndroidManifest.xml
vendored
Normal file
9
wlauto/external/uiauto/app/src/main/AndroidManifest.xml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.arm.wlauto.uiauto">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
</application>
|
||||
</manifest>
|
58
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/ApplaunchInterface.java
vendored
Normal file
58
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/ApplaunchInterface.java
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
/* 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.support.test.uiautomator.UiObject;
|
||||
|
||||
// Import the uiautomator libraries
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** Initialize the instrumentation for the workload */
|
||||
public void initialize_instrumentation();
|
||||
|
||||
}
|
256
wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java → wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/BaseUiAutomation.java
vendored
Executable file → Normal file
256
wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java → wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/BaseUiAutomation.java
vendored
Executable file → Normal file
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2015 ARM Limited
|
||||
/* 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.
|
||||
@@ -13,38 +13,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import java.io.File;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
import android.support.test.uiautomator.UiSelector;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||
import android.support.test.uiautomator.UiScrollable;
|
||||
import android.support.test.uiautomator.UiWatcher;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import static android.support.test.InstrumentationRegistry.getArguments;
|
||||
|
||||
// Import the uiautomator libraries
|
||||
import com.android.uiautomator.core.UiObject;
|
||||
import com.android.uiautomator.core.UiObjectNotFoundException;
|
||||
import com.android.uiautomator.core.UiScrollable;
|
||||
import com.android.uiautomator.core.UiSelector;
|
||||
import com.android.uiautomator.core.UiDevice;
|
||||
import com.android.uiautomator.core.UiWatcher;
|
||||
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
|
||||
public class BaseUiAutomation {
|
||||
|
||||
public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
|
||||
public long uiAutoTimeout = TimeUnit.SECONDS.toMillis(4);
|
||||
// Time in milliseconds
|
||||
public long uiAutoTimeout = 4000;
|
||||
|
||||
public enum ScreenOrientation { RIGHT, NATURAL, LEFT };
|
||||
public enum Direction { UP, DOWN, LEFT, RIGHT, NULL };
|
||||
@@ -53,6 +55,18 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
public static final int CLICK_REPEAT_INTERVAL_MINIMUM = 5;
|
||||
public static final int CLICK_REPEAT_INTERVAL_DEFAULT = 50;
|
||||
|
||||
public Bundle parameters;
|
||||
|
||||
public Instrumentation mInstrumentation;
|
||||
public Context mContext;
|
||||
public UiDevice mDevice;
|
||||
|
||||
public void initialize_instrumentation(){
|
||||
mInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||
mDevice = UiDevice.getInstance(mInstrumentation);
|
||||
mContext = mInstrumentation.getTargetContext();
|
||||
}
|
||||
|
||||
/*
|
||||
* Used by clickUiObject() methods in order to provide a consistent API
|
||||
*/
|
||||
@@ -84,7 +98,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
|
||||
public ActionLogger(String testTag, Bundle parameters) {
|
||||
this.testTag = testTag;
|
||||
this.enabled = Boolean.parseBoolean(parameters.getString("markers_enabled"));
|
||||
this.enabled = parameters.getBoolean("markers_enabled");
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -101,7 +115,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void sleep(int second) {
|
||||
super.sleep(second * 1000);
|
||||
SystemClock.sleep(second * 1000);
|
||||
}
|
||||
|
||||
public boolean takeScreenshot(String name) {
|
||||
@@ -109,7 +123,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
String pngDir = params.getString("workdir");
|
||||
|
||||
try {
|
||||
return getUiDevice().takeScreenshot(new File(pngDir, name + ".png"));
|
||||
return mDevice.takeScreenshot(new File(pngDir, name + ".png"));
|
||||
} catch (NoSuchMethodError e) {
|
||||
return true;
|
||||
}
|
||||
@@ -121,8 +135,8 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
|
||||
public void waitText(String text, int second) throws UiObjectNotFoundException {
|
||||
UiSelector selector = new UiSelector();
|
||||
UiObject textObj = new UiObject(selector.text(text)
|
||||
.className("android.widget.TextView"));
|
||||
UiObject textObj = mDevice.findObject(selector.text(text)
|
||||
.className("android.widget.TextView"));
|
||||
waitObject(textObj, second);
|
||||
}
|
||||
|
||||
@@ -213,47 +227,51 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void registerWatcher(String name, UiWatcher watcher) {
|
||||
UiDevice.getInstance().registerWatcher(name, watcher);
|
||||
mDevice.registerWatcher(name, watcher);
|
||||
}
|
||||
|
||||
public void runWatchers() {
|
||||
UiDevice.getInstance().runWatchers();
|
||||
mDevice.runWatchers();
|
||||
}
|
||||
|
||||
public void removeWatcher(String name) {
|
||||
UiDevice.getInstance().removeWatcher(name);
|
||||
mDevice.removeWatcher(name);
|
||||
}
|
||||
|
||||
public void pressEnter() {
|
||||
UiDevice.getInstance().pressEnter();
|
||||
mDevice.pressEnter();
|
||||
}
|
||||
|
||||
public void pressHome() {
|
||||
mDevice.pressHome();
|
||||
}
|
||||
|
||||
public void pressBack() {
|
||||
UiDevice.getInstance().pressBack();
|
||||
mDevice.pressBack();
|
||||
}
|
||||
|
||||
public void pressDPadUp() {
|
||||
UiDevice.getInstance().pressDPadUp();
|
||||
mDevice.pressDPadUp();
|
||||
}
|
||||
|
||||
public void pressDPadDown() {
|
||||
UiDevice.getInstance().pressDPadDown();
|
||||
mDevice.pressDPadDown();
|
||||
}
|
||||
|
||||
public void pressDPadLeft() {
|
||||
UiDevice.getInstance().pressDPadLeft();
|
||||
mDevice.pressDPadLeft();
|
||||
}
|
||||
|
||||
public void pressDPadRight() {
|
||||
UiDevice.getInstance().pressDPadRight();
|
||||
mDevice.pressDPadRight();
|
||||
}
|
||||
|
||||
public int getDisplayHeight() {
|
||||
return UiDevice.getInstance().getDisplayHeight();
|
||||
return mDevice.getDisplayHeight();
|
||||
}
|
||||
|
||||
public int getDisplayWidth() {
|
||||
return UiDevice.getInstance().getDisplayWidth();
|
||||
return mDevice.getDisplayWidth();
|
||||
}
|
||||
|
||||
public int getDisplayCentreWidth() {
|
||||
@@ -269,11 +287,11 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void tapDisplay(int x, int y) {
|
||||
UiDevice.getInstance().click(x, y);
|
||||
mDevice.click(x, y);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeUp(int steps) {
|
||||
UiDevice.getInstance().swipe(
|
||||
mDevice.swipe(
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() + (getDisplayCentreHeight() / 2)),
|
||||
getDisplayCentreWidth(),
|
||||
@@ -282,7 +300,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeDown(int steps) {
|
||||
UiDevice.getInstance().swipe(
|
||||
mDevice.swipe(
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() / 2),
|
||||
getDisplayCentreWidth(),
|
||||
@@ -291,7 +309,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeLeft(int steps) {
|
||||
UiDevice.getInstance().swipe(
|
||||
mDevice.swipe(
|
||||
(getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)),
|
||||
getDisplayCentreHeight(),
|
||||
(getDisplayCentreWidth() / 2),
|
||||
@@ -300,7 +318,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeRight(int steps) {
|
||||
UiDevice.getInstance().swipe(
|
||||
mDevice.swipe(
|
||||
(getDisplayCentreWidth() / 2),
|
||||
getDisplayCentreHeight(),
|
||||
(getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)),
|
||||
@@ -405,13 +423,13 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
public void setScreenOrientation(ScreenOrientation orientation) throws Exception {
|
||||
switch (orientation) {
|
||||
case RIGHT:
|
||||
getUiDevice().setOrientationRight();
|
||||
mDevice.setOrientationRight();
|
||||
break;
|
||||
case NATURAL:
|
||||
getUiDevice().setOrientationNatural();
|
||||
mDevice.setOrientationNatural();
|
||||
break;
|
||||
case LEFT:
|
||||
getUiDevice().setOrientationLeft();
|
||||
mDevice.setOrientationLeft();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No orientation specified");
|
||||
@@ -419,25 +437,25 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void unsetScreenOrientation() throws Exception {
|
||||
getUiDevice().unfreezeRotation();
|
||||
mDevice.unfreezeRotation();
|
||||
}
|
||||
|
||||
public void uiObjectPerformLongClick(UiObject view, int steps) throws Exception {
|
||||
public void uiObjectPerformLongClick(UiObject view, int steps) throws Exception {
|
||||
Rect rect = view.getBounds();
|
||||
UiDevice.getInstance().swipe(rect.centerX(), rect.centerY(),
|
||||
rect.centerX(), rect.centerY(), steps);
|
||||
mDevice.swipe(rect.centerX(), rect.centerY(),
|
||||
rect.centerX(), rect.centerY(), steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeVertical(int startY, int endY, int xCoordinate, int steps) {
|
||||
getUiDevice().swipe(startY, xCoordinate, endY, xCoordinate, steps);
|
||||
mDevice.swipe(startY, xCoordinate, endY, xCoordinate, steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeHorizontal(int startX, int endX, int yCoordinate, int steps) {
|
||||
getUiDevice().swipe(startX, yCoordinate, endX, yCoordinate, steps);
|
||||
mDevice.swipe(startX, yCoordinate, endX, yCoordinate, steps);
|
||||
}
|
||||
|
||||
public void uiObjectPinch(UiObject view, PinchType direction, int steps,
|
||||
int percent) throws Exception {
|
||||
int percent) throws Exception {
|
||||
if (direction.equals(PinchType.IN)) {
|
||||
view.pinchIn(percent, steps);
|
||||
} else if (direction.equals(PinchType.OUT)) {
|
||||
@@ -446,7 +464,7 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public void uiObjectVertPinch(UiObject view, PinchType direction,
|
||||
int steps, int percent) throws Exception {
|
||||
int steps, int percent) throws Exception {
|
||||
if (direction.equals(PinchType.IN)) {
|
||||
uiObjectVertPinchIn(view, steps, percent);
|
||||
} else if (direction.equals(PinchType.OUT)) {
|
||||
@@ -511,20 +529,20 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByResourceId(String resourceId, String className, long timeout) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().resourceId(resourceId)
|
||||
.className(className));
|
||||
UiObject object = mDevice.findObject(new UiSelector().resourceId(resourceId)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
resourceId, className));
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
resourceId, className));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByResourceId(String id) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().resourceId(id));
|
||||
UiObject object = mDevice.findObject(new UiSelector().resourceId(id));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with resource ID: " + id);
|
||||
throw new UiObjectNotFoundException("Could not find view with resource ID: " + id);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
@@ -534,20 +552,20 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByDescription(String description, String className, long timeout) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().descriptionContains(description)
|
||||
.className(className));
|
||||
UiObject object = mDevice.findObject(new UiSelector().descriptionContains(description)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
description, className));
|
||||
description, className));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByDescription(String desc) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().descriptionContains(desc));
|
||||
UiObject object = mDevice.findObject(new UiSelector().descriptionContains(desc));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with description: " + desc);
|
||||
throw new UiObjectNotFoundException("Could not find view with description: " + desc);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
@@ -557,8 +575,8 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByText(String text, String className, long timeout) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().textContains(text)
|
||||
.className(className));
|
||||
UiObject object = mDevice.findObject(new UiSelector().textContains(text)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
text, className));
|
||||
@@ -567,10 +585,10 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByText(String text) throws Exception {
|
||||
UiObject object = new UiObject(new UiSelector().textContains(text));
|
||||
UiObject object = mDevice.findObject(new UiSelector().textContains(text));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with text: " + text);
|
||||
throw new UiObjectNotFoundException("Could not find view with text: " + text);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
@@ -578,10 +596,10 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
// Helper to select a folder in the gallery
|
||||
public void selectGalleryFolder(String directory) throws Exception {
|
||||
UiObject workdir =
|
||||
new UiObject(new UiSelector().text(directory)
|
||||
.className("android.widget.TextView"));
|
||||
mDevice.findObject(new UiSelector().text(directory)
|
||||
.className("android.widget.TextView"));
|
||||
UiScrollable scrollView =
|
||||
new UiScrollable(new UiSelector().scrollable(true));
|
||||
new UiScrollable(new UiSelector().scrollable(true));
|
||||
|
||||
// If the folder is not present wait for a short time for
|
||||
// the media server to refresh its index.
|
||||
@@ -615,4 +633,96 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
|
||||
throw new UiObjectNotFoundException("Could not find folder : " + directory);
|
||||
}
|
||||
}
|
||||
|
||||
// Override getParams function to decode a url encoded parameter bundle before
|
||||
// passing it to workloads.
|
||||
public Bundle getParams() {
|
||||
// Get the original parameter bundle
|
||||
parameters = getArguments();
|
||||
|
||||
// Decode each parameter in the bundle, except null values and "class", as this
|
||||
// used to control instrumentation and therefore not encoded.
|
||||
for (String key : parameters.keySet()) {
|
||||
String param = parameters.getString(key);
|
||||
if (param != null && !key.equals("class")) {
|
||||
param = android.net.Uri.decode(param);
|
||||
parameters = decode(parameters, key, param);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Helper function to decode a string and insert it as an appropriate type
|
||||
// into a provided bundle with its key.
|
||||
// Each bundle parameter will be a urlencoded string with 2 characters prefixed to the value
|
||||
// used to store the original type information, e.g. 'fl' -> list of floats.
|
||||
private Bundle decode(Bundle parameters, String key, String value) {
|
||||
char value_type = value.charAt(0);
|
||||
char value_dimension = value.charAt(1);
|
||||
String param = value.substring(2);
|
||||
|
||||
if (value_dimension == 's') {
|
||||
if (value_type == 's') {
|
||||
parameters.putString(key, param);
|
||||
} else if (value_type == 'f') {
|
||||
parameters.putFloat(key, Float.parseFloat(param));
|
||||
} else if (value_type == 'd') {
|
||||
parameters.putDouble(key, Double.parseDouble(param));
|
||||
} else if (value_type == 'b') {
|
||||
parameters.putBoolean(key, Boolean.parseBoolean(param));
|
||||
} else if (value_type == 'i') {
|
||||
parameters.putInt(key, Integer.parseInt(param));
|
||||
} else if (value_type == 'n') {
|
||||
parameters.putString(key, "None");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding:" + key + value
|
||||
+ " - unknown format");
|
||||
}
|
||||
} else if (value_dimension == 'l') {
|
||||
return decodeArray(parameters, key, value_type, param);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding:" + key + value
|
||||
+ " - unknown format");
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Helper function to deal with decoding arrays and update the bundle with
|
||||
// an appropriate array type. The string "0newelement0" is used to distinguish
|
||||
// each element for each other in the array when encoded.
|
||||
private Bundle decodeArray(Bundle parameters, String key, char type, String value) {
|
||||
String[] string_list = value.split("0newelement0");
|
||||
if (type == 's') {
|
||||
parameters.putStringArray(key, string_list);
|
||||
}
|
||||
else if (type == 'i') {
|
||||
int[] int_list = new int[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
int_list[i] = Integer.parseInt(string_list[i]);
|
||||
}
|
||||
parameters.putIntArray(key, int_list);
|
||||
} else if (type == 'f') {
|
||||
float[] float_list = new float[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
float_list[i] = Float.parseFloat(string_list[i]);
|
||||
}
|
||||
parameters.putFloatArray(key, float_list);
|
||||
} else if (type == 'd') {
|
||||
double[] double_list = new double[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
double_list[i] = Double.parseDouble(string_list[i]);
|
||||
}
|
||||
parameters.putDoubleArray(key, double_list);
|
||||
} else if (type == 'b') {
|
||||
boolean[] boolean_list = new boolean[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
boolean_list[i] = Boolean.parseBoolean(string_list[i]);
|
||||
}
|
||||
parameters.putBooleanArray(key, boolean_list);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding array: " +
|
||||
value + " - unknown format");
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
}
|
35
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UiAutoUtils.java
vendored
Normal file
35
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UiAutoUtils.java
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/* 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;
|
||||
|
||||
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_name");
|
||||
if (activityName.equals("None")) {
|
||||
launchCommand = String.format("am start --user -3 %s", packageName);
|
||||
}
|
||||
else {
|
||||
launchCommand = String.format("am start --user -3 -n %s/%s", packageName, activityName);
|
||||
}
|
||||
return launchCommand;
|
||||
}
|
||||
}
|
@@ -15,10 +15,31 @@
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class UxPerfUiAutomation extends BaseUiAutomation {
|
||||
|
||||
protected String packageName;
|
||||
protected String packageID;
|
||||
|
||||
//Get application package parameters and create package ID
|
||||
public void getPackageParameters() {
|
||||
packageName = parameters.getString("package_name");
|
||||
packageID = packageName + ":id/";
|
||||
}
|
||||
|
||||
public void setWorkloadParameters(Bundle parameters, String packageName, String packageID){
|
||||
this.parameters = parameters;
|
||||
this.packageName = packageName;
|
||||
this.packageID = packageID;
|
||||
}
|
||||
|
||||
public String getPackageID(){
|
||||
return packageID;
|
||||
}
|
||||
|
||||
private Logger logger = Logger.getLogger(UxPerfUiAutomation.class.getName());
|
||||
|
||||
public enum GestureType { UIDEVICE_SWIPE, UIOBJECT_SWIPE, PINCH };
|
24
wlauto/external/uiauto/build.gradle
vendored
Normal file
24
wlauto/external/uiauto/build.gradle
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.2'
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
12
wlauto/external/uiauto/build.sh
vendored
12
wlauto/external/uiauto/build.sh
vendored
@@ -14,14 +14,18 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
# Ensure gradelw exists before starting
|
||||
if [[ ! -f gradlew ]]; then
|
||||
echo 'gradlew file not found! Check that you are in the right directory.'
|
||||
exit 9
|
||||
fi
|
||||
|
||||
# Build and return appropriate exit code if failed
|
||||
ant build
|
||||
./gradlew clean :app:assembleDebug
|
||||
exit_code=$?
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "ERROR: 'ant build' exited with code $exit_code"
|
||||
echo "ERROR: 'gradle build' exited with code $exit_code"
|
||||
exit $exit_code
|
||||
fi
|
||||
|
||||
cp bin/classes/com/arm/wlauto/uiauto/*.class ../../common/android
|
||||
cp app/build/outputs/aar/app-debug.aar ../../common/android/uiauto.aar
|
||||
|
92
wlauto/external/uiauto/build.xml
vendored
92
wlauto/external/uiauto/build.xml
vendored
@@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="com.arm.wlauto.uiauto" 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>
|
BIN
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed May 03 15:42:44 BST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
160
wlauto/external/uiauto/gradlew
vendored
Executable file
160
wlauto/external/uiauto/gradlew
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
wlauto/external/uiauto/gradlew.bat
vendored
Normal file
90
wlauto/external/uiauto/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
14
wlauto/external/uiauto/project.properties
vendored
14
wlauto/external/uiauto/project.properties
vendored
@@ -1,14 +0,0 @@
|
||||
# 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
|
1
wlauto/external/uiauto/settings.gradle
vendored
Normal file
1
wlauto/external/uiauto/settings.gradle
vendored
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
131
wlauto/instrumentation/acmecape/__init__.py
Normal file
131
wlauto/instrumentation/acmecape/__init__.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#pylint: disable=attribute-defined-outside-init
|
||||
from __future__ import division
|
||||
import csv
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
from fcntl import fcntl, F_GETFL, F_SETFL
|
||||
from string import Template
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
from wlauto import Instrument, Parameter
|
||||
from wlauto.exceptions import HostError
|
||||
from wlauto.utils.misc import which
|
||||
|
||||
|
||||
IIOCAP_CMD_TEMPLATE = Template("""
|
||||
${iio_capture} -n ${host} -b ${buffer_size} -c -f ${outfile} ${iio_device}
|
||||
""")
|
||||
|
||||
|
||||
def _read_nonblock(pipe, size=1024):
|
||||
fd = pipe.fileno()
|
||||
flags = fcntl(fd, F_GETFL)
|
||||
flags |= os.O_NONBLOCK
|
||||
fcntl(fd, F_SETFL, flags)
|
||||
|
||||
output = ''
|
||||
try:
|
||||
while True:
|
||||
output += pipe.read(size)
|
||||
except IOError:
|
||||
pass
|
||||
return output
|
||||
|
||||
|
||||
class AcmeCapeInstrument(Instrument):
|
||||
|
||||
name = 'acmecape'
|
||||
description = """
|
||||
Instrumetnation for the BayLibre ACME cape for power/energy measurment.
|
||||
"""
|
||||
|
||||
parameters = [
|
||||
Parameter('iio-capture', default=which('iio-capture'),
|
||||
description="""
|
||||
Path to the iio-capture binary will be taken from the
|
||||
environment, if not specfied.
|
||||
"""),
|
||||
Parameter('host', default='baylibre-acme.local',
|
||||
description="""
|
||||
Host name (or IP address) of the ACME cape board.
|
||||
"""),
|
||||
Parameter('iio-device', default='iio:device0',
|
||||
description="""
|
||||
"""),
|
||||
Parameter('buffer-size', kind=int, default=256,
|
||||
description="""
|
||||
Size of the capture buffer (in KB).
|
||||
"""),
|
||||
]
|
||||
|
||||
def initialize(self, context):
|
||||
if self.iio_capture is None:
|
||||
raise HostError('Missing iio-capture binary')
|
||||
self.command = None
|
||||
self.subprocess = None
|
||||
|
||||
def setup(self, context):
|
||||
self.outfile = os.path.join(context.output_directory, 'acme-capture.csv')
|
||||
params = dict(
|
||||
iio_capture=self.iio_capture,
|
||||
host=self.host,
|
||||
buffer_size=self.buffer_size,
|
||||
iio_device=self.iio_device,
|
||||
outfile=self.outfile,
|
||||
)
|
||||
self.command = IIOCAP_CMD_TEMPLATE.substitute(**params)
|
||||
self.logger.debug('ACME cape command: {}'.format(self.command))
|
||||
|
||||
def very_fast_start(self, context): # pylint: disable=unused-argument
|
||||
self.subprocess = Popen(self.command.split(), stdout=PIPE, stderr=STDOUT)
|
||||
|
||||
def very_fast_stop(self, context): # pylint: disable=unused-argument
|
||||
self.subprocess.terminate()
|
||||
|
||||
def update_result(self, context):
|
||||
timeout_secs = 10
|
||||
for _ in xrange(timeout_secs):
|
||||
if self.subprocess.poll() is not None:
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
output = _read_nonblock(self.subprocess.stdout)
|
||||
self.subprocess.kill()
|
||||
self.logger.error('iio-capture did not terminate gracefully')
|
||||
if self.subprocess.poll() is None:
|
||||
msg = 'Could not terminate iio-capture:\n{}'
|
||||
raise HostError(msg.format(output))
|
||||
if not os.path.isfile(self.outfile):
|
||||
raise HostError('Output CSV not generated.')
|
||||
|
||||
context.add_iteration_artifact('iio-capture', self.outfile, 'data')
|
||||
if os.stat(self.outfile).st_size == 0:
|
||||
self.logger.warning('"{}" appears to be empty'.format(self.outfile))
|
||||
return
|
||||
self._compute_stats(context)
|
||||
|
||||
def _compute_stats(self, context):
|
||||
with open(self.outfile, 'rb') as fh:
|
||||
reader = csv.reader(fh, skipinitialspace=True)
|
||||
header = reader.next()
|
||||
power_index = header.index('power mW')
|
||||
ts_index = header.index('timestamp ms')
|
||||
|
||||
last_ts = 0.0
|
||||
energy_uj = 0
|
||||
ave_power_mw = 0.0
|
||||
|
||||
for i, row in enumerate(reader):
|
||||
row_power_mw = float(row[power_index])
|
||||
row_ts = float(row[ts_index])
|
||||
|
||||
if i == 0:
|
||||
ave_power_mw = row_power_mw
|
||||
else:
|
||||
ave_power_mw = ave_power_mw + (row_power_mw - ave_power_mw) / i
|
||||
energy_uj += row_power_mw * (row_ts - last_ts)
|
||||
last_ts = row_ts
|
||||
|
||||
context.add_metric('power', ave_power_mw, 'milliwatts')
|
||||
context.add_metric('energy', energy_uj / 1000000, 'joules')
|
@@ -266,7 +266,7 @@ class Daq(Instrument):
|
||||
metric_name = '{}_{}'.format(port, metric)
|
||||
context.result.add_metric(metric_name, round(value, 3), UNITS[metric])
|
||||
self._results[key][metric_name] = round(value, 3)
|
||||
energy = sum(data[metrics.index('power')]) * (self.sampling_rate / 1000000)
|
||||
energy = sum(data[metrics.index('power')]) / self.sampling_rate
|
||||
context.result.add_metric('{}_energy'.format(port), round(energy, 3), UNITS['energy'])
|
||||
|
||||
def teardown(self, context):
|
||||
|
@@ -650,7 +650,7 @@ class EnergyModelInstrument(Instrument):
|
||||
def enable_all_cores(self):
|
||||
counter = Counter(self.device.core_names)
|
||||
for core, number in counter.iteritems():
|
||||
self.device.set_number_of_online_cpus(core, number)
|
||||
self.device.set_number_of_online_cores(core, number)
|
||||
self.big_cpus = self.device.get_online_cpus(self.big_core)
|
||||
self.little_cpus = self.device.get_online_cpus(self.little_core)
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -133,6 +132,12 @@ class FpsInstrument(Instrument):
|
||||
android/services/surfaceflinger/FrameTracker.h (as of the time of writing
|
||||
currently 128) and a frame rate of 60 fps that is applicable to most devices.
|
||||
"""),
|
||||
Parameter('force_surfaceflinger', kind=boolean, default=False,
|
||||
description="""
|
||||
By default, the method to capture fps data is based on Android version.
|
||||
If this is set to true, force the instrument to use the SurfaceFlinger method
|
||||
regardless of its Android version.
|
||||
"""),
|
||||
]
|
||||
|
||||
def __init__(self, device, **kwargs):
|
||||
@@ -153,37 +158,43 @@ class FpsInstrument(Instrument):
|
||||
|
||||
def setup(self, context):
|
||||
workload = context.workload
|
||||
if hasattr(workload, 'view'):
|
||||
self.fps_outfile = os.path.join(context.output_directory, 'fps.csv')
|
||||
self.outfile = os.path.join(context.output_directory, 'frames.csv')
|
||||
# Android M brings a new method of collecting FPS data
|
||||
if self.device.get_sdk_version() >= 23:
|
||||
# gfxinfo takes in the package name rather than a single view/activity
|
||||
# so there is no 'list_command' to run and compare against a list of
|
||||
# views/activities. Additionally, clearing the stats requires the package
|
||||
# so we need to clear for every package in the workload.
|
||||
# Usually there is only one package, but some workloads may run multiple
|
||||
# packages so each one must be reset before continuing
|
||||
self.fps_method = 'gfxinfo'
|
||||
runcmd = 'dumpsys gfxinfo {} framestats'
|
||||
lstcmd = None
|
||||
params = workload.package
|
||||
params = [params] if isinstance(params, basestring) else params
|
||||
for pkg in params:
|
||||
self.device.execute('dumpsys gfxinfo {} reset'.format(pkg))
|
||||
else:
|
||||
self.fps_method = 'surfaceflinger'
|
||||
runcmd = 'dumpsys SurfaceFlinger --latency {}'
|
||||
lstcmd = 'dumpsys SurfaceFlinger --list'
|
||||
params = workload.view
|
||||
self.device.execute('dumpsys SurfaceFlinger --latency-clear ')
|
||||
|
||||
self.collector = LatencyCollector(self.outfile, self.device, params or '',
|
||||
self.keep_raw, self.logger, self.dumpsys_period,
|
||||
runcmd, lstcmd, self.fps_method)
|
||||
else:
|
||||
use_gfxinfo = not self.force_surfaceflinger and (self.device.get_sdk_version() >= 23)
|
||||
if use_gfxinfo and not hasattr(workload, 'package'):
|
||||
self.logger.debug('Workload does not contain a package; falling back to SurfaceFlinger...')
|
||||
use_gfxinfo = False
|
||||
if not use_gfxinfo and not hasattr(workload, 'view'):
|
||||
self.logger.debug('Workload does not contain a view; disabling...')
|
||||
self.is_enabled = False
|
||||
return
|
||||
|
||||
self.fps_outfile = os.path.join(context.output_directory, 'fps.csv')
|
||||
self.outfile = os.path.join(context.output_directory, 'frames.csv')
|
||||
# Android M brings a new method of collecting FPS data
|
||||
if use_gfxinfo:
|
||||
# gfxinfo takes in the package name rather than a single view/activity
|
||||
# so there is no 'list_command' to run and compare against a list of
|
||||
# views/activities. Additionally, clearing the stats requires the package
|
||||
# so we need to clear for every package in the workload.
|
||||
# Usually there is only one package, but some workloads may run multiple
|
||||
# packages so each one must be reset before continuing
|
||||
self.fps_method = 'gfxinfo'
|
||||
runcmd = 'dumpsys gfxinfo {} framestats'
|
||||
lstcmd = None
|
||||
params = workload.package
|
||||
params = [params] if isinstance(params, basestring) else params
|
||||
for pkg in params:
|
||||
self.device.execute('dumpsys gfxinfo {} reset'.format(pkg))
|
||||
else:
|
||||
self.fps_method = 'surfaceflinger'
|
||||
runcmd = 'dumpsys SurfaceFlinger --latency "{}"'
|
||||
lstcmd = 'dumpsys SurfaceFlinger --list'
|
||||
params = workload.view
|
||||
self.device.execute('dumpsys SurfaceFlinger --latency-clear ')
|
||||
|
||||
self.collector = LatencyCollector(self.outfile, self.device, params or '',
|
||||
self.keep_raw, self.logger, self.dumpsys_period,
|
||||
runcmd, lstcmd, self.fps_method)
|
||||
|
||||
def start(self, context):
|
||||
if self.is_enabled:
|
||||
@@ -306,7 +317,7 @@ class LatencyCollector(threading.Thread):
|
||||
# Then check for each activity in this list and if there is a match,
|
||||
# process the output. If no command is provided, then always process.
|
||||
if self.list_command:
|
||||
view_list = self.device.execute(self.list_command).split()
|
||||
view_list = self.device.execute(self.list_command).replace('\r\n', '\n').replace('\r', '\n').split('\n')
|
||||
for activity in self.activities:
|
||||
if activity in view_list:
|
||||
wfh.write(self.device.execute(self.command_template.format(activity)))
|
||||
@@ -387,6 +398,8 @@ class LatencyCollector(threading.Thread):
|
||||
if match:
|
||||
data = match.group(0)[:-1]
|
||||
data = map(int, data.split(','))
|
||||
# Ignore additional fields
|
||||
data = data[:len(self.header)]
|
||||
frame = GfxInfoFrame(*data)
|
||||
if frame not in self.frames:
|
||||
if frame.Flags & GFXINFO_EXEMPT:
|
||||
|
@@ -147,6 +147,8 @@ class FreqSweep(Instrument):
|
||||
spec.id = '{}_{}_{}'.format(spec.id, sweep_spec['label'], freq)
|
||||
spec.classifiers['core'] = sweep_spec['cluster']
|
||||
spec.classifiers['freq'] = freq
|
||||
for k, v in sweep_spec.get('classifiers', {}).iteritems():
|
||||
spec.classifiers[k] = v
|
||||
spec.load(self.device, context.config.ext_loader)
|
||||
spec.workload.init_resources(context)
|
||||
spec.workload.validate()
|
||||
|
@@ -46,14 +46,7 @@ from wlauto.utils.types import list_of_strings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SysfsExtractor(Instrument):
|
||||
|
||||
name = 'sysfs_extractor'
|
||||
description = """
|
||||
Collects the contest of a set of directories, before and after workload execution
|
||||
and diffs the result.
|
||||
|
||||
"""
|
||||
class FsExtractor(Instrument):
|
||||
|
||||
mount_command = 'mount -t tmpfs -o size={} tmpfs {}'
|
||||
extract_timeout = 30
|
||||
@@ -81,12 +74,11 @@ class SysfsExtractor(Instrument):
|
||||
description="""Size of the tempfs partition."""),
|
||||
]
|
||||
|
||||
def initialize(self, context):
|
||||
def initialize_tmpfs(self, context):
|
||||
if not self.device.is_rooted and self.use_tmpfs: # pylint: disable=access-member-before-definition
|
||||
raise ConfigError('use_tempfs must be False for an unrooted device.')
|
||||
elif self.use_tmpfs is None: # pylint: disable=access-member-before-definition
|
||||
self.use_tmpfs = self.device.is_rooted
|
||||
|
||||
if self.use_tmpfs:
|
||||
self.on_device_before = self.device.path.join(self.tmpfs_mount_point, 'before')
|
||||
self.on_device_after = self.device.path.join(self.tmpfs_mount_point, 'after')
|
||||
@@ -197,6 +189,19 @@ class SysfsExtractor(Instrument):
|
||||
return os.path.dirname(as_relative(directory).replace(self.device.path.sep, os.sep))
|
||||
|
||||
|
||||
class SysfsExtractor(FsExtractor):
|
||||
|
||||
name = 'sysfs_extractor'
|
||||
description = """
|
||||
Collects the contest of a set of directories, before and after workload execution
|
||||
and diffs the result.
|
||||
|
||||
"""
|
||||
|
||||
def initialize(self, context):
|
||||
self.initialize_tmpfs(context)
|
||||
|
||||
|
||||
class ExecutionTimeInstrument(Instrument):
|
||||
|
||||
name = 'execution_time'
|
||||
@@ -261,7 +266,7 @@ class InterruptStatsInstrument(Instrument):
|
||||
_diff_interrupt_files(self.before_file, self.after_file, _f(self.diff_file))
|
||||
|
||||
|
||||
class DynamicFrequencyInstrument(SysfsExtractor):
|
||||
class DynamicFrequencyInstrument(FsExtractor):
|
||||
|
||||
name = 'cpufreq'
|
||||
description = """
|
||||
@@ -275,6 +280,9 @@ class DynamicFrequencyInstrument(SysfsExtractor):
|
||||
Parameter('paths', mandatory=False, override=True),
|
||||
]
|
||||
|
||||
def initialize(self, context):
|
||||
self.initialize_tmpfs(context)
|
||||
|
||||
def setup(self, context):
|
||||
self.paths = ['/sys/devices/system/cpu']
|
||||
if self.use_tmpfs:
|
||||
|
@@ -106,8 +106,7 @@ class PerfInstrument(Instrument):
|
||||
self.device.kick_off(command)
|
||||
|
||||
def stop(self, context):
|
||||
as_root = self.device.platform == 'android'
|
||||
self.device.killall('sleep', as_root=as_root)
|
||||
self.device.killall('sleep')
|
||||
|
||||
def update_result(self, context):
|
||||
for label in self.labels:
|
||||
|
@@ -35,13 +35,13 @@ class TraceCmdInstrument(Instrument):
|
||||
|
||||
name = 'trace-cmd'
|
||||
description = """
|
||||
trace-cmd is an instrument which interacts with Ftrace Linux kernel internal
|
||||
trace-cmd is an instrument which interacts with ftrace Linux kernel internal
|
||||
tracer
|
||||
|
||||
From trace-cmd man page:
|
||||
|
||||
trace-cmd command interacts with the Ftrace tracer that is built inside the
|
||||
Linux kernel. It interfaces with the Ftrace specific files found in the
|
||||
trace-cmd command interacts with the ftrace tracer that is built inside the
|
||||
Linux kernel. It interfaces with the ftrace specific files found in the
|
||||
debugfs file system under the tracing directory.
|
||||
|
||||
trace-cmd reads a list of events it will trace, which can be specified in
|
||||
@@ -49,12 +49,8 @@ class TraceCmdInstrument(Instrument):
|
||||
|
||||
trace_events = ['irq*', 'power*']
|
||||
|
||||
If no event is specified in the config file, trace-cmd traces the following events:
|
||||
|
||||
- sched*
|
||||
- irq*
|
||||
- power*
|
||||
- cpufreq_interactive*
|
||||
If no event is specified, a default set of events that are generally considered useful
|
||||
for debugging/profiling purposes will be enabled.
|
||||
|
||||
The list of available events can be obtained by rooting and running the following
|
||||
command line on the device ::
|
||||
@@ -67,7 +63,7 @@ class TraceCmdInstrument(Instrument):
|
||||
trace_cmd_buffer_size = 8000
|
||||
|
||||
The maximum buffer size varies from device to device, but there is a maximum and trying
|
||||
to set buffer size beyound that will fail. If you plan on collecting a lot of trace over
|
||||
to set buffer size beyond that will fail. If you plan on collecting a lot of trace over
|
||||
long periods of time, the buffer size will not be enough and you will only get trace for
|
||||
the last portion of your run. To deal with this you can set the ``trace_mode`` setting to
|
||||
``'record'`` (the default is ``'start'``)::
|
||||
@@ -76,23 +72,28 @@ class TraceCmdInstrument(Instrument):
|
||||
|
||||
This will cause trace-cmd to trace into file(s) on disk, rather than the buffer, and so the
|
||||
limit for the max size of the trace is set by the storage available on device. Bear in mind
|
||||
that ``'record'`` mode *is* more instrusive than the default, so if you do not plan on
|
||||
that ``'record'`` mode *is* more intrusive than the default, so if you do not plan on
|
||||
generating a lot of trace, it is best to use the default ``'start'`` mode.
|
||||
|
||||
.. note:: Mode names correspend to the underlying trace-cmd exectuable's command used to
|
||||
.. note:: Mode names correspond to the underlying trace-cmd executable's command used to
|
||||
implement them. You can find out more about what is happening in each case from
|
||||
trace-cmd documentation: https://lwn.net/Articles/341902/.
|
||||
|
||||
This instrument comes with an Android trace-cmd binary that will be copied and used on the
|
||||
device, however post-processing will be done on-host and you must have trace-cmd installed and
|
||||
in your path. On Ubuntu systems, this may be done with::
|
||||
This instrument comes with an trace-cmd binary that will be copied and used
|
||||
on the device, however post-processing will be, by default, done on-host and you must
|
||||
have trace-cmd installed and in your path. On Ubuntu systems, this may be
|
||||
done with::
|
||||
|
||||
sudo apt-get install trace-cmd
|
||||
|
||||
Alternatively, you may set ``report_on_target`` parameter to ``True`` to enable on-target
|
||||
processing (this is useful when running on non-Linux hosts, but is likely to take longer
|
||||
and may fail on particularly resource-constrained targets).
|
||||
|
||||
"""
|
||||
|
||||
parameters = [
|
||||
Parameter('events', kind=list, default=['sched*', 'irq*', 'power*', 'cpufreq_interactive*'],
|
||||
Parameter('events', kind=list, default=['sched*', 'irq*', 'power*'],
|
||||
global_alias='trace_events',
|
||||
description="""
|
||||
Specifies the list of events to be traced. Each event in the list will be passed to
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -90,12 +90,14 @@ class ReventGetter(ResourceGetter):
|
||||
self.resolver.register(self, 'revent', GetterPriority.package)
|
||||
|
||||
def get(self, resource, **kwargs):
|
||||
# name format: [model/device_name.stage.revent]
|
||||
device_model = resource.owner.device.get_device_model()
|
||||
wa_device_name = resource.owner.device.name
|
||||
for name in [device_model, wa_device_name]:
|
||||
if not name:
|
||||
continue
|
||||
filename = '.'.join([name, resource.stage, 'revent']).lower()
|
||||
self.logger.debug('Trying to get {0}.'.format(str(filename)))
|
||||
location = _d(os.path.join(self.get_base_location(resource), 'revent_files'))
|
||||
for candidate in os.listdir(location):
|
||||
if candidate.lower() == filename.lower():
|
||||
@@ -227,7 +229,8 @@ class DependencyFileGetter(ResourceGetter):
|
||||
def get(self, resource, **kwargs):
|
||||
force = kwargs.get('force')
|
||||
remote_path = os.path.join(self.mount_point, self.relative_path, resource.path)
|
||||
local_path = os.path.join(resource.owner.dependencies_directory, os.path.basename(resource.path))
|
||||
local_path = _f(os.path.join(settings.dependencies_directory, '__remote',
|
||||
resource.owner.name, os.path.basename(resource.path)))
|
||||
|
||||
if not os.path.exists(local_path) or force:
|
||||
if not os.path.exists(remote_path):
|
||||
@@ -288,7 +291,7 @@ class EnvironmentDependencyGetter(ResourceGetter):
|
||||
return path
|
||||
|
||||
|
||||
class ExtensionAssetGetter(DependencyFileGetter):
|
||||
class ExtensionAssetGetter(EnvironmentDependencyGetter):
|
||||
|
||||
name = 'extension_asset'
|
||||
resource_type = 'extension_asset'
|
||||
@@ -552,7 +555,7 @@ def get_from_location_by_extension(resource, location, extension, version=None,
|
||||
|
||||
|
||||
def get_from_list_by_extension(resource, filelist, extension, version=None, variant=None):
|
||||
filelist = [ff for ff in filelist if os.path.splitext(ff)[1].lower().endswith(extension)]
|
||||
filelist = [ff for ff in filelist if os.path.splitext(ff)[1].lower().endswith('.' + extension)]
|
||||
if variant:
|
||||
filelist = [ff for ff in filelist if variant.lower() in os.path.basename(ff).lower()]
|
||||
if version:
|
||||
@@ -562,6 +565,8 @@ def get_from_list_by_extension(resource, filelist, extension, version=None, vari
|
||||
filelist = [ff for ff in filelist if version.lower() in os.path.basename(ff).lower()]
|
||||
if extension == 'apk':
|
||||
filelist = [ff for ff in filelist if not ApkInfo(ff).native_code or resource.platform in ApkInfo(ff).native_code]
|
||||
filelist = [ff for ff in filelist if not resource.package or resource.package == ApkInfo(ff).package]
|
||||
filelist = [ff for ff in filelist if resource.uiauto == ('com.arm.wlauto.uiauto' in ApkInfo(ff).package)]
|
||||
if len(filelist) == 1:
|
||||
return filelist[0]
|
||||
elif not filelist:
|
||||
|
@@ -121,6 +121,20 @@ class CpuStatesProcessor(ResultProcessor):
|
||||
:`ignore`: The start marker will be ignored. All events in the trace will be used.
|
||||
:`error`: An error will be raised if the start marker is not found in the trace.
|
||||
:`try`: If the start marker is not found, all events in the trace will be used.
|
||||
"""),
|
||||
Parameter('no_idle', kind=bool, default=False,
|
||||
description="""
|
||||
Indicate that there will be no idle transitions in the trace. By default, a core
|
||||
will be reported as being in an "unknown" state until the first idle transtion for
|
||||
that core. Normally, this is not an issue, as cores are "nudged" as part of the setup
|
||||
to ensure that there is an idle transtion before the meassured region. However, if all
|
||||
idle states for the core have been disabled, or if the kernel does not have cpuidle,
|
||||
the nudge will not result in an idle transition, which would cause the cores to be
|
||||
reported to be in "unknown" state for the entire execution.
|
||||
|
||||
If this parameter is set to ``True``, the processor will assuming that cores are
|
||||
running prior to the begining of the issue, and they will leave unknown state on
|
||||
the first frequency transition.
|
||||
""")
|
||||
]
|
||||
|
||||
@@ -215,6 +229,7 @@ class CpuStatesProcessor(ResultProcessor):
|
||||
cpu_utilisation=cpu_utilisation,
|
||||
max_freq_list=self.max_freq_list,
|
||||
start_marker_handling=self.start_marker_handling,
|
||||
no_idle=self.no_idle,
|
||||
)
|
||||
parallel_report = reports.pop(0)
|
||||
powerstate_report = reports.pop(0)
|
||||
|
@@ -29,6 +29,7 @@ from wlauto import File, Parameter, ResultProcessor
|
||||
from wlauto.exceptions import ConfigError, ResultProcessorError
|
||||
import wlauto.utils.ipython as ipython
|
||||
from wlauto.utils.misc import open_file
|
||||
from wlauto.utils.types import file_path
|
||||
|
||||
|
||||
DEFAULT_NOTEBOOK_TEMPLATE = 'template.ipynb'
|
||||
@@ -59,6 +60,7 @@ class IPythonNotebookExporter(ResultProcessor):
|
||||
|
||||
parameters = [
|
||||
Parameter('notebook_template', default=DEFAULT_NOTEBOOK_TEMPLATE,
|
||||
kind=file_path,
|
||||
description='''Filename of the ipython notebook template. If
|
||||
no `notebook_template` is specified, the example template
|
||||
above is used.'''),
|
||||
@@ -72,7 +74,7 @@ class IPythonNotebookExporter(ResultProcessor):
|
||||
ending in ``.pdf``.'''),
|
||||
Parameter('show_notebook', kind=bool,
|
||||
description='Open a web browser with the resulting notebook.'),
|
||||
Parameter('notebook_directory',
|
||||
Parameter('notebook_directory', kind=file_path,
|
||||
description='''Path to the notebooks directory served by the
|
||||
ipython notebook server. You must set it if
|
||||
``show_notebook`` is selected. The ipython notebook
|
||||
|
@@ -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[:-6]) 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))
|
||||
|
@@ -17,11 +17,12 @@
|
||||
# pylint: disable=R0201
|
||||
from unittest import TestCase
|
||||
|
||||
from nose.tools import raises, assert_equal, assert_not_equal # pylint: disable=E0611
|
||||
from nose.tools import raises, assert_equal, assert_not_equal, assert_true # pylint: disable=E0611
|
||||
|
||||
from wlauto.utils.android import check_output
|
||||
from wlauto.utils.misc import merge_dicts, merge_lists, TimeoutError
|
||||
from wlauto.utils.types import list_or_integer, list_or_bool, caseless_string, arguments
|
||||
from wlauto.utils.types import (list_or_integer, list_or_bool, caseless_string, arguments,
|
||||
ParameterDict)
|
||||
|
||||
|
||||
class TestCheckOutput(TestCase):
|
||||
@@ -88,3 +89,126 @@ class TestTypes(TestCase):
|
||||
assert_equal(arguments('--foo 7 --bar "fizz buzz"'),
|
||||
['--foo', '7', '--bar', 'fizz buzz'])
|
||||
assert_equal(arguments(['test', 42]), ['test', '42'])
|
||||
|
||||
class TestParameterDict(TestCase):
|
||||
|
||||
# Define test parameters
|
||||
orig_params = {
|
||||
'string' : 'A Test String',
|
||||
'string_list' : ['A Test', 'List', 'With', '\n in.'],
|
||||
'bool_list' : [False, True, True],
|
||||
'int' : 42,
|
||||
'float' : 1.23,
|
||||
'long' : long(987),
|
||||
'none' : None,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.params = ParameterDict()
|
||||
self.params['string'] = self.orig_params['string']
|
||||
self.params['string_list'] = self.orig_params['string_list']
|
||||
self.params['bool_list'] = self.orig_params['bool_list']
|
||||
self.params['int'] = self.orig_params['int']
|
||||
self.params['float'] = self.orig_params['float']
|
||||
self.params['long'] = self.orig_params['long']
|
||||
self.params['none'] = self.orig_params['none']
|
||||
|
||||
# Test values are encoded correctly
|
||||
def test_getEncodedItems(self):
|
||||
encoded = {
|
||||
'string' : 'ssA%20Test%20String',
|
||||
'string_list' : 'slA%20Test0newelement0List0newelement0With0newelement0%0A%20in.',
|
||||
'bool_list' : 'blFalse0newelement0True0newelement0True',
|
||||
'int' : 'is42',
|
||||
'float' : 'fs1.23',
|
||||
'long' : 'ds987',
|
||||
'none' : 'nsNone',
|
||||
}
|
||||
# Test iter_encoded_items
|
||||
for k, v in self.params.iter_encoded_items():
|
||||
assert_equal(v, encoded[k])
|
||||
|
||||
# Test get single encoded value
|
||||
assert_equal(self.params.get_encoded_value('string'), encoded['string'])
|
||||
assert_equal(self.params.get_encoded_value('string_list'), encoded['string_list'])
|
||||
assert_equal(self.params.get_encoded_value('bool_list'), encoded['bool_list'])
|
||||
assert_equal(self.params.get_encoded_value('int'), encoded['int'])
|
||||
assert_equal(self.params.get_encoded_value('float'), encoded['float'])
|
||||
assert_equal(self.params.get_encoded_value('long'), encoded['long'])
|
||||
assert_equal(self.params.get_encoded_value('none'), encoded['none'])
|
||||
|
||||
# Test it behaves like a normal dict
|
||||
def test_getitem(self):
|
||||
assert_equal(self.params['string'], self.orig_params['string'])
|
||||
assert_equal(self.params['string_list'], self.orig_params['string_list'])
|
||||
assert_equal(self.params['bool_list'], self.orig_params['bool_list'])
|
||||
assert_equal(self.params['int'], self.orig_params['int'])
|
||||
assert_equal(self.params['float'], self.orig_params['float'])
|
||||
assert_equal(self.params['long'], self.orig_params['long'])
|
||||
assert_equal(self.params['none'], self.orig_params['none'])
|
||||
|
||||
def test_get(self):
|
||||
assert_equal(self.params.get('string'), self.orig_params['string'])
|
||||
assert_equal(self.params.get('string_list'), self.orig_params['string_list'])
|
||||
assert_equal(self.params.get('bool_list'), self.orig_params['bool_list'])
|
||||
assert_equal(self.params.get('int'), self.orig_params['int'])
|
||||
assert_equal(self.params.get('float'), self.orig_params['float'])
|
||||
assert_equal(self.params.get('long'), self.orig_params['long'])
|
||||
assert_equal(self.params.get('none'), self.orig_params['none'])
|
||||
|
||||
def test_contains(self):
|
||||
assert_true(self.orig_params['string'] in self.params.values())
|
||||
assert_true(self.orig_params['string_list'] in self.params.values())
|
||||
assert_true(self.orig_params['bool_list'] in self.params.values())
|
||||
assert_true(self.orig_params['int'] in self.params.values())
|
||||
assert_true(self.orig_params['float'] in self.params.values())
|
||||
assert_true(self.orig_params['long'] in self.params.values())
|
||||
assert_true(self.orig_params['none'] in self.params.values())
|
||||
|
||||
def test_pop(self):
|
||||
assert_equal(self.params.pop('string'), self.orig_params['string'])
|
||||
assert_equal(self.params.pop('string_list'), self.orig_params['string_list'])
|
||||
assert_equal(self.params.pop('bool_list'), self.orig_params['bool_list'])
|
||||
assert_equal(self.params.pop('int'), self.orig_params['int'])
|
||||
assert_equal(self.params.pop('float'), self.orig_params['float'])
|
||||
assert_equal(self.params.pop('long'), self.orig_params['long'])
|
||||
assert_equal(self.params.pop('none'), self.orig_params['none'])
|
||||
|
||||
self.params['string'] = self.orig_params['string']
|
||||
assert_equal(self.params.popitem(), ('string', self.orig_params['string']))
|
||||
|
||||
def test_iteritems(self):
|
||||
for k, v in self.params.iteritems():
|
||||
assert_equal(v, self.orig_params[k])
|
||||
|
||||
def test_parameter_dict_update(self):
|
||||
params_1 = ParameterDict()
|
||||
params_2 = ParameterDict()
|
||||
|
||||
# Test two ParameterDicts
|
||||
params_1['string'] = self.orig_params['string']
|
||||
params_1['string_list'] = self.orig_params['string_list']
|
||||
params_1['bool_list'] = self.orig_params['bool_list']
|
||||
params_2['int'] = self.orig_params['int']
|
||||
params_2['float'] = self.orig_params['float']
|
||||
params_2['long'] = self.orig_params['long']
|
||||
params_2['none'] = self.orig_params['none']
|
||||
|
||||
params_1.update(params_2)
|
||||
assert_equal(params_1, self.params)
|
||||
|
||||
# Test update with normal dict
|
||||
params_3 = ParameterDict()
|
||||
std_dict = dict()
|
||||
|
||||
params_3['string'] = self.orig_params['string']
|
||||
std_dict['string_list'] = self.orig_params['string_list']
|
||||
std_dict['bool_list'] = self.orig_params['bool_list']
|
||||
std_dict['int'] = self.orig_params['int']
|
||||
std_dict['float'] = self.orig_params['float']
|
||||
std_dict['long'] = self.orig_params['long']
|
||||
std_dict['none'] = self.orig_params['none']
|
||||
|
||||
params_3.update(std_dict)
|
||||
for key in params_3.keys():
|
||||
assert_equal(params_3[key], self.params[key])
|
||||
|
@@ -27,7 +27,7 @@ import re
|
||||
|
||||
from wlauto.exceptions import DeviceError, ConfigError, HostError, WAError
|
||||
from wlauto.utils.misc import (check_output, escape_single_quotes,
|
||||
escape_double_quotes, get_null,
|
||||
escape_double_quotes, get_null, which,
|
||||
CalledProcessErrorWithStderr, ABI_MAP)
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ logger = logging.getLogger('android')
|
||||
# See:
|
||||
# http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels
|
||||
ANDROID_VERSION_MAP = {
|
||||
25: 'NOUGAT_MR1',
|
||||
24: 'NOUGAT',
|
||||
23: 'MARSHMALLOW',
|
||||
22: 'LOLLIPOP_MR1',
|
||||
21: 'LOLLIPOP',
|
||||
20: 'KITKAT_WATCH',
|
||||
@@ -100,6 +103,24 @@ ANDROID_NORMAL_PERMISSIONS = [
|
||||
'UNINSTALL_SHORTCUT',
|
||||
]
|
||||
|
||||
ANDROID_UNCHANGEABLE_PERMISSIONS = [
|
||||
'USE_CREDENTIALS',
|
||||
'MANAGE_ACCOUNTS',
|
||||
'DOWNLOAD_WITHOUT_NOTIFICATION',
|
||||
'AUTHENTICATE_ACCOUNTS',
|
||||
'WRITE_SETTINGS',
|
||||
'WRITE_SYNC_STATS',
|
||||
'SUBSCRIBED_FEEDS_WRITE',
|
||||
'SUBSCRIBED_FEEDS_READ',
|
||||
'READ_PROFILE',
|
||||
'WRITE_MEDIA_STORAGE',
|
||||
'RESTART_PACKAGES',
|
||||
'MOUNT_UNMOUNT_FILESYSTEMS',
|
||||
'CLEAR_APP_CACHE',
|
||||
'GET_TASKS',
|
||||
]
|
||||
|
||||
|
||||
# Package versions that are known to have problems with AndroidUiAutoBenchmark workloads.
|
||||
# NOTE: ABI versions are not included.
|
||||
UNSUPPORTED_PACKAGES = {
|
||||
@@ -346,7 +367,10 @@ def adb_shell(device, command, timeout=None, check_exit_code=False, as_root=Fals
|
||||
message += '\n{}'.format(am_start_error.findall(output)[0])
|
||||
raise DeviceError(message)
|
||||
else:
|
||||
raise DeviceError('adb has returned early; did not get an exit code. Was kill-server invoked?')
|
||||
message = 'adb has returned early; did not get an exit code. '\
|
||||
'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\
|
||||
'-----ERROR:\n-----\n{}\n-----'
|
||||
raise DeviceError(message.format(raw_output, error))
|
||||
else: # do not check exit code
|
||||
try:
|
||||
output, error = check_output(full_command, timeout, shell=True)
|
||||
@@ -434,19 +458,26 @@ def _initialize_without_android_home(env):
|
||||
def _init_common(env):
|
||||
logger.debug('ANDROID_HOME: {}'.format(env.android_home))
|
||||
build_tools_directory = os.path.join(env.android_home, 'build-tools')
|
||||
if not os.path.isdir(build_tools_directory):
|
||||
msg = 'ANDROID_HOME ({}) does not appear to have valid Android SDK install (cannot find build-tools)'
|
||||
raise HostError(msg.format(env.android_home))
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logger.debug('Using aapt for version {}'.format(version))
|
||||
env.aapt = aapt_path
|
||||
break
|
||||
if os.path.isdir(build_tools_directory):
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logger.debug('Using aapt for version {}'.format(version))
|
||||
env.aapt = aapt_path
|
||||
break
|
||||
else:
|
||||
raise HostError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
else:
|
||||
raise HostError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
|
||||
# User may already installed 'aapt' from package provided by distro's
|
||||
# Try finding in $PATH
|
||||
aapt_path = which('aapt')
|
||||
if aapt_path:
|
||||
logger.debug('Using aapt from {}'.format(aapt_path))
|
||||
env.aapt = aapt_path
|
||||
else:
|
||||
msg = 'ANDROID_HOME ({}) does not appear to have valid Android SDK install (cannot find build-tools)'
|
||||
raise HostError(msg.format(env.android_home))
|
||||
|
||||
def _check_env():
|
||||
global android_home, platform_tools, adb, aapt # pylint: disable=W0603
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
@@ -36,20 +36,23 @@ NBFORMAT_VERSION = 3
|
||||
|
||||
|
||||
if IPython:
|
||||
if LooseVersion('5.0.0') > LooseVersion(IPython.__version__) >= LooseVersion('4.0.0'):
|
||||
import nbformat
|
||||
from jupyter_client.manager import KernelManager
|
||||
if LooseVersion('6.0.0') > LooseVersion(IPython.__version__) >= LooseVersion('4.0.0'):
|
||||
try:
|
||||
import nbformat
|
||||
from jupyter_client.manager import KernelManager
|
||||
|
||||
def read_notebook(notebook_in): # pylint: disable=function-redefined
|
||||
return nbformat.reads(notebook_in, NBFORMAT_VERSION) # pylint: disable=E1101
|
||||
def read_notebook(notebook_in): # pylint: disable=function-redefined
|
||||
return nbformat.reads(notebook_in, NBFORMAT_VERSION) # pylint: disable=E1101
|
||||
|
||||
def write_notebook(notebook, fout): # pylint: disable=function-redefined
|
||||
nbformat.write(notebook, fout) # pylint: disable=E1101
|
||||
def write_notebook(notebook, fout): # pylint: disable=function-redefined
|
||||
nbformat.write(notebook, fout) # pylint: disable=E1101
|
||||
|
||||
NotebookNode = nbformat.NotebookNode # pylint: disable=E1101
|
||||
NotebookNode = nbformat.NotebookNode # pylint: disable=E1101
|
||||
|
||||
IPYTHON_NBCONVERT_HTML = ['jupyter', 'nbconvert', '--to html']
|
||||
IPYTHON_NBCONVERT_PDF = ['jupyter', 'nbconvert', '--to pdf']
|
||||
IPYTHON_NBCONVERT_HTML = ['jupyter', 'nbconvert', '--to html']
|
||||
IPYTHON_NBCONVERT_PDF = ['jupyter', 'nbconvert', '--to pdf']
|
||||
except ImportError:
|
||||
import_error_str = 'Please install "jupyter" as well as "ipython" packages.'
|
||||
|
||||
elif LooseVersion('4.0.0') > LooseVersion(IPython.__version__) >= LooseVersion('3.0.0'):
|
||||
from IPython.kernel import KernelManager
|
||||
|
@@ -848,3 +848,16 @@ def memoized(func):
|
||||
return __memo_cache[id_string]
|
||||
|
||||
return memoize_wrapper
|
||||
|
||||
|
||||
def commonprefix(file_list, sep=os.sep):
|
||||
"""
|
||||
Find the lowest common base folder of a passed list of files.
|
||||
"""
|
||||
common_path = os.path.commonprefix(file_list)
|
||||
cp_split = common_path.split(sep)
|
||||
other_split = file_list[0].split(sep)
|
||||
last = len(cp_split) - 1
|
||||
if cp_split[last] != other_split[last]:
|
||||
cp_split = cp_split[:-1]
|
||||
return sep.join(cp_split)
|
||||
|
@@ -113,11 +113,12 @@ class SystemPowerState(object):
|
||||
def num_cores(self):
|
||||
return len(self.cpus)
|
||||
|
||||
def __init__(self, num_cores):
|
||||
def __init__(self, num_cores, no_idle=False):
|
||||
self.timestamp = None
|
||||
self.cpus = []
|
||||
idle_state = -1 if no_idle else None
|
||||
for _ in xrange(num_cores):
|
||||
self.cpus.append(CpuPowerState())
|
||||
self.cpus.append(CpuPowerState(idle_state=idle_state))
|
||||
|
||||
def copy(self):
|
||||
new = SystemPowerState(self.num_cores)
|
||||
@@ -154,9 +155,9 @@ class PowerStateProcessor(object):
|
||||
|
||||
def __init__(self, core_clusters, num_idle_states,
|
||||
first_cluster_state=sys.maxint, first_system_state=sys.maxint,
|
||||
wait_for_start_marker=False):
|
||||
self.power_state = SystemPowerState(len(core_clusters))
|
||||
self.requested_states = defaultdict(lambda: -1) # cpu_id -> requeseted state
|
||||
wait_for_start_marker=False, no_idle=False):
|
||||
self.power_state = SystemPowerState(len(core_clusters), no_idle=no_idle)
|
||||
self.requested_states = {} # cpu_id -> requeseted state
|
||||
self.wait_for_start_marker = wait_for_start_marker
|
||||
self._saw_start_marker = False
|
||||
self._saw_stop_marker = False
|
||||
@@ -230,6 +231,7 @@ class PowerStateProcessor(object):
|
||||
def _process_idle_entry(self, event):
|
||||
if self.cpu_states[event.cpu_id].is_idling:
|
||||
raise ValueError('Got idle state entry event for an idling core: {}'.format(event))
|
||||
self.requested_states[event.cpu_id] = event.idle_state
|
||||
self._try_transition_to_idle_state(event.cpu_id, event.idle_state)
|
||||
|
||||
def _process_idle_exit(self, event):
|
||||
@@ -250,18 +252,11 @@ class PowerStateProcessor(object):
|
||||
|
||||
def _try_transition_to_idle_state(self, cpu_id, idle_state):
|
||||
related_ids = self.idle_related_cpus[(cpu_id, idle_state)]
|
||||
idle_state = idle_state
|
||||
|
||||
# Tristate: True - can transition, False - can't transition,
|
||||
# None - unknown idle state on at least one related cpu
|
||||
transition_check = self._can_enter_state(related_ids, idle_state)
|
||||
|
||||
if not transition_check:
|
||||
# If we can't enter an idle state right now, record that we've
|
||||
# requested it, so that we may enter it later (once all related
|
||||
# cpus also want a state at least as deep).
|
||||
self.requested_states[cpu_id] = idle_state
|
||||
|
||||
if transition_check is None:
|
||||
# Unknown state on a related cpu means we're not sure whether we're
|
||||
# entering requested state or a shallower one
|
||||
@@ -276,8 +271,6 @@ class PowerStateProcessor(object):
|
||||
self.cpu_states[cpu_id].idle_state = idle_state
|
||||
for rid in related_ids:
|
||||
self.cpu_states[rid].idle_state = idle_state
|
||||
if self.requested_states[rid] == idle_state:
|
||||
del self.requested_states[rid] # request satisfied, so remove
|
||||
|
||||
def _can_enter_state(self, related_ids, state):
|
||||
"""
|
||||
@@ -288,12 +281,13 @@ class PowerStateProcessor(object):
|
||||
|
||||
"""
|
||||
for rid in related_ids:
|
||||
rid_requested_state = self.requested_states[rid]
|
||||
rid_requested_state = self.requested_states.get(rid, None)
|
||||
rid_current_state = self.cpu_states[rid].idle_state
|
||||
if rid_current_state is None:
|
||||
return None
|
||||
if rid_current_state < state and rid_requested_state < state:
|
||||
return False
|
||||
if rid_current_state < state:
|
||||
if rid_requested_state is None or rid_requested_state < state:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -340,6 +334,36 @@ def gather_core_states(system_state_stream, freq_dependent_idle_states=None): #
|
||||
yield (system_state.timestamp, core_states)
|
||||
|
||||
|
||||
def record_state_transitions(reporter, stream):
|
||||
for event in stream:
|
||||
if event.kind == 'transition':
|
||||
reporter.record_transition(event)
|
||||
yield event
|
||||
|
||||
|
||||
class PowerStateTransitions(object):
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self._wfh = open(filepath, 'w')
|
||||
self.writer = csv.writer(self._wfh)
|
||||
headers = ['timestamp', 'cpu_id', 'frequency', 'idle_state']
|
||||
self.writer.writerow(headers)
|
||||
|
||||
def update(self, timestamp, core_states): # NOQA
|
||||
# Just recording transitions, not doing anything
|
||||
# with states.
|
||||
pass
|
||||
|
||||
def record_transition(self, transition):
|
||||
row = [transition.timestamp, transition.cpu_id,
|
||||
transition.frequency, transition.idle_state]
|
||||
self.writer.writerow(row)
|
||||
|
||||
def report(self):
|
||||
self._wfh.close()
|
||||
|
||||
|
||||
class PowerStateTimeline(object):
|
||||
|
||||
def __init__(self, filepath, core_names, idle_state_names):
|
||||
@@ -626,7 +650,8 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
|
||||
num_idle_states, first_cluster_state=sys.maxint,
|
||||
first_system_state=sys.maxint, use_ratios=False,
|
||||
timeline_csv_file=None, cpu_utilisation=None,
|
||||
max_freq_list=None, start_marker_handling='error'):
|
||||
max_freq_list=None, start_marker_handling='error',
|
||||
transitions_csv_file=None, no_idle=False):
|
||||
# pylint: disable=too-many-locals,too-many-branches
|
||||
trace = TraceCmdTrace(trace_file,
|
||||
filter_markers=False,
|
||||
@@ -647,7 +672,8 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
|
||||
num_idle_states=num_idle_states,
|
||||
first_cluster_state=first_cluster_state,
|
||||
first_system_state=first_system_state,
|
||||
wait_for_start_marker=wait_for_start_marker)
|
||||
wait_for_start_marker=wait_for_start_marker,
|
||||
no_idle=no_idle)
|
||||
reporters = [
|
||||
ParallelStats(core_clusters, use_ratios),
|
||||
PowerStateStats(core_names, idle_state_names, use_ratios)
|
||||
@@ -663,7 +689,13 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
|
||||
|
||||
event_stream = trace.parse()
|
||||
transition_stream = stream_cpu_power_transitions(event_stream)
|
||||
power_state_stream = ps_processor.process(transition_stream)
|
||||
if transitions_csv_file:
|
||||
trans_reporter = PowerStateTransitions(transitions_csv_file)
|
||||
reporters.append(trans_reporter)
|
||||
recorded_trans_stream = record_state_transitions(trans_reporter, transition_stream)
|
||||
power_state_stream = ps_processor.process(recorded_trans_stream)
|
||||
else:
|
||||
power_state_stream = ps_processor.process(transition_stream)
|
||||
core_state_stream = gather_core_states(power_state_stream)
|
||||
|
||||
for timestamp, states in core_state_stream:
|
||||
@@ -700,6 +732,8 @@ def main():
|
||||
cpu_utilisation=args.cpu_utilisation,
|
||||
max_freq_list=args.max_freq_list,
|
||||
start_marker_handling=args.start_marker_handling,
|
||||
transitions_csv_file=args.transitions_file,
|
||||
no_idle=args.no_idle,
|
||||
)
|
||||
|
||||
parallel_report = reports.pop(0)
|
||||
@@ -773,6 +807,11 @@ def parse_arguments(): # NOQA
|
||||
A timeline of core power states will be written to the specified file in
|
||||
CSV format.
|
||||
''')
|
||||
parser.add_argument('-T', '--transitions-file', metavar='FILE',
|
||||
help='''
|
||||
A timeline of core power state transitions will be
|
||||
written to the specified file in CSV format.
|
||||
''')
|
||||
parser.add_argument('-u', '--cpu-utilisation', metavar='FILE',
|
||||
help='''
|
||||
A timeline of cpu(s) utilisation will be written to the specified file in
|
||||
@@ -797,6 +836,13 @@ def parse_arguments(): # NOQA
|
||||
error: An error will be raised if the start marker is not found in the trace.
|
||||
try: If the start marker is not found, all events in the trace will be used.
|
||||
''')
|
||||
parser.add_argument('-N', '--no-idle', action='store_true',
|
||||
help='''
|
||||
Assume that cpuidle is not present or disabled on the system, and therefore that the
|
||||
initial state of the cores is that they are running. This flag is necessary because
|
||||
the processor assumes the cores are in an unknown state until it sees the first idle
|
||||
transition, which will never come if cpuidle is absent.
|
||||
''')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@@ -142,9 +142,10 @@ class ReventRecording(object):
|
||||
first = last = events.next()
|
||||
except StopIteration:
|
||||
self._duration = 0
|
||||
for last in events:
|
||||
pass
|
||||
self._duration = (last.time - first.time).total_seconds()
|
||||
else:
|
||||
for last in events:
|
||||
pass
|
||||
self._duration = (last.time - first.time).total_seconds()
|
||||
else: # not streaming
|
||||
if not self._events:
|
||||
self._duration = 0
|
||||
|
@@ -22,6 +22,7 @@ import re
|
||||
import threading
|
||||
import tempfile
|
||||
import shutil
|
||||
import time
|
||||
|
||||
from pexpect import EOF, TIMEOUT, spawn, pxssh
|
||||
|
||||
@@ -36,21 +37,45 @@ sshpass = None
|
||||
logger = logging.getLogger('ssh')
|
||||
|
||||
|
||||
def ssh_get_shell(host, username, password=None, keyfile=None, port=None, timeout=10, telnet=False):
|
||||
def ssh_get_shell(host, username, password=None, keyfile=None, port=None, timeout=10, telnet=False, original_prompt=None):
|
||||
_check_env()
|
||||
if telnet:
|
||||
if keyfile:
|
||||
raise ConfigError('keyfile may not be used with a telnet connection.')
|
||||
conn = TelnetConnection()
|
||||
else: # ssh
|
||||
conn = pxssh.pxssh() # pylint: disable=redefined-variable-type
|
||||
try:
|
||||
if keyfile:
|
||||
conn.login(host, username, ssh_key=keyfile, port=port, login_timeout=timeout)
|
||||
else:
|
||||
conn.login(host, username, password, port=port, login_timeout=timeout)
|
||||
except EOF:
|
||||
raise DeviceError('Could not connect to {}; is the host name correct?'.format(host))
|
||||
start_time = time.time()
|
||||
extra_login_args = {}
|
||||
while True:
|
||||
if telnet:
|
||||
if keyfile:
|
||||
raise ValueError('keyfile may not be used with a telnet connection.')
|
||||
conn = TelnetConnection()
|
||||
if original_prompt:
|
||||
extra_login_args['original_prompt'] = original_prompt
|
||||
if port is None:
|
||||
port = 23
|
||||
else: # ssh
|
||||
conn = pxssh.pxssh()
|
||||
|
||||
try:
|
||||
if keyfile:
|
||||
conn.login(host, username, ssh_key=keyfile, port=port, login_timeout=timeout, **extra_login_args)
|
||||
else:
|
||||
conn.login(host, username, password, port=port, login_timeout=timeout, **extra_login_args)
|
||||
break
|
||||
except EOF:
|
||||
timeout -= time.time() - start_time
|
||||
if timeout <= 0:
|
||||
message = 'Could not connect to {}; is the host name correct?'
|
||||
raise DeviceError(message.format(host))
|
||||
time.sleep(5)
|
||||
|
||||
conn.sendline('shopt -s checkwinsize')
|
||||
conn.prompt()
|
||||
conn.setwinsize(500,200)
|
||||
conn.sendline('')
|
||||
conn.prompt()
|
||||
conn.sendline('stty rows 500')
|
||||
conn.prompt()
|
||||
conn.sendline('stty cols 200')
|
||||
conn.prompt()
|
||||
conn.setecho(False)
|
||||
return conn
|
||||
|
||||
|
||||
|
@@ -136,9 +136,6 @@ def verify_state(screenshot_file, state_defs_path, workload_phase):
|
||||
with open(statedefs_file) as fh:
|
||||
state_definitions = yaml.load(fh)
|
||||
|
||||
# run a match on the screenshot
|
||||
matched_state = match_state(screenshot_file, state_defs_path, state_definitions)
|
||||
|
||||
# find what the expected state is for the given workload phase
|
||||
expected_state = None
|
||||
for phase in state_definitions["workload_phases"]:
|
||||
@@ -148,4 +145,7 @@ def verify_state(screenshot_file, state_defs_path, workload_phase):
|
||||
if expected_state is None:
|
||||
raise StateDefinitionError("Phase not defined")
|
||||
|
||||
# run a match on the screenshot
|
||||
matched_state = match_state(screenshot_file, state_defs_path, state_definitions)
|
||||
|
||||
return expected_state == matched_state
|
||||
|
@@ -41,7 +41,22 @@ class TraceCmdEvent(object):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ['thread', 'reporting_cpu_id', 'timestamp', 'name', 'text', 'fields']
|
||||
__slots__ = ['thread', 'reporting_cpu_id', 'timestamp', 'name', 'text', '_fields', '_parser']
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
if self._fields is not None:
|
||||
return self._fields
|
||||
self._fields = {}
|
||||
|
||||
if self._parser:
|
||||
try:
|
||||
self._parser(self, self.text)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# unknown format assume user does not care or know how to
|
||||
# parse self.text
|
||||
pass
|
||||
return self._fields
|
||||
|
||||
def __init__(self, thread, cpu_id, ts, name, body, parser=None):
|
||||
"""
|
||||
@@ -70,15 +85,8 @@ class TraceCmdEvent(object):
|
||||
self.timestamp = numeric(ts)
|
||||
self.name = name
|
||||
self.text = body
|
||||
self.fields = {}
|
||||
|
||||
if parser:
|
||||
try:
|
||||
parser(self, self.text)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# unknown format assume user does not care or know how to
|
||||
# parse self.text
|
||||
pass
|
||||
self._fields = None
|
||||
self._parser = parser
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
@@ -143,7 +151,7 @@ def default_body_parser(event, text):
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
event.fields[k] = v
|
||||
event._fields[k] = v
|
||||
|
||||
|
||||
def regex_body_parser(regex, flags=0):
|
||||
@@ -166,9 +174,9 @@ def regex_body_parser(regex, flags=0):
|
||||
if match:
|
||||
for k, v in match.groupdict().iteritems():
|
||||
try:
|
||||
event.fields[k] = int(v)
|
||||
event._fields[k] = int(v)
|
||||
except ValueError:
|
||||
event.fields[k] = v
|
||||
event._fields[k] = v
|
||||
|
||||
return regex_parser_func
|
||||
|
||||
@@ -191,6 +199,20 @@ def sched_switch_parser(event, text):
|
||||
return default_body_parser(event, text.replace('==>', ''))
|
||||
|
||||
|
||||
def sched_stat_parser(event, text):
|
||||
"""
|
||||
sched_stat_* events unclude the units, "[ns]", in an otherwise
|
||||
regular key=value sequence; so the units need to be stripped out first.
|
||||
"""
|
||||
return default_body_parser(event, text.replace(' [ns]', ''))
|
||||
|
||||
|
||||
def sched_wakeup_parser(event, text):
|
||||
regex = re.compile(r'(?P<comm>\S+):(?P<pid>\d+) \[(?P<prio>\d+)\] success=(?P<success>\d) CPU:(?P<cpu>\d+)')
|
||||
parse_func = regex_body_parser(regex)
|
||||
return parse_func(event, text)
|
||||
|
||||
|
||||
# Maps event onto the corresponding parser for its body text. A parser may be
|
||||
# a callable with signature
|
||||
#
|
||||
@@ -200,12 +222,16 @@ def sched_switch_parser(event, text):
|
||||
# regex). In case of a string/regex, its named groups will be used to populate
|
||||
# the event's attributes.
|
||||
EVENT_PARSER_MAP = {
|
||||
'sched_stat_blocked': sched_stat_parser,
|
||||
'sched_stat_iowait': sched_stat_parser,
|
||||
'sched_stat_runtime': sched_stat_parser,
|
||||
'sched_stat_sleep': sched_stat_parser,
|
||||
'sched_stat_wait': sched_stat_parser,
|
||||
'sched_switch': sched_switch_parser,
|
||||
'sched_wakeup': sched_wakeup_parser,
|
||||
'sched_wakeup_new': sched_wakeup_parser,
|
||||
}
|
||||
|
||||
TRACE_EVENT_REGEX = re.compile(r'^\s+(?P<thread>\S+.*?\S+)\s+\[(?P<cpu_id>\d+)\]\s+(?P<ts>[\d.]+):\s+'
|
||||
r'(?P<name>[^:]+):\s+(?P<body>.*?)\s*$')
|
||||
|
||||
HEADER_REGEX = re.compile(r'^\s*(?:version|cpus)\s*=\s*([\d.]+)\s*$')
|
||||
|
||||
DROPPED_EVENTS_REGEX = re.compile(r'CPU:(?P<cpu_id>\d+) \[\d*\s*EVENTS DROPPED\]')
|
||||
@@ -213,6 +239,28 @@ DROPPED_EVENTS_REGEX = re.compile(r'CPU:(?P<cpu_id>\d+) \[\d*\s*EVENTS DROPPED\]
|
||||
EMPTY_CPU_REGEX = re.compile(r'CPU \d+ is empty')
|
||||
|
||||
|
||||
def split_trace_event_line(line):
|
||||
"""
|
||||
Split a trace-cmd event line into the preamble (containing the task, cpu id
|
||||
and timestamp), the event name, and the event body. Each of these is
|
||||
delimited by a ': ' (optionally followed by more whitespace), however ': '
|
||||
may also appear in the body of the event and in the thread name. This
|
||||
attempts to identify the correct split by ensureing the there is a '['
|
||||
(used to mark the cpu id and not a valid character for a task name) in the
|
||||
peramble.
|
||||
|
||||
"""
|
||||
parts = line.split(': ')
|
||||
if len(parts) <= 3:
|
||||
return parts
|
||||
|
||||
preamble = parts.pop(0)
|
||||
while '[' not in preamble:
|
||||
preamble += ': ' + parts.pop(0)
|
||||
event_name = parts.pop(0)
|
||||
return (preamble, event_name, ': '.join(parts))
|
||||
|
||||
|
||||
class TraceCmdTrace(object):
|
||||
|
||||
@property
|
||||
@@ -248,27 +296,30 @@ class TraceCmdTrace(object):
|
||||
elif TRACE_MARKER_STOP in line:
|
||||
break
|
||||
|
||||
match = DROPPED_EVENTS_REGEX.search(line)
|
||||
if match:
|
||||
yield DroppedEventsEvent(match.group('cpu_id'))
|
||||
continue
|
||||
|
||||
matched = False
|
||||
for rx in [HEADER_REGEX, EMPTY_CPU_REGEX]:
|
||||
match = rx.search(line)
|
||||
if 'EVENTS DROPPED' in line:
|
||||
match = DROPPED_EVENTS_REGEX.search(line)
|
||||
if match:
|
||||
logger.debug(line.strip())
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
yield DroppedEventsEvent(match.group('cpu_id'))
|
||||
continue
|
||||
|
||||
if line.startswith('version') or line.startswith('cpus') or\
|
||||
line.startswith('CPU:'):
|
||||
matched = False
|
||||
for rx in [HEADER_REGEX, EMPTY_CPU_REGEX]:
|
||||
match = rx.search(line)
|
||||
if match:
|
||||
logger.debug(line.strip())
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
continue
|
||||
|
||||
# <thread/cpu/timestamp>: <event name>: <event body>
|
||||
parts = split_trace_event_line(line)
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
|
||||
match = TRACE_EVENT_REGEX.search(line)
|
||||
if not match:
|
||||
logger.warning('Invalid trace event: "{}"'.format(line))
|
||||
continue
|
||||
|
||||
event_name = match.group('name')
|
||||
event_name = parts[1].strip()
|
||||
|
||||
if filters:
|
||||
found = False
|
||||
@@ -279,10 +330,22 @@ class TraceCmdTrace(object):
|
||||
if not found:
|
||||
continue
|
||||
|
||||
thread_string, rest = parts[0].rsplit(' [', 1)
|
||||
cpu_id, ts_string = rest.split('] ')
|
||||
body = parts[2].strip()
|
||||
|
||||
body_parser = EVENT_PARSER_MAP.get(event_name, default_body_parser)
|
||||
if isinstance(body_parser, basestring) or isinstance(body_parser, re._pattern_type): # pylint: disable=protected-access
|
||||
body_parser = regex_body_parser(body_parser)
|
||||
yield TraceCmdEvent(parser=body_parser, **match.groupdict())
|
||||
|
||||
yield TraceCmdEvent(
|
||||
thread=thread_string.strip(),
|
||||
cpu_id=cpu_id,
|
||||
ts=ts_string.strip(),
|
||||
name=event_name,
|
||||
body=body,
|
||||
parser=body_parser,
|
||||
)
|
||||
else:
|
||||
if self.filter_markers and inside_marked_region:
|
||||
logger.warning('Did not encounter a stop marker in trace')
|
||||
|
@@ -30,6 +30,7 @@ import re
|
||||
import math
|
||||
import shlex
|
||||
from collections import defaultdict
|
||||
from urllib import quote, unquote
|
||||
|
||||
from wlauto.utils.misc import isiterable, to_identifier
|
||||
|
||||
@@ -82,6 +83,9 @@ def numeric(value):
|
||||
return ivalue
|
||||
return fvalue
|
||||
|
||||
def file_path(value):
|
||||
"""Handles expansion of paths containing '~'"""
|
||||
return os.path.expanduser(value)
|
||||
|
||||
def list_of_strs(value):
|
||||
"""
|
||||
@@ -328,3 +332,119 @@ class range_dict(dict):
|
||||
def __setitem__(self, i, v):
|
||||
i = int(i)
|
||||
super(range_dict, self).__setitem__(i, v)
|
||||
|
||||
|
||||
class ParameterDict(dict):
|
||||
"""
|
||||
A dict-like object that automatically encodes various types into a url safe string,
|
||||
and enforces a single type for the contents in a list.
|
||||
Each value is first prefixed with 2 letters to preserve type when encoding to a string.
|
||||
The format used is "value_type, value_dimension" e.g a 'list of floats' would become 'fl'.
|
||||
"""
|
||||
|
||||
# Function to determine the appropriate prefix based on the parameters type
|
||||
@staticmethod
|
||||
def _get_prefix(obj):
|
||||
if isinstance(obj, basestring):
|
||||
prefix = 's'
|
||||
elif isinstance(obj, float):
|
||||
prefix = 'f'
|
||||
elif isinstance(obj, long):
|
||||
prefix = 'd'
|
||||
elif isinstance(obj, bool):
|
||||
prefix = 'b'
|
||||
elif isinstance(obj, int):
|
||||
prefix = 'i'
|
||||
elif obj is None:
|
||||
prefix = 'n'
|
||||
else:
|
||||
raise ValueError('Unable to encode {} {}'.format(obj, type(obj)))
|
||||
return prefix
|
||||
|
||||
# Function to add prefix and urlencode a provided parameter.
|
||||
@staticmethod
|
||||
def _encode(obj):
|
||||
if isinstance(obj, list):
|
||||
t = type(obj[0])
|
||||
prefix = ParameterDict._get_prefix(obj[0]) + 'l'
|
||||
for item in obj:
|
||||
if not isinstance(item, t):
|
||||
msg = 'Lists must only contain a single type, contains {} and {}'
|
||||
raise ValueError(msg.format(t, type(item)))
|
||||
obj = '0newelement0'.join(str(x) for x in obj)
|
||||
else:
|
||||
prefix = ParameterDict._get_prefix(obj) + 's'
|
||||
return quote(prefix + str(obj))
|
||||
|
||||
# Function to decode a string and return a value of the original parameter type.
|
||||
# pylint: disable=too-many-return-statements
|
||||
@staticmethod
|
||||
def _decode(string):
|
||||
value_type = string[:1]
|
||||
value_dimension = string[1:2]
|
||||
value = unquote(string[2:])
|
||||
if value_dimension == 's':
|
||||
if value_type == 's':
|
||||
return str(value)
|
||||
elif value_type == 'b':
|
||||
return boolean(value)
|
||||
elif value_type == 'd':
|
||||
return long(value)
|
||||
elif value_type == 'f':
|
||||
return float(value)
|
||||
elif value_type == 'i':
|
||||
return int(value)
|
||||
elif value_type == 'n':
|
||||
return None
|
||||
elif value_dimension == 'l':
|
||||
return [ParameterDict._decode(value_type + 's' + x)
|
||||
for x in value.split('0newelement0')]
|
||||
else:
|
||||
raise ValueError('Unknown {} {}'.format(type(string), string))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for k, v in kwargs.iteritems():
|
||||
self.__setitem__(k, v)
|
||||
dict.__init__(self, *args)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
dict.__setitem__(self, name, self._encode(value))
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self._decode(dict.__getitem__(self, name))
|
||||
|
||||
def __contains__(self, item):
|
||||
return dict.__contains__(self, self._encode(item))
|
||||
|
||||
def __iter__(self):
|
||||
return iter((k, self._decode(v)) for (k, v) in self.items())
|
||||
|
||||
def iteritems(self):
|
||||
return self.__iter__()
|
||||
|
||||
def get(self, name):
|
||||
return self._decode(dict.get(self, name))
|
||||
|
||||
def pop(self, key):
|
||||
return self._decode(dict.pop(self, key))
|
||||
|
||||
def popitem(self):
|
||||
key, value = dict.popitem(self)
|
||||
return (key, self._decode(value))
|
||||
|
||||
def iter_encoded_items(self):
|
||||
return dict.iteritems(self)
|
||||
|
||||
def get_encoded_value(self, name):
|
||||
return dict.__getitem__(self, name)
|
||||
|
||||
def values(self):
|
||||
return [self[k] for k in dict.keys(self)]
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
for d in list(args) + [kwargs]:
|
||||
if isinstance(d, ParameterDict):
|
||||
dict.update(self, d)
|
||||
else:
|
||||
for k, v in d.iteritems():
|
||||
self[k] = v
|
||||
|
170
wlauto/utils/uxperf.py
Executable file
170
wlauto/utils/uxperf.py
Executable 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(int(ts) / 1e6) 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))
|
@@ -24,7 +24,6 @@ class AdobeReader(AndroidUxPerfWorkload):
|
||||
|
||||
name = 'adobereader'
|
||||
package = 'com.adobe.reader'
|
||||
min_apk_version = '16.1'
|
||||
activity = 'com.adobe.reader.AdobeReader'
|
||||
view = [package + '/com.adobe.reader.help.AROnboardingHelpActivity',
|
||||
package + '/com.adobe.reader.viewer.ARSplitPaneActivity',
|
||||
@@ -45,6 +44,8 @@ class AdobeReader(AndroidUxPerfWorkload):
|
||||
3. Search test:
|
||||
Search ``document_name`` for each string in the ``search_string_list``
|
||||
4. Close the document
|
||||
|
||||
Known working APK version: 16.1
|
||||
'''
|
||||
|
||||
default_search_strings = [
|
||||
@@ -75,8 +76,8 @@ class AdobeReader(AndroidUxPerfWorkload):
|
||||
|
||||
def validate(self):
|
||||
super(AdobeReader, self).validate()
|
||||
self.uiauto_params['filename'] = self.document_name.replace(' ', '0space0')
|
||||
self.uiauto_params['search_string_list'] = '0newline0'.join([x.replace(' ', '0space0') for x in self.search_string_list])
|
||||
self.uiauto_params['filename'] = self.document_name
|
||||
self.uiauto_params['search_string_list'] = self.search_string_list
|
||||
# Only accept certain file formats
|
||||
if os.path.splitext(self.document_name.lower())[1] not in ['.pdf']:
|
||||
raise ValidationError('{} must be a PDF file'.format(self.document_name))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user