diff options
Diffstat (limited to 'tools/power/pm-graph/sleepgraph.py')
| -rwxr-xr-x | tools/power/pm-graph/sleepgraph.py | 618 | 
1 files changed, 342 insertions, 276 deletions
diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py index 4f46a7a1feb6..f7d1c1f62f86 100755 --- a/tools/power/pm-graph/sleepgraph.py +++ b/tools/power/pm-graph/sleepgraph.py @@ -1,9 +1,18 @@ -#!/usr/bin/python2 +#!/usr/bin/python  # SPDX-License-Identifier: GPL-2.0-only  #  # Tool for analyzing suspend/resume timing  # Copyright (c) 2013, Intel Corporation.  # +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +# more details. +#  # Authors:  #	 Todd Brandt <[email protected]>  # @@ -48,9 +57,10 @@ import string  import re  import platform  import signal +import codecs  from datetime import datetime  import struct -import ConfigParser +import configparser  import gzip  from threading import Thread  from subprocess import call, Popen, PIPE @@ -60,6 +70,9 @@ def pprint(msg):  	print(msg)  	sys.stdout.flush() +def ascii(text): +	return text.decode('ascii', 'ignore') +  # ----------------- CLASSES --------------------  # Class: SystemValues @@ -68,7 +81,7 @@ def pprint(msg):  #	 store system values and test parameters  class SystemValues:  	title = 'SleepGraph' -	version = '5.4' +	version = '5.5'  	ansi = False  	rs = 0  	display = '' @@ -78,7 +91,7 @@ class SystemValues:  	testlog = True  	dmesglog = True  	ftracelog = False -	tstat = False +	tstat = True  	mindevlen = 0.0  	mincglen = 0.0  	cgphase = '' @@ -147,6 +160,7 @@ class SystemValues:  	devdump = False  	mixedphaseheight = True  	devprops = dict() +	platinfo = []  	predelay = 0  	postdelay = 0  	pmdebug = '' @@ -323,13 +337,20 @@ class SystemValues:  			sys.exit(1)  		return False  	def getExec(self, cmd): -		dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', -			'/usr/local/sbin', '/usr/local/bin'] -		for path in dirlist: +		try: +			fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout +			out = ascii(fp.read()).strip() +			fp.close() +		except: +			out = '' +		if out: +			return out +		for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin', +			'/usr/local/sbin', '/usr/local/bin']:  			cmdfull = os.path.join(path, cmd)  			if os.path.exists(cmdfull):  				return cmdfull -		return '' +		return out  	def setPrecision(self, num):  		if num < 0 or num > 6:  			return @@ -455,7 +476,7 @@ class SystemValues:  		fp = Popen('dmesg', stdout=PIPE).stdout  		ktime = '0'  		for line in fp: -			line = line.replace('\r\n', '') +			line = ascii(line).replace('\r\n', '')  			idx = line.find('[')  			if idx > 1:  				line = line[idx:] @@ -469,7 +490,7 @@ class SystemValues:  		# store all new dmesg lines since initdmesg was called  		fp = Popen('dmesg', stdout=PIPE).stdout  		for line in fp: -			line = line.replace('\r\n', '') +			line = ascii(line).replace('\r\n', '')  			idx = line.find('[')  			if idx > 1:  				line = line[idx:] @@ -501,7 +522,7 @@ class SystemValues:  			call('cat '+self.tpath+'available_filter_functions', shell=True)  			return  		master = self.listFromFile(self.tpath+'available_filter_functions') -		for i in self.tracefuncs: +		for i in sorted(self.tracefuncs):  			if 'func' in self.tracefuncs[i]:  				i = self.tracefuncs[i]['func']  			if i in master: @@ -628,7 +649,7 @@ class SystemValues:  		self.fsetVal(kprobeevents, 'kprobe_events')  		if output:  			check = self.fgetVal('kprobe_events') -			linesack = (len(check.split('\n')) - 1) / 2 +			linesack = (len(check.split('\n')) - 1) // 2  			pprint('    kprobe functions enabled: %d/%d' % (linesack, linesout))  		self.fsetVal('1', 'events/kprobes/enable')  	def testKprobe(self, kname, kprobe): @@ -646,19 +667,19 @@ class SystemValues:  		if linesack < linesout:  			return False  		return True -	def setVal(self, val, file, mode='w'): +	def setVal(self, val, file):  		if not os.path.exists(file):  			return False  		try: -			fp = open(file, mode, 0) -			fp.write(val) +			fp = open(file, 'wb', 0) +			fp.write(val.encode())  			fp.flush()  			fp.close()  		except:  			return False  		return True -	def fsetVal(self, val, path, mode='w'): -		return self.setVal(val, self.tpath+path, mode) +	def fsetVal(self, val, path): +		return self.setVal(val, self.tpath+path)  	def getVal(self, file):  		res = ''  		if not os.path.exists(file): @@ -719,7 +740,7 @@ class SystemValues:  			tgtsize = min(self.memfree, bmax)  		else:  			tgtsize = 65536 -		while not self.fsetVal('%d' % (tgtsize / cpus), 'buffer_size_kb'): +		while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):  			# if the size failed to set, lower it and keep trying  			tgtsize -= 65536  			if tgtsize < 65536: @@ -863,14 +884,23 @@ class SystemValues:  		isgz = self.gzip  		if mode == 'r':  			try: -				with gzip.open(filename, mode+'b') as fp: +				with gzip.open(filename, mode+'t') as fp:  					test = fp.read(64)  				isgz = True  			except:  				isgz = False  		if isgz: -			return gzip.open(filename, mode+'b') +			return gzip.open(filename, mode+'t')  		return open(filename, mode) +	def b64unzip(self, data): +		try: +			out = codecs.decode(base64.b64decode(data), 'zlib').decode() +		except: +			out = data +		return out +	def b64zip(self, data): +		out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode() +		return out  	def mcelog(self, clear=False):  		cmd = self.getExec('mcelog')  		if not cmd: @@ -878,12 +908,124 @@ class SystemValues:  		if clear:  			call(cmd+' > /dev/null 2>&1', shell=True)  			return '' -		fp = Popen([cmd], stdout=PIPE, stderr=PIPE).stdout -		out = fp.read().strip() -		fp.close() +		try: +			fp = Popen([cmd], stdout=PIPE, stderr=PIPE).stdout +			out = ascii(fp.read()).strip() +			fp.close() +		except: +			return ''  		if not out:  			return '' -		return base64.b64encode(out.encode('zlib')) +		return self.b64zip(out) +	def platforminfo(self): +		# add platform info on to a completed ftrace file +		if not os.path.exists(self.ftracefile): +			return False +		footer = '#\n' + +		# add test command string line if need be +		if self.suspendmode == 'command' and self.testcommand: +			footer += '# platform-testcmd: %s\n' % (self.testcommand) + +		# get a list of target devices from the ftrace file +		props = dict() +		tp = TestProps() +		tf = self.openlog(self.ftracefile, 'r') +		for line in tf: +			# determine the trace data type (required for further parsing) +			m = re.match(tp.tracertypefmt, line) +			if(m): +				tp.setTracerType(m.group('t')) +				continue +			# parse only valid lines, if this is not one move on +			m = re.match(tp.ftrace_line_fmt, line) +			if(not m or 'device_pm_callback_start' not in line): +				continue +			m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg')); +			if(not m): +				continue +			dev = m.group('d') +			if dev not in props: +				props[dev] = DevProps() +		tf.close() + +		# now get the syspath for each target device +		for dirname, dirnames, filenames in os.walk('/sys/devices'): +			if(re.match('.*/power', dirname) and 'async' in filenames): +				dev = dirname.split('/')[-2] +				if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)): +					props[dev].syspath = dirname[:-6] + +		# now fill in the properties for our target devices +		for dev in sorted(props): +			dirname = props[dev].syspath +			if not dirname or not os.path.exists(dirname): +				continue +			with open(dirname+'/power/async') as fp: +				text = fp.read() +				props[dev].isasync = False +				if 'enabled' in text: +					props[dev].isasync = True +			fields = os.listdir(dirname) +			if 'product' in fields: +				with open(dirname+'/product', 'rb') as fp: +					props[dev].altname = ascii(fp.read()) +			elif 'name' in fields: +				with open(dirname+'/name', 'rb') as fp: +					props[dev].altname = ascii(fp.read()) +			elif 'model' in fields: +				with open(dirname+'/model', 'rb') as fp: +					props[dev].altname = ascii(fp.read()) +			elif 'description' in fields: +				with open(dirname+'/description', 'rb') as fp: +					props[dev].altname = ascii(fp.read()) +			elif 'id' in fields: +				with open(dirname+'/id', 'rb') as fp: +					props[dev].altname = ascii(fp.read()) +			elif 'idVendor' in fields and 'idProduct' in fields: +				idv, idp = '', '' +				with open(dirname+'/idVendor', 'rb') as fp: +					idv = ascii(fp.read()).strip() +				with open(dirname+'/idProduct', 'rb') as fp: +					idp = ascii(fp.read()).strip() +				props[dev].altname = '%s:%s' % (idv, idp) +			if props[dev].altname: +				out = props[dev].altname.strip().replace('\n', ' ')\ +					.replace(',', ' ').replace(';', ' ') +				props[dev].altname = out + +		# add a devinfo line to the bottom of ftrace +		out = '' +		for dev in sorted(props): +			out += props[dev].out(dev) +		footer += '# platform-devinfo: %s\n' % self.b64zip(out) + +		# add a line for each of these commands with their outputs +		cmds = [ +			['pcidevices', 'lspci', '-tv'], +			['interrupts', 'cat', '/proc/interrupts'], +			['gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/gpe*'], +		] +		for cargs in cmds: +			name = cargs[0] +			cmdline = ' '.join(cargs[1:]) +			cmdpath = self.getExec(cargs[1]) +			if not cmdpath: +				continue +			cmd = [cmdpath] + cargs[2:] +			try: +				fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout +				info = ascii(fp.read()).strip() +				fp.close() +			except: +				continue +			if not info: +				continue +			footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info)) + +		with self.openlog(self.ftracefile, 'a') as fp: +			fp.write(footer) +		return True  	def haveTurbostat(self):  		if not self.tstat:  			return False @@ -891,31 +1033,40 @@ class SystemValues:  		if not cmd:  			return False  		fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr -		out = fp.read().strip() +		out = ascii(fp.read()).strip()  		fp.close() -		return re.match('turbostat version [0-9\.]* .*', out) +		if re.match('turbostat version [0-9\.]* .*', out): +			sysvals.vprint(out) +			return True +		return False  	def turbostat(self):  		cmd = self.getExec('turbostat') -		if not cmd: -			return 'missing turbostat executable' -		text = [] +		rawout = keyline = valline = ''  		fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)  		fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr  		for line in fp: -			if re.match('[0-9.]* sec', line): +			line = ascii(line) +			rawout += line +			if keyline and valline:  				continue -			text.append(line.split()) +			if re.match('(?i)Avg_MHz.*', line): +				keyline = line.strip().split() +			elif keyline: +				valline = line.strip().split()  		fp.close() -		if len(text) < 2: -			return 'turbostat output format error' +		if not keyline or not valline or len(keyline) != len(valline): +			errmsg = 'unrecognized turbostat output:\n'+rawout.strip() +			sysvals.vprint(errmsg) +			if not sysvals.verbose: +				pprint(errmsg) +			return '' +		if sysvals.verbose: +			pprint(rawout.strip())  		out = [] -		for key in text[0]: -			values = [] -			idx = text[0].index(key) -			for line in text[1:]: -				if len(line) > idx: -					values.append(line[idx]) -			out.append('%s=%s' % (key, ','.join(values))) +		for key in keyline: +			idx = keyline.index(key) +			val = valline[idx] +			out.append('%s=%s' % (key, val))  		return '|'.join(out)  	def checkWifi(self):  		out = dict() @@ -924,7 +1075,7 @@ class SystemValues:  			return out  		fp = Popen(iwcmd, stdout=PIPE, stderr=PIPE).stdout  		for line in fp: -			m = re.match('(?P<dev>\S*) .* ESSID:(?P<ess>\S*)', line) +			m = re.match('(?P<dev>\S*) .* ESSID:(?P<ess>\S*)', ascii(line))  			if not m:  				continue  			out['device'] = m.group('dev') @@ -935,7 +1086,7 @@ class SystemValues:  		if 'device' in out:  			fp = Popen([ifcmd, out['device']], stdout=PIPE, stderr=PIPE).stdout  			for line in fp: -				m = re.match('.* inet (?P<ip>[0-9\.]*)', line) +				m = re.match('.* inet (?P<ip>[0-9\.]*)', ascii(line))  				if m:  					out['ip'] = m.group('ip')  					break @@ -990,13 +1141,13 @@ class DevProps:  	def __init__(self):  		self.syspath = ''  		self.altname = '' -		self.async = True +		self.isasync = True  		self.xtraclass = ''  		self.xtrainfo = ''  	def out(self, dev): -		return '%s,%s,%d;' % (dev, self.altname, self.async) +		return '%s,%s,%d;' % (dev, self.altname, self.isasync)  	def debug(self, dev): -		pprint('%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.async)) +		pprint('%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.isasync))  	def altName(self, dev):  		if not self.altname or self.altname == dev:  			return dev @@ -1004,13 +1155,13 @@ class DevProps:  	def xtraClass(self):  		if self.xtraclass:  			return ' '+self.xtraclass -		if not self.async: +		if not self.isasync:  			return ' sync'  		return ''  	def xtraInfo(self):  		if self.xtraclass:  			return ' '+self.xtraclass -		if self.async: +		if self.isasync:  			return ' async_device'  		return ' sync_device' @@ -1108,7 +1259,7 @@ class Data:  		return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])  	def initDevicegroups(self):  		# called when phases are all finished being added -		for phase in self.dmesg.keys(): +		for phase in sorted(self.dmesg.keys()):  			if '*' in phase:  				p = phase.split('*')  				pnew = '%s%d' % (p[0], len(p)) @@ -1430,16 +1581,7 @@ class Data:  		return phase  	def sortedDevices(self, phase):  		list = self.dmesg[phase]['list'] -		slist = [] -		tmp = dict() -		for devname in list: -			dev = list[devname] -			if dev['length'] == 0: -				continue -			tmp[dev['start']] = devname -		for t in sorted(tmp): -			slist.append(tmp[t]) -		return slist +		return sorted(list, key=lambda k:list[k]['start'])  	def fixupInitcalls(self, phase):  		# if any calls never returned, clip them at system resume end  		phaselist = self.dmesg[phase]['list'] @@ -1576,7 +1718,7 @@ class Data:  				maxname = '%d' % self.maxDeviceNameSize(phase)  				fmt = '%3d) %'+maxname+'s - %f - %f'  				c = 1 -				for name in devlist: +				for name in sorted(devlist):  					s = devlist[name]['start']  					e = devlist[name]['end']  					sysvals.vprint(fmt % (c, name, s, e)) @@ -1588,7 +1730,7 @@ class Data:  		devlist = []  		for phase in self.sortedPhases():  			list = self.deviceChildren(devname, phase) -			for dev in list: +			for dev in sorted(list):  				if dev not in devlist:  					devlist.append(dev)  		return devlist @@ -1628,16 +1770,16 @@ class Data:  	def rootDeviceList(self):  		# list of devices graphed  		real = [] -		for phase in self.dmesg: +		for phase in self.sortedPhases():  			list = self.dmesg[phase]['list'] -			for dev in list: +			for dev in sorted(list):  				if list[dev]['pid'] >= 0 and dev not in real:  					real.append(dev)  		# list of top-most root devices  		rootlist = [] -		for phase in self.dmesg: +		for phase in self.sortedPhases():  			list = self.dmesg[phase]['list'] -			for dev in list: +			for dev in sorted(list):  				pdev = list[dev]['par']  				pid = list[dev]['pid']  				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)): @@ -1718,9 +1860,9 @@ class Data:  	def createProcessUsageEvents(self):  		# get an array of process names  		proclist = [] -		for t in self.pstl: +		for t in sorted(self.pstl):  			pslist = self.pstl[t] -			for ps in pslist: +			for ps in sorted(pslist):  				if ps not in proclist:  					proclist.append(ps)  		# get a list of data points for suspend and resume @@ -1765,7 +1907,7 @@ class Data:  	def debugPrint(self):  		for p in self.sortedPhases():  			list = self.dmesg[p]['list'] -			for devname in list: +			for devname in sorted(list):  				dev = list[devname]  				if 'ftrace' in dev:  					dev['ftrace'].debugPrint(' [%s]' % devname) @@ -2466,7 +2608,7 @@ class Timeline:  		# if there is 1 line per row, draw them the standard way  		for t, p in standardphases:  			for i in sorted(self.rowheight[t][p]): -				self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p]) +				self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])  	def createZoomBox(self, mode='command', testcount=1):  		# Create bounding box, add buttons  		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n' @@ -2537,6 +2679,7 @@ class TestProps:  	cmdlinefmt = '^# command \| (?P<cmd>.*)'  	kparamsfmt = '^# kparams \| (?P<kp>.*)'  	devpropfmt = '# Device Properties: .*' +	pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'  	tracertypefmt = '# tracer: (?P<t>.*)'  	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'  	procexecfmt = 'ps - (?P<ps>.*)$' @@ -2571,12 +2714,6 @@ class TestProps:  			self.ftrace_line_fmt = self.ftrace_line_fmt_nop  		else:  			doError('Invalid tracer format: [%s]' % tracer) -	def decode(self, data): -		try: -			out = base64.b64decode(data).decode('zlib') -		except: -			out = data -		return out  	def stampInfo(self, line):  		if re.match(self.stampfmt, line):  			self.stamp = line @@ -2660,7 +2797,7 @@ class TestProps:  		if len(self.mcelog) > data.testnumber:  			m = re.match(self.mcelogfmt, self.mcelog[data.testnumber])  			if m: -				data.mcelog = self.decode(m.group('m')) +				data.mcelog = sv.b64unzip(m.group('m'))  		# turbostat data  		if len(self.turbostat) > data.testnumber:  			m = re.match(self.tstatfmt, self.turbostat[data.testnumber]) @@ -2681,6 +2818,46 @@ class TestProps:  			m = re.match(self.testerrfmt, self.testerror[data.testnumber])  			if m:  				data.enterfail = m.group('e') +	def devprops(self, data): +		props = dict() +		devlist = data.split(';') +		for dev in devlist: +			f = dev.split(',') +			if len(f) < 3: +				continue +			dev = f[0] +			props[dev] = DevProps() +			props[dev].altname = f[1] +			if int(f[2]): +				props[dev].isasync = True +			else: +				props[dev].isasync = False +		return props +	def parseDevprops(self, line, sv): +		idx = line.index(': ') + 2 +		if idx >= len(line): +			return +		props = self.devprops(line[idx:]) +		if sv.suspendmode == 'command' and 'testcommandstring' in props: +			sv.testcommand = props['testcommandstring'].altname +		sv.devprops = props +	def parsePlatformInfo(self, line, sv): +		m = re.match(self.pinfofmt, line) +		if not m: +			return +		name, info = m.group('val'), m.group('info') +		if name == 'devinfo': +			sv.devprops = self.devprops(sv.b64unzip(info)) +			return +		elif name == 'testcmd': +			sv.testcommand = info +			return +		field = info.split('|') +		if len(field) < 2: +			return +		cmdline = field[0].strip() +		output = sv.b64unzip(field[1].strip()) +		sv.platinfo.append([name, cmdline, output])  # Class: TestRun  # Description: @@ -2701,7 +2878,7 @@ class ProcessMonitor:  		process = Popen(c, shell=True, stdout=PIPE)  		running = dict()  		for line in process.stdout: -			data = line.split() +			data = ascii(line).split()  			pid = data[0]  			name = re.sub('[()]', '', data[1])  			user = int(data[13]) @@ -2805,7 +2982,11 @@ def appendIncompleteTraceLog(testruns):  			continue  		# device properties line  		if(re.match(tp.devpropfmt, line)): -			devProps(line) +			tp.parseDevprops(line, sysvals) +			continue +		# platform info line +		if(re.match(tp.pinfofmt, line)): +			tp.parsePlatformInfo(line, sysvals)  			continue  		# parse only valid lines, if this is not one move on  		m = re.match(tp.ftrace_line_fmt, line) @@ -2902,7 +3083,7 @@ def parseTraceLog(live=False):  		sysvals.setupAllKprobes()  	ksuscalls = ['pm_prepare_console']  	krescalls = ['pm_restore_console'] -	tracewatch = [] +	tracewatch = ['irq_wakeup']  	if sysvals.usekprobes:  		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',  			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', @@ -2928,7 +3109,11 @@ def parseTraceLog(live=False):  			continue  		# device properties line  		if(re.match(tp.devpropfmt, line)): -			devProps(line) +			tp.parseDevprops(line, sysvals) +			continue +		# platform info line +		if(re.match(tp.pinfofmt, line)): +			tp.parsePlatformInfo(line, sysvals)  			continue  		# ignore all other commented lines  		if line[0] == '#': @@ -3001,16 +3186,11 @@ def parseTraceLog(live=False):  					isbegin = False  				else:  					continue -				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name) -				if(m): -					val = m.group('val') -					if val == '0': -						name = m.group('name') -					else: -						name = m.group('name')+'['+val+']' +				if '[' in t.name: +					m = re.match('(?P<name>.*)\[.*', t.name)  				else:  					m = re.match('(?P<name>.*) .*', t.name) -					name = m.group('name') +				name = m.group('name')  				# ignore these events  				if(name.split('[')[0] in tracewatch):  					continue @@ -3045,6 +3225,8 @@ def parseTraceLog(live=False):  				elif(re.match('machine_suspend\[.*', t.name)):  					if(isbegin):  						lp = data.lastPhase() +						if lp == 'resume_machine': +							data.dmesg[lp]['end'] = t.time  						phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)  						data.setPhase(phase, t.time, False)  						if data.tSuspended == 0: @@ -3213,11 +3395,11 @@ def parseTraceLog(live=False):  		# add the traceevent data to the device hierarchy  		if(sysvals.usetraceevents):  			# add actual trace funcs -			for name in test.ttemp: +			for name in sorted(test.ttemp):  				for event in test.ttemp[name]:  					data.newActionGlobal(name, event['begin'], event['end'], event['pid'])  			# add the kprobe based virtual tracefuncs as actual devices -			for key in tp.ktemp: +			for key in sorted(tp.ktemp):  				name, pid = key  				if name not in sysvals.tracefuncs:  					continue @@ -3231,7 +3413,7 @@ def parseTraceLog(live=False):  					data.newActionGlobal(e['name'], kb, ke, pid, color)  			# add config base kprobes and dev kprobes  			if sysvals.usedevsrc: -				for key in tp.ktemp: +				for key in sorted(tp.ktemp):  					name, pid = key  					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:  						continue @@ -3244,7 +3426,7 @@ def parseTraceLog(live=False):  		if sysvals.usecallgraph:  			# add the callgraph data to the device hierarchy  			sortlist = dict() -			for key in test.ftemp: +			for key in sorted(test.ftemp):  				proc, pid = key  				for cg in test.ftemp[key]:  					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0): @@ -3582,7 +3764,7 @@ def parseKernelLog(data):  		# if trace events are not available, these are better than nothing  		if(not sysvals.usetraceevents):  			# look for known actions -			for a in at: +			for a in sorted(at):  				if(re.match(at[a]['smsg'], msg)):  					if(a not in actions):  						actions[a] = [] @@ -3641,7 +3823,7 @@ def parseKernelLog(data):  		data.tResumed = data.tSuspended  	# fill in any actions we've found -	for name in actions: +	for name in sorted(actions):  		for event in actions[name]:  			data.newActionGlobal(name, event['begin'], event['end']) @@ -3761,7 +3943,7 @@ def createHTMLSummarySimple(testruns, htmlfile, title):  		if lastmode and lastmode != mode and num > 0:  			for i in range(2):  				s = sorted(tMed[i]) -				list[lastmode]['med'][i] = s[int(len(s)/2)] +				list[lastmode]['med'][i] = s[int(len(s)//2)]  				iMed[i] = tMed[i][list[lastmode]['med'][i]]  			list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]  			list[lastmode]['min'] = tMin @@ -3803,7 +3985,7 @@ def createHTMLSummarySimple(testruns, htmlfile, title):  	if lastmode and num > 0:  		for i in range(2):  			s = sorted(tMed[i]) -			list[lastmode]['med'][i] = s[int(len(s)/2)] +			list[lastmode]['med'][i] = s[int(len(s)//2)]  			iMed[i] = tMed[i][list[lastmode]['med'][i]]  		list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]  		list[lastmode]['min'] = tMin @@ -3845,7 +4027,7 @@ def createHTMLSummarySimple(testruns, htmlfile, title):  		'</tr>\n'  	headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\  		colspan+'></td></tr>\n' -	for mode in list: +	for mode in sorted(list):  		# header line for each suspend mode  		num = 0  		tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\ @@ -3944,7 +4126,8 @@ def createHTMLDeviceSummary(testruns, htmlfile, title):  			th.format('Average Time') + th.format('Count') +\  			th.format('Worst Time') + th.format('Host (worst time)') +\  			th.format('Link (worst time)') + '</tr>\n' -		for name in sorted(devlist, key=lambda k:devlist[k]['worst'], reverse=True): +		for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \ +			devlist[k]['total'], devlist[k]['name']), reverse=True):  			data = devall[type][name]  			data['average'] = data['total'] / data['count']  			if data['average'] < limit: @@ -4085,7 +4268,7 @@ def createHTML(testruns, testfail):  		if(tTotal == 0):  			doError('No timeline data')  		if(len(data.tLow) > 0): -			low_time = '|'.join(data.tLow) +			low_time = '+'.join(data.tLow)  		if sysvals.suspendmode == 'command':  			run_time = '%.0f'%((data.end-data.start)*1000)  			if sysvals.testcommand: @@ -4151,7 +4334,7 @@ def createHTML(testruns, testfail):  		for group in data.devicegroups:  			devlist = []  			for phase in group: -				for devname in data.tdevlist[phase]: +				for devname in sorted(data.tdevlist[phase]):  					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])  					devlist.append(d)  					if d.isa('kth'): @@ -4230,7 +4413,7 @@ def createHTML(testruns, testfail):  			for b in phases[dir]:  				# draw the devices for this phase  				phaselist = data.dmesg[b]['list'] -				for d in data.tdevlist[b]: +				for d in sorted(data.tdevlist[b]):  					name = d  					drv = ''  					dev = phaselist[d] @@ -4971,13 +5154,9 @@ def executeSuspend():  			if mode == 'freeze' and sysvals.haveTurbostat():  				# execution will pause here  				turbo = sysvals.turbostat() -				if '|' in turbo: +				if turbo:  					tdata['turbo'] = turbo -				else: -					tdata['error'] = turbo  			else: -				if sysvals.haveTurbostat(): -					sysvals.vprint('WARNING: ignoring turbostat in mode "%s"' % mode)  				pf = open(sysvals.powerfile, 'w')  				pf.write(mode)  				# execution will pause here @@ -5024,7 +5203,7 @@ def executeSuspend():  			op.write(line)  		op.close()  		sysvals.fsetVal('', 'trace') -		devProps() +		sysvals.platforminfo()  	return testdata  def readFile(file): @@ -5040,9 +5219,9 @@ def readFile(file):  #	 The time string, e.g. "1901m16s"  def ms2nice(val):  	val = int(val) -	h = val / 3600000 -	m = (val / 60000) % 60 -	s = (val / 1000) % 60 +	h = val // 3600000 +	m = (val // 60000) % 60 +	s = (val // 1000) % 60  	if h > 0:  		return '%d:%02d:%02d' % (h, m, s)  	if m > 0: @@ -5115,127 +5294,6 @@ def deviceInfo(output=''):  		print(lines[i])  	return res -# Function: devProps -# Description: -#	 Retrieve a list of properties for all devices in the trace log -def devProps(data=0): -	props = dict() - -	if data: -		idx = data.index(': ') + 2 -		if idx >= len(data): -			return -		devlist = data[idx:].split(';') -		for dev in devlist: -			f = dev.split(',') -			if len(f) < 3: -				continue -			dev = f[0] -			props[dev] = DevProps() -			props[dev].altname = f[1] -			if int(f[2]): -				props[dev].async = True -			else: -				props[dev].async = False -			sysvals.devprops = props -		if sysvals.suspendmode == 'command' and 'testcommandstring' in props: -			sysvals.testcommand = props['testcommandstring'].altname -		return - -	if(os.path.exists(sysvals.ftracefile) == False): -		doError('%s does not exist' % sysvals.ftracefile) - -	# first get the list of devices we need properties for -	msghead = 'Additional data added by AnalyzeSuspend' -	alreadystamped = False -	tp = TestProps() -	tf = sysvals.openlog(sysvals.ftracefile, 'r') -	for line in tf: -		if msghead in line: -			alreadystamped = True -			continue -		# determine the trace data type (required for further parsing) -		m = re.match(tp.tracertypefmt, line) -		if(m): -			tp.setTracerType(m.group('t')) -			continue -		# parse only valid lines, if this is not one move on -		m = re.match(tp.ftrace_line_fmt, line) -		if(not m or 'device_pm_callback_start' not in line): -			continue -		m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg')); -		if(not m): -			continue -		dev = m.group('d') -		if dev not in props: -			props[dev] = DevProps() -	tf.close() - -	if not alreadystamped and sysvals.suspendmode == 'command': -		out = '#\n# '+msghead+'\n# Device Properties: ' -		out += 'testcommandstring,%s,0;' % (sysvals.testcommand) -		with sysvals.openlog(sysvals.ftracefile, 'a') as fp: -			fp.write(out+'\n') -		sysvals.devprops = props -		return - -	# now get the syspath for each of our target devices -	for dirname, dirnames, filenames in os.walk('/sys/devices'): -		if(re.match('.*/power', dirname) and 'async' in filenames): -			dev = dirname.split('/')[-2] -			if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)): -				props[dev].syspath = dirname[:-6] - -	# now fill in the properties for our target devices -	for dev in props: -		dirname = props[dev].syspath -		if not dirname or not os.path.exists(dirname): -			continue -		with open(dirname+'/power/async') as fp: -			text = fp.read() -			props[dev].async = False -			if 'enabled' in text: -				props[dev].async = True -		fields = os.listdir(dirname) -		if 'product' in fields: -			with open(dirname+'/product') as fp: -				props[dev].altname = fp.read() -		elif 'name' in fields: -			with open(dirname+'/name') as fp: -				props[dev].altname = fp.read() -		elif 'model' in fields: -			with open(dirname+'/model') as fp: -				props[dev].altname = fp.read() -		elif 'description' in fields: -			with open(dirname+'/description') as fp: -				props[dev].altname = fp.read() -		elif 'id' in fields: -			with open(dirname+'/id') as fp: -				props[dev].altname = fp.read() -		elif 'idVendor' in fields and 'idProduct' in fields: -			idv, idp = '', '' -			with open(dirname+'/idVendor') as fp: -				idv = fp.read().strip() -			with open(dirname+'/idProduct') as fp: -				idp = fp.read().strip() -			props[dev].altname = '%s:%s' % (idv, idp) - -		if props[dev].altname: -			out = props[dev].altname.strip().replace('\n', ' ') -			out = out.replace(',', ' ') -			out = out.replace(';', ' ') -			props[dev].altname = out - -	# and now write the data to the ftrace file -	if not alreadystamped: -		out = '#\n# '+msghead+'\n# Device Properties: ' -		for dev in sorted(props): -			out += props[dev].out(dev) -		with sysvals.openlog(sysvals.ftracefile, 'a') as fp: -			fp.write(out+'\n') - -	sysvals.devprops = props -  # Function: getModes  # Description:  #	 Determine the supported power modes on this system @@ -5339,11 +5397,11 @@ def dmidecode(mempath, fatal=False):  	# search for either an SM table or DMI table  	i = base = length = num = 0  	while(i < memsize): -		if buf[i:i+4] == '_SM_' and i < memsize - 16: +		if buf[i:i+4] == b'_SM_' and i < memsize - 16:  			length = struct.unpack('H', buf[i+22:i+24])[0]  			base, num = struct.unpack('IH', buf[i+24:i+30])  			break -		elif buf[i:i+5] == '_DMI_': +		elif buf[i:i+5] == b'_DMI_':  			length = struct.unpack('H', buf[i+6:i+8])[0]  			base, num = struct.unpack('IH', buf[i+8:i+14])  			break @@ -5376,15 +5434,15 @@ def dmidecode(mempath, fatal=False):  			if 0 == struct.unpack('H', buf[n:n+2])[0]:  				break  			n += 1 -		data = buf[i+size:n+2].split('\0') +		data = buf[i+size:n+2].split(b'\0')  		for name in info:  			itype, idxadr = info[name]  			if itype == type: -				idx = struct.unpack('B', buf[i+idxadr])[0] +				idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]  				if idx > 0 and idx < len(data) - 1: -					s = data[idx-1].strip() -					if s and s.lower() != 'to be filled by o.e.m.': -						out[name] = data[idx-1] +					s = data[idx-1].decode('utf-8') +					if s.strip() and s.strip().lower() != 'to be filled by o.e.m.': +						out[name] = s  		i = n + 2  		count += 1  	return out @@ -5409,7 +5467,7 @@ def getBattery():  	return (ac, charge)  def displayControl(cmd): -	xset, ret = 'xset -d :0.0 {0}', 0 +	xset, ret = 'timeout 10 xset -d :0.0 {0}', 0  	if sysvals.sudouser:  		xset = 'sudo -u %s %s' % (sysvals.sudouser, xset)  	if cmd == 'init': @@ -5433,7 +5491,7 @@ def displayControl(cmd):  		fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout  		ret = 'unknown'  		for line in fp: -			m = re.match('[\s]*Monitor is (?P<m>.*)', line) +			m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))  			if(m and len(m.group('m')) >= 2):  				out = m.group('m').lower()  				ret = out[3:] if out[0:2] == 'in' else out @@ -5495,10 +5553,11 @@ def getFPDT(output):  		'               OEM Revision : %u\n'\  		'                 Creator ID : %s\n'\  		'           Creator Revision : 0x%x\n'\ -		'' % (table[0], table[0], table[1], table[2], table[3], -			table[4], table[5], table[6], table[7], table[8])) +		'' % (ascii(table[0]), ascii(table[0]), table[1], table[2], +			table[3], ascii(table[4]), ascii(table[5]), table[6], +			ascii(table[7]), table[8])) -	if(table[0] != 'FPDT'): +	if(table[0] != b'FPDT'):  		if(output):  			doError('Invalid FPDT table')  		return False @@ -5530,8 +5589,8 @@ def getFPDT(output):  			return [0, 0]  		rechead = struct.unpack('4sI', first)  		recdata = fp.read(rechead[1]-8) -		if(rechead[0] == 'FBPT'): -			record = struct.unpack('HBBIQQQQQ', recdata) +		if(rechead[0] == b'FBPT'): +			record = struct.unpack('HBBIQQQQQ', recdata[:48])  			if(output):  				pprint('%s (%s)\n'\  				'                  Reset END : %u ns\n'\ @@ -5539,11 +5598,11 @@ def getFPDT(output):  				' OS Loader StartImage Start : %u ns\n'\  				'     ExitBootServices Entry : %u ns\n'\  				'      ExitBootServices Exit : %u ns'\ -				'' % (rectype[header[0]], rechead[0], record[4], record[5], +				'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],  					record[6], record[7], record[8])) -		elif(rechead[0] == 'S3PT'): +		elif(rechead[0] == b'S3PT'):  			if(output): -				pprint('%s (%s)' % (rectype[header[0]], rechead[0])) +				pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))  			j = 0  			while(j < len(recdata)):  				prechead = struct.unpack('HBB', recdata[j:j+4]) @@ -5689,7 +5748,7 @@ def doError(msg, help=False):  def getArgInt(name, args, min, max, main=True):  	if main:  		try: -			arg = args.next() +			arg = next(args)  		except:  			doError(name+': no argument supplied', True)  	else: @@ -5708,7 +5767,7 @@ def getArgInt(name, args, min, max, main=True):  def getArgFloat(name, args, min, max, main=True):  	if main:  		try: -			arg = args.next() +			arg = next(args)  		except:  			doError(name+': no argument supplied', True)  	else: @@ -5737,9 +5796,12 @@ def processData(live=False):  			parseKernelLog(data)  		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):  			appendIncompleteTraceLog(testruns) +	shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr', +			'memsz', 'mode', 'numcpu', 'plat', 'time']  	sysvals.vprint('System Info:')  	for key in sorted(sysvals.stamp): -		sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key])) +		if key in shown: +			sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key]))  	if sysvals.kparams:  		sysvals.vprint('Kparams:\n    %s' % sysvals.kparams)  	sysvals.vprint('Command:\n    %s' % sysvals.cmdline) @@ -5768,6 +5830,12 @@ def processData(live=False):  				(w[0], w[1])  			sysvals.vprint(s)  		data.printDetails() +		if len(sysvals.platinfo) > 0: +			sysvals.vprint('\nPlatform Info:') +			for info in sysvals.platinfo: +				sysvals.vprint(info[0]+' - '+info[1]) +				sysvals.vprint(info[2]) +			sysvals.vprint('')  	if sysvals.cgdump:  		for data in testruns:  			data.debugPrint() @@ -5951,7 +6019,7 @@ def data_from_html(file, outpath, issues, fulldetail=False):  		worst[d] = {'name':'', 'time': 0.0}  		dev = devices[d] if d in devices else 0  		if dev and len(dev.keys()) > 0: -			n = sorted(dev, key=dev.get, reverse=True)[0] +			n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]  			worst[d]['name'], worst[d]['time'] = n, dev[n]  	data = {  		'mode': stmp[2], @@ -5976,7 +6044,7 @@ def data_from_html(file, outpath, issues, fulldetail=False):  		data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)  	return data -def genHtml(subdir): +def genHtml(subdir, force=False):  	for dirname, dirnames, filenames in os.walk(subdir):  		sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''  		for filename in filenames: @@ -5986,7 +6054,7 @@ def genHtml(subdir):  				sysvals.ftracefile = os.path.join(dirname, filename)  		sysvals.setOutputFile()  		if sysvals.ftracefile and sysvals.htmlfile and \ -			not os.path.exists(sysvals.htmlfile): +			(force or not os.path.exists(sysvals.htmlfile)):  			pprint('FTRACE: %s' % sysvals.ftracefile)  			if sysvals.dmesgfile:  				pprint('DMESG : %s' % sysvals.dmesgfile) @@ -6042,7 +6110,7 @@ def checkArgBool(name, value):  # Description:  #	 Configure the script via the info in a config file  def configFromFile(file): -	Config = ConfigParser.ConfigParser() +	Config = configparser.ConfigParser()  	Config.read(file)  	sections = Config.sections() @@ -6270,7 +6338,7 @@ def printHelp():  	'                default: suspend-{date}-{time}\n'\  	'   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\  	'   -addlogs     Add the dmesg and ftrace logs to the html output\n'\ -	'   -turbostat   Use turbostat to execute the command in freeze mode (default: disabled)\n'\ +	'   -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\  	'   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)\n'\  	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\  	'   -result fn   Export a results table to a text file for parsing.\n'\ @@ -6340,7 +6408,7 @@ if __name__ == '__main__':  	for arg in args:  		if(arg == '-m'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No mode supplied', True)  			if val == 'command' and not sysvals.testcommand: @@ -6384,10 +6452,8 @@ if __name__ == '__main__':  			sysvals.dmesglog = True  		elif(arg == '-addlogftrace'):  			sysvals.ftracelog = True -		elif(arg == '-turbostat'): -			sysvals.tstat = True -			if not sysvals.haveTurbostat(): -				doError('Turbostat command not found') +		elif(arg == '-noturbostat'): +			sysvals.tstat = False  		elif(arg == '-verbose'):  			sysvals.verbose = True  		elif(arg == '-proc'): @@ -6400,7 +6466,7 @@ if __name__ == '__main__':  			sysvals.gzip = True  		elif(arg == '-rs'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('-rs requires "enable" or "disable"', True)  			if val.lower() in switchvalues: @@ -6412,7 +6478,7 @@ if __name__ == '__main__':  				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)  		elif(arg == '-display'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('-display requires an mode value', True)  			disopt = ['on', 'off', 'standby', 'suspend'] @@ -6423,7 +6489,7 @@ if __name__ == '__main__':  			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)  		elif(arg == '-rtcwake'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No rtcwake time supplied', True)  			if val.lower() in switchoff: @@ -6443,7 +6509,7 @@ if __name__ == '__main__':  			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)  		elif(arg == '-cgphase'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No phase name supplied', True)  			d = Data(0) @@ -6453,19 +6519,19 @@ if __name__ == '__main__':  			sysvals.cgphase = val  		elif(arg == '-cgfilter'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No callgraph functions supplied', True)  			sysvals.setCallgraphFilter(val)  		elif(arg == '-skipkprobe'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No kprobe functions supplied', True)  			sysvals.skipKprobes(val)  		elif(arg == '-cgskip'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No file supplied', True)  			if val.lower() in switchoff: @@ -6480,7 +6546,7 @@ if __name__ == '__main__':  			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)  		elif(arg == '-cmd'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No command string supplied', True)  			sysvals.testcommand = val @@ -6495,13 +6561,13 @@ if __name__ == '__main__':  			sysvals.multitest['delay'] = getArgInt('-multi n d (delay between tests)', args, 0, 3600)  		elif(arg == '-o'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No subdirectory name supplied', True)  			sysvals.outdir = sysvals.setOutputFolder(val)  		elif(arg == '-config'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No text file supplied', True)  			file = sysvals.configFile(val) @@ -6510,7 +6576,7 @@ if __name__ == '__main__':  			configFromFile(file)  		elif(arg == '-fadd'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No text file supplied', True)  			file = sysvals.configFile(val) @@ -6519,7 +6585,7 @@ if __name__ == '__main__':  			sysvals.addFtraceFilterFunctions(file)  		elif(arg == '-dmesg'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No dmesg file supplied', True)  			sysvals.notestrun = True @@ -6528,7 +6594,7 @@ if __name__ == '__main__':  				doError('%s does not exist' % sysvals.dmesgfile)  		elif(arg == '-ftrace'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No ftrace file supplied', True)  			sysvals.notestrun = True @@ -6537,7 +6603,7 @@ if __name__ == '__main__':  				doError('%s does not exist' % sysvals.ftracefile)  		elif(arg == '-summary'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No directory supplied', True)  			cmd = 'summary' @@ -6547,13 +6613,13 @@ if __name__ == '__main__':  				doError('%s is not accesible' % val)  		elif(arg == '-filter'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No devnames supplied', True)  			sysvals.setDeviceFilter(val)  		elif(arg == '-result'):  			try: -				val = args.next() +				val = next(args)  			except:  				doError('No result file supplied', True)  			sysvals.result = val  |