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