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