Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

pm-graph: AnalyzeBoot v2.2

- add -cgskip option to reduce callgraph output size
- add -cgfilter option to focus on a list of devices
- add -result option for exporting batch test results
- removed all phoronix hooks, use -result to enable batch testing
- changed argument -f to match sleegraph, -f = -callgraph
- use -fstat for function status instead of -f
- add -verbose option to print out timeline stats and kernel options
- include command string and kernel params in timeline output header

Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Todd E Brandt and committed by
Rafael J. Wysocki
d83a76a8 a6fbdbb2

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