| Ben Cheng | 50bbde0 | 2015-06-09 17:04:36 +0800 | [diff] [blame] | 1 | #!/usr/bin/env python | 
|  | 2 |  | 
|  | 3 | # Copyright (C) 2015 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 | """Compare two bootcharts and list start/end timestamps on key processes. | 
|  | 18 |  | 
|  | 19 | This script extracts two bootchart.tgz files and compares the timestamps | 
|  | 20 | in proc_ps.log for selected processes. The proc_ps.log file consists of | 
|  | 21 | repetitive blocks of the following format: | 
|  | 22 |  | 
|  | 23 | timestamp1 (jiffies) | 
|  | 24 | dumps of /proc/<pid>/stat | 
|  | 25 |  | 
|  | 26 | timestamp2 | 
|  | 27 | dumps of /proc/<pid>/stat | 
|  | 28 |  | 
|  | 29 | The timestamps are 200ms apart, and the creation time of selected processes | 
|  | 30 | are listed. The termination time of the boot animation process is also listed | 
|  | 31 | as a coarse indication about when the boot process is complete as perceived by | 
|  | 32 | the user. | 
|  | 33 | """ | 
|  | 34 |  | 
|  | 35 | import sys | 
|  | 36 | import tarfile | 
|  | 37 |  | 
|  | 38 | # The bootchart timestamps are 200ms apart, but the USER_HZ value is not | 
|  | 39 | # reported in the bootchart, so we use the first two timestamps to calculate | 
|  | 40 | # the wall clock time of a jiffy. | 
|  | 41 | jiffy_to_wallclock = { | 
|  | 42 | '1st_timestamp': -1, | 
|  | 43 | '2nd_timestamp': -1, | 
|  | 44 | 'jiffy_to_wallclock': -1 | 
|  | 45 | } | 
|  | 46 |  | 
|  | 47 | def analyze_process_maps(process_map1, process_map2, jiffy_record): | 
|  | 48 | # List interesting processes here | 
|  | 49 | processes_of_interest = [ | 
|  | 50 | '/init', | 
|  | 51 | '/system/bin/surfaceflinger', | 
|  | 52 | '/system/bin/bootanimation', | 
|  | 53 | 'zygote64', | 
|  | 54 | 'zygote', | 
|  | 55 | 'system_server' | 
|  | 56 | ] | 
|  | 57 |  | 
|  | 58 | jw = jiffy_record['jiffy_to_wallclock'] | 
|  | 59 | print "process: baseline experiment (delta)" | 
|  | 60 | print " - Unit is ms (a jiffy is %d ms on the system)" % jw | 
|  | 61 | print "------------------------------------" | 
|  | 62 | for p in processes_of_interest: | 
|  | 63 | # e.g., 32-bit system doesn't have zygote64 | 
|  | 64 | if p in process_map1 and p in process_map2: | 
|  | 65 | print "%s: %d %d (%+d)" % ( | 
|  | 66 | p, process_map1[p]['start_time'] * jw, | 
|  | 67 | process_map2[p]['start_time'] * jw, | 
|  | 68 | (process_map2[p]['start_time'] - | 
|  | 69 | process_map1[p]['start_time']) * jw) | 
|  | 70 |  | 
|  | 71 | # Print the last tick for the bootanimation process | 
|  | 72 | print "bootanimation ends at: %d %d (%+d)" % ( | 
|  | 73 | process_map1['/system/bin/bootanimation']['last_tick'] * jw, | 
|  | 74 | process_map2['/system/bin/bootanimation']['last_tick'] * jw, | 
|  | 75 | (process_map2['/system/bin/bootanimation']['last_tick'] - | 
|  | 76 | process_map1['/system/bin/bootanimation']['last_tick']) * jw) | 
|  | 77 |  | 
|  | 78 | def parse_proc_file(pathname, process_map, jiffy_record=None): | 
|  | 79 | # Uncompress bootchart.tgz | 
|  | 80 | with tarfile.open(pathname + '/bootchart.tgz', 'r:*') as tf: | 
|  | 81 | try: | 
|  | 82 | # Read proc_ps.log | 
|  | 83 | f = tf.extractfile('proc_ps.log') | 
|  | 84 |  | 
|  | 85 | # Break proc_ps into chunks based on timestamps | 
|  | 86 | blocks = f.read().split('\n\n') | 
|  | 87 | for b in blocks: | 
|  | 88 | lines = b.split('\n') | 
|  | 89 | if not lines[0]: | 
|  | 90 | break | 
|  | 91 |  | 
|  | 92 | # 200ms apart in jiffies | 
|  | 93 | timestamp = int(lines[0]); | 
|  | 94 |  | 
|  | 95 | # Figure out the wall clock time of a jiffy | 
|  | 96 | if jiffy_record is not None: | 
|  | 97 | if jiffy_record['1st_timestamp'] == -1: | 
|  | 98 | jiffy_record['1st_timestamp'] = timestamp | 
|  | 99 | elif jiffy_record['jiffy_to_wallclock'] == -1: | 
|  | 100 | # Not really needed but for debugging purposes | 
|  | 101 | jiffy_record['2nd_timestamp'] = timestamp | 
|  | 102 | value = 200 / (timestamp - | 
|  | 103 | jiffy_record['1st_timestamp']) | 
|  | 104 | # Fix the rounding error | 
|  | 105 | # e.g., 201 jiffies in 200ms when USER_HZ is 1000 | 
|  | 106 | if value == 0: | 
|  | 107 | value = 1 | 
|  | 108 | # e.g., 21 jiffies in 200ms when USER_HZ is 100 | 
|  | 109 | elif value == 9: | 
|  | 110 | value = 10 | 
|  | 111 | jiffy_record['jiffy_to_wallclock'] = value | 
|  | 112 |  | 
|  | 113 | # Populate the process_map table | 
|  | 114 | for line in lines[1:]: | 
|  | 115 | segs = line.split(' ') | 
|  | 116 |  | 
|  | 117 | #  0: pid | 
|  | 118 | #  1: process name | 
|  | 119 | # 17: priority | 
|  | 120 | # 18: nice | 
|  | 121 | # 21: creation time | 
|  | 122 |  | 
|  | 123 | proc_name = segs[1].strip('()') | 
|  | 124 | if proc_name in process_map: | 
|  | 125 | process = process_map[proc_name] | 
|  | 126 | else: | 
|  | 127 | process = {'start_time': int(segs[21])} | 
|  | 128 | process_map[proc_name] = process | 
|  | 129 |  | 
|  | 130 | process['last_tick'] = timestamp | 
|  | 131 | finally: | 
|  | 132 | f.close() | 
|  | 133 |  | 
|  | 134 | def main(): | 
|  | 135 | if len(sys.argv) != 3: | 
|  | 136 | print "Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0] | 
|  | 137 | sys.exit(1) | 
|  | 138 |  | 
|  | 139 | process_map1 = {} | 
|  | 140 | process_map2 = {} | 
|  | 141 | parse_proc_file(sys.argv[1], process_map1, jiffy_to_wallclock) | 
|  | 142 | parse_proc_file(sys.argv[2], process_map2) | 
|  | 143 | analyze_process_maps(process_map1, process_map2, jiffy_to_wallclock) | 
|  | 144 |  | 
|  | 145 | if __name__ == "__main__": | 
|  | 146 | main() |