aboutsummaryrefslogtreecommitdiffstats
path: root/tools/power/pm-graph/analyze_boot.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/power/pm-graph/analyze_boot.py')
-rwxr-xr-xtools/power/pm-graph/analyze_boot.py586
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
43class SystemValues(aslib.SystemValues): 43class 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
125sysvals = SystemValues() 203sysvals = 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
193def loadKernelLog(): 277def 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
254def loadTraceLog(data): 355def 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
403def 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
356def createBootGraph(data, embedded): 464def 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('<', '&lt').replace('>', '&gt') 638 line = line.replace('<', '&lt').replace('>', '&gt')
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
768def 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
661def printHelp(): 788def 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():
698if __name__ == '__main__': 836if __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)