• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2019 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
18"""
19    This script is part of controlling simpleperf recording in user code. It is used to prepare
20    profiling environment (upload simpleperf to device and enable profiling) before recording
21    and collect recording data on host after recording.
22    Controlling simpleperf recording is done in below steps:
23    1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code.
24    2. Run `api_profiler.py prepare` to prepare profiling environment.
25    3. Run the app one or more times to generate recording data.
26    4. Run `api_profiler.py collect` to collect recording data on host.
27"""
28
29from argparse import Namespace
30import logging
31import os
32import os.path
33import shutil
34import zipfile
35
36from simpleperf_utils import (AdbHelper, BaseArgumentParser,
37                              get_target_binary_path, log_exit, remove)
38
39
40class ApiProfiler:
41    def __init__(self, args: Namespace):
42        self.args = args
43        self.adb = AdbHelper()
44
45    def prepare_recording(self):
46        self.enable_profiling_on_device()
47        self.upload_simpleperf_to_device()
48        self.run_simpleperf_prepare_cmd()
49
50    def enable_profiling_on_device(self):
51        android_version = self.adb.get_android_version()
52        if android_version >= 10:
53            self.adb.set_property('debug.perf_event_max_sample_rate',
54                                  str(self.args.max_sample_rate))
55            self.adb.set_property('debug.perf_cpu_time_max_percent', str(self.args.max_cpu_percent))
56            self.adb.set_property('debug.perf_event_mlock_kb', str(self.args.max_memory_in_kb))
57        self.adb.set_property('security.perf_harden', '0')
58
59    def upload_simpleperf_to_device(self):
60        device_arch = self.adb.get_device_arch()
61        simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
62        self.adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
63        self.adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
64
65    def run_simpleperf_prepare_cmd(self):
66        cmd_args = ['shell', '/data/local/tmp/simpleperf', 'api-prepare', '--app', self.args.app]
67        if self.args.days:
68            cmd_args += ['--days', str(self.args.days)]
69        self.adb.check_run(cmd_args)
70
71    def collect_data(self):
72        if not os.path.isdir(self.args.out_dir):
73            os.makedirs(self.args.out_dir)
74        self.download_recording_data()
75        self.unzip_recording_data()
76
77    def download_recording_data(self):
78        """ download recording data to simpleperf_data.zip."""
79        self.upload_simpleperf_to_device()
80        self.adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-collect',
81                            '--app', self.args.app, '-o', '/data/local/tmp/simpleperf_data.zip'])
82        self.adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', self.args.out_dir])
83        self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
84
85    def unzip_recording_data(self):
86        zip_file_path = os.path.join(self.args.out_dir, 'simpleperf_data.zip')
87        with zipfile.ZipFile(zip_file_path, 'r') as zip_fh:
88            names = zip_fh.namelist()
89            logging.info('There are %d recording data files.' % len(names))
90            for name in names:
91                logging.info('recording file: %s' % os.path.join(self.args.out_dir, name))
92                zip_fh.extract(name, self.args.out_dir)
93        remove(zip_file_path)
94
95
96def main():
97    parser = BaseArgumentParser(description=__doc__)
98    subparsers = parser.add_subparsers(title='actions', dest='command')
99
100    prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.')
101    prepare_parser.add_argument('-p', '--app', required=True, help="""
102                                The app package name of the app profiled.""")
103    prepare_parser.add_argument('-d', '--days', type=int, help="""
104                                By default, the recording permission is reset after device reboot.
105                                But on Android >= 13, we can use --days to set how long we want the
106                                permission to persist. It can last after device reboot.
107                                """)
108    prepare_parser.add_argument('--max-sample-rate', type=int, default=100000, help="""
109                                Set max sample rate (only on Android >= Q).""")
110    prepare_parser.add_argument('--max-cpu-percent', type=int, default=25, help="""
111                                Set max cpu percent for recording (only on Android >= Q).""")
112    prepare_parser.add_argument('--max-memory-in-kb', type=int,
113                                default=(1024 + 1) * 4 * 8, help="""
114                                Set max kernel buffer size for recording (only on Android >= Q).
115                                """)
116
117    collect_parser = subparsers.add_parser('collect', help='Collect recording data.')
118    collect_parser.add_argument('-p', '--app', required=True, help="""
119                                The app package name of the app profiled.""")
120    collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help="""
121                                The directory to store recording data.""")
122    args = parser.parse_args()
123
124    if args.command == 'prepare':
125        ApiProfiler(args).prepare_recording()
126    elif args.command == 'collect':
127        ApiProfiler(args).collect_data()
128
129
130if __name__ == '__main__':
131    main()
132