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