Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 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 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 22 | import yaml |
| 23 | import argparse |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 24 | import re |
| 25 | import os |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 26 | import subprocess |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 27 | import time |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 28 | import math |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 29 | import datetime |
| 30 | import sys |
| 31 | import operator |
| 32 | |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 33 | TIME_DMESG = "\[\s*(\d+\.\d+)\]" |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 34 | TIME_LOGCAT = "[0-9]+\.?[0-9]*" |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 35 | VALUE_TOO_BIG = 600 |
| 36 | MAX_RETRIES = 5 |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 37 | DEBUG = False |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 38 | |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 39 | def main(): |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 40 | args = init_arguments() |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 41 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 42 | if args.iterate < 1: |
| 43 | raise Exception('Number of iteration must be >=1'); |
| 44 | |
| 45 | if args.iterate > 1 and not args.reboot: |
| 46 | print "Forcing reboot flag" |
| 47 | args.reboot = True |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 48 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 49 | cfg = yaml.load(args.config) |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 50 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 51 | search_events = {key: re.compile(pattern) |
| 52 | for key, pattern in cfg['events'].iteritems()} |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 53 | timing_events = {key: re.compile(pattern) |
| 54 | for key, pattern in cfg['timings'].iteritems()} |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 55 | if 'stop_event' not in cfg: |
| 56 | raise Exception('Logcat stop_event is missing in config'); |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 57 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 58 | data_points = {} |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 59 | timing_points = {} |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 60 | for it in range(0, args.iterate): |
| 61 | if args.iterate > 1: |
| 62 | print "Run: {0}".format(it + 1) |
| 63 | attempt = 1 |
| 64 | processing_data = None |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 65 | timings = None |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 66 | while attempt <= MAX_RETRIES and processing_data is None: |
| 67 | attempt += 1 |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 68 | processing_data, timings = iterate( |
| 69 | args, search_events, timing_events, cfg) |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 70 | |
| 71 | if processing_data is None: |
| 72 | # Processing error |
| 73 | print "Failed to collect valid samples for run {0}".format(it + 1) |
| 74 | continue |
| 75 | for k, v in processing_data.iteritems(): |
| 76 | if k not in data_points: |
| 77 | data_points[k] = [] |
| 78 | data_points[k].append(v['value']) |
| 79 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 80 | if timings is not None: |
| 81 | for k, v in timings.iteritems(): |
| 82 | if k not in timing_points: |
| 83 | timing_points[k] = [] |
| 84 | timing_points[k].append(v) |
| 85 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 86 | if args.iterate > 1: |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 87 | if timing_points and args.timings: |
| 88 | print "Avg time values after {0} runs".format(args.iterate) |
| 89 | print '{0:30}: {1:<7} {2:<7}'.format("Event", "Mean", "stddev") |
| 90 | |
| 91 | for item in sorted(timing_points.items(), key=operator.itemgetter(1)): |
| 92 | print '{0:30}: {1:<7.5} {2:<7.5}'.format( |
| 93 | item[0], sum(item[1])/len(item[1]), stddev(item[1])) |
| 94 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 95 | print "Avg values after {0} runs".format(args.iterate) |
| 96 | print '{0:30}: {1:<7} {2:<7}'.format("Event", "Mean", "stddev") |
| 97 | |
| 98 | for item in sorted(data_points.items(), key=operator.itemgetter(1)): |
| 99 | print '{0:30}: {1:<7.5} {2:<7.5}'.format( |
| 100 | item[0], sum(item[1])/len(item[1]), stddev(item[1])) |
| 101 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 102 | def iterate(args, search_events, timings, cfg): |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 103 | if args.reboot: |
| 104 | reboot() |
| 105 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 106 | logcat_events, logcat_timing_events = collect_events( |
| 107 | search_events, 'adb logcat -b all -v epoch', timings, cfg['stop_event']) |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 108 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 109 | dmesg_events, e = collect_events(search_events, 'adb shell dmesg', {}) |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 110 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 111 | logcat_event_time = extract_time( |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 112 | logcat_events, TIME_LOGCAT, float); |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 113 | logcat_original_time = extract_time( |
| 114 | logcat_events, TIME_LOGCAT, str); |
| 115 | dmesg_event_time = extract_time( |
| 116 | dmesg_events, TIME_DMESG, float); |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 117 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 118 | events = {} |
| 119 | diff_time = 0 |
| 120 | max_time = 0 |
| 121 | events_to_correct = [] |
| 122 | replaced_from_dmesg = set() |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 123 | |
| 124 | time_correction_delta = 0 |
| 125 | time_correction_time = 0 |
| 126 | if ('time_correction_key' in cfg |
| 127 | and cfg['time_correction_key'] in logcat_events): |
| 128 | match = search_events[cfg['time_correction_key']].search( |
| 129 | logcat_events[cfg['time_correction_key']]) |
| 130 | if match and logcat_event_time[cfg['time_correction_key']]: |
| 131 | time_correction_delta = float(match.group(1)) |
| 132 | time_correction_time = logcat_event_time[cfg['time_correction_key']] |
| 133 | |
| 134 | debug("time_correction_delta = {0}, time_correction_time = {1}".format( |
| 135 | time_correction_delta, time_correction_time)) |
| 136 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 137 | for k, v in logcat_event_time.iteritems(): |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 138 | debug("event[{0}, {1}]".format(k, v)) |
| 139 | if v <= time_correction_time: |
| 140 | logcat_event_time[k] += time_correction_delta |
| 141 | v = v + time_correction_delta |
| 142 | debug("correcting event to event[{0}, {1}]".format(k, v)) |
| 143 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 144 | events[k] = v |
| 145 | if k in dmesg_event_time: |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 146 | debug("{0} is in dmesg".format(k)) |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 147 | if dmesg_event_time[k] > max_time: |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 148 | debug("{0} is bigger max={1}".format(dmesg_event_time[k], max_time)) |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 149 | max_time = dmesg_event_time[k] |
| 150 | diff_time = v - max_time |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 151 | debug("diff_time={0}".format(diff_time)) |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 152 | events[k] = dmesg_event_time[k] |
| 153 | replaced_from_dmesg.add(k) |
| 154 | else: |
| 155 | events_to_correct.append(k) |
| 156 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 157 | debug("diff_time={0}".format(diff_time)) |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 158 | for k in events_to_correct: |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 159 | debug("k={0}, {1}".format(k, events[k])) |
| 160 | if round(events[k] - diff_time, 3) >= 0: |
| 161 | events[k] = round(events[k] - diff_time, 3) |
| 162 | if events[k] > VALUE_TOO_BIG and not args.ignore: |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 163 | print "Event {0} value {1} too big , possible processing error".format( |
| 164 | k, events[k]) |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 165 | return None, None |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 166 | |
| 167 | data_points = {} |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 168 | timing_points = {} |
| 169 | |
| 170 | if args.timings: |
| 171 | for k, l in logcat_timing_events.iteritems(): |
| 172 | for v in l: |
| 173 | name, time_v = extract_timing(v, timings) |
| 174 | if name: |
| 175 | timing_points[name] = time_v |
| 176 | print "Event timing" |
| 177 | for item in sorted(timing_points.items(), key=operator.itemgetter(1)): |
| 178 | print '{0:30}: {1:<7.5}'.format( |
| 179 | item[0], item[1]) |
| 180 | print "-----------------" |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 181 | |
| 182 | for item in sorted(events.items(), key=operator.itemgetter(1)): |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 183 | data_points[item[0]] = { |
| 184 | 'value': item[1], |
| 185 | 'from_dmesg': item[0] in replaced_from_dmesg, |
| 186 | 'logcat_value': logcat_original_time[item[0]] |
| 187 | } |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 188 | print '{0:30}: {1:<7.5} {2:1} ({3})'.format( |
| 189 | item[0], item[1], '*' if item[0] in replaced_from_dmesg else '', |
| 190 | logcat_original_time[item[0]]) |
| 191 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 192 | print '\n* - event time was obtained from dmesg log\n' |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 193 | return data_points, timing_points |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 194 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 195 | def debug(string): |
| 196 | if DEBUG: |
| 197 | print string |
| 198 | |
| 199 | def extract_timing(s, patterns): |
| 200 | for k, p in patterns.iteritems(): |
| 201 | m = p.search(s) |
| 202 | if m: |
| 203 | g_dict = m.groupdict() |
| 204 | return g_dict['name'], float(g_dict['time']) |
| 205 | return None, Node |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 206 | |
| 207 | def init_arguments(): |
| 208 | parser = argparse.ArgumentParser(description='Measures boot time.') |
| 209 | parser.add_argument('-r', '--reboot', dest='reboot', |
| 210 | action='store_true', |
| 211 | help='adb reboot device for measurement', ) |
| 212 | parser.add_argument('-c', '--config', dest='config', |
| 213 | default='config.yaml', type=argparse.FileType('r'), |
| 214 | help='config file for the tool', ) |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 215 | parser.add_argument('-n', '--iterate', dest='iterate', type=int, default=1, |
| 216 | help='number of time to repeat the measurement', ) |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 217 | parser.add_argument('-g', '--ignore', dest='ignore', action='store_true', |
| 218 | help='ignore too big values error', ) |
| 219 | parser.add_argument('-t', '--timings', dest='timings', action='store_true', |
| 220 | help='print individual component times', default=True, ) |
| 221 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 222 | return parser.parse_args() |
| 223 | |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 224 | def collect_events(search_events, command, timings, stop_event=None): |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 225 | events = {} |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 226 | timing_events = {} |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 227 | process = subprocess.Popen(command, shell=True, |
| 228 | stdout=subprocess.PIPE); |
| 229 | out = process.stdout |
| 230 | data_available = stop_event is None |
| 231 | for line in out: |
| 232 | if not data_available: |
| 233 | print "Collecting data samples. Please wait...\n" |
| 234 | data_available = True |
| 235 | event = get_boot_event(line, search_events); |
| 236 | if event: |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 237 | debug("event[{0}] captured: {1}".format(event, line)) |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 238 | events[event] = line |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 239 | |
| 240 | timing_event = get_boot_event(line, timings); |
| 241 | if timing_event: |
| 242 | if timing_event not in timing_events: |
| 243 | timing_events[timing_event] = [] |
| 244 | timing_events[timing_event].append(line) |
| 245 | debug("timing_event[{0}] captured: {1}".format(timing_event, line)) |
| 246 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 247 | if stop_event and stop_event in line: |
| 248 | break; |
| 249 | process.terminate() |
Vitalii Tomkiv | 49dcaf2 | 2016-10-31 11:25:01 -0700 | [diff] [blame^] | 250 | return events, timing_events |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 251 | |
| 252 | def get_boot_event(line, events): |
| 253 | for event_key, event_pattern in events.iteritems(): |
| 254 | if event_pattern.search(line): |
| 255 | return event_key |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 256 | return None |
| 257 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 258 | def extract_time(events, pattern, date_transform_function): |
| 259 | result = {} |
| 260 | for event, data in events.iteritems(): |
| 261 | found = re.findall(pattern, data) |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 262 | if len(found) > 0: |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 263 | result[event] = date_transform_function(found[0]) |
| 264 | else: |
| 265 | print "Failed to find time for event: ", event, data |
| 266 | return result |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 267 | |
Vitalii Tomkiv | d704edd | 2016-07-13 17:27:03 -0700 | [diff] [blame] | 268 | def reboot(): |
| 269 | print 'Rebooting the device' |
| 270 | subprocess.Popen('adb reboot', shell=True).wait() |
| 271 | |
| 272 | def logcat_time_func(offset_year): |
| 273 | def f(date_str): |
| 274 | ndate = datetime.datetime.strptime(str(offset_year) + '-' + |
| 275 | date_str, '%Y-%m-%d %H:%M:%S.%f') |
| 276 | return datetime_to_unix_time(ndate) |
| 277 | return f |
| 278 | |
| 279 | |
| 280 | def datetime_to_unix_time(ndate): |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 281 | return time.mktime(ndate.timetuple()) + ndate.microsecond/1000000.0 |
| 282 | |
Vitalii Tomkiv | abc9233 | 2016-09-20 11:55:44 -0700 | [diff] [blame] | 283 | def stddev(data): |
| 284 | items_count = len(data) |
| 285 | avg = sum(data) / items_count |
| 286 | sq_diffs_sum = sum([(v - avg) ** 2 for v in data]) |
| 287 | variance = sq_diffs_sum / items_count |
| 288 | return math.sqrt(variance) |
| 289 | |
Vitalii Tomkiv | ed4c98c | 2016-04-28 15:20:57 -0700 | [diff] [blame] | 290 | if __name__ == '__main__': |
| 291 | main() |