4898 lines · 157.1 KB · Python
RawDownload
#!/usr/bin/python## 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.## You should have received a copy of the GNU General Public License along with# this program; if not, write to the Free Software Foundation, Inc.,# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.## Authors:#	 Todd Brandt <todd.e.brandt@linux.intel.com>## Links:#	 Home Page#	   https://01.org/suspendresume#	 Source repo#	   https://github.com/01org/suspendresume#	 Documentation#	   Getting Started#	     https://01.org/suspendresume/documentation/getting-started#	   Command List:#	     https://01.org/suspendresume/documentation/command-list## Description:#	 This tool is designed to assist kernel and OS developers in optimizing#	 their linux stack's suspend/resume time. Using a kernel image built#	 with a few extra options enabled, the tool will execute a suspend and#	 will capture dmesg and ftrace data until resume is complete. This data#	 is transformed into a device timeline and a callgraph to give a quick#	 and detailed view of which devices and callbacks are taking the most#	 time in suspend/resume. The output is a single html file which can be#	 viewed in firefox or chrome.##	 The following kernel build options are required:#		 CONFIG_PM_DEBUG=y#		 CONFIG_PM_SLEEP_DEBUG=y#		 CONFIG_FTRACE=y#		 CONFIG_FUNCTION_TRACER=y#		 CONFIG_FUNCTION_GRAPH_TRACER=y#		 CONFIG_KPROBES=y#		 CONFIG_KPROBES_ON_FTRACE=y##	 For kernel versions older than 3.15:#	 The following additional kernel parameters are required:#		 (e.g. in file /etc/default/grub)#		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."# # ----------------- LIBRARIES -------------------- import sysimport timeimport osimport stringimport reimport platformfrom datetime import datetimeimport structimport ConfigParser # ----------------- CLASSES -------------------- # Class: SystemValues# Description:#	 A global, single-instance container used to#	 store system values and test parametersclass SystemValues:	ansi = False	version = '4.2'	verbose = False	addlogs = False	mindevlen = 0.001	mincglen = 1.0	srgap = 0	cgexp = False	outdir = ''	testdir = '.'	tpath = '/sys/kernel/debug/tracing/'	fpdtpath = '/sys/firmware/acpi/tables/FPDT'	epath = '/sys/kernel/debug/tracing/events/power/'	traceevents = [		'suspend_resume',		'device_pm_callback_end',		'device_pm_callback_start'	]	testcommand = ''	mempath = '/dev/mem'	powerfile = '/sys/power/state'	suspendmode = 'mem'	hostname = 'localhost'	prefix = 'test'	teststamp = ''	dmesgstart = 0.0	dmesgfile = ''	ftracefile = ''	htmlfile = ''	embedded = False	rtcwake = False	rtcwaketime = 10	rtcpath = ''	devicefilter = []	stamp = 0	execcount = 1	x2delay = 0	usecallgraph = False	usetraceevents = False	usetraceeventsonly = False	usetracemarkers = True	usekprobes = True	usedevsrc = False	notestrun = False	devprops = dict()	postresumetime = 0	devpropfmt = '# Device Properties: .*'	tracertypefmt = '# tracer: (?P<t>.*)'	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'	postresumefmt = '# post resume time (?P<t>[0-9]*)$'	stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'	kprobecolor = 'rgba(204,204,204,0.5)'	synccolor = 'rgba(204,204,204,0.5)'	debugfuncs = []	tracefuncs = {		'sys_sync': dict(),		'pm_prepare_console': dict(),		'pm_notifier_call_chain': dict(),		'freeze_processes': dict(),		'freeze_kernel_threads': dict(),		'pm_restrict_gfp_mask': dict(),		'acpi_suspend_begin': dict(),		'suspend_console': dict(),		'acpi_pm_prepare': dict(),		'syscore_suspend': dict(),		'arch_enable_nonboot_cpus_end': dict(),		'syscore_resume': dict(),		'acpi_pm_finish': dict(),		'resume_console': dict(),		'acpi_pm_end': dict(),		'pm_restore_gfp_mask': dict(),		'thaw_processes': dict(),		'pm_restore_console': dict(),		'CPU_OFF': {			'func':'_cpu_down',			'args_x86_64': {'cpu':'%di:s32'},			'format': 'CPU_OFF[{cpu}]',			'mask': 'CPU_.*_DOWN'		},		'CPU_ON': {			'func':'_cpu_up',			'args_x86_64': {'cpu':'%di:s32'},			'format': 'CPU_ON[{cpu}]',			'mask': 'CPU_.*_UP'		},	}	dev_tracefuncs = {		# general wait/delay/sleep		'msleep': { 'args_x86_64': {'time':'%di:s32'} },		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'} },		'acpi_os_stall': dict(),		# ACPI		'acpi_resume_power_resources': dict(),		'acpi_ps_parse_aml': dict(),		# filesystem		'ext4_sync_fs': dict(),		# ATA		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },		# i915		'i915_gem_restore_gtt_mappings': dict(),		'intel_opregion_setup': dict(),		'intel_dp_detect': dict(),		'intel_hdmi_detect': dict(),		'intel_opregion_init': dict(),	}	kprobes_postresume = [		{			'name': 'ataportrst',			'func': 'ata_eh_recover',			'args': {'port':'+36(%di):s32'},			'format': 'ata{port}_port_reset',			'mask': 'ata.*_port_reset'		}	]	kprobes = dict()	timeformat = '%.3f'	def __init__(self):		# if this is a phoronix test run, set some default options		if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ):			self.embedded = True			self.addlogs = True			self.htmlfile = os.environ['LOG_FILE']		self.hostname = platform.node()		if(self.hostname == ''):			self.hostname = 'localhost'		rtc = "rtc0"		if os.path.exists('/dev/rtc'):			rtc = os.readlink('/dev/rtc')		rtc = '/sys/class/rtc/'+rtc		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):			self.rtcpath = rtc		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):			self.ansi = True	def setPrecision(self, num):		if num < 0 or num > 6:			return		self.timeformat = '%.{0}f'.format(num)	def setOutputFile(self):		if((self.htmlfile == '') and (self.dmesgfile != '')):			m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)			if(m):				self.htmlfile = m.group('name')+'.html'		if((self.htmlfile == '') and (self.ftracefile != '')):			m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)			if(m):				self.htmlfile = m.group('name')+'.html'		if(self.htmlfile == ''):			self.htmlfile = 'output.html'	def initTestOutput(self, subdir, testpath=''):		self.prefix = self.hostname		v = open('/proc/version', 'r').read().strip()		kver = string.split(v)[2]		n = datetime.now()		testtime = n.strftime('suspend-%m%d%y-%H%M%S')		if not testpath:			testpath = n.strftime('suspend-%y%m%d-%H%M%S')		if(subdir != "."):			self.testdir = subdir+"/"+testpath		else:			self.testdir = testpath		self.teststamp = \			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver		if(self.embedded):			self.dmesgfile = \				'/tmp/'+testtime+'_'+self.suspendmode+'_dmesg.txt'			self.ftracefile = \				'/tmp/'+testtime+'_'+self.suspendmode+'_ftrace.txt'			return		self.dmesgfile = \			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'		self.ftracefile = \			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'		self.htmlfile = \			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'		if not os.path.isdir(self.testdir):			os.mkdir(self.testdir)	def setDeviceFilter(self, devnames):		self.devicefilter = string.split(devnames)	def rtcWakeAlarmOn(self):		os.system('echo 0 > '+self.rtcpath+'/wakealarm')		outD = open(self.rtcpath+'/date', 'r').read().strip()		outT = open(self.rtcpath+'/time', 'r').read().strip()		mD = re.match('^(?P<y>[0-9]*)-(?P<m>[0-9]*)-(?P<d>[0-9]*)', outD)		mT = re.match('^(?P<h>[0-9]*):(?P<m>[0-9]*):(?P<s>[0-9]*)', outT)		if(mD and mT):			# get the current time from hardware			utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds())			dt = datetime(\				int(mD.group('y')), int(mD.group('m')), int(mD.group('d')),				int(mT.group('h')), int(mT.group('m')), int(mT.group('s')))			nowtime = int(dt.strftime('%s')) + utcoffset		else:			# if hardware time fails, use the software time			nowtime = int(datetime.now().strftime('%s'))		alarm = nowtime + self.rtcwaketime		os.system('echo %d > %s/wakealarm' % (alarm, self.rtcpath))	def rtcWakeAlarmOff(self):		os.system('echo 0 > %s/wakealarm' % self.rtcpath)	def initdmesg(self):		# get the latest time stamp from the dmesg log		fp = os.popen('dmesg')		ktime = '0'		for line in fp:			line = line.replace('\r\n', '')			idx = line.find('[')			if idx > 1:				line = line[idx:]			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)			if(m):				ktime = m.group('ktime')		fp.close()		self.dmesgstart = float(ktime)	def getdmesg(self):		# store all new dmesg lines since initdmesg was called		fp = os.popen('dmesg')		op = open(self.dmesgfile, 'a')		for line in fp:			line = line.replace('\r\n', '')			idx = line.find('[')			if idx > 1:				line = line[idx:]			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)			if(not m):				continue			ktime = float(m.group('ktime'))			if ktime > self.dmesgstart:				op.write(line)		fp.close()		op.close()	def addFtraceFilterFunctions(self, file):		fp = open(file)		list = fp.read().split('\n')		fp.close()		for i in list:			if len(i) < 2:				continue			self.tracefuncs[i] = dict()	def getFtraceFilterFunctions(self, current):		rootCheck(True)		if not current:			os.system('cat '+self.tpath+'available_filter_functions')			return		fp = open(self.tpath+'available_filter_functions')		master = fp.read().split('\n')		fp.close()		if len(self.debugfuncs) > 0:			for i in self.debugfuncs:				if i in master:					print i				else:					print self.colorText(i)		else:			for i in self.tracefuncs:				if 'func' in self.tracefuncs[i]:					i = self.tracefuncs[i]['func']				if i in master:					print i				else:					print self.colorText(i)	def setFtraceFilterFunctions(self, list):		fp = open(self.tpath+'available_filter_functions')		master = fp.read().split('\n')		fp.close()		flist = ''		for i in list:			if i not in master:				continue			if ' [' in i:				flist += i.split(' ')[0]+'\n'			else:				flist += i+'\n'		fp = open(self.tpath+'set_graph_function', 'w')		fp.write(flist)		fp.close()	def kprobeMatch(self, name, target):		if name not in self.kprobes:			return False		if re.match(self.kprobes[name]['mask'], target):			return True		return False	def basicKprobe(self, name):		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name,'mask': name}	def defaultKprobe(self, name, kdata):		k = kdata		for field in ['name', 'format', 'mask', 'func']:			if field not in k:				k[field] = name		archargs = 'args_'+platform.machine()		if archargs in k:			k['args'] = k[archargs]		else:			k['args'] = dict()			k['format'] = name		self.kprobes[name] = k	def kprobeColor(self, name):		if name not in self.kprobes or 'color' not in self.kprobes[name]:			return ''		return self.kprobes[name]['color']	def kprobeDisplayName(self, name, dataraw):		if name not in self.kprobes:			self.basicKprobe(name)		data = ''		quote=0		# first remvoe any spaces inside quotes, and the quotes		for c in dataraw:			if c == '"':				quote = (quote + 1) % 2			if quote and c == ' ':				data += '_'			elif c != '"':				data += c		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']		arglist = dict()		# now process the args		for arg in sorted(args):			arglist[arg] = ''			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);			if m:				arglist[arg] = m.group('arg')			else:				m = re.match('.* '+arg+'=(?P<arg>.*)', data);				if m:					arglist[arg] = m.group('arg')		out = fmt.format(**arglist)		out = out.replace(' ', '_').replace('"', '')		return out	def kprobeText(self, kprobe):		name, fmt, func, args = kprobe['name'], kprobe['format'], kprobe['func'], kprobe['args']		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func), False)		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):			if arg not in args:				doError('Kprobe "%s" is missing argument "%s"' % (name, arg), False)		val = 'p:%s_cal %s' % (name, func)		for i in sorted(args):			val += ' %s=%s' % (i, args[i])		val += '\nr:%s_ret %s $retval\n' % (name, func)		return val	def addKprobes(self):		# first test each kprobe		print('INITIALIZING KPROBES...')		rejects = []		for name in sorted(self.kprobes):			if not self.testKprobe(self.kprobes[name]):				rejects.append(name)		# remove all failed ones from the list		for name in rejects:			vprint('Skipping KPROBE: %s' % name)			self.kprobes.pop(name)		self.fsetVal('', 'kprobe_events')		kprobeevents = ''		# set the kprobes all at once		for kp in self.kprobes:			val = self.kprobeText(self.kprobes[kp])			vprint('Adding KPROBE: %s\n%s' % (kp, val.strip()))			kprobeevents += self.kprobeText(self.kprobes[kp])		self.fsetVal(kprobeevents, 'kprobe_events')		# verify that the kprobes were set as ordered		check = self.fgetVal('kprobe_events')		linesout = len(kprobeevents.split('\n'))		linesack = len(check.split('\n'))		if linesack < linesout:			# if not, try appending the kprobes 1 by 1			for kp in self.kprobes:				kprobeevents = self.kprobeText(self.kprobes[kp])				self.fsetVal(kprobeevents, 'kprobe_events', 'a')		self.fsetVal('1', 'events/kprobes/enable')	def testKprobe(self, kprobe):		kprobeevents = self.kprobeText(kprobe)		if not kprobeevents:			return False		try:			self.fsetVal(kprobeevents, 'kprobe_events')			check = self.fgetVal('kprobe_events')		except:			return False		linesout = len(kprobeevents.split('\n'))		linesack = len(check.split('\n'))		if linesack < linesout:			return False		return True	def fsetVal(self, val, path, mode='w'):		file = self.tpath+path		if not os.path.exists(file):			return False		try:			fp = open(file, mode)			fp.write(val)			fp.close()		except:			pass		return True	def fgetVal(self, path):		file = self.tpath+path		res = ''		if not os.path.exists(file):			return res		try:			fp = open(file, 'r')			res = fp.read()			fp.close()		except:			pass		return res	def cleanupFtrace(self):		if(self.usecallgraph or self.usetraceevents):			self.fsetVal('0', 'events/kprobes/enable')			self.fsetVal('', 'kprobe_events')	def setupAllKprobes(self):		for name in self.tracefuncs:			self.defaultKprobe(name, self.tracefuncs[name])		for name in self.dev_tracefuncs:			self.defaultKprobe(name, self.dev_tracefuncs[name])	def isCallgraphFunc(self, name):		if len(self.debugfuncs) < 1 and self.suspendmode == 'command':			return True		if name in self.debugfuncs:			return True		funclist = []		for i in self.tracefuncs:			if 'func' in self.tracefuncs[i]:

Lines 1–500 of 4898.