diff options
-rw-r--r-- | tools/power/pm-graph/bootgraph.8 | 18 | ||||
-rwxr-xr-x | tools/power/pm-graph/bootgraph.py | 219 |
2 files changed, 160 insertions, 77 deletions
diff --git a/tools/power/pm-graph/bootgraph.8 b/tools/power/pm-graph/bootgraph.8 index dbdafcf546df..64d513f80a2a 100644 --- a/tools/power/pm-graph/bootgraph.8 +++ b/tools/power/pm-graph/bootgraph.8 | |||
@@ -37,6 +37,9 @@ Print the current tool version | |||
37 | Add the dmesg log to the html output. It will be viewable by | 37 | Add the dmesg log to the html output. It will be viewable by |
38 | clicking a button in the timeline. | 38 | clicking a button in the timeline. |
39 | .TP | 39 | .TP |
40 | \fB-result \fIfile\fR | ||
41 | Export a results table to a text file for parsing. | ||
42 | .TP | ||
40 | \fB-o \fIname\fR | 43 | \fB-o \fIname\fR |
41 | Overrides the output subdirectory name when running a new test. | 44 | Overrides the output subdirectory name when running a new test. |
42 | Use {date}, {time}, {hostname} for current values. | 45 | Use {date}, {time}, {hostname} for current values. |
@@ -44,14 +47,14 @@ Use {date}, {time}, {hostname} for current values. | |||
44 | e.g. boot-{hostname}-{date}-{time} | 47 | e.g. boot-{hostname}-{date}-{time} |
45 | .SS "advanced" | 48 | .SS "advanced" |
46 | .TP | 49 | .TP |
47 | \fB-f\fR | 50 | \fB-f or -callgraph\fR |
48 | Use ftrace to add function detail (default: disabled) | ||
49 | .TP | ||
50 | \fB-callgraph\fR | ||
51 | Use ftrace to create initcall callgraphs (default: disabled). If -func | 51 | Use ftrace to create initcall callgraphs (default: disabled). If -func |
52 | is not used there will be one callgraph per initcall. This can produce | 52 | is not used there will be one callgraph per initcall. This can produce |
53 | very large outputs, i.e. 10MB - 100MB. | 53 | very large outputs, i.e. 10MB - 100MB. |
54 | .TP | 54 | .TP |
55 | \fB-fstat\fR | ||
56 | Use ftrace to add function detail (default: disabled) | ||
57 | .TP | ||
55 | \fB-maxdepth \fIlevel\fR | 58 | \fB-maxdepth \fIlevel\fR |
56 | limit the callgraph trace depth to \fIlevel\fR (default: 2). This is | 59 | limit the callgraph trace depth to \fIlevel\fR (default: 2). This is |
57 | the best way to limit the output size when using -callgraph. | 60 | the best way to limit the output size when using -callgraph. |
@@ -67,6 +70,13 @@ Reduce callgraph output in the timeline by limiting it to a list of calls. The | |||
67 | argument can be a single function name or a comma delimited list. | 70 | argument can be a single function name or a comma delimited list. |
68 | (default: none) | 71 | (default: none) |
69 | .TP | 72 | .TP |
73 | \fB-cgskip \fIfile\fR | ||
74 | Reduce callgraph output in the timeline by skipping over uninteresting | ||
75 | functions in the trace, e.g. printk or console_unlock. The functions listed | ||
76 | in this file will show up as empty leaves in the callgraph with only the start/end | ||
77 | times displayed. | ||
78 | (default: none) | ||
79 | .TP | ||
70 | \fB-timeprec \fIn\fR | 80 | \fB-timeprec \fIn\fR |
71 | Number of significant digits in timestamps (0:S, 3:ms, [6:us]) | 81 | Number of significant digits in timestamps (0:S, 3:ms, [6:us]) |
72 | .TP | 82 | .TP |
diff --git a/tools/power/pm-graph/bootgraph.py b/tools/power/pm-graph/bootgraph.py index e83df141a597..abb4c38f029b 100755 --- a/tools/power/pm-graph/bootgraph.py +++ b/tools/power/pm-graph/bootgraph.py | |||
@@ -32,7 +32,7 @@ import platform | |||
32 | import shutil | 32 | import shutil |
33 | from datetime import datetime, timedelta | 33 | from datetime import datetime, timedelta |
34 | from subprocess import call, Popen, PIPE | 34 | from subprocess import call, Popen, PIPE |
35 | import analyze_suspend as aslib | 35 | import sleepgraph as aslib |
36 | 36 | ||
37 | # ----------------- CLASSES -------------------- | 37 | # ----------------- CLASSES -------------------- |
38 | 38 | ||
@@ -42,23 +42,18 @@ import analyze_suspend as aslib | |||
42 | # store system values and test parameters | 42 | # store system values and test parameters |
43 | class SystemValues(aslib.SystemValues): | 43 | class SystemValues(aslib.SystemValues): |
44 | title = 'BootGraph' | 44 | title = 'BootGraph' |
45 | version = '2.1' | 45 | version = '2.2' |
46 | hostname = 'localhost' | 46 | hostname = 'localhost' |
47 | testtime = '' | 47 | testtime = '' |
48 | kernel = '' | 48 | kernel = '' |
49 | dmesgfile = '' | 49 | dmesgfile = '' |
50 | ftracefile = '' | 50 | ftracefile = '' |
51 | htmlfile = 'bootgraph.html' | 51 | htmlfile = 'bootgraph.html' |
52 | outfile = '' | ||
53 | testdir = '' | 52 | testdir = '' |
54 | testdirprefix = 'boot' | 53 | kparams = '' |
55 | embedded = False | 54 | result = '' |
56 | testlog = False | ||
57 | dmesglog = False | ||
58 | ftracelog = False | ||
59 | useftrace = False | 55 | useftrace = False |
60 | usecallgraph = False | 56 | usecallgraph = False |
61 | usedevsrc = True | ||
62 | suspendmode = 'boot' | 57 | suspendmode = 'boot' |
63 | max_graph_depth = 2 | 58 | max_graph_depth = 2 |
64 | graph_filter = 'do_one_initcall' | 59 | graph_filter = 'do_one_initcall' |
@@ -69,11 +64,6 @@ class SystemValues(aslib.SystemValues): | |||
69 | bootloader = 'grub' | 64 | bootloader = 'grub' |
70 | blexec = [] | 65 | blexec = [] |
71 | def __init__(self): | 66 | def __init__(self): |
72 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): | ||
73 | self.embedded = True | ||
74 | self.dmesglog = True | ||
75 | self.outfile = os.environ['LOG_FILE'] | ||
76 | self.htmlfile = os.environ['LOG_FILE'] | ||
77 | self.hostname = platform.node() | 67 | self.hostname = platform.node() |
78 | self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') | 68 | self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') |
79 | if os.path.exists('/proc/version'): | 69 | if os.path.exists('/proc/version'): |
@@ -148,11 +138,18 @@ class SystemValues(aslib.SystemValues): | |||
148 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) | 138 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) |
149 | args = iter(sys.argv[1:]) | 139 | args = iter(sys.argv[1:]) |
150 | for arg in args: | 140 | for arg in args: |
151 | if arg in ['-h', '-v', '-cronjob', '-reboot']: | 141 | if arg in ['-h', '-v', '-cronjob', '-reboot', '-verbose']: |
152 | continue | 142 | continue |
153 | elif arg in ['-o', '-dmesg', '-ftrace', '-func']: | 143 | elif arg in ['-o', '-dmesg', '-ftrace', '-func']: |
154 | args.next() | 144 | args.next() |
155 | continue | 145 | continue |
146 | elif arg == '-result': | ||
147 | cmdline += ' %s "%s"' % (arg, os.path.abspath(args.next())) | ||
148 | continue | ||
149 | elif arg == '-cgskip': | ||
150 | file = self.configFile(args.next()) | ||
151 | cmdline += ' %s "%s"' % (arg, os.path.abspath(file)) | ||
152 | continue | ||
156 | cmdline += ' '+arg | 153 | cmdline += ' '+arg |
157 | if self.graph_filter != 'do_one_initcall': | 154 | if self.graph_filter != 'do_one_initcall': |
158 | cmdline += ' -func "%s"' % self.graph_filter | 155 | cmdline += ' -func "%s"' % self.graph_filter |
@@ -166,14 +163,6 @@ class SystemValues(aslib.SystemValues): | |||
166 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' | 163 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' |
167 | print 'CMDLINE="%s"' % cmdline | 164 | print 'CMDLINE="%s"' % cmdline |
168 | sys.exit() | 165 | 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): | 166 | def blGrub(self): |
178 | blcmd = '' | 167 | blcmd = '' |
179 | for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: | 168 | for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: |
@@ -199,6 +188,14 @@ class SystemValues(aslib.SystemValues): | |||
199 | self.blGrub() | 188 | self.blGrub() |
200 | else: | 189 | else: |
201 | doError('unknown boot loader: %s' % self.bootloader) | 190 | doError('unknown boot loader: %s' % self.bootloader) |
191 | def writeDatafileHeader(self, filename): | ||
192 | self.kparams = open('/proc/cmdline', 'r').read().strip() | ||
193 | fp = open(filename, 'w') | ||
194 | fp.write(self.teststamp+'\n') | ||
195 | fp.write(self.sysstamp+'\n') | ||
196 | fp.write('# command | %s\n' % self.cmdline) | ||
197 | fp.write('# kparams | %s\n' % self.kparams) | ||
198 | fp.close() | ||
202 | 199 | ||
203 | sysvals = SystemValues() | 200 | sysvals = SystemValues() |
204 | 201 | ||
@@ -249,7 +246,7 @@ class Data(aslib.Data): | |||
249 | return name | 246 | return name |
250 | def deviceMatch(self, pid, cg): | 247 | def deviceMatch(self, pid, cg): |
251 | if cg.end - cg.start == 0: | 248 | if cg.end - cg.start == 0: |
252 | return True | 249 | return '' |
253 | for p in data.phases: | 250 | for p in data.phases: |
254 | list = self.dmesg[p]['list'] | 251 | list = self.dmesg[p]['list'] |
255 | for devname in list: | 252 | for devname in list: |
@@ -260,14 +257,25 @@ class Data(aslib.Data): | |||
260 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): | 257 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): |
261 | dev['ftrace'] = cg | 258 | dev['ftrace'] = cg |
262 | self.do_one_initcall = True | 259 | self.do_one_initcall = True |
263 | return True | 260 | return devname |
264 | else: | 261 | else: |
265 | if(cg.start > dev['start'] and cg.end < dev['end']): | 262 | if(cg.start > dev['start'] and cg.end < dev['end']): |
266 | if 'ftraces' not in dev: | 263 | if 'ftraces' not in dev: |
267 | dev['ftraces'] = [] | 264 | dev['ftraces'] = [] |
268 | dev['ftraces'].append(cg) | 265 | dev['ftraces'].append(cg) |
269 | return True | 266 | return devname |
270 | return False | 267 | return '' |
268 | def printDetails(self): | ||
269 | sysvals.vprint('Timeline Details:') | ||
270 | sysvals.vprint(' Host: %s' % sysvals.hostname) | ||
271 | sysvals.vprint(' Kernel: %s' % sysvals.kernel) | ||
272 | sysvals.vprint(' Test time: %s' % sysvals.testtime) | ||
273 | sysvals.vprint(' Boot time: %s' % self.boottime) | ||
274 | for phase in self.phases: | ||
275 | dc = len(self.dmesg[phase]['list']) | ||
276 | sysvals.vprint('%9s mode: %.3f - %.3f (%d initcalls)' % (phase, | ||
277 | self.dmesg[phase]['start']*1000, | ||
278 | self.dmesg[phase]['end']*1000, dc)) | ||
271 | 279 | ||
272 | # ----------------- FUNCTIONS -------------------- | 280 | # ----------------- FUNCTIONS -------------------- |
273 | 281 | ||
@@ -275,6 +283,8 @@ class Data(aslib.Data): | |||
275 | # Description: | 283 | # Description: |
276 | # parse a kernel log for boot data | 284 | # parse a kernel log for boot data |
277 | def parseKernelLog(): | 285 | def parseKernelLog(): |
286 | sysvals.vprint('Analyzing the dmesg data (%s)...' % \ | ||
287 | os.path.basename(sysvals.dmesgfile)) | ||
278 | phase = 'kernel' | 288 | phase = 'kernel' |
279 | data = Data(0) | 289 | data = Data(0) |
280 | data.dmesg['kernel']['start'] = data.start = ktime = 0.0 | 290 | data.dmesg['kernel']['start'] = data.start = ktime = 0.0 |
@@ -298,6 +308,12 @@ def parseKernelLog(): | |||
298 | elif re.match(tp.sysinfofmt, line): | 308 | elif re.match(tp.sysinfofmt, line): |
299 | tp.sysinfo = line | 309 | tp.sysinfo = line |
300 | continue | 310 | continue |
311 | elif re.match(tp.cmdlinefmt, line): | ||
312 | tp.cmdline = line | ||
313 | continue | ||
314 | elif re.match(tp.kparamsfmt, line): | ||
315 | tp.kparams = line | ||
316 | continue | ||
301 | idx = line.find('[') | 317 | idx = line.find('[') |
302 | if idx > 1: | 318 | if idx > 1: |
303 | line = line[idx:] | 319 | line = line[idx:] |
@@ -353,6 +369,17 @@ def parseKernelLog(): | |||
353 | # Description: | 369 | # Description: |
354 | # Check if trace is available and copy to a temp file | 370 | # Check if trace is available and copy to a temp file |
355 | def parseTraceLog(data): | 371 | def parseTraceLog(data): |
372 | sysvals.vprint('Analyzing the ftrace data (%s)...' % \ | ||
373 | os.path.basename(sysvals.ftracefile)) | ||
374 | # if available, calculate cgfilter allowable ranges | ||
375 | cgfilter = [] | ||
376 | if len(sysvals.cgfilter) > 0: | ||
377 | for p in data.phases: | ||
378 | list = data.dmesg[p]['list'] | ||
379 | for i in sysvals.cgfilter: | ||
380 | if i in list: | ||
381 | cgfilter.append([list[i]['start']-0.0001, | ||
382 | list[i]['end']+0.0001]) | ||
356 | # parse the trace log | 383 | # parse the trace log |
357 | ftemp = dict() | 384 | ftemp = dict() |
358 | tp = aslib.TestProps() | 385 | tp = aslib.TestProps() |
@@ -366,7 +393,16 @@ def parseTraceLog(data): | |||
366 | continue | 393 | continue |
367 | m_time, m_proc, m_pid, m_msg, m_dur = \ | 394 | m_time, m_proc, m_pid, m_msg, m_dur = \ |
368 | m.group('time', 'proc', 'pid', 'msg', 'dur') | 395 | m.group('time', 'proc', 'pid', 'msg', 'dur') |
369 | if float(m_time) > data.end: | 396 | t = float(m_time) |
397 | if len(cgfilter) > 0: | ||
398 | allow = False | ||
399 | for r in cgfilter: | ||
400 | if t >= r[0] and t < r[1]: | ||
401 | allow = True | ||
402 | break | ||
403 | if not allow: | ||
404 | continue | ||
405 | if t > data.end: | ||
370 | break | 406 | break |
371 | if(m_time and m_pid and m_msg): | 407 | if(m_time and m_pid and m_msg): |
372 | t = aslib.FTraceLine(m_time, m_msg, m_dur) | 408 | t = aslib.FTraceLine(m_time, m_msg, m_dur) |
@@ -378,24 +414,36 @@ def parseTraceLog(data): | |||
378 | key = (m_proc, pid) | 414 | key = (m_proc, pid) |
379 | if(key not in ftemp): | 415 | if(key not in ftemp): |
380 | ftemp[key] = [] | 416 | ftemp[key] = [] |
381 | ftemp[key].append(aslib.FTraceCallGraph(pid)) | 417 | ftemp[key].append(aslib.FTraceCallGraph(pid, sysvals)) |
382 | cg = ftemp[key][-1] | 418 | cg = ftemp[key][-1] |
383 | if(cg.addLine(t)): | 419 | res = cg.addLine(t) |
384 | ftemp[key].append(aslib.FTraceCallGraph(pid)) | 420 | if(res != 0): |
421 | ftemp[key].append(aslib.FTraceCallGraph(pid, sysvals)) | ||
422 | if(res == -1): | ||
423 | ftemp[key][-1].addLine(t) | ||
424 | |||
385 | tf.close() | 425 | tf.close() |
386 | 426 | ||
387 | # add the callgraph data to the device hierarchy | 427 | # add the callgraph data to the device hierarchy |
388 | for key in ftemp: | 428 | for key in ftemp: |
389 | proc, pid = key | 429 | proc, pid = key |
390 | for cg in ftemp[key]: | 430 | for cg in ftemp[key]: |
391 | if len(cg.list) < 1 or cg.invalid: | 431 | if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0): |
392 | continue | 432 | continue |
393 | if(not cg.postProcess()): | 433 | if(not cg.postProcess()): |
394 | print('Sanity check failed for %s-%d' % (proc, pid)) | 434 | print('Sanity check failed for %s-%d' % (proc, pid)) |
395 | continue | 435 | continue |
396 | # match cg data to devices | 436 | # match cg data to devices |
397 | if not data.deviceMatch(pid, cg): | 437 | devname = data.deviceMatch(pid, cg) |
398 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) | 438 | if not devname: |
439 | kind = 'Orphan' | ||
440 | if cg.partial: | ||
441 | kind = 'Partial' | ||
442 | sysvals.vprint('%s callgraph found for %s %s-%d [%f - %f]' %\ | ||
443 | (kind, cg.name, proc, pid, cg.start, cg.end)) | ||
444 | elif len(cg.list) > 1000000: | ||
445 | print 'WARNING: the callgraph found for %s is massive! (%d lines)' %\ | ||
446 | (devname, len(cg.list)) | ||
399 | 447 | ||
400 | # Function: retrieveLogs | 448 | # Function: retrieveLogs |
401 | # Description: | 449 | # Description: |
@@ -473,7 +521,7 @@ def createBootGraph(data): | |||
473 | devtl = aslib.Timeline(100, 20) | 521 | devtl = aslib.Timeline(100, 20) |
474 | 522 | ||
475 | # write the test title and general info header | 523 | # write the test title and general info header |
476 | devtl.createHeader(sysvals) | 524 | devtl.createHeader(sysvals, sysvals.stamp) |
477 | 525 | ||
478 | # Generate the header for this timeline | 526 | # Generate the header for this timeline |
479 | t0 = data.start | 527 | t0 = data.start |
@@ -574,12 +622,9 @@ def createBootGraph(data): | |||
574 | data.dmesg[phase]['color'], phase+'_mode', phase[0]) | 622 | data.dmesg[phase]['color'], phase+'_mode', phase[0]) |
575 | devtl.html += '</div>\n' | 623 | devtl.html += '</div>\n' |
576 | 624 | ||
577 | if(sysvals.outfile == sysvals.htmlfile): | 625 | hf = open(sysvals.htmlfile, 'w') |
578 | hf = open(sysvals.htmlfile, 'a') | ||
579 | else: | ||
580 | hf = open(sysvals.htmlfile, 'w') | ||
581 | 626 | ||
582 | # add the css if this is not an embedded run | 627 | # add the css |
583 | extra = '\ | 628 | extra = '\ |
584 | .c1 {background:rgba(209,0,0,0.4);}\n\ | 629 | .c1 {background:rgba(209,0,0,0.4);}\n\ |
585 | .c2 {background:rgba(255,102,34,0.4);}\n\ | 630 | .c2 {background:rgba(255,102,34,0.4);}\n\ |
@@ -597,8 +642,7 @@ def createBootGraph(data): | |||
597 | .fstat td {text-align:left;width:35px;}\n\ | 642 | .fstat td {text-align:left;width:35px;}\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\ | 643 | .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\ |
599 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' | 644 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' |
600 | if(not sysvals.embedded): | 645 | aslib.addCSS(hf, sysvals, 1, False, extra) |
601 | aslib.addCSS(hf, sysvals, 1, False, extra) | ||
602 | 646 | ||
603 | # write the device timeline | 647 | # write the device timeline |
604 | hf.write(devtl.html) | 648 | hf.write(devtl.html) |
@@ -631,6 +675,9 @@ def createBootGraph(data): | |||
631 | if(sysvals.usecallgraph): | 675 | if(sysvals.usecallgraph): |
632 | aslib.addCallgraphs(sysvals, hf, data) | 676 | aslib.addCallgraphs(sysvals, hf, data) |
633 | 677 | ||
678 | # add the test log as a hidden div | ||
679 | if sysvals.testlog and sysvals.logmsg: | ||
680 | hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n') | ||
634 | # add the dmesg log as a hidden div | 681 | # add the dmesg log as a hidden div |
635 | if sysvals.dmesglog: | 682 | if sysvals.dmesglog: |
636 | hf.write('<div id="dmesglog" style="display:none;">\n') | 683 | hf.write('<div id="dmesglog" style="display:none;">\n') |
@@ -639,14 +686,9 @@ def createBootGraph(data): | |||
639 | hf.write(line) | 686 | hf.write(line) |
640 | hf.write('</div>\n') | 687 | hf.write('</div>\n') |
641 | 688 | ||
642 | if(not sysvals.embedded): | 689 | # write the footer and close |
643 | # write the footer and close | 690 | aslib.addScriptCode(hf, [data]) |
644 | aslib.addScriptCode(hf, [data]) | 691 | hf.write('</body>\n</html>\n') |
645 | hf.write('</body>\n</html>\n') | ||
646 | else: | ||
647 | # embedded out will be loaded in a page, skip the js | ||
648 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ | ||
649 | (data.start*1000, data.end*1000)) | ||
650 | hf.close() | 692 | hf.close() |
651 | return True | 693 | return True |
652 | 694 | ||
@@ -780,6 +822,7 @@ def doError(msg, help=False): | |||
780 | if help == True: | 822 | if help == True: |
781 | printHelp() | 823 | printHelp() |
782 | print 'ERROR: %s\n' % msg | 824 | print 'ERROR: %s\n' % msg |
825 | sysvals.outputResult({'error':msg}) | ||
783 | sys.exit() | 826 | sys.exit() |
784 | 827 | ||
785 | # Function: printHelp | 828 | # Function: printHelp |
@@ -806,18 +849,21 @@ def printHelp(): | |||
806 | print('Options:') | 849 | print('Options:') |
807 | print(' -h Print this help text') | 850 | print(' -h Print this help text') |
808 | print(' -v Print the current tool version') | 851 | print(' -v Print the current tool version') |
852 | print(' -verbose Print extra information during execution and analysis') | ||
809 | print(' -addlogs Add the dmesg log to the html output') | 853 | print(' -addlogs Add the dmesg log to the html output') |
854 | print(' -result fn Export a results table to a text file for parsing.') | ||
810 | print(' -o name Overrides the output subdirectory name when running a new test') | 855 | print(' -o name Overrides the output subdirectory name when running a new test') |
811 | print(' default: boot-{date}-{time}') | 856 | print(' default: boot-{date}-{time}') |
812 | print(' [advanced]') | 857 | print(' [advanced]') |
813 | print(' -f Use ftrace to add function detail (default: disabled)') | 858 | print(' -fstat Use ftrace to add function detail and statistics (default: disabled)') |
814 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') | 859 | print(' -f/-callgraph Add callgraph detail, can be very large (default: disabled)') |
815 | print(' -maxdepth N limit the callgraph data to N call levels (default: 2)') | 860 | print(' -maxdepth N limit the callgraph data to N call levels (default: 2)') |
816 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') | 861 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') |
817 | print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') | 862 | print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') |
818 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') | 863 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') |
819 | print(' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') | 864 | print(' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') |
820 | print(' -cgfilter S Filter the callgraph output in the timeline') | 865 | print(' -cgfilter S Filter the callgraph output in the timeline') |
866 | print(' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)') | ||
821 | print(' -bl name Use the following boot loader for kernel params (default: grub)') | 867 | print(' -bl name Use the following boot loader for kernel params (default: grub)') |
822 | print(' -reboot Reboot the machine automatically and generate a new timeline') | 868 | print(' -reboot Reboot the machine automatically and generate a new timeline') |
823 | print(' -manual Show the steps to generate a new timeline manually (used with -reboot)') | 869 | print(' -manual Show the steps to generate a new timeline manually (used with -reboot)') |
@@ -837,8 +883,13 @@ if __name__ == '__main__': | |||
837 | # loop through the command line arguments | 883 | # loop through the command line arguments |
838 | cmd = '' | 884 | cmd = '' |
839 | testrun = True | 885 | testrun = True |
886 | switchoff = ['disable', 'off', 'false', '0'] | ||
840 | simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] | 887 | simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] |
888 | cgskip = '' | ||
889 | if '-f' in sys.argv: | ||
890 | cgskip = sysvals.configFile('cgskip.txt') | ||
841 | args = iter(sys.argv[1:]) | 891 | args = iter(sys.argv[1:]) |
892 | mdset = False | ||
842 | for arg in args: | 893 | for arg in args: |
843 | if(arg == '-h'): | 894 | if(arg == '-h'): |
844 | printHelp() | 895 | printHelp() |
@@ -846,13 +897,17 @@ if __name__ == '__main__': | |||
846 | elif(arg == '-v'): | 897 | elif(arg == '-v'): |
847 | print("Version %s" % sysvals.version) | 898 | print("Version %s" % sysvals.version) |
848 | sys.exit() | 899 | sys.exit() |
900 | elif(arg == '-verbose'): | ||
901 | sysvals.verbose = True | ||
849 | elif(arg in simplecmds): | 902 | elif(arg in simplecmds): |
850 | cmd = arg[1:] | 903 | cmd = arg[1:] |
851 | elif(arg == '-f'): | 904 | elif(arg == '-fstat'): |
852 | sysvals.useftrace = True | 905 | sysvals.useftrace = True |
853 | elif(arg == '-callgraph'): | 906 | elif(arg == '-callgraph' or arg == '-f'): |
854 | sysvals.useftrace = True | 907 | sysvals.useftrace = True |
855 | sysvals.usecallgraph = True | 908 | sysvals.usecallgraph = True |
909 | elif(arg == '-cgdump'): | ||
910 | sysvals.cgdump = True | ||
856 | elif(arg == '-mincg'): | 911 | elif(arg == '-mincg'): |
857 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) | 912 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) |
858 | elif(arg == '-cgfilter'): | 913 | elif(arg == '-cgfilter'): |
@@ -860,7 +915,18 @@ if __name__ == '__main__': | |||
860 | val = args.next() | 915 | val = args.next() |
861 | except: | 916 | except: |
862 | doError('No callgraph functions supplied', True) | 917 | doError('No callgraph functions supplied', True) |
863 | sysvals.setDeviceFilter(val) | 918 | sysvals.setCallgraphFilter(val) |
919 | elif(arg == '-cgskip'): | ||
920 | try: | ||
921 | val = args.next() | ||
922 | except: | ||
923 | doError('No file supplied', True) | ||
924 | if val.lower() in switchoff: | ||
925 | cgskip = '' | ||
926 | else: | ||
927 | cgskip = sysvals.configFile(val) | ||
928 | if(not cgskip): | ||
929 | doError('%s does not exist' % cgskip) | ||
864 | elif(arg == '-bl'): | 930 | elif(arg == '-bl'): |
865 | try: | 931 | try: |
866 | val = args.next() | 932 | val = args.next() |
@@ -872,6 +938,7 @@ if __name__ == '__main__': | |||
872 | elif(arg == '-timeprec'): | 938 | elif(arg == '-timeprec'): |
873 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) | 939 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) |
874 | elif(arg == '-maxdepth'): | 940 | elif(arg == '-maxdepth'): |
941 | mdset = True | ||
875 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) | 942 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) |
876 | elif(arg == '-func'): | 943 | elif(arg == '-func'): |
877 | try: | 944 | try: |
@@ -902,8 +969,6 @@ if __name__ == '__main__': | |||
902 | doError('No dmesg file supplied', True) | 969 | doError('No dmesg file supplied', True) |
903 | if(os.path.exists(val) == False): | 970 | if(os.path.exists(val) == False): |
904 | doError('%s does not exist' % val) | 971 | doError('%s does not exist' % val) |
905 | if(sysvals.htmlfile == val or sysvals.outfile == val): | ||
906 | doError('Output filename collision') | ||
907 | testrun = False | 972 | testrun = False |
908 | sysvals.dmesgfile = val | 973 | sysvals.dmesgfile = val |
909 | elif(arg == '-o'): | 974 | elif(arg == '-o'): |
@@ -912,6 +977,12 @@ if __name__ == '__main__': | |||
912 | except: | 977 | except: |
913 | doError('No subdirectory name supplied', True) | 978 | doError('No subdirectory name supplied', True) |
914 | sysvals.testdir = sysvals.setOutputFolder(val) | 979 | sysvals.testdir = sysvals.setOutputFolder(val) |
980 | elif(arg == '-result'): | ||
981 | try: | ||
982 | val = args.next() | ||
983 | except: | ||
984 | doError('No result file supplied', True) | ||
985 | sysvals.result = val | ||
915 | elif(arg == '-reboot'): | 986 | elif(arg == '-reboot'): |
916 | sysvals.reboot = True | 987 | sysvals.reboot = True |
917 | elif(arg == '-manual'): | 988 | elif(arg == '-manual'): |
@@ -947,7 +1018,7 @@ if __name__ == '__main__': | |||
947 | sysvals.getBootLoader() | 1018 | sysvals.getBootLoader() |
948 | print 'Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec) | 1019 | print 'Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec) |
949 | elif(cmd == 'sysinfo'): | 1020 | elif(cmd == 'sysinfo'): |
950 | sysvals.printSystemInfo() | 1021 | sysvals.printSystemInfo(True) |
951 | sys.exit() | 1022 | sys.exit() |
952 | 1023 | ||
953 | # reboot: update grub, setup a cronjob, and reboot | 1024 | # reboot: update grub, setup a cronjob, and reboot |
@@ -963,6 +1034,10 @@ if __name__ == '__main__': | |||
963 | sysvals.manualRebootRequired() | 1034 | sysvals.manualRebootRequired() |
964 | sys.exit() | 1035 | sys.exit() |
965 | 1036 | ||
1037 | if sysvals.usecallgraph and cgskip: | ||
1038 | sysvals.vprint('Using cgskip file: %s' % cgskip) | ||
1039 | sysvals.setCallgraphBlacklist(cgskip) | ||
1040 | |||
966 | # cronjob: remove the cronjob, grub changes, and disable ftrace | 1041 | # cronjob: remove the cronjob, grub changes, and disable ftrace |
967 | if sysvals.iscronjob: | 1042 | if sysvals.iscronjob: |
968 | updateCron(True) | 1043 | updateCron(True) |
@@ -980,29 +1055,23 @@ if __name__ == '__main__': | |||
980 | 1055 | ||
981 | # process the log data | 1056 | # process the log data |
982 | if sysvals.dmesgfile: | 1057 | if sysvals.dmesgfile: |
1058 | if not mdset: | ||
1059 | sysvals.max_graph_depth = 0 | ||
983 | data = parseKernelLog() | 1060 | data = parseKernelLog() |
984 | if(not data.valid): | 1061 | if(not data.valid): |
985 | doError('No initcall data found in %s' % sysvals.dmesgfile) | 1062 | doError('No initcall data found in %s' % sysvals.dmesgfile) |
986 | if sysvals.useftrace and sysvals.ftracefile: | 1063 | if sysvals.useftrace and sysvals.ftracefile: |
987 | parseTraceLog(data) | 1064 | parseTraceLog(data) |
1065 | if sysvals.cgdump: | ||
1066 | data.debugPrint() | ||
1067 | sys.exit() | ||
988 | else: | 1068 | else: |
989 | doError('dmesg file required') | 1069 | doError('dmesg file required') |
990 | 1070 | ||
991 | print(' Host: %s' % sysvals.hostname) | 1071 | sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile) |
992 | print(' Test time: %s' % sysvals.testtime) | 1072 | sysvals.vprint('Command:\n %s' % sysvals.cmdline) |
993 | print(' Boot time: %s' % data.boottime) | 1073 | sysvals.vprint('Kernel parameters:\n %s' % sysvals.kparams) |
994 | print('Kernel Version: %s' % sysvals.kernel) | 1074 | data.printDetails() |
995 | print(' Kernel start: %.3f' % (data.start * 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) | 1075 | createBootGraph(data) |
1007 | 1076 | ||
1008 | # if running as root, change output dir owner to sudo_user | 1077 | # if running as root, change output dir owner to sudo_user |
@@ -1010,3 +1079,7 @@ if __name__ == '__main__': | |||
1010 | os.getuid() == 0 and 'SUDO_USER' in os.environ: | 1079 | os.getuid() == 0 and 'SUDO_USER' in os.environ: |
1011 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' | 1080 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' |
1012 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) | 1081 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) |
1082 | |||
1083 | sysvals.stamp['boot'] = (data.tUserMode - data.start) * 1000 | ||
1084 | sysvals.stamp['lastinit'] = data.end * 1000 | ||
1085 | sysvals.outputResult(sysvals.stamp) | ||