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""" 37 38import os 39import re 40import shutil 41import signal 42import sys 43import tempfile 44import time 45import unittest 46 47from simpleperf_report_lib import ReportLib 48from utils import * 49 50has_google_protobuf = True 51try: 52 import google.protobuf 53except: 54 has_google_protobuf = False 55 56inferno_script = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh") 57 58support_trace_offcpu = None 59 60def is_trace_offcpu_supported(): 61 global support_trace_offcpu 62 if support_trace_offcpu is None: 63 adb = AdbHelper() 64 adb.check_run_and_return_output(['push', 65 'bin/android/%s/simpleperf' % adb.get_device_arch(), 66 "/data/local/tmp"]) 67 adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) 68 output = adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list', 69 '--show-features']) 70 support_trace_offcpu = 'trace-offcpu' in output 71 return support_trace_offcpu 72 73def build_testdata(): 74 """ Collect testdata from ../testdata and ../demo. """ 75 from_testdata_path = os.path.join('..', 'testdata') 76 from_demo_path = os.path.join('..', 'demo') 77 from_script_testdata_path = 'script_testdata' 78 if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or 79 not from_script_testdata_path): 80 return 81 copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data', 82 'perf_with_tracepoint_event.data'] 83 copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative', 84 'SimpleperfExampleOfKotlin'] 85 86 testdata_path = "testdata" 87 remove(testdata_path) 88 os.mkdir(testdata_path) 89 for testdata in copy_testdata_list: 90 shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path) 91 for demo in copy_demo_list: 92 shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo)) 93 for f in os.listdir(from_script_testdata_path): 94 shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path) 95 96class TestBase(unittest.TestCase): 97 def run_cmd(self, args, return_output=False): 98 if args[0].endswith('.py'): 99 args = [sys.executable] + args 100 use_shell = args[0].endswith('.bat') 101 try: 102 if not return_output: 103 returncode = subprocess.call(args, shell=use_shell) 104 else: 105 subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell) 106 (output_data, _) = subproc.communicate() 107 returncode = subproc.returncode 108 except: 109 returncode = None 110 self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args) 111 if return_output: 112 return output_data 113 114 115class TestExampleBase(TestBase): 116 @classmethod 117 def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): 118 cls.adb = AdbHelper(enable_switch_to_root=adb_root) 119 cls.example_path = os.path.join("testdata", example_name) 120 if not os.path.isdir(cls.example_path): 121 log_fatal("can't find " + cls.example_path) 122 for root, _, files in os.walk(cls.example_path): 123 if 'app-profiling.apk' in files: 124 cls.apk_path = os.path.join(root, 'app-profiling.apk') 125 break 126 if not hasattr(cls, 'apk_path'): 127 log_fatal("can't find app-profiling.apk under " + cls.example_path) 128 cls.package_name = package_name 129 cls.activity_name = activity_name 130 cls.abi = "arm64" 131 if abi and abi != "arm64" and abi.find("arm") != -1: 132 cls.abi = "arm" 133 args = ["install", "-r"] 134 if abi: 135 args += ["--abi", abi] 136 args.append(cls.apk_path) 137 cls.adb.check_run(args) 138 cls.adb_root = adb_root 139 cls.compiled = False 140 141 def setUp(self): 142 if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported(): 143 self.skipTest('trace-offcpu is not supported on device') 144 145 @classmethod 146 def tearDownClass(cls): 147 if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful(): 148 return 149 if hasattr(cls, 'package_name'): 150 cls.adb.check_run(["uninstall", cls.package_name]) 151 remove("binary_cache") 152 remove("annotated_files") 153 remove("perf.data") 154 remove("report.txt") 155 remove("pprof.profile") 156 157 def run(self, result=None): 158 self.__class__.test_result = result 159 super(TestBase, self).run(result) 160 161 def run_app_profiler(self, record_arg = "-g -f 1000 --duration 3 -e cpu-cycles:u", 162 build_binary_cache=True, skip_compile=False, start_activity=True, 163 native_lib_dir=None, profile_from_launch=False, add_arch=False): 164 args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path, 165 "-r", record_arg, "-o", "perf.data"] 166 if not build_binary_cache: 167 args.append("-nb") 168 if skip_compile or self.__class__.compiled: 169 args.append("-nc") 170 if start_activity: 171 args += ["-a", self.activity_name] 172 if native_lib_dir: 173 args += ["-lib", native_lib_dir] 174 if profile_from_launch: 175 args.append("--profile_from_launch") 176 if add_arch: 177 args += ["--arch", self.abi] 178 if not self.adb_root: 179 args.append("--disable_adb_root") 180 self.run_cmd(args) 181 self.check_exist(file="perf.data") 182 if build_binary_cache: 183 self.check_exist(dir="binary_cache") 184 if not skip_compile: 185 self.__class__.compiled = True 186 187 def check_exist(self, file=None, dir=None): 188 if file: 189 self.assertTrue(os.path.isfile(file), file) 190 if dir: 191 self.assertTrue(os.path.isdir(dir), dir) 192 193 def check_file_under_dir(self, dir, file): 194 self.check_exist(dir=dir) 195 for _, _, files in os.walk(dir): 196 for f in files: 197 if f == file: 198 return 199 self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dir, file)) 200 201 202 def check_strings_in_file(self, file, strings): 203 self.check_exist(file=file) 204 with open(file, 'r') as fh: 205 self.check_strings_in_content(fh.read(), strings) 206 207 def check_strings_in_content(self, content, strings): 208 for s in strings: 209 self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) 210 211 def check_annotation_summary(self, summary_file, check_entries): 212 """ check_entries is a list of (name, accumulated_period, period). 213 This function checks for each entry, if the line containing [name] 214 has at least required accumulated_period and period. 215 """ 216 self.check_exist(file=summary_file) 217 with open(summary_file, 'r') as fh: 218 summary = fh.read() 219 fulfilled = [False for x in check_entries] 220 if not hasattr(self, "summary_check_re"): 221 self.summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%') 222 for line in summary.split('\n'): 223 for i in range(len(check_entries)): 224 (name, need_acc_period, need_period) = check_entries[i] 225 if not fulfilled[i] and name in line: 226 m = self.summary_check_re.search(line) 227 if m: 228 acc_period = float(m.group(1)) 229 period = float(m.group(2)) 230 if acc_period >= need_acc_period and period >= need_period: 231 fulfilled[i] = True 232 self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled) 233 234 def check_inferno_report_html(self, check_entries, file="report.html"): 235 self.check_exist(file=file) 236 with open(file, 'r') as fh: 237 data = fh.read() 238 fulfilled = [False for _ in check_entries] 239 for line in data.split('\n'): 240 # each entry is a (function_name, min_percentage) pair. 241 for i, entry in enumerate(check_entries): 242 if fulfilled[i] or line.find(entry[0]) == -1: 243 continue 244 m = re.search(r'(\d+\.\d+)%', line) 245 if m and float(m.group(1)) >= entry[1]: 246 fulfilled[i] = True 247 break 248 self.assertEqual(fulfilled, [True for x in check_entries]) 249 250 def common_test_app_profiler(self): 251 self.run_cmd(["app_profiler.py", "-h"]) 252 remove("binary_cache") 253 self.run_app_profiler(build_binary_cache=False) 254 self.assertFalse(os.path.isdir("binary_cache")) 255 args = ["binary_cache_builder.py"] 256 if not self.adb_root: 257 args.append("--disable_adb_root") 258 self.run_cmd(args) 259 self.check_exist(dir="binary_cache") 260 remove("binary_cache") 261 self.run_app_profiler(build_binary_cache=True) 262 self.run_app_profiler(skip_compile=True) 263 self.run_app_profiler(start_activity=False) 264 265 def common_test_report(self): 266 self.run_cmd(["report.py", "-h"]) 267 self.run_app_profiler(build_binary_cache=False) 268 self.run_cmd(["report.py"]) 269 self.run_cmd(["report.py", "-i", "perf.data"]) 270 self.run_cmd(["report.py", "-g"]) 271 self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"]) 272 273 def common_test_annotate(self): 274 self.run_cmd(["annotate.py", "-h"]) 275 self.run_app_profiler() 276 remove("annotated_files") 277 self.run_cmd(["annotate.py", "-s", self.example_path]) 278 self.check_exist(dir="annotated_files") 279 280 def common_test_report_sample(self, check_strings): 281 self.run_cmd(["report_sample.py", "-h"]) 282 remove("binary_cache") 283 self.run_app_profiler(build_binary_cache=False) 284 self.run_cmd(["report_sample.py"]) 285 output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True) 286 self.check_strings_in_content(output, check_strings) 287 self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --no-dump-symbols") 288 output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True) 289 self.check_strings_in_content(output, check_strings) 290 291 def common_test_pprof_proto_generator(self, check_strings_with_lines, 292 check_strings_without_lines): 293 if not has_google_protobuf: 294 log_info('Skip test for pprof_proto_generator because google.protobuf is missing') 295 return 296 self.run_cmd(["pprof_proto_generator.py", "-h"]) 297 self.run_app_profiler() 298 self.run_cmd(["pprof_proto_generator.py"]) 299 remove("pprof.profile") 300 self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"]) 301 self.check_exist(file="pprof.profile") 302 self.run_cmd(["pprof_proto_generator.py", "--show"]) 303 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 304 return_output=True) 305 self.check_strings_in_content(output, check_strings_with_lines + 306 ["has_line_numbers: True"]) 307 remove("binary_cache") 308 self.run_cmd(["pprof_proto_generator.py"]) 309 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 310 return_output=True) 311 self.check_strings_in_content(output, check_strings_without_lines + 312 ["has_line_numbers: False"]) 313 314 def common_test_inferno(self): 315 self.run_cmd([inferno_script, "-h"]) 316 remove("perf.data") 317 append_args = [] if self.adb_root else ["--disable_adb_root"] 318 self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args) 319 self.check_exist(file="perf.data") 320 self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1", 321 "-nc"] + append_args) 322 self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles", 323 "-t", "1", "-nc"] + append_args) 324 self.run_cmd([inferno_script, "-sc"]) 325 326 def common_test_report_html(self): 327 self.run_cmd(['report_html.py', '-h']) 328 self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') 329 self.run_cmd(['report_html.py']) 330 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata']) 331 self.run_cmd(['report_html.py', '--add_disassembly']) 332 # Test with multiple perf.data. 333 shutil.move('perf.data', 'perf2.data') 334 self.run_app_profiler() 335 self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) 336 remove('perf2.data') 337 338 339class TestExamplePureJava(TestExampleBase): 340 @classmethod 341 def setUpClass(cls): 342 cls.prepare("SimpleperfExamplePureJava", 343 "com.example.simpleperf.simpleperfexamplepurejava", 344 ".MainActivity") 345 346 def test_app_profiler(self): 347 self.common_test_app_profiler() 348 349 def test_app_profiler_profile_from_launch(self): 350 self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) 351 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 352 self.check_strings_in_file("report.txt", 353 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()", 354 "__start_thread"]) 355 356 def test_app_profiler_multiprocesses(self): 357 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 358 self.adb.check_run(['shell', 'am', 'start', '-n', 359 self.package_name + '/.MultiProcessActivity']) 360 # Wait until both MultiProcessActivity and MultiProcessService set up. 361 time.sleep(3) 362 self.run_app_profiler(skip_compile=True, start_activity=False) 363 self.run_cmd(["report.py", "-o", "report.txt"]) 364 self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"]) 365 366 def test_app_profiler_with_ctrl_c(self): 367 if is_windows(): 368 return 369 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 370 time.sleep(1) 371 args = [sys.executable, "app_profiler.py", "--app", self.package_name, 372 "-r", "--duration 10000", "-nc", "--disable_adb_root"] 373 subproc = subprocess.Popen(args) 374 time.sleep(3) 375 376 subproc.send_signal(signal.SIGINT) 377 subproc.wait() 378 self.assertEqual(subproc.returncode, 0) 379 self.run_cmd(["report.py"]) 380 381 def test_app_profiler_stop_after_app_exit(self): 382 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 383 time.sleep(1) 384 subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name, 385 '-r', '--duration 10000', '-nc', '--disable_adb_root']) 386 time.sleep(3) 387 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 388 subproc.wait() 389 self.assertEqual(subproc.returncode, 0) 390 self.run_cmd(["report.py"]) 391 392 def test_report(self): 393 self.common_test_report() 394 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 395 self.check_strings_in_file("report.txt", 396 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()", 397 "__start_thread"]) 398 399 def test_annotate(self): 400 self.common_test_annotate() 401 self.check_file_under_dir("annotated_files", "MainActivity.java") 402 summary_file = os.path.join("annotated_files", "summary") 403 self.check_annotation_summary(summary_file, 404 [("MainActivity.java", 80, 80), 405 ("run", 80, 0), 406 ("callFunction", 0, 0), 407 ("line 23", 80, 0)]) 408 409 def test_report_sample(self): 410 self.common_test_report_sample( 411 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()", 412 "__start_thread"]) 413 414 def test_pprof_proto_generator(self): 415 self.common_test_pprof_proto_generator( 416 check_strings_with_lines= 417 ["com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java", 418 "run"], 419 check_strings_without_lines= 420 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"]) 421 422 def test_inferno(self): 423 self.common_test_inferno() 424 self.run_app_profiler() 425 self.run_cmd([inferno_script, "-sc"]) 426 self.check_inferno_report_html( 427 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)]) 428 self.run_cmd([inferno_script, "-sc", "-o", "report2.html"]) 429 self.check_inferno_report_html( 430 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)], 431 "report2.html") 432 remove("report2.html") 433 434 def test_inferno_in_another_dir(self): 435 test_dir = 'inferno_testdir' 436 saved_dir = os.getcwd() 437 remove(test_dir) 438 os.mkdir(test_dir) 439 os.chdir(test_dir) 440 self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'), 441 '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3']) 442 self.check_exist(file="perf.data") 443 self.run_cmd([inferno_script, "-sc"]) 444 os.chdir(saved_dir) 445 remove(test_dir) 446 447 def test_report_html(self): 448 self.common_test_report_html() 449 450 451class TestExamplePureJavaRoot(TestExampleBase): 452 @classmethod 453 def setUpClass(cls): 454 cls.prepare("SimpleperfExamplePureJava", 455 "com.example.simpleperf.simpleperfexamplepurejava", 456 ".MainActivity", 457 adb_root=True) 458 459 def test_app_profiler(self): 460 self.common_test_app_profiler() 461 462 463class TestExamplePureJavaTraceOffCpu(TestExampleBase): 464 @classmethod 465 def setUpClass(cls): 466 cls.prepare("SimpleperfExamplePureJava", 467 "com.example.simpleperf.simpleperfexamplepurejava", 468 ".SleepActivity") 469 470 def test_smoke(self): 471 self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") 472 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 473 self.check_strings_in_file("report.txt", 474 ["com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run()", 475 "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()", 476 "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)" 477 ]) 478 remove("annotated_files") 479 self.run_cmd(["annotate.py", "-s", self.example_path]) 480 self.check_exist(dir="annotated_files") 481 self.check_file_under_dir("annotated_files", "SleepActivity.java") 482 summary_file = os.path.join("annotated_files", "summary") 483 self.check_annotation_summary(summary_file, 484 [("SleepActivity.java", 80, 20), 485 ("run", 80, 0), 486 ("RunFunction", 20, 20), 487 ("SleepFunction", 20, 0), 488 ("line 24", 20, 0), 489 ("line 32", 20, 0)]) 490 self.run_cmd([inferno_script, "-sc"]) 491 self.check_inferno_report_html( 492 [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80), 493 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()', 494 20), 495 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)', 496 20)]) 497 498 499class TestExampleWithNative(TestExampleBase): 500 @classmethod 501 def setUpClass(cls): 502 cls.prepare("SimpleperfExampleWithNative", 503 "com.example.simpleperf.simpleperfexamplewithnative", 504 ".MainActivity") 505 506 def test_app_profiler(self): 507 self.common_test_app_profiler() 508 remove("binary_cache") 509 self.run_app_profiler(native_lib_dir=self.example_path) 510 511 def test_app_profiler_profile_from_launch(self): 512 self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) 513 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 514 self.check_strings_in_file("report.txt", 515 ["BusyLoopThread", 516 "__start_thread"]) 517 518 def test_report(self): 519 self.common_test_report() 520 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 521 self.check_strings_in_file("report.txt", 522 ["BusyLoopThread", 523 "__start_thread"]) 524 525 def test_annotate(self): 526 self.common_test_annotate() 527 self.check_file_under_dir("annotated_files", "native-lib.cpp") 528 summary_file = os.path.join("annotated_files", "summary") 529 self.check_annotation_summary(summary_file, 530 [("native-lib.cpp", 20, 0), 531 ("BusyLoopThread", 20, 0), 532 ("line 46", 20, 0)]) 533 534 def test_report_sample(self): 535 self.common_test_report_sample( 536 ["BusyLoopThread", 537 "__start_thread"]) 538 539 def test_pprof_proto_generator(self): 540 self.common_test_pprof_proto_generator( 541 check_strings_with_lines= 542 ["native-lib.cpp", 543 "BusyLoopThread"], 544 check_strings_without_lines= 545 ["BusyLoopThread"]) 546 547 def test_inferno(self): 548 self.common_test_inferno() 549 self.run_app_profiler() 550 self.run_cmd([inferno_script, "-sc"]) 551 self.check_inferno_report_html([('BusyLoopThread', 20)]) 552 553 def test_report_html(self): 554 self.common_test_report_html() 555 556 557class TestExampleWithNativeRoot(TestExampleBase): 558 @classmethod 559 def setUpClass(cls): 560 cls.prepare("SimpleperfExampleWithNative", 561 "com.example.simpleperf.simpleperfexamplewithnative", 562 ".MainActivity", 563 adb_root=True) 564 565 def test_app_profiler(self): 566 self.common_test_app_profiler() 567 remove("binary_cache") 568 self.run_app_profiler(native_lib_dir=self.example_path) 569 570 571class TestExampleWithNativeTraceOffCpu(TestExampleBase): 572 @classmethod 573 def setUpClass(cls): 574 cls.prepare("SimpleperfExampleWithNative", 575 "com.example.simpleperf.simpleperfexamplewithnative", 576 ".SleepActivity") 577 578 def test_smoke(self): 579 self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") 580 self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"]) 581 self.check_strings_in_file("report.txt", 582 ["SleepThread(void*)", 583 "RunFunction()", 584 "SleepFunction(unsigned long long)"]) 585 remove("annotated_files") 586 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) 587 self.check_exist(dir="annotated_files") 588 self.check_file_under_dir("annotated_files", "native-lib.cpp") 589 summary_file = os.path.join("annotated_files", "summary") 590 self.check_annotation_summary(summary_file, 591 [("native-lib.cpp", 80, 20), 592 ("SleepThread", 80, 0), 593 ("RunFunction", 20, 20), 594 ("SleepFunction", 20, 0), 595 ("line 73", 20, 0), 596 ("line 83", 20, 0)]) 597 self.run_cmd([inferno_script, "-sc"]) 598 self.check_inferno_report_html([('SleepThread', 80), 599 ('RunFunction', 20), 600 ('SleepFunction', 20)]) 601 602 603class TestExampleWithNativeJniCall(TestExampleBase): 604 @classmethod 605 def setUpClass(cls): 606 cls.prepare("SimpleperfExampleWithNative", 607 "com.example.simpleperf.simpleperfexamplewithnative", 608 ".MixActivity") 609 610 def test_smoke(self): 611 self.run_app_profiler() 612 self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) 613 self.check_strings_in_file("report.txt", 614 ["void com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run()", 615 "int com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction(int)", 616 "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) 617 remove("annotated_files") 618 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) 619 self.check_exist(dir="annotated_files") 620 self.check_file_under_dir("annotated_files", "native-lib.cpp") 621 self.check_file_under_dir("annotated_files", "MixActivity.java") 622 summary_file = os.path.join("annotated_files", "summary") 623 self.check_annotation_summary(summary_file, 624 [("MixActivity.java", 80, 0), 625 ("run", 80, 0), 626 ("line 26", 20, 0), 627 ("native-lib.cpp", 5, 0), 628 ("line 40", 5, 0)]) 629 self.run_cmd([inferno_script, "-sc"]) 630 631 632class TestExampleWithNativeForceArm(TestExampleWithNative): 633 @classmethod 634 def setUpClass(cls): 635 cls.prepare("SimpleperfExampleWithNative", 636 "com.example.simpleperf.simpleperfexamplewithnative", 637 ".MainActivity", 638 abi="armeabi-v7a") 639 640 641class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot): 642 @classmethod 643 def setUpClass(cls): 644 cls.prepare("SimpleperfExampleWithNative", 645 "com.example.simpleperf.simpleperfexamplewithnative", 646 ".MainActivity", 647 abi="armeabi-v7a", 648 adb_root=False) 649 650 651class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu): 652 @classmethod 653 def setUpClass(cls): 654 cls.prepare("SimpleperfExampleWithNative", 655 "com.example.simpleperf.simpleperfexamplewithnative", 656 ".SleepActivity", 657 abi="armeabi-v7a") 658 659 660class TestExampleOfKotlin(TestExampleBase): 661 @classmethod 662 def setUpClass(cls): 663 cls.prepare("SimpleperfExampleOfKotlin", 664 "com.example.simpleperf.simpleperfexampleofkotlin", 665 ".MainActivity") 666 667 def test_app_profiler(self): 668 self.common_test_app_profiler() 669 670 def test_app_profiler_profile_from_launch(self): 671 self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False) 672 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 673 self.check_strings_in_file("report.txt", 674 ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()", 675 "__start_thread"]) 676 677 def test_report(self): 678 self.common_test_report() 679 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 680 self.check_strings_in_file("report.txt", 681 ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()", 682 "__start_thread"]) 683 684 def test_annotate(self): 685 self.common_test_annotate() 686 self.check_file_under_dir("annotated_files", "MainActivity.kt") 687 summary_file = os.path.join("annotated_files", "summary") 688 self.check_annotation_summary(summary_file, 689 [("MainActivity.kt", 80, 80), 690 ("run", 80, 0), 691 ("callFunction", 0, 0), 692 ("line 19", 80, 0), 693 ("line 25", 0, 0)]) 694 695 def test_report_sample(self): 696 self.common_test_report_sample( 697 ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()", 698 "__start_thread"]) 699 700 def test_pprof_proto_generator(self): 701 self.common_test_pprof_proto_generator( 702 check_strings_with_lines= 703 ["com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt", 704 "run"], 705 check_strings_without_lines= 706 ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"]) 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( 713 [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()', 714 80)]) 715 716 def test_report_html(self): 717 self.common_test_report_html() 718 719 720class TestExampleOfKotlinRoot(TestExampleBase): 721 @classmethod 722 def setUpClass(cls): 723 cls.prepare("SimpleperfExampleOfKotlin", 724 "com.example.simpleperf.simpleperfexampleofkotlin", 725 ".MainActivity", 726 adb_root=True) 727 728 def test_app_profiler(self): 729 self.common_test_app_profiler() 730 731 732class TestExampleOfKotlinTraceOffCpu(TestExampleBase): 733 @classmethod 734 def setUpClass(cls): 735 cls.prepare("SimpleperfExampleOfKotlin", 736 "com.example.simpleperf.simpleperfexampleofkotlin", 737 ".SleepActivity") 738 739 def test_smoke(self): 740 self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu") 741 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 742 self.check_strings_in_file("report.txt", 743 ["void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()", 744 "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()", 745 "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)" 746 ]) 747 remove("annotated_files") 748 self.run_cmd(["annotate.py", "-s", self.example_path]) 749 self.check_exist(dir="annotated_files") 750 self.check_file_under_dir("annotated_files", "SleepActivity.kt") 751 summary_file = os.path.join("annotated_files", "summary") 752 self.check_annotation_summary(summary_file, 753 [("SleepActivity.kt", 80, 20), 754 ("run", 80, 0), 755 ("RunFunction", 20, 20), 756 ("SleepFunction", 20, 0), 757 ("line 24", 20, 0), 758 ("line 32", 20, 0)]) 759 self.run_cmd([inferno_script, "-sc"]) 760 self.check_inferno_report_html( 761 [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()', 762 80), 763 ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()', 764 20), 765 ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)', 766 20)]) 767 768 769class TestProfilingNativeProgram(TestExampleBase): 770 def test_smoke(self): 771 if not AdbHelper().switch_to_root(): 772 log_info('skip TestProfilingNativeProgram on non-rooted devices.') 773 return 774 remove("perf.data") 775 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger", 776 "-r", "-g -f 1000 --duration 3 -e cpu-cycles:u"]) 777 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 778 779 780class TestProfilingCmd(TestExampleBase): 781 def test_smoke(self): 782 remove("perf.data") 783 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) 784 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 785 786 def test_set_arch(self): 787 arch = AdbHelper().get_device_arch() 788 remove("perf.data") 789 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--arch", arch]) 790 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 791 792 793class TestProfilingNativeProgram(TestExampleBase): 794 def test_smoke(self): 795 adb = AdbHelper() 796 if adb.switch_to_root(): 797 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) 798 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 799 self.run_cmd([inferno_script, "-sc"]) 800 self.run_cmd([inferno_script, "-np", "surfaceflinger"]) 801 802 803class TestReportLib(unittest.TestCase): 804 def setUp(self): 805 self.report_lib = ReportLib() 806 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) 807 808 def tearDown(self): 809 self.report_lib.Close() 810 811 def test_build_id(self): 812 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 813 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 814 815 def test_symbol(self): 816 found_func2 = False 817 while self.report_lib.GetNextSample(): 818 sample = self.report_lib.GetCurrentSample() 819 symbol = self.report_lib.GetSymbolOfCurrentSample() 820 if symbol.symbol_name == 'func2(int, int)': 821 found_func2 = True 822 self.assertEqual(symbol.symbol_addr, 0x4004ed) 823 self.assertEqual(symbol.symbol_len, 0x14) 824 self.assertTrue(found_func2) 825 826 def test_sample(self): 827 found_sample = False 828 while self.report_lib.GetNextSample(): 829 sample = self.report_lib.GetCurrentSample() 830 if sample.ip == 0x4004ff and sample.time == 7637889424953: 831 found_sample = True 832 self.assertEqual(sample.pid, 15926) 833 self.assertEqual(sample.tid, 15926) 834 self.assertEqual(sample.thread_comm, 't2') 835 self.assertEqual(sample.cpu, 5) 836 self.assertEqual(sample.period, 694614) 837 event = self.report_lib.GetEventOfCurrentSample() 838 self.assertEqual(event.name, 'cpu-cycles') 839 callchain = self.report_lib.GetCallChainOfCurrentSample() 840 self.assertEqual(callchain.nr, 0) 841 self.assertTrue(found_sample) 842 843 def test_meta_info(self): 844 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 845 meta_info = self.report_lib.MetaInfo() 846 self.assertTrue("simpleperf_version" in meta_info) 847 self.assertEqual(meta_info["system_wide_collection"], "false") 848 self.assertEqual(meta_info["trace_offcpu"], "true") 849 self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") 850 self.assertTrue("product_props" in meta_info) 851 852 def test_event_name_from_meta_info(self): 853 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) 854 event_names = set() 855 while self.report_lib.GetNextSample(): 856 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 857 self.assertTrue('sched:sched_switch' in event_names) 858 self.assertTrue('cpu-cycles' in event_names) 859 860 def test_record_cmd(self): 861 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 862 self.assertEqual(self.report_lib.GetRecordCmd(), 863 "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64") 864 865 def test_offcpu(self): 866 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 867 total_period = 0 868 sleep_function_period = 0 869 sleep_function_name = "SleepFunction(unsigned long long)" 870 while self.report_lib.GetNextSample(): 871 sample = self.report_lib.GetCurrentSample() 872 total_period += sample.period 873 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 874 sleep_function_period += sample.period 875 continue 876 callchain = self.report_lib.GetCallChainOfCurrentSample() 877 for i in range(callchain.nr): 878 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 879 sleep_function_period += sample.period 880 break 881 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles') 882 sleep_percentage = float(sleep_function_period) / total_period 883 self.assertGreater(sleep_percentage, 0.30) 884 885 886class TestRunSimpleperfOnDevice(TestBase): 887 def test_smoke(self): 888 self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) 889 890 891class TestTools(unittest.TestCase): 892 def test_addr2nearestline(self): 893 binary_cache_path = 'testdata' 894 test_map = { 895 '/simpleperf_runtest_two_functions_arm64': [ 896 { 897 'func_addr': 0x668, 898 'addr': 0x668, 899 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20', 900 }, 901 { 902 'func_addr': 0x668, 903 'addr': 0x6a4, 904 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 905 system/extras/simpleperf/runtest/two_functions.cpp:22""", 906 }, 907 ], 908 '/simpleperf_runtest_two_functions_arm': [ 909 { 910 'func_addr': 0x784, 911 'addr': 0x7b0, 912 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 913 system/extras/simpleperf/runtest/two_functions.cpp:23""", 914 }, 915 { 916 'func_addr': 0x784, 917 'addr': 0x7d0, 918 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 919 system/extras/simpleperf/runtest/two_functions.cpp:23""", 920 } 921 ], 922 '/simpleperf_runtest_two_functions_x86_64': [ 923 { 924 'func_addr': 0x840, 925 'addr': 0x840, 926 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', 927 }, 928 { 929 'func_addr': 0x920, 930 'addr': 0x94a, 931 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 932 system/extras/simpleperf/runtest/two_functions.cpp:22""", 933 } 934 ], 935 '/simpleperf_runtest_two_functions_x86': [ 936 { 937 'func_addr': 0x6d0, 938 'addr': 0x6da, 939 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', 940 }, 941 { 942 'func_addr': 0x710, 943 'addr': 0x749, 944 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 945 system/extras/simpleperf/runtest/two_functions.cpp:22""", 946 } 947 ], 948 } 949 addr2line = Addr2Nearestline(None, binary_cache_path) 950 for dso_path in test_map: 951 test_addrs = test_map[dso_path] 952 for test_addr in test_addrs: 953 addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr']) 954 addr2line.convert_addrs_to_lines() 955 for dso_path in test_map: 956 dso = addr2line.get_dso(dso_path) 957 self.assertTrue(dso is not None) 958 test_addrs = test_map[dso_path] 959 for test_addr in test_addrs: 960 source_str = test_addr['source'] 961 expected_source = [] 962 for line in source_str.split('\n'): 963 items = line.split(':') 964 expected_source.append((items[0].strip(), int(items[1]))) 965 actual_source = addr2line.get_addr_source(dso, test_addr['addr']) 966 self.assertTrue(actual_source is not None) 967 self.assertEqual(len(actual_source), len(expected_source)) 968 for i in range(len(expected_source)): 969 actual_file_path, actual_line = actual_source[i] 970 self.assertEqual(actual_file_path, expected_source[i][0]) 971 self.assertEqual(actual_line, expected_source[i][1]) 972 973 def test_objdump(self): 974 binary_cache_path = 'testdata' 975 test_map = { 976 '/simpleperf_runtest_two_functions_arm64': { 977 'start_addr': 0x668, 978 'len': 116, 979 'expected_items': [ 980 ('main():', 0), 981 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 982 (' 694: add x20, x20, #0x6de', 0x694), 983 ], 984 }, 985 '/simpleperf_runtest_two_functions_arm': { 986 'start_addr': 0x784, 987 'len': 80, 988 'expected_items': [ 989 ('main():', 0), 990 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 991 (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae), 992 ], 993 }, 994 '/simpleperf_runtest_two_functions_x86_64': { 995 'start_addr': 0x920, 996 'len': 201, 997 'expected_items': [ 998 ('main():', 0), 999 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1000 (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e), 1001 ], 1002 }, 1003 '/simpleperf_runtest_two_functions_x86': { 1004 'start_addr': 0x710, 1005 'len': 98, 1006 'expected_items': [ 1007 ('main():', 0), 1008 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1009 (' 748: cmp $0x5f5e100,%ebp', 0x748), 1010 ], 1011 }, 1012 } 1013 objdump = Objdump(None, binary_cache_path) 1014 for dso_path in test_map: 1015 dso_info = test_map[dso_path] 1016 disassemble_code = objdump.disassemble_code(dso_path, dso_info['start_addr'], 1017 dso_info['len']) 1018 self.assertTrue(disassemble_code) 1019 for item in dso_info['expected_items']: 1020 self.assertTrue(item in disassemble_code) 1021 1022 1023def main(): 1024 os.chdir(get_script_dir()) 1025 build_testdata() 1026 if AdbHelper().get_android_version() < 7: 1027 log_info("Skip tests on Android version < N.") 1028 sys.exit(0) 1029 unittest.main(failfast=True) 1030 1031if __name__ == '__main__': 1032 main() 1033