blob: adae2393428c77b7933055376f08a11fcfb37493 [file] [log] [blame]
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +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 adb_utils
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +02007import profile_utils
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +01008
9import argparse
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020010import os
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010011import sys
12import time
13
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020014class Device:
15
16 def __init__(self, device_id, device_pin):
17 self.device_id = device_id
18 self.device_pin = device_pin
19
20def extend_startup_descriptors(startup_descriptors, iteration, device, options):
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020021 (logcat, profile, profile_classes_and_methods) = \
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020022 generate_startup_profile(device, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020023 if options.logcat:
24 write_tmp_logcat(logcat, iteration, options)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020025 current_startup_descriptors = get_r8_startup_descriptors_from_logcat(
26 logcat, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020027 else:
28 write_tmp_profile(profile, iteration, options)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020029 write_tmp_profile_classes_and_methods(
30 profile_classes_and_methods, iteration, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020031 current_startup_descriptors = \
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +020032 profile_utils.transform_art_profile_to_r8_startup_list(
Christoffer Quist Adamsen84da5262022-08-24 13:54:21 +020033 profile_classes_and_methods, options.generalize_synthetics)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020034 write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +020035 new_startup_descriptors = add_r8_startup_descriptors(
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010036 startup_descriptors, current_startup_descriptors)
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +020037 number_of_new_startup_descriptors = \
38 len(new_startup_descriptors) - len(startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010039 if options.out is not None:
40 print(
41 'Found %i new startup descriptors in iteration %i'
42 % (number_of_new_startup_descriptors, iteration + 1))
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +020043 return new_startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010044
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020045def generate_startup_profile(device, options):
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020046 logcat = None
47 profile = None
48 profile_classes_and_methods = None
49 if options.use_existing_profile:
50 # Verify presence of profile.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020051 adb_utils.check_app_has_profile_data(options.app_id, device.device_id)
52 profile = adb_utils.get_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020053 profile_classes_and_methods = \
54 adb_utils.get_classes_and_methods_from_app_profile(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020055 options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020056 else:
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010057 # Unlock device.
58 tear_down_options = adb_utils.prepare_for_interaction_with_device(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020059 device.device_id, device.device_pin)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010060
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020061 logcat_process = None
62 if options.logcat:
63 # Clear logcat and start capturing logcat.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020064 adb_utils.clear_logcat(device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020065 logcat_process = adb_utils.start_logcat(
Christoffer Quist Adamsenee34b202023-09-13 12:42:56 +020066 device.device_id, format='tag', filter='R8:I ActivityTaskManager:I *:S')
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020067 else:
68 # Clear existing profile data.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020069 adb_utils.clear_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020070
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010071 # Launch activity to generate startup profile on device.
72 adb_utils.launch_activity(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020073 options.app_id, options.main_activity, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010074
75 # Wait for activity startup.
76 time.sleep(options.startup_duration)
77
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020078 if options.logcat:
79 # Get startup descriptors from logcat.
80 logcat = adb_utils.stop_logcat(logcat_process)
81 else:
82 # Capture startup profile.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020083 adb_utils.capture_app_profile_data(options.app_id, device.device_id)
84 profile = adb_utils.get_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020085 profile_classes_and_methods = \
86 adb_utils.get_classes_and_methods_from_app_profile(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020087 options.app_id, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010088
89 # Shutdown app.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020090 adb_utils.stop_app(options.app_id, device.device_id)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020091 adb_utils.teardown_after_interaction_with_device(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020092 tear_down_options, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010093
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020094 return (logcat, profile, profile_classes_and_methods)
95
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020096def get_r8_startup_descriptors_from_logcat(logcat, options):
97 post_startup = False
98 startup_descriptors = {}
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020099 for line in logcat:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200100 line_elements = parse_logcat_line(line)
101 if line_elements is None:
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200102 continue
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200103 (priority, tag, message) = line_elements
104 if tag == 'ActivityTaskManager':
105 if message.startswith('START') \
106 or message.startswith('Activity pause timeout for') \
107 or message.startswith('Activity top resumed state loss timeout for') \
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200108 or message.startswith('Force removing') \
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200109 or message.startswith(
110 'Launch timeout has expired, giving up wake lock!'):
111 continue
112 elif message.startswith('Displayed %s/' % options.app_id):
113 print('Entering post startup: %s' % message)
114 post_startup = True
115 continue
Christoffer Quist Adamsenee34b202023-09-13 12:42:56 +0200116 elif tag == 'R8':
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200117 if is_startup_descriptor(message):
118 startup_descriptors[message] = {
119 'conditional_startup': False,
Christoffer Quist Adamsenee34b202023-09-13 12:42:56 +0200120 'hot': False,
121 'post_startup': post_startup,
122 'startup': True
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200123 }
124 continue
125 # Reaching here means we didn't expect this line.
126 report_unrecognized_logcat_line(line)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200127 return startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100128
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200129def is_startup_descriptor(string):
130 # The descriptor should start with the holder (possibly prefixed with 'S').
131 if not any(string.startswith('%sL' % flags) for flags in ['', 'S']):
132 return False
133 # The descriptor should end with ';', a primitive type, or void.
134 if not string.endswith(';') \
135 and not any(string.endswith(c) for c in get_primitive_descriptors()) \
136 and not string.endswith('V'):
137 return False
138 return True
139
140def get_primitive_descriptors():
141 return ['Z', 'B', 'S', 'C', 'I', 'F', 'J', 'D']
142
143def parse_logcat_line(line):
144 if line == '--------- beginning of kernel':
145 return None
146 if line == '--------- beginning of main':
147 return None
148 if line == '--------- beginning of system':
149 return None
150
151 priority = None
152 tag = None
153
154 try:
155 priority_end = line.index('/')
156 priority = line[0:priority_end]
157 line = line[priority_end + 1:]
158 except ValueError:
159 return report_unrecognized_logcat_line(line)
160
161 try:
162 tag_end = line.index(':')
163 tag = line[0:tag_end].strip()
164 line = line[tag_end + 1 :]
165 except ValueError:
166 return report_unrecognized_logcat_line(line)
167
168 message = line.strip()
169 return (priority, tag, message)
170
171def report_unrecognized_logcat_line(line):
172 print('Unrecognized line in logcat: %s' % line)
173
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200174def add_r8_startup_descriptors(old_startup_descriptors, startup_descriptors_to_add):
175 new_startup_descriptors = {}
176 if len(old_startup_descriptors) == 0:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200177 for startup_descriptor, flags in startup_descriptors_to_add.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200178 new_startup_descriptors[startup_descriptor] = flags.copy()
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200179 else:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200180 # Merge the new startup descriptors with the old descriptors in a way so
181 # that new startup descriptors are added next to the startup descriptors
182 # they are close to in the newly generated list of startup descriptors.
183 startup_descriptors_to_add_after_key = {}
184 startup_descriptors_to_add_in_the_end = {}
185 closest_seen_startup_descriptor = None
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200186 for startup_descriptor, flags in startup_descriptors_to_add.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200187 if startup_descriptor in old_startup_descriptors:
188 closest_seen_startup_descriptor = startup_descriptor
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200189 else:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200190 if closest_seen_startup_descriptor is None:
191 # Insert this new startup descriptor in the end of the result.
192 startup_descriptors_to_add_in_the_end[startup_descriptor] = flags
193 else:
194 # Record that this should be inserted after
195 # closest_seen_startup_descriptor.
196 pending_startup_descriptors = \
197 startup_descriptors_to_add_after_key.setdefault(
198 closest_seen_startup_descriptor, {})
199 pending_startup_descriptors[startup_descriptor] = flags
200 for startup_descriptor, flags in old_startup_descriptors.items():
201 # Merge flags if this also exists in startup_descriptors_to_add.
202 if startup_descriptor in startup_descriptors_to_add:
203 merged_flags = flags.copy()
204 other_flags = startup_descriptors_to_add[startup_descriptor]
205 assert not other_flags['conditional_startup']
Christoffer Quist Adamseneb29bfe2023-02-21 14:50:22 +0100206 merged_flags['hot'] = \
207 merged_flags['hot'] or other_flags['hot']
208 merged_flags['startup'] = \
209 merged_flags['startup'] or other_flags['startup']
210 merged_flags['post_startup'] = \
211 merged_flags['post_startup'] or other_flags['post_startup']
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200212 new_startup_descriptors[startup_descriptor] = merged_flags
213 else:
214 new_startup_descriptors[startup_descriptor] = flags.copy()
215 # Flush startup descriptors that followed this item in the new trace.
216 if startup_descriptor in startup_descriptors_to_add_after_key:
217 pending_startup_descriptors = \
218 startup_descriptors_to_add_after_key[startup_descriptor]
219 for pending_startup_descriptor, pending_flags \
220 in pending_startup_descriptors.items():
221 new_startup_descriptors[pending_startup_descriptor] = \
222 pending_flags.copy()
223 # Insert remaining new startup descriptors in the end.
224 for startup_descriptor, flags \
225 in startup_descriptors_to_add_in_the_end.items():
226 assert startup_descriptor not in new_startup_descriptors
227 new_startup_descriptors[startup_descriptor] = flags.copy()
228 return new_startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100229
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200230def write_tmp_binary_artifact(artifact, iteration, options, name):
231 if not options.tmp_dir:
232 return
233 out_dir = os.path.join(options.tmp_dir, str(iteration))
234 os.makedirs(out_dir, exist_ok=True)
235 path = os.path.join(out_dir, name)
236 with open(path, 'wb') as f:
237 f.write(artifact)
238
239def write_tmp_textual_artifact(artifact, iteration, options, name, item_to_string=None):
240 if not options.tmp_dir:
241 return
242 out_dir = os.path.join(options.tmp_dir, str(iteration))
243 os.makedirs(out_dir, exist_ok=True)
244 path = os.path.join(out_dir, name)
245 with open(path, 'w') as f:
246 for item in artifact:
247 f.write(item if item_to_string is None else item_to_string(item))
248 f.write('\n')
249
250def write_tmp_logcat(logcat, iteration, options):
251 write_tmp_textual_artifact(logcat, iteration, options, 'logcat.txt')
252
253def write_tmp_profile(profile, iteration, options):
254 write_tmp_binary_artifact(profile, iteration, options, 'primary.prof')
255
256def write_tmp_profile_classes_and_methods(
257 profile_classes_and_methods, iteration, options):
258 def item_to_string(item):
Christoffer Quist Adamsen81a87922022-08-11 13:46:16 +0200259 (descriptor, flags) = item
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200260 return '%s%s%s%s' % (
261 'H' if flags.get('hot') else '',
262 'S' if flags.get('startup') else '',
263 'P' if flags.get('post_startup') else '',
264 descriptor)
265 write_tmp_textual_artifact(
Christoffer Quist Adamsen81a87922022-08-11 13:46:16 +0200266 profile_classes_and_methods.items(),
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200267 iteration,
268 options,
269 'profile.txt',
270 item_to_string)
271
272def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200273 lines = [
274 startup_descriptor_to_string(startup_descriptor, flags)
275 for startup_descriptor, flags in startup_descriptors.items()]
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200276 write_tmp_textual_artifact(
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200277 lines, iteration, options, 'startup-descriptors.txt')
278
279def startup_descriptor_to_string(startup_descriptor, flags):
280 result = ''
Christoffer Quist Adamseneb29bfe2023-02-21 14:50:22 +0100281 if flags['hot']:
282 result += 'H'
283 if flags['startup']:
284 result += 'S'
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200285 if flags['post_startup']:
Christoffer Quist Adamseneb29bfe2023-02-21 14:50:22 +0100286 result += 'P'
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200287 result += startup_descriptor
288 return result
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200289
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200290def should_include_startup_descriptor(descriptor, flags, options):
291 if flags.get('conditional_startup') \
292 and not options.include_conditional_startup:
293 return False
294 if flags.get('post_startup') \
295 and not flags.get('startup') \
296 and not options.include_post_startup:
297 return False
298 return True
299
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100300def parse_options(argv):
301 result = argparse.ArgumentParser(
302 description='Generate a perfetto trace file.')
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200303 result.add_argument('--apk',
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200304 help='Path to the .apk')
305 result.add_argument('--apks',
306 help='Path to the .apks')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100307 result.add_argument('--app-id',
308 help='The application ID of interest',
309 required=True)
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200310 result.add_argument('--bundle',
311 help='Path to the .aab')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100312 result.add_argument('--device-id',
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200313 help='Device id (e.g., emulator-5554).',
314 action='append')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100315 result.add_argument('--device-pin',
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200316 help='Device pin code (e.g., 1234)',
317 action='append')
Christoffer Quist Adamsen84da5262022-08-24 13:54:21 +0200318 result.add_argument('--generalize-synthetics',
319 help='Whether synthetics should be abstracted into their '
320 'synthetic contexts',
321 action='store_true',
322 default=False)
Christoffer Quist Adamsenee34b202023-09-13 12:42:56 +0200323 result.add_argument('--grant-post-notification-permission',
324 help='Grants the android.permission.POST_NOTIFICATIONS '
325 'permission before launching the app',
326 default=False,
327 action='store_true')
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200328 result.add_argument('--logcat',
329 action='store_true',
330 default=False)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200331 result.add_argument('--include-conditional-startup',
332 help='Include conditional startup classes and methods in '
333 'the R8 startup descriptors',
334 action='store_true',
335 default=False)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100336 result.add_argument('--include-post-startup',
337 help='Include post startup classes and methods in the R8 '
338 'startup descriptors',
339 action='store_true',
340 default=False)
341 result.add_argument('--iterations',
342 help='Number of profiles to generate',
343 default=1,
344 type=int)
345 result.add_argument('--main-activity',
346 help='Main activity class name')
347 result.add_argument('--out',
348 help='File where to store startup descriptors (defaults '
349 'to stdout)')
350 result.add_argument('--startup-duration',
351 help='Duration in seconds before shutting down app',
352 default=15,
353 type=int)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200354 result.add_argument('--tmp-dir',
355 help='Directory where to store intermediate artifacts'
356 ' (by default these are not emitted)')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100357 result.add_argument('--until-stable',
358 help='Repeat profile generation until no new startup '
359 'descriptors are found',
360 action='store_true',
361 default=False)
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200362 result.add_argument('--until-stable-iterations',
363 help='Number of times that profile generation must must '
364 'not find new startup descriptors before exiting',
365 default=1,
366 type=int)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100367 result.add_argument('--use-existing-profile',
368 help='Do not launch app to generate startup profile',
369 action='store_true',
370 default=False)
371 options, args = result.parse_known_args(argv)
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200372
373 # Read the device pins.
374 device_pins = options.device_pin or []
375 del options.device_pin
376
377 # Convert the device ids and pins into a list of devices.
378 options.devices = []
379 if options.device_id is None:
380 # Assume a single device is attached.
381 options.devices.append(
382 Device(None, device_pins[0] if len(device_pins) > 0 else None))
383 else:
384 for i in range(len(options.device_id)):
385 device_id = options.device_id[i]
386 device_pin = device_pins[i] if i < len(device_pins) else None
387 options.devices.append(Device(device_id, device_pin))
388 del options.device_id
389
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200390 paths = [
391 path for path in [options.apk, options.apks, options.bundle]
392 if path is not None]
393 assert len(paths) <= 1, 'Expected at most one .apk, .apks, or .aab file.'
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100394 assert options.main_activity is not None or options.use_existing_profile, \
395 'Argument --main-activity is required except when running with ' \
396 '--use-existing-profile.'
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200397
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100398 return options, args
399
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200400def run_on_device(device, options, startup_descriptors):
401 adb_utils.root(device.device_id)
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200402 if options.apk:
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200403 adb_utils.uninstall(options.app_id, device.device_id)
404 adb_utils.install(options.apk, device.device_id)
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200405 elif options.apks:
406 adb_utils.uninstall(options.app_id, device.device_id)
407 adb_utils.install_apks(options.apks, device.device_id)
408 elif options.bundle:
409 adb_utils.uninstall(options.app_id, device.device_id)
410 adb_utils.install_bundle(options.bundle, device.device_id)
Christoffer Quist Adamsenee34b202023-09-13 12:42:56 +0200411 # Grant notifications.
412 if options.grant_post_notification_permission:
413 adb_utils.grant(
414 options.app_id,
415 'android.permission.POST_NOTIFICATIONS',
416 device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100417 if options.until_stable:
418 iteration = 0
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200419 stable_iterations = 0
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100420 while True:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200421 old_startup_descriptors = startup_descriptors
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200422 startup_descriptors = extend_startup_descriptors(
423 old_startup_descriptors, iteration, device, options)
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200424 diff = len(startup_descriptors) - len(old_startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100425 if diff == 0:
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200426 stable_iterations = stable_iterations + 1
427 if stable_iterations == options.until_stable_iterations:
428 break
429 else:
430 stable_iterations = 0
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100431 iteration = iteration + 1
432 else:
433 for iteration in range(options.iterations):
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200434 startup_descriptors = extend_startup_descriptors(
435 startup_descriptors, iteration, device, options)
436 return startup_descriptors
437
438def main(argv):
439 (options, args) = parse_options(argv)
440 startup_descriptors = {}
441 for device in options.devices:
442 startup_descriptors = run_on_device(device, options, startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100443 if options.out is not None:
444 with open(options.out, 'w') as f:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200445 for startup_descriptor, flags in startup_descriptors.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200446 if should_include_startup_descriptor(startup_descriptor, flags, options):
447 f.write(startup_descriptor_to_string(startup_descriptor, flags))
448 f.write('\n')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100449 else:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200450 for startup_descriptor, flags in startup_descriptors.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200451 if should_include_startup_descriptor(startup_descriptor, flags, options):
452 print(startup_descriptor_to_string(startup_descriptor, flags))
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100453
454if __name__ == '__main__':
455 sys.exit(main(sys.argv[1:]))