1#!/usr/bin/env python3 2# 3# Copyright (C) 2021 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 17from app_profiler import NativeLibDownloader 18import shutil 19import subprocess 20import sys 21 22from simpleperf_utils import str_to_bytes, bytes_to_str, remove 23from . test_utils import TestBase, TestHelper, INFERNO_SCRIPT 24 25 26class TestNativeProfiling(TestBase): 27 def setUp(self): 28 super(TestNativeProfiling, self).setUp() 29 self.is_rooted_device = TestHelper.adb.switch_to_root() 30 31 def test_profile_cmd(self): 32 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) 33 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 34 35 def test_profile_native_program(self): 36 if not self.is_rooted_device: 37 return 38 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) 39 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 40 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 41 self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"]) 42 43 def test_profile_pids(self): 44 if not self.is_rooted_device: 45 return 46 pid = int(TestHelper.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) 47 self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) 48 self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) 49 self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) 50 self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1']) 51 self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1']) 52 53 def test_profile_system_wide(self): 54 if not self.is_rooted_device: 55 return 56 self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1']) 57 58 def test_device_not_connected(self): 59 args = [sys.executable, TestHelper.script_path('app_profiler.py'), '-cmd', 'ls'] 60 proc = subprocess.run( 61 args, env={'ANDROID_SERIAL': 'not_exist_device'}, 62 stderr=subprocess.PIPE, text=True) 63 self.assertIn('No Android device is connected via ADB.', proc.stderr) 64 65 def test_android_version(self): 66 self.assertGreaterEqual(TestHelper.adb.get_android_version(), 9) 67 68 69class TestNativeLibDownloader(TestBase): 70 def setUp(self): 71 super(TestNativeLibDownloader, self).setUp() 72 self.adb = TestHelper.adb 73 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 74 self.ndk_path = TestHelper.ndk_path 75 76 def tearDown(self): 77 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 78 super(TestNativeLibDownloader, self).tearDown() 79 80 def list_lib_on_device(self, path): 81 result, output = self.adb.run_and_return_output(['shell', 'ls', '-llc', path]) 82 return output if result else '' 83 84 def test_smoke(self): 85 # Sync all native libs on device. 86 downloader = NativeLibDownloader(self.ndk_path, 'arm64', self.adb) 87 downloader.collect_native_libs_on_host(TestHelper.testdata_path( 88 'SimpleperfExampleCpp/app/build/intermediates/cmake/debug')) 89 self.assertEqual(len(downloader.host_build_id_map), 2) 90 for entry in downloader.host_build_id_map.values(): 91 self.assertEqual(entry.score, 3) 92 downloader.collect_native_libs_on_device() 93 self.assertEqual(len(downloader.device_build_id_map), 0) 94 95 lib_list = list(downloader.host_build_id_map.items()) 96 for sync_count in [0, 1, 2]: 97 build_id_map = {} 98 for i in range(sync_count): 99 build_id_map[lib_list[i][0]] = lib_list[i][1] 100 downloader.host_build_id_map = build_id_map 101 downloader.sync_native_libs_on_device() 102 downloader.collect_native_libs_on_device() 103 self.assertEqual(len(downloader.device_build_id_map), sync_count) 104 for i, item in enumerate(lib_list): 105 build_id = item[0] 106 name = item[1].name 107 if i < sync_count: 108 self.assertTrue(build_id in downloader.device_build_id_map) 109 self.assertEqual(name, downloader.device_build_id_map[build_id]) 110 self.assertTrue(self.list_lib_on_device(downloader.dir_on_device + name)) 111 else: 112 self.assertTrue(build_id not in downloader.device_build_id_map) 113 self.assertFalse(self.list_lib_on_device(downloader.dir_on_device + name)) 114 if sync_count == 1: 115 self.adb.run(['pull', '/data/local/tmp/native_libs/build_id_list', 116 'build_id_list']) 117 with open('build_id_list', 'rb') as fh: 118 self.assertEqual(bytes_to_str(fh.read()), 119 '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name)) 120 remove('build_id_list') 121 122 def test_handle_wrong_build_id_list(self): 123 with open('build_id_list', 'wb') as fh: 124 fh.write(str_to_bytes('fake_build_id=binary_not_exist\n')) 125 self.adb.check_run(['shell', 'mkdir', '-p', '/data/local/tmp/native_libs']) 126 self.adb.check_run(['push', 'build_id_list', '/data/local/tmp/native_libs']) 127 remove('build_id_list') 128 downloader = NativeLibDownloader(self.ndk_path, 'arm64', self.adb) 129 downloader.collect_native_libs_on_device() 130 self.assertEqual(len(downloader.device_build_id_map), 0) 131 132 def test_download_file_without_build_id(self): 133 downloader = NativeLibDownloader(self.ndk_path, 'x86_64', self.adb) 134 name = 'elf.so' 135 shutil.copyfile(TestHelper.testdata_path('data/symfs_without_build_id/elf'), name) 136 downloader.collect_native_libs_on_host('.') 137 downloader.collect_native_libs_on_device() 138 self.assertIn(name, downloader.no_build_id_file_map) 139 # Check if file without build id can be downloaded. 140 downloader.sync_native_libs_on_device() 141 target_file = downloader.dir_on_device + name 142 target_file_stat = self.list_lib_on_device(target_file) 143 self.assertTrue(target_file_stat) 144 145 # No need to re-download if file size doesn't change. 146 downloader.sync_native_libs_on_device() 147 self.assertEqual(target_file_stat, self.list_lib_on_device(target_file)) 148 149 # Need to re-download if file size changes. 150 self.adb.check_run(['shell', 'truncate', '-s', '0', target_file]) 151 target_file_stat = self.list_lib_on_device(target_file) 152 downloader.sync_native_libs_on_device() 153 self.assertNotEqual(target_file_stat, self.list_lib_on_device(target_file)) 154