• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(".")
3592