aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTodd E Brandt <todd.e.brandt@linux.intel.com>2017-07-05 17:42:55 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-07-21 19:56:13 -0400
commit49218edd649ae4922aa776aad5b7a65d028c6e96 (patch)
treea7787d8ec09ac9817f7521bd36cb125be8e2eca9
parent5771a8c08880cdca3bfb4a3fc6d309d6bba20877 (diff)
pm-graph: AnalyzeSuspend v4.7
- changed -rtcwake parameter to be on & 15 sec by default, to disable rtcwake use: "-rtcwake off" - changed behavior of -o: renames HTML file on rerun, subdir on new run - changed execution_misalignment error to missing_function_name - add sysinfo to logs and timeline via a custom dmidecode call it supplants dmidecode tool when used as a library call - add -sysinfo command, displays dmidecode values and cpu/mem info - set trace buffer size to lesser of memtotal/2 or 2GB when using callgraph - add support for /sys/power/mem_sleep. if mem_sleep found: mem-shallow=standby, mem-s2idle=freeze, mem-deep=mem - remove redundant javascript - cosmetic changes to HTML layout Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rwxr-xr-xtools/power/pm-graph/analyze_suspend.py534
1 files changed, 379 insertions, 155 deletions
diff --git a/tools/power/pm-graph/analyze_suspend.py b/tools/power/pm-graph/analyze_suspend.py
index a9206e67fc1f..1b60fe203741 100755
--- a/tools/power/pm-graph/analyze_suspend.py
+++ b/tools/power/pm-graph/analyze_suspend.py
@@ -68,10 +68,12 @@ from subprocess import call, Popen, PIPE
68# store system values and test parameters 68# store system values and test parameters
69class SystemValues: 69class SystemValues:
70 title = 'SleepGraph' 70 title = 'SleepGraph'
71 version = '4.6' 71 version = '4.7'
72 ansi = False 72 ansi = False
73 verbose = False 73 verbose = False
74 addlogs = False 74 testlog = True
75 dmesglog = False
76 ftracelog = False
75 mindevlen = 0.0 77 mindevlen = 0.0
76 mincglen = 0.0 78 mincglen = 0.0
77 cgphase = '' 79 cgphase = ''
@@ -79,10 +81,11 @@ class SystemValues:
79 max_graph_depth = 0 81 max_graph_depth = 0
80 callloopmaxgap = 0.0001 82 callloopmaxgap = 0.0001
81 callloopmaxlen = 0.005 83 callloopmaxlen = 0.005
84 cpucount = 0
85 memtotal = 204800
82 srgap = 0 86 srgap = 0
83 cgexp = False 87 cgexp = False
84 outdir = '' 88 testdir = ''
85 testdir = '.'
86 tpath = '/sys/kernel/debug/tracing/' 89 tpath = '/sys/kernel/debug/tracing/'
87 fpdtpath = '/sys/firmware/acpi/tables/FPDT' 90 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
88 epath = '/sys/kernel/debug/tracing/events/power/' 91 epath = '/sys/kernel/debug/tracing/events/power/'
@@ -95,14 +98,17 @@ class SystemValues:
95 testcommand = '' 98 testcommand = ''
96 mempath = '/dev/mem' 99 mempath = '/dev/mem'
97 powerfile = '/sys/power/state' 100 powerfile = '/sys/power/state'
101 mempowerfile = '/sys/power/mem_sleep'
98 suspendmode = 'mem' 102 suspendmode = 'mem'
103 memmode = ''
99 hostname = 'localhost' 104 hostname = 'localhost'
100 prefix = 'test' 105 prefix = 'test'
101 teststamp = '' 106 teststamp = ''
107 sysstamp = ''
102 dmesgstart = 0.0 108 dmesgstart = 0.0
103 dmesgfile = '' 109 dmesgfile = ''
104 ftracefile = '' 110 ftracefile = ''
105 htmlfile = '' 111 htmlfile = 'output.html'
106 embedded = False 112 embedded = False
107 rtcwake = True 113 rtcwake = True
108 rtcwaketime = 15 114 rtcwaketime = 15
@@ -127,9 +133,6 @@ class SystemValues:
127 devpropfmt = '# Device Properties: .*' 133 devpropfmt = '# Device Properties: .*'
128 tracertypefmt = '# tracer: (?P<t>.*)' 134 tracertypefmt = '# tracer: (?P<t>.*)'
129 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$' 135 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
130 stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
131 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
132 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
133 tracefuncs = { 136 tracefuncs = {
134 'sys_sync': dict(), 137 'sys_sync': dict(),
135 'pm_prepare_console': dict(), 138 'pm_prepare_console': dict(),
@@ -218,7 +221,7 @@ class SystemValues:
218 # if this is a phoronix test run, set some default options 221 # if this is a phoronix test run, set some default options
219 if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): 222 if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ):
220 self.embedded = True 223 self.embedded = True
221 self.addlogs = True 224 self.dmesglog = self.ftracelog = True
222 self.htmlfile = os.environ['LOG_FILE'] 225 self.htmlfile = os.environ['LOG_FILE']
223 self.archargs = 'args_'+platform.machine() 226 self.archargs = 'args_'+platform.machine()
224 self.hostname = platform.node() 227 self.hostname = platform.node()
@@ -233,6 +236,13 @@ class SystemValues:
233 self.rtcpath = rtc 236 self.rtcpath = rtc
234 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()): 237 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
235 self.ansi = True 238 self.ansi = True
239 self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
240 def rootCheck(self, fatal=True):
241 if(os.access(self.powerfile, os.W_OK)):
242 return True
243 if fatal:
244 doError('This command requires sysfs mount and root access')
245 return False
236 def rootUser(self, fatal=False): 246 def rootUser(self, fatal=False):
237 if 'USER' in os.environ and os.environ['USER'] == 'root': 247 if 'USER' in os.environ and os.environ['USER'] == 'root':
238 return True 248 return True
@@ -249,30 +259,60 @@ class SystemValues:
249 args['date'] = n.strftime('%y%m%d') 259 args['date'] = n.strftime('%y%m%d')
250 args['time'] = n.strftime('%H%M%S') 260 args['time'] = n.strftime('%H%M%S')
251 args['hostname'] = self.hostname 261 args['hostname'] = self.hostname
252 self.outdir = value.format(**args) 262 return value.format(**args)
253 def setOutputFile(self): 263 def setOutputFile(self):
254 if((self.htmlfile == '') and (self.dmesgfile != '')): 264 if self.dmesgfile != '':
255 m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile) 265 m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)
256 if(m): 266 if(m):
257 self.htmlfile = m.group('name')+'.html' 267 self.htmlfile = m.group('name')+'.html'
258 if((self.htmlfile == '') and (self.ftracefile != '')): 268 if self.ftracefile != '':
259 m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile) 269 m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)
260 if(m): 270 if(m):
261 self.htmlfile = m.group('name')+'.html' 271 self.htmlfile = m.group('name')+'.html'
262 if(self.htmlfile == ''): 272 def systemInfo(self, info):
263 self.htmlfile = 'output.html' 273 p = c = m = b = ''
264 def initTestOutput(self, subdir, testpath=''): 274 if 'baseboard-manufacturer' in info:
275 m = info['baseboard-manufacturer']
276 elif 'system-manufacturer' in info:
277 m = info['system-manufacturer']
278 if 'baseboard-product-name' in info:
279 p = info['baseboard-product-name']
280 elif 'system-product-name' in info:
281 p = info['system-product-name']
282 if 'processor-version' in info:
283 c = info['processor-version']
284 if 'bios-version' in info:
285 b = info['bios-version']
286 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | numcpu:%d | memsz:%d' % \
287 (m, p, c, b, self.cpucount, self.memtotal)
288 def printSystemInfo(self):
289 self.rootCheck(True)
290 out = dmidecode(self.mempath, True)
291 fmt = '%-24s: %s'
292 for name in sorted(out):
293 print fmt % (name, out[name])
294 print fmt % ('cpucount', ('%d' % self.cpucount))
295 print fmt % ('memtotal', ('%d kB' % self.memtotal))
296 def cpuInfo(self):
297 self.cpucount = 0
298 fp = open('/proc/cpuinfo', 'r')
299 for line in fp:
300 if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
301 self.cpucount += 1
302 fp.close()
303 fp = open('/proc/meminfo', 'r')
304 for line in fp:
305 m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
306 if m:
307 self.memtotal = int(m.group('sz'))
308 break
309 fp.close()
310 def initTestOutput(self, name):
265 self.prefix = self.hostname 311 self.prefix = self.hostname
266 v = open('/proc/version', 'r').read().strip() 312 v = open('/proc/version', 'r').read().strip()
267 kver = string.split(v)[2] 313 kver = string.split(v)[2]
268 n = datetime.now() 314 fmt = name+'-%m%d%y-%H%M%S'
269 testtime = n.strftime('suspend-%m%d%y-%H%M%S') 315 testtime = datetime.now().strftime(fmt)
270 if not testpath:
271 testpath = n.strftime('suspend-%y%m%d-%H%M%S')
272 if(subdir != "."):
273 self.testdir = subdir+"/"+testpath
274 else:
275 self.testdir = testpath
276 self.teststamp = \ 316 self.teststamp = \
277 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver 317 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
278 if(self.embedded): 318 if(self.embedded):
@@ -355,7 +395,7 @@ class SystemValues:
355 continue 395 continue
356 self.tracefuncs[i] = dict() 396 self.tracefuncs[i] = dict()
357 def getFtraceFilterFunctions(self, current): 397 def getFtraceFilterFunctions(self, current):
358 rootCheck(True) 398 self.rootCheck(True)
359 if not current: 399 if not current:
360 call('cat '+self.tpath+'available_filter_functions', shell=True) 400 call('cat '+self.tpath+'available_filter_functions', shell=True)
361 return 401 return
@@ -453,7 +493,7 @@ class SystemValues:
453 val += '\nr:%s_ret %s $retval\n' % (name, func) 493 val += '\nr:%s_ret %s $retval\n' % (name, func)
454 return val 494 return val
455 def addKprobes(self, output=False): 495 def addKprobes(self, output=False):
456 if len(sysvals.kprobes) < 1: 496 if len(self.kprobes) < 1:
457 return 497 return
458 if output: 498 if output:
459 print(' kprobe functions in this kernel:') 499 print(' kprobe functions in this kernel:')
@@ -525,7 +565,7 @@ class SystemValues:
525 fp.flush() 565 fp.flush()
526 fp.close() 566 fp.close()
527 except: 567 except:
528 pass 568 return False
529 return True 569 return True
530 def fgetVal(self, path): 570 def fgetVal(self, path):
531 file = self.tpath+path 571 file = self.tpath+path
@@ -566,9 +606,15 @@ class SystemValues:
566 self.cleanupFtrace() 606 self.cleanupFtrace()
567 # set the trace clock to global 607 # set the trace clock to global
568 self.fsetVal('global', 'trace_clock') 608 self.fsetVal('global', 'trace_clock')
569 # set trace buffer to a huge value
570 self.fsetVal('nop', 'current_tracer') 609 self.fsetVal('nop', 'current_tracer')
571 self.fsetVal('131073', 'buffer_size_kb') 610 # set trace buffer to a huge value
611 if self.usecallgraph or self.usedevsrc:
612 tgtsize = min(self.memtotal / 2, 2*1024*1024)
613 maxbuf = '%d' % (tgtsize / max(1, self.cpucount))
614 if self.cpucount < 1 or not self.fsetVal(maxbuf, 'buffer_size_kb'):
615 self.fsetVal('131072', 'buffer_size_kb')
616 else:
617 self.fsetVal('16384', 'buffer_size_kb')
572 # go no further if this is just a status check 618 # go no further if this is just a status check
573 if testing: 619 if testing:
574 return 620 return
@@ -641,6 +687,15 @@ class SystemValues:
641 if not self.ansi: 687 if not self.ansi:
642 return str 688 return str
643 return '\x1B[%d;40m%s\x1B[m' % (color, str) 689 return '\x1B[%d;40m%s\x1B[m' % (color, str)
690 def writeDatafileHeader(self, filename, fwdata=[]):
691 fp = open(filename, 'w')
692 fp.write(self.teststamp+'\n')
693 fp.write(self.sysstamp+'\n')
694 if(self.suspendmode == 'mem' or self.suspendmode == 'command'):
695 for fw in fwdata:
696 if(fw):
697 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
698 fp.close()
644 699
645sysvals = SystemValues() 700sysvals = SystemValues()
646suspendmodename = { 701suspendmodename = {
@@ -1008,6 +1063,12 @@ class Data:
1008 else: 1063 else:
1009 self.trimTime(self.tSuspended, \ 1064 self.trimTime(self.tSuspended, \
1010 self.tResumed-self.tSuspended, False) 1065 self.tResumed-self.tSuspended, False)
1066 def getTimeValues(self):
1067 sktime = (self.dmesg['suspend_machine']['end'] - \
1068 self.tKernSus) * 1000
1069 rktime = (self.dmesg['resume_complete']['end'] - \
1070 self.dmesg['resume_machine']['start']) * 1000
1071 return (sktime, rktime)
1011 def setPhase(self, phase, ktime, isbegin): 1072 def setPhase(self, phase, ktime, isbegin):
1012 if(isbegin): 1073 if(isbegin):
1013 self.dmesg[phase]['start'] = ktime 1074 self.dmesg[phase]['start'] = ktime
@@ -1517,7 +1578,7 @@ class FTraceCallGraph:
1517 prelinedep += 1 1578 prelinedep += 1
1518 last = 0 1579 last = 0
1519 lasttime = line.time 1580 lasttime = line.time
1520 virtualfname = 'execution_misalignment' 1581 virtualfname = 'missing_function_name'
1521 if len(self.list) > 0: 1582 if len(self.list) > 0:
1522 last = self.list[-1] 1583 last = self.list[-1]
1523 lasttime = last.time 1584 lasttime = last.time
@@ -1773,24 +1834,30 @@ class Timeline:
1773 html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n' 1834 html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
1774 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n' 1835 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
1775 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n' 1836 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
1837 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
1776 def __init__(self, rowheight, scaleheight): 1838 def __init__(self, rowheight, scaleheight):
1777 self.rowH = rowheight 1839 self.rowH = rowheight
1778 self.scaleH = scaleheight 1840 self.scaleH = scaleheight
1779 self.html = '' 1841 self.html = ''
1780 def createHeader(self, sv, suppress=''): 1842 def createHeader(self, sv):
1781 if(not sv.stamp['time']): 1843 if(not sv.stamp['time']):
1782 return 1844 return
1783 self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \ 1845 self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \
1784 % (sv.title, sv.version) 1846 % (sv.title, sv.version)
1785 if sv.logmsg and 'log' not in suppress: 1847 if sv.logmsg and sv.testlog:
1786 self.html += '<button id="showtest" class="logbtn">log</button>' 1848 self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
1787 if sv.addlogs and 'dmesg' not in suppress: 1849 if sv.dmesglog:
1788 self.html += '<button id="showdmesg" class="logbtn">dmesg</button>' 1850 self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
1789 if sv.addlogs and sv.ftracefile and 'ftrace' not in suppress: 1851 if sv.ftracelog:
1790 self.html += '<button id="showftrace" class="logbtn">ftrace</button>' 1852 self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
1791 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n' 1853 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
1792 self.html += headline_stamp.format(sv.stamp['host'], sv.stamp['kernel'], 1854 self.html += headline_stamp.format(sv.stamp['host'], sv.stamp['kernel'],
1793 sv.stamp['mode'], sv.stamp['time']) 1855 sv.stamp['mode'], sv.stamp['time'])
1856 if 'man' in sv.stamp and 'plat' in sv.stamp and 'cpu' in sv.stamp:
1857 headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
1858 self.html += headline_sysinfo.format(sv.stamp['man'],
1859 sv.stamp['plat'], sv.stamp['cpu'])
1860
1794 # Function: getDeviceRows 1861 # Function: getDeviceRows
1795 # Description: 1862 # Description:
1796 # determine how may rows the device funcs will take 1863 # determine how may rows the device funcs will take
@@ -1839,7 +1906,7 @@ class Timeline:
1839 # devlist: the list of devices/actions in a group of contiguous phases 1906 # devlist: the list of devices/actions in a group of contiguous phases
1840 # Output: 1907 # Output:
1841 # The total number of rows needed to display this phase of the timeline 1908 # The total number of rows needed to display this phase of the timeline
1842 def getPhaseRows(self, devlist, row=0): 1909 def getPhaseRows(self, devlist, row=0, sortby='length'):
1843 # clear all rows and set them to undefined 1910 # clear all rows and set them to undefined
1844 remaining = len(devlist) 1911 remaining = len(devlist)
1845 rowdata = dict() 1912 rowdata = dict()
@@ -1852,8 +1919,12 @@ class Timeline:
1852 if tp not in myphases: 1919 if tp not in myphases:
1853 myphases.append(tp) 1920 myphases.append(tp)
1854 dev['row'] = -1 1921 dev['row'] = -1
1855 # sort by length 1st, then name 2nd 1922 if sortby == 'start':
1856 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name']) 1923 # sort by start 1st, then length 2nd
1924 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
1925 else:
1926 # sort by length 1st, then name 2nd
1927 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
1857 if 'src' in dev: 1928 if 'src' in dev:
1858 dev['devrows'] = self.getDeviceRows(dev['src']) 1929 dev['devrows'] = self.getDeviceRows(dev['src'])
1859 # sort the devlist by length so that large items graph on top 1930 # sort the devlist by length so that large items graph on top
@@ -1995,8 +2066,13 @@ class Timeline:
1995# A list of values describing the properties of these test runs 2066# A list of values describing the properties of these test runs
1996class TestProps: 2067class TestProps:
1997 stamp = '' 2068 stamp = ''
2069 sysinfo = ''
1998 S0i3 = False 2070 S0i3 = False
1999 fwdata = [] 2071 fwdata = []
2072 stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2073 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2074 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2075 sysinfofmt = '^# sysinfo .*'
2000 ftrace_line_fmt_fg = \ 2076 ftrace_line_fmt_fg = \
2001 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\ 2077 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2002 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\ 2078 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
@@ -2019,6 +2095,36 @@ class TestProps:
2019 self.ftrace_line_fmt = self.ftrace_line_fmt_nop 2095 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2020 else: 2096 else:
2021 doError('Invalid tracer format: [%s]' % tracer) 2097 doError('Invalid tracer format: [%s]' % tracer)
2098 def parseStamp(self, data, sv):
2099 m = re.match(self.stampfmt, self.stamp)
2100 data.stamp = {'time': '', 'host': '', 'mode': ''}
2101 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2102 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2103 int(m.group('S')))
2104 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2105 data.stamp['host'] = m.group('host')
2106 data.stamp['mode'] = m.group('mode')
2107 data.stamp['kernel'] = m.group('kernel')
2108 if re.match(self.sysinfofmt, self.sysinfo):
2109 for f in self.sysinfo.split('|'):
2110 if '#' in f:
2111 continue
2112 tmp = f.strip().split(':', 1)
2113 key = tmp[0]
2114 val = tmp[1]
2115 data.stamp[key] = val
2116 sv.hostname = data.stamp['host']
2117 sv.suspendmode = data.stamp['mode']
2118 if sv.suspendmode == 'command' and sv.ftracefile != '':
2119 modes = ['on', 'freeze', 'standby', 'mem']
2120 out = Popen(['grep', 'suspend_enter', sv.ftracefile],
2121 stderr=PIPE, stdout=PIPE).stdout.read()
2122 m = re.match('.* suspend_enter\[(?P<mode>.*)\]', out)
2123 if m and m.group('mode') in ['1', '2', '3']:
2124 sv.suspendmode = modes[int(m.group('mode'))]
2125 data.stamp['mode'] = sv.suspendmode
2126 if not sv.stamp:
2127 sv.stamp = data.stamp
2022 2128
2023# Class: TestRun 2129# Class: TestRun
2024# Description: 2130# Description:
@@ -2090,35 +2196,6 @@ def vprint(msg):
2090 if(sysvals.verbose): 2196 if(sysvals.verbose):
2091 print(msg) 2197 print(msg)
2092 2198
2093# Function: parseStamp
2094# Description:
2095# Pull in the stamp comment line from the data file(s),
2096# create the stamp, and add it to the global sysvals object
2097# Arguments:
2098# m: the valid re.match output for the stamp line
2099def parseStamp(line, data):
2100 m = re.match(sysvals.stampfmt, line)
2101 data.stamp = {'time': '', 'host': '', 'mode': ''}
2102 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2103 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2104 int(m.group('S')))
2105 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2106 data.stamp['host'] = m.group('host')
2107 data.stamp['mode'] = m.group('mode')
2108 data.stamp['kernel'] = m.group('kernel')
2109 sysvals.hostname = data.stamp['host']
2110 sysvals.suspendmode = data.stamp['mode']
2111 if sysvals.suspendmode == 'command' and sysvals.ftracefile != '':
2112 modes = ['on', 'freeze', 'standby', 'mem']
2113 out = Popen(['grep', 'suspend_enter', sysvals.ftracefile],
2114 stderr=PIPE, stdout=PIPE).stdout.read()
2115 m = re.match('.* suspend_enter\[(?P<mode>.*)\]', out)
2116 if m and m.group('mode') in ['1', '2', '3']:
2117 sysvals.suspendmode = modes[int(m.group('mode'))]
2118 data.stamp['mode'] = sysvals.suspendmode
2119 if not sysvals.stamp:
2120 sysvals.stamp = data.stamp
2121
2122# Function: doesTraceLogHaveTraceEvents 2199# Function: doesTraceLogHaveTraceEvents
2123# Description: 2200# Description:
2124# Quickly determine if the ftrace log has some or all of the trace events 2201# Quickly determine if the ftrace log has some or all of the trace events
@@ -2136,11 +2213,6 @@ def doesTraceLogHaveTraceEvents():
2136 sysvals.usekprobes = True 2213 sysvals.usekprobes = True
2137 out = Popen(['head', '-1', sysvals.ftracefile], 2214 out = Popen(['head', '-1', sysvals.ftracefile],
2138 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '') 2215 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
2139 m = re.match(sysvals.stampfmt, out)
2140 if m and m.group('mode') == 'command':
2141 sysvals.usetraceeventsonly = True
2142 sysvals.usetraceevents = True
2143 return
2144 # figure out what level of trace events are supported 2216 # figure out what level of trace events are supported
2145 sysvals.usetraceeventsonly = True 2217 sysvals.usetraceeventsonly = True
2146 sysvals.usetraceevents = False 2218 sysvals.usetraceevents = False
@@ -2182,11 +2254,13 @@ def appendIncompleteTraceLog(testruns):
2182 for line in tf: 2254 for line in tf:
2183 # remove any latent carriage returns 2255 # remove any latent carriage returns
2184 line = line.replace('\r\n', '') 2256 line = line.replace('\r\n', '')
2185 # grab the time stamp 2257 # grab the stamp and sysinfo
2186 m = re.match(sysvals.stampfmt, line) 2258 if re.match(tp.stampfmt, line):
2187 if(m):
2188 tp.stamp = line 2259 tp.stamp = line
2189 continue 2260 continue
2261 elif re.match(tp.sysinfofmt, line):
2262 tp.sysinfo = line
2263 continue
2190 # determine the trace data type (required for further parsing) 2264 # determine the trace data type (required for further parsing)
2191 m = re.match(sysvals.tracertypefmt, line) 2265 m = re.match(sysvals.tracertypefmt, line)
2192 if(m): 2266 if(m):
@@ -2219,7 +2293,7 @@ def appendIncompleteTraceLog(testruns):
2219 # look for the suspend start marker 2293 # look for the suspend start marker
2220 if(t.startMarker()): 2294 if(t.startMarker()):
2221 data = testrun[testidx].data 2295 data = testrun[testidx].data
2222 parseStamp(tp.stamp, data) 2296 tp.parseStamp(data, sysvals)
2223 data.setStart(t.time) 2297 data.setStart(t.time)
2224 continue 2298 continue
2225 if(not data): 2299 if(not data):
@@ -2389,11 +2463,13 @@ def parseTraceLog():
2389 for line in tf: 2463 for line in tf:
2390 # remove any latent carriage returns 2464 # remove any latent carriage returns
2391 line = line.replace('\r\n', '') 2465 line = line.replace('\r\n', '')
2392 # stamp line: each stamp means a new test run 2466 # stamp and sysinfo lines
2393 m = re.match(sysvals.stampfmt, line) 2467 if re.match(tp.stampfmt, line):
2394 if(m):
2395 tp.stamp = line 2468 tp.stamp = line
2396 continue 2469 continue
2470 elif re.match(tp.sysinfofmt, line):
2471 tp.sysinfo = line
2472 continue
2397 # firmware line: pull out any firmware data 2473 # firmware line: pull out any firmware data
2398 m = re.match(sysvals.firmwarefmt, line) 2474 m = re.match(sysvals.firmwarefmt, line)
2399 if(m): 2475 if(m):
@@ -2439,7 +2515,7 @@ def parseTraceLog():
2439 testdata.append(data) 2515 testdata.append(data)
2440 testrun = TestRun(data) 2516 testrun = TestRun(data)
2441 testruns.append(testrun) 2517 testruns.append(testrun)
2442 parseStamp(tp.stamp, data) 2518 tp.parseStamp(data, sysvals)
2443 data.setStart(t.time) 2519 data.setStart(t.time)
2444 data.tKernSus = t.time 2520 data.tKernSus = t.time
2445 continue 2521 continue
@@ -2820,10 +2896,13 @@ def loadKernelLog(justtext=False):
2820 idx = line.find('[') 2896 idx = line.find('[')
2821 if idx > 1: 2897 if idx > 1:
2822 line = line[idx:] 2898 line = line[idx:]
2823 m = re.match(sysvals.stampfmt, line) 2899 # grab the stamp and sysinfo
2824 if(m): 2900 if re.match(tp.stampfmt, line):
2825 tp.stamp = line 2901 tp.stamp = line
2826 continue 2902 continue
2903 elif re.match(tp.sysinfofmt, line):
2904 tp.sysinfo = line
2905 continue
2827 m = re.match(sysvals.firmwarefmt, line) 2906 m = re.match(sysvals.firmwarefmt, line)
2828 if(m): 2907 if(m):
2829 tp.fwdata.append((int(m.group('s')), int(m.group('r')))) 2908 tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
@@ -2839,7 +2918,7 @@ def loadKernelLog(justtext=False):
2839 if(data): 2918 if(data):
2840 testruns.append(data) 2919 testruns.append(data)
2841 data = Data(len(testruns)) 2920 data = Data(len(testruns))
2842 parseStamp(tp.stamp, data) 2921 tp.parseStamp(data, sysvals)
2843 if len(tp.fwdata) > data.testnumber: 2922 if len(tp.fwdata) > data.testnumber:
2844 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber] 2923 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2845 if(data.fwSuspend > 0 or data.fwResume > 0): 2924 if(data.fwSuspend > 0 or data.fwResume > 0):
@@ -3170,6 +3249,8 @@ def addCallgraphs(sv, hf, data):
3170 continue 3249 continue
3171 list = data.dmesg[p]['list'] 3250 list = data.dmesg[p]['list']
3172 for devname in data.sortedDevices(p): 3251 for devname in data.sortedDevices(p):
3252 if len(sv.devicefilter) > 0 and devname not in sv.devicefilter:
3253 continue
3173 dev = list[devname] 3254 dev = list[devname]
3174 color = 'white' 3255 color = 'white'
3175 if 'color' in data.dmesg[p]: 3256 if 'color' in data.dmesg[p]:
@@ -3309,7 +3390,6 @@ def createHTML(testruns):
3309 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">ERROR&rarr;</div>\n' 3390 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">ERROR&rarr;</div>\n'
3310 html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n' 3391 html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n'
3311 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n' 3392 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
3312 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
3313 html_timetotal = '<table class="time1">\n<tr>'\ 3393 html_timetotal = '<table class="time1">\n<tr>'\
3314 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\ 3394 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
3315 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\ 3395 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
@@ -3346,10 +3426,7 @@ def createHTML(testruns):
3346 # Generate the header for this timeline 3426 # Generate the header for this timeline
3347 for data in testruns: 3427 for data in testruns:
3348 tTotal = data.end - data.start 3428 tTotal = data.end - data.start
3349 sktime = (data.dmesg['suspend_machine']['end'] - \ 3429 sktime, rktime = data.getTimeValues()
3350 data.tKernSus) * 1000
3351 rktime = (data.dmesg['resume_complete']['end'] - \
3352 data.dmesg['resume_machine']['start']) * 1000
3353 if(tTotal == 0): 3430 if(tTotal == 0):
3354 print('ERROR: No timeline data') 3431 print('ERROR: No timeline data')
3355 sys.exit() 3432 sys.exit()
@@ -3581,7 +3658,7 @@ def createHTML(testruns):
3581 id += tmp[1][0] 3658 id += tmp[1][0]
3582 order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) 3659 order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3583 name = string.replace(phase, '_', ' &nbsp;') 3660 name = string.replace(phase, '_', ' &nbsp;')
3584 devtl.html += html_legend.format(order, \ 3661 devtl.html += devtl.html_legend.format(order, \
3585 data.dmesg[phase]['color'], name, id) 3662 data.dmesg[phase]['color'], name, id)
3586 devtl.html += '</div>\n' 3663 devtl.html += '</div>\n'
3587 3664
@@ -3628,10 +3705,10 @@ def createHTML(testruns):
3628 addCallgraphs(sysvals, hf, data) 3705 addCallgraphs(sysvals, hf, data)
3629 3706
3630 # add the test log as a hidden div 3707 # add the test log as a hidden div
3631 if sysvals.logmsg: 3708 if sysvals.testlog and sysvals.logmsg:
3632 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n') 3709 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
3633 # add the dmesg log as a hidden div 3710 # add the dmesg log as a hidden div
3634 if sysvals.addlogs and sysvals.dmesgfile: 3711 if sysvals.dmesglog and sysvals.dmesgfile:
3635 hf.write('<div id="dmesglog" style="display:none;">\n') 3712 hf.write('<div id="dmesglog" style="display:none;">\n')
3636 lf = open(sysvals.dmesgfile, 'r') 3713 lf = open(sysvals.dmesgfile, 'r')
3637 for line in lf: 3714 for line in lf:
@@ -3640,7 +3717,7 @@ def createHTML(testruns):
3640 lf.close() 3717 lf.close()
3641 hf.write('</div>\n') 3718 hf.write('</div>\n')
3642 # add the ftrace log as a hidden div 3719 # add the ftrace log as a hidden div
3643 if sysvals.addlogs and sysvals.ftracefile: 3720 if sysvals.ftracelog and sysvals.ftracefile:
3644 hf.write('<div id="ftracelog" style="display:none;">\n') 3721 hf.write('<div id="ftracelog" style="display:none;">\n')
3645 lf = open(sysvals.ftracefile, 'r') 3722 lf = open(sysvals.ftracefile, 'r')
3646 for line in lf: 3723 for line in lf:
@@ -3701,6 +3778,7 @@ def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
3701 <style type=\'text/css\'>\n\ 3778 <style type=\'text/css\'>\n\
3702 body {overflow-y:scroll;}\n\ 3779 body {overflow-y:scroll;}\n\
3703 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\ 3780 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
3781 .stamp.sysinfo {font:10px Arial;}\n\
3704 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\ 3782 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
3705 .callgraph article * {padding-left:28px;}\n\ 3783 .callgraph article * {padding-left:28px;}\n\
3706 h1 {color:black;font:bold 30px Times;}\n\ 3784 h1 {color:black;font:bold 30px Times;}\n\
@@ -3746,7 +3824,7 @@ def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
3746 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\ 3824 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
3747 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\ 3825 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
3748 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\ 3826 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
3749 .logbtn {position:relative;float:right;height:25px;width:50px;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\ 3827 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
3750 .devlist {position:'+devlistpos+';width:190px;}\n\ 3828 .devlist {position:'+devlistpos+';width:190px;}\n\
3751 a:link {color:white;text-decoration:none;}\n\ 3829 a:link {color:white;text-decoration:none;}\n\
3752 a:visited {color:white;}\n\ 3830 a:visited {color:white;}\n\
@@ -4084,8 +4162,6 @@ def addScriptCode(hf, testruns):
4084 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\ 4162 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4085 ' win.document.close();\n'\ 4163 ' win.document.close();\n'\
4086 ' }\n'\ 4164 ' }\n'\
4087 ' function onClickPhase(e) {\n'\
4088 ' }\n'\
4089 ' function onMouseDown(e) {\n'\ 4165 ' function onMouseDown(e) {\n'\
4090 ' dragval[0] = e.clientX;\n'\ 4166 ' dragval[0] = e.clientX;\n'\
4091 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\ 4167 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
@@ -4120,9 +4196,6 @@ def addScriptCode(hf, testruns):
4120 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\ 4196 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
4121 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\ 4197 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
4122 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\ 4198 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
4123 ' var list = document.getElementsByClassName("square");\n'\
4124 ' for (var i = 0; i < list.length; i++)\n'\
4125 ' list[i].onclick = onClickPhase;\n'\
4126 ' var list = document.getElementsByClassName("err");\n'\ 4199 ' var list = document.getElementsByClassName("err");\n'\
4127 ' for (var i = 0; i < list.length; i++)\n'\ 4200 ' for (var i = 0; i < list.length; i++)\n'\
4128 ' list[i].onclick = errWindow;\n'\ 4201 ' list[i].onclick = errWindow;\n'\
@@ -4193,8 +4266,14 @@ def executeSuspend():
4193 if sysvals.testcommand != '': 4266 if sysvals.testcommand != '':
4194 call(sysvals.testcommand+' 2>&1', shell=True); 4267 call(sysvals.testcommand+' 2>&1', shell=True);
4195 else: 4268 else:
4269 mode = sysvals.suspendmode
4270 if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
4271 mode = 'mem'
4272 pf = open(sysvals.mempowerfile, 'w')
4273 pf.write(sysvals.memmode)
4274 pf.close()
4196 pf = open(sysvals.powerfile, 'w') 4275 pf = open(sysvals.powerfile, 'w')
4197 pf.write(sysvals.suspendmode) 4276 pf.write(mode)
4198 # execution will pause here 4277 # execution will pause here
4199 try: 4278 try:
4200 pf.close() 4279 pf.close()
@@ -4219,24 +4298,15 @@ def executeSuspend():
4219 pm.stop() 4298 pm.stop()
4220 sysvals.fsetVal('0', 'tracing_on') 4299 sysvals.fsetVal('0', 'tracing_on')
4221 print('CAPTURING TRACE') 4300 print('CAPTURING TRACE')
4222 writeDatafileHeader(sysvals.ftracefile, fwdata) 4301 sysvals.writeDatafileHeader(sysvals.ftracefile, fwdata)
4223 call('cat '+tp+'trace >> '+sysvals.ftracefile, shell=True) 4302 call('cat '+tp+'trace >> '+sysvals.ftracefile, shell=True)
4224 sysvals.fsetVal('', 'trace') 4303 sysvals.fsetVal('', 'trace')
4225 devProps() 4304 devProps()
4226 # grab a copy of the dmesg output 4305 # grab a copy of the dmesg output
4227 print('CAPTURING DMESG') 4306 print('CAPTURING DMESG')
4228 writeDatafileHeader(sysvals.dmesgfile, fwdata) 4307 sysvals.writeDatafileHeader(sysvals.dmesgfile, fwdata)
4229 sysvals.getdmesg() 4308 sysvals.getdmesg()
4230 4309
4231def writeDatafileHeader(filename, fwdata):
4232 fp = open(filename, 'a')
4233 fp.write(sysvals.teststamp+'\n')
4234 if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4235 for fw in fwdata:
4236 if(fw):
4237 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
4238 fp.close()
4239
4240# Function: setUSBDevicesAuto 4310# Function: setUSBDevicesAuto
4241# Description: 4311# Description:
4242# Set the autosuspend control parameter of all USB devices to auto 4312# Set the autosuspend control parameter of all USB devices to auto
@@ -4244,7 +4314,7 @@ def writeDatafileHeader(filename, fwdata):
4244# to always-on since the kernel cant determine if the device can 4314# to always-on since the kernel cant determine if the device can
4245# properly autosuspend 4315# properly autosuspend
4246def setUSBDevicesAuto(): 4316def setUSBDevicesAuto():
4247 rootCheck(True) 4317 sysvals.rootCheck(True)
4248 for dirname, dirnames, filenames in os.walk('/sys/devices'): 4318 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4249 if(re.match('.*/usb[0-9]*.*', dirname) and 4319 if(re.match('.*/usb[0-9]*.*', dirname) and
4250 'idVendor' in filenames and 'idProduct' in filenames): 4320 'idVendor' in filenames and 'idProduct' in filenames):
@@ -4467,13 +4537,146 @@ def devProps(data=0):
4467# Output: 4537# Output:
4468# A string list of the available modes 4538# A string list of the available modes
4469def getModes(): 4539def getModes():
4470 modes = '' 4540 modes = []
4471 if(os.path.exists(sysvals.powerfile)): 4541 if(os.path.exists(sysvals.powerfile)):
4472 fp = open(sysvals.powerfile, 'r') 4542 fp = open(sysvals.powerfile, 'r')
4473 modes = string.split(fp.read()) 4543 modes = string.split(fp.read())
4474 fp.close() 4544 fp.close()
4545 if(os.path.exists(sysvals.mempowerfile)):
4546 deep = False
4547 fp = open(sysvals.mempowerfile, 'r')
4548 for m in string.split(fp.read()):
4549 memmode = m.strip('[]')
4550 if memmode == 'deep':
4551 deep = True
4552 else:
4553 modes.append('mem-%s' % memmode)
4554 fp.close()
4555 if 'mem' in modes and not deep:
4556 modes.remove('mem')
4475 return modes 4557 return modes
4476 4558
4559# Function: dmidecode
4560# Description:
4561# Read the bios tables and pull out system info
4562# Arguments:
4563# mempath: /dev/mem or custom mem path
4564# fatal: True to exit on error, False to return empty dict
4565# Output:
4566# A dict object with all available key/values
4567def dmidecode(mempath, fatal=False):
4568 out = dict()
4569
4570 # the list of values to retrieve, with hardcoded (type, idx)
4571 info = {
4572 'bios-vendor': (0, 4),
4573 'bios-version': (0, 5),
4574 'bios-release-date': (0, 8),
4575 'system-manufacturer': (1, 4),
4576 'system-product-name': (1, 5),
4577 'system-version': (1, 6),
4578 'system-serial-number': (1, 7),
4579 'baseboard-manufacturer': (2, 4),
4580 'baseboard-product-name': (2, 5),
4581 'baseboard-version': (2, 6),
4582 'baseboard-serial-number': (2, 7),
4583 'chassis-manufacturer': (3, 4),
4584 'chassis-type': (3, 5),
4585 'chassis-version': (3, 6),
4586 'chassis-serial-number': (3, 7),
4587 'processor-manufacturer': (4, 7),
4588 'processor-version': (4, 16),
4589 }
4590 if(not os.path.exists(mempath)):
4591 if(fatal):
4592 doError('file does not exist: %s' % mempath)
4593 return out
4594 if(not os.access(mempath, os.R_OK)):
4595 if(fatal):
4596 doError('file is not readable: %s' % mempath)
4597 return out
4598
4599 # by default use legacy scan, but try to use EFI first
4600 memaddr = 0xf0000
4601 memsize = 0x10000
4602 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
4603 if not os.path.exists(ep) or not os.access(ep, os.R_OK):
4604 continue
4605 fp = open(ep, 'r')
4606 buf = fp.read()
4607 fp.close()
4608 i = buf.find('SMBIOS=')
4609 if i >= 0:
4610 try:
4611 memaddr = int(buf[i+7:], 16)
4612 memsize = 0x20
4613 except:
4614 continue
4615
4616 # read in the memory for scanning
4617 fp = open(mempath, 'rb')
4618 try:
4619 fp.seek(memaddr)
4620 buf = fp.read(memsize)
4621 except:
4622 if(fatal):
4623 doError('DMI table is unreachable, sorry')
4624 else:
4625 return out
4626 fp.close()
4627
4628 # search for either an SM table or DMI table
4629 i = base = length = num = 0
4630 while(i < memsize):
4631 if buf[i:i+4] == '_SM_' and i < memsize - 16:
4632 length = struct.unpack('H', buf[i+22:i+24])[0]
4633 base, num = struct.unpack('IH', buf[i+24:i+30])
4634 break
4635 elif buf[i:i+5] == '_DMI_':
4636 length = struct.unpack('H', buf[i+6:i+8])[0]
4637 base, num = struct.unpack('IH', buf[i+8:i+14])
4638 break
4639 i += 16
4640 if base == 0 and length == 0 and num == 0:
4641 if(fatal):
4642 doError('Neither SMBIOS nor DMI were found')
4643 else:
4644 return out
4645
4646 # read in the SM or DMI table
4647 fp = open(mempath, 'rb')
4648 try:
4649 fp.seek(base)
4650 buf = fp.read(length)
4651 except:
4652 if(fatal):
4653 doError('DMI table is unreachable, sorry')
4654 else:
4655 return out
4656 fp.close()
4657
4658 # scan the table for the values we want
4659 count = i = 0
4660 while(count < num and i <= len(buf) - 4):
4661 type, size, handle = struct.unpack('BBH', buf[i:i+4])
4662 n = i + size
4663 while n < len(buf) - 1:
4664 if 0 == struct.unpack('H', buf[n:n+2])[0]:
4665 break
4666 n += 1
4667 data = buf[i+size:n+2].split('\0')
4668 for name in info:
4669 itype, idxadr = info[name]
4670 if itype == type:
4671 idx = struct.unpack('B', buf[i+idxadr])[0]
4672 if idx > 0 and idx < len(data) - 1:
4673 s = data[idx-1].strip()
4674 if s and s.lower() != 'to be filled by o.e.m.':
4675 out[name] = data[idx-1]
4676 i = n + 2
4677 count += 1
4678 return out
4679
4477# Function: getFPDT 4680# Function: getFPDT
4478# Description: 4681# Description:
4479# Read the acpi bios tables and pull out FPDT, the firmware data 4682# Read the acpi bios tables and pull out FPDT, the firmware data
@@ -4487,7 +4690,7 @@ def getFPDT(output):
4487 prectype[0] = 'Basic S3 Resume Performance Record' 4690 prectype[0] = 'Basic S3 Resume Performance Record'
4488 prectype[1] = 'Basic S3 Suspend Performance Record' 4691 prectype[1] = 'Basic S3 Suspend Performance Record'
4489 4692
4490 rootCheck(True) 4693 sysvals.rootCheck(True)
4491 if(not os.path.exists(sysvals.fpdtpath)): 4694 if(not os.path.exists(sysvals.fpdtpath)):
4492 if(output): 4695 if(output):
4493 doError('file does not exist: %s' % sysvals.fpdtpath) 4696 doError('file does not exist: %s' % sysvals.fpdtpath)
@@ -4617,7 +4820,7 @@ def statusCheck(probecheck=False):
4617 4820
4618 # check we have root access 4821 # check we have root access
4619 res = sysvals.colorText('NO (No features of this tool will work!)') 4822 res = sysvals.colorText('NO (No features of this tool will work!)')
4620 if(rootCheck(False)): 4823 if(sysvals.rootCheck(False)):
4621 res = 'YES' 4824 res = 'YES'
4622 print(' have root access: %s' % res) 4825 print(' have root access: %s' % res)
4623 if(res != 'YES'): 4826 if(res != 'YES'):
@@ -4716,16 +4919,6 @@ def doError(msg, help=False):
4716 print('ERROR: %s\n') % msg 4919 print('ERROR: %s\n') % msg
4717 sys.exit() 4920 sys.exit()
4718 4921
4719# Function: rootCheck
4720# Description:
4721# quick check to see if we have root access
4722def rootCheck(fatal):
4723 if(os.access(sysvals.powerfile, os.W_OK)):
4724 return True
4725 if fatal:
4726 doError('This command requires sysfs mount and root access')
4727 return False
4728
4729# Function: getArgInt 4922# Function: getArgInt
4730# Description: 4923# Description:
4731# pull out an integer argument from the command line with checks 4924# pull out an integer argument from the command line with checks
@@ -4779,6 +4972,7 @@ def processData():
4779 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)): 4972 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
4780 appendIncompleteTraceLog(testruns) 4973 appendIncompleteTraceLog(testruns)
4781 createHTML(testruns) 4974 createHTML(testruns)
4975 return testruns
4782 4976
4783# Function: rerunTest 4977# Function: rerunTest
4784# Description: 4978# Description:
@@ -4790,17 +4984,20 @@ def rerunTest():
4790 doError('recreating this html output requires a dmesg file') 4984 doError('recreating this html output requires a dmesg file')
4791 sysvals.setOutputFile() 4985 sysvals.setOutputFile()
4792 vprint('Output file: %s' % sysvals.htmlfile) 4986 vprint('Output file: %s' % sysvals.htmlfile)
4793 if(os.path.exists(sysvals.htmlfile) and not os.access(sysvals.htmlfile, os.W_OK)): 4987 if os.path.exists(sysvals.htmlfile):
4794 doError('missing permission to write to %s' % sysvals.htmlfile) 4988 if not os.path.isfile(sysvals.htmlfile):
4795 processData() 4989 doError('a directory already exists with this name: %s' % sysvals.htmlfile)
4990 elif not os.access(sysvals.htmlfile, os.W_OK):
4991 doError('missing permission to write to %s' % sysvals.htmlfile)
4992 return processData()
4796 4993
4797# Function: runTest 4994# Function: runTest
4798# Description: 4995# Description:
4799# execute a suspend/resume, gather the logs, and generate the output 4996# execute a suspend/resume, gather the logs, and generate the output
4800def runTest(subdir, testpath=''): 4997def runTest():
4801 # prepare for the test 4998 # prepare for the test
4802 sysvals.initFtrace() 4999 sysvals.initFtrace()
4803 sysvals.initTestOutput(subdir, testpath) 5000 sysvals.initTestOutput('suspend')
4804 vprint('Output files:\n\t%s\n\t%s\n\t%s' % \ 5001 vprint('Output files:\n\t%s\n\t%s\n\t%s' % \
4805 (sysvals.dmesgfile, sysvals.ftracefile, sysvals.htmlfile)) 5002 (sysvals.dmesgfile, sysvals.ftracefile, sysvals.htmlfile))
4806 5003
@@ -4897,7 +5094,7 @@ def configFromFile(file):
4897 if(opt.lower() == 'verbose'): 5094 if(opt.lower() == 'verbose'):
4898 sysvals.verbose = checkArgBool(value) 5095 sysvals.verbose = checkArgBool(value)
4899 elif(opt.lower() == 'addlogs'): 5096 elif(opt.lower() == 'addlogs'):
4900 sysvals.addlogs = checkArgBool(value) 5097 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(value)
4901 elif(opt.lower() == 'dev'): 5098 elif(opt.lower() == 'dev'):
4902 sysvals.usedevsrc = checkArgBool(value) 5099 sysvals.usedevsrc = checkArgBool(value)
4903 elif(opt.lower() == 'proc'): 5100 elif(opt.lower() == 'proc'):
@@ -4947,7 +5144,7 @@ def configFromFile(file):
4947 elif(opt.lower() == 'mincg'): 5144 elif(opt.lower() == 'mincg'):
4948 sysvals.mincglen = getArgFloat('-mincg', value, 0.0, 10000.0, False) 5145 sysvals.mincglen = getArgFloat('-mincg', value, 0.0, 10000.0, False)
4949 elif(opt.lower() == 'output-dir'): 5146 elif(opt.lower() == 'output-dir'):
4950 sysvals.setOutputFolder(value) 5147 sysvals.testdir = sysvals.setOutputFolder(value)
4951 5148
4952 if sysvals.suspendmode == 'command' and not sysvals.testcommand: 5149 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
4953 doError('No command supplied for mode "command"') 5150 doError('No command supplied for mode "command"')
@@ -5030,8 +5227,6 @@ def configFromFile(file):
5030# Description: 5227# Description:
5031# print out the help text 5228# print out the help text
5032def printHelp(): 5229def printHelp():
5033 modes = getModes()
5034
5035 print('') 5230 print('')
5036 print('%s v%s' % (sysvals.title, sysvals.version)) 5231 print('%s v%s' % (sysvals.title, sysvals.version))
5037 print('Usage: sudo sleepgraph <options> <commands>') 5232 print('Usage: sudo sleepgraph <options> <commands>')
@@ -5048,7 +5243,7 @@ def printHelp():
5048 print(' If no specific command is given, the default behavior is to initiate') 5243 print(' If no specific command is given, the default behavior is to initiate')
5049 print(' a suspend/resume and capture the dmesg/ftrace output as an html timeline.') 5244 print(' a suspend/resume and capture the dmesg/ftrace output as an html timeline.')
5050 print('') 5245 print('')
5051 print(' Generates output files in subdirectory: suspend-mmddyy-HHMMSS') 5246 print(' Generates output files in subdirectory: suspend-yymmdd-HHMMSS')
5052 print(' HTML output: <hostname>_<mode>.html') 5247 print(' HTML output: <hostname>_<mode>.html')
5053 print(' raw dmesg output: <hostname>_<mode>_dmesg.txt') 5248 print(' raw dmesg output: <hostname>_<mode>_dmesg.txt')
5054 print(' raw ftrace output: <hostname>_<mode>_ftrace.txt') 5249 print(' raw ftrace output: <hostname>_<mode>_ftrace.txt')
@@ -5058,8 +5253,9 @@ def printHelp():
5058 print(' -v Print the current tool version') 5253 print(' -v Print the current tool version')
5059 print(' -config fn Pull arguments and config options from file fn') 5254 print(' -config fn Pull arguments and config options from file fn')
5060 print(' -verbose Print extra information during execution and analysis') 5255 print(' -verbose Print extra information during execution and analysis')
5061 print(' -m mode Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode) 5256 print(' -m mode Mode to initiate for suspend (default: %s)') % (sysvals.suspendmode)
5062 print(' -o subdir Override the output subdirectory') 5257 print(' -o name Overrides the output subdirectory name when running a new test')
5258 print(' default: suspend-{date}-{time}')
5063 print(' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)') 5259 print(' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)')
5064 print(' -addlogs Add the dmesg and ftrace logs to the html output') 5260 print(' -addlogs Add the dmesg and ftrace logs to the html output')
5065 print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)') 5261 print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)')
@@ -5084,17 +5280,20 @@ def printHelp():
5084 print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)') 5280 print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)')
5085 print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)') 5281 print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)')
5086 print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)') 5282 print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
5087 print(' [commands]') 5283 print('')
5088 print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)') 5284 print('Other commands:')
5089 print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)')
5090 print(' -summary directory Create a summary of all test in this dir')
5091 print(' -modes List available suspend modes') 5285 print(' -modes List available suspend modes')
5092 print(' -status Test to see if the system is enabled to run this tool') 5286 print(' -status Test to see if the system is enabled to run this tool')
5093 print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table') 5287 print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table')
5288 print(' -sysinfo Print out system info extracted from BIOS')
5094 print(' -usbtopo Print out the current USB topology with power info') 5289 print(' -usbtopo Print out the current USB topology with power info')
5095 print(' -usbauto Enable autosuspend for all connected USB devices') 5290 print(' -usbauto Enable autosuspend for all connected USB devices')
5096 print(' -flist Print the list of functions currently being captured in ftrace') 5291 print(' -flist Print the list of functions currently being captured in ftrace')
5097 print(' -flistall Print all functions capable of being captured in ftrace') 5292 print(' -flistall Print all functions capable of being captured in ftrace')
5293 print(' -summary directory Create a summary of all test in this dir')
5294 print(' [redo]')
5295 print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)')
5296 print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)')
5098 print('') 5297 print('')
5099 return True 5298 return True
5100 5299
@@ -5102,9 +5301,9 @@ def printHelp():
5102# exec start (skipped if script is loaded as library) 5301# exec start (skipped if script is loaded as library)
5103if __name__ == '__main__': 5302if __name__ == '__main__':
5104 cmd = '' 5303 cmd = ''
5105 cmdarg = '' 5304 outdir = ''
5106 multitest = {'run': False, 'count': 0, 'delay': 0} 5305 multitest = {'run': False, 'count': 0, 'delay': 0}
5107 simplecmds = ['-modes', '-fpdt', '-flist', '-flistall', '-usbtopo', '-usbauto', '-status'] 5306 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-usbtopo', '-usbauto', '-status']
5108 # loop through the command line arguments 5307 # loop through the command line arguments
5109 args = iter(sys.argv[1:]) 5308 args = iter(sys.argv[1:])
5110 for arg in args: 5309 for arg in args:
@@ -5135,7 +5334,7 @@ if __name__ == '__main__':
5135 elif(arg == '-f'): 5334 elif(arg == '-f'):
5136 sysvals.usecallgraph = True 5335 sysvals.usecallgraph = True
5137 elif(arg == '-addlogs'): 5336 elif(arg == '-addlogs'):
5138 sysvals.addlogs = True 5337 sysvals.dmesglog = sysvals.ftracelog = True
5139 elif(arg == '-verbose'): 5338 elif(arg == '-verbose'):
5140 sysvals.verbose = True 5339 sysvals.verbose = True
5141 elif(arg == '-proc'): 5340 elif(arg == '-proc'):
@@ -5195,7 +5394,7 @@ if __name__ == '__main__':
5195 val = args.next() 5394 val = args.next()
5196 except: 5395 except:
5197 doError('No subdirectory name supplied', True) 5396 doError('No subdirectory name supplied', True)
5198 sysvals.setOutputFolder(val) 5397 outdir = sysvals.setOutputFolder(val)
5199 elif(arg == '-config'): 5398 elif(arg == '-config'):
5200 try: 5399 try:
5201 val = args.next() 5400 val = args.next()
@@ -5236,7 +5435,7 @@ if __name__ == '__main__':
5236 except: 5435 except:
5237 doError('No directory supplied', True) 5436 doError('No directory supplied', True)
5238 cmd = 'summary' 5437 cmd = 'summary'
5239 cmdarg = val 5438 outdir = val
5240 sysvals.notestrun = True 5439 sysvals.notestrun = True
5241 if(os.path.isdir(val) == False): 5440 if(os.path.isdir(val) == False):
5242 doError('%s is not accesible' % val) 5441 doError('%s is not accesible' % val)
@@ -5260,11 +5459,14 @@ if __name__ == '__main__':
5260 sysvals.mincglen = sysvals.mindevlen 5459 sysvals.mincglen = sysvals.mindevlen
5261 5460
5262 # just run a utility command and exit 5461 # just run a utility command and exit
5462 sysvals.cpuInfo()
5263 if(cmd != ''): 5463 if(cmd != ''):
5264 if(cmd == 'status'): 5464 if(cmd == 'status'):
5265 statusCheck(True) 5465 statusCheck(True)
5266 elif(cmd == 'fpdt'): 5466 elif(cmd == 'fpdt'):
5267 getFPDT(True) 5467 getFPDT(True)
5468 elif(cmd == 'sysinfo'):
5469 sysvals.printSystemInfo()
5268 elif(cmd == 'usbtopo'): 5470 elif(cmd == 'usbtopo'):
5269 detectUSB() 5471 detectUSB()
5270 elif(cmd == 'modes'): 5472 elif(cmd == 'modes'):
@@ -5276,7 +5478,7 @@ if __name__ == '__main__':
5276 elif(cmd == 'usbauto'): 5478 elif(cmd == 'usbauto'):
5277 setUSBDevicesAuto() 5479 setUSBDevicesAuto()
5278 elif(cmd == 'summary'): 5480 elif(cmd == 'summary'):
5279 runSummary(cmdarg, True) 5481 runSummary(outdir, True)
5280 sys.exit() 5482 sys.exit()
5281 5483
5282 # if instructed, re-analyze existing data files 5484 # if instructed, re-analyze existing data files
@@ -5289,21 +5491,43 @@ if __name__ == '__main__':
5289 print('Check FAILED, aborting the test run!') 5491 print('Check FAILED, aborting the test run!')
5290 sys.exit() 5492 sys.exit()
5291 5493
5494 # extract mem modes and convert
5495 mode = sysvals.suspendmode
5496 if 'mem' == mode[:3]:
5497 if '-' in mode:
5498 memmode = mode.split('-')[-1]
5499 else:
5500 memmode = 'deep'
5501 if memmode == 'shallow':
5502 mode = 'standby'
5503 elif memmode == 's2idle':
5504 mode = 'freeze'
5505 else:
5506 mode = 'mem'
5507 sysvals.memmode = memmode
5508 sysvals.suspendmode = mode
5509
5510 sysvals.systemInfo(dmidecode(sysvals.mempath))
5511
5292 if multitest['run']: 5512 if multitest['run']:
5293 # run multiple tests in a separate subdirectory 5513 # run multiple tests in a separate subdirectory
5294 s = 'x%d' % multitest['count'] 5514 if not outdir:
5295 if not sysvals.outdir: 5515 s = 'suspend-x%d' % multitest['count']
5296 sysvals.outdir = datetime.now().strftime('suspend-'+s+'-%m%d%y-%H%M%S') 5516 outdir = datetime.now().strftime(s+'-%y%m%d-%H%M%S')
5297 if not os.path.isdir(sysvals.outdir): 5517 if not os.path.isdir(outdir):
5298 os.mkdir(sysvals.outdir) 5518 os.mkdir(outdir)
5299 for i in range(multitest['count']): 5519 for i in range(multitest['count']):
5300 if(i != 0): 5520 if(i != 0):
5301 print('Waiting %d seconds...' % (multitest['delay'])) 5521 print('Waiting %d seconds...' % (multitest['delay']))
5302 time.sleep(multitest['delay']) 5522 time.sleep(multitest['delay'])
5303 print('TEST (%d/%d) START' % (i+1, multitest['count'])) 5523 print('TEST (%d/%d) START' % (i+1, multitest['count']))
5304 runTest(sysvals.outdir) 5524 fmt = 'suspend-%y%m%d-%H%M%S'
5525 sysvals.testdir = os.path.join(outdir, datetime.now().strftime(fmt))
5526 runTest()
5305 print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count'])) 5527 print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count']))
5306 runSummary(sysvals.outdir, False) 5528 runSummary(outdir, False)
5307 else: 5529 else:
5530 if outdir:
5531 sysvals.testdir = outdir
5308 # run the test in the current directory 5532 # run the test in the current directory
5309 runTest('.', sysvals.outdir) 5533 runTest()