OSDN Git Service

simpleperf: add tests for scripts.
authorYabin Cui <yabinc@google.com>
Tue, 18 Jul 2017 01:08:57 +0000 (18:08 -0700)
committerYabin Cui <yabinc@google.com>
Fri, 21 Jul 2017 22:13:15 +0000 (15:13 -0700)
Also adjust scripts based on test results:
1. Use `pidof` in app_profiler.py.
2. Improve the way finding source files in annotate.py.
3. Change report.py to be a python wrapper of simpleperf report command,
   so users don't need to find different simpleperf binaries for different platforms.
4. Change README.md accordingly.

Bug: http://b/63006886
Test: run test.py on all platforms.

Change-Id: I00b098a1c72824226e509d7b1e5405c7cc43b856

24 files changed:
simpleperf/README.md
simpleperf/demo/README.md
simpleperf/scripts/Android.mk
simpleperf/scripts/annotate.py
simpleperf/scripts/app_profiler.py
simpleperf/scripts/binary_cache_builder.py
simpleperf/scripts/pprof_proto_generator.py
simpleperf/scripts/report.py
simpleperf/scripts/test.py [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/app-profiling.apk [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExamplePureJava/app-profiling.apk [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExamplePureJava/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/app-profiling.apk [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/cpp/native-lib.cpp [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/java/com/example/simpleperf/simpleperfexamplewithnative/MainActivity.java [new file with mode: 0644]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/arm64-v8a/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi-v7a/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips64/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86_64/libnative-lib.so [new file with mode: 0755]
simpleperf/scripts/utils.py

index 5311f03..f2f0f0e 100644 (file)
@@ -30,6 +30,7 @@ Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/
     - [Annotate source code](#annotate-source-code)
 - [Answers to common issues](#answers-to-common-issues)
     - [The correct way to pull perf.data on host](#the-correct-way-to-pull-perfdata-on-host)
+    - [Why we suggest profiling on android >= N devices](#why-we-suggest-profiling-on-android-n-devices)
 
 ## Simpleperf introduction
 
@@ -494,12 +495,12 @@ should be true. So we need to use debug [build type](https://developer.android.c
 instead of release build type. It is understandable because we can't profile others' apps.
 However, on a rooted Android device, the application doesn't need to be debuggable.
 
-**2. Run on an Android device >= L.**
-Profiling on emulators are not yet supported. And to profile Java code, we need
-the jvm running in oat mode, which is only available >= L.
+**2. Run on an Android >= N device.**
+We suggest profiling on an Android >= N device. The reason is [here](#why-we-suggest-profiling-on-android-n-devices).
+
 
 **3. On Android O, add `wrap.sh` in the apk.**
-To profile Java code, we need the jvm running in oat mode. But on Android O,
+To profile Java code, we need ART running in oat mode. But on Android O,
 debuggable applications are forced to run in jit mode. To work around this,
 we need to add a `wrap.sh` in the apk. So if you are running on Android O device,
 Check [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle)
@@ -579,12 +580,13 @@ and access the native binaries.
     # Start the app if needed
     $ adb shell am start -n com.example.simpleperf.simpleperfexamplepurejava/.MainActivity
 
-    # Run `ps` in the app's context. On Android >= O devicces, run `ps -e` instead.
-    $ adb shell run-as com.example.simpleperf.simpleperfexamplepurejava ps | grep simpleperf
-    u0_a151   6885  3346  1590504 53980 SyS_epoll_ 6fc2024b6c S com.example.simpleperf.simpleperfexamplepurejava
+    $ adb shell pidof com.example.simpleperf.simpleperfexamplepurejava
+    6885
 
 So the id of the app process is `6885`. We will use this number in the command lines below,
-please replace this number with what you get by running `ps` command.
+please replace this number with what you get by running `pidof` command.
+On Android <= M, pidof may not exist or work well, and you can try
+`ps | grep com.example.simpleperf.simpleperfexamplepurejava` instead.
 
 **4. Download simpleperf to the app's data directory**
 
@@ -621,9 +623,8 @@ There are many options to record profiling data, check [record command](#simplep
     $ adb shell "run-as com.example.simpleperf.simpleperfexamplepurejava cat perf.data | tee /data/local/tmp/perf.data >/dev/null"
     $ adb pull /data/local/tmp/perf.data
 
-    # Report samples using corresponding simpleperf executable on host.
-    # On windows, use "bin\windows\x86_64\simpleperf" instead.
-    $ bin/linux/x86_64/simpleperf report
+    # Report samples using report.py, report.py is a python wrapper of simpleperf report command.
+    $ python report.py
     ...
     Overhead  Command   Pid   Tid   Shared Object                                                                     Symbol
     83.54%    Thread-2  6885  6900  /data/app/com.example.simpleperf.simpleperfexamplepurejava-2/oat/arm64/base.odex  void com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()
@@ -637,32 +638,19 @@ There are many ways to show reports, check [report command](#simpleperf-report)
 
 Besides command lines, We can use `app-profiler.py` to profile Android applications.
 It downloads simpleperf on device, records perf.data, and collects profiling
-results and native binaries on host. It is configured by `app-profiler.config`.
-
-**1. Fill `app-profiler.config`**
-
-    Change `app_package_name` line to  app_package_name="com.example.simpleperf.simpleperfexamplepurejava"
-    Change `apk_file_path` line to apk_file_path = "../SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk"
-    Change `android_studio_project_dir` line to android_studio_project_dir = "../SimpleperfExamplePureJava/"
-    Change `record_options` line to record_options = "--duration 10"
-
-`apk_file_path` is needed to fully compile the application on Android L/M. It is
-not necessary on Android >= N.
-
-`android_studio_project_dir` is used to search native libraries in the
-application. It is not necessary for profiling.
+results and native binaries on host.
 
-`record_options` can be set to any option accepted by simpleperf record command.
+**1. Record perf.data by running `app-profiler.py`**
 
-**2. Run `app-profiler.py`**
-
-    $ python app_profiler.py
+    $ python app_profiler.py --app com.example.simpleperf.simpleperfexamplepurejava \
+         --apk ../SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk \
+         -r "-e cpu-cycles:u --duration 10"
 
 
 If running successfully, it will collect profiling data in perf.data in current
 directory, and related native binaries in binary_cache/.
 
-**3. Report perf.data**
+**2. Report perf.data**
 
 We can use `report.py` to report perf.data.
 
@@ -698,9 +686,11 @@ When using command lines, add `-g` option like below:
 
     $ adb shell run-as com.example.simpleperf.simpleperfexamplepurejava ./simpleperf record -g -p 6685 --duration 10
 
-When using python scripts, change `app-profiler.config` as below:
+When using app_profiler.py, add "-g" in record option as below:
 
-    Change `record_options` line to record_options = "--duration 10 -g"
+    $ python app_profiler.py --app com.example.simpleperf.simpleperfexamplepurejava \
+        --apk ../SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk \
+        -r "-e cpu-cycles:u --duration 10 -g"
 
 Recording dwarf based call graph needs support of debug information
 in native binaries. So if using native libraries in the application,
@@ -713,9 +703,11 @@ When using command lines, add `--call-graph fp` option like below:
 
     $ adb shell run-as com.example.simpleperf.simpleperfexamplepurejava ./simpleperf record --call-graph fp -p 6685 --duration 10
 
-When using python scripts, change `app-profiler.config` as below:
+When using app_profiler.py, add "--call-graph fp" in record option as below:
 
-    Change `record_options` line to record_options = "--duration 10 --call-graph fp"
+    $ python app_profiler.py --app com.example.simpleperf.simpleperfexamplepurejava \
+        --apk ../SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk \
+        -r "-e cpu-cycles:u --duration 10 --call-graph fp"
 
 Recording stack frame based call graphs needs support of stack frame
 register. Notice that on arm architecture, the stack frame register
@@ -742,9 +734,12 @@ To report call graph using command lines, add `-g` option.
                |--16.22%-- int com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.callFunction(int)
                |    |--99.97%-- [hit in function]
 
-To report call graph using python scripts, add `-g` option.
+To report call graph using report.py, add `-g` option.
 
+    # In text mode
     $ python report.py -g
+    # In GUI mode
+    $ python report.py -g --gui
     # Double-click an item started with '+' to show its callgraph.
 
 ### Visualize profiling data
@@ -808,6 +803,7 @@ It's content is similar to below:
 ## Answers to common issues
 
 ### The correct way to pull perf.data on host
+
 As perf.data is generated in app's context, it can't be pulled directly to host.
 One way is to `adb shell run-as xxx cat perf.data >perf.data`. However, it
 doesn't work well on Windows, because the content can be modified when it goes
@@ -817,6 +813,19 @@ then pull it on host. The commands are as below:
     $adb shell "run-as xxx cat perf.data | tee /data/local/tmp/perf.data >/dev/null"
     $adb pull /data/local/tmp/perf.data
 
+
+### Why we suggest profiling on Android >= N devices?
+```
+1. Running on a device reflects a real running situation, so we suggest
+profiling on real devices instead of emulators.
+2. To profile Java code, we need ART running in oat mode, which is only
+available >= L for rooted devices, and >= N for non-rooted devices.
+3. Old Android versions are likely to be shipped with old kernels (< 3.18),
+which may not support profiling features like dwarf based call graph.
+4. Old Android versions are likely to be shipped with Arm32 chips. In Arm32
+mode, stack frame based call graph doesn't work well.
+```
+
 ## Inferno
 
 ![logo](./inferno/inferno_small.png)
index 2d2c377..b8c3886 100644 (file)
@@ -52,14 +52,12 @@ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava
 3. Show profiling data:
 ```
 a. show call graph in txt mode
-    # On windows, use "bin\windows\x86\simpleperf" instead.
-    $ bin/linux/x86_64/simpleperf report -g | more
-        If on other hosts, use corresponding simpleperf binary.
+    $ python report.py -g | more
 b. show call graph in gui mode
-    $ python report.py -g
+    $ python report.py -g --gui
 c. show samples in source code
     $ python annotate.py -s ../demo/SimpleperfExamplePureJava
-    $ gvim annotated_files/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
+    $ find annotated_files -name "MainActivity.java"
         check the annoated source file MainActivity.java.
 ```
 
@@ -91,14 +89,12 @@ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
 3. Show profiling data:
 ```
 a. show call graph in txt mode
-    # On windows, use "bin\windows\x86\simpleperf" instead.
-    $ bin/linux/x86_64/simpleperf report -g | more
-        If on other hosts, use corresponding simpleperf binary.
+    $ python report.py -g | more
 b. show call graph in gui mode
-    $ python report.py -g
+    $ python report.py -g --gui
 c. show samples in source code
     $ python annotate.py -s ../demo/SimpleperfExampleWithNative
-    $ find . -name "native-lib.cpp" | xargs gvim
+    $ find annotated_files -name "native-lib.cpp"
         check the annoated source file native-lib.cpp.
 ```
 
@@ -130,13 +126,10 @@ $ python app_profiler.py -p com.example.simpleperf.simpleperfexampleofkotlin
 3. Show profiling data:
 ```
 a. show call graph in txt mode
-    # On windows, use "bin\windows\x86\simpleperf" instead.
-    $ bin/linux/x86_64/simpleperf report -g | more
-        If on other hosts, use corresponding simpleperf binary.
+    $ python report.py -g | more
 b. show call graph in gui mode
-    $ python report.py -g
+    $ python report.py -g --gui
 c. show samples in source code
     $ python annotate.py -s ../demo/SimpleperfExampleOfKotlin
-    $ find . -name "MainActivity.kt" | xargs gvim
-        check the annoated source file MainActivity.kt.
+    $ find . -name "MainActivity.kt"
 ```
index b19734c..e94a8ed 100644 (file)
@@ -16,7 +16,8 @@
 LOCAL_PATH := $(call my-dir)
 
 SIMPLEPERF_SCRIPT_LIST := $(wildcard $(LOCAL_PATH)/*.py $(LOCAL_PATH)/*.config) \
-                          $(LOCAL_PATH)/../README.md
+                          $(LOCAL_PATH)/../README.md \
+                          $(LOCAL_PATH)/testdata
 
 SIMPLEPERF_SCRIPT_LIST := $(filter-out $(LOCAL_PATH)/update.py,$(SIMPLEPERF_SCRIPT_LIST))
 
index 27414ac..156af6d 100644 (file)
@@ -538,15 +538,20 @@ class SourceFileAnnotator(object):
         source_files = self.source_file_dict.get(filename)
         if source_files is None:
             return None
-        match_count = 0
-        result = None
+        best_path_count = 0
+        best_path = None
+        best_suffix_len = 0
         for path in source_files:
-            if path.find(file) != -1:
-                match_count += 1
-                result = path
-        if match_count > 1:
+            suffix_len = len(os.path.commonprefix((path[::-1], file[::-1])))
+            if suffix_len > best_suffix_len:
+                best_suffix_len = suffix_len
+                best_path = path
+                best_path_count = 1
+            elif suffix_len == best_suffix_len:
+                best_path_count += 1
+        if best_path_count > 1:
             log_warning('multiple source for %s, select %s' % (file, result))
-        return result
+        return best_path
 
 
     def _annotate_files(self):
index 683d6b4..d4ced47 100644 (file)
@@ -45,12 +45,13 @@ class AppProfiler(object):
     def __init__(self, config):
         self.check_config(config)
         self.config = config
-        self.adb = AdbHelper()
+        self.adb = AdbHelper(enable_switch_to_root=not config['disable_adb_root'])
         self.is_root_device = False
         self.android_version = 0
         self.device_arch = None
         self.app_arch = None
         self.app_pid = None
+        self.has_symfs_on_device = False
 
 
     def check_config(self, config):
@@ -184,12 +185,15 @@ class AppProfiler(object):
 
 
     def _find_app_process(self):
-        ps_args = ['-e'] if self.android_version >= 8 else []
-        result, output = self.adb.run_and_return_output(['shell', 'ps'] + ps_args)
+        # On Android >= N, pidof is available. Otherwise, we can use ps.
+        if self.android_version >= 7:
+            result, output = self.adb.run_and_return_output(['shell', 'pidof',
+                                                             self.config['app_package_name']])
+            return int(output) if result else None
+        result, output = self.adb.run_and_return_output(['shell', 'ps'], log_output=False)
         if not result:
             return None
-        output = output.split('\n')
-        for line in output:
+        for line in output.split('\n'):
             strs = line.split()
             if len(strs) > 2 and strs[-1].find(self.config['app_package_name']) != -1:
                 return int(strs[1])
@@ -201,7 +205,7 @@ class AppProfiler(object):
         if self.app_pid is None:
             log_exit("can't find process for app [%s]" % self.config['app_package_name'])
         if self.device_arch in ['aarch64', 'x86_64']:
-            output = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid])
+            output = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid], log_output=False)
             if output.find('linker64') != -1:
                 self.app_arch = self.device_arch
             else:
@@ -234,7 +238,7 @@ class AppProfiler(object):
                     filename_dict[file] = path
                 else:
                     log_info('%s is worse than %s' % (path, old_path))
-        maps = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid])
+        maps = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid], log_output=False)
         searched_lib = dict()
         for item in maps.split():
             if item.endswith('.so') and searched_lib.get(item) is None:
@@ -248,6 +252,7 @@ class AppProfiler(object):
                 self.adb.check_run(['push', path, '/data/local/tmp'])
                 self.run_in_app_dir(['mkdir', '-p', dirname])
                 self.run_in_app_dir(['cp', '/data/local/tmp/' + filename, dirname])
+                self.has_symfs_on_device = True
 
 
     def _is_lib_better(self, new_path, old_path):
@@ -275,9 +280,11 @@ class AppProfiler(object):
         subproc = None
         returncode = None
         try:
-            args = self.get_run_in_app_dir_args([
-                './simpleperf', 'record', self.config['record_options'], '-p',
-                str(self.app_pid), '--symfs', '.'])
+            args = ['./simpleperf', 'record', self.config['record_options'],
+                    '-p', str(self.app_pid)]
+            if self.has_symfs_on_device:
+                args += ['--symfs', '.']
+            args = self.get_run_in_app_dir_args(args)
             adb_args = [self.adb.adb_path] + args
             log_debug('run adb cmd: %s' % adb_args)
             subproc = subprocess.Popen(adb_args)
@@ -317,12 +324,12 @@ class AppProfiler(object):
             binary_cache_builder.build_binary_cache()
 
 
-    def run_in_app_dir(self, args, stdout_file=None, check_result=True):
+    def run_in_app_dir(self, args, stdout_file=None, check_result=True, log_output=True):
         args = self.get_run_in_app_dir_args(args)
         if check_result:
-            return self.adb.check_run_and_return_output(args, stdout_file)
+            return self.adb.check_run_and_return_output(args, stdout_file, log_output=log_output)
         else:
-            return self.adb.run_and_return_output(args, stdout_file)
+            return self.adb.run_and_return_output(args, stdout_file, log_output=log_output)
 
 
     def get_run_in_app_dir_args(self, args):
@@ -339,7 +346,7 @@ def main():
     parser.add_argument('--config', default='app_profiler.config', help=
 """Set configuration file. Default is app_profiler.config. The configurations
 can be overridden by options in cmdline.""")
-    parser.add_argument('-p', '--package_name', help=
+    parser.add_argument('-p', '--app', help=
 """The package name of the profiled Android app.""")
     parser.add_argument('-lib', '--native_lib_dir', help=
 """Path to find debug version of native shared libraries used in the app.""")
@@ -363,10 +370,12 @@ an instrumentation test.""")
     parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help=
 """By default we collect binaries used in profiling data from device to
 binary_cache directory. It can be used to annotate source code. This option skips it.""")
+    parser.add_argument('--disable_adb_root', action='store_true', help=
+"""Force adb to run in non root mode.""")
     args = parser.parse_args()
     config = load_config(args.config)
-    if args.package_name:
-        config['app_package_name'] = args.package_name
+    if args.app:
+        config['app_package_name'] = args.app
     if args.native_lib_dir:
         config['native_lib_dir'] = args.native_lib_dir
     if args.skip_recompile:
@@ -385,6 +394,7 @@ binary_cache directory. It can be used to annotate source code. This option skip
         config['perf_data_path'] = args.perf_data_path
     if args.skip_collect_binaries:
         config['collect_binaries'] = False
+    config['disable_adb_root'] = args.disable_adb_root
 
     profiler = AppProfiler(config)
     profiler.profile()
index 4ce4a32..546f76b 100644 (file)
@@ -48,7 +48,7 @@ class BinaryCacheBuilder(object):
         for symfs_dir in self.symfs_dirs:
             if not os.path.isdir(symfs_dir):
                 log_exit("symfs_dir '%s' is not a directory" % symfs_dir)
-        self.adb = AdbHelper()
+        self.adb = AdbHelper(enable_switch_to_root=not config['disable_adb_root'])
         self.readelf_path = find_tool_path('readelf')
         if not self.readelf_path and self.symfs_dirs:
             log_warning("Debug shared libraries on host are not used because can't find readelf.")
@@ -236,10 +236,13 @@ def main():
     parser.add_argument('-lib', '--native_lib_dir', nargs='+', help=
 """Path to find debug version of native shared libraries used in the app.""",
                         action='append')
+    parser.add_argument('--disable_adb_root', action='store_true', help=
+"""Force adb to run in non root mode.""")
     args = parser.parse_args()
     config = {}
     config['perf_data_path'] = args.perf_data_path
     config['symfs_dirs'] = flatten_arg_list(args.native_lib_dir)
+    config['disable_adb_root'] = args.disable_adb_root
 
     builder = BinaryCacheBuilder(config)
     builder.build_binary_cache()
index ad51911..ff2a530 100644 (file)
@@ -28,7 +28,6 @@ from __future__ import print_function
 import argparse
 import os
 import os.path
-import profile_pb2
 import re
 import shutil
 import sys
@@ -38,6 +37,12 @@ from annotate import Addr2Line
 from simpleperf_report_lib import *
 from utils import *
 
+try:
+    import google.protobuf
+except:
+    log_exit('google.protobuf module is missing. Please install it first.')
+
+import profile_pb2
 
 def load_pprof_profile(filename):
     profile = profile_pb2.Profile()
index 87a7c97..590d0f0 100644 (file)
@@ -24,6 +24,7 @@ simpleperf report command. The reporter will call `simpleperf report` to
 generate report file, and display it.
 """
 
+import os
 import os.path
 import re
 import subprocess
@@ -266,38 +267,76 @@ class ReportWindow(object):
       self.display_call_tree(tree, id, child, indent + 1)
 
 
-def display_report_file(report_file):
-  fh = open(report_file, 'r')
-  lines = fh.readlines()
-  fh.close()
+def display_report_file(report_file, self_kill_after_sec):
+    fh = open(report_file, 'r')
+    lines = fh.readlines()
+    fh.close()
 
-  lines = [x.rstrip() for x in lines]
-  event_reports = parse_event_reports(lines)
+    lines = [x.rstrip() for x in lines]
+    event_reports = parse_event_reports(lines)
 
-  if event_reports:
-    root = Tk()
-    for i in range(len(event_reports)):
-      report = event_reports[i]
-      parent = root if i == 0 else Toplevel(root)
-      ReportWindow(parent, report.context, report.title_line, report.report_items)
-    root.mainloop()
+    if event_reports:
+        root = Tk()
+        for i in range(len(event_reports)):
+            report = event_reports[i]
+            parent = root if i == 0 else Toplevel(root)
+            ReportWindow(parent, report.context, report.title_line, report.report_items)
+        if self_kill_after_sec:
+            root.after(self_kill_after_sec * 1000, lambda: root.destroy())
+        root.mainloop()
 
 
-def call_simpleperf_report(args, report_file):
-  output_fh = open(report_file, 'w')
-  simpleperf_path = get_host_binary_path('simpleperf')
-  args = [simpleperf_path, 'report', '--full-callgraph'] + args
-  subprocess.check_call(args, stdout=output_fh)
-  output_fh.close()
+def call_simpleperf_report(args, show_gui, self_kill_after_sec):
+    simpleperf_path = get_host_binary_path('simpleperf')
+    if not show_gui:
+        subprocess.check_call([simpleperf_path, 'report'] + args)
+    else:
+        report_file = 'perf.report'
+        subprocess.check_call([simpleperf_path, 'report', '--full-callgraph'] + args +
+                              ['-o', report_file])
+        display_report_file(report_file, self_kill_after_sec=self_kill_after_sec)
+
+
+def get_simpleperf_report_help_msg():
+    simpleperf_path = get_host_binary_path('simpleperf')
+    args = [simpleperf_path, 'report', '-h']
+    proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+    (stdoutdata, _) = proc.communicate()
+    return stdoutdata[stdoutdata.find('\n') + 1:]
 
 
 def main():
-  if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]):
-    display_report_file(sys.argv[1])
-  else:
-    call_simpleperf_report(sys.argv[1:], 'perf.report')
-    display_report_file('perf.report')
+    self_kill_after_sec = 0
+    args = sys.argv[1:]
+    if args and args[0] == "--self-kill-for-testing":
+        self_kill_after_sec = 1
+        args = args[1:]
+    if len(args) == 1 and os.path.isfile(args[0]):
+        display_report_file(args[0], self_kill_after_sec=self_kill_after_sec)
+
+    i = 0
+    args_for_report_cmd = []
+    show_gui = False
+    while i < len(args):
+        if args[i] == '-h' or args[i] == '--help':
+            print('report.py   A python wrapper for simpleperf report command.')
+            print('report.py [')
+            print('Options supported by simpleperf report command:')
+            print(get_simpleperf_report_help_msg())
+            print('\nOptions supported by report.py:')
+            print('--gui   Show report result in a gui window.')
+            print('\nIt also supports showing a report generated by simpleperf report cmd:')
+            print('\n  python report.py report_file')
+            sys.exit(0)
+        elif args[i] == '--gui':
+            show_gui = True
+            i += 1
+        else:
+            args_for_report_cmd.append(args[i])
+            i += 1
+
+    call_simpleperf_report(args_for_report_cmd, show_gui, self_kill_after_sec)
 
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
new file mode 100644 (file)
index 0000000..27327d9
--- /dev/null
@@ -0,0 +1,424 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# 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.
+#
+"""test.py: Tests for simpleperf python scripts.
+
+These are smoke tests Using examples to run python scripts.
+For each example, we go through the steps of running each python script.
+Examples are collected from simpleperf/demo, which includes:
+  SimpleperfExamplePureJava
+  SimpleperfExampleWithNative
+  SimpleperfExampleOfKotlin
+
+Tested python scripts include:
+  app_profiler.py
+  report.py
+  annotate.py
+  report_sample.py
+  pprof_proto_generator.py
+
+Test using both `adb root` and `adb unroot`.
+
+"""
+
+import os
+import re
+import shutil
+import sys
+import tempfile
+import unittest
+from utils import *
+
+has_google_protobuf = True
+try:
+    import google.protobuf
+except:
+    has_google_protobuf = False
+
+
+class TestExamplesBase(unittest.TestCase):
+    @classmethod
+    def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
+        cls.adb = AdbHelper(enable_switch_to_root=adb_root)
+        cls.example_path = os.path.join("testdata", example_name)
+        if not os.path.isdir(cls.example_path):
+            log_fatal("can't find " + cls.example_path)
+        cls.apk_path = os.path.join(cls.example_path, "app-profiling.apk")
+        if not os.path.isfile(cls.apk_path):
+            log_fatal("can't find " + cls.apk_path)
+        cls.package_name = package_name
+        cls.activity_name = activity_name
+        cls.python_path = sys.executable
+        args = ["install", "-r"]
+        if abi:
+            args += ["--abi", abi]
+        args.append(cls.apk_path)
+        cls.adb.check_run(args)
+        cls.adb_root = adb_root
+        cls.compiled = False
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.adb.check_run(["uninstall", cls.package_name])
+
+    @classmethod
+    def cleanupTestFiles(cls):
+        cls.remove("binary_cache")
+        cls.remove("annotated_files")
+        cls.remove("perf.data")
+        cls.remove("report.txt")
+        cls.remove("pprof.profile")
+
+    def run_cmd(self, args, return_output=False):
+        args = [self.python_path] + args
+        try:
+            if not return_output:
+                returncode = subprocess.call(args)
+            else:
+                subproc = subprocess.Popen(args, stdout=subprocess.PIPE)
+                (output_data, _) = subproc.communicate()
+                returncode = subproc.returncode
+        except:
+            returncode = None
+        self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
+        if return_output:
+            return output_data
+
+    def run_app_profiler(self, record_arg = "-g --duration 3 -e cpu-cycles:u",
+                         build_binary_cache=True, skip_compile=False, start_activity=True,
+                         native_lib_dir=None):
+        args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path,
+                "-a", self.activity_name, "-r", record_arg, "-o", "perf.data"]
+        if not build_binary_cache:
+            args.append("-nb")
+        if skip_compile or self.__class__.compiled:
+            args.append("-nc")
+        if start_activity:
+            args += ["-a", self.activity_name]
+        if native_lib_dir:
+            args += ["-lib", native_lib_dir]
+        if not self.adb_root:
+            args.append("--disable_adb_root")
+        self.run_cmd(args)
+        self.check_exist(file="perf.data")
+        if build_binary_cache:
+            self.check_exist(dir="binary_cache")
+        if not skip_compile:
+            self.__class__.compiled = True
+
+    @classmethod
+    def remove(cls, dir):
+        shutil.rmtree(dir, ignore_errors=True)
+
+    def check_exist(self, file=None, dir=None):
+        if file:
+            self.assertTrue(os.path.isfile(file), file)
+        if dir:
+            self.assertTrue(os.path.isdir(dir), dir)
+
+    def check_file_under_dir(self, dir, file):
+        self.check_exist(dir=dir)
+        for _, _, files in os.walk(dir):
+            for f in files:
+                if f == file:
+                    return
+        self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dir, file))
+
+
+    def check_strings_in_file(self, file, strings):
+        self.check_exist(file=file)
+        with open(file, 'r') as fh:
+            self.check_strings_in_content(fh.read(), strings)
+
+    def check_strings_in_content(self, content, strings):
+        for s in strings:
+            self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content))
+
+    def check_annotation_summary(self, summary_file, check_entries):
+        """ check_entries is a list of (name, accumulated_period, period).
+            This function checks for each entry, if the line containing [name]
+            has at least required accumulated_period and period.
+        """
+        self.check_exist(file=summary_file)
+        with open(summary_file, 'r') as fh:
+            summary = fh.read()
+        fulfilled = [False for x in check_entries]
+        if not hasattr(self, "summary_check_re"):
+            self.summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%')
+        for line in summary.split('\n'):
+            for i in range(len(check_entries)):
+                (name, need_acc_period, need_period) = check_entries[i]
+                if not fulfilled[i] and line.find(name) != -1:
+                    m = self.summary_check_re.search(line)
+                    if m:
+                        acc_period = float(m.group(1))
+                        period = float(m.group(2))
+                        if acc_period >= need_acc_period and period >= need_period:
+                            fulfilled[i] = True
+        self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
+
+    def common_test_app_profiler(self):
+        self.run_cmd(["app_profiler.py", "-h"])
+        self.remove("binary_cache")
+        self.run_app_profiler(build_binary_cache=False)
+        self.assertFalse(os.path.isdir("binary_cache"))
+        args = ["binary_cache_builder.py"]
+        if not self.adb_root:
+            args.append("--disable_adb_root")
+        self.run_cmd(args)
+        self.check_exist(dir="binary_cache")
+        self.remove("binary_cache")
+        self.run_app_profiler(build_binary_cache=True)
+        self.run_app_profiler(skip_compile=True)
+        self.run_app_profiler(start_activity=False)
+
+
+    def common_test_report(self):
+        self.run_cmd(["report.py", "-h"])
+        self.run_app_profiler(build_binary_cache=False)
+        self.run_cmd(["report.py"])
+        self.run_cmd(["report.py", "-i", "perf.data"])
+        self.run_cmd(["report.py", "-g"])
+        self.run_cmd(["report.py", "--self-kill-for-testing",  "-g", "--gui"])
+
+    def common_test_annotate(self):
+        self.run_cmd(["annotate.py", "-h"])
+        self.run_app_profiler()
+        self.remove("annotated_files")
+        self.run_cmd(["annotate.py", "-s", self.example_path])
+        self.check_exist(dir="annotated_files")
+
+    def common_test_report_sample(self, check_strings):
+        self.run_cmd(["report_sample.py", "-h"])
+        self.remove("binary_cache")
+        self.run_app_profiler(build_binary_cache=False)
+        self.run_cmd(["report_sample.py"])
+        output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
+        self.check_strings_in_content(output, check_strings)
+        self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u, --no-dump-symbols")
+        output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True)
+        self.check_strings_in_content(output, check_strings)
+
+    def common_test_pprof_proto_generator(self, check_strings_with_lines,
+                                          check_strings_without_lines):
+        if not has_google_protobuf:
+            log_info('Skip test for pprof_proto_generator because google.protobuf is missing')
+            return
+        self.run_cmd(["pprof_proto_generator.py", "-h"])
+        self.run_app_profiler()
+        self.run_cmd(["pprof_proto_generator.py"])
+        self.remove("pprof.profile")
+        self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
+        self.check_exist(file="pprof.profile")
+        self.run_cmd(["pprof_proto_generator.py", "--show"])
+        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
+                              return_output=True)
+        self.check_strings_in_content(output, check_strings_with_lines +
+                                              ["has_line_numbers: True"])
+        self.remove("binary_cache")
+        self.run_cmd(["pprof_proto_generator.py"])
+        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
+                              return_output=True)
+        self.check_strings_in_content(output, check_strings_without_lines +
+                                              ["has_line_numbers: False"])
+
+
+class TestExamplePureJava(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExamplePureJava",
+                    "com.example.simpleperf.simpleperfexamplepurejava",
+                    ".MainActivity")
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+
+    def test_report(self):
+        self.common_test_report()
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
+             "__start_thread"])
+
+    def test_annotate(self):
+        self.common_test_annotate()
+        self.check_file_under_dir("annotated_files", "MainActivity.java")
+        summary_file = os.path.join("annotated_files", "summary")
+        self.check_annotation_summary(summary_file,
+            [("MainActivity.java", 80, 80),
+             ("run", 80, 0),
+             ("callFunction", 0, 0),
+             ("line 24", 80, 0)])
+
+    def test_report_sample(self):
+        self.common_test_report_sample(
+            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
+             "__start_thread"])
+
+    def test_pprof_proto_generator(self):
+        self.common_test_pprof_proto_generator(
+            check_strings_with_lines=
+                ["com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
+                 "run"],
+            check_strings_without_lines=
+                ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"])
+
+
+class TestExamplePureJavaRoot(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExamplePureJava",
+                    "com.example.simpleperf.simpleperfexamplepurejava",
+                    ".MainActivity",
+                    adb_root=True)
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+
+
+class TestExampleWithNative(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleWithNative",
+                    "com.example.simpleperf.simpleperfexamplewithnative",
+                    ".MainActivity")
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+        self.remove("binary_cache")
+        self.run_app_profiler(native_lib_dir=self.example_path)
+
+    def test_report(self):
+        self.common_test_report()
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["BusyLoopThread",
+             "__start_thread"])
+
+    def test_annotate(self):
+        self.common_test_annotate()
+        self.check_file_under_dir("annotated_files", "native-lib.cpp")
+        summary_file = os.path.join("annotated_files", "summary")
+        self.check_annotation_summary(summary_file,
+            [("native-lib.cpp", 20, 0),
+             ("BusyLoopThread", 20, 0),
+             ("line 46", 20, 0)])
+
+    def test_report_sample(self):
+        self.common_test_report_sample(
+            ["BusyLoopThread",
+             "__start_thread"])
+
+    def test_pprof_proto_generator(self):
+        self.common_test_pprof_proto_generator(
+            check_strings_with_lines=
+                ["native-lib.cpp",
+                 "BusyLoopThread"],
+            check_strings_without_lines=
+                ["BusyLoopThread"])
+
+
+class TestExampleWithNativeRoot(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleWithNative",
+                    "com.example.simpleperf.simpleperfexamplewithnative",
+                    ".MainActivity",
+                    adb_root=True)
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+        self.remove("binary_cache")
+        self.run_app_profiler(native_lib_dir=self.example_path)
+
+
+class TestExampleWithNativeForceArm(TestExampleWithNative):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleWithNative",
+                    "com.example.simpleperf.simpleperfexamplewithnative",
+                    ".MainActivity",
+                    abi="armeabi-v7a")
+
+
+class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleWithNative",
+                    "com.example.simpleperf.simpleperfexamplewithnative",
+                    ".MainActivity",
+                    abi="armeabi-v7a",
+                    adb_root=False)
+
+
+class TestExampleOfKotlin(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleOfKotlin",
+                    "com.example.simpleperf.simpleperfexampleofkotlin",
+                    ".MainActivity")
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+
+    def test_report(self):
+        self.common_test_report()
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
+             "__start_thread"])
+
+    def test_annotate(self):
+        self.common_test_annotate()
+        self.check_file_under_dir("annotated_files", "MainActivity.kt")
+        summary_file = os.path.join("annotated_files", "summary")
+        self.check_annotation_summary(summary_file,
+            [("MainActivity.kt", 80, 80),
+             ("run", 80, 0),
+             ("callFunction", 0, 0),
+             ("line 19", 80, 0),
+             ("line 25", 0, 0)])
+
+    def test_report_sample(self):
+        self.common_test_report_sample(
+            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
+             "__start_thread"])
+
+    def test_pprof_proto_generator(self):
+        self.common_test_pprof_proto_generator(
+            check_strings_with_lines=
+                ["com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
+                 "run"],
+            check_strings_without_lines=
+                ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"])
+
+
+class TestExampleOfKotlinRoot(TestExamplesBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleOfKotlin",
+                    "com.example.simpleperf.simpleperfexampleofkotlin",
+                    ".MainActivity",
+                    adb_root=True)
+
+    def test_app_profiler(self):
+        self.common_test_app_profiler()
+
+
+if __name__ == '__main__':
+    test_program = unittest.main(failfast=True, exit=False)
+    if test_program.result.wasSuccessful():
+        TestExamplesBase.cleanupTestFiles()
\ No newline at end of file
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/app-profiling.apk b/simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/app-profiling.apk
new file mode 100644 (file)
index 0000000..d153960
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/app-profiling.apk differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt b/simpleperf/scripts/testdata/SimpleperfExampleOfKotlin/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt
new file mode 100644 (file)
index 0000000..55d47aa
--- /dev/null
@@ -0,0 +1,29 @@
+package com.example.simpleperf.simpleperfexampleofkotlin
+
+import android.support.v7.app.AppCompatActivity
+import android.os.Bundle
+
+class MainActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        createBusyThread()
+    }
+
+    fun createBusyThread() {
+        object : Thread() {
+            var i = 0
+
+            override fun run() {
+                while (true) {
+                    i = callFunction(i)
+                }
+            }
+
+            fun callFunction(i: Int): Int {
+                return i + 1
+            }
+        }.start()
+    }
+}
diff --git a/simpleperf/scripts/testdata/SimpleperfExamplePureJava/app-profiling.apk b/simpleperf/scripts/testdata/SimpleperfExamplePureJava/app-profiling.apk
new file mode 100644 (file)
index 0000000..8ac1eac
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExamplePureJava/app-profiling.apk differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExamplePureJava/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java b/simpleperf/scripts/testdata/SimpleperfExamplePureJava/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
new file mode 100644 (file)
index 0000000..18ff524
--- /dev/null
@@ -0,0 +1,33 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        createBusyThread();
+    }
+
+    void createBusyThread() {
+        new Thread(new Runnable() {
+
+            volatile int i = 0;
+
+            @Override
+            public void run() {
+                while (true) {
+                    i = callFunction(i);
+                }
+            }
+
+            private int callFunction(int a) {
+                return a+1;
+            }
+        }).start();
+    }
+}
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/app-profiling.apk b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/app-profiling.apk
new file mode 100644 (file)
index 0000000..8ff564e
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/app-profiling.apk differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/cpp/native-lib.cpp b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/cpp/native-lib.cpp
new file mode 100644 (file)
index 0000000..5cd0607
--- /dev/null
@@ -0,0 +1,62 @@
+#include <jni.h>
+
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <string>
+
+
+
+extern "C"
+JNIEXPORT jstring JNICALL
+Java_com_example_simpleperf_simpleperfexamplewithnative_MainActivity_stringFromJNI(
+        JNIEnv *env,
+        jobject /* this */) {
+    std::string hello = "Hello from C++";
+    return env->NewStringUTF(hello.c_str());
+}
+
+static void ThrowErrnoException(JNIEnv* env, const char* function_name, int err) {
+    jclass cls = env->FindClass("android/system/ErrnoException");
+    if (cls == nullptr) {
+        return;
+    }
+    jmethodID cid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;I)V");
+    if (cid == nullptr) {
+        return;
+    }
+    jstring msg = env->NewStringUTF(function_name);
+    if (msg == nullptr) {
+        return;
+    }
+    jthrowable obj = (jthrowable)env->NewObject(cls, cid, msg, err);
+    if (obj == nullptr) {
+        return;
+    }
+    env->Throw(obj);
+}
+
+int CallFunction(int a) {
+    return a + atoi("1");
+}
+
+static void* BusyLoopThread(void*) {
+    volatile int i = 0;
+    while (true) {
+        i = CallFunction(i);
+    }
+    return nullptr;
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_example_simpleperf_simpleperfexamplewithnative_MainActivity_createBusyThreadFromJNI(
+        JNIEnv *env,
+        jobject /* this */) {
+    pthread_t thread;
+    int ret = pthread_create(&thread, nullptr, BusyLoopThread, nullptr);
+    if (ret) {
+        ThrowErrnoException(env, "pthread_create", ret);
+        return;
+    }
+}
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/java/com/example/simpleperf/simpleperfexamplewithnative/MainActivity.java b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/java/com/example/simpleperf/simpleperfexamplewithnative/MainActivity.java
new file mode 100644 (file)
index 0000000..4dbf482
--- /dev/null
@@ -0,0 +1,33 @@
+package com.example.simpleperf.simpleperfexamplewithnative;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class MainActivity extends AppCompatActivity {
+
+    // Used to load the 'native-lib' library on application startup.
+    static {
+        System.loadLibrary("native-lib");
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        // Example of a call to a native method
+        TextView tv = (TextView) findViewById(R.id.sample_text);
+        tv.setText(stringFromJNI());
+
+        createBusyThreadFromJNI();
+    }
+
+    /**
+     * A native method that is implemented by the 'native-lib' native library,
+     * which is packaged with this application.
+     */
+    public native String stringFromJNI();
+
+    private native void createBusyThreadFromJNI();
+}
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/arm64-v8a/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/arm64-v8a/libnative-lib.so
new file mode 100755 (executable)
index 0000000..bcc09b5
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/arm64-v8a/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi-v7a/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi-v7a/libnative-lib.so
new file mode 100755 (executable)
index 0000000..944633e
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi-v7a/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi/libnative-lib.so
new file mode 100755 (executable)
index 0000000..9246e43
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/armeabi/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips/libnative-lib.so
new file mode 100755 (executable)
index 0000000..06793de
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips64/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips64/libnative-lib.so
new file mode 100755 (executable)
index 0000000..f604433
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/mips64/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86/libnative-lib.so
new file mode 100755 (executable)
index 0000000..b2d3471
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86/libnative-lib.so differ
diff --git a/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86_64/libnative-lib.so b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86_64/libnative-lib.so
new file mode 100755 (executable)
index 0000000..dbf5fe4
Binary files /dev/null and b/simpleperf/scripts/testdata/SimpleperfExampleWithNative/shared_libs/cmake/profiling/obj/x86_64/libnative-lib.so differ
index 2e2c69a..88b2f90 100644 (file)
@@ -24,6 +24,7 @@ import os
 import os.path
 import subprocess
 import sys
+import time
 
 def get_script_dir():
     return os.path.dirname(os.path.realpath(__file__))
@@ -167,18 +168,19 @@ def find_tool_path(toolname):
 
 
 class AdbHelper(object):
-    def __init__(self):
+    def __init__(self, enable_switch_to_root=True):
         adb_path = find_tool_path('adb')
         if not adb_path:
             log_exit("Can't find adb in PATH environment.")
         self.adb_path = adb_path
+        self.enable_switch_to_root = enable_switch_to_root
 
 
     def run(self, adb_args):
         return self.run_and_return_output(adb_args)[0]
 
 
-    def run_and_return_output(self, adb_args, stdout_file=None):
+    def run_and_return_output(self, adb_args, stdout_file=None, log_output=True):
         adb_args = [self.adb_path] + adb_args
         log_debug('run adb cmd: %s' % adb_args)
         if stdout_file:
@@ -192,7 +194,8 @@ class AdbHelper(object):
         result = (returncode == 0)
         if stdoutdata and adb_args[1] != 'push' and adb_args[1] != 'pull':
             stdoutdata = bytes_to_str(stdoutdata)
-            log_debug(stdoutdata)
+            if log_output:
+                log_debug(stdoutdata)
         log_debug('run adb cmd: %s  [result %s]' % (adb_args, result))
         return (result, stdoutdata)
 
@@ -200,14 +203,29 @@ class AdbHelper(object):
         self.check_run_and_return_output(adb_args)
 
 
-    def check_run_and_return_output(self, adb_args, stdout_file=None):
-        result, stdoutdata = self.run_and_return_output(adb_args, stdout_file)
+    def check_run_and_return_output(self, adb_args, stdout_file=None, log_output=True):
+        result, stdoutdata = self.run_and_return_output(adb_args, stdout_file, log_output)
         if not result:
             log_exit('run "adb %s" failed' % adb_args)
         return stdoutdata
 
 
+    def _unroot(self):
+        result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
+        if not result:
+            return
+        if stdoutdata.find('root') == -1:
+            return
+        log_info('unroot adb')
+        self.run(['unroot'])
+        self.run(['wait-for-device'])
+        time.sleep(1)
+
+
     def switch_to_root(self):
+        if not self.enable_switch_to_root:
+            self._unroot()
+            return False
         result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
         if not result:
             return False
@@ -217,6 +235,8 @@ class AdbHelper(object):
         if build_type == 'user':
             return False
         self.run(['root'])
+        time.sleep(1)
+        self.run(['wait-for-device'])
         result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
         if result and stdoutdata.find('root') != -1:
             return True