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