1#!/usr/bin/env python 2# 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""test.py: Tests for simpleperf python scripts. 18 19These are smoke tests Using examples to run python scripts. 20For each example, we go through the steps of running each python script. 21Examples are collected from simpleperf/demo, which includes: 22 SimpleperfExamplePureJava 23 SimpleperfExampleWithNative 24 SimpleperfExampleOfKotlin 25 26Tested python scripts include: 27 app_profiler.py 28 report.py 29 annotate.py 30 report_sample.py 31 pprof_proto_generator.py 32 report_html.py 33 34Test using both `adb root` and `adb unroot`. 35 36""" 37from __future__ import print_function 38import argparse 39import collections 40import filecmp 41import fnmatch 42import inspect 43import json 44import logging 45import os 46import re 47import shutil 48import signal 49import subprocess 50import sys 51import time 52import types 53import unittest 54 55from app_profiler import NativeLibDownloader 56from binary_cache_builder import BinaryCacheBuilder 57from simpleperf_report_lib import ReportLib 58from utils import log_exit, log_info, log_fatal 59from utils import AdbHelper, Addr2Nearestline, bytes_to_str, find_tool_path, get_script_dir 60from utils import is_elf_file, is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher 61from utils import str_to_bytes 62 63try: 64 # pylint: disable=unused-import 65 import google.protobuf 66 # pylint: disable=ungrouped-imports 67 from pprof_proto_generator import load_pprof_profile 68 HAS_GOOGLE_PROTOBUF = True 69except ImportError: 70 HAS_GOOGLE_PROTOBUF = False 71 72INFERNO_SCRIPT = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh") 73 74 75class TestLogger(object): 76 """ Write test progress in sys.stderr and keep verbose log in log file. """ 77 def __init__(self): 78 self.log_file = self.get_log_file(3 if is_python3() else 2) 79 if os.path.isfile(self.log_file): 80 remove(self.log_file) 81 # Logs can come from multiple processes. So use append mode to avoid overwrite. 82 self.log_fh = open(self.log_file, 'a') 83 logging.basicConfig(filename=self.log_file) 84 85 @staticmethod 86 def get_log_file(python_version): 87 return 'test_python_%d.log' % python_version 88 89 def writeln(self, s): 90 return self.write(s + '\n') 91 92 def write(self, s): 93 sys.stderr.write(s) 94 self.log_fh.write(s) 95 # Child processes can also write to log file, so flush it immediately to keep the order. 96 self.flush() 97 98 def flush(self): 99 self.log_fh.flush() 100 101 102TEST_LOGGER = TestLogger() 103 104 105class TestHelper(object): 106 """ Keep global test info. """ 107 108 def __init__(self): 109 self.python_version = 3 if is_python3() else 2 110 self.repeat_count = 0 111 self.script_dir = os.path.abspath(get_script_dir()) 112 self.cur_dir = os.getcwd() 113 self.testdata_dir = os.path.join(self.cur_dir, 'testdata') 114 self.test_base_dir = self.get_test_base_dir(self.python_version) 115 self.adb = AdbHelper(enable_switch_to_root=True) 116 self.android_version = self.adb.get_android_version() 117 self.device_features = None 118 self.browser_option = [] 119 120 def get_test_base_dir(self, python_version): 121 """ Return the dir of generated data for a python version. """ 122 return os.path.join(self.cur_dir, 'test_python_%d' % python_version) 123 124 def testdata_path(self, testdata_name): 125 """ Return the path of a test data. """ 126 return os.path.join(self.testdata_dir, testdata_name.replace('/', os.sep)) 127 128 def test_dir(self, test_name): 129 """ Return the dir to run a test. """ 130 return os.path.join( 131 self.test_base_dir, 'repeat_%d' % TEST_HELPER.repeat_count, test_name) 132 133 def script_path(self, script_name): 134 """ Return the dir of python scripts. """ 135 return os.path.join(self.script_dir, script_name) 136 137 def get_device_features(self): 138 if self.device_features is None: 139 args = [sys.executable, self.script_path( 140 'run_simpleperf_on_device.py'), 'list', '--show-features'] 141 output = subprocess.check_output(args, stderr=TEST_LOGGER.log_fh) 142 output = bytes_to_str(output) 143 self.device_features = output.split() 144 return self.device_features 145 146 def is_trace_offcpu_supported(self): 147 return 'trace-offcpu' in self.get_device_features() 148 149 def build_testdata(self): 150 """ Collect testdata in self.testdata_dir. 151 In system/extras/simpleperf/scripts, testdata comes from: 152 <script_dir>/../testdata, <script_dir>/script_testdata, <script_dir>/../demo 153 In prebuilts/simpleperf, testdata comes from: 154 <script_dir>/testdata 155 """ 156 if os.path.isdir(self.testdata_dir): 157 return # already built 158 os.makedirs(self.testdata_dir) 159 160 source_dirs = [os.path.join('..', 'testdata'), 'script_testdata', 161 os.path.join('..', 'demo'), 'testdata'] 162 source_dirs = [os.path.join(self.script_dir, x) for x in source_dirs] 163 source_dirs = [x for x in source_dirs if os.path.isdir(x)] 164 165 for source_dir in source_dirs: 166 for name in os.listdir(source_dir): 167 source = os.path.join(source_dir, name) 168 target = os.path.join(self.testdata_dir, name) 169 if os.path.exists(target): 170 continue 171 if os.path.isfile(source): 172 shutil.copyfile(source, target) 173 elif os.path.isdir(source): 174 shutil.copytree(source, target) 175 176 def get_32bit_abi(self): 177 return self.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] 178 179 180TEST_HELPER = TestHelper() 181 182 183class TestBase(unittest.TestCase): 184 def setUp(self): 185 """ Run each test in a separate dir. """ 186 self.test_dir = TEST_HELPER.test_dir('%s.%s' % ( 187 self.__class__.__name__, self._testMethodName)) 188 os.makedirs(self.test_dir) 189 os.chdir(self.test_dir) 190 191 def run_cmd(self, args, return_output=False): 192 if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT: 193 args += TEST_HELPER.browser_option 194 if args[0].endswith('.py'): 195 args = [sys.executable, TEST_HELPER.script_path(args[0])] + args[1:] 196 use_shell = args[0].endswith('.bat') 197 try: 198 if not return_output: 199 returncode = subprocess.call(args, shell=use_shell, stderr=TEST_LOGGER.log_fh) 200 else: 201 subproc = subprocess.Popen(args, stdout=subprocess.PIPE, 202 stderr=TEST_LOGGER.log_fh, shell=use_shell) 203 (output_data, _) = subproc.communicate() 204 output_data = bytes_to_str(output_data) 205 returncode = subproc.returncode 206 except OSError: 207 returncode = None 208 self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args) 209 if return_output: 210 return output_data 211 return '' 212 213 def check_strings_in_file(self, filename, strings): 214 self.check_exist(filename=filename) 215 with open(filename, 'r') as fh: 216 self.check_strings_in_content(fh.read(), strings) 217 218 def check_exist(self, filename=None, dirname=None): 219 if filename: 220 self.assertTrue(os.path.isfile(filename), filename) 221 if dirname: 222 self.assertTrue(os.path.isdir(dirname), dirname) 223 224 def check_strings_in_content(self, content, strings): 225 for s in strings: 226 self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) 227 228 229class TestExampleBase(TestBase): 230 @classmethod 231 def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): 232 cls.adb = AdbHelper(enable_switch_to_root=adb_root) 233 cls.example_path = TEST_HELPER.testdata_path(example_name) 234 if not os.path.isdir(cls.example_path): 235 log_fatal("can't find " + cls.example_path) 236 for root, _, files in os.walk(cls.example_path): 237 if 'app-profiling.apk' in files: 238 cls.apk_path = os.path.join(root, 'app-profiling.apk') 239 break 240 if not hasattr(cls, 'apk_path'): 241 log_fatal("can't find app-profiling.apk under " + cls.example_path) 242 cls.package_name = package_name 243 cls.activity_name = activity_name 244 args = ["install", "-r"] 245 if abi: 246 args += ["--abi", abi] 247 args.append(cls.apk_path) 248 cls.adb.check_run(args) 249 cls.adb_root = adb_root 250 cls.has_perf_data_for_report = False 251 # On Android >= P (version 9), we can profile JITed and interpreted Java code. 252 # So only compile Java code on Android <= O (version 8). 253 cls.use_compiled_java_code = TEST_HELPER.android_version <= 8 254 cls.testcase_dir = TEST_HELPER.test_dir(cls.__name__) 255 256 @classmethod 257 def tearDownClass(cls): 258 remove(cls.testcase_dir) 259 if hasattr(cls, 'package_name'): 260 cls.adb.check_run(["uninstall", cls.package_name]) 261 262 def setUp(self): 263 super(TestExampleBase, self).setUp() 264 if 'TraceOffCpu' in self.id() and not TEST_HELPER.is_trace_offcpu_supported(): 265 self.skipTest('trace-offcpu is not supported on device') 266 # Use testcase_dir to share a common perf.data for reporting. So we don't need to 267 # generate it for each test. 268 if not os.path.isdir(self.testcase_dir): 269 os.makedirs(self.testcase_dir) 270 os.chdir(self.testcase_dir) 271 self.run_app_profiler(compile_java_code=self.use_compiled_java_code) 272 remove(self.test_dir) 273 shutil.copytree(self.testcase_dir, self.test_dir) 274 os.chdir(self.test_dir) 275 276 def run(self, result=None): 277 self.__class__.test_result = result 278 super(TestExampleBase, self).run(result) 279 280 def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True, 281 start_activity=True, compile_java_code=False): 282 args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data'] 283 if not build_binary_cache: 284 args.append("-nb") 285 if compile_java_code: 286 args.append('--compile_java_code') 287 if start_activity: 288 args += ["-a", self.activity_name] 289 args += ["-lib", self.example_path] 290 if not self.adb_root: 291 args.append("--disable_adb_root") 292 self.run_cmd(args) 293 self.check_exist(filename="perf.data") 294 if build_binary_cache: 295 self.check_exist(dirname="binary_cache") 296 297 def check_file_under_dir(self, dirname, filename): 298 self.check_exist(dirname=dirname) 299 for _, _, files in os.walk(dirname): 300 for f in files: 301 if f == filename: 302 return 303 self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename)) 304 305 def check_annotation_summary(self, summary_file, check_entries): 306 """ check_entries is a list of (name, accumulated_period, period). 307 This function checks for each entry, if the line containing [name] 308 has at least required accumulated_period and period. 309 """ 310 self.check_exist(filename=summary_file) 311 with open(summary_file, 'r') as fh: 312 summary = fh.read() 313 fulfilled = [False for x in check_entries] 314 summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%') 315 for line in summary.split('\n'): 316 for i, (name, need_acc_period, need_period) in enumerate(check_entries): 317 if not fulfilled[i] and name in line: 318 m = summary_check_re.search(line) 319 if m: 320 acc_period = float(m.group(1)) 321 period = float(m.group(2)) 322 if acc_period >= need_acc_period and period >= need_period: 323 fulfilled[i] = True 324 self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled) 325 326 def check_inferno_report_html(self, check_entries, filename="report.html"): 327 self.check_exist(filename=filename) 328 with open(filename, 'r') as fh: 329 data = fh.read() 330 fulfilled = [False for _ in check_entries] 331 for line in data.split('\n'): 332 # each entry is a (function_name, min_percentage) pair. 333 for i, entry in enumerate(check_entries): 334 if fulfilled[i] or line.find(entry[0]) == -1: 335 continue 336 m = re.search(r'(\d+\.\d+)%', line) 337 if m and float(m.group(1)) >= entry[1]: 338 fulfilled[i] = True 339 break 340 self.assertEqual(fulfilled, [True for _ in check_entries]) 341 342 def common_test_app_profiler(self): 343 self.run_cmd(["app_profiler.py", "-h"]) 344 remove("binary_cache") 345 self.run_app_profiler(build_binary_cache=False) 346 self.assertFalse(os.path.isdir("binary_cache")) 347 args = ["binary_cache_builder.py"] 348 if not self.adb_root: 349 args.append("--disable_adb_root") 350 self.run_cmd(args) 351 self.check_exist(dirname="binary_cache") 352 remove("binary_cache") 353 self.run_app_profiler(build_binary_cache=True) 354 self.run_app_profiler() 355 self.run_app_profiler(start_activity=False) 356 357 def common_test_report(self): 358 self.run_cmd(["report.py", "-h"]) 359 self.run_cmd(["report.py"]) 360 self.run_cmd(["report.py", "-i", "perf.data"]) 361 self.run_cmd(["report.py", "-g"]) 362 self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"]) 363 364 def common_test_annotate(self): 365 self.run_cmd(["annotate.py", "-h"]) 366 remove("annotated_files") 367 self.run_cmd(["annotate.py", "-s", self.example_path]) 368 self.check_exist(dirname="annotated_files") 369 370 def common_test_report_sample(self, check_strings): 371 self.run_cmd(["report_sample.py", "-h"]) 372 self.run_cmd(["report_sample.py"]) 373 output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True) 374 self.check_strings_in_content(output, check_strings) 375 376 def common_test_pprof_proto_generator(self, check_strings_with_lines, 377 check_strings_without_lines): 378 if not HAS_GOOGLE_PROTOBUF: 379 log_info('Skip test for pprof_proto_generator because google.protobuf is missing') 380 return 381 self.run_cmd(["pprof_proto_generator.py", "-h"]) 382 self.run_cmd(["pprof_proto_generator.py"]) 383 remove("pprof.profile") 384 self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"]) 385 self.check_exist(filename="pprof.profile") 386 self.run_cmd(["pprof_proto_generator.py", "--show"]) 387 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 388 return_output=True) 389 self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"]) 390 remove("binary_cache") 391 self.run_cmd(["pprof_proto_generator.py"]) 392 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 393 return_output=True) 394 self.check_strings_in_content(output, check_strings_without_lines + 395 ["has_line_numbers: False"]) 396 397 def common_test_inferno(self): 398 self.run_cmd([INFERNO_SCRIPT, "-h"]) 399 remove("perf.data") 400 append_args = [] if self.adb_root else ["--disable_adb_root"] 401 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args) 402 self.check_exist(filename="perf.data") 403 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] + 404 append_args) 405 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles", 406 "-t", "1"] + append_args) 407 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 408 409 def common_test_report_html(self): 410 self.run_cmd(['report_html.py', '-h']) 411 self.run_cmd(['report_html.py']) 412 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata']) 413 self.run_cmd(['report_html.py', '--add_disassembly']) 414 # Test with multiple perf.data. 415 shutil.move('perf.data', 'perf2.data') 416 self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') 417 self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) 418 419 420class TestExamplePureJava(TestExampleBase): 421 @classmethod 422 def setUpClass(cls): 423 cls.prepare("SimpleperfExamplePureJava", 424 "com.example.simpleperf.simpleperfexamplepurejava", 425 ".MainActivity") 426 427 def test_app_profiler(self): 428 self.common_test_app_profiler() 429 430 def test_app_profiler_profile_from_launch(self): 431 self.run_app_profiler(start_activity=True, build_binary_cache=False) 432 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 433 self.check_strings_in_file("report.txt", [ 434 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 435 "__start_thread"]) 436 437 def test_app_profiler_multiprocesses(self): 438 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 439 self.adb.check_run(['shell', 'am', 'start', '-n', 440 self.package_name + '/.MultiProcessActivity']) 441 # Wait until both MultiProcessActivity and MultiProcessService set up. 442 time.sleep(3) 443 self.run_app_profiler(start_activity=False) 444 self.run_cmd(["report.py", "-o", "report.txt"]) 445 self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"]) 446 447 def test_app_profiler_with_ctrl_c(self): 448 if is_windows(): 449 return 450 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 451 time.sleep(1) 452 args = [sys.executable, TEST_HELPER.script_path("app_profiler.py"), 453 "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"] 454 subproc = subprocess.Popen(args) 455 time.sleep(3) 456 457 subproc.send_signal(signal.SIGINT) 458 subproc.wait() 459 self.assertEqual(subproc.returncode, 0) 460 self.run_cmd(["report.py"]) 461 462 def test_app_profiler_stop_after_app_exit(self): 463 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 464 time.sleep(1) 465 subproc = subprocess.Popen( 466 [sys.executable, TEST_HELPER.script_path('app_profiler.py'), 467 '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root']) 468 time.sleep(3) 469 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 470 subproc.wait() 471 self.assertEqual(subproc.returncode, 0) 472 self.run_cmd(["report.py"]) 473 474 def test_app_profiler_with_ndk_path(self): 475 # Although we pass an invalid ndk path, it should be able to find tools in default ndk path. 476 self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name, 477 '--ndk_path', '.']) 478 479 def test_report(self): 480 self.common_test_report() 481 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 482 self.check_strings_in_file("report.txt", [ 483 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 484 "__start_thread"]) 485 486 def test_profile_with_process_id(self): 487 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 488 time.sleep(1) 489 pid = self.adb.check_run_and_return_output([ 490 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip() 491 self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid) 492 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 493 self.check_strings_in_file("report.txt", [ 494 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 495 "__start_thread"]) 496 497 def test_annotate(self): 498 self.common_test_annotate() 499 if not self.use_compiled_java_code: 500 # Currently annotating Java code is only supported when the Java code is compiled. 501 return 502 self.check_file_under_dir("annotated_files", "MainActivity.java") 503 summary_file = os.path.join("annotated_files", "summary") 504 self.check_annotation_summary(summary_file, [ 505 ("MainActivity.java", 80, 80), 506 ("run", 80, 0), 507 ("callFunction", 0, 0), 508 ("line 23", 80, 0)]) 509 510 def test_report_sample(self): 511 self.common_test_report_sample( 512 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 513 "__start_thread"]) 514 515 def test_pprof_proto_generator(self): 516 check_strings_with_lines = [] 517 if self.use_compiled_java_code: 518 check_strings_with_lines = [ 519 "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java", 520 "run"] 521 self.common_test_pprof_proto_generator( 522 check_strings_with_lines=check_strings_with_lines, 523 check_strings_without_lines=[ 524 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"]) 525 526 def test_inferno(self): 527 self.common_test_inferno() 528 self.run_app_profiler() 529 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 530 self.check_inferno_report_html( 531 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)]) 532 self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"]) 533 self.check_inferno_report_html( 534 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)], 535 "report2.html") 536 537 def test_inferno_in_another_dir(self): 538 test_dir = 'inferno_testdir' 539 os.mkdir(test_dir) 540 os.chdir(test_dir) 541 self.run_cmd(['app_profiler.py', '--app', self.package_name, 542 '-r', '-e task-clock:u -g --duration 3']) 543 self.check_exist(filename="perf.data") 544 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 545 546 def test_report_html(self): 547 self.common_test_report_html() 548 549 def test_run_simpleperf_without_usb_connection(self): 550 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 551 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p', 552 self.package_name, '--size_limit', '1M']) 553 self.adb.check_run(['kill-server']) 554 time.sleep(3) 555 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop']) 556 self.check_exist(filename="perf.data") 557 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 558 559 560class TestExamplePureJavaRoot(TestExampleBase): 561 @classmethod 562 def setUpClass(cls): 563 cls.prepare("SimpleperfExamplePureJava", 564 "com.example.simpleperf.simpleperfexamplepurejava", 565 ".MainActivity", 566 adb_root=True) 567 568 def test_app_profiler(self): 569 self.common_test_app_profiler() 570 571 572class TestExamplePureJavaTraceOffCpu(TestExampleBase): 573 @classmethod 574 def setUpClass(cls): 575 cls.prepare("SimpleperfExamplePureJava", 576 "com.example.simpleperf.simpleperfexamplepurejava", 577 ".SleepActivity") 578 579 def test_smoke(self): 580 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 581 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 582 self.check_strings_in_file("report.txt", [ 583 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run", 584 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction", 585 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction" 586 ]) 587 remove("annotated_files") 588 self.run_cmd(["annotate.py", "-s", self.example_path]) 589 self.check_exist(dirname="annotated_files") 590 if self.use_compiled_java_code: 591 self.check_file_under_dir("annotated_files", "SleepActivity.java") 592 summary_file = os.path.join("annotated_files", "summary") 593 self.check_annotation_summary(summary_file, [ 594 ("SleepActivity.java", 80, 20), 595 ("run", 80, 0), 596 ("RunFunction", 20, 20), 597 ("SleepFunction", 20, 0), 598 ("line 24", 1, 0), 599 ("line 32", 20, 0)]) 600 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 601 self.check_inferno_report_html( 602 [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80), 603 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction', 604 20), 605 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction', 606 20)]) 607 608 609class TestExampleWithNative(TestExampleBase): 610 @classmethod 611 def setUpClass(cls): 612 cls.prepare("SimpleperfExampleWithNative", 613 "com.example.simpleperf.simpleperfexamplewithnative", 614 ".MainActivity") 615 616 def test_app_profiler(self): 617 self.common_test_app_profiler() 618 619 def test_app_profiler_profile_from_launch(self): 620 self.run_app_profiler(start_activity=True, build_binary_cache=False) 621 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 622 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 623 624 def test_report(self): 625 self.common_test_report() 626 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 627 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 628 629 def test_annotate(self): 630 self.common_test_annotate() 631 self.check_file_under_dir("annotated_files", "native-lib.cpp") 632 summary_file = os.path.join("annotated_files", "summary") 633 self.check_annotation_summary(summary_file, [ 634 ("native-lib.cpp", 20, 0), 635 ("BusyLoopThread", 20, 0), 636 ("line 46", 20, 0)]) 637 638 def test_report_sample(self): 639 self.common_test_report_sample( 640 ["BusyLoopThread", 641 "__start_thread"]) 642 643 def test_pprof_proto_generator(self): 644 check_strings_with_lines = [ 645 "native-lib.cpp", 646 "BusyLoopThread", 647 # Check if dso name in perf.data is replaced by binary path in binary_cache. 648 'filename: binary_cache/data/app/com.example.simpleperf.simpleperfexamplewithnative-'] 649 self.common_test_pprof_proto_generator( 650 check_strings_with_lines, 651 check_strings_without_lines=["BusyLoopThread"]) 652 653 def test_inferno(self): 654 self.common_test_inferno() 655 self.run_app_profiler() 656 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 657 self.check_inferno_report_html([('BusyLoopThread', 20)]) 658 659 def test_report_html(self): 660 self.common_test_report_html() 661 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata', 662 '--add_disassembly', '--binary_filter', "libnative-lib.so"]) 663 664 665class TestExampleWithNativeRoot(TestExampleBase): 666 @classmethod 667 def setUpClass(cls): 668 cls.prepare("SimpleperfExampleWithNative", 669 "com.example.simpleperf.simpleperfexamplewithnative", 670 ".MainActivity", 671 adb_root=True) 672 673 def test_app_profiler(self): 674 self.common_test_app_profiler() 675 676 677class TestExampleWithNativeTraceOffCpu(TestExampleBase): 678 @classmethod 679 def setUpClass(cls): 680 cls.prepare("SimpleperfExampleWithNative", 681 "com.example.simpleperf.simpleperfexamplewithnative", 682 ".SleepActivity") 683 684 def test_smoke(self): 685 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 686 self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"]) 687 self.check_strings_in_file("report.txt", [ 688 "SleepThread(void*)", 689 "RunFunction()", 690 "SleepFunction(unsigned long long)"]) 691 remove("annotated_files") 692 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) 693 self.check_exist(dirname="annotated_files") 694 self.check_file_under_dir("annotated_files", "native-lib.cpp") 695 summary_file = os.path.join("annotated_files", "summary") 696 self.check_annotation_summary(summary_file, [ 697 ("native-lib.cpp", 80, 20), 698 ("SleepThread", 80, 0), 699 ("RunFunction", 20, 20), 700 ("SleepFunction", 20, 0), 701 ("line 73", 20, 0), 702 ("line 83", 20, 0)]) 703 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 704 self.check_inferno_report_html([('SleepThread', 80), 705 ('RunFunction', 20), 706 ('SleepFunction', 20)]) 707 708 709class TestExampleWithNativeJniCall(TestExampleBase): 710 @classmethod 711 def setUpClass(cls): 712 cls.prepare("SimpleperfExampleWithNative", 713 "com.example.simpleperf.simpleperfexamplewithnative", 714 ".MixActivity") 715 716 def test_smoke(self): 717 self.run_app_profiler() 718 self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) 719 self.check_strings_in_file("report.txt", [ 720 "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run", 721 "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) 722 remove("annotated_files") 723 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) 724 self.check_exist(dirname="annotated_files") 725 self.check_file_under_dir("annotated_files", "native-lib.cpp") 726 summary_file = os.path.join("annotated_files", "summary") 727 self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)]) 728 if self.use_compiled_java_code: 729 self.check_file_under_dir("annotated_files", "MixActivity.java") 730 self.check_annotation_summary(summary_file, [ 731 ("MixActivity.java", 80, 0), 732 ("run", 80, 0), 733 ("line 26", 20, 0), 734 ("native-lib.cpp", 5, 0), 735 ("line 40", 5, 0)]) 736 737 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 738 739 740class TestExampleWithNativeForce32Bit(TestExampleWithNative): 741 @classmethod 742 def setUpClass(cls): 743 cls.prepare("SimpleperfExampleWithNative", 744 "com.example.simpleperf.simpleperfexamplewithnative", 745 ".MainActivity", 746 abi=TEST_HELPER.get_32bit_abi()) 747 748 749class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot): 750 @classmethod 751 def setUpClass(cls): 752 cls.prepare("SimpleperfExampleWithNative", 753 "com.example.simpleperf.simpleperfexamplewithnative", 754 ".MainActivity", 755 abi=TEST_HELPER.get_32bit_abi(), 756 adb_root=False) 757 758 759class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu): 760 @classmethod 761 def setUpClass(cls): 762 cls.prepare("SimpleperfExampleWithNative", 763 "com.example.simpleperf.simpleperfexamplewithnative", 764 ".SleepActivity", 765 abi=TEST_HELPER.get_32bit_abi()) 766 767 768class TestExampleOfKotlin(TestExampleBase): 769 @classmethod 770 def setUpClass(cls): 771 cls.prepare("SimpleperfExampleOfKotlin", 772 "com.example.simpleperf.simpleperfexampleofkotlin", 773 ".MainActivity") 774 775 def test_app_profiler(self): 776 self.common_test_app_profiler() 777 778 def test_app_profiler_profile_from_launch(self): 779 self.run_app_profiler(start_activity=True, build_binary_cache=False) 780 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 781 self.check_strings_in_file("report.txt", [ 782 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 783 "run", "__start_thread"]) 784 785 def test_report(self): 786 self.common_test_report() 787 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 788 self.check_strings_in_file("report.txt", [ 789 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 790 "run", "__start_thread"]) 791 792 def test_annotate(self): 793 if not self.use_compiled_java_code: 794 return 795 self.common_test_annotate() 796 self.check_file_under_dir("annotated_files", "MainActivity.kt") 797 summary_file = os.path.join("annotated_files", "summary") 798 self.check_annotation_summary(summary_file, [ 799 ("MainActivity.kt", 80, 80), 800 ("run", 80, 0), 801 ("callFunction", 0, 0), 802 ("line 19", 80, 0), 803 ("line 25", 0, 0)]) 804 805 def test_report_sample(self): 806 self.common_test_report_sample([ 807 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 808 "run", "__start_thread"]) 809 810 def test_pprof_proto_generator(self): 811 check_strings_with_lines = [] 812 if self.use_compiled_java_code: 813 check_strings_with_lines = [ 814 "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt", 815 "run"] 816 self.common_test_pprof_proto_generator( 817 check_strings_with_lines=check_strings_with_lines, 818 check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." + 819 "MainActivity$createBusyThread$1.run"]) 820 821 def test_inferno(self): 822 self.common_test_inferno() 823 self.run_app_profiler() 824 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 825 self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' + 826 'MainActivity$createBusyThread$1.run', 80)]) 827 828 def test_report_html(self): 829 self.common_test_report_html() 830 831 832class TestExampleOfKotlinRoot(TestExampleBase): 833 @classmethod 834 def setUpClass(cls): 835 cls.prepare("SimpleperfExampleOfKotlin", 836 "com.example.simpleperf.simpleperfexampleofkotlin", 837 ".MainActivity", 838 adb_root=True) 839 840 def test_app_profiler(self): 841 self.common_test_app_profiler() 842 843 844class TestExampleOfKotlinTraceOffCpu(TestExampleBase): 845 @classmethod 846 def setUpClass(cls): 847 cls.prepare("SimpleperfExampleOfKotlin", 848 "com.example.simpleperf.simpleperfexampleofkotlin", 849 ".SleepActivity") 850 851 def test_smoke(self): 852 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 853 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 854 function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \ 855 "SleepActivity$createRunSleepThread$1." 856 self.check_strings_in_file("report.txt", [ 857 function_prefix + "run", 858 function_prefix + "RunFunction", 859 function_prefix + "SleepFunction" 860 ]) 861 if self.use_compiled_java_code: 862 remove("annotated_files") 863 self.run_cmd(["annotate.py", "-s", self.example_path]) 864 self.check_exist(dirname="annotated_files") 865 self.check_file_under_dir("annotated_files", "SleepActivity.kt") 866 summary_file = os.path.join("annotated_files", "summary") 867 self.check_annotation_summary(summary_file, [ 868 ("SleepActivity.kt", 80, 20), 869 ("run", 80, 0), 870 ("RunFunction", 20, 20), 871 ("SleepFunction", 20, 0), 872 ("line 24", 20, 0), 873 ("line 32", 20, 0)]) 874 875 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 876 self.check_inferno_report_html([ 877 (function_prefix + 'run', 80), 878 (function_prefix + 'RunFunction', 20), 879 (function_prefix + 'SleepFunction', 20)]) 880 881 882class TestNativeProfiling(TestBase): 883 def setUp(self): 884 super(TestNativeProfiling, self).setUp() 885 self.is_rooted_device = TEST_HELPER.adb.switch_to_root() 886 887 def test_profile_cmd(self): 888 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) 889 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 890 891 def test_profile_native_program(self): 892 if not self.is_rooted_device: 893 return 894 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) 895 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 896 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 897 self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"]) 898 899 def test_profile_pids(self): 900 if not self.is_rooted_device: 901 return 902 pid = int(TEST_HELPER.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) 903 self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) 904 self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) 905 self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) 906 self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1']) 907 self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1']) 908 909 def test_profile_system_wide(self): 910 if not self.is_rooted_device: 911 return 912 self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1']) 913 914 915class TestReportLib(TestBase): 916 def setUp(self): 917 super(TestReportLib, self).setUp() 918 self.report_lib = ReportLib() 919 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_symbols.data')) 920 921 def tearDown(self): 922 self.report_lib.Close() 923 super(TestReportLib, self).tearDown() 924 925 def test_build_id(self): 926 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 927 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 928 929 def test_symbol(self): 930 found_func2 = False 931 while self.report_lib.GetNextSample(): 932 symbol = self.report_lib.GetSymbolOfCurrentSample() 933 if symbol.symbol_name == 'func2(int, int)': 934 found_func2 = True 935 self.assertEqual(symbol.symbol_addr, 0x4004ed) 936 self.assertEqual(symbol.symbol_len, 0x14) 937 self.assertTrue(found_func2) 938 939 def test_sample(self): 940 found_sample = False 941 while self.report_lib.GetNextSample(): 942 sample = self.report_lib.GetCurrentSample() 943 if sample.ip == 0x4004ff and sample.time == 7637889424953: 944 found_sample = True 945 self.assertEqual(sample.pid, 15926) 946 self.assertEqual(sample.tid, 15926) 947 self.assertEqual(sample.thread_comm, 't2') 948 self.assertEqual(sample.cpu, 5) 949 self.assertEqual(sample.period, 694614) 950 event = self.report_lib.GetEventOfCurrentSample() 951 self.assertEqual(event.name, 'cpu-cycles') 952 callchain = self.report_lib.GetCallChainOfCurrentSample() 953 self.assertEqual(callchain.nr, 0) 954 self.assertTrue(found_sample) 955 956 def test_meta_info(self): 957 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) 958 meta_info = self.report_lib.MetaInfo() 959 self.assertTrue("simpleperf_version" in meta_info) 960 self.assertEqual(meta_info["system_wide_collection"], "false") 961 self.assertEqual(meta_info["trace_offcpu"], "true") 962 self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") 963 self.assertTrue("product_props" in meta_info) 964 965 def test_event_name_from_meta_info(self): 966 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) 967 event_names = set() 968 while self.report_lib.GetNextSample(): 969 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 970 self.assertTrue('sched:sched_switch' in event_names) 971 self.assertTrue('cpu-cycles' in event_names) 972 973 def test_record_cmd(self): 974 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) 975 self.assertEqual(self.report_lib.GetRecordCmd(), 976 "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " + 977 "./simpleperf_runtest_run_and_sleep64") 978 979 def test_offcpu(self): 980 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) 981 total_period = 0 982 sleep_function_period = 0 983 sleep_function_name = "SleepFunction(unsigned long long)" 984 while self.report_lib.GetNextSample(): 985 sample = self.report_lib.GetCurrentSample() 986 total_period += sample.period 987 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 988 sleep_function_period += sample.period 989 continue 990 callchain = self.report_lib.GetCallChainOfCurrentSample() 991 for i in range(callchain.nr): 992 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 993 sleep_function_period += sample.period 994 break 995 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles') 996 sleep_percentage = float(sleep_function_period) / total_period 997 self.assertGreater(sleep_percentage, 0.30) 998 999 def test_show_art_frames(self): 1000 def has_art_frame(report_lib): 1001 report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) 1002 result = False 1003 while report_lib.GetNextSample(): 1004 callchain = report_lib.GetCallChainOfCurrentSample() 1005 for i in range(callchain.nr): 1006 if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart': 1007 result = True 1008 break 1009 report_lib.Close() 1010 return result 1011 1012 report_lib = ReportLib() 1013 self.assertFalse(has_art_frame(report_lib)) 1014 report_lib = ReportLib() 1015 report_lib.ShowArtFrames(False) 1016 self.assertFalse(has_art_frame(report_lib)) 1017 report_lib = ReportLib() 1018 report_lib.ShowArtFrames(True) 1019 self.assertTrue(has_art_frame(report_lib)) 1020 1021 def test_merge_java_methods(self): 1022 def parse_dso_names(report_lib): 1023 dso_names = set() 1024 report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) 1025 while report_lib.GetNextSample(): 1026 dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) 1027 callchain = report_lib.GetCallChainOfCurrentSample() 1028 for i in range(callchain.nr): 1029 dso_names.add(callchain.entries[i].symbol.dso_name) 1030 report_lib.Close() 1031 has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names) 1032 has_jit_cache = '[JIT cache]' in dso_names 1033 return has_jit_symfiles, has_jit_cache 1034 1035 report_lib = ReportLib() 1036 self.assertEqual(parse_dso_names(report_lib), (False, True)) 1037 1038 report_lib = ReportLib() 1039 report_lib.MergeJavaMethods(True) 1040 self.assertEqual(parse_dso_names(report_lib), (False, True)) 1041 1042 report_lib = ReportLib() 1043 report_lib.MergeJavaMethods(False) 1044 self.assertEqual(parse_dso_names(report_lib), (True, False)) 1045 1046 def test_tracing_data(self): 1047 self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) 1048 has_tracing_data = False 1049 while self.report_lib.GetNextSample(): 1050 event = self.report_lib.GetEventOfCurrentSample() 1051 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 1052 if event.name == 'sched:sched_switch': 1053 self.assertIsNotNone(tracing_data) 1054 self.assertIn('prev_pid', tracing_data) 1055 self.assertIn('next_comm', tracing_data) 1056 if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4': 1057 has_tracing_data = True 1058 else: 1059 self.assertIsNone(tracing_data) 1060 self.assertTrue(has_tracing_data) 1061 1062 1063class TestRunSimpleperfOnDevice(TestBase): 1064 def test_smoke(self): 1065 self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) 1066 1067 1068class TestTools(TestBase): 1069 def test_addr2nearestline(self): 1070 self.run_addr2nearestline_test(True) 1071 self.run_addr2nearestline_test(False) 1072 1073 def run_addr2nearestline_test(self, with_function_name): 1074 binary_cache_path = TEST_HELPER.testdata_dir 1075 test_map = { 1076 '/simpleperf_runtest_two_functions_arm64': [ 1077 { 1078 'func_addr': 0x668, 1079 'addr': 0x668, 1080 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20', 1081 'function': 'main', 1082 }, 1083 { 1084 'func_addr': 0x668, 1085 'addr': 0x6a4, 1086 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 1087 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1088 'function': """Function1() 1089 main""", 1090 }, 1091 ], 1092 '/simpleperf_runtest_two_functions_arm': [ 1093 { 1094 'func_addr': 0x784, 1095 'addr': 0x7b0, 1096 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 1097 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1098 'function': """Function2() 1099 main""", 1100 }, 1101 { 1102 'func_addr': 0x784, 1103 'addr': 0x7d0, 1104 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 1105 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1106 'function': """Function2() 1107 main""", 1108 } 1109 ], 1110 '/simpleperf_runtest_two_functions_x86_64': [ 1111 { 1112 'func_addr': 0x840, 1113 'addr': 0x840, 1114 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', 1115 'function': 'Function1()', 1116 }, 1117 { 1118 'func_addr': 0x920, 1119 'addr': 0x94a, 1120 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 1121 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1122 'function': """Function1() 1123 main""", 1124 } 1125 ], 1126 '/simpleperf_runtest_two_functions_x86': [ 1127 { 1128 'func_addr': 0x6d0, 1129 'addr': 0x6da, 1130 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', 1131 'function': 'Function2()', 1132 }, 1133 { 1134 'func_addr': 0x710, 1135 'addr': 0x749, 1136 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 1137 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1138 'function': """Function1() 1139 main""", 1140 } 1141 ], 1142 } 1143 addr2line = Addr2Nearestline(None, binary_cache_path, with_function_name) 1144 for dso_path in test_map: 1145 test_addrs = test_map[dso_path] 1146 for test_addr in test_addrs: 1147 addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr']) 1148 addr2line.convert_addrs_to_lines() 1149 for dso_path in test_map: 1150 dso = addr2line.get_dso(dso_path) 1151 self.assertTrue(dso is not None) 1152 test_addrs = test_map[dso_path] 1153 for test_addr in test_addrs: 1154 expected_files = [] 1155 expected_lines = [] 1156 expected_functions = [] 1157 for line in test_addr['source'].split('\n'): 1158 items = line.split(':') 1159 expected_files.append(items[0].strip()) 1160 expected_lines.append(int(items[1])) 1161 for line in test_addr['function'].split('\n'): 1162 expected_functions.append(line.strip()) 1163 self.assertEqual(len(expected_files), len(expected_functions)) 1164 1165 actual_source = addr2line.get_addr_source(dso, test_addr['addr']) 1166 self.assertTrue(actual_source is not None) 1167 self.assertEqual(len(actual_source), len(expected_files)) 1168 for i, source in enumerate(actual_source): 1169 self.assertEqual(len(source), 3 if with_function_name else 2) 1170 self.assertEqual(source[0], expected_files[i]) 1171 self.assertEqual(source[1], expected_lines[i]) 1172 if with_function_name: 1173 self.assertEqual(source[2], expected_functions[i]) 1174 1175 def test_objdump(self): 1176 binary_cache_path = TEST_HELPER.testdata_dir 1177 test_map = { 1178 '/simpleperf_runtest_two_functions_arm64': { 1179 'start_addr': 0x668, 1180 'len': 116, 1181 'expected_items': [ 1182 ('main():', 0), 1183 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1184 (' 694: add x20, x20, #0x6de', 0x694), 1185 ], 1186 }, 1187 '/simpleperf_runtest_two_functions_arm': { 1188 'start_addr': 0x784, 1189 'len': 80, 1190 'expected_items': [ 1191 ('main():', 0), 1192 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1193 (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae), 1194 ], 1195 }, 1196 '/simpleperf_runtest_two_functions_x86_64': { 1197 'start_addr': 0x920, 1198 'len': 201, 1199 'expected_items': [ 1200 ('main():', 0), 1201 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1202 (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e), 1203 ], 1204 }, 1205 '/simpleperf_runtest_two_functions_x86': { 1206 'start_addr': 0x710, 1207 'len': 98, 1208 'expected_items': [ 1209 ('main():', 0), 1210 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1211 (' 748: cmp $0x5f5e100,%ebp', 0x748), 1212 ], 1213 }, 1214 } 1215 objdump = Objdump(None, binary_cache_path) 1216 for dso_path in test_map: 1217 dso = test_map[dso_path] 1218 dso_info = objdump.get_dso_info(dso_path) 1219 self.assertIsNotNone(dso_info) 1220 disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len']) 1221 self.assertTrue(disassemble_code) 1222 for item in dso['expected_items']: 1223 self.assertTrue(item in disassemble_code) 1224 1225 def test_readelf(self): 1226 test_map = { 1227 'simpleperf_runtest_two_functions_arm64': { 1228 'arch': 'arm64', 1229 'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000', 1230 'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym', 1231 '.dynstr', '.gnu.hash', '.gnu.version', '.gnu.version_r', '.rela.dyn', 1232 '.rela.plt', '.plt', '.text', '.rodata', '.eh_frame', '.eh_frame_hdr', 1233 '.preinit_array', '.init_array', '.fini_array', '.dynamic', '.got', 1234 '.got.plt', '.data', '.bss', '.comment', '.debug_str', '.debug_loc', 1235 '.debug_abbrev', '.debug_info', '.debug_ranges', '.debug_macinfo', 1236 '.debug_pubnames', '.debug_pubtypes', '.debug_line', 1237 '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'], 1238 }, 1239 'simpleperf_runtest_two_functions_arm': { 1240 'arch': 'arm', 1241 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000', 1242 }, 1243 'simpleperf_runtest_two_functions_x86_64': { 1244 'arch': 'x86_64', 1245 }, 1246 'simpleperf_runtest_two_functions_x86': { 1247 'arch': 'x86', 1248 } 1249 } 1250 readelf = ReadElf(None) 1251 for dso_path in test_map: 1252 dso_info = test_map[dso_path] 1253 path = os.path.join(TEST_HELPER.testdata_dir, dso_path) 1254 self.assertEqual(dso_info['arch'], readelf.get_arch(path)) 1255 if 'build_id' in dso_info: 1256 self.assertEqual(dso_info['build_id'], readelf.get_build_id(path)) 1257 if 'sections' in dso_info: 1258 self.assertEqual(dso_info['sections'], readelf.get_sections(path)) 1259 self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown') 1260 self.assertEqual(readelf.get_build_id('not_exist_file'), '') 1261 self.assertEqual(readelf.get_sections('not_exist_file'), []) 1262 1263 def test_source_file_searcher(self): 1264 searcher = SourceFileSearcher( 1265 [TEST_HELPER.testdata_path('SimpleperfExampleWithNative'), 1266 TEST_HELPER.testdata_path('SimpleperfExampleOfKotlin')]) 1267 def format_path(path): 1268 return os.path.join(TEST_HELPER.testdata_dir, path.replace('/', os.sep)) 1269 # Find a C++ file with pure file name. 1270 self.assertEqual( 1271 format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1272 searcher.get_real_path('native-lib.cpp')) 1273 # Find a C++ file with an absolute file path. 1274 self.assertEqual( 1275 format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1276 searcher.get_real_path('/data/native-lib.cpp')) 1277 # Find a Java file. 1278 self.assertEqual( 1279 format_path('SimpleperfExampleWithNative/app/src/main/java/com/example/' + 1280 'simpleperf/simpleperfexamplewithnative/MainActivity.java'), 1281 searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java')) 1282 # Find a Kotlin file. 1283 self.assertEqual( 1284 format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + 1285 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'), 1286 searcher.get_real_path('MainActivity.kt')) 1287 1288 def test_is_elf_file(self): 1289 self.assertTrue(is_elf_file(TEST_HELPER.testdata_path( 1290 'simpleperf_runtest_two_functions_arm'))) 1291 with open('not_elf', 'wb') as fh: 1292 fh.write(b'\x90123') 1293 try: 1294 self.assertFalse(is_elf_file('not_elf')) 1295 finally: 1296 remove('not_elf') 1297 1298 1299class TestNativeLibDownloader(TestBase): 1300 def setUp(self): 1301 super(TestNativeLibDownloader, self).setUp() 1302 self.adb = TEST_HELPER.adb 1303 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1304 1305 def tearDown(self): 1306 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1307 super(TestNativeLibDownloader, self).tearDown() 1308 1309 def list_lib_on_device(self, path): 1310 result, output = self.adb.run_and_return_output( 1311 ['shell', 'ls', '-llc', path], log_output=False) 1312 return output if result else '' 1313 1314 def test_smoke(self): 1315 # Sync all native libs on device. 1316 downloader = NativeLibDownloader(None, 'arm64', self.adb) 1317 downloader.collect_native_libs_on_host(TEST_HELPER.testdata_path( 1318 'SimpleperfExampleWithNative/app/build/intermediates/cmake/profiling')) 1319 self.assertEqual(len(downloader.host_build_id_map), 2) 1320 for entry in downloader.host_build_id_map.values(): 1321 self.assertEqual(entry.score, 3) 1322 downloader.collect_native_libs_on_device() 1323 self.assertEqual(len(downloader.device_build_id_map), 0) 1324 1325 lib_list = list(downloader.host_build_id_map.items()) 1326 for sync_count in [0, 1, 2]: 1327 build_id_map = {} 1328 for i in range(sync_count): 1329 build_id_map[lib_list[i][0]] = lib_list[i][1] 1330 downloader.host_build_id_map = build_id_map 1331 downloader.sync_native_libs_on_device() 1332 downloader.collect_native_libs_on_device() 1333 self.assertEqual(len(downloader.device_build_id_map), sync_count) 1334 for i, item in enumerate(lib_list): 1335 build_id = item[0] 1336 name = item[1].name 1337 if i < sync_count: 1338 self.assertTrue(build_id in downloader.device_build_id_map) 1339 self.assertEqual(name, downloader.device_build_id_map[build_id]) 1340 self.assertTrue(self.list_lib_on_device(downloader.dir_on_device + name)) 1341 else: 1342 self.assertTrue(build_id not in downloader.device_build_id_map) 1343 self.assertFalse(self.list_lib_on_device(downloader.dir_on_device + name)) 1344 if sync_count == 1: 1345 self.adb.run(['pull', '/data/local/tmp/native_libs/build_id_list', 1346 'build_id_list']) 1347 with open('build_id_list', 'rb') as fh: 1348 self.assertEqual(bytes_to_str(fh.read()), 1349 '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name)) 1350 remove('build_id_list') 1351 1352 def test_handle_wrong_build_id_list(self): 1353 with open('build_id_list', 'wb') as fh: 1354 fh.write(str_to_bytes('fake_build_id=binary_not_exist\n')) 1355 self.adb.check_run(['shell', 'mkdir', '-p', '/data/local/tmp/native_libs']) 1356 self.adb.check_run(['push', 'build_id_list', '/data/local/tmp/native_libs']) 1357 remove('build_id_list') 1358 downloader = NativeLibDownloader(None, 'arm64', self.adb) 1359 downloader.collect_native_libs_on_device() 1360 self.assertEqual(len(downloader.device_build_id_map), 0) 1361 1362 def test_download_file_without_build_id(self): 1363 downloader = NativeLibDownloader(None, 'x86_64', self.adb) 1364 name = 'elf.so' 1365 shutil.copyfile(TEST_HELPER.testdata_path('data/symfs_without_build_id/elf'), name) 1366 downloader.collect_native_libs_on_host('.') 1367 downloader.collect_native_libs_on_device() 1368 self.assertIn(name, downloader.no_build_id_file_map) 1369 # Check if file wihtout build id can be downloaded. 1370 downloader.sync_native_libs_on_device() 1371 target_file = downloader.dir_on_device + name 1372 target_file_stat = self.list_lib_on_device(target_file) 1373 self.assertTrue(target_file_stat) 1374 1375 # No need to re-download if file size doesn't change. 1376 downloader.sync_native_libs_on_device() 1377 self.assertEqual(target_file_stat, self.list_lib_on_device(target_file)) 1378 1379 # Need to re-download if file size changes. 1380 self.adb.check_run(['shell', 'truncate', '-s', '0', target_file]) 1381 target_file_stat = self.list_lib_on_device(target_file) 1382 downloader.sync_native_libs_on_device() 1383 self.assertNotEqual(target_file_stat, self.list_lib_on_device(target_file)) 1384 1385 1386class TestReportHtml(TestBase): 1387 def test_long_callchain(self): 1388 self.run_cmd(['report_html.py', '-i', 1389 TEST_HELPER.testdata_path('perf_with_long_callchain.data')]) 1390 1391 def test_aggregated_by_thread_name(self): 1392 # Calculate event_count for each thread name before aggregation. 1393 event_count_for_thread_name = collections.defaultdict(lambda: 0) 1394 # use "--min_func_percent 0" to avoid cutting any thread. 1395 self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', 1396 TEST_HELPER.testdata_path('aggregatable_perf1.data'), 1397 TEST_HELPER.testdata_path('aggregatable_perf2.data')]) 1398 record_data = self._load_record_data_in_html('report.html') 1399 event = record_data['sampleInfo'][0] 1400 for process in event['processes']: 1401 for thread in process['threads']: 1402 thread_name = record_data['threadNames'][str(thread['tid'])] 1403 event_count_for_thread_name[thread_name] += thread['eventCount'] 1404 1405 # Check event count for each thread after aggregation. 1406 self.run_cmd(['report_html.py', '--aggregate-by-thread-name', 1407 '--min_func_percent', '0', '-i', 1408 TEST_HELPER.testdata_path('aggregatable_perf1.data'), 1409 TEST_HELPER.testdata_path('aggregatable_perf2.data')]) 1410 record_data = self._load_record_data_in_html('report.html') 1411 event = record_data['sampleInfo'][0] 1412 hit_count = 0 1413 for process in event['processes']: 1414 for thread in process['threads']: 1415 thread_name = record_data['threadNames'][str(thread['tid'])] 1416 self.assertEqual(thread['eventCount'], 1417 event_count_for_thread_name[thread_name]) 1418 hit_count += 1 1419 self.assertEqual(hit_count, len(event_count_for_thread_name)) 1420 1421 def test_no_empty_process(self): 1422 """ Test not showing a process having no threads. """ 1423 perf_data = TEST_HELPER.testdata_path('two_process_perf.data') 1424 self.run_cmd(['report_html.py', '-i', perf_data]) 1425 record_data = self._load_record_data_in_html('report.html') 1426 processes = record_data['sampleInfo'][0]['processes'] 1427 self.assertEqual(len(processes), 2) 1428 1429 # One process is removed because all its threads are removed for not 1430 # reaching the min_func_percent limit. 1431 self.run_cmd(['report_html.py', '-i', perf_data, '--min_func_percent', '20']) 1432 record_data = self._load_record_data_in_html('report.html') 1433 processes = record_data['sampleInfo'][0]['processes'] 1434 self.assertEqual(len(processes), 1) 1435 1436 def _load_record_data_in_html(self, html_file): 1437 with open(html_file, 'r') as fh: 1438 data = fh.read() 1439 start_str = 'type="application/json"' 1440 end_str = '</script>' 1441 start_pos = data.find(start_str) 1442 self.assertNotEqual(start_pos, -1) 1443 start_pos = data.find('>', start_pos) 1444 self.assertNotEqual(start_pos, -1) 1445 start_pos += 1 1446 end_pos = data.find(end_str, start_pos) 1447 self.assertNotEqual(end_pos, -1) 1448 json_data = data[start_pos:end_pos] 1449 return json.loads(json_data) 1450 1451 1452class TestBinaryCacheBuilder(TestBase): 1453 def test_copy_binaries_from_symfs_dirs(self): 1454 readelf = ReadElf(None) 1455 strip = find_tool_path('strip', arch='arm') 1456 self.assertIsNotNone(strip) 1457 symfs_dir = os.path.join(self.test_dir, 'symfs_dir') 1458 remove(symfs_dir) 1459 os.mkdir(symfs_dir) 1460 filename = 'simpleperf_runtest_two_functions_arm' 1461 origin_file = TEST_HELPER.testdata_path(filename) 1462 source_file = os.path.join(symfs_dir, filename) 1463 target_file = os.path.join('binary_cache', filename) 1464 expected_build_id = readelf.get_build_id(origin_file) 1465 binary_cache_builder = BinaryCacheBuilder(None, False) 1466 binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id 1467 1468 # Copy binary if target file doesn't exist. 1469 remove(target_file) 1470 self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file]) 1471 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1472 self.assertTrue(filecmp.cmp(target_file, source_file)) 1473 1474 # Copy binary if target file doesn't have .symtab and source file has .symtab. 1475 self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file]) 1476 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1477 self.assertTrue(filecmp.cmp(target_file, source_file)) 1478 1479 # Copy binary if target file doesn't have .debug_line and source_files has .debug_line. 1480 shutil.copy(origin_file, source_file) 1481 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1482 self.assertTrue(filecmp.cmp(target_file, source_file)) 1483 1484 def test_copy_elf_without_build_id_from_symfs_dir(self): 1485 binary_cache_builder = BinaryCacheBuilder(None, False) 1486 binary_cache_builder.binaries['elf'] = '' 1487 symfs_dir = TEST_HELPER.testdata_path('data/symfs_without_build_id') 1488 source_file = os.path.join(symfs_dir, 'elf') 1489 target_file = os.path.join('binary_cache', 'elf') 1490 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1491 self.assertTrue(filecmp.cmp(target_file, source_file)) 1492 binary_cache_builder.pull_binaries_from_device() 1493 self.assertTrue(filecmp.cmp(target_file, source_file)) 1494 1495 1496class TestApiProfiler(TestBase): 1497 def run_api_test(self, package_name, apk_name, expected_reports, min_android_version): 1498 adb = TEST_HELPER.adb 1499 if TEST_HELPER.android_version < ord(min_android_version) - ord('L') + 5: 1500 log_info('skip this test on Android < %s.' % min_android_version) 1501 return 1502 # step 1: Prepare profiling. 1503 self.run_cmd(['api_profiler.py', 'prepare']) 1504 # step 2: Install and run the app. 1505 apk_path = TEST_HELPER.testdata_path(apk_name) 1506 adb.run(['uninstall', package_name]) 1507 adb.check_run(['install', '-t', apk_path]) 1508 # Without sleep, the activity may be killed by post install intent ACTION_PACKAGE_CHANGED. 1509 time.sleep(3) 1510 adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity']) 1511 # step 3: Wait until the app exits. 1512 time.sleep(4) 1513 while True: 1514 result = adb.run(['shell', 'pidof', package_name]) 1515 if not result: 1516 break 1517 time.sleep(1) 1518 # step 4: Collect recording data. 1519 remove('simpleperf_data') 1520 self.run_cmd(['api_profiler.py', 'collect', '-p', package_name, '-o', 'simpleperf_data']) 1521 # step 5: Check recording data. 1522 names = os.listdir('simpleperf_data') 1523 self.assertGreater(len(names), 0) 1524 for name in names: 1525 path = os.path.join('simpleperf_data', name) 1526 remove('report.txt') 1527 self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path]) 1528 self.check_strings_in_file('report.txt', expected_reports) 1529 # step 6: Clean up. 1530 adb.check_run(['uninstall', package_name]) 1531 1532 def run_cpp_api_test(self, apk_name, min_android_version): 1533 self.run_api_test('simpleperf.demo.cpp_api', apk_name, ['BusyThreadFunc'], 1534 min_android_version) 1535 1536 def test_cpp_api_on_a_debuggable_app_targeting_prev_q(self): 1537 # The source code of the apk is in simpleperf/demo/CppApi (with a small change to exit 1538 # after recording). 1539 self.run_cpp_api_test('cpp_api-debug_prev_Q.apk', 'N') 1540 1541 def test_cpp_api_on_a_debuggable_app_targeting_q(self): 1542 self.run_cpp_api_test('cpp_api-debug_Q.apk', 'N') 1543 1544 def test_cpp_api_on_a_profileable_app_targeting_prev_q(self): 1545 # a release apk with <profileable android:shell="true" /> 1546 self.run_cpp_api_test('cpp_api-profile_prev_Q.apk', 'Q') 1547 1548 def test_cpp_api_on_a_profileable_app_targeting_q(self): 1549 self.run_cpp_api_test('cpp_api-profile_Q.apk', 'Q') 1550 1551 def run_java_api_test(self, apk_name, min_android_version): 1552 self.run_api_test('simpleperf.demo.java_api', apk_name, 1553 ['simpleperf.demo.java_api.MainActivity', 'java.lang.Thread.run'], 1554 min_android_version) 1555 1556 def test_java_api_on_a_debuggable_app_targeting_prev_q(self): 1557 # The source code of the apk is in simpleperf/demo/JavaApi (with a small change to exit 1558 # after recording). 1559 self.run_java_api_test('java_api-debug_prev_Q.apk', 'P') 1560 1561 def test_java_api_on_a_debuggable_app_targeting_q(self): 1562 self.run_java_api_test('java_api-debug_Q.apk', 'P') 1563 1564 def test_java_api_on_a_profileable_app_targeting_prev_q(self): 1565 # a release apk with <profileable android:shell="true" /> 1566 self.run_java_api_test('java_api-profile_prev_Q.apk', 'Q') 1567 1568 def test_java_api_on_a_profileable_app_targeting_q(self): 1569 self.run_java_api_test('java_api-profile_Q.apk', 'Q') 1570 1571 1572class TestPprofProtoGenerator(TestBase): 1573 def setUp(self): 1574 super(TestPprofProtoGenerator, self).setUp() 1575 if not HAS_GOOGLE_PROTOBUF: 1576 raise unittest.SkipTest( 1577 'Skip test for pprof_proto_generator because google.protobuf is missing') 1578 1579 def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'): 1580 testdata_path = TEST_HELPER.testdata_path(testdata_file) 1581 options = options or [] 1582 self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options) 1583 return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) 1584 1585 def test_show_art_frames(self): 1586 art_frame_str = 'art::interpreter::DoCall' 1587 # By default, don't show art frames. 1588 self.assertNotIn(art_frame_str, self.run_generator()) 1589 # Use --show_art_frames to show art frames. 1590 self.assertIn(art_frame_str, self.run_generator(['--show_art_frames'])) 1591 1592 def test_pid_filter(self): 1593 key = 'PlayScene::DoFrame()' # function in process 10419 1594 self.assertIn(key, self.run_generator()) 1595 self.assertIn(key, self.run_generator(['--pid', '10419'])) 1596 self.assertIn(key, self.run_generator(['--pid', '10419', '10416'])) 1597 self.assertNotIn(key, self.run_generator(['--pid', '10416'])) 1598 1599 def test_tid_filter(self): 1600 key1 = 'art::ProfileSaver::Run()' # function in thread 10459 1601 key2 = 'PlayScene::DoFrame()' # function in thread 10463 1602 for options in ([], ['--tid', '10459', '10463']): 1603 output = self.run_generator(options) 1604 self.assertIn(key1, output) 1605 self.assertIn(key2, output) 1606 output = self.run_generator(['--tid', '10459']) 1607 self.assertIn(key1, output) 1608 self.assertNotIn(key2, output) 1609 output = self.run_generator(['--tid', '10463']) 1610 self.assertNotIn(key1, output) 1611 self.assertIn(key2, output) 1612 1613 def test_comm_filter(self): 1614 key1 = 'art::ProfileSaver::Run()' # function in thread 'Profile Saver' 1615 key2 = 'PlayScene::DoFrame()' # function in thread 'e.sample.tunnel' 1616 for options in ([], ['--comm', 'Profile Saver', 'e.sample.tunnel']): 1617 output = self.run_generator(options) 1618 self.assertIn(key1, output) 1619 self.assertIn(key2, output) 1620 output = self.run_generator(['--comm', 'Profile Saver']) 1621 self.assertIn(key1, output) 1622 self.assertNotIn(key2, output) 1623 output = self.run_generator(['--comm', 'e.sample.tunnel']) 1624 self.assertNotIn(key1, output) 1625 self.assertIn(key2, output) 1626 1627 def test_build_id(self): 1628 """ Test the build ids generated are not padded with zeros. """ 1629 self.assertIn('build_id: e3e938cc9e40de2cfe1a5ac7595897de(', self.run_generator()) 1630 1631 def test_location_address(self): 1632 """ Test if the address of a location is within the memory range of the corresponding 1633 mapping. 1634 """ 1635 self.run_cmd(['pprof_proto_generator.py', '-i', 1636 TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')]) 1637 1638 profile = load_pprof_profile('pprof.profile') 1639 # pylint: disable=no-member 1640 for location in profile.location: 1641 mapping = profile.mapping[location.mapping_id - 1] 1642 self.assertLessEqual(mapping.memory_start, location.address) 1643 self.assertGreaterEqual(mapping.memory_limit, location.address) 1644 1645 1646class TestRecordingRealApps(TestBase): 1647 def setUp(self): 1648 super(TestRecordingRealApps, self).setUp() 1649 self.adb = TEST_HELPER.adb 1650 self.installed_packages = [] 1651 1652 def tearDown(self): 1653 for package in self.installed_packages: 1654 self.adb.run(['shell', 'pm', 'uninstall', package]) 1655 super(TestRecordingRealApps, self).tearDown() 1656 1657 def install_apk(self, apk_path, package_name): 1658 self.adb.run(['install', '-t', apk_path]) 1659 self.installed_packages.append(package_name) 1660 1661 def start_app(self, start_cmd): 1662 subprocess.Popen(self.adb.adb_path + ' ' + start_cmd, shell=True, 1663 stdout=TEST_LOGGER.log_fh, stderr=TEST_LOGGER.log_fh) 1664 1665 def record_data(self, package_name, record_arg): 1666 self.run_cmd(['app_profiler.py', '--app', package_name, '-r', record_arg]) 1667 1668 def check_symbol_in_record_file(self, symbol_name): 1669 self.run_cmd(['report.py', '--children', '-o', 'report.txt']) 1670 self.check_strings_in_file('report.txt', [symbol_name]) 1671 1672 def test_recording_displaybitmaps(self): 1673 self.install_apk(TEST_HELPER.testdata_path('DisplayBitmaps.apk'), 1674 'com.example.android.displayingbitmaps') 1675 self.install_apk(TEST_HELPER.testdata_path('DisplayBitmapsTest.apk'), 1676 'com.example.android.displayingbitmaps.test') 1677 self.start_app('shell am instrument -w -r -e debug false -e class ' + 1678 'com.example.android.displayingbitmaps.tests.GridViewTest ' + 1679 'com.example.android.displayingbitmaps.test/' + 1680 'androidx.test.runner.AndroidJUnitRunner') 1681 self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10') 1682 if TEST_HELPER.android_version >= 9: 1683 self.check_symbol_in_record_file('androidx.test.espresso') 1684 1685 def test_recording_endless_tunnel(self): 1686 self.install_apk(TEST_HELPER.testdata_path( 1687 'EndlessTunnel.apk'), 'com.google.sample.tunnel') 1688 self.start_app('shell am start -n com.google.sample.tunnel/android.app.NativeActivity -a ' + 1689 'android.intent.action.MAIN -c android.intent.category.LAUNCHER') 1690 self.record_data('com.google.sample.tunnel', '-e cpu-clock -g --duration 10') 1691 self.check_symbol_in_record_file('PlayScene::DoFrame') 1692 1693 1694def get_all_tests(): 1695 tests = [] 1696 for name, value in globals().items(): 1697 if isinstance(value, type) and issubclass(value, unittest.TestCase): 1698 for member_name, member in inspect.getmembers(value): 1699 if isinstance(member, (types.MethodType, types.FunctionType)): 1700 if member_name.startswith('test'): 1701 tests.append(name + '.' + member_name) 1702 return sorted(tests) 1703 1704 1705def run_tests(tests, repeats): 1706 TEST_HELPER.build_testdata() 1707 argv = [sys.argv[0]] + tests 1708 test_runner = unittest.TextTestRunner(stream=TEST_LOGGER, verbosity=2) 1709 success = True 1710 for repeat in range(1, repeats + 1): 1711 print('Run tests with python %d for %dth time\n%s' % ( 1712 TEST_HELPER.python_version, repeat, '\n'.join(tests)), file=TEST_LOGGER) 1713 TEST_HELPER.repeat_count = repeat 1714 test_program = unittest.main(argv=argv, testRunner=test_runner, exit=False) 1715 if not test_program.result.wasSuccessful(): 1716 success = False 1717 return success 1718 1719 1720def main(): 1721 parser = argparse.ArgumentParser(description='Test simpleperf scripts') 1722 parser.add_argument('--list-tests', action='store_true', help='List all tests.') 1723 parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.') 1724 parser.add_argument('--python-version', choices=['2', '3', 'both'], default='both', help=""" 1725 Run tests on which python versions.""") 1726 parser.add_argument('--repeat', type=int, nargs=1, default=[1], help='run test multiple times') 1727 parser.add_argument('--no-test-result', dest='report_test_result', 1728 action='store_false', help="Don't report test result.") 1729 parser.add_argument('--browser', action='store_true', help='pop report html file in browser.') 1730 parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.') 1731 args = parser.parse_args() 1732 tests = get_all_tests() 1733 if args.list_tests: 1734 print('\n'.join(tests)) 1735 return True 1736 if args.test_from: 1737 start_pos = 0 1738 while start_pos < len(tests) and tests[start_pos] != args.test_from[0]: 1739 start_pos += 1 1740 if start_pos == len(tests): 1741 log_exit("Can't find test %s" % args.test_from[0]) 1742 tests = tests[start_pos:] 1743 if args.pattern: 1744 patterns = [re.compile(fnmatch.translate(x)) for x in args.pattern] 1745 tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)] 1746 if not tests: 1747 log_exit('No tests are matched.') 1748 1749 if TEST_HELPER.android_version < 7: 1750 print("Skip tests on Android version < N.", file=TEST_LOGGER) 1751 return False 1752 1753 if args.python_version == 'both': 1754 python_versions = [2, 3] 1755 else: 1756 python_versions = [int(args.python_version)] 1757 1758 for python_version in python_versions: 1759 remove(TEST_HELPER.get_test_base_dir(python_version)) 1760 1761 if not args.browser: 1762 TEST_HELPER.browser_option = ['--no_browser'] 1763 1764 test_results = [] 1765 for version in python_versions: 1766 os.chdir(TEST_HELPER.cur_dir) 1767 if version == TEST_HELPER.python_version: 1768 test_result = run_tests(tests, args.repeat[0]) 1769 else: 1770 argv = ['python3' if version == 3 else 'python'] 1771 argv.append(TEST_HELPER.script_path('test.py')) 1772 argv += sys.argv[1:] 1773 argv += ['--python-version', str(version), '--no-test-result'] 1774 test_result = subprocess.call(argv) == 0 1775 test_results.append(test_result) 1776 1777 if args.report_test_result: 1778 for version, test_result in zip(python_versions, test_results): 1779 if not test_result: 1780 print('Tests with python %d failed, see %s for details.' % 1781 (version, TEST_LOGGER.get_log_file(version)), file=TEST_LOGGER) 1782 1783 return test_results.count(False) == 0 1784 1785 1786if __name__ == '__main__': 1787 sys.exit(0 if main() else 1) 1788