blob: 0b36b64687d26eb885a4d5950d405288df041c7b [file] [log] [blame]
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +01001#!/usr/bin/env python3
2# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
6import argparse
7import os
8import sys
9import time
10
11try:
12 from perfetto.trace_processor import TraceProcessor
13except ImportError:
14 sys.exit(
15 'Unable to analyze perfetto trace without the perfetto library. '
16 'Install instructions:\n'
17 ' sudo apt install python3-pip\n'
18 ' pip3 install perfetto')
19
20sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21
22import adb_utils
23import perfetto_utils
24import utils
25
26def setup(options):
27 # Increase screen off timeout to avoid device screen turns off.
28 twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
29 previous_screen_off_timeout = adb_utils.get_screen_off_timeout(
30 options.device_id)
31 adb_utils.set_screen_off_timeout(
32 twenty_four_hours_in_millis, options.device_id)
33
34 # Unlock device.
35 adb_utils.unlock(options.device_id, options.device_pin)
36
37 tear_down_options = {
38 'previous_screen_off_timeout': previous_screen_off_timeout
39 }
40 return tear_down_options
41
42def tear_down(options, tear_down_options):
43 # Reset screen off timeout.
44 adb_utils.set_screen_off_timeout(
45 tear_down_options['previous_screen_off_timeout'],
46 options.device_id)
47
48def run_all(options, tmp_dir):
49 # Launch app while collecting information.
50 data_avg = {}
51 for iteration in range(options.iterations):
52 print('Starting iteration %i' % iteration)
53 out_dir = os.path.join(options.out_dir, str(iteration))
54 prepare_for_run(out_dir, options)
55 data = run(out_dir, options, tmp_dir)
56 add_data(data_avg, data)
57 print("Result:")
58 print(data)
59 print("Done")
60 for key, value in data_avg.items():
61 if isinstance(value, int):
62 data_avg[key] = value / options.iterations
63 print("Average result:")
64 print(data_avg)
65 write_data(options.out_dir, data_avg)
66
67def prepare_for_run(out_dir, options):
68 adb_utils.root(options.device_id)
69 adb_utils.uninstall(options.app_id, options.device_id)
70 adb_utils.install(options.apk, options.device_id)
71 adb_utils.clear_profile_data(options.app_id, options.device_id)
72 if options.aot:
73 adb_utils.force_compilation(options.app_id, options.device_id)
74 elif options.aot_profile:
75 adb_utils.launch_activity(
76 options.app_id, options.main_activity, options.device_id)
77 time.sleep(options.aot_profile_sleep)
78 adb_utils.stop_app(options.app_id, options.device_id)
79 adb_utils.force_profile_compilation(options.app_id, options.device_id)
80
81 adb_utils.drop_caches(options.device_id)
82 os.makedirs(out_dir, exist_ok=True)
83
84def run(out_dir, options, tmp_dir):
85 assert adb_utils.get_screen_state().is_on_and_unlocked()
86
87 # Start perfetto trace collector.
88 perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
89 out_dir, tmp_dir)
90
91 # Launch main activity.
92 adb_utils.launch_activity(
93 options.app_id, options.main_activity, options.device_id)
94
95 # Wait for perfetto trace collector to stop.
96 perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
97
98 # Get minor and major page faults from app process.
99 data = compute_data(perfetto_trace_path, options)
100 write_data(out_dir, data)
101 return data
102
103def add_data(sum_data, data):
104 for key, value in data.items():
105 if key == 'time':
106 continue
107 if hasattr(sum_data, key):
108 if key == 'app_id':
109 assert sum_data[key] == value
110 else:
111 existing_value = sum_data[key]
112 assert isinstance(value, int)
113 assert isinstance(existing_value, int)
114 sum_data[key] = existing_value + value
115 else:
116 sum_data[key] = value
117
118def compute_data(perfetto_trace_path, options):
119 minfl, majfl = adb_utils.get_minor_major_page_faults(
120 options.app_id, options.device_id)
121 data = {
122 'app_id': options.app_id,
123 'time': time.ctime(time.time()),
124 'minfl': minfl,
125 'majfl': majfl
126 }
127 startup_data = compute_startup_data(perfetto_trace_path, options)
128 return data | startup_data
129
130def compute_startup_data(perfetto_trace_path, options):
131 trace_processor = TraceProcessor(file_path=perfetto_trace_path)
132
133 # Compute time to first frame according to the builtin android_startup metric.
134 startup_metric = trace_processor.metric(['android_startup'])
135 time_to_first_frame_ms = \
136 startup_metric.android_startup.startup[0].to_first_frame.dur_ms
137
138 # Compute time to first and last doFrame event.
139 bind_application_slice = perfetto_utils.find_unique_slice_by_name(
140 'bindApplication', options, trace_processor)
141 activity_start_slice = perfetto_utils.find_unique_slice_by_name(
142 'activityStart', options, trace_processor)
143 do_frame_slices = perfetto_utils.find_slices_by_name(
144 'Choreographer#doFrame', options, trace_processor)
145 first_do_frame_slice = next(do_frame_slices)
146 *_, last_do_frame_slice = do_frame_slices
147
148 return {
149 'time_to_first_frame_ms': time_to_first_frame_ms,
150 'time_to_first_choreographer_do_frame_ms':
151 perfetto_utils.get_slice_end_since_start(
152 first_do_frame_slice, bind_application_slice),
153 'time_to_last_choreographer_do_frame_ms':
154 perfetto_utils.get_slice_end_since_start(
155 last_do_frame_slice, bind_application_slice)
156 }
157
158def write_data(out_dir, data):
159 data_path = os.path.join(out_dir, 'data.txt')
160 with open(data_path, 'w') as f:
161 for key, value in data.items():
162 f.write('%s=%s\n' % (key, str(value)))
163
164def parse_options(argv):
165 result = argparse.ArgumentParser(
166 description='Generate a perfetto trace file.')
167 result.add_argument('--app-id',
168 help='The application ID of interest',
169 required=True)
170 result.add_argument('--aot',
171 help='Enable force compilation',
172 default=False,
173 action='store_true')
174 result.add_argument('--aot-profile',
175 help='Enable force compilation using profiles',
176 default=False,
177 action='store_true')
178 result.add_argument('--aot-profile-sleep',
179 help='Duration in seconds before forcing compilation',
180 default=15,
181 type=int)
182 result.add_argument('--apk',
183 help='Path to the APK',
184 required=True)
185 result.add_argument('--device-id',
186 help='Device id (e.g., emulator-5554).')
187 result.add_argument('--device-pin',
188 help='Device pin code (e.g., 1234)')
189 result.add_argument('--iterations',
190 help='Number of traces to generate',
191 default=1,
192 type=int)
193 result.add_argument('--main-activity',
194 help='Main activity class name',
195 required=True)
196 result.add_argument('--out-dir',
197 help='Directory to store trace files in',
198 required=True)
199 options, args = result.parse_known_args(argv)
200 assert (not options.aot) or (not options.aot_profile)
201 return options, args
202
203def main(argv):
204 (options, args) = parse_options(argv)
205 with utils.TempDir() as tmp_dir:
206 tear_down_options = setup(options)
207 run_all(options, tmp_dir)
208 tear_down(options, tear_down_options)
209
210if __name__ == '__main__':
211 sys.exit(main(sys.argv[1:]))