xref: /aosp_15_r20/system/extras/simpleperf/scripts/test/app_profiler_test.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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