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