Linux Audio

Check our new training course

Loading...
v4.6
   1#!/usr/bin/python
   2#
   3# Tool for analyzing suspend/resume timing
   4# Copyright (c) 2013, Intel Corporation.
   5#
   6# This program is free software; you can redistribute it and/or modify it
   7# under the terms and conditions of the GNU General Public License,
   8# version 2, as published by the Free Software Foundation.
   9#
  10# This program is distributed in the hope it will be useful, but WITHOUT
  11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  13# more details.
  14#
  15# You should have received a copy of the GNU General Public License along with
  16# this program; if not, write to the Free Software Foundation, Inc.,
  17# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
  18#
  19# Authors:
  20#	 Todd Brandt <todd.e.brandt@linux.intel.com>
  21#
 
 
 
 
 
 
 
 
 
 
 
  22# Description:
  23#	 This tool is designed to assist kernel and OS developers in optimizing
  24#	 their linux stack's suspend/resume time. Using a kernel image built
  25#	 with a few extra options enabled, the tool will execute a suspend and
  26#	 will capture dmesg and ftrace data until resume is complete. This data
  27#	 is transformed into a device timeline and a callgraph to give a quick
  28#	 and detailed view of which devices and callbacks are taking the most
  29#	 time in suspend/resume. The output is a single html file which can be
  30#	 viewed in firefox or chrome.
  31#
  32#	 The following kernel build options are required:
  33#		 CONFIG_PM_DEBUG=y
  34#		 CONFIG_PM_SLEEP_DEBUG=y
  35#		 CONFIG_FTRACE=y
  36#		 CONFIG_FUNCTION_TRACER=y
  37#		 CONFIG_FUNCTION_GRAPH_TRACER=y
 
 
  38#
  39#	 For kernel versions older than 3.15:
  40#	 The following additional kernel parameters are required:
  41#		 (e.g. in file /etc/default/grub)
  42#		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
  43#
  44
  45# ----------------- LIBRARIES --------------------
  46
  47import sys
  48import time
  49import os
  50import string
  51import re
  52import platform
  53from datetime import datetime
  54import struct
 
  55
  56# ----------------- CLASSES --------------------
  57
  58# Class: SystemValues
  59# Description:
  60#	 A global, single-instance container used to
  61#	 store system values and test parameters
  62class SystemValues:
  63	version = 3.0
 
  64	verbose = False
 
 
 
 
 
 
  65	testdir = '.'
  66	tpath = '/sys/kernel/debug/tracing/'
  67	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
  68	epath = '/sys/kernel/debug/tracing/events/power/'
  69	traceevents = [
  70		'suspend_resume',
  71		'device_pm_callback_end',
  72		'device_pm_callback_start'
  73	]
  74	modename = {
  75		'freeze': 'Suspend-To-Idle (S0)',
  76		'standby': 'Power-On Suspend (S1)',
  77		'mem': 'Suspend-to-RAM (S3)',
  78		'disk': 'Suspend-to-disk (S4)'
  79	}
  80	mempath = '/dev/mem'
  81	powerfile = '/sys/power/state'
  82	suspendmode = 'mem'
  83	hostname = 'localhost'
  84	prefix = 'test'
  85	teststamp = ''
 
  86	dmesgfile = ''
  87	ftracefile = ''
  88	htmlfile = ''
 
  89	rtcwake = False
  90	rtcwaketime = 10
  91	rtcpath = ''
  92	android = False
  93	adb = 'adb'
  94	devicefilter = []
  95	stamp = 0
  96	execcount = 1
  97	x2delay = 0
  98	usecallgraph = False
  99	usetraceevents = False
 100	usetraceeventsonly = False
 
 
 
 101	notestrun = False
 102	altdevname = dict()
 103	postresumetime = 0
 
 104	tracertypefmt = '# tracer: (?P<t>.*)'
 105	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
 106	postresumefmt = '# post resume time (?P<t>[0-9]*)$'
 107	stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
 108				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
 109				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 110	def __init__(self):
 
 
 
 
 
 111		self.hostname = platform.node()
 112		if(self.hostname == ''):
 113			self.hostname = 'localhost'
 114		rtc = "rtc0"
 115		if os.path.exists('/dev/rtc'):
 116			rtc = os.readlink('/dev/rtc')
 117		rtc = '/sys/class/rtc/'+rtc
 118		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 119			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 120			self.rtcpath = rtc
 
 
 
 
 
 
 121	def setOutputFile(self):
 122		if((self.htmlfile == '') and (self.dmesgfile != '')):
 123			m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)
 124			if(m):
 125				self.htmlfile = m.group('name')+'.html'
 126		if((self.htmlfile == '') and (self.ftracefile != '')):
 127			m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)
 128			if(m):
 129				self.htmlfile = m.group('name')+'.html'
 130		if(self.htmlfile == ''):
 131			self.htmlfile = 'output.html'
 132	def initTestOutput(self, subdir):
 133		if(not self.android):
 134			self.prefix = self.hostname
 135			v = open('/proc/version', 'r').read().strip()
 136			kver = string.split(v)[2]
 137		else:
 138			self.prefix = 'android'
 139			v = os.popen(self.adb+' shell cat /proc/version').read().strip()
 140			kver = string.split(v)[2]
 141		testtime = datetime.now().strftime('suspend-%m%d%y-%H%M%S')
 142		if(subdir != "."):
 143			self.testdir = subdir+"/"+testtime
 144		else:
 145			self.testdir = testtime
 146		self.teststamp = \
 147			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 
 
 
 
 
 
 148		self.dmesgfile = \
 149			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'
 150		self.ftracefile = \
 151			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'
 152		self.htmlfile = \
 153			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 154		os.mkdir(self.testdir)
 
 155	def setDeviceFilter(self, devnames):
 156		self.devicefilter = string.split(devnames)
 157	def rtcWakeAlarm(self):
 158		os.system('echo 0 > '+self.rtcpath+'/wakealarm')
 159		outD = open(self.rtcpath+'/date', 'r').read().strip()
 160		outT = open(self.rtcpath+'/time', 'r').read().strip()
 161		mD = re.match('^(?P<y>[0-9]*)-(?P<m>[0-9]*)-(?P<d>[0-9]*)', outD)
 162		mT = re.match('^(?P<h>[0-9]*):(?P<m>[0-9]*):(?P<s>[0-9]*)', outT)
 163		if(mD and mT):
 164			# get the current time from hardware
 165			utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds())
 166			dt = datetime(\
 167				int(mD.group('y')), int(mD.group('m')), int(mD.group('d')),
 168				int(mT.group('h')), int(mT.group('m')), int(mT.group('s')))
 169			nowtime = int(dt.strftime('%s')) + utcoffset
 170		else:
 171			# if hardware time fails, use the software time
 172			nowtime = int(datetime.now().strftime('%s'))
 173		alarm = nowtime + self.rtcwaketime
 174		os.system('echo %d > %s/wakealarm' % (alarm, self.rtcpath))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 175
 176sysvals = SystemValues()
 177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 178# Class: DeviceNode
 179# Description:
 180#	 A container used to create a device hierachy, with a single root node
 181#	 and a tree of child nodes. Used by Data.deviceTopology()
 182class DeviceNode:
 183	name = ''
 184	children = 0
 185	depth = 0
 186	def __init__(self, nodename, nodedepth):
 187		self.name = nodename
 188		self.children = []
 189		self.depth = nodedepth
 190
 191# Class: Data
 192# Description:
 193#	 The primary container for suspend/resume test data. There is one for
 194#	 each test run. The data is organized into a cronological hierarchy:
 195#	 Data.dmesg {
 196#		root structure, started as dmesg & ftrace, but now only ftrace
 197#		contents: times for suspend start/end, resume start/end, fwdata
 198#		phases {
 199#			10 sequential, non-overlapping phases of S/R
 200#			contents: times for phase start/end, order/color data for html
 201#			devlist {
 202#				device callback or action list for this phase
 203#				device {
 204#					a single device callback or generic action
 205#					contents: start/stop times, pid/cpu/driver info
 206#						parents/children, html id for timeline/callgraph
 207#						optionally includes an ftrace callgraph
 208#						optionally includes intradev trace events
 209#				}
 210#			}
 211#		}
 212#	}
 213#
 214class Data:
 215	dmesg = {}  # root data structure
 216	phases = [] # ordered list of phases
 217	start = 0.0 # test start
 218	end = 0.0   # test end
 219	tSuspended = 0.0 # low-level suspend start
 220	tResumed = 0.0   # low-level resume start
 221	tLow = 0.0       # time spent in low-level suspend (standby/freeze)
 222	fwValid = False  # is firmware data available
 223	fwSuspend = 0    # time spent in firmware suspend
 224	fwResume = 0     # time spent in firmware resume
 225	dmesgtext = []   # dmesg text file in memory
 226	testnumber = 0
 227	idstr = ''
 228	html_device_id = 0
 229	stamp = 0
 230	outfile = ''
 
 231	def __init__(self, num):
 232		idchar = 'abcdefghijklmnopqrstuvwxyz'
 233		self.testnumber = num
 234		self.idstr = idchar[num]
 235		self.dmesgtext = []
 236		self.phases = []
 237		self.dmesg = { # fixed list of 10 phases
 238			'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
 239								'row': 0, 'color': '#CCFFCC', 'order': 0},
 240			        'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
 241								'row': 0, 'color': '#88FF88', 'order': 1},
 242			   'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
 243								'row': 0, 'color': '#00AA00', 'order': 2},
 244			  'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 245								'row': 0, 'color': '#008888', 'order': 3},
 246		    'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 247								'row': 0, 'color': '#0000FF', 'order': 4},
 248			 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 249								'row': 0, 'color': '#FF0000', 'order': 5},
 250			   'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 251								'row': 0, 'color': '#FF9900', 'order': 6},
 252			   'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
 253								'row': 0, 'color': '#FFCC00', 'order': 7},
 254			         'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
 255								'row': 0, 'color': '#FFFF88', 'order': 8},
 256			'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
 257								'row': 0, 'color': '#FFFFCC', 'order': 9}
 258		}
 259		self.phases = self.sortedPhases()
 
 
 
 260	def getStart(self):
 261		return self.dmesg[self.phases[0]]['start']
 262	def setStart(self, time):
 263		self.start = time
 264		self.dmesg[self.phases[0]]['start'] = time
 265	def getEnd(self):
 266		return self.dmesg[self.phases[-1]]['end']
 267	def setEnd(self, time):
 268		self.end = time
 269		self.dmesg[self.phases[-1]]['end'] = time
 270	def isTraceEventOutsideDeviceCalls(self, pid, time):
 271		for phase in self.phases:
 272			list = self.dmesg[phase]['list']
 273			for dev in list:
 274				d = list[dev]
 275				if(d['pid'] == pid and time >= d['start'] and
 276					time <= d['end']):
 277					return False
 278		return True
 279	def addIntraDevTraceEvent(self, action, name, pid, time):
 280		if(action == 'mutex_lock_try'):
 281			color = 'red'
 282		elif(action == 'mutex_lock_pass'):
 283			color = 'green'
 284		elif(action == 'mutex_unlock'):
 285			color = 'blue'
 286		else:
 287			# create separate colors based on the name
 288			v1 = len(name)*10 % 256
 289			v2 = string.count(name, 'e')*100 % 256
 290			v3 = ord(name[0])*20 % 256
 291			color = '#%06X' % ((v1*0x10000) + (v2*0x100) + v3)
 292		for phase in self.phases:
 293			list = self.dmesg[phase]['list']
 294			for dev in list:
 295				d = list[dev]
 296				if(d['pid'] == pid and time >= d['start'] and
 297					time <= d['end']):
 298					e = TraceEvent(action, name, color, time)
 299					if('traceevents' not in d):
 300						d['traceevents'] = []
 301					d['traceevents'].append(e)
 302					return d
 303					break
 304		return 0
 305	def capIntraDevTraceEvent(self, action, name, pid, time):
 306		for phase in self.phases:
 307			list = self.dmesg[phase]['list']
 308			for dev in list:
 309				d = list[dev]
 310				if(d['pid'] == pid and time >= d['start'] and
 311					time <= d['end']):
 312					if('traceevents' not in d):
 313						return
 314					for e in d['traceevents']:
 315						if(e.action == action and
 316							e.name == name and not e.ready):
 317							e.length = time - e.time
 318							e.ready = True
 319							break
 320					return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 321	def trimTimeVal(self, t, t0, dT, left):
 322		if left:
 323			if(t > t0):
 324				if(t - dT < t0):
 325					return t0
 326				return t - dT
 327			else:
 328				return t
 329		else:
 330			if(t < t0 + dT):
 331				if(t > t0):
 332					return t0 + dT
 333				return t + dT
 334			else:
 335				return t
 336	def trimTime(self, t0, dT, left):
 337		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
 338		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
 339		self.start = self.trimTimeVal(self.start, t0, dT, left)
 340		self.end = self.trimTimeVal(self.end, t0, dT, left)
 341		for phase in self.phases:
 342			p = self.dmesg[phase]
 343			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
 344			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
 345			list = p['list']
 346			for name in list:
 347				d = list[name]
 348				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
 349				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
 350				if('ftrace' in d):
 351					cg = d['ftrace']
 352					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
 353					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
 354					for line in cg.list:
 355						line.time = self.trimTimeVal(line.time, t0, dT, left)
 356				if('traceevents' in d):
 357					for e in d['traceevents']:
 358						e.time = self.trimTimeVal(e.time, t0, dT, left)
 359	def normalizeTime(self, tZero):
 360		# first trim out any standby or freeze clock time
 361		if(self.tSuspended != self.tResumed):
 362			if(self.tResumed > tZero):
 363				self.trimTime(self.tSuspended, \
 364					self.tResumed-self.tSuspended, True)
 365			else:
 366				self.trimTime(self.tSuspended, \
 367					self.tResumed-self.tSuspended, False)
 368		# shift the timeline so that tZero is the new 0
 369		self.tSuspended -= tZero
 370		self.tResumed -= tZero
 371		self.start -= tZero
 372		self.end -= tZero
 373		for phase in self.phases:
 374			p = self.dmesg[phase]
 375			p['start'] -= tZero
 376			p['end'] -= tZero
 377			list = p['list']
 378			for name in list:
 379				d = list[name]
 380				d['start'] -= tZero
 381				d['end'] -= tZero
 382				if('ftrace' in d):
 383					cg = d['ftrace']
 384					cg.start -= tZero
 385					cg.end -= tZero
 386					for line in cg.list:
 387						line.time -= tZero
 388				if('traceevents' in d):
 389					for e in d['traceevents']:
 390						e.time -= tZero
 391	def newPhaseWithSingleAction(self, phasename, devname, start, end, color):
 392		for phase in self.phases:
 393			self.dmesg[phase]['order'] += 1
 394		self.html_device_id += 1
 395		devid = '%s%d' % (self.idstr, self.html_device_id)
 396		list = dict()
 397		list[devname] = \
 398			{'start': start, 'end': end, 'pid': 0, 'par': '',
 399			'length': (end-start), 'row': 0, 'id': devid, 'drv': '' };
 400		self.dmesg[phasename] = \
 401			{'list': list, 'start': start, 'end': end,
 402			'row': 0, 'color': color, 'order': 0}
 403		self.phases = self.sortedPhases()
 404	def newPhase(self, phasename, start, end, color, order):
 405		if(order < 0):
 406			order = len(self.phases)
 407		for phase in self.phases[order:]:
 408			self.dmesg[phase]['order'] += 1
 409		if(order > 0):
 410			p = self.phases[order-1]
 411			self.dmesg[p]['end'] = start
 412		if(order < len(self.phases)):
 413			p = self.phases[order]
 414			self.dmesg[p]['start'] = end
 415		list = dict()
 416		self.dmesg[phasename] = \
 417			{'list': list, 'start': start, 'end': end,
 418			'row': 0, 'color': color, 'order': order}
 419		self.phases = self.sortedPhases()
 
 420	def setPhase(self, phase, ktime, isbegin):
 421		if(isbegin):
 422			self.dmesg[phase]['start'] = ktime
 423		else:
 424			self.dmesg[phase]['end'] = ktime
 425	def dmesgSortVal(self, phase):
 426		return self.dmesg[phase]['order']
 427	def sortedPhases(self):
 428		return sorted(self.dmesg, key=self.dmesgSortVal)
 429	def sortedDevices(self, phase):
 430		list = self.dmesg[phase]['list']
 431		slist = []
 432		tmp = dict()
 433		for devname in list:
 434			dev = list[devname]
 435			tmp[dev['start']] = devname
 436		for t in sorted(tmp):
 437			slist.append(tmp[t])
 438		return slist
 439	def fixupInitcalls(self, phase, end):
 440		# if any calls never returned, clip them at system resume end
 441		phaselist = self.dmesg[phase]['list']
 442		for devname in phaselist:
 443			dev = phaselist[devname]
 444			if(dev['end'] < 0):
 445				dev['end'] = end
 
 
 
 446				vprint('%s (%s): callback didnt return' % (devname, phase))
 447	def deviceFilter(self, devicefilter):
 448		# remove all by the relatives of the filter devnames
 449		filter = []
 450		for phase in self.phases:
 451			list = self.dmesg[phase]['list']
 452			for name in devicefilter:
 453				dev = name
 454				while(dev in list):
 455					if(dev not in filter):
 456						filter.append(dev)
 457					dev = list[dev]['par']
 458				children = self.deviceDescendants(name, phase)
 459				for dev in children:
 460					if(dev not in filter):
 461						filter.append(dev)
 462		for phase in self.phases:
 463			list = self.dmesg[phase]['list']
 464			rmlist = []
 465			for name in list:
 466				pid = list[name]['pid']
 467				if(name not in filter and pid >= 0):
 468					rmlist.append(name)
 469			for name in rmlist:
 470				del list[name]
 471	def fixupInitcallsThatDidntReturn(self):
 472		# if any calls never returned, clip them at system resume end
 473		for phase in self.phases:
 474			self.fixupInitcalls(phase, self.getEnd())
 475	def newActionGlobal(self, name, start, end):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 476		# which phase is this device callback or action "in"
 477		targetphase = "none"
 
 478		overlap = 0.0
 
 479		for phase in self.phases:
 480			pstart = self.dmesg[phase]['start']
 481			pend = self.dmesg[phase]['end']
 482			o = max(0, min(end, pend) - max(start, pstart))
 483			if(o > overlap):
 
 
 
 
 484				targetphase = phase
 485				overlap = o
 
 
 
 
 
 486		if targetphase in self.phases:
 487			self.newAction(targetphase, name, -1, '', start, end, '')
 488			return True
 489		return False
 490	def newAction(self, phase, name, pid, parent, start, end, drv):
 491		# new device callback for a specific phase
 492		self.html_device_id += 1
 493		devid = '%s%d' % (self.idstr, self.html_device_id)
 494		list = self.dmesg[phase]['list']
 495		length = -1.0
 496		if(start >= 0 and end >= 0):
 497			length = end - start
 
 
 
 
 
 
 498		list[name] = {'start': start, 'end': end, 'pid': pid, 'par': parent,
 499					  'length': length, 'row': 0, 'id': devid, 'drv': drv }
 
 
 
 
 
 500	def deviceIDs(self, devlist, phase):
 501		idlist = []
 502		list = self.dmesg[phase]['list']
 503		for devname in list:
 504			if devname in devlist:
 505				idlist.append(list[devname]['id'])
 506		return idlist
 507	def deviceParentID(self, devname, phase):
 508		pdev = ''
 509		pdevid = ''
 510		list = self.dmesg[phase]['list']
 511		if devname in list:
 512			pdev = list[devname]['par']
 513		if pdev in list:
 514			return list[pdev]['id']
 515		return pdev
 516	def deviceChildren(self, devname, phase):
 517		devlist = []
 518		list = self.dmesg[phase]['list']
 519		for child in list:
 520			if(list[child]['par'] == devname):
 521				devlist.append(child)
 522		return devlist
 523	def deviceDescendants(self, devname, phase):
 524		children = self.deviceChildren(devname, phase)
 525		family = children
 526		for child in children:
 527			family += self.deviceDescendants(child, phase)
 528		return family
 529	def deviceChildrenIDs(self, devname, phase):
 530		devlist = self.deviceChildren(devname, phase)
 531		return self.deviceIDs(devlist, phase)
 532	def printDetails(self):
 533		vprint('          test start: %f' % self.start)
 534		for phase in self.phases:
 535			dc = len(self.dmesg[phase]['list'])
 536			vprint('    %16s: %f - %f (%d devices)' % (phase, \
 537				self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
 538		vprint('            test end: %f' % self.end)
 
 
 
 
 
 
 
 
 539	def masterTopology(self, name, list, depth):
 540		node = DeviceNode(name, depth)
 541		for cname in list:
 542			clist = self.deviceChildren(cname, 'resume')
 
 
 
 543			cnode = self.masterTopology(cname, clist, depth+1)
 544			node.children.append(cnode)
 545		return node
 546	def printTopology(self, node):
 547		html = ''
 548		if node.name:
 549			info = ''
 550			drv = ''
 551			for phase in self.phases:
 552				list = self.dmesg[phase]['list']
 553				if node.name in list:
 554					s = list[node.name]['start']
 555					e = list[node.name]['end']
 556					if list[node.name]['drv']:
 557						drv = ' {'+list[node.name]['drv']+'}'
 558					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
 559			html += '<li><b>'+node.name+drv+'</b>'
 560			if info:
 561				html += '<ul>'+info+'</ul>'
 562			html += '</li>'
 563		if len(node.children) > 0:
 564			html += '<ul>'
 565			for cnode in node.children:
 566				html += self.printTopology(cnode)
 567			html += '</ul>'
 568		return html
 569	def rootDeviceList(self):
 570		# list of devices graphed
 571		real = []
 572		for phase in self.dmesg:
 573			list = self.dmesg[phase]['list']
 574			for dev in list:
 575				if list[dev]['pid'] >= 0 and dev not in real:
 576					real.append(dev)
 577		# list of top-most root devices
 578		rootlist = []
 579		for phase in self.dmesg:
 580			list = self.dmesg[phase]['list']
 581			for dev in list:
 582				pdev = list[dev]['par']
 583				if(re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
 
 584					continue
 585				if pdev and pdev not in real and pdev not in rootlist:
 586					rootlist.append(pdev)
 587		return rootlist
 588	def deviceTopology(self):
 589		rootlist = self.rootDeviceList()
 590		master = self.masterTopology('', rootlist, 0)
 591		return self.printTopology(master)
 
 
 
 
 
 
 
 
 
 
 
 
 592
 593# Class: TraceEvent
 594# Description:
 595#	 A container for trace event data found in the ftrace file
 596class TraceEvent:
 597	ready = False
 598	name = ''
 599	time = 0.0
 600	color = '#FFFFFF'
 601	length = 0.0
 602	action = ''
 603	def __init__(self, a, n, c, t):
 604		self.action = a
 605		self.name = n
 606		self.color = c
 607		self.time = t
 
 608
 609# Class: FTraceLine
 610# Description:
 611#	 A container for a single line of ftrace data. There are six basic types:
 612#		 callgraph line:
 613#			  call: "  dpm_run_callback() {"
 614#			return: "  }"
 615#			  leaf: " dpm_run_callback();"
 616#		 trace event:
 617#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
 618#			 suspend_resume: phase or custom exec block data
 619#			 device_pm_callback: device callback info
 620class FTraceLine:
 621	time = 0.0
 622	length = 0.0
 623	fcall = False
 624	freturn = False
 625	fevent = False
 
 626	depth = 0
 627	name = ''
 628	type = ''
 629	def __init__(self, t, m, d):
 630		self.time = float(t)
 
 
 631		# is this a trace event
 632		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
 633			if(d == 'traceevent'):
 634				# nop format trace event
 635				msg = m
 636			else:
 637				# function_graph format trace event
 638				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
 639				msg = em.group('msg')
 640
 641			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
 642			if(emm):
 643				self.name = emm.group('msg')
 644				self.type = emm.group('call')
 645			else:
 646				self.name = msg
 
 
 
 
 
 
 
 
 
 
 
 
 647			self.fevent = True
 648			return
 649		# convert the duration to seconds
 650		if(d):
 651			self.length = float(d)/1000000
 652		# the indentation determines the depth
 653		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
 654		if(not match):
 655			return
 656		self.depth = self.getDepth(match.group('d'))
 657		m = match.group('o')
 658		# function return
 659		if(m[0] == '}'):
 660			self.freturn = True
 661			if(len(m) > 1):
 662				# includes comment with function name
 663				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
 664				if(match):
 665					self.name = match.group('n')
 666		# function call
 667		else:
 668			self.fcall = True
 669			# function call with children
 670			if(m[-1] == '{'):
 671				match = re.match('^(?P<n>.*) *\(.*', m)
 672				if(match):
 673					self.name = match.group('n')
 674			# function call with no children (leaf)
 675			elif(m[-1] == ';'):
 676				self.freturn = True
 677				match = re.match('^(?P<n>.*) *\(.*', m)
 678				if(match):
 679					self.name = match.group('n')
 680			# something else (possibly a trace marker)
 681			else:
 682				self.name = m
 683	def getDepth(self, str):
 684		return len(str)/2
 685	def debugPrint(self, dev):
 686		if(self.freturn and self.fcall):
 687			print('%s -- %f (%02d): %s(); (%.3f us)' % (dev, self.time, \
 688				self.depth, self.name, self.length*1000000))
 689		elif(self.freturn):
 690			print('%s -- %f (%02d): %s} (%.3f us)' % (dev, self.time, \
 691				self.depth, self.name, self.length*1000000))
 692		else:
 693			print('%s -- %f (%02d): %s() { (%.3f us)' % (dev, self.time, \
 694				self.depth, self.name, self.length*1000000))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 695
 696# Class: FTraceCallGraph
 697# Description:
 698#	 A container for the ftrace callgraph of a single recursive function.
 699#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
 700#	 Each instance is tied to a single device in a single phase, and is
 701#	 comprised of an ordered list of FTraceLine objects
 702class FTraceCallGraph:
 703	start = -1.0
 704	end = -1.0
 705	list = []
 706	invalid = False
 707	depth = 0
 708	def __init__(self):
 
 709		self.start = -1.0
 710		self.end = -1.0
 711		self.list = []
 712		self.depth = 0
 713	def setDepth(self, line):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 714		if(line.fcall and not line.freturn):
 715			line.depth = self.depth
 716			self.depth += 1
 717		elif(line.freturn and not line.fcall):
 718			self.depth -= 1
 719			line.depth = self.depth
 720		else:
 721			line.depth = self.depth
 722	def addLine(self, line, match):
 723		if(not self.invalid):
 724			self.setDepth(line)
 725		if(line.depth == 0 and line.freturn):
 726			if(self.start < 0):
 727				self.start = line.time
 728			self.end = line.time
 729			self.list.append(line)
 
 
 
 730			return True
 731		if(self.invalid):
 732			return False
 733		if(len(self.list) >= 1000000 or self.depth < 0):
 734			if(len(self.list) > 0):
 735				first = self.list[0]
 736				self.list = []
 737				self.list.append(first)
 738			self.invalid = True
 739			if(not match):
 740				return False
 741			id = 'task %s cpu %s' % (match.group('pid'), match.group('cpu'))
 742			window = '(%f - %f)' % (self.start, line.time)
 743			if(self.depth < 0):
 744				print('Too much data for '+id+\
 745					' (buffer overflow), ignoring this callback')
 746			else:
 747				print('Too much data for '+id+\
 748					' '+window+', ignoring this callback')
 749			return False
 750		self.list.append(line)
 751		if(self.start < 0):
 752			self.start = line.time
 753		return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 754	def slice(self, t0, tN):
 755		minicg = FTraceCallGraph()
 756		count = -1
 757		firstdepth = 0
 758		for l in self.list:
 759			if(l.time < t0 or l.time > tN):
 760				continue
 761			if(count < 0):
 762				if(not l.fcall or l.name == 'dev_driver_string'):
 763					continue
 764				firstdepth = l.depth
 765				count = 0
 766			l.depth -= firstdepth
 767			minicg.addLine(l, 0)
 768			if((count == 0 and l.freturn and l.fcall) or
 769				(count > 0 and l.depth <= 0)):
 770				break
 771			count += 1
 772		return minicg
 773	def sanityCheck(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 774		stack = dict()
 775		cnt = 0
 776		for l in self.list:
 777			if(l.fcall and not l.freturn):
 778				stack[l.depth] = l
 779				cnt += 1
 780			elif(l.freturn and not l.fcall):
 781				if(l.depth not in stack):
 
 
 
 782					return False
 
 783				stack[l.depth].length = l.length
 784				stack[l.depth] = 0
 785				l.length = 0
 786				cnt -= 1
 787		if(cnt == 0):
 
 788			return True
 789		return False
 790	def debugPrint(self, filename):
 791		if(filename == 'stdout'):
 792			print('[%f - %f]') % (self.start, self.end)
 793			for l in self.list:
 794				if(l.freturn and l.fcall):
 795					print('%f (%02d): %s(); (%.3f us)' % (l.time, \
 796						l.depth, l.name, l.length*1000000))
 797				elif(l.freturn):
 798					print('%f (%02d): %s} (%.3f us)' % (l.time, \
 799						l.depth, l.name, l.length*1000000))
 800				else:
 801					print('%f (%02d): %s() { (%.3f us)' % (l.time, \
 802						l.depth, l.name, l.length*1000000))
 803			print(' ')
 804		else:
 805			fp = open(filename, 'w')
 806			print(filename)
 807			for l in self.list:
 808				if(l.freturn and l.fcall):
 809					fp.write('%f (%02d): %s(); (%.3f us)\n' % (l.time, \
 810						l.depth, l.name, l.length*1000000))
 811				elif(l.freturn):
 812					fp.write('%f (%02d): %s} (%.3f us)\n' % (l.time, \
 813						l.depth, l.name, l.length*1000000))
 814				else:
 815					fp.write('%f (%02d): %s() { (%.3f us)\n' % (l.time, \
 816						l.depth, l.name, l.length*1000000))
 817			fp.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 818
 819# Class: Timeline
 820# Description:
 821#	 A container for a suspend/resume html timeline. In older versions
 822#	 of the script there were multiple timelines, but in the latest
 823#	 there is only one.
 824class Timeline:
 825	html = {}
 826	scaleH = 0.0 # height of the row as a percent of the timeline height
 827	rowH = 0.0 # height of each row in percent of the timeline height
 828	row_height_pixels = 30
 829	maxrows = 0
 830	height = 0
 831	def __init__(self):
 
 
 
 
 
 832		self.html = {
 
 833			'timeline': '',
 834			'legend': '',
 835			'scale': ''
 836		}
 837	def setRows(self, rows):
 838		self.maxrows = int(rows)
 839		self.scaleH = 100.0/float(self.maxrows)
 840		self.height = self.maxrows*self.row_height_pixels
 841		r = float(self.maxrows - 1)
 842		if(r < 1.0):
 843			r = 1.0
 844		self.rowH = (100.0 - self.scaleH)/r
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 845
 846# Class: TestRun
 847# Description:
 848#	 A container for a suspend/resume test run. This is necessary as
 849#	 there could be more than one, and they need to be separate.
 850class TestRun:
 
 
 
 851	ftrace_line_fmt_fg = \
 852		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
 853		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
 854		'[ +!]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
 855	ftrace_line_fmt_nop = \
 856		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
 857		'(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
 858		'(?P<msg>.*)'
 859	ftrace_line_fmt = ftrace_line_fmt_nop
 860	cgformat = False
 861	ftemp = dict()
 862	ttemp = dict()
 863	inthepipe = False
 864	tracertype = ''
 865	data = 0
 866	def __init__(self, dataobj):
 867		self.data = dataobj
 868		self.ftemp = dict()
 869		self.ttemp = dict()
 870	def isReady(self):
 871		if(tracertype == '' or not data):
 872			return False
 873		return True
 874	def setTracerType(self, tracer):
 875		self.tracertype = tracer
 876		if(tracer == 'function_graph'):
 877			self.cgformat = True
 878			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
 879		elif(tracer == 'nop'):
 880			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
 881		else:
 882			doError('Invalid tracer format: [%s]' % tracer, False)
 883
 
 
 
 
 
 
 
 
 
 
 
 
 
 884# ----------------- FUNCTIONS --------------------
 885
 886# Function: vprint
 887# Description:
 888#	 verbose print (prints only with -verbose option)
 889# Arguments:
 890#	 msg: the debug/log message to print
 891def vprint(msg):
 892	global sysvals
 893	if(sysvals.verbose):
 894		print(msg)
 895
 896# Function: initFtrace
 897# Description:
 898#	 Configure ftrace to use trace events and/or a callgraph
 899def initFtrace():
 900	global sysvals
 901
 902	tp = sysvals.tpath
 903	cf = 'dpm_run_callback'
 904	if(sysvals.usetraceeventsonly):
 905		cf = '-e dpm_prepare -e dpm_complete -e dpm_run_callback'
 906	if(sysvals.usecallgraph or sysvals.usetraceevents):
 907		print('INITIALIZING FTRACE...')
 908		# turn trace off
 909		os.system('echo 0 > '+tp+'tracing_on')
 910		# set the trace clock to global
 911		os.system('echo global > '+tp+'trace_clock')
 912		# set trace buffer to a huge value
 913		os.system('echo nop > '+tp+'current_tracer')
 914		os.system('echo 100000 > '+tp+'buffer_size_kb')
 915		# initialize the callgraph trace, unless this is an x2 run
 916		if(sysvals.usecallgraph and sysvals.execcount == 1):
 917			# set trace type
 918			os.system('echo function_graph > '+tp+'current_tracer')
 919			os.system('echo "" > '+tp+'set_ftrace_filter')
 920			# set trace format options
 921			os.system('echo funcgraph-abstime > '+tp+'trace_options')
 922			os.system('echo funcgraph-proc > '+tp+'trace_options')
 923			# focus only on device suspend and resume
 924			os.system('cat '+tp+'available_filter_functions | grep '+\
 925				cf+' > '+tp+'set_graph_function')
 926		if(sysvals.usetraceevents):
 927			# turn trace events on
 928			events = iter(sysvals.traceevents)
 929			for e in events:
 930				os.system('echo 1 > '+sysvals.epath+e+'/enable')
 931		# clear the trace buffer
 932		os.system('echo "" > '+tp+'trace')
 933
 934# Function: initFtraceAndroid
 935# Description:
 936#	 Configure ftrace to capture trace events
 937def initFtraceAndroid():
 938	global sysvals
 939
 940	tp = sysvals.tpath
 941	if(sysvals.usetraceevents):
 942		print('INITIALIZING FTRACE...')
 943		# turn trace off
 944		os.system(sysvals.adb+" shell 'echo 0 > "+tp+"tracing_on'")
 945		# set the trace clock to global
 946		os.system(sysvals.adb+" shell 'echo global > "+tp+"trace_clock'")
 947		# set trace buffer to a huge value
 948		os.system(sysvals.adb+" shell 'echo nop > "+tp+"current_tracer'")
 949		os.system(sysvals.adb+" shell 'echo 10000 > "+tp+"buffer_size_kb'")
 950		# turn trace events on
 951		events = iter(sysvals.traceevents)
 952		for e in events:
 953			os.system(sysvals.adb+" shell 'echo 1 > "+\
 954				sysvals.epath+e+"/enable'")
 955		# clear the trace buffer
 956		os.system(sysvals.adb+" shell 'echo \"\" > "+tp+"trace'")
 957
 958# Function: verifyFtrace
 959# Description:
 960#	 Check that ftrace is working on the system
 961# Output:
 962#	 True or False
 963def verifyFtrace():
 964	global sysvals
 965	# files needed for any trace data
 966	files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 967			 'trace_marker', 'trace_options', 'tracing_on']
 968	# files needed for callgraph trace data
 969	tp = sysvals.tpath
 970	if(sysvals.usecallgraph):
 971		files += [
 972			'available_filter_functions',
 973			'set_ftrace_filter',
 974			'set_graph_function'
 975		]
 976	for f in files:
 977		if(sysvals.android):
 978			out = os.popen(sysvals.adb+' shell ls '+tp+f).read().strip()
 979			if(out != tp+f):
 980				return False
 981		else:
 982			if(os.path.exists(tp+f) == False):
 983				return False
 984	return True
 985
 986# Function: parseStamp
 987# Description:
 988#	 Pull in the stamp comment line from the data file(s),
 989#	 create the stamp, and add it to the global sysvals object
 990# Arguments:
 991#	 m: the valid re.match output for the stamp line
 992def parseStamp(m, data):
 993	global sysvals
 
 
 994	data.stamp = {'time': '', 'host': '', 'mode': ''}
 995	dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
 996		int(m.group('d')), int(m.group('H')), int(m.group('M')),
 997		int(m.group('S')))
 998	data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
 999	data.stamp['host'] = m.group('host')
1000	data.stamp['mode'] = m.group('mode')
1001	data.stamp['kernel'] = m.group('kernel')
 
1002	sysvals.suspendmode = data.stamp['mode']
1003	if not sysvals.stamp:
1004		sysvals.stamp = data.stamp
1005
1006# Function: diffStamp
1007# Description:
1008#	compare the host, kernel, and mode fields in 3 stamps
1009# Arguments:
1010#	 stamp1: string array with mode, kernel, and host
1011#	 stamp2: string array with mode, kernel, and host
1012# Return:
1013#	True if stamps differ, False if they're the same
1014def diffStamp(stamp1, stamp2):
1015	if 'host' in stamp1 and 'host' in stamp2:
1016		if stamp1['host'] != stamp2['host']:
1017			return True
1018	if 'kernel' in stamp1 and 'kernel' in stamp2:
1019		if stamp1['kernel'] != stamp2['kernel']:
1020			return True
1021	if 'mode' in stamp1 and 'mode' in stamp2:
1022		if stamp1['mode'] != stamp2['mode']:
1023			return True
1024	return False
1025
1026# Function: doesTraceLogHaveTraceEvents
1027# Description:
1028#	 Quickly determine if the ftrace log has some or all of the trace events
1029#	 required for primary parsing. Set the usetraceevents and/or
1030#	 usetraceeventsonly flags in the global sysvals object
1031def doesTraceLogHaveTraceEvents():
1032	global sysvals
1033
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1034	sysvals.usetraceeventsonly = True
1035	sysvals.usetraceevents = False
1036	for e in sysvals.traceevents:
1037		out = os.popen('cat '+sysvals.ftracefile+' | grep "'+e+': "').read()
1038		if(not out):
1039			sysvals.usetraceeventsonly = False
1040		if(e == 'suspend_resume' and out):
1041			sysvals.usetraceevents = True
 
 
 
 
 
1042
1043# Function: appendIncompleteTraceLog
1044# Description:
1045#	 [deprecated for kernel 3.15 or newer]
1046#	 Legacy support of ftrace outputs that lack the device_pm_callback
1047#	 and/or suspend_resume trace events. The primary data should be
1048#	 taken from dmesg, and this ftrace is used only for callgraph data
1049#	 or custom actions in the timeline. The data is appended to the Data
1050#	 objects provided.
1051# Arguments:
1052#	 testruns: the array of Data objects obtained from parseKernelLog
1053def appendIncompleteTraceLog(testruns):
1054	global sysvals
1055
1056	# create TestRun vessels for ftrace parsing
1057	testcnt = len(testruns)
1058	testidx = -1
1059	testrun = []
1060	for data in testruns:
1061		testrun.append(TestRun(data))
1062
1063	# extract the callgraph and traceevent data
1064	vprint('Analyzing the ftrace data...')
 
1065	tf = open(sysvals.ftracefile, 'r')
 
1066	for line in tf:
1067		# remove any latent carriage returns
1068		line = line.replace('\r\n', '')
1069		# grab the time stamp first (signifies the start of the test run)
1070		m = re.match(sysvals.stampfmt, line)
1071		if(m):
1072			testidx += 1
1073			parseStamp(m, testrun[testidx].data)
1074			continue
1075		# pull out any firmware data
1076		if(re.match(sysvals.firmwarefmt, line)):
1077			continue
1078		# if we havent found a test time stamp yet keep spinning til we do
1079		if(testidx < 0):
1080			continue
1081		# determine the trace data type (required for further parsing)
1082		m = re.match(sysvals.tracertypefmt, line)
1083		if(m):
1084			tracer = m.group('t')
1085			testrun[testidx].setTracerType(tracer)
 
 
 
1086			continue
1087		# parse only valid lines, if this isnt one move on
1088		m = re.match(testrun[testidx].ftrace_line_fmt, line)
1089		if(not m):
1090			continue
1091		# gather the basic message data from the line
1092		m_time = m.group('time')
1093		m_pid = m.group('pid')
1094		m_msg = m.group('msg')
1095		if(testrun[testidx].cgformat):
1096			m_param3 = m.group('dur')
1097		else:
1098			m_param3 = 'traceevent'
1099		if(m_time and m_pid and m_msg):
1100			t = FTraceLine(m_time, m_msg, m_param3)
1101			pid = int(m_pid)
1102		else:
1103			continue
1104		# the line should be a call, return, or event
1105		if(not t.fcall and not t.freturn and not t.fevent):
1106			continue
1107		# only parse the ftrace data during suspend/resume
1108		data = testrun[testidx].data
1109		if(not testrun[testidx].inthepipe):
1110			# look for the suspend start marker
1111			if(t.fevent):
1112				if(t.name == 'SUSPEND START'):
1113					testrun[testidx].inthepipe = True
1114					data.setStart(t.time)
1115				continue
1116		else:
1117			# trace event processing
1118			if(t.fevent):
1119				if(t.name == 'RESUME COMPLETE'):
1120					testrun[testidx].inthepipe = False
1121					data.setEnd(t.time)
1122					if(testidx == testcnt - 1):
1123						break
1124					continue
1125				# general trace events have two types, begin and end
1126				if(re.match('(?P<name>.*) begin$', t.name)):
1127					isbegin = True
1128				elif(re.match('(?P<name>.*) end$', t.name)):
1129					isbegin = False
 
 
 
 
 
 
1130				else:
1131					continue
1132				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
1133				if(m):
1134					val = m.group('val')
1135					if val == '0':
1136						name = m.group('name')
1137					else:
1138						name = m.group('name')+'['+val+']'
 
 
 
 
 
 
 
 
 
 
 
 
1139				else:
1140					m = re.match('(?P<name>.*) .*', t.name)
1141					name = m.group('name')
1142				# special processing for trace events
1143				if re.match('dpm_prepare\[.*', name):
1144					continue
1145				elif re.match('machine_suspend.*', name):
1146					continue
1147				elif re.match('suspend_enter\[.*', name):
1148					if(not isbegin):
1149						data.dmesg['suspend_prepare']['end'] = t.time
1150					continue
1151				elif re.match('dpm_suspend\[.*', name):
1152					if(not isbegin):
1153						data.dmesg['suspend']['end'] = t.time
1154					continue
1155				elif re.match('dpm_suspend_late\[.*', name):
1156					if(isbegin):
1157						data.dmesg['suspend_late']['start'] = t.time
1158					else:
1159						data.dmesg['suspend_late']['end'] = t.time
1160					continue
1161				elif re.match('dpm_suspend_noirq\[.*', name):
1162					if(isbegin):
1163						data.dmesg['suspend_noirq']['start'] = t.time
1164					else:
1165						data.dmesg['suspend_noirq']['end'] = t.time
1166					continue
1167				elif re.match('dpm_resume_noirq\[.*', name):
1168					if(isbegin):
1169						data.dmesg['resume_machine']['end'] = t.time
1170						data.dmesg['resume_noirq']['start'] = t.time
1171					else:
1172						data.dmesg['resume_noirq']['end'] = t.time
1173					continue
1174				elif re.match('dpm_resume_early\[.*', name):
1175					if(isbegin):
1176						data.dmesg['resume_early']['start'] = t.time
1177					else:
1178						data.dmesg['resume_early']['end'] = t.time
1179					continue
1180				elif re.match('dpm_resume\[.*', name):
1181					if(isbegin):
1182						data.dmesg['resume']['start'] = t.time
1183					else:
1184						data.dmesg['resume']['end'] = t.time
1185					continue
1186				elif re.match('dpm_complete\[.*', name):
1187					if(isbegin):
1188						data.dmesg['resume_complete']['start'] = t.time
1189					else:
1190						data.dmesg['resume_complete']['end'] = t.time
1191					continue
1192				# is this trace event outside of the devices calls
1193				if(data.isTraceEventOutsideDeviceCalls(pid, t.time)):
1194					# global events (outside device calls) are simply graphed
1195					if(isbegin):
1196						# store each trace event in ttemp
1197						if(name not in testrun[testidx].ttemp):
1198							testrun[testidx].ttemp[name] = []
1199						testrun[testidx].ttemp[name].append(\
1200							{'begin': t.time, 'end': t.time})
1201					else:
1202						# finish off matching trace event in ttemp
1203						if(name in testrun[testidx].ttemp):
1204							testrun[testidx].ttemp[name][-1]['end'] = t.time
1205				else:
1206					if(isbegin):
1207						data.addIntraDevTraceEvent('', name, pid, t.time)
1208					else:
1209						data.capIntraDevTraceEvent('', name, pid, t.time)
1210			# call/return processing
1211			elif sysvals.usecallgraph:
1212				# create a callgraph object for the data
1213				if(pid not in testrun[testidx].ftemp):
1214					testrun[testidx].ftemp[pid] = []
1215					testrun[testidx].ftemp[pid].append(FTraceCallGraph())
1216				# when the call is finished, see which device matches it
1217				cg = testrun[testidx].ftemp[pid][-1]
1218				if(cg.addLine(t, m)):
1219					testrun[testidx].ftemp[pid].append(FTraceCallGraph())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1220	tf.close()
1221
1222	for test in testrun:
1223		# add the traceevent data to the device hierarchy
1224		if(sysvals.usetraceevents):
1225			for name in test.ttemp:
1226				for event in test.ttemp[name]:
1227					begin = event['begin']
1228					end = event['end']
1229					# if event starts before timeline start, expand timeline
1230					if(begin < test.data.start):
1231						test.data.setStart(begin)
1232					# if event ends after timeline end, expand the timeline
1233					if(end > test.data.end):
1234						test.data.setEnd(end)
1235					test.data.newActionGlobal(name, begin, end)
1236
1237		# add the callgraph data to the device hierarchy
1238		for pid in test.ftemp:
1239			for cg in test.ftemp[pid]:
1240				if(not cg.sanityCheck()):
 
 
1241					id = 'task %s cpu %s' % (pid, m.group('cpu'))
1242					vprint('Sanity check failed for '+\
1243						id+', ignoring this callback')
1244					continue
1245				callstart = cg.start
1246				callend = cg.end
1247				for p in test.data.phases:
1248					if(test.data.dmesg[p]['start'] <= callstart and
1249						callstart <= test.data.dmesg[p]['end']):
1250						list = test.data.dmesg[p]['list']
1251						for devname in list:
1252							dev = list[devname]
1253							if(pid == dev['pid'] and
1254								callstart <= dev['start'] and
1255								callend >= dev['end']):
1256								dev['ftrace'] = cg
1257						break
1258
1259		if(sysvals.verbose):
1260			test.data.printDetails()
1261
1262
1263	# add the time in between the tests as a new phase so we can see it
1264	if(len(testruns) > 1):
1265		t1e = testruns[0].getEnd()
1266		t2s = testruns[-1].getStart()
1267		testruns[-1].newPhaseWithSingleAction('user mode', \
1268			'user mode', t1e, t2s, '#FF9966')
1269
1270# Function: parseTraceLog
1271# Description:
1272#	 Analyze an ftrace log output file generated from this app during
1273#	 the execution phase. Used when the ftrace log is the primary data source
1274#	 and includes the suspend_resume and device_pm_callback trace events
1275#	 The ftrace filename is taken from sysvals
1276# Output:
1277#	 An array of Data objects
1278def parseTraceLog():
1279	global sysvals
1280
1281	vprint('Analyzing the ftrace data...')
1282	if(os.path.exists(sysvals.ftracefile) == False):
1283		doError('%s doesnt exist' % sysvals.ftracefile, False)
 
 
 
 
 
 
1284
1285	# extract the callgraph and traceevent data
 
1286	testruns = []
1287	testdata = []
1288	testrun = 0
1289	data = 0
1290	tf = open(sysvals.ftracefile, 'r')
1291	phase = 'suspend_prepare'
1292	for line in tf:
1293		# remove any latent carriage returns
1294		line = line.replace('\r\n', '')
1295		# stamp line: each stamp means a new test run
1296		m = re.match(sysvals.stampfmt, line)
1297		if(m):
1298			data = Data(len(testdata))
1299			testdata.append(data)
1300			testrun = TestRun(data)
1301			testruns.append(testrun)
1302			parseStamp(m, data)
1303			continue
1304		if(not data):
1305			continue
1306		# firmware line: pull out any firmware data
1307		m = re.match(sysvals.firmwarefmt, line)
1308		if(m):
1309			data.fwSuspend = int(m.group('s'))
1310			data.fwResume = int(m.group('r'))
1311			if(data.fwSuspend > 0 or data.fwResume > 0):
1312				data.fwValid = True
1313			continue
1314		# tracer type line: determine the trace data type
1315		m = re.match(sysvals.tracertypefmt, line)
1316		if(m):
1317			tracer = m.group('t')
1318			testrun.setTracerType(tracer)
1319			continue
1320		# post resume time line: did this test run include post-resume data
1321		m = re.match(sysvals.postresumefmt, line)
1322		if(m):
1323			t = int(m.group('t'))
1324			if(t > 0):
1325				sysvals.postresumetime = t
1326			continue
 
 
 
 
1327		# ftrace line: parse only valid lines
1328		m = re.match(testrun.ftrace_line_fmt, line)
1329		if(not m):
1330			continue
1331		# gather the basic message data from the line
1332		m_time = m.group('time')
 
1333		m_pid = m.group('pid')
1334		m_msg = m.group('msg')
1335		if(testrun.cgformat):
1336			m_param3 = m.group('dur')
1337		else:
1338			m_param3 = 'traceevent'
1339		if(m_time and m_pid and m_msg):
1340			t = FTraceLine(m_time, m_msg, m_param3)
1341			pid = int(m_pid)
1342		else:
1343			continue
1344		# the line should be a call, return, or event
1345		if(not t.fcall and not t.freturn and not t.fevent):
1346			continue
1347		# only parse the ftrace data during suspend/resume
1348		if(not testrun.inthepipe):
1349			# look for the suspend start marker
1350			if(t.fevent):
1351				if(t.name == 'SUSPEND START'):
1352					testrun.inthepipe = True
1353					data.setStart(t.time)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1354			continue
1355		# trace event processing
1356		if(t.fevent):
1357			if(t.name == 'RESUME COMPLETE'):
1358				if(sysvals.postresumetime > 0):
1359					phase = 'post_resume'
1360					data.newPhase(phase, t.time, t.time, '#FF9966', -1)
1361				else:
1362					testrun.inthepipe = False
1363				data.setEnd(t.time)
1364				continue
1365			if(phase == 'post_resume'):
1366				data.setEnd(t.time)
1367			if(t.type == 'suspend_resume'):
1368				# suspend_resume trace events have two types, begin and end
1369				if(re.match('(?P<name>.*) begin$', t.name)):
1370					isbegin = True
1371				elif(re.match('(?P<name>.*) end$', t.name)):
1372					isbegin = False
1373				else:
1374					continue
1375				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
1376				if(m):
1377					val = m.group('val')
1378					if val == '0':
1379						name = m.group('name')
1380					else:
1381						name = m.group('name')+'['+val+']'
1382				else:
1383					m = re.match('(?P<name>.*) .*', t.name)
1384					name = m.group('name')
1385				# ignore these events
1386				if(re.match('acpi_suspend\[.*', t.name) or
1387					re.match('suspend_enter\[.*', name)):
1388					continue
1389				# -- phase changes --
1390				# suspend_prepare start
1391				if(re.match('dpm_prepare\[.*', t.name)):
1392					phase = 'suspend_prepare'
1393					if(not isbegin):
1394						data.dmesg[phase]['end'] = t.time
1395					continue
1396				# suspend start
1397				elif(re.match('dpm_suspend\[.*', t.name)):
1398					phase = 'suspend'
1399					data.setPhase(phase, t.time, isbegin)
1400					continue
1401				# suspend_late start
1402				elif(re.match('dpm_suspend_late\[.*', t.name)):
1403					phase = 'suspend_late'
1404					data.setPhase(phase, t.time, isbegin)
1405					continue
1406				# suspend_noirq start
1407				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
1408					phase = 'suspend_noirq'
1409					data.setPhase(phase, t.time, isbegin)
1410					if(not isbegin):
1411						phase = 'suspend_machine'
1412						data.dmesg[phase]['start'] = t.time
1413					continue
1414				# suspend_machine/resume_machine
1415				elif(re.match('machine_suspend\[.*', t.name)):
1416					if(isbegin):
1417						phase = 'suspend_machine'
1418						data.dmesg[phase]['end'] = t.time
1419						data.tSuspended = t.time
1420					else:
1421						if(sysvals.suspendmode in ['mem', 'disk']):
1422							data.dmesg['suspend_machine']['end'] = t.time
1423							data.tSuspended = t.time
1424						phase = 'resume_machine'
1425						data.dmesg[phase]['start'] = t.time
1426						data.tResumed = t.time
1427						data.tLow = data.tResumed - data.tSuspended
1428					continue
 
 
 
 
 
 
 
 
 
1429				# resume_noirq start
1430				elif(re.match('dpm_resume_noirq\[.*', t.name)):
1431					phase = 'resume_noirq'
1432					data.setPhase(phase, t.time, isbegin)
1433					if(isbegin):
1434						data.dmesg['resume_machine']['end'] = t.time
1435					continue
1436				# resume_early start
1437				elif(re.match('dpm_resume_early\[.*', t.name)):
1438					phase = 'resume_early'
1439					data.setPhase(phase, t.time, isbegin)
1440					continue
1441				# resume start
1442				elif(re.match('dpm_resume\[.*', t.name)):
1443					phase = 'resume'
1444					data.setPhase(phase, t.time, isbegin)
1445					continue
1446				# resume complete start
1447				elif(re.match('dpm_complete\[.*', t.name)):
1448					phase = 'resume_complete'
1449					if(isbegin):
1450						data.dmesg[phase]['start'] = t.time
1451					continue
1452
1453				# is this trace event outside of the devices calls
1454				if(data.isTraceEventOutsideDeviceCalls(pid, t.time)):
1455					# global events (outside device calls) are simply graphed
1456					if(name not in testrun.ttemp):
1457						testrun.ttemp[name] = []
1458					if(isbegin):
1459						# create a new list entry
1460						testrun.ttemp[name].append(\
1461							{'begin': t.time, 'end': t.time})
1462					else:
1463						if(len(testrun.ttemp[name]) > 0):
1464							# if an antry exists, assume this is its end
1465							testrun.ttemp[name][-1]['end'] = t.time
1466						elif(phase == 'post_resume'):
1467							# post resume events can just have ends
1468							testrun.ttemp[name].append({
1469								'begin': data.dmesg[phase]['start'],
1470								'end': t.time})
1471				else:
1472					if(isbegin):
1473						data.addIntraDevTraceEvent('', name, pid, t.time)
1474					else:
1475						data.capIntraDevTraceEvent('', name, pid, t.time)
 
 
 
 
1476			# device callback start
1477			elif(t.type == 'device_pm_callback_start'):
1478				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
1479					t.name);
1480				if(not m):
1481					continue
1482				drv = m.group('drv')
1483				n = m.group('d')
1484				p = m.group('p')
1485				if(n and p):
1486					data.newAction(phase, n, pid, p, t.time, -1, drv)
1487			# device callback finish
1488			elif(t.type == 'device_pm_callback_end'):
1489				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
1490				if(not m):
1491					continue
1492				n = m.group('d')
1493				list = data.dmesg[phase]['list']
1494				if(n in list):
1495					dev = list[n]
1496					dev['length'] = t.time - dev['start']
1497					dev['end'] = t.time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1498		# callgraph processing
1499		elif sysvals.usecallgraph:
1500			# this shouldn't happen, but JIC, ignore callgraph data post-res
1501			if(phase == 'post_resume'):
1502				continue
1503			# create a callgraph object for the data
1504			if(pid not in testrun.ftemp):
1505				testrun.ftemp[pid] = []
1506				testrun.ftemp[pid].append(FTraceCallGraph())
 
1507			# when the call is finished, see which device matches it
1508			cg = testrun.ftemp[pid][-1]
1509			if(cg.addLine(t, m)):
1510				testrun.ftemp[pid].append(FTraceCallGraph())
1511	tf.close()
1512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1513	for test in testruns:
1514		# add the traceevent data to the device hierarchy
1515		if(sysvals.usetraceevents):
 
1516			for name in test.ttemp:
1517				for event in test.ttemp[name]:
1518					begin = event['begin']
1519					end = event['end']
1520					# if event starts before timeline start, expand timeline
1521					if(begin < test.data.start):
1522						test.data.setStart(begin)
1523					# if event ends after timeline end, expand the timeline
1524					if(end > test.data.end):
1525						test.data.setEnd(end)
1526					test.data.newActionGlobal(name, begin, end)
1527
1528		# add the callgraph data to the device hierarchy
1529		borderphase = {
1530			'dpm_prepare': 'suspend_prepare',
1531			'dpm_complete': 'resume_complete'
1532		}
1533		for pid in test.ftemp:
1534			for cg in test.ftemp[pid]:
1535				if len(cg.list) < 2:
1536					continue
1537				if(not cg.sanityCheck()):
1538					id = 'task %s cpu %s' % (pid, m.group('cpu'))
1539					vprint('Sanity check failed for '+\
1540						id+', ignoring this callback')
1541					continue
1542				callstart = cg.start
1543				callend = cg.end
1544				if(cg.list[0].name in borderphase):
1545					p = borderphase[cg.list[0].name]
1546					list = test.data.dmesg[p]['list']
1547					for devname in list:
1548						dev = list[devname]
1549						if(pid == dev['pid'] and
1550							callstart <= dev['start'] and
1551							callend >= dev['end']):
1552							dev['ftrace'] = cg.slice(dev['start'], dev['end'])
1553					continue
1554				if(cg.list[0].name != 'dpm_run_callback'):
1555					continue
1556				for p in test.data.phases:
1557					if(test.data.dmesg[p]['start'] <= callstart and
1558						callstart <= test.data.dmesg[p]['end']):
1559						list = test.data.dmesg[p]['list']
1560						for devname in list:
1561							dev = list[devname]
1562							if(pid == dev['pid'] and
1563								callstart <= dev['start'] and
1564								callend >= dev['end']):
1565								dev['ftrace'] = cg
1566						break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1567
1568	# fill in any missing phases
1569	for data in testdata:
1570		lp = data.phases[0]
1571		for p in data.phases:
1572			if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
1573				print('WARNING: phase "%s" is missing!' % p)
1574			if(data.dmesg[p]['start'] < 0):
1575				data.dmesg[p]['start'] = data.dmesg[lp]['end']
1576				if(p == 'resume_machine'):
1577					data.tSuspended = data.dmesg[lp]['end']
1578					data.tResumed = data.dmesg[lp]['end']
1579					data.tLow = 0
1580			if(data.dmesg[p]['end'] < 0):
1581				data.dmesg[p]['end'] = data.dmesg[p]['start']
1582			lp = p
1583
1584		if(len(sysvals.devicefilter) > 0):
1585			data.deviceFilter(sysvals.devicefilter)
1586		data.fixupInitcallsThatDidntReturn()
1587		if(sysvals.verbose):
1588			data.printDetails()
1589
1590	# add the time in between the tests as a new phase so we can see it
1591	if(len(testdata) > 1):
1592		t1e = testdata[0].getEnd()
1593		t2s = testdata[-1].getStart()
1594		testdata[-1].newPhaseWithSingleAction('user mode', \
1595			'user mode', t1e, t2s, '#FF9966')
1596	return testdata
1597
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1598# Function: loadKernelLog
1599# Description:
1600#	 [deprecated for kernel 3.15.0 or newer]
1601#	 load the dmesg file into memory and fix up any ordering issues
1602#	 The dmesg filename is taken from sysvals
1603# Output:
1604#	 An array of empty Data objects with only their dmesgtext attributes set
1605def loadKernelLog():
1606	global sysvals
1607
1608	vprint('Analyzing the dmesg data...')
1609	if(os.path.exists(sysvals.dmesgfile) == False):
1610		doError('%s doesnt exist' % sysvals.dmesgfile, False)
1611
1612	# there can be multiple test runs in a single file delineated by stamps
 
1613	testruns = []
1614	data = 0
1615	lf = open(sysvals.dmesgfile, 'r')
1616	for line in lf:
1617		line = line.replace('\r\n', '')
1618		idx = line.find('[')
1619		if idx > 1:
1620			line = line[idx:]
1621		m = re.match(sysvals.stampfmt, line)
1622		if(m):
1623			if(data):
1624				testruns.append(data)
1625			data = Data(len(testruns))
1626			parseStamp(m, data)
1627			continue
1628		if(not data):
1629			continue
1630		m = re.match(sysvals.firmwarefmt, line)
1631		if(m):
1632			data.fwSuspend = int(m.group('s'))
1633			data.fwResume = int(m.group('r'))
1634			if(data.fwSuspend > 0 or data.fwResume > 0):
1635				data.fwValid = True
1636			continue
1637		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1638		if(m):
1639			data.dmesgtext.append(line)
1640			if(re.match('ACPI: resume from mwait', m.group('msg'))):
1641				print('NOTE: This suspend appears to be freeze rather than'+\
1642					' %s, it will be treated as such' % sysvals.suspendmode)
1643				sysvals.suspendmode = 'freeze'
1644		else:
1645			vprint('ignoring dmesg line: %s' % line.replace('\n', ''))
1646	testruns.append(data)
 
 
 
 
 
 
 
 
 
 
 
 
1647	lf.close()
1648
1649	if(not data):
1650		print('ERROR: analyze_suspend header missing from dmesg log')
1651		sys.exit()
 
 
 
 
1652
1653	# fix lines with same timestamp/function with the call and return swapped
1654	for data in testruns:
1655		last = ''
1656		for line in data.dmesgtext:
1657			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
1658				'(?P<f>.*)\+ @ .*, parent: .*', line)
1659			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
1660				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
1661			if(mc and mr and (mc.group('t') == mr.group('t')) and
1662				(mc.group('f') == mr.group('f'))):
1663				i = data.dmesgtext.index(last)
1664				j = data.dmesgtext.index(line)
1665				data.dmesgtext[i] = line
1666				data.dmesgtext[j] = last
1667			last = line
1668	return testruns
1669
1670# Function: parseKernelLog
1671# Description:
1672#	 [deprecated for kernel 3.15.0 or newer]
1673#	 Analyse a dmesg log output file generated from this app during
1674#	 the execution phase. Create a set of device structures in memory
1675#	 for subsequent formatting in the html output file
1676#	 This call is only for legacy support on kernels where the ftrace
1677#	 data lacks the suspend_resume or device_pm_callbacks trace events.
1678# Arguments:
1679#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
1680# Output:
1681#	 The filled Data object
1682def parseKernelLog(data):
1683	global sysvals
1684
1685	phase = 'suspend_runtime'
1686
1687	if(data.fwValid):
1688		vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
1689			(data.fwSuspend, data.fwResume))
1690
1691	# dmesg phase match table
1692	dm = {
1693		'suspend_prepare': 'PM: Syncing filesystems.*',
1694		        'suspend': 'PM: Entering [a-z]* sleep.*',
1695		   'suspend_late': 'PM: suspend of devices complete after.*',
1696		  'suspend_noirq': 'PM: late suspend of devices complete after.*',
1697		'suspend_machine': 'PM: noirq suspend of devices complete after.*',
1698		 'resume_machine': 'ACPI: Low-level resume complete.*',
1699		   'resume_noirq': 'ACPI: Waking up from system sleep state.*',
1700		   'resume_early': 'PM: noirq resume of devices complete after.*',
1701		         'resume': 'PM: early resume of devices complete after.*',
1702		'resume_complete': 'PM: resume of devices complete after.*',
1703		    'post_resume': '.*Restarting tasks \.\.\..*',
1704	}
1705	if(sysvals.suspendmode == 'standby'):
1706		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
1707	elif(sysvals.suspendmode == 'disk'):
1708		dm['suspend_late'] = 'PM: freeze of devices complete after.*'
1709		dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
1710		dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
1711		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
1712		dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
1713		dm['resume'] = 'PM: early restore of devices complete after.*'
1714		dm['resume_complete'] = 'PM: restore of devices complete after.*'
1715	elif(sysvals.suspendmode == 'freeze'):
1716		dm['resume_machine'] = 'ACPI: resume from mwait'
1717
1718	# action table (expected events that occur and show up in dmesg)
1719	at = {
1720		'sync_filesystems': {
1721			'smsg': 'PM: Syncing filesystems.*',
1722			'emsg': 'PM: Preparing system for mem sleep.*' },
1723		'freeze_user_processes': {
1724			'smsg': 'Freezing user space processes .*',
1725			'emsg': 'Freezing remaining freezable tasks.*' },
1726		'freeze_tasks': {
1727			'smsg': 'Freezing remaining freezable tasks.*',
1728			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
1729		'ACPI prepare': {
1730			'smsg': 'ACPI: Preparing to enter system sleep state.*',
1731			'emsg': 'PM: Saving platform NVS memory.*' },
1732		'PM vns': {
1733			'smsg': 'PM: Saving platform NVS memory.*',
1734			'emsg': 'Disabling non-boot CPUs .*' },
1735	}
1736
1737	t0 = -1.0
1738	cpu_start = -1.0
1739	prevktime = -1.0
1740	actions = dict()
1741	for line in data.dmesgtext:
1742		# -- preprocessing --
1743		# parse each dmesg line into the time and message
1744		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1745		if(m):
1746			val = m.group('ktime')
1747			try:
1748				ktime = float(val)
1749			except:
1750				doWarning('INVALID DMESG LINE: '+\
1751					line.replace('\n', ''), 'dmesg')
1752				continue
1753			msg = m.group('msg')
1754			# initialize data start to first line time
1755			if t0 < 0:
1756				data.setStart(ktime)
1757				t0 = ktime
1758		else:
1759			continue
1760
1761		# hack for determining resume_machine end for freeze
1762		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
1763			and phase == 'resume_machine' and \
1764			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
1765			data.dmesg['resume_machine']['end'] = ktime
1766			phase = 'resume_noirq'
1767			data.dmesg[phase]['start'] = ktime
1768
1769		# -- phase changes --
1770		# suspend start
1771		if(re.match(dm['suspend_prepare'], msg)):
1772			phase = 'suspend_prepare'
1773			data.dmesg[phase]['start'] = ktime
1774			data.setStart(ktime)
1775		# suspend start
1776		elif(re.match(dm['suspend'], msg)):
1777			data.dmesg['suspend_prepare']['end'] = ktime
1778			phase = 'suspend'
1779			data.dmesg[phase]['start'] = ktime
1780		# suspend_late start
1781		elif(re.match(dm['suspend_late'], msg)):
1782			data.dmesg['suspend']['end'] = ktime
1783			phase = 'suspend_late'
1784			data.dmesg[phase]['start'] = ktime
1785		# suspend_noirq start
1786		elif(re.match(dm['suspend_noirq'], msg)):
1787			data.dmesg['suspend_late']['end'] = ktime
1788			phase = 'suspend_noirq'
1789			data.dmesg[phase]['start'] = ktime
1790		# suspend_machine start
1791		elif(re.match(dm['suspend_machine'], msg)):
1792			data.dmesg['suspend_noirq']['end'] = ktime
1793			phase = 'suspend_machine'
1794			data.dmesg[phase]['start'] = ktime
1795		# resume_machine start
1796		elif(re.match(dm['resume_machine'], msg)):
1797			if(sysvals.suspendmode in ['freeze', 'standby']):
1798				data.tSuspended = prevktime
1799				data.dmesg['suspend_machine']['end'] = prevktime
1800			else:
1801				data.tSuspended = ktime
1802				data.dmesg['suspend_machine']['end'] = ktime
1803			phase = 'resume_machine'
1804			data.tResumed = ktime
1805			data.tLow = data.tResumed - data.tSuspended
1806			data.dmesg[phase]['start'] = ktime
1807		# resume_noirq start
1808		elif(re.match(dm['resume_noirq'], msg)):
1809			data.dmesg['resume_machine']['end'] = ktime
1810			phase = 'resume_noirq'
1811			data.dmesg[phase]['start'] = ktime
1812		# resume_early start
1813		elif(re.match(dm['resume_early'], msg)):
1814			data.dmesg['resume_noirq']['end'] = ktime
1815			phase = 'resume_early'
1816			data.dmesg[phase]['start'] = ktime
1817		# resume start
1818		elif(re.match(dm['resume'], msg)):
1819			data.dmesg['resume_early']['end'] = ktime
1820			phase = 'resume'
1821			data.dmesg[phase]['start'] = ktime
1822		# resume complete start
1823		elif(re.match(dm['resume_complete'], msg)):
1824			data.dmesg['resume']['end'] = ktime
1825			phase = 'resume_complete'
1826			data.dmesg[phase]['start'] = ktime
1827		# post resume start
1828		elif(re.match(dm['post_resume'], msg)):
1829			data.dmesg['resume_complete']['end'] = ktime
1830			data.setEnd(ktime)
1831			phase = 'post_resume'
1832			break
1833
1834		# -- device callbacks --
1835		if(phase in data.phases):
1836			# device init call
1837			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
1838				sm = re.match('calling  (?P<f>.*)\+ @ '+\
1839					'(?P<n>.*), parent: (?P<p>.*)', msg);
1840				f = sm.group('f')
1841				n = sm.group('n')
1842				p = sm.group('p')
1843				if(f and n and p):
1844					data.newAction(phase, f, int(n), p, ktime, -1, '')
1845			# device init return
1846			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
1847				'(?P<t>.*) usecs', msg)):
1848				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
1849					'(?P<t>.*) usecs(?P<a>.*)', msg);
1850				f = sm.group('f')
1851				t = sm.group('t')
1852				list = data.dmesg[phase]['list']
1853				if(f in list):
1854					dev = list[f]
1855					dev['length'] = int(t)
1856					dev['end'] = ktime
1857
1858		# -- non-devicecallback actions --
1859		# if trace events are not available, these are better than nothing
1860		if(not sysvals.usetraceevents):
1861			# look for known actions
1862			for a in at:
1863				if(re.match(at[a]['smsg'], msg)):
1864					if(a not in actions):
1865						actions[a] = []
1866					actions[a].append({'begin': ktime, 'end': ktime})
1867				if(re.match(at[a]['emsg'], msg)):
1868					actions[a][-1]['end'] = ktime
 
1869			# now look for CPU on/off events
1870			if(re.match('Disabling non-boot CPUs .*', msg)):
1871				# start of first cpu suspend
1872				cpu_start = ktime
1873			elif(re.match('Enabling non-boot CPUs .*', msg)):
1874				# start of first cpu resume
1875				cpu_start = ktime
1876			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
1877				# end of a cpu suspend, start of the next
1878				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
1879				cpu = 'CPU'+m.group('cpu')
1880				if(cpu not in actions):
1881					actions[cpu] = []
1882				actions[cpu].append({'begin': cpu_start, 'end': ktime})
1883				cpu_start = ktime
1884			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
1885				# end of a cpu resume, start of the next
1886				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
1887				cpu = 'CPU'+m.group('cpu')
1888				if(cpu not in actions):
1889					actions[cpu] = []
1890				actions[cpu].append({'begin': cpu_start, 'end': ktime})
1891				cpu_start = ktime
1892		prevktime = ktime
1893
1894	# fill in any missing phases
1895	lp = data.phases[0]
1896	for p in data.phases:
1897		if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
1898			print('WARNING: phase "%s" is missing, something went wrong!' % p)
1899			print('    In %s, this dmesg line denotes the start of %s:' % \
1900				(sysvals.suspendmode, p))
1901			print('        "%s"' % dm[p])
1902		if(data.dmesg[p]['start'] < 0):
1903			data.dmesg[p]['start'] = data.dmesg[lp]['end']
1904			if(p == 'resume_machine'):
1905				data.tSuspended = data.dmesg[lp]['end']
1906				data.tResumed = data.dmesg[lp]['end']
1907				data.tLow = 0
1908		if(data.dmesg[p]['end'] < 0):
1909			data.dmesg[p]['end'] = data.dmesg[p]['start']
1910		lp = p
1911
1912	# fill in any actions we've found
1913	for name in actions:
1914		for event in actions[name]:
1915			begin = event['begin']
1916			end = event['end']
1917			# if event starts before timeline start, expand timeline
1918			if(begin < data.start):
1919				data.setStart(begin)
1920			# if event ends after timeline end, expand the timeline
1921			if(end > data.end):
1922				data.setEnd(end)
1923			data.newActionGlobal(name, begin, end)
1924
1925	if(sysvals.verbose):
1926		data.printDetails()
1927	if(len(sysvals.devicefilter) > 0):
1928		data.deviceFilter(sysvals.devicefilter)
1929	data.fixupInitcallsThatDidntReturn()
1930	return True
1931
1932# Function: setTimelineRows
1933# Description:
1934#	 Organize the timeline entries into the smallest
1935#	 number of rows possible, with no entry overlapping
1936# Arguments:
1937#	 list: the list of devices/actions for a single phase
1938#	 sortedkeys: cronologically sorted key list to use
1939# Output:
1940#	 The total number of rows needed to display this phase of the timeline
1941def setTimelineRows(list, sortedkeys):
1942
1943	# clear all rows and set them to undefined
1944	remaining = len(list)
1945	rowdata = dict()
1946	row = 0
1947	for item in list:
1948		list[item]['row'] = -1
1949
1950	# try to pack each row with as many ranges as possible
1951	while(remaining > 0):
1952		if(row not in rowdata):
1953			rowdata[row] = []
1954		for item in sortedkeys:
1955			if(list[item]['row'] < 0):
1956				s = list[item]['start']
1957				e = list[item]['end']
1958				valid = True
1959				for ritem in rowdata[row]:
1960					rs = ritem['start']
1961					re = ritem['end']
1962					if(not (((s <= rs) and (e <= rs)) or
1963						((s >= re) and (e >= re)))):
1964						valid = False
1965						break
1966				if(valid):
1967					rowdata[row].append(list[item])
1968					list[item]['row'] = row
1969					remaining -= 1
1970		row += 1
1971	return row
1972
1973# Function: createTimeScale
1974# Description:
1975#	 Create the timescale header for the html timeline
1976# Arguments:
1977#	 t0: start time (suspend begin)
1978#	 tMax: end time (resume end)
1979#	 tSuspend: time when suspend occurs, i.e. the zero time
1980# Output:
1981#	 The html code needed to display the time scale
1982def createTimeScale(t0, tMax, tSuspended):
1983	timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
1984	output = '<div id="timescale">\n'
1985
1986	# set scale for timeline
1987	tTotal = tMax - t0
1988	tS = 0.1
1989	if(tTotal <= 0):
1990		return output
1991	if(tTotal > 4):
1992		tS = 1
1993	if(tSuspended < 0):
1994		for i in range(int(tTotal/tS)+1):
1995			pos = '%0.3f' % (100 - ((float(i)*tS*100)/tTotal))
1996			if(i > 0):
1997				val = '%0.fms' % (float(i)*tS*1000)
1998			else:
1999				val = ''
2000			output += timescale.format(pos, val)
2001	else:
2002		tSuspend = tSuspended - t0
2003		divTotal = int(tTotal/tS) + 1
2004		divSuspend = int(tSuspend/tS)
2005		s0 = (tSuspend - tS*divSuspend)*100/tTotal
2006		for i in range(divTotal):
2007			pos = '%0.3f' % (100 - ((float(i)*tS*100)/tTotal) - s0)
2008			if((i == 0) and (s0 < 3)):
2009				val = ''
2010			elif(i == divSuspend):
2011				val = 'S/R'
2012			else:
2013				val = '%0.fms' % (float(i-divSuspend)*tS*1000)
2014			output += timescale.format(pos, val)
2015	output += '</div>\n'
2016	return output
2017
2018# Function: createHTMLSummarySimple
2019# Description:
2020#	 Create summary html file for a series of tests
2021# Arguments:
2022#	 testruns: array of Data objects from parseTraceLog
2023def createHTMLSummarySimple(testruns, htmlfile):
2024	global sysvals
2025
2026	# print out the basic summary of all the tests
2027	hf = open(htmlfile, 'w')
2028
2029	# write the html header first (html head, css code, up to body start)
2030	html = '<!DOCTYPE html>\n<html>\n<head>\n\
2031	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
2032	<title>AnalyzeSuspend Summary</title>\n\
2033	<style type=\'text/css\'>\n\
2034		body {overflow-y: scroll;}\n\
2035		.stamp {width: 100%;text-align:center;background-color:#495E09;line-height:30px;color:white;font: 25px Arial;}\n\
2036		table {width:100%;border-collapse: collapse;}\n\
2037		.summary {font: 22px Arial;border:1px solid;}\n\
2038		th {border: 1px solid black;background-color:#A7C942;color:white;}\n\
2039		td {text-align: center;}\n\
2040		tr.alt td {background-color:#EAF2D3;}\n\
2041		tr.avg td {background-color:#BDE34C;}\n\
2042		a:link {color: #90B521;}\n\
2043		a:visited {color: #495E09;}\n\
2044		a:hover {color: #B1DF28;}\n\
2045		a:active {color: #FFFFFF;}\n\
2046	</style>\n</head>\n<body>\n'
2047
2048	# group test header
2049	count = len(testruns)
2050	headline_stamp = '<div class="stamp">{0} {1} {2} {3} ({4} tests)</div>\n'
2051	html += headline_stamp.format(sysvals.stamp['host'],
2052		sysvals.stamp['kernel'], sysvals.stamp['mode'],
2053		sysvals.stamp['time'], count)
2054
2055	# check to see if all the tests have the same value
2056	stampcolumns = False
2057	for data in testruns:
2058		if diffStamp(sysvals.stamp, data.stamp):
2059			stampcolumns = True
2060			break
2061
2062	th = '\t<th>{0}</th>\n'
2063	td = '\t<td>{0}</td>\n'
2064	tdlink = '\t<td><a href="{0}">Click Here</a></td>\n'
2065
2066	# table header
2067	html += '<table class="summary">\n<tr>\n'
2068	html += th.format("Test #")
2069	if stampcolumns:
2070		html += th.format("Hostname")
2071		html += th.format("Kernel Version")
2072		html += th.format("Suspend Mode")
2073	html += th.format("Test Time")
2074	html += th.format("Suspend Time")
2075	html += th.format("Resume Time")
2076	html += th.format("Detail")
2077	html += '</tr>\n'
2078
2079	# test data, 1 row per test
2080	sTimeAvg = 0.0
2081	rTimeAvg = 0.0
2082	num = 1
2083	for data in testruns:
2084		# data.end is the end of post_resume
2085		resumeEnd = data.dmesg['resume_complete']['end']
2086		if num % 2 == 1:
2087			html += '<tr class="alt">\n'
2088		else:
2089			html += '<tr>\n'
2090
2091		# test num
2092		html += td.format("test %d" % num)
2093		num += 1
2094		if stampcolumns:
2095			# host name
2096			val = "unknown"
2097			if('host' in data.stamp):
2098				val = data.stamp['host']
2099			html += td.format(val)
2100			# host kernel
2101			val = "unknown"
2102			if('kernel' in data.stamp):
2103				val = data.stamp['kernel']
2104			html += td.format(val)
2105			# suspend mode
2106			val = "unknown"
2107			if('mode' in data.stamp):
2108				val = data.stamp['mode']
2109			html += td.format(val)
2110		# test time
2111		val = "unknown"
2112		if('time' in data.stamp):
2113			val = data.stamp['time']
2114		html += td.format(val)
2115		# suspend time
2116		sTime = (data.tSuspended - data.start)*1000
2117		sTimeAvg += sTime
2118		html += td.format("%3.3f ms" % sTime)
2119		# resume time
2120		rTime = (resumeEnd - data.tResumed)*1000
2121		rTimeAvg += rTime
2122		html += td.format("%3.3f ms" % rTime)
2123		# link to the output html
2124		html += tdlink.format(data.outfile)
2125
2126		html += '</tr>\n'
2127
2128	# last line: test average
2129	if(count > 0):
2130		sTimeAvg /= count
2131		rTimeAvg /= count
2132	html += '<tr class="avg">\n'
2133	html += td.format('Average') 	# name
2134	if stampcolumns:
2135		html += td.format('')			# host
2136		html += td.format('')			# kernel
2137		html += td.format('')			# mode
2138	html += td.format('')			# time
2139	html += td.format("%3.3f ms" % sTimeAvg)	# suspend time
2140	html += td.format("%3.3f ms" % rTimeAvg)	# resume time
2141	html += td.format('')			# output link
2142	html += '</tr>\n'
2143
2144	# flush the data to file
2145	hf.write(html+'</table>\n')
2146	hf.write('</body>\n</html>\n')
2147	hf.close()
2148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2149# Function: createHTML
2150# Description:
2151#	 Create the output html file from the resident test data
2152# Arguments:
2153#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
2154# Output:
2155#	 True if the html file was created, false if it failed
2156def createHTML(testruns):
2157	global sysvals
2158
 
 
 
 
2159	for data in testruns:
2160		data.normalizeTime(testruns[-1].tSuspended)
2161
2162	x2changes = ['', 'absolute']
2163	if len(testruns) > 1:
2164		x2changes = ['1', 'relative']
2165	# html function templates
 
2166	headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2167	html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail%s</button>' % x2changes[0]
2168	html_zoombox = '<center><button id="zoomin">ZOOM IN</button><button id="zoomout">ZOOM OUT</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2169	html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2170	html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2171	html_device = '<div id="{0}" title="{1}" class="thread" style="left:{2}%;top:{3}%;height:{4}%;width:{5}%;">{6}</div>\n'
2172	html_traceevent = '<div title="{0}" class="traceevent" style="left:{1}%;top:{2}%;height:{3}%;width:{4}%;border:1px solid {5};background-color:{5}">{6}</div>\n'
2173	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}%;height:{3}%;background-color:{4}">{5}</div>\n'
 
2174	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background-color:{3}"></div>\n'
2175	html_legend = '<div class="square" style="left:{0}%;background-color:{1}">&nbsp;{2}</div>\n'
2176	html_timetotal = '<table class="time1">\n<tr>'\
2177		'<td class="green">{2} Suspend Time: <b>{0} ms</b></td>'\
2178		'<td class="yellow">{2} Resume Time: <b>{1} ms</b></td>'\
2179		'</tr>\n</table>\n'
2180	html_timetotal2 = '<table class="time1">\n<tr>'\
2181		'<td class="green">{3} Suspend Time: <b>{0} ms</b></td>'\
2182		'<td class="gray">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
2183		'<td class="yellow">{3} Resume Time: <b>{2} ms</b></td>'\
2184		'</tr>\n</table>\n'
 
 
 
 
2185	html_timegroups = '<table class="time2">\n<tr>'\
2186		'<td class="green">{4}Kernel Suspend: {0} ms</td>'\
2187		'<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
2188		'<td class="purple">{4}Firmware Resume: {2} ms</td>'\
2189		'<td class="yellow">{4}Kernel Resume: {3} ms</td>'\
2190		'</tr>\n</table>\n'
2191
 
 
 
 
 
 
 
 
 
2192	# device timeline
2193	vprint('Creating Device Timeline...')
2194	devtl = Timeline()
 
2195
2196	# Generate the header for this timeline
2197	textnum = ['First', 'Second']
2198	for data in testruns:
2199		tTotal = data.end - data.start
2200		tEnd = data.dmesg['resume_complete']['end']
2201		if(tTotal == 0):
2202			print('ERROR: No timeline data')
2203			sys.exit()
2204		if(data.tLow > 0):
2205			low_time = '%.0f'%(data.tLow*1000)
2206		if data.fwValid:
 
 
 
 
 
 
 
 
 
 
2207			suspend_time = '%.0f'%((data.tSuspended-data.start)*1000 + \
2208				(data.fwSuspend/1000000.0))
2209			resume_time = '%.0f'%((tEnd-data.tSuspended)*1000 + \
2210				(data.fwResume/1000000.0))
2211			testdesc1 = 'Total'
2212			testdesc2 = ''
2213			if(len(testruns) > 1):
2214				testdesc1 = testdesc2 = textnum[data.testnumber]
2215				testdesc2 += ' '
2216			if(data.tLow == 0):
2217				thtml = html_timetotal.format(suspend_time, \
2218					resume_time, testdesc1)
2219			else:
2220				thtml = html_timetotal2.format(suspend_time, low_time, \
2221					resume_time, testdesc1)
2222			devtl.html['timeline'] += thtml
2223			sktime = '%.3f'%((data.dmesg['suspend_machine']['end'] - \
2224				data.getStart())*1000)
2225			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
2226			rftime = '%.3f'%(data.fwResume / 1000000.0)
2227			rktime = '%.3f'%((data.getEnd() - \
2228				data.dmesg['resume_machine']['start'])*1000)
2229			devtl.html['timeline'] += html_timegroups.format(sktime, \
2230				sftime, rftime, rktime, testdesc2)
2231		else:
2232			suspend_time = '%.0f'%((data.tSuspended-data.start)*1000)
2233			resume_time = '%.0f'%((tEnd-data.tSuspended)*1000)
2234			testdesc = 'Kernel'
2235			if(len(testruns) > 1):
2236				testdesc = textnum[data.testnumber]+' '+testdesc
2237			if(data.tLow == 0):
2238				thtml = html_timetotal.format(suspend_time, \
2239					resume_time, testdesc)
2240			else:
2241				thtml = html_timetotal2.format(suspend_time, low_time, \
2242					resume_time, testdesc)
2243			devtl.html['timeline'] += thtml
2244
2245	# time scale for potentially multiple datasets
2246	t0 = testruns[0].start
2247	tMax = testruns[-1].end
2248	tSuspended = testruns[-1].tSuspended
2249	tTotal = tMax - t0
2250
2251	# determine the maximum number of rows we need to draw
2252	timelinerows = 0
2253	for data in testruns:
2254		for phase in data.dmesg:
2255			list = data.dmesg[phase]['list']
2256			rows = setTimelineRows(list, list)
2257			data.dmesg[phase]['row'] = rows
2258			if(rows > timelinerows):
2259				timelinerows = rows
2260
2261	# calculate the timeline height and create bounding box, add buttons
2262	devtl.setRows(timelinerows + 1)
2263	devtl.html['timeline'] += html_devlist1
2264	if len(testruns) > 1:
2265		devtl.html['timeline'] += html_devlist2
 
 
2266	devtl.html['timeline'] += html_zoombox
2267	devtl.html['timeline'] += html_timeline.format('dmesg', devtl.height)
2268
2269	# draw the colored boxes for each of the phases
2270	for data in testruns:
2271		for b in data.dmesg:
2272			phase = data.dmesg[b]
2273			length = phase['end']-phase['start']
2274			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
2275			width = '%.3f' % ((length*100.0)/tTotal)
2276			devtl.html['timeline'] += html_phase.format(left, width, \
2277				'%.3f'%devtl.scaleH, '%.3f'%(100-devtl.scaleH), \
2278				data.dmesg[b]['color'], '')
2279
2280	# draw the time scale, try to make the number of labels readable
2281	devtl.html['scale'] = createTimeScale(t0, tMax, tSuspended)
2282	devtl.html['timeline'] += devtl.html['scale']
2283	for data in testruns:
2284		for b in data.dmesg:
2285			phaselist = data.dmesg[b]['list']
2286			for d in phaselist:
2287				name = d
2288				drv = ''
2289				dev = phaselist[d]
2290				if(d in sysvals.altdevname):
2291					name = sysvals.altdevname[d]
2292				if('drv' in dev and dev['drv']):
2293					drv = ' {%s}' % dev['drv']
2294				height = (100.0 - devtl.scaleH)/data.dmesg[b]['row']
2295				top = '%.3f' % ((dev['row']*height) + devtl.scaleH)
2296				left = '%.3f' % (((dev['start']-t0)*100)/tTotal)
2297				width = '%.3f' % (((dev['end']-dev['start'])*100)/tTotal)
2298				length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
2299				color = 'rgba(204,204,204,0.5)'
2300				devtl.html['timeline'] += html_device.format(dev['id'], \
2301					d+drv+length+b, left, top, '%.3f'%height, width, name+drv)
2302
2303	# draw any trace events found
2304	for data in testruns:
2305		for b in data.dmesg:
2306			phaselist = data.dmesg[b]['list']
2307			for name in phaselist:
2308				dev = phaselist[name]
2309				if('traceevents' in dev):
2310					vprint('Debug trace events found for device %s' % name)
2311					vprint('%20s %20s %10s %8s' % ('action', \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2312						'name', 'time(ms)', 'length(ms)'))
2313					for e in dev['traceevents']:
2314						vprint('%20s %20s %10.3f %8.3f' % (e.action, \
2315							e.name, e.time*1000, e.length*1000))
2316						height = (100.0 - devtl.scaleH)/data.dmesg[b]['row']
2317						top = '%.3f' % ((dev['row']*height) + devtl.scaleH)
2318						left = '%.3f' % (((e.time-t0)*100)/tTotal)
2319						width = '%.3f' % (e.length*100/tTotal)
2320						color = 'rgba(204,204,204,0.5)'
2321						devtl.html['timeline'] += \
2322							html_traceevent.format(e.action+' '+e.name, \
2323								left, top, '%.3f'%height, \
2324								width, e.color, '')
 
 
 
2325
2326	# timeline is finished
2327	devtl.html['timeline'] += '</div>\n</div>\n'
2328
2329	# draw a legend which describes the phases by color
2330	data = testruns[-1]
2331	devtl.html['legend'] = '<div class="legend">\n'
2332	pdelta = 100.0/len(data.phases)
2333	pmargin = pdelta / 4.0
2334	for phase in data.phases:
2335		order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
2336		name = string.replace(phase, '_', ' &nbsp;')
2337		devtl.html['legend'] += html_legend.format(order, \
2338			data.dmesg[phase]['color'], name)
2339	devtl.html['legend'] += '</div>\n'
 
 
 
 
 
2340
2341	hf = open(sysvals.htmlfile, 'w')
2342	thread_height = 0
 
 
 
 
 
 
2343
2344	# write the html header first (html head, css code, up to body start)
2345	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
2346	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
2347	<title>AnalyzeSuspend</title>\n\
2348	<style type=\'text/css\'>\n\
2349		body {overflow-y: scroll;}\n\
2350		.stamp {width: 100%;text-align:center;background-color:gray;line-height:30px;color:white;font: 25px Arial;}\n\
2351		.callgraph {margin-top: 30px;box-shadow: 5px 5px 20px black;}\n\
2352		.callgraph article * {padding-left: 28px;}\n\
2353		h1 {color:black;font: bold 30px Times;}\n\
2354		t0 {color:black;font: bold 30px Times;}\n\
2355		t1 {color:black;font: 30px Times;}\n\
2356		t2 {color:black;font: 25px Times;}\n\
2357		t3 {color:black;font: 20px Times;white-space:nowrap;}\n\
2358		t4 {color:black;font: bold 30px Times;line-height:60px;white-space:nowrap;}\n\
 
 
2359		table {width:100%;}\n\
2360		.gray {background-color:rgba(80,80,80,0.1);}\n\
2361		.green {background-color:rgba(204,255,204,0.4);}\n\
2362		.purple {background-color:rgba(128,0,128,0.2);}\n\
2363		.yellow {background-color:rgba(255,255,204,0.4);}\n\
2364		.time1 {font: 22px Arial;border:1px solid;}\n\
2365		.time2 {font: 15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
2366		td {text-align: center;}\n\
2367		r {color:#500000;font:15px Tahoma;}\n\
2368		n {color:#505050;font:15px Tahoma;}\n\
2369		.tdhl {color: red;}\n\
2370		.hide {display: none;}\n\
2371		.pf {display: none;}\n\
2372		.pf:checked + label {background: url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
2373		.pf:not(:checked) ~ label {background: url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
2374		.pf:checked ~ *:not(:nth-child(2)) {display: none;}\n\
2375		.zoombox {position: relative; width: 100%; overflow-x: scroll;}\n\
2376		.timeline {position: relative; font-size: 14px;cursor: pointer;width: 100%; overflow: hidden; background-color:#dddddd;}\n\
2377		.thread {position: absolute; height: '+'%.3f'%thread_height+'%; overflow: hidden; line-height: 30px; border:1px solid;text-align:center;white-space:nowrap;background-color:rgba(204,204,204,0.5);}\n\
2378		.thread:hover {background-color:white;border:1px solid red;z-index:10;}\n\
2379		.hover {background-color:white;border:1px solid red;z-index:10;}\n\
2380		.traceevent {position: absolute;opacity: 0.3;height: '+'%.3f'%thread_height+'%;width:0;overflow:hidden;line-height:30px;text-align:center;white-space:nowrap;}\n\
2381		.phase {position: absolute;overflow: hidden;border:0px;text-align:center;}\n\
 
 
 
 
 
2382		.phaselet {position:absolute;overflow:hidden;border:0px;text-align:center;height:100px;font-size:24px;}\n\
2383		.t {position:absolute;top:0%;height:100%;border-right:1px solid black;}\n\
2384		.legend {position: relative; width: 100%; height: 40px; text-align: center;margin-bottom:20px}\n\
2385		.legend .square {position:absolute;top:10px; width: 0px;height: 20px;border:1px solid;padding-left:20px;}\n\
2386		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
 
2387		.devlist {position:'+x2changes[1]+';width:190px;}\n\
2388		#devicedetail {height:100px;box-shadow: 5px 5px 20px black;}\n\
 
 
 
 
 
 
 
2389	</style>\n</head>\n<body>\n'
2390	hf.write(html_header)
 
 
 
 
 
 
 
2391
2392	# write the test title and general info header
2393	if(sysvals.stamp['time'] != ""):
 
 
 
 
 
2394		hf.write(headline_stamp.format(sysvals.stamp['host'],
2395			sysvals.stamp['kernel'], sysvals.stamp['mode'], \
2396				sysvals.stamp['time']))
2397
2398	# write the device timeline
 
2399	hf.write(devtl.html['timeline'])
2400	hf.write(devtl.html['legend'])
2401	hf.write('<div id="devicedetailtitle"></div>\n')
2402	hf.write('<div id="devicedetail" style="display:none;">\n')
2403	# draw the colored boxes for the device detail section
2404	for data in testruns:
2405		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
2406		for b in data.phases:
2407			phase = data.dmesg[b]
2408			length = phase['end']-phase['start']
2409			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
2410			width = '%.3f' % ((length*100.0)/tTotal)
2411			hf.write(html_phaselet.format(b, left, width, \
2412				data.dmesg[b]['color']))
 
 
 
2413		hf.write('</div>\n')
2414	hf.write('</div>\n')
2415
2416	# write the ftrace data (callgraph)
2417	data = testruns[-1]
2418	if(sysvals.usecallgraph):
2419		hf.write('<section id="callgraphs" class="callgraph">\n')
2420		# write out the ftrace data converted to html
2421		html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
2422		html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
2423		html_func_end = '</article>\n'
2424		html_func_leaf = '<article>{0} {1}</article>\n'
2425		num = 0
2426		for p in data.phases:
2427			list = data.dmesg[p]['list']
2428			for devname in data.sortedDevices(p):
2429				if('ftrace' not in list[devname]):
2430					continue
2431				name = devname
2432				if(devname in sysvals.altdevname):
2433					name = sysvals.altdevname[devname]
2434				devid = list[devname]['id']
2435				cg = list[devname]['ftrace']
2436				flen = '<r>(%.3f ms @ %.3f to %.3f)</r>' % \
2437					((cg.end - cg.start)*1000, cg.start*1000, cg.end*1000)
 
 
 
 
 
 
 
 
 
 
2438				hf.write(html_func_top.format(devid, data.dmesg[p]['color'], \
2439					num, name+' '+p, flen))
2440				num += 1
2441				for line in cg.list:
2442					if(line.length < 0.000000001):
2443						flen = ''
2444					else:
2445						flen = '<n>(%.3f ms @ %.3f)</n>' % (line.length*1000, \
2446							line.time*1000)
2447					if(line.freturn and line.fcall):
2448						hf.write(html_func_leaf.format(line.name, flen))
2449					elif(line.freturn):
2450						hf.write(html_func_end)
2451					else:
2452						hf.write(html_func_start.format(num, line.name, flen))
2453						num += 1
2454				hf.write(html_func_end)
2455		hf.write('\n\n    </section>\n')
2456	# write the footer and close
2457	addScriptCode(hf, testruns)
2458	hf.write('</body>\n</html>\n')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2459	hf.close()
2460	return True
2461
2462# Function: addScriptCode
2463# Description:
2464#	 Adds the javascript code to the output html
2465# Arguments:
2466#	 hf: the open html file pointer
2467#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
2468def addScriptCode(hf, testruns):
2469	t0 = (testruns[0].start - testruns[-1].tSuspended) * 1000
2470	tMax = (testruns[-1].end - testruns[-1].tSuspended) * 1000
2471	# create an array in javascript memory with the device details
2472	detail = '	var devtable = [];\n'
2473	for data in testruns:
2474		topo = data.deviceTopology()
2475		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
2476	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
2477	# add the code which will manipulate the data in the browser
2478	script_code = \
2479	'<script type="text/javascript">\n'+detail+\
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2480	'	function zoomTimeline() {\n'\
2481	'		var timescale = document.getElementById("timescale");\n'\
2482	'		var dmesg = document.getElementById("dmesg");\n'\
2483	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
2484	'		var val = parseFloat(dmesg.style.width);\n'\
2485	'		var newval = 100;\n'\
2486	'		var sh = window.outerWidth / 2;\n'\
2487	'		if(this.id == "zoomin") {\n'\
2488	'			newval = val * 1.2;\n'\
2489	'			if(newval > 40000) newval = 40000;\n'\
2490	'			dmesg.style.width = newval+"%";\n'\
2491	'			zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
2492	'		} else if (this.id == "zoomout") {\n'\
2493	'			newval = val / 1.2;\n'\
2494	'			if(newval < 100) newval = 100;\n'\
2495	'			dmesg.style.width = newval+"%";\n'\
2496	'			zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
2497	'		} else {\n'\
2498	'			zoombox.scrollLeft = 0;\n'\
2499	'			dmesg.style.width = "100%";\n'\
2500	'		}\n'\
2501	'		var html = "";\n'\
2502	'		var t0 = bounds[0];\n'\
2503	'		var tMax = bounds[1];\n'\
2504	'		var tTotal = tMax - t0;\n'\
2505	'		var wTotal = tTotal * 100.0 / newval;\n'\
2506	'		for(var tS = 1000; (wTotal / tS) < 3; tS /= 10);\n'\
2507	'		if(tS < 1) tS = 1;\n'\
2508	'		for(var s = ((t0 / tS)|0) * tS; s < tMax; s += tS) {\n'\
2509	'			var pos = (tMax - s) * 100.0 / tTotal;\n'\
2510	'			var name = (s == 0)?"S/R":(s+"ms");\n'\
2511	'			html += "<div class=\\"t\\" style=\\"right:"+pos+"%\\">"+name+"</div>";\n'\
2512	'		}\n'\
2513	'		timescale.innerHTML = html;\n'\
2514	'	}\n'\
2515	'	function deviceHover() {\n'\
2516	'		var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
2517	'		var dmesg = document.getElementById("dmesg");\n'\
2518	'		var dev = dmesg.getElementsByClassName("thread");\n'\
2519	'		var cpu = -1;\n'\
2520	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
2521	'			cpu = parseInt(name.slice(7));\n'\
2522	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
2523	'			cpu = parseInt(name.slice(8));\n'\
2524	'		for (var i = 0; i < dev.length; i++) {\n'\
2525	'			dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
 
2526	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
2527	'				(name == dname))\n'\
2528	'			{\n'\
2529	'				dev[i].className = "thread hover";\n'\
2530	'			} else {\n'\
2531	'				dev[i].className = "thread";\n'\
2532	'			}\n'\
2533	'		}\n'\
2534	'	}\n'\
2535	'	function deviceUnhover() {\n'\
2536	'		var dmesg = document.getElementById("dmesg");\n'\
2537	'		var dev = dmesg.getElementsByClassName("thread");\n'\
2538	'		for (var i = 0; i < dev.length; i++) {\n'\
2539	'			dev[i].className = "thread";\n'\
2540	'		}\n'\
2541	'	}\n'\
2542	'	function deviceTitle(title, total, cpu) {\n'\
2543	'		var prefix = "Total";\n'\
2544	'		if(total.length > 3) {\n'\
2545	'			prefix = "Average";\n'\
2546	'			total[1] = (total[1]+total[3])/2;\n'\
2547	'			total[2] = (total[2]+total[4])/2;\n'\
2548	'		}\n'\
2549	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
2550	'		var name = title.slice(0, title.indexOf(" "));\n'\
2551	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
2552	'		var driver = "";\n'\
2553	'		var tS = "<t2>(</t2>";\n'\
2554	'		var tR = "<t2>)</t2>";\n'\
2555	'		if(total[1] > 0)\n'\
2556	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
2557	'		if(total[2] > 0)\n'\
2558	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
2559	'		var s = title.indexOf("{");\n'\
2560	'		var e = title.indexOf("}");\n'\
2561	'		if((s >= 0) && (e >= 0))\n'\
2562	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
2563	'		if(total[1] > 0 && total[2] > 0)\n'\
2564	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
2565	'		else\n'\
2566	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
2567	'		return name;\n'\
2568	'	}\n'\
2569	'	function deviceDetail() {\n'\
2570	'		var devinfo = document.getElementById("devicedetail");\n'\
2571	'		devinfo.style.display = "block";\n'\
2572	'		var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
2573	'		var cpu = -1;\n'\
2574	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
2575	'			cpu = parseInt(name.slice(7));\n'\
2576	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
2577	'			cpu = parseInt(name.slice(8));\n'\
2578	'		var dmesg = document.getElementById("dmesg");\n'\
2579	'		var dev = dmesg.getElementsByClassName("thread");\n'\
2580	'		var idlist = [];\n'\
2581	'		var pdata = [[]];\n'\
 
 
2582	'		var pd = pdata[0];\n'\
2583	'		var total = [0.0, 0.0, 0.0];\n'\
2584	'		for (var i = 0; i < dev.length; i++) {\n'\
2585	'			dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
2586	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
2587	'				(name == dname))\n'\
2588	'			{\n'\
2589	'				idlist[idlist.length] = dev[i].id;\n'\
2590	'				var tidx = 1;\n'\
2591	'				if(dev[i].id[0] == "a") {\n'\
2592	'					pd = pdata[0];\n'\
2593	'				} else {\n'\
2594	'					if(pdata.length == 1) pdata[1] = [];\n'\
2595	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
2596	'					pd = pdata[1];\n'\
2597	'					tidx = 3;\n'\
2598	'				}\n'\
2599	'				var info = dev[i].title.split(" ");\n'\
2600	'				var pname = info[info.length-1];\n'\
2601	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
2602	'				total[0] += pd[pname];\n'\
2603	'				if(pname.indexOf("suspend") >= 0)\n'\
2604	'					total[tidx] += pd[pname];\n'\
2605	'				else\n'\
2606	'					total[tidx+1] += pd[pname];\n'\
2607	'			}\n'\
2608	'		}\n'\
2609	'		var devname = deviceTitle(this.title, total, cpu);\n'\
2610	'		var left = 0.0;\n'\
2611	'		for (var t = 0; t < pdata.length; t++) {\n'\
2612	'			pd = pdata[t];\n'\
2613	'			devinfo = document.getElementById("devicedetail"+t);\n'\
2614	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
2615	'			for (var i = 0; i < phases.length; i++) {\n'\
2616	'				if(phases[i].id in pd) {\n'\
2617	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
2618	'					var fs = 32;\n'\
2619	'					if(w < 8) fs = 4*w | 0;\n'\
2620	'					var fs2 = fs*3/4;\n'\
2621	'					phases[i].style.width = w+"%";\n'\
2622	'					phases[i].style.left = left+"%";\n'\
2623	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
2624	'					left += w;\n'\
2625	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
2626	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace("_", " ")+"</t3>";\n'\
2627	'					phases[i].innerHTML = time+pname;\n'\
2628	'				} else {\n'\
2629	'					phases[i].style.width = "0%";\n'\
2630	'					phases[i].style.left = left+"%";\n'\
2631	'				}\n'\
2632	'			}\n'\
2633	'		}\n'\
2634	'		var cglist = document.getElementById("callgraphs");\n'\
2635	'		if(!cglist) return;\n'\
2636	'		var cg = cglist.getElementsByClassName("atop");\n'\
 
2637	'		for (var i = 0; i < cg.length; i++) {\n'\
2638	'			if(idlist.indexOf(cg[i].id) >= 0) {\n'\
2639	'				cg[i].style.display = "block";\n'\
2640	'			} else {\n'\
2641	'				cg[i].style.display = "none";\n'\
2642	'			}\n'\
2643	'		}\n'\
2644	'	}\n'\
2645	'	function devListWindow(e) {\n'\
2646	'		var sx = e.clientX;\n'\
2647	'		if(sx > window.innerWidth - 440)\n'\
2648	'			sx = window.innerWidth - 440;\n'\
2649	'		var cfg="top="+e.screenY+", left="+sx+", width=440, height=720, scrollbars=yes";\n'\
2650	'		var win = window.open("", "_blank", cfg);\n'\
2651	'		if(window.chrome) win.moveBy(sx, 0);\n'\
2652	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
2653	'			"<style type=\\"text/css\\">"+\n'\
2654	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
2655	'			"</style>"\n'\
2656	'		var dt = devtable[0];\n'\
2657	'		if(e.target.id != "devlist1")\n'\
2658	'			dt = devtable[1];\n'\
2659	'		win.document.write(html+dt);\n'\
2660	'	}\n'\
 
 
 
 
 
 
 
 
 
 
 
2661	'	window.addEventListener("load", function () {\n'\
2662	'		var dmesg = document.getElementById("dmesg");\n'\
2663	'		dmesg.style.width = "100%"\n'\
2664	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
2665	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
2666	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
2667	'		var devlist = document.getElementsByClassName("devlist");\n'\
2668	'		for (var i = 0; i < devlist.length; i++)\n'\
2669	'			devlist[i].onclick = devListWindow;\n'\
 
 
 
 
 
 
2670	'		var dev = dmesg.getElementsByClassName("thread");\n'\
2671	'		for (var i = 0; i < dev.length; i++) {\n'\
2672	'			dev[i].onclick = deviceDetail;\n'\
2673	'			dev[i].onmouseover = deviceHover;\n'\
2674	'			dev[i].onmouseout = deviceUnhover;\n'\
2675	'		}\n'\
2676	'		zoomTimeline();\n'\
2677	'	});\n'\
2678	'</script>\n'
2679	hf.write(script_code);
2680
2681# Function: executeSuspend
2682# Description:
2683#	 Execute system suspend through the sysfs interface, then copy the output
2684#	 dmesg and ftrace files to the test output directory.
2685def executeSuspend():
2686	global sysvals
2687
2688	detectUSB(False)
2689	t0 = time.time()*1000
2690	tp = sysvals.tpath
 
 
 
 
 
 
 
2691	# execute however many s/r runs requested
2692	for count in range(1,sysvals.execcount+1):
2693		# clear the kernel ring buffer just as we start
2694		os.system('dmesg -C')
2695		# enable callgraph ftrace only for the second run
2696		if(sysvals.usecallgraph and count == 2):
2697			# set trace type
2698			os.system('echo function_graph > '+tp+'current_tracer')
2699			os.system('echo "" > '+tp+'set_ftrace_filter')
2700			# set trace format options
2701			os.system('echo funcgraph-abstime > '+tp+'trace_options')
2702			os.system('echo funcgraph-proc > '+tp+'trace_options')
2703			# focus only on device suspend and resume
2704			os.system('cat '+tp+'available_filter_functions | '+\
2705				'grep dpm_run_callback > '+tp+'set_graph_function')
2706		# if this is test2 and there's a delay, start here
2707		if(count > 1 and sysvals.x2delay > 0):
2708			tN = time.time()*1000
2709			while (tN - t0) < sysvals.x2delay:
2710				tN = time.time()*1000
2711				time.sleep(0.001)
2712		# start ftrace
2713		if(sysvals.usecallgraph or sysvals.usetraceevents):
2714			print('START TRACING')
2715			os.system('echo 1 > '+tp+'tracing_on')
2716		# initiate suspend
2717		if(sysvals.usecallgraph or sysvals.usetraceevents):
2718			os.system('echo SUSPEND START > '+tp+'trace_marker')
2719		if(sysvals.rtcwake):
2720			print('SUSPEND START')
2721			print('will autoresume in %d seconds' % sysvals.rtcwaketime)
2722			sysvals.rtcWakeAlarm()
2723		else:
2724			print('SUSPEND START (press a key to resume)')
2725		pf = open(sysvals.powerfile, 'w')
2726		pf.write(sysvals.suspendmode)
2727		# execution will pause here
2728		pf.close()
 
 
 
 
 
 
 
 
 
 
2729		t0 = time.time()*1000
 
 
2730		# return from suspend
2731		print('RESUME COMPLETE')
2732		if(sysvals.usecallgraph or sysvals.usetraceevents):
2733			os.system('echo RESUME COMPLETE > '+tp+'trace_marker')
2734		# see if there's firmware timing data to be had
2735		t = sysvals.postresumetime
2736		if(t > 0):
2737			print('Waiting %d seconds for POST-RESUME trace events...' % t)
2738			time.sleep(t)
2739		# stop ftrace
2740		if(sysvals.usecallgraph or sysvals.usetraceevents):
2741			os.system('echo 0 > '+tp+'tracing_on')
2742			print('CAPTURING TRACE')
2743			writeDatafileHeader(sysvals.ftracefile)
2744			os.system('cat '+tp+'trace >> '+sysvals.ftracefile)
2745			os.system('echo "" > '+tp+'trace')
2746		# grab a copy of the dmesg output
2747		print('CAPTURING DMESG')
2748		writeDatafileHeader(sysvals.dmesgfile)
2749		os.system('dmesg -c >> '+sysvals.dmesgfile)
 
 
 
2750
2751def writeDatafileHeader(filename):
2752	global sysvals
2753
2754	fw = getFPDT(False)
2755	prt = sysvals.postresumetime
2756	fp = open(filename, 'a')
2757	fp.write(sysvals.teststamp+'\n')
2758	if(fw):
2759		fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
 
 
2760	if(prt > 0):
2761		fp.write('# post resume time %u\n' % prt)
2762	fp.close()
2763
2764# Function: executeAndroidSuspend
2765# Description:
2766#	 Execute system suspend through the sysfs interface
2767#	 on a remote android device, then transfer the output
2768#	 dmesg and ftrace files to the local output directory.
2769def executeAndroidSuspend():
2770	global sysvals
2771
2772	# check to see if the display is currently off
2773	tp = sysvals.tpath
2774	out = os.popen(sysvals.adb+\
2775		' shell dumpsys power | grep mScreenOn').read().strip()
2776	# if so we need to turn it on so we can issue a new suspend
2777	if(out.endswith('false')):
2778		print('Waking the device up for the test...')
2779		# send the KEYPAD_POWER keyevent to wake it up
2780		os.system(sysvals.adb+' shell input keyevent 26')
2781		# wait a few seconds so the user can see the device wake up
2782		time.sleep(3)
2783	# execute however many s/r runs requested
2784	for count in range(1,sysvals.execcount+1):
2785		# clear the kernel ring buffer just as we start
2786		os.system(sysvals.adb+' shell dmesg -c > /dev/null 2>&1')
2787		# start ftrace
2788		if(sysvals.usetraceevents):
2789			print('START TRACING')
2790			os.system(sysvals.adb+" shell 'echo 1 > "+tp+"tracing_on'")
2791		# initiate suspend
2792		for count in range(1,sysvals.execcount+1):
2793			if(sysvals.usetraceevents):
2794				os.system(sysvals.adb+\
2795					" shell 'echo SUSPEND START > "+tp+"trace_marker'")
2796			print('SUSPEND START (press a key on the device to resume)')
2797			os.system(sysvals.adb+" shell 'echo "+sysvals.suspendmode+\
2798				" > "+sysvals.powerfile+"'")
2799			# execution will pause here, then adb will exit
2800			while(True):
2801				check = os.popen(sysvals.adb+\
2802					' shell pwd 2>/dev/null').read().strip()
2803				if(len(check) > 0):
2804					break
2805				time.sleep(1)
2806			if(sysvals.usetraceevents):
2807				os.system(sysvals.adb+" shell 'echo RESUME COMPLETE > "+tp+\
2808					"trace_marker'")
2809		# return from suspend
2810		print('RESUME COMPLETE')
2811		# stop ftrace
2812		if(sysvals.usetraceevents):
2813			os.system(sysvals.adb+" shell 'echo 0 > "+tp+"tracing_on'")
2814			print('CAPTURING TRACE')
2815			os.system('echo "'+sysvals.teststamp+'" > '+sysvals.ftracefile)
2816			os.system(sysvals.adb+' shell cat '+tp+\
2817				'trace >> '+sysvals.ftracefile)
2818		# grab a copy of the dmesg output
2819		print('CAPTURING DMESG')
2820		os.system('echo "'+sysvals.teststamp+'" > '+sysvals.dmesgfile)
2821		os.system(sysvals.adb+' shell dmesg >> '+sysvals.dmesgfile)
2822
2823# Function: setUSBDevicesAuto
2824# Description:
2825#	 Set the autosuspend control parameter of all USB devices to auto
2826#	 This can be dangerous, so use at your own risk, most devices are set
2827#	 to always-on since the kernel cant determine if the device can
2828#	 properly autosuspend
2829def setUSBDevicesAuto():
2830	global sysvals
2831
2832	rootCheck()
2833	for dirname, dirnames, filenames in os.walk('/sys/devices'):
2834		if(re.match('.*/usb[0-9]*.*', dirname) and
2835			'idVendor' in filenames and 'idProduct' in filenames):
2836			os.system('echo auto > %s/power/control' % dirname)
2837			name = dirname.split('/')[-1]
2838			desc = os.popen('cat %s/product 2>/dev/null' % \
2839				dirname).read().replace('\n', '')
2840			ctrl = os.popen('cat %s/power/control 2>/dev/null' % \
2841				dirname).read().replace('\n', '')
2842			print('control is %s for %6s: %s' % (ctrl, name, desc))
2843
2844# Function: yesno
2845# Description:
2846#	 Print out an equivalent Y or N for a set of known parameter values
2847# Output:
2848#	 'Y', 'N', or ' ' if the value is unknown
2849def yesno(val):
2850	yesvals = ['auto', 'enabled', 'active', '1']
2851	novals = ['on', 'disabled', 'suspended', 'forbidden', 'unsupported']
2852	if val in yesvals:
2853		return 'Y'
2854	elif val in novals:
2855		return 'N'
2856	return ' '
2857
2858# Function: ms2nice
2859# Description:
2860#	 Print out a very concise time string in minutes and seconds
2861# Output:
2862#	 The time string, e.g. "1901m16s"
2863def ms2nice(val):
2864	ms = 0
2865	try:
2866		ms = int(val)
2867	except:
2868		return 0.0
2869	m = ms / 60000
2870	s = (ms / 1000) - (m * 60)
2871	return '%3dm%2ds' % (m, s)
2872
2873# Function: detectUSB
2874# Description:
2875#	 Detect all the USB hosts and devices currently connected and add
2876#	 a list of USB device names to sysvals for better timeline readability
2877# Arguments:
2878#	 output: True to output the info to stdout, False otherwise
2879def detectUSB(output):
2880	global sysvals
2881
2882	field = {'idVendor':'', 'idProduct':'', 'product':'', 'speed':''}
2883	power = {'async':'', 'autosuspend':'', 'autosuspend_delay_ms':'',
2884			 'control':'', 'persist':'', 'runtime_enabled':'',
2885			 'runtime_status':'', 'runtime_usage':'',
2886			'runtime_active_time':'',
2887			'runtime_suspended_time':'',
2888			'active_duration':'',
2889			'connected_duration':''}
2890	if(output):
2891		print('LEGEND')
2892		print('---------------------------------------------------------------------------------------------')
2893		print('  A = async/sync PM queue Y/N                       D = autosuspend delay (seconds)')
2894		print('  S = autosuspend Y/N                         rACTIVE = runtime active (min/sec)')
2895		print('  P = persist across suspend Y/N              rSUSPEN = runtime suspend (min/sec)')
2896		print('  E = runtime suspend enabled/forbidden Y/N    ACTIVE = active duration (min/sec)')
2897		print('  R = runtime status active/suspended Y/N     CONNECT = connected duration (min/sec)')
2898		print('  U = runtime usage count')
2899		print('---------------------------------------------------------------------------------------------')
2900		print('  NAME       ID      DESCRIPTION         SPEED A S P E R U D rACTIVE rSUSPEN  ACTIVE CONNECT')
2901		print('---------------------------------------------------------------------------------------------')
2902
2903	for dirname, dirnames, filenames in os.walk('/sys/devices'):
2904		if(re.match('.*/usb[0-9]*.*', dirname) and
2905			'idVendor' in filenames and 'idProduct' in filenames):
2906			for i in field:
2907				field[i] = os.popen('cat %s/%s 2>/dev/null' % \
2908					(dirname, i)).read().replace('\n', '')
2909			name = dirname.split('/')[-1]
2910			if(len(field['product']) > 0):
2911				sysvals.altdevname[name] = \
2912					'%s [%s]' % (field['product'], name)
2913			else:
2914				sysvals.altdevname[name] = \
2915					'%s:%s [%s]' % (field['idVendor'], \
2916						field['idProduct'], name)
2917			if(output):
2918				for i in power:
2919					power[i] = os.popen('cat %s/power/%s 2>/dev/null' % \
2920						(dirname, i)).read().replace('\n', '')
2921				if(re.match('usb[0-9]*', name)):
2922					first = '%-8s' % name
2923				else:
2924					first = '%8s' % name
2925				print('%s [%s:%s] %-20s %-4s %1s %1s %1s %1s %1s %1s %1s %s %s %s %s' % \
2926					(first, field['idVendor'], field['idProduct'], \
2927					field['product'][0:20], field['speed'], \
2928					yesno(power['async']), \
2929					yesno(power['control']), \
2930					yesno(power['persist']), \
2931					yesno(power['runtime_enabled']), \
2932					yesno(power['runtime_status']), \
2933					power['runtime_usage'], \
2934					power['autosuspend'], \
2935					ms2nice(power['runtime_active_time']), \
2936					ms2nice(power['runtime_suspended_time']), \
2937					ms2nice(power['active_duration']), \
2938					ms2nice(power['connected_duration'])))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2939
2940# Function: getModes
2941# Description:
2942#	 Determine the supported power modes on this system
2943# Output:
2944#	 A string list of the available modes
2945def getModes():
2946	global sysvals
2947	modes = ''
2948	if(not sysvals.android):
2949		if(os.path.exists(sysvals.powerfile)):
2950			fp = open(sysvals.powerfile, 'r')
2951			modes = string.split(fp.read())
2952			fp.close()
2953	else:
2954		line = os.popen(sysvals.adb+' shell cat '+\
2955			sysvals.powerfile).read().strip()
2956		modes = string.split(line)
2957	return modes
2958
2959# Function: getFPDT
2960# Description:
2961#	 Read the acpi bios tables and pull out FPDT, the firmware data
2962# Arguments:
2963#	 output: True to output the info to stdout, False otherwise
2964def getFPDT(output):
2965	global sysvals
2966
2967	rectype = {}
2968	rectype[0] = 'Firmware Basic Boot Performance Record'
2969	rectype[1] = 'S3 Performance Table Record'
2970	prectype = {}
2971	prectype[0] = 'Basic S3 Resume Performance Record'
2972	prectype[1] = 'Basic S3 Suspend Performance Record'
2973
2974	rootCheck()
2975	if(not os.path.exists(sysvals.fpdtpath)):
2976		if(output):
2977			doError('file doesnt exist: %s' % sysvals.fpdtpath, False)
2978		return False
2979	if(not os.access(sysvals.fpdtpath, os.R_OK)):
2980		if(output):
2981			doError('file isnt readable: %s' % sysvals.fpdtpath, False)
2982		return False
2983	if(not os.path.exists(sysvals.mempath)):
2984		if(output):
2985			doError('file doesnt exist: %s' % sysvals.mempath, False)
2986		return False
2987	if(not os.access(sysvals.mempath, os.R_OK)):
2988		if(output):
2989			doError('file isnt readable: %s' % sysvals.mempath, False)
2990		return False
2991
2992	fp = open(sysvals.fpdtpath, 'rb')
2993	buf = fp.read()
2994	fp.close()
2995
2996	if(len(buf) < 36):
2997		if(output):
2998			doError('Invalid FPDT table data, should '+\
2999				'be at least 36 bytes', False)
3000		return False
3001
3002	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
3003	if(output):
3004		print('')
3005		print('Firmware Performance Data Table (%s)' % table[0])
3006		print('                  Signature : %s' % table[0])
3007		print('               Table Length : %u' % table[1])
3008		print('                   Revision : %u' % table[2])
3009		print('                   Checksum : 0x%x' % table[3])
3010		print('                     OEM ID : %s' % table[4])
3011		print('               OEM Table ID : %s' % table[5])
3012		print('               OEM Revision : %u' % table[6])
3013		print('                 Creator ID : %s' % table[7])
3014		print('           Creator Revision : 0x%x' % table[8])
3015		print('')
3016
3017	if(table[0] != 'FPDT'):
3018		if(output):
3019			doError('Invalid FPDT table')
3020		return False
3021	if(len(buf) <= 36):
3022		return False
3023	i = 0
3024	fwData = [0, 0]
3025	records = buf[36:]
3026	fp = open(sysvals.mempath, 'rb')
3027	while(i < len(records)):
3028		header = struct.unpack('HBB', records[i:i+4])
3029		if(header[0] not in rectype):
 
3030			continue
3031		if(header[1] != 16):
 
3032			continue
3033		addr = struct.unpack('Q', records[i+8:i+16])[0]
3034		try:
3035			fp.seek(addr)
3036			first = fp.read(8)
3037		except:
3038			doError('Bad address 0x%x in %s' % (addr, sysvals.mempath), False)
 
 
3039		rechead = struct.unpack('4sI', first)
3040		recdata = fp.read(rechead[1]-8)
3041		if(rechead[0] == 'FBPT'):
3042			record = struct.unpack('HBBIQQQQQ', recdata)
3043			if(output):
3044				print('%s (%s)' % (rectype[header[0]], rechead[0]))
3045				print('                  Reset END : %u ns' % record[4])
3046				print('  OS Loader LoadImage Start : %u ns' % record[5])
3047				print(' OS Loader StartImage Start : %u ns' % record[6])
3048				print('     ExitBootServices Entry : %u ns' % record[7])
3049				print('      ExitBootServices Exit : %u ns' % record[8])
3050		elif(rechead[0] == 'S3PT'):
3051			if(output):
3052				print('%s (%s)' % (rectype[header[0]], rechead[0]))
3053			j = 0
3054			while(j < len(recdata)):
3055				prechead = struct.unpack('HBB', recdata[j:j+4])
3056				if(prechead[0] not in prectype):
3057					continue
3058				if(prechead[0] == 0):
3059					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
3060					fwData[1] = record[2]
3061					if(output):
3062						print('    %s' % prectype[prechead[0]])
3063						print('               Resume Count : %u' % \
3064							record[1])
3065						print('                 FullResume : %u ns' % \
3066							record[2])
3067						print('              AverageResume : %u ns' % \
3068							record[3])
3069				elif(prechead[0] == 1):
3070					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
3071					fwData[0] = record[1] - record[0]
3072					if(output):
3073						print('    %s' % prectype[prechead[0]])
3074						print('               SuspendStart : %u ns' % \
3075							record[0])
3076						print('                 SuspendEnd : %u ns' % \
3077							record[1])
3078						print('                SuspendTime : %u ns' % \
3079							fwData[0])
3080				j += prechead[1]
3081		if(output):
3082			print('')
3083		i += header[1]
3084	fp.close()
3085	return fwData
3086
3087# Function: statusCheck
3088# Description:
3089#	 Verify that the requested command and options will work, and
3090#	 print the results to the terminal
3091# Output:
3092#	 True if the test will work, False if not
3093def statusCheck():
3094	global sysvals
3095	status = True
3096
3097	if(sysvals.android):
3098		print('Checking the android system ...')
3099	else:
3100		print('Checking this system (%s)...' % platform.node())
3101
3102	# check if adb is connected to a device
3103	if(sysvals.android):
3104		res = 'NO'
3105		out = os.popen(sysvals.adb+' get-state').read().strip()
3106		if(out == 'device'):
3107			res = 'YES'
3108		print('    is android device connected: %s' % res)
3109		if(res != 'YES'):
3110			print('    Please connect the device before using this tool')
3111			return False
3112
3113	# check we have root access
3114	res = 'NO (No features of this tool will work!)'
3115	if(sysvals.android):
3116		out = os.popen(sysvals.adb+' shell id').read().strip()
3117		if('root' in out):
3118			res = 'YES'
3119	else:
3120		if(os.environ['USER'] == 'root'):
3121			res = 'YES'
3122	print('    have root access: %s' % res)
3123	if(res != 'YES'):
3124		if(sysvals.android):
3125			print('    Try running "adb root" to restart the daemon as root')
3126		else:
3127			print('    Try running this script with sudo')
3128		return False
3129
3130	# check sysfs is mounted
3131	res = 'NO (No features of this tool will work!)'
3132	if(sysvals.android):
3133		out = os.popen(sysvals.adb+' shell ls '+\
3134			sysvals.powerfile).read().strip()
3135		if(out == sysvals.powerfile):
3136			res = 'YES'
3137	else:
3138		if(os.path.exists(sysvals.powerfile)):
3139			res = 'YES'
3140	print('    is sysfs mounted: %s' % res)
3141	if(res != 'YES'):
3142		return False
3143
3144	# check target mode is a valid mode
3145	res = 'NO'
3146	modes = getModes()
3147	if(sysvals.suspendmode in modes):
3148		res = 'YES'
3149	else:
3150		status = False
3151	print('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
3152	if(res == 'NO'):
3153		print('      valid power modes are: %s' % modes)
3154		print('      please choose one with -m')
3155
3156	# check if the tool can unlock the device
3157	if(sysvals.android):
3158		res = 'YES'
3159		out1 = os.popen(sysvals.adb+\
3160			' shell dumpsys power | grep mScreenOn').read().strip()
3161		out2 = os.popen(sysvals.adb+\
3162			' shell input').read().strip()
3163		if(not out1.startswith('mScreenOn') or not out2.startswith('usage')):
3164			res = 'NO (wake the android device up before running the test)'
3165		print('    can I unlock the screen: %s' % res)
3166
3167	# check if ftrace is available
3168	res = 'NO'
3169	ftgood = verifyFtrace()
3170	if(ftgood):
3171		res = 'YES'
3172	elif(sysvals.usecallgraph):
3173		status = False
3174	print('    is ftrace supported: %s' % res)
3175
 
 
 
 
 
 
 
 
 
3176	# what data source are we using
3177	res = 'DMESG'
3178	if(ftgood):
3179		sysvals.usetraceeventsonly = True
3180		sysvals.usetraceevents = False
3181		for e in sysvals.traceevents:
3182			check = False
3183			if(sysvals.android):
3184				out = os.popen(sysvals.adb+' shell ls -d '+\
3185					sysvals.epath+e).read().strip()
3186				if(out == sysvals.epath+e):
3187					check = True
3188			else:
3189				if(os.path.exists(sysvals.epath+e)):
3190					check = True
3191			if(not check):
3192				sysvals.usetraceeventsonly = False
3193			if(e == 'suspend_resume' and check):
3194				sysvals.usetraceevents = True
3195		if(sysvals.usetraceevents and sysvals.usetraceeventsonly):
3196			res = 'FTRACE (all trace events found)'
3197		elif(sysvals.usetraceevents):
3198			res = 'DMESG and FTRACE (suspend_resume trace event found)'
3199	print('    timeline data source: %s' % res)
3200
3201	# check if rtcwake
3202	res = 'NO'
3203	if(sysvals.rtcpath != ''):
3204		res = 'YES'
3205	elif(sysvals.rtcwake):
3206		status = False
3207	print('    is rtcwake supported: %s' % res)
3208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3209	return status
3210
3211# Function: doError
3212# Description:
3213#	 generic error function for catastrphic failures
3214# Arguments:
3215#	 msg: the error message to print
3216#	 help: True if printHelp should be called after, False otherwise
3217def doError(msg, help):
3218	if(help == True):
3219		printHelp()
3220	print('ERROR: %s\n') % msg
3221	sys.exit()
3222
3223# Function: doWarning
3224# Description:
3225#	 generic warning function for non-catastrophic anomalies
3226# Arguments:
3227#	 msg: the warning message to print
3228#	 file: If not empty, a filename to request be sent to the owner for debug
3229def doWarning(msg, file):
3230	print('/* %s */') % msg
3231	if(file):
3232		print('/* For a fix, please send this'+\
3233			' %s file to <todd.e.brandt@intel.com> */' % file)
3234
3235# Function: rootCheck
3236# Description:
3237#	 quick check to see if we have root access
3238def rootCheck():
3239	if(os.environ['USER'] != 'root'):
3240		doError('This script must be run as root', False)
 
 
 
 
3241
3242# Function: getArgInt
3243# Description:
3244#	 pull out an integer argument from the command line with checks
3245def getArgInt(name, args, min, max):
3246	try:
3247		arg = args.next()
3248	except:
3249		doError(name+': no argument supplied', True)
 
 
 
3250	try:
3251		val = int(arg)
3252	except:
3253		doError(name+': non-integer value given', True)
3254	if(val < min or val > max):
3255		doError(name+': value should be between %d and %d' % (min, max), True)
3256	return val
3257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3258# Function: rerunTest
3259# Description:
3260#	 generate an output from an existing set of ftrace/dmesg logs
3261def rerunTest():
3262	global sysvals
3263
3264	if(sysvals.ftracefile != ''):
3265		doesTraceLogHaveTraceEvents()
3266	if(sysvals.dmesgfile == '' and not sysvals.usetraceeventsonly):
3267		doError('recreating this html output '+\
3268			'requires a dmesg file', False)
3269	sysvals.setOutputFile()
3270	vprint('Output file: %s' % sysvals.htmlfile)
3271	print('PROCESSING DATA')
3272	if(sysvals.usetraceeventsonly):
3273		testruns = parseTraceLog()
3274	else:
3275		testruns = loadKernelLog()
3276		for data in testruns:
3277			parseKernelLog(data)
3278		if(sysvals.ftracefile != ''):
3279			appendIncompleteTraceLog(testruns)
3280	createHTML(testruns)
3281
3282# Function: runTest
3283# Description:
3284#	 execute a suspend/resume, gather the logs, and generate the output
3285def runTest(subdir):
3286	global sysvals
3287
3288	# prepare for the test
3289	if(not sysvals.android):
3290		initFtrace()
3291	else:
3292		initFtraceAndroid()
3293	sysvals.initTestOutput(subdir)
3294
3295	vprint('Output files:\n    %s' % sysvals.dmesgfile)
3296	if(sysvals.usecallgraph or
3297		sysvals.usetraceevents or
3298		sysvals.usetraceeventsonly):
3299		vprint('    %s' % sysvals.ftracefile)
3300	vprint('    %s' % sysvals.htmlfile)
3301
3302	# execute the test
3303	if(not sysvals.android):
3304		executeSuspend()
3305	else:
3306		executeAndroidSuspend()
3307
3308	# analyze the data and create the html output
3309	print('PROCESSING DATA')
3310	if(sysvals.usetraceeventsonly):
3311		# data for kernels 3.15 or newer is entirely in ftrace
3312		testruns = parseTraceLog()
3313	else:
3314		# data for kernels older than 3.15 is primarily in dmesg
3315		testruns = loadKernelLog()
3316		for data in testruns:
3317			parseKernelLog(data)
3318		if(sysvals.usecallgraph or sysvals.usetraceevents):
3319			appendIncompleteTraceLog(testruns)
3320	createHTML(testruns)
3321
3322# Function: runSummary
3323# Description:
3324#	 create a summary of tests in a sub-directory
3325def runSummary(subdir, output):
3326	global sysvals
3327
3328	# get a list of ftrace output files
3329	files = []
3330	for dirname, dirnames, filenames in os.walk(subdir):
3331		for filename in filenames:
3332			if(re.match('.*_ftrace.txt', filename)):
3333				files.append("%s/%s" % (dirname, filename))
3334
3335	# process the files in order and get an array of data objects
3336	testruns = []
3337	for file in sorted(files):
3338		if output:
3339			print("Test found in %s" % os.path.dirname(file))
3340		sysvals.ftracefile = file
3341		sysvals.dmesgfile = file.replace('_ftrace.txt', '_dmesg.txt')
3342		doesTraceLogHaveTraceEvents()
3343		sysvals.usecallgraph = False
3344		if not sysvals.usetraceeventsonly:
3345			if(not os.path.exists(sysvals.dmesgfile)):
3346				print("Skipping %s: not a valid test input" % file)
3347				continue
3348			else:
3349				if output:
3350					f = os.path.basename(sysvals.ftracefile)
3351					d = os.path.basename(sysvals.dmesgfile)
3352					print("\tInput files: %s and %s" % (f, d))
3353				testdata = loadKernelLog()
3354				data = testdata[0]
3355				parseKernelLog(data)
3356				testdata = [data]
3357				appendIncompleteTraceLog(testdata)
3358		else:
3359			if output:
3360				print("\tInput file: %s" % os.path.basename(sysvals.ftracefile))
3361			testdata = parseTraceLog()
3362			data = testdata[0]
3363		data.normalizeTime(data.tSuspended)
3364		link = file.replace(subdir+'/', '').replace('_ftrace.txt', '.html')
3365		data.outfile = link
3366		testruns.append(data)
3367
3368	createHTMLSummarySimple(testruns, subdir+'/summary.html')
3369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3370# Function: printHelp
3371# Description:
3372#	 print out the help text
3373def printHelp():
3374	global sysvals
3375	modes = getModes()
3376
3377	print('')
3378	print('AnalyzeSuspend v%.1f' % sysvals.version)
3379	print('Usage: sudo analyze_suspend.py <options>')
3380	print('')
3381	print('Description:')
3382	print('  This tool is designed to assist kernel and OS developers in optimizing')
3383	print('  their linux stack\'s suspend/resume time. Using a kernel image built')
3384	print('  with a few extra options enabled, the tool will execute a suspend and')
3385	print('  capture dmesg and ftrace data until resume is complete. This data is')
3386	print('  transformed into a device timeline and an optional callgraph to give')
3387	print('  a detailed view of which devices/subsystems are taking the most')
3388	print('  time in suspend/resume.')
3389	print('')
3390	print('  Generates output files in subdirectory: suspend-mmddyy-HHMMSS')
3391	print('   HTML output:                    <hostname>_<mode>.html')
3392	print('   raw dmesg output:               <hostname>_<mode>_dmesg.txt')
3393	print('   raw ftrace output:              <hostname>_<mode>_ftrace.txt')
3394	print('')
3395	print('Options:')
3396	print('  [general]')
3397	print('    -h          Print this help text')
3398	print('    -v          Print the current tool version')
 
3399	print('    -verbose    Print extra information during execution and analysis')
3400	print('    -status     Test to see if the system is enabled to run this tool')
3401	print('    -modes      List available suspend modes')
3402	print('    -m mode     Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode)
3403	print('    -rtcwake t  Use rtcwake to autoresume after <t> seconds (default: disabled)')
3404	print('  [advanced]')
 
 
 
 
 
 
 
 
 
 
3405	print('    -f          Use ftrace to create device callgraphs (default: disabled)')
3406	print('    -filter "d1 d2 ..." Filter out all but this list of dev names')
 
 
 
 
 
 
3407	print('    -x2         Run two suspend/resumes back to back (default: disabled)')
3408	print('    -x2delay t  Minimum millisecond delay <t> between the two test runs (default: 0 ms)')
3409	print('    -postres t  Time after resume completion to wait for post-resume events (default: 0 S)')
3410	print('    -multi n d  Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
3411	print('                be created in a new subdirectory with a summary page.')
3412	print('  [utilities]')
3413	print('    -fpdt       Print out the contents of the ACPI Firmware Performance Data Table')
3414	print('    -usbtopo    Print out the current USB topology with power info')
3415	print('    -usbauto    Enable autosuspend for all connected USB devices')
3416	print('  [android testing]')
3417	print('    -adb binary Use the given adb binary to run the test on an android device.')
3418	print('                The device should already be connected and with root access.')
3419	print('                Commands will be executed on the device using "adb shell"')
3420	print('  [re-analyze data from previous runs]')
3421	print('    -ftrace ftracefile  Create HTML output using ftrace input')
3422	print('    -dmesg dmesgfile    Create HTML output using dmesg (not needed for kernel >= 3.15)')
3423	print('    -summary directory  Create a summary of all test in this dir')
3424	print('')
3425	return True
3426
3427# ----------------- MAIN --------------------
3428# exec start (skipped if script is loaded as library)
3429if __name__ == '__main__':
3430	cmd = ''
3431	cmdarg = ''
3432	multitest = {'run': False, 'count': 0, 'delay': 0}
 
3433	# loop through the command line arguments
3434	args = iter(sys.argv[1:])
3435	for arg in args:
3436		if(arg == '-m'):
3437			try:
3438				val = args.next()
3439			except:
3440				doError('No mode supplied', True)
 
 
3441			sysvals.suspendmode = val
3442		elif(arg == '-adb'):
3443			try:
3444				val = args.next()
3445			except:
3446				doError('No adb binary supplied', True)
3447			if(not os.path.exists(val)):
3448				doError('file doesnt exist: %s' % val, False)
3449			if(not os.access(val, os.X_OK)):
3450				doError('file isnt executable: %s' % val, False)
3451			try:
3452				check = os.popen(val+' version').read().strip()
3453			except:
3454				doError('adb version failed to execute', False)
3455			if(not re.match('Android Debug Bridge .*', check)):
3456				doError('adb version failed to execute', False)
3457			sysvals.adb = val
3458			sysvals.android = True
3459		elif(arg == '-x2'):
3460			if(sysvals.postresumetime > 0):
3461				doError('-x2 is not compatible with -postres', False)
3462			sysvals.execcount = 2
 
 
3463		elif(arg == '-x2delay'):
3464			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
3465		elif(arg == '-postres'):
3466			if(sysvals.execcount != 1):
3467				doError('-x2 is not compatible with -postres', False)
3468			sysvals.postresumetime = getArgInt('-postres', args, 0, 3600)
3469		elif(arg == '-f'):
3470			sysvals.usecallgraph = True
3471		elif(arg == '-modes'):
3472			cmd = 'modes'
3473		elif(arg == '-fpdt'):
3474			cmd = 'fpdt'
3475		elif(arg == '-usbtopo'):
3476			cmd = 'usbtopo'
3477		elif(arg == '-usbauto'):
3478			cmd = 'usbauto'
3479		elif(arg == '-status'):
3480			cmd = 'status'
3481		elif(arg == '-verbose'):
3482			sysvals.verbose = True
3483		elif(arg == '-v'):
3484			print("Version %.1f" % sysvals.version)
3485			sys.exit()
 
3486		elif(arg == '-rtcwake'):
3487			sysvals.rtcwake = True
3488			sysvals.rtcwaketime = getArgInt('-rtcwake', args, 0, 3600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3489		elif(arg == '-multi'):
3490			multitest['run'] = True
3491			multitest['count'] = getArgInt('-multi n (exec count)', args, 2, 1000000)
3492			multitest['delay'] = getArgInt('-multi d (delay between tests)', args, 0, 3600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3493		elif(arg == '-dmesg'):
3494			try:
3495				val = args.next()
3496			except:
3497				doError('No dmesg file supplied', True)
3498			sysvals.notestrun = True
3499			sysvals.dmesgfile = val
3500			if(os.path.exists(sysvals.dmesgfile) == False):
3501				doError('%s doesnt exist' % sysvals.dmesgfile, False)
3502		elif(arg == '-ftrace'):
3503			try:
3504				val = args.next()
3505			except:
3506				doError('No ftrace file supplied', True)
3507			sysvals.notestrun = True
3508			sysvals.usecallgraph = True
3509			sysvals.ftracefile = val
3510			if(os.path.exists(sysvals.ftracefile) == False):
3511				doError('%s doesnt exist' % sysvals.ftracefile, False)
3512		elif(arg == '-summary'):
3513			try:
3514				val = args.next()
3515			except:
3516				doError('No directory supplied', True)
3517			cmd = 'summary'
3518			cmdarg = val
3519			sysvals.notestrun = True
3520			if(os.path.isdir(val) == False):
3521				doError('%s isnt accesible' % val, False)
3522		elif(arg == '-filter'):
3523			try:
3524				val = args.next()
3525			except:
3526				doError('No devnames supplied', True)
3527			sysvals.setDeviceFilter(val)
3528		elif(arg == '-h'):
3529			printHelp()
3530			sys.exit()
3531		else:
3532			doError('Invalid argument: '+arg, True)
3533
 
 
 
 
3534	# just run a utility command and exit
3535	if(cmd != ''):
3536		if(cmd == 'status'):
3537			statusCheck()
3538		elif(cmd == 'fpdt'):
3539			if(sysvals.android):
3540				doError('cannot read FPDT on android device', False)
3541			getFPDT(True)
3542		elif(cmd == 'usbtopo'):
3543			if(sysvals.android):
3544				doError('cannot read USB topology '+\
3545					'on an android device', False)
3546			detectUSB(True)
3547		elif(cmd == 'modes'):
3548			modes = getModes()
3549			print modes
 
 
 
 
3550		elif(cmd == 'usbauto'):
3551			setUSBDevicesAuto()
3552		elif(cmd == 'summary'):
3553			print("Generating a summary of folder \"%s\"" % cmdarg)
3554			runSummary(cmdarg, True)
3555		sys.exit()
3556
3557	# run test on android device
3558	if(sysvals.android):
3559		if(sysvals.usecallgraph):
3560			doError('ftrace (-f) is not yet supported '+\
3561				'in the android kernel', False)
3562		if(sysvals.notestrun):
3563			doError('cannot analyze test files on the '+\
3564				'android device', False)
3565
3566	# if instructed, re-analyze existing data files
3567	if(sysvals.notestrun):
3568		rerunTest()
3569		sys.exit()
3570
3571	# verify that we can run a test
3572	if(not statusCheck()):
3573		print('Check FAILED, aborting the test run!')
3574		sys.exit()
3575
3576	if multitest['run']:
3577		# run multiple tests in a separte subdirectory
3578		s = 'x%d' % multitest['count']
3579		subdir = datetime.now().strftime('suspend-'+s+'-%m%d%y-%H%M%S')
3580		os.mkdir(subdir)
 
 
3581		for i in range(multitest['count']):
3582			if(i != 0):
3583				print('Waiting %d seconds...' % (multitest['delay']))
3584				time.sleep(multitest['delay'])
3585			print('TEST (%d/%d) START' % (i+1, multitest['count']))
3586			runTest(subdir)
3587			print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count']))
3588		runSummary(subdir, False)
3589	else:
3590		# run the test in the current directory
3591		runTest(".")
v4.10.11
   1#!/usr/bin/python
   2#
   3# Tool for analyzing suspend/resume timing
   4# Copyright (c) 2013, Intel Corporation.
   5#
   6# This program is free software; you can redistribute it and/or modify it
   7# under the terms and conditions of the GNU General Public License,
   8# version 2, as published by the Free Software Foundation.
   9#
  10# This program is distributed in the hope it will be useful, but WITHOUT
  11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  13# more details.
  14#
  15# You should have received a copy of the GNU General Public License along with
  16# this program; if not, write to the Free Software Foundation, Inc.,
  17# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
  18#
  19# Authors:
  20#	 Todd Brandt <todd.e.brandt@linux.intel.com>
  21#
  22# Links:
  23#	 Home Page
  24#	   https://01.org/suspendresume
  25#	 Source repo
  26#	   https://github.com/01org/suspendresume
  27#	 Documentation
  28#	   Getting Started
  29#	     https://01.org/suspendresume/documentation/getting-started
  30#	   Command List:
  31#	     https://01.org/suspendresume/documentation/command-list
  32#
  33# Description:
  34#	 This tool is designed to assist kernel and OS developers in optimizing
  35#	 their linux stack's suspend/resume time. Using a kernel image built
  36#	 with a few extra options enabled, the tool will execute a suspend and
  37#	 will capture dmesg and ftrace data until resume is complete. This data
  38#	 is transformed into a device timeline and a callgraph to give a quick
  39#	 and detailed view of which devices and callbacks are taking the most
  40#	 time in suspend/resume. The output is a single html file which can be
  41#	 viewed in firefox or chrome.
  42#
  43#	 The following kernel build options are required:
  44#		 CONFIG_PM_DEBUG=y
  45#		 CONFIG_PM_SLEEP_DEBUG=y
  46#		 CONFIG_FTRACE=y
  47#		 CONFIG_FUNCTION_TRACER=y
  48#		 CONFIG_FUNCTION_GRAPH_TRACER=y
  49#		 CONFIG_KPROBES=y
  50#		 CONFIG_KPROBES_ON_FTRACE=y
  51#
  52#	 For kernel versions older than 3.15:
  53#	 The following additional kernel parameters are required:
  54#		 (e.g. in file /etc/default/grub)
  55#		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
  56#
  57
  58# ----------------- LIBRARIES --------------------
  59
  60import sys
  61import time
  62import os
  63import string
  64import re
  65import platform
  66from datetime import datetime
  67import struct
  68import ConfigParser
  69
  70# ----------------- CLASSES --------------------
  71
  72# Class: SystemValues
  73# Description:
  74#	 A global, single-instance container used to
  75#	 store system values and test parameters
  76class SystemValues:
  77	ansi = False
  78	version = '4.2'
  79	verbose = False
  80	addlogs = False
  81	mindevlen = 0.001
  82	mincglen = 1.0
  83	srgap = 0
  84	cgexp = False
  85	outdir = ''
  86	testdir = '.'
  87	tpath = '/sys/kernel/debug/tracing/'
  88	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
  89	epath = '/sys/kernel/debug/tracing/events/power/'
  90	traceevents = [
  91		'suspend_resume',
  92		'device_pm_callback_end',
  93		'device_pm_callback_start'
  94	]
  95	testcommand = ''
 
 
 
 
 
  96	mempath = '/dev/mem'
  97	powerfile = '/sys/power/state'
  98	suspendmode = 'mem'
  99	hostname = 'localhost'
 100	prefix = 'test'
 101	teststamp = ''
 102	dmesgstart = 0.0
 103	dmesgfile = ''
 104	ftracefile = ''
 105	htmlfile = ''
 106	embedded = False
 107	rtcwake = False
 108	rtcwaketime = 10
 109	rtcpath = ''
 
 
 110	devicefilter = []
 111	stamp = 0
 112	execcount = 1
 113	x2delay = 0
 114	usecallgraph = False
 115	usetraceevents = False
 116	usetraceeventsonly = False
 117	usetracemarkers = True
 118	usekprobes = True
 119	usedevsrc = False
 120	notestrun = False
 121	devprops = dict()
 122	postresumetime = 0
 123	devpropfmt = '# Device Properties: .*'
 124	tracertypefmt = '# tracer: (?P<t>.*)'
 125	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
 126	postresumefmt = '# post resume time (?P<t>[0-9]*)$'
 127	stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
 128				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
 129				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
 130	kprobecolor = 'rgba(204,204,204,0.5)'
 131	synccolor = 'rgba(204,204,204,0.5)'
 132	debugfuncs = []
 133	tracefuncs = {
 134		'sys_sync': dict(),
 135		'pm_prepare_console': dict(),
 136		'pm_notifier_call_chain': dict(),
 137		'freeze_processes': dict(),
 138		'freeze_kernel_threads': dict(),
 139		'pm_restrict_gfp_mask': dict(),
 140		'acpi_suspend_begin': dict(),
 141		'suspend_console': dict(),
 142		'acpi_pm_prepare': dict(),
 143		'syscore_suspend': dict(),
 144		'arch_enable_nonboot_cpus_end': dict(),
 145		'syscore_resume': dict(),
 146		'acpi_pm_finish': dict(),
 147		'resume_console': dict(),
 148		'acpi_pm_end': dict(),
 149		'pm_restore_gfp_mask': dict(),
 150		'thaw_processes': dict(),
 151		'pm_restore_console': dict(),
 152		'CPU_OFF': {
 153			'func':'_cpu_down',
 154			'args_x86_64': {'cpu':'%di:s32'},
 155			'format': 'CPU_OFF[{cpu}]',
 156			'mask': 'CPU_.*_DOWN'
 157		},
 158		'CPU_ON': {
 159			'func':'_cpu_up',
 160			'args_x86_64': {'cpu':'%di:s32'},
 161			'format': 'CPU_ON[{cpu}]',
 162			'mask': 'CPU_.*_UP'
 163		},
 164	}
 165	dev_tracefuncs = {
 166		# general wait/delay/sleep
 167		'msleep': { 'args_x86_64': {'time':'%di:s32'} },
 168		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'} },
 169		'acpi_os_stall': dict(),
 170		# ACPI
 171		'acpi_resume_power_resources': dict(),
 172		'acpi_ps_parse_aml': dict(),
 173		# filesystem
 174		'ext4_sync_fs': dict(),
 175		# ATA
 176		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
 177		# i915
 178		'i915_gem_restore_gtt_mappings': dict(),
 179		'intel_opregion_setup': dict(),
 180		'intel_dp_detect': dict(),
 181		'intel_hdmi_detect': dict(),
 182		'intel_opregion_init': dict(),
 183	}
 184	kprobes_postresume = [
 185		{
 186			'name': 'ataportrst',
 187			'func': 'ata_eh_recover',
 188			'args': {'port':'+36(%di):s32'},
 189			'format': 'ata{port}_port_reset',
 190			'mask': 'ata.*_port_reset'
 191		}
 192	]
 193	kprobes = dict()
 194	timeformat = '%.3f'
 195	def __init__(self):
 196		# if this is a phoronix test run, set some default options
 197		if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ):
 198			self.embedded = True
 199			self.addlogs = True
 200			self.htmlfile = os.environ['LOG_FILE']
 201		self.hostname = platform.node()
 202		if(self.hostname == ''):
 203			self.hostname = 'localhost'
 204		rtc = "rtc0"
 205		if os.path.exists('/dev/rtc'):
 206			rtc = os.readlink('/dev/rtc')
 207		rtc = '/sys/class/rtc/'+rtc
 208		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 209			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 210			self.rtcpath = rtc
 211		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
 212			self.ansi = True
 213	def setPrecision(self, num):
 214		if num < 0 or num > 6:
 215			return
 216		self.timeformat = '%.{0}f'.format(num)
 217	def setOutputFile(self):
 218		if((self.htmlfile == '') and (self.dmesgfile != '')):
 219			m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)
 220			if(m):
 221				self.htmlfile = m.group('name')+'.html'
 222		if((self.htmlfile == '') and (self.ftracefile != '')):
 223			m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)
 224			if(m):
 225				self.htmlfile = m.group('name')+'.html'
 226		if(self.htmlfile == ''):
 227			self.htmlfile = 'output.html'
 228	def initTestOutput(self, subdir, testpath=''):
 229		self.prefix = self.hostname
 230		v = open('/proc/version', 'r').read().strip()
 231		kver = string.split(v)[2]
 232		n = datetime.now()
 233		testtime = n.strftime('suspend-%m%d%y-%H%M%S')
 234		if not testpath:
 235			testpath = n.strftime('suspend-%y%m%d-%H%M%S')
 
 
 236		if(subdir != "."):
 237			self.testdir = subdir+"/"+testpath
 238		else:
 239			self.testdir = testpath
 240		self.teststamp = \
 241			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 242		if(self.embedded):
 243			self.dmesgfile = \
 244				'/tmp/'+testtime+'_'+self.suspendmode+'_dmesg.txt'
 245			self.ftracefile = \
 246				'/tmp/'+testtime+'_'+self.suspendmode+'_ftrace.txt'
 247			return
 248		self.dmesgfile = \
 249			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'
 250		self.ftracefile = \
 251			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'
 252		self.htmlfile = \
 253			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 254		if not os.path.isdir(self.testdir):
 255			os.mkdir(self.testdir)
 256	def setDeviceFilter(self, devnames):
 257		self.devicefilter = string.split(devnames)
 258	def rtcWakeAlarmOn(self):
 259		os.system('echo 0 > '+self.rtcpath+'/wakealarm')
 260		outD = open(self.rtcpath+'/date', 'r').read().strip()
 261		outT = open(self.rtcpath+'/time', 'r').read().strip()
 262		mD = re.match('^(?P<y>[0-9]*)-(?P<m>[0-9]*)-(?P<d>[0-9]*)', outD)
 263		mT = re.match('^(?P<h>[0-9]*):(?P<m>[0-9]*):(?P<s>[0-9]*)', outT)
 264		if(mD and mT):
 265			# get the current time from hardware
 266			utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds())
 267			dt = datetime(\
 268				int(mD.group('y')), int(mD.group('m')), int(mD.group('d')),
 269				int(mT.group('h')), int(mT.group('m')), int(mT.group('s')))
 270			nowtime = int(dt.strftime('%s')) + utcoffset
 271		else:
 272			# if hardware time fails, use the software time
 273			nowtime = int(datetime.now().strftime('%s'))
 274		alarm = nowtime + self.rtcwaketime
 275		os.system('echo %d > %s/wakealarm' % (alarm, self.rtcpath))
 276	def rtcWakeAlarmOff(self):
 277		os.system('echo 0 > %s/wakealarm' % self.rtcpath)
 278	def initdmesg(self):
 279		# get the latest time stamp from the dmesg log
 280		fp = os.popen('dmesg')
 281		ktime = '0'
 282		for line in fp:
 283			line = line.replace('\r\n', '')
 284			idx = line.find('[')
 285			if idx > 1:
 286				line = line[idx:]
 287			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 288			if(m):
 289				ktime = m.group('ktime')
 290		fp.close()
 291		self.dmesgstart = float(ktime)
 292	def getdmesg(self):
 293		# store all new dmesg lines since initdmesg was called
 294		fp = os.popen('dmesg')
 295		op = open(self.dmesgfile, 'a')
 296		for line in fp:
 297			line = line.replace('\r\n', '')
 298			idx = line.find('[')
 299			if idx > 1:
 300				line = line[idx:]
 301			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 302			if(not m):
 303				continue
 304			ktime = float(m.group('ktime'))
 305			if ktime > self.dmesgstart:
 306				op.write(line)
 307		fp.close()
 308		op.close()
 309	def addFtraceFilterFunctions(self, file):
 310		fp = open(file)
 311		list = fp.read().split('\n')
 312		fp.close()
 313		for i in list:
 314			if len(i) < 2:
 315				continue
 316			self.tracefuncs[i] = dict()
 317	def getFtraceFilterFunctions(self, current):
 318		rootCheck(True)
 319		if not current:
 320			os.system('cat '+self.tpath+'available_filter_functions')
 321			return
 322		fp = open(self.tpath+'available_filter_functions')
 323		master = fp.read().split('\n')
 324		fp.close()
 325		if len(self.debugfuncs) > 0:
 326			for i in self.debugfuncs:
 327				if i in master:
 328					print i
 329				else:
 330					print self.colorText(i)
 331		else:
 332			for i in self.tracefuncs:
 333				if 'func' in self.tracefuncs[i]:
 334					i = self.tracefuncs[i]['func']
 335				if i in master:
 336					print i
 337				else:
 338					print self.colorText(i)
 339	def setFtraceFilterFunctions(self, list):
 340		fp = open(self.tpath+'available_filter_functions')
 341		master = fp.read().split('\n')
 342		fp.close()
 343		flist = ''
 344		for i in list:
 345			if i not in master:
 346				continue
 347			if ' [' in i:
 348				flist += i.split(' ')[0]+'\n'
 349			else:
 350				flist += i+'\n'
 351		fp = open(self.tpath+'set_graph_function', 'w')
 352		fp.write(flist)
 353		fp.close()
 354	def kprobeMatch(self, name, target):
 355		if name not in self.kprobes:
 356			return False
 357		if re.match(self.kprobes[name]['mask'], target):
 358			return True
 359		return False
 360	def basicKprobe(self, name):
 361		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name,'mask': name}
 362	def defaultKprobe(self, name, kdata):
 363		k = kdata
 364		for field in ['name', 'format', 'mask', 'func']:
 365			if field not in k:
 366				k[field] = name
 367		archargs = 'args_'+platform.machine()
 368		if archargs in k:
 369			k['args'] = k[archargs]
 370		else:
 371			k['args'] = dict()
 372			k['format'] = name
 373		self.kprobes[name] = k
 374	def kprobeColor(self, name):
 375		if name not in self.kprobes or 'color' not in self.kprobes[name]:
 376			return ''
 377		return self.kprobes[name]['color']
 378	def kprobeDisplayName(self, name, dataraw):
 379		if name not in self.kprobes:
 380			self.basicKprobe(name)
 381		data = ''
 382		quote=0
 383		# first remvoe any spaces inside quotes, and the quotes
 384		for c in dataraw:
 385			if c == '"':
 386				quote = (quote + 1) % 2
 387			if quote and c == ' ':
 388				data += '_'
 389			elif c != '"':
 390				data += c
 391		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
 392		arglist = dict()
 393		# now process the args
 394		for arg in sorted(args):
 395			arglist[arg] = ''
 396			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
 397			if m:
 398				arglist[arg] = m.group('arg')
 399			else:
 400				m = re.match('.* '+arg+'=(?P<arg>.*)', data);
 401				if m:
 402					arglist[arg] = m.group('arg')
 403		out = fmt.format(**arglist)
 404		out = out.replace(' ', '_').replace('"', '')
 405		return out
 406	def kprobeText(self, kprobe):
 407		name, fmt, func, args = kprobe['name'], kprobe['format'], kprobe['func'], kprobe['args']
 408		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
 409			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func), False)
 410		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
 411			if arg not in args:
 412				doError('Kprobe "%s" is missing argument "%s"' % (name, arg), False)
 413		val = 'p:%s_cal %s' % (name, func)
 414		for i in sorted(args):
 415			val += ' %s=%s' % (i, args[i])
 416		val += '\nr:%s_ret %s $retval\n' % (name, func)
 417		return val
 418	def addKprobes(self):
 419		# first test each kprobe
 420		print('INITIALIZING KPROBES...')
 421		rejects = []
 422		for name in sorted(self.kprobes):
 423			if not self.testKprobe(self.kprobes[name]):
 424				rejects.append(name)
 425		# remove all failed ones from the list
 426		for name in rejects:
 427			vprint('Skipping KPROBE: %s' % name)
 428			self.kprobes.pop(name)
 429		self.fsetVal('', 'kprobe_events')
 430		kprobeevents = ''
 431		# set the kprobes all at once
 432		for kp in self.kprobes:
 433			val = self.kprobeText(self.kprobes[kp])
 434			vprint('Adding KPROBE: %s\n%s' % (kp, val.strip()))
 435			kprobeevents += self.kprobeText(self.kprobes[kp])
 436		self.fsetVal(kprobeevents, 'kprobe_events')
 437		# verify that the kprobes were set as ordered
 438		check = self.fgetVal('kprobe_events')
 439		linesout = len(kprobeevents.split('\n'))
 440		linesack = len(check.split('\n'))
 441		if linesack < linesout:
 442			# if not, try appending the kprobes 1 by 1
 443			for kp in self.kprobes:
 444				kprobeevents = self.kprobeText(self.kprobes[kp])
 445				self.fsetVal(kprobeevents, 'kprobe_events', 'a')
 446		self.fsetVal('1', 'events/kprobes/enable')
 447	def testKprobe(self, kprobe):
 448		kprobeevents = self.kprobeText(kprobe)
 449		if not kprobeevents:
 450			return False
 451		try:
 452			self.fsetVal(kprobeevents, 'kprobe_events')
 453			check = self.fgetVal('kprobe_events')
 454		except:
 455			return False
 456		linesout = len(kprobeevents.split('\n'))
 457		linesack = len(check.split('\n'))
 458		if linesack < linesout:
 459			return False
 460		return True
 461	def fsetVal(self, val, path, mode='w'):
 462		file = self.tpath+path
 463		if not os.path.exists(file):
 464			return False
 465		try:
 466			fp = open(file, mode)
 467			fp.write(val)
 468			fp.close()
 469		except:
 470			pass
 471		return True
 472	def fgetVal(self, path):
 473		file = self.tpath+path
 474		res = ''
 475		if not os.path.exists(file):
 476			return res
 477		try:
 478			fp = open(file, 'r')
 479			res = fp.read()
 480			fp.close()
 481		except:
 482			pass
 483		return res
 484	def cleanupFtrace(self):
 485		if(self.usecallgraph or self.usetraceevents):
 486			self.fsetVal('0', 'events/kprobes/enable')
 487			self.fsetVal('', 'kprobe_events')
 488	def setupAllKprobes(self):
 489		for name in self.tracefuncs:
 490			self.defaultKprobe(name, self.tracefuncs[name])
 491		for name in self.dev_tracefuncs:
 492			self.defaultKprobe(name, self.dev_tracefuncs[name])
 493	def isCallgraphFunc(self, name):
 494		if len(self.debugfuncs) < 1 and self.suspendmode == 'command':
 495			return True
 496		if name in self.debugfuncs:
 497			return True
 498		funclist = []
 499		for i in self.tracefuncs:
 500			if 'func' in self.tracefuncs[i]:
 501				funclist.append(self.tracefuncs[i]['func'])
 502			else:
 503				funclist.append(i)
 504		if name in funclist:
 505			return True
 506		return False
 507	def initFtrace(self, testing=False):
 508		tp = self.tpath
 509		print('INITIALIZING FTRACE...')
 510		# turn trace off
 511		self.fsetVal('0', 'tracing_on')
 512		self.cleanupFtrace()
 513		# set the trace clock to global
 514		self.fsetVal('global', 'trace_clock')
 515		# set trace buffer to a huge value
 516		self.fsetVal('nop', 'current_tracer')
 517		self.fsetVal('100000', 'buffer_size_kb')
 518		# go no further if this is just a status check
 519		if testing:
 520			return
 521		if self.usekprobes:
 522			# add tracefunc kprobes so long as were not using full callgraph
 523			if(not self.usecallgraph or len(self.debugfuncs) > 0):
 524				for name in self.tracefuncs:
 525					self.defaultKprobe(name, self.tracefuncs[name])
 526				if self.usedevsrc:
 527					for name in self.dev_tracefuncs:
 528						self.defaultKprobe(name, self.dev_tracefuncs[name])
 529			else:
 530				self.usedevsrc = False
 531			self.addKprobes()
 532		# initialize the callgraph trace, unless this is an x2 run
 533		if(self.usecallgraph):
 534			# set trace type
 535			self.fsetVal('function_graph', 'current_tracer')
 536			self.fsetVal('', 'set_ftrace_filter')
 537			# set trace format options
 538			self.fsetVal('print-parent', 'trace_options')
 539			self.fsetVal('funcgraph-abstime', 'trace_options')
 540			self.fsetVal('funcgraph-cpu', 'trace_options')
 541			self.fsetVal('funcgraph-duration', 'trace_options')
 542			self.fsetVal('funcgraph-proc', 'trace_options')
 543			self.fsetVal('funcgraph-tail', 'trace_options')
 544			self.fsetVal('nofuncgraph-overhead', 'trace_options')
 545			self.fsetVal('context-info', 'trace_options')
 546			self.fsetVal('graph-time', 'trace_options')
 547			self.fsetVal('0', 'max_graph_depth')
 548			if len(self.debugfuncs) > 0:
 549				self.setFtraceFilterFunctions(self.debugfuncs)
 550			elif self.suspendmode == 'command':
 551				self.fsetVal('', 'set_graph_function')
 552			else:
 553				cf = ['dpm_run_callback']
 554				if(self.usetraceeventsonly):
 555					cf += ['dpm_prepare', 'dpm_complete']
 556				for fn in self.tracefuncs:
 557					if 'func' in self.tracefuncs[fn]:
 558						cf.append(self.tracefuncs[fn]['func'])
 559					else:
 560						cf.append(fn)
 561				self.setFtraceFilterFunctions(cf)
 562		if(self.usetraceevents):
 563			# turn trace events on
 564			events = iter(self.traceevents)
 565			for e in events:
 566				self.fsetVal('1', 'events/power/'+e+'/enable')
 567		# clear the trace buffer
 568		self.fsetVal('', 'trace')
 569	def verifyFtrace(self):
 570		# files needed for any trace data
 571		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 572				 'trace_marker', 'trace_options', 'tracing_on']
 573		# files needed for callgraph trace data
 574		tp = self.tpath
 575		if(self.usecallgraph):
 576			files += [
 577				'available_filter_functions',
 578				'set_ftrace_filter',
 579				'set_graph_function'
 580			]
 581		for f in files:
 582			if(os.path.exists(tp+f) == False):
 583				return False
 584		return True
 585	def verifyKprobes(self):
 586		# files needed for kprobes to work
 587		files = ['kprobe_events', 'events']
 588		tp = self.tpath
 589		for f in files:
 590			if(os.path.exists(tp+f) == False):
 591				return False
 592		return True
 593	def colorText(self, str):
 594		if not self.ansi:
 595			return str
 596		return '\x1B[31;40m'+str+'\x1B[m'
 597
 598sysvals = SystemValues()
 599
 600# Class: DevProps
 601# Description:
 602#	 Simple class which holds property values collected
 603#	 for all the devices used in the timeline.
 604class DevProps:
 605	syspath = ''
 606	altname = ''
 607	async = True
 608	xtraclass = ''
 609	xtrainfo = ''
 610	def out(self, dev):
 611		return '%s,%s,%d;' % (dev, self.altname, self.async)
 612	def debug(self, dev):
 613		print '%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.async)
 614	def altName(self, dev):
 615		if not self.altname or self.altname == dev:
 616			return dev
 617		return '%s [%s]' % (self.altname, dev)
 618	def xtraClass(self):
 619		if self.xtraclass:
 620			return ' '+self.xtraclass
 621		if not self.async:
 622			return ' sync'
 623		return ''
 624	def xtraInfo(self):
 625		if self.xtraclass:
 626			return ' '+self.xtraclass
 627		if self.async:
 628			return ' async'
 629		return ' sync'
 630
 631# Class: DeviceNode
 632# Description:
 633#	 A container used to create a device hierachy, with a single root node
 634#	 and a tree of child nodes. Used by Data.deviceTopology()
 635class DeviceNode:
 636	name = ''
 637	children = 0
 638	depth = 0
 639	def __init__(self, nodename, nodedepth):
 640		self.name = nodename
 641		self.children = []
 642		self.depth = nodedepth
 643
 644# Class: Data
 645# Description:
 646#	 The primary container for suspend/resume test data. There is one for
 647#	 each test run. The data is organized into a cronological hierarchy:
 648#	 Data.dmesg {
 649#		root structure, started as dmesg & ftrace, but now only ftrace
 650#		contents: times for suspend start/end, resume start/end, fwdata
 651#		phases {
 652#			10 sequential, non-overlapping phases of S/R
 653#			contents: times for phase start/end, order/color data for html
 654#			devlist {
 655#				device callback or action list for this phase
 656#				device {
 657#					a single device callback or generic action
 658#					contents: start/stop times, pid/cpu/driver info
 659#						parents/children, html id for timeline/callgraph
 660#						optionally includes an ftrace callgraph
 661#						optionally includes intradev trace events
 662#				}
 663#			}
 664#		}
 665#	}
 666#
 667class Data:
 668	dmesg = {}  # root data structure
 669	phases = [] # ordered list of phases
 670	start = 0.0 # test start
 671	end = 0.0   # test end
 672	tSuspended = 0.0 # low-level suspend start
 673	tResumed = 0.0   # low-level resume start
 674	tLow = 0.0       # time spent in low-level suspend (standby/freeze)
 675	fwValid = False  # is firmware data available
 676	fwSuspend = 0    # time spent in firmware suspend
 677	fwResume = 0     # time spent in firmware resume
 678	dmesgtext = []   # dmesg text file in memory
 679	testnumber = 0
 680	idstr = ''
 681	html_device_id = 0
 682	stamp = 0
 683	outfile = ''
 684	dev_ubiquitous = ['msleep', 'udelay']
 685	def __init__(self, num):
 686		idchar = 'abcdefghijklmnopqrstuvwxyz'
 687		self.testnumber = num
 688		self.idstr = idchar[num]
 689		self.dmesgtext = []
 690		self.phases = []
 691		self.dmesg = { # fixed list of 10 phases
 692			'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
 693								'row': 0, 'color': '#CCFFCC', 'order': 0},
 694			        'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
 695								'row': 0, 'color': '#88FF88', 'order': 1},
 696			   'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
 697								'row': 0, 'color': '#00AA00', 'order': 2},
 698			  'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 699								'row': 0, 'color': '#008888', 'order': 3},
 700		    'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 701								'row': 0, 'color': '#0000FF', 'order': 4},
 702			 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 703								'row': 0, 'color': '#FF0000', 'order': 5},
 704			   'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 705								'row': 0, 'color': '#FF9900', 'order': 6},
 706			   'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
 707								'row': 0, 'color': '#FFCC00', 'order': 7},
 708			         'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
 709								'row': 0, 'color': '#FFFF88', 'order': 8},
 710			'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
 711								'row': 0, 'color': '#FFFFCC', 'order': 9}
 712		}
 713		self.phases = self.sortedPhases()
 714		self.devicegroups = []
 715		for phase in self.phases:
 716			self.devicegroups.append([phase])
 717	def getStart(self):
 718		return self.dmesg[self.phases[0]]['start']
 719	def setStart(self, time):
 720		self.start = time
 721		self.dmesg[self.phases[0]]['start'] = time
 722	def getEnd(self):
 723		return self.dmesg[self.phases[-1]]['end']
 724	def setEnd(self, time):
 725		self.end = time
 726		self.dmesg[self.phases[-1]]['end'] = time
 727	def isTraceEventOutsideDeviceCalls(self, pid, time):
 728		for phase in self.phases:
 729			list = self.dmesg[phase]['list']
 730			for dev in list:
 731				d = list[dev]
 732				if(d['pid'] == pid and time >= d['start'] and
 733					time < d['end']):
 734					return False
 735		return True
 736	def targetDevice(self, phaselist, start, end, pid=-1):
 737		tgtdev = ''
 738		for phase in phaselist:
 
 
 
 
 
 
 
 
 
 
 
 739			list = self.dmesg[phase]['list']
 740			for devname in list:
 741				dev = list[devname]
 742				if(pid >= 0 and dev['pid'] != pid):
 743					continue
 744				devS = dev['start']
 745				devE = dev['end']
 746				if(start < devS or start >= devE or end <= devS or end > devE):
 747					continue
 748				tgtdev = dev
 749				break
 750		return tgtdev
 751	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
 752		machstart = self.dmesg['suspend_machine']['start']
 753		machend = self.dmesg['resume_machine']['end']
 754		tgtdev = self.targetDevice(self.phases, start, end, pid)
 755		if not tgtdev and start >= machstart and end < machend:
 756			# device calls in machine phases should be serial
 757			tgtdev = self.targetDevice(['suspend_machine', 'resume_machine'], start, end)
 758		if not tgtdev:
 759			if 'scsi_eh' in proc:
 760				self.newActionGlobal(proc, start, end, pid)
 761				self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
 762			else:
 763				vprint('IGNORE: %s[%s](%d) [%f - %f] | %s | %s | %s' % (displayname, kprobename,
 764					pid, start, end, cdata, rdata, proc))
 765			return False
 766		# detail block fits within tgtdev
 767		if('src' not in tgtdev):
 768			tgtdev['src'] = []
 769		title = cdata+' '+rdata
 770		mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
 771		m = re.match(mstr, title)
 772		if m:
 773			c = m.group('caller')
 774			a = m.group('args').strip()
 775			r = m.group('ret')
 776			if len(r) > 6:
 777				r = ''
 778			else:
 779				r = 'ret=%s ' % r
 780			l = '%0.3fms' % ((end - start) * 1000)
 781			if kprobename in self.dev_ubiquitous:
 782				title = '%s(%s) <- %s, %s(%s)' % (displayname, a, c, r, l)
 783			else:
 784				title = '%s(%s) %s(%s)' % (displayname, a, r, l)
 785		e = TraceEvent(title, kprobename, start, end - start)
 786		tgtdev['src'].append(e)
 787		return True
 788	def trimTimeVal(self, t, t0, dT, left):
 789		if left:
 790			if(t > t0):
 791				if(t - dT < t0):
 792					return t0
 793				return t - dT
 794			else:
 795				return t
 796		else:
 797			if(t < t0 + dT):
 798				if(t > t0):
 799					return t0 + dT
 800				return t + dT
 801			else:
 802				return t
 803	def trimTime(self, t0, dT, left):
 804		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
 805		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
 806		self.start = self.trimTimeVal(self.start, t0, dT, left)
 807		self.end = self.trimTimeVal(self.end, t0, dT, left)
 808		for phase in self.phases:
 809			p = self.dmesg[phase]
 810			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
 811			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
 812			list = p['list']
 813			for name in list:
 814				d = list[name]
 815				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
 816				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
 817				if('ftrace' in d):
 818					cg = d['ftrace']
 819					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
 820					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
 821					for line in cg.list:
 822						line.time = self.trimTimeVal(line.time, t0, dT, left)
 823				if('src' in d):
 824					for e in d['src']:
 825						e.time = self.trimTimeVal(e.time, t0, dT, left)
 826	def normalizeTime(self, tZero):
 827		# trim out any standby or freeze clock time
 828		if(self.tSuspended != self.tResumed):
 829			if(self.tResumed > tZero):
 830				self.trimTime(self.tSuspended, \
 831					self.tResumed-self.tSuspended, True)
 832			else:
 833				self.trimTime(self.tSuspended, \
 834					self.tResumed-self.tSuspended, False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 835	def newPhaseWithSingleAction(self, phasename, devname, start, end, color):
 836		for phase in self.phases:
 837			self.dmesg[phase]['order'] += 1
 838		self.html_device_id += 1
 839		devid = '%s%d' % (self.idstr, self.html_device_id)
 840		list = dict()
 841		list[devname] = \
 842			{'start': start, 'end': end, 'pid': 0, 'par': '',
 843			'length': (end-start), 'row': 0, 'id': devid, 'drv': '' };
 844		self.dmesg[phasename] = \
 845			{'list': list, 'start': start, 'end': end,
 846			'row': 0, 'color': color, 'order': 0}
 847		self.phases = self.sortedPhases()
 848	def newPhase(self, phasename, start, end, color, order):
 849		if(order < 0):
 850			order = len(self.phases)
 851		for phase in self.phases[order:]:
 852			self.dmesg[phase]['order'] += 1
 853		if(order > 0):
 854			p = self.phases[order-1]
 855			self.dmesg[p]['end'] = start
 856		if(order < len(self.phases)):
 857			p = self.phases[order]
 858			self.dmesg[p]['start'] = end
 859		list = dict()
 860		self.dmesg[phasename] = \
 861			{'list': list, 'start': start, 'end': end,
 862			'row': 0, 'color': color, 'order': order}
 863		self.phases = self.sortedPhases()
 864		self.devicegroups.append([phasename])
 865	def setPhase(self, phase, ktime, isbegin):
 866		if(isbegin):
 867			self.dmesg[phase]['start'] = ktime
 868		else:
 869			self.dmesg[phase]['end'] = ktime
 870	def dmesgSortVal(self, phase):
 871		return self.dmesg[phase]['order']
 872	def sortedPhases(self):
 873		return sorted(self.dmesg, key=self.dmesgSortVal)
 874	def sortedDevices(self, phase):
 875		list = self.dmesg[phase]['list']
 876		slist = []
 877		tmp = dict()
 878		for devname in list:
 879			dev = list[devname]
 880			tmp[dev['start']] = devname
 881		for t in sorted(tmp):
 882			slist.append(tmp[t])
 883		return slist
 884	def fixupInitcalls(self, phase, end):
 885		# if any calls never returned, clip them at system resume end
 886		phaselist = self.dmesg[phase]['list']
 887		for devname in phaselist:
 888			dev = phaselist[devname]
 889			if(dev['end'] < 0):
 890				for p in self.phases:
 891					if self.dmesg[p]['end'] > dev['start']:
 892						dev['end'] = self.dmesg[p]['end']
 893						break
 894				vprint('%s (%s): callback didnt return' % (devname, phase))
 895	def deviceFilter(self, devicefilter):
 896		# remove all by the relatives of the filter devnames
 897		filter = []
 898		for phase in self.phases:
 899			list = self.dmesg[phase]['list']
 900			for name in devicefilter:
 901				dev = name
 902				while(dev in list):
 903					if(dev not in filter):
 904						filter.append(dev)
 905					dev = list[dev]['par']
 906				children = self.deviceDescendants(name, phase)
 907				for dev in children:
 908					if(dev not in filter):
 909						filter.append(dev)
 910		for phase in self.phases:
 911			list = self.dmesg[phase]['list']
 912			rmlist = []
 913			for name in list:
 914				pid = list[name]['pid']
 915				if(name not in filter and pid >= 0):
 916					rmlist.append(name)
 917			for name in rmlist:
 918				del list[name]
 919	def fixupInitcallsThatDidntReturn(self):
 920		# if any calls never returned, clip them at system resume end
 921		for phase in self.phases:
 922			self.fixupInitcalls(phase, self.getEnd())
 923	def isInsideTimeline(self, start, end):
 924		if(self.start <= start and self.end > start):
 925			return True
 926		return False
 927	def phaseOverlap(self, phases):
 928		rmgroups = []
 929		newgroup = []
 930		for group in self.devicegroups:
 931			for phase in phases:
 932				if phase not in group:
 933					continue
 934				for p in group:
 935					if p not in newgroup:
 936						newgroup.append(p)
 937				if group not in rmgroups:
 938					rmgroups.append(group)
 939		for group in rmgroups:
 940			self.devicegroups.remove(group)
 941		self.devicegroups.append(newgroup)
 942	def newActionGlobal(self, name, start, end, pid=-1, color=''):
 943		# if event starts before timeline start, expand timeline
 944		if(start < self.start):
 945			self.setStart(start)
 946		# if event ends after timeline end, expand the timeline
 947		if(end > self.end):
 948			self.setEnd(end)
 949		# which phase is this device callback or action "in"
 950		targetphase = "none"
 951		htmlclass = ''
 952		overlap = 0.0
 953		phases = []
 954		for phase in self.phases:
 955			pstart = self.dmesg[phase]['start']
 956			pend = self.dmesg[phase]['end']
 957			o = max(0, min(end, pend) - max(start, pstart))
 958			if o > 0:
 959				phases.append(phase)
 960			if o > overlap:
 961				if overlap > 0 and phase == 'post_resume':
 962					continue
 963				targetphase = phase
 964				overlap = o
 965		if pid == -2:
 966			htmlclass = ' bg'
 967		if len(phases) > 1:
 968			htmlclass = ' bg'
 969			self.phaseOverlap(phases)
 970		if targetphase in self.phases:
 971			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
 972			return (targetphase, newname)
 973		return False
 974	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
 975		# new device callback for a specific phase
 976		self.html_device_id += 1
 977		devid = '%s%d' % (self.idstr, self.html_device_id)
 978		list = self.dmesg[phase]['list']
 979		length = -1.0
 980		if(start >= 0 and end >= 0):
 981			length = end - start
 982		if pid == -2:
 983			i = 2
 984			origname = name
 985			while(name in list):
 986				name = '%s[%d]' % (origname, i)
 987				i += 1
 988		list[name] = {'start': start, 'end': end, 'pid': pid, 'par': parent,
 989					  'length': length, 'row': 0, 'id': devid, 'drv': drv }
 990		if htmlclass:
 991			list[name]['htmlclass'] = htmlclass
 992		if color:
 993			list[name]['color'] = color
 994		return name
 995	def deviceIDs(self, devlist, phase):
 996		idlist = []
 997		list = self.dmesg[phase]['list']
 998		for devname in list:
 999			if devname in devlist:
1000				idlist.append(list[devname]['id'])
1001		return idlist
1002	def deviceParentID(self, devname, phase):
1003		pdev = ''
1004		pdevid = ''
1005		list = self.dmesg[phase]['list']
1006		if devname in list:
1007			pdev = list[devname]['par']
1008		if pdev in list:
1009			return list[pdev]['id']
1010		return pdev
1011	def deviceChildren(self, devname, phase):
1012		devlist = []
1013		list = self.dmesg[phase]['list']
1014		for child in list:
1015			if(list[child]['par'] == devname):
1016				devlist.append(child)
1017		return devlist
1018	def deviceDescendants(self, devname, phase):
1019		children = self.deviceChildren(devname, phase)
1020		family = children
1021		for child in children:
1022			family += self.deviceDescendants(child, phase)
1023		return family
1024	def deviceChildrenIDs(self, devname, phase):
1025		devlist = self.deviceChildren(devname, phase)
1026		return self.deviceIDs(devlist, phase)
1027	def printDetails(self):
1028		vprint('          test start: %f' % self.start)
1029		for phase in self.phases:
1030			dc = len(self.dmesg[phase]['list'])
1031			vprint('    %16s: %f - %f (%d devices)' % (phase, \
1032				self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
1033		vprint('            test end: %f' % self.end)
1034	def deviceChildrenAllPhases(self, devname):
1035		devlist = []
1036		for phase in self.phases:
1037			list = self.deviceChildren(devname, phase)
1038			for dev in list:
1039				if dev not in devlist:
1040					devlist.append(dev)
1041		return devlist
1042	def masterTopology(self, name, list, depth):
1043		node = DeviceNode(name, depth)
1044		for cname in list:
1045			# avoid recursions
1046			if name == cname:
1047				continue
1048			clist = self.deviceChildrenAllPhases(cname)
1049			cnode = self.masterTopology(cname, clist, depth+1)
1050			node.children.append(cnode)
1051		return node
1052	def printTopology(self, node):
1053		html = ''
1054		if node.name:
1055			info = ''
1056			drv = ''
1057			for phase in self.phases:
1058				list = self.dmesg[phase]['list']
1059				if node.name in list:
1060					s = list[node.name]['start']
1061					e = list[node.name]['end']
1062					if list[node.name]['drv']:
1063						drv = ' {'+list[node.name]['drv']+'}'
1064					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1065			html += '<li><b>'+node.name+drv+'</b>'
1066			if info:
1067				html += '<ul>'+info+'</ul>'
1068			html += '</li>'
1069		if len(node.children) > 0:
1070			html += '<ul>'
1071			for cnode in node.children:
1072				html += self.printTopology(cnode)
1073			html += '</ul>'
1074		return html
1075	def rootDeviceList(self):
1076		# list of devices graphed
1077		real = []
1078		for phase in self.dmesg:
1079			list = self.dmesg[phase]['list']
1080			for dev in list:
1081				if list[dev]['pid'] >= 0 and dev not in real:
1082					real.append(dev)
1083		# list of top-most root devices
1084		rootlist = []
1085		for phase in self.dmesg:
1086			list = self.dmesg[phase]['list']
1087			for dev in list:
1088				pdev = list[dev]['par']
1089				pid = list[dev]['pid']
1090				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1091					continue
1092				if pdev and pdev not in real and pdev not in rootlist:
1093					rootlist.append(pdev)
1094		return rootlist
1095	def deviceTopology(self):
1096		rootlist = self.rootDeviceList()
1097		master = self.masterTopology('', rootlist, 0)
1098		return self.printTopology(master)
1099	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1100		# only select devices that will actually show up in html
1101		self.tdevlist = dict()
1102		for phase in self.dmesg:
1103			devlist = []
1104			list = self.dmesg[phase]['list']
1105			for dev in list:
1106				length = (list[dev]['end'] - list[dev]['start']) * 1000
1107				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1108				if width != '0.000000' and length >= mindevlen:
1109					devlist.append(dev)
1110			self.tdevlist[phase] = devlist
1111
1112# Class: TraceEvent
1113# Description:
1114#	 A container for trace event data found in the ftrace file
1115class TraceEvent:
1116	text = ''
 
1117	time = 0.0
 
1118	length = 0.0
1119	title = ''
1120	row = 0
1121	def __init__(self, a, n, t, l):
1122		self.title = a
1123		self.text = n
1124		self.time = t
1125		self.length = l
1126
1127# Class: FTraceLine
1128# Description:
1129#	 A container for a single line of ftrace data. There are six basic types:
1130#		 callgraph line:
1131#			  call: "  dpm_run_callback() {"
1132#			return: "  }"
1133#			  leaf: " dpm_run_callback();"
1134#		 trace event:
1135#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
1136#			 suspend_resume: phase or custom exec block data
1137#			 device_pm_callback: device callback info
1138class FTraceLine:
1139	time = 0.0
1140	length = 0.0
1141	fcall = False
1142	freturn = False
1143	fevent = False
1144	fkprobe = False
1145	depth = 0
1146	name = ''
1147	type = ''
1148	def __init__(self, t, m='', d=''):
1149		self.time = float(t)
1150		if not m and not d:
1151			return
1152		# is this a trace event
1153		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1154			if(d == 'traceevent'):
1155				# nop format trace event
1156				msg = m
1157			else:
1158				# function_graph format trace event
1159				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1160				msg = em.group('msg')
1161
1162			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1163			if(emm):
1164				self.name = emm.group('msg')
1165				self.type = emm.group('call')
1166			else:
1167				self.name = msg
1168			km = re.match('^(?P<n>.*)_cal$', self.type)
1169			if km:
1170				self.fcall = True
1171				self.fkprobe = True
1172				self.type = km.group('n')
1173				return
1174			km = re.match('^(?P<n>.*)_ret$', self.type)
1175			if km:
1176				self.freturn = True
1177				self.fkprobe = True
1178				self.type = km.group('n')
1179				return
1180			self.fevent = True
1181			return
1182		# convert the duration to seconds
1183		if(d):
1184			self.length = float(d)/1000000
1185		# the indentation determines the depth
1186		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
1187		if(not match):
1188			return
1189		self.depth = self.getDepth(match.group('d'))
1190		m = match.group('o')
1191		# function return
1192		if(m[0] == '}'):
1193			self.freturn = True
1194			if(len(m) > 1):
1195				# includes comment with function name
1196				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
1197				if(match):
1198					self.name = match.group('n').strip()
1199		# function call
1200		else:
1201			self.fcall = True
1202			# function call with children
1203			if(m[-1] == '{'):
1204				match = re.match('^(?P<n>.*) *\(.*', m)
1205				if(match):
1206					self.name = match.group('n').strip()
1207			# function call with no children (leaf)
1208			elif(m[-1] == ';'):
1209				self.freturn = True
1210				match = re.match('^(?P<n>.*) *\(.*', m)
1211				if(match):
1212					self.name = match.group('n').strip()
1213			# something else (possibly a trace marker)
1214			else:
1215				self.name = m
1216	def getDepth(self, str):
1217		return len(str)/2
1218	def debugPrint(self, dev=''):
1219		if(self.freturn and self.fcall):
1220			print('%s -- %f (%02d): %s(); (%.3f us)' % (dev, self.time, \
1221				self.depth, self.name, self.length*1000000))
1222		elif(self.freturn):
1223			print('%s -- %f (%02d): %s} (%.3f us)' % (dev, self.time, \
1224				self.depth, self.name, self.length*1000000))
1225		else:
1226			print('%s -- %f (%02d): %s() { (%.3f us)' % (dev, self.time, \
1227				self.depth, self.name, self.length*1000000))
1228	def startMarker(self):
1229		global sysvals
1230		# Is this the starting line of a suspend?
1231		if not self.fevent:
1232			return False
1233		if sysvals.usetracemarkers:
1234			if(self.name == 'SUSPEND START'):
1235				return True
1236			return False
1237		else:
1238			if(self.type == 'suspend_resume' and
1239				re.match('suspend_enter\[.*\] begin', self.name)):
1240				return True
1241			return False
1242	def endMarker(self):
1243		# Is this the ending line of a resume?
1244		if not self.fevent:
1245			return False
1246		if sysvals.usetracemarkers:
1247			if(self.name == 'RESUME COMPLETE'):
1248				return True
1249			return False
1250		else:
1251			if(self.type == 'suspend_resume' and
1252				re.match('thaw_processes\[.*\] end', self.name)):
1253				return True
1254			return False
1255
1256# Class: FTraceCallGraph
1257# Description:
1258#	 A container for the ftrace callgraph of a single recursive function.
1259#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
1260#	 Each instance is tied to a single device in a single phase, and is
1261#	 comprised of an ordered list of FTraceLine objects
1262class FTraceCallGraph:
1263	start = -1.0
1264	end = -1.0
1265	list = []
1266	invalid = False
1267	depth = 0
1268	pid = 0
1269	def __init__(self, pid):
1270		self.start = -1.0
1271		self.end = -1.0
1272		self.list = []
1273		self.depth = 0
1274		self.pid = pid
1275	def addLine(self, line, debug=False):
1276		# if this is already invalid, just leave
1277		if(self.invalid):
1278			return False
1279		# invalidate on too much data or bad depth
1280		if(len(self.list) >= 1000000 or self.depth < 0):
1281			self.invalidate(line)
1282			return False
1283		# compare current depth with this lines pre-call depth
1284		prelinedep = line.depth
1285		if(line.freturn and not line.fcall):
1286			prelinedep += 1
1287		last = 0
1288		lasttime = line.time
1289		virtualfname = 'execution_misalignment'
1290		if len(self.list) > 0:
1291			last = self.list[-1]
1292			lasttime = last.time
1293		# handle low misalignments by inserting returns
1294		if prelinedep < self.depth:
1295			if debug and last:
1296				print '-------- task %d --------' % self.pid
1297				last.debugPrint()
1298			idx = 0
1299			# add return calls to get the depth down
1300			while prelinedep < self.depth:
1301				if debug:
1302					print 'MISALIGN LOW (add returns): C%d - eC%d' % (self.depth, prelinedep)
1303				self.depth -= 1
1304				if idx == 0 and last and last.fcall and not last.freturn:
1305					# special case, turn last call into a leaf
1306					last.depth = self.depth
1307					last.freturn = True
1308					last.length = line.time - last.time
1309					if debug:
1310						last.debugPrint()
1311				else:
1312					vline = FTraceLine(lasttime)
1313					vline.depth = self.depth
1314					vline.name = virtualfname
1315					vline.freturn = True
1316					self.list.append(vline)
1317					if debug:
1318						vline.debugPrint()
1319				idx += 1
1320			if debug:
1321				line.debugPrint()
1322				print ''
1323		# handle high misalignments by inserting calls
1324		elif prelinedep > self.depth:
1325			if debug and last:
1326				print '-------- task %d --------' % self.pid
1327				last.debugPrint()
1328			idx = 0
1329			# add calls to get the depth up
1330			while prelinedep > self.depth:
1331				if debug:
1332					print 'MISALIGN HIGH (add calls): C%d - eC%d' % (self.depth, prelinedep)
1333				if idx == 0 and line.freturn and not line.fcall:
1334					# special case, turn this return into a leaf
1335					line.fcall = True
1336					prelinedep -= 1
1337				else:
1338					vline = FTraceLine(lasttime)
1339					vline.depth = self.depth
1340					vline.name = virtualfname
1341					vline.fcall = True
1342					if debug:
1343						vline.debugPrint()
1344					self.list.append(vline)
1345					self.depth += 1
1346					if not last:
1347						self.start = vline.time
1348				idx += 1
1349			if debug:
1350				line.debugPrint()
1351				print ''
1352		# process the call and set the new depth
1353		if(line.fcall and not line.freturn):
 
1354			self.depth += 1
1355		elif(line.freturn and not line.fcall):
1356			self.depth -= 1
1357		if len(self.list) < 1:
1358			self.start = line.time
1359		self.list.append(line)
 
 
 
1360		if(line.depth == 0 and line.freturn):
1361			if(self.start < 0):
1362				self.start = line.time
1363			self.end = line.time
1364			if line.fcall:
1365				self.end += line.length
1366			if self.list[0].name == virtualfname:
1367				self.invalid = True
1368			return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1369		return False
1370	def invalidate(self, line):
1371		if(len(self.list) > 0):
1372			first = self.list[0]
1373			self.list = []
1374			self.list.append(first)
1375		self.invalid = True
1376		id = 'task %s' % (self.pid)
1377		window = '(%f - %f)' % (self.start, line.time)
1378		if(self.depth < 0):
1379			vprint('Too much data for '+id+\
1380				' (buffer overflow), ignoring this callback')
1381		else:
1382			vprint('Too much data for '+id+\
1383				' '+window+', ignoring this callback')
1384	def slice(self, t0, tN):
1385		minicg = FTraceCallGraph(0)
1386		count = -1
1387		firstdepth = 0
1388		for l in self.list:
1389			if(l.time < t0 or l.time > tN):
1390				continue
1391			if(count < 0):
1392				if(not l.fcall or l.name == 'dev_driver_string'):
1393					continue
1394				firstdepth = l.depth
1395				count = 0
1396			l.depth -= firstdepth
1397			minicg.addLine(l)
1398			if((count == 0 and l.freturn and l.fcall) or
1399				(count > 0 and l.depth <= 0)):
1400				break
1401			count += 1
1402		return minicg
1403	def repair(self, enddepth):
1404		# bring the depth back to 0 with additional returns
1405		fixed = False
1406		last = self.list[-1]
1407		for i in reversed(range(enddepth)):
1408			t = FTraceLine(last.time)
1409			t.depth = i
1410			t.freturn = True
1411			fixed = self.addLine(t)
1412			if fixed:
1413				self.end = last.time
1414				return True
1415		return False
1416	def postProcess(self, debug=False):
1417		stack = dict()
1418		cnt = 0
1419		for l in self.list:
1420			if(l.fcall and not l.freturn):
1421				stack[l.depth] = l
1422				cnt += 1
1423			elif(l.freturn and not l.fcall):
1424				if(l.depth not in stack):
1425					if debug:
1426						print 'Post Process Error: Depth missing'
1427						l.debugPrint()
1428					return False
1429				# transfer total time from return line to call line
1430				stack[l.depth].length = l.length
1431				stack.pop(l.depth)
1432				l.length = 0
1433				cnt -= 1
1434		if(cnt == 0):
1435			# trace caught the whole call tree
1436			return True
1437		elif(cnt < 0):
1438			if debug:
1439				print 'Post Process Error: Depth is less than 0'
1440			return False
1441		# trace ended before call tree finished
1442		return self.repair(cnt)
1443	def deviceMatch(self, pid, data):
1444		found = False
1445		# add the callgraph data to the device hierarchy
1446		borderphase = {
1447			'dpm_prepare': 'suspend_prepare',
1448			'dpm_complete': 'resume_complete'
1449		}
1450		if(self.list[0].name in borderphase):
1451			p = borderphase[self.list[0].name]
1452			list = data.dmesg[p]['list']
1453			for devname in list:
1454				dev = list[devname]
1455				if(pid == dev['pid'] and
1456					self.start <= dev['start'] and
1457					self.end >= dev['end']):
1458					dev['ftrace'] = self.slice(dev['start'], dev['end'])
1459					found = True
1460			return found
1461		for p in data.phases:
1462			if(data.dmesg[p]['start'] <= self.start and
1463				self.start <= data.dmesg[p]['end']):
1464				list = data.dmesg[p]['list']
1465				for devname in list:
1466					dev = list[devname]
1467					if(pid == dev['pid'] and
1468						self.start <= dev['start'] and
1469						self.end >= dev['end']):
1470						dev['ftrace'] = self
1471						found = True
1472						break
1473				break
1474		return found
1475	def newActionFromFunction(self, data):
1476		name = self.list[0].name
1477		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
1478			return
1479		fs = self.start
1480		fe = self.end
1481		if fs < data.start or fe > data.end:
1482			return
1483		phase = ''
1484		for p in data.phases:
1485			if(data.dmesg[p]['start'] <= self.start and
1486				self.start < data.dmesg[p]['end']):
1487				phase = p
1488				break
1489		if not phase:
1490			return
1491		out = data.newActionGlobal(name, fs, fe, -2)
1492		if out:
1493			phase, myname = out
1494			data.dmesg[phase]['list'][myname]['ftrace'] = self
1495	def debugPrint(self):
1496		print('[%f - %f] %s (%d)') % (self.start, self.end, self.list[0].name, self.pid)
1497		for l in self.list:
1498			if(l.freturn and l.fcall):
1499				print('%f (%02d): %s(); (%.3f us)' % (l.time, \
1500					l.depth, l.name, l.length*1000000))
1501			elif(l.freturn):
1502				print('%f (%02d): %s} (%.3f us)' % (l.time, \
1503					l.depth, l.name, l.length*1000000))
1504			else:
1505				print('%f (%02d): %s() { (%.3f us)' % (l.time, \
1506					l.depth, l.name, l.length*1000000))
1507		print(' ')
1508
1509# Class: Timeline
1510# Description:
1511#	 A container for a device timeline which calculates
1512#	 all the html properties to display it correctly
 
1513class Timeline:
1514	html = {}
1515	height = 0	# total timeline height
1516	scaleH = 20	# timescale (top) row height
1517	rowH = 30	# device row height
1518	bodyH = 0	# body height
1519	rows = 0	# total timeline rows
1520	phases = []
1521	rowmaxlines = dict()
1522	rowcount = dict()
1523	rowheight = dict()
1524	def __init__(self, rowheight):
1525		self.rowH = rowheight
1526		self.html = {
1527			'header': '',
1528			'timeline': '',
1529			'legend': '',
 
1530		}
1531	# Function: getDeviceRows
1532	# Description:
1533	#    determine how may rows the device funcs will take
1534	# Arguments:
1535	#	 rawlist: the list of devices/actions for a single phase
1536	# Output:
1537	#	 The total number of rows needed to display this phase of the timeline
1538	def getDeviceRows(self, rawlist):
1539		# clear all rows and set them to undefined
1540		lendict = dict()
1541		for item in rawlist:
1542			item.row = -1
1543			lendict[item] = item.length
1544		list = []
1545		for i in sorted(lendict, key=lendict.get, reverse=True):
1546			list.append(i)
1547		remaining = len(list)
1548		rowdata = dict()
1549		row = 1
1550		# try to pack each row with as many ranges as possible
1551		while(remaining > 0):
1552			if(row not in rowdata):
1553				rowdata[row] = []
1554			for i in list:
1555				if(i.row >= 0):
1556					continue
1557				s = i.time
1558				e = i.time + i.length
1559				valid = True
1560				for ritem in rowdata[row]:
1561					rs = ritem.time
1562					re = ritem.time + ritem.length
1563					if(not (((s <= rs) and (e <= rs)) or
1564						((s >= re) and (e >= re)))):
1565						valid = False
1566						break
1567				if(valid):
1568					rowdata[row].append(i)
1569					i.row = row
1570					remaining -= 1
1571			row += 1
1572		return row
1573	# Function: getPhaseRows
1574	# Description:
1575	#	 Organize the timeline entries into the smallest
1576	#	 number of rows possible, with no entry overlapping
1577	# Arguments:
1578	#	 list: the list of devices/actions for a single phase
1579	#	 devlist: string list of device names to use
1580	# Output:
1581	#	 The total number of rows needed to display this phase of the timeline
1582	def getPhaseRows(self, dmesg, devlist):
1583		# clear all rows and set them to undefined
1584		remaining = len(devlist)
1585		rowdata = dict()
1586		row = 0
1587		lendict = dict()
1588		myphases = []
1589		for item in devlist:
1590			if item[0] not in self.phases:
1591				self.phases.append(item[0])
1592			if item[0] not in myphases:
1593				myphases.append(item[0])
1594				self.rowmaxlines[item[0]] = dict()
1595				self.rowheight[item[0]] = dict()
1596			dev = dmesg[item[0]]['list'][item[1]]
1597			dev['row'] = -1
1598			lendict[item] = float(dev['end']) - float(dev['start'])
1599			if 'src' in dev:
1600				dev['devrows'] = self.getDeviceRows(dev['src'])
1601		lenlist = []
1602		for i in sorted(lendict, key=lendict.get, reverse=True):
1603			lenlist.append(i)
1604		orderedlist = []
1605		for item in lenlist:
1606			dev = dmesg[item[0]]['list'][item[1]]
1607			if dev['pid'] == -2:
1608				orderedlist.append(item)
1609		for item in lenlist:
1610			if item not in orderedlist:
1611				orderedlist.append(item)
1612		# try to pack each row with as many ranges as possible
1613		while(remaining > 0):
1614			rowheight = 1
1615			if(row not in rowdata):
1616				rowdata[row] = []
1617			for item in orderedlist:
1618				dev = dmesg[item[0]]['list'][item[1]]
1619				if(dev['row'] < 0):
1620					s = dev['start']
1621					e = dev['end']
1622					valid = True
1623					for ritem in rowdata[row]:
1624						rs = ritem['start']
1625						re = ritem['end']
1626						if(not (((s <= rs) and (e <= rs)) or
1627							((s >= re) and (e >= re)))):
1628							valid = False
1629							break
1630					if(valid):
1631						rowdata[row].append(dev)
1632						dev['row'] = row
1633						remaining -= 1
1634						if 'devrows' in dev and dev['devrows'] > rowheight:
1635							rowheight = dev['devrows']
1636			for phase in myphases:
1637				self.rowmaxlines[phase][row] = rowheight
1638				self.rowheight[phase][row] = rowheight * self.rowH
1639			row += 1
1640		if(row > self.rows):
1641			self.rows = int(row)
1642		for phase in myphases:
1643			self.rowcount[phase] = row
1644		return row
1645	def phaseRowHeight(self, phase, row):
1646		return self.rowheight[phase][row]
1647	def phaseRowTop(self, phase, row):
1648		top = 0
1649		for i in sorted(self.rowheight[phase]):
1650			if i >= row:
1651				break
1652			top += self.rowheight[phase][i]
1653		return top
1654	# Function: calcTotalRows
1655	# Description:
1656	#	 Calculate the heights and offsets for the header and rows
1657	def calcTotalRows(self):
1658		maxrows = 0
1659		standardphases = []
1660		for phase in self.phases:
1661			total = 0
1662			for i in sorted(self.rowmaxlines[phase]):
1663				total += self.rowmaxlines[phase][i]
1664			if total > maxrows:
1665				maxrows = total
1666			if total == self.rowcount[phase]:
1667				standardphases.append(phase)
1668		self.height = self.scaleH + (maxrows*self.rowH)
1669		self.bodyH = self.height - self.scaleH
1670		for phase in standardphases:
1671			for i in sorted(self.rowheight[phase]):
1672				self.rowheight[phase][i] = self.bodyH/self.rowcount[phase]
1673	# Function: createTimeScale
1674	# Description:
1675	#	 Create the timescale for a timeline block
1676	# Arguments:
1677	#	 m0: start time (mode begin)
1678	#	 mMax: end time (mode end)
1679	#	 tTotal: total timeline time
1680	#	 mode: suspend or resume
1681	# Output:
1682	#	 The html code needed to display the time scale
1683	def createTimeScale(self, m0, mMax, tTotal, mode):
1684		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
1685		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">Resume</div>\n'
1686		output = '<div class="timescale">\n'
1687		# set scale for timeline
1688		mTotal = mMax - m0
1689		tS = 0.1
1690		if(tTotal <= 0):
1691			return output+'</div>\n'
1692		if(tTotal > 4):
1693			tS = 1
1694		divTotal = int(mTotal/tS) + 1
1695		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
1696		for i in range(divTotal):
1697			htmlline = ''
1698			if(mode == 'resume'):
1699				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
1700				val = '%0.fms' % (float(i)*tS*1000)
1701				htmlline = timescale.format(pos, val)
1702				if(i == 0):
1703					htmlline = rline
1704			else:
1705				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
1706				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
1707				if(i == divTotal - 1):
1708					val = 'Suspend'
1709				htmlline = timescale.format(pos, val)
1710			output += htmlline
1711		output += '</div>\n'
1712		return output
1713
1714# Class: TestProps
1715# Description:
1716#	 A list of values describing the properties of these test runs
1717class TestProps:
1718	stamp = ''
1719	tracertype = ''
1720	S0i3 = False
1721	fwdata = []
1722	ftrace_line_fmt_fg = \
1723		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
1724		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
1725		'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
1726	ftrace_line_fmt_nop = \
1727		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
1728		'(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
1729		'(?P<msg>.*)'
1730	ftrace_line_fmt = ftrace_line_fmt_nop
1731	cgformat = False
 
 
 
 
1732	data = 0
1733	ktemp = dict()
1734	def __init__(self):
1735		self.ktemp = dict()
 
 
 
 
 
1736	def setTracerType(self, tracer):
1737		self.tracertype = tracer
1738		if(tracer == 'function_graph'):
1739			self.cgformat = True
1740			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
1741		elif(tracer == 'nop'):
1742			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
1743		else:
1744			doError('Invalid tracer format: [%s]' % tracer, False)
1745
1746# Class: TestRun
1747# Description:
1748#	 A container for a suspend/resume test run. This is necessary as
1749#	 there could be more than one, and they need to be separate.
1750class TestRun:
1751	ftemp = dict()
1752	ttemp = dict()
1753	data = 0
1754	def __init__(self, dataobj):
1755		self.data = dataobj
1756		self.ftemp = dict()
1757		self.ttemp = dict()
1758
1759# ----------------- FUNCTIONS --------------------
1760
1761# Function: vprint
1762# Description:
1763#	 verbose print (prints only with -verbose option)
1764# Arguments:
1765#	 msg: the debug/log message to print
1766def vprint(msg):
1767	global sysvals
1768	if(sysvals.verbose):
1769		print(msg)
1770
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1771# Function: parseStamp
1772# Description:
1773#	 Pull in the stamp comment line from the data file(s),
1774#	 create the stamp, and add it to the global sysvals object
1775# Arguments:
1776#	 m: the valid re.match output for the stamp line
1777def parseStamp(line, data):
1778	global sysvals
1779
1780	m = re.match(sysvals.stampfmt, line)
1781	data.stamp = {'time': '', 'host': '', 'mode': ''}
1782	dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
1783		int(m.group('d')), int(m.group('H')), int(m.group('M')),
1784		int(m.group('S')))
1785	data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
1786	data.stamp['host'] = m.group('host')
1787	data.stamp['mode'] = m.group('mode')
1788	data.stamp['kernel'] = m.group('kernel')
1789	sysvals.hostname = data.stamp['host']
1790	sysvals.suspendmode = data.stamp['mode']
1791	if not sysvals.stamp:
1792		sysvals.stamp = data.stamp
1793
1794# Function: diffStamp
1795# Description:
1796#	compare the host, kernel, and mode fields in 3 stamps
1797# Arguments:
1798#	 stamp1: string array with mode, kernel, and host
1799#	 stamp2: string array with mode, kernel, and host
1800# Return:
1801#	True if stamps differ, False if they're the same
1802def diffStamp(stamp1, stamp2):
1803	if 'host' in stamp1 and 'host' in stamp2:
1804		if stamp1['host'] != stamp2['host']:
1805			return True
1806	if 'kernel' in stamp1 and 'kernel' in stamp2:
1807		if stamp1['kernel'] != stamp2['kernel']:
1808			return True
1809	if 'mode' in stamp1 and 'mode' in stamp2:
1810		if stamp1['mode'] != stamp2['mode']:
1811			return True
1812	return False
1813
1814# Function: doesTraceLogHaveTraceEvents
1815# Description:
1816#	 Quickly determine if the ftrace log has some or all of the trace events
1817#	 required for primary parsing. Set the usetraceevents and/or
1818#	 usetraceeventsonly flags in the global sysvals object
1819def doesTraceLogHaveTraceEvents():
1820	global sysvals
1821
1822	# check for kprobes
1823	sysvals.usekprobes = False
1824	out = os.system('grep -q "_cal: (" '+sysvals.ftracefile)
1825	if(out == 0):
1826		sysvals.usekprobes = True
1827	# check for callgraph data on trace event blocks
1828	out = os.system('grep -q "_cpu_down()" '+sysvals.ftracefile)
1829	if(out == 0):
1830		sysvals.usekprobes = True
1831	out = os.popen('head -1 '+sysvals.ftracefile).read().replace('\n', '')
1832	m = re.match(sysvals.stampfmt, out)
1833	if m and m.group('mode') == 'command':
1834		sysvals.usetraceeventsonly = True
1835		sysvals.usetraceevents = True
1836		return
1837	# figure out what level of trace events are supported
1838	sysvals.usetraceeventsonly = True
1839	sysvals.usetraceevents = False
1840	for e in sysvals.traceevents:
1841		out = os.system('grep -q "'+e+': " '+sysvals.ftracefile)
1842		if(out != 0):
1843			sysvals.usetraceeventsonly = False
1844		if(e == 'suspend_resume' and out == 0):
1845			sysvals.usetraceevents = True
1846	# determine is this log is properly formatted
1847	for e in ['SUSPEND START', 'RESUME COMPLETE']:
1848		out = os.system('grep -q "'+e+'" '+sysvals.ftracefile)
1849		if(out != 0):
1850			sysvals.usetracemarkers = False
1851
1852# Function: appendIncompleteTraceLog
1853# Description:
1854#	 [deprecated for kernel 3.15 or newer]
1855#	 Legacy support of ftrace outputs that lack the device_pm_callback
1856#	 and/or suspend_resume trace events. The primary data should be
1857#	 taken from dmesg, and this ftrace is used only for callgraph data
1858#	 or custom actions in the timeline. The data is appended to the Data
1859#	 objects provided.
1860# Arguments:
1861#	 testruns: the array of Data objects obtained from parseKernelLog
1862def appendIncompleteTraceLog(testruns):
1863	global sysvals
1864
1865	# create TestRun vessels for ftrace parsing
1866	testcnt = len(testruns)
1867	testidx = 0
1868	testrun = []
1869	for data in testruns:
1870		testrun.append(TestRun(data))
1871
1872	# extract the callgraph and traceevent data
1873	vprint('Analyzing the ftrace data...')
1874	tp = TestProps()
1875	tf = open(sysvals.ftracefile, 'r')
1876	data = 0
1877	for line in tf:
1878		# remove any latent carriage returns
1879		line = line.replace('\r\n', '')
1880		# grab the time stamp
1881		m = re.match(sysvals.stampfmt, line)
1882		if(m):
1883			tp.stamp = line
 
 
 
 
 
 
 
1884			continue
1885		# determine the trace data type (required for further parsing)
1886		m = re.match(sysvals.tracertypefmt, line)
1887		if(m):
1888			tp.setTracerType(m.group('t'))
1889			continue
1890		# device properties line
1891		if(re.match(sysvals.devpropfmt, line)):
1892			devProps(line)
1893			continue
1894		# parse only valid lines, if this is not one move on
1895		m = re.match(tp.ftrace_line_fmt, line)
1896		if(not m):
1897			continue
1898		# gather the basic message data from the line
1899		m_time = m.group('time')
1900		m_pid = m.group('pid')
1901		m_msg = m.group('msg')
1902		if(tp.cgformat):
1903			m_param3 = m.group('dur')
1904		else:
1905			m_param3 = 'traceevent'
1906		if(m_time and m_pid and m_msg):
1907			t = FTraceLine(m_time, m_msg, m_param3)
1908			pid = int(m_pid)
1909		else:
1910			continue
1911		# the line should be a call, return, or event
1912		if(not t.fcall and not t.freturn and not t.fevent):
1913			continue
1914		# look for the suspend start marker
1915		if(t.startMarker()):
1916			data = testrun[testidx].data
1917			parseStamp(tp.stamp, data)
1918			data.setStart(t.time)
1919			continue
1920		if(not data):
1921			continue
1922		# find the end of resume
1923		if(t.endMarker()):
1924			data.setEnd(t.time)
1925			testidx += 1
1926			if(testidx >= testcnt):
1927				break
1928			continue
1929		# trace event processing
1930		if(t.fevent):
1931			# general trace events have two types, begin and end
1932			if(re.match('(?P<name>.*) begin$', t.name)):
1933				isbegin = True
1934			elif(re.match('(?P<name>.*) end$', t.name)):
1935				isbegin = False
1936			else:
1937				continue
1938			m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
1939			if(m):
1940				val = m.group('val')
1941				if val == '0':
1942					name = m.group('name')
1943				else:
1944					name = m.group('name')+'['+val+']'
1945			else:
1946				m = re.match('(?P<name>.*) .*', t.name)
1947				name = m.group('name')
1948			# special processing for trace events
1949			if re.match('dpm_prepare\[.*', name):
1950				continue
1951			elif re.match('machine_suspend.*', name):
1952				continue
1953			elif re.match('suspend_enter\[.*', name):
1954				if(not isbegin):
1955					data.dmesg['suspend_prepare']['end'] = t.time
1956				continue
1957			elif re.match('dpm_suspend\[.*', name):
1958				if(not isbegin):
1959					data.dmesg['suspend']['end'] = t.time
1960				continue
1961			elif re.match('dpm_suspend_late\[.*', name):
1962				if(isbegin):
1963					data.dmesg['suspend_late']['start'] = t.time
1964				else:
1965					data.dmesg['suspend_late']['end'] = t.time
1966				continue
1967			elif re.match('dpm_suspend_noirq\[.*', name):
1968				if(isbegin):
1969					data.dmesg['suspend_noirq']['start'] = t.time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1970				else:
1971					data.dmesg['suspend_noirq']['end'] = t.time
1972				continue
1973			elif re.match('dpm_resume_noirq\[.*', name):
1974				if(isbegin):
1975					data.dmesg['resume_machine']['end'] = t.time
1976					data.dmesg['resume_noirq']['start'] = t.time
1977				else:
1978					data.dmesg['resume_noirq']['end'] = t.time
1979				continue
1980			elif re.match('dpm_resume_early\[.*', name):
1981				if(isbegin):
1982					data.dmesg['resume_early']['start'] = t.time
1983				else:
1984					data.dmesg['resume_early']['end'] = t.time
1985				continue
1986			elif re.match('dpm_resume\[.*', name):
1987				if(isbegin):
1988					data.dmesg['resume']['start'] = t.time
1989				else:
1990					data.dmesg['resume']['end'] = t.time
1991				continue
1992			elif re.match('dpm_complete\[.*', name):
1993				if(isbegin):
1994					data.dmesg['resume_complete']['start'] = t.time
1995				else:
1996					data.dmesg['resume_complete']['end'] = t.time
1997				continue
1998			# skip trace events inside devices calls
1999			if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2000				continue
2001			# global events (outside device calls) are simply graphed
2002			if(isbegin):
2003				# store each trace event in ttemp
2004				if(name not in testrun[testidx].ttemp):
2005					testrun[testidx].ttemp[name] = []
2006				testrun[testidx].ttemp[name].append(\
2007					{'begin': t.time, 'end': t.time})
2008			else:
2009				# finish off matching trace event in ttemp
2010				if(name in testrun[testidx].ttemp):
2011					testrun[testidx].ttemp[name][-1]['end'] = t.time
2012		# call/return processing
2013		elif sysvals.usecallgraph:
2014			# create a callgraph object for the data
2015			if(pid not in testrun[testidx].ftemp):
2016				testrun[testidx].ftemp[pid] = []
2017				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid))
2018			# when the call is finished, see which device matches it
2019			cg = testrun[testidx].ftemp[pid][-1]
2020			if(cg.addLine(t)):
2021				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid))
2022	tf.close()
2023
2024	for test in testrun:
2025		# add the traceevent data to the device hierarchy
2026		if(sysvals.usetraceevents):
2027			for name in test.ttemp:
2028				for event in test.ttemp[name]:
2029					test.data.newActionGlobal(name, event['begin'], event['end'])
 
 
 
 
 
 
 
 
2030
2031		# add the callgraph data to the device hierarchy
2032		for pid in test.ftemp:
2033			for cg in test.ftemp[pid]:
2034				if len(cg.list) < 1 or cg.invalid:
2035					continue
2036				if(not cg.postProcess()):
2037					id = 'task %s cpu %s' % (pid, m.group('cpu'))
2038					vprint('Sanity check failed for '+\
2039						id+', ignoring this callback')
2040					continue
2041				callstart = cg.start
2042				callend = cg.end
2043				for p in test.data.phases:
2044					if(test.data.dmesg[p]['start'] <= callstart and
2045						callstart <= test.data.dmesg[p]['end']):
2046						list = test.data.dmesg[p]['list']
2047						for devname in list:
2048							dev = list[devname]
2049							if(pid == dev['pid'] and
2050								callstart <= dev['start'] and
2051								callend >= dev['end']):
2052								dev['ftrace'] = cg
2053						break
2054
2055		if(sysvals.verbose):
2056			test.data.printDetails()
2057
 
 
 
 
 
 
 
 
2058# Function: parseTraceLog
2059# Description:
2060#	 Analyze an ftrace log output file generated from this app during
2061#	 the execution phase. Used when the ftrace log is the primary data source
2062#	 and includes the suspend_resume and device_pm_callback trace events
2063#	 The ftrace filename is taken from sysvals
2064# Output:
2065#	 An array of Data objects
2066def parseTraceLog():
2067	global sysvals
2068
2069	vprint('Analyzing the ftrace data...')
2070	if(os.path.exists(sysvals.ftracefile) == False):
2071		doError('%s does not exist' % sysvals.ftracefile, False)
2072
2073	sysvals.setupAllKprobes()
2074	tracewatch = ['suspend_enter']
2075	if sysvals.usekprobes:
2076		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
2077			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', 'CPU_OFF']
2078
2079	# extract the callgraph and traceevent data
2080	tp = TestProps()
2081	testruns = []
2082	testdata = []
2083	testrun = 0
2084	data = 0
2085	tf = open(sysvals.ftracefile, 'r')
2086	phase = 'suspend_prepare'
2087	for line in tf:
2088		# remove any latent carriage returns
2089		line = line.replace('\r\n', '')
2090		# stamp line: each stamp means a new test run
2091		m = re.match(sysvals.stampfmt, line)
2092		if(m):
2093			tp.stamp = line
 
 
 
 
 
 
2094			continue
2095		# firmware line: pull out any firmware data
2096		m = re.match(sysvals.firmwarefmt, line)
2097		if(m):
2098			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
 
 
 
2099			continue
2100		# tracer type line: determine the trace data type
2101		m = re.match(sysvals.tracertypefmt, line)
2102		if(m):
2103			tp.setTracerType(m.group('t'))
 
2104			continue
2105		# post resume time line: did this test run include post-resume data
2106		m = re.match(sysvals.postresumefmt, line)
2107		if(m):
2108			t = int(m.group('t'))
2109			if(t > 0):
2110				sysvals.postresumetime = t
2111			continue
2112		# device properties line
2113		if(re.match(sysvals.devpropfmt, line)):
2114			devProps(line)
2115			continue
2116		# ftrace line: parse only valid lines
2117		m = re.match(tp.ftrace_line_fmt, line)
2118		if(not m):
2119			continue
2120		# gather the basic message data from the line
2121		m_time = m.group('time')
2122		m_proc = m.group('proc')
2123		m_pid = m.group('pid')
2124		m_msg = m.group('msg')
2125		if(tp.cgformat):
2126			m_param3 = m.group('dur')
2127		else:
2128			m_param3 = 'traceevent'
2129		if(m_time and m_pid and m_msg):
2130			t = FTraceLine(m_time, m_msg, m_param3)
2131			pid = int(m_pid)
2132		else:
2133			continue
2134		# the line should be a call, return, or event
2135		if(not t.fcall and not t.freturn and not t.fevent):
2136			continue
2137		# find the start of suspend
2138		if(t.startMarker()):
2139			phase = 'suspend_prepare'
2140			data = Data(len(testdata))
2141			testdata.append(data)
2142			testrun = TestRun(data)
2143			testruns.append(testrun)
2144			parseStamp(tp.stamp, data)
2145			if len(tp.fwdata) > data.testnumber:
2146				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2147				if(data.fwSuspend > 0 or data.fwResume > 0):
2148					data.fwValid = True
2149			data.setStart(t.time)
2150			continue
2151		if(not data):
2152			continue
2153		# find the end of resume
2154		if(t.endMarker()):
2155			if(sysvals.usetracemarkers and sysvals.postresumetime > 0):
2156				phase = 'post_resume'
2157				data.newPhase(phase, t.time, t.time, '#F0F0F0', -1)
2158			data.setEnd(t.time)
2159			if(not sysvals.usetracemarkers):
2160				# no trace markers? then quit and be sure to finish recording
2161				# the event we used to trigger resume end
2162				if(len(testrun.ttemp['thaw_processes']) > 0):
2163					# if an entry exists, assume this is its end
2164					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
2165				break
2166			continue
2167		# trace event processing
2168		if(t.fevent):
 
 
 
 
 
 
 
 
2169			if(phase == 'post_resume'):
2170				data.setEnd(t.time)
2171			if(t.type == 'suspend_resume'):
2172				# suspend_resume trace events have two types, begin and end
2173				if(re.match('(?P<name>.*) begin$', t.name)):
2174					isbegin = True
2175				elif(re.match('(?P<name>.*) end$', t.name)):
2176					isbegin = False
2177				else:
2178					continue
2179				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2180				if(m):
2181					val = m.group('val')
2182					if val == '0':
2183						name = m.group('name')
2184					else:
2185						name = m.group('name')+'['+val+']'
2186				else:
2187					m = re.match('(?P<name>.*) .*', t.name)
2188					name = m.group('name')
2189				# ignore these events
2190				if(name.split('[')[0] in tracewatch):
 
2191					continue
2192				# -- phase changes --
2193				# suspend_prepare start
2194				if(re.match('dpm_prepare\[.*', t.name)):
2195					phase = 'suspend_prepare'
2196					if(not isbegin):
2197						data.dmesg[phase]['end'] = t.time
2198					continue
2199				# suspend start
2200				elif(re.match('dpm_suspend\[.*', t.name)):
2201					phase = 'suspend'
2202					data.setPhase(phase, t.time, isbegin)
2203					continue
2204				# suspend_late start
2205				elif(re.match('dpm_suspend_late\[.*', t.name)):
2206					phase = 'suspend_late'
2207					data.setPhase(phase, t.time, isbegin)
2208					continue
2209				# suspend_noirq start
2210				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
2211					phase = 'suspend_noirq'
2212					data.setPhase(phase, t.time, isbegin)
2213					if(not isbegin):
2214						phase = 'suspend_machine'
2215						data.dmesg[phase]['start'] = t.time
2216					continue
2217				# suspend_machine/resume_machine
2218				elif(re.match('machine_suspend\[.*', t.name)):
2219					if(isbegin):
2220						phase = 'suspend_machine'
2221						data.dmesg[phase]['end'] = t.time
2222						data.tSuspended = t.time
2223					else:
2224						if(sysvals.suspendmode in ['mem', 'disk'] and not tp.S0i3):
2225							data.dmesg['suspend_machine']['end'] = t.time
2226							data.tSuspended = t.time
2227						phase = 'resume_machine'
2228						data.dmesg[phase]['start'] = t.time
2229						data.tResumed = t.time
2230						data.tLow = data.tResumed - data.tSuspended
2231					continue
2232				# acpi_suspend
2233				elif(re.match('acpi_suspend\[.*', t.name)):
2234					# acpi_suspend[0] S0i3
2235					if(re.match('acpi_suspend\[0\] begin', t.name)):
2236						if(sysvals.suspendmode == 'mem'):
2237							tp.S0i3 = True
2238							data.dmesg['suspend_machine']['end'] = t.time
2239							data.tSuspended = t.time
2240					continue
2241				# resume_noirq start
2242				elif(re.match('dpm_resume_noirq\[.*', t.name)):
2243					phase = 'resume_noirq'
2244					data.setPhase(phase, t.time, isbegin)
2245					if(isbegin):
2246						data.dmesg['resume_machine']['end'] = t.time
2247					continue
2248				# resume_early start
2249				elif(re.match('dpm_resume_early\[.*', t.name)):
2250					phase = 'resume_early'
2251					data.setPhase(phase, t.time, isbegin)
2252					continue
2253				# resume start
2254				elif(re.match('dpm_resume\[.*', t.name)):
2255					phase = 'resume'
2256					data.setPhase(phase, t.time, isbegin)
2257					continue
2258				# resume complete start
2259				elif(re.match('dpm_complete\[.*', t.name)):
2260					phase = 'resume_complete'
2261					if(isbegin):
2262						data.dmesg[phase]['start'] = t.time
2263					continue
2264				# skip trace events inside devices calls
2265				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2266					continue
2267				# global events (outside device calls) are graphed
2268				if(name not in testrun.ttemp):
2269					testrun.ttemp[name] = []
2270				if(isbegin):
2271					# create a new list entry
2272					testrun.ttemp[name].append(\
2273						{'begin': t.time, 'end': t.time, 'pid': pid})
 
 
 
 
 
 
 
 
 
2274				else:
2275					if(len(testrun.ttemp[name]) > 0):
2276						# if an entry exists, assume this is its end
2277						testrun.ttemp[name][-1]['end'] = t.time
2278					elif(phase == 'post_resume'):
2279						# post resume events can just have ends
2280						testrun.ttemp[name].append({
2281							'begin': data.dmesg[phase]['start'],
2282							'end': t.time})
2283			# device callback start
2284			elif(t.type == 'device_pm_callback_start'):
2285				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
2286					t.name);
2287				if(not m):
2288					continue
2289				drv = m.group('drv')
2290				n = m.group('d')
2291				p = m.group('p')
2292				if(n and p):
2293					data.newAction(phase, n, pid, p, t.time, -1, drv)
2294			# device callback finish
2295			elif(t.type == 'device_pm_callback_end'):
2296				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
2297				if(not m):
2298					continue
2299				n = m.group('d')
2300				list = data.dmesg[phase]['list']
2301				if(n in list):
2302					dev = list[n]
2303					dev['length'] = t.time - dev['start']
2304					dev['end'] = t.time
2305		# kprobe event processing
2306		elif(t.fkprobe):
2307			kprobename = t.type
2308			kprobedata = t.name
2309			key = (kprobename, pid)
2310			# displayname is generated from kprobe data
2311			displayname = ''
2312			if(t.fcall):
2313				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
2314				if not displayname:
2315					continue
2316				if(key not in tp.ktemp):
2317					tp.ktemp[key] = []
2318				tp.ktemp[key].append({
2319					'pid': pid,
2320					'begin': t.time,
2321					'end': t.time,
2322					'name': displayname,
2323					'cdata': kprobedata,
2324					'proc': m_proc,
2325				})
2326			elif(t.freturn):
2327				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
2328					continue
2329				e = tp.ktemp[key][-1]
2330				if e['begin'] < 0.0 or t.time - e['begin'] < 0.000001:
2331					tp.ktemp[key].pop()
2332				else:
2333					e['end'] = t.time
2334					e['rdata'] = kprobedata
2335		# callgraph processing
2336		elif sysvals.usecallgraph:
 
 
 
2337			# create a callgraph object for the data
2338			key = (m_proc, pid)
2339			if(key not in testrun.ftemp):
2340				testrun.ftemp[key] = []
2341				testrun.ftemp[key].append(FTraceCallGraph(pid))
2342			# when the call is finished, see which device matches it
2343			cg = testrun.ftemp[key][-1]
2344			if(cg.addLine(t)):
2345				testrun.ftemp[key].append(FTraceCallGraph(pid))
2346	tf.close()
2347
2348	if sysvals.suspendmode == 'command':
2349		for test in testruns:
2350			for p in test.data.phases:
2351				if p == 'resume_complete':
2352					test.data.dmesg[p]['start'] = test.data.start
2353					test.data.dmesg[p]['end'] = test.data.end
2354				else:
2355					test.data.dmesg[p]['start'] = test.data.start
2356					test.data.dmesg[p]['end'] = test.data.start
2357			test.data.tSuspended = test.data.start
2358			test.data.tResumed = test.data.start
2359			test.data.tLow = 0
2360			test.data.fwValid = False
2361
2362	for test in testruns:
2363		# add the traceevent data to the device hierarchy
2364		if(sysvals.usetraceevents):
2365			# add actual trace funcs
2366			for name in test.ttemp:
2367				for event in test.ttemp[name]:
2368					test.data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
2369			# add the kprobe based virtual tracefuncs as actual devices
2370			for key in tp.ktemp:
2371				name, pid = key
2372				if name not in sysvals.tracefuncs:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2373					continue
2374				for e in tp.ktemp[key]:
2375					kb, ke = e['begin'], e['end']
2376					if kb == ke or not test.data.isInsideTimeline(kb, ke):
2377						continue
2378					test.data.newActionGlobal(e['name'], kb, ke, pid)
2379			# add config base kprobes and dev kprobes
2380			for key in tp.ktemp:
2381				name, pid = key
2382				if name in sysvals.tracefuncs:
 
 
 
 
2383					continue
2384				for e in tp.ktemp[key]:
2385					kb, ke = e['begin'], e['end']
2386					if kb == ke or not test.data.isInsideTimeline(kb, ke):
2387						continue
2388					color = sysvals.kprobeColor(e['name'])
2389					if name not in sysvals.dev_tracefuncs:
2390						# config base kprobe
2391						test.data.newActionGlobal(e['name'], kb, ke, -2, color)
2392					elif sysvals.usedevsrc:
2393						# dev kprobe
2394						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
2395							ke, e['cdata'], e['rdata'])
2396		if sysvals.usecallgraph:
2397			# add the callgraph data to the device hierarchy
2398			sortlist = dict()
2399			for key in test.ftemp:
2400				proc, pid = key
2401				for cg in test.ftemp[key]:
2402					if len(cg.list) < 1 or cg.invalid:
2403						continue
2404					if(not cg.postProcess()):
2405						id = 'task %s' % (pid)
2406						vprint('Sanity check failed for '+\
2407							id+', ignoring this callback')
2408						continue
2409					# match cg data to devices
2410					if sysvals.suspendmode == 'command' or not cg.deviceMatch(pid, test.data):
2411						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
2412						sortlist[sortkey] = cg
2413			# create blocks for orphan cg data
2414			for sortkey in sorted(sortlist):
2415				cg = sortlist[sortkey]
2416				name = cg.list[0].name
2417				if sysvals.isCallgraphFunc(name):
2418					vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
2419					cg.newActionFromFunction(test.data)
2420
2421	if sysvals.suspendmode == 'command':
2422		if(sysvals.verbose):
2423			for data in testdata:
2424				data.printDetails()
2425		return testdata
2426
2427	# fill in any missing phases
2428	for data in testdata:
2429		lp = data.phases[0]
2430		for p in data.phases:
2431			if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
2432				print('WARNING: phase "%s" is missing!' % p)
2433			if(data.dmesg[p]['start'] < 0):
2434				data.dmesg[p]['start'] = data.dmesg[lp]['end']
2435				if(p == 'resume_machine'):
2436					data.tSuspended = data.dmesg[lp]['end']
2437					data.tResumed = data.dmesg[lp]['end']
2438					data.tLow = 0
2439			if(data.dmesg[p]['end'] < 0):
2440				data.dmesg[p]['end'] = data.dmesg[p]['start']
2441			lp = p
2442
2443		if(len(sysvals.devicefilter) > 0):
2444			data.deviceFilter(sysvals.devicefilter)
2445		data.fixupInitcallsThatDidntReturn()
2446		if(sysvals.verbose):
2447			data.printDetails()
2448
 
 
 
 
 
 
2449	return testdata
2450
2451# Function: loadRawKernelLog
2452# Description:
2453#	 Load a raw kernel log that wasn't created by this tool, it might be
2454#	 possible to extract a valid suspend/resume log
2455def loadRawKernelLog(dmesgfile):
2456	global sysvals
2457
2458	stamp = {'time': '', 'host': '', 'mode': 'mem', 'kernel': ''}
2459	stamp['time'] = datetime.now().strftime('%B %d %Y, %I:%M:%S %p')
2460	stamp['host'] = sysvals.hostname
2461
2462	testruns = []
2463	data = 0
2464	lf = open(dmesgfile, 'r')
2465	for line in lf:
2466		line = line.replace('\r\n', '')
2467		idx = line.find('[')
2468		if idx > 1:
2469			line = line[idx:]
2470		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
2471		if(not m):
2472			continue
2473		msg = m.group("msg")
2474		m = re.match('PM: Syncing filesystems.*', msg)
2475		if(m):
2476			if(data):
2477				testruns.append(data)
2478			data = Data(len(testruns))
2479			data.stamp = stamp
2480		if(data):
2481			m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
2482			if(m):
2483				stamp['kernel'] = m.group('k')
2484			m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
2485			if(m):
2486				stamp['mode'] = m.group('m')
2487			data.dmesgtext.append(line)
2488	if(data):
2489		testruns.append(data)
2490		sysvals.stamp = stamp
2491		sysvals.suspendmode = stamp['mode']
2492	lf.close()
2493	return testruns
2494
2495# Function: loadKernelLog
2496# Description:
2497#	 [deprecated for kernel 3.15.0 or newer]
2498#	 load the dmesg file into memory and fix up any ordering issues
2499#	 The dmesg filename is taken from sysvals
2500# Output:
2501#	 An array of empty Data objects with only their dmesgtext attributes set
2502def loadKernelLog():
2503	global sysvals
2504
2505	vprint('Analyzing the dmesg data...')
2506	if(os.path.exists(sysvals.dmesgfile) == False):
2507		doError('%s does not exist' % sysvals.dmesgfile, False)
2508
2509	# there can be multiple test runs in a single file
2510	tp = TestProps()
2511	testruns = []
2512	data = 0
2513	lf = open(sysvals.dmesgfile, 'r')
2514	for line in lf:
2515		line = line.replace('\r\n', '')
2516		idx = line.find('[')
2517		if idx > 1:
2518			line = line[idx:]
2519		m = re.match(sysvals.stampfmt, line)
2520		if(m):
2521			tp.stamp = line
 
 
 
 
 
2522			continue
2523		m = re.match(sysvals.firmwarefmt, line)
2524		if(m):
2525			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
 
 
 
2526			continue
2527		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
2528		if(not m):
2529			continue
2530		msg = m.group("msg")
2531		if(re.match('PM: Syncing filesystems.*', msg)):
2532			if(data):
2533				testruns.append(data)
2534			data = Data(len(testruns))
2535			parseStamp(tp.stamp, data)
2536			if len(tp.fwdata) > data.testnumber:
2537				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2538				if(data.fwSuspend > 0 or data.fwResume > 0):
2539					data.fwValid = True
2540		if(re.match('ACPI: resume from mwait', msg)):
2541			print('NOTE: This suspend appears to be freeze rather than'+\
2542				' %s, it will be treated as such' % sysvals.suspendmode)
2543			sysvals.suspendmode = 'freeze'
2544		if(not data):
2545			continue
2546		data.dmesgtext.append(line)
2547	if(data):
2548		testruns.append(data)
2549	lf.close()
2550
2551	if(len(testruns) < 1):
2552		# bad log, but see if you can extract something meaningful anyway
2553		testruns = loadRawKernelLog(sysvals.dmesgfile)
2554
2555	if(len(testruns) < 1):
2556		doError(' dmesg log is completely unreadable: %s' \
2557			% sysvals.dmesgfile, False)
2558
2559	# fix lines with same timestamp/function with the call and return swapped
2560	for data in testruns:
2561		last = ''
2562		for line in data.dmesgtext:
2563			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
2564				'(?P<f>.*)\+ @ .*, parent: .*', line)
2565			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
2566				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
2567			if(mc and mr and (mc.group('t') == mr.group('t')) and
2568				(mc.group('f') == mr.group('f'))):
2569				i = data.dmesgtext.index(last)
2570				j = data.dmesgtext.index(line)
2571				data.dmesgtext[i] = line
2572				data.dmesgtext[j] = last
2573			last = line
2574	return testruns
2575
2576# Function: parseKernelLog
2577# Description:
2578#	 [deprecated for kernel 3.15.0 or newer]
2579#	 Analyse a dmesg log output file generated from this app during
2580#	 the execution phase. Create a set of device structures in memory
2581#	 for subsequent formatting in the html output file
2582#	 This call is only for legacy support on kernels where the ftrace
2583#	 data lacks the suspend_resume or device_pm_callbacks trace events.
2584# Arguments:
2585#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
2586# Output:
2587#	 The filled Data object
2588def parseKernelLog(data):
2589	global sysvals
2590
2591	phase = 'suspend_runtime'
2592
2593	if(data.fwValid):
2594		vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
2595			(data.fwSuspend, data.fwResume))
2596
2597	# dmesg phase match table
2598	dm = {
2599		'suspend_prepare': 'PM: Syncing filesystems.*',
2600		        'suspend': 'PM: Entering [a-z]* sleep.*',
2601		   'suspend_late': 'PM: suspend of devices complete after.*',
2602		  'suspend_noirq': 'PM: late suspend of devices complete after.*',
2603		'suspend_machine': 'PM: noirq suspend of devices complete after.*',
2604		 'resume_machine': 'ACPI: Low-level resume complete.*',
2605		   'resume_noirq': 'ACPI: Waking up from system sleep state.*',
2606		   'resume_early': 'PM: noirq resume of devices complete after.*',
2607		         'resume': 'PM: early resume of devices complete after.*',
2608		'resume_complete': 'PM: resume of devices complete after.*',
2609		    'post_resume': '.*Restarting tasks \.\.\..*',
2610	}
2611	if(sysvals.suspendmode == 'standby'):
2612		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
2613	elif(sysvals.suspendmode == 'disk'):
2614		dm['suspend_late'] = 'PM: freeze of devices complete after.*'
2615		dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
2616		dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
2617		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
2618		dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
2619		dm['resume'] = 'PM: early restore of devices complete after.*'
2620		dm['resume_complete'] = 'PM: restore of devices complete after.*'
2621	elif(sysvals.suspendmode == 'freeze'):
2622		dm['resume_machine'] = 'ACPI: resume from mwait'
2623
2624	# action table (expected events that occur and show up in dmesg)
2625	at = {
2626		'sync_filesystems': {
2627			'smsg': 'PM: Syncing filesystems.*',
2628			'emsg': 'PM: Preparing system for mem sleep.*' },
2629		'freeze_user_processes': {
2630			'smsg': 'Freezing user space processes .*',
2631			'emsg': 'Freezing remaining freezable tasks.*' },
2632		'freeze_tasks': {
2633			'smsg': 'Freezing remaining freezable tasks.*',
2634			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
2635		'ACPI prepare': {
2636			'smsg': 'ACPI: Preparing to enter system sleep state.*',
2637			'emsg': 'PM: Saving platform NVS memory.*' },
2638		'PM vns': {
2639			'smsg': 'PM: Saving platform NVS memory.*',
2640			'emsg': 'Disabling non-boot CPUs .*' },
2641	}
2642
2643	t0 = -1.0
2644	cpu_start = -1.0
2645	prevktime = -1.0
2646	actions = dict()
2647	for line in data.dmesgtext:
2648		# -- preprocessing --
2649		# parse each dmesg line into the time and message
2650		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
2651		if(m):
2652			val = m.group('ktime')
2653			try:
2654				ktime = float(val)
2655			except:
2656				doWarning('INVALID DMESG LINE: '+\
2657					line.replace('\n', ''), 'dmesg')
2658				continue
2659			msg = m.group('msg')
2660			# initialize data start to first line time
2661			if t0 < 0:
2662				data.setStart(ktime)
2663				t0 = ktime
2664		else:
2665			continue
2666
2667		# hack for determining resume_machine end for freeze
2668		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
2669			and phase == 'resume_machine' and \
2670			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
2671			data.dmesg['resume_machine']['end'] = ktime
2672			phase = 'resume_noirq'
2673			data.dmesg[phase]['start'] = ktime
2674
2675		# -- phase changes --
2676		# suspend start
2677		if(re.match(dm['suspend_prepare'], msg)):
2678			phase = 'suspend_prepare'
2679			data.dmesg[phase]['start'] = ktime
2680			data.setStart(ktime)
2681		# suspend start
2682		elif(re.match(dm['suspend'], msg)):
2683			data.dmesg['suspend_prepare']['end'] = ktime
2684			phase = 'suspend'
2685			data.dmesg[phase]['start'] = ktime
2686		# suspend_late start
2687		elif(re.match(dm['suspend_late'], msg)):
2688			data.dmesg['suspend']['end'] = ktime
2689			phase = 'suspend_late'
2690			data.dmesg[phase]['start'] = ktime
2691		# suspend_noirq start
2692		elif(re.match(dm['suspend_noirq'], msg)):
2693			data.dmesg['suspend_late']['end'] = ktime
2694			phase = 'suspend_noirq'
2695			data.dmesg[phase]['start'] = ktime
2696		# suspend_machine start
2697		elif(re.match(dm['suspend_machine'], msg)):
2698			data.dmesg['suspend_noirq']['end'] = ktime
2699			phase = 'suspend_machine'
2700			data.dmesg[phase]['start'] = ktime
2701		# resume_machine start
2702		elif(re.match(dm['resume_machine'], msg)):
2703			if(sysvals.suspendmode in ['freeze', 'standby']):
2704				data.tSuspended = prevktime
2705				data.dmesg['suspend_machine']['end'] = prevktime
2706			else:
2707				data.tSuspended = ktime
2708				data.dmesg['suspend_machine']['end'] = ktime
2709			phase = 'resume_machine'
2710			data.tResumed = ktime
2711			data.tLow = data.tResumed - data.tSuspended
2712			data.dmesg[phase]['start'] = ktime
2713		# resume_noirq start
2714		elif(re.match(dm['resume_noirq'], msg)):
2715			data.dmesg['resume_machine']['end'] = ktime
2716			phase = 'resume_noirq'
2717			data.dmesg[phase]['start'] = ktime
2718		# resume_early start
2719		elif(re.match(dm['resume_early'], msg)):
2720			data.dmesg['resume_noirq']['end'] = ktime
2721			phase = 'resume_early'
2722			data.dmesg[phase]['start'] = ktime
2723		# resume start
2724		elif(re.match(dm['resume'], msg)):
2725			data.dmesg['resume_early']['end'] = ktime
2726			phase = 'resume'
2727			data.dmesg[phase]['start'] = ktime
2728		# resume complete start
2729		elif(re.match(dm['resume_complete'], msg)):
2730			data.dmesg['resume']['end'] = ktime
2731			phase = 'resume_complete'
2732			data.dmesg[phase]['start'] = ktime
2733		# post resume start
2734		elif(re.match(dm['post_resume'], msg)):
2735			data.dmesg['resume_complete']['end'] = ktime
2736			data.setEnd(ktime)
2737			phase = 'post_resume'
2738			break
2739
2740		# -- device callbacks --
2741		if(phase in data.phases):
2742			# device init call
2743			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
2744				sm = re.match('calling  (?P<f>.*)\+ @ '+\
2745					'(?P<n>.*), parent: (?P<p>.*)', msg);
2746				f = sm.group('f')
2747				n = sm.group('n')
2748				p = sm.group('p')
2749				if(f and n and p):
2750					data.newAction(phase, f, int(n), p, ktime, -1, '')
2751			# device init return
2752			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
2753				'(?P<t>.*) usecs', msg)):
2754				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
2755					'(?P<t>.*) usecs(?P<a>.*)', msg);
2756				f = sm.group('f')
2757				t = sm.group('t')
2758				list = data.dmesg[phase]['list']
2759				if(f in list):
2760					dev = list[f]
2761					dev['length'] = int(t)
2762					dev['end'] = ktime
2763
2764		# -- non-devicecallback actions --
2765		# if trace events are not available, these are better than nothing
2766		if(not sysvals.usetraceevents):
2767			# look for known actions
2768			for a in at:
2769				if(re.match(at[a]['smsg'], msg)):
2770					if(a not in actions):
2771						actions[a] = []
2772					actions[a].append({'begin': ktime, 'end': ktime})
2773				if(re.match(at[a]['emsg'], msg)):
2774					if(a in actions):
2775						actions[a][-1]['end'] = ktime
2776			# now look for CPU on/off events
2777			if(re.match('Disabling non-boot CPUs .*', msg)):
2778				# start of first cpu suspend
2779				cpu_start = ktime
2780			elif(re.match('Enabling non-boot CPUs .*', msg)):
2781				# start of first cpu resume
2782				cpu_start = ktime
2783			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
2784				# end of a cpu suspend, start of the next
2785				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
2786				cpu = 'CPU'+m.group('cpu')
2787				if(cpu not in actions):
2788					actions[cpu] = []
2789				actions[cpu].append({'begin': cpu_start, 'end': ktime})
2790				cpu_start = ktime
2791			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
2792				# end of a cpu resume, start of the next
2793				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
2794				cpu = 'CPU'+m.group('cpu')
2795				if(cpu not in actions):
2796					actions[cpu] = []
2797				actions[cpu].append({'begin': cpu_start, 'end': ktime})
2798				cpu_start = ktime
2799		prevktime = ktime
2800
2801	# fill in any missing phases
2802	lp = data.phases[0]
2803	for p in data.phases:
2804		if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
2805			print('WARNING: phase "%s" is missing, something went wrong!' % p)
2806			print('    In %s, this dmesg line denotes the start of %s:' % \
2807				(sysvals.suspendmode, p))
2808			print('        "%s"' % dm[p])
2809		if(data.dmesg[p]['start'] < 0):
2810			data.dmesg[p]['start'] = data.dmesg[lp]['end']
2811			if(p == 'resume_machine'):
2812				data.tSuspended = data.dmesg[lp]['end']
2813				data.tResumed = data.dmesg[lp]['end']
2814				data.tLow = 0
2815		if(data.dmesg[p]['end'] < 0):
2816			data.dmesg[p]['end'] = data.dmesg[p]['start']
2817		lp = p
2818
2819	# fill in any actions we've found
2820	for name in actions:
2821		for event in actions[name]:
2822			data.newActionGlobal(name, event['begin'], event['end'])
 
 
 
 
 
 
 
 
2823
2824	if(sysvals.verbose):
2825		data.printDetails()
2826	if(len(sysvals.devicefilter) > 0):
2827		data.deviceFilter(sysvals.devicefilter)
2828	data.fixupInitcallsThatDidntReturn()
2829	return True
2830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2831# Function: createHTMLSummarySimple
2832# Description:
2833#	 Create summary html file for a series of tests
2834# Arguments:
2835#	 testruns: array of Data objects from parseTraceLog
2836def createHTMLSummarySimple(testruns, htmlfile):
2837	global sysvals
2838
2839	# print out the basic summary of all the tests
2840	hf = open(htmlfile, 'w')
2841
2842	# write the html header first (html head, css code, up to body start)
2843	html = '<!DOCTYPE html>\n<html>\n<head>\n\
2844	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
2845	<title>AnalyzeSuspend Summary</title>\n\
2846	<style type=\'text/css\'>\n\
2847		body {overflow-y: scroll;}\n\
2848		.stamp {width: 100%;text-align:center;background-color:#495E09;line-height:30px;color:white;font: 25px Arial;}\n\
2849		table {width:100%;border-collapse: collapse;}\n\
2850		.summary {font: 22px Arial;border:1px solid;}\n\
2851		th {border: 1px solid black;background-color:#A7C942;color:white;}\n\
2852		td {text-align: center;}\n\
2853		tr.alt td {background-color:#EAF2D3;}\n\
2854		tr.avg td {background-color:#BDE34C;}\n\
2855		a:link {color: #90B521;}\n\
2856		a:visited {color: #495E09;}\n\
2857		a:hover {color: #B1DF28;}\n\
2858		a:active {color: #FFFFFF;}\n\
2859	</style>\n</head>\n<body>\n'
2860
2861	# group test header
2862	count = len(testruns)
2863	headline_stamp = '<div class="stamp">{0} {1} {2} {3} ({4} tests)</div>\n'
2864	html += headline_stamp.format(sysvals.stamp['host'],
2865		sysvals.stamp['kernel'], sysvals.stamp['mode'],
2866		sysvals.stamp['time'], count)
2867
2868	# check to see if all the tests have the same value
2869	stampcolumns = False
2870	for data in testruns:
2871		if diffStamp(sysvals.stamp, data.stamp):
2872			stampcolumns = True
2873			break
2874
2875	th = '\t<th>{0}</th>\n'
2876	td = '\t<td>{0}</td>\n'
2877	tdlink = '\t<td><a href="{0}">Click Here</a></td>\n'
2878
2879	# table header
2880	html += '<table class="summary">\n<tr>\n'
2881	html += th.format("Test #")
2882	if stampcolumns:
2883		html += th.format("Hostname")
2884		html += th.format("Kernel Version")
2885		html += th.format("Suspend Mode")
2886	html += th.format("Test Time")
2887	html += th.format("Suspend Time")
2888	html += th.format("Resume Time")
2889	html += th.format("Detail")
2890	html += '</tr>\n'
2891
2892	# test data, 1 row per test
2893	sTimeAvg = 0.0
2894	rTimeAvg = 0.0
2895	num = 1
2896	for data in testruns:
2897		# data.end is the end of post_resume
2898		resumeEnd = data.dmesg['resume_complete']['end']
2899		if num % 2 == 1:
2900			html += '<tr class="alt">\n'
2901		else:
2902			html += '<tr>\n'
2903
2904		# test num
2905		html += td.format("test %d" % num)
2906		num += 1
2907		if stampcolumns:
2908			# host name
2909			val = "unknown"
2910			if('host' in data.stamp):
2911				val = data.stamp['host']
2912			html += td.format(val)
2913			# host kernel
2914			val = "unknown"
2915			if('kernel' in data.stamp):
2916				val = data.stamp['kernel']
2917			html += td.format(val)
2918			# suspend mode
2919			val = "unknown"
2920			if('mode' in data.stamp):
2921				val = data.stamp['mode']
2922			html += td.format(val)
2923		# test time
2924		val = "unknown"
2925		if('time' in data.stamp):
2926			val = data.stamp['time']
2927		html += td.format(val)
2928		# suspend time
2929		sTime = (data.tSuspended - data.start)*1000
2930		sTimeAvg += sTime
2931		html += td.format("%3.3f ms" % sTime)
2932		# resume time
2933		rTime = (resumeEnd - data.tResumed)*1000
2934		rTimeAvg += rTime
2935		html += td.format("%3.3f ms" % rTime)
2936		# link to the output html
2937		html += tdlink.format(data.outfile)
2938
2939		html += '</tr>\n'
2940
2941	# last line: test average
2942	if(count > 0):
2943		sTimeAvg /= count
2944		rTimeAvg /= count
2945	html += '<tr class="avg">\n'
2946	html += td.format('Average') 	# name
2947	if stampcolumns:
2948		html += td.format('')			# host
2949		html += td.format('')			# kernel
2950		html += td.format('')			# mode
2951	html += td.format('')			# time
2952	html += td.format("%3.3f ms" % sTimeAvg)	# suspend time
2953	html += td.format("%3.3f ms" % rTimeAvg)	# resume time
2954	html += td.format('')			# output link
2955	html += '</tr>\n'
2956
2957	# flush the data to file
2958	hf.write(html+'</table>\n')
2959	hf.write('</body>\n</html>\n')
2960	hf.close()
2961
2962def htmlTitle():
2963	global sysvals
2964	modename = {
2965		'freeze': 'Freeze (S0)',
2966		'standby': 'Standby (S1)',
2967		'mem': 'Suspend (S3)',
2968		'disk': 'Hibernate (S4)'
2969	}
2970	kernel = sysvals.stamp['kernel']
2971	host = sysvals.hostname[0].upper()+sysvals.hostname[1:]
2972	mode = sysvals.suspendmode
2973	if sysvals.suspendmode in modename:
2974		mode = modename[sysvals.suspendmode]
2975	return host+' '+mode+' '+kernel
2976
2977def ordinal(value):
2978	suffix = 'th'
2979	if value < 10 or value > 19:
2980		if value % 10 == 1:
2981			suffix = 'st'
2982		elif value % 10 == 2:
2983			suffix = 'nd'
2984		elif value % 10 == 3:
2985			suffix = 'rd'
2986	return '%d%s' % (value, suffix)
2987
2988# Function: createHTML
2989# Description:
2990#	 Create the output html file from the resident test data
2991# Arguments:
2992#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
2993# Output:
2994#	 True if the html file was created, false if it failed
2995def createHTML(testruns):
2996	global sysvals
2997
2998	if len(testruns) < 1:
2999		print('ERROR: Not enough test data to build a timeline')
3000		return
3001
3002	for data in testruns:
3003		data.normalizeTime(testruns[-1].tSuspended)
3004
3005	x2changes = ['', 'absolute']
3006	if len(testruns) > 1:
3007		x2changes = ['1', 'relative']
3008	# html function templates
3009	headline_version = '<div class="version"><a href="https://01.org/suspendresume">AnalyzeSuspend v%s</a></div>' % sysvals.version
3010	headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
3011	html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail%s</button>' % x2changes[0]
3012	html_zoombox = '<center><button id="zoomin">ZOOM IN</button><button id="zoomout">ZOOM OUT</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
3013	html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
3014	html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
3015	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;">\n'
3016	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'
3017	html_traceevent = '<div title="{0}" class="traceevent" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;">{5}</div>\n'
3018	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background-color:{4}">{5}</div>\n'
3019	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background-color:{3}"></div>\n'
3020	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background-color:{1}">&nbsp;{2}</div>\n'
3021	html_timetotal = '<table class="time1">\n<tr>'\
3022		'<td class="green">{2} Suspend Time: <b>{0} ms</b></td>'\
3023		'<td class="yellow">{2} Resume Time: <b>{1} ms</b></td>'\
3024		'</tr>\n</table>\n'
3025	html_timetotal2 = '<table class="time1">\n<tr>'\
3026		'<td class="green">{3} Suspend Time: <b>{0} ms</b></td>'\
3027		'<td class="gray">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
3028		'<td class="yellow">{3} Resume Time: <b>{2} ms</b></td>'\
3029		'</tr>\n</table>\n'
3030	html_timetotal3 = '<table class="time1">\n<tr>'\
3031		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
3032		'<td class="yellow">Command: <b>{1}</b></td>'\
3033		'</tr>\n</table>\n'
3034	html_timegroups = '<table class="time2">\n<tr>'\
3035		'<td class="green">{4}Kernel Suspend: {0} ms</td>'\
3036		'<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
3037		'<td class="purple">{4}Firmware Resume: {2} ms</td>'\
3038		'<td class="yellow">{4}Kernel Resume: {3} ms</td>'\
3039		'</tr>\n</table>\n'
3040
3041	# html format variables
3042	rowheight = 30
3043	devtextS = '14px'
3044	devtextH = '30px'
3045	hoverZ = 'z-index:10;'
3046
3047	if sysvals.usedevsrc:
3048		hoverZ = ''
3049
3050	# device timeline
3051	vprint('Creating Device Timeline...')
3052
3053	devtl = Timeline(rowheight)
3054
3055	# Generate the header for this timeline
 
3056	for data in testruns:
3057		tTotal = data.end - data.start
3058		tEnd = data.dmesg['resume_complete']['end']
3059		if(tTotal == 0):
3060			print('ERROR: No timeline data')
3061			sys.exit()
3062		if(data.tLow > 0):
3063			low_time = '%.0f'%(data.tLow*1000)
3064		if sysvals.suspendmode == 'command':
3065			run_time = '%.0f'%((data.end-data.start)*1000)
3066			if sysvals.testcommand:
3067				testdesc = sysvals.testcommand
3068			else:
3069				testdesc = 'unknown'
3070			if(len(testruns) > 1):
3071				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3072			thtml = html_timetotal3.format(run_time, testdesc)
3073			devtl.html['header'] += thtml
3074		elif data.fwValid:
3075			suspend_time = '%.0f'%((data.tSuspended-data.start)*1000 + \
3076				(data.fwSuspend/1000000.0))
3077			resume_time = '%.0f'%((tEnd-data.tSuspended)*1000 + \
3078				(data.fwResume/1000000.0))
3079			testdesc1 = 'Total'
3080			testdesc2 = ''
3081			if(len(testruns) > 1):
3082				testdesc1 = testdesc2 = ordinal(data.testnumber+1)
3083				testdesc2 += ' '
3084			if(data.tLow == 0):
3085				thtml = html_timetotal.format(suspend_time, \
3086					resume_time, testdesc1)
3087			else:
3088				thtml = html_timetotal2.format(suspend_time, low_time, \
3089					resume_time, testdesc1)
3090			devtl.html['header'] += thtml
3091			sktime = '%.3f'%((data.dmesg['suspend_machine']['end'] - \
3092				data.getStart())*1000)
3093			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
3094			rftime = '%.3f'%(data.fwResume / 1000000.0)
3095			rktime = '%.3f'%((data.dmesg['resume_complete']['end'] - \
3096				data.dmesg['resume_machine']['start'])*1000)
3097			devtl.html['header'] += html_timegroups.format(sktime, \
3098				sftime, rftime, rktime, testdesc2)
3099		else:
3100			suspend_time = '%.0f'%((data.tSuspended-data.start)*1000)
3101			resume_time = '%.0f'%((tEnd-data.tSuspended)*1000)
3102			testdesc = 'Kernel'
3103			if(len(testruns) > 1):
3104				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3105			if(data.tLow == 0):
3106				thtml = html_timetotal.format(suspend_time, \
3107					resume_time, testdesc)
3108			else:
3109				thtml = html_timetotal2.format(suspend_time, low_time, \
3110					resume_time, testdesc)
3111			devtl.html['header'] += thtml
3112
3113	# time scale for potentially multiple datasets
3114	t0 = testruns[0].start
3115	tMax = testruns[-1].end
3116	tSuspended = testruns[-1].tSuspended
3117	tTotal = tMax - t0
3118
3119	# determine the maximum number of rows we need to draw
 
3120	for data in testruns:
3121		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
3122		for group in data.devicegroups:
3123			devlist = []
3124			for phase in group:
3125				for devname in data.tdevlist[phase]:
3126					devlist.append((phase,devname))
3127			devtl.getPhaseRows(data.dmesg, devlist)
3128	devtl.calcTotalRows()
3129
3130	# create bounding box, add buttons
3131	if sysvals.suspendmode != 'command':
3132		devtl.html['timeline'] += html_devlist1
3133		if len(testruns) > 1:
3134			devtl.html['timeline'] += html_devlist2
3135	devtl.html['timeline'] += html_zoombox
3136	devtl.html['timeline'] += html_timeline.format('dmesg', devtl.height)
3137
3138	# draw the full timeline
3139	phases = {'suspend':[],'resume':[]}
3140	for phase in data.dmesg:
3141		if 'resume' in phase:
3142			phases['resume'].append(phase)
3143		else:
3144			phases['suspend'].append(phase)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3145
3146	# draw each test run chronologically
3147	for data in testruns:
3148		# if nore than one test, draw a block to represent user mode
3149		if(data.testnumber > 0):
3150			m0 = testruns[data.testnumber-1].end
3151			mMax = testruns[data.testnumber].start
3152			mTotal = mMax - m0
3153			name = 'usermode%d' % data.testnumber
3154			top = '%d' % devtl.scaleH
3155			left = '%f' % (((m0-t0)*100.0)/tTotal)
3156			width = '%f' % ((mTotal*100.0)/tTotal)
3157			title = 'user mode (%0.3f ms) ' % (mTotal*1000)
3158			devtl.html['timeline'] += html_device.format(name, \
3159				title, left, top, '%d'%devtl.bodyH, width, '', '', '')
3160		# now draw the actual timeline blocks
3161		for dir in phases:
3162			# draw suspend and resume blocks separately
3163			bname = '%s%d' % (dir[0], data.testnumber)
3164			if dir == 'suspend':
3165				m0 = testruns[data.testnumber].start
3166				mMax = testruns[data.testnumber].tSuspended
3167				mTotal = mMax - m0
3168				left = '%f' % (((m0-t0)*100.0)/tTotal)
3169			else:
3170				m0 = testruns[data.testnumber].tSuspended
3171				mMax = testruns[data.testnumber].end
3172				mTotal = mMax - m0
3173				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
3174			# if a timeline block is 0 length, skip altogether
3175			if mTotal == 0:
3176				continue
3177			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
3178			devtl.html['timeline'] += html_tblock.format(bname, left, width)
3179			for b in sorted(phases[dir]):
3180				# draw the phase color background
3181				phase = data.dmesg[b]
3182				length = phase['end']-phase['start']
3183				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
3184				width = '%f' % ((length*100.0)/mTotal)
3185				devtl.html['timeline'] += html_phase.format(left, width, \
3186					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
3187					data.dmesg[b]['color'], '')
3188				# draw the devices for this phase
3189				phaselist = data.dmesg[b]['list']
3190				for d in data.tdevlist[b]:
3191					name = d
3192					drv = ''
3193					dev = phaselist[d]
3194					xtraclass = ''
3195					xtrainfo = ''
3196					xtrastyle = ''
3197					if 'htmlclass' in dev:
3198						xtraclass = dev['htmlclass']
3199						xtrainfo = dev['htmlclass']
3200					if 'color' in dev:
3201						xtrastyle = 'background-color:%s;' % dev['color']
3202					if(d in sysvals.devprops):
3203						name = sysvals.devprops[d].altName(d)
3204						xtraclass = sysvals.devprops[d].xtraClass()
3205						xtrainfo = sysvals.devprops[d].xtraInfo()
3206					if('drv' in dev and dev['drv']):
3207						drv = ' {%s}' % dev['drv']
3208					rowheight = devtl.phaseRowHeight(b, dev['row'])
3209					rowtop = devtl.phaseRowTop(b, dev['row'])
3210					top = '%.3f' % (rowtop + devtl.scaleH)
3211					left = '%f' % (((dev['start']-m0)*100)/mTotal)
3212					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
3213					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
3214					if sysvals.suspendmode == 'command':
3215						title = name+drv+xtrainfo+length+'cmdexec'
3216					else:
3217						title = name+drv+xtrainfo+length+b
3218					devtl.html['timeline'] += html_device.format(dev['id'], \
3219						title, left, top, '%.3f'%rowheight, width, \
3220						d+drv, xtraclass, xtrastyle)
3221					if('src' not in dev):
3222						continue
3223					# draw any trace events for this device
3224					vprint('Debug trace events found for device %s' % d)
3225					vprint('%20s %20s %10s %8s' % ('title', \
3226						'name', 'time(ms)', 'length(ms)'))
3227					for e in dev['src']:
3228						vprint('%20s %20s %10.3f %8.3f' % (e.title, \
3229							e.text, e.time*1000, e.length*1000))
3230						height = devtl.rowH
3231						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
3232						left = '%f' % (((e.time-m0)*100)/mTotal)
3233						width = '%f' % (e.length*100/mTotal)
3234						color = 'rgba(204,204,204,0.5)'
3235						devtl.html['timeline'] += \
3236							html_traceevent.format(e.title, \
3237								left, top, '%.3f'%height, \
3238								width, e.text)
3239			# draw the time scale, try to make the number of labels readable
3240			devtl.html['timeline'] += devtl.createTimeScale(m0, mMax, tTotal, dir)
3241			devtl.html['timeline'] += '</div>\n'
3242
3243	# timeline is finished
3244	devtl.html['timeline'] += '</div>\n</div>\n'
3245
3246	# draw a legend which describes the phases by color
3247	if sysvals.suspendmode != 'command':
3248		data = testruns[-1]
3249		devtl.html['legend'] = '<div class="legend">\n'
3250		pdelta = 100.0/len(data.phases)
3251		pmargin = pdelta / 4.0
3252		for phase in data.phases:
3253			tmp = phase.split('_')
3254			id = tmp[0][0]
3255			if(len(tmp) > 1):
3256				id += tmp[1][0]
3257			order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3258			name = string.replace(phase, '_', ' &nbsp;')
3259			devtl.html['legend'] += html_legend.format(order, \
3260				data.dmesg[phase]['color'], name, id)
3261		devtl.html['legend'] += '</div>\n'
3262
3263	hf = open(sysvals.htmlfile, 'w')
3264
3265	if not sysvals.cgexp:
3266		cgchk = 'checked'
3267		cgnchk = 'not(:checked)'
3268	else:
3269		cgchk = 'not(:checked)'
3270		cgnchk = 'checked'
3271
3272	# write the html header first (html head, css code, up to body start)
3273	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
3274	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3275	<title>'+htmlTitle()+'</title>\n\
3276	<style type=\'text/css\'>\n\
3277		body {overflow-y:scroll;}\n\
3278		.stamp {width:100%;text-align:center;background-color:gray;line-height:30px;color:white;font:25px Arial;}\n\
3279		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
3280		.callgraph article * {padding-left:28px;}\n\
3281		h1 {color:black;font:bold 30px Times;}\n\
3282		t0 {color:black;font:bold 30px Times;}\n\
3283		t1 {color:black;font:30px Times;}\n\
3284		t2 {color:black;font:25px Times;}\n\
3285		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
3286		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
3287		cS {color:blue;font:bold 11px Times;}\n\
3288		cR {color:red;font:bold 11px Times;}\n\
3289		table {width:100%;}\n\
3290		.gray {background-color:rgba(80,80,80,0.1);}\n\
3291		.green {background-color:rgba(204,255,204,0.4);}\n\
3292		.purple {background-color:rgba(128,0,128,0.2);}\n\
3293		.yellow {background-color:rgba(255,255,204,0.4);}\n\
3294		.time1 {font:22px Arial;border:1px solid;}\n\
3295		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
3296		td {text-align:center;}\n\
3297		r {color:#500000;font:15px Tahoma;}\n\
3298		n {color:#505050;font:15px Tahoma;}\n\
3299		.tdhl {color:red;}\n\
3300		.hide {display:none;}\n\
3301		.pf {display:none;}\n\
3302		.pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
3303		.pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
3304		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
3305		.zoombox {position:relative;width:100%;overflow-x:scroll;}\n\
3306		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
3307		.thread {position:absolute;height:0%;overflow:hidden;line-height:'+devtextH+';font-size:'+devtextS+';border:1px solid;text-align:center;white-space:nowrap;background-color:rgba(204,204,204,0.5);}\n\
3308		.thread.sync {background-color:'+sysvals.synccolor+';}\n\
3309		.thread.bg {background-color:'+sysvals.kprobecolor+';}\n\
3310		.thread:hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\
3311		.hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\
3312		.hover.sync {background-color:white;}\n\
3313		.hover.bg {background-color:white;}\n\
3314		.traceevent {position:absolute;font-size:10px;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,rgba(204,204,204,1),rgba(150,150,150,1));}\n\
3315		.traceevent:hover {background:white;}\n\
3316		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
3317		.phaselet {position:absolute;overflow:hidden;border:0px;text-align:center;height:100px;font-size:24px;}\n\
3318		.t {z-index:2;position:absolute;pointer-events:none;top:0%;height:100%;border-right:1px solid black;}\n\
3319		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
3320		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
3321		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
3322		.logbtn {position:relative;float:right;height:25px;width:50px;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
3323		.devlist {position:'+x2changes[1]+';width:190px;}\n\
3324		a:link {color:white;text-decoration:none;}\n\
3325		a:visited {color:white;}\n\
3326		a:hover {color:white;}\n\
3327		a:active {color:white;}\n\
3328		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
3329		#devicedetail {height:100px;box-shadow:5px 5px 20px black;}\n\
3330		.tblock {position:absolute;height:100%;}\n\
3331		.bg {z-index:1;}\n\
3332	</style>\n</head>\n<body>\n'
3333
3334	# no header or css if its embedded
3335	if(sysvals.embedded):
3336		hf.write('pass True tSus %.3f tRes %.3f tLow %.3f fwvalid %s tSus %.3f tRes %.3f\n' %
3337			(data.tSuspended-data.start, data.end-data.tSuspended, data.tLow, data.fwValid, \
3338				data.fwSuspend/1000000, data.fwResume/1000000))
3339	else:
3340		hf.write(html_header)
3341
3342	# write the test title and general info header
3343	if(sysvals.stamp['time'] != ""):
3344		hf.write(headline_version)
3345		if sysvals.addlogs and sysvals.dmesgfile:
3346			hf.write('<button id="showdmesg" class="logbtn">dmesg</button>')
3347		if sysvals.addlogs and sysvals.ftracefile:
3348			hf.write('<button id="showftrace" class="logbtn">ftrace</button>')
3349		hf.write(headline_stamp.format(sysvals.stamp['host'],
3350			sysvals.stamp['kernel'], sysvals.stamp['mode'], \
3351				sysvals.stamp['time']))
3352
3353	# write the device timeline
3354	hf.write(devtl.html['header'])
3355	hf.write(devtl.html['timeline'])
3356	hf.write(devtl.html['legend'])
3357	hf.write('<div id="devicedetailtitle"></div>\n')
3358	hf.write('<div id="devicedetail" style="display:none;">\n')
3359	# draw the colored boxes for the device detail section
3360	for data in testruns:
3361		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
3362		for b in data.phases:
3363			phase = data.dmesg[b]
3364			length = phase['end']-phase['start']
3365			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
3366			width = '%.3f' % ((length*100.0)/tTotal)
3367			hf.write(html_phaselet.format(b, left, width, \
3368				data.dmesg[b]['color']))
3369		if sysvals.suspendmode == 'command':
3370			hf.write(html_phaselet.format('cmdexec', '0', '0', \
3371				data.dmesg['resume_complete']['color']))
3372		hf.write('</div>\n')
3373	hf.write('</div>\n')
3374
3375	# write the ftrace data (callgraph)
3376	data = testruns[-1]
3377	if(sysvals.usecallgraph and not sysvals.embedded):
3378		hf.write('<section id="callgraphs" class="callgraph">\n')
3379		# write out the ftrace data converted to html
3380		html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
3381		html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3382		html_func_end = '</article>\n'
3383		html_func_leaf = '<article>{0} {1}</article>\n'
3384		num = 0
3385		for p in data.phases:
3386			list = data.dmesg[p]['list']
3387			for devname in data.sortedDevices(p):
3388				if('ftrace' not in list[devname]):
3389					continue
 
 
 
3390				devid = list[devname]['id']
3391				cg = list[devname]['ftrace']
3392				clen = (cg.end - cg.start) * 1000
3393				if clen < sysvals.mincglen:
3394					continue
3395				fmt = '<r>(%.3f ms @ '+sysvals.timeformat+' to '+sysvals.timeformat+')</r>'
3396				flen = fmt % (clen, cg.start, cg.end)
3397				name = devname
3398				if(devname in sysvals.devprops):
3399					name = sysvals.devprops[devname].altName(devname)
3400				if sysvals.suspendmode == 'command':
3401					ftitle = name
3402				else:
3403					ftitle = name+' '+p
3404				hf.write(html_func_top.format(devid, data.dmesg[p]['color'], \
3405					num, ftitle, flen))
3406				num += 1
3407				for line in cg.list:
3408					if(line.length < 0.000000001):
3409						flen = ''
3410					else:
3411						fmt = '<n>(%.3f ms @ '+sysvals.timeformat+')</n>'
3412						flen = fmt % (line.length*1000, line.time)
3413					if(line.freturn and line.fcall):
3414						hf.write(html_func_leaf.format(line.name, flen))
3415					elif(line.freturn):
3416						hf.write(html_func_end)
3417					else:
3418						hf.write(html_func_start.format(num, line.name, flen))
3419						num += 1
3420				hf.write(html_func_end)
3421		hf.write('\n\n    </section>\n')
3422
3423	# add the dmesg log as a hidden div
3424	if sysvals.addlogs and sysvals.dmesgfile:
3425		hf.write('<div id="dmesglog" style="display:none;">\n')
3426		lf = open(sysvals.dmesgfile, 'r')
3427		for line in lf:
3428			hf.write(line)
3429		lf.close()
3430		hf.write('</div>\n')
3431	# add the ftrace log as a hidden div
3432	if sysvals.addlogs and sysvals.ftracefile:
3433		hf.write('<div id="ftracelog" style="display:none;">\n')
3434		lf = open(sysvals.ftracefile, 'r')
3435		for line in lf:
3436			hf.write(line)
3437		lf.close()
3438		hf.write('</div>\n')
3439
3440	if(not sysvals.embedded):
3441		# write the footer and close
3442		addScriptCode(hf, testruns)
3443		hf.write('</body>\n</html>\n')
3444	else:
3445		# embedded out will be loaded in a page, skip the js
3446		t0 = (testruns[0].start - testruns[-1].tSuspended) * 1000
3447		tMax = (testruns[-1].end - testruns[-1].tSuspended) * 1000
3448		# add js code in a div entry for later evaluation
3449		detail = 'var bounds = [%f,%f];\n' % (t0, tMax)
3450		detail += 'var devtable = [\n'
3451		for data in testruns:
3452			topo = data.deviceTopology()
3453			detail += '\t"%s",\n' % (topo)
3454		detail += '];\n'
3455		hf.write('<div id=customcode style=display:none>\n'+detail+'</div>\n')
3456	hf.close()
3457	return True
3458
3459# Function: addScriptCode
3460# Description:
3461#	 Adds the javascript code to the output html
3462# Arguments:
3463#	 hf: the open html file pointer
3464#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
3465def addScriptCode(hf, testruns):
3466	t0 = testruns[0].start * 1000
3467	tMax = testruns[-1].end * 1000
3468	# create an array in javascript memory with the device details
3469	detail = '	var devtable = [];\n'
3470	for data in testruns:
3471		topo = data.deviceTopology()
3472		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
3473	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
3474	# add the code which will manipulate the data in the browser
3475	script_code = \
3476	'<script type="text/javascript">\n'+detail+\
3477	'	var resolution = -1;\n'\
3478	'	function redrawTimescale(t0, tMax, tS) {\n'\
3479	'		var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;"><cR><-R</cR></div>\';\n'\
3480	'		var tTotal = tMax - t0;\n'\
3481	'		var list = document.getElementsByClassName("tblock");\n'\
3482	'		for (var i = 0; i < list.length; i++) {\n'\
3483	'			var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
3484	'			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
3485	'			var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
3486	'			var mMax = m0 + mTotal;\n'\
3487	'			var html = "";\n'\
3488	'			var divTotal = Math.floor(mTotal/tS) + 1;\n'\
3489	'			if(divTotal > 1000) continue;\n'\
3490	'			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
3491	'			var pos = 0.0, val = 0.0;\n'\
3492	'			for (var j = 0; j < divTotal; j++) {\n'\
3493	'				var htmlline = "";\n'\
3494	'				if(list[i].id[5] == "r") {\n'\
3495	'					pos = 100 - (((j)*tS*100)/mTotal);\n'\
3496	'					val = (j)*tS;\n'\
3497	'					htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
3498	'					if(j == 0)\n'\
3499	'						htmlline = rline;\n'\
3500	'				} else {\n'\
3501	'					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
3502	'					val = (j-divTotal+1)*tS;\n'\
3503	'					if(j == divTotal - 1)\n'\
3504	'						htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S-></cS></div>\';\n'\
3505	'					else\n'\
3506	'						htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
3507	'				}\n'\
3508	'				html += htmlline;\n'\
3509	'			}\n'\
3510	'			timescale.innerHTML = html;\n'\
3511	'		}\n'\
3512	'	}\n'\
3513	'	function zoomTimeline() {\n'\
 
3514	'		var dmesg = document.getElementById("dmesg");\n'\
3515	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
3516	'		var val = parseFloat(dmesg.style.width);\n'\
3517	'		var newval = 100;\n'\
3518	'		var sh = window.outerWidth / 2;\n'\
3519	'		if(this.id == "zoomin") {\n'\
3520	'			newval = val * 1.2;\n'\
3521	'			if(newval > 910034) newval = 910034;\n'\
3522	'			dmesg.style.width = newval+"%";\n'\
3523	'			zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
3524	'		} else if (this.id == "zoomout") {\n'\
3525	'			newval = val / 1.2;\n'\
3526	'			if(newval < 100) newval = 100;\n'\
3527	'			dmesg.style.width = newval+"%";\n'\
3528	'			zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
3529	'		} else {\n'\
3530	'			zoombox.scrollLeft = 0;\n'\
3531	'			dmesg.style.width = "100%";\n'\
3532	'		}\n'\
3533	'		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
3534	'		var t0 = bounds[0];\n'\
3535	'		var tMax = bounds[1];\n'\
3536	'		var tTotal = tMax - t0;\n'\
3537	'		var wTotal = tTotal * 100.0 / newval;\n'\
3538	'		var idx = 7*window.innerWidth/1100;\n'\
3539	'		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
3540	'		if(i >= tS.length) i = tS.length - 1;\n'\
3541	'		if(tS[i] == resolution) return;\n'\
3542	'		resolution = tS[i];\n'\
3543	'		redrawTimescale(t0, tMax, tS[i]);\n'\
 
 
3544	'	}\n'\
3545	'	function deviceHover() {\n'\
3546	'		var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
3547	'		var dmesg = document.getElementById("dmesg");\n'\
3548	'		var dev = dmesg.getElementsByClassName("thread");\n'\
3549	'		var cpu = -1;\n'\
3550	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
3551	'			cpu = parseInt(name.slice(7));\n'\
3552	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
3553	'			cpu = parseInt(name.slice(8));\n'\
3554	'		for (var i = 0; i < dev.length; i++) {\n'\
3555	'			dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
3556	'			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
3557	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
3558	'				(name == dname))\n'\
3559	'			{\n'\
3560	'				dev[i].className = "hover "+cname;\n'\
3561	'			} else {\n'\
3562	'				dev[i].className = cname;\n'\
3563	'			}\n'\
3564	'		}\n'\
3565	'	}\n'\
3566	'	function deviceUnhover() {\n'\
3567	'		var dmesg = document.getElementById("dmesg");\n'\
3568	'		var dev = dmesg.getElementsByClassName("thread");\n'\
3569	'		for (var i = 0; i < dev.length; i++) {\n'\
3570	'			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
3571	'		}\n'\
3572	'	}\n'\
3573	'	function deviceTitle(title, total, cpu) {\n'\
3574	'		var prefix = "Total";\n'\
3575	'		if(total.length > 3) {\n'\
3576	'			prefix = "Average";\n'\
3577	'			total[1] = (total[1]+total[3])/2;\n'\
3578	'			total[2] = (total[2]+total[4])/2;\n'\
3579	'		}\n'\
3580	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
3581	'		var name = title.slice(0, title.indexOf(" ("));\n'\
3582	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
3583	'		var driver = "";\n'\
3584	'		var tS = "<t2>(</t2>";\n'\
3585	'		var tR = "<t2>)</t2>";\n'\
3586	'		if(total[1] > 0)\n'\
3587	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
3588	'		if(total[2] > 0)\n'\
3589	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
3590	'		var s = title.indexOf("{");\n'\
3591	'		var e = title.indexOf("}");\n'\
3592	'		if((s >= 0) && (e >= 0))\n'\
3593	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
3594	'		if(total[1] > 0 && total[2] > 0)\n'\
3595	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
3596	'		else\n'\
3597	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
3598	'		return name;\n'\
3599	'	}\n'\
3600	'	function deviceDetail() {\n'\
3601	'		var devinfo = document.getElementById("devicedetail");\n'\
3602	'		devinfo.style.display = "block";\n'\
3603	'		var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
3604	'		var cpu = -1;\n'\
3605	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
3606	'			cpu = parseInt(name.slice(7));\n'\
3607	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
3608	'			cpu = parseInt(name.slice(8));\n'\
3609	'		var dmesg = document.getElementById("dmesg");\n'\
3610	'		var dev = dmesg.getElementsByClassName("thread");\n'\
3611	'		var idlist = [];\n'\
3612	'		var pdata = [[]];\n'\
3613	'		if(document.getElementById("devicedetail1"))\n'\
3614	'			pdata = [[], []];\n'\
3615	'		var pd = pdata[0];\n'\
3616	'		var total = [0.0, 0.0, 0.0];\n'\
3617	'		for (var i = 0; i < dev.length; i++) {\n'\
3618	'			dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
3619	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
3620	'				(name == dname))\n'\
3621	'			{\n'\
3622	'				idlist[idlist.length] = dev[i].id;\n'\
3623	'				var tidx = 1;\n'\
3624	'				if(dev[i].id[0] == "a") {\n'\
3625	'					pd = pdata[0];\n'\
3626	'				} else {\n'\
3627	'					if(pdata.length == 1) pdata[1] = [];\n'\
3628	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
3629	'					pd = pdata[1];\n'\
3630	'					tidx = 3;\n'\
3631	'				}\n'\
3632	'				var info = dev[i].title.split(" ");\n'\
3633	'				var pname = info[info.length-1];\n'\
3634	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
3635	'				total[0] += pd[pname];\n'\
3636	'				if(pname.indexOf("suspend") >= 0)\n'\
3637	'					total[tidx] += pd[pname];\n'\
3638	'				else\n'\
3639	'					total[tidx+1] += pd[pname];\n'\
3640	'			}\n'\
3641	'		}\n'\
3642	'		var devname = deviceTitle(this.title, total, cpu);\n'\
3643	'		var left = 0.0;\n'\
3644	'		for (var t = 0; t < pdata.length; t++) {\n'\
3645	'			pd = pdata[t];\n'\
3646	'			devinfo = document.getElementById("devicedetail"+t);\n'\
3647	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
3648	'			for (var i = 0; i < phases.length; i++) {\n'\
3649	'				if(phases[i].id in pd) {\n'\
3650	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
3651	'					var fs = 32;\n'\
3652	'					if(w < 8) fs = 4*w | 0;\n'\
3653	'					var fs2 = fs*3/4;\n'\
3654	'					phases[i].style.width = w+"%";\n'\
3655	'					phases[i].style.left = left+"%";\n'\
3656	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
3657	'					left += w;\n'\
3658	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
3659	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace("_", " ")+"</t3>";\n'\
3660	'					phases[i].innerHTML = time+pname;\n'\
3661	'				} else {\n'\
3662	'					phases[i].style.width = "0%";\n'\
3663	'					phases[i].style.left = left+"%";\n'\
3664	'				}\n'\
3665	'			}\n'\
3666	'		}\n'\
3667	'		var cglist = document.getElementById("callgraphs");\n'\
3668	'		if(!cglist) return;\n'\
3669	'		var cg = cglist.getElementsByClassName("atop");\n'\
3670	'		if(cg.length < 10) return;\n'\
3671	'		for (var i = 0; i < cg.length; i++) {\n'\
3672	'			if(idlist.indexOf(cg[i].id) >= 0) {\n'\
3673	'				cg[i].style.display = "block";\n'\
3674	'			} else {\n'\
3675	'				cg[i].style.display = "none";\n'\
3676	'			}\n'\
3677	'		}\n'\
3678	'	}\n'\
3679	'	function devListWindow(e) {\n'\
3680	'		var sx = e.clientX;\n'\
3681	'		if(sx > window.innerWidth - 440)\n'\
3682	'			sx = window.innerWidth - 440;\n'\
3683	'		var cfg="top="+e.screenY+", left="+sx+", width=440, height=720, scrollbars=yes";\n'\
3684	'		var win = window.open("", "_blank", cfg);\n'\
3685	'		if(window.chrome) win.moveBy(sx, 0);\n'\
3686	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
3687	'			"<style type=\\"text/css\\">"+\n'\
3688	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
3689	'			"</style>"\n'\
3690	'		var dt = devtable[0];\n'\
3691	'		if(e.target.id != "devlist1")\n'\
3692	'			dt = devtable[1];\n'\
3693	'		win.document.write(html+dt);\n'\
3694	'	}\n'\
3695	'	function logWindow(e) {\n'\
3696	'		var name = e.target.id.slice(4);\n'\
3697	'		var win = window.open();\n'\
3698	'		var log = document.getElementById(name+"log");\n'\
3699	'		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
3700	'		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
3701	'		win.document.close();\n'\
3702	'	}\n'\
3703	'	function onClickPhase(e) {\n'\
3704	'	}\n'\
3705	'	window.addEventListener("resize", function () {zoomTimeline();});\n'\
3706	'	window.addEventListener("load", function () {\n'\
3707	'		var dmesg = document.getElementById("dmesg");\n'\
3708	'		dmesg.style.width = "100%"\n'\
3709	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
3710	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
3711	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
3712	'		var list = document.getElementsByClassName("square");\n'\
3713	'		for (var i = 0; i < list.length; i++)\n'\
3714	'			list[i].onclick = onClickPhase;\n'\
3715	'		var list = document.getElementsByClassName("logbtn");\n'\
3716	'		for (var i = 0; i < list.length; i++)\n'\
3717	'			list[i].onclick = logWindow;\n'\
3718	'		list = document.getElementsByClassName("devlist");\n'\
3719	'		for (var i = 0; i < list.length; i++)\n'\
3720	'			list[i].onclick = devListWindow;\n'\
3721	'		var dev = dmesg.getElementsByClassName("thread");\n'\
3722	'		for (var i = 0; i < dev.length; i++) {\n'\
3723	'			dev[i].onclick = deviceDetail;\n'\
3724	'			dev[i].onmouseover = deviceHover;\n'\
3725	'			dev[i].onmouseout = deviceUnhover;\n'\
3726	'		}\n'\
3727	'		zoomTimeline();\n'\
3728	'	});\n'\
3729	'</script>\n'
3730	hf.write(script_code);
3731
3732# Function: executeSuspend
3733# Description:
3734#	 Execute system suspend through the sysfs interface, then copy the output
3735#	 dmesg and ftrace files to the test output directory.
3736def executeSuspend():
3737	global sysvals
3738
 
3739	t0 = time.time()*1000
3740	tp = sysvals.tpath
3741	fwdata = []
3742	# mark the start point in the kernel ring buffer just as we start
3743	sysvals.initdmesg()
3744	# start ftrace
3745	if(sysvals.usecallgraph or sysvals.usetraceevents):
3746		print('START TRACING')
3747		sysvals.fsetVal('1', 'tracing_on')
3748	# execute however many s/r runs requested
3749	for count in range(1,sysvals.execcount+1):
 
 
 
 
 
 
 
 
 
 
 
 
 
3750		# if this is test2 and there's a delay, start here
3751		if(count > 1 and sysvals.x2delay > 0):
3752			tN = time.time()*1000
3753			while (tN - t0) < sysvals.x2delay:
3754				tN = time.time()*1000
3755				time.sleep(0.001)
 
 
 
 
3756		# initiate suspend
3757		if(sysvals.usecallgraph or sysvals.usetraceevents):
3758			sysvals.fsetVal('SUSPEND START', 'trace_marker')
3759		if sysvals.suspendmode == 'command':
3760			print('COMMAND START')
3761			if(sysvals.rtcwake):
3762				print('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
3763				sysvals.rtcWakeAlarmOn()
3764			os.system(sysvals.testcommand)
3765		else:
3766			if(sysvals.rtcwake):
3767				print('SUSPEND START')
3768				print('will autoresume in %d seconds' % sysvals.rtcwaketime)
3769				sysvals.rtcWakeAlarmOn()
3770			else:
3771				print('SUSPEND START (press a key to resume)')
3772			pf = open(sysvals.powerfile, 'w')
3773			pf.write(sysvals.suspendmode)
3774			# execution will pause here
3775			try:
3776				pf.close()
3777			except:
3778				pass
3779		t0 = time.time()*1000
3780		if(sysvals.rtcwake):
3781			sysvals.rtcWakeAlarmOff()
3782		# return from suspend
3783		print('RESUME COMPLETE')
3784		if(sysvals.usecallgraph or sysvals.usetraceevents):
3785			sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
3786		if(sysvals.suspendmode == 'mem'):
3787			fwdata.append(getFPDT(False))
3788	# look for post resume events after the last test run
3789	t = sysvals.postresumetime
3790	if(t > 0):
3791		print('Waiting %d seconds for POST-RESUME trace events...' % t)
3792		time.sleep(t)
3793	# stop ftrace
3794	if(sysvals.usecallgraph or sysvals.usetraceevents):
3795		sysvals.fsetVal('0', 'tracing_on')
3796		print('CAPTURING TRACE')
3797		writeDatafileHeader(sysvals.ftracefile, fwdata)
3798		os.system('cat '+tp+'trace >> '+sysvals.ftracefile)
3799		sysvals.fsetVal('', 'trace')
3800		devProps()
3801	# grab a copy of the dmesg output
3802	print('CAPTURING DMESG')
3803	writeDatafileHeader(sysvals.dmesgfile, fwdata)
3804	sysvals.getdmesg()
3805
3806def writeDatafileHeader(filename, fwdata):
3807	global sysvals
3808
 
3809	prt = sysvals.postresumetime
3810	fp = open(filename, 'a')
3811	fp.write(sysvals.teststamp+'\n')
3812	if(sysvals.suspendmode == 'mem'):
3813		for fw in fwdata:
3814			if(fw):
3815				fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
3816	if(prt > 0):
3817		fp.write('# post resume time %u\n' % prt)
3818	fp.close()
3819
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3820# Function: setUSBDevicesAuto
3821# Description:
3822#	 Set the autosuspend control parameter of all USB devices to auto
3823#	 This can be dangerous, so use at your own risk, most devices are set
3824#	 to always-on since the kernel cant determine if the device can
3825#	 properly autosuspend
3826def setUSBDevicesAuto():
3827	global sysvals
3828
3829	rootCheck(True)
3830	for dirname, dirnames, filenames in os.walk('/sys/devices'):
3831		if(re.match('.*/usb[0-9]*.*', dirname) and
3832			'idVendor' in filenames and 'idProduct' in filenames):
3833			os.system('echo auto > %s/power/control' % dirname)
3834			name = dirname.split('/')[-1]
3835			desc = os.popen('cat %s/product 2>/dev/null' % \
3836				dirname).read().replace('\n', '')
3837			ctrl = os.popen('cat %s/power/control 2>/dev/null' % \
3838				dirname).read().replace('\n', '')
3839			print('control is %s for %6s: %s' % (ctrl, name, desc))
3840
3841# Function: yesno
3842# Description:
3843#	 Print out an equivalent Y or N for a set of known parameter values
3844# Output:
3845#	 'Y', 'N', or ' ' if the value is unknown
3846def yesno(val):
3847	yesvals = ['auto', 'enabled', 'active', '1']
3848	novals = ['on', 'disabled', 'suspended', 'forbidden', 'unsupported']
3849	if val in yesvals:
3850		return 'Y'
3851	elif val in novals:
3852		return 'N'
3853	return ' '
3854
3855# Function: ms2nice
3856# Description:
3857#	 Print out a very concise time string in minutes and seconds
3858# Output:
3859#	 The time string, e.g. "1901m16s"
3860def ms2nice(val):
3861	ms = 0
3862	try:
3863		ms = int(val)
3864	except:
3865		return 0.0
3866	m = ms / 60000
3867	s = (ms / 1000) - (m * 60)
3868	return '%3dm%2ds' % (m, s)
3869
3870# Function: detectUSB
3871# Description:
3872#	 Detect all the USB hosts and devices currently connected and add
3873#	 a list of USB device names to sysvals for better timeline readability
3874def detectUSB():
 
 
3875	global sysvals
3876
3877	field = {'idVendor':'', 'idProduct':'', 'product':'', 'speed':''}
3878	power = {'async':'', 'autosuspend':'', 'autosuspend_delay_ms':'',
3879			 'control':'', 'persist':'', 'runtime_enabled':'',
3880			 'runtime_status':'', 'runtime_usage':'',
3881			'runtime_active_time':'',
3882			'runtime_suspended_time':'',
3883			'active_duration':'',
3884			'connected_duration':''}
3885
3886	print('LEGEND')
3887	print('---------------------------------------------------------------------------------------------')
3888	print('  A = async/sync PM queue Y/N                       D = autosuspend delay (seconds)')
3889	print('  S = autosuspend Y/N                         rACTIVE = runtime active (min/sec)')
3890	print('  P = persist across suspend Y/N              rSUSPEN = runtime suspend (min/sec)')
3891	print('  E = runtime suspend enabled/forbidden Y/N    ACTIVE = active duration (min/sec)')
3892	print('  R = runtime status active/suspended Y/N     CONNECT = connected duration (min/sec)')
3893	print('  U = runtime usage count')
3894	print('---------------------------------------------------------------------------------------------')
3895	print('  NAME       ID      DESCRIPTION         SPEED A S P E R U D rACTIVE rSUSPEN  ACTIVE CONNECT')
3896	print('---------------------------------------------------------------------------------------------')
3897
3898	for dirname, dirnames, filenames in os.walk('/sys/devices'):
3899		if(re.match('.*/usb[0-9]*.*', dirname) and
3900			'idVendor' in filenames and 'idProduct' in filenames):
3901			for i in field:
3902				field[i] = os.popen('cat %s/%s 2>/dev/null' % \
3903					(dirname, i)).read().replace('\n', '')
3904			name = dirname.split('/')[-1]
3905			for i in power:
3906				power[i] = os.popen('cat %s/power/%s 2>/dev/null' % \
3907					(dirname, i)).read().replace('\n', '')
3908			if(re.match('usb[0-9]*', name)):
3909				first = '%-8s' % name
3910			else:
3911				first = '%8s' % name
3912			print('%s [%s:%s] %-20s %-4s %1s %1s %1s %1s %1s %1s %1s %s %s %s %s' % \
3913				(first, field['idVendor'], field['idProduct'], \
3914				field['product'][0:20], field['speed'], \
3915				yesno(power['async']), \
3916				yesno(power['control']), \
3917				yesno(power['persist']), \
3918				yesno(power['runtime_enabled']), \
3919				yesno(power['runtime_status']), \
3920				power['runtime_usage'], \
3921				power['autosuspend'], \
3922				ms2nice(power['runtime_active_time']), \
3923				ms2nice(power['runtime_suspended_time']), \
3924				ms2nice(power['active_duration']), \
3925				ms2nice(power['connected_duration'])))
3926
3927# Function: devProps
3928# Description:
3929#	 Retrieve a list of properties for all devices in the trace log
3930def devProps(data=0):
3931	global sysvals
3932	props = dict()
3933
3934	if data:
3935		idx = data.index(': ') + 2
3936		if idx >= len(data):
3937			return
3938		devlist = data[idx:].split(';')
3939		for dev in devlist:
3940			f = dev.split(',')
3941			if len(f) < 3:
3942				continue
3943			dev = f[0]
3944			props[dev] = DevProps()
3945			props[dev].altname = f[1]
3946			if int(f[2]):
3947				props[dev].async = True
3948			else:
3949				props[dev].async = False
3950			sysvals.devprops = props
3951		if sysvals.suspendmode == 'command' and 'testcommandstring' in props:
3952			sysvals.testcommand = props['testcommandstring'].altname
3953		return
3954
3955	if(os.path.exists(sysvals.ftracefile) == False):
3956		doError('%s does not exist' % sysvals.ftracefile, False)
3957
3958	# first get the list of devices we need properties for
3959	msghead = 'Additional data added by AnalyzeSuspend'
3960	alreadystamped = False
3961	tp = TestProps()
3962	tf = open(sysvals.ftracefile, 'r')
3963	for line in tf:
3964		if msghead in line:
3965			alreadystamped = True
3966			continue
3967		# determine the trace data type (required for further parsing)
3968		m = re.match(sysvals.tracertypefmt, line)
3969		if(m):
3970			tp.setTracerType(m.group('t'))
3971			continue
3972		# parse only valid lines, if this is not one move on
3973		m = re.match(tp.ftrace_line_fmt, line)
3974		if(not m or 'device_pm_callback_start' not in line):
3975			continue
3976		m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
3977		if(not m):
3978			continue
3979		drv, dev, par = m.group('drv'), m.group('d'), m.group('p')
3980		if dev not in props:
3981			props[dev] = DevProps()
3982	tf.close()
3983
3984	if not alreadystamped and sysvals.suspendmode == 'command':
3985		out = '#\n# '+msghead+'\n# Device Properties: '
3986		out += 'testcommandstring,%s,0;' % (sysvals.testcommand)
3987		with open(sysvals.ftracefile, 'a') as fp:
3988			fp.write(out+'\n')
3989		sysvals.devprops = props
3990		return
3991
3992	# now get the syspath for each of our target devices
3993	for dirname, dirnames, filenames in os.walk('/sys/devices'):
3994		if(re.match('.*/power', dirname) and 'async' in filenames):
3995			dev = dirname.split('/')[-2]
3996			if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
3997				props[dev].syspath = dirname[:-6]
3998
3999	# now fill in the properties for our target devices
4000	for dev in props:
4001		dirname = props[dev].syspath
4002		if not dirname or not os.path.exists(dirname):
4003			continue
4004		with open(dirname+'/power/async') as fp:
4005			text = fp.read()
4006			props[dev].async = False
4007			if 'enabled' in text:
4008				props[dev].async = True
4009		fields = os.listdir(dirname)
4010		if 'product' in fields:
4011			with open(dirname+'/product') as fp:
4012				props[dev].altname = fp.read()
4013		elif 'name' in fields:
4014			with open(dirname+'/name') as fp:
4015				props[dev].altname = fp.read()
4016		elif 'model' in fields:
4017			with open(dirname+'/model') as fp:
4018				props[dev].altname = fp.read()
4019		elif 'description' in fields:
4020			with open(dirname+'/description') as fp:
4021				props[dev].altname = fp.read()
4022		elif 'id' in fields:
4023			with open(dirname+'/id') as fp:
4024				props[dev].altname = fp.read()
4025		elif 'idVendor' in fields and 'idProduct' in fields:
4026			idv, idp = '', ''
4027			with open(dirname+'/idVendor') as fp:
4028				idv = fp.read().strip()
4029			with open(dirname+'/idProduct') as fp:
4030				idp = fp.read().strip()
4031			props[dev].altname = '%s:%s' % (idv, idp)
4032
4033		if props[dev].altname:
4034			out = props[dev].altname.strip().replace('\n', ' ')
4035			out = out.replace(',', ' ')
4036			out = out.replace(';', ' ')
4037			props[dev].altname = out
4038
4039	# and now write the data to the ftrace file
4040	if not alreadystamped:
4041		out = '#\n# '+msghead+'\n# Device Properties: '
4042		for dev in sorted(props):
4043			out += props[dev].out(dev)
4044		with open(sysvals.ftracefile, 'a') as fp:
4045			fp.write(out+'\n')
4046
4047	sysvals.devprops = props
4048
4049# Function: getModes
4050# Description:
4051#	 Determine the supported power modes on this system
4052# Output:
4053#	 A string list of the available modes
4054def getModes():
4055	global sysvals
4056	modes = ''
4057	if(os.path.exists(sysvals.powerfile)):
4058		fp = open(sysvals.powerfile, 'r')
4059		modes = string.split(fp.read())
4060		fp.close()
 
 
 
 
 
4061	return modes
4062
4063# Function: getFPDT
4064# Description:
4065#	 Read the acpi bios tables and pull out FPDT, the firmware data
4066# Arguments:
4067#	 output: True to output the info to stdout, False otherwise
4068def getFPDT(output):
4069	global sysvals
4070
4071	rectype = {}
4072	rectype[0] = 'Firmware Basic Boot Performance Record'
4073	rectype[1] = 'S3 Performance Table Record'
4074	prectype = {}
4075	prectype[0] = 'Basic S3 Resume Performance Record'
4076	prectype[1] = 'Basic S3 Suspend Performance Record'
4077
4078	rootCheck(True)
4079	if(not os.path.exists(sysvals.fpdtpath)):
4080		if(output):
4081			doError('file does not exist: %s' % sysvals.fpdtpath, False)
4082		return False
4083	if(not os.access(sysvals.fpdtpath, os.R_OK)):
4084		if(output):
4085			doError('file is not readable: %s' % sysvals.fpdtpath, False)
4086		return False
4087	if(not os.path.exists(sysvals.mempath)):
4088		if(output):
4089			doError('file does not exist: %s' % sysvals.mempath, False)
4090		return False
4091	if(not os.access(sysvals.mempath, os.R_OK)):
4092		if(output):
4093			doError('file is not readable: %s' % sysvals.mempath, False)
4094		return False
4095
4096	fp = open(sysvals.fpdtpath, 'rb')
4097	buf = fp.read()
4098	fp.close()
4099
4100	if(len(buf) < 36):
4101		if(output):
4102			doError('Invalid FPDT table data, should '+\
4103				'be at least 36 bytes', False)
4104		return False
4105
4106	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
4107	if(output):
4108		print('')
4109		print('Firmware Performance Data Table (%s)' % table[0])
4110		print('                  Signature : %s' % table[0])
4111		print('               Table Length : %u' % table[1])
4112		print('                   Revision : %u' % table[2])
4113		print('                   Checksum : 0x%x' % table[3])
4114		print('                     OEM ID : %s' % table[4])
4115		print('               OEM Table ID : %s' % table[5])
4116		print('               OEM Revision : %u' % table[6])
4117		print('                 Creator ID : %s' % table[7])
4118		print('           Creator Revision : 0x%x' % table[8])
4119		print('')
4120
4121	if(table[0] != 'FPDT'):
4122		if(output):
4123			doError('Invalid FPDT table')
4124		return False
4125	if(len(buf) <= 36):
4126		return False
4127	i = 0
4128	fwData = [0, 0]
4129	records = buf[36:]
4130	fp = open(sysvals.mempath, 'rb')
4131	while(i < len(records)):
4132		header = struct.unpack('HBB', records[i:i+4])
4133		if(header[0] not in rectype):
4134			i += header[1]
4135			continue
4136		if(header[1] != 16):
4137			i += header[1]
4138			continue
4139		addr = struct.unpack('Q', records[i+8:i+16])[0]
4140		try:
4141			fp.seek(addr)
4142			first = fp.read(8)
4143		except:
4144			if(output):
4145				print('Bad address 0x%x in %s' % (addr, sysvals.mempath))
4146			return [0, 0]
4147		rechead = struct.unpack('4sI', first)
4148		recdata = fp.read(rechead[1]-8)
4149		if(rechead[0] == 'FBPT'):
4150			record = struct.unpack('HBBIQQQQQ', recdata)
4151			if(output):
4152				print('%s (%s)' % (rectype[header[0]], rechead[0]))
4153				print('                  Reset END : %u ns' % record[4])
4154				print('  OS Loader LoadImage Start : %u ns' % record[5])
4155				print(' OS Loader StartImage Start : %u ns' % record[6])
4156				print('     ExitBootServices Entry : %u ns' % record[7])
4157				print('      ExitBootServices Exit : %u ns' % record[8])
4158		elif(rechead[0] == 'S3PT'):
4159			if(output):
4160				print('%s (%s)' % (rectype[header[0]], rechead[0]))
4161			j = 0
4162			while(j < len(recdata)):
4163				prechead = struct.unpack('HBB', recdata[j:j+4])
4164				if(prechead[0] not in prectype):
4165					continue
4166				if(prechead[0] == 0):
4167					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
4168					fwData[1] = record[2]
4169					if(output):
4170						print('    %s' % prectype[prechead[0]])
4171						print('               Resume Count : %u' % \
4172							record[1])
4173						print('                 FullResume : %u ns' % \
4174							record[2])
4175						print('              AverageResume : %u ns' % \
4176							record[3])
4177				elif(prechead[0] == 1):
4178					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
4179					fwData[0] = record[1] - record[0]
4180					if(output):
4181						print('    %s' % prectype[prechead[0]])
4182						print('               SuspendStart : %u ns' % \
4183							record[0])
4184						print('                 SuspendEnd : %u ns' % \
4185							record[1])
4186						print('                SuspendTime : %u ns' % \
4187							fwData[0])
4188				j += prechead[1]
4189		if(output):
4190			print('')
4191		i += header[1]
4192	fp.close()
4193	return fwData
4194
4195# Function: statusCheck
4196# Description:
4197#	 Verify that the requested command and options will work, and
4198#	 print the results to the terminal
4199# Output:
4200#	 True if the test will work, False if not
4201def statusCheck(probecheck=False):
4202	global sysvals
4203	status = True
4204
4205	print('Checking this system (%s)...' % platform.node())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4206
4207	# check we have root access
4208	res = sysvals.colorText('NO (No features of this tool will work!)')
4209	if(rootCheck(False)):
4210		res = 'YES'
 
 
 
 
 
4211	print('    have root access: %s' % res)
4212	if(res != 'YES'):
4213		print('    Try running this script with sudo')
 
 
 
4214		return False
4215
4216	# check sysfs is mounted
4217	res = sysvals.colorText('NO (No features of this tool will work!)')
4218	if(os.path.exists(sysvals.powerfile)):
4219		res = 'YES'
 
 
 
 
 
 
4220	print('    is sysfs mounted: %s' % res)
4221	if(res != 'YES'):
4222		return False
4223
4224	# check target mode is a valid mode
4225	if sysvals.suspendmode != 'command':
4226		res = sysvals.colorText('NO')
4227		modes = getModes()
4228		if(sysvals.suspendmode in modes):
4229			res = 'YES'
4230		else:
4231			status = False
4232		print('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
4233		if(res == 'NO'):
4234			print('      valid power modes are: %s' % modes)
4235			print('      please choose one with -m')
 
 
 
 
 
 
 
 
 
 
4236
4237	# check if ftrace is available
4238	res = sysvals.colorText('NO')
4239	ftgood = sysvals.verifyFtrace()
4240	if(ftgood):
4241		res = 'YES'
4242	elif(sysvals.usecallgraph):
4243		status = False
4244	print('    is ftrace supported: %s' % res)
4245
4246	# check if kprobes are available
4247	res = sysvals.colorText('NO')
4248	sysvals.usekprobes = sysvals.verifyKprobes()
4249	if(sysvals.usekprobes):
4250		res = 'YES'
4251	else:
4252		sysvals.usedevsrc = False
4253	print('    are kprobes supported: %s' % res)
4254
4255	# what data source are we using
4256	res = 'DMESG'
4257	if(ftgood):
4258		sysvals.usetraceeventsonly = True
4259		sysvals.usetraceevents = False
4260		for e in sysvals.traceevents:
4261			check = False
4262			if(os.path.exists(sysvals.epath+e)):
4263				check = True
 
 
 
 
 
 
4264			if(not check):
4265				sysvals.usetraceeventsonly = False
4266			if(e == 'suspend_resume' and check):
4267				sysvals.usetraceevents = True
4268		if(sysvals.usetraceevents and sysvals.usetraceeventsonly):
4269			res = 'FTRACE (all trace events found)'
4270		elif(sysvals.usetraceevents):
4271			res = 'DMESG and FTRACE (suspend_resume trace event found)'
4272	print('    timeline data source: %s' % res)
4273
4274	# check if rtcwake
4275	res = sysvals.colorText('NO')
4276	if(sysvals.rtcpath != ''):
4277		res = 'YES'
4278	elif(sysvals.rtcwake):
4279		status = False
4280	print('    is rtcwake supported: %s' % res)
4281
4282	if not probecheck:
4283		return status
4284
4285	if (sysvals.usecallgraph and len(sysvals.debugfuncs) > 0) or len(sysvals.kprobes) > 0:
4286		sysvals.initFtrace(True)
4287
4288	# verify callgraph debugfuncs
4289	if sysvals.usecallgraph and len(sysvals.debugfuncs) > 0:
4290		print('    verifying these ftrace callgraph functions work:')
4291		sysvals.setFtraceFilterFunctions(sysvals.debugfuncs)
4292		fp = open(sysvals.tpath+'set_graph_function', 'r')
4293		flist = fp.read().split('\n')
4294		fp.close()
4295		for func in sysvals.debugfuncs:
4296			res = sysvals.colorText('NO')
4297			if func in flist:
4298				res = 'YES'
4299			else:
4300				for i in flist:
4301					if ' [' in i and func == i.split(' ')[0]:
4302						res = 'YES'
4303						break
4304			print('         %s: %s' % (func, res))
4305
4306	# verify kprobes
4307	if len(sysvals.kprobes) > 0:
4308		print('    verifying these kprobes work:')
4309		for name in sorted(sysvals.kprobes):
4310			if name in sysvals.tracefuncs:
4311				continue
4312			res = sysvals.colorText('NO')
4313			if sysvals.testKprobe(sysvals.kprobes[name]):
4314				res = 'YES'
4315			print('         %s: %s' % (name, res))
4316
4317	return status
4318
4319# Function: doError
4320# Description:
4321#	 generic error function for catastrphic failures
4322# Arguments:
4323#	 msg: the error message to print
4324#	 help: True if printHelp should be called after, False otherwise
4325def doError(msg, help):
4326	if(help == True):
4327		printHelp()
4328	print('ERROR: %s\n') % msg
4329	sys.exit()
4330
4331# Function: doWarning
4332# Description:
4333#	 generic warning function for non-catastrophic anomalies
4334# Arguments:
4335#	 msg: the warning message to print
4336#	 file: If not empty, a filename to request be sent to the owner for debug
4337def doWarning(msg, file=''):
4338	print('/* %s */') % msg
4339	if(file):
4340		print('/* For a fix, please send this'+\
4341			' %s file to <todd.e.brandt@intel.com> */' % file)
4342
4343# Function: rootCheck
4344# Description:
4345#	 quick check to see if we have root access
4346def rootCheck(fatal):
4347	global sysvals
4348	if(os.access(sysvals.powerfile, os.W_OK)):
4349		return True
4350	if fatal:
4351		doError('This command must be run as root', False)
4352	return False
4353
4354# Function: getArgInt
4355# Description:
4356#	 pull out an integer argument from the command line with checks
4357def getArgInt(name, args, min, max, main=True):
4358	if main:
4359		try:
4360			arg = args.next()
4361		except:
4362			doError(name+': no argument supplied', True)
4363	else:
4364		arg = args
4365	try:
4366		val = int(arg)
4367	except:
4368		doError(name+': non-integer value given', True)
4369	if(val < min or val > max):
4370		doError(name+': value should be between %d and %d' % (min, max), True)
4371	return val
4372
4373# Function: getArgFloat
4374# Description:
4375#	 pull out a float argument from the command line with checks
4376def getArgFloat(name, args, min, max, main=True):
4377	if main:
4378		try:
4379			arg = args.next()
4380		except:
4381			doError(name+': no argument supplied', True)
4382	else:
4383		arg = args
4384	try:
4385		val = float(arg)
4386	except:
4387		doError(name+': non-numerical value given', True)
4388	if(val < min or val > max):
4389		doError(name+': value should be between %f and %f' % (min, max), True)
4390	return val
4391
4392# Function: rerunTest
4393# Description:
4394#	 generate an output from an existing set of ftrace/dmesg logs
4395def rerunTest():
4396	global sysvals
4397
4398	if(sysvals.ftracefile != ''):
4399		doesTraceLogHaveTraceEvents()
4400	if(sysvals.dmesgfile == '' and not sysvals.usetraceeventsonly):
4401		doError('recreating this html output '+\
4402			'requires a dmesg file', False)
4403	sysvals.setOutputFile()
4404	vprint('Output file: %s' % sysvals.htmlfile)
4405	print('PROCESSING DATA')
4406	if(sysvals.usetraceeventsonly):
4407		testruns = parseTraceLog()
4408	else:
4409		testruns = loadKernelLog()
4410		for data in testruns:
4411			parseKernelLog(data)
4412		if(sysvals.ftracefile != ''):
4413			appendIncompleteTraceLog(testruns)
4414	createHTML(testruns)
4415
4416# Function: runTest
4417# Description:
4418#	 execute a suspend/resume, gather the logs, and generate the output
4419def runTest(subdir, testpath=''):
4420	global sysvals
4421
4422	# prepare for the test
4423	sysvals.initFtrace()
4424	sysvals.initTestOutput(subdir, testpath)
 
 
 
4425
4426	vprint('Output files:\n    %s' % sysvals.dmesgfile)
4427	if(sysvals.usecallgraph or
4428		sysvals.usetraceevents or
4429		sysvals.usetraceeventsonly):
4430		vprint('    %s' % sysvals.ftracefile)
4431	vprint('    %s' % sysvals.htmlfile)
4432
4433	# execute the test
4434	executeSuspend()
4435	sysvals.cleanupFtrace()
 
 
4436
4437	# analyze the data and create the html output
4438	print('PROCESSING DATA')
4439	if(sysvals.usetraceeventsonly):
4440		# data for kernels 3.15 or newer is entirely in ftrace
4441		testruns = parseTraceLog()
4442	else:
4443		# data for kernels older than 3.15 is primarily in dmesg
4444		testruns = loadKernelLog()
4445		for data in testruns:
4446			parseKernelLog(data)
4447		if(sysvals.usecallgraph or sysvals.usetraceevents):
4448			appendIncompleteTraceLog(testruns)
4449	createHTML(testruns)
4450
4451# Function: runSummary
4452# Description:
4453#	 create a summary of tests in a sub-directory
4454def runSummary(subdir, output):
4455	global sysvals
4456
4457	# get a list of ftrace output files
4458	files = []
4459	for dirname, dirnames, filenames in os.walk(subdir):
4460		for filename in filenames:
4461			if(re.match('.*_ftrace.txt', filename)):
4462				files.append("%s/%s" % (dirname, filename))
4463
4464	# process the files in order and get an array of data objects
4465	testruns = []
4466	for file in sorted(files):
4467		if output:
4468			print("Test found in %s" % os.path.dirname(file))
4469		sysvals.ftracefile = file
4470		sysvals.dmesgfile = file.replace('_ftrace.txt', '_dmesg.txt')
4471		doesTraceLogHaveTraceEvents()
4472		sysvals.usecallgraph = False
4473		if not sysvals.usetraceeventsonly:
4474			if(not os.path.exists(sysvals.dmesgfile)):
4475				print("Skipping %s: not a valid test input" % file)
4476				continue
4477			else:
4478				if output:
4479					f = os.path.basename(sysvals.ftracefile)
4480					d = os.path.basename(sysvals.dmesgfile)
4481					print("\tInput files: %s and %s" % (f, d))
4482				testdata = loadKernelLog()
4483				data = testdata[0]
4484				parseKernelLog(data)
4485				testdata = [data]
4486				appendIncompleteTraceLog(testdata)
4487		else:
4488			if output:
4489				print("\tInput file: %s" % os.path.basename(sysvals.ftracefile))
4490			testdata = parseTraceLog()
4491			data = testdata[0]
4492		data.normalizeTime(data.tSuspended)
4493		link = file.replace(subdir+'/', '').replace('_ftrace.txt', '.html')
4494		data.outfile = link
4495		testruns.append(data)
4496
4497	createHTMLSummarySimple(testruns, subdir+'/summary.html')
4498
4499# Function: checkArgBool
4500# Description:
4501#	 check if a boolean string value is true or false
4502def checkArgBool(value):
4503	yes = ['1', 'true', 'yes', 'on']
4504	if value.lower() in yes:
4505		return True
4506	return False
4507
4508# Function: configFromFile
4509# Description:
4510#	 Configure the script via the info in a config file
4511def configFromFile(file):
4512	global sysvals
4513	Config = ConfigParser.ConfigParser()
4514
4515	ignorekprobes = False
4516	Config.read(file)
4517	sections = Config.sections()
4518	if 'Settings' in sections:
4519		for opt in Config.options('Settings'):
4520			value = Config.get('Settings', opt).lower()
4521			if(opt.lower() == 'verbose'):
4522				sysvals.verbose = checkArgBool(value)
4523			elif(opt.lower() == 'addlogs'):
4524				sysvals.addlogs = checkArgBool(value)
4525			elif(opt.lower() == 'dev'):
4526				sysvals.usedevsrc = checkArgBool(value)
4527			elif(opt.lower() == 'ignorekprobes'):
4528				ignorekprobes = checkArgBool(value)
4529			elif(opt.lower() == 'x2'):
4530				if checkArgBool(value):
4531					sysvals.execcount = 2
4532			elif(opt.lower() == 'callgraph'):
4533				sysvals.usecallgraph = checkArgBool(value)
4534			elif(opt.lower() == 'callgraphfunc'):
4535				sysvals.debugfuncs = []
4536				if value:
4537					value = value.split(',')
4538				for i in value:
4539					sysvals.debugfuncs.append(i.strip())
4540			elif(opt.lower() == 'expandcg'):
4541				sysvals.cgexp = checkArgBool(value)
4542			elif(opt.lower() == 'srgap'):
4543				if checkArgBool(value):
4544					sysvals.srgap = 5
4545			elif(opt.lower() == 'mode'):
4546				sysvals.suspendmode = value
4547			elif(opt.lower() == 'command'):
4548				sysvals.testcommand = value
4549			elif(opt.lower() == 'x2delay'):
4550				sysvals.x2delay = getArgInt('-x2delay', value, 0, 60000, False)
4551			elif(opt.lower() == 'postres'):
4552				sysvals.postresumetime = getArgInt('-postres', value, 0, 3600, False)
4553			elif(opt.lower() == 'rtcwake'):
4554				sysvals.rtcwake = True
4555				sysvals.rtcwaketime = getArgInt('-rtcwake', value, 0, 3600, False)
4556			elif(opt.lower() == 'timeprec'):
4557				sysvals.setPrecision(getArgInt('-timeprec', value, 0, 6, False))
4558			elif(opt.lower() == 'mindev'):
4559				sysvals.mindevlen = getArgFloat('-mindev', value, 0.0, 10000.0, False)
4560			elif(opt.lower() == 'mincg'):
4561				sysvals.mincglen = getArgFloat('-mincg', value, 0.0, 10000.0, False)
4562			elif(opt.lower() == 'kprobecolor'):
4563				try:
4564					val = int(value, 16)
4565					sysvals.kprobecolor = '#'+value
4566				except:
4567					sysvals.kprobecolor = value
4568			elif(opt.lower() == 'synccolor'):
4569				try:
4570					val = int(value, 16)
4571					sysvals.synccolor = '#'+value
4572				except:
4573					sysvals.synccolor = value
4574			elif(opt.lower() == 'output-dir'):
4575				args = dict()
4576				n = datetime.now()
4577				args['date'] = n.strftime('%y%m%d')
4578				args['time'] = n.strftime('%H%M%S')
4579				args['hostname'] = sysvals.hostname
4580				sysvals.outdir = value.format(**args)
4581
4582	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
4583		doError('No command supplied for mode "command"', False)
4584	if sysvals.usedevsrc and sysvals.usecallgraph:
4585		doError('dev and callgraph cannot both be true', False)
4586	if sysvals.usecallgraph and sysvals.execcount > 1:
4587		doError('-x2 is not compatible with -f', False)
4588
4589	if ignorekprobes:
4590		return
4591
4592	kprobes = dict()
4593	archkprobe = 'Kprobe_'+platform.machine()
4594	if archkprobe in sections:
4595		for name in Config.options(archkprobe):
4596			kprobes[name] = Config.get(archkprobe, name)
4597	if 'Kprobe' in sections:
4598		for name in Config.options('Kprobe'):
4599			kprobes[name] = Config.get('Kprobe', name)
4600
4601	for name in kprobes:
4602		function = name
4603		format = name
4604		color = ''
4605		args = dict()
4606		data = kprobes[name].split()
4607		i = 0
4608		for val in data:
4609			# bracketted strings are special formatting, read them separately
4610			if val[0] == '[' and val[-1] == ']':
4611				for prop in val[1:-1].split(','):
4612					p = prop.split('=')
4613					if p[0] == 'color':
4614						try:
4615							color = int(p[1], 16)
4616							color = '#'+p[1]
4617						except:
4618							color = p[1]
4619				continue
4620			# first real arg should be the format string
4621			if i == 0:
4622				format = val
4623			# all other args are actual function args
4624			else:
4625				d = val.split('=')
4626				args[d[0]] = d[1]
4627			i += 1
4628		if not function or not format:
4629			doError('Invalid kprobe: %s' % name, False)
4630		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
4631			if arg not in args:
4632				doError('Kprobe "%s" is missing argument "%s"' % (name, arg), False)
4633		if name in sysvals.kprobes:
4634			doError('Duplicate kprobe found "%s"' % (name), False)
4635		vprint('Adding KPROBE: %s %s %s %s' % (name, function, format, args))
4636		sysvals.kprobes[name] = {
4637			'name': name,
4638			'func': function,
4639			'format': format,
4640			'args': args,
4641			'mask': re.sub('{(?P<n>[a-z,A-Z,0-9]*)}', '.*', format)
4642		}
4643		if color:
4644			sysvals.kprobes[name]['color'] = color
4645
4646# Function: printHelp
4647# Description:
4648#	 print out the help text
4649def printHelp():
4650	global sysvals
4651	modes = getModes()
4652
4653	print('')
4654	print('AnalyzeSuspend v%s' % sysvals.version)
4655	print('Usage: sudo analyze_suspend.py <options>')
4656	print('')
4657	print('Description:')
4658	print('  This tool is designed to assist kernel and OS developers in optimizing')
4659	print('  their linux stack\'s suspend/resume time. Using a kernel image built')
4660	print('  with a few extra options enabled, the tool will execute a suspend and')
4661	print('  capture dmesg and ftrace data until resume is complete. This data is')
4662	print('  transformed into a device timeline and an optional callgraph to give')
4663	print('  a detailed view of which devices/subsystems are taking the most')
4664	print('  time in suspend/resume.')
4665	print('')
4666	print('  Generates output files in subdirectory: suspend-mmddyy-HHMMSS')
4667	print('   HTML output:                    <hostname>_<mode>.html')
4668	print('   raw dmesg output:               <hostname>_<mode>_dmesg.txt')
4669	print('   raw ftrace output:              <hostname>_<mode>_ftrace.txt')
4670	print('')
4671	print('Options:')
4672	print('  [general]')
4673	print('    -h          Print this help text')
4674	print('    -v          Print the current tool version')
4675	print('    -config file Pull arguments and config options from a file')
4676	print('    -verbose    Print extra information during execution and analysis')
4677	print('    -status     Test to see if the system is enabled to run this tool')
4678	print('    -modes      List available suspend modes')
4679	print('    -m mode     Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode)
4680	print('    -o subdir   Override the output subdirectory')
4681	print('  [advanced]')
4682	print('    -rtcwake t  Use rtcwake to autoresume after <t> seconds (default: disabled)')
4683	print('    -addlogs    Add the dmesg and ftrace logs to the html output')
4684	print('    -multi n d  Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
4685	print('                be created in a new subdirectory with a summary page.')
4686	print('    -srgap      Add a visible gap in the timeline between sus/res (default: disabled)')
4687	print('    -cmd {s}    Instead of suspend/resume, run a command, e.g. "sync -d"')
4688	print('    -mindev ms  Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)')
4689	print('    -mincg  ms  Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)')
4690	print('    -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
4691	print('  [debug]')
4692	print('    -f          Use ftrace to create device callgraphs (default: disabled)')
4693	print('    -expandcg   pre-expand the callgraph data in the html output (default: disabled)')
4694	print('    -flist      Print the list of functions currently being captured in ftrace')
4695	print('    -flistall   Print all functions capable of being captured in ftrace')
4696	print('    -fadd file  Add functions to be graphed in the timeline from a list in a text file')
4697	print('    -filter "d1 d2 ..." Filter out all but this list of device names')
4698	print('    -dev        Display common low level functions in the timeline')
4699	print('  [post-resume task analysis]')
4700	print('    -x2         Run two suspend/resumes back to back (default: disabled)')
4701	print('    -x2delay t  Minimum millisecond delay <t> between the two test runs (default: 0 ms)')
4702	print('    -postres t  Time after resume completion to wait for post-resume events (default: 0 S)')
 
 
4703	print('  [utilities]')
4704	print('    -fpdt       Print out the contents of the ACPI Firmware Performance Data Table')
4705	print('    -usbtopo    Print out the current USB topology with power info')
4706	print('    -usbauto    Enable autosuspend for all connected USB devices')
 
 
 
 
4707	print('  [re-analyze data from previous runs]')
4708	print('    -ftrace ftracefile  Create HTML output using ftrace input')
4709	print('    -dmesg dmesgfile    Create HTML output using dmesg (not needed for kernel >= 3.15)')
4710	print('    -summary directory  Create a summary of all test in this dir')
4711	print('')
4712	return True
4713
4714# ----------------- MAIN --------------------
4715# exec start (skipped if script is loaded as library)
4716if __name__ == '__main__':
4717	cmd = ''
4718	cmdarg = ''
4719	multitest = {'run': False, 'count': 0, 'delay': 0}
4720	simplecmds = ['-modes', '-fpdt', '-flist', '-flistall', '-usbtopo', '-usbauto', '-status']
4721	# loop through the command line arguments
4722	args = iter(sys.argv[1:])
4723	for arg in args:
4724		if(arg == '-m'):
4725			try:
4726				val = args.next()
4727			except:
4728				doError('No mode supplied', True)
4729			if val == 'command' and not sysvals.testcommand:
4730				doError('No command supplied for mode "command"', True)
4731			sysvals.suspendmode = val
4732		elif(arg in simplecmds):
4733			cmd = arg[1:]
4734		elif(arg == '-h'):
4735			printHelp()
4736			sys.exit()
4737		elif(arg == '-v'):
4738			print("Version %s" % sysvals.version)
4739			sys.exit()
 
 
 
 
 
 
 
 
 
4740		elif(arg == '-x2'):
 
 
4741			sysvals.execcount = 2
4742			if(sysvals.usecallgraph):
4743				doError('-x2 is not compatible with -f', False)
4744		elif(arg == '-x2delay'):
4745			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
4746		elif(arg == '-postres'):
 
 
4747			sysvals.postresumetime = getArgInt('-postres', args, 0, 3600)
4748		elif(arg == '-f'):
4749			sysvals.usecallgraph = True
4750			if(sysvals.execcount > 1):
4751				doError('-x2 is not compatible with -f', False)
4752			if(sysvals.usedevsrc):
4753				doError('-dev is not compatible with -f', False)
4754		elif(arg == '-addlogs'):
4755			sysvals.addlogs = True
 
 
 
 
4756		elif(arg == '-verbose'):
4757			sysvals.verbose = True
4758		elif(arg == '-dev'):
4759			sysvals.usedevsrc = True
4760			if(sysvals.usecallgraph):
4761				doError('-dev is not compatible with -f', False)
4762		elif(arg == '-rtcwake'):
4763			sysvals.rtcwake = True
4764			sysvals.rtcwaketime = getArgInt('-rtcwake', args, 0, 3600)
4765		elif(arg == '-timeprec'):
4766			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
4767		elif(arg == '-mindev'):
4768			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
4769		elif(arg == '-mincg'):
4770			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
4771		elif(arg == '-cmd'):
4772			try:
4773				val = args.next()
4774			except:
4775				doError('No command string supplied', True)
4776			sysvals.testcommand = val
4777			sysvals.suspendmode = 'command'
4778		elif(arg == '-expandcg'):
4779			sysvals.cgexp = True
4780		elif(arg == '-srgap'):
4781			sysvals.srgap = 5
4782		elif(arg == '-multi'):
4783			multitest['run'] = True
4784			multitest['count'] = getArgInt('-multi n (exec count)', args, 2, 1000000)
4785			multitest['delay'] = getArgInt('-multi d (delay between tests)', args, 0, 3600)
4786		elif(arg == '-o'):
4787			try:
4788				val = args.next()
4789			except:
4790				doError('No subdirectory name supplied', True)
4791			sysvals.outdir = val
4792		elif(arg == '-config'):
4793			try:
4794				val = args.next()
4795			except:
4796				doError('No text file supplied', True)
4797			if(os.path.exists(val) == False):
4798				doError('%s does not exist' % val, False)
4799			configFromFile(val)
4800		elif(arg == '-fadd'):
4801			try:
4802				val = args.next()
4803			except:
4804				doError('No text file supplied', True)
4805			if(os.path.exists(val) == False):
4806				doError('%s does not exist' % val, False)
4807			sysvals.addFtraceFilterFunctions(val)
4808		elif(arg == '-dmesg'):
4809			try:
4810				val = args.next()
4811			except:
4812				doError('No dmesg file supplied', True)
4813			sysvals.notestrun = True
4814			sysvals.dmesgfile = val
4815			if(os.path.exists(sysvals.dmesgfile) == False):
4816				doError('%s does not exist' % sysvals.dmesgfile, False)
4817		elif(arg == '-ftrace'):
4818			try:
4819				val = args.next()
4820			except:
4821				doError('No ftrace file supplied', True)
4822			sysvals.notestrun = True
 
4823			sysvals.ftracefile = val
4824			if(os.path.exists(sysvals.ftracefile) == False):
4825				doError('%s does not exist' % sysvals.ftracefile, False)
4826		elif(arg == '-summary'):
4827			try:
4828				val = args.next()
4829			except:
4830				doError('No directory supplied', True)
4831			cmd = 'summary'
4832			cmdarg = val
4833			sysvals.notestrun = True
4834			if(os.path.isdir(val) == False):
4835				doError('%s is not accesible' % val, False)
4836		elif(arg == '-filter'):
4837			try:
4838				val = args.next()
4839			except:
4840				doError('No devnames supplied', True)
4841			sysvals.setDeviceFilter(val)
 
 
 
4842		else:
4843			doError('Invalid argument: '+arg, True)
4844
4845	# callgraph size cannot exceed device size
4846	if sysvals.mincglen < sysvals.mindevlen:
4847		sysvals.mincglen = sysvals.mindevlen
4848
4849	# just run a utility command and exit
4850	if(cmd != ''):
4851		if(cmd == 'status'):
4852			statusCheck(True)
4853		elif(cmd == 'fpdt'):
 
 
4854			getFPDT(True)
4855		elif(cmd == 'usbtopo'):
4856			detectUSB()
 
 
 
4857		elif(cmd == 'modes'):
4858			modes = getModes()
4859			print modes
4860		elif(cmd == 'flist'):
4861			sysvals.getFtraceFilterFunctions(True)
4862		elif(cmd == 'flistall'):
4863			sysvals.getFtraceFilterFunctions(False)
4864		elif(cmd == 'usbauto'):
4865			setUSBDevicesAuto()
4866		elif(cmd == 'summary'):
4867			print("Generating a summary of folder \"%s\"" % cmdarg)
4868			runSummary(cmdarg, True)
4869		sys.exit()
4870
 
 
 
 
 
 
 
 
 
4871	# if instructed, re-analyze existing data files
4872	if(sysvals.notestrun):
4873		rerunTest()
4874		sys.exit()
4875
4876	# verify that we can run a test
4877	if(not statusCheck()):
4878		print('Check FAILED, aborting the test run!')
4879		sys.exit()
4880
4881	if multitest['run']:
4882		# run multiple tests in a separate subdirectory
4883		s = 'x%d' % multitest['count']
4884		if not sysvals.outdir:
4885			sysvals.outdir = datetime.now().strftime('suspend-'+s+'-%m%d%y-%H%M%S')
4886		if not os.path.isdir(sysvals.outdir):
4887			os.mkdir(sysvals.outdir)
4888		for i in range(multitest['count']):
4889			if(i != 0):
4890				print('Waiting %d seconds...' % (multitest['delay']))
4891				time.sleep(multitest['delay'])
4892			print('TEST (%d/%d) START' % (i+1, multitest['count']))
4893			runTest(sysvals.outdir)
4894			print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count']))
4895		runSummary(sysvals.outdir, False)
4896	else:
4897		# run the test in the current directory
4898		runTest('.', sysvals.outdir)