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