1 #!/usr/bin/python2
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 # Authors:
16 #	 Todd Brandt <todd.e.brandt@linux.intel.com>
17 #
18 # Links:
19 #	 Home Page
20 #	   https://01.org/suspendresume
21 #	 Source repo
22 #	   git@github.com:01org/pm-graph
23 #
24 # Description:
25 #	 This tool is designed to assist kernel and OS developers in optimizing
26 #	 their linux stack's suspend/resume time. Using a kernel image built
27 #	 with a few extra options enabled, the tool will execute a suspend and
28 #	 will capture dmesg and ftrace data until resume is complete. This data
29 #	 is transformed into a device timeline and a callgraph to give a quick
30 #	 and detailed view of which devices and callbacks are taking the most
31 #	 time in suspend/resume. The output is a single html file which can be
32 #	 viewed in firefox or chrome.
33 #
34 #	 The following kernel build options are required:
35 #		 CONFIG_PM_DEBUG=y
36 #		 CONFIG_PM_SLEEP_DEBUG=y
37 #		 CONFIG_FTRACE=y
38 #		 CONFIG_FUNCTION_TRACER=y
39 #		 CONFIG_FUNCTION_GRAPH_TRACER=y
40 #		 CONFIG_KPROBES=y
41 #		 CONFIG_KPROBES_ON_FTRACE=y
42 #
43 #	 For kernel versions older than 3.15:
44 #	 The following additional kernel parameters are required:
45 #		 (e.g. in file /etc/default/grub)
46 #		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
47 #
48 
49 # ----------------- LIBRARIES --------------------
50 
51 import sys
52 import time
53 import os
54 import string
55 import re
56 import platform
57 from datetime import datetime
58 import struct
59 import ConfigParser
60 import gzip
61 from threading import Thread
62 from subprocess import call, Popen, PIPE
63 
64 # ----------------- CLASSES --------------------
65 
66 # Class: SystemValues
67 # Description:
68 #	 A global, single-instance container used to
69 #	 store system values and test parameters
70 class SystemValues:
71 	title = 'SleepGraph'
72 	version = '5.1'
73 	ansi = False
74 	rs = 0
75 	display = 0
76 	gzip = False
77 	sync = False
78 	verbose = False
79 	testlog = True
80 	dmesglog = False
81 	ftracelog = False
82 	mindevlen = 0.0
83 	mincglen = 0.0
84 	cgphase = ''
85 	cgtest = -1
86 	cgskip = ''
87 	multitest = {'run': False, 'count': 0, 'delay': 0}
88 	max_graph_depth = 0
89 	callloopmaxgap = 0.0001
90 	callloopmaxlen = 0.005
91 	bufsize = 0
92 	cpucount = 0
93 	memtotal = 204800
94 	memfree = 204800
95 	srgap = 0
96 	cgexp = False
97 	testdir = ''
98 	outdir = ''
99 	tpath = '/sys/kernel/debug/tracing/'
100 	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
101 	epath = '/sys/kernel/debug/tracing/events/power/'
102 	traceevents = [
103 		'suspend_resume',
104 		'device_pm_callback_end',
105 		'device_pm_callback_start'
106 	]
107 	logmsg = ''
108 	testcommand = ''
109 	mempath = '/dev/mem'
110 	powerfile = '/sys/power/state'
111 	mempowerfile = '/sys/power/mem_sleep'
112 	suspendmode = 'mem'
113 	memmode = ''
114 	hostname = 'localhost'
115 	prefix = 'test'
116 	teststamp = ''
117 	sysstamp = ''
118 	dmesgstart = 0.0
119 	dmesgfile = ''
120 	ftracefile = ''
121 	htmlfile = 'output.html'
122 	result = ''
123 	rtcwake = True
124 	rtcwaketime = 15
125 	rtcpath = ''
126 	devicefilter = []
127 	cgfilter = []
128 	stamp = 0
129 	execcount = 1
130 	x2delay = 0
131 	skiphtml = False
132 	usecallgraph = False
133 	usetraceevents = False
134 	usetracemarkers = True
135 	usekprobes = True
136 	usedevsrc = False
137 	useprocmon = False
138 	notestrun = False
139 	cgdump = False
140 	mixedphaseheight = True
141 	devprops = dict()
142 	predelay = 0
143 	postdelay = 0
144 	procexecfmt = 'ps - (?P<ps>.*)$'
145 	devpropfmt = '# Device Properties: .*'
146 	tracertypefmt = '# tracer: (?P<t>.*)'
147 	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
148 	tracefuncs = {
149 		'sys_sync': {},
150 		'__pm_notifier_call_chain': {},
151 		'pm_prepare_console': {},
152 		'pm_notifier_call_chain': {},
153 		'freeze_processes': {},
154 		'freeze_kernel_threads': {},
155 		'pm_restrict_gfp_mask': {},
156 		'acpi_suspend_begin': {},
157 		'acpi_hibernation_begin': {},
158 		'acpi_hibernation_enter': {},
159 		'acpi_hibernation_leave': {},
160 		'acpi_pm_freeze': {},
161 		'acpi_pm_thaw': {},
162 		'hibernate_preallocate_memory': {},
163 		'create_basic_memory_bitmaps': {},
164 		'swsusp_write': {},
165 		'suspend_console': {},
166 		'acpi_pm_prepare': {},
167 		'syscore_suspend': {},
168 		'arch_enable_nonboot_cpus_end': {},
169 		'syscore_resume': {},
170 		'acpi_pm_finish': {},
171 		'resume_console': {},
172 		'acpi_pm_end': {},
173 		'pm_restore_gfp_mask': {},
174 		'thaw_processes': {},
175 		'pm_restore_console': {},
176 		'CPU_OFF': {
177 			'func':'_cpu_down',
178 			'args_x86_64': {'cpu':'%di:s32'},
179 			'format': 'CPU_OFF[{cpu}]'
180 		},
181 		'CPU_ON': {
182 			'func':'_cpu_up',
183 			'args_x86_64': {'cpu':'%di:s32'},
184 			'format': 'CPU_ON[{cpu}]'
185 		},
186 	}
187 	dev_tracefuncs = {
188 		# general wait/delay/sleep
189 		'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
190 		'schedule_timeout_uninterruptible': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
191 		'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
192 		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
193 		'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
194 		'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
195 		'acpi_os_stall': {'ub': 1},
196 		# ACPI
197 		'acpi_resume_power_resources': {},
198 		'acpi_ps_parse_aml': {},
199 		# filesystem
200 		'ext4_sync_fs': {},
201 		# 80211
202 		'iwlagn_mac_start': {},
203 		'iwlagn_alloc_bcast_station': {},
204 		'iwl_trans_pcie_start_hw': {},
205 		'iwl_trans_pcie_start_fw': {},
206 		'iwl_run_init_ucode': {},
207 		'iwl_load_ucode_wait_alive': {},
208 		'iwl_alive_start': {},
209 		'iwlagn_mac_stop': {},
210 		'iwlagn_mac_suspend': {},
211 		'iwlagn_mac_resume': {},
212 		'iwlagn_mac_add_interface': {},
213 		'iwlagn_mac_remove_interface': {},
214 		'iwlagn_mac_change_interface': {},
215 		'iwlagn_mac_config': {},
216 		'iwlagn_configure_filter': {},
217 		'iwlagn_mac_hw_scan': {},
218 		'iwlagn_bss_info_changed': {},
219 		'iwlagn_mac_channel_switch': {},
220 		'iwlagn_mac_flush': {},
221 		# ATA
222 		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
223 		# i915
224 		'i915_gem_resume': {},
225 		'i915_restore_state': {},
226 		'intel_opregion_setup': {},
227 		'g4x_pre_enable_dp': {},
228 		'vlv_pre_enable_dp': {},
229 		'chv_pre_enable_dp': {},
230 		'g4x_enable_dp': {},
231 		'vlv_enable_dp': {},
232 		'intel_hpd_init': {},
233 		'intel_opregion_register': {},
234 		'intel_dp_detect': {},
235 		'intel_hdmi_detect': {},
236 		'intel_opregion_init': {},
237 		'intel_fbdev_set_suspend': {},
238 	}
239 	cgblacklist = []
240 	kprobes = dict()
241 	timeformat = '%.3f'
242 	cmdline = '%s %s' % \
243 			(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
244 	def __init__(self):
245 		self.archargs = 'args_'+platform.machine()
246 		self.hostname = platform.node()
247 		if(self.hostname == ''):
248 			self.hostname = 'localhost'
249 		rtc = "rtc0"
250 		if os.path.exists('/dev/rtc'):
251 			rtc = os.readlink('/dev/rtc')
252 		rtc = '/sys/class/rtc/'+rtc
253 		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
254 			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
255 			self.rtcpath = rtc
256 		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
257 			self.ansi = True
258 		self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
259 	def vprint(self, msg):
260 		self.logmsg += msg+'\n'
261 		if(self.verbose):
262 			print(msg)
263 	def rootCheck(self, fatal=True):
264 		if(os.access(self.powerfile, os.W_OK)):
265 			return True
266 		if fatal:
267 			msg = 'This command requires sysfs mount and root access'
268 			print('ERROR: %s\n') % msg
269 			self.outputResult({'error':msg})
270 			sys.exit()
271 		return False
272 	def rootUser(self, fatal=False):
273 		if 'USER' in os.environ and os.environ['USER'] == 'root':
274 			return True
275 		if fatal:
276 			msg = 'This command must be run as root'
277 			print('ERROR: %s\n') % msg
278 			self.outputResult({'error':msg})
279 			sys.exit()
280 		return False
281 	def getExec(self, cmd):
282 		dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
283 			'/usr/local/sbin', '/usr/local/bin']
284 		for path in dirlist:
285 			cmdfull = os.path.join(path, cmd)
286 			if os.path.exists(cmdfull):
287 				return cmdfull
288 		return ''
289 	def setPrecision(self, num):
290 		if num < 0 or num > 6:
291 			return
292 		self.timeformat = '%.{0}f'.format(num)
293 	def setOutputFolder(self, value):
294 		args = dict()
295 		n = datetime.now()
296 		args['date'] = n.strftime('%y%m%d')
297 		args['time'] = n.strftime('%H%M%S')
298 		args['hostname'] = args['host'] = self.hostname
299 		return value.format(**args)
300 	def setOutputFile(self):
301 		if self.dmesgfile != '':
302 			m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
303 			if(m):
304 				self.htmlfile = m.group('name')+'.html'
305 		if self.ftracefile != '':
306 			m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
307 			if(m):
308 				self.htmlfile = m.group('name')+'.html'
309 	def systemInfo(self, info):
310 		p = c = m = b = ''
311 		if 'baseboard-manufacturer' in info:
312 			m = info['baseboard-manufacturer']
313 		elif 'system-manufacturer' in info:
314 			m = info['system-manufacturer']
315 		if 'baseboard-product-name' in info:
316 			p = info['baseboard-product-name']
317 		elif 'system-product-name' in info:
318 			p = info['system-product-name']
319 		if 'processor-version' in info:
320 			c = info['processor-version']
321 		if 'bios-version' in info:
322 			b = info['bios-version']
323 		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | numcpu:%d | memsz:%d | memfr:%d' % \
324 			(m, p, c, b, self.cpucount, self.memtotal, self.memfree)
325 	def printSystemInfo(self, fatal=False):
326 		self.rootCheck(True)
327 		out = dmidecode(self.mempath, fatal)
328 		if len(out) < 1:
329 			return
330 		fmt = '%-24s: %s'
331 		for name in sorted(out):
332 			print fmt % (name, out[name])
333 		print fmt % ('cpucount', ('%d' % self.cpucount))
334 		print fmt % ('memtotal', ('%d kB' % self.memtotal))
335 		print fmt % ('memfree', ('%d kB' % self.memfree))
336 	def cpuInfo(self):
337 		self.cpucount = 0
338 		fp = open('/proc/cpuinfo', 'r')
339 		for line in fp:
340 			if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
341 				self.cpucount += 1
342 		fp.close()
343 		fp = open('/proc/meminfo', 'r')
344 		for line in fp:
345 			m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
346 			if m:
347 				self.memtotal = int(m.group('sz'))
348 			m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
349 			if m:
350 				self.memfree = int(m.group('sz'))
351 		fp.close()
352 	def initTestOutput(self, name):
353 		self.prefix = self.hostname
354 		v = open('/proc/version', 'r').read().strip()
355 		kver = string.split(v)[2]
356 		fmt = name+'-%m%d%y-%H%M%S'
357 		testtime = datetime.now().strftime(fmt)
358 		self.teststamp = \
359 			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
360 		ext = ''
361 		if self.gzip:
362 			ext = '.gz'
363 		self.dmesgfile = \
364 			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
365 		self.ftracefile = \
366 			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
367 		self.htmlfile = \
368 			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
369 		if not os.path.isdir(self.testdir):
370 			os.mkdir(self.testdir)
371 	def getValueList(self, value):
372 		out = []
373 		for i in value.split(','):
374 			if i.strip():
375 				out.append(i.strip())
376 		return out
377 	def setDeviceFilter(self, value):
378 		self.devicefilter = self.getValueList(value)
379 	def setCallgraphFilter(self, value):
380 		self.cgfilter = self.getValueList(value)
381 	def setCallgraphBlacklist(self, file):
382 		self.cgblacklist = self.listFromFile(file)
383 	def rtcWakeAlarmOn(self):
384 		call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
385 		nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
386 		if nowtime:
387 			nowtime = int(nowtime)
388 		else:
389 			# if hardware time fails, use the software time
390 			nowtime = int(datetime.now().strftime('%s'))
391 		alarm = nowtime + self.rtcwaketime
392 		call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
393 	def rtcWakeAlarmOff(self):
394 		call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
395 	def initdmesg(self):
396 		# get the latest time stamp from the dmesg log
397 		fp = Popen('dmesg', stdout=PIPE).stdout
398 		ktime = '0'
399 		for line in fp:
400 			line = line.replace('\r\n', '')
401 			idx = line.find('[')
402 			if idx > 1:
403 				line = line[idx:]
404 			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
405 			if(m):
406 				ktime = m.group('ktime')
407 		fp.close()
408 		self.dmesgstart = float(ktime)
409 	def getdmesg(self, fwdata=[]):
410 		op = self.writeDatafileHeader(sysvals.dmesgfile, fwdata)
411 		# store all new dmesg lines since initdmesg was called
412 		fp = Popen('dmesg', stdout=PIPE).stdout
413 		for line in fp:
414 			line = line.replace('\r\n', '')
415 			idx = line.find('[')
416 			if idx > 1:
417 				line = line[idx:]
418 			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
419 			if(not m):
420 				continue
421 			ktime = float(m.group('ktime'))
422 			if ktime > self.dmesgstart:
423 				op.write(line)
424 		fp.close()
425 		op.close()
426 	def listFromFile(self, file):
427 		list = []
428 		fp = open(file)
429 		for i in fp.read().split('\n'):
430 			i = i.strip()
431 			if i and i[0] != '#':
432 				list.append(i)
433 		fp.close()
434 		return list
435 	def addFtraceFilterFunctions(self, file):
436 		for i in self.listFromFile(file):
437 			if len(i) < 2:
438 				continue
439 			self.tracefuncs[i] = dict()
440 	def getFtraceFilterFunctions(self, current):
441 		self.rootCheck(True)
442 		if not current:
443 			call('cat '+self.tpath+'available_filter_functions', shell=True)
444 			return
445 		master = self.listFromFile(self.tpath+'available_filter_functions')
446 		for i in self.tracefuncs:
447 			if 'func' in self.tracefuncs[i]:
448 				i = self.tracefuncs[i]['func']
449 			if i in master:
450 				print i
451 			else:
452 				print self.colorText(i)
453 	def setFtraceFilterFunctions(self, list):
454 		master = self.listFromFile(self.tpath+'available_filter_functions')
455 		flist = ''
456 		for i in list:
457 			if i not in master:
458 				continue
459 			if ' [' in i:
460 				flist += i.split(' ')[0]+'\n'
461 			else:
462 				flist += i+'\n'
463 		fp = open(self.tpath+'set_graph_function', 'w')
464 		fp.write(flist)
465 		fp.close()
466 	def basicKprobe(self, name):
467 		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
468 	def defaultKprobe(self, name, kdata):
469 		k = kdata
470 		for field in ['name', 'format', 'func']:
471 			if field not in k:
472 				k[field] = name
473 		if self.archargs in k:
474 			k['args'] = k[self.archargs]
475 		else:
476 			k['args'] = dict()
477 			k['format'] = name
478 		self.kprobes[name] = k
479 	def kprobeColor(self, name):
480 		if name not in self.kprobes or 'color' not in self.kprobes[name]:
481 			return ''
482 		return self.kprobes[name]['color']
483 	def kprobeDisplayName(self, name, dataraw):
484 		if name not in self.kprobes:
485 			self.basicKprobe(name)
486 		data = ''
487 		quote=0
488 		# first remvoe any spaces inside quotes, and the quotes
489 		for c in dataraw:
490 			if c == '"':
491 				quote = (quote + 1) % 2
492 			if quote and c == ' ':
493 				data += '_'
494 			elif c != '"':
495 				data += c
496 		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
497 		arglist = dict()
498 		# now process the args
499 		for arg in sorted(args):
500 			arglist[arg] = ''
501 			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
502 			if m:
503 				arglist[arg] = m.group('arg')
504 			else:
505 				m = re.match('.* '+arg+'=(?P<arg>.*)', data);
506 				if m:
507 					arglist[arg] = m.group('arg')
508 		out = fmt.format(**arglist)
509 		out = out.replace(' ', '_').replace('"', '')
510 		return out
511 	def kprobeText(self, kname, kprobe):
512 		name = fmt = func = kname
513 		args = dict()
514 		if 'name' in kprobe:
515 			name = kprobe['name']
516 		if 'format' in kprobe:
517 			fmt = kprobe['format']
518 		if 'func' in kprobe:
519 			func = kprobe['func']
520 		if self.archargs in kprobe:
521 			args = kprobe[self.archargs]
522 		if 'args' in kprobe:
523 			args = kprobe['args']
524 		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
525 			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
526 		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
527 			if arg not in args:
528 				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
529 		val = 'p:%s_cal %s' % (name, func)
530 		for i in sorted(args):
531 			val += ' %s=%s' % (i, args[i])
532 		val += '\nr:%s_ret %s $retval\n' % (name, func)
533 		return val
534 	def addKprobes(self, output=False):
535 		if len(self.kprobes) < 1:
536 			return
537 		if output:
538 			print('    kprobe functions in this kernel:')
539 		# first test each kprobe
540 		rejects = []
541 		# sort kprobes: trace, ub-dev, custom, dev
542 		kpl = [[], [], [], []]
543 		linesout = len(self.kprobes)
544 		for name in sorted(self.kprobes):
545 			res = self.colorText('YES', 32)
546 			if not self.testKprobe(name, self.kprobes[name]):
547 				res = self.colorText('NO')
548 				rejects.append(name)
549 			else:
550 				if name in self.tracefuncs:
551 					kpl[0].append(name)
552 				elif name in self.dev_tracefuncs:
553 					if 'ub' in self.dev_tracefuncs[name]:
554 						kpl[1].append(name)
555 					else:
556 						kpl[3].append(name)
557 				else:
558 					kpl[2].append(name)
559 			if output:
560 				print('         %s: %s' % (name, res))
561 		kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
562 		# remove all failed ones from the list
563 		for name in rejects:
564 			self.kprobes.pop(name)
565 		# set the kprobes all at once
566 		self.fsetVal('', 'kprobe_events')
567 		kprobeevents = ''
568 		for kp in kplist:
569 			kprobeevents += self.kprobeText(kp, self.kprobes[kp])
570 		self.fsetVal(kprobeevents, 'kprobe_events')
571 		if output:
572 			check = self.fgetVal('kprobe_events')
573 			linesack = (len(check.split('\n')) - 1) / 2
574 			print('    kprobe functions enabled: %d/%d' % (linesack, linesout))
575 		self.fsetVal('1', 'events/kprobes/enable')
576 	def testKprobe(self, kname, kprobe):
577 		self.fsetVal('0', 'events/kprobes/enable')
578 		kprobeevents = self.kprobeText(kname, kprobe)
579 		if not kprobeevents:
580 			return False
581 		try:
582 			self.fsetVal(kprobeevents, 'kprobe_events')
583 			check = self.fgetVal('kprobe_events')
584 		except:
585 			return False
586 		linesout = len(kprobeevents.split('\n'))
587 		linesack = len(check.split('\n'))
588 		if linesack < linesout:
589 			return False
590 		return True
591 	def setVal(self, val, file, mode='w'):
592 		if not os.path.exists(file):
593 			return False
594 		try:
595 			fp = open(file, mode, 0)
596 			fp.write(val)
597 			fp.flush()
598 			fp.close()
599 		except:
600 			return False
601 		return True
602 	def fsetVal(self, val, path, mode='w'):
603 		return self.setVal(val, self.tpath+path, mode)
604 	def getVal(self, file):
605 		res = ''
606 		if not os.path.exists(file):
607 			return res
608 		try:
609 			fp = open(file, 'r')
610 			res = fp.read()
611 			fp.close()
612 		except:
613 			pass
614 		return res
615 	def fgetVal(self, path):
616 		return self.getVal(self.tpath+path)
617 	def cleanupFtrace(self):
618 		if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
619 			self.fsetVal('0', 'events/kprobes/enable')
620 			self.fsetVal('', 'kprobe_events')
621 			self.fsetVal('1024', 'buffer_size_kb')
622 	def setupAllKprobes(self):
623 		for name in self.tracefuncs:
624 			self.defaultKprobe(name, self.tracefuncs[name])
625 		for name in self.dev_tracefuncs:
626 			self.defaultKprobe(name, self.dev_tracefuncs[name])
627 	def isCallgraphFunc(self, name):
628 		if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
629 			return True
630 		for i in self.tracefuncs:
631 			if 'func' in self.tracefuncs[i]:
632 				f = self.tracefuncs[i]['func']
633 			else:
634 				f = i
635 			if name == f:
636 				return True
637 		return False
638 	def initFtrace(self):
639 		self.printSystemInfo(False)
640 		print('INITIALIZING FTRACE...')
641 		# turn trace off
642 		self.fsetVal('0', 'tracing_on')
643 		self.cleanupFtrace()
644 		# set the trace clock to global
645 		self.fsetVal('global', 'trace_clock')
646 		self.fsetVal('nop', 'current_tracer')
647 		# set trace buffer to an appropriate value
648 		cpus = max(1, self.cpucount)
649 		if self.bufsize > 0:
650 			tgtsize = self.bufsize
651 		elif self.usecallgraph or self.usedevsrc:
652 			tgtsize = min(self.memfree, 3*1024*1024)
653 		else:
654 			tgtsize = 65536
655 		while not self.fsetVal('%d' % (tgtsize / cpus), 'buffer_size_kb'):
656 			# if the size failed to set, lower it and keep trying
657 			tgtsize -= 65536
658 			if tgtsize < 65536:
659 				tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
660 				break
661 		print 'Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus)
662 		# initialize the callgraph trace
663 		if(self.usecallgraph):
664 			# set trace type
665 			self.fsetVal('function_graph', 'current_tracer')
666 			self.fsetVal('', 'set_ftrace_filter')
667 			# set trace format options
668 			self.fsetVal('print-parent', 'trace_options')
669 			self.fsetVal('funcgraph-abstime', 'trace_options')
670 			self.fsetVal('funcgraph-cpu', 'trace_options')
671 			self.fsetVal('funcgraph-duration', 'trace_options')
672 			self.fsetVal('funcgraph-proc', 'trace_options')
673 			self.fsetVal('funcgraph-tail', 'trace_options')
674 			self.fsetVal('nofuncgraph-overhead', 'trace_options')
675 			self.fsetVal('context-info', 'trace_options')
676 			self.fsetVal('graph-time', 'trace_options')
677 			self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
678 			cf = ['dpm_run_callback']
679 			if(self.usetraceevents):
680 				cf += ['dpm_prepare', 'dpm_complete']
681 			for fn in self.tracefuncs:
682 				if 'func' in self.tracefuncs[fn]:
683 					cf.append(self.tracefuncs[fn]['func'])
684 				else:
685 					cf.append(fn)
686 			self.setFtraceFilterFunctions(cf)
687 		# initialize the kprobe trace
688 		elif self.usekprobes:
689 			for name in self.tracefuncs:
690 				self.defaultKprobe(name, self.tracefuncs[name])
691 			if self.usedevsrc:
692 				for name in self.dev_tracefuncs:
693 					self.defaultKprobe(name, self.dev_tracefuncs[name])
694 			print('INITIALIZING KPROBES...')
695 			self.addKprobes(self.verbose)
696 		if(self.usetraceevents):
697 			# turn trace events on
698 			events = iter(self.traceevents)
699 			for e in events:
700 				self.fsetVal('1', 'events/power/'+e+'/enable')
701 		# clear the trace buffer
702 		self.fsetVal('', 'trace')
703 	def verifyFtrace(self):
704 		# files needed for any trace data
705 		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
706 				 'trace_marker', 'trace_options', 'tracing_on']
707 		# files needed for callgraph trace data
708 		tp = self.tpath
709 		if(self.usecallgraph):
710 			files += [
711 				'available_filter_functions',
712 				'set_ftrace_filter',
713 				'set_graph_function'
714 			]
715 		for f in files:
716 			if(os.path.exists(tp+f) == False):
717 				return False
718 		return True
719 	def verifyKprobes(self):
720 		# files needed for kprobes to work
721 		files = ['kprobe_events', 'events']
722 		tp = self.tpath
723 		for f in files:
724 			if(os.path.exists(tp+f) == False):
725 				return False
726 		return True
727 	def colorText(self, str, color=31):
728 		if not self.ansi:
729 			return str
730 		return '\x1B[%d;40m%s\x1B[m' % (color, str)
731 	def writeDatafileHeader(self, filename, fwdata=[]):
732 		fp = self.openlog(filename, 'w')
733 		fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
734 		if(self.suspendmode == 'mem' or self.suspendmode == 'command'):
735 			for fw in fwdata:
736 				if(fw):
737 					fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
738 		return fp
739 	def sudouser(self, dir):
740 		if os.path.exists(dir) and os.getuid() == 0 and \
741 			'SUDO_USER' in os.environ:
742 			cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
743 			call(cmd.format(os.environ['SUDO_USER'], dir), shell=True)
744 	def outputResult(self, testdata, num=0):
745 		if not self.result:
746 			return
747 		n = ''
748 		if num > 0:
749 			n = '%d' % num
750 		fp = open(self.result, 'a')
751 		if 'error' in testdata:
752 			fp.write('result%s: fail\n' % n)
753 			fp.write('error%s: %s\n' % (n, testdata['error']))
754 		else:
755 			fp.write('result%s: pass\n' % n)
756 		for v in ['suspend', 'resume', 'boot', 'lastinit']:
757 			if v in testdata:
758 				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
759 		for v in ['fwsuspend', 'fwresume']:
760 			if v in testdata:
761 				fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
762 		if 'bugurl' in testdata:
763 			fp.write('url%s: %s\n' % (n, testdata['bugurl']))
764 		fp.close()
765 		self.sudouser(self.result)
766 	def configFile(self, file):
767 		dir = os.path.dirname(os.path.realpath(__file__))
768 		if os.path.exists(file):
769 			return file
770 		elif os.path.exists(dir+'/'+file):
771 			return dir+'/'+file
772 		elif os.path.exists(dir+'/config/'+file):
773 			return dir+'/config/'+file
774 		return ''
775 	def openlog(self, filename, mode):
776 		isgz = self.gzip
777 		if mode == 'r':
778 			try:
779 				with gzip.open(filename, mode+'b') as fp:
780 					test = fp.read(64)
781 				isgz = True
782 			except:
783 				isgz = False
784 		if isgz:
785 			return gzip.open(filename, mode+'b')
786 		return open(filename, mode)
787 
788 sysvals = SystemValues()
789 switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
790 switchoff = ['disable', 'off', 'false', '0']
791 suspendmodename = {
792 	'freeze': 'Freeze (S0)',
793 	'standby': 'Standby (S1)',
794 	'mem': 'Suspend (S3)',
795 	'disk': 'Hibernate (S4)'
796 }
797 
798 # Class: DevProps
799 # Description:
800 #	 Simple class which holds property values collected
801 #	 for all the devices used in the timeline.
802 class DevProps:
803 	syspath = ''
804 	altname = ''
805 	async = True
806 	xtraclass = ''
807 	xtrainfo = ''
808 	def out(self, dev):
809 		return '%s,%s,%d;' % (dev, self.altname, self.async)
810 	def debug(self, dev):
811 		print '%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.async)
812 	def altName(self, dev):
813 		if not self.altname or self.altname == dev:
814 			return dev
815 		return '%s [%s]' % (self.altname, dev)
816 	def xtraClass(self):
817 		if self.xtraclass:
818 			return ' '+self.xtraclass
819 		if not self.async:
820 			return ' sync'
821 		return ''
822 	def xtraInfo(self):
823 		if self.xtraclass:
824 			return ' '+self.xtraclass
825 		if self.async:
826 			return ' async_device'
827 		return ' sync_device'
828 
829 # Class: DeviceNode
830 # Description:
831 #	 A container used to create a device hierachy, with a single root node
832 #	 and a tree of child nodes. Used by Data.deviceTopology()
833 class DeviceNode:
834 	name = ''
835 	children = 0
836 	depth = 0
837 	def __init__(self, nodename, nodedepth):
838 		self.name = nodename
839 		self.children = []
840 		self.depth = nodedepth
841 
842 # Class: Data
843 # Description:
844 #	 The primary container for suspend/resume test data. There is one for
845 #	 each test run. The data is organized into a cronological hierarchy:
846 #	 Data.dmesg {
847 #		phases {
848 #			10 sequential, non-overlapping phases of S/R
849 #			contents: times for phase start/end, order/color data for html
850 #			devlist {
851 #				device callback or action list for this phase
852 #				device {
853 #					a single device callback or generic action
854 #					contents: start/stop times, pid/cpu/driver info
855 #						parents/children, html id for timeline/callgraph
856 #						optionally includes an ftrace callgraph
857 #						optionally includes dev/ps data
858 #				}
859 #			}
860 #		}
861 #	}
862 #
863 class Data:
864 	dmesg = {}  # root data structure
865 	phases = [] # ordered list of phases
866 	start = 0.0 # test start
867 	end = 0.0   # test end
868 	tSuspended = 0.0 # low-level suspend start
869 	tResumed = 0.0   # low-level resume start
870 	tKernSus = 0.0   # kernel level suspend start
871 	tKernRes = 0.0   # kernel level resume end
872 	tLow = 0.0       # time spent in low-level suspend (standby/freeze)
873 	fwValid = False  # is firmware data available
874 	fwSuspend = 0    # time spent in firmware suspend
875 	fwResume = 0     # time spent in firmware resume
876 	dmesgtext = []   # dmesg text file in memory
877 	pstl = 0         # process timeline
878 	testnumber = 0
879 	idstr = ''
880 	html_device_id = 0
881 	stamp = 0
882 	outfile = ''
883 	devpids = []
884 	kerror = False
885 	def __init__(self, num):
886 		idchar = 'abcdefghij'
887 		self.pstl = dict()
888 		self.testnumber = num
889 		self.idstr = idchar[num]
890 		self.dmesgtext = []
891 		self.phases = []
892 		self.dmesg = { # fixed list of 10 phases
893 			'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
894 								'row': 0, 'color': '#CCFFCC', 'order': 0},
895 			        'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
896 								'row': 0, 'color': '#88FF88', 'order': 1},
897 			   'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
898 								'row': 0, 'color': '#00AA00', 'order': 2},
899 			  'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
900 								'row': 0, 'color': '#008888', 'order': 3},
901 		    'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
902 								'row': 0, 'color': '#0000FF', 'order': 4},
903 			 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
904 								'row': 0, 'color': '#FF0000', 'order': 5},
905 			   'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
906 								'row': 0, 'color': '#FF9900', 'order': 6},
907 			   'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
908 								'row': 0, 'color': '#FFCC00', 'order': 7},
909 			         'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
910 								'row': 0, 'color': '#FFFF88', 'order': 8},
911 			'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
912 								'row': 0, 'color': '#FFFFCC', 'order': 9}
913 		}
914 		self.phases = self.sortedPhases()
915 		self.devicegroups = []
916 		for phase in self.phases:
917 			self.devicegroups.append([phase])
918 		self.errorinfo = {'suspend':[],'resume':[]}
919 	def extractErrorInfo(self):
920 		elist = {
921 			'HWERROR' : '.*\[ *Hardware Error *\].*',
922 			'FWBUG'   : '.*\[ *Firmware Bug *\].*',
923 			'BUG'     : '.*BUG.*',
924 			'ERROR'   : '.*ERROR.*',
925 			'WARNING' : '.*WARNING.*',
926 			'IRQ'     : '.*genirq: .*',
927 			'TASKFAIL': '.*Freezing of tasks failed.*',
928 		}
929 		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
930 		i = 0
931 		list = []
932 		for line in lf:
933 			i += 1
934 			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
935 			if not m:
936 				continue
937 			t = float(m.group('ktime'))
938 			if t < self.start or t > self.end:
939 				continue
940 			dir = 'suspend' if t < self.tSuspended else 'resume'
941 			msg = m.group('msg')
942 			for err in elist:
943 				if re.match(elist[err], msg):
944 					list.append((err, dir, t, i, i))
945 					self.kerror = True
946 					break
947 		for e in list:
948 			type, dir, t, idx1, idx2 = e
949 			sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t))
950 			self.errorinfo[dir].append((type, t, idx1, idx2))
951 		if self.kerror:
952 			sysvals.dmesglog = True
953 		lf.close()
954 	def setStart(self, time):
955 		self.start = time
956 	def setEnd(self, time):
957 		self.end = time
958 	def isTraceEventOutsideDeviceCalls(self, pid, time):
959 		for phase in self.phases:
960 			list = self.dmesg[phase]['list']
961 			for dev in list:
962 				d = list[dev]
963 				if(d['pid'] == pid and time >= d['start'] and
964 					time < d['end']):
965 					return False
966 		return True
967 	def phaseCollision(self, phase, isbegin, line):
968 		key = 'end'
969 		if isbegin:
970 			key = 'start'
971 		if self.dmesg[phase][key] >= 0:
972 			sysvals.vprint('IGNORE: %s' % line.strip())
973 			return True
974 		return False
975 	def sourcePhase(self, start):
976 		for phase in self.phases:
977 			pend = self.dmesg[phase]['end']
978 			if start <= pend:
979 				return phase
980 		return 'resume_complete'
981 	def sourceDevice(self, phaselist, start, end, pid, type):
982 		tgtdev = ''
983 		for phase in phaselist:
984 			list = self.dmesg[phase]['list']
985 			for devname in list:
986 				dev = list[devname]
987 				# pid must match
988 				if dev['pid'] != pid:
989 					continue
990 				devS = dev['start']
991 				devE = dev['end']
992 				if type == 'device':
993 					# device target event is entirely inside the source boundary
994 					if(start < devS or start >= devE or end <= devS or end > devE):
995 						continue
996 				elif type == 'thread':
997 					# thread target event will expand the source boundary
998 					if start < devS:
999 						dev['start'] = start
1000 					if end > devE:
1001 						dev['end'] = end
1002 				tgtdev = dev
1003 				break
1004 		return tgtdev
1005 	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1006 		# try to place the call in a device
1007 		tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device')
1008 		# calls with device pids that occur outside device bounds are dropped
1009 		# TODO: include these somehow
1010 		if not tgtdev and pid in self.devpids:
1011 			return False
1012 		# try to place the call in a thread
1013 		if not tgtdev:
1014 			tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread')
1015 		# create new thread blocks, expand as new calls are found
1016 		if not tgtdev:
1017 			if proc == '<...>':
1018 				threadname = 'kthread-%d' % (pid)
1019 			else:
1020 				threadname = '%s-%d' % (proc, pid)
1021 			tgtphase = self.sourcePhase(start)
1022 			self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1023 			return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1024 		# this should not happen
1025 		if not tgtdev:
1026 			sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1027 				(start, end, proc, pid, kprobename, cdata, rdata))
1028 			return False
1029 		# place the call data inside the src element of the tgtdev
1030 		if('src' not in tgtdev):
1031 			tgtdev['src'] = []
1032 		dtf = sysvals.dev_tracefuncs
1033 		ubiquitous = False
1034 		if kprobename in dtf and 'ub' in dtf[kprobename]:
1035 			ubiquitous = True
1036 		title = cdata+' '+rdata
1037 		mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1038 		m = re.match(mstr, title)
1039 		if m:
1040 			c = m.group('caller')
1041 			a = m.group('args').strip()
1042 			r = m.group('ret')
1043 			if len(r) > 6:
1044 				r = ''
1045 			else:
1046 				r = 'ret=%s ' % r
1047 			if ubiquitous and c in dtf and 'ub' in dtf[c]:
1048 				return False
1049 		color = sysvals.kprobeColor(kprobename)
1050 		e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1051 		tgtdev['src'].append(e)
1052 		return True
1053 	def overflowDevices(self):
1054 		# get a list of devices that extend beyond the end of this test run
1055 		devlist = []
1056 		for phase in self.phases:
1057 			list = self.dmesg[phase]['list']
1058 			for devname in list:
1059 				dev = list[devname]
1060 				if dev['end'] > self.end:
1061 					devlist.append(dev)
1062 		return devlist
1063 	def mergeOverlapDevices(self, devlist):
1064 		# merge any devices that overlap devlist
1065 		for dev in devlist:
1066 			devname = dev['name']
1067 			for phase in self.phases:
1068 				list = self.dmesg[phase]['list']
1069 				if devname not in list:
1070 					continue
1071 				tdev = list[devname]
1072 				o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1073 				if o <= 0:
1074 					continue
1075 				dev['end'] = tdev['end']
1076 				if 'src' not in dev or 'src' not in tdev:
1077 					continue
1078 				dev['src'] += tdev['src']
1079 				del list[devname]
1080 	def usurpTouchingThread(self, name, dev):
1081 		# the caller test has priority of this thread, give it to him
1082 		for phase in self.phases:
1083 			list = self.dmesg[phase]['list']
1084 			if name in list:
1085 				tdev = list[name]
1086 				if tdev['start'] - dev['end'] < 0.1:
1087 					dev['end'] = tdev['end']
1088 					if 'src' not in dev:
1089 						dev['src'] = []
1090 					if 'src' in tdev:
1091 						dev['src'] += tdev['src']
1092 					del list[name]
1093 				break
1094 	def stitchTouchingThreads(self, testlist):
1095 		# merge any threads between tests that touch
1096 		for phase in self.phases:
1097 			list = self.dmesg[phase]['list']
1098 			for devname in list:
1099 				dev = list[devname]
1100 				if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1101 					continue
1102 				for data in testlist:
1103 					data.usurpTouchingThread(devname, dev)
1104 	def optimizeDevSrc(self):
1105 		# merge any src call loops to reduce timeline size
1106 		for phase in self.phases:
1107 			list = self.dmesg[phase]['list']
1108 			for dev in list:
1109 				if 'src' not in list[dev]:
1110 					continue
1111 				src = list[dev]['src']
1112 				p = 0
1113 				for e in sorted(src, key=lambda event: event.time):
1114 					if not p or not e.repeat(p):
1115 						p = e
1116 						continue
1117 					# e is another iteration of p, move it into p
1118 					p.end = e.end
1119 					p.length = p.end - p.time
1120 					p.count += 1
1121 					src.remove(e)
1122 	def trimTimeVal(self, t, t0, dT, left):
1123 		if left:
1124 			if(t > t0):
1125 				if(t - dT < t0):
1126 					return t0
1127 				return t - dT
1128 			else:
1129 				return t
1130 		else:
1131 			if(t < t0 + dT):
1132 				if(t > t0):
1133 					return t0 + dT
1134 				return t + dT
1135 			else:
1136 				return t
1137 	def trimTime(self, t0, dT, left):
1138 		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1139 		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1140 		self.start = self.trimTimeVal(self.start, t0, dT, left)
1141 		self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1142 		self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1143 		self.end = self.trimTimeVal(self.end, t0, dT, left)
1144 		for phase in self.phases:
1145 			p = self.dmesg[phase]
1146 			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1147 			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1148 			list = p['list']
1149 			for name in list:
1150 				d = list[name]
1151 				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1152 				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1153 				if('ftrace' in d):
1154 					cg = d['ftrace']
1155 					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1156 					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1157 					for line in cg.list:
1158 						line.time = self.trimTimeVal(line.time, t0, dT, left)
1159 				if('src' in d):
1160 					for e in d['src']:
1161 						e.time = self.trimTimeVal(e.time, t0, dT, left)
1162 		for dir in ['suspend', 'resume']:
1163 			list = []
1164 			for e in self.errorinfo[dir]:
1165 				type, tm, idx1, idx2 = e
1166 				tm = self.trimTimeVal(tm, t0, dT, left)
1167 				list.append((type, tm, idx1, idx2))
1168 			self.errorinfo[dir] = list
1169 	def normalizeTime(self, tZero):
1170 		# trim out any standby or freeze clock time
1171 		if(self.tSuspended != self.tResumed):
1172 			if(self.tResumed > tZero):
1173 				self.trimTime(self.tSuspended, \
1174 					self.tResumed-self.tSuspended, True)
1175 			else:
1176 				self.trimTime(self.tSuspended, \
1177 					self.tResumed-self.tSuspended, False)
1178 	def getTimeValues(self):
1179 		sktime = (self.dmesg['suspend_machine']['end'] - \
1180 			self.tKernSus) * 1000
1181 		rktime = (self.dmesg['resume_complete']['end'] - \
1182 			self.dmesg['resume_machine']['start']) * 1000
1183 		return (sktime, rktime)
1184 	def setPhase(self, phase, ktime, isbegin):
1185 		if(isbegin):
1186 			self.dmesg[phase]['start'] = ktime
1187 		else:
1188 			self.dmesg[phase]['end'] = ktime
1189 	def dmesgSortVal(self, phase):
1190 		return self.dmesg[phase]['order']
1191 	def sortedPhases(self):
1192 		return sorted(self.dmesg, key=self.dmesgSortVal)
1193 	def sortedDevices(self, phase):
1194 		list = self.dmesg[phase]['list']
1195 		slist = []
1196 		tmp = dict()
1197 		for devname in list:
1198 			dev = list[devname]
1199 			if dev['length'] == 0:
1200 				continue
1201 			tmp[dev['start']] = devname
1202 		for t in sorted(tmp):
1203 			slist.append(tmp[t])
1204 		return slist
1205 	def fixupInitcalls(self, phase):
1206 		# if any calls never returned, clip them at system resume end
1207 		phaselist = self.dmesg[phase]['list']
1208 		for devname in phaselist:
1209 			dev = phaselist[devname]
1210 			if(dev['end'] < 0):
1211 				for p in self.phases:
1212 					if self.dmesg[p]['end'] > dev['start']:
1213 						dev['end'] = self.dmesg[p]['end']
1214 						break
1215 				sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1216 	def deviceFilter(self, devicefilter):
1217 		for phase in self.phases:
1218 			list = self.dmesg[phase]['list']
1219 			rmlist = []
1220 			for name in list:
1221 				keep = False
1222 				for filter in devicefilter:
1223 					if filter in name or \
1224 						('drv' in list[name] and filter in list[name]['drv']):
1225 						keep = True
1226 				if not keep:
1227 					rmlist.append(name)
1228 			for name in rmlist:
1229 				del list[name]
1230 	def fixupInitcallsThatDidntReturn(self):
1231 		# if any calls never returned, clip them at system resume end
1232 		for phase in self.phases:
1233 			self.fixupInitcalls(phase)
1234 	def phaseOverlap(self, phases):
1235 		rmgroups = []
1236 		newgroup = []
1237 		for group in self.devicegroups:
1238 			for phase in phases:
1239 				if phase not in group:
1240 					continue
1241 				for p in group:
1242 					if p not in newgroup:
1243 						newgroup.append(p)
1244 				if group not in rmgroups:
1245 					rmgroups.append(group)
1246 		for group in rmgroups:
1247 			self.devicegroups.remove(group)
1248 		self.devicegroups.append(newgroup)
1249 	def newActionGlobal(self, name, start, end, pid=-1, color=''):
1250 		# which phase is this device callback or action in
1251 		targetphase = 'none'
1252 		htmlclass = ''
1253 		overlap = 0.0
1254 		phases = []
1255 		for phase in self.phases:
1256 			pstart = self.dmesg[phase]['start']
1257 			pend = self.dmesg[phase]['end']
1258 			# see if the action overlaps this phase
1259 			o = max(0, min(end, pend) - max(start, pstart))
1260 			if o > 0:
1261 				phases.append(phase)
1262 			# set the target phase to the one that overlaps most
1263 			if o > overlap:
1264 				if overlap > 0 and phase == 'post_resume':
1265 					continue
1266 				targetphase = phase
1267 				overlap = o
1268 		# if no target phase was found, pin it to the edge
1269 		if targetphase == 'none':
1270 			p0start = self.dmesg[self.phases[0]]['start']
1271 			if start <= p0start:
1272 				targetphase = self.phases[0]
1273 			else:
1274 				targetphase = self.phases[-1]
1275 		if pid == -2:
1276 			htmlclass = ' bg'
1277 		elif pid == -3:
1278 			htmlclass = ' ps'
1279 		if len(phases) > 1:
1280 			htmlclass = ' bg'
1281 			self.phaseOverlap(phases)
1282 		if targetphase in self.phases:
1283 			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1284 			return (targetphase, newname)
1285 		return False
1286 	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1287 		# new device callback for a specific phase
1288 		self.html_device_id += 1
1289 		devid = '%s%d' % (self.idstr, self.html_device_id)
1290 		list = self.dmesg[phase]['list']
1291 		length = -1.0
1292 		if(start >= 0 and end >= 0):
1293 			length = end - start
1294 		if pid == -2:
1295 			i = 2
1296 			origname = name
1297 			while(name in list):
1298 				name = '%s[%d]' % (origname, i)
1299 				i += 1
1300 		list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1301 			'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1302 		if htmlclass:
1303 			list[name]['htmlclass'] = htmlclass
1304 		if color:
1305 			list[name]['color'] = color
1306 		return name
1307 	def deviceChildren(self, devname, phase):
1308 		devlist = []
1309 		list = self.dmesg[phase]['list']
1310 		for child in list:
1311 			if(list[child]['par'] == devname):
1312 				devlist.append(child)
1313 		return devlist
1314 	def printDetails(self):
1315 		sysvals.vprint('Timeline Details:')
1316 		sysvals.vprint('          test start: %f' % self.start)
1317 		sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1318 		for phase in self.phases:
1319 			dc = len(self.dmesg[phase]['list'])
1320 			sysvals.vprint('    %16s: %f - %f (%d devices)' % (phase, \
1321 				self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
1322 		sysvals.vprint('   kernel resume end: %f' % self.tKernRes)
1323 		sysvals.vprint('            test end: %f' % self.end)
1324 	def deviceChildrenAllPhases(self, devname):
1325 		devlist = []
1326 		for phase in self.phases:
1327 			list = self.deviceChildren(devname, phase)
1328 			for dev in list:
1329 				if dev not in devlist:
1330 					devlist.append(dev)
1331 		return devlist
1332 	def masterTopology(self, name, list, depth):
1333 		node = DeviceNode(name, depth)
1334 		for cname in list:
1335 			# avoid recursions
1336 			if name == cname:
1337 				continue
1338 			clist = self.deviceChildrenAllPhases(cname)
1339 			cnode = self.masterTopology(cname, clist, depth+1)
1340 			node.children.append(cnode)
1341 		return node
1342 	def printTopology(self, node):
1343 		html = ''
1344 		if node.name:
1345 			info = ''
1346 			drv = ''
1347 			for phase in self.phases:
1348 				list = self.dmesg[phase]['list']
1349 				if node.name in list:
1350 					s = list[node.name]['start']
1351 					e = list[node.name]['end']
1352 					if list[node.name]['drv']:
1353 						drv = ' {'+list[node.name]['drv']+'}'
1354 					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1355 			html += '<li><b>'+node.name+drv+'</b>'
1356 			if info:
1357 				html += '<ul>'+info+'</ul>'
1358 			html += '</li>'
1359 		if len(node.children) > 0:
1360 			html += '<ul>'
1361 			for cnode in node.children:
1362 				html += self.printTopology(cnode)
1363 			html += '</ul>'
1364 		return html
1365 	def rootDeviceList(self):
1366 		# list of devices graphed
1367 		real = []
1368 		for phase in self.dmesg:
1369 			list = self.dmesg[phase]['list']
1370 			for dev in list:
1371 				if list[dev]['pid'] >= 0 and dev not in real:
1372 					real.append(dev)
1373 		# list of top-most root devices
1374 		rootlist = []
1375 		for phase in self.dmesg:
1376 			list = self.dmesg[phase]['list']
1377 			for dev in list:
1378 				pdev = list[dev]['par']
1379 				pid = list[dev]['pid']
1380 				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1381 					continue
1382 				if pdev and pdev not in real and pdev not in rootlist:
1383 					rootlist.append(pdev)
1384 		return rootlist
1385 	def deviceTopology(self):
1386 		rootlist = self.rootDeviceList()
1387 		master = self.masterTopology('', rootlist, 0)
1388 		return self.printTopology(master)
1389 	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1390 		# only select devices that will actually show up in html
1391 		self.tdevlist = dict()
1392 		for phase in self.dmesg:
1393 			devlist = []
1394 			list = self.dmesg[phase]['list']
1395 			for dev in list:
1396 				length = (list[dev]['end'] - list[dev]['start']) * 1000
1397 				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1398 				if width != '0.000000' and length >= mindevlen:
1399 					devlist.append(dev)
1400 			self.tdevlist[phase] = devlist
1401 	def addHorizontalDivider(self, devname, devend):
1402 		phase = 'suspend_prepare'
1403 		self.newAction(phase, devname, -2, '', \
1404 			self.start, devend, '', ' sec', '')
1405 		if phase not in self.tdevlist:
1406 			self.tdevlist[phase] = []
1407 		self.tdevlist[phase].append(devname)
1408 		d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1409 		return d
1410 	def addProcessUsageEvent(self, name, times):
1411 		# get the start and end times for this process
1412 		maxC = 0
1413 		tlast = 0
1414 		start = -1
1415 		end = -1
1416 		for t in sorted(times):
1417 			if tlast == 0:
1418 				tlast = t
1419 				continue
1420 			if name in self.pstl[t]:
1421 				if start == -1 or tlast < start:
1422 					start = tlast
1423 				if end == -1 or t > end:
1424 					end = t
1425 			tlast = t
1426 		if start == -1 or end == -1:
1427 			return 0
1428 		# add a new action for this process and get the object
1429 		out = self.newActionGlobal(name, start, end, -3)
1430 		if not out:
1431 			return 0
1432 		phase, devname = out
1433 		dev = self.dmesg[phase]['list'][devname]
1434 		# get the cpu exec data
1435 		tlast = 0
1436 		clast = 0
1437 		cpuexec = dict()
1438 		for t in sorted(times):
1439 			if tlast == 0 or t <= start or t > end:
1440 				tlast = t
1441 				continue
1442 			list = self.pstl[t]
1443 			c = 0
1444 			if name in list:
1445 				c = list[name]
1446 			if c > maxC:
1447 				maxC = c
1448 			if c != clast:
1449 				key = (tlast, t)
1450 				cpuexec[key] = c
1451 				tlast = t
1452 				clast = c
1453 		dev['cpuexec'] = cpuexec
1454 		return maxC
1455 	def createProcessUsageEvents(self):
1456 		# get an array of process names
1457 		proclist = []
1458 		for t in self.pstl:
1459 			pslist = self.pstl[t]
1460 			for ps in pslist:
1461 				if ps not in proclist:
1462 					proclist.append(ps)
1463 		# get a list of data points for suspend and resume
1464 		tsus = []
1465 		tres = []
1466 		for t in sorted(self.pstl):
1467 			if t < self.tSuspended:
1468 				tsus.append(t)
1469 			else:
1470 				tres.append(t)
1471 		# process the events for suspend and resume
1472 		if len(proclist) > 0:
1473 			sysvals.vprint('Process Execution:')
1474 		for ps in proclist:
1475 			c = self.addProcessUsageEvent(ps, tsus)
1476 			if c > 0:
1477 				sysvals.vprint('%25s (sus): %d' % (ps, c))
1478 			c = self.addProcessUsageEvent(ps, tres)
1479 			if c > 0:
1480 				sysvals.vprint('%25s (res): %d' % (ps, c))
1481 	def debugPrint(self):
1482 		for p in self.phases:
1483 			list = self.dmesg[p]['list']
1484 			for devname in list:
1485 				dev = list[devname]
1486 				if 'ftrace' in dev:
1487 					dev['ftrace'].debugPrint(' [%s]' % devname)
1488 
1489 # Class: DevFunction
1490 # Description:
1491 #	 A container for kprobe function data we want in the dev timeline
1492 class DevFunction:
1493 	row = 0
1494 	count = 1
1495 	def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
1496 		self.name = name
1497 		self.args = args
1498 		self.caller = caller
1499 		self.ret = ret
1500 		self.time = start
1501 		self.length = end - start
1502 		self.end = end
1503 		self.ubiquitous = u
1504 		self.proc = proc
1505 		self.pid = pid
1506 		self.color = color
1507 	def title(self):
1508 		cnt = ''
1509 		if self.count > 1:
1510 			cnt = '(x%d)' % self.count
1511 		l = '%0.3fms' % (self.length * 1000)
1512 		if self.ubiquitous:
1513 			title = '%s(%s)%s <- %s, %s(%s)' % \
1514 				(self.name, self.args, cnt, self.caller, self.ret, l)
1515 		else:
1516 			title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
1517 		return title.replace('"', '')
1518 	def text(self):
1519 		if self.count > 1:
1520 			text = '%s(x%d)' % (self.name, self.count)
1521 		else:
1522 			text = self.name
1523 		return text
1524 	def repeat(self, tgt):
1525 		# is the tgt call just a repeat of this call (e.g. are we in a loop)
1526 		dt = self.time - tgt.end
1527 		# only combine calls if -all- attributes are identical
1528 		if tgt.caller == self.caller and \
1529 			tgt.name == self.name and tgt.args == self.args and \
1530 			tgt.proc == self.proc and tgt.pid == self.pid and \
1531 			tgt.ret == self.ret and dt >= 0 and \
1532 			dt <= sysvals.callloopmaxgap and \
1533 			self.length < sysvals.callloopmaxlen:
1534 			return True
1535 		return False
1536 
1537 # Class: FTraceLine
1538 # Description:
1539 #	 A container for a single line of ftrace data. There are six basic types:
1540 #		 callgraph line:
1541 #			  call: "  dpm_run_callback() {"
1542 #			return: "  }"
1543 #			  leaf: " dpm_run_callback();"
1544 #		 trace event:
1545 #			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
1546 #			 suspend_resume: phase or custom exec block data
1547 #			 device_pm_callback: device callback info
1548 class FTraceLine:
1549 	time = 0.0
1550 	length = 0.0
1551 	fcall = False
1552 	freturn = False
1553 	fevent = False
1554 	fkprobe = False
1555 	depth = 0
1556 	name = ''
1557 	type = ''
1558 	def __init__(self, t, m='', d=''):
1559 		self.time = float(t)
1560 		if not m and not d:
1561 			return
1562 		# is this a trace event
1563 		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1564 			if(d == 'traceevent'):
1565 				# nop format trace event
1566 				msg = m
1567 			else:
1568 				# function_graph format trace event
1569 				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1570 				msg = em.group('msg')
1571 
1572 			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1573 			if(emm):
1574 				self.name = emm.group('msg')
1575 				self.type = emm.group('call')
1576 			else:
1577 				self.name = msg
1578 			km = re.match('^(?P<n>.*)_cal$', self.type)
1579 			if km:
1580 				self.fcall = True
1581 				self.fkprobe = True
1582 				self.type = km.group('n')
1583 				return
1584 			km = re.match('^(?P<n>.*)_ret$', self.type)
1585 			if km:
1586 				self.freturn = True
1587 				self.fkprobe = True
1588 				self.type = km.group('n')
1589 				return
1590 			self.fevent = True
1591 			return
1592 		# convert the duration to seconds
1593 		if(d):
1594 			self.length = float(d)/1000000
1595 		# the indentation determines the depth
1596 		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
1597 		if(not match):
1598 			return
1599 		self.depth = self.getDepth(match.group('d'))
1600 		m = match.group('o')
1601 		# function return
1602 		if(m[0] == '}'):
1603 			self.freturn = True
1604 			if(len(m) > 1):
1605 				# includes comment with function name
1606 				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
1607 				if(match):
1608 					self.name = match.group('n').strip()
1609 		# function call
1610 		else:
1611 			self.fcall = True
1612 			# function call with children
1613 			if(m[-1] == '{'):
1614 				match = re.match('^(?P<n>.*) *\(.*', m)
1615 				if(match):
1616 					self.name = match.group('n').strip()
1617 			# function call with no children (leaf)
1618 			elif(m[-1] == ';'):
1619 				self.freturn = True
1620 				match = re.match('^(?P<n>.*) *\(.*', m)
1621 				if(match):
1622 					self.name = match.group('n').strip()
1623 			# something else (possibly a trace marker)
1624 			else:
1625 				self.name = m
1626 	def isCall(self):
1627 		return self.fcall and not self.freturn
1628 	def isReturn(self):
1629 		return self.freturn and not self.fcall
1630 	def isLeaf(self):
1631 		return self.fcall and self.freturn
1632 	def getDepth(self, str):
1633 		return len(str)/2
1634 	def debugPrint(self, info=''):
1635 		if self.isLeaf():
1636 			print(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
1637 				self.depth, self.name, self.length*1000000, info))
1638 		elif self.freturn:
1639 			print(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
1640 				self.depth, self.name, self.length*1000000, info))
1641 		else:
1642 			print(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
1643 				self.depth, self.name, self.length*1000000, info))
1644 	def startMarker(self):
1645 		# Is this the starting line of a suspend?
1646 		if not self.fevent:
1647 			return False
1648 		if sysvals.usetracemarkers:
1649 			if(self.name == 'SUSPEND START'):
1650 				return True
1651 			return False
1652 		else:
1653 			if(self.type == 'suspend_resume' and
1654 				re.match('suspend_enter\[.*\] begin', self.name)):
1655 				return True
1656 			return False
1657 	def endMarker(self):
1658 		# Is this the ending line of a resume?
1659 		if not self.fevent:
1660 			return False
1661 		if sysvals.usetracemarkers:
1662 			if(self.name == 'RESUME COMPLETE'):
1663 				return True
1664 			return False
1665 		else:
1666 			if(self.type == 'suspend_resume' and
1667 				re.match('thaw_processes\[.*\] end', self.name)):
1668 				return True
1669 			return False
1670 
1671 # Class: FTraceCallGraph
1672 # Description:
1673 #	 A container for the ftrace callgraph of a single recursive function.
1674 #	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
1675 #	 Each instance is tied to a single device in a single phase, and is
1676 #	 comprised of an ordered list of FTraceLine objects
1677 class FTraceCallGraph:
1678 	id = ''
1679 	start = -1.0
1680 	end = -1.0
1681 	list = []
1682 	invalid = False
1683 	depth = 0
1684 	pid = 0
1685 	name = ''
1686 	partial = False
1687 	vfname = 'missing_function_name'
1688 	ignore = False
1689 	sv = 0
1690 	def __init__(self, pid, sv):
1691 		self.start = -1.0
1692 		self.end = -1.0
1693 		self.list = []
1694 		self.depth = 0
1695 		self.pid = pid
1696 		self.sv = sv
1697 	def addLine(self, line):
1698 		# if this is already invalid, just leave
1699 		if(self.invalid):
1700 			if(line.depth == 0 and line.freturn):
1701 				return 1
1702 			return 0
1703 		# invalidate on bad depth
1704 		if(self.depth < 0):
1705 			self.invalidate(line)
1706 			return 0
1707 		# ignore data til we return to the current depth
1708 		if self.ignore:
1709 			if line.depth > self.depth:
1710 				return 0
1711 			else:
1712 				self.list[-1].freturn = True
1713 				self.list[-1].length = line.time - self.list[-1].time
1714 				self.ignore = False
1715 				# if this is a return at self.depth, no more work is needed
1716 				if line.depth == self.depth and line.isReturn():
1717 					if line.depth == 0:
1718 						self.end = line.time
1719 						return 1
1720 					return 0
1721 		# compare current depth with this lines pre-call depth
1722 		prelinedep = line.depth
1723 		if line.isReturn():
1724 			prelinedep += 1
1725 		last = 0
1726 		lasttime = line.time
1727 		if len(self.list) > 0:
1728 			last = self.list[-1]
1729 			lasttime = last.time
1730 			if last.isLeaf():
1731 				lasttime += last.length
1732 		# handle low misalignments by inserting returns
1733 		mismatch = prelinedep - self.depth
1734 		warning = self.sv.verbose and abs(mismatch) > 1
1735 		info = []
1736 		if mismatch < 0:
1737 			idx = 0
1738 			# add return calls to get the depth down
1739 			while prelinedep < self.depth:
1740 				self.depth -= 1
1741 				if idx == 0 and last and last.isCall():
1742 					# special case, turn last call into a leaf
1743 					last.depth = self.depth
1744 					last.freturn = True
1745 					last.length = line.time - last.time
1746 					if warning:
1747 						info.append(('[make leaf]', last))
1748 				else:
1749 					vline = FTraceLine(lasttime)
1750 					vline.depth = self.depth
1751 					vline.name = self.vfname
1752 					vline.freturn = True
1753 					self.list.append(vline)
1754 					if warning:
1755 						if idx == 0:
1756 							info.append(('', last))
1757 						info.append(('[add return]', vline))
1758 				idx += 1
1759 			if warning:
1760 				info.append(('', line))
1761 		# handle high misalignments by inserting calls
1762 		elif mismatch > 0:
1763 			idx = 0
1764 			if warning:
1765 				info.append(('', last))
1766 			# add calls to get the depth up
1767 			while prelinedep > self.depth:
1768 				if idx == 0 and line.isReturn():
1769 					# special case, turn this return into a leaf
1770 					line.fcall = True
1771 					prelinedep -= 1
1772 					if warning:
1773 						info.append(('[make leaf]', line))
1774 				else:
1775 					vline = FTraceLine(lasttime)
1776 					vline.depth = self.depth
1777 					vline.name = self.vfname
1778 					vline.fcall = True
1779 					self.list.append(vline)
1780 					self.depth += 1
1781 					if not last:
1782 						self.start = vline.time
1783 					if warning:
1784 						info.append(('[add call]', vline))
1785 				idx += 1
1786 			if warning and ('[make leaf]', line) not in info:
1787 				info.append(('', line))
1788 		if warning:
1789 			print 'WARNING: ftrace data missing, corrections made:'
1790 			for i in info:
1791 				t, obj = i
1792 				if obj:
1793 					obj.debugPrint(t)
1794 		# process the call and set the new depth
1795 		skipadd = False
1796 		md = self.sv.max_graph_depth
1797 		if line.isCall():
1798 			# ignore blacklisted/overdepth funcs
1799 			if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
1800 				self.ignore = True
1801 			else:
1802 				self.depth += 1
1803 		elif line.isReturn():
1804 			self.depth -= 1
1805 			# remove blacklisted/overdepth/empty funcs that slipped through
1806 			if (last and last.isCall() and last.depth == line.depth) or \
1807 				(md and last and last.depth >= md) or \
1808 				(line.name in self.sv.cgblacklist):
1809 				while len(self.list) > 0 and self.list[-1].depth > line.depth:
1810 					self.list.pop(-1)
1811 				if len(self.list) == 0:
1812 					self.invalid = True
1813 					return 1
1814 				self.list[-1].freturn = True
1815 				self.list[-1].length = line.time - self.list[-1].time
1816 				self.list[-1].name = line.name
1817 				skipadd = True
1818 		if len(self.list) < 1:
1819 			self.start = line.time
1820 		# check for a mismatch that returned all the way to callgraph end
1821 		res = 1
1822 		if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
1823 			line = self.list[-1]
1824 			skipadd = True
1825 			res = -1
1826 		if not skipadd:
1827 			self.list.append(line)
1828 		if(line.depth == 0 and line.freturn):
1829 			if(self.start < 0):
1830 				self.start = line.time
1831 			self.end = line.time
1832 			if line.fcall:
1833 				self.end += line.length
1834 			if self.list[0].name == self.vfname:
1835 				self.invalid = True
1836 			if res == -1:
1837 				self.partial = True
1838 			return res
1839 		return 0
1840 	def invalidate(self, line):
1841 		if(len(self.list) > 0):
1842 			first = self.list[0]
1843 			self.list = []
1844 			self.list.append(first)
1845 		self.invalid = True
1846 		id = 'task %s' % (self.pid)
1847 		window = '(%f - %f)' % (self.start, line.time)
1848 		if(self.depth < 0):
1849 			print('Data misalignment for '+id+\
1850 				' (buffer overflow), ignoring this callback')
1851 		else:
1852 			print('Too much data for '+id+\
1853 				' '+window+', ignoring this callback')
1854 	def slice(self, dev):
1855 		minicg = FTraceCallGraph(dev['pid'], self.sv)
1856 		minicg.name = self.name
1857 		mydepth = -1
1858 		good = False
1859 		for l in self.list:
1860 			if(l.time < dev['start'] or l.time > dev['end']):
1861 				continue
1862 			if mydepth < 0:
1863 				if l.name == 'mutex_lock' and l.freturn:
1864 					mydepth = l.depth
1865 				continue
1866 			elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
1867 				good = True
1868 				break
1869 			l.depth -= mydepth
1870 			minicg.addLine(l)
1871 		if not good or len(minicg.list) < 1:
1872 			return 0
1873 		return minicg
1874 	def repair(self, enddepth):
1875 		# bring the depth back to 0 with additional returns
1876 		fixed = False
1877 		last = self.list[-1]
1878 		for i in reversed(range(enddepth)):
1879 			t = FTraceLine(last.time)
1880 			t.depth = i
1881 			t.freturn = True
1882 			fixed = self.addLine(t)
1883 			if fixed != 0:
1884 				self.end = last.time
1885 				return True
1886 		return False
1887 	def postProcess(self):
1888 		if len(self.list) > 0:
1889 			self.name = self.list[0].name
1890 		stack = dict()
1891 		cnt = 0
1892 		last = 0
1893 		for l in self.list:
1894 			# ftrace bug: reported duration is not reliable
1895 			# check each leaf and clip it at max possible length
1896 			if last and last.isLeaf():
1897 				if last.length > l.time - last.time:
1898 					last.length = l.time - last.time
1899 			if l.isCall():
1900 				stack[l.depth] = l
1901 				cnt += 1
1902 			elif l.isReturn():
1903 				if(l.depth not in stack):
1904 					if self.sv.verbose:
1905 						print 'Post Process Error: Depth missing'
1906 						l.debugPrint()
1907 					return False
1908 				# calculate call length from call/return lines
1909 				cl = stack[l.depth]
1910 				cl.length = l.time - cl.time
1911 				if cl.name == self.vfname:
1912 					cl.name = l.name
1913 				stack.pop(l.depth)
1914 				l.length = 0
1915 				cnt -= 1
1916 			last = l
1917 		if(cnt == 0):
1918 			# trace caught the whole call tree
1919 			return True
1920 		elif(cnt < 0):
1921 			if self.sv.verbose:
1922 				print 'Post Process Error: Depth is less than 0'
1923 			return False
1924 		# trace ended before call tree finished
1925 		return self.repair(cnt)
1926 	def deviceMatch(self, pid, data):
1927 		found = ''
1928 		# add the callgraph data to the device hierarchy
1929 		borderphase = {
1930 			'dpm_prepare': 'suspend_prepare',
1931 			'dpm_complete': 'resume_complete'
1932 		}
1933 		if(self.name in borderphase):
1934 			p = borderphase[self.name]
1935 			list = data.dmesg[p]['list']
1936 			for devname in list:
1937 				dev = list[devname]
1938 				if(pid == dev['pid'] and
1939 					self.start <= dev['start'] and
1940 					self.end >= dev['end']):
1941 					cg = self.slice(dev)
1942 					if cg:
1943 						dev['ftrace'] = cg
1944 					found = devname
1945 			return found
1946 		for p in data.phases:
1947 			if(data.dmesg[p]['start'] <= self.start and
1948 				self.start <= data.dmesg[p]['end']):
1949 				list = data.dmesg[p]['list']
1950 				for devname in list:
1951 					dev = list[devname]
1952 					if(pid == dev['pid'] and
1953 						self.start <= dev['start'] and
1954 						self.end >= dev['end']):
1955 						dev['ftrace'] = self
1956 						found = devname
1957 						break
1958 				break
1959 		return found
1960 	def newActionFromFunction(self, data):
1961 		name = self.name
1962 		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
1963 			return
1964 		fs = self.start
1965 		fe = self.end
1966 		if fs < data.start or fe > data.end:
1967 			return
1968 		phase = ''
1969 		for p in data.phases:
1970 			if(data.dmesg[p]['start'] <= self.start and
1971 				self.start < data.dmesg[p]['end']):
1972 				phase = p
1973 				break
1974 		if not phase:
1975 			return
1976 		out = data.newActionGlobal(name, fs, fe, -2)
1977 		if out:
1978 			phase, myname = out
1979 			data.dmesg[phase]['list'][myname]['ftrace'] = self
1980 	def debugPrint(self, info=''):
1981 		print('%s pid=%d [%f - %f] %.3f us') % \
1982 			(self.name, self.pid, self.start, self.end,
1983 			(self.end - self.start)*1000000)
1984 		for l in self.list:
1985 			if l.isLeaf():
1986 				print('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
1987 					l.depth, l.name, l.length*1000000, info))
1988 			elif l.freturn:
1989 				print('%f (%02d): %s} (%.3f us)%s' % (l.time, \
1990 					l.depth, l.name, l.length*1000000, info))
1991 			else:
1992 				print('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
1993 					l.depth, l.name, l.length*1000000, info))
1994 		print(' ')
1995 
1996 class DevItem:
1997 	def __init__(self, test, phase, dev):
1998 		self.test = test
1999 		self.phase = phase
2000 		self.dev = dev
2001 	def isa(self, cls):
2002 		if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2003 			return True
2004 		return False
2005 
2006 # Class: Timeline
2007 # Description:
2008 #	 A container for a device timeline which calculates
2009 #	 all the html properties to display it correctly
2010 class Timeline:
2011 	html = ''
2012 	height = 0	# total timeline height
2013 	scaleH = 20	# timescale (top) row height
2014 	rowH = 30	# device row height
2015 	bodyH = 0	# body height
2016 	rows = 0	# total timeline rows
2017 	rowlines = dict()
2018 	rowheight = dict()
2019 	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2020 	html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2021 	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2022 	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2023 	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2024 	def __init__(self, rowheight, scaleheight):
2025 		self.rowH = rowheight
2026 		self.scaleH = scaleheight
2027 		self.html = ''
2028 	def createHeader(self, sv, stamp):
2029 		if(not stamp['time']):
2030 			return
2031 		self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \
2032 			% (sv.title, sv.version)
2033 		if sv.logmsg and sv.testlog:
2034 			self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2035 		if sv.dmesglog:
2036 			self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2037 		if sv.ftracelog:
2038 			self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2039 		headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2040 		self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2041 			stamp['mode'], stamp['time'])
2042 		if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2043 			stamp['man'] and stamp['plat'] and stamp['cpu']:
2044 			headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2045 			self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2046 
2047 	# Function: getDeviceRows
2048 	# Description:
2049 	#    determine how may rows the device funcs will take
2050 	# Arguments:
2051 	#	 rawlist: the list of devices/actions for a single phase
2052 	# Output:
2053 	#	 The total number of rows needed to display this phase of the timeline
2054 	def getDeviceRows(self, rawlist):
2055 		# clear all rows and set them to undefined
2056 		sortdict = dict()
2057 		for item in rawlist:
2058 			item.row = -1
2059 			sortdict[item] = item.length
2060 		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2061 		remaining = len(sortlist)
2062 		rowdata = dict()
2063 		row = 1
2064 		# try to pack each row with as many ranges as possible
2065 		while(remaining > 0):
2066 			if(row not in rowdata):
2067 				rowdata[row] = []
2068 			for i in sortlist:
2069 				if(i.row >= 0):
2070 					continue
2071 				s = i.time
2072 				e = i.time + i.length
2073 				valid = True
2074 				for ritem in rowdata[row]:
2075 					rs = ritem.time
2076 					re = ritem.time + ritem.length
2077 					if(not (((s <= rs) and (e <= rs)) or
2078 						((s >= re) and (e >= re)))):
2079 						valid = False
2080 						break
2081 				if(valid):
2082 					rowdata[row].append(i)
2083 					i.row = row
2084 					remaining -= 1
2085 			row += 1
2086 		return row
2087 	# Function: getPhaseRows
2088 	# Description:
2089 	#	 Organize the timeline entries into the smallest
2090 	#	 number of rows possible, with no entry overlapping
2091 	# Arguments:
2092 	#	 devlist: the list of devices/actions in a group of contiguous phases
2093 	# Output:
2094 	#	 The total number of rows needed to display this phase of the timeline
2095 	def getPhaseRows(self, devlist, row=0, sortby='length'):
2096 		# clear all rows and set them to undefined
2097 		remaining = len(devlist)
2098 		rowdata = dict()
2099 		sortdict = dict()
2100 		myphases = []
2101 		# initialize all device rows to -1 and calculate devrows
2102 		for item in devlist:
2103 			dev = item.dev
2104 			tp = (item.test, item.phase)
2105 			if tp not in myphases:
2106 				myphases.append(tp)
2107 			dev['row'] = -1
2108 			if sortby == 'start':
2109 				# sort by start 1st, then length 2nd
2110 				sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2111 			else:
2112 				# sort by length 1st, then name 2nd
2113 				sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2114 			if 'src' in dev:
2115 				dev['devrows'] = self.getDeviceRows(dev['src'])
2116 		# sort the devlist by length so that large items graph on top
2117 		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2118 		orderedlist = []
2119 		for item in sortlist:
2120 			if item.dev['pid'] == -2:
2121 				orderedlist.append(item)
2122 		for item in sortlist:
2123 			if item not in orderedlist:
2124 				orderedlist.append(item)
2125 		# try to pack each row with as many devices as possible
2126 		while(remaining > 0):
2127 			rowheight = 1
2128 			if(row not in rowdata):
2129 				rowdata[row] = []
2130 			for item in orderedlist:
2131 				dev = item.dev
2132 				if(dev['row'] < 0):
2133 					s = dev['start']
2134 					e = dev['end']
2135 					valid = True
2136 					for ritem in rowdata[row]:
2137 						rs = ritem.dev['start']
2138 						re = ritem.dev['end']
2139 						if(not (((s <= rs) and (e <= rs)) or
2140 							((s >= re) and (e >= re)))):
2141 							valid = False
2142 							break
2143 					if(valid):
2144 						rowdata[row].append(item)
2145 						dev['row'] = row
2146 						remaining -= 1
2147 						if 'devrows' in dev and dev['devrows'] > rowheight:
2148 							rowheight = dev['devrows']
2149 			for t, p in myphases:
2150 				if t not in self.rowlines or t not in self.rowheight:
2151 					self.rowlines[t] = dict()
2152 					self.rowheight[t] = dict()
2153 				if p not in self.rowlines[t] or p not in self.rowheight[t]:
2154 					self.rowlines[t][p] = dict()
2155 					self.rowheight[t][p] = dict()
2156 				rh = self.rowH
2157 				# section headers should use a different row height
2158 				if len(rowdata[row]) == 1 and \
2159 					'htmlclass' in rowdata[row][0].dev and \
2160 					'sec' in rowdata[row][0].dev['htmlclass']:
2161 					rh = 15
2162 				self.rowlines[t][p][row] = rowheight
2163 				self.rowheight[t][p][row] = rowheight * rh
2164 			row += 1
2165 		if(row > self.rows):
2166 			self.rows = int(row)
2167 		return row
2168 	def phaseRowHeight(self, test, phase, row):
2169 		return self.rowheight[test][phase][row]
2170 	def phaseRowTop(self, test, phase, row):
2171 		top = 0
2172 		for i in sorted(self.rowheight[test][phase]):
2173 			if i >= row:
2174 				break
2175 			top += self.rowheight[test][phase][i]
2176 		return top
2177 	def calcTotalRows(self):
2178 		# Calculate the heights and offsets for the header and rows
2179 		maxrows = 0
2180 		standardphases = []
2181 		for t in self.rowlines:
2182 			for p in self.rowlines[t]:
2183 				total = 0
2184 				for i in sorted(self.rowlines[t][p]):
2185 					total += self.rowlines[t][p][i]
2186 				if total > maxrows:
2187 					maxrows = total
2188 				if total == len(self.rowlines[t][p]):
2189 					standardphases.append((t, p))
2190 		self.height = self.scaleH + (maxrows*self.rowH)
2191 		self.bodyH = self.height - self.scaleH
2192 		# if there is 1 line per row, draw them the standard way
2193 		for t, p in standardphases:
2194 			for i in sorted(self.rowheight[t][p]):
2195 				self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p])
2196 	def createZoomBox(self, mode='command', testcount=1):
2197 		# Create bounding box, add buttons
2198 		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2199 		html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2200 		html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2201 		html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2202 		if mode != 'command':
2203 			if testcount > 1:
2204 				self.html += html_devlist2
2205 				self.html += html_devlist1.format('1')
2206 			else:
2207 				self.html += html_devlist1.format('')
2208 		self.html += html_zoombox
2209 		self.html += html_timeline.format('dmesg', self.height)
2210 	# Function: createTimeScale
2211 	# Description:
2212 	#	 Create the timescale for a timeline block
2213 	# Arguments:
2214 	#	 m0: start time (mode begin)
2215 	#	 mMax: end time (mode end)
2216 	#	 tTotal: total timeline time
2217 	#	 mode: suspend or resume
2218 	# Output:
2219 	#	 The html code needed to display the time scale
2220 	def createTimeScale(self, m0, mMax, tTotal, mode):
2221 		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2222 		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2223 		output = '<div class="timescale">\n'
2224 		# set scale for timeline
2225 		mTotal = mMax - m0
2226 		tS = 0.1
2227 		if(tTotal <= 0):
2228 			return output+'</div>\n'
2229 		if(tTotal > 4):
2230 			tS = 1
2231 		divTotal = int(mTotal/tS) + 1
2232 		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2233 		for i in range(divTotal):
2234 			htmlline = ''
2235 			if(mode == 'suspend'):
2236 				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2237 				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2238 				if(i == divTotal - 1):
2239 					val = mode
2240 				htmlline = timescale.format(pos, val)
2241 			else:
2242 				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2243 				val = '%0.fms' % (float(i)*tS*1000)
2244 				htmlline = timescale.format(pos, val)
2245 				if(i == 0):
2246 					htmlline = rline.format(mode)
2247 			output += htmlline
2248 		self.html += output+'</div>\n'
2249 
2250 # Class: TestProps
2251 # Description:
2252 #	 A list of values describing the properties of these test runs
2253 class TestProps:
2254 	stamp = ''
2255 	sysinfo = ''
2256 	cmdline = ''
2257 	kparams = ''
2258 	S0i3 = False
2259 	fwdata = []
2260 	stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2261 				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2262 				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2263 	sysinfofmt = '^# sysinfo .*'
2264 	cmdlinefmt = '^# command \| (?P<cmd>.*)'
2265 	kparamsfmt = '^# kparams \| (?P<kp>.*)'
2266 	ftrace_line_fmt_fg = \
2267 		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2268 		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2269 		'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
2270 	ftrace_line_fmt_nop = \
2271 		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2272 		'(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
2273 		'(?P<msg>.*)'
2274 	ftrace_line_fmt = ftrace_line_fmt_nop
2275 	cgformat = False
2276 	data = 0
2277 	ktemp = dict()
2278 	def __init__(self):
2279 		self.ktemp = dict()
2280 	def setTracerType(self, tracer):
2281 		if(tracer == 'function_graph'):
2282 			self.cgformat = True
2283 			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2284 		elif(tracer == 'nop'):
2285 			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2286 		else:
2287 			doError('Invalid tracer format: [%s]' % tracer)
2288 	def parseStamp(self, data, sv):
2289 		m = re.match(self.stampfmt, self.stamp)
2290 		data.stamp = {'time': '', 'host': '', 'mode': ''}
2291 		dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2292 			int(m.group('d')), int(m.group('H')), int(m.group('M')),
2293 			int(m.group('S')))
2294 		data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2295 		data.stamp['host'] = m.group('host')
2296 		data.stamp['mode'] = m.group('mode')
2297 		data.stamp['kernel'] = m.group('kernel')
2298 		if re.match(self.sysinfofmt, self.sysinfo):
2299 			for f in self.sysinfo.split('|'):
2300 				if '#' in f:
2301 					continue
2302 				tmp = f.strip().split(':', 1)
2303 				key = tmp[0]
2304 				val = tmp[1]
2305 				data.stamp[key] = val
2306 		sv.hostname = data.stamp['host']
2307 		sv.suspendmode = data.stamp['mode']
2308 		if sv.suspendmode == 'command' and sv.ftracefile != '':
2309 			modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2310 			fp = sysvals.openlog(sv.ftracefile, 'r')
2311 			for line in fp:
2312 				m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
2313 				if m and m.group('mode') in ['1', '2', '3', '4']:
2314 					sv.suspendmode = modes[int(m.group('mode'))]
2315 					data.stamp['mode'] = sv.suspendmode
2316 					break
2317 			fp.close()
2318 		m = re.match(self.cmdlinefmt, self.cmdline)
2319 		if m:
2320 			sv.cmdline = m.group('cmd')
2321 		if self.kparams:
2322 			m = re.match(self.kparamsfmt, self.kparams)
2323 			if m:
2324 				sv.kparams = m.group('kp')
2325 		if not sv.stamp:
2326 			sv.stamp = data.stamp
2327 
2328 # Class: TestRun
2329 # Description:
2330 #	 A container for a suspend/resume test run. This is necessary as
2331 #	 there could be more than one, and they need to be separate.
2332 class TestRun:
2333 	ftemp = dict()
2334 	ttemp = dict()
2335 	data = 0
2336 	def __init__(self, dataobj):
2337 		self.data = dataobj
2338 		self.ftemp = dict()
2339 		self.ttemp = dict()
2340 
2341 class ProcessMonitor:
2342 	proclist = dict()
2343 	running = False
2344 	def procstat(self):
2345 		c = ['cat /proc/[1-9]*/stat 2>/dev/null']
2346 		process = Popen(c, shell=True, stdout=PIPE)
2347 		running = dict()
2348 		for line in process.stdout:
2349 			data = line.split()
2350 			pid = data[0]
2351 			name = re.sub('[()]', '', data[1])
2352 			user = int(data[13])
2353 			kern = int(data[14])
2354 			kjiff = ujiff = 0
2355 			if pid not in self.proclist:
2356 				self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
2357 			else:
2358 				val = self.proclist[pid]
2359 				ujiff = user - val['user']
2360 				kjiff = kern - val['kern']
2361 				val['user'] = user
2362 				val['kern'] = kern
2363 			if ujiff > 0 or kjiff > 0:
2364 				running[pid] = ujiff + kjiff
2365 		process.wait()
2366 		out = ''
2367 		for pid in running:
2368 			jiffies = running[pid]
2369 			val = self.proclist[pid]
2370 			if out:
2371 				out += ','
2372 			out += '%s-%s %d' % (val['name'], pid, jiffies)
2373 		return 'ps - '+out
2374 	def processMonitor(self, tid):
2375 		while self.running:
2376 			out = self.procstat()
2377 			if out:
2378 				sysvals.fsetVal(out, 'trace_marker')
2379 	def start(self):
2380 		self.thread = Thread(target=self.processMonitor, args=(0,))
2381 		self.running = True
2382 		self.thread.start()
2383 	def stop(self):
2384 		self.running = False
2385 
2386 # ----------------- FUNCTIONS --------------------
2387 
2388 # Function: doesTraceLogHaveTraceEvents
2389 # Description:
2390 #	 Quickly determine if the ftrace log has all of the trace events,
2391 #	 markers, and/or kprobes required for primary parsing.
2392 def doesTraceLogHaveTraceEvents():
2393 	kpcheck = ['_cal: (', '_cpu_down()']
2394 	techeck = ['suspend_resume']
2395 	tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
2396 	sysvals.usekprobes = False
2397 	fp = sysvals.openlog(sysvals.ftracefile, 'r')
2398 	for line in fp:
2399 		# check for kprobes
2400 		if not sysvals.usekprobes:
2401 			for i in kpcheck:
2402 				if i in line:
2403 					sysvals.usekprobes = True
2404 		# check for all necessary trace events
2405 		check = techeck[:]
2406 		for i in techeck:
2407 			if i in line:
2408 				check.remove(i)
2409 		techeck = check
2410 		# check for all necessary trace markers
2411 		check = tmcheck[:]
2412 		for i in tmcheck:
2413 			if i in line:
2414 				check.remove(i)
2415 		tmcheck = check
2416 	fp.close()
2417 	if len(techeck) == 0:
2418 		sysvals.usetraceevents = True
2419 	else:
2420 		sysvals.usetraceevents = False
2421 	if len(tmcheck) == 0:
2422 		sysvals.usetracemarkers = True
2423 	else:
2424 		sysvals.usetracemarkers = False
2425 
2426 # Function: appendIncompleteTraceLog
2427 # Description:
2428 #	 [deprecated for kernel 3.15 or newer]
2429 #	 Legacy support of ftrace outputs that lack the device_pm_callback
2430 #	 and/or suspend_resume trace events. The primary data should be
2431 #	 taken from dmesg, and this ftrace is used only for callgraph data
2432 #	 or custom actions in the timeline. The data is appended to the Data
2433 #	 objects provided.
2434 # Arguments:
2435 #	 testruns: the array of Data objects obtained from parseKernelLog
2436 def appendIncompleteTraceLog(testruns):
2437 	# create TestRun vessels for ftrace parsing
2438 	testcnt = len(testruns)
2439 	testidx = 0
2440 	testrun = []
2441 	for data in testruns:
2442 		testrun.append(TestRun(data))
2443 
2444 	# extract the callgraph and traceevent data
2445 	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2446 		os.path.basename(sysvals.ftracefile))
2447 	tp = TestProps()
2448 	tf = sysvals.openlog(sysvals.ftracefile, 'r')
2449 	data = 0
2450 	for line in tf:
2451 		# remove any latent carriage returns
2452 		line = line.replace('\r\n', '')
2453 		# grab the stamp and sysinfo
2454 		if re.match(tp.stampfmt, line):
2455 			tp.stamp = line
2456 			continue
2457 		elif re.match(tp.sysinfofmt, line):
2458 			tp.sysinfo = line
2459 			continue
2460 		elif re.match(tp.cmdlinefmt, line):
2461 			tp.cmdline = line
2462 			continue
2463 		# determine the trace data type (required for further parsing)
2464 		m = re.match(sysvals.tracertypefmt, line)
2465 		if(m):
2466 			tp.setTracerType(m.group('t'))
2467 			continue
2468 		# device properties line
2469 		if(re.match(sysvals.devpropfmt, line)):
2470 			devProps(line)
2471 			continue
2472 		# parse only valid lines, if this is not one move on
2473 		m = re.match(tp.ftrace_line_fmt, line)
2474 		if(not m):
2475 			continue
2476 		# gather the basic message data from the line
2477 		m_time = m.group('time')
2478 		m_pid = m.group('pid')
2479 		m_msg = m.group('msg')
2480 		if(tp.cgformat):
2481 			m_param3 = m.group('dur')
2482 		else:
2483 			m_param3 = 'traceevent'
2484 		if(m_time and m_pid and m_msg):
2485 			t = FTraceLine(m_time, m_msg, m_param3)
2486 			pid = int(m_pid)
2487 		else:
2488 			continue
2489 		# the line should be a call, return, or event
2490 		if(not t.fcall and not t.freturn and not t.fevent):
2491 			continue
2492 		# look for the suspend start marker
2493 		if(t.startMarker()):
2494 			data = testrun[testidx].data
2495 			tp.parseStamp(data, sysvals)
2496 			data.setStart(t.time)
2497 			continue
2498 		if(not data):
2499 			continue
2500 		# find the end of resume
2501 		if(t.endMarker()):
2502 			data.setEnd(t.time)
2503 			testidx += 1
2504 			if(testidx >= testcnt):
2505 				break
2506 			continue
2507 		# trace event processing
2508 		if(t.fevent):
2509 			# general trace events have two types, begin and end
2510 			if(re.match('(?P<name>.*) begin$', t.name)):
2511 				isbegin = True
2512 			elif(re.match('(?P<name>.*) end$', t.name)):
2513 				isbegin = False
2514 			else:
2515 				continue
2516 			m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2517 			if(m):
2518 				val = m.group('val')
2519 				if val == '0':
2520 					name = m.group('name')
2521 				else:
2522 					name = m.group('name')+'['+val+']'
2523 			else:
2524 				m = re.match('(?P<name>.*) .*', t.name)
2525 				name = m.group('name')
2526 			# special processing for trace events
2527 			if re.match('dpm_prepare\[.*', name):
2528 				continue
2529 			elif re.match('machine_suspend.*', name):
2530 				continue
2531 			elif re.match('suspend_enter\[.*', name):
2532 				if(not isbegin):
2533 					data.dmesg['suspend_prepare']['end'] = t.time
2534 				continue
2535 			elif re.match('dpm_suspend\[.*', name):
2536 				if(not isbegin):
2537 					data.dmesg['suspend']['end'] = t.time
2538 				continue
2539 			elif re.match('dpm_suspend_late\[.*', name):
2540 				if(isbegin):
2541 					data.dmesg['suspend_late']['start'] = t.time
2542 				else:
2543 					data.dmesg['suspend_late']['end'] = t.time
2544 				continue
2545 			elif re.match('dpm_suspend_noirq\[.*', name):
2546 				if(isbegin):
2547 					data.dmesg['suspend_noirq']['start'] = t.time
2548 				else:
2549 					data.dmesg['suspend_noirq']['end'] = t.time
2550 				continue
2551 			elif re.match('dpm_resume_noirq\[.*', name):
2552 				if(isbegin):
2553 					data.dmesg['resume_machine']['end'] = t.time
2554 					data.dmesg['resume_noirq']['start'] = t.time
2555 				else:
2556 					data.dmesg['resume_noirq']['end'] = t.time
2557 				continue
2558 			elif re.match('dpm_resume_early\[.*', name):
2559 				if(isbegin):
2560 					data.dmesg['resume_early']['start'] = t.time
2561 				else:
2562 					data.dmesg['resume_early']['end'] = t.time
2563 				continue
2564 			elif re.match('dpm_resume\[.*', name):
2565 				if(isbegin):
2566 					data.dmesg['resume']['start'] = t.time
2567 				else:
2568 					data.dmesg['resume']['end'] = t.time
2569 				continue
2570 			elif re.match('dpm_complete\[.*', name):
2571 				if(isbegin):
2572 					data.dmesg['resume_complete']['start'] = t.time
2573 				else:
2574 					data.dmesg['resume_complete']['end'] = t.time
2575 				continue
2576 			# skip trace events inside devices calls
2577 			if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2578 				continue
2579 			# global events (outside device calls) are simply graphed
2580 			if(isbegin):
2581 				# store each trace event in ttemp
2582 				if(name not in testrun[testidx].ttemp):
2583 					testrun[testidx].ttemp[name] = []
2584 				testrun[testidx].ttemp[name].append(\
2585 					{'begin': t.time, 'end': t.time})
2586 			else:
2587 				# finish off matching trace event in ttemp
2588 				if(name in testrun[testidx].ttemp):
2589 					testrun[testidx].ttemp[name][-1]['end'] = t.time
2590 		# call/return processing
2591 		elif sysvals.usecallgraph:
2592 			# create a callgraph object for the data
2593 			if(pid not in testrun[testidx].ftemp):
2594 				testrun[testidx].ftemp[pid] = []
2595 				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2596 			# when the call is finished, see which device matches it
2597 			cg = testrun[testidx].ftemp[pid][-1]
2598 			res = cg.addLine(t)
2599 			if(res != 0):
2600 				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2601 			if(res == -1):
2602 				testrun[testidx].ftemp[pid][-1].addLine(t)
2603 	tf.close()
2604 
2605 	for test in testrun:
2606 		# add the traceevent data to the device hierarchy
2607 		if(sysvals.usetraceevents):
2608 			for name in test.ttemp:
2609 				for event in test.ttemp[name]:
2610 					test.data.newActionGlobal(name, event['begin'], event['end'])
2611 
2612 		# add the callgraph data to the device hierarchy
2613 		for pid in test.ftemp:
2614 			for cg in test.ftemp[pid]:
2615 				if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
2616 					continue
2617 				if(not cg.postProcess()):
2618 					id = 'task %s cpu %s' % (pid, m.group('cpu'))
2619 					sysvals.vprint('Sanity check failed for '+\
2620 						id+', ignoring this callback')
2621 					continue
2622 				callstart = cg.start
2623 				callend = cg.end
2624 				for p in test.data.phases:
2625 					if(test.data.dmesg[p]['start'] <= callstart and
2626 						callstart <= test.data.dmesg[p]['end']):
2627 						list = test.data.dmesg[p]['list']
2628 						for devname in list:
2629 							dev = list[devname]
2630 							if(pid == dev['pid'] and
2631 								callstart <= dev['start'] and
2632 								callend >= dev['end']):
2633 								dev['ftrace'] = cg
2634 						break
2635 
2636 # Function: parseTraceLog
2637 # Description:
2638 #	 Analyze an ftrace log output file generated from this app during
2639 #	 the execution phase. Used when the ftrace log is the primary data source
2640 #	 and includes the suspend_resume and device_pm_callback trace events
2641 #	 The ftrace filename is taken from sysvals
2642 # Output:
2643 #	 An array of Data objects
2644 def parseTraceLog(live=False):
2645 	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2646 		os.path.basename(sysvals.ftracefile))
2647 	if(os.path.exists(sysvals.ftracefile) == False):
2648 		doError('%s does not exist' % sysvals.ftracefile)
2649 	if not live:
2650 		sysvals.setupAllKprobes()
2651 	tracewatch = []
2652 	if sysvals.usekprobes:
2653 		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
2654 			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', 'CPU_OFF']
2655 
2656 	# extract the callgraph and traceevent data
2657 	tp = TestProps()
2658 	testruns = []
2659 	testdata = []
2660 	testrun = 0
2661 	data = 0
2662 	tf = sysvals.openlog(sysvals.ftracefile, 'r')
2663 	phase = 'suspend_prepare'
2664 	for line in tf:
2665 		# remove any latent carriage returns
2666 		line = line.replace('\r\n', '')
2667 		# stamp and sysinfo lines
2668 		if re.match(tp.stampfmt, line):
2669 			tp.stamp = line
2670 			continue
2671 		elif re.match(tp.sysinfofmt, line):
2672 			tp.sysinfo = line
2673 			continue
2674 		elif re.match(tp.cmdlinefmt, line):
2675 			tp.cmdline = line
2676 			continue
2677 		# firmware line: pull out any firmware data
2678 		m = re.match(sysvals.firmwarefmt, line)
2679 		if(m):
2680 			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
2681 			continue
2682 		# tracer type line: determine the trace data type
2683 		m = re.match(sysvals.tracertypefmt, line)
2684 		if(m):
2685 			tp.setTracerType(m.group('t'))
2686 			continue
2687 		# device properties line
2688 		if(re.match(sysvals.devpropfmt, line)):
2689 			devProps(line)
2690 			continue
2691 		# ignore all other commented lines
2692 		if line[0] == '#':
2693 			continue
2694 		# ftrace line: parse only valid lines
2695 		m = re.match(tp.ftrace_line_fmt, line)
2696 		if(not m):
2697 			continue
2698 		# gather the basic message data from the line
2699 		m_time = m.group('time')
2700 		m_proc = m.group('proc')
2701 		m_pid = m.group('pid')
2702 		m_msg = m.group('msg')
2703 		if(tp.cgformat):
2704 			m_param3 = m.group('dur')
2705 		else:
2706 			m_param3 = 'traceevent'
2707 		if(m_time and m_pid and m_msg):
2708 			t = FTraceLine(m_time, m_msg, m_param3)
2709 			pid = int(m_pid)
2710 		else:
2711 			continue
2712 		# the line should be a call, return, or event
2713 		if(not t.fcall and not t.freturn and not t.fevent):
2714 			continue
2715 		# find the start of suspend
2716 		if(t.startMarker()):
2717 			phase = 'suspend_prepare'
2718 			data = Data(len(testdata))
2719 			testdata.append(data)
2720 			testrun = TestRun(data)
2721 			testruns.append(testrun)
2722 			tp.parseStamp(data, sysvals)
2723 			data.setStart(t.time)
2724 			data.tKernSus = t.time
2725 			continue
2726 		if(not data):
2727 			continue
2728 		# process cpu exec line
2729 		if t.type == 'tracing_mark_write':
2730 			m = re.match(sysvals.procexecfmt, t.name)
2731 			if(m):
2732 				proclist = dict()
2733 				for ps in m.group('ps').split(','):
2734 					val = ps.split()
2735 					if not val:
2736 						continue
2737 					name = val[0].replace('--', '-')
2738 					proclist[name] = int(val[1])
2739 				data.pstl[t.time] = proclist
2740 				continue
2741 		# find the end of resume
2742 		if(t.endMarker()):
2743 			data.setEnd(t.time)
2744 			if data.tKernRes == 0.0:
2745 				data.tKernRes = t.time
2746 			if data.dmesg['resume_complete']['end'] < 0:
2747 				data.dmesg['resume_complete']['end'] = t.time
2748 			if sysvals.suspendmode == 'mem' and len(tp.fwdata) > data.testnumber:
2749 				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2750 				if(data.tSuspended != 0 and data.tResumed != 0 and \
2751 					(data.fwSuspend > 0 or data.fwResume > 0)):
2752 					data.fwValid = True
2753 			if(not sysvals.usetracemarkers):
2754 				# no trace markers? then quit and be sure to finish recording
2755 				# the event we used to trigger resume end
2756 				if(len(testrun.ttemp['thaw_processes']) > 0):
2757 					# if an entry exists, assume this is its end
2758 					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
2759 				break
2760 			continue
2761 		# trace event processing
2762 		if(t.fevent):
2763 			if(phase == 'post_resume'):
2764 				data.setEnd(t.time)
2765 			if(t.type == 'suspend_resume'):
2766 				# suspend_resume trace events have two types, begin and end
2767 				if(re.match('(?P<name>.*) begin$', t.name)):
2768 					isbegin = True
2769 				elif(re.match('(?P<name>.*) end$', t.name)):
2770 					isbegin = False
2771 				else:
2772 					continue
2773 				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2774 				if(m):
2775 					val = m.group('val')
2776 					if val == '0':
2777 						name = m.group('name')
2778 					else:
2779 						name = m.group('name')+'['+val+']'
2780 				else:
2781 					m = re.match('(?P<name>.*) .*', t.name)
2782 					name = m.group('name')
2783 				# ignore these events
2784 				if(name.split('[')[0] in tracewatch):
2785 					continue
2786 				# -- phase changes --
2787 				# start of kernel suspend
2788 				if(re.match('suspend_enter\[.*', t.name)):
2789 					if(isbegin and data.start == data.tKernSus):
2790 						data.dmesg[phase]['start'] = t.time
2791 						data.tKernSus = t.time
2792 					continue
2793 				# suspend_prepare start
2794 				elif(re.match('dpm_prepare\[.*', t.name)):
2795 					phase = 'suspend_prepare'
2796 					if(not isbegin):
2797 						data.dmesg[phase]['end'] = t.time
2798 						if data.dmesg[phase]['start'] < 0:
2799 							data.dmesg[phase]['start'] = data.start
2800 					continue
2801 				# suspend start
2802 				elif(re.match('dpm_suspend\[.*', t.name)):
2803 					phase = 'suspend'
2804 					data.setPhase(phase, t.time, isbegin)
2805 					continue
2806 				# suspend_late start
2807 				elif(re.match('dpm_suspend_late\[.*', t.name)):
2808 					phase = 'suspend_late'
2809 					data.setPhase(phase, t.time, isbegin)
2810 					continue
2811 				# suspend_noirq start
2812 				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
2813 					if data.phaseCollision('suspend_noirq', isbegin, line):
2814 						continue
2815 					phase = 'suspend_noirq'
2816 					data.setPhase(phase, t.time, isbegin)
2817 					if(not isbegin):
2818 						phase = 'suspend_machine'
2819 						data.dmesg[phase]['start'] = t.time
2820 					continue
2821 				# suspend_machine/resume_machine
2822 				elif(re.match('machine_suspend\[.*', t.name)):
2823 					if(isbegin):
2824 						phase = 'suspend_machine'
2825 						data.dmesg[phase]['end'] = t.time
2826 						data.tSuspended = t.time
2827 					else:
2828 						if(sysvals.suspendmode in ['mem', 'disk'] and not tp.S0i3):
2829 							data.dmesg['suspend_machine']['end'] = t.time
2830 							data.tSuspended = t.time
2831 						phase = 'resume_machine'
2832 						data.dmesg[phase]['start'] = t.time
2833 						data.tResumed = t.time
2834 						data.tLow = data.tResumed - data.tSuspended
2835 					continue
2836 				# acpi_suspend
2837 				elif(re.match('acpi_suspend\[.*', t.name)):
2838 					# acpi_suspend[0] S0i3
2839 					if(re.match('acpi_suspend\[0\] begin', t.name)):
2840 						if(sysvals.suspendmode == 'mem'):
2841 							tp.S0i3 = True
2842 							data.dmesg['suspend_machine']['end'] = t.time
2843 							data.tSuspended = t.time
2844 					continue
2845 				# resume_noirq start
2846 				elif(re.match('dpm_resume_noirq\[.*', t.name)):
2847 					if data.phaseCollision('resume_noirq', isbegin, line):
2848 						continue
2849 					phase = 'resume_noirq'
2850 					data.setPhase(phase, t.time, isbegin)
2851 					if(isbegin):
2852 						data.dmesg['resume_machine']['end'] = t.time
2853 					continue
2854 				# resume_early start
2855 				elif(re.match('dpm_resume_early\[.*', t.name)):
2856 					phase = 'resume_early'
2857 					data.setPhase(phase, t.time, isbegin)
2858 					continue
2859 				# resume start
2860 				elif(re.match('dpm_resume\[.*', t.name)):
2861 					phase = 'resume'
2862 					data.setPhase(phase, t.time, isbegin)
2863 					continue
2864 				# resume complete start
2865 				elif(re.match('dpm_complete\[.*', t.name)):
2866 					phase = 'resume_complete'
2867 					if(isbegin):
2868 						data.dmesg[phase]['start'] = t.time
2869 					continue
2870 				# skip trace events inside devices calls
2871 				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2872 					continue
2873 				# global events (outside device calls) are graphed
2874 				if(name not in testrun.ttemp):
2875 					testrun.ttemp[name] = []
2876 				if(isbegin):
2877 					# create a new list entry
2878 					testrun.ttemp[name].append(\
2879 						{'begin': t.time, 'end': t.time, 'pid': pid})
2880 				else:
2881 					if(len(testrun.ttemp[name]) > 0):
2882 						# if an entry exists, assume this is its end
2883 						testrun.ttemp[name][-1]['end'] = t.time
2884 					elif(phase == 'post_resume'):
2885 						# post resume events can just have ends
2886 						testrun.ttemp[name].append({
2887 							'begin': data.dmesg[phase]['start'],
2888 							'end': t.time})
2889 			# device callback start
2890 			elif(t.type == 'device_pm_callback_start'):
2891 				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
2892 					t.name);
2893 				if(not m):
2894 					continue
2895 				drv = m.group('drv')
2896 				n = m.group('d')
2897 				p = m.group('p')
2898 				if(n and p):
2899 					data.newAction(phase, n, pid, p, t.time, -1, drv)
2900 					if pid not in data.devpids:
2901 						data.devpids.append(pid)
2902 			# device callback finish
2903 			elif(t.type == 'device_pm_callback_end'):
2904 				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
2905 				if(not m):
2906 					continue
2907 				n = m.group('d')
2908 				list = data.dmesg[phase]['list']
2909 				if(n in list):
2910 					dev = list[n]
2911 					dev['length'] = t.time - dev['start']
2912 					dev['end'] = t.time
2913 		# kprobe event processing
2914 		elif(t.fkprobe):
2915 			kprobename = t.type
2916 			kprobedata = t.name
2917 			key = (kprobename, pid)
2918 			# displayname is generated from kprobe data
2919 			displayname = ''
2920 			if(t.fcall):
2921 				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
2922 				if not displayname:
2923 					continue
2924 				if(key not in tp.ktemp):
2925 					tp.ktemp[key] = []
2926 				tp.ktemp[key].append({
2927 					'pid': pid,
2928 					'begin': t.time,
2929 					'end': t.time,
2930 					'name': displayname,
2931 					'cdata': kprobedata,
2932 					'proc': m_proc,
2933 				})
2934 			elif(t.freturn):
2935 				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
2936 					continue
2937 				e = tp.ktemp[key][-1]
2938 				if e['begin'] < 0.0 or t.time - e['begin'] < 0.000001:
2939 					tp.ktemp[key].pop()
2940 				else:
2941 					e['end'] = t.time
2942 					e['rdata'] = kprobedata
2943 				# end of kernel resume
2944 				if(kprobename == 'pm_notifier_call_chain' or \
2945 					kprobename == 'pm_restore_console'):
2946 					data.dmesg[phase]['end'] = t.time
2947 					data.tKernRes = t.time
2948 
2949 		# callgraph processing
2950 		elif sysvals.usecallgraph:
2951 			# create a callgraph object for the data
2952 			key = (m_proc, pid)
2953 			if(key not in testrun.ftemp):
2954 				testrun.ftemp[key] = []
2955 				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2956 			# when the call is finished, see which device matches it
2957 			cg = testrun.ftemp[key][-1]
2958 			res = cg.addLine(t)
2959 			if(res != 0):
2960 				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2961 			if(res == -1):
2962 				testrun.ftemp[key][-1].addLine(t)
2963 	tf.close()
2964 
2965 	if sysvals.suspendmode == 'command':
2966 		for test in testruns:
2967 			for p in test.data.phases:
2968 				if p == 'suspend_prepare':
2969 					test.data.dmesg[p]['start'] = test.data.start
2970 					test.data.dmesg[p]['end'] = test.data.end
2971 				else:
2972 					test.data.dmesg[p]['start'] = test.data.end
2973 					test.data.dmesg[p]['end'] = test.data.end
2974 			test.data.tSuspended = test.data.end
2975 			test.data.tResumed = test.data.end
2976 			test.data.tLow = 0
2977 			test.data.fwValid = False
2978 
2979 	# dev source and procmon events can be unreadable with mixed phase height
2980 	if sysvals.usedevsrc or sysvals.useprocmon:
2981 		sysvals.mixedphaseheight = False
2982 
2983 	for i in range(len(testruns)):
2984 		test = testruns[i]
2985 		data = test.data
2986 		# find the total time range for this test (begin, end)
2987 		tlb, tle = data.start, data.end
2988 		if i < len(testruns) - 1:
2989 			tle = testruns[i+1].data.start
2990 		# add the process usage data to the timeline
2991 		if sysvals.useprocmon:
2992 			data.createProcessUsageEvents()
2993 		# add the traceevent data to the device hierarchy
2994 		if(sysvals.usetraceevents):
2995 			# add actual trace funcs
2996 			for name in test.ttemp:
2997 				for event in test.ttemp[name]:
2998 					data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
2999 			# add the kprobe based virtual tracefuncs as actual devices
3000 			for key in tp.ktemp:
3001 				name, pid = key
3002 				if name not in sysvals.tracefuncs:
3003 					continue
3004 				for e in tp.ktemp[key]:
3005 					kb, ke = e['begin'], e['end']
3006 					if kb == ke or tlb > kb or tle <= kb:
3007 						continue
3008 					color = sysvals.kprobeColor(name)
3009 					data.newActionGlobal(e['name'], kb, ke, pid, color)
3010 			# add config base kprobes and dev kprobes
3011 			if sysvals.usedevsrc:
3012 				for key in tp.ktemp:
3013 					name, pid = key
3014 					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3015 						continue
3016 					for e in tp.ktemp[key]:
3017 						kb, ke = e['begin'], e['end']
3018 						if kb == ke or tlb > kb or tle <= kb:
3019 							continue
3020 						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3021 							ke, e['cdata'], e['rdata'])
3022 		if sysvals.usecallgraph:
3023 			# add the callgraph data to the device hierarchy
3024 			sortlist = dict()
3025 			for key in test.ftemp:
3026 				proc, pid = key
3027 				for cg in test.ftemp[key]:
3028 					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3029 						continue
3030 					if(not cg.postProcess()):
3031 						id = 'task %s' % (pid)
3032 						sysvals.vprint('Sanity check failed for '+\
3033 							id+', ignoring this callback')
3034 						continue
3035 					# match cg data to devices
3036 					devname = ''
3037 					if sysvals.suspendmode != 'command':
3038 						devname = cg.deviceMatch(pid, data)
3039 					if not devname:
3040 						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3041 						sortlist[sortkey] = cg
3042 					elif len(cg.list) > 1000000:
3043 						print 'WARNING: the callgraph for %s is massive (%d lines)' %\
3044 							(devname, len(cg.list))
3045 			# create blocks for orphan cg data
3046 			for sortkey in sorted(sortlist):
3047 				cg = sortlist[sortkey]
3048 				name = cg.name
3049 				if sysvals.isCallgraphFunc(name):
3050 					sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3051 					cg.newActionFromFunction(data)
3052 	if sysvals.suspendmode == 'command':
3053 		return (testdata, '')
3054 
3055 	# fill in any missing phases
3056 	error = []
3057 	for data in testdata:
3058 		tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3059 		terr = ''
3060 		lp = data.phases[0]
3061 		for p in data.phases:
3062 			if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3063 				if not terr:
3064 					print 'TEST%s FAILED: %s failed in %s phase' % (tn, sysvals.suspendmode, lp)
3065 					terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, lp)
3066 					error.append(terr)
3067 				sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3068 			if(data.dmesg[p]['start'] < 0):
3069 				data.dmesg[p]['start'] = data.dmesg[lp]['end']
3070 				if(p == 'resume_machine'):
3071 					data.tSuspended = data.dmesg[lp]['end']
3072 					data.tResumed = data.dmesg[lp]['end']
3073 					data.tLow = 0
3074 			if(data.dmesg[p]['end'] < 0):
3075 				data.dmesg[p]['end'] = data.dmesg[p]['start']
3076 			if(p != lp and not ('machine' in p and 'machine' in lp)):
3077 				data.dmesg[lp]['end'] = data.dmesg[p]['start']
3078 			lp = p
3079 
3080 		if(len(sysvals.devicefilter) > 0):
3081 			data.deviceFilter(sysvals.devicefilter)
3082 		data.fixupInitcallsThatDidntReturn()
3083 		if sysvals.usedevsrc:
3084 			data.optimizeDevSrc()
3085 
3086 	# x2: merge any overlapping devices between test runs
3087 	if sysvals.usedevsrc and len(testdata) > 1:
3088 		tc = len(testdata)
3089 		for i in range(tc - 1):
3090 			devlist = testdata[i].overflowDevices()
3091 			for j in range(i + 1, tc):
3092 				testdata[j].mergeOverlapDevices(devlist)
3093 		testdata[0].stitchTouchingThreads(testdata[1:])
3094 	return (testdata, ', '.join(error))
3095 
3096 # Function: loadKernelLog
3097 # Description:
3098 #	 [deprecated for kernel 3.15.0 or newer]
3099 #	 load the dmesg file into memory and fix up any ordering issues
3100 #	 The dmesg filename is taken from sysvals
3101 # Output:
3102 #	 An array of empty Data objects with only their dmesgtext attributes set
3103 def loadKernelLog():
3104 	sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3105 		os.path.basename(sysvals.dmesgfile))
3106 	if(os.path.exists(sysvals.dmesgfile) == False):
3107 		doError('%s does not exist' % sysvals.dmesgfile)
3108 
3109 	# there can be multiple test runs in a single file
3110 	tp = TestProps()
3111 	tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3112 	testruns = []
3113 	data = 0
3114 	lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3115 	for line in lf:
3116 		line = line.replace('\r\n', '')
3117 		idx = line.find('[')
3118 		if idx > 1:
3119 			line = line[idx:]
3120 		# grab the stamp and sysinfo
3121 		if re.match(tp.stampfmt, line):
3122 			tp.stamp = line
3123 			continue
3124 		elif re.match(tp.sysinfofmt, line):
3125 			tp.sysinfo = line
3126 			continue
3127 		elif re.match(tp.cmdlinefmt, line):
3128 			tp.cmdline = line
3129 			continue
3130 		m = re.match(sysvals.firmwarefmt, line)
3131 		if(m):
3132 			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
3133 			continue
3134 		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3135 		if(not m):
3136 			continue
3137 		msg = m.group("msg")
3138 		if(re.match('PM: Syncing filesystems.*', msg)):
3139 			if(data):
3140 				testruns.append(data)
3141 			data = Data(len(testruns))
3142 			tp.parseStamp(data, sysvals)
3143 			if len(tp.fwdata) > data.testnumber:
3144 				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
3145 				if(data.fwSuspend > 0 or data.fwResume > 0):
3146 					data.fwValid = True
3147 		if(not data):
3148 			continue
3149 		m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3150 		if(m):
3151 			sysvals.stamp['kernel'] = m.group('k')
3152 		m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3153 		if(m):
3154 			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3155 		data.dmesgtext.append(line)
3156 	lf.close()
3157 
3158 	if data:
3159 		testruns.append(data)
3160 	if len(testruns) < 1:
3161 		print('ERROR: dmesg log has no suspend/resume data: %s' \
3162 			% sysvals.dmesgfile)
3163 
3164 	# fix lines with same timestamp/function with the call and return swapped
3165 	for data in testruns:
3166 		last = ''
3167 		for line in data.dmesgtext:
3168 			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
3169 				'(?P<f>.*)\+ @ .*, parent: .*', line)
3170 			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3171 				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3172 			if(mc and mr and (mc.group('t') == mr.group('t')) and
3173 				(mc.group('f') == mr.group('f'))):
3174 				i = data.dmesgtext.index(last)
3175 				j = data.dmesgtext.index(line)
3176 				data.dmesgtext[i] = line
3177 				data.dmesgtext[j] = last
3178 			last = line
3179 	return testruns
3180 
3181 # Function: parseKernelLog
3182 # Description:
3183 #	 [deprecated for kernel 3.15.0 or newer]
3184 #	 Analyse a dmesg log output file generated from this app during
3185 #	 the execution phase. Create a set of device structures in memory
3186 #	 for subsequent formatting in the html output file
3187 #	 This call is only for legacy support on kernels where the ftrace
3188 #	 data lacks the suspend_resume or device_pm_callbacks trace events.
3189 # Arguments:
3190 #	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3191 # Output:
3192 #	 The filled Data object
3193 def parseKernelLog(data):
3194 	phase = 'suspend_runtime'
3195 
3196 	if(data.fwValid):
3197 		sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3198 			(data.fwSuspend, data.fwResume))
3199 
3200 	# dmesg phase match table
3201 	dm = {
3202 		'suspend_prepare': 'PM: Syncing filesystems.*',
3203 		        'suspend': 'PM: Entering [a-z]* sleep.*',
3204 		   'suspend_late': 'PM: suspend of devices complete after.*',
3205 		  'suspend_noirq': 'PM: late suspend of devices complete after.*',
3206 		'suspend_machine': 'PM: noirq suspend of devices complete after.*',
3207 		 'resume_machine': 'ACPI: Low-level resume complete.*',
3208 		   'resume_noirq': 'ACPI: Waking up from system sleep state.*',
3209 		   'resume_early': 'PM: noirq resume of devices complete after.*',
3210 		         'resume': 'PM: early resume of devices complete after.*',
3211 		'resume_complete': 'PM: resume of devices complete after.*',
3212 		    'post_resume': '.*Restarting tasks \.\.\..*',
3213 	}
3214 	if(sysvals.suspendmode == 'standby'):
3215 		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3216 	elif(sysvals.suspendmode == 'disk'):
3217 		dm['suspend_late'] = 'PM: freeze of devices complete after.*'
3218 		dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
3219 		dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
3220 		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3221 		dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
3222 		dm['resume'] = 'PM: early restore of devices complete after.*'
3223 		dm['resume_complete'] = 'PM: restore of devices complete after.*'
3224 	elif(sysvals.suspendmode == 'freeze'):
3225 		dm['resume_machine'] = 'ACPI: resume from mwait'
3226 
3227 	# action table (expected events that occur and show up in dmesg)
3228 	at = {
3229 		'sync_filesystems': {
3230 			'smsg': 'PM: Syncing filesystems.*',
3231 			'emsg': 'PM: Preparing system for mem sleep.*' },
3232 		'freeze_user_processes': {
3233 			'smsg': 'Freezing user space processes .*',
3234 			'emsg': 'Freezing remaining freezable tasks.*' },
3235 		'freeze_tasks': {
3236 			'smsg': 'Freezing remaining freezable tasks.*',
3237 			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3238 		'ACPI prepare': {
3239 			'smsg': 'ACPI: Preparing to enter system sleep state.*',
3240 			'emsg': 'PM: Saving platform NVS memory.*' },
3241 		'PM vns': {
3242 			'smsg': 'PM: Saving platform NVS memory.*',
3243 			'emsg': 'Disabling non-boot CPUs .*' },
3244 	}
3245 
3246 	t0 = -1.0
3247 	cpu_start = -1.0
3248 	prevktime = -1.0
3249 	actions = dict()
3250 	for line in data.dmesgtext:
3251 		# parse each dmesg line into the time and message
3252 		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3253 		if(m):
3254 			val = m.group('ktime')
3255 			try:
3256 				ktime = float(val)
3257 			except:
3258 				continue
3259 			msg = m.group('msg')
3260 			# initialize data start to first line time
3261 			if t0 < 0:
3262 				data.setStart(ktime)
3263 				t0 = ktime
3264 		else:
3265 			continue
3266 
3267 		# hack for determining resume_machine end for freeze
3268 		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3269 			and phase == 'resume_machine' and \
3270 			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3271 			data.dmesg['resume_machine']['end'] = ktime
3272 			phase = 'resume_noirq'
3273 			data.dmesg[phase]['start'] = ktime
3274 
3275 		# suspend start
3276 		if(re.match(dm['suspend_prepare'], msg)):
3277 			phase = 'suspend_prepare'
3278 			data.dmesg[phase]['start'] = ktime
3279 			data.setStart(ktime)
3280 			data.tKernSus = ktime
3281 		# suspend start
3282 		elif(re.match(dm['suspend'], msg)):
3283 			data.dmesg['suspend_prepare']['end'] = ktime
3284 			phase = 'suspend'
3285 			data.dmesg[phase]['start'] = ktime
3286 		# suspend_late start
3287 		elif(re.match(dm['suspend_late'], msg)):
3288 			data.dmesg['suspend']['end'] = ktime
3289 			phase = 'suspend_late'
3290 			data.dmesg[phase]['start'] = ktime
3291 		# suspend_noirq start
3292 		elif(re.match(dm['suspend_noirq'], msg)):
3293 			data.dmesg['suspend_late']['end'] = ktime
3294 			phase = 'suspend_noirq'
3295 			data.dmesg[phase]['start'] = ktime
3296 		# suspend_machine start
3297 		elif(re.match(dm['suspend_machine'], msg)):
3298 			data.dmesg['suspend_noirq']['end'] = ktime
3299 			phase = 'suspend_machine'
3300 			data.dmesg[phase]['start'] = ktime
3301 		# resume_machine start
3302 		elif(re.match(dm['resume_machine'], msg)):
3303 			if(sysvals.suspendmode in ['freeze', 'standby']):
3304 				data.tSuspended = prevktime
3305 				data.dmesg['suspend_machine']['end'] = prevktime
3306 			else:
3307 				data.tSuspended = ktime
3308 				data.dmesg['suspend_machine']['end'] = ktime
3309 			phase = 'resume_machine'
3310 			data.tResumed = ktime
3311 			data.tLow = data.tResumed - data.tSuspended
3312 			data.dmesg[phase]['start'] = ktime
3313 		# resume_noirq start
3314 		elif(re.match(dm['resume_noirq'], msg)):
3315 			data.dmesg['resume_machine']['end'] = ktime
3316 			phase = 'resume_noirq'
3317 			data.dmesg[phase]['start'] = ktime
3318 		# resume_early start
3319 		elif(re.match(dm['resume_early'], msg)):
3320 			data.dmesg['resume_noirq']['end'] = ktime
3321 			phase = 'resume_early'
3322 			data.dmesg[phase]['start'] = ktime
3323 		# resume start
3324 		elif(re.match(dm['resume'], msg)):
3325 			data.dmesg['resume_early']['end'] = ktime
3326 			phase = 'resume'
3327 			data.dmesg[phase]['start'] = ktime
3328 		# resume complete start
3329 		elif(re.match(dm['resume_complete'], msg)):
3330 			data.dmesg['resume']['end'] = ktime
3331 			phase = 'resume_complete'
3332 			data.dmesg[phase]['start'] = ktime
3333 		# post resume start
3334 		elif(re.match(dm['post_resume'], msg)):
3335 			data.dmesg['resume_complete']['end'] = ktime
3336 			data.setEnd(ktime)
3337 			data.tKernRes = ktime
3338 			break
3339 
3340 		# -- device callbacks --
3341 		if(phase in data.phases):
3342 			# device init call
3343 			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3344 				sm = re.match('calling  (?P<f>.*)\+ @ '+\
3345 					'(?P<n>.*), parent: (?P<p>.*)', msg);
3346 				f = sm.group('f')
3347 				n = sm.group('n')
3348 				p = sm.group('p')
3349 				if(f and n and p):
3350 					data.newAction(phase, f, int(n), p, ktime, -1, '')
3351 			# device init return
3352 			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3353 				'(?P<t>.*) usecs', msg)):
3354 				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3355 					'(?P<t>.*) usecs(?P<a>.*)', msg);
3356 				f = sm.group('f')
3357 				t = sm.group('t')
3358 				list = data.dmesg[phase]['list']
3359 				if(f in list):
3360 					dev = list[f]
3361 					dev['length'] = int(t)
3362 					dev['end'] = ktime
3363 
3364 		# if trace events are not available, these are better than nothing
3365 		if(not sysvals.usetraceevents):
3366 			# look for known actions
3367 			for a in at:
3368 				if(re.match(at[a]['smsg'], msg)):
3369 					if(a not in actions):
3370 						actions[a] = []
3371 					actions[a].append({'begin': ktime, 'end': ktime})
3372 				if(re.match(at[a]['emsg'], msg)):
3373 					if(a in actions):
3374 						actions[a][-1]['end'] = ktime
3375 			# now look for CPU on/off events
3376 			if(re.match('Disabling non-boot CPUs .*', msg)):
3377 				# start of first cpu suspend
3378 				cpu_start = ktime
3379 			elif(re.match('Enabling non-boot CPUs .*', msg)):
3380 				# start of first cpu resume
3381 				cpu_start = ktime
3382 			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3383 				# end of a cpu suspend, start of the next
3384 				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3385 				cpu = 'CPU'+m.group('cpu')
3386 				if(cpu not in actions):
3387 					actions[cpu] = []
3388 				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3389 				cpu_start = ktime
3390 			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3391 				# end of a cpu resume, start of the next
3392 				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3393 				cpu = 'CPU'+m.group('cpu')
3394 				if(cpu not in actions):
3395 					actions[cpu] = []
3396 				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3397 				cpu_start = ktime
3398 		prevktime = ktime
3399 
3400 	# fill in any missing phases
3401 	lp = data.phases[0]
3402 	for p in data.phases:
3403 		if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3404 			print('WARNING: phase "%s" is missing, something went wrong!' % p)
3405 			print('    In %s, this dmesg line denotes the start of %s:' % \
3406 				(sysvals.suspendmode, p))
3407 			print('        "%s"' % dm[p])
3408 		if(data.dmesg[p]['start'] < 0):
3409 			data.dmesg[p]['start'] = data.dmesg[lp]['end']
3410 			if(p == 'resume_machine'):
3411 				data.tSuspended = data.dmesg[lp]['end']
3412 				data.tResumed = data.dmesg[lp]['end']
3413 				data.tLow = 0
3414 		if(data.dmesg[p]['end'] < 0):
3415 			data.dmesg[p]['end'] = data.dmesg[p]['start']
3416 		lp = p
3417 
3418 	# fill in any actions we've found
3419 	for name in actions:
3420 		for event in actions[name]:
3421 			data.newActionGlobal(name, event['begin'], event['end'])
3422 
3423 	if(len(sysvals.devicefilter) > 0):
3424 		data.deviceFilter(sysvals.devicefilter)
3425 	data.fixupInitcallsThatDidntReturn()
3426 	return True
3427 
3428 def callgraphHTML(sv, hf, num, cg, title, color, devid):
3429 	html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
3430 	html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3431 	html_func_end = '</article>\n'
3432 	html_func_leaf = '<article>{0} {1}</article>\n'
3433 
3434 	cgid = devid
3435 	if cg.id:
3436 		cgid += cg.id
3437 	cglen = (cg.end - cg.start) * 1000
3438 	if cglen < sv.mincglen:
3439 		return num
3440 
3441 	fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
3442 	flen = fmt % (cglen, cg.start, cg.end)
3443 	hf.write(html_func_top.format(cgid, color, num, title, flen))
3444 	num += 1
3445 	for line in cg.list:
3446 		if(line.length < 0.000000001):
3447 			flen = ''
3448 		else:
3449 			fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
3450 			flen = fmt % (line.length*1000, line.time)
3451 		if line.isLeaf():
3452 			hf.write(html_func_leaf.format(line.name, flen))
3453 		elif line.freturn:
3454 			hf.write(html_func_end)
3455 		else:
3456 			hf.write(html_func_start.format(num, line.name, flen))
3457 			num += 1
3458 	hf.write(html_func_end)
3459 	return num
3460 
3461 def addCallgraphs(sv, hf, data):
3462 	hf.write('<section id="callgraphs" class="callgraph">\n')
3463 	# write out the ftrace data converted to html
3464 	num = 0
3465 	for p in data.phases:
3466 		if sv.cgphase and p != sv.cgphase:
3467 			continue
3468 		list = data.dmesg[p]['list']
3469 		for devname in data.sortedDevices(p):
3470 			if len(sv.cgfilter) > 0 and devname not in sv.cgfilter:
3471 				continue
3472 			dev = list[devname]
3473 			color = 'white'
3474 			if 'color' in data.dmesg[p]:
3475 				color = data.dmesg[p]['color']
3476 			if 'color' in dev:
3477 				color = dev['color']
3478 			name = devname
3479 			if(devname in sv.devprops):
3480 				name = sv.devprops[devname].altName(devname)
3481 			if sv.suspendmode in suspendmodename:
3482 				name += ' '+p
3483 			if('ftrace' in dev):
3484 				cg = dev['ftrace']
3485 				num = callgraphHTML(sv, hf, num, cg,
3486 					name, color, dev['id'])
3487 			if('ftraces' in dev):
3488 				for cg in dev['ftraces']:
3489 					num = callgraphHTML(sv, hf, num, cg,
3490 						name+' &rarr; '+cg.name, color, dev['id'])
3491 	hf.write('\n\n    </section>\n')
3492 
3493 # Function: createHTMLSummarySimple
3494 # Description:
3495 #	 Create summary html file for a series of tests
3496 # Arguments:
3497 #	 testruns: array of Data objects from parseTraceLog
3498 def createHTMLSummarySimple(testruns, htmlfile, folder):
3499 	# write the html header first (html head, css code, up to body start)
3500 	html = '<!DOCTYPE html>\n<html>\n<head>\n\
3501 	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3502 	<title>SleepGraph Summary</title>\n\
3503 	<style type=\'text/css\'>\n\
3504 		.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
3505 		table {width:100%;border-collapse: collapse;}\n\
3506 		.summary {border:1px solid;}\n\
3507 		th {border: 1px solid black;background:#222;color:white;}\n\
3508 		td {font: 16px "Times New Roman";text-align: center;}\n\
3509 		tr.head td {border: 1px solid black;background:#aaa;}\n\
3510 		tr.alt {background-color:#ddd;}\n\
3511 		tr.notice {color:red;}\n\
3512 		.minval {background-color:#BBFFBB;}\n\
3513 		.medval {background-color:#BBBBFF;}\n\
3514 		.maxval {background-color:#FFBBBB;}\n\
3515 		.head a {color:#000;text-decoration: none;}\n\
3516 	</style>\n</head>\n<body>\n'
3517 
3518 	# extract the test data into list
3519 	list = dict()
3520 	tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [[], []]
3521 	iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
3522 	num = 0
3523 	lastmode = ''
3524 	cnt = {'pass':0, 'fail':0, 'hang':0}
3525 	for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
3526 		mode = data['mode']
3527 		if mode not in list:
3528 			list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
3529 		if lastmode and lastmode != mode and num > 0:
3530 			for i in range(2):
3531 				s = sorted(tMed[i])
3532 				list[lastmode]['med'][i] = s[int(len(s)/2)]
3533 				iMed[i] = tMed[i].index(list[lastmode]['med'][i])
3534 			list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
3535 			list[lastmode]['min'] = tMin
3536 			list[lastmode]['max'] = tMax
3537 			list[lastmode]['idx'] = (iMin, iMed, iMax)
3538 			tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [[], []]
3539 			iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
3540 			num = 0
3541 		tVal = [float(data['suspend']), float(data['resume'])]
3542 		list[mode]['data'].append([data['host'], data['kernel'],
3543 			data['time'], tVal[0], tVal[1], data['url'], data['result'],
3544 			data['issues']])
3545 		idx = len(list[mode]['data']) - 1
3546 		if data['result'] == 'pass':
3547 			cnt['pass'] += 1
3548 			for i in range(2):
3549 				tMed[i].append(tVal[i])
3550 				tAvg[i] += tVal[i]
3551 				if tMin[i] == 0 or tVal[i] < tMin[i]:
3552 					iMin[i] = idx
3553 					tMin[i] = tVal[i]
3554 				if tMax[i] == 0 or tVal[i] > tMax[i]:
3555 					iMax[i] = idx
3556 					tMax[i] = tVal[i]
3557 			num += 1
3558 		elif data['result'] == 'hang':
3559 			cnt['hang'] += 1
3560 		elif data['result'] == 'fail':
3561 			cnt['fail'] += 1
3562 		lastmode = mode
3563 	if lastmode and num > 0:
3564 		for i in range(2):
3565 			s = sorted(tMed[i])
3566 			list[lastmode]['med'][i] = s[int(len(s)/2)]
3567 			iMed[i] = tMed[i].index(list[lastmode]['med'][i])
3568 		list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
3569 		list[lastmode]['min'] = tMin
3570 		list[lastmode]['max'] = tMax
3571 		list[lastmode]['idx'] = (iMin, iMed, iMax)
3572 
3573 	# group test header
3574 	desc = []
3575 	for ilk in sorted(cnt, reverse=True):
3576 		if cnt[ilk] > 0:
3577 			desc.append('%d %s' % (cnt[ilk], ilk))
3578 	html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (folder, len(testruns), ', '.join(desc))
3579 	th = '\t<th>{0}</th>\n'
3580 	td = '\t<td>{0}</td>\n'
3581 	tdh = '\t<td{1}>{0}</td>\n'
3582 	tdlink = '\t<td><a href="{0}">html</a></td>\n'
3583 
3584 	# table header
3585 	html += '<table class="summary">\n<tr>\n' + th.format('#') +\
3586 		th.format('Mode') + th.format('Host') + th.format('Kernel') +\
3587 		th.format('Test Time') + th.format('Result') + th.format('Issues') +\
3588 		th.format('Suspend') + th.format('Resume') + th.format('Detail') + '</tr>\n'
3589 
3590 	# export list into html
3591 	head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
3592 		'<td colspan=8 class="sus">Suspend Avg={2} '+\
3593 		'<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
3594 		'<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
3595 		'<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
3596 		'Resume Avg={6} '+\
3597 		'<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
3598 		'<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
3599 		'<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
3600 		'</tr>\n'
3601 	headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan=8></td></tr>\n'
3602 	for mode in list:
3603 		# header line for each suspend mode
3604 		num = 0
3605 		tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
3606 			list[mode]['max'], list[mode]['med']
3607 		count = len(list[mode]['data'])
3608 		if 'idx' in list[mode]:
3609 			iMin, iMed, iMax = list[mode]['idx']
3610 			html += head.format('%d' % count, mode.upper(),
3611 				'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
3612 				'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
3613 				mode.lower()
3614 			)
3615 		else:
3616 			iMin = iMed = iMax = [-1, -1, -1]
3617 			html += headnone.format('%d' % count, mode.upper())
3618 		for d in list[mode]['data']:
3619 			# row classes - alternate row color
3620 			rcls = ['alt'] if num % 2 == 1 else []
3621 			if d[6] != 'pass':
3622 				rcls.append('notice')
3623 			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
3624 			# figure out if the line has sus or res highlighted
3625 			idx = list[mode]['data'].index(d)
3626 			tHigh = ['', '']
3627 			for i in range(2):
3628 				tag = 's%s' % mode if i == 0 else 'r%s' % mode
3629 				if idx == iMin[i]:
3630 					tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
3631 				elif idx == iMax[i]:
3632 					tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
3633 				elif idx == iMed[i]:
3634 					tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
3635 			html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
3636 			html += td.format(mode)										# mode
3637 			html += td.format(d[0])										# host
3638 			html += td.format(d[1])										# kernel
3639 			html += td.format(d[2])										# time
3640 			html += td.format(d[6])										# result
3641 			html += td.format(d[7])										# issues
3642 			html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('')	# suspend
3643 			html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('')	# resume
3644 			html += tdlink.format(d[5]) if d[5] else td.format('')		# url
3645 			html += '</tr>\n'
3646 			num += 1
3647 
3648 	# flush the data to file
3649 	hf = open(htmlfile, 'w')
3650 	hf.write(html+'</table>\n</body>\n</html>\n')
3651 	hf.close()
3652 
3653 def ordinal(value):
3654 	suffix = 'th'
3655 	if value < 10 or value > 19:
3656 		if value % 10 == 1:
3657 			suffix = 'st'
3658 		elif value % 10 == 2:
3659 			suffix = 'nd'
3660 		elif value % 10 == 3:
3661 			suffix = 'rd'
3662 	return '%d%s' % (value, suffix)
3663 
3664 # Function: createHTML
3665 # Description:
3666 #	 Create the output html file from the resident test data
3667 # Arguments:
3668 #	 testruns: array of Data objects from parseKernelLog or parseTraceLog
3669 # Output:
3670 #	 True if the html file was created, false if it failed
3671 def createHTML(testruns, testfail):
3672 	if len(testruns) < 1:
3673 		print('ERROR: Not enough test data to build a timeline')
3674 		return
3675 
3676 	kerror = False
3677 	for data in testruns:
3678 		if data.kerror:
3679 			kerror = True
3680 		data.normalizeTime(testruns[-1].tSuspended)
3681 
3682 	# html function templates
3683 	html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
3684 	html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n'
3685 	html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
3686 	html_timetotal = '<table class="time1">\n<tr>'\
3687 		'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
3688 		'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
3689 		'</tr>\n</table>\n'
3690 	html_timetotal2 = '<table class="time1">\n<tr>'\
3691 		'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
3692 		'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
3693 		'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
3694 		'</tr>\n</table>\n'
3695 	html_timetotal3 = '<table class="time1">\n<tr>'\
3696 		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
3697 		'<td class="yellow">Command: <b>{1}</b></td>'\
3698 		'</tr>\n</table>\n'
3699 	html_timegroups = '<table class="time2">\n<tr>'\
3700 		'<td class="green" title="time from kernel enter_state({5}) to firmware mode [kernel time only]">{4}Kernel Suspend: {0} ms</td>'\
3701 		'<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
3702 		'<td class="purple">{4}Firmware Resume: {2} ms</td>'\
3703 		'<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
3704 		'</tr>\n</table>\n'
3705 	html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
3706 
3707 	# html format variables
3708 	scaleH = 20
3709 	if kerror:
3710 		scaleH = 40
3711 
3712 	# device timeline
3713 	devtl = Timeline(30, scaleH)
3714 
3715 	# write the test title and general info header
3716 	devtl.createHeader(sysvals, testruns[0].stamp)
3717 
3718 	# Generate the header for this timeline
3719 	for data in testruns:
3720 		tTotal = data.end - data.start
3721 		sktime, rktime = data.getTimeValues()
3722 		if(tTotal == 0):
3723 			doError('No timeline data')
3724 		if(data.tLow > 0):
3725 			low_time = '%.0f'%(data.tLow*1000)
3726 		if sysvals.suspendmode == 'command':
3727 			run_time = '%.0f'%((data.end-data.start)*1000)
3728 			if sysvals.testcommand:
3729 				testdesc = sysvals.testcommand
3730 			else:
3731 				testdesc = 'unknown'
3732 			if(len(testruns) > 1):
3733 				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3734 			thtml = html_timetotal3.format(run_time, testdesc)
3735 			devtl.html += thtml
3736 		elif data.fwValid:
3737 			suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0))
3738 			resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0))
3739 			testdesc1 = 'Total'
3740 			testdesc2 = ''
3741 			stitle = 'time from kernel enter_state(%s) to low-power mode [kernel & firmware time]' % sysvals.suspendmode
3742 			rtitle = 'time from low-power mode to return from kernel enter_state(%s) [firmware & kernel time]' % sysvals.suspendmode
3743 			if(len(testruns) > 1):
3744 				testdesc1 = testdesc2 = ordinal(data.testnumber+1)
3745 				testdesc2 += ' '
3746 			if(data.tLow == 0):
3747 				thtml = html_timetotal.format(suspend_time, \
3748 					resume_time, testdesc1, stitle, rtitle)
3749 			else:
3750 				thtml = html_timetotal2.format(suspend_time, low_time, \
3751 					resume_time, testdesc1, stitle, rtitle)
3752 			devtl.html += thtml
3753 			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
3754 			rftime = '%.3f'%(data.fwResume / 1000000.0)
3755 			devtl.html += html_timegroups.format('%.3f'%sktime, \
3756 				sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode)
3757 		else:
3758 			suspend_time = '%.3f' % sktime
3759 			resume_time = '%.3f' % rktime
3760 			testdesc = 'Kernel'
3761 			stitle = 'time from kernel enter_state(%s) to firmware mode [kernel time only]' % sysvals.suspendmode
3762 			rtitle = 'time from firmware mode to return from kernel enter_state(%s) [kernel time only]' % sysvals.suspendmode
3763 			if(len(testruns) > 1):
3764 				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3765 			if(data.tLow == 0):
3766 				thtml = html_timetotal.format(suspend_time, \
3767 					resume_time, testdesc, stitle, rtitle)
3768 			else:
3769 				thtml = html_timetotal2.format(suspend_time, low_time, \
3770 					resume_time, testdesc, stitle, rtitle)
3771 			devtl.html += thtml
3772 
3773 	if testfail:
3774 		devtl.html += html_fail.format(testfail)
3775 
3776 	# time scale for potentially multiple datasets
3777 	t0 = testruns[0].start
3778 	tMax = testruns[-1].end
3779 	tTotal = tMax - t0
3780 
3781 	# determine the maximum number of rows we need to draw
3782 	fulllist = []
3783 	threadlist = []
3784 	pscnt = 0
3785 	devcnt = 0
3786 	for data in testruns:
3787 		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
3788 		for group in data.devicegroups:
3789 			devlist = []
3790 			for phase in group:
3791 				for devname in data.tdevlist[phase]:
3792 					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
3793 					devlist.append(d)
3794 					if d.isa('kth'):
3795 						threadlist.append(d)
3796 					else:
3797 						if d.isa('ps'):
3798 							pscnt += 1
3799 						else:
3800 							devcnt += 1
3801 						fulllist.append(d)
3802 			if sysvals.mixedphaseheight:
3803 				devtl.getPhaseRows(devlist)
3804 	if not sysvals.mixedphaseheight:
3805 		if len(threadlist) > 0 and len(fulllist) > 0:
3806 			if pscnt > 0 and devcnt > 0:
3807 				msg = 'user processes & device pm callbacks'
3808 			elif pscnt > 0:
3809 				msg = 'user processes'
3810 			else:
3811 				msg = 'device pm callbacks'
3812 			d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
3813 			fulllist.insert(0, d)
3814 		devtl.getPhaseRows(fulllist)
3815 		if len(threadlist) > 0:
3816 			d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
3817 			threadlist.insert(0, d)
3818 			devtl.getPhaseRows(threadlist, devtl.rows)
3819 	devtl.calcTotalRows()
3820 
3821 	# draw the full timeline
3822 	devtl.createZoomBox(sysvals.suspendmode, len(testruns))
3823 	phases = {'suspend':[],'resume':[]}
3824 	for phase in data.dmesg:
3825 		if 'resume' in phase:
3826 			phases['resume'].append(phase)
3827 		else:
3828 			phases['suspend'].append(phase)
3829 
3830 	# draw each test run chronologically
3831 	for data in testruns:
3832 		# now draw the actual timeline blocks
3833 		for dir in phases:
3834 			# draw suspend and resume blocks separately
3835 			bname = '%s%d' % (dir[0], data.testnumber)
3836 			if dir == 'suspend':
3837 				m0 = data.start
3838 				mMax = data.tSuspended
3839 				left = '%f' % (((m0-t0)*100.0)/tTotal)
3840 			else:
3841 				m0 = data.tSuspended
3842 				mMax = data.end
3843 				# in an x2 run, remove any gap between blocks
3844 				if len(testruns) > 1 and data.testnumber == 0:
3845 					mMax = testruns[1].start
3846 				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
3847 			mTotal = mMax - m0
3848 			# if a timeline block is 0 length, skip altogether
3849 			if mTotal == 0:
3850 				continue
3851 			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
3852 			devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
3853 			for b in sorted(phases[dir]):
3854 				# draw the phase color background
3855 				phase = data.dmesg[b]
3856 				length = phase['end']-phase['start']
3857 				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
3858 				width = '%f' % ((length*100.0)/mTotal)
3859 				devtl.html += devtl.html_phase.format(left, width, \
3860 					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
3861 					data.dmesg[b]['color'], '')
3862 			for e in data.errorinfo[dir]:
3863 				# draw red lines for any kernel errors found
3864 				type, t, idx1, idx2 = e
3865 				id = '%d_%d' % (idx1, idx2)
3866 				right = '%f' % (((mMax-t)*100.0)/mTotal)
3867 				devtl.html += html_error.format(right, id, type)
3868 			for b in sorted(phases[dir]):
3869 				# draw the devices for this phase
3870 				phaselist = data.dmesg[b]['list']
3871 				for d in data.tdevlist[b]:
3872 					name = d
3873 					drv = ''
3874 					dev = phaselist[d]
3875 					xtraclass = ''
3876 					xtrainfo = ''
3877 					xtrastyle = ''
3878 					if 'htmlclass' in dev:
3879 						xtraclass = dev['htmlclass']
3880 					if 'color' in dev:
3881 						xtrastyle = 'background:%s;' % dev['color']
3882 					if(d in sysvals.devprops):
3883 						name = sysvals.devprops[d].altName(d)
3884 						xtraclass = sysvals.devprops[d].xtraClass()
3885 						xtrainfo = sysvals.devprops[d].xtraInfo()
3886 					elif xtraclass == ' kth':
3887 						xtrainfo = ' kernel_thread'
3888 					if('drv' in dev and dev['drv']):
3889 						drv = ' {%s}' % dev['drv']
3890 					rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
3891 					rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
3892 					top = '%.3f' % (rowtop + devtl.scaleH)
3893 					left = '%f' % (((dev['start']-m0)*100)/mTotal)
3894 					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
3895 					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
3896 					title = name+drv+xtrainfo+length
3897 					if sysvals.suspendmode == 'command':
3898 						title += sysvals.testcommand
3899 					elif xtraclass == ' ps':
3900 						if 'suspend' in b:
3901 							title += 'pre_suspend_process'
3902 						else:
3903 							title += 'post_resume_process'
3904 					else:
3905 						title += b
3906 					devtl.html += devtl.html_device.format(dev['id'], \
3907 						title, left, top, '%.3f'%rowheight, width, \
3908 						d+drv, xtraclass, xtrastyle)
3909 					if('cpuexec' in dev):
3910 						for t in sorted(dev['cpuexec']):
3911 							start, end = t
3912 							j = float(dev['cpuexec'][t]) / 5
3913 							if j > 1.0:
3914 								j = 1.0
3915 							height = '%.3f' % (rowheight/3)
3916 							top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
3917 							left = '%f' % (((start-m0)*100)/mTotal)
3918 							width = '%f' % ((end-start)*100/mTotal)
3919 							color = 'rgba(255, 0, 0, %f)' % j
3920 							devtl.html += \
3921 								html_cpuexec.format(left, top, height, width, color)
3922 					if('src' not in dev):
3923 						continue
3924 					# draw any trace events for this device
3925 					for e in dev['src']:
3926 						height = '%.3f' % devtl.rowH
3927 						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
3928 						left = '%f' % (((e.time-m0)*100)/mTotal)
3929 						width = '%f' % (e.length*100/mTotal)
3930 						xtrastyle = ''
3931 						if e.color:
3932 							xtrastyle = 'background:%s;' % e.color
3933 						devtl.html += \
3934 							html_traceevent.format(e.title(), \
3935 								left, top, height, width, e.text(), '', xtrastyle)
3936 			# draw the time scale, try to make the number of labels readable
3937 			devtl.createTimeScale(m0, mMax, tTotal, dir)
3938 			devtl.html += '</div>\n'
3939 
3940 	# timeline is finished
3941 	devtl.html += '</div>\n</div>\n'
3942 
3943 	# draw a legend which describes the phases by color
3944 	if sysvals.suspendmode != 'command':
3945 		data = testruns[-1]
3946 		devtl.html += '<div class="legend">\n'
3947 		pdelta = 100.0/len(data.phases)
3948 		pmargin = pdelta / 4.0
3949 		for phase in data.phases:
3950 			tmp = phase.split('_')
3951 			id = tmp[0][0]
3952 			if(len(tmp) > 1):
3953 				id += tmp[1][0]
3954 			order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3955 			name = string.replace(phase, '_', ' &nbsp;')
3956 			devtl.html += devtl.html_legend.format(order, \
3957 				data.dmesg[phase]['color'], name, id)
3958 		devtl.html += '</div>\n'
3959 
3960 	hf = open(sysvals.htmlfile, 'w')
3961 	addCSS(hf, sysvals, len(testruns), kerror)
3962 
3963 	# write the device timeline
3964 	hf.write(devtl.html)
3965 	hf.write('<div id="devicedetailtitle"></div>\n')
3966 	hf.write('<div id="devicedetail" style="display:none;">\n')
3967 	# draw the colored boxes for the device detail section
3968 	for data in testruns:
3969 		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
3970 		pscolor = 'linear-gradient(to top left, #ccc, #eee)'
3971 		hf.write(devtl.html_phaselet.format('pre_suspend_process', \
3972 			'0', '0', pscolor))
3973 		for b in data.phases:
3974 			phase = data.dmesg[b]
3975 			length = phase['end']-phase['start']
3976 			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
3977 			width = '%.3f' % ((length*100.0)/tTotal)
3978 			hf.write(devtl.html_phaselet.format(b, left, width, \
3979 				data.dmesg[b]['color']))
3980 		hf.write(devtl.html_phaselet.format('post_resume_process', \
3981 			'0', '0', pscolor))
3982 		if sysvals.suspendmode == 'command':
3983 			hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
3984 		hf.write('</div>\n')
3985 	hf.write('</div>\n')
3986 
3987 	# write the ftrace data (callgraph)
3988 	if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
3989 		data = testruns[sysvals.cgtest]
3990 	else:
3991 		data = testruns[-1]
3992 	if sysvals.usecallgraph:
3993 		addCallgraphs(sysvals, hf, data)
3994 
3995 	# add the test log as a hidden div
3996 	if sysvals.testlog and sysvals.logmsg:
3997 		hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
3998 	# add the dmesg log as a hidden div
3999 	if sysvals.dmesglog and sysvals.dmesgfile:
4000 		hf.write('<div id="dmesglog" style="display:none;">\n')
4001 		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4002 		for line in lf:
4003 			line = line.replace('<', '&lt').replace('>', '&gt')
4004 			hf.write(line)
4005 		lf.close()
4006 		hf.write('</div>\n')
4007 	# add the ftrace log as a hidden div
4008 	if sysvals.ftracelog and sysvals.ftracefile:
4009 		hf.write('<div id="ftracelog" style="display:none;">\n')
4010 		lf = sysvals.openlog(sysvals.ftracefile, 'r')
4011 		for line in lf:
4012 			hf.write(line)
4013 		lf.close()
4014 		hf.write('</div>\n')
4015 
4016 	# write the footer and close
4017 	addScriptCode(hf, testruns)
4018 	hf.write('</body>\n</html>\n')
4019 	hf.close()
4020 	return True
4021 
4022 def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4023 	kernel = sv.stamp['kernel']
4024 	host = sv.hostname[0].upper()+sv.hostname[1:]
4025 	mode = sv.suspendmode
4026 	if sv.suspendmode in suspendmodename:
4027 		mode = suspendmodename[sv.suspendmode]
4028 	title = host+' '+mode+' '+kernel
4029 
4030 	# various format changes by flags
4031 	cgchk = 'checked'
4032 	cgnchk = 'not(:checked)'
4033 	if sv.cgexp:
4034 		cgchk = 'not(:checked)'
4035 		cgnchk = 'checked'
4036 
4037 	hoverZ = 'z-index:8;'
4038 	if sv.usedevsrc:
4039 		hoverZ = ''
4040 
4041 	devlistpos = 'absolute'
4042 	if testcount > 1:
4043 		devlistpos = 'relative'
4044 
4045 	scaleTH = 20
4046 	if kerror:
4047 		scaleTH = 60
4048 
4049 	# write the html header first (html head, css code, up to body start)
4050 	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4051 	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4052 	<title>'+title+'</title>\n\
4053 	<style type=\'text/css\'>\n\
4054 		body {overflow-y:scroll;}\n\
4055 		.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4056 		.stamp.sysinfo {font:10px Arial;}\n\
4057 		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4058 		.callgraph article * {padding-left:28px;}\n\
4059 		h1 {color:black;font:bold 30px Times;}\n\
4060 		t0 {color:black;font:bold 30px Times;}\n\
4061 		t1 {color:black;font:30px Times;}\n\
4062 		t2 {color:black;font:25px Times;}\n\
4063 		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
4064 		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4065 		cS {font:bold 13px Times;}\n\
4066 		table {width:100%;}\n\
4067 		.gray {background:rgba(80,80,80,0.1);}\n\
4068 		.green {background:rgba(204,255,204,0.4);}\n\
4069 		.purple {background:rgba(128,0,128,0.2);}\n\
4070 		.yellow {background:rgba(255,255,204,0.4);}\n\
4071 		.blue {background:rgba(169,208,245,0.4);}\n\
4072 		.time1 {font:22px Arial;border:1px solid;}\n\
4073 		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4074 		.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
4075 		td {text-align:center;}\n\
4076 		r {color:#500000;font:15px Tahoma;}\n\
4077 		n {color:#505050;font:15px Tahoma;}\n\
4078 		.tdhl {color:red;}\n\
4079 		.hide {display:none;}\n\
4080 		.pf {display:none;}\n\
4081 		.pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4082 		.pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4083 		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4084 		.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4085 		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4086 		.thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\
4087 		.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4088 		.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4089 		.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4090 		.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4091 		.hover.sync {background:white;}\n\
4092 		.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4093 		.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4094 		.traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\
4095 		.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4096 		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4097 		.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4098 		.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4099 		.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4100 		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4101 		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4102 		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4103 		.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4104 		.devlist {position:'+devlistpos+';width:190px;}\n\
4105 		a:link {color:white;text-decoration:none;}\n\
4106 		a:visited {color:white;}\n\
4107 		a:hover {color:white;}\n\
4108 		a:active {color:white;}\n\
4109 		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4110 		#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4111 		.tblock {position:absolute;height:100%;background:#ddd;}\n\
4112 		.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4113 		.bg {z-index:1;}\n\
4114 '+extra+'\
4115 	</style>\n</head>\n<body>\n'
4116 	hf.write(html_header)
4117 
4118 # Function: addScriptCode
4119 # Description:
4120 #	 Adds the javascript code to the output html
4121 # Arguments:
4122 #	 hf: the open html file pointer
4123 #	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4124 def addScriptCode(hf, testruns):
4125 	t0 = testruns[0].start * 1000
4126 	tMax = testruns[-1].end * 1000
4127 	# create an array in javascript memory with the device details
4128 	detail = '	var devtable = [];\n'
4129 	for data in testruns:
4130 		topo = data.deviceTopology()
4131 		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
4132 	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
4133 	# add the code which will manipulate the data in the browser
4134 	script_code = \
4135 	'<script type="text/javascript">\n'+detail+\
4136 	'	var resolution = -1;\n'\
4137 	'	var dragval = [0, 0];\n'\
4138 	'	function redrawTimescale(t0, tMax, tS) {\n'\
4139 	'		var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4140 	'		var tTotal = tMax - t0;\n'\
4141 	'		var list = document.getElementsByClassName("tblock");\n'\
4142 	'		for (var i = 0; i < list.length; i++) {\n'\
4143 	'			var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4144 	'			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4145 	'			var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4146 	'			var mMax = m0 + mTotal;\n'\
4147 	'			var html = "";\n'\
4148 	'			var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4149 	'			if(divTotal > 1000) continue;\n'\
4150 	'			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4151 	'			var pos = 0.0, val = 0.0;\n'\
4152 	'			for (var j = 0; j < divTotal; j++) {\n'\
4153 	'				var htmlline = "";\n'\
4154 	'				var mode = list[i].id[5];\n'\
4155 	'				if(mode == "s") {\n'\
4156 	'					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4157 	'					val = (j-divTotal+1)*tS;\n'\
4158 	'					if(j == divTotal - 1)\n'\
4159 	'						htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S&rarr;</cS></div>\';\n'\
4160 	'					else\n'\
4161 	'						htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4162 	'				} else {\n'\
4163 	'					pos = 100 - (((j)*tS*100)/mTotal);\n'\
4164 	'					val = (j)*tS;\n'\
4165 	'					htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4166 	'					if(j == 0)\n'\
4167 	'						if(mode == "r")\n'\
4168 	'							htmlline = rline+"<cS>&larr;R</cS></div>";\n'\
4169 	'						else\n'\
4170 	'							htmlline = rline+"<cS>0ms</div>";\n'\
4171 	'				}\n'\
4172 	'				html += htmlline;\n'\
4173 	'			}\n'\
4174 	'			timescale.innerHTML = html;\n'\
4175 	'		}\n'\
4176 	'	}\n'\
4177 	'	function zoomTimeline() {\n'\
4178 	'		var dmesg = document.getElementById("dmesg");\n'\
4179 	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4180 	'		var left = zoombox.scrollLeft;\n'\
4181 	'		var val = parseFloat(dmesg.style.width);\n'\
4182 	'		var newval = 100;\n'\
4183 	'		var sh = window.outerWidth / 2;\n'\
4184 	'		if(this.id == "zoomin") {\n'\
4185 	'			newval = val * 1.2;\n'\
4186 	'			if(newval > 910034) newval = 910034;\n'\
4187 	'			dmesg.style.width = newval+"%";\n'\
4188 	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4189 	'		} else if (this.id == "zoomout") {\n'\
4190 	'			newval = val / 1.2;\n'\
4191 	'			if(newval < 100) newval = 100;\n'\
4192 	'			dmesg.style.width = newval+"%";\n'\
4193 	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4194 	'		} else {\n'\
4195 	'			zoombox.scrollLeft = 0;\n'\
4196 	'			dmesg.style.width = "100%";\n'\
4197 	'		}\n'\
4198 	'		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4199 	'		var t0 = bounds[0];\n'\
4200 	'		var tMax = bounds[1];\n'\
4201 	'		var tTotal = tMax - t0;\n'\
4202 	'		var wTotal = tTotal * 100.0 / newval;\n'\
4203 	'		var idx = 7*window.innerWidth/1100;\n'\
4204 	'		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4205 	'		if(i >= tS.length) i = tS.length - 1;\n'\
4206 	'		if(tS[i] == resolution) return;\n'\
4207 	'		resolution = tS[i];\n'\
4208 	'		redrawTimescale(t0, tMax, tS[i]);\n'\
4209 	'	}\n'\
4210 	'	function deviceName(title) {\n'\
4211 	'		var name = title.slice(0, title.indexOf(" ("));\n'\
4212 	'		return name;\n'\
4213 	'	}\n'\
4214 	'	function deviceHover() {\n'\
4215 	'		var name = deviceName(this.title);\n'\
4216 	'		var dmesg = document.getElementById("dmesg");\n'\
4217 	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4218 	'		var cpu = -1;\n'\
4219 	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4220 	'			cpu = parseInt(name.slice(7));\n'\
4221 	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4222 	'			cpu = parseInt(name.slice(8));\n'\
4223 	'		for (var i = 0; i < dev.length; i++) {\n'\
4224 	'			dname = deviceName(dev[i].title);\n'\
4225 	'			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4226 	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4227 	'				(name == dname))\n'\
4228 	'			{\n'\
4229 	'				dev[i].className = "hover "+cname;\n'\
4230 	'			} else {\n'\
4231 	'				dev[i].className = cname;\n'\
4232 	'			}\n'\
4233 	'		}\n'\
4234 	'	}\n'\
4235 	'	function deviceUnhover() {\n'\
4236 	'		var dmesg = document.getElementById("dmesg");\n'\
4237 	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4238 	'		for (var i = 0; i < dev.length; i++) {\n'\
4239 	'			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4240 	'		}\n'\
4241 	'	}\n'\
4242 	'	function deviceTitle(title, total, cpu) {\n'\
4243 	'		var prefix = "Total";\n'\
4244 	'		if(total.length > 3) {\n'\
4245 	'			prefix = "Average";\n'\
4246 	'			total[1] = (total[1]+total[3])/2;\n'\
4247 	'			total[2] = (total[2]+total[4])/2;\n'\
4248 	'		}\n'\
4249 	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
4250 	'		var name = deviceName(title);\n'\
4251 	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
4252 	'		var driver = "";\n'\
4253 	'		var tS = "<t2>(</t2>";\n'\
4254 	'		var tR = "<t2>)</t2>";\n'\
4255 	'		if(total[1] > 0)\n'\
4256 	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
4257 	'		if(total[2] > 0)\n'\
4258 	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
4259 	'		var s = title.indexOf("{");\n'\
4260 	'		var e = title.indexOf("}");\n'\
4261 	'		if((s >= 0) && (e >= 0))\n'\
4262 	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
4263 	'		if(total[1] > 0 && total[2] > 0)\n'\
4264 	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
4265 	'		else\n'\
4266 	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
4267 	'		return name;\n'\
4268 	'	}\n'\
4269 	'	function deviceDetail() {\n'\
4270 	'		var devinfo = document.getElementById("devicedetail");\n'\
4271 	'		devinfo.style.display = "block";\n'\
4272 	'		var name = deviceName(this.title);\n'\
4273 	'		var cpu = -1;\n'\
4274 	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4275 	'			cpu = parseInt(name.slice(7));\n'\
4276 	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4277 	'			cpu = parseInt(name.slice(8));\n'\
4278 	'		var dmesg = document.getElementById("dmesg");\n'\
4279 	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4280 	'		var idlist = [];\n'\
4281 	'		var pdata = [[]];\n'\
4282 	'		if(document.getElementById("devicedetail1"))\n'\
4283 	'			pdata = [[], []];\n'\
4284 	'		var pd = pdata[0];\n'\
4285 	'		var total = [0.0, 0.0, 0.0];\n'\
4286 	'		for (var i = 0; i < dev.length; i++) {\n'\
4287 	'			dname = deviceName(dev[i].title);\n'\
4288 	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4289 	'				(name == dname))\n'\
4290 	'			{\n'\
4291 	'				idlist[idlist.length] = dev[i].id;\n'\
4292 	'				var tidx = 1;\n'\
4293 	'				if(dev[i].id[0] == "a") {\n'\
4294 	'					pd = pdata[0];\n'\
4295 	'				} else {\n'\
4296 	'					if(pdata.length == 1) pdata[1] = [];\n'\
4297 	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
4298 	'					pd = pdata[1];\n'\
4299 	'					tidx = 3;\n'\
4300 	'				}\n'\
4301 	'				var info = dev[i].title.split(" ");\n'\
4302 	'				var pname = info[info.length-1];\n'\
4303 	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
4304 	'				total[0] += pd[pname];\n'\
4305 	'				if(pname.indexOf("suspend") >= 0)\n'\
4306 	'					total[tidx] += pd[pname];\n'\
4307 	'				else\n'\
4308 	'					total[tidx+1] += pd[pname];\n'\
4309 	'			}\n'\
4310 	'		}\n'\
4311 	'		var devname = deviceTitle(this.title, total, cpu);\n'\
4312 	'		var left = 0.0;\n'\
4313 	'		for (var t = 0; t < pdata.length; t++) {\n'\
4314 	'			pd = pdata[t];\n'\
4315 	'			devinfo = document.getElementById("devicedetail"+t);\n'\
4316 	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
4317 	'			for (var i = 0; i < phases.length; i++) {\n'\
4318 	'				if(phases[i].id in pd) {\n'\
4319 	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
4320 	'					var fs = 32;\n'\
4321 	'					if(w < 8) fs = 4*w | 0;\n'\
4322 	'					var fs2 = fs*3/4;\n'\
4323 	'					phases[i].style.width = w+"%";\n'\
4324 	'					phases[i].style.left = left+"%";\n'\
4325 	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
4326 	'					left += w;\n'\
4327 	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
4328 	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
4329 	'					phases[i].innerHTML = time+pname;\n'\
4330 	'				} else {\n'\
4331 	'					phases[i].style.width = "0%";\n'\
4332 	'					phases[i].style.left = left+"%";\n'\
4333 	'				}\n'\
4334 	'			}\n'\
4335 	'		}\n'\
4336 	'		if(typeof devstats !== \'undefined\')\n'\
4337 	'			callDetail(this.id, this.title);\n'\
4338 	'		var cglist = document.getElementById("callgraphs");\n'\
4339 	'		if(!cglist) return;\n'\
4340 	'		var cg = cglist.getElementsByClassName("atop");\n'\
4341 	'		if(cg.length < 10) return;\n'\
4342 	'		for (var i = 0; i < cg.length; i++) {\n'\
4343 	'			cgid = cg[i].id.split("x")[0]\n'\
4344 	'			if(idlist.indexOf(cgid) >= 0) {\n'\
4345 	'				cg[i].style.display = "block";\n'\
4346 	'			} else {\n'\
4347 	'				cg[i].style.display = "none";\n'\
4348 	'			}\n'\
4349 	'		}\n'\
4350 	'	}\n'\
4351 	'	function callDetail(devid, devtitle) {\n'\
4352 	'		if(!(devid in devstats) || devstats[devid].length < 1)\n'\
4353 	'			return;\n'\
4354 	'		var list = devstats[devid];\n'\
4355 	'		var tmp = devtitle.split(" ");\n'\
4356 	'		var name = tmp[0], phase = tmp[tmp.length-1];\n'\
4357 	'		var dd = document.getElementById(phase);\n'\
4358 	'		var total = parseFloat(tmp[1].slice(1));\n'\
4359 	'		var mlist = [];\n'\
4360 	'		var maxlen = 0;\n'\
4361 	'		var info = []\n'\
4362 	'		for(var i in list) {\n'\
4363 	'			if(list[i][0] == "@") {\n'\
4364 	'				info = list[i].split("|");\n'\
4365 	'				continue;\n'\
4366 	'			}\n'\
4367 	'			var tmp = list[i].split("|");\n'\
4368 	'			var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
4369 	'			var p = (t*100.0/total).toFixed(2);\n'\
4370 	'			mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
4371 	'			if(f.length > maxlen)\n'\
4372 	'				maxlen = f.length;\n'\
4373 	'		}\n'\
4374 	'		var pad = 5;\n'\
4375 	'		if(mlist.length == 0) pad = 30;\n'\
4376 	'		var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
4377 	'		if(info.length > 2)\n'\
4378 	'			html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
4379 	'		if(info.length > 3)\n'\
4380 	'			html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
4381 	'		if(info.length > 4)\n'\
4382 	'			html += ", return=<b>"+info[4]+"</b>";\n'\
4383 	'		html += "</t3></div>";\n'\
4384 	'		if(mlist.length > 0) {\n'\
4385 	'			html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
4386 	'			for(var i in mlist)\n'\
4387 	'				html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
4388 	'			html += "</tr><tr><th>Calls</th>";\n'\
4389 	'			for(var i in mlist)\n'\
4390 	'				html += "<td>"+mlist[i][1]+"</td>";\n'\
4391 	'			html += "</tr><tr><th>Time(ms)</th>";\n'\
4392 	'			for(var i in mlist)\n'\
4393 	'				html += "<td>"+mlist[i][2]+"</td>";\n'\
4394 	'			html += "</tr><tr><th>Percent</th>";\n'\
4395 	'			for(var i in mlist)\n'\
4396 	'				html += "<td>"+mlist[i][3]+"</td>";\n'\
4397 	'			html += "</tr></table>";\n'\
4398 	'		}\n'\
4399 	'		dd.innerHTML = html;\n'\
4400 	'		var height = (maxlen*5)+100;\n'\
4401 	'		dd.style.height = height+"px";\n'\
4402 	'		document.getElementById("devicedetail").style.height = height+"px";\n'\
4403 	'	}\n'\
4404 	'	function callSelect() {\n'\
4405 	'		var cglist = document.getElementById("callgraphs");\n'\
4406 	'		if(!cglist) return;\n'\
4407 	'		var cg = cglist.getElementsByClassName("atop");\n'\
4408 	'		for (var i = 0; i < cg.length; i++) {\n'\
4409 	'			if(this.id == cg[i].id) {\n'\
4410 	'				cg[i].style.display = "block";\n'\
4411 	'			} else {\n'\
4412 	'				cg[i].style.display = "none";\n'\
4413 	'			}\n'\
4414 	'		}\n'\
4415 	'	}\n'\
4416 	'	function devListWindow(e) {\n'\
4417 	'		var win = window.open();\n'\
4418 	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
4419 	'			"<style type=\\"text/css\\">"+\n'\
4420 	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
4421 	'			"</style>"\n'\
4422 	'		var dt = devtable[0];\n'\
4423 	'		if(e.target.id != "devlist1")\n'\
4424 	'			dt = devtable[1];\n'\
4425 	'		win.document.write(html+dt);\n'\
4426 	'	}\n'\
4427 	'	function errWindow() {\n'\
4428 	'		var range = this.id.split("_");\n'\
4429 	'		var idx1 = parseInt(range[0]);\n'\
4430 	'		var idx2 = parseInt(range[1]);\n'\
4431 	'		var win = window.open();\n'\
4432 	'		var log = document.getElementById("dmesglog");\n'\
4433 	'		var title = "<title>dmesg log</title>";\n'\
4434 	'		var text = log.innerHTML.split("\\n");\n'\
4435 	'		var html = "";\n'\
4436 	'		for(var i = 0; i < text.length; i++) {\n'\
4437 	'			if(i == idx1) {\n'\
4438 	'				html += "<e id=target>"+text[i]+"</e>\\n";\n'\
4439 	'			} else if(i > idx1 && i <= idx2) {\n'\
4440 	'				html += "<e>"+text[i]+"</e>\\n";\n'\
4441 	'			} else {\n'\
4442 	'				html += text[i]+"\\n";\n'\
4443 	'			}\n'\
4444 	'		}\n'\
4445 	'		win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
4446 	'		win.location.hash = "#target";\n'\
4447 	'		win.document.close();\n'\
4448 	'	}\n'\
4449 	'	function logWindow(e) {\n'\
4450 	'		var name = e.target.id.slice(4);\n'\
4451 	'		var win = window.open();\n'\
4452 	'		var log = document.getElementById(name+"log");\n'\
4453 	'		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
4454 	'		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4455 	'		win.document.close();\n'\
4456 	'	}\n'\
4457 	'	function onMouseDown(e) {\n'\
4458 	'		dragval[0] = e.clientX;\n'\
4459 	'		dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
4460 	'		document.onmousemove = onMouseMove;\n'\
4461 	'	}\n'\
4462 	'	function onMouseMove(e) {\n'\
4463 	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4464 	'		zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
4465 	'	}\n'\
4466 	'	function onMouseUp(e) {\n'\
4467 	'		document.onmousemove = null;\n'\
4468 	'	}\n'\
4469 	'	function onKeyPress(e) {\n'\
4470 	'		var c = e.charCode;\n'\
4471 	'		if(c != 42 && c != 43 && c != 45) return;\n'\
4472 	'		var click = document.createEvent("Events");\n'\
4473 	'		click.initEvent("click", true, false);\n'\
4474 	'		if(c == 43)  \n'\
4475 	'			document.getElementById("zoomin").dispatchEvent(click);\n'\
4476 	'		else if(c == 45)\n'\
4477 	'			document.getElementById("zoomout").dispatchEvent(click);\n'\
4478 	'		else if(c == 42)\n'\
4479 	'			document.getElementById("zoomdef").dispatchEvent(click);\n'\
4480 	'	}\n'\
4481 	'	window.addEventListener("resize", function () {zoomTimeline();});\n'\
4482 	'	window.addEventListener("load", function () {\n'\
4483 	'		var dmesg = document.getElementById("dmesg");\n'\
4484 	'		dmesg.style.width = "100%"\n'\
4485 	'		dmesg.onmousedown = onMouseDown;\n'\
4486 	'		document.onmouseup = onMouseUp;\n'\
4487 	'		document.onkeypress = onKeyPress;\n'\
4488 	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
4489 	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
4490 	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
4491 	'		var list = document.getElementsByClassName("err");\n'\
4492 	'		for (var i = 0; i < list.length; i++)\n'\
4493 	'			list[i].onclick = errWindow;\n'\
4494 	'		var list = document.getElementsByClassName("logbtn");\n'\
4495 	'		for (var i = 0; i < list.length; i++)\n'\
4496 	'			list[i].onclick = logWindow;\n'\
4497 	'		list = document.getElementsByClassName("devlist");\n'\
4498 	'		for (var i = 0; i < list.length; i++)\n'\
4499 	'			list[i].onclick = devListWindow;\n'\
4500 	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4501 	'		for (var i = 0; i < dev.length; i++) {\n'\
4502 	'			dev[i].onclick = deviceDetail;\n'\
4503 	'			dev[i].onmouseover = deviceHover;\n'\
4504 	'			dev[i].onmouseout = deviceUnhover;\n'\
4505 	'		}\n'\
4506 	'		var dev = dmesg.getElementsByClassName("srccall");\n'\
4507 	'		for (var i = 0; i < dev.length; i++)\n'\
4508 	'			dev[i].onclick = callSelect;\n'\
4509 	'		zoomTimeline();\n'\
4510 	'	});\n'\
4511 	'</script>\n'
4512 	hf.write(script_code);
4513 
4514 def setRuntimeSuspend(before=True):
4515 	global sysvals
4516 	sv = sysvals
4517 	if sv.rs == 0:
4518 		return
4519 	if before:
4520 		# runtime suspend disable or enable
4521 		if sv.rs > 0:
4522 			sv.rstgt, sv.rsval, sv.rsdir = 'on', 'auto', 'enabled'
4523 		else:
4524 			sv.rstgt, sv.rsval, sv.rsdir = 'auto', 'on', 'disabled'
4525 		print('CONFIGURING RUNTIME SUSPEND...')
4526 		sv.rslist = deviceInfo(sv.rstgt)
4527 		for i in sv.rslist:
4528 			sv.setVal(sv.rsval, i)
4529 		print('runtime suspend %s on all devices (%d changed)' % (sv.rsdir, len(sv.rslist)))
4530 		print('waiting 5 seconds...')
4531 		time.sleep(5)
4532 	else:
4533 		# runtime suspend re-enable or re-disable
4534 		for i in sv.rslist:
4535 			sv.setVal(sv.rstgt, i)
4536 		print('runtime suspend settings restored on %d devices' % len(sv.rslist))
4537 
4538 # Function: executeSuspend
4539 # Description:
4540 #	 Execute system suspend through the sysfs interface, then copy the output
4541 #	 dmesg and ftrace files to the test output directory.
4542 def executeSuspend():
4543 	pm = ProcessMonitor()
4544 	tp = sysvals.tpath
4545 	fwdata = []
4546 	# run these commands to prepare the system for suspend
4547 	if sysvals.display:
4548 		if sysvals.display > 0:
4549 			print('TURN DISPLAY ON')
4550 			call('xset -d :0.0 dpms force suspend', shell=True)
4551 			call('xset -d :0.0 dpms force on', shell=True)
4552 		else:
4553 			print('TURN DISPLAY OFF')
4554 			call('xset -d :0.0 dpms force suspend', shell=True)
4555 		time.sleep(1)
4556 	if sysvals.sync:
4557 		print('SYNCING FILESYSTEMS')
4558 		call('sync', shell=True)
4559 	# mark the start point in the kernel ring buffer just as we start
4560 	sysvals.initdmesg()
4561 	# start ftrace
4562 	if(sysvals.usecallgraph or sysvals.usetraceevents):
4563 		print('START TRACING')
4564 		sysvals.fsetVal('1', 'tracing_on')
4565 		if sysvals.useprocmon:
4566 			pm.start()
4567 	# execute however many s/r runs requested
4568 	for count in range(1,sysvals.execcount+1):
4569 		# x2delay in between test runs
4570 		if(count > 1 and sysvals.x2delay > 0):
4571 			sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
4572 			time.sleep(sysvals.x2delay/1000.0)
4573 			sysvals.fsetVal('WAIT END', 'trace_marker')
4574 		# start message
4575 		if sysvals.testcommand != '':
4576 			print('COMMAND START')
4577 		else:
4578 			if(sysvals.rtcwake):
4579 				print('SUSPEND START')
4580 			else:
4581 				print('SUSPEND START (press a key to resume)')
4582 		# set rtcwake
4583 		if(sysvals.rtcwake):
4584 			print('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
4585 			sysvals.rtcWakeAlarmOn()
4586 		# start of suspend trace marker
4587 		if(sysvals.usecallgraph or sysvals.usetraceevents):
4588 			sysvals.fsetVal('SUSPEND START', 'trace_marker')
4589 		# predelay delay
4590 		if(count == 1 and sysvals.predelay > 0):
4591 			sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
4592 			time.sleep(sysvals.predelay/1000.0)
4593 			sysvals.fsetVal('WAIT END', 'trace_marker')
4594 		# initiate suspend or command
4595 		if sysvals.testcommand != '':
4596 			call(sysvals.testcommand+' 2>&1', shell=True);
4597 		else:
4598 			mode = sysvals.suspendmode
4599 			if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
4600 				mode = 'mem'
4601 				pf = open(sysvals.mempowerfile, 'w')
4602 				pf.write(sysvals.memmode)
4603 				pf.close()
4604 			pf = open(sysvals.powerfile, 'w')
4605 			pf.write(mode)
4606 			# execution will pause here
4607 			try:
4608 				pf.close()
4609 			except:
4610 				pass
4611 		if(sysvals.rtcwake):
4612 			sysvals.rtcWakeAlarmOff()
4613 		# postdelay delay
4614 		if(count == sysvals.execcount and sysvals.postdelay > 0):
4615 			sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
4616 			time.sleep(sysvals.postdelay/1000.0)
4617 			sysvals.fsetVal('WAIT END', 'trace_marker')
4618 		# return from suspend
4619 		print('RESUME COMPLETE')
4620 		if(sysvals.usecallgraph or sysvals.usetraceevents):
4621 			sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
4622 		if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4623 			fwdata.append(getFPDT(False))
4624 	# stop ftrace
4625 	if(sysvals.usecallgraph or sysvals.usetraceevents):
4626 		if sysvals.useprocmon:
4627 			pm.stop()
4628 		sysvals.fsetVal('0', 'tracing_on')
4629 		print('CAPTURING TRACE')
4630 		op = sysvals.writeDatafileHeader(sysvals.ftracefile, fwdata)
4631 		fp = open(tp+'trace', 'r')
4632 		for line in fp:
4633 			op.write(line)
4634 		op.close()
4635 		sysvals.fsetVal('', 'trace')
4636 		devProps()
4637 	# grab a copy of the dmesg output
4638 	print('CAPTURING DMESG')
4639 	sysvals.getdmesg(fwdata)
4640 
4641 def readFile(file):
4642 	if os.path.islink(file):
4643 		return os.readlink(file).split('/')[-1]
4644 	else:
4645 		return sysvals.getVal(file).strip()
4646 
4647 # Function: ms2nice
4648 # Description:
4649 #	 Print out a very concise time string in minutes and seconds
4650 # Output:
4651 #	 The time string, e.g. "1901m16s"
4652 def ms2nice(val):
4653 	val = int(val)
4654 	h = val / 3600000
4655 	m = (val / 60000) % 60
4656 	s = (val / 1000) % 60
4657 	if h > 0:
4658 		return '%d:%02d:%02d' % (h, m, s)
4659 	if m > 0:
4660 		return '%02d:%02d' % (m, s)
4661 	return '%ds' % s
4662 
4663 def yesno(val):
4664 	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
4665 		'active':'A', 'suspended':'S', 'suspending':'S'}
4666 	if val not in list:
4667 		return ' '
4668 	return list[val]
4669 
4670 # Function: deviceInfo
4671 # Description:
4672 #	 Detect all the USB hosts and devices currently connected and add
4673 #	 a list of USB device names to sysvals for better timeline readability
4674 def deviceInfo(output=''):
4675 	if not output:
4676 		print('LEGEND')
4677 		print('---------------------------------------------------------------------------------------------')
4678 		print('  A = async/sync PM queue (A/S)               C = runtime active children')
4679 		print('  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)')
4680 		print('  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)')
4681 		print('  U = runtime usage count')
4682 		print('---------------------------------------------------------------------------------------------')
4683 		print('DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND')
4684 		print('---------------------------------------------------------------------------------------------')
4685 
4686 	res = []
4687 	tgtval = 'runtime_status'
4688 	lines = dict()
4689 	for dirname, dirnames, filenames in os.walk('/sys/devices'):
4690 		if(not re.match('.*/power', dirname) or
4691 			'control' not in filenames or
4692 			tgtval not in filenames):
4693 			continue
4694 		name = ''
4695 		dirname = dirname[:-6]
4696 		device = dirname.split('/')[-1]
4697 		power = dict()
4698 		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
4699 		# only list devices which support runtime suspend
4700 		if power[tgtval] not in ['active', 'suspended', 'suspending']:
4701 			continue
4702 		for i in ['product', 'driver', 'subsystem']:
4703 			file = '%s/%s' % (dirname, i)
4704 			if os.path.exists(file):
4705 				name = readFile(file)
4706 				break
4707 		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
4708 			'runtime_active_kids', 'runtime_active_time',
4709 			'runtime_suspended_time']:
4710 			if i in filenames:
4711 				power[i] = readFile('%s/power/%s' % (dirname, i))
4712 		if output:
4713 			if power['control'] == output:
4714 				res.append('%s/power/control' % dirname)
4715 			continue
4716 		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
4717 			(device[:26], name[:26],
4718 			yesno(power['async']), \
4719 			yesno(power['control']), \
4720 			yesno(power['runtime_status']), \
4721 			power['runtime_usage'], \
4722 			power['runtime_active_kids'], \
4723 			ms2nice(power['runtime_active_time']), \
4724 			ms2nice(power['runtime_suspended_time']))
4725 	for i in sorted(lines):
4726 		print lines[i]
4727 	return res
4728 
4729 # Function: devProps
4730 # Description:
4731 #	 Retrieve a list of properties for all devices in the trace log
4732 def devProps(data=0):
4733 	props = dict()
4734 
4735 	if data:
4736 		idx = data.index(': ') + 2
4737 		if idx >= len(data):
4738 			return
4739 		devlist = data[idx:].split(';')
4740 		for dev in devlist:
4741 			f = dev.split(',')
4742 			if len(f) < 3:
4743 				continue
4744 			dev = f[0]
4745 			props[dev] = DevProps()
4746 			props[dev].altname = f[1]
4747 			if int(f[2]):
4748 				props[dev].async = True
4749 			else:
4750 				props[dev].async = False
4751 			sysvals.devprops = props
4752 		if sysvals.suspendmode == 'command' and 'testcommandstring' in props:
4753 			sysvals.testcommand = props['testcommandstring'].altname
4754 		return
4755 
4756 	if(os.path.exists(sysvals.ftracefile) == False):
4757 		doError('%s does not exist' % sysvals.ftracefile)
4758 
4759 	# first get the list of devices we need properties for
4760 	msghead = 'Additional data added by AnalyzeSuspend'
4761 	alreadystamped = False
4762 	tp = TestProps()
4763 	tf = sysvals.openlog(sysvals.ftracefile, 'r')
4764 	for line in tf:
4765 		if msghead in line:
4766 			alreadystamped = True
4767 			continue
4768 		# determine the trace data type (required for further parsing)
4769 		m = re.match(sysvals.tracertypefmt, line)
4770 		if(m):
4771 			tp.setTracerType(m.group('t'))
4772 			continue
4773 		# parse only valid lines, if this is not one move on
4774 		m = re.match(tp.ftrace_line_fmt, line)
4775 		if(not m or 'device_pm_callback_start' not in line):
4776 			continue
4777 		m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
4778 		if(not m):
4779 			continue
4780 		dev = m.group('d')
4781 		if dev not in props:
4782 			props[dev] = DevProps()
4783 	tf.close()
4784 
4785 	if not alreadystamped and sysvals.suspendmode == 'command':
4786 		out = '#\n# '+msghead+'\n# Device Properties: '
4787 		out += 'testcommandstring,%s,0;' % (sysvals.testcommand)
4788 		with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4789 			fp.write(out+'\n')
4790 		sysvals.devprops = props
4791 		return
4792 
4793 	# now get the syspath for each of our target devices
4794 	for dirname, dirnames, filenames in os.walk('/sys/devices'):
4795 		if(re.match('.*/power', dirname) and 'async' in filenames):
4796 			dev = dirname.split('/')[-2]
4797 			if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
4798 				props[dev].syspath = dirname[:-6]
4799 
4800 	# now fill in the properties for our target devices
4801 	for dev in props:
4802 		dirname = props[dev].syspath
4803 		if not dirname or not os.path.exists(dirname):
4804 			continue
4805 		with open(dirname+'/power/async') as fp:
4806 			text = fp.read()
4807 			props[dev].async = False
4808 			if 'enabled' in text:
4809 				props[dev].async = True
4810 		fields = os.listdir(dirname)
4811 		if 'product' in fields:
4812 			with open(dirname+'/product') as fp:
4813 				props[dev].altname = fp.read()
4814 		elif 'name' in fields:
4815 			with open(dirname+'/name') as fp:
4816 				props[dev].altname = fp.read()
4817 		elif 'model' in fields:
4818 			with open(dirname+'/model') as fp:
4819 				props[dev].altname = fp.read()
4820 		elif 'description' in fields:
4821 			with open(dirname+'/description') as fp:
4822 				props[dev].altname = fp.read()
4823 		elif 'id' in fields:
4824 			with open(dirname+'/id') as fp:
4825 				props[dev].altname = fp.read()
4826 		elif 'idVendor' in fields and 'idProduct' in fields:
4827 			idv, idp = '', ''
4828 			with open(dirname+'/idVendor') as fp:
4829 				idv = fp.read().strip()
4830 			with open(dirname+'/idProduct') as fp:
4831 				idp = fp.read().strip()
4832 			props[dev].altname = '%s:%s' % (idv, idp)
4833 
4834 		if props[dev].altname:
4835 			out = props[dev].altname.strip().replace('\n', ' ')
4836 			out = out.replace(',', ' ')
4837 			out = out.replace(';', ' ')
4838 			props[dev].altname = out
4839 
4840 	# and now write the data to the ftrace file
4841 	if not alreadystamped:
4842 		out = '#\n# '+msghead+'\n# Device Properties: '
4843 		for dev in sorted(props):
4844 			out += props[dev].out(dev)
4845 		with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4846 			fp.write(out+'\n')
4847 
4848 	sysvals.devprops = props
4849 
4850 # Function: getModes
4851 # Description:
4852 #	 Determine the supported power modes on this system
4853 # Output:
4854 #	 A string list of the available modes
4855 def getModes():
4856 	modes = []
4857 	if(os.path.exists(sysvals.powerfile)):
4858 		fp = open(sysvals.powerfile, 'r')
4859 		modes = string.split(fp.read())
4860 		fp.close()
4861 	if(os.path.exists(sysvals.mempowerfile)):
4862 		deep = False
4863 		fp = open(sysvals.mempowerfile, 'r')
4864 		for m in string.split(fp.read()):
4865 			memmode = m.strip('[]')
4866 			if memmode == 'deep':
4867 				deep = True
4868 			else:
4869 				modes.append('mem-%s' % memmode)
4870 		fp.close()
4871 		if 'mem' in modes and not deep:
4872 			modes.remove('mem')
4873 	return modes
4874 
4875 # Function: dmidecode
4876 # Description:
4877 #	 Read the bios tables and pull out system info
4878 # Arguments:
4879 #	 mempath: /dev/mem or custom mem path
4880 #	 fatal: True to exit on error, False to return empty dict
4881 # Output:
4882 #	 A dict object with all available key/values
4883 def dmidecode(mempath, fatal=False):
4884 	out = dict()
4885 
4886 	# the list of values to retrieve, with hardcoded (type, idx)
4887 	info = {
4888 		'bios-vendor': (0, 4),
4889 		'bios-version': (0, 5),
4890 		'bios-release-date': (0, 8),
4891 		'system-manufacturer': (1, 4),
4892 		'system-product-name': (1, 5),
4893 		'system-version': (1, 6),
4894 		'system-serial-number': (1, 7),
4895 		'baseboard-manufacturer': (2, 4),
4896 		'baseboard-product-name': (2, 5),
4897 		'baseboard-version': (2, 6),
4898 		'baseboard-serial-number': (2, 7),
4899 		'chassis-manufacturer': (3, 4),
4900 		'chassis-type': (3, 5),
4901 		'chassis-version': (3, 6),
4902 		'chassis-serial-number': (3, 7),
4903 		'processor-manufacturer': (4, 7),
4904 		'processor-version': (4, 16),
4905 	}
4906 	if(not os.path.exists(mempath)):
4907 		if(fatal):
4908 			doError('file does not exist: %s' % mempath)
4909 		return out
4910 	if(not os.access(mempath, os.R_OK)):
4911 		if(fatal):
4912 			doError('file is not readable: %s' % mempath)
4913 		return out
4914 
4915 	# by default use legacy scan, but try to use EFI first
4916 	memaddr = 0xf0000
4917 	memsize = 0x10000
4918 	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
4919 		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
4920 			continue
4921 		fp = open(ep, 'r')
4922 		buf = fp.read()
4923 		fp.close()
4924 		i = buf.find('SMBIOS=')
4925 		if i >= 0:
4926 			try:
4927 				memaddr = int(buf[i+7:], 16)
4928 				memsize = 0x20
4929 			except:
4930 				continue
4931 
4932 	# read in the memory for scanning
4933 	fp = open(mempath, 'rb')
4934 	try:
4935 		fp.seek(memaddr)
4936 		buf = fp.read(memsize)
4937 	except:
4938 		if(fatal):
4939 			doError('DMI table is unreachable, sorry')
4940 		else:
4941 			return out
4942 	fp.close()
4943 
4944 	# search for either an SM table or DMI table
4945 	i = base = length = num = 0
4946 	while(i < memsize):
4947 		if buf[i:i+4] == '_SM_' and i < memsize - 16:
4948 			length = struct.unpack('H', buf[i+22:i+24])[0]
4949 			base, num = struct.unpack('IH', buf[i+24:i+30])
4950 			break
4951 		elif buf[i:i+5] == '_DMI_':
4952 			length = struct.unpack('H', buf[i+6:i+8])[0]
4953 			base, num = struct.unpack('IH', buf[i+8:i+14])
4954 			break
4955 		i += 16
4956 	if base == 0 and length == 0 and num == 0:
4957 		if(fatal):
4958 			doError('Neither SMBIOS nor DMI were found')
4959 		else:
4960 			return out
4961 
4962 	# read in the SM or DMI table
4963 	fp = open(mempath, 'rb')
4964 	try:
4965 		fp.seek(base)
4966 		buf = fp.read(length)
4967 	except:
4968 		if(fatal):
4969 			doError('DMI table is unreachable, sorry')
4970 		else:
4971 			return out
4972 	fp.close()
4973 
4974 	# scan the table for the values we want
4975 	count = i = 0
4976 	while(count < num and i <= len(buf) - 4):
4977 		type, size, handle = struct.unpack('BBH', buf[i:i+4])
4978 		n = i + size
4979 		while n < len(buf) - 1:
4980 			if 0 == struct.unpack('H', buf[n:n+2])[0]:
4981 				break
4982 			n += 1
4983 		data = buf[i+size:n+2].split('\0')
4984 		for name in info:
4985 			itype, idxadr = info[name]
4986 			if itype == type:
4987 				idx = struct.unpack('B', buf[i+idxadr])[0]
4988 				if idx > 0 and idx < len(data) - 1:
4989 					s = data[idx-1].strip()
4990 					if s and s.lower() != 'to be filled by o.e.m.':
4991 						out[name] = data[idx-1]
4992 		i = n + 2
4993 		count += 1
4994 	return out
4995 
4996 def getBattery():
4997 	p = '/sys/class/power_supply'
4998 	bat = dict()
4999 	for d in os.listdir(p):
5000 		type = sysvals.getVal(os.path.join(p, d, 'type')).strip().lower()
5001 		if type != 'battery':
5002 			continue
5003 		for v in ['status', 'energy_now', 'capacity_now']:
5004 			bat[v] = sysvals.getVal(os.path.join(p, d, v)).strip().lower()
5005 		break
5006 	ac = True
5007 	if 'status' in bat and 'discharging' in bat['status']:
5008 		ac = False
5009 	charge = 0
5010 	for v in ['energy_now', 'capacity_now']:
5011 		if v in bat and bat[v]:
5012 			charge = int(bat[v])
5013 	return (ac, charge)
5014 
5015 # Function: getFPDT
5016 # Description:
5017 #	 Read the acpi bios tables and pull out FPDT, the firmware data
5018 # Arguments:
5019 #	 output: True to output the info to stdout, False otherwise
5020 def getFPDT(output):
5021 	rectype = {}
5022 	rectype[0] = 'Firmware Basic Boot Performance Record'
5023 	rectype[1] = 'S3 Performance Table Record'
5024 	prectype = {}
5025 	prectype[0] = 'Basic S3 Resume Performance Record'
5026 	prectype[1] = 'Basic S3 Suspend Performance Record'
5027 
5028 	sysvals.rootCheck(True)
5029 	if(not os.path.exists(sysvals.fpdtpath)):
5030 		if(output):
5031 			doError('file does not exist: %s' % sysvals.fpdtpath)
5032 		return False
5033 	if(not os.access(sysvals.fpdtpath, os.R_OK)):
5034 		if(output):
5035 			doError('file is not readable: %s' % sysvals.fpdtpath)
5036 		return False
5037 	if(not os.path.exists(sysvals.mempath)):
5038 		if(output):
5039 			doError('file does not exist: %s' % sysvals.mempath)
5040 		return False
5041 	if(not os.access(sysvals.mempath, os.R_OK)):
5042 		if(output):
5043 			doError('file is not readable: %s' % sysvals.mempath)
5044 		return False
5045 
5046 	fp = open(sysvals.fpdtpath, 'rb')
5047 	buf = fp.read()
5048 	fp.close()
5049 
5050 	if(len(buf) < 36):
5051 		if(output):
5052 			doError('Invalid FPDT table data, should '+\
5053 				'be at least 36 bytes')
5054 		return False
5055 
5056 	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5057 	if(output):
5058 		print('')
5059 		print('Firmware Performance Data Table (%s)' % table[0])
5060 		print('                  Signature : %s' % table[0])
5061 		print('               Table Length : %u' % table[1])
5062 		print('                   Revision : %u' % table[2])
5063 		print('                   Checksum : 0x%x' % table[3])
5064 		print('                     OEM ID : %s' % table[4])
5065 		print('               OEM Table ID : %s' % table[5])
5066 		print('               OEM Revision : %u' % table[6])
5067 		print('                 Creator ID : %s' % table[7])
5068 		print('           Creator Revision : 0x%x' % table[8])
5069 		print('')
5070 
5071 	if(table[0] != 'FPDT'):
5072 		if(output):
5073 			doError('Invalid FPDT table')
5074 		return False
5075 	if(len(buf) <= 36):
5076 		return False
5077 	i = 0
5078 	fwData = [0, 0]
5079 	records = buf[36:]
5080 	fp = open(sysvals.mempath, 'rb')
5081 	while(i < len(records)):
5082 		header = struct.unpack('HBB', records[i:i+4])
5083 		if(header[0] not in rectype):
5084 			i += header[1]
5085 			continue
5086 		if(header[1] != 16):
5087 			i += header[1]
5088 			continue
5089 		addr = struct.unpack('Q', records[i+8:i+16])[0]
5090 		try:
5091 			fp.seek(addr)
5092 			first = fp.read(8)
5093 		except:
5094 			if(output):
5095 				print('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5096 			return [0, 0]
5097 		rechead = struct.unpack('4sI', first)
5098 		recdata = fp.read(rechead[1]-8)
5099 		if(rechead[0] == 'FBPT'):
5100 			record = struct.unpack('HBBIQQQQQ', recdata)
5101 			if(output):
5102 				print('%s (%s)' % (rectype[header[0]], rechead[0]))
5103 				print('                  Reset END : %u ns' % record[4])
5104 				print('  OS Loader LoadImage Start : %u ns' % record[5])
5105 				print(' OS Loader StartImage Start : %u ns' % record[6])
5106 				print('     ExitBootServices Entry : %u ns' % record[7])
5107 				print('      ExitBootServices Exit : %u ns' % record[8])
5108 		elif(rechead[0] == 'S3PT'):
5109 			if(output):
5110 				print('%s (%s)' % (rectype[header[0]], rechead[0]))
5111 			j = 0
5112 			while(j < len(recdata)):
5113 				prechead = struct.unpack('HBB', recdata[j:j+4])
5114 				if(prechead[0] not in prectype):
5115 					continue
5116 				if(prechead[0] == 0):
5117 					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5118 					fwData[1] = record[2]
5119 					if(output):
5120 						print('    %s' % prectype[prechead[0]])
5121 						print('               Resume Count : %u' % \
5122 							record[1])
5123 						print('                 FullResume : %u ns' % \
5124 							record[2])
5125 						print('              AverageResume : %u ns' % \
5126 							record[3])
5127 				elif(prechead[0] == 1):
5128 					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5129 					fwData[0] = record[1] - record[0]
5130 					if(output):
5131 						print('    %s' % prectype[prechead[0]])
5132 						print('               SuspendStart : %u ns' % \
5133 							record[0])
5134 						print('                 SuspendEnd : %u ns' % \
5135 							record[1])
5136 						print('                SuspendTime : %u ns' % \
5137 							fwData[0])
5138 				j += prechead[1]
5139 		if(output):
5140 			print('')
5141 		i += header[1]
5142 	fp.close()
5143 	return fwData
5144 
5145 # Function: statusCheck
5146 # Description:
5147 #	 Verify that the requested command and options will work, and
5148 #	 print the results to the terminal
5149 # Output:
5150 #	 True if the test will work, False if not
5151 def statusCheck(probecheck=False):
5152 	status = True
5153 
5154 	print('Checking this system (%s)...' % platform.node())
5155 
5156 	# check we have root access
5157 	res = sysvals.colorText('NO (No features of this tool will work!)')
5158 	if(sysvals.rootCheck(False)):
5159 		res = 'YES'
5160 	print('    have root access: %s' % res)
5161 	if(res != 'YES'):
5162 		print('    Try running this script with sudo')
5163 		return False
5164 
5165 	# check sysfs is mounted
5166 	res = sysvals.colorText('NO (No features of this tool will work!)')
5167 	if(os.path.exists(sysvals.powerfile)):
5168 		res = 'YES'
5169 	print('    is sysfs mounted: %s' % res)
5170 	if(res != 'YES'):
5171 		return False
5172 
5173 	# check target mode is a valid mode
5174 	if sysvals.suspendmode != 'command':
5175 		res = sysvals.colorText('NO')
5176 		modes = getModes()
5177 		if(sysvals.suspendmode in modes):
5178 			res = 'YES'
5179 		else:
5180 			status = False
5181 		print('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5182 		if(res == 'NO'):
5183 			print('      valid power modes are: %s' % modes)
5184 			print('      please choose one with -m')
5185 
5186 	# check if ftrace is available
5187 	res = sysvals.colorText('NO')
5188 	ftgood = sysvals.verifyFtrace()
5189 	if(ftgood):
5190 		res = 'YES'
5191 	elif(sysvals.usecallgraph):
5192 		status = False
5193 	print('    is ftrace supported: %s' % res)
5194 
5195 	# check if kprobes are available
5196 	res = sysvals.colorText('NO')
5197 	sysvals.usekprobes = sysvals.verifyKprobes()
5198 	if(sysvals.usekprobes):
5199 		res = 'YES'
5200 	else:
5201 		sysvals.usedevsrc = False
5202 	print('    are kprobes supported: %s' % res)
5203 
5204 	# what data source are we using
5205 	res = 'DMESG'
5206 	if(ftgood):
5207 		sysvals.usetraceevents = True
5208 		for e in sysvals.traceevents:
5209 			if not os.path.exists(sysvals.epath+e):
5210 				sysvals.usetraceevents = False
5211 		if(sysvals.usetraceevents):
5212 			res = 'FTRACE (all trace events found)'
5213 	print('    timeline data source: %s' % res)
5214 
5215 	# check if rtcwake
5216 	res = sysvals.colorText('NO')
5217 	if(sysvals.rtcpath != ''):
5218 		res = 'YES'
5219 	elif(sysvals.rtcwake):
5220 		status = False
5221 	print('    is rtcwake supported: %s' % res)
5222 
5223 	if not probecheck:
5224 		return status
5225 
5226 	# verify kprobes
5227 	if sysvals.usekprobes:
5228 		for name in sysvals.tracefuncs:
5229 			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5230 		if sysvals.usedevsrc:
5231 			for name in sysvals.dev_tracefuncs:
5232 				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5233 		sysvals.addKprobes(True)
5234 
5235 	return status
5236 
5237 # Function: doError
5238 # Description:
5239 #	 generic error function for catastrphic failures
5240 # Arguments:
5241 #	 msg: the error message to print
5242 #	 help: True if printHelp should be called after, False otherwise
5243 def doError(msg, help=False):
5244 	if(help == True):
5245 		printHelp()
5246 	print('ERROR: %s\n') % msg
5247 	sysvals.outputResult({'error':msg})
5248 	sys.exit()
5249 
5250 # Function: getArgInt
5251 # Description:
5252 #	 pull out an integer argument from the command line with checks
5253 def getArgInt(name, args, min, max, main=True):
5254 	if main:
5255 		try:
5256 			arg = args.next()
5257 		except:
5258 			doError(name+': no argument supplied', True)
5259 	else:
5260 		arg = args
5261 	try:
5262 		val = int(arg)
5263 	except:
5264 		doError(name+': non-integer value given', True)
5265 	if(val < min or val > max):
5266 		doError(name+': value should be between %d and %d' % (min, max), True)
5267 	return val
5268 
5269 # Function: getArgFloat
5270 # Description:
5271 #	 pull out a float argument from the command line with checks
5272 def getArgFloat(name, args, min, max, main=True):
5273 	if main:
5274 		try:
5275 			arg = args.next()
5276 		except:
5277 			doError(name+': no argument supplied', True)
5278 	else:
5279 		arg = args
5280 	try:
5281 		val = float(arg)
5282 	except:
5283 		doError(name+': non-numerical value given', True)
5284 	if(val < min or val > max):
5285 		doError(name+': value should be between %f and %f' % (min, max), True)
5286 	return val
5287 
5288 def processData(live=False):
5289 	print('PROCESSING DATA')
5290 	error = ''
5291 	if(sysvals.usetraceevents):
5292 		testruns, error = parseTraceLog(live)
5293 		if sysvals.dmesgfile:
5294 			for data in testruns:
5295 				data.extractErrorInfo()
5296 	else:
5297 		testruns = loadKernelLog()
5298 		for data in testruns:
5299 			parseKernelLog(data)
5300 		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5301 			appendIncompleteTraceLog(testruns)
5302 	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
5303 	for data in testruns:
5304 		data.printDetails()
5305 	if sysvals.cgdump:
5306 		for data in testruns:
5307 			data.debugPrint()
5308 		sys.exit()
5309 	if len(testruns) < 1:
5310 		return (testruns, {'error': 'timeline generation failed'})
5311 	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
5312 	createHTML(testruns, error)
5313 	print('DONE')
5314 	data = testruns[0]
5315 	stamp = data.stamp
5316 	stamp['suspend'], stamp['resume'] = data.getTimeValues()
5317 	if data.fwValid:
5318 		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
5319 	if error:
5320 		stamp['error'] = error
5321 	return (testruns, stamp)
5322 
5323 # Function: rerunTest
5324 # Description:
5325 #	 generate an output from an existing set of ftrace/dmesg logs
5326 def rerunTest():
5327 	if sysvals.ftracefile:
5328 		doesTraceLogHaveTraceEvents()
5329 	if not sysvals.dmesgfile and not sysvals.usetraceevents:
5330 		doError('recreating this html output requires a dmesg file')
5331 	sysvals.setOutputFile()
5332 	if os.path.exists(sysvals.htmlfile):
5333 		if not os.path.isfile(sysvals.htmlfile):
5334 			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
5335 		elif not os.access(sysvals.htmlfile, os.W_OK):
5336 			doError('missing permission to write to %s' % sysvals.htmlfile)
5337 	testruns, stamp = processData(False)
5338 	return stamp
5339 
5340 # Function: runTest
5341 # Description:
5342 #	 execute a suspend/resume, gather the logs, and generate the output
5343 def runTest(n=0):
5344 	# prepare for the test
5345 	sysvals.initFtrace()
5346 	sysvals.initTestOutput('suspend')
5347 
5348 	# execute the test
5349 	executeSuspend()
5350 	sysvals.cleanupFtrace()
5351 	if sysvals.skiphtml:
5352 		sysvals.sudouser(sysvals.testdir)
5353 		return
5354 	testruns, stamp = processData(True)
5355 	for data in testruns:
5356 		del data
5357 	sysvals.sudouser(sysvals.testdir)
5358 	sysvals.outputResult(stamp, n)
5359 
5360 def find_in_html(html, start, end, firstonly=True):
5361 	n, out = 0, []
5362 	while n < len(html):
5363 		m = re.search(start, html[n:])
5364 		if not m:
5365 			break
5366 		i = m.end()
5367 		m = re.search(end, html[n+i:])
5368 		if not m:
5369 			break
5370 		j = m.start()
5371 		str = html[n+i:n+i+j]
5372 		if end == 'ms':
5373 			num = re.search(r'[-+]?\d*\.\d+|\d+', str)
5374 			str = num.group() if num else 'NaN'
5375 		if firstonly:
5376 			return str
5377 		out.append(str)
5378 		n += i+j
5379 	if firstonly:
5380 		return ''
5381 	return out
5382 
5383 # Function: runSummary
5384 # Description:
5385 #	 create a summary of tests in a sub-directory
5386 def runSummary(subdir, local=True, genhtml=False):
5387 	inpath = os.path.abspath(subdir)
5388 	outpath = inpath
5389 	if local:
5390 		outpath = os.path.abspath('.')
5391 	print('Generating a summary of folder "%s"' % inpath)
5392 	if genhtml:
5393 		for dirname, dirnames, filenames in os.walk(subdir):
5394 			sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
5395 			for filename in filenames:
5396 				if(re.match('.*_dmesg.txt', filename)):
5397 					sysvals.dmesgfile = os.path.join(dirname, filename)
5398 				elif(re.match('.*_ftrace.txt', filename)):
5399 					sysvals.ftracefile = os.path.join(dirname, filename)
5400 			sysvals.setOutputFile()
5401 			if sysvals.ftracefile and sysvals.htmlfile and \
5402 				not os.path.exists(sysvals.htmlfile):
5403 				print('FTRACE: %s' % sysvals.ftracefile)
5404 				if sysvals.dmesgfile:
5405 					print('DMESG : %s' % sysvals.dmesgfile)
5406 				rerunTest()
5407 	testruns = []
5408 	for dirname, dirnames, filenames in os.walk(subdir):
5409 		for filename in filenames:
5410 			if(not re.match('.*.html', filename)):
5411 				continue
5412 			file = os.path.join(dirname, filename)
5413 			html = open(file, 'r').read()
5414 			suspend = find_in_html(html, 'Kernel Suspend', 'ms')
5415 			resume = find_in_html(html, 'Kernel Resume', 'ms')
5416 			line = find_in_html(html, '<div class="stamp">', '</div>')
5417 			stmp = line.split()
5418 			if not suspend or not resume or len(stmp) != 8:
5419 				continue
5420 			try:
5421 				dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
5422 			except:
5423 				continue
5424 			tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
5425 			error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
5426 			result = 'fail' if error else 'pass'
5427 			ilist = []
5428 			e = find_in_html(html, 'class="err"[\w=":;\.%\- ]*>', '&rarr;</div>', False)
5429 			for i in list(set(e)):
5430 				ilist.append('%sx%d' % (i, e.count(i)) if e.count(i) > 1 else i)
5431 			data = {
5432 				'mode': stmp[2],
5433 				'host': stmp[0],
5434 				'kernel': stmp[1],
5435 				'time': tstr,
5436 				'result': result,
5437 				'issues': ','.join(ilist),
5438 				'suspend': suspend,
5439 				'resume': resume,
5440 				'url': os.path.relpath(file, outpath),
5441 			}
5442 			testruns.append(data)
5443 	outfile = os.path.join(outpath, 'summary.html')
5444 	print('Summary file: %s' % outfile)
5445 	createHTMLSummarySimple(testruns, outfile, inpath)
5446 
5447 # Function: checkArgBool
5448 # Description:
5449 #	 check if a boolean string value is true or false
5450 def checkArgBool(name, value):
5451 	if value in switchvalues:
5452 		if value in switchoff:
5453 			return False
5454 		return True
5455 	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
5456 	return False
5457 
5458 # Function: configFromFile
5459 # Description:
5460 #	 Configure the script via the info in a config file
5461 def configFromFile(file):
5462 	Config = ConfigParser.ConfigParser()
5463 
5464 	Config.read(file)
5465 	sections = Config.sections()
5466 	overridekprobes = False
5467 	overridedevkprobes = False
5468 	if 'Settings' in sections:
5469 		for opt in Config.options('Settings'):
5470 			value = Config.get('Settings', opt).lower()
5471 			option = opt.lower()
5472 			if(option == 'verbose'):
5473 				sysvals.verbose = checkArgBool(option, value)
5474 			elif(option == 'addlogs'):
5475 				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
5476 			elif(option == 'dev'):
5477 				sysvals.usedevsrc = checkArgBool(option, value)
5478 			elif(option == 'proc'):
5479 				sysvals.useprocmon = checkArgBool(option, value)
5480 			elif(option == 'x2'):
5481 				if checkArgBool(option, value):
5482 					sysvals.execcount = 2
5483 			elif(option == 'callgraph'):
5484 				sysvals.usecallgraph = checkArgBool(option, value)
5485 			elif(option == 'override-timeline-functions'):
5486 				overridekprobes = checkArgBool(option, value)
5487 			elif(option == 'override-dev-timeline-functions'):
5488 				overridedevkprobes = checkArgBool(option, value)
5489 			elif(option == 'skiphtml'):
5490 				sysvals.skiphtml = checkArgBool(option, value)
5491 			elif(option == 'sync'):
5492 				sysvals.sync = checkArgBool(option, value)
5493 			elif(option == 'rs' or option == 'runtimesuspend'):
5494 				if value in switchvalues:
5495 					if value in switchoff:
5496 						sysvals.rs = -1
5497 					else:
5498 						sysvals.rs = 1
5499 				else:
5500 					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
5501 			elif(option == 'display'):
5502 				if value in switchvalues:
5503 					if value in switchoff:
5504 						sysvals.display = -1
5505 					else:
5506 						sysvals.display = 1
5507 				else:
5508 					doError('invalid value --> (%s: %s), use "on/off"' % (option, value), True)
5509 			elif(option == 'gzip'):
5510 				sysvals.gzip = checkArgBool(option, value)
5511 			elif(option == 'cgfilter'):
5512 				sysvals.setCallgraphFilter(value)
5513 			elif(option == 'cgskip'):
5514 				if value in switchoff:
5515 					sysvals.cgskip = ''
5516 				else:
5517 					sysvals.cgskip = sysvals.configFile(val)
5518 					if(not sysvals.cgskip):
5519 						doError('%s does not exist' % sysvals.cgskip)
5520 			elif(option == 'cgtest'):
5521 				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
5522 			elif(option == 'cgphase'):
5523 				d = Data(0)
5524 				if value not in d.phases:
5525 					doError('invalid phase --> (%s: %s), valid phases are %s'\
5526 						% (option, value, d.phases), True)
5527 				sysvals.cgphase = value
5528 			elif(option == 'fadd'):
5529 				file = sysvals.configFile(value)
5530 				if(not file):
5531 					doError('%s does not exist' % value)
5532 				sysvals.addFtraceFilterFunctions(file)
5533 			elif(option == 'result'):
5534 				sysvals.result = value
5535 			elif(option == 'multi'):
5536 				nums = value.split()
5537 				if len(nums) != 2:
5538 					doError('multi requires 2 integers (exec_count and delay)', True)
5539 				sysvals.multitest['run'] = True
5540 				sysvals.multitest['count'] = getArgInt('multi: n d (exec count)', nums[0], 2, 1000000, False)
5541 				sysvals.multitest['delay'] = getArgInt('multi: n d (delay between tests)', nums[1], 0, 3600, False)
5542 			elif(option == 'devicefilter'):
5543 				sysvals.setDeviceFilter(value)
5544 			elif(option == 'expandcg'):
5545 				sysvals.cgexp = checkArgBool(option, value)
5546 			elif(option == 'srgap'):
5547 				if checkArgBool(option, value):
5548 					sysvals.srgap = 5
5549 			elif(option == 'mode'):
5550 				sysvals.suspendmode = value
5551 			elif(option == 'command' or option == 'cmd'):
5552 				sysvals.testcommand = value
5553 			elif(option == 'x2delay'):
5554 				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
5555 			elif(option == 'predelay'):
5556 				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
5557 			elif(option == 'postdelay'):
5558 				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
5559 			elif(option == 'maxdepth'):
5560 				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
5561 			elif(option == 'rtcwake'):
5562 				if value in switchoff:
5563 					sysvals.rtcwake = False
5564 				else:
5565 					sysvals.rtcwake = True
5566 					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
5567 			elif(option == 'timeprec'):
5568 				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
5569 			elif(option == 'mindev'):
5570 				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
5571 			elif(option == 'callloop-maxgap'):
5572 				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
5573 			elif(option == 'callloop-maxlen'):
5574 				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
5575 			elif(option == 'mincg'):
5576 				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
5577 			elif(option == 'bufsize'):
5578 				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
5579 			elif(option == 'output-dir'):
5580 				sysvals.outdir = sysvals.setOutputFolder(value)
5581 
5582 	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
5583 		doError('No command supplied for mode "command"')
5584 
5585 	# compatibility errors
5586 	if sysvals.usedevsrc and sysvals.usecallgraph:
5587 		doError('-dev is not compatible with -f')
5588 	if sysvals.usecallgraph and sysvals.useprocmon:
5589 		doError('-proc is not compatible with -f')
5590 
5591 	if overridekprobes:
5592 		sysvals.tracefuncs = dict()
5593 	if overridedevkprobes:
5594 		sysvals.dev_tracefuncs = dict()
5595 
5596 	kprobes = dict()
5597 	kprobesec = 'dev_timeline_functions_'+platform.machine()
5598 	if kprobesec in sections:
5599 		for name in Config.options(kprobesec):
5600 			text = Config.get(kprobesec, name)
5601 			kprobes[name] = (text, True)
5602 	kprobesec = 'timeline_functions_'+platform.machine()
5603 	if kprobesec in sections:
5604 		for name in Config.options(kprobesec):
5605 			if name in kprobes:
5606 				doError('Duplicate timeline function found "%s"' % (name))
5607 			text = Config.get(kprobesec, name)
5608 			kprobes[name] = (text, False)
5609 
5610 	for name in kprobes:
5611 		function = name
5612 		format = name
5613 		color = ''
5614 		args = dict()
5615 		text, dev = kprobes[name]
5616 		data = text.split()
5617 		i = 0
5618 		for val in data:
5619 			# bracketted strings are special formatting, read them separately
5620 			if val[0] == '[' and val[-1] == ']':
5621 				for prop in val[1:-1].split(','):
5622 					p = prop.split('=')
5623 					if p[0] == 'color':
5624 						try:
5625 							color = int(p[1], 16)
5626 							color = '#'+p[1]
5627 						except:
5628 							color = p[1]
5629 				continue
5630 			# first real arg should be the format string
5631 			if i == 0:
5632 				format = val
5633 			# all other args are actual function args
5634 			else:
5635 				d = val.split('=')
5636 				args[d[0]] = d[1]
5637 			i += 1
5638 		if not function or not format:
5639 			doError('Invalid kprobe: %s' % name)
5640 		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
5641 			if arg not in args:
5642 				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
5643 		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
5644 			doError('Duplicate timeline function found "%s"' % (name))
5645 
5646 		kp = {
5647 			'name': name,
5648 			'func': function,
5649 			'format': format,
5650 			sysvals.archargs: args
5651 		}
5652 		if color:
5653 			kp['color'] = color
5654 		if dev:
5655 			sysvals.dev_tracefuncs[name] = kp
5656 		else:
5657 			sysvals.tracefuncs[name] = kp
5658 
5659 # Function: printHelp
5660 # Description:
5661 #	 print out the help text
5662 def printHelp():
5663 	print('')
5664 	print('%s v%s' % (sysvals.title, sysvals.version))
5665 	print('Usage: sudo sleepgraph <options> <commands>')
5666 	print('')
5667 	print('Description:')
5668 	print('  This tool is designed to assist kernel and OS developers in optimizing')
5669 	print('  their linux stack\'s suspend/resume time. Using a kernel image built')
5670 	print('  with a few extra options enabled, the tool will execute a suspend and')
5671 	print('  capture dmesg and ftrace data until resume is complete. This data is')
5672 	print('  transformed into a device timeline and an optional callgraph to give')
5673 	print('  a detailed view of which devices/subsystems are taking the most')
5674 	print('  time in suspend/resume.')
5675 	print('')
5676 	print('  If no specific command is given, the default behavior is to initiate')
5677 	print('  a suspend/resume and capture the dmesg/ftrace output as an html timeline.')
5678 	print('')
5679 	print('  Generates output files in subdirectory: suspend-yymmdd-HHMMSS')
5680 	print('   HTML output:                    <hostname>_<mode>.html')
5681 	print('   raw dmesg output:               <hostname>_<mode>_dmesg.txt')
5682 	print('   raw ftrace output:              <hostname>_<mode>_ftrace.txt')
5683 	print('')
5684 	print('Options:')
5685 	print('   -h           Print this help text')
5686 	print('   -v           Print the current tool version')
5687 	print('   -config fn   Pull arguments and config options from file fn')
5688 	print('   -verbose     Print extra information during execution and analysis')
5689 	print('   -m mode      Mode to initiate for suspend (default: %s)') % (sysvals.suspendmode)
5690 	print('   -o name      Overrides the output subdirectory name when running a new test')
5691 	print('                default: suspend-{date}-{time}')
5692 	print('   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)')
5693 	print('   -addlogs     Add the dmesg and ftrace logs to the html output')
5694 	print('   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)')
5695 	print('   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)')
5696 	print('   -result fn   Export a results table to a text file for parsing.')
5697 	print('  [testprep]')
5698 	print('   -sync        Sync the filesystems before starting the test')
5699 	print('   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test')
5700 	print('   -display on/off  Turn the display on or off for the test')
5701 	print('  [advanced]')
5702 	print('   -gzip        Gzip the trace and dmesg logs to save space')
5703 	print('   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"')
5704 	print('   -proc        Add usermode process info into the timeline (default: disabled)')
5705 	print('   -dev         Add kernel function calls and threads to the timeline (default: disabled)')
5706 	print('   -x2          Run two suspend/resumes back to back (default: disabled)')
5707 	print('   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)')
5708 	print('   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)')
5709 	print('   -postdelay t Include t ms delay after last resume (default: 0 ms)')
5710 	print('   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)')
5711 	print('   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
5712 	print('                be created in a new subdirectory with a summary page.')
5713 	print('  [debug]')
5714 	print('   -f           Use ftrace to create device callgraphs (default: disabled)')
5715 	print('   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)')
5716 	print('   -expandcg    pre-expand the callgraph data in the html output (default: disabled)')
5717 	print('   -fadd file   Add functions to be graphed in the timeline from a list in a text file')
5718 	print('   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names')
5719 	print('   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)')
5720 	print('   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)')
5721 	print('   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)')
5722 	print('   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
5723 	print('   -cgfilter S  Filter the callgraph output in the timeline')
5724 	print('   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)')
5725 	print('   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)')
5726 	print('')
5727 	print('Other commands:')
5728 	print('   -modes       List available suspend modes')
5729 	print('   -status      Test to see if the system is enabled to run this tool')
5730 	print('   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table')
5731 	print('   -battery     Print out battery info (if available)')
5732 	print('   -sysinfo     Print out system info extracted from BIOS')
5733 	print('   -devinfo     Print out the pm settings of all devices which support runtime suspend')
5734 	print('   -flist       Print the list of functions currently being captured in ftrace')
5735 	print('   -flistall    Print all functions capable of being captured in ftrace')
5736 	print('   -summary dir Create a summary of tests in this dir [-genhtml builds missing html]')
5737 	print('  [redo]')
5738 	print('   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)')
5739 	print('   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)')
5740 	print('')
5741 	return True
5742 
5743 # ----------------- MAIN --------------------
5744 # exec start (skipped if script is loaded as library)
5745 if __name__ == '__main__':
5746 	genhtml = False
5747 	cmd = ''
5748 	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-devinfo', '-status', '-battery']
5749 	if '-f' in sys.argv:
5750 		sysvals.cgskip = sysvals.configFile('cgskip.txt')
5751 	# loop through the command line arguments
5752 	args = iter(sys.argv[1:])
5753 	for arg in args:
5754 		if(arg == '-m'):
5755 			try:
5756 				val = args.next()
5757 			except:
5758 				doError('No mode supplied', True)
5759 			if val == 'command' and not sysvals.testcommand:
5760 				doError('No command supplied for mode "command"', True)
5761 			sysvals.suspendmode = val
5762 		elif(arg in simplecmds):
5763 			cmd = arg[1:]
5764 		elif(arg == '-h'):
5765 			printHelp()
5766 			sys.exit()
5767 		elif(arg == '-v'):
5768 			print("Version %s" % sysvals.version)
5769 			sys.exit()
5770 		elif(arg == '-x2'):
5771 			sysvals.execcount = 2
5772 		elif(arg == '-x2delay'):
5773 			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
5774 		elif(arg == '-predelay'):
5775 			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
5776 		elif(arg == '-postdelay'):
5777 			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
5778 		elif(arg == '-f'):
5779 			sysvals.usecallgraph = True
5780 		elif(arg == '-skiphtml'):
5781 			sysvals.skiphtml = True
5782 		elif(arg == '-cgdump'):
5783 			sysvals.cgdump = True
5784 		elif(arg == '-genhtml'):
5785 			genhtml = True
5786 		elif(arg == '-addlogs'):
5787 			sysvals.dmesglog = sysvals.ftracelog = True
5788 		elif(arg == '-verbose'):
5789 			sysvals.verbose = True
5790 		elif(arg == '-proc'):
5791 			sysvals.useprocmon = True
5792 		elif(arg == '-dev'):
5793 			sysvals.usedevsrc = True
5794 		elif(arg == '-sync'):
5795 			sysvals.sync = True
5796 		elif(arg == '-gzip'):
5797 			sysvals.gzip = True
5798 		elif(arg == '-rs'):
5799 			try:
5800 				val = args.next()
5801 			except:
5802 				doError('-rs requires "enable" or "disable"', True)
5803 			if val.lower() in switchvalues:
5804 				if val.lower() in switchoff:
5805 					sysvals.rs = -1
5806 				else:
5807 					sysvals.rs = 1
5808 			else:
5809 				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
5810 		elif(arg == '-display'):
5811 			try:
5812 				val = args.next()
5813 			except:
5814 				doError('-display requires "on" or "off"', True)
5815 			if val.lower() in switchvalues:
5816 				if val.lower() in switchoff:
5817 					sysvals.display = -1
5818 				else:
5819 					sysvals.display = 1
5820 			else:
5821 				doError('invalid option: %s, use "on/off"' % val, True)
5822 		elif(arg == '-maxdepth'):
5823 			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
5824 		elif(arg == '-rtcwake'):
5825 			try:
5826 				val = args.next()
5827 			except:
5828 				doError('No rtcwake time supplied', True)
5829 			if val.lower() in switchoff:
5830 				sysvals.rtcwake = False
5831 			else:
5832 				sysvals.rtcwake = True
5833 				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
5834 		elif(arg == '-timeprec'):
5835 			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
5836 		elif(arg == '-mindev'):
5837 			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
5838 		elif(arg == '-mincg'):
5839 			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
5840 		elif(arg == '-bufsize'):
5841 			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
5842 		elif(arg == '-cgtest'):
5843 			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
5844 		elif(arg == '-cgphase'):
5845 			try:
5846 				val = args.next()
5847 			except:
5848 				doError('No phase name supplied', True)
5849 			d = Data(0)
5850 			if val not in d.phases:
5851 				doError('invalid phase --> (%s: %s), valid phases are %s'\
5852 					% (arg, val, d.phases), True)
5853 			sysvals.cgphase = val
5854 		elif(arg == '-cgfilter'):
5855 			try:
5856 				val = args.next()
5857 			except:
5858 				doError('No callgraph functions supplied', True)
5859 			sysvals.setCallgraphFilter(val)
5860 		elif(arg == '-cgskip'):
5861 			try:
5862 				val = args.next()
5863 			except:
5864 				doError('No file supplied', True)
5865 			if val.lower() in switchoff:
5866 				sysvals.cgskip = ''
5867 			else:
5868 				sysvals.cgskip = sysvals.configFile(val)
5869 				if(not sysvals.cgskip):
5870 					doError('%s does not exist' % sysvals.cgskip)
5871 		elif(arg == '-callloop-maxgap'):
5872 			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
5873 		elif(arg == '-callloop-maxlen'):
5874 			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
5875 		elif(arg == '-cmd'):
5876 			try:
5877 				val = args.next()
5878 			except:
5879 				doError('No command string supplied', True)
5880 			sysvals.testcommand = val
5881 			sysvals.suspendmode = 'command'
5882 		elif(arg == '-expandcg'):
5883 			sysvals.cgexp = True
5884 		elif(arg == '-srgap'):
5885 			sysvals.srgap = 5
5886 		elif(arg == '-multi'):
5887 			sysvals.multitest['run'] = True
5888 			sysvals.multitest['count'] = getArgInt('-multi n d (exec count)', args, 2, 1000000)
5889 			sysvals.multitest['delay'] = getArgInt('-multi n d (delay between tests)', args, 0, 3600)
5890 		elif(arg == '-o'):
5891 			try:
5892 				val = args.next()
5893 			except:
5894 				doError('No subdirectory name supplied', True)
5895 			sysvals.outdir = sysvals.setOutputFolder(val)
5896 		elif(arg == '-config'):
5897 			try:
5898 				val = args.next()
5899 			except:
5900 				doError('No text file supplied', True)
5901 			file = sysvals.configFile(val)
5902 			if(not file):
5903 				doError('%s does not exist' % val)
5904 			configFromFile(file)
5905 		elif(arg == '-fadd'):
5906 			try:
5907 				val = args.next()
5908 			except:
5909 				doError('No text file supplied', True)
5910 			file = sysvals.configFile(val)
5911 			if(not file):
5912 				doError('%s does not exist' % val)
5913 			sysvals.addFtraceFilterFunctions(file)
5914 		elif(arg == '-dmesg'):
5915 			try:
5916 				val = args.next()
5917 			except:
5918 				doError('No dmesg file supplied', True)
5919 			sysvals.notestrun = True
5920 			sysvals.dmesgfile = val
5921 			if(os.path.exists(sysvals.dmesgfile) == False):
5922 				doError('%s does not exist' % sysvals.dmesgfile)
5923 		elif(arg == '-ftrace'):
5924 			try:
5925 				val = args.next()
5926 			except:
5927 				doError('No ftrace file supplied', True)
5928 			sysvals.notestrun = True
5929 			sysvals.ftracefile = val
5930 			if(os.path.exists(sysvals.ftracefile) == False):
5931 				doError('%s does not exist' % sysvals.ftracefile)
5932 		elif(arg == '-summary'):
5933 			try:
5934 				val = args.next()
5935 			except:
5936 				doError('No directory supplied', True)
5937 			cmd = 'summary'
5938 			sysvals.outdir = val
5939 			sysvals.notestrun = True
5940 			if(os.path.isdir(val) == False):
5941 				doError('%s is not accesible' % val)
5942 		elif(arg == '-filter'):
5943 			try:
5944 				val = args.next()
5945 			except:
5946 				doError('No devnames supplied', True)
5947 			sysvals.setDeviceFilter(val)
5948 		elif(arg == '-result'):
5949 			try:
5950 				val = args.next()
5951 			except:
5952 				doError('No result file supplied', True)
5953 			sysvals.result = val
5954 		else:
5955 			doError('Invalid argument: '+arg, True)
5956 
5957 	# compatibility errors
5958 	if(sysvals.usecallgraph and sysvals.usedevsrc):
5959 		doError('-dev is not compatible with -f')
5960 	if(sysvals.usecallgraph and sysvals.useprocmon):
5961 		doError('-proc is not compatible with -f')
5962 
5963 	if sysvals.usecallgraph and sysvals.cgskip:
5964 		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
5965 		sysvals.setCallgraphBlacklist(sysvals.cgskip)
5966 
5967 	# callgraph size cannot exceed device size
5968 	if sysvals.mincglen < sysvals.mindevlen:
5969 		sysvals.mincglen = sysvals.mindevlen
5970 
5971 	# remove existing buffers before calculating memory
5972 	if(sysvals.usecallgraph or sysvals.usedevsrc):
5973 		sysvals.fsetVal('16', 'buffer_size_kb')
5974 	sysvals.cpuInfo()
5975 
5976 	# just run a utility command and exit
5977 	if(cmd != ''):
5978 		if(cmd == 'status'):
5979 			statusCheck(True)
5980 		elif(cmd == 'fpdt'):
5981 			getFPDT(True)
5982 		elif(cmd == 'battery'):
5983 			print 'AC Connect: %s\nCharge: %d' % getBattery()
5984 		elif(cmd == 'sysinfo'):
5985 			sysvals.printSystemInfo(True)
5986 		elif(cmd == 'devinfo'):
5987 			deviceInfo()
5988 		elif(cmd == 'modes'):
5989 			print getModes()
5990 		elif(cmd == 'flist'):
5991 			sysvals.getFtraceFilterFunctions(True)
5992 		elif(cmd == 'flistall'):
5993 			sysvals.getFtraceFilterFunctions(False)
5994 		elif(cmd == 'summary'):
5995 			runSummary(sysvals.outdir, True, genhtml)
5996 		sys.exit()
5997 
5998 	# if instructed, re-analyze existing data files
5999 	if(sysvals.notestrun):
6000 		stamp = rerunTest()
6001 		sysvals.outputResult(stamp)
6002 		sys.exit()
6003 
6004 	# verify that we can run a test
6005 	if(not statusCheck()):
6006 		doError('Check FAILED, aborting the test run!')
6007 
6008 	# extract mem modes and convert
6009 	mode = sysvals.suspendmode
6010 	if 'mem' == mode[:3]:
6011 		if '-' in mode:
6012 			memmode = mode.split('-')[-1]
6013 		else:
6014 			memmode = 'deep'
6015 		if memmode == 'shallow':
6016 			mode = 'standby'
6017 		elif memmode ==  's2idle':
6018 			mode = 'freeze'
6019 		else:
6020 			mode = 'mem'
6021 		sysvals.memmode = memmode
6022 		sysvals.suspendmode = mode
6023 
6024 	sysvals.systemInfo(dmidecode(sysvals.mempath))
6025 
6026 	setRuntimeSuspend(True)
6027 	if sysvals.display:
6028 		call('xset -d :0.0 dpms 0 0 0', shell=True)
6029 		call('xset -d :0.0 s off', shell=True)
6030 	if sysvals.multitest['run']:
6031 		# run multiple tests in a separate subdirectory
6032 		if not sysvals.outdir:
6033 			s = 'suspend-x%d' % sysvals.multitest['count']
6034 			sysvals.outdir = datetime.now().strftime(s+'-%y%m%d-%H%M%S')
6035 		if not os.path.isdir(sysvals.outdir):
6036 			os.mkdir(sysvals.outdir)
6037 		for i in range(sysvals.multitest['count']):
6038 			if(i != 0):
6039 				print('Waiting %d seconds...' % (sysvals.multitest['delay']))
6040 				time.sleep(sysvals.multitest['delay'])
6041 			print('TEST (%d/%d) START' % (i+1, sysvals.multitest['count']))
6042 			fmt = 'suspend-%y%m%d-%H%M%S'
6043 			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
6044 			runTest(i+1)
6045 			print('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count']))
6046 			sysvals.logmsg = ''
6047 		if not sysvals.skiphtml:
6048 			runSummary(sysvals.outdir, False, False)
6049 		sysvals.sudouser(sysvals.outdir)
6050 	else:
6051 		if sysvals.outdir:
6052 			sysvals.testdir = sysvals.outdir
6053 		# run the test in the current directory
6054 		runTest()
6055 	if sysvals.display:
6056 		call('xset -d :0.0 s reset', shell=True)
6057 	setRuntimeSuspend(False)
6058