12c9a583bSTodd Brandt#!/usr/bin/env python3 22025cf9eSThomas Gleixner# SPDX-License-Identifier: GPL-2.0-only 3a6fbdbb2STodd E Brandt# 4a6fbdbb2STodd E Brandt# Tool for analyzing boot timing 5a6fbdbb2STodd E Brandt# Copyright (c) 2013, Intel Corporation. 6a6fbdbb2STodd E Brandt# 71446794aSTodd Brandt# This program is free software; you can redistribute it and/or modify it 81446794aSTodd Brandt# under the terms and conditions of the GNU General Public License, 91446794aSTodd Brandt# version 2, as published by the Free Software Foundation. 101446794aSTodd Brandt# 111446794aSTodd Brandt# This program is distributed in the hope it will be useful, but WITHOUT 121446794aSTodd Brandt# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 131446794aSTodd Brandt# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 141446794aSTodd Brandt# more details. 151446794aSTodd Brandt# 16a6fbdbb2STodd E Brandt# Authors: 17a6fbdbb2STodd E Brandt# Todd Brandt <todd.e.brandt@linux.intel.com> 18a6fbdbb2STodd E Brandt# 19a6fbdbb2STodd E Brandt# Description: 20a6fbdbb2STodd E Brandt# This tool is designed to assist kernel and OS developers in optimizing 21a6fbdbb2STodd E Brandt# their linux stack's boot time. It creates an html representation of 22a6fbdbb2STodd E Brandt# the kernel boot timeline up to the start of the init process. 23a6fbdbb2STodd E Brandt# 24a6fbdbb2STodd E Brandt 25a6fbdbb2STodd E Brandt# ----------------- LIBRARIES -------------------- 26a6fbdbb2STodd E Brandt 27a6fbdbb2STodd E Brandtimport sys 28a6fbdbb2STodd E Brandtimport time 29a6fbdbb2STodd E Brandtimport os 30a6fbdbb2STodd E Brandtimport string 31a6fbdbb2STodd E Brandtimport re 32a6fbdbb2STodd E Brandtimport platform 33a6fbdbb2STodd E Brandtimport shutil 34a6fbdbb2STodd E Brandtfrom datetime import datetime, timedelta 35a6fbdbb2STodd E Brandtfrom subprocess import call, Popen, PIPE 36d83a76a8STodd E Brandtimport sleepgraph as aslib 37a6fbdbb2STodd E Brandt 3818d3f8fcSTodd Brandtdef pprint(msg): 3918d3f8fcSTodd Brandt print(msg) 4018d3f8fcSTodd Brandt sys.stdout.flush() 4118d3f8fcSTodd Brandt 42a6fbdbb2STodd E Brandt# ----------------- CLASSES -------------------- 43a6fbdbb2STodd E Brandt 44a6fbdbb2STodd E Brandt# Class: SystemValues 45a6fbdbb2STodd E Brandt# Description: 46a6fbdbb2STodd E Brandt# A global, single-instance container used to 47a6fbdbb2STodd E Brandt# store system values and test parameters 48a6fbdbb2STodd E Brandtclass SystemValues(aslib.SystemValues): 49a6fbdbb2STodd E Brandt title = 'BootGraph' 50d83a76a8STodd E Brandt version = '2.2' 51a6fbdbb2STodd E Brandt hostname = 'localhost' 52a6fbdbb2STodd E Brandt testtime = '' 53a6fbdbb2STodd E Brandt kernel = '' 54a6fbdbb2STodd E Brandt dmesgfile = '' 55a6fbdbb2STodd E Brandt ftracefile = '' 56a6fbdbb2STodd E Brandt htmlfile = 'bootgraph.html' 57a6fbdbb2STodd E Brandt testdir = '' 58d83a76a8STodd E Brandt kparams = '' 59d83a76a8STodd E Brandt result = '' 60a6fbdbb2STodd E Brandt useftrace = False 61a6fbdbb2STodd E Brandt usecallgraph = False 62a6fbdbb2STodd E Brandt suspendmode = 'boot' 63a6fbdbb2STodd E Brandt max_graph_depth = 2 64a6fbdbb2STodd E Brandt graph_filter = 'do_one_initcall' 65a6fbdbb2STodd E Brandt reboot = False 66a6fbdbb2STodd E Brandt manual = False 67a6fbdbb2STodd E Brandt iscronjob = False 68a6fbdbb2STodd E Brandt timeformat = '%.6f' 69a6fbdbb2STodd E Brandt bootloader = 'grub' 70a6fbdbb2STodd E Brandt blexec = [] 71a6fbdbb2STodd E Brandt def __init__(self): 72b3f6c43dSTodd Brandt self.kernel, self.hostname = 'unknown', platform.node() 73a6fbdbb2STodd E Brandt self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') 74a6fbdbb2STodd E Brandt if os.path.exists('/proc/version'): 75a6fbdbb2STodd E Brandt fp = open('/proc/version', 'r') 76b3f6c43dSTodd Brandt self.kernel = self.kernelVersion(fp.read().strip()) 77a6fbdbb2STodd E Brandt fp.close() 78a6fbdbb2STodd E Brandt self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S') 79a6fbdbb2STodd E Brandt def kernelVersion(self, msg): 80*8b2f0cb6STodd Brandt m = re.match(r'^[Ll]inux *[Vv]ersion *(?P<v>\S*) .*', msg) 81b3f6c43dSTodd Brandt if m: 82b3f6c43dSTodd Brandt return m.group('v') 83b3f6c43dSTodd Brandt return 'unknown' 84a6fbdbb2STodd E Brandt def checkFtraceKernelVersion(self): 85*8b2f0cb6STodd Brandt m = re.match(r'^(?P<x>[0-9]*)\.(?P<y>[0-9]*)\.(?P<z>[0-9]*).*', self.kernel) 86b3f6c43dSTodd Brandt if m: 87b3f6c43dSTodd Brandt val = tuple(map(int, m.groups())) 88a6fbdbb2STodd E Brandt if val >= (4, 10, 0): 89a6fbdbb2STodd E Brandt return True 90a6fbdbb2STodd E Brandt return False 91a6fbdbb2STodd E Brandt def kernelParams(self): 92a6fbdbb2STodd E Brandt cmdline = 'initcall_debug log_buf_len=32M' 93a6fbdbb2STodd E Brandt if self.useftrace: 94a6fbdbb2STodd E Brandt if self.cpucount > 0: 951446794aSTodd Brandt bs = min(self.memtotal // 2, 2*1024*1024) // self.cpucount 96a6fbdbb2STodd E Brandt else: 97a6fbdbb2STodd E Brandt bs = 131072 98a6fbdbb2STodd E Brandt cmdline += ' trace_buf_size=%dK trace_clock=global '\ 99a6fbdbb2STodd E Brandt 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ 100a6fbdbb2STodd E Brandt 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ 101a6fbdbb2STodd E Brandt 'nofuncgraph-overhead,context-info,graph-time '\ 102a6fbdbb2STodd E Brandt 'ftrace=function_graph '\ 103a6fbdbb2STodd E Brandt 'ftrace_graph_max_depth=%d '\ 104a6fbdbb2STodd E Brandt 'ftrace_graph_filter=%s' % \ 105a6fbdbb2STodd E Brandt (bs, self.max_graph_depth, self.graph_filter) 106a6fbdbb2STodd E Brandt return cmdline 107a6fbdbb2STodd E Brandt def setGraphFilter(self, val): 108a6fbdbb2STodd E Brandt master = self.getBootFtraceFilterFunctions() 109a6fbdbb2STodd E Brandt fs = '' 110a6fbdbb2STodd E Brandt for i in val.split(','): 111a6fbdbb2STodd E Brandt func = i.strip() 112a6fbdbb2STodd E Brandt if func == '': 113a6fbdbb2STodd E Brandt doError('badly formatted filter function string') 114a6fbdbb2STodd E Brandt if '[' in func or ']' in func: 115a6fbdbb2STodd E Brandt doError('loadable module functions not allowed - "%s"' % func) 116a6fbdbb2STodd E Brandt if ' ' in func: 117a6fbdbb2STodd E Brandt doError('spaces found in filter functions - "%s"' % func) 118a6fbdbb2STodd E Brandt if func not in master: 119a6fbdbb2STodd E Brandt doError('function "%s" not available for ftrace' % func) 120a6fbdbb2STodd E Brandt if not fs: 121a6fbdbb2STodd E Brandt fs = func 122a6fbdbb2STodd E Brandt else: 123a6fbdbb2STodd E Brandt fs += ','+func 124a6fbdbb2STodd E Brandt if not fs: 125a6fbdbb2STodd E Brandt doError('badly formatted filter function string') 126a6fbdbb2STodd E Brandt self.graph_filter = fs 127a6fbdbb2STodd E Brandt def getBootFtraceFilterFunctions(self): 128a6fbdbb2STodd E Brandt self.rootCheck(True) 129a6fbdbb2STodd E Brandt fp = open(self.tpath+'available_filter_functions') 130a6fbdbb2STodd E Brandt fulllist = fp.read().split('\n') 131a6fbdbb2STodd E Brandt fp.close() 132a6fbdbb2STodd E Brandt list = [] 133a6fbdbb2STodd E Brandt for i in fulllist: 134a6fbdbb2STodd E Brandt if not i or ' ' in i or '[' in i or ']' in i: 135a6fbdbb2STodd E Brandt continue 136a6fbdbb2STodd E Brandt list.append(i) 137a6fbdbb2STodd E Brandt return list 138a6fbdbb2STodd E Brandt def myCronJob(self, line): 139a6fbdbb2STodd E Brandt if '@reboot' not in line: 140a6fbdbb2STodd E Brandt return False 141a6fbdbb2STodd E Brandt if 'bootgraph' in line or 'analyze_boot.py' in line or '-cronjob' in line: 142a6fbdbb2STodd E Brandt return True 143a6fbdbb2STodd E Brandt return False 144a6fbdbb2STodd E Brandt def cronjobCmdString(self): 145a6fbdbb2STodd E Brandt cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) 146a6fbdbb2STodd E Brandt args = iter(sys.argv[1:]) 147a6fbdbb2STodd E Brandt for arg in args: 148d83a76a8STodd E Brandt if arg in ['-h', '-v', '-cronjob', '-reboot', '-verbose']: 149a6fbdbb2STodd E Brandt continue 150a6fbdbb2STodd E Brandt elif arg in ['-o', '-dmesg', '-ftrace', '-func']: 1511446794aSTodd Brandt next(args) 152a6fbdbb2STodd E Brandt continue 153d83a76a8STodd E Brandt elif arg == '-result': 1541446794aSTodd Brandt cmdline += ' %s "%s"' % (arg, os.path.abspath(next(args))) 155d83a76a8STodd E Brandt continue 156d83a76a8STodd E Brandt elif arg == '-cgskip': 1571446794aSTodd Brandt file = self.configFile(next(args)) 158d83a76a8STodd E Brandt cmdline += ' %s "%s"' % (arg, os.path.abspath(file)) 159d83a76a8STodd E Brandt continue 160a6fbdbb2STodd E Brandt cmdline += ' '+arg 161a6fbdbb2STodd E Brandt if self.graph_filter != 'do_one_initcall': 162a6fbdbb2STodd E Brandt cmdline += ' -func "%s"' % self.graph_filter 163a6fbdbb2STodd E Brandt cmdline += ' -o "%s"' % os.path.abspath(self.testdir) 164a6fbdbb2STodd E Brandt return cmdline 165a6fbdbb2STodd E Brandt def manualRebootRequired(self): 166a6fbdbb2STodd E Brandt cmdline = self.kernelParams() 16718d3f8fcSTodd Brandt pprint('To generate a new timeline manually, follow these steps:\n\n'\ 16818d3f8fcSTodd Brandt '1. Add the CMDLINE string to your kernel command line.\n'\ 16918d3f8fcSTodd Brandt '2. Reboot the system.\n'\ 17018d3f8fcSTodd Brandt '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n\n'\ 17118d3f8fcSTodd Brandt 'CMDLINE="%s"' % cmdline) 172a6fbdbb2STodd E Brandt sys.exit() 173a6fbdbb2STodd E Brandt def blGrub(self): 174a6fbdbb2STodd E Brandt blcmd = '' 175a6fbdbb2STodd E Brandt for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: 176a6fbdbb2STodd E Brandt if blcmd: 177a6fbdbb2STodd E Brandt break 178a6fbdbb2STodd E Brandt blcmd = self.getExec(cmd) 179a6fbdbb2STodd E Brandt if not blcmd: 180a6fbdbb2STodd E Brandt doError('[GRUB] missing update command') 181a6fbdbb2STodd E Brandt if not os.path.exists('/etc/default/grub'): 182a6fbdbb2STodd E Brandt doError('[GRUB] missing /etc/default/grub') 183a6fbdbb2STodd E Brandt if 'grub2' in blcmd: 184a6fbdbb2STodd E Brandt cfg = '/boot/grub2/grub.cfg' 185a6fbdbb2STodd E Brandt else: 186a6fbdbb2STodd E Brandt cfg = '/boot/grub/grub.cfg' 187a6fbdbb2STodd E Brandt if not os.path.exists(cfg): 188a6fbdbb2STodd E Brandt doError('[GRUB] missing %s' % cfg) 189a6fbdbb2STodd E Brandt if 'update-grub' in blcmd: 190a6fbdbb2STodd E Brandt self.blexec = [blcmd] 191a6fbdbb2STodd E Brandt else: 192a6fbdbb2STodd E Brandt self.blexec = [blcmd, '-o', cfg] 193a6fbdbb2STodd E Brandt def getBootLoader(self): 194a6fbdbb2STodd E Brandt if self.bootloader == 'grub': 195a6fbdbb2STodd E Brandt self.blGrub() 196a6fbdbb2STodd E Brandt else: 197a6fbdbb2STodd E Brandt doError('unknown boot loader: %s' % self.bootloader) 198d83a76a8STodd E Brandt def writeDatafileHeader(self, filename): 199d83a76a8STodd E Brandt self.kparams = open('/proc/cmdline', 'r').read().strip() 200d83a76a8STodd E Brandt fp = open(filename, 'w') 201d83a76a8STodd E Brandt fp.write(self.teststamp+'\n') 202d83a76a8STodd E Brandt fp.write(self.sysstamp+'\n') 203d83a76a8STodd E Brandt fp.write('# command | %s\n' % self.cmdline) 204d83a76a8STodd E Brandt fp.write('# kparams | %s\n' % self.kparams) 205d83a76a8STodd E Brandt fp.close() 206a6fbdbb2STodd E Brandt 207a6fbdbb2STodd E Brandtsysvals = SystemValues() 208a6fbdbb2STodd E Brandt 209a6fbdbb2STodd E Brandt# Class: Data 210a6fbdbb2STodd E Brandt# Description: 211a6fbdbb2STodd E Brandt# The primary container for test data. 212a6fbdbb2STodd E Brandtclass Data(aslib.Data): 213a6fbdbb2STodd E Brandt dmesg = {} # root data structure 214a6fbdbb2STodd E Brandt start = 0.0 # test start 215a6fbdbb2STodd E Brandt end = 0.0 # test end 216a6fbdbb2STodd E Brandt dmesgtext = [] # dmesg text file in memory 217a6fbdbb2STodd E Brandt testnumber = 0 218a6fbdbb2STodd E Brandt idstr = '' 219a6fbdbb2STodd E Brandt html_device_id = 0 220a6fbdbb2STodd E Brandt valid = False 221a6fbdbb2STodd E Brandt tUserMode = 0.0 222a6fbdbb2STodd E Brandt boottime = '' 223a6fbdbb2STodd E Brandt phases = ['kernel', 'user'] 224a6fbdbb2STodd E Brandt do_one_initcall = False 225a6fbdbb2STodd E Brandt def __init__(self, num): 226a6fbdbb2STodd E Brandt self.testnumber = num 227a6fbdbb2STodd E Brandt self.idstr = 'a' 228a6fbdbb2STodd E Brandt self.dmesgtext = [] 229a6fbdbb2STodd E Brandt self.dmesg = { 230a6fbdbb2STodd E Brandt 'kernel': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, 231a6fbdbb2STodd E Brandt 'order': 0, 'color': 'linear-gradient(to bottom, #fff, #bcf)'}, 232a6fbdbb2STodd E Brandt 'user': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, 233a6fbdbb2STodd E Brandt 'order': 1, 'color': '#fff'} 234a6fbdbb2STodd E Brandt } 235a6fbdbb2STodd E Brandt def deviceTopology(self): 236a6fbdbb2STodd E Brandt return '' 237a6fbdbb2STodd E Brandt def newAction(self, phase, name, pid, start, end, ret, ulen): 238a6fbdbb2STodd E Brandt # new device callback for a specific phase 239a6fbdbb2STodd E Brandt self.html_device_id += 1 240a6fbdbb2STodd E Brandt devid = '%s%d' % (self.idstr, self.html_device_id) 241a6fbdbb2STodd E Brandt list = self.dmesg[phase]['list'] 242a6fbdbb2STodd E Brandt length = -1.0 243a6fbdbb2STodd E Brandt if(start >= 0 and end >= 0): 244a6fbdbb2STodd E Brandt length = end - start 245a6fbdbb2STodd E Brandt i = 2 246a6fbdbb2STodd E Brandt origname = name 247a6fbdbb2STodd E Brandt while(name in list): 248a6fbdbb2STodd E Brandt name = '%s[%d]' % (origname, i) 249a6fbdbb2STodd E Brandt i += 1 250a6fbdbb2STodd E Brandt list[name] = {'name': name, 'start': start, 'end': end, 251a6fbdbb2STodd E Brandt 'pid': pid, 'length': length, 'row': 0, 'id': devid, 252a6fbdbb2STodd E Brandt 'ret': ret, 'ulen': ulen } 253a6fbdbb2STodd E Brandt return name 254a6fbdbb2STodd E Brandt def deviceMatch(self, pid, cg): 255a6fbdbb2STodd E Brandt if cg.end - cg.start == 0: 256d83a76a8STodd E Brandt return '' 257a6fbdbb2STodd E Brandt for p in data.phases: 258a6fbdbb2STodd E Brandt list = self.dmesg[p]['list'] 259a6fbdbb2STodd E Brandt for devname in list: 260a6fbdbb2STodd E Brandt dev = list[devname] 261a6fbdbb2STodd E Brandt if pid != dev['pid']: 262a6fbdbb2STodd E Brandt continue 263a6fbdbb2STodd E Brandt if cg.name == 'do_one_initcall': 264a6fbdbb2STodd E Brandt if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): 265a6fbdbb2STodd E Brandt dev['ftrace'] = cg 266a6fbdbb2STodd E Brandt self.do_one_initcall = True 267d83a76a8STodd E Brandt return devname 268a6fbdbb2STodd E Brandt else: 269a6fbdbb2STodd E Brandt if(cg.start > dev['start'] and cg.end < dev['end']): 270a6fbdbb2STodd E Brandt if 'ftraces' not in dev: 271a6fbdbb2STodd E Brandt dev['ftraces'] = [] 272a6fbdbb2STodd E Brandt dev['ftraces'].append(cg) 273d83a76a8STodd E Brandt return devname 274d83a76a8STodd E Brandt return '' 275d83a76a8STodd E Brandt def printDetails(self): 276d83a76a8STodd E Brandt sysvals.vprint('Timeline Details:') 277d83a76a8STodd E Brandt sysvals.vprint(' Host: %s' % sysvals.hostname) 278d83a76a8STodd E Brandt sysvals.vprint(' Kernel: %s' % sysvals.kernel) 279d83a76a8STodd E Brandt sysvals.vprint(' Test time: %s' % sysvals.testtime) 280d83a76a8STodd E Brandt sysvals.vprint(' Boot time: %s' % self.boottime) 281d83a76a8STodd E Brandt for phase in self.phases: 282d83a76a8STodd E Brandt dc = len(self.dmesg[phase]['list']) 283d83a76a8STodd E Brandt sysvals.vprint('%9s mode: %.3f - %.3f (%d initcalls)' % (phase, 284d83a76a8STodd E Brandt self.dmesg[phase]['start']*1000, 285d83a76a8STodd E Brandt self.dmesg[phase]['end']*1000, dc)) 286a6fbdbb2STodd E Brandt 287a6fbdbb2STodd E Brandt# ----------------- FUNCTIONS -------------------- 288a6fbdbb2STodd E Brandt 289a6fbdbb2STodd E Brandt# Function: parseKernelLog 290a6fbdbb2STodd E Brandt# Description: 291a6fbdbb2STodd E Brandt# parse a kernel log for boot data 292a6fbdbb2STodd E Brandtdef parseKernelLog(): 293d83a76a8STodd E Brandt sysvals.vprint('Analyzing the dmesg data (%s)...' % \ 294d83a76a8STodd E Brandt os.path.basename(sysvals.dmesgfile)) 295a6fbdbb2STodd E Brandt phase = 'kernel' 296a6fbdbb2STodd E Brandt data = Data(0) 297a6fbdbb2STodd E Brandt data.dmesg['kernel']['start'] = data.start = ktime = 0.0 298a6fbdbb2STodd E Brandt sysvals.stamp = { 299a6fbdbb2STodd E Brandt 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), 300a6fbdbb2STodd E Brandt 'host': sysvals.hostname, 301a6fbdbb2STodd E Brandt 'mode': 'boot', 'kernel': ''} 302a6fbdbb2STodd E Brandt 303a6fbdbb2STodd E Brandt tp = aslib.TestProps() 304a6fbdbb2STodd E Brandt devtemp = dict() 305a6fbdbb2STodd E Brandt if(sysvals.dmesgfile): 3061446794aSTodd Brandt lf = open(sysvals.dmesgfile, 'rb') 307a6fbdbb2STodd E Brandt else: 308a6fbdbb2STodd E Brandt lf = Popen('dmesg', stdout=PIPE).stdout 309a6fbdbb2STodd E Brandt for line in lf: 3101446794aSTodd Brandt line = aslib.ascii(line).replace('\r\n', '') 311a6fbdbb2STodd E Brandt # grab the stamp and sysinfo 312a6fbdbb2STodd E Brandt if re.match(tp.stampfmt, line): 313a6fbdbb2STodd E Brandt tp.stamp = line 314a6fbdbb2STodd E Brandt continue 315a6fbdbb2STodd E Brandt elif re.match(tp.sysinfofmt, line): 316a6fbdbb2STodd E Brandt tp.sysinfo = line 317a6fbdbb2STodd E Brandt continue 318d83a76a8STodd E Brandt elif re.match(tp.cmdlinefmt, line): 319d83a76a8STodd E Brandt tp.cmdline = line 320d83a76a8STodd E Brandt continue 321d83a76a8STodd E Brandt elif re.match(tp.kparamsfmt, line): 322d83a76a8STodd E Brandt tp.kparams = line 323d83a76a8STodd E Brandt continue 324a6fbdbb2STodd E Brandt idx = line.find('[') 325a6fbdbb2STodd E Brandt if idx > 1: 326a6fbdbb2STodd E Brandt line = line[idx:] 327*8b2f0cb6STodd Brandt m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line) 328a6fbdbb2STodd E Brandt if(not m): 329a6fbdbb2STodd E Brandt continue 330a6fbdbb2STodd E Brandt ktime = float(m.group('ktime')) 331a6fbdbb2STodd E Brandt if(ktime > 120): 332a6fbdbb2STodd E Brandt break 333a6fbdbb2STodd E Brandt msg = m.group('msg') 334a6fbdbb2STodd E Brandt data.dmesgtext.append(line) 335*8b2f0cb6STodd Brandt if(ktime == 0.0 and re.match(r'^Linux version .*', msg)): 336a6fbdbb2STodd E Brandt if(not sysvals.stamp['kernel']): 337a6fbdbb2STodd E Brandt sysvals.stamp['kernel'] = sysvals.kernelVersion(msg) 338a6fbdbb2STodd E Brandt continue 339*8b2f0cb6STodd Brandt m = re.match(r'.* setting system clock to (?P<d>[0-9\-]*)[ A-Z](?P<t>[0-9:]*) UTC.*', msg) 340a6fbdbb2STodd E Brandt if(m): 34145dd0a42STodd Brandt bt = datetime.strptime(m.group('d')+' '+m.group('t'), '%Y-%m-%d %H:%M:%S') 342a6fbdbb2STodd E Brandt bt = bt - timedelta(seconds=int(ktime)) 343a6fbdbb2STodd E Brandt data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') 344a6fbdbb2STodd E Brandt sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') 345a6fbdbb2STodd E Brandt continue 346*8b2f0cb6STodd Brandt m = re.match(r'^calling *(?P<f>.*)\+.* @ (?P<p>[0-9]*)', msg) 347a6fbdbb2STodd E Brandt if(m): 348a6fbdbb2STodd E Brandt func = m.group('f') 349a6fbdbb2STodd E Brandt pid = int(m.group('p')) 350a6fbdbb2STodd E Brandt devtemp[func] = (ktime, pid) 351a6fbdbb2STodd E Brandt continue 352*8b2f0cb6STodd Brandt m = re.match(r'^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) 353a6fbdbb2STodd E Brandt if(m): 354a6fbdbb2STodd E Brandt data.valid = True 355a6fbdbb2STodd E Brandt data.end = ktime 356a6fbdbb2STodd E Brandt f, r, t = m.group('f', 'r', 't') 357a6fbdbb2STodd E Brandt if(f in devtemp): 358a6fbdbb2STodd E Brandt start, pid = devtemp[f] 359a6fbdbb2STodd E Brandt data.newAction(phase, f, pid, start, ktime, int(r), int(t)) 360a6fbdbb2STodd E Brandt del devtemp[f] 361a6fbdbb2STodd E Brandt continue 362*8b2f0cb6STodd Brandt if(re.match(r'^Freeing unused kernel .*', msg)): 363a6fbdbb2STodd E Brandt data.tUserMode = ktime 364a6fbdbb2STodd E Brandt data.dmesg['kernel']['end'] = ktime 365a6fbdbb2STodd E Brandt data.dmesg['user']['start'] = ktime 366a6fbdbb2STodd E Brandt phase = 'user' 367a6fbdbb2STodd E Brandt 368a6fbdbb2STodd E Brandt if tp.stamp: 369a6fbdbb2STodd E Brandt sysvals.stamp = 0 370a6fbdbb2STodd E Brandt tp.parseStamp(data, sysvals) 371a6fbdbb2STodd E Brandt data.dmesg['user']['end'] = data.end 372a6fbdbb2STodd E Brandt lf.close() 373a6fbdbb2STodd E Brandt return data 374a6fbdbb2STodd E Brandt 375a6fbdbb2STodd E Brandt# Function: parseTraceLog 376a6fbdbb2STodd E Brandt# Description: 377a6fbdbb2STodd E Brandt# Check if trace is available and copy to a temp file 378a6fbdbb2STodd E Brandtdef parseTraceLog(data): 379d83a76a8STodd E Brandt sysvals.vprint('Analyzing the ftrace data (%s)...' % \ 380d83a76a8STodd E Brandt os.path.basename(sysvals.ftracefile)) 381d83a76a8STodd E Brandt # if available, calculate cgfilter allowable ranges 382d83a76a8STodd E Brandt cgfilter = [] 383d83a76a8STodd E Brandt if len(sysvals.cgfilter) > 0: 384d83a76a8STodd E Brandt for p in data.phases: 385d83a76a8STodd E Brandt list = data.dmesg[p]['list'] 386d83a76a8STodd E Brandt for i in sysvals.cgfilter: 387d83a76a8STodd E Brandt if i in list: 388d83a76a8STodd E Brandt cgfilter.append([list[i]['start']-0.0001, 389d83a76a8STodd E Brandt list[i]['end']+0.0001]) 390a6fbdbb2STodd E Brandt # parse the trace log 391a6fbdbb2STodd E Brandt ftemp = dict() 392a6fbdbb2STodd E Brandt tp = aslib.TestProps() 393a6fbdbb2STodd E Brandt tp.setTracerType('function_graph') 394a6fbdbb2STodd E Brandt tf = open(sysvals.ftracefile, 'r') 395a6fbdbb2STodd E Brandt for line in tf: 396a6fbdbb2STodd E Brandt if line[0] == '#': 397a6fbdbb2STodd E Brandt continue 398a6fbdbb2STodd E Brandt m = re.match(tp.ftrace_line_fmt, line.strip()) 399a6fbdbb2STodd E Brandt if(not m): 400a6fbdbb2STodd E Brandt continue 401a6fbdbb2STodd E Brandt m_time, m_proc, m_pid, m_msg, m_dur = \ 402a6fbdbb2STodd E Brandt m.group('time', 'proc', 'pid', 'msg', 'dur') 403d83a76a8STodd E Brandt t = float(m_time) 404d83a76a8STodd E Brandt if len(cgfilter) > 0: 405d83a76a8STodd E Brandt allow = False 406d83a76a8STodd E Brandt for r in cgfilter: 407d83a76a8STodd E Brandt if t >= r[0] and t < r[1]: 408d83a76a8STodd E Brandt allow = True 409d83a76a8STodd E Brandt break 410d83a76a8STodd E Brandt if not allow: 411d83a76a8STodd E Brandt continue 412d83a76a8STodd E Brandt if t > data.end: 413a6fbdbb2STodd E Brandt break 414a6fbdbb2STodd E Brandt if(m_time and m_pid and m_msg): 415a6fbdbb2STodd E Brandt t = aslib.FTraceLine(m_time, m_msg, m_dur) 416a6fbdbb2STodd E Brandt pid = int(m_pid) 417a6fbdbb2STodd E Brandt else: 418a6fbdbb2STodd E Brandt continue 419a6fbdbb2STodd E Brandt if t.fevent or t.fkprobe: 420a6fbdbb2STodd E Brandt continue 421a6fbdbb2STodd E Brandt key = (m_proc, pid) 422a6fbdbb2STodd E Brandt if(key not in ftemp): 423a6fbdbb2STodd E Brandt ftemp[key] = [] 424d83a76a8STodd E Brandt ftemp[key].append(aslib.FTraceCallGraph(pid, sysvals)) 425a6fbdbb2STodd E Brandt cg = ftemp[key][-1] 426d83a76a8STodd E Brandt res = cg.addLine(t) 427d83a76a8STodd E Brandt if(res != 0): 428d83a76a8STodd E Brandt ftemp[key].append(aslib.FTraceCallGraph(pid, sysvals)) 429d83a76a8STodd E Brandt if(res == -1): 430d83a76a8STodd E Brandt ftemp[key][-1].addLine(t) 431d83a76a8STodd E Brandt 432a6fbdbb2STodd E Brandt tf.close() 433a6fbdbb2STodd E Brandt 434a6fbdbb2STodd E Brandt # add the callgraph data to the device hierarchy 435a6fbdbb2STodd E Brandt for key in ftemp: 436a6fbdbb2STodd E Brandt proc, pid = key 437a6fbdbb2STodd E Brandt for cg in ftemp[key]: 438d83a76a8STodd E Brandt if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0): 439a6fbdbb2STodd E Brandt continue 440a6fbdbb2STodd E Brandt if(not cg.postProcess()): 44118d3f8fcSTodd Brandt pprint('Sanity check failed for %s-%d' % (proc, pid)) 442a6fbdbb2STodd E Brandt continue 443a6fbdbb2STodd E Brandt # match cg data to devices 444d83a76a8STodd E Brandt devname = data.deviceMatch(pid, cg) 445d83a76a8STodd E Brandt if not devname: 446d83a76a8STodd E Brandt kind = 'Orphan' 447d83a76a8STodd E Brandt if cg.partial: 448d83a76a8STodd E Brandt kind = 'Partial' 449d83a76a8STodd E Brandt sysvals.vprint('%s callgraph found for %s %s-%d [%f - %f]' %\ 450d83a76a8STodd E Brandt (kind, cg.name, proc, pid, cg.start, cg.end)) 451d83a76a8STodd E Brandt elif len(cg.list) > 1000000: 45218d3f8fcSTodd Brandt pprint('WARNING: the callgraph found for %s is massive! (%d lines)' %\ 45318d3f8fcSTodd Brandt (devname, len(cg.list))) 454a6fbdbb2STodd E Brandt 455a6fbdbb2STodd E Brandt# Function: retrieveLogs 456a6fbdbb2STodd E Brandt# Description: 457a6fbdbb2STodd E Brandt# Create copies of dmesg and/or ftrace for later processing 458a6fbdbb2STodd E Brandtdef retrieveLogs(): 459a6fbdbb2STodd E Brandt # check ftrace is configured first 460a6fbdbb2STodd E Brandt if sysvals.useftrace: 461a6fbdbb2STodd E Brandt tracer = sysvals.fgetVal('current_tracer').strip() 462a6fbdbb2STodd E Brandt if tracer != 'function_graph': 463a6fbdbb2STodd E Brandt doError('ftrace not configured for a boot callgraph') 464a6fbdbb2STodd E Brandt # create the folder and get dmesg 465a6fbdbb2STodd E Brandt sysvals.systemInfo(aslib.dmidecode(sysvals.mempath)) 466a6fbdbb2STodd E Brandt sysvals.initTestOutput('boot') 467a6fbdbb2STodd E Brandt sysvals.writeDatafileHeader(sysvals.dmesgfile) 468a6fbdbb2STodd E Brandt call('dmesg >> '+sysvals.dmesgfile, shell=True) 469a6fbdbb2STodd E Brandt if not sysvals.useftrace: 470a6fbdbb2STodd E Brandt return 471a6fbdbb2STodd E Brandt # get ftrace 472a6fbdbb2STodd E Brandt sysvals.writeDatafileHeader(sysvals.ftracefile) 473a6fbdbb2STodd E Brandt call('cat '+sysvals.tpath+'trace >> '+sysvals.ftracefile, shell=True) 474a6fbdbb2STodd E Brandt 475a6fbdbb2STodd E Brandt# Function: colorForName 476a6fbdbb2STodd E Brandt# Description: 477a6fbdbb2STodd E Brandt# Generate a repeatable color from a list for a given name 478a6fbdbb2STodd E Brandtdef colorForName(name): 479a6fbdbb2STodd E Brandt list = [ 480a6fbdbb2STodd E Brandt ('c1', '#ec9999'), 481a6fbdbb2STodd E Brandt ('c2', '#ffc1a6'), 482a6fbdbb2STodd E Brandt ('c3', '#fff0a6'), 483a6fbdbb2STodd E Brandt ('c4', '#adf199'), 484a6fbdbb2STodd E Brandt ('c5', '#9fadea'), 485a6fbdbb2STodd E Brandt ('c6', '#a699c1'), 486a6fbdbb2STodd E Brandt ('c7', '#ad99b4'), 487a6fbdbb2STodd E Brandt ('c8', '#eaffea'), 488a6fbdbb2STodd E Brandt ('c9', '#dcecfb'), 489a6fbdbb2STodd E Brandt ('c10', '#ffffea') 490a6fbdbb2STodd E Brandt ] 491a6fbdbb2STodd E Brandt i = 0 492a6fbdbb2STodd E Brandt total = 0 493a6fbdbb2STodd E Brandt count = len(list) 494a6fbdbb2STodd E Brandt while i < len(name): 495a6fbdbb2STodd E Brandt total += ord(name[i]) 496a6fbdbb2STodd E Brandt i += 1 497a6fbdbb2STodd E Brandt return list[total % count] 498a6fbdbb2STodd E Brandt 499a6fbdbb2STodd E Brandtdef cgOverview(cg, minlen): 500a6fbdbb2STodd E Brandt stats = dict() 501a6fbdbb2STodd E Brandt large = [] 502a6fbdbb2STodd E Brandt for l in cg.list: 503a6fbdbb2STodd E Brandt if l.fcall and l.depth == 1: 504a6fbdbb2STodd E Brandt if l.length >= minlen: 505a6fbdbb2STodd E Brandt large.append(l) 506a6fbdbb2STodd E Brandt if l.name not in stats: 507a6fbdbb2STodd E Brandt stats[l.name] = [0, 0.0] 508a6fbdbb2STodd E Brandt stats[l.name][0] += (l.length * 1000.0) 509a6fbdbb2STodd E Brandt stats[l.name][1] += 1 510a6fbdbb2STodd E Brandt return (large, stats) 511a6fbdbb2STodd E Brandt 512a6fbdbb2STodd E Brandt# Function: createBootGraph 513a6fbdbb2STodd E Brandt# Description: 514a6fbdbb2STodd E Brandt# Create the output html file from the resident test data 515a6fbdbb2STodd E Brandt# Arguments: 516a6fbdbb2STodd E Brandt# testruns: array of Data objects from parseKernelLog or parseTraceLog 517a6fbdbb2STodd E Brandt# Output: 518a6fbdbb2STodd E Brandt# True if the html file was created, false if it failed 519a6fbdbb2STodd E Brandtdef createBootGraph(data): 520a6fbdbb2STodd E Brandt # html function templates 521a6fbdbb2STodd E Brandt 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' 522a6fbdbb2STodd E Brandt html_timetotal = '<table class="time1">\n<tr>'\ 523a6fbdbb2STodd E Brandt '<td class="blue">Init process starts @ <b>{0} ms</b></td>'\ 524a6fbdbb2STodd E Brandt '<td class="blue">Last initcall ends @ <b>{1} ms</b></td>'\ 525a6fbdbb2STodd E Brandt '</tr>\n</table>\n' 526a6fbdbb2STodd E Brandt 527a6fbdbb2STodd E Brandt # device timeline 528a6fbdbb2STodd E Brandt devtl = aslib.Timeline(100, 20) 529a6fbdbb2STodd E Brandt 530a6fbdbb2STodd E Brandt # write the test title and general info header 531d83a76a8STodd E Brandt devtl.createHeader(sysvals, sysvals.stamp) 532a6fbdbb2STodd E Brandt 533a6fbdbb2STodd E Brandt # Generate the header for this timeline 534a6fbdbb2STodd E Brandt t0 = data.start 535a6fbdbb2STodd E Brandt tMax = data.end 536a6fbdbb2STodd E Brandt tTotal = tMax - t0 537a6fbdbb2STodd E Brandt if(tTotal == 0): 53818d3f8fcSTodd Brandt pprint('ERROR: No timeline data') 539a6fbdbb2STodd E Brandt return False 540a6fbdbb2STodd E Brandt user_mode = '%.0f'%(data.tUserMode*1000) 541a6fbdbb2STodd E Brandt last_init = '%.0f'%(tTotal*1000) 542a6fbdbb2STodd E Brandt devtl.html += html_timetotal.format(user_mode, last_init) 543a6fbdbb2STodd E Brandt 544a6fbdbb2STodd E Brandt # determine the maximum number of rows we need to draw 545a6fbdbb2STodd E Brandt devlist = [] 546a6fbdbb2STodd E Brandt for p in data.phases: 547a6fbdbb2STodd E Brandt list = data.dmesg[p]['list'] 548a6fbdbb2STodd E Brandt for devname in list: 549a6fbdbb2STodd E Brandt d = aslib.DevItem(0, p, list[devname]) 550a6fbdbb2STodd E Brandt devlist.append(d) 551a6fbdbb2STodd E Brandt devtl.getPhaseRows(devlist, 0, 'start') 552a6fbdbb2STodd E Brandt devtl.calcTotalRows() 553a6fbdbb2STodd E Brandt 554a6fbdbb2STodd E Brandt # draw the timeline background 555a6fbdbb2STodd E Brandt devtl.createZoomBox() 556a6fbdbb2STodd E Brandt devtl.html += devtl.html_tblock.format('boot', '0', '100', devtl.scaleH) 557a6fbdbb2STodd E Brandt for p in data.phases: 558a6fbdbb2STodd E Brandt phase = data.dmesg[p] 559a6fbdbb2STodd E Brandt length = phase['end']-phase['start'] 560a6fbdbb2STodd E Brandt left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) 561a6fbdbb2STodd E Brandt width = '%.3f' % ((length*100.0)/tTotal) 562a6fbdbb2STodd E Brandt devtl.html += devtl.html_phase.format(left, width, \ 563a6fbdbb2STodd E Brandt '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ 564a6fbdbb2STodd E Brandt phase['color'], '') 565a6fbdbb2STodd E Brandt 566a6fbdbb2STodd E Brandt # draw the device timeline 567a6fbdbb2STodd E Brandt num = 0 568a6fbdbb2STodd E Brandt devstats = dict() 569a6fbdbb2STodd E Brandt for phase in data.phases: 570a6fbdbb2STodd E Brandt list = data.dmesg[phase]['list'] 571a6fbdbb2STodd E Brandt for devname in sorted(list): 572a6fbdbb2STodd E Brandt cls, color = colorForName(devname) 573a6fbdbb2STodd E Brandt dev = list[devname] 574a6fbdbb2STodd E Brandt info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, 575a6fbdbb2STodd E Brandt dev['ulen']/1000.0, dev['ret']) 576a6fbdbb2STodd E Brandt devstats[dev['id']] = {'info':info} 577a6fbdbb2STodd E Brandt dev['color'] = color 578a6fbdbb2STodd E Brandt height = devtl.phaseRowHeight(0, phase, dev['row']) 579a6fbdbb2STodd E Brandt top = '%.6f' % ((dev['row']*height) + devtl.scaleH) 580a6fbdbb2STodd E Brandt left = '%.6f' % (((dev['start']-t0)*100)/tTotal) 581a6fbdbb2STodd E Brandt width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) 582a6fbdbb2STodd E Brandt length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) 583a6fbdbb2STodd E Brandt devtl.html += devtl.html_device.format(dev['id'], 584a6fbdbb2STodd E Brandt devname+length+phase+'_mode', left, top, '%.3f'%height, 585a6fbdbb2STodd E Brandt width, devname, ' '+cls, '') 586a6fbdbb2STodd E Brandt rowtop = devtl.phaseRowTop(0, phase, dev['row']) 587a6fbdbb2STodd E Brandt height = '%.6f' % (devtl.rowH / 2) 588a6fbdbb2STodd E Brandt top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) 589a6fbdbb2STodd E Brandt if data.do_one_initcall: 590a6fbdbb2STodd E Brandt if('ftrace' not in dev): 591a6fbdbb2STodd E Brandt continue 592a6fbdbb2STodd E Brandt cg = dev['ftrace'] 593a6fbdbb2STodd E Brandt large, stats = cgOverview(cg, 0.001) 594a6fbdbb2STodd E Brandt devstats[dev['id']]['fstat'] = stats 595a6fbdbb2STodd E Brandt for l in large: 596a6fbdbb2STodd E Brandt left = '%f' % (((l.time-t0)*100)/tTotal) 597a6fbdbb2STodd E Brandt width = '%f' % (l.length*100/tTotal) 598a6fbdbb2STodd E Brandt title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) 599a6fbdbb2STodd E Brandt devtl.html += html_srccall.format(l.name, left, 600a6fbdbb2STodd E Brandt top, height, width, title, 'x%d'%num) 601a6fbdbb2STodd E Brandt num += 1 602a6fbdbb2STodd E Brandt continue 603a6fbdbb2STodd E Brandt if('ftraces' not in dev): 604a6fbdbb2STodd E Brandt continue 605a6fbdbb2STodd E Brandt for cg in dev['ftraces']: 606a6fbdbb2STodd E Brandt left = '%f' % (((cg.start-t0)*100)/tTotal) 607a6fbdbb2STodd E Brandt width = '%f' % ((cg.end-cg.start)*100/tTotal) 608a6fbdbb2STodd E Brandt cglen = (cg.end - cg.start) * 1000.0 609a6fbdbb2STodd E Brandt title = '%s (%0.3fms)' % (cg.name, cglen) 610a6fbdbb2STodd E Brandt cg.id = 'x%d' % num 611a6fbdbb2STodd E Brandt devtl.html += html_srccall.format(cg.name, left, 612a6fbdbb2STodd E Brandt top, height, width, title, dev['id']+cg.id) 613a6fbdbb2STodd E Brandt num += 1 614a6fbdbb2STodd E Brandt 615a6fbdbb2STodd E Brandt # draw the time scale, try to make the number of labels readable 616a6fbdbb2STodd E Brandt devtl.createTimeScale(t0, tMax, tTotal, 'boot') 617a6fbdbb2STodd E Brandt devtl.html += '</div>\n' 618a6fbdbb2STodd E Brandt 619a6fbdbb2STodd E Brandt # timeline is finished 620a6fbdbb2STodd E Brandt devtl.html += '</div>\n</div>\n' 621a6fbdbb2STodd E Brandt 622a6fbdbb2STodd E Brandt # draw a legend which describes the phases by color 623a6fbdbb2STodd E Brandt devtl.html += '<div class="legend">\n' 624a6fbdbb2STodd E Brandt pdelta = 20.0 625a6fbdbb2STodd E Brandt pmargin = 36.0 626a6fbdbb2STodd E Brandt for phase in data.phases: 627a6fbdbb2STodd E Brandt order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) 628a6fbdbb2STodd E Brandt devtl.html += devtl.html_legend.format(order, \ 629a6fbdbb2STodd E Brandt data.dmesg[phase]['color'], phase+'_mode', phase[0]) 630a6fbdbb2STodd E Brandt devtl.html += '</div>\n' 631a6fbdbb2STodd E Brandt 632a6fbdbb2STodd E Brandt hf = open(sysvals.htmlfile, 'w') 633a6fbdbb2STodd E Brandt 634d83a76a8STodd E Brandt # add the css 635a6fbdbb2STodd E Brandt extra = '\ 636a6fbdbb2STodd E Brandt .c1 {background:rgba(209,0,0,0.4);}\n\ 637a6fbdbb2STodd E Brandt .c2 {background:rgba(255,102,34,0.4);}\n\ 638a6fbdbb2STodd E Brandt .c3 {background:rgba(255,218,33,0.4);}\n\ 639a6fbdbb2STodd E Brandt .c4 {background:rgba(51,221,0,0.4);}\n\ 640a6fbdbb2STodd E Brandt .c5 {background:rgba(17,51,204,0.4);}\n\ 641a6fbdbb2STodd E Brandt .c6 {background:rgba(34,0,102,0.4);}\n\ 642a6fbdbb2STodd E Brandt .c7 {background:rgba(51,0,68,0.4);}\n\ 643a6fbdbb2STodd E Brandt .c8 {background:rgba(204,255,204,0.4);}\n\ 644a6fbdbb2STodd E Brandt .c9 {background:rgba(169,208,245,0.4);}\n\ 645a6fbdbb2STodd E Brandt .c10 {background:rgba(255,255,204,0.4);}\n\ 646a6fbdbb2STodd E Brandt .vt {transform:rotate(-60deg);transform-origin:0 0;}\n\ 647a6fbdbb2STodd E Brandt table.fstat {table-layout:fixed;padding:150px 15px 0 0;font-size:10px;column-width:30px;}\n\ 648a6fbdbb2STodd E Brandt .fstat th {width:55px;}\n\ 649a6fbdbb2STodd E Brandt .fstat td {text-align:left;width:35px;}\n\ 650a6fbdbb2STodd E Brandt .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\ 651a6fbdbb2STodd E Brandt .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' 652a6fbdbb2STodd E Brandt aslib.addCSS(hf, sysvals, 1, False, extra) 653a6fbdbb2STodd E Brandt 654a6fbdbb2STodd E Brandt # write the device timeline 655a6fbdbb2STodd E Brandt hf.write(devtl.html) 656a6fbdbb2STodd E Brandt 657a6fbdbb2STodd E Brandt # add boot specific html 658a6fbdbb2STodd E Brandt statinfo = 'var devstats = {\n' 659a6fbdbb2STodd E Brandt for n in sorted(devstats): 660a6fbdbb2STodd E Brandt statinfo += '\t"%s": [\n\t\t"%s",\n' % (n, devstats[n]['info']) 661a6fbdbb2STodd E Brandt if 'fstat' in devstats[n]: 662a6fbdbb2STodd E Brandt funcs = devstats[n]['fstat'] 6631446794aSTodd Brandt for f in sorted(funcs, key=lambda k:(funcs[k], k), reverse=True): 664a6fbdbb2STodd E Brandt if funcs[f][0] < 0.01 and len(funcs) > 10: 665a6fbdbb2STodd E Brandt break 666a6fbdbb2STodd E Brandt statinfo += '\t\t"%f|%s|%d",\n' % (funcs[f][0], f, funcs[f][1]) 667a6fbdbb2STodd E Brandt statinfo += '\t],\n' 668a6fbdbb2STodd E Brandt statinfo += '};\n' 669a6fbdbb2STodd E Brandt html = \ 670a6fbdbb2STodd E Brandt '<div id="devicedetailtitle"></div>\n'\ 671a6fbdbb2STodd E Brandt '<div id="devicedetail" style="display:none;">\n'\ 672a6fbdbb2STodd E Brandt '<div id="devicedetail0">\n' 673a6fbdbb2STodd E Brandt for p in data.phases: 674a6fbdbb2STodd E Brandt phase = data.dmesg[p] 675a6fbdbb2STodd E Brandt html += devtl.html_phaselet.format(p+'_mode', '0', '100', phase['color']) 676a6fbdbb2STodd E Brandt html += '</div>\n</div>\n'\ 677a6fbdbb2STodd E Brandt '<script type="text/javascript">\n'+statinfo+\ 678a6fbdbb2STodd E Brandt '</script>\n' 679a6fbdbb2STodd E Brandt hf.write(html) 680a6fbdbb2STodd E Brandt 681a6fbdbb2STodd E Brandt # add the callgraph html 682a6fbdbb2STodd E Brandt if(sysvals.usecallgraph): 683a6fbdbb2STodd E Brandt aslib.addCallgraphs(sysvals, hf, data) 684a6fbdbb2STodd E Brandt 685d83a76a8STodd E Brandt # add the test log as a hidden div 686d83a76a8STodd E Brandt if sysvals.testlog and sysvals.logmsg: 687d83a76a8STodd E Brandt hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n') 688a6fbdbb2STodd E Brandt # add the dmesg log as a hidden div 689a6fbdbb2STodd E Brandt if sysvals.dmesglog: 690a6fbdbb2STodd E Brandt hf.write('<div id="dmesglog" style="display:none;">\n') 691a6fbdbb2STodd E Brandt for line in data.dmesgtext: 692a6fbdbb2STodd E Brandt line = line.replace('<', '<').replace('>', '>') 693a6fbdbb2STodd E Brandt hf.write(line) 694a6fbdbb2STodd E Brandt hf.write('</div>\n') 695a6fbdbb2STodd E Brandt 696a6fbdbb2STodd E Brandt # write the footer and close 697a6fbdbb2STodd E Brandt aslib.addScriptCode(hf, [data]) 698a6fbdbb2STodd E Brandt hf.write('</body>\n</html>\n') 699a6fbdbb2STodd E Brandt hf.close() 700a6fbdbb2STodd E Brandt return True 701a6fbdbb2STodd E Brandt 702a6fbdbb2STodd E Brandt# Function: updateCron 703a6fbdbb2STodd E Brandt# Description: 704a6fbdbb2STodd E Brandt# (restore=False) Set the tool to run automatically on reboot 705a6fbdbb2STodd E Brandt# (restore=True) Restore the original crontab 706a6fbdbb2STodd E Brandtdef updateCron(restore=False): 707a6fbdbb2STodd E Brandt if not restore: 708a6fbdbb2STodd E Brandt sysvals.rootUser(True) 709a6fbdbb2STodd E Brandt crondir = '/var/spool/cron/crontabs/' 710a6fbdbb2STodd E Brandt if not os.path.exists(crondir): 711a6fbdbb2STodd E Brandt crondir = '/var/spool/cron/' 712a6fbdbb2STodd E Brandt if not os.path.exists(crondir): 713a6fbdbb2STodd E Brandt doError('%s not found' % crondir) 714a6fbdbb2STodd E Brandt cronfile = crondir+'root' 715a6fbdbb2STodd E Brandt backfile = crondir+'root-analyze_boot-backup' 716a6fbdbb2STodd E Brandt cmd = sysvals.getExec('crontab') 717a6fbdbb2STodd E Brandt if not cmd: 718a6fbdbb2STodd E Brandt doError('crontab not found') 719a6fbdbb2STodd E Brandt # on restore: move the backup cron back into place 720a6fbdbb2STodd E Brandt if restore: 721a6fbdbb2STodd E Brandt if os.path.exists(backfile): 722a6fbdbb2STodd E Brandt shutil.move(backfile, cronfile) 723a6fbdbb2STodd E Brandt call([cmd, cronfile]) 724a6fbdbb2STodd E Brandt return 725a6fbdbb2STodd E Brandt # backup current cron and install new one with reboot 726a6fbdbb2STodd E Brandt if os.path.exists(cronfile): 727a6fbdbb2STodd E Brandt shutil.move(cronfile, backfile) 728a6fbdbb2STodd E Brandt else: 729a6fbdbb2STodd E Brandt fp = open(backfile, 'w') 730a6fbdbb2STodd E Brandt fp.close() 731a6fbdbb2STodd E Brandt res = -1 732a6fbdbb2STodd E Brandt try: 733a6fbdbb2STodd E Brandt fp = open(backfile, 'r') 734a6fbdbb2STodd E Brandt op = open(cronfile, 'w') 735a6fbdbb2STodd E Brandt for line in fp: 736a6fbdbb2STodd E Brandt if not sysvals.myCronJob(line): 737a6fbdbb2STodd E Brandt op.write(line) 738a6fbdbb2STodd E Brandt continue 739a6fbdbb2STodd E Brandt fp.close() 740a6fbdbb2STodd E Brandt op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) 741a6fbdbb2STodd E Brandt op.close() 742a6fbdbb2STodd E Brandt res = call([cmd, cronfile]) 7431446794aSTodd Brandt except Exception as e: 74418d3f8fcSTodd Brandt pprint('Exception: %s' % str(e)) 745a6fbdbb2STodd E Brandt shutil.move(backfile, cronfile) 746a6fbdbb2STodd E Brandt res = -1 747a6fbdbb2STodd E Brandt if res != 0: 748a6fbdbb2STodd E Brandt doError('crontab failed') 749a6fbdbb2STodd E Brandt 750a6fbdbb2STodd E Brandt# Function: updateGrub 751a6fbdbb2STodd E Brandt# Description: 752a6fbdbb2STodd E Brandt# update grub.cfg for all kernels with our parameters 753a6fbdbb2STodd E Brandtdef updateGrub(restore=False): 754a6fbdbb2STodd E Brandt # call update-grub on restore 755a6fbdbb2STodd E Brandt if restore: 756a6fbdbb2STodd E Brandt try: 757a6fbdbb2STodd E Brandt call(sysvals.blexec, stderr=PIPE, stdout=PIPE, 758a6fbdbb2STodd E Brandt env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) 7591446794aSTodd Brandt except Exception as e: 76018d3f8fcSTodd Brandt pprint('Exception: %s\n' % str(e)) 761a6fbdbb2STodd E Brandt return 762a6fbdbb2STodd E Brandt # extract the option and create a grub config without it 763a6fbdbb2STodd E Brandt sysvals.rootUser(True) 764a6fbdbb2STodd E Brandt tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' 765a6fbdbb2STodd E Brandt cmdline = '' 766a6fbdbb2STodd E Brandt grubfile = '/etc/default/grub' 767a6fbdbb2STodd E Brandt tempfile = '/etc/default/grub.analyze_boot' 768a6fbdbb2STodd E Brandt shutil.move(grubfile, tempfile) 769a6fbdbb2STodd E Brandt res = -1 770a6fbdbb2STodd E Brandt try: 771a6fbdbb2STodd E Brandt fp = open(tempfile, 'r') 772a6fbdbb2STodd E Brandt op = open(grubfile, 'w') 773a6fbdbb2STodd E Brandt cont = False 774a6fbdbb2STodd E Brandt for line in fp: 775a6fbdbb2STodd E Brandt line = line.strip() 776a6fbdbb2STodd E Brandt if len(line) == 0 or line[0] == '#': 777a6fbdbb2STodd E Brandt continue 778a6fbdbb2STodd E Brandt opt = line.split('=')[0].strip() 779a6fbdbb2STodd E Brandt if opt == tgtopt: 780a6fbdbb2STodd E Brandt cmdline = line.split('=', 1)[1].strip('\\') 781a6fbdbb2STodd E Brandt if line[-1] == '\\': 782a6fbdbb2STodd E Brandt cont = True 783a6fbdbb2STodd E Brandt elif cont: 784a6fbdbb2STodd E Brandt cmdline += line.strip('\\') 785a6fbdbb2STodd E Brandt if line[-1] != '\\': 786a6fbdbb2STodd E Brandt cont = False 787a6fbdbb2STodd E Brandt else: 788a6fbdbb2STodd E Brandt op.write('%s\n' % line) 789a6fbdbb2STodd E Brandt fp.close() 790a6fbdbb2STodd E Brandt # if the target option value is in quotes, strip them 791a6fbdbb2STodd E Brandt sp = '"' 792a6fbdbb2STodd E Brandt val = cmdline.strip() 793a6fbdbb2STodd E Brandt if val and (val[0] == '\'' or val[0] == '"'): 794a6fbdbb2STodd E Brandt sp = val[0] 795a6fbdbb2STodd E Brandt val = val.strip(sp) 796a6fbdbb2STodd E Brandt cmdline = val 797a6fbdbb2STodd E Brandt # append our cmd line options 798a6fbdbb2STodd E Brandt if len(cmdline) > 0: 799a6fbdbb2STodd E Brandt cmdline += ' ' 800a6fbdbb2STodd E Brandt cmdline += sysvals.kernelParams() 801a6fbdbb2STodd E Brandt # write out the updated target option 802a6fbdbb2STodd E Brandt op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) 803a6fbdbb2STodd E Brandt op.close() 804a6fbdbb2STodd E Brandt res = call(sysvals.blexec) 805a6fbdbb2STodd E Brandt os.remove(grubfile) 8061446794aSTodd Brandt except Exception as e: 80718d3f8fcSTodd Brandt pprint('Exception: %s' % str(e)) 808a6fbdbb2STodd E Brandt res = -1 809a6fbdbb2STodd E Brandt # cleanup 810a6fbdbb2STodd E Brandt shutil.move(tempfile, grubfile) 811a6fbdbb2STodd E Brandt if res != 0: 812a6fbdbb2STodd E Brandt doError('update grub failed') 813a6fbdbb2STodd E Brandt 814a6fbdbb2STodd E Brandt# Function: updateKernelParams 815a6fbdbb2STodd E Brandt# Description: 816a6fbdbb2STodd E Brandt# update boot conf for all kernels with our parameters 817a6fbdbb2STodd E Brandtdef updateKernelParams(restore=False): 818a6fbdbb2STodd E Brandt # find the boot loader 819a6fbdbb2STodd E Brandt sysvals.getBootLoader() 820a6fbdbb2STodd E Brandt if sysvals.bootloader == 'grub': 821a6fbdbb2STodd E Brandt updateGrub(restore) 822a6fbdbb2STodd E Brandt 823a6fbdbb2STodd E Brandt# Function: doError Description: 824a6fbdbb2STodd E Brandt# generic error function for catastrphic failures 825a6fbdbb2STodd E Brandt# Arguments: 826a6fbdbb2STodd E Brandt# msg: the error message to print 827a6fbdbb2STodd E Brandt# help: True if printHelp should be called after, False otherwise 828a6fbdbb2STodd E Brandtdef doError(msg, help=False): 829a6fbdbb2STodd E Brandt if help == True: 830a6fbdbb2STodd E Brandt printHelp() 83118d3f8fcSTodd Brandt pprint('ERROR: %s\n' % msg) 832d83a76a8STodd E Brandt sysvals.outputResult({'error':msg}) 833a6fbdbb2STodd E Brandt sys.exit() 834a6fbdbb2STodd E Brandt 835a6fbdbb2STodd E Brandt# Function: printHelp 836a6fbdbb2STodd E Brandt# Description: 837a6fbdbb2STodd E Brandt# print out the help text 838a6fbdbb2STodd E Brandtdef printHelp(): 83918d3f8fcSTodd Brandt pprint('\n%s v%s\n'\ 84018d3f8fcSTodd Brandt 'Usage: bootgraph <options> <command>\n'\ 84118d3f8fcSTodd Brandt '\n'\ 84218d3f8fcSTodd Brandt 'Description:\n'\ 84318d3f8fcSTodd Brandt ' This tool reads in a dmesg log of linux kernel boot and\n'\ 84418d3f8fcSTodd Brandt ' creates an html representation of the boot timeline up to\n'\ 84518d3f8fcSTodd Brandt ' the start of the init process.\n'\ 84618d3f8fcSTodd Brandt '\n'\ 84718d3f8fcSTodd Brandt ' If no specific command is given the tool reads the current dmesg\n'\ 84818d3f8fcSTodd Brandt ' and/or ftrace log and creates a timeline\n'\ 84918d3f8fcSTodd Brandt '\n'\ 85018d3f8fcSTodd Brandt ' Generates output files in subdirectory: boot-yymmdd-HHMMSS\n'\ 85118d3f8fcSTodd Brandt ' HTML output: <hostname>_boot.html\n'\ 85218d3f8fcSTodd Brandt ' raw dmesg output: <hostname>_boot_dmesg.txt\n'\ 85318d3f8fcSTodd Brandt ' raw ftrace output: <hostname>_boot_ftrace.txt\n'\ 85418d3f8fcSTodd Brandt '\n'\ 85518d3f8fcSTodd Brandt 'Options:\n'\ 85618d3f8fcSTodd Brandt ' -h Print this help text\n'\ 85718d3f8fcSTodd Brandt ' -v Print the current tool version\n'\ 85818d3f8fcSTodd Brandt ' -verbose Print extra information during execution and analysis\n'\ 85918d3f8fcSTodd Brandt ' -addlogs Add the dmesg log to the html output\n'\ 86018d3f8fcSTodd Brandt ' -result fn Export a results table to a text file for parsing.\n'\ 86118d3f8fcSTodd Brandt ' -o name Overrides the output subdirectory name when running a new test\n'\ 86218d3f8fcSTodd Brandt ' default: boot-{date}-{time}\n'\ 86318d3f8fcSTodd Brandt ' [advanced]\n'\ 86418d3f8fcSTodd Brandt ' -fstat Use ftrace to add function detail and statistics (default: disabled)\n'\ 86518d3f8fcSTodd Brandt ' -f/-callgraph Add callgraph detail, can be very large (default: disabled)\n'\ 86618d3f8fcSTodd Brandt ' -maxdepth N limit the callgraph data to N call levels (default: 2)\n'\ 86718d3f8fcSTodd Brandt ' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\ 86818d3f8fcSTodd Brandt ' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])\n'\ 86918d3f8fcSTodd Brandt ' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\ 87018d3f8fcSTodd Brandt ' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)\n'\ 87118d3f8fcSTodd Brandt ' -cgfilter S Filter the callgraph output in the timeline\n'\ 87218d3f8fcSTodd Brandt ' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\ 87318d3f8fcSTodd Brandt ' -bl name Use the following boot loader for kernel params (default: grub)\n'\ 87418d3f8fcSTodd Brandt ' -reboot Reboot the machine automatically and generate a new timeline\n'\ 87518d3f8fcSTodd Brandt ' -manual Show the steps to generate a new timeline manually (used with -reboot)\n'\ 87618d3f8fcSTodd Brandt '\n'\ 87718d3f8fcSTodd Brandt 'Other commands:\n'\ 87818d3f8fcSTodd Brandt ' -flistall Print all functions capable of being captured in ftrace\n'\ 87918d3f8fcSTodd Brandt ' -sysinfo Print out system info extracted from BIOS\n'\ 8801446794aSTodd Brandt ' -which exec Print an executable path, should function even without PATH\n'\ 88118d3f8fcSTodd Brandt ' [redo]\n'\ 88218d3f8fcSTodd Brandt ' -dmesg file Create HTML output using dmesg input (used with -ftrace)\n'\ 88318d3f8fcSTodd Brandt ' -ftrace file Create HTML output using ftrace input (used with -dmesg)\n'\ 88418d3f8fcSTodd Brandt '' % (sysvals.title, sysvals.version)) 885a6fbdbb2STodd E Brandt return True 886a6fbdbb2STodd E Brandt 887a6fbdbb2STodd E Brandt# ----------------- MAIN -------------------- 888a6fbdbb2STodd E Brandt# exec start (skipped if script is loaded as library) 889a6fbdbb2STodd E Brandtif __name__ == '__main__': 890a6fbdbb2STodd E Brandt # loop through the command line arguments 891a6fbdbb2STodd E Brandt cmd = '' 892a6fbdbb2STodd E Brandt testrun = True 893d83a76a8STodd E Brandt switchoff = ['disable', 'off', 'false', '0'] 894a6fbdbb2STodd E Brandt simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] 895d83a76a8STodd E Brandt cgskip = '' 896d83a76a8STodd E Brandt if '-f' in sys.argv: 897d83a76a8STodd E Brandt cgskip = sysvals.configFile('cgskip.txt') 898a6fbdbb2STodd E Brandt args = iter(sys.argv[1:]) 899d83a76a8STodd E Brandt mdset = False 900a6fbdbb2STodd E Brandt for arg in args: 901a6fbdbb2STodd E Brandt if(arg == '-h'): 902a6fbdbb2STodd E Brandt printHelp() 903a6fbdbb2STodd E Brandt sys.exit() 904a6fbdbb2STodd E Brandt elif(arg == '-v'): 90518d3f8fcSTodd Brandt pprint("Version %s" % sysvals.version) 906a6fbdbb2STodd E Brandt sys.exit() 907d83a76a8STodd E Brandt elif(arg == '-verbose'): 908d83a76a8STodd E Brandt sysvals.verbose = True 909a6fbdbb2STodd E Brandt elif(arg in simplecmds): 910a6fbdbb2STodd E Brandt cmd = arg[1:] 911d83a76a8STodd E Brandt elif(arg == '-fstat'): 912a6fbdbb2STodd E Brandt sysvals.useftrace = True 913d83a76a8STodd E Brandt elif(arg == '-callgraph' or arg == '-f'): 914a6fbdbb2STodd E Brandt sysvals.useftrace = True 915a6fbdbb2STodd E Brandt sysvals.usecallgraph = True 916d83a76a8STodd E Brandt elif(arg == '-cgdump'): 917d83a76a8STodd E Brandt sysvals.cgdump = True 918a6fbdbb2STodd E Brandt elif(arg == '-mincg'): 919a6fbdbb2STodd E Brandt sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) 920a6fbdbb2STodd E Brandt elif(arg == '-cgfilter'): 921a6fbdbb2STodd E Brandt try: 9221446794aSTodd Brandt val = next(args) 923a6fbdbb2STodd E Brandt except: 924a6fbdbb2STodd E Brandt doError('No callgraph functions supplied', True) 925d83a76a8STodd E Brandt sysvals.setCallgraphFilter(val) 926d83a76a8STodd E Brandt elif(arg == '-cgskip'): 927d83a76a8STodd E Brandt try: 9281446794aSTodd Brandt val = next(args) 929d83a76a8STodd E Brandt except: 930d83a76a8STodd E Brandt doError('No file supplied', True) 931d83a76a8STodd E Brandt if val.lower() in switchoff: 932d83a76a8STodd E Brandt cgskip = '' 933d83a76a8STodd E Brandt else: 934d83a76a8STodd E Brandt cgskip = sysvals.configFile(val) 935d83a76a8STodd E Brandt if(not cgskip): 936d83a76a8STodd E Brandt doError('%s does not exist' % cgskip) 937a6fbdbb2STodd E Brandt elif(arg == '-bl'): 938a6fbdbb2STodd E Brandt try: 9391446794aSTodd Brandt val = next(args) 940a6fbdbb2STodd E Brandt except: 941a6fbdbb2STodd E Brandt doError('No boot loader name supplied', True) 942a6fbdbb2STodd E Brandt if val.lower() not in ['grub']: 943a6fbdbb2STodd E Brandt doError('Unknown boot loader: %s' % val, True) 944a6fbdbb2STodd E Brandt sysvals.bootloader = val.lower() 945a6fbdbb2STodd E Brandt elif(arg == '-timeprec'): 946a6fbdbb2STodd E Brandt sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) 947a6fbdbb2STodd E Brandt elif(arg == '-maxdepth'): 948d83a76a8STodd E Brandt mdset = True 949a6fbdbb2STodd E Brandt sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) 950a6fbdbb2STodd E Brandt elif(arg == '-func'): 951a6fbdbb2STodd E Brandt try: 9521446794aSTodd Brandt val = next(args) 953a6fbdbb2STodd E Brandt except: 954a6fbdbb2STodd E Brandt doError('No filter functions supplied', True) 955a6fbdbb2STodd E Brandt sysvals.useftrace = True 956a6fbdbb2STodd E Brandt sysvals.usecallgraph = True 957a6fbdbb2STodd E Brandt sysvals.rootCheck(True) 958a6fbdbb2STodd E Brandt sysvals.setGraphFilter(val) 959a6fbdbb2STodd E Brandt elif(arg == '-ftrace'): 960a6fbdbb2STodd E Brandt try: 9611446794aSTodd Brandt val = next(args) 962a6fbdbb2STodd E Brandt except: 963a6fbdbb2STodd E Brandt doError('No ftrace file supplied', True) 964a6fbdbb2STodd E Brandt if(os.path.exists(val) == False): 965a6fbdbb2STodd E Brandt doError('%s does not exist' % val) 966a6fbdbb2STodd E Brandt testrun = False 967a6fbdbb2STodd E Brandt sysvals.ftracefile = val 968a6fbdbb2STodd E Brandt elif(arg == '-addlogs'): 969a6fbdbb2STodd E Brandt sysvals.dmesglog = True 970a6fbdbb2STodd E Brandt elif(arg == '-expandcg'): 971a6fbdbb2STodd E Brandt sysvals.cgexp = True 972a6fbdbb2STodd E Brandt elif(arg == '-dmesg'): 973a6fbdbb2STodd E Brandt try: 9741446794aSTodd Brandt val = next(args) 975a6fbdbb2STodd E Brandt except: 976a6fbdbb2STodd E Brandt doError('No dmesg file supplied', True) 977a6fbdbb2STodd E Brandt if(os.path.exists(val) == False): 978a6fbdbb2STodd E Brandt doError('%s does not exist' % val) 979a6fbdbb2STodd E Brandt testrun = False 980a6fbdbb2STodd E Brandt sysvals.dmesgfile = val 981a6fbdbb2STodd E Brandt elif(arg == '-o'): 982a6fbdbb2STodd E Brandt try: 9831446794aSTodd Brandt val = next(args) 984a6fbdbb2STodd E Brandt except: 985a6fbdbb2STodd E Brandt doError('No subdirectory name supplied', True) 986a6fbdbb2STodd E Brandt sysvals.testdir = sysvals.setOutputFolder(val) 987d83a76a8STodd E Brandt elif(arg == '-result'): 988d83a76a8STodd E Brandt try: 9891446794aSTodd Brandt val = next(args) 990d83a76a8STodd E Brandt except: 991d83a76a8STodd E Brandt doError('No result file supplied', True) 992d83a76a8STodd E Brandt sysvals.result = val 993a6fbdbb2STodd E Brandt elif(arg == '-reboot'): 994a6fbdbb2STodd E Brandt sysvals.reboot = True 995a6fbdbb2STodd E Brandt elif(arg == '-manual'): 996a6fbdbb2STodd E Brandt sysvals.reboot = True 997a6fbdbb2STodd E Brandt sysvals.manual = True 998a6fbdbb2STodd E Brandt # remaining options are only for cron job use 999a6fbdbb2STodd E Brandt elif(arg == '-cronjob'): 1000a6fbdbb2STodd E Brandt sysvals.iscronjob = True 10011446794aSTodd Brandt elif(arg == '-which'): 10021446794aSTodd Brandt try: 10031446794aSTodd Brandt val = next(args) 10041446794aSTodd Brandt except: 10051446794aSTodd Brandt doError('No executable supplied', True) 10061446794aSTodd Brandt out = sysvals.getExec(val) 10071446794aSTodd Brandt if not out: 10081446794aSTodd Brandt print('%s not found' % val) 10091446794aSTodd Brandt sys.exit(1) 10101446794aSTodd Brandt print(out) 10111446794aSTodd Brandt sys.exit(0) 1012a6fbdbb2STodd E Brandt else: 1013a6fbdbb2STodd E Brandt doError('Invalid argument: '+arg, True) 1014a6fbdbb2STodd E Brandt 1015a6fbdbb2STodd E Brandt # compatibility errors and access checks 1016a6fbdbb2STodd E Brandt if(sysvals.iscronjob and (sysvals.reboot or \ 1017a6fbdbb2STodd E Brandt sysvals.dmesgfile or sysvals.ftracefile or cmd)): 1018a6fbdbb2STodd E Brandt doError('-cronjob is meant for batch purposes only') 1019a6fbdbb2STodd E Brandt if(sysvals.reboot and (sysvals.dmesgfile or sysvals.ftracefile)): 1020a6fbdbb2STodd E Brandt doError('-reboot and -dmesg/-ftrace are incompatible') 1021a6fbdbb2STodd E Brandt if cmd or sysvals.reboot or sysvals.iscronjob or testrun: 1022a6fbdbb2STodd E Brandt sysvals.rootCheck(True) 1023a6fbdbb2STodd E Brandt if (testrun and sysvals.useftrace) or cmd == 'flistall': 1024a6fbdbb2STodd E Brandt if not sysvals.verifyFtrace(): 1025a6fbdbb2STodd E Brandt doError('Ftrace is not properly enabled') 1026a6fbdbb2STodd E Brandt 1027a6fbdbb2STodd E Brandt # run utility commands 1028a6fbdbb2STodd E Brandt sysvals.cpuInfo() 1029a6fbdbb2STodd E Brandt if cmd != '': 1030a6fbdbb2STodd E Brandt if cmd == 'kpupdate': 1031a6fbdbb2STodd E Brandt updateKernelParams() 1032a6fbdbb2STodd E Brandt elif cmd == 'flistall': 1033a6fbdbb2STodd E Brandt for f in sysvals.getBootFtraceFilterFunctions(): 103445dd0a42STodd Brandt print(f) 1035a6fbdbb2STodd E Brandt elif cmd == 'checkbl': 1036a6fbdbb2STodd E Brandt sysvals.getBootLoader() 103718d3f8fcSTodd Brandt pprint('Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec)) 1038a6fbdbb2STodd E Brandt elif(cmd == 'sysinfo'): 1039d83a76a8STodd E Brandt sysvals.printSystemInfo(True) 1040a6fbdbb2STodd E Brandt sys.exit() 1041a6fbdbb2STodd E Brandt 1042a6fbdbb2STodd E Brandt # reboot: update grub, setup a cronjob, and reboot 1043a6fbdbb2STodd E Brandt if sysvals.reboot: 1044a6fbdbb2STodd E Brandt if (sysvals.useftrace or sysvals.usecallgraph) and \ 1045a6fbdbb2STodd E Brandt not sysvals.checkFtraceKernelVersion(): 1046a6fbdbb2STodd E Brandt doError('Ftrace functionality requires kernel v4.10 or newer') 1047a6fbdbb2STodd E Brandt if not sysvals.manual: 1048a6fbdbb2STodd E Brandt updateKernelParams() 1049a6fbdbb2STodd E Brandt updateCron() 1050a6fbdbb2STodd E Brandt call('reboot') 1051a6fbdbb2STodd E Brandt else: 1052a6fbdbb2STodd E Brandt sysvals.manualRebootRequired() 1053a6fbdbb2STodd E Brandt sys.exit() 1054a6fbdbb2STodd E Brandt 1055d83a76a8STodd E Brandt if sysvals.usecallgraph and cgskip: 1056d83a76a8STodd E Brandt sysvals.vprint('Using cgskip file: %s' % cgskip) 1057d83a76a8STodd E Brandt sysvals.setCallgraphBlacklist(cgskip) 1058d83a76a8STodd E Brandt 1059a6fbdbb2STodd E Brandt # cronjob: remove the cronjob, grub changes, and disable ftrace 1060a6fbdbb2STodd E Brandt if sysvals.iscronjob: 1061a6fbdbb2STodd E Brandt updateCron(True) 1062a6fbdbb2STodd E Brandt updateKernelParams(True) 1063a6fbdbb2STodd E Brandt try: 1064a6fbdbb2STodd E Brandt sysvals.fsetVal('0', 'tracing_on') 1065a6fbdbb2STodd E Brandt except: 1066a6fbdbb2STodd E Brandt pass 1067a6fbdbb2STodd E Brandt 1068a6fbdbb2STodd E Brandt # testrun: generate copies of the logs 1069a6fbdbb2STodd E Brandt if testrun: 1070a6fbdbb2STodd E Brandt retrieveLogs() 1071a6fbdbb2STodd E Brandt else: 1072a6fbdbb2STodd E Brandt sysvals.setOutputFile() 1073a6fbdbb2STodd E Brandt 1074a6fbdbb2STodd E Brandt # process the log data 1075a6fbdbb2STodd E Brandt if sysvals.dmesgfile: 1076d83a76a8STodd E Brandt if not mdset: 1077d83a76a8STodd E Brandt sysvals.max_graph_depth = 0 1078a6fbdbb2STodd E Brandt data = parseKernelLog() 1079a6fbdbb2STodd E Brandt if(not data.valid): 1080a6fbdbb2STodd E Brandt doError('No initcall data found in %s' % sysvals.dmesgfile) 1081a6fbdbb2STodd E Brandt if sysvals.useftrace and sysvals.ftracefile: 1082a6fbdbb2STodd E Brandt parseTraceLog(data) 1083d83a76a8STodd E Brandt if sysvals.cgdump: 1084d83a76a8STodd E Brandt data.debugPrint() 1085d83a76a8STodd E Brandt sys.exit() 1086a6fbdbb2STodd E Brandt else: 1087a6fbdbb2STodd E Brandt doError('dmesg file required') 1088a6fbdbb2STodd E Brandt 1089d83a76a8STodd E Brandt sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile) 1090d83a76a8STodd E Brandt sysvals.vprint('Command:\n %s' % sysvals.cmdline) 1091d83a76a8STodd E Brandt sysvals.vprint('Kernel parameters:\n %s' % sysvals.kparams) 1092d83a76a8STodd E Brandt data.printDetails() 1093a6fbdbb2STodd E Brandt createBootGraph(data) 1094a6fbdbb2STodd E Brandt 1095a6fbdbb2STodd E Brandt # if running as root, change output dir owner to sudo_user 1096a6fbdbb2STodd E Brandt if testrun and os.path.isdir(sysvals.testdir) and \ 1097a6fbdbb2STodd E Brandt os.getuid() == 0 and 'SUDO_USER' in os.environ: 1098a6fbdbb2STodd E Brandt cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' 1099a6fbdbb2STodd E Brandt call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) 1100d83a76a8STodd E Brandt 1101d83a76a8STodd E Brandt sysvals.stamp['boot'] = (data.tUserMode - data.start) * 1000 1102d83a76a8STodd E Brandt sysvals.stamp['lastinit'] = data.end * 1000 1103d83a76a8STodd E Brandt sysvals.outputResult(sysvals.stamp) 1104