diff options
| -rwxr-xr-x | tools/power/pm-graph/analyze_boot.py | 586 |
1 files changed, 387 insertions, 199 deletions
diff --git a/tools/power/pm-graph/analyze_boot.py b/tools/power/pm-graph/analyze_boot.py index 3e1dcbbf1adc..e83df141a597 100755 --- a/tools/power/pm-graph/analyze_boot.py +++ b/tools/power/pm-graph/analyze_boot.py | |||
| @@ -42,7 +42,7 @@ import analyze_suspend as aslib | |||
| 42 | # store system values and test parameters | 42 | # store system values and test parameters |
| 43 | class SystemValues(aslib.SystemValues): | 43 | class SystemValues(aslib.SystemValues): |
| 44 | title = 'BootGraph' | 44 | title = 'BootGraph' |
| 45 | version = 2.0 | 45 | version = '2.1' |
| 46 | hostname = 'localhost' | 46 | hostname = 'localhost' |
| 47 | testtime = '' | 47 | testtime = '' |
| 48 | kernel = '' | 48 | kernel = '' |
| @@ -50,9 +50,14 @@ class SystemValues(aslib.SystemValues): | |||
| 50 | ftracefile = '' | 50 | ftracefile = '' |
| 51 | htmlfile = 'bootgraph.html' | 51 | htmlfile = 'bootgraph.html' |
| 52 | outfile = '' | 52 | outfile = '' |
| 53 | phoronix = False | 53 | testdir = '' |
| 54 | addlogs = False | 54 | testdirprefix = 'boot' |
| 55 | embedded = False | ||
| 56 | testlog = False | ||
| 57 | dmesglog = False | ||
| 58 | ftracelog = False | ||
| 55 | useftrace = False | 59 | useftrace = False |
| 60 | usecallgraph = False | ||
| 56 | usedevsrc = True | 61 | usedevsrc = True |
| 57 | suspendmode = 'boot' | 62 | suspendmode = 'boot' |
| 58 | max_graph_depth = 2 | 63 | max_graph_depth = 2 |
| @@ -61,10 +66,12 @@ class SystemValues(aslib.SystemValues): | |||
| 61 | manual = False | 66 | manual = False |
| 62 | iscronjob = False | 67 | iscronjob = False |
| 63 | timeformat = '%.6f' | 68 | timeformat = '%.6f' |
| 69 | bootloader = 'grub' | ||
| 70 | blexec = [] | ||
| 64 | def __init__(self): | 71 | def __init__(self): |
| 65 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): | 72 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): |
| 66 | self.phoronix = True | 73 | self.embedded = True |
| 67 | self.addlogs = True | 74 | self.dmesglog = True |
| 68 | self.outfile = os.environ['LOG_FILE'] | 75 | self.outfile = os.environ['LOG_FILE'] |
| 69 | self.htmlfile = os.environ['LOG_FILE'] | 76 | self.htmlfile = os.environ['LOG_FILE'] |
| 70 | self.hostname = platform.node() | 77 | self.hostname = platform.node() |
| @@ -76,42 +83,80 @@ class SystemValues(aslib.SystemValues): | |||
| 76 | self.kernel = self.kernelVersion(val) | 83 | self.kernel = self.kernelVersion(val) |
| 77 | else: | 84 | else: |
| 78 | self.kernel = 'unknown' | 85 | self.kernel = 'unknown' |
| 86 | self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S') | ||
| 79 | def kernelVersion(self, msg): | 87 | def kernelVersion(self, msg): |
| 80 | return msg.split()[2] | 88 | return msg.split()[2] |
| 89 | def checkFtraceKernelVersion(self): | ||
| 90 | val = tuple(map(int, self.kernel.split('-')[0].split('.'))) | ||
| 91 | if val >= (4, 10, 0): | ||
| 92 | return True | ||
| 93 | return False | ||
| 81 | def kernelParams(self): | 94 | def kernelParams(self): |
| 82 | cmdline = 'initcall_debug log_buf_len=32M' | 95 | cmdline = 'initcall_debug log_buf_len=32M' |
| 83 | if self.useftrace: | 96 | if self.useftrace: |
| 84 | cmdline += ' trace_buf_size=128M trace_clock=global '\ | 97 | if self.cpucount > 0: |
| 98 | bs = min(self.memtotal / 2, 2*1024*1024) / self.cpucount | ||
| 99 | else: | ||
| 100 | bs = 131072 | ||
| 101 | cmdline += ' trace_buf_size=%dK trace_clock=global '\ | ||
| 85 | 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ | 102 | 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ |
| 86 | 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ | 103 | 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ |
| 87 | 'nofuncgraph-overhead,context-info,graph-time '\ | 104 | 'nofuncgraph-overhead,context-info,graph-time '\ |
| 88 | 'ftrace=function_graph '\ | 105 | 'ftrace=function_graph '\ |
| 89 | 'ftrace_graph_max_depth=%d '\ | 106 | 'ftrace_graph_max_depth=%d '\ |
| 90 | 'ftrace_graph_filter=%s' % \ | 107 | 'ftrace_graph_filter=%s' % \ |
| 91 | (self.max_graph_depth, self.graph_filter) | 108 | (bs, self.max_graph_depth, self.graph_filter) |
| 92 | return cmdline | 109 | return cmdline |
| 93 | def setGraphFilter(self, val): | 110 | def setGraphFilter(self, val): |
| 94 | fp = open(self.tpath+'available_filter_functions') | 111 | master = self.getBootFtraceFilterFunctions() |
| 95 | master = fp.read().split('\n') | 112 | fs = '' |
| 96 | fp.close() | ||
| 97 | for i in val.split(','): | 113 | for i in val.split(','): |
| 98 | func = i.strip() | 114 | func = i.strip() |
| 115 | if func == '': | ||
| 116 | doError('badly formatted filter function string') | ||
| 117 | if '[' in func or ']' in func: | ||
| 118 | doError('loadable module functions not allowed - "%s"' % func) | ||
| 119 | if ' ' in func: | ||
| 120 | doError('spaces found in filter functions - "%s"' % func) | ||
| 99 | if func not in master: | 121 | if func not in master: |
| 100 | doError('function "%s" not available for ftrace' % func) | 122 | doError('function "%s" not available for ftrace' % func) |
| 101 | self.graph_filter = val | 123 | if not fs: |
| 124 | fs = func | ||
| 125 | else: | ||
| 126 | fs += ','+func | ||
| 127 | if not fs: | ||
| 128 | doError('badly formatted filter function string') | ||
| 129 | self.graph_filter = fs | ||
| 130 | def getBootFtraceFilterFunctions(self): | ||
| 131 | self.rootCheck(True) | ||
| 132 | fp = open(self.tpath+'available_filter_functions') | ||
| 133 | fulllist = fp.read().split('\n') | ||
| 134 | fp.close() | ||
| 135 | list = [] | ||
| 136 | for i in fulllist: | ||
| 137 | if not i or ' ' in i or '[' in i or ']' in i: | ||
| 138 | continue | ||
| 139 | list.append(i) | ||
| 140 | return list | ||
| 141 | def myCronJob(self, line): | ||
| 142 | if '@reboot' not in line: | ||
| 143 | return False | ||
| 144 | if 'bootgraph' in line or 'analyze_boot.py' in line or '-cronjob' in line: | ||
| 145 | return True | ||
| 146 | return False | ||
| 102 | def cronjobCmdString(self): | 147 | def cronjobCmdString(self): |
| 103 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) | 148 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) |
| 104 | args = iter(sys.argv[1:]) | 149 | args = iter(sys.argv[1:]) |
| 105 | for arg in args: | 150 | for arg in args: |
| 106 | if arg in ['-h', '-v', '-cronjob', '-reboot']: | 151 | if arg in ['-h', '-v', '-cronjob', '-reboot']: |
| 107 | continue | 152 | continue |
| 108 | elif arg in ['-o', '-dmesg', '-ftrace', '-filter']: | 153 | elif arg in ['-o', '-dmesg', '-ftrace', '-func']: |
| 109 | args.next() | 154 | args.next() |
| 110 | continue | 155 | continue |
| 111 | cmdline += ' '+arg | 156 | cmdline += ' '+arg |
| 112 | if self.graph_filter != 'do_one_initcall': | 157 | if self.graph_filter != 'do_one_initcall': |
| 113 | cmdline += ' -filter "%s"' % self.graph_filter | 158 | cmdline += ' -func "%s"' % self.graph_filter |
| 114 | cmdline += ' -o "%s"' % os.path.abspath(self.htmlfile) | 159 | cmdline += ' -o "%s"' % os.path.abspath(self.testdir) |
| 115 | return cmdline | 160 | return cmdline |
| 116 | def manualRebootRequired(self): | 161 | def manualRebootRequired(self): |
| 117 | cmdline = self.kernelParams() | 162 | cmdline = self.kernelParams() |
| @@ -121,6 +166,39 @@ class SystemValues(aslib.SystemValues): | |||
| 121 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' | 166 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' |
| 122 | print 'CMDLINE="%s"' % cmdline | 167 | print 'CMDLINE="%s"' % cmdline |
| 123 | sys.exit() | 168 | sys.exit() |
| 169 | def getExec(self, cmd): | ||
| 170 | dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', | ||
| 171 | '/usr/local/sbin', '/usr/local/bin'] | ||
| 172 | for path in dirlist: | ||
| 173 | cmdfull = os.path.join(path, cmd) | ||
| 174 | if os.path.exists(cmdfull): | ||
| 175 | return cmdfull | ||
| 176 | return '' | ||
| 177 | def blGrub(self): | ||
| 178 | blcmd = '' | ||
| 179 | for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: | ||
| 180 | if blcmd: | ||
| 181 | break | ||
| 182 | blcmd = self.getExec(cmd) | ||
| 183 | if not blcmd: | ||
| 184 | doError('[GRUB] missing update command') | ||
| 185 | if not os.path.exists('/etc/default/grub'): | ||
| 186 | doError('[GRUB] missing /etc/default/grub') | ||
| 187 | if 'grub2' in blcmd: | ||
| 188 | cfg = '/boot/grub2/grub.cfg' | ||
| 189 | else: | ||
| 190 | cfg = '/boot/grub/grub.cfg' | ||
| 191 | if not os.path.exists(cfg): | ||
| 192 | doError('[GRUB] missing %s' % cfg) | ||
| 193 | if 'update-grub' in blcmd: | ||
| 194 | self.blexec = [blcmd] | ||
| 195 | else: | ||
| 196 | self.blexec = [blcmd, '-o', cfg] | ||
| 197 | def getBootLoader(self): | ||
| 198 | if self.bootloader == 'grub': | ||
| 199 | self.blGrub() | ||
| 200 | else: | ||
| 201 | doError('unknown boot loader: %s' % self.bootloader) | ||
| 124 | 202 | ||
| 125 | sysvals = SystemValues() | 203 | sysvals = SystemValues() |
| 126 | 204 | ||
| @@ -136,20 +214,23 @@ class Data(aslib.Data): | |||
| 136 | idstr = '' | 214 | idstr = '' |
| 137 | html_device_id = 0 | 215 | html_device_id = 0 |
| 138 | valid = False | 216 | valid = False |
| 139 | initstart = 0.0 | 217 | tUserMode = 0.0 |
| 140 | boottime = '' | 218 | boottime = '' |
| 141 | phases = ['boot'] | 219 | phases = ['kernel', 'user'] |
| 142 | do_one_initcall = False | 220 | do_one_initcall = False |
| 143 | def __init__(self, num): | 221 | def __init__(self, num): |
| 144 | self.testnumber = num | 222 | self.testnumber = num |
| 145 | self.idstr = 'a' | 223 | self.idstr = 'a' |
| 146 | self.dmesgtext = [] | 224 | self.dmesgtext = [] |
| 147 | self.dmesg = { | 225 | self.dmesg = { |
| 148 | 'boot': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, 'color': '#dddddd'} | 226 | 'kernel': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, |
| 227 | 'order': 0, 'color': 'linear-gradient(to bottom, #fff, #bcf)'}, | ||
| 228 | 'user': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, | ||
| 229 | 'order': 1, 'color': '#fff'} | ||
| 149 | } | 230 | } |
| 150 | def deviceTopology(self): | 231 | def deviceTopology(self): |
| 151 | return '' | 232 | return '' |
| 152 | def newAction(self, phase, name, start, end, ret, ulen): | 233 | def newAction(self, phase, name, pid, start, end, ret, ulen): |
| 153 | # new device callback for a specific phase | 234 | # new device callback for a specific phase |
| 154 | self.html_device_id += 1 | 235 | self.html_device_id += 1 |
| 155 | devid = '%s%d' % (self.idstr, self.html_device_id) | 236 | devid = '%s%d' % (self.idstr, self.html_device_id) |
| @@ -163,41 +244,46 @@ class Data(aslib.Data): | |||
| 163 | name = '%s[%d]' % (origname, i) | 244 | name = '%s[%d]' % (origname, i) |
| 164 | i += 1 | 245 | i += 1 |
| 165 | list[name] = {'name': name, 'start': start, 'end': end, | 246 | list[name] = {'name': name, 'start': start, 'end': end, |
| 166 | 'pid': 0, 'length': length, 'row': 0, 'id': devid, | 247 | 'pid': pid, 'length': length, 'row': 0, 'id': devid, |
| 167 | 'ret': ret, 'ulen': ulen } | 248 | 'ret': ret, 'ulen': ulen } |
| 168 | return name | 249 | return name |
| 169 | def deviceMatch(self, cg): | 250 | def deviceMatch(self, pid, cg): |
| 170 | if cg.end - cg.start == 0: | 251 | if cg.end - cg.start == 0: |
| 171 | return True | 252 | return True |
| 172 | list = self.dmesg['boot']['list'] | 253 | for p in data.phases: |
| 173 | for devname in list: | 254 | list = self.dmesg[p]['list'] |
| 174 | dev = list[devname] | 255 | for devname in list: |
| 175 | if cg.name == 'do_one_initcall': | 256 | dev = list[devname] |
| 176 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): | 257 | if pid != dev['pid']: |
| 177 | dev['ftrace'] = cg | 258 | continue |
| 178 | self.do_one_initcall = True | 259 | if cg.name == 'do_one_initcall': |
| 179 | return True | 260 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): |
| 180 | else: | 261 | dev['ftrace'] = cg |
| 181 | if(cg.start > dev['start'] and cg.end < dev['end']): | 262 | self.do_one_initcall = True |
| 182 | if 'ftraces' not in dev: | 263 | return True |
| 183 | dev['ftraces'] = [] | 264 | else: |
| 184 | dev['ftraces'].append(cg) | 265 | if(cg.start > dev['start'] and cg.end < dev['end']): |
| 185 | return True | 266 | if 'ftraces' not in dev: |
| 267 | dev['ftraces'] = [] | ||
| 268 | dev['ftraces'].append(cg) | ||
| 269 | return True | ||
| 186 | return False | 270 | return False |
| 187 | 271 | ||
| 188 | # ----------------- FUNCTIONS -------------------- | 272 | # ----------------- FUNCTIONS -------------------- |
| 189 | 273 | ||
| 190 | # Function: loadKernelLog | 274 | # Function: parseKernelLog |
| 191 | # Description: | 275 | # Description: |
| 192 | # Load a raw kernel log from dmesg | 276 | # parse a kernel log for boot data |
| 193 | def loadKernelLog(): | 277 | def parseKernelLog(): |
| 278 | phase = 'kernel' | ||
| 194 | data = Data(0) | 279 | data = Data(0) |
| 195 | data.dmesg['boot']['start'] = data.start = ktime = 0.0 | 280 | data.dmesg['kernel']['start'] = data.start = ktime = 0.0 |
| 196 | sysvals.stamp = { | 281 | sysvals.stamp = { |
| 197 | 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), | 282 | 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), |
| 198 | 'host': sysvals.hostname, | 283 | 'host': sysvals.hostname, |
| 199 | 'mode': 'boot', 'kernel': ''} | 284 | 'mode': 'boot', 'kernel': ''} |
| 200 | 285 | ||
| 286 | tp = aslib.TestProps() | ||
| 201 | devtemp = dict() | 287 | devtemp = dict() |
| 202 | if(sysvals.dmesgfile): | 288 | if(sysvals.dmesgfile): |
| 203 | lf = open(sysvals.dmesgfile, 'r') | 289 | lf = open(sysvals.dmesgfile, 'r') |
| @@ -205,6 +291,13 @@ def loadKernelLog(): | |||
| 205 | lf = Popen('dmesg', stdout=PIPE).stdout | 291 | lf = Popen('dmesg', stdout=PIPE).stdout |
| 206 | for line in lf: | 292 | for line in lf: |
| 207 | line = line.replace('\r\n', '') | 293 | line = line.replace('\r\n', '') |
| 294 | # grab the stamp and sysinfo | ||
| 295 | if re.match(tp.stampfmt, line): | ||
| 296 | tp.stamp = line | ||
| 297 | continue | ||
| 298 | elif re.match(tp.sysinfofmt, line): | ||
| 299 | tp.sysinfo = line | ||
| 300 | continue | ||
| 208 | idx = line.find('[') | 301 | idx = line.find('[') |
| 209 | if idx > 1: | 302 | if idx > 1: |
| 210 | line = line[idx:] | 303 | line = line[idx:] |
| @@ -215,7 +308,6 @@ def loadKernelLog(): | |||
| 215 | if(ktime > 120): | 308 | if(ktime > 120): |
| 216 | break | 309 | break |
| 217 | msg = m.group('msg') | 310 | msg = m.group('msg') |
| 218 | data.end = data.initstart = ktime | ||
| 219 | data.dmesgtext.append(line) | 311 | data.dmesgtext.append(line) |
| 220 | if(ktime == 0.0 and re.match('^Linux version .*', msg)): | 312 | if(ktime == 0.0 and re.match('^Linux version .*', msg)): |
| 221 | if(not sysvals.stamp['kernel']): | 313 | if(not sysvals.stamp['kernel']): |
| @@ -228,43 +320,39 @@ def loadKernelLog(): | |||
| 228 | data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') | 320 | data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') |
| 229 | sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') | 321 | sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') |
| 230 | continue | 322 | continue |
| 231 | m = re.match('^calling *(?P<f>.*)\+.*', msg) | 323 | m = re.match('^calling *(?P<f>.*)\+.* @ (?P<p>[0-9]*)', msg) |
| 232 | if(m): | 324 | if(m): |
| 233 | devtemp[m.group('f')] = ktime | 325 | func = m.group('f') |
| 326 | pid = int(m.group('p')) | ||
| 327 | devtemp[func] = (ktime, pid) | ||
| 234 | continue | 328 | continue |
| 235 | m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) | 329 | m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) |
| 236 | if(m): | 330 | if(m): |
| 237 | data.valid = True | 331 | data.valid = True |
| 332 | data.end = ktime | ||
| 238 | f, r, t = m.group('f', 'r', 't') | 333 | f, r, t = m.group('f', 'r', 't') |
| 239 | if(f in devtemp): | 334 | if(f in devtemp): |
| 240 | data.newAction('boot', f, devtemp[f], ktime, int(r), int(t)) | 335 | start, pid = devtemp[f] |
| 241 | data.end = ktime | 336 | data.newAction(phase, f, pid, start, ktime, int(r), int(t)) |
| 242 | del devtemp[f] | 337 | del devtemp[f] |
| 243 | continue | 338 | continue |
| 244 | if(re.match('^Freeing unused kernel memory.*', msg)): | 339 | if(re.match('^Freeing unused kernel memory.*', msg)): |
| 245 | break | 340 | data.tUserMode = ktime |
| 246 | 341 | data.dmesg['kernel']['end'] = ktime | |
| 247 | data.dmesg['boot']['end'] = data.end | 342 | data.dmesg['user']['start'] = ktime |
| 343 | phase = 'user' | ||
| 344 | |||
| 345 | if tp.stamp: | ||
| 346 | sysvals.stamp = 0 | ||
| 347 | tp.parseStamp(data, sysvals) | ||
| 348 | data.dmesg['user']['end'] = data.end | ||
| 248 | lf.close() | 349 | lf.close() |
| 249 | return data | 350 | return data |
| 250 | 351 | ||
| 251 | # Function: loadTraceLog | 352 | # Function: parseTraceLog |
| 252 | # Description: | 353 | # Description: |
| 253 | # Check if trace is available and copy to a temp file | 354 | # Check if trace is available and copy to a temp file |
| 254 | def loadTraceLog(data): | 355 | def parseTraceLog(data): |
| 255 | # load the data to a temp file if none given | ||
| 256 | if not sysvals.ftracefile: | ||
| 257 | lib = aslib.sysvals | ||
| 258 | aslib.rootCheck(True) | ||
| 259 | if not lib.verifyFtrace(): | ||
| 260 | doError('ftrace not available') | ||
| 261 | if lib.fgetVal('current_tracer').strip() != 'function_graph': | ||
| 262 | doError('ftrace not configured for a boot callgraph') | ||
| 263 | sysvals.ftracefile = '/tmp/boot_ftrace.%s.txt' % os.getpid() | ||
| 264 | call('cat '+lib.tpath+'trace > '+sysvals.ftracefile, shell=True) | ||
| 265 | if not sysvals.ftracefile: | ||
| 266 | doError('No trace data available') | ||
| 267 | |||
| 268 | # parse the trace log | 356 | # parse the trace log |
| 269 | ftemp = dict() | 357 | ftemp = dict() |
| 270 | tp = aslib.TestProps() | 358 | tp = aslib.TestProps() |
| @@ -306,9 +394,29 @@ def loadTraceLog(data): | |||
| 306 | print('Sanity check failed for %s-%d' % (proc, pid)) | 394 | print('Sanity check failed for %s-%d' % (proc, pid)) |
| 307 | continue | 395 | continue |
| 308 | # match cg data to devices | 396 | # match cg data to devices |
| 309 | if not data.deviceMatch(cg): | 397 | if not data.deviceMatch(pid, cg): |
| 310 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) | 398 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) |
| 311 | 399 | ||
| 400 | # Function: retrieveLogs | ||
| 401 | # Description: | ||
| 402 | # Create copies of dmesg and/or ftrace for later processing | ||
| 403 | def retrieveLogs(): | ||
| 404 | # check ftrace is configured first | ||
| 405 | if sysvals.useftrace: | ||
| 406 | tracer = sysvals.fgetVal('current_tracer').strip() | ||
| 407 | if tracer != 'function_graph': | ||
| 408 | doError('ftrace not configured for a boot callgraph') | ||
| 409 | # create the folder and get dmesg | ||
| 410 | sysvals.systemInfo(aslib.dmidecode(sysvals.mempath)) | ||
| 411 | sysvals.initTestOutput('boot') | ||
| 412 | sysvals.writeDatafileHeader(sysvals.dmesgfile) | ||
| 413 | call('dmesg >> '+sysvals.dmesgfile, shell=True) | ||
| 414 | if not sysvals.useftrace: | ||
| 415 | return | ||
| 416 | # get ftrace | ||
| 417 | sysvals.writeDatafileHeader(sysvals.ftracefile) | ||
| 418 | call('cat '+sysvals.tpath+'trace >> '+sysvals.ftracefile, shell=True) | ||
| 419 | |||
| 312 | # Function: colorForName | 420 | # Function: colorForName |
| 313 | # Description: | 421 | # Description: |
| 314 | # Generate a repeatable color from a list for a given name | 422 | # Generate a repeatable color from a list for a given name |
| @@ -353,18 +461,19 @@ def cgOverview(cg, minlen): | |||
| 353 | # testruns: array of Data objects from parseKernelLog or parseTraceLog | 461 | # testruns: array of Data objects from parseKernelLog or parseTraceLog |
| 354 | # Output: | 462 | # Output: |
| 355 | # True if the html file was created, false if it failed | 463 | # True if the html file was created, false if it failed |
| 356 | def createBootGraph(data, embedded): | 464 | def createBootGraph(data): |
| 357 | # html function templates | 465 | # html function templates |
| 358 | html_srccall = '<div id={6} title="{5}" class="srccall" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;">{0}</div>\n' | 466 | html_srccall = '<div id={6} title="{5}" class="srccall" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;">{0}</div>\n' |
| 359 | html_timetotal = '<table class="time1">\n<tr>'\ | 467 | html_timetotal = '<table class="time1">\n<tr>'\ |
| 360 | '<td class="blue">Time from Kernel Boot to start of User Mode: <b>{0} ms</b></td>'\ | 468 | '<td class="blue">Init process starts @ <b>{0} ms</b></td>'\ |
| 469 | '<td class="blue">Last initcall ends @ <b>{1} ms</b></td>'\ | ||
| 361 | '</tr>\n</table>\n' | 470 | '</tr>\n</table>\n' |
| 362 | 471 | ||
| 363 | # device timeline | 472 | # device timeline |
| 364 | devtl = aslib.Timeline(100, 20) | 473 | devtl = aslib.Timeline(100, 20) |
| 365 | 474 | ||
| 366 | # write the test title and general info header | 475 | # write the test title and general info header |
| 367 | devtl.createHeader(sysvals, 'noftrace') | 476 | devtl.createHeader(sysvals) |
| 368 | 477 | ||
| 369 | # Generate the header for this timeline | 478 | # Generate the header for this timeline |
| 370 | t0 = data.start | 479 | t0 = data.start |
| @@ -373,84 +482,98 @@ def createBootGraph(data, embedded): | |||
| 373 | if(tTotal == 0): | 482 | if(tTotal == 0): |
| 374 | print('ERROR: No timeline data') | 483 | print('ERROR: No timeline data') |
| 375 | return False | 484 | return False |
| 376 | boot_time = '%.0f'%(tTotal*1000) | 485 | user_mode = '%.0f'%(data.tUserMode*1000) |
| 377 | devtl.html += html_timetotal.format(boot_time) | 486 | last_init = '%.0f'%(tTotal*1000) |
| 487 | devtl.html += html_timetotal.format(user_mode, last_init) | ||
| 378 | 488 | ||
| 379 | # determine the maximum number of rows we need to draw | 489 | # determine the maximum number of rows we need to draw |
| 380 | phase = 'boot' | ||
| 381 | list = data.dmesg[phase]['list'] | ||
| 382 | devlist = [] | 490 | devlist = [] |
| 383 | for devname in list: | 491 | for p in data.phases: |
| 384 | d = aslib.DevItem(0, phase, list[devname]) | 492 | list = data.dmesg[p]['list'] |
| 385 | devlist.append(d) | 493 | for devname in list: |
| 386 | devtl.getPhaseRows(devlist) | 494 | d = aslib.DevItem(0, p, list[devname]) |
| 495 | devlist.append(d) | ||
| 496 | devtl.getPhaseRows(devlist, 0, 'start') | ||
| 387 | devtl.calcTotalRows() | 497 | devtl.calcTotalRows() |
| 388 | 498 | ||
| 389 | # draw the timeline background | 499 | # draw the timeline background |
| 390 | devtl.createZoomBox() | 500 | devtl.createZoomBox() |
| 391 | boot = data.dmesg[phase] | 501 | devtl.html += devtl.html_tblock.format('boot', '0', '100', devtl.scaleH) |
| 392 | length = boot['end']-boot['start'] | 502 | for p in data.phases: |
| 393 | left = '%.3f' % (((boot['start']-t0)*100.0)/tTotal) | 503 | phase = data.dmesg[p] |
| 394 | width = '%.3f' % ((length*100.0)/tTotal) | 504 | length = phase['end']-phase['start'] |
| 395 | devtl.html += devtl.html_tblock.format(phase, left, width, devtl.scaleH) | 505 | left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) |
| 396 | devtl.html += devtl.html_phase.format('0', '100', \ | 506 | width = '%.3f' % ((length*100.0)/tTotal) |
| 397 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ | 507 | devtl.html += devtl.html_phase.format(left, width, \ |
| 398 | 'white', '') | 508 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ |
| 509 | phase['color'], '') | ||
| 399 | 510 | ||
| 400 | # draw the device timeline | 511 | # draw the device timeline |
| 401 | num = 0 | 512 | num = 0 |
| 402 | devstats = dict() | 513 | devstats = dict() |
| 403 | for devname in sorted(list): | 514 | for phase in data.phases: |
| 404 | cls, color = colorForName(devname) | 515 | list = data.dmesg[phase]['list'] |
| 405 | dev = list[devname] | 516 | for devname in sorted(list): |
| 406 | info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, | 517 | cls, color = colorForName(devname) |
| 407 | dev['ulen']/1000.0, dev['ret']) | 518 | dev = list[devname] |
| 408 | devstats[dev['id']] = {'info':info} | 519 | info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, |
| 409 | dev['color'] = color | 520 | dev['ulen']/1000.0, dev['ret']) |
| 410 | height = devtl.phaseRowHeight(0, phase, dev['row']) | 521 | devstats[dev['id']] = {'info':info} |
| 411 | top = '%.6f' % ((dev['row']*height) + devtl.scaleH) | 522 | dev['color'] = color |
| 412 | left = '%.6f' % (((dev['start']-t0)*100)/tTotal) | 523 | height = devtl.phaseRowHeight(0, phase, dev['row']) |
| 413 | width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) | 524 | top = '%.6f' % ((dev['row']*height) + devtl.scaleH) |
| 414 | length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) | 525 | left = '%.6f' % (((dev['start']-t0)*100)/tTotal) |
| 415 | devtl.html += devtl.html_device.format(dev['id'], | 526 | width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) |
| 416 | devname+length+'kernel_mode', left, top, '%.3f'%height, | 527 | length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) |
| 417 | width, devname, ' '+cls, '') | 528 | devtl.html += devtl.html_device.format(dev['id'], |
| 418 | rowtop = devtl.phaseRowTop(0, phase, dev['row']) | 529 | devname+length+phase+'_mode', left, top, '%.3f'%height, |
| 419 | height = '%.6f' % (devtl.rowH / 2) | 530 | width, devname, ' '+cls, '') |
| 420 | top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) | 531 | rowtop = devtl.phaseRowTop(0, phase, dev['row']) |
| 421 | if data.do_one_initcall: | 532 | height = '%.6f' % (devtl.rowH / 2) |
| 422 | if('ftrace' not in dev): | 533 | top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) |
| 534 | if data.do_one_initcall: | ||
| 535 | if('ftrace' not in dev): | ||
| 536 | continue | ||
| 537 | cg = dev['ftrace'] | ||
| 538 | large, stats = cgOverview(cg, 0.001) | ||
| 539 | devstats[dev['id']]['fstat'] = stats | ||
| 540 | for l in large: | ||
| 541 | left = '%f' % (((l.time-t0)*100)/tTotal) | ||
| 542 | width = '%f' % (l.length*100/tTotal) | ||
| 543 | title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) | ||
| 544 | devtl.html += html_srccall.format(l.name, left, | ||
| 545 | top, height, width, title, 'x%d'%num) | ||
| 546 | num += 1 | ||
| 547 | continue | ||
| 548 | if('ftraces' not in dev): | ||
| 423 | continue | 549 | continue |
| 424 | cg = dev['ftrace'] | 550 | for cg in dev['ftraces']: |
| 425 | large, stats = cgOverview(cg, 0.001) | 551 | left = '%f' % (((cg.start-t0)*100)/tTotal) |
| 426 | devstats[dev['id']]['fstat'] = stats | 552 | width = '%f' % ((cg.end-cg.start)*100/tTotal) |
| 427 | for l in large: | 553 | cglen = (cg.end - cg.start) * 1000.0 |
| 428 | left = '%f' % (((l.time-t0)*100)/tTotal) | 554 | title = '%s (%0.3fms)' % (cg.name, cglen) |
| 429 | width = '%f' % (l.length*100/tTotal) | 555 | cg.id = 'x%d' % num |
| 430 | title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) | 556 | devtl.html += html_srccall.format(cg.name, left, |
| 431 | devtl.html += html_srccall.format(l.name, left, | 557 | top, height, width, title, dev['id']+cg.id) |
| 432 | top, height, width, title, 'x%d'%num) | ||
| 433 | num += 1 | 558 | num += 1 |
| 434 | continue | ||
| 435 | if('ftraces' not in dev): | ||
| 436 | continue | ||
| 437 | for cg in dev['ftraces']: | ||
| 438 | left = '%f' % (((cg.start-t0)*100)/tTotal) | ||
| 439 | width = '%f' % ((cg.end-cg.start)*100/tTotal) | ||
| 440 | cglen = (cg.end - cg.start) * 1000.0 | ||
| 441 | title = '%s (%0.3fms)' % (cg.name, cglen) | ||
| 442 | cg.id = 'x%d' % num | ||
| 443 | devtl.html += html_srccall.format(cg.name, left, | ||
| 444 | top, height, width, title, dev['id']+cg.id) | ||
| 445 | num += 1 | ||
| 446 | 559 | ||
| 447 | # draw the time scale, try to make the number of labels readable | 560 | # draw the time scale, try to make the number of labels readable |
| 448 | devtl.createTimeScale(t0, tMax, tTotal, phase) | 561 | devtl.createTimeScale(t0, tMax, tTotal, 'boot') |
| 449 | devtl.html += '</div>\n' | 562 | devtl.html += '</div>\n' |
| 450 | 563 | ||
| 451 | # timeline is finished | 564 | # timeline is finished |
| 452 | devtl.html += '</div>\n</div>\n' | 565 | devtl.html += '</div>\n</div>\n' |
| 453 | 566 | ||
| 567 | # draw a legend which describes the phases by color | ||
| 568 | devtl.html += '<div class="legend">\n' | ||
| 569 | pdelta = 20.0 | ||
| 570 | pmargin = 36.0 | ||
| 571 | for phase in data.phases: | ||
| 572 | order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) | ||
| 573 | devtl.html += devtl.html_legend.format(order, \ | ||
| 574 | data.dmesg[phase]['color'], phase+'_mode', phase[0]) | ||
| 575 | devtl.html += '</div>\n' | ||
| 576 | |||
| 454 | if(sysvals.outfile == sysvals.htmlfile): | 577 | if(sysvals.outfile == sysvals.htmlfile): |
| 455 | hf = open(sysvals.htmlfile, 'a') | 578 | hf = open(sysvals.htmlfile, 'a') |
| 456 | else: | 579 | else: |
| @@ -474,7 +597,7 @@ def createBootGraph(data, embedded): | |||
| 474 | .fstat td {text-align:left;width:35px;}\n\ | 597 | .fstat td {text-align:left;width:35px;}\n\ |
| 475 | .srccall {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\ | 598 | .srccall {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\ |
| 476 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' | 599 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' |
| 477 | if(not embedded): | 600 | if(not sysvals.embedded): |
| 478 | aslib.addCSS(hf, sysvals, 1, False, extra) | 601 | aslib.addCSS(hf, sysvals, 1, False, extra) |
| 479 | 602 | ||
| 480 | # write the device timeline | 603 | # write the device timeline |
| @@ -495,9 +618,11 @@ def createBootGraph(data, embedded): | |||
| 495 | html = \ | 618 | html = \ |
| 496 | '<div id="devicedetailtitle"></div>\n'\ | 619 | '<div id="devicedetailtitle"></div>\n'\ |
| 497 | '<div id="devicedetail" style="display:none;">\n'\ | 620 | '<div id="devicedetail" style="display:none;">\n'\ |
| 498 | '<div id="devicedetail0">\n'\ | 621 | '<div id="devicedetail0">\n' |
| 499 | '<div id="kernel_mode" class="phaselet" style="left:0%;width:100%;background:#DDDDDD"></div>\n'\ | 622 | for p in data.phases: |
| 500 | '</div>\n</div>\n'\ | 623 | phase = data.dmesg[p] |
| 624 | html += devtl.html_phaselet.format(p+'_mode', '0', '100', phase['color']) | ||
| 625 | html += '</div>\n</div>\n'\ | ||
| 501 | '<script type="text/javascript">\n'+statinfo+\ | 626 | '<script type="text/javascript">\n'+statinfo+\ |
| 502 | '</script>\n' | 627 | '</script>\n' |
| 503 | hf.write(html) | 628 | hf.write(html) |
| @@ -507,21 +632,21 @@ def createBootGraph(data, embedded): | |||
| 507 | aslib.addCallgraphs(sysvals, hf, data) | 632 | aslib.addCallgraphs(sysvals, hf, data) |
| 508 | 633 | ||
| 509 | # add the dmesg log as a hidden div | 634 | # add the dmesg log as a hidden div |
| 510 | if sysvals.addlogs: | 635 | if sysvals.dmesglog: |
| 511 | hf.write('<div id="dmesglog" style="display:none;">\n') | 636 | hf.write('<div id="dmesglog" style="display:none;">\n') |
| 512 | for line in data.dmesgtext: | 637 | for line in data.dmesgtext: |
| 513 | line = line.replace('<', '<').replace('>', '>') | 638 | line = line.replace('<', '<').replace('>', '>') |
| 514 | hf.write(line) | 639 | hf.write(line) |
| 515 | hf.write('</div>\n') | 640 | hf.write('</div>\n') |
| 516 | 641 | ||
| 517 | if(not embedded): | 642 | if(not sysvals.embedded): |
| 518 | # write the footer and close | 643 | # write the footer and close |
| 519 | aslib.addScriptCode(hf, [data]) | 644 | aslib.addScriptCode(hf, [data]) |
| 520 | hf.write('</body>\n</html>\n') | 645 | hf.write('</body>\n</html>\n') |
| 521 | else: | 646 | else: |
| 522 | # embedded out will be loaded in a page, skip the js | 647 | # embedded out will be loaded in a page, skip the js |
| 523 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ | 648 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ |
| 524 | (data.start*1000, data.initstart*1000)) | 649 | (data.start*1000, data.end*1000)) |
| 525 | hf.close() | 650 | hf.close() |
| 526 | return True | 651 | return True |
| 527 | 652 | ||
| @@ -533,17 +658,20 @@ def updateCron(restore=False): | |||
| 533 | if not restore: | 658 | if not restore: |
| 534 | sysvals.rootUser(True) | 659 | sysvals.rootUser(True) |
| 535 | crondir = '/var/spool/cron/crontabs/' | 660 | crondir = '/var/spool/cron/crontabs/' |
| 536 | cronfile = crondir+'root' | 661 | if not os.path.exists(crondir): |
| 537 | backfile = crondir+'root-analyze_boot-backup' | 662 | crondir = '/var/spool/cron/' |
| 538 | if not os.path.exists(crondir): | 663 | if not os.path.exists(crondir): |
| 539 | doError('%s not found' % crondir) | 664 | doError('%s not found' % crondir) |
| 540 | out = Popen(['which', 'crontab'], stdout=PIPE).stdout.read() | 665 | cronfile = crondir+'root' |
| 541 | if not out: | 666 | backfile = crondir+'root-analyze_boot-backup' |
| 667 | cmd = sysvals.getExec('crontab') | ||
| 668 | if not cmd: | ||
| 542 | doError('crontab not found') | 669 | doError('crontab not found') |
| 543 | # on restore: move the backup cron back into place | 670 | # on restore: move the backup cron back into place |
| 544 | if restore: | 671 | if restore: |
| 545 | if os.path.exists(backfile): | 672 | if os.path.exists(backfile): |
| 546 | shutil.move(backfile, cronfile) | 673 | shutil.move(backfile, cronfile) |
| 674 | call([cmd, cronfile]) | ||
| 547 | return | 675 | return |
| 548 | # backup current cron and install new one with reboot | 676 | # backup current cron and install new one with reboot |
| 549 | if os.path.exists(cronfile): | 677 | if os.path.exists(cronfile): |
| @@ -556,13 +684,13 @@ def updateCron(restore=False): | |||
| 556 | fp = open(backfile, 'r') | 684 | fp = open(backfile, 'r') |
| 557 | op = open(cronfile, 'w') | 685 | op = open(cronfile, 'w') |
| 558 | for line in fp: | 686 | for line in fp: |
| 559 | if '@reboot' not in line: | 687 | if not sysvals.myCronJob(line): |
| 560 | op.write(line) | 688 | op.write(line) |
| 561 | continue | 689 | continue |
| 562 | fp.close() | 690 | fp.close() |
| 563 | op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) | 691 | op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) |
| 564 | op.close() | 692 | op.close() |
| 565 | res = call('crontab %s' % cronfile, shell=True) | 693 | res = call([cmd, cronfile]) |
| 566 | except Exception, e: | 694 | except Exception, e: |
| 567 | print 'Exception: %s' % str(e) | 695 | print 'Exception: %s' % str(e) |
| 568 | shutil.move(backfile, cronfile) | 696 | shutil.move(backfile, cronfile) |
| @@ -577,25 +705,16 @@ def updateGrub(restore=False): | |||
| 577 | # call update-grub on restore | 705 | # call update-grub on restore |
| 578 | if restore: | 706 | if restore: |
| 579 | try: | 707 | try: |
| 580 | call(['update-grub'], stderr=PIPE, stdout=PIPE, | 708 | call(sysvals.blexec, stderr=PIPE, stdout=PIPE, |
| 581 | env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) | 709 | env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) |
| 582 | except Exception, e: | 710 | except Exception, e: |
| 583 | print 'Exception: %s\n' % str(e) | 711 | print 'Exception: %s\n' % str(e) |
| 584 | return | 712 | return |
| 585 | # verify we can do this | ||
| 586 | sysvals.rootUser(True) | ||
| 587 | grubfile = '/etc/default/grub' | ||
| 588 | if not os.path.exists(grubfile): | ||
| 589 | print 'ERROR: Unable to set the kernel parameters via grub.\n' | ||
| 590 | sysvals.manualRebootRequired() | ||
| 591 | out = Popen(['which', 'update-grub'], stdout=PIPE).stdout.read() | ||
| 592 | if not out: | ||
| 593 | print 'ERROR: Unable to set the kernel parameters via grub.\n' | ||
| 594 | sysvals.manualRebootRequired() | ||
| 595 | |||
| 596 | # extract the option and create a grub config without it | 713 | # extract the option and create a grub config without it |
| 714 | sysvals.rootUser(True) | ||
| 597 | tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' | 715 | tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' |
| 598 | cmdline = '' | 716 | cmdline = '' |
| 717 | grubfile = '/etc/default/grub' | ||
| 599 | tempfile = '/etc/default/grub.analyze_boot' | 718 | tempfile = '/etc/default/grub.analyze_boot' |
| 600 | shutil.move(grubfile, tempfile) | 719 | shutil.move(grubfile, tempfile) |
| 601 | res = -1 | 720 | res = -1 |
| @@ -622,7 +741,7 @@ def updateGrub(restore=False): | |||
| 622 | # if the target option value is in quotes, strip them | 741 | # if the target option value is in quotes, strip them |
| 623 | sp = '"' | 742 | sp = '"' |
| 624 | val = cmdline.strip() | 743 | val = cmdline.strip() |
| 625 | if val[0] == '\'' or val[0] == '"': | 744 | if val and (val[0] == '\'' or val[0] == '"'): |
| 626 | sp = val[0] | 745 | sp = val[0] |
| 627 | val = val.strip(sp) | 746 | val = val.strip(sp) |
| 628 | cmdline = val | 747 | cmdline = val |
| @@ -633,7 +752,7 @@ def updateGrub(restore=False): | |||
| 633 | # write out the updated target option | 752 | # write out the updated target option |
| 634 | op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) | 753 | op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) |
| 635 | op.close() | 754 | op.close() |
| 636 | res = call('update-grub') | 755 | res = call(sysvals.blexec) |
| 637 | os.remove(grubfile) | 756 | os.remove(grubfile) |
| 638 | except Exception, e: | 757 | except Exception, e: |
| 639 | print 'Exception: %s' % str(e) | 758 | print 'Exception: %s' % str(e) |
| @@ -641,10 +760,18 @@ def updateGrub(restore=False): | |||
| 641 | # cleanup | 760 | # cleanup |
| 642 | shutil.move(tempfile, grubfile) | 761 | shutil.move(tempfile, grubfile) |
| 643 | if res != 0: | 762 | if res != 0: |
| 644 | doError('update-grub failed') | 763 | doError('update grub failed') |
| 645 | 764 | ||
| 646 | # Function: doError | 765 | # Function: updateKernelParams |
| 647 | # Description: | 766 | # Description: |
| 767 | # update boot conf for all kernels with our parameters | ||
| 768 | def updateKernelParams(restore=False): | ||
| 769 | # find the boot loader | ||
| 770 | sysvals.getBootLoader() | ||
| 771 | if sysvals.bootloader == 'grub': | ||
| 772 | updateGrub(restore) | ||
| 773 | |||
| 774 | # Function: doError Description: | ||
| 648 | # generic error function for catastrphic failures | 775 | # generic error function for catastrphic failures |
| 649 | # Arguments: | 776 | # Arguments: |
| 650 | # msg: the error message to print | 777 | # msg: the error message to print |
| @@ -660,7 +787,7 @@ def doError(msg, help=False): | |||
| 660 | # print out the help text | 787 | # print out the help text |
| 661 | def printHelp(): | 788 | def printHelp(): |
| 662 | print('') | 789 | print('') |
| 663 | print('%s v%.1f' % (sysvals.title, sysvals.version)) | 790 | print('%s v%s' % (sysvals.title, sysvals.version)) |
| 664 | print('Usage: bootgraph <options> <command>') | 791 | print('Usage: bootgraph <options> <command>') |
| 665 | print('') | 792 | print('') |
| 666 | print('Description:') | 793 | print('Description:') |
| @@ -669,13 +796,19 @@ def printHelp(): | |||
| 669 | print(' the start of the init process.') | 796 | print(' the start of the init process.') |
| 670 | print('') | 797 | print('') |
| 671 | print(' If no specific command is given the tool reads the current dmesg') | 798 | print(' If no specific command is given the tool reads the current dmesg') |
| 672 | print(' and/or ftrace log and outputs bootgraph.html') | 799 | print(' and/or ftrace log and creates a timeline') |
| 800 | print('') | ||
| 801 | print(' Generates output files in subdirectory: boot-yymmdd-HHMMSS') | ||
| 802 | print(' HTML output: <hostname>_boot.html') | ||
| 803 | print(' raw dmesg output: <hostname>_boot_dmesg.txt') | ||
| 804 | print(' raw ftrace output: <hostname>_boot_ftrace.txt') | ||
| 673 | print('') | 805 | print('') |
| 674 | print('Options:') | 806 | print('Options:') |
| 675 | print(' -h Print this help text') | 807 | print(' -h Print this help text') |
| 676 | print(' -v Print the current tool version') | 808 | print(' -v Print the current tool version') |
| 677 | print(' -addlogs Add the dmesg log to the html output') | 809 | print(' -addlogs Add the dmesg log to the html output') |
| 678 | print(' -o file Html timeline name (default: bootgraph.html)') | 810 | print(' -o name Overrides the output subdirectory name when running a new test') |
| 811 | print(' default: boot-{date}-{time}') | ||
| 679 | print(' [advanced]') | 812 | print(' [advanced]') |
| 680 | print(' -f Use ftrace to add function detail (default: disabled)') | 813 | print(' -f Use ftrace to add function detail (default: disabled)') |
| 681 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') | 814 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') |
| @@ -683,13 +816,18 @@ def printHelp(): | |||
| 683 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') | 816 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') |
| 684 | print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') | 817 | print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') |
| 685 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') | 818 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') |
| 686 | print(' -filter list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') | 819 | print(' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') |
| 687 | print(' [commands]') | 820 | print(' -cgfilter S Filter the callgraph output in the timeline') |
| 821 | print(' -bl name Use the following boot loader for kernel params (default: grub)') | ||
| 688 | print(' -reboot Reboot the machine automatically and generate a new timeline') | 822 | print(' -reboot Reboot the machine automatically and generate a new timeline') |
| 689 | print(' -manual Show the requirements to generate a new timeline manually') | 823 | print(' -manual Show the steps to generate a new timeline manually (used with -reboot)') |
| 690 | print(' -dmesg file Load a stored dmesg file (used with -ftrace)') | 824 | print('') |
| 691 | print(' -ftrace file Load a stored ftrace file (used with -dmesg)') | 825 | print('Other commands:') |
| 692 | print(' -flistall Print all functions capable of being captured in ftrace') | 826 | print(' -flistall Print all functions capable of being captured in ftrace') |
| 827 | print(' -sysinfo Print out system info extracted from BIOS') | ||
| 828 | print(' [redo]') | ||
| 829 | print(' -dmesg file Create HTML output using dmesg input (used with -ftrace)') | ||
| 830 | print(' -ftrace file Create HTML output using ftrace input (used with -dmesg)') | ||
| 693 | print('') | 831 | print('') |
| 694 | return True | 832 | return True |
| 695 | 833 | ||
| @@ -698,14 +836,15 @@ def printHelp(): | |||
| 698 | if __name__ == '__main__': | 836 | if __name__ == '__main__': |
| 699 | # loop through the command line arguments | 837 | # loop through the command line arguments |
| 700 | cmd = '' | 838 | cmd = '' |
| 701 | simplecmds = ['-updategrub', '-flistall'] | 839 | testrun = True |
| 840 | simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] | ||
| 702 | args = iter(sys.argv[1:]) | 841 | args = iter(sys.argv[1:]) |
| 703 | for arg in args: | 842 | for arg in args: |
| 704 | if(arg == '-h'): | 843 | if(arg == '-h'): |
| 705 | printHelp() | 844 | printHelp() |
| 706 | sys.exit() | 845 | sys.exit() |
| 707 | elif(arg == '-v'): | 846 | elif(arg == '-v'): |
| 708 | print("Version %.1f" % sysvals.version) | 847 | print("Version %s" % sysvals.version) |
| 709 | sys.exit() | 848 | sys.exit() |
| 710 | elif(arg in simplecmds): | 849 | elif(arg in simplecmds): |
| 711 | cmd = arg[1:] | 850 | cmd = arg[1:] |
| @@ -716,16 +855,32 @@ if __name__ == '__main__': | |||
| 716 | sysvals.usecallgraph = True | 855 | sysvals.usecallgraph = True |
| 717 | elif(arg == '-mincg'): | 856 | elif(arg == '-mincg'): |
| 718 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) | 857 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) |
| 858 | elif(arg == '-cgfilter'): | ||
| 859 | try: | ||
| 860 | val = args.next() | ||
| 861 | except: | ||
| 862 | doError('No callgraph functions supplied', True) | ||
| 863 | sysvals.setDeviceFilter(val) | ||
| 864 | elif(arg == '-bl'): | ||
| 865 | try: | ||
| 866 | val = args.next() | ||
| 867 | except: | ||
| 868 | doError('No boot loader name supplied', True) | ||
| 869 | if val.lower() not in ['grub']: | ||
| 870 | doError('Unknown boot loader: %s' % val, True) | ||
| 871 | sysvals.bootloader = val.lower() | ||
| 719 | elif(arg == '-timeprec'): | 872 | elif(arg == '-timeprec'): |
| 720 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) | 873 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) |
| 721 | elif(arg == '-maxdepth'): | 874 | elif(arg == '-maxdepth'): |
| 722 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) | 875 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) |
| 723 | elif(arg == '-filter'): | 876 | elif(arg == '-func'): |
| 724 | try: | 877 | try: |
| 725 | val = args.next() | 878 | val = args.next() |
| 726 | except: | 879 | except: |
| 727 | doError('No filter functions supplied', True) | 880 | doError('No filter functions supplied', True) |
| 728 | aslib.rootCheck(True) | 881 | sysvals.useftrace = True |
| 882 | sysvals.usecallgraph = True | ||
| 883 | sysvals.rootCheck(True) | ||
| 729 | sysvals.setGraphFilter(val) | 884 | sysvals.setGraphFilter(val) |
| 730 | elif(arg == '-ftrace'): | 885 | elif(arg == '-ftrace'): |
| 731 | try: | 886 | try: |
| @@ -734,9 +889,10 @@ if __name__ == '__main__': | |||
| 734 | doError('No ftrace file supplied', True) | 889 | doError('No ftrace file supplied', True) |
| 735 | if(os.path.exists(val) == False): | 890 | if(os.path.exists(val) == False): |
| 736 | doError('%s does not exist' % val) | 891 | doError('%s does not exist' % val) |
| 892 | testrun = False | ||
| 737 | sysvals.ftracefile = val | 893 | sysvals.ftracefile = val |
| 738 | elif(arg == '-addlogs'): | 894 | elif(arg == '-addlogs'): |
| 739 | sysvals.addlogs = True | 895 | sysvals.dmesglog = True |
| 740 | elif(arg == '-expandcg'): | 896 | elif(arg == '-expandcg'): |
| 741 | sysvals.cgexp = True | 897 | sysvals.cgexp = True |
| 742 | elif(arg == '-dmesg'): | 898 | elif(arg == '-dmesg'): |
| @@ -748,18 +904,15 @@ if __name__ == '__main__': | |||
| 748 | doError('%s does not exist' % val) | 904 | doError('%s does not exist' % val) |
| 749 | if(sysvals.htmlfile == val or sysvals.outfile == val): | 905 | if(sysvals.htmlfile == val or sysvals.outfile == val): |
| 750 | doError('Output filename collision') | 906 | doError('Output filename collision') |
| 907 | testrun = False | ||
| 751 | sysvals.dmesgfile = val | 908 | sysvals.dmesgfile = val |
| 752 | elif(arg == '-o'): | 909 | elif(arg == '-o'): |
| 753 | try: | 910 | try: |
| 754 | val = args.next() | 911 | val = args.next() |
| 755 | except: | 912 | except: |
| 756 | doError('No HTML filename supplied', True) | 913 | doError('No subdirectory name supplied', True) |
| 757 | if(sysvals.dmesgfile == val or sysvals.ftracefile == val): | 914 | sysvals.testdir = sysvals.setOutputFolder(val) |
| 758 | doError('Output filename collision') | ||
| 759 | sysvals.htmlfile = val | ||
| 760 | elif(arg == '-reboot'): | 915 | elif(arg == '-reboot'): |
| 761 | if sysvals.iscronjob: | ||
| 762 | doError('-reboot and -cronjob are incompatible') | ||
| 763 | sysvals.reboot = True | 916 | sysvals.reboot = True |
| 764 | elif(arg == '-manual'): | 917 | elif(arg == '-manual'): |
| 765 | sysvals.reboot = True | 918 | sysvals.reboot = True |
| @@ -767,58 +920,93 @@ if __name__ == '__main__': | |||
| 767 | # remaining options are only for cron job use | 920 | # remaining options are only for cron job use |
| 768 | elif(arg == '-cronjob'): | 921 | elif(arg == '-cronjob'): |
| 769 | sysvals.iscronjob = True | 922 | sysvals.iscronjob = True |
| 770 | if sysvals.reboot: | ||
| 771 | doError('-reboot and -cronjob are incompatible') | ||
| 772 | else: | 923 | else: |
| 773 | doError('Invalid argument: '+arg, True) | 924 | doError('Invalid argument: '+arg, True) |
| 774 | 925 | ||
| 926 | # compatibility errors and access checks | ||
| 927 | if(sysvals.iscronjob and (sysvals.reboot or \ | ||
| 928 | sysvals.dmesgfile or sysvals.ftracefile or cmd)): | ||
| 929 | doError('-cronjob is meant for batch purposes only') | ||
| 930 | if(sysvals.reboot and (sysvals.dmesgfile or sysvals.ftracefile)): | ||
| 931 | doError('-reboot and -dmesg/-ftrace are incompatible') | ||
| 932 | if cmd or sysvals.reboot or sysvals.iscronjob or testrun: | ||
| 933 | sysvals.rootCheck(True) | ||
| 934 | if (testrun and sysvals.useftrace) or cmd == 'flistall': | ||
| 935 | if not sysvals.verifyFtrace(): | ||
| 936 | doError('Ftrace is not properly enabled') | ||
| 937 | |||
| 938 | # run utility commands | ||
| 939 | sysvals.cpuInfo() | ||
| 775 | if cmd != '': | 940 | if cmd != '': |
| 776 | if cmd == 'updategrub': | 941 | if cmd == 'kpupdate': |
| 777 | updateGrub() | 942 | updateKernelParams() |
| 778 | elif cmd == 'flistall': | 943 | elif cmd == 'flistall': |
| 779 | sysvals.getFtraceFilterFunctions(False) | 944 | for f in sysvals.getBootFtraceFilterFunctions(): |
| 945 | print f | ||
| 946 | elif cmd == 'checkbl': | ||
| 947 | sysvals.getBootLoader() | ||
| 948 | print 'Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec) | ||
| 949 | elif(cmd == 'sysinfo'): | ||
| 950 | sysvals.printSystemInfo() | ||
| 780 | sys.exit() | 951 | sys.exit() |
| 781 | 952 | ||
| 782 | # update grub, setup a cronjob, and reboot | 953 | # reboot: update grub, setup a cronjob, and reboot |
| 783 | if sysvals.reboot: | 954 | if sysvals.reboot: |
| 955 | if (sysvals.useftrace or sysvals.usecallgraph) and \ | ||
| 956 | not sysvals.checkFtraceKernelVersion(): | ||
| 957 | doError('Ftrace functionality requires kernel v4.10 or newer') | ||
| 784 | if not sysvals.manual: | 958 | if not sysvals.manual: |
| 785 | updateGrub() | 959 | updateKernelParams() |
| 786 | updateCron() | 960 | updateCron() |
| 787 | call('reboot') | 961 | call('reboot') |
| 788 | else: | 962 | else: |
| 789 | sysvals.manualRebootRequired() | 963 | sysvals.manualRebootRequired() |
| 790 | sys.exit() | 964 | sys.exit() |
| 791 | 965 | ||
| 792 | # disable the cronjob | 966 | # cronjob: remove the cronjob, grub changes, and disable ftrace |
| 793 | if sysvals.iscronjob: | 967 | if sysvals.iscronjob: |
| 794 | updateCron(True) | 968 | updateCron(True) |
| 795 | updateGrub(True) | 969 | updateKernelParams(True) |
| 970 | try: | ||
| 971 | sysvals.fsetVal('0', 'tracing_on') | ||
| 972 | except: | ||
| 973 | pass | ||
| 796 | 974 | ||
| 797 | data = loadKernelLog() | 975 | # testrun: generate copies of the logs |
| 798 | if sysvals.useftrace: | 976 | if testrun: |
| 799 | loadTraceLog(data) | 977 | retrieveLogs() |
| 800 | if sysvals.iscronjob: | 978 | else: |
| 801 | try: | 979 | sysvals.setOutputFile() |
| 802 | sysvals.fsetVal('0', 'tracing_on') | ||
| 803 | except: | ||
| 804 | pass | ||
| 805 | 980 | ||
| 806 | if(sysvals.outfile and sysvals.phoronix): | 981 | # process the log data |
| 807 | fp = open(sysvals.outfile, 'w') | 982 | if sysvals.dmesgfile: |
| 808 | fp.write('pass %s initstart %.3f end %.3f boot %s\n' % | 983 | data = parseKernelLog() |
| 809 | (data.valid, data.initstart*1000, data.end*1000, data.boottime)) | 984 | if(not data.valid): |
| 810 | fp.close() | ||
| 811 | if(not data.valid): | ||
| 812 | if sysvals.dmesgfile: | ||
| 813 | doError('No initcall data found in %s' % sysvals.dmesgfile) | 985 | doError('No initcall data found in %s' % sysvals.dmesgfile) |
| 814 | else: | 986 | if sysvals.useftrace and sysvals.ftracefile: |
| 815 | doError('No initcall data found, is initcall_debug enabled?') | 987 | parseTraceLog(data) |
| 988 | else: | ||
| 989 | doError('dmesg file required') | ||
| 816 | 990 | ||
| 817 | print(' Host: %s' % sysvals.hostname) | 991 | print(' Host: %s' % sysvals.hostname) |
| 818 | print(' Test time: %s' % sysvals.testtime) | 992 | print(' Test time: %s' % sysvals.testtime) |
| 819 | print(' Boot time: %s' % data.boottime) | 993 | print(' Boot time: %s' % data.boottime) |
| 820 | print('Kernel Version: %s' % sysvals.kernel) | 994 | print('Kernel Version: %s' % sysvals.kernel) |
| 821 | print(' Kernel start: %.3f' % (data.start * 1000)) | 995 | print(' Kernel start: %.3f' % (data.start * 1000)) |
| 822 | print(' init start: %.3f' % (data.initstart * 1000)) | 996 | print('Usermode start: %.3f' % (data.tUserMode * 1000)) |
| 997 | print('Last Init Call: %.3f' % (data.end * 1000)) | ||
| 998 | |||
| 999 | # handle embedded output logs | ||
| 1000 | if(sysvals.outfile and sysvals.embedded): | ||
| 1001 | fp = open(sysvals.outfile, 'w') | ||
| 1002 | fp.write('pass %s initstart %.3f end %.3f boot %s\n' % | ||
| 1003 | (data.valid, data.tUserMode*1000, data.end*1000, data.boottime)) | ||
| 1004 | fp.close() | ||
| 1005 | |||
| 1006 | createBootGraph(data) | ||
| 823 | 1007 | ||
| 824 | createBootGraph(data, sysvals.phoronix) | 1008 | # if running as root, change output dir owner to sudo_user |
| 1009 | if testrun and os.path.isdir(sysvals.testdir) and \ | ||
| 1010 | os.getuid() == 0 and 'SUDO_USER' in os.environ: | ||
| 1011 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' | ||
| 1012 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) | ||
