1 #!/usr/bin/python 2 3 # Copyright (C) 2016 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 """Tool to analyze logcat and dmesg logs. 18 19 bootanalyze read logcat and dmesg loga and determines key points for boot. 20 """ 21 22 import argparse 23 import collections 24 import datetime 25 import math 26 import operator 27 import os 28 import re 29 import select 30 import subprocess 31 import sys 32 import time 33 import threading 34 import yaml 35 36 from datetime import datetime, date 37 38 39 TIME_DMESG = "\[\s*(\d+\.\d+)\]" 40 TIME_LOGCAT = "[0-9]+\.?[0-9]*" 41 KERNEL_TIME_KEY = "kernel" 42 BOOT_ANIM_END_TIME_KEY = "BootAnimEnd" 43 KERNEL_BOOT_COMPLETE = "BootComplete_kernel" 44 LOGCAT_BOOT_COMPLETE = "BootComplete" 45 LAUNCHER_START = "LauncherStart" 46 BOOT_TIME_TOO_BIG = 200.0 47 MAX_RETRIES = 5 48 DEBUG = False 49 ADB_CMD = "adb" 50 TIMING_THRESHOLD = 5.0 51 BOOT_PROP = "\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]" 52 BOOTLOADER_TIME_PROP = "\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]" 53 54 max_wait_time = BOOT_TIME_TOO_BIG 55 56 def main(): 57 global ADB_CMD 58 59 args = init_arguments() 60 61 if args.iterate < 1: 62 raise Exception('Number of iteration must be >=1'); 63 64 if args.iterate > 1 and not args.reboot: 65 print "Forcing reboot flag" 66 args.reboot = True 67 68 if args.serial: 69 ADB_CMD = "%s %s" % ("adb -s", args.serial) 70 71 error_time = BOOT_TIME_TOO_BIG * 10 72 if args.errortime: 73 error_time = float(args.errortime) 74 if args.maxwaittime: 75 global max_wait_time 76 max_wait_time = float(args.maxwaittime) 77 78 components_to_monitor = {} 79 if args.componentmonitor: 80 items = args.componentmonitor.split(",") 81 for item in items: 82 kv = item.split("=") 83 key = kv[0] 84 value = float(kv[1]) 85 components_to_monitor[key] = value 86 87 cfg = yaml.load(args.config) 88 89 if args.stressfs: 90 if run_adb_cmd('install -r -g ' + args.stressfs) != 0: 91 raise Exception('StressFS APK not installed'); 92 93 if args.iterate > 1 and args.bootchart: 94 run_adb_shell_cmd_as_root('touch /data/bootchart/enabled') 95 96 search_events_pattern = {key: re.compile(pattern) 97 for key, pattern in cfg['events'].iteritems()} 98 timing_events_pattern = {key: re.compile(pattern) 99 for key, pattern in cfg['timings'].iteritems()} 100 shutdown_events_pattern = {key: re.compile(pattern) 101 for key, pattern in cfg['shutdown_events'].iteritems()} 102 103 data_points = {} 104 kernel_timing_points = collections.OrderedDict() 105 logcat_timing_points = collections.OrderedDict() 106 boottime_points = collections.OrderedDict() 107 boot_chart_file_name_prefix = "bootchart-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S") 108 systrace_file_name_prefix = "systrace-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S") 109 shutdown_event_all = collections.OrderedDict() 110 shutdown_timing_event_all = collections.OrderedDict() 111 for it in range(0, args.iterate): 112 if args.iterate > 1: 113 print "Run: {0}".format(it) 114 attempt = 1 115 processing_data = None 116 timings = None 117 boottime_events = None 118 while attempt <= MAX_RETRIES and processing_data is None: 119 attempt += 1 120 processing_data, kernel_timings, logcat_timings, boottime_events, shutdown_events,\ 121 shutdown_timing_events = iterate(\ 122 args, search_events_pattern, timing_events_pattern, shutdown_events_pattern, cfg,\ 123 error_time, components_to_monitor) 124 if shutdown_events: 125 for k, v in shutdown_events.iteritems(): 126 events = shutdown_event_all.get(k) 127 if not events: 128 events = [] 129 shutdown_event_all[k] = events 130 events.append(v) 131 if shutdown_timing_events: 132 for k, v in shutdown_timing_events.iteritems(): 133 events = shutdown_timing_event_all.get(k) 134 if not events: 135 events = [] 136 shutdown_timing_event_all[k] = events 137 events.append(v) 138 if not processing_data or not boottime_events: 139 # Processing error 140 print "Failed to collect valid samples for run {0}".format(it) 141 continue 142 if args.bootchart: 143 grab_bootchart(boot_chart_file_name_prefix + "_run_" + str(it)) 144 145 if args.systrace: 146 grab_systrace(systrace_file_name_prefix + "_run_" + str(it)) 147 for k, v in processing_data.iteritems(): 148 if k not in data_points: 149 data_points[k] = [] 150 data_points[k].append(v['value']) 151 152 if kernel_timings is not None: 153 for k, v in kernel_timings.iteritems(): 154 if k not in kernel_timing_points: 155 kernel_timing_points[k] = [] 156 kernel_timing_points[k].append(v) 157 if logcat_timings is not None: 158 for k, v in logcat_timings.iteritems(): 159 if k not in logcat_timing_points: 160 logcat_timing_points[k] = [] 161 logcat_timing_points[k].append(v) 162 163 for k, v in boottime_events.iteritems(): 164 if not k in boottime_points: 165 boottime_points[k] = [] 166 boottime_points[k].append(v) 167 168 if args.stressfs: 169 run_adb_cmd('uninstall com.android.car.test.stressfs') 170 run_adb_shell_cmd('"rm -rf /storage/emulated/0/stressfs_data*"') 171 172 if args.iterate > 1: 173 print "-----------------" 174 print "\nshutdown events after {0} runs".format(args.iterate) 175 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 176 for item in shutdown_event_all.items(): 177 num_runs = len(item[1]) 178 print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format( 179 item[0], sum(item[1])/num_runs, stddev(item[1]),\ 180 "*time taken" if item[0].startswith("init.") else "",\ 181 num_runs if num_runs != args.iterate else "") 182 print "\nshutdown timing events after {0} runs".format(args.iterate) 183 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 184 for item in shutdown_timing_event_all.items(): 185 num_runs = len(item[1]) 186 print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format( 187 item[0], sum(item[1])/num_runs, stddev(item[1]),\ 188 "*time taken" if item[0].startswith("init.") else "",\ 189 num_runs if num_runs != args.iterate else "") 190 191 print "-----------------" 192 print "ro.boottime.* after {0} runs".format(args.iterate) 193 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 194 for item in boottime_points.items(): 195 num_runs = len(item[1]) 196 print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format( 197 item[0], sum(item[1])/num_runs, stddev(item[1]),\ 198 "*time taken" if item[0].startswith("init.") else "",\ 199 num_runs if num_runs != args.iterate else "") 200 201 if args.timings: 202 dump_timings_points_summary("Kernel", kernel_timing_points, args) 203 dump_timings_points_summary("Logcat", logcat_timing_points, args) 204 205 206 print "-----------------" 207 print "Avg values after {0} runs".format(args.iterate) 208 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 209 210 average_with_stddev = [] 211 for item in data_points.items(): 212 average_with_stddev.append((item[0], sum(item[1])/len(item[1]), stddev(item[1]),\ 213 len(item[1]))) 214 for item in sorted(average_with_stddev, key=lambda entry: entry[1]): 215 print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format( 216 item[0], item[1], item[2], item[3] if item[3] != args.iterate else "") 217 218 run_adb_shell_cmd_as_root('rm /data/bootchart/enabled') 219 220 221 def dump_timings_points_summary(msg_header, timing_points, args): 222 averaged_timing_points = [] 223 for item in timing_points.items(): 224 average = sum(item[1])/len(item[1]) 225 std_dev = stddev(item[1]) 226 averaged_timing_points.append((item[0], average, std_dev, len(item[1]))) 227 228 print "-----------------" 229 print msg_header + " timing in order, Avg time values after {0} runs".format(args.iterate) 230 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 231 for item in averaged_timing_points: 232 print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format( 233 item[0], item[1], item[2], item[3] if item[3] != args.iterate else "") 234 235 print "-----------------" 236 print msg_header + " timing top items, Avg time values after {0} runs".format(args.iterate) 237 print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs") 238 for item in sorted(averaged_timing_points, key=lambda entry: entry[1], reverse=True): 239 if item[1] < TIMING_THRESHOLD: 240 break 241 print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format( 242 item[0], item[1], item[2], item[3] if item[3] != args.iterate else "") 243 244 def capture_bugreport(bugreport_hint, boot_complete_time): 245 now = datetime.now() 246 bugreport_file = ("bugreport-%s-" + bugreport_hint + "-%s.zip") \ 247 % (now.strftime("%Y-%m-%d-%H-%M-%S"), str(boot_complete_time)) 248 print "Boot up time too big, will capture bugreport %s" % (bugreport_file) 249 os.system(ADB_CMD + " bugreport " + bugreport_file) 250 251 def generate_timing_points(timing_events, timings): 252 timing_points = collections.OrderedDict() 253 for k, l in timing_events.iteritems(): 254 for v in l: 255 name, time_v = extract_timing(v, timings) 256 if name and time_v: 257 if v.find("SystemServerTimingAsync") > 0: 258 name = "(" + name + ")" 259 new_name = name 260 name_index = 0 261 while timing_points.get(new_name): # if the name is already taken, append #digit 262 name_index += 1 263 new_name = name + "#" + str(name_index) 264 name = new_name 265 if k.endswith("_secs"): 266 timing_points[name] = time_v * 1000.0 267 else: 268 timing_points[name] = time_v 269 return timing_points 270 271 def dump_timing_points(msg_header, timing_points): 272 print msg_header + " event timing in time order, key: time" 273 for item in timing_points.items(): 274 print '{0:30}: {1:<7.5}'.format(item[0], item[1]) 275 print "-----------------" 276 print msg_header + " event timing top items" 277 for item in sorted(timing_points.items(), key=operator.itemgetter(1), reverse = True): 278 if item[1] < TIMING_THRESHOLD: 279 break 280 print '{0:30}: {1:<7.5}'.format( 281 item[0], item[1]) 282 print "-----------------" 283 284 def handle_reboot_log(capture_log_on_error, shutdown_events_pattern, components_to_monitor): 285 shutdown_events, shutdown_timing_events = collect_logcat_for_shutdown(capture_log_on_error,\ 286 shutdown_events_pattern, components_to_monitor) 287 print "\nshutdown events: time" 288 for item in shutdown_events.items(): 289 print '{0:30}: {1:<7.5}'.format(item[0], item[1]) 290 print "\nshutdown timing events: time" 291 for item in shutdown_timing_events.items(): 292 print '{0:30}: {1:<7.5}'.format(item[0], item[1]) 293 return shutdown_events, shutdown_timing_events 294 295 def iterate(args, search_events_pattern, timings_pattern, shutdown_events_pattern, cfg, error_time,\ 296 components_to_monitor): 297 shutdown_events = None 298 shutdown_timing_events = None 299 if args.reboot: 300 # sleep to make sure that logcat reader is reading before adb is gone by reboot. ugly but make 301 # impl simple. 302 t = threading.Thread(target = lambda : (time.sleep(2), reboot(args.serial, args.stressfs != '',\ 303 args.permissive, args.adb_reboot))) 304 t.start() 305 shutdown_events, shutdown_timing_events = handle_reboot_log(True, shutdown_events_pattern,\ 306 components_to_monitor) 307 t.join() 308 309 dmesg_events, kernel_timing_events = collect_events(search_events_pattern, ADB_CMD +\ 310 ' shell su root dmesg -w', timings_pattern,\ 311 [ KERNEL_BOOT_COMPLETE ], True) 312 313 logcat_stop_events = [ LOGCAT_BOOT_COMPLETE, KERNEL_BOOT_COMPLETE, LAUNCHER_START] 314 if args.fs_check: 315 logcat_stop_events.append("FsStat") 316 logcat_events, logcat_timing_events = collect_events( 317 search_events_pattern, ADB_CMD + ' logcat -b all -v epoch', timings_pattern,\ 318 logcat_stop_events, False) 319 logcat_event_time = extract_time( 320 logcat_events, TIME_LOGCAT, float); 321 logcat_original_time = extract_time( 322 logcat_events, TIME_LOGCAT, str); 323 dmesg_event_time = extract_time( 324 dmesg_events, TIME_DMESG, float); 325 boottime_events = fetch_boottime_property() 326 events = {} 327 diff_time = 0 328 max_time = 0 329 events_to_correct = [] 330 replaced_from_dmesg = set() 331 332 time_correction_delta = 0 333 time_correction_time = 0 334 if ('time_correction_key' in cfg 335 and cfg['time_correction_key'] in logcat_events): 336 match = search_events[cfg['time_correction_key']].search( 337 logcat_events[cfg['time_correction_key']]) 338 if match and logcat_event_time[cfg['time_correction_key']]: 339 time_correction_delta = float(match.group(1)) 340 time_correction_time = logcat_event_time[cfg['time_correction_key']] 341 342 debug("time_correction_delta = {0}, time_correction_time = {1}".format( 343 time_correction_delta, time_correction_time)) 344 345 for k, v in logcat_event_time.iteritems(): 346 if v <= time_correction_time: 347 logcat_event_time[k] += time_correction_delta 348 v = v + time_correction_delta 349 debug("correcting event to event[{0}, {1}]".format(k, v)) 350 351 if not logcat_event_time.get(KERNEL_TIME_KEY): 352 print "kernel time not captured in logcat, cannot get time diff" 353 return None, None, None, None 354 diffs = [] 355 diffs.append((logcat_event_time[KERNEL_TIME_KEY], logcat_event_time[KERNEL_TIME_KEY])) 356 if logcat_event_time.get(BOOT_ANIM_END_TIME_KEY) and dmesg_event_time.get(BOOT_ANIM_END_TIME_KEY): 357 diffs.append((logcat_event_time[BOOT_ANIM_END_TIME_KEY],\ 358 logcat_event_time[BOOT_ANIM_END_TIME_KEY] -\ 359 dmesg_event_time[BOOT_ANIM_END_TIME_KEY])) 360 if not dmesg_event_time.get(KERNEL_BOOT_COMPLETE): 361 print "BootAnimEnd time or BootComplete-kernel not captured in both log" +\ 362 ", cannot get time diff" 363 return None, None, None, None 364 diffs.append((logcat_event_time[KERNEL_BOOT_COMPLETE],\ 365 logcat_event_time[KERNEL_BOOT_COMPLETE] - dmesg_event_time[KERNEL_BOOT_COMPLETE])) 366 367 for k, v in logcat_event_time.iteritems(): 368 debug("event[{0}, {1}]".format(k, v)) 369 events[k] = v 370 if k in dmesg_event_time: 371 debug("{0} is in dmesg".format(k)) 372 events[k] = dmesg_event_time[k] 373 replaced_from_dmesg.add(k) 374 else: 375 events_to_correct.append(k) 376 377 diff_prev = diffs[0] 378 for k in events_to_correct: 379 diff = diffs[0] 380 while diff[0] < events[k] and len(diffs) > 1: 381 diffs.pop(0) 382 diff_prev = diff 383 diff = diffs[0] 384 events[k] = events[k] - diff[1] 385 if events[k] < 0.0: 386 if events[k] < -0.1: # maybe previous one is better fit 387 events[k] = events[k] + diff[1] - diff_prev[1] 388 else: 389 events[k] = 0.0 390 391 data_points = collections.OrderedDict() 392 393 print "-----------------" 394 print "ro.boottime.*: time" 395 for item in boottime_events.items(): 396 print '{0:30}: {1:<7.5} {2}'.format(item[0], item[1],\ 397 "*time taken" if item[0].startswith("init.") else "") 398 print "-----------------" 399 400 if args.timings: 401 kernel_timing_points = generate_timing_points(kernel_timing_events, timings_pattern) 402 logcat_timing_points = generate_timing_points(logcat_timing_events, timings_pattern) 403 dump_timing_points("Kernel", kernel_timing_points) 404 dump_timing_points("Logcat", logcat_timing_points) 405 406 for item in sorted(events.items(), key=operator.itemgetter(1)): 407 data_points[item[0]] = { 408 'value': item[1], 409 'from_dmesg': item[0] in replaced_from_dmesg, 410 'logcat_value': logcat_original_time[item[0]] 411 } 412 # add times with bootloader 413 if events.get("BootComplete") and boottime_events.get("bootloader"): 414 total = events["BootComplete"] + boottime_events["bootloader"] 415 data_points["*BootComplete+Bootloader"] = { 416 'value': total, 417 'from_dmesg': False, 418 'logcat_value': 0.0 419 } 420 if events.get("LauncherStart") and boottime_events.get("bootloader"): 421 total = events["LauncherStart"] + boottime_events["bootloader"] 422 data_points["*LauncherStart+Bootloader"] = { 423 'value': total, 424 'from_dmesg': False, 425 'logcat_value': 0.0 426 } 427 for k, v in data_points.iteritems(): 428 print '{0:30}: {1:<7.5} {2:1} ({3})'.format( 429 k, v['value'], '*' if v['from_dmesg'] else '', v['logcat_value']) 430 431 print '\n* - event time was obtained from dmesg log\n' 432 433 if events[LOGCAT_BOOT_COMPLETE] > error_time and not args.ignore: 434 capture_bugreport("bootuptoolong", events[LOGCAT_BOOT_COMPLETE]) 435 436 for k, v in components_to_monitor.iteritems(): 437 logcat_value_measured = logcat_timing_points.get(k) 438 kernel_value_measured = kernel_timing_points.get(k) 439 data_from_data_points = data_points.get(k) 440 if logcat_value_measured and logcat_value_measured > v: 441 capture_bugreport(k + "-" + str(logcat_value_measured), events[LOGCAT_BOOT_COMPLETE]) 442 break 443 elif kernel_value_measured and kernel_value_measured > v: 444 capture_bugreport(k + "-" + str(kernel_value_measured), events[LOGCAT_BOOT_COMPLETE]) 445 break 446 elif data_from_data_points and data_from_data_points['value'] * 1000.0 > v: 447 capture_bugreport(k + "-" + str(data_from_data_points['value']), events[LOGCAT_BOOT_COMPLETE]) 448 break 449 450 if args.fs_check: 451 fs_stat = None 452 if logcat_events.get("FsStat"): 453 fs_stat_pattern = cfg["events"]["FsStat"] 454 m = re.search(fs_stat_pattern, logcat_events.get("FsStat")) 455 if m: 456 fs_stat = m.group(1) 457 print 'fs_stat:', fs_stat 458 459 if fs_stat: 460 fs_stat_val = int(fs_stat, 0) 461 if (fs_stat_val & ~0x17) != 0: 462 capture_bugreport("fs_stat_" + fs_stat, events[LOGCAT_BOOT_COMPLETE]) 463 464 return data_points, kernel_timing_points, logcat_timing_points, boottime_events, shutdown_events,\ 465 shutdown_timing_events 466 467 def debug(string): 468 if DEBUG: 469 print string 470 471 def extract_timing(s, patterns): 472 for k, p in patterns.iteritems(): 473 m = p.search(s) 474 if m: 475 g_dict = m.groupdict() 476 return g_dict['name'], float(g_dict['time']) 477 return None, None 478 479 def init_arguments(): 480 parser = argparse.ArgumentParser(description='Measures boot time.') 481 parser.add_argument('-r', '--reboot', dest='reboot', 482 action='store_true', 483 help='reboot device for measurement', ) 484 parser.add_argument('-c', '--config', dest='config', 485 default='config.yaml', type=argparse.FileType('r'), 486 help='config file for the tool', ) 487 parser.add_argument('-s', '--stressfs', dest='stressfs', 488 default='', type=str, 489 help='APK file for the stressfs tool used to write to the data partition ' +\ 490 'during shutdown') 491 parser.add_argument('-n', '--iterate', dest='iterate', type=int, default=1, 492 help='number of time to repeat the measurement', ) 493 parser.add_argument('-g', '--ignore', dest='ignore', action='store_true', 494 help='ignore too big values error', ) 495 parser.add_argument('-t', '--timings', dest='timings', action='store_true', 496 help='print individual component times', default=True, ) 497 parser.add_argument('-p', '--serial', dest='serial', action='store', 498 help='android device serial number') 499 parser.add_argument('-e', '--errortime', dest='errortime', action='store', 500 help='handle bootup time bigger than this as error') 501 parser.add_argument('-w', '--maxwaittime', dest='maxwaittime', action='store', 502 help='wait for up to this time to collect logs. Retry after this time.' +\ 503 ' Default is 200 sec.') 504 parser.add_argument('-f', '--fs_check', dest='fs_check', 505 action='store_true', 506 help='check fs_stat after reboot', ) 507 parser.add_argument('-a', '--adb_reboot', dest='adb_reboot', 508 action='store_true', 509 help='reboot with adb reboot', ) 510 parser.add_argument('-v', '--permissive', dest='permissive', 511 action='store_true', 512 help='set selinux into permissive before reboot', ) 513 parser.add_argument('-m', '--componentmonitor', dest='componentmonitor', action='store', 514 help='capture bugreport if specified timing component is taking more than ' +\ 515 'certain time. Unlike errortime, the result will not be rejected in' +\ 516 'averaging. Format is key1=time1,key2=time2...') 517 parser.add_argument('-b', '--bootchart', dest='bootchart', 518 action='store_true', 519 help='collect bootchart from the device.', ) 520 parser.add_argument('-y', '--systrace', dest='systrace', 521 action='store_true', 522 help='collect systrace from the device. kernel trace should be already enabled', ) 523 return parser.parse_args() 524 525 def handle_zygote_event(zygote_pids, events, event, line): 526 words = line.split() 527 if len(words) > 1: 528 pid = int(words[1]) 529 if len(zygote_pids) == 2: 530 if pid == zygote_pids[1]: # secondary 531 event = event + "-secondary" 532 elif len(zygote_pids) == 1: 533 if zygote_pids[0] != pid: # new pid, need to decide if old ones were secondary 534 primary_pid = min(pid, zygote_pids[0]) 535 secondary_pid = max(pid, zygote_pids[0]) 536 zygote_pids.pop() 537 zygote_pids.append(primary_pid) 538 zygote_pids.append(secondary_pid) 539 if pid == primary_pid: # old one was secondary: 540 move_to_secondary = [] 541 for k, l in events.iteritems(): 542 if k.startswith("zygote"): 543 move_to_secondary.append((k, l)) 544 for item in move_to_secondary: 545 del events[item[0]] 546 if item[0].endswith("-secondary"): 547 print "Secondary already exists for event %s while found new pid %d, primary %d "\ 548 % (item[0], secondary_pid, primary_pid) 549 else: 550 events[item[0] + "-secondary"] = item[1] 551 else: 552 event = event + "-secondary" 553 else: 554 zygote_pids.append(pid) 555 events[event] = line 556 557 def update_name_if_already_exist(events, name): 558 existing_event = events.get(name) 559 i = 0 560 new_name = name 561 while existing_event: 562 i += 1 563 new_name = name + "_" + str(i) 564 existing_event = events.get(new_name) 565 return new_name 566 567 def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\ 568 log_capture_conditions): 569 events = collections.OrderedDict() 570 # shutdown does not have timing_events but calculated from checking Xyz - XyzDone / XyzTimeout 571 timing_events = collections.OrderedDict() 572 process = subprocess.Popen(ADB_CMD + ' logcat -b all -v epoch', shell=True, 573 stdout=subprocess.PIPE); 574 lines = [] 575 capture_log = False 576 shutdown_start_time = 0 577 while (True): 578 line = process.stdout.readline().lstrip().rstrip() 579 if not line: 580 break 581 lines.append(line) 582 event = get_boot_event(line, shutdown_events_pattern); 583 if not event: 584 continue 585 time = extract_a_time(line, TIME_LOGCAT, float) 586 if not time: 587 print "cannot get time from: " + line 588 continue 589 if shutdown_start_time == 0: 590 shutdown_start_time = time 591 time = time - shutdown_start_time 592 events[event] = time 593 time_limit1 = log_capture_conditions.get(event) 594 if time_limit1 and time_limit1 <= time: 595 capture_log = True 596 pair_event = None 597 if event.endswith('Done'): 598 pair_event = event[:-4] 599 elif event.endswith('Timeout'): 600 pair_event = event[:-7] 601 if capture_log_on_error: 602 capture_log = True 603 if not pair_event: 604 continue 605 start_time = events.get(pair_event) 606 if not start_time: 607 print "No start event for " + event 608 continue 609 time_spent = time - start_time 610 timing_event_name = pair_event + "Duration" 611 timing_events[timing_event_name] = time_spent 612 time_limit2 = log_capture_conditions.get(timing_event_name) 613 if time_limit2 and time_limit2 <= time_spent: 614 capture_log = True 615 616 if capture_log: 617 now = datetime.now() 618 log_file = ("shutdownlog-error-%s.txt") % (now.strftime("%Y-%m-%d-%H-%M-%S")) 619 print "Shutdown error, capture log to %s" % (log_file) 620 with open(log_file, 'w') as f: 621 f.write('\n'.join(lines)) 622 return events, timing_events 623 624 625 def collect_events(search_events, command, timings, stop_events, disable_timing_after_zygote): 626 events = collections.OrderedDict() 627 timing_events = {} 628 process = subprocess.Popen(command, shell=True, 629 stdout=subprocess.PIPE); 630 data_available = stop_events is None 631 zygote_pids = [] 632 start_time = time.time() 633 zygote_found = False 634 635 line = None 636 read_poll = select.poll() 637 read_poll.register(process.stdout, select.POLLIN) 638 while True: 639 time_left = start_time + max_wait_time - time.time() 640 if time_left <= 0: 641 print "timeout waiting for event, continue", time_left 642 break 643 read_r = read_poll.poll(time_left * 1000.0) 644 if len(read_r) > 0 and read_r[0][1] == select.POLLIN: 645 line = process.stdout.readline() 646 else: 647 print "poll timeout waiting for event, continue", time_left 648 break 649 if not data_available: 650 print "Collecting data samples from '%s'. Please wait...\n" % command 651 data_available = True 652 event = get_boot_event(line, search_events); 653 if event: 654 debug("event[{0}] captured: {1}".format(event, line)) 655 if event == "starting_zygote": 656 events[event] = line 657 zygote_found = True 658 elif event.startswith("zygote"): 659 handle_zygote_event(zygote_pids, events, event, line) 660 else: 661 new_event = update_name_if_already_exist(events, event) 662 events[new_event] = line 663 if event in stop_events: 664 stop_events.remove(event) 665 print "remaining stop_events:", stop_events 666 if len(stop_events) == 0: 667 break; 668 669 timing_event = get_boot_event(line, timings); 670 if timing_event and (not disable_timing_after_zygote or not zygote_found): 671 if timing_event not in timing_events: 672 timing_events[timing_event] = [] 673 timing_events[timing_event].append(line) 674 debug("timing_event[{0}] captured: {1}".format(timing_event, line)) 675 676 process.terminate() 677 return events, timing_events 678 679 def fetch_boottime_property(): 680 cmd = ADB_CMD + ' shell su root getprop' 681 events = {} 682 process = subprocess.Popen(cmd, shell=True, 683 stdout=subprocess.PIPE); 684 out = process.stdout 685 pattern = re.compile(BOOT_PROP) 686 pattern_bootloader = re.compile(BOOTLOADER_TIME_PROP) 687 bootloader_time = 0.0 688 for line in out: 689 match = pattern.match(line) 690 if match: 691 if match.group(1).startswith("init."): 692 events[match.group(1)] = float(match.group(2)) / 1000.0 #ms to s 693 else: 694 events[match.group(1)] = float(match.group(2)) / 1000000000.0 #ns to s 695 match = pattern_bootloader.match(line) 696 if match: 697 items = match.group(1).split(",") 698 for item in items: 699 entry_pair = item.split(":") 700 entry_name = entry_pair[0] 701 time_spent = float(entry_pair[1]) / 1000 #ms to s 702 if entry_name != "SW": 703 bootloader_time = bootloader_time + time_spent 704 ordered_event = collections.OrderedDict() 705 if bootloader_time != 0.0: 706 ordered_event["bootloader"] = bootloader_time; 707 for item in sorted(events.items(), key=operator.itemgetter(1)): 708 ordered_event[item[0]] = item[1] 709 return ordered_event 710 711 712 def get_boot_event(line, events): 713 for event_key, event_pattern in events.iteritems(): 714 if event_pattern.search(line): 715 return event_key 716 return None 717 718 def extract_a_time(line, pattern, date_transform_function): 719 found = re.findall(pattern, line) 720 if len(found) > 0: 721 return date_transform_function(found[0]) 722 else: 723 return None 724 725 def extract_time(events, pattern, date_transform_function): 726 result = collections.OrderedDict() 727 for event, data in events.iteritems(): 728 time = extract_a_time(data, pattern, date_transform_function) 729 if time: 730 result[event] = time 731 else: 732 print "Failed to find time for event: ", event, data 733 return result 734 735 736 def do_reboot(serial, use_adb_reboot): 737 original_devices = subprocess.check_output("adb devices", shell=True) 738 if use_adb_reboot: 739 print 'Rebooting the device using adb reboot' 740 run_adb_cmd('reboot') 741 else: 742 print 'Rebooting the device using svc power reboot' 743 run_adb_shell_cmd_as_root('svc power reboot') 744 # Wait for the device to go away 745 retry = 0 746 while retry < 20: 747 current_devices = subprocess.check_output("adb devices", shell=True) 748 if original_devices != current_devices: 749 if not serial or (serial and current_devices.find(serial) < 0): 750 return True 751 time.sleep(1) 752 retry += 1 753 return False 754 755 def reboot(serial, use_stressfs, permissive, use_adb_reboot): 756 if use_stressfs: 757 print 'Starting write to data partition' 758 run_adb_shell_cmd('am start' +\ 759 ' -n com.android.car.test.stressfs/.WritingActivity' +\ 760 ' -a com.android.car.test.stressfs.START') 761 # Give this app some time to start. 762 time.sleep(1) 763 if permissive: 764 run_adb_shell_cmd_as_root('setenforce 0') 765 766 retry = 0 767 while retry < 5: 768 if do_reboot(serial, use_adb_reboot): 769 break 770 retry += 1 771 772 print 'Waiting the device' 773 run_adb_cmd('wait-for-device') 774 775 def run_adb_cmd(cmd): 776 return subprocess.call(ADB_CMD + ' ' + cmd, shell=True) 777 778 def run_adb_shell_cmd(cmd): 779 return subprocess.call(ADB_CMD + ' shell ' + cmd, shell=True) 780 781 def run_adb_shell_cmd_as_root(cmd): 782 return subprocess.call(ADB_CMD + ' shell su root ' + cmd, shell=True) 783 784 def logcat_time_func(offset_year): 785 def f(date_str): 786 ndate = datetime.datetime.strptime(str(offset_year) + '-' + 787 date_str, '%Y-%m-%d %H:%M:%S.%f') 788 return datetime_to_unix_time(ndate) 789 return f 790 791 def datetime_to_unix_time(ndate): 792 return time.mktime(ndate.timetuple()) + ndate.microsecond/1000000.0 793 794 def stddev(data): 795 items_count = len(data) 796 avg = sum(data) / items_count 797 sq_diffs_sum = sum([(v - avg) ** 2 for v in data]) 798 variance = sq_diffs_sum / items_count 799 return math.sqrt(variance) 800 801 def grab_bootchart(boot_chart_file_name): 802 subprocess.call("./system/core/init/grab-bootchart.sh", shell=True) 803 print "Saving boot chart as " + boot_chart_file_name + ".tgz" 804 subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ./' + boot_chart_file_name + '.tgz',\ 805 shell=True) 806 subprocess.call('cp ./bootchart.png ./' + boot_chart_file_name + '.png', shell=True) 807 808 def grab_systrace(systrace_file_name): 809 trace_file = systrace_file_name + "_trace.txt" 810 with open(trace_file, 'w') as f: 811 f.write("TRACE:\n") 812 run_adb_shell_cmd_as_root("cat /d/tracing/trace >> " + trace_file) 813 html_file = systrace_file_name + ".html" 814 subprocess.call("./external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\ 815 html_file, shell=True) 816 817 if __name__ == '__main__': 818 main() 819