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 filecmp 40import fnmatch 41import inspect 42import os 43import re 44import shutil 45import signal 46import subprocess 47import sys 48import time 49import types 50import unittest 51 52from app_profiler import NativeLibDownloader 53from binary_cache_builder import BinaryCacheBuilder 54from simpleperf_report_lib import ReportLib 55from utils import log_exit, log_info, log_fatal 56from utils import AdbHelper, Addr2Nearestline, bytes_to_str, find_tool_path, get_script_dir 57from utils import is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher 58 59try: 60 # pylint: disable=unused-import 61 import google.protobuf 62 HAS_GOOGLE_PROTOBUF = True 63except ImportError: 64 HAS_GOOGLE_PROTOBUF = False 65 66INFERNO_SCRIPT = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh") 67 68def get_device_features(): 69 adb = AdbHelper() 70 adb.check_run_and_return_output(['push', 71 'bin/android/%s/simpleperf' % adb.get_device_arch(), 72 '/data/local/tmp']) 73 adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) 74 return adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list', 75 '--show-features']) 76 77def is_trace_offcpu_supported(): 78 if not hasattr(is_trace_offcpu_supported, 'value'): 79 is_trace_offcpu_supported.value = 'trace-offcpu' in get_device_features() 80 return is_trace_offcpu_supported.value 81 82 83def build_testdata(): 84 """ Collect testdata from ../testdata and ../demo. """ 85 from_testdata_path = os.path.join('..', 'testdata') 86 from_demo_path = os.path.join('..', 'demo') 87 from_script_testdata_path = 'script_testdata' 88 if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or 89 not from_script_testdata_path): 90 return 91 copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data', 92 'perf_with_tracepoint_event.data', 'perf_with_interpreter_frames.data'] 93 copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative', 94 'SimpleperfExampleOfKotlin'] 95 96 testdata_path = "testdata" 97 remove(testdata_path) 98 os.mkdir(testdata_path) 99 for testdata in copy_testdata_list: 100 shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path) 101 for demo in copy_demo_list: 102 shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo)) 103 for f in os.listdir(from_script_testdata_path): 104 shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path) 105 106class TestBase(unittest.TestCase): 107 def run_cmd(self, args, return_output=False): 108 if args[0].endswith('.py'): 109 args = [sys.executable] + args 110 use_shell = args[0].endswith('.bat') 111 try: 112 if not return_output: 113 returncode = subprocess.call(args, shell=use_shell) 114 else: 115 subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell) 116 (output_data, _) = subproc.communicate() 117 output_data = bytes_to_str(output_data) 118 returncode = subproc.returncode 119 except OSError: 120 returncode = None 121 self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args) 122 if return_output: 123 return output_data 124 return '' 125 126 127class TestExampleBase(TestBase): 128 @classmethod 129 def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): 130 cls.adb = AdbHelper(enable_switch_to_root=adb_root) 131 cls.example_path = os.path.join("testdata", example_name) 132 if not os.path.isdir(cls.example_path): 133 log_fatal("can't find " + cls.example_path) 134 for root, _, files in os.walk(cls.example_path): 135 if 'app-profiling.apk' in files: 136 cls.apk_path = os.path.join(root, 'app-profiling.apk') 137 break 138 if not hasattr(cls, 'apk_path'): 139 log_fatal("can't find app-profiling.apk under " + cls.example_path) 140 cls.package_name = package_name 141 cls.activity_name = activity_name 142 cls.abi = "arm64" 143 if abi and abi != "arm64" and abi.find("arm") != -1: 144 cls.abi = "arm" 145 args = ["install", "-r"] 146 if abi: 147 args += ["--abi", abi] 148 args.append(cls.apk_path) 149 cls.adb.check_run(args) 150 cls.adb_root = adb_root 151 cls.compiled = False 152 cls.has_perf_data_for_report = False 153 android_version = cls.adb.get_android_version() 154 # On Android >= P (version 9), we can profile JITed and interpreted Java code. 155 # So only compile Java code on Android <= O (version 8). 156 cls.use_compiled_java_code = android_version <= 8 157 158 def setUp(self): 159 if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported(): 160 self.skipTest('trace-offcpu is not supported on device') 161 cls = self.__class__ 162 if not cls.has_perf_data_for_report: 163 cls.has_perf_data_for_report = True 164 self.run_app_profiler() 165 shutil.copy('perf.data', 'perf.data_for_report') 166 remove('binary_cache_for_report') 167 shutil.copytree('binary_cache', 'binary_cache_for_report') 168 else: 169 shutil.copy('perf.data_for_report', 'perf.data') 170 remove('binary_cache') 171 shutil.copytree('binary_cache_for_report', 'binary_cache') 172 173 @classmethod 174 def tearDownClass(cls): 175 if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful(): 176 return 177 if hasattr(cls, 'package_name'): 178 cls.adb.check_run(["uninstall", cls.package_name]) 179 remove("binary_cache") 180 remove("annotated_files") 181 remove("perf.data") 182 remove("report.txt") 183 remove("pprof.profile") 184 if cls.has_perf_data_for_report: 185 cls.has_perf_data_for_report = False 186 remove('perf.data_for_report') 187 remove('binary_cache_for_report') 188 189 def run(self, result=None): 190 self.__class__.test_result = result 191 super(TestExampleBase, self).run(result) 192 193 def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True, 194 start_activity=True): 195 args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data'] 196 if not build_binary_cache: 197 args.append("-nb") 198 if self.use_compiled_java_code and not self.__class__.compiled: 199 args.append('--compile_java_code') 200 self.__class__.compiled = True 201 if start_activity: 202 args += ["-a", self.activity_name] 203 args += ["-lib", self.example_path] 204 if not self.adb_root: 205 args.append("--disable_adb_root") 206 self.run_cmd(args) 207 self.check_exist(filename="perf.data") 208 if build_binary_cache: 209 self.check_exist(dirname="binary_cache") 210 211 def check_exist(self, filename=None, dirname=None): 212 if filename: 213 self.assertTrue(os.path.isfile(filename), filename) 214 if dirname: 215 self.assertTrue(os.path.isdir(dirname), dirname) 216 217 def check_file_under_dir(self, dirname, filename): 218 self.check_exist(dirname=dirname) 219 for _, _, files in os.walk(dirname): 220 for f in files: 221 if f == filename: 222 return 223 self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename)) 224 225 226 def check_strings_in_file(self, filename, strings): 227 self.check_exist(filename=filename) 228 with open(filename, 'r') as fh: 229 self.check_strings_in_content(fh.read(), strings) 230 231 def check_strings_in_content(self, content, strings): 232 for s in strings: 233 self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) 234 235 def check_annotation_summary(self, summary_file, check_entries): 236 """ check_entries is a list of (name, accumulated_period, period). 237 This function checks for each entry, if the line containing [name] 238 has at least required accumulated_period and period. 239 """ 240 self.check_exist(filename=summary_file) 241 with open(summary_file, 'r') as fh: 242 summary = fh.read() 243 fulfilled = [False for x in check_entries] 244 summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%') 245 for line in summary.split('\n'): 246 for i, (name, need_acc_period, need_period) in enumerate(check_entries): 247 if not fulfilled[i] and name in line: 248 m = summary_check_re.search(line) 249 if m: 250 acc_period = float(m.group(1)) 251 period = float(m.group(2)) 252 if acc_period >= need_acc_period and period >= need_period: 253 fulfilled[i] = True 254 self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled) 255 256 def check_inferno_report_html(self, check_entries, filename="report.html"): 257 self.check_exist(filename=filename) 258 with open(filename, 'r') as fh: 259 data = fh.read() 260 fulfilled = [False for _ in check_entries] 261 for line in data.split('\n'): 262 # each entry is a (function_name, min_percentage) pair. 263 for i, entry in enumerate(check_entries): 264 if fulfilled[i] or line.find(entry[0]) == -1: 265 continue 266 m = re.search(r'(\d+\.\d+)%', line) 267 if m and float(m.group(1)) >= entry[1]: 268 fulfilled[i] = True 269 break 270 self.assertEqual(fulfilled, [True for _ in check_entries]) 271 272 def common_test_app_profiler(self): 273 self.run_cmd(["app_profiler.py", "-h"]) 274 remove("binary_cache") 275 self.run_app_profiler(build_binary_cache=False) 276 self.assertFalse(os.path.isdir("binary_cache")) 277 args = ["binary_cache_builder.py"] 278 if not self.adb_root: 279 args.append("--disable_adb_root") 280 self.run_cmd(args) 281 self.check_exist(dirname="binary_cache") 282 remove("binary_cache") 283 self.run_app_profiler(build_binary_cache=True) 284 self.run_app_profiler() 285 self.run_app_profiler(start_activity=False) 286 287 def common_test_report(self): 288 self.run_cmd(["report.py", "-h"]) 289 self.run_cmd(["report.py"]) 290 self.run_cmd(["report.py", "-i", "perf.data"]) 291 self.run_cmd(["report.py", "-g"]) 292 self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"]) 293 294 def common_test_annotate(self): 295 self.run_cmd(["annotate.py", "-h"]) 296 remove("annotated_files") 297 self.run_cmd(["annotate.py", "-s", self.example_path]) 298 self.check_exist(dirname="annotated_files") 299 300 def common_test_report_sample(self, check_strings): 301 self.run_cmd(["report_sample.py", "-h"]) 302 self.run_cmd(["report_sample.py"]) 303 output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True) 304 self.check_strings_in_content(output, check_strings) 305 306 def common_test_pprof_proto_generator(self, check_strings_with_lines, 307 check_strings_without_lines): 308 if not HAS_GOOGLE_PROTOBUF: 309 log_info('Skip test for pprof_proto_generator because google.protobuf is missing') 310 return 311 self.run_cmd(["pprof_proto_generator.py", "-h"]) 312 self.run_cmd(["pprof_proto_generator.py"]) 313 remove("pprof.profile") 314 self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"]) 315 self.check_exist(filename="pprof.profile") 316 self.run_cmd(["pprof_proto_generator.py", "--show"]) 317 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 318 return_output=True) 319 self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"]) 320 remove("binary_cache") 321 self.run_cmd(["pprof_proto_generator.py"]) 322 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 323 return_output=True) 324 self.check_strings_in_content(output, check_strings_without_lines + 325 ["has_line_numbers: False"]) 326 327 def common_test_inferno(self): 328 self.run_cmd([INFERNO_SCRIPT, "-h"]) 329 remove("perf.data") 330 append_args = [] if self.adb_root else ["--disable_adb_root"] 331 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args) 332 self.check_exist(filename="perf.data") 333 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] + 334 append_args) 335 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles", 336 "-t", "1"] + append_args) 337 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 338 339 def common_test_report_html(self): 340 self.run_cmd(['report_html.py', '-h']) 341 self.run_cmd(['report_html.py']) 342 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata']) 343 self.run_cmd(['report_html.py', '--add_disassembly']) 344 # Test with multiple perf.data. 345 shutil.move('perf.data', 'perf2.data') 346 self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') 347 self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) 348 remove('perf2.data') 349 350 351class TestExamplePureJava(TestExampleBase): 352 @classmethod 353 def setUpClass(cls): 354 cls.prepare("SimpleperfExamplePureJava", 355 "com.example.simpleperf.simpleperfexamplepurejava", 356 ".MainActivity") 357 358 def test_app_profiler(self): 359 self.common_test_app_profiler() 360 361 def test_app_profiler_profile_from_launch(self): 362 self.run_app_profiler(start_activity=True, build_binary_cache=False) 363 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 364 self.check_strings_in_file("report.txt", [ 365 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 366 "__start_thread"]) 367 368 def test_app_profiler_multiprocesses(self): 369 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 370 self.adb.check_run(['shell', 'am', 'start', '-n', 371 self.package_name + '/.MultiProcessActivity']) 372 # Wait until both MultiProcessActivity and MultiProcessService set up. 373 time.sleep(3) 374 self.run_app_profiler(start_activity=False) 375 self.run_cmd(["report.py", "-o", "report.txt"]) 376 self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"]) 377 378 def test_app_profiler_with_ctrl_c(self): 379 if is_windows(): 380 return 381 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 382 time.sleep(1) 383 args = [sys.executable, "app_profiler.py", "--app", self.package_name, 384 "-r", "--duration 10000", "--disable_adb_root"] 385 subproc = subprocess.Popen(args) 386 time.sleep(3) 387 388 subproc.send_signal(signal.SIGINT) 389 subproc.wait() 390 self.assertEqual(subproc.returncode, 0) 391 self.run_cmd(["report.py"]) 392 393 def test_app_profiler_stop_after_app_exit(self): 394 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 395 time.sleep(1) 396 subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name, 397 '-r', '--duration 10000', '--disable_adb_root']) 398 time.sleep(3) 399 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 400 subproc.wait() 401 self.assertEqual(subproc.returncode, 0) 402 self.run_cmd(["report.py"]) 403 404 def test_app_profiler_with_ndk_path(self): 405 # Although we pass an invalid ndk path, it should be able to find tools in default ndk path. 406 self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name, 407 '--ndk_path', '.']) 408 409 def test_report(self): 410 self.common_test_report() 411 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 412 self.check_strings_in_file("report.txt", [ 413 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 414 "__start_thread"]) 415 416 def test_profile_with_process_id(self): 417 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 418 time.sleep(1) 419 pid = self.adb.check_run_and_return_output([ 420 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip() 421 self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid) 422 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 423 self.check_strings_in_file("report.txt", [ 424 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 425 "__start_thread"]) 426 427 def test_annotate(self): 428 self.common_test_annotate() 429 if not self.use_compiled_java_code: 430 # Currently annotating Java code is only supported when the Java code is compiled. 431 return 432 self.check_file_under_dir("annotated_files", "MainActivity.java") 433 summary_file = os.path.join("annotated_files", "summary") 434 self.check_annotation_summary(summary_file, [ 435 ("MainActivity.java", 80, 80), 436 ("run", 80, 0), 437 ("callFunction", 0, 0), 438 ("line 23", 80, 0)]) 439 440 def test_report_sample(self): 441 self.common_test_report_sample( 442 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 443 "__start_thread"]) 444 445 def test_pprof_proto_generator(self): 446 check_strings_with_lines = [] 447 if self.use_compiled_java_code: 448 check_strings_with_lines = [ 449 "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java", 450 "run"] 451 self.common_test_pprof_proto_generator( 452 check_strings_with_lines=check_strings_with_lines, 453 check_strings_without_lines=[ 454 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"]) 455 456 def test_inferno(self): 457 self.common_test_inferno() 458 self.run_app_profiler() 459 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 460 self.check_inferno_report_html( 461 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)]) 462 self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"]) 463 self.check_inferno_report_html( 464 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)], 465 "report2.html") 466 remove("report2.html") 467 468 def test_inferno_in_another_dir(self): 469 test_dir = 'inferno_testdir' 470 saved_dir = os.getcwd() 471 remove(test_dir) 472 os.mkdir(test_dir) 473 os.chdir(test_dir) 474 self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'), 475 '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3']) 476 self.check_exist(filename="perf.data") 477 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 478 os.chdir(saved_dir) 479 remove(test_dir) 480 481 def test_report_html(self): 482 self.common_test_report_html() 483 484 def test_run_simpleperf_without_usb_connection(self): 485 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 486 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p', 487 self.package_name, '--size_limit', '1M']) 488 self.adb.check_run(['kill-server']) 489 time.sleep(3) 490 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop']) 491 self.check_exist(filename="perf.data") 492 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 493 494 495class TestExamplePureJavaRoot(TestExampleBase): 496 @classmethod 497 def setUpClass(cls): 498 cls.prepare("SimpleperfExamplePureJava", 499 "com.example.simpleperf.simpleperfexamplepurejava", 500 ".MainActivity", 501 adb_root=True) 502 503 def test_app_profiler(self): 504 self.common_test_app_profiler() 505 506 507class TestExamplePureJavaTraceOffCpu(TestExampleBase): 508 @classmethod 509 def setUpClass(cls): 510 cls.prepare("SimpleperfExamplePureJava", 511 "com.example.simpleperf.simpleperfexamplepurejava", 512 ".SleepActivity") 513 514 def test_smoke(self): 515 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 516 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 517 self.check_strings_in_file("report.txt", [ 518 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run", 519 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction", 520 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction" 521 ]) 522 remove("annotated_files") 523 self.run_cmd(["annotate.py", "-s", self.example_path]) 524 self.check_exist(dirname="annotated_files") 525 if self.use_compiled_java_code: 526 self.check_file_under_dir("annotated_files", "SleepActivity.java") 527 summary_file = os.path.join("annotated_files", "summary") 528 self.check_annotation_summary(summary_file, [ 529 ("SleepActivity.java", 80, 20), 530 ("run", 80, 0), 531 ("RunFunction", 20, 20), 532 ("SleepFunction", 20, 0), 533 ("line 24", 20, 0), 534 ("line 32", 20, 0)]) 535 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 536 self.check_inferno_report_html( 537 [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80), 538 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction', 539 20), 540 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction', 541 20)]) 542 543 544class TestExampleWithNative(TestExampleBase): 545 @classmethod 546 def setUpClass(cls): 547 cls.prepare("SimpleperfExampleWithNative", 548 "com.example.simpleperf.simpleperfexamplewithnative", 549 ".MainActivity") 550 551 def test_app_profiler(self): 552 self.common_test_app_profiler() 553 554 def test_app_profiler_profile_from_launch(self): 555 self.run_app_profiler(start_activity=True, build_binary_cache=False) 556 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 557 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 558 559 def test_report(self): 560 self.common_test_report() 561 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 562 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 563 564 def test_annotate(self): 565 self.common_test_annotate() 566 self.check_file_under_dir("annotated_files", "native-lib.cpp") 567 summary_file = os.path.join("annotated_files", "summary") 568 self.check_annotation_summary(summary_file, [ 569 ("native-lib.cpp", 20, 0), 570 ("BusyLoopThread", 20, 0), 571 ("line 46", 20, 0)]) 572 573 def test_report_sample(self): 574 self.common_test_report_sample( 575 ["BusyLoopThread", 576 "__start_thread"]) 577 578 def test_pprof_proto_generator(self): 579 self.common_test_pprof_proto_generator( 580 check_strings_with_lines=["native-lib.cpp", "BusyLoopThread"], 581 check_strings_without_lines=["BusyLoopThread"]) 582 583 def test_inferno(self): 584 self.common_test_inferno() 585 self.run_app_profiler() 586 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 587 self.check_inferno_report_html([('BusyLoopThread', 20)]) 588 589 def test_report_html(self): 590 self.common_test_report_html() 591 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata', 592 '--add_disassembly', '--binary_filter', "libnative-lib.so"]) 593 594 595class TestExampleWithNativeRoot(TestExampleBase): 596 @classmethod 597 def setUpClass(cls): 598 cls.prepare("SimpleperfExampleWithNative", 599 "com.example.simpleperf.simpleperfexamplewithnative", 600 ".MainActivity", 601 adb_root=True) 602 603 def test_app_profiler(self): 604 self.common_test_app_profiler() 605 606 607class TestExampleWithNativeTraceOffCpu(TestExampleBase): 608 @classmethod 609 def setUpClass(cls): 610 cls.prepare("SimpleperfExampleWithNative", 611 "com.example.simpleperf.simpleperfexamplewithnative", 612 ".SleepActivity") 613 614 def test_smoke(self): 615 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 616 self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"]) 617 self.check_strings_in_file("report.txt", [ 618 "SleepThread(void*)", 619 "RunFunction()", 620 "SleepFunction(unsigned long long)"]) 621 remove("annotated_files") 622 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) 623 self.check_exist(dirname="annotated_files") 624 self.check_file_under_dir("annotated_files", "native-lib.cpp") 625 summary_file = os.path.join("annotated_files", "summary") 626 self.check_annotation_summary(summary_file, [ 627 ("native-lib.cpp", 80, 20), 628 ("SleepThread", 80, 0), 629 ("RunFunction", 20, 20), 630 ("SleepFunction", 20, 0), 631 ("line 73", 20, 0), 632 ("line 83", 20, 0)]) 633 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 634 self.check_inferno_report_html([('SleepThread', 80), 635 ('RunFunction', 20), 636 ('SleepFunction', 20)]) 637 638 639class TestExampleWithNativeJniCall(TestExampleBase): 640 @classmethod 641 def setUpClass(cls): 642 cls.prepare("SimpleperfExampleWithNative", 643 "com.example.simpleperf.simpleperfexamplewithnative", 644 ".MixActivity") 645 646 def test_smoke(self): 647 self.run_app_profiler() 648 self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) 649 self.check_strings_in_file("report.txt", [ 650 "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run", 651 "com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction", 652 "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) 653 remove("annotated_files") 654 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) 655 self.check_exist(dirname="annotated_files") 656 self.check_file_under_dir("annotated_files", "native-lib.cpp") 657 summary_file = os.path.join("annotated_files", "summary") 658 self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)]) 659 if self.use_compiled_java_code: 660 self.check_file_under_dir("annotated_files", "MixActivity.java") 661 self.check_annotation_summary(summary_file, [ 662 ("MixActivity.java", 80, 0), 663 ("run", 80, 0), 664 ("line 26", 20, 0), 665 ("native-lib.cpp", 5, 0), 666 ("line 40", 5, 0)]) 667 668 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 669 670 671class TestExampleWithNativeForceArm(TestExampleWithNative): 672 @classmethod 673 def setUpClass(cls): 674 cls.prepare("SimpleperfExampleWithNative", 675 "com.example.simpleperf.simpleperfexamplewithnative", 676 ".MainActivity", 677 abi="armeabi-v7a") 678 679 680class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot): 681 @classmethod 682 def setUpClass(cls): 683 cls.prepare("SimpleperfExampleWithNative", 684 "com.example.simpleperf.simpleperfexamplewithnative", 685 ".MainActivity", 686 abi="armeabi-v7a", 687 adb_root=False) 688 689 690class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu): 691 @classmethod 692 def setUpClass(cls): 693 cls.prepare("SimpleperfExampleWithNative", 694 "com.example.simpleperf.simpleperfexamplewithnative", 695 ".SleepActivity", 696 abi="armeabi-v7a") 697 698 699class TestExampleOfKotlin(TestExampleBase): 700 @classmethod 701 def setUpClass(cls): 702 cls.prepare("SimpleperfExampleOfKotlin", 703 "com.example.simpleperf.simpleperfexampleofkotlin", 704 ".MainActivity") 705 706 def test_app_profiler(self): 707 self.common_test_app_profiler() 708 709 def test_app_profiler_profile_from_launch(self): 710 self.run_app_profiler(start_activity=True, build_binary_cache=False) 711 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 712 self.check_strings_in_file("report.txt", [ 713 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 714 "run", "__start_thread"]) 715 716 def test_report(self): 717 self.common_test_report() 718 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 719 self.check_strings_in_file("report.txt", [ 720 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 721 "run", "__start_thread"]) 722 723 def test_annotate(self): 724 if not self.use_compiled_java_code: 725 return 726 self.common_test_annotate() 727 self.check_file_under_dir("annotated_files", "MainActivity.kt") 728 summary_file = os.path.join("annotated_files", "summary") 729 self.check_annotation_summary(summary_file, [ 730 ("MainActivity.kt", 80, 80), 731 ("run", 80, 0), 732 ("callFunction", 0, 0), 733 ("line 19", 80, 0), 734 ("line 25", 0, 0)]) 735 736 def test_report_sample(self): 737 self.common_test_report_sample([ 738 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 739 "run", "__start_thread"]) 740 741 def test_pprof_proto_generator(self): 742 check_strings_with_lines = [] 743 if self.use_compiled_java_code: 744 check_strings_with_lines = [ 745 "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt", 746 "run"] 747 self.common_test_pprof_proto_generator( 748 check_strings_with_lines=check_strings_with_lines, 749 check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." + 750 "MainActivity$createBusyThread$1.run"]) 751 752 def test_inferno(self): 753 self.common_test_inferno() 754 self.run_app_profiler() 755 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 756 self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' + 757 'MainActivity$createBusyThread$1.run', 80)]) 758 759 def test_report_html(self): 760 self.common_test_report_html() 761 762 763class TestExampleOfKotlinRoot(TestExampleBase): 764 @classmethod 765 def setUpClass(cls): 766 cls.prepare("SimpleperfExampleOfKotlin", 767 "com.example.simpleperf.simpleperfexampleofkotlin", 768 ".MainActivity", 769 adb_root=True) 770 771 def test_app_profiler(self): 772 self.common_test_app_profiler() 773 774 775class TestExampleOfKotlinTraceOffCpu(TestExampleBase): 776 @classmethod 777 def setUpClass(cls): 778 cls.prepare("SimpleperfExampleOfKotlin", 779 "com.example.simpleperf.simpleperfexampleofkotlin", 780 ".SleepActivity") 781 782 def test_smoke(self): 783 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 784 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 785 function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \ 786 "SleepActivity$createRunSleepThread$1." 787 self.check_strings_in_file("report.txt", [ 788 function_prefix + "run", 789 function_prefix + "RunFunction", 790 function_prefix + "SleepFunction" 791 ]) 792 if self.use_compiled_java_code: 793 remove("annotated_files") 794 self.run_cmd(["annotate.py", "-s", self.example_path]) 795 self.check_exist(dirname="annotated_files") 796 self.check_file_under_dir("annotated_files", "SleepActivity.kt") 797 summary_file = os.path.join("annotated_files", "summary") 798 self.check_annotation_summary(summary_file, [ 799 ("SleepActivity.kt", 80, 20), 800 ("run", 80, 0), 801 ("RunFunction", 20, 20), 802 ("SleepFunction", 20, 0), 803 ("line 24", 20, 0), 804 ("line 32", 20, 0)]) 805 806 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 807 self.check_inferno_report_html([ 808 (function_prefix + 'run', 80), 809 (function_prefix + 'RunFunction', 20), 810 (function_prefix + 'SleepFunction', 20)]) 811 812 813class TestNativeProfiling(TestBase): 814 def setUp(self): 815 self.adb = AdbHelper() 816 self.is_rooted_device = self.adb.switch_to_root() 817 818 def test_profile_cmd(self): 819 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) 820 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 821 822 def test_profile_native_program(self): 823 if not self.is_rooted_device: 824 return 825 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) 826 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 827 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 828 self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"]) 829 830 def test_profile_pids(self): 831 if not self.is_rooted_device: 832 return 833 pid = int(self.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) 834 self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) 835 self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) 836 self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) 837 self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1']) 838 self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1']) 839 840 def test_profile_system_wide(self): 841 if not self.is_rooted_device: 842 return 843 self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1']) 844 845 846class TestReportLib(unittest.TestCase): 847 def setUp(self): 848 self.report_lib = ReportLib() 849 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) 850 851 def tearDown(self): 852 self.report_lib.Close() 853 854 def test_build_id(self): 855 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 856 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 857 858 def test_symbol(self): 859 found_func2 = False 860 while self.report_lib.GetNextSample(): 861 symbol = self.report_lib.GetSymbolOfCurrentSample() 862 if symbol.symbol_name == 'func2(int, int)': 863 found_func2 = True 864 self.assertEqual(symbol.symbol_addr, 0x4004ed) 865 self.assertEqual(symbol.symbol_len, 0x14) 866 self.assertTrue(found_func2) 867 868 def test_sample(self): 869 found_sample = False 870 while self.report_lib.GetNextSample(): 871 sample = self.report_lib.GetCurrentSample() 872 if sample.ip == 0x4004ff and sample.time == 7637889424953: 873 found_sample = True 874 self.assertEqual(sample.pid, 15926) 875 self.assertEqual(sample.tid, 15926) 876 self.assertEqual(sample.thread_comm, 't2') 877 self.assertEqual(sample.cpu, 5) 878 self.assertEqual(sample.period, 694614) 879 event = self.report_lib.GetEventOfCurrentSample() 880 self.assertEqual(event.name, 'cpu-cycles') 881 callchain = self.report_lib.GetCallChainOfCurrentSample() 882 self.assertEqual(callchain.nr, 0) 883 self.assertTrue(found_sample) 884 885 def test_meta_info(self): 886 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 887 meta_info = self.report_lib.MetaInfo() 888 self.assertTrue("simpleperf_version" in meta_info) 889 self.assertEqual(meta_info["system_wide_collection"], "false") 890 self.assertEqual(meta_info["trace_offcpu"], "true") 891 self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") 892 self.assertTrue("product_props" in meta_info) 893 894 def test_event_name_from_meta_info(self): 895 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) 896 event_names = set() 897 while self.report_lib.GetNextSample(): 898 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 899 self.assertTrue('sched:sched_switch' in event_names) 900 self.assertTrue('cpu-cycles' in event_names) 901 902 def test_record_cmd(self): 903 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 904 self.assertEqual(self.report_lib.GetRecordCmd(), 905 "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " + 906 "./simpleperf_runtest_run_and_sleep64") 907 908 def test_offcpu(self): 909 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 910 total_period = 0 911 sleep_function_period = 0 912 sleep_function_name = "SleepFunction(unsigned long long)" 913 while self.report_lib.GetNextSample(): 914 sample = self.report_lib.GetCurrentSample() 915 total_period += sample.period 916 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 917 sleep_function_period += sample.period 918 continue 919 callchain = self.report_lib.GetCallChainOfCurrentSample() 920 for i in range(callchain.nr): 921 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 922 sleep_function_period += sample.period 923 break 924 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles') 925 sleep_percentage = float(sleep_function_period) / total_period 926 self.assertGreater(sleep_percentage, 0.30) 927 928 def test_show_art_frames(self): 929 def has_art_frame(report_lib): 930 report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data')) 931 result = False 932 while report_lib.GetNextSample(): 933 callchain = report_lib.GetCallChainOfCurrentSample() 934 for i in range(callchain.nr): 935 if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart': 936 result = True 937 break 938 report_lib.Close() 939 return result 940 941 report_lib = ReportLib() 942 self.assertFalse(has_art_frame(report_lib)) 943 report_lib = ReportLib() 944 report_lib.ShowArtFrames(False) 945 self.assertFalse(has_art_frame(report_lib)) 946 report_lib = ReportLib() 947 report_lib.ShowArtFrames(True) 948 self.assertTrue(has_art_frame(report_lib)) 949 950 def test_tracing_data(self): 951 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) 952 has_tracing_data = False 953 while self.report_lib.GetNextSample(): 954 event = self.report_lib.GetEventOfCurrentSample() 955 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 956 if event.name == 'sched:sched_switch': 957 self.assertIsNotNone(tracing_data) 958 self.assertIn('prev_pid', tracing_data) 959 self.assertIn('next_comm', tracing_data) 960 if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4': 961 has_tracing_data = True 962 else: 963 self.assertIsNone(tracing_data) 964 self.assertTrue(has_tracing_data) 965 966 967class TestRunSimpleperfOnDevice(TestBase): 968 def test_smoke(self): 969 self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) 970 971 972class TestTools(unittest.TestCase): 973 def test_addr2nearestline(self): 974 self.run_addr2nearestline_test(True) 975 self.run_addr2nearestline_test(False) 976 977 def run_addr2nearestline_test(self, with_function_name): 978 binary_cache_path = 'testdata' 979 test_map = { 980 '/simpleperf_runtest_two_functions_arm64': [ 981 { 982 'func_addr': 0x668, 983 'addr': 0x668, 984 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20', 985 'function': 'main', 986 }, 987 { 988 'func_addr': 0x668, 989 'addr': 0x6a4, 990 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 991 system/extras/simpleperf/runtest/two_functions.cpp:22""", 992 'function': """Function1() 993 main""", 994 }, 995 ], 996 '/simpleperf_runtest_two_functions_arm': [ 997 { 998 'func_addr': 0x784, 999 'addr': 0x7b0, 1000 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 1001 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1002 'function': """Function2() 1003 main""", 1004 }, 1005 { 1006 'func_addr': 0x784, 1007 'addr': 0x7d0, 1008 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 1009 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1010 'function': """Function2() 1011 main""", 1012 } 1013 ], 1014 '/simpleperf_runtest_two_functions_x86_64': [ 1015 { 1016 'func_addr': 0x840, 1017 'addr': 0x840, 1018 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', 1019 'function': 'Function1()', 1020 }, 1021 { 1022 'func_addr': 0x920, 1023 'addr': 0x94a, 1024 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 1025 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1026 'function': """Function1() 1027 main""", 1028 } 1029 ], 1030 '/simpleperf_runtest_two_functions_x86': [ 1031 { 1032 'func_addr': 0x6d0, 1033 'addr': 0x6da, 1034 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', 1035 'function': 'Function2()', 1036 }, 1037 { 1038 'func_addr': 0x710, 1039 'addr': 0x749, 1040 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 1041 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1042 'function': """Function1() 1043 main""", 1044 } 1045 ], 1046 } 1047 addr2line = Addr2Nearestline(None, binary_cache_path, with_function_name) 1048 for dso_path in test_map: 1049 test_addrs = test_map[dso_path] 1050 for test_addr in test_addrs: 1051 addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr']) 1052 addr2line.convert_addrs_to_lines() 1053 for dso_path in test_map: 1054 dso = addr2line.get_dso(dso_path) 1055 self.assertTrue(dso is not None) 1056 test_addrs = test_map[dso_path] 1057 for test_addr in test_addrs: 1058 expected_files = [] 1059 expected_lines = [] 1060 expected_functions = [] 1061 for line in test_addr['source'].split('\n'): 1062 items = line.split(':') 1063 expected_files.append(items[0].strip()) 1064 expected_lines.append(int(items[1])) 1065 for line in test_addr['function'].split('\n'): 1066 expected_functions.append(line.strip()) 1067 self.assertEquals(len(expected_files), len(expected_functions)) 1068 1069 actual_source = addr2line.get_addr_source(dso, test_addr['addr']) 1070 self.assertTrue(actual_source is not None) 1071 self.assertEqual(len(actual_source), len(expected_files)) 1072 for i, source in enumerate(actual_source): 1073 self.assertEqual(len(source), 3 if with_function_name else 2) 1074 self.assertEqual(source[0], expected_files[i]) 1075 self.assertEqual(source[1], expected_lines[i]) 1076 if with_function_name: 1077 self.assertEqual(source[2], expected_functions[i]) 1078 1079 def test_objdump(self): 1080 binary_cache_path = 'testdata' 1081 test_map = { 1082 '/simpleperf_runtest_two_functions_arm64': { 1083 'start_addr': 0x668, 1084 'len': 116, 1085 'expected_items': [ 1086 ('main():', 0), 1087 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1088 (' 694: add x20, x20, #0x6de', 0x694), 1089 ], 1090 }, 1091 '/simpleperf_runtest_two_functions_arm': { 1092 'start_addr': 0x784, 1093 'len': 80, 1094 'expected_items': [ 1095 ('main():', 0), 1096 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1097 (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae), 1098 ], 1099 }, 1100 '/simpleperf_runtest_two_functions_x86_64': { 1101 'start_addr': 0x920, 1102 'len': 201, 1103 'expected_items': [ 1104 ('main():', 0), 1105 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1106 (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e), 1107 ], 1108 }, 1109 '/simpleperf_runtest_two_functions_x86': { 1110 'start_addr': 0x710, 1111 'len': 98, 1112 'expected_items': [ 1113 ('main():', 0), 1114 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1115 (' 748: cmp $0x5f5e100,%ebp', 0x748), 1116 ], 1117 }, 1118 } 1119 objdump = Objdump(None, binary_cache_path) 1120 for dso_path in test_map: 1121 dso = test_map[dso_path] 1122 dso_info = objdump.get_dso_info(dso_path) 1123 self.assertIsNotNone(dso_info) 1124 disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len']) 1125 self.assertTrue(disassemble_code) 1126 for item in dso['expected_items']: 1127 self.assertTrue(item in disassemble_code) 1128 1129 def test_readelf(self): 1130 test_map = { 1131 '/simpleperf_runtest_two_functions_arm64': { 1132 'arch': 'arm64', 1133 'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000', 1134 'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym', 1135 '.dynstr', '.gnu.hash', '.gnu.version', '.gnu.version_r', '.rela.dyn', 1136 '.rela.plt', '.plt', '.text', '.rodata', '.eh_frame', '.eh_frame_hdr', 1137 '.preinit_array', '.init_array', '.fini_array', '.dynamic', '.got', 1138 '.got.plt', '.data', '.bss', '.comment', '.debug_str', '.debug_loc', 1139 '.debug_abbrev', '.debug_info', '.debug_ranges', '.debug_macinfo', 1140 '.debug_pubnames', '.debug_pubtypes', '.debug_line', 1141 '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'], 1142 }, 1143 '/simpleperf_runtest_two_functions_arm': { 1144 'arch': 'arm', 1145 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000', 1146 }, 1147 '/simpleperf_runtest_two_functions_x86_64': { 1148 'arch': 'x86_64', 1149 }, 1150 '/simpleperf_runtest_two_functions_x86': { 1151 'arch': 'x86', 1152 } 1153 } 1154 readelf = ReadElf(None) 1155 for dso_path in test_map: 1156 dso_info = test_map[dso_path] 1157 path = 'testdata' + dso_path 1158 self.assertEqual(dso_info['arch'], readelf.get_arch(path)) 1159 if 'build_id' in dso_info: 1160 self.assertEqual(dso_info['build_id'], readelf.get_build_id(path)) 1161 if 'sections' in dso_info: 1162 self.assertEqual(dso_info['sections'], readelf.get_sections(path)) 1163 self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown') 1164 self.assertEqual(readelf.get_build_id('not_exist_file'), '') 1165 self.assertEqual(readelf.get_sections('not_exist_file'), []) 1166 1167 def test_source_file_searcher(self): 1168 searcher = SourceFileSearcher(['testdata']) 1169 def format_path(path): 1170 return path.replace('/', os.sep) 1171 # Find a C++ file with pure file name. 1172 self.assertEquals( 1173 format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1174 searcher.get_real_path('native-lib.cpp')) 1175 # Find a C++ file with an absolute file path. 1176 self.assertEquals( 1177 format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1178 searcher.get_real_path('/data/native-lib.cpp')) 1179 # Find a Java file. 1180 self.assertEquals( 1181 format_path('testdata/SimpleperfExampleWithNative/app/src/main/java/com/example/' + 1182 'simpleperf/simpleperfexamplewithnative/MainActivity.java'), 1183 searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java')) 1184 # Find a Kotlin file. 1185 self.assertEquals( 1186 format_path('testdata/SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + 1187 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'), 1188 searcher.get_real_path('MainActivity.kt')) 1189 1190 1191class TestNativeLibDownloader(unittest.TestCase): 1192 def test_smoke(self): 1193 adb = AdbHelper() 1194 1195 def is_lib_on_device(path): 1196 return adb.run(['shell', 'ls', path]) 1197 1198 # Sync all native libs on device. 1199 adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1200 downloader = NativeLibDownloader(None, 'arm64', adb) 1201 downloader.collect_native_libs_on_host(os.path.join( 1202 'testdata', 'SimpleperfExampleWithNative', 'app', 'build', 'intermediates', 'cmake', 1203 'profiling')) 1204 self.assertEqual(len(downloader.host_build_id_map), 2) 1205 for entry in downloader.host_build_id_map.values(): 1206 self.assertEqual(entry.score, 3) 1207 downloader.collect_native_libs_on_device() 1208 self.assertEqual(len(downloader.device_build_id_map), 0) 1209 1210 lib_list = list(downloader.host_build_id_map.items()) 1211 for sync_count in [0, 1, 2]: 1212 build_id_map = {} 1213 for i in range(sync_count): 1214 build_id_map[lib_list[i][0]] = lib_list[i][1] 1215 downloader.host_build_id_map = build_id_map 1216 downloader.sync_natives_libs_on_device() 1217 downloader.collect_native_libs_on_device() 1218 self.assertEqual(len(downloader.device_build_id_map), sync_count) 1219 for i, item in enumerate(lib_list): 1220 build_id = item[0] 1221 name = item[1].name 1222 if i < sync_count: 1223 self.assertTrue(build_id in downloader.device_build_id_map) 1224 self.assertEqual(name, downloader.device_build_id_map[build_id]) 1225 self.assertTrue(is_lib_on_device(downloader.dir_on_device + name)) 1226 else: 1227 self.assertTrue(build_id not in downloader.device_build_id_map) 1228 self.assertFalse(is_lib_on_device(downloader.dir_on_device + name)) 1229 if sync_count == 1: 1230 adb.run(['pull', '/data/local/tmp/native_libs/build_id_list', 'build_id_list']) 1231 with open('build_id_list', 'rb') as fh: 1232 self.assertEqual(bytes_to_str(fh.read()), 1233 '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name)) 1234 remove('build_id_list') 1235 adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1236 1237 1238class TestReportHtml(TestBase): 1239 def test_long_callchain(self): 1240 self.run_cmd(['report_html.py', '-i', 'testdata/perf_with_long_callchain.data']) 1241 1242 1243class TestBinaryCacheBuilder(TestBase): 1244 def test_copy_binaries_from_symfs_dirs(self): 1245 readelf = ReadElf(None) 1246 strip = find_tool_path('strip', arch='arm') 1247 self.assertIsNotNone(strip) 1248 symfs_dir = os.path.join('testdata', 'symfs_dir') 1249 remove(symfs_dir) 1250 os.mkdir(symfs_dir) 1251 filename = 'simpleperf_runtest_two_functions_arm' 1252 origin_file = os.path.join('testdata', filename) 1253 source_file = os.path.join(symfs_dir, filename) 1254 target_file = os.path.join('binary_cache', filename) 1255 expected_build_id = readelf.get_build_id(origin_file) 1256 binary_cache_builder = BinaryCacheBuilder(None, False) 1257 binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id 1258 1259 # Copy binary if target file doesn't exist. 1260 remove(target_file) 1261 self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file]) 1262 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1263 self.assertTrue(filecmp.cmp(target_file, source_file)) 1264 1265 # Copy binary if target file doesn't have .symtab and source file has .symtab. 1266 self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file]) 1267 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1268 self.assertTrue(filecmp.cmp(target_file, source_file)) 1269 1270 # Copy binary if target file doesn't have .debug_line and source_files has .debug_line. 1271 shutil.copy(origin_file, source_file) 1272 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1273 self.assertTrue(filecmp.cmp(target_file, source_file)) 1274 1275 1276def get_all_tests(): 1277 tests = [] 1278 for name, value in globals().items(): 1279 if isinstance(value, type) and issubclass(value, unittest.TestCase): 1280 for member_name, member in inspect.getmembers(value): 1281 if isinstance(member, (types.MethodType, types.FunctionType)): 1282 if member_name.startswith('test'): 1283 tests.append(name + '.' + member_name) 1284 return sorted(tests) 1285 1286 1287def run_tests(tests): 1288 os.chdir(get_script_dir()) 1289 build_testdata() 1290 log_info('Run tests with python%d\n%s' % (3 if is_python3() else 2, '\n'.join(tests))) 1291 argv = [sys.argv[0]] + tests 1292 unittest.main(argv=argv, failfast=True, verbosity=2, exit=False) 1293 1294 1295def main(): 1296 parser = argparse.ArgumentParser(description='Test simpleperf scripts') 1297 parser.add_argument('--list-tests', action='store_true', help='List all tests.') 1298 parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.') 1299 parser.add_argument('--python-version', choices=['2', '3', 'both'], default='both', help=""" 1300 Run tests on which python versions.""") 1301 parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.') 1302 args = parser.parse_args() 1303 tests = get_all_tests() 1304 if args.list_tests: 1305 print('\n'.join(tests)) 1306 return 1307 if args.test_from: 1308 start_pos = 0 1309 while start_pos < len(tests) and tests[start_pos] != args.test_from[0]: 1310 start_pos += 1 1311 if start_pos == len(tests): 1312 log_exit("Can't find test %s" % args.test_from[0]) 1313 tests = tests[start_pos:] 1314 if args.pattern: 1315 pattern = re.compile(fnmatch.translate(args.pattern[0])) 1316 new_tests = [] 1317 for test in tests: 1318 if pattern.match(test): 1319 new_tests.append(test) 1320 tests = new_tests 1321 if not tests: 1322 log_exit('No tests are matched.') 1323 1324 if AdbHelper().get_android_version() < 7: 1325 log_info("Skip tests on Android version < N.") 1326 sys.exit(0) 1327 1328 if args.python_version == 'both': 1329 python_versions = [2, 3] 1330 else: 1331 python_versions = [int(args.python_version)] 1332 current_version = 3 if is_python3() else 2 1333 for version in python_versions: 1334 if version != current_version: 1335 argv = ['python3' if version == 3 else 'python'] 1336 argv.append(os.path.join(get_script_dir(), 'test.py')) 1337 argv += sys.argv[1:] 1338 argv += ['--python-version', str(version)] 1339 subprocess.check_call(argv) 1340 else: 1341 run_tests(tests) 1342 1343 1344if __name__ == '__main__': 1345 main() 1346