| #!/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 |
| report_html.py |
| |
| Test using both `adb root` and `adb unroot`. |
| |
| """ |
| |
| import os |
| import re |
| import shutil |
| import signal |
| import sys |
| import tempfile |
| import time |
| import unittest |
| from utils import * |
| from simpleperf_report_lib import ReportLib |
| |
| has_google_protobuf = True |
| try: |
| import google.protobuf |
| except: |
| has_google_protobuf = False |
| |
| inferno_script = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh") |
| |
| support_trace_offcpu = None |
| |
| def is_trace_offcpu_supported(): |
| global support_trace_offcpu |
| if support_trace_offcpu is None: |
| adb = AdbHelper() |
| adb.check_run_and_return_output(['push', |
| 'bin/android/%s/simpleperf' % adb.get_device_arch(), |
| "/data/local/tmp"]) |
| adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) |
| output = adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list', |
| '--show-features']) |
| support_trace_offcpu = 'trace-offcpu' in output |
| return support_trace_offcpu |
| |
| def build_testdata(): |
| """ Collect testdata from ../testdata and ../demo. """ |
| from_testdata_path = os.path.join('..', 'testdata') |
| from_demo_path = os.path.join('..', 'demo') |
| from_script_testdata_path = 'script_testdata' |
| if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or |
| not from_script_testdata_path): |
| return |
| copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data', |
| 'perf_with_tracepoint_event.data'] |
| copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative', |
| 'SimpleperfExampleOfKotlin'] |
| |
| testdata_path = "testdata" |
| remove(testdata_path) |
| os.mkdir(testdata_path) |
| for testdata in copy_testdata_list: |
| shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path) |
| for demo in copy_demo_list: |
| shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo)) |
| for f in os.listdir(from_script_testdata_path): |
| shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path) |
| |
| class TestBase(unittest.TestCase): |
| def run_cmd(self, args, return_output=False): |
| if args[0].endswith('.py'): |
| args = [sys.executable] + args |
| use_shell = args[0].endswith('.bat') |
| try: |
| if not return_output: |
| returncode = subprocess.call(args, shell=use_shell) |
| else: |
| subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell) |
| (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 |
| |
| |
| class TestExampleBase(TestBase): |
| @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) |
| for root, _, files in os.walk(cls.example_path): |
| if 'app-profiling.apk' in files: |
| cls.apk_path = os.path.join(root, 'app-profiling.apk') |
| break |
| if not hasattr(cls, 'apk_path'): |
| log_fatal("can't find app-profiling.apk under " + cls.example_path) |
| cls.package_name = package_name |
| cls.activity_name = activity_name |
| cls.abi = "arm64" |
| if abi and abi != "arm64" and abi.find("arm") != -1: |
| cls.abi = "arm" |
| 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 |
| |
| def setUp(self): |
| if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported(): |
| self.skipTest('trace-offcpu is not supported on device') |
| |
| @classmethod |
| def tearDownClass(cls): |
| if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful(): |
| return |
| if hasattr(cls, 'package_name'): |
| cls.adb.check_run(["uninstall", cls.package_name]) |
| remove("binary_cache") |
| remove("annotated_files") |
| remove("perf.data") |
| remove("report.txt") |
| remove("pprof.profile") |
| |
| def run(self, result=None): |
| self.__class__.test_result = result |
| super(TestBase, self).run(result) |
| |
| def run_app_profiler(self, record_arg = "-g -f 1000 --duration 3 -e cpu-cycles:u", |
| build_binary_cache=True, skip_compile=False, start_activity=True, |
| native_lib_dir=None, profile_from_launch=False, add_arch=False): |
| args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path, |
| "-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 profile_from_launch: |
| args.append("--profile_from_launch") |
| if add_arch: |
| args += ["--arch", self.abi] |
| 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 |
| |
| 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 name in line: |
| 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 check_inferno_report_html(self, check_entries, file="report.html"): |
| self.check_exist(file=file) |
| with open(file, 'r') as fh: |
| data = fh.read() |
| fulfilled = [False for _ in check_entries] |
| for line in data.split('\n'): |
| # each entry is a (function_name, min_percentage) pair. |
| for i, entry in enumerate(check_entries): |
| if fulfilled[i] or line.find(entry[0]) == -1: |
| continue |
| m = re.search(r'(\d+\.\d+)%', line) |
| if m and float(m.group(1)) >= entry[1]: |
| fulfilled[i] = True |
| break |
| self.assertEqual(fulfilled, [True for x in check_entries]) |
| |
| def common_test_app_profiler(self): |
| self.run_cmd(["app_profiler.py", "-h"]) |
| 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") |
| 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() |
| 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"]) |
| 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 -f 1000 --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"]) |
| 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"]) |
| 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"]) |
| |
| def common_test_inferno(self): |
| self.run_cmd([inferno_script, "-h"]) |
| remove("perf.data") |
| append_args = [] if self.adb_root else ["--disable_adb_root"] |
| self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args) |
| self.check_exist(file="perf.data") |
| self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1", |
| "-nc"] + append_args) |
| self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles", |
| "-t", "1", "-nc"] + append_args) |
| self.run_cmd([inferno_script, "-sc"]) |
| |
| def common_test_report_html(self): |
| self.run_cmd(['report_html.py', '-h']) |
| self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') |
| self.run_cmd(['report_html.py']) |
| self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata']) |
| self.run_cmd(['report_html.py', '--add_disassembly']) |
| # Test with multiple perf.data. |
| shutil.move('perf.data', 'perf2.data') |
| self.run_app_profiler() |
| self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) |
| remove('perf2.data') |
| |
| |
| class TestExamplePureJava(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExamplePureJava", |
| "com.example.simpleperf.simpleperfexamplepurejava", |
| ".MainActivity") |
| |
| def test_app_profiler(self): |
| self.common_test_app_profiler() |
| |
| def test_app_profiler_profile_from_launch(self): |
| self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) |
| 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_app_profiler_multiprocesses(self): |
| self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) |
| self.adb.check_run(['shell', 'am', 'start', '-n', |
| self.package_name + '/.MultiProcessActivity']) |
| # Wait until both MultiProcessActivity and MultiProcessService set up. |
| time.sleep(3) |
| self.run_app_profiler(skip_compile=True, start_activity=False) |
| self.run_cmd(["report.py", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"]) |
| |
| def test_app_profiler_with_ctrl_c(self): |
| if is_windows(): |
| return |
| # `adb root` and `adb unroot` may consumes more time than 3 sec. So |
| # do it in advance to make sure ctrl-c happens when recording. |
| if self.adb_root: |
| self.adb.switch_to_root() |
| else: |
| self.adb._unroot() |
| args = [sys.executable, "app_profiler.py", "--app", self.package_name, |
| "-r", "--duration 10000", "-nc"] |
| if not self.adb_root: |
| args.append("--disable_adb_root") |
| subproc = subprocess.Popen(args) |
| time.sleep(3) |
| |
| subproc.send_signal(signal.SIGINT) |
| subproc.wait() |
| self.assertEqual(subproc.returncode, 0) |
| self.run_cmd(["report.py"]) |
| |
| 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 23", 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()"]) |
| |
| def test_inferno(self): |
| self.common_test_inferno() |
| self.run_app_profiler() |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html( |
| [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)]) |
| self.run_cmd([inferno_script, "-sc", "-o", "report2.html"]) |
| self.check_inferno_report_html( |
| [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)], |
| "report2.html") |
| remove("report2.html") |
| |
| def test_inferno_in_another_dir(self): |
| test_dir = 'inferno_testdir' |
| saved_dir = os.getcwd() |
| remove(test_dir) |
| os.mkdir(test_dir) |
| os.chdir(test_dir) |
| self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'), |
| '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3']) |
| self.check_exist(file="perf.data") |
| self.run_cmd([inferno_script, "-sc"]) |
| os.chdir(saved_dir) |
| remove(test_dir) |
| |
| def test_report_html(self): |
| self.common_test_report_html() |
| |
| |
| class TestExamplePureJavaRoot(TestExampleBase): |
| @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 TestExamplePureJavaTraceOffCpu(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExamplePureJava", |
| "com.example.simpleperf.simpleperfexamplepurejava", |
| ".SleepActivity") |
| |
| def test_smoke(self): |
| self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", |
| ["com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run()", |
| "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()", |
| "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)" |
| ]) |
| remove("annotated_files") |
| self.run_cmd(["annotate.py", "-s", self.example_path]) |
| self.check_exist(dir="annotated_files") |
| self.check_file_under_dir("annotated_files", "SleepActivity.java") |
| summary_file = os.path.join("annotated_files", "summary") |
| self.check_annotation_summary(summary_file, |
| [("SleepActivity.java", 80, 20), |
| ("run", 80, 0), |
| ("RunFunction", 20, 20), |
| ("SleepFunction", 20, 0), |
| ("line 24", 20, 0), |
| ("line 32", 20, 0)]) |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html( |
| [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80), |
| ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()', |
| 20), |
| ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)', |
| 20)]) |
| |
| |
| class TestExampleWithNative(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleWithNative", |
| "com.example.simpleperf.simpleperfexamplewithnative", |
| ".MainActivity") |
| |
| def test_app_profiler(self): |
| self.common_test_app_profiler() |
| remove("binary_cache") |
| self.run_app_profiler(native_lib_dir=self.example_path) |
| |
| def test_app_profiler_profile_from_launch(self): |
| self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", |
| ["BusyLoopThread", |
| "__start_thread"]) |
| |
| 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"]) |
| |
| def test_inferno(self): |
| self.common_test_inferno() |
| self.run_app_profiler() |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html([('BusyLoopThread', 20)]) |
| |
| def test_report_html(self): |
| self.common_test_report_html() |
| |
| |
| class TestExampleWithNativeRoot(TestExampleBase): |
| @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() |
| remove("binary_cache") |
| self.run_app_profiler(native_lib_dir=self.example_path) |
| |
| |
| class TestExampleWithNativeTraceOffCpu(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleWithNative", |
| "com.example.simpleperf.simpleperfexamplewithnative", |
| ".SleepActivity") |
| |
| def test_smoke(self): |
| self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") |
| self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", |
| ["SleepThread(void*)", |
| "RunFunction()", |
| "SleepFunction(unsigned long long)"]) |
| remove("annotated_files") |
| self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) |
| self.check_exist(dir="annotated_files") |
| 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", 80, 20), |
| ("SleepThread", 80, 0), |
| ("RunFunction", 20, 20), |
| ("SleepFunction", 20, 0), |
| ("line 73", 20, 0), |
| ("line 83", 20, 0)]) |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html([('SleepThread', 80), |
| ('RunFunction', 20), |
| ('SleepFunction', 20)]) |
| |
| |
| class TestExampleWithNativeJniCall(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleWithNative", |
| "com.example.simpleperf.simpleperfexamplewithnative", |
| ".MixActivity") |
| |
| def test_smoke(self): |
| self.run_app_profiler() |
| self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", |
| ["void com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run()", |
| "int com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction(int)", |
| "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) |
| remove("annotated_files") |
| self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) |
| self.check_exist(dir="annotated_files") |
| self.check_file_under_dir("annotated_files", "native-lib.cpp") |
| self.check_file_under_dir("annotated_files", "MixActivity.java") |
| summary_file = os.path.join("annotated_files", "summary") |
| self.check_annotation_summary(summary_file, |
| [("MixActivity.java", 80, 0), |
| ("run", 80, 0), |
| ("line 26", 20, 0), |
| ("native-lib.cpp", 5, 0), |
| ("line 40", 5, 0)]) |
| self.run_cmd([inferno_script, "-sc"]) |
| |
| |
| 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 TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleWithNative", |
| "com.example.simpleperf.simpleperfexamplewithnative", |
| ".SleepActivity", |
| abi="armeabi-v7a") |
| |
| |
| class TestExampleOfKotlin(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleOfKotlin", |
| "com.example.simpleperf.simpleperfexampleofkotlin", |
| ".MainActivity") |
| |
| def test_app_profiler(self): |
| self.common_test_app_profiler() |
| |
| def test_app_profiler_profile_from_launch(self): |
| self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) |
| 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_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()"]) |
| |
| def test_inferno(self): |
| self.common_test_inferno() |
| self.run_app_profiler() |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html( |
| [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()', |
| 80)]) |
| |
| def test_report_html(self): |
| self.common_test_report_html() |
| |
| |
| class TestExampleOfKotlinRoot(TestExampleBase): |
| @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() |
| |
| |
| class TestExampleOfKotlinTraceOffCpu(TestExampleBase): |
| @classmethod |
| def setUpClass(cls): |
| cls.prepare("SimpleperfExampleOfKotlin", |
| "com.example.simpleperf.simpleperfexampleofkotlin", |
| ".SleepActivity") |
| |
| def test_smoke(self): |
| self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| self.check_strings_in_file("report.txt", |
| ["void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()", |
| "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()", |
| "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)" |
| ]) |
| remove("annotated_files") |
| self.run_cmd(["annotate.py", "-s", self.example_path]) |
| self.check_exist(dir="annotated_files") |
| self.check_file_under_dir("annotated_files", "SleepActivity.kt") |
| summary_file = os.path.join("annotated_files", "summary") |
| self.check_annotation_summary(summary_file, |
| [("SleepActivity.kt", 80, 20), |
| ("run", 80, 0), |
| ("RunFunction", 20, 20), |
| ("SleepFunction", 20, 0), |
| ("line 24", 20, 0), |
| ("line 32", 20, 0)]) |
| self.run_cmd([inferno_script, "-sc"]) |
| self.check_inferno_report_html( |
| [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()', |
| 80), |
| ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()', |
| 20), |
| ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)', |
| 20)]) |
| |
| |
| class TestProfilingNativeProgram(TestExampleBase): |
| def test_smoke(self): |
| if not AdbHelper().switch_to_root(): |
| log_info('skip TestProfilingNativeProgram on non-rooted devices.') |
| return |
| remove("perf.data") |
| self.run_cmd(["app_profiler.py", "-np", "surfaceflinger", |
| "-r", "-g -f 1000 --duration 3 -e cpu-cycles:u"]) |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| |
| |
| class TestProfilingCmd(TestExampleBase): |
| def test_smoke(self): |
| remove("perf.data") |
| self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| |
| def test_set_arch(self): |
| arch = AdbHelper().get_device_arch() |
| remove("perf.data") |
| self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--arch", arch]) |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| |
| |
| class TestProfilingNativeProgram(TestExampleBase): |
| def test_smoke(self): |
| adb = AdbHelper() |
| if adb.switch_to_root(): |
| self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) |
| self.run_cmd(["report.py", "-g", "-o", "report.txt"]) |
| self.run_cmd([inferno_script, "-sc"]) |
| self.run_cmd([inferno_script, "-np", "surfaceflinger"]) |
| |
| |
| class TestReportLib(unittest.TestCase): |
| def setUp(self): |
| self.report_lib = ReportLib() |
| self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) |
| |
| def tearDown(self): |
| self.report_lib.Close() |
| |
| def test_build_id(self): |
| build_id = self.report_lib.GetBuildIdForPath('/data/t2') |
| self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') |
| |
| def test_symbol(self): |
| found_func2 = False |
| while self.report_lib.GetNextSample(): |
| sample = self.report_lib.GetCurrentSample() |
| symbol = self.report_lib.GetSymbolOfCurrentSample() |
| if symbol.symbol_name == 'func2(int, int)': |
| found_func2 = True |
| self.assertEqual(symbol.symbol_addr, 0x4004ed) |
| self.assertEqual(symbol.symbol_len, 0x14) |
| self.assertTrue(found_func2) |
| |
| def test_sample(self): |
| found_sample = False |
| while self.report_lib.GetNextSample(): |
| sample = self.report_lib.GetCurrentSample() |
| if sample.ip == 0x4004ff and sample.time == 7637889424953: |
| found_sample = True |
| self.assertEqual(sample.pid, 15926) |
| self.assertEqual(sample.tid, 15926) |
| self.assertEqual(sample.thread_comm, 't2') |
| self.assertEqual(sample.cpu, 5) |
| self.assertEqual(sample.period, 694614) |
| event = self.report_lib.GetEventOfCurrentSample() |
| self.assertEqual(event.name, 'cpu-cycles') |
| callchain = self.report_lib.GetCallChainOfCurrentSample() |
| self.assertEqual(callchain.nr, 0) |
| self.assertTrue(found_sample) |
| |
| def test_meta_info(self): |
| self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) |
| meta_info = self.report_lib.MetaInfo() |
| self.assertTrue("simpleperf_version" in meta_info) |
| self.assertEqual(meta_info["system_wide_collection"], "false") |
| self.assertEqual(meta_info["trace_offcpu"], "true") |
| self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") |
| self.assertTrue("product_props" in meta_info) |
| |
| def test_event_name_from_meta_info(self): |
| self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) |
| event_names = set() |
| while self.report_lib.GetNextSample(): |
| event_names.add(self.report_lib.GetEventOfCurrentSample().name) |
| self.assertTrue('sched:sched_switch' in event_names) |
| self.assertTrue('cpu-cycles' in event_names) |
| |
| def test_record_cmd(self): |
| self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) |
| self.assertEqual(self.report_lib.GetRecordCmd(), |
| "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64") |
| |
| def test_offcpu(self): |
| self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) |
| total_period = 0 |
| sleep_function_period = 0 |
| sleep_function_name = "SleepFunction(unsigned long long)" |
| while self.report_lib.GetNextSample(): |
| sample = self.report_lib.GetCurrentSample() |
| total_period += sample.period |
| if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: |
| sleep_function_period += sample.period |
| continue |
| callchain = self.report_lib.GetCallChainOfCurrentSample() |
| for i in range(callchain.nr): |
| if callchain.entries[i].symbol.symbol_name == sleep_function_name: |
| sleep_function_period += sample.period |
| break |
| self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles') |
| sleep_percentage = float(sleep_function_period) / total_period |
| self.assertGreater(sleep_percentage, 0.30) |
| |
| |
| class TestRunSimpleperfOnDevice(TestBase): |
| def test_smoke(self): |
| self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) |
| |
| |
| class TestTools(unittest.TestCase): |
| def test_addr2nearestline(self): |
| binary_cache_path = 'testdata' |
| test_map = { |
| '/simpleperf_runtest_two_functions_arm64': [ |
| { |
| 'func_addr': 0x668, |
| 'addr': 0x668, |
| 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20', |
| }, |
| { |
| 'func_addr': 0x668, |
| 'addr': 0x6a4, |
| 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 |
| system/extras/simpleperf/runtest/two_functions.cpp:22""", |
| }, |
| ], |
| '/simpleperf_runtest_two_functions_arm': [ |
| { |
| 'func_addr': 0x784, |
| 'addr': 0x7b0, |
| 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 |
| system/extras/simpleperf/runtest/two_functions.cpp:23""", |
| }, |
| { |
| 'func_addr': 0x784, |
| 'addr': 0x7d0, |
| 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 |
| system/extras/simpleperf/runtest/two_functions.cpp:23""", |
| } |
| ], |
| '/simpleperf_runtest_two_functions_x86_64': [ |
| { |
| 'func_addr': 0x840, |
| 'addr': 0x840, |
| 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', |
| }, |
| { |
| 'func_addr': 0x920, |
| 'addr': 0x94a, |
| 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 |
| system/extras/simpleperf/runtest/two_functions.cpp:22""", |
| } |
| ], |
| '/simpleperf_runtest_two_functions_x86': [ |
| { |
| 'func_addr': 0x6d0, |
| 'addr': 0x6da, |
| 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', |
| }, |
| { |
| 'func_addr': 0x710, |
| 'addr': 0x749, |
| 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 |
| system/extras/simpleperf/runtest/two_functions.cpp:22""", |
| } |
| ], |
| } |
| addr2line = Addr2Nearestline(None, binary_cache_path) |
| for dso_path in test_map: |
| test_addrs = test_map[dso_path] |
| for test_addr in test_addrs: |
| addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr']) |
| addr2line.convert_addrs_to_lines() |
| for dso_path in test_map: |
| dso = addr2line.get_dso(dso_path) |
| self.assertTrue(dso is not None) |
| test_addrs = test_map[dso_path] |
| for test_addr in test_addrs: |
| source_str = test_addr['source'] |
| expected_source = [] |
| for line in source_str.split('\n'): |
| items = line.split(':') |
| expected_source.append((items[0].strip(), int(items[1]))) |
| actual_source = addr2line.get_addr_source(dso, test_addr['addr']) |
| self.assertTrue(actual_source is not None) |
| self.assertEqual(len(actual_source), len(expected_source)) |
| for i in range(len(expected_source)): |
| actual_file_path, actual_line = actual_source[i] |
| self.assertEqual(actual_file_path, expected_source[i][0]) |
| self.assertEqual(actual_line, expected_source[i][1]) |
| |
| def test_objdump(self): |
| binary_cache_path = 'testdata' |
| test_map = { |
| '/simpleperf_runtest_two_functions_arm64': { |
| 'start_addr': 0x668, |
| 'len': 116, |
| 'expected_items': [ |
| ('main():', 0), |
| ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), |
| (' 694: add x20, x20, #0x6de', 0x694), |
| ], |
| }, |
| '/simpleperf_runtest_two_functions_arm': { |
| 'start_addr': 0x784, |
| 'len': 80, |
| 'expected_items': [ |
| ('main():', 0), |
| ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), |
| (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae), |
| ], |
| }, |
| '/simpleperf_runtest_two_functions_x86_64': { |
| 'start_addr': 0x920, |
| 'len': 201, |
| 'expected_items': [ |
| ('main():', 0), |
| ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), |
| (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e), |
| ], |
| }, |
| '/simpleperf_runtest_two_functions_x86': { |
| 'start_addr': 0x710, |
| 'len': 98, |
| 'expected_items': [ |
| ('main():', 0), |
| ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), |
| (' 748: cmp $0x5f5e100,%ebp', 0x748), |
| ], |
| }, |
| } |
| objdump = Objdump(None, binary_cache_path) |
| for dso_path in test_map: |
| dso_info = test_map[dso_path] |
| disassemble_code = objdump.disassemble_code(dso_path, dso_info['start_addr'], |
| dso_info['len']) |
| self.assertTrue(disassemble_code) |
| for item in dso_info['expected_items']: |
| self.assertTrue(item in disassemble_code) |
| |
| |
| def main(): |
| os.chdir(get_script_dir()) |
| build_testdata() |
| if AdbHelper().get_android_version() < 7: |
| log_info("Skip tests on Android version < N.") |
| sys.exit(0) |
| unittest.main(failfast=True) |
| |
| if __name__ == '__main__': |
| main() |