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