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/debug/tracing/'
124	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
125	epath = '/sys/kernel/debug/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', 'r')
5560		for line in fp:
5561			op.write(line)
5562		op.close()
5563		sv.fsetVal('', 'trace')
5564		sv.platforminfo(cmdafter)
5565
5566def readFile(file):
5567	if os.path.islink(file):
5568		return os.readlink(file).split('/')[-1]
5569	else:
5570		return sysvals.getVal(file).strip()
5571
5572# Function: ms2nice
5573# Description:
5574#	 Print out a very concise time string in minutes and seconds
5575# Output:
5576#	 The time string, e.g. "1901m16s"
5577def ms2nice(val):
5578	val = int(val)
5579	h = val // 3600000
5580	m = (val // 60000) % 60
5581	s = (val // 1000) % 60
5582	if h > 0:
5583		return '%d:%02d:%02d' % (h, m, s)
5584	if m > 0:
5585		return '%02d:%02d' % (m, s)
5586	return '%ds' % s
5587
5588def yesno(val):
5589	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5590		'active':'A', 'suspended':'S', 'suspending':'S'}
5591	if val not in list:
5592		return ' '
5593	return list[val]
5594
5595# Function: deviceInfo
5596# Description:
5597#	 Detect all the USB hosts and devices currently connected and add
5598#	 a list of USB device names to sysvals for better timeline readability
5599def deviceInfo(output=''):
5600	if not output:
5601		pprint('LEGEND\n'\
5602		'---------------------------------------------------------------------------------------------\n'\
5603		'  A = async/sync PM queue (A/S)               C = runtime active children\n'\
5604		'  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)\n'\
5605		'  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)\n'\
5606		'  U = runtime usage count\n'\
5607		'---------------------------------------------------------------------------------------------\n'\
5608		'DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND\n'\
5609		'---------------------------------------------------------------------------------------------')
5610
5611	res = []
5612	tgtval = 'runtime_status'
5613	lines = dict()
5614	for dirname, dirnames, filenames in os.walk('/sys/devices'):
5615		if(not re.match('.*/power', dirname) or
5616			'control' not in filenames or
5617			tgtval not in filenames):
5618			continue
5619		name = ''
5620		dirname = dirname[:-6]
5621		device = dirname.split('/')[-1]
5622		power = dict()
5623		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5624		# only list devices which support runtime suspend
5625		if power[tgtval] not in ['active', 'suspended', 'suspending']:
5626			continue
5627		for i in ['product', 'driver', 'subsystem']:
5628			file = '%s/%s' % (dirname, i)
5629			if os.path.exists(file):
5630				name = readFile(file)
5631				break
5632		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5633			'runtime_active_kids', 'runtime_active_time',
5634			'runtime_suspended_time']:
5635			if i in filenames:
5636				power[i] = readFile('%s/power/%s' % (dirname, i))
5637		if output:
5638			if power['control'] == output:
5639				res.append('%s/power/control' % dirname)
5640			continue
5641		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5642			(device[:26], name[:26],
5643			yesno(power['async']), \
5644			yesno(power['control']), \
5645			yesno(power['runtime_status']), \
5646			power['runtime_usage'], \
5647			power['runtime_active_kids'], \
5648			ms2nice(power['runtime_active_time']), \
5649			ms2nice(power['runtime_suspended_time']))
5650	for i in sorted(lines):
5651		print(lines[i])
5652	return res
5653
5654# Function: getModes
5655# Description:
5656#	 Determine the supported power modes on this system
5657# Output:
5658#	 A string list of the available modes
5659def getModes():
5660	modes = []
5661	if(os.path.exists(sysvals.powerfile)):
5662		fp = open(sysvals.powerfile, 'r')
5663		modes = fp.read().split()
5664		fp.close()
5665	if(os.path.exists(sysvals.mempowerfile)):
5666		deep = False
5667		fp = open(sysvals.mempowerfile, 'r')
5668		for m in fp.read().split():
5669			memmode = m.strip('[]')
5670			if memmode == 'deep':
5671				deep = True
5672			else:
5673				modes.append('mem-%s' % memmode)
5674		fp.close()
5675		if 'mem' in modes and not deep:
5676			modes.remove('mem')
5677	if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5678		fp = open(sysvals.diskpowerfile, 'r')
5679		for m in fp.read().split():
5680			modes.append('disk-%s' % m.strip('[]'))
5681		fp.close()
5682	return modes
5683
5684# Function: dmidecode
5685# Description:
5686#	 Read the bios tables and pull out system info
5687# Arguments:
5688#	 mempath: /dev/mem or custom mem path
5689#	 fatal: True to exit on error, False to return empty dict
5690# Output:
5691#	 A dict object with all available key/values
5692def dmidecode(mempath, fatal=False):
5693	out = dict()
5694
5695	# the list of values to retrieve, with hardcoded (type, idx)
5696	info = {
5697		'bios-vendor': (0, 4),
5698		'bios-version': (0, 5),
5699		'bios-release-date': (0, 8),
5700		'system-manufacturer': (1, 4),
5701		'system-product-name': (1, 5),
5702		'system-version': (1, 6),
5703		'system-serial-number': (1, 7),
5704		'baseboard-manufacturer': (2, 4),
5705		'baseboard-product-name': (2, 5),
5706		'baseboard-version': (2, 6),
5707		'baseboard-serial-number': (2, 7),
5708		'chassis-manufacturer': (3, 4),
5709		'chassis-type': (3, 5),
5710		'chassis-version': (3, 6),
5711		'chassis-serial-number': (3, 7),
5712		'processor-manufacturer': (4, 7),
5713		'processor-version': (4, 16),
5714	}
5715	if(not os.path.exists(mempath)):
5716		if(fatal):
5717			doError('file does not exist: %s' % mempath)
5718		return out
5719	if(not os.access(mempath, os.R_OK)):
5720		if(fatal):
5721			doError('file is not readable: %s' % mempath)
5722		return out
5723
5724	# by default use legacy scan, but try to use EFI first
5725	memaddr = 0xf0000
5726	memsize = 0x10000
5727	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5728		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5729			continue
5730		fp = open(ep, 'r')
5731		buf = fp.read()
5732		fp.close()
5733		i = buf.find('SMBIOS=')
5734		if i >= 0:
5735			try:
5736				memaddr = int(buf[i+7:], 16)
5737				memsize = 0x20
5738			except:
5739				continue
5740
5741	# read in the memory for scanning
5742	try:
5743		fp = open(mempath, 'rb')
5744		fp.seek(memaddr)
5745		buf = fp.read(memsize)
5746	except:
5747		if(fatal):
5748			doError('DMI table is unreachable, sorry')
5749		else:
5750			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5751			return out
5752	fp.close()
5753
5754	# search for either an SM table or DMI table
5755	i = base = length = num = 0
5756	while(i < memsize):
5757		if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5758			length = struct.unpack('H', buf[i+22:i+24])[0]
5759			base, num = struct.unpack('IH', buf[i+24:i+30])
5760			break
5761		elif buf[i:i+5] == b'_DMI_':
5762			length = struct.unpack('H', buf[i+6:i+8])[0]
5763			base, num = struct.unpack('IH', buf[i+8:i+14])
5764			break
5765		i += 16
5766	if base == 0 and length == 0 and num == 0:
5767		if(fatal):
5768			doError('Neither SMBIOS nor DMI were found')
5769		else:
5770			return out
5771
5772	# read in the SM or DMI table
5773	try:
5774		fp = open(mempath, 'rb')
5775		fp.seek(base)
5776		buf = fp.read(length)
5777	except:
5778		if(fatal):
5779			doError('DMI table is unreachable, sorry')
5780		else:
5781			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5782			return out
5783	fp.close()
5784
5785	# scan the table for the values we want
5786	count = i = 0
5787	while(count < num and i <= len(buf) - 4):
5788		type, size, handle = struct.unpack('BBH', buf[i:i+4])
5789		n = i + size
5790		while n < len(buf) - 1:
5791			if 0 == struct.unpack('H', buf[n:n+2])[0]:
5792				break
5793			n += 1
5794		data = buf[i+size:n+2].split(b'\0')
5795		for name in info:
5796			itype, idxadr = info[name]
5797			if itype == type:
5798				idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5799				if idx > 0 and idx < len(data) - 1:
5800					s = data[idx-1].decode('utf-8')
5801					if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5802						out[name] = s
5803		i = n + 2
5804		count += 1
5805	return out
5806
5807# Function: getFPDT
5808# Description:
5809#	 Read the acpi bios tables and pull out FPDT, the firmware data
5810# Arguments:
5811#	 output: True to output the info to stdout, False otherwise
5812def getFPDT(output):
5813	rectype = {}
5814	rectype[0] = 'Firmware Basic Boot Performance Record'
5815	rectype[1] = 'S3 Performance Table Record'
5816	prectype = {}
5817	prectype[0] = 'Basic S3 Resume Performance Record'
5818	prectype[1] = 'Basic S3 Suspend Performance Record'
5819
5820	sysvals.rootCheck(True)
5821	if(not os.path.exists(sysvals.fpdtpath)):
5822		if(output):
5823			doError('file does not exist: %s' % sysvals.fpdtpath)
5824		return False
5825	if(not os.access(sysvals.fpdtpath, os.R_OK)):
5826		if(output):
5827			doError('file is not readable: %s' % sysvals.fpdtpath)
5828		return False
5829	if(not os.path.exists(sysvals.mempath)):
5830		if(output):
5831			doError('file does not exist: %s' % sysvals.mempath)
5832		return False
5833	if(not os.access(sysvals.mempath, os.R_OK)):
5834		if(output):
5835			doError('file is not readable: %s' % sysvals.mempath)
5836		return False
5837
5838	fp = open(sysvals.fpdtpath, 'rb')
5839	buf = fp.read()
5840	fp.close()
5841
5842	if(len(buf) < 36):
5843		if(output):
5844			doError('Invalid FPDT table data, should '+\
5845				'be at least 36 bytes')
5846		return False
5847
5848	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5849	if(output):
5850		pprint('\n'\
5851		'Firmware Performance Data Table (%s)\n'\
5852		'                  Signature : %s\n'\
5853		'               Table Length : %u\n'\
5854		'                   Revision : %u\n'\
5855		'                   Checksum : 0x%x\n'\
5856		'                     OEM ID : %s\n'\
5857		'               OEM Table ID : %s\n'\
5858		'               OEM Revision : %u\n'\
5859		'                 Creator ID : %s\n'\
5860		'           Creator Revision : 0x%x\n'\
5861		'' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5862			table[3], ascii(table[4]), ascii(table[5]), table[6],
5863			ascii(table[7]), table[8]))
5864
5865	if(table[0] != b'FPDT'):
5866		if(output):
5867			doError('Invalid FPDT table')
5868		return False
5869	if(len(buf) <= 36):
5870		return False
5871	i = 0
5872	fwData = [0, 0]
5873	records = buf[36:]
5874	try:
5875		fp = open(sysvals.mempath, 'rb')
5876	except:
5877		pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5878		return False
5879	while(i < len(records)):
5880		header = struct.unpack('HBB', records[i:i+4])
5881		if(header[0] not in rectype):
5882			i += header[1]
5883			continue
5884		if(header[1] != 16):
5885			i += header[1]
5886			continue
5887		addr = struct.unpack('Q', records[i+8:i+16])[0]
5888		try:
5889			fp.seek(addr)
5890			first = fp.read(8)
5891		except:
5892			if(output):
5893				pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5894			return [0, 0]
5895		rechead = struct.unpack('4sI', first)
5896		recdata = fp.read(rechead[1]-8)
5897		if(rechead[0] == b'FBPT'):
5898			record = struct.unpack('HBBIQQQQQ', recdata[:48])
5899			if(output):
5900				pprint('%s (%s)\n'\
5901				'                  Reset END : %u ns\n'\
5902				'  OS Loader LoadImage Start : %u ns\n'\
5903				' OS Loader StartImage Start : %u ns\n'\
5904				'     ExitBootServices Entry : %u ns\n'\
5905				'      ExitBootServices Exit : %u ns'\
5906				'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5907					record[6], record[7], record[8]))
5908		elif(rechead[0] == b'S3PT'):
5909			if(output):
5910				pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5911			j = 0
5912			while(j < len(recdata)):
5913				prechead = struct.unpack('HBB', recdata[j:j+4])
5914				if(prechead[0] not in prectype):
5915					continue
5916				if(prechead[0] == 0):
5917					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5918					fwData[1] = record[2]
5919					if(output):
5920						pprint('    %s\n'\
5921						'               Resume Count : %u\n'\
5922						'                 FullResume : %u ns\n'\
5923						'              AverageResume : %u ns'\
5924						'' % (prectype[prechead[0]], record[1],
5925								record[2], record[3]))
5926				elif(prechead[0] == 1):
5927					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5928					fwData[0] = record[1] - record[0]
5929					if(output):
5930						pprint('    %s\n'\
5931						'               SuspendStart : %u ns\n'\
5932						'                 SuspendEnd : %u ns\n'\
5933						'                SuspendTime : %u ns'\
5934						'' % (prectype[prechead[0]], record[0],
5935								record[1], fwData[0]))
5936
5937				j += prechead[1]
5938		if(output):
5939			pprint('')
5940		i += header[1]
5941	fp.close()
5942	return fwData
5943
5944# Function: statusCheck
5945# Description:
5946#	 Verify that the requested command and options will work, and
5947#	 print the results to the terminal
5948# Output:
5949#	 True if the test will work, False if not
5950def statusCheck(probecheck=False):
5951	status = ''
5952
5953	pprint('Checking this system (%s)...' % platform.node())
5954
5955	# check we have root access
5956	res = sysvals.colorText('NO (No features of this tool will work!)')
5957	if(sysvals.rootCheck(False)):
5958		res = 'YES'
5959	pprint('    have root access: %s' % res)
5960	if(res != 'YES'):
5961		pprint('    Try running this script with sudo')
5962		return 'missing root access'
5963
5964	# check sysfs is mounted
5965	res = sysvals.colorText('NO (No features of this tool will work!)')
5966	if(os.path.exists(sysvals.powerfile)):
5967		res = 'YES'
5968	pprint('    is sysfs mounted: %s' % res)
5969	if(res != 'YES'):
5970		return 'sysfs is missing'
5971
5972	# check target mode is a valid mode
5973	if sysvals.suspendmode != 'command':
5974		res = sysvals.colorText('NO')
5975		modes = getModes()
5976		if(sysvals.suspendmode in modes):
5977			res = 'YES'
5978		else:
5979			status = '%s mode is not supported' % sysvals.suspendmode
5980		pprint('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5981		if(res == 'NO'):
5982			pprint('      valid power modes are: %s' % modes)
5983			pprint('      please choose one with -m')
5984
5985	# check if ftrace is available
5986	if sysvals.useftrace:
5987		res = sysvals.colorText('NO')
5988		sysvals.useftrace = sysvals.verifyFtrace()
5989		efmt = '"{0}" uses ftrace, and it is not properly supported'
5990		if sysvals.useftrace:
5991			res = 'YES'
5992		elif sysvals.usecallgraph:
5993			status = efmt.format('-f')
5994		elif sysvals.usedevsrc:
5995			status = efmt.format('-dev')
5996		elif sysvals.useprocmon:
5997			status = efmt.format('-proc')
5998		pprint('    is ftrace supported: %s' % res)
5999
6000	# check if kprobes are available
6001	if sysvals.usekprobes:
6002		res = sysvals.colorText('NO')
6003		sysvals.usekprobes = sysvals.verifyKprobes()
6004		if(sysvals.usekprobes):
6005			res = 'YES'
6006		else:
6007			sysvals.usedevsrc = False
6008		pprint('    are kprobes supported: %s' % res)
6009
6010	# what data source are we using
6011	res = 'DMESG (very limited, ftrace is preferred)'
6012	if sysvals.useftrace:
6013		sysvals.usetraceevents = True
6014		for e in sysvals.traceevents:
6015			if not os.path.exists(sysvals.epath+e):
6016				sysvals.usetraceevents = False
6017		if(sysvals.usetraceevents):
6018			res = 'FTRACE (all trace events found)'
6019	pprint('    timeline data source: %s' % res)
6020
6021	# check if rtcwake
6022	res = sysvals.colorText('NO')
6023	if(sysvals.rtcpath != ''):
6024		res = 'YES'
6025	elif(sysvals.rtcwake):
6026		status = 'rtcwake is not properly supported'
6027	pprint('    is rtcwake supported: %s' % res)
6028
6029	# check info commands
6030	pprint('    optional commands this tool may use for info:')
6031	no = sysvals.colorText('MISSING')
6032	yes = sysvals.colorText('FOUND', 32)
6033	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
6034		if c == 'turbostat':
6035			res = yes if sysvals.haveTurbostat() else no
6036		else:
6037			res = yes if sysvals.getExec(c) else no
6038		pprint('        %s: %s' % (c, res))
6039
6040	if not probecheck:
6041		return status
6042
6043	# verify kprobes
6044	if sysvals.usekprobes:
6045		for name in sysvals.tracefuncs:
6046			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
6047		if sysvals.usedevsrc:
6048			for name in sysvals.dev_tracefuncs:
6049				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
6050		sysvals.addKprobes(True)
6051
6052	return status
6053
6054# Function: doError
6055# Description:
6056#	 generic error function for catastrphic failures
6057# Arguments:
6058#	 msg: the error message to print
6059#	 help: True if printHelp should be called after, False otherwise
6060def doError(msg, help=False):
6061	if(help == True):
6062		printHelp()
6063	pprint('ERROR: %s\n' % msg)
6064	sysvals.outputResult({'error':msg})
6065	sys.exit(1)
6066
6067# Function: getArgInt
6068# Description:
6069#	 pull out an integer argument from the command line with checks
6070def getArgInt(name, args, min, max, main=True):
6071	if main:
6072		try:
6073			arg = next(args)
6074		except:
6075			doError(name+': no argument supplied', True)
6076	else:
6077		arg = args
6078	try:
6079		val = int(arg)
6080	except:
6081		doError(name+': non-integer value given', True)
6082	if(val < min or val > max):
6083		doError(name+': value should be between %d and %d' % (min, max), True)
6084	return val
6085
6086# Function: getArgFloat
6087# Description:
6088#	 pull out a float argument from the command line with checks
6089def getArgFloat(name, args, min, max, main=True):
6090	if main:
6091		try:
6092			arg = next(args)
6093		except:
6094			doError(name+': no argument supplied', True)
6095	else:
6096		arg = args
6097	try:
6098		val = float(arg)
6099	except:
6100		doError(name+': non-numerical value given', True)
6101	if(val < min or val > max):
6102		doError(name+': value should be between %f and %f' % (min, max), True)
6103	return val
6104
6105def processData(live=False, quiet=False):
6106	if not quiet:
6107		pprint('PROCESSING: %s' % sysvals.htmlfile)
6108	sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
6109		(sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
6110	error = ''
6111	if(sysvals.usetraceevents):
6112		testruns, error = parseTraceLog(live)
6113		if sysvals.dmesgfile:
6114			for data in testruns:
6115				data.extractErrorInfo()
6116	else:
6117		testruns = loadKernelLog()
6118		for data in testruns:
6119			parseKernelLog(data)
6120		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
6121			appendIncompleteTraceLog(testruns)
6122	if not sysvals.stamp:
6123		pprint('ERROR: data does not include the expected stamp')
6124		return (testruns, {'error': 'timeline generation failed'})
6125	shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
6126			'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
6127	sysvals.vprint('System Info:')
6128	for key in sorted(sysvals.stamp):
6129		if key in shown:
6130			sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key]))
6131	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
6132	for data in testruns:
6133		if data.turbostat:
6134			idx, s = 0, 'Turbostat:\n    '
6135			for val in data.turbostat.split('|'):
6136				idx += len(val) + 1
6137				if idx >= 80:
6138					idx = 0
6139					s += '\n    '
6140				s += val + ' '
6141			sysvals.vprint(s)
6142		data.printDetails()
6143	if len(sysvals.platinfo) > 0:
6144		sysvals.vprint('\nPlatform Info:')
6145		for info in sysvals.platinfo:
6146			sysvals.vprint('[%s - %s]' % (info[0], info[1]))
6147			sysvals.vprint(info[2])
6148		sysvals.vprint('')
6149	if sysvals.cgdump:
6150		for data in testruns:
6151			data.debugPrint()
6152		sys.exit(0)
6153	if len(testruns) < 1:
6154		pprint('ERROR: Not enough test data to build a timeline')
6155		return (testruns, {'error': 'timeline generation failed'})
6156	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6157	createHTML(testruns, error)
6158	if not quiet:
6159		pprint('DONE:       %s' % sysvals.htmlfile)
6160	data = testruns[0]
6161	stamp = data.stamp
6162	stamp['suspend'], stamp['resume'] = data.getTimeValues()
6163	if data.fwValid:
6164		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6165	if error:
6166		stamp['error'] = error
6167	return (testruns, stamp)
6168
6169# Function: rerunTest
6170# Description:
6171#	 generate an output from an existing set of ftrace/dmesg logs
6172def rerunTest(htmlfile=''):
6173	if sysvals.ftracefile:
6174		doesTraceLogHaveTraceEvents()
6175	if not sysvals.dmesgfile and not sysvals.usetraceevents:
6176		doError('recreating this html output requires a dmesg file')
6177	if htmlfile:
6178		sysvals.htmlfile = htmlfile
6179	else:
6180		sysvals.setOutputFile()
6181	if os.path.exists(sysvals.htmlfile):
6182		if not os.path.isfile(sysvals.htmlfile):
6183			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6184		elif not os.access(sysvals.htmlfile, os.W_OK):
6185			doError('missing permission to write to %s' % sysvals.htmlfile)
6186	testruns, stamp = processData()
6187	sysvals.resetlog()
6188	return stamp
6189
6190# Function: runTest
6191# Description:
6192#	 execute a suspend/resume, gather the logs, and generate the output
6193def runTest(n=0, quiet=False):
6194	# prepare for the test
6195	sysvals.initTestOutput('suspend')
6196	op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6197	op.write('# EXECUTION TRACE START\n')
6198	op.close()
6199	if n <= 1:
6200		if sysvals.rs != 0:
6201			sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6202			sysvals.setRuntimeSuspend(True)
6203		if sysvals.display:
6204			ret = sysvals.displayControl('init')
6205			sysvals.dlog('xset display init, ret = %d' % ret)
6206	sysvals.testVal(sysvals.pmdpath, 'basic', '1')
6207	sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
6208	sysvals.dlog('initialize ftrace')
6209	sysvals.initFtrace(quiet)
6210
6211	# execute the test
6212	executeSuspend(quiet)
6213	sysvals.cleanupFtrace()
6214	if sysvals.skiphtml:
6215		sysvals.outputResult({}, n)
6216		sysvals.sudoUserchown(sysvals.testdir)
6217		return
6218	testruns, stamp = processData(True, quiet)
6219	for data in testruns:
6220		del data
6221	sysvals.sudoUserchown(sysvals.testdir)
6222	sysvals.outputResult(stamp, n)
6223	if 'error' in stamp:
6224		return 2
6225	return 0
6226
6227def find_in_html(html, start, end, firstonly=True):
6228	cnt, out, list = len(html), [], []
6229	if firstonly:
6230		m = re.search(start, html)
6231		if m:
6232			list.append(m)
6233	else:
6234		list = re.finditer(start, html)
6235	for match in list:
6236		s = match.end()
6237		e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6238		m = re.search(end, html[s:e])
6239		if not m:
6240			break
6241		e = s + m.start()
6242		str = html[s:e]
6243		if end == 'ms':
6244			num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6245			str = num.group() if num else 'NaN'
6246		if firstonly:
6247			return str
6248		out.append(str)
6249	if firstonly:
6250		return ''
6251	return out
6252
6253def data_from_html(file, outpath, issues, fulldetail=False):
6254	html = open(file, 'r').read()
6255	sysvals.htmlfile = os.path.relpath(file, outpath)
6256	# extract general info
6257	suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6258	resume = find_in_html(html, 'Kernel Resume', 'ms')
6259	sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6260	line = find_in_html(html, '<div class="stamp">', '</div>')
6261	stmp = line.split()
6262	if not suspend or not resume or len(stmp) != 8:
6263		return False
6264	try:
6265		dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6266	except:
6267		return False
6268	sysvals.hostname = stmp[0]
6269	tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6270	error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6271	if error:
6272		m = re.match('[a-z0-9]* failed in (?P<p>\S*).*', error)
6273		if m:
6274			result = 'fail in %s' % m.group('p')
6275		else:
6276			result = 'fail'
6277	else:
6278		result = 'pass'
6279	# extract error info
6280	tp, ilist = False, []
6281	extra = dict()
6282	log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6283		'</div>').strip()
6284	if log:
6285		d = Data(0)
6286		d.end = 999999999
6287		d.dmesgtext = log.split('\n')
6288		tp = d.extractErrorInfo()
6289		for msg in tp.msglist:
6290			sysvals.errorSummary(issues, msg)
6291		if stmp[2] == 'freeze':
6292			extra = d.turbostatInfo()
6293		elist = dict()
6294		for dir in d.errorinfo:
6295			for err in d.errorinfo[dir]:
6296				if err[0] not in elist:
6297					elist[err[0]] = 0
6298				elist[err[0]] += 1
6299		for i in elist:
6300			ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6301		line = find_in_html(log, '# wifi ', '\n')
6302		if line:
6303			extra['wifi'] = line
6304		line = find_in_html(log, '# netfix ', '\n')
6305		if line:
6306			extra['netfix'] = line
6307	low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6308	for lowstr in ['waking', '+']:
6309		if not low:
6310			break
6311		if lowstr not in low:
6312			continue
6313		if lowstr == '+':
6314			issue = 'S2LOOPx%d' % len(low.split('+'))
6315		else:
6316			m = re.match('.*waking *(?P<n>[0-9]*) *times.*', low)
6317			issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6318		match = [i for i in issues if i['match'] == issue]
6319		if len(match) > 0:
6320			match[0]['count'] += 1
6321			if sysvals.hostname not in match[0]['urls']:
6322				match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6323			elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6324				match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6325		else:
6326			issues.append({
6327				'match': issue, 'count': 1, 'line': issue,
6328				'urls': {sysvals.hostname: [sysvals.htmlfile]},
6329			})
6330		ilist.append(issue)
6331	# extract device info
6332	devices = dict()
6333	for line in html.split('\n'):
6334		m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6335		if not m or 'thread kth' in line or 'thread sec' in line:
6336			continue
6337		m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6338		if not m:
6339			continue
6340		name, time, phase = m.group('n'), m.group('t'), m.group('p')
6341		if name == 'async_synchronize_full':
6342			continue
6343		if ' async' in name or ' sync' in name:
6344			name = ' '.join(name.split(' ')[:-1])
6345		if phase.startswith('suspend'):
6346			d = 'suspend'
6347		elif phase.startswith('resume'):
6348			d = 'resume'
6349		else:
6350			continue
6351		if d not in devices:
6352			devices[d] = dict()
6353		if name not in devices[d]:
6354			devices[d][name] = 0.0
6355		devices[d][name] += float(time)
6356	# create worst device info
6357	worst = dict()
6358	for d in ['suspend', 'resume']:
6359		worst[d] = {'name':'', 'time': 0.0}
6360		dev = devices[d] if d in devices else 0
6361		if dev and len(dev.keys()) > 0:
6362			n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6363			worst[d]['name'], worst[d]['time'] = n, dev[n]
6364	data = {
6365		'mode': stmp[2],
6366		'host': stmp[0],
6367		'kernel': stmp[1],
6368		'sysinfo': sysinfo,
6369		'time': tstr,
6370		'result': result,
6371		'issues': ' '.join(ilist),
6372		'suspend': suspend,
6373		'resume': resume,
6374		'devlist': devices,
6375		'sus_worst': worst['suspend']['name'],
6376		'sus_worsttime': worst['suspend']['time'],
6377		'res_worst': worst['resume']['name'],
6378		'res_worsttime': worst['resume']['time'],
6379		'url': sysvals.htmlfile,
6380	}
6381	for key in extra:
6382		data[key] = extra[key]
6383	if fulldetail:
6384		data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6385	if tp:
6386		for arg in ['-multi ', '-info ']:
6387			if arg in tp.cmdline:
6388				data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6389				break
6390	return data
6391
6392def genHtml(subdir, force=False):
6393	for dirname, dirnames, filenames in os.walk(subdir):
6394		sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6395		for filename in filenames:
6396			file = os.path.join(dirname, filename)
6397			if sysvals.usable(file):
6398				if(re.match('.*_dmesg.txt', filename)):
6399					sysvals.dmesgfile = file
6400				elif(re.match('.*_ftrace.txt', filename)):
6401					sysvals.ftracefile = file
6402		sysvals.setOutputFile()
6403		if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6404			(force or not sysvals.usable(sysvals.htmlfile, True)):
6405			pprint('FTRACE: %s' % sysvals.ftracefile)
6406			if sysvals.dmesgfile:
6407				pprint('DMESG : %s' % sysvals.dmesgfile)
6408			rerunTest()
6409
6410# Function: runSummary
6411# Description:
6412#	 create a summary of tests in a sub-directory
6413def runSummary(subdir, local=True, genhtml=False):
6414	inpath = os.path.abspath(subdir)
6415	outpath = os.path.abspath('.') if local else inpath
6416	pprint('Generating a summary of folder:\n   %s' % inpath)
6417	if genhtml:
6418		genHtml(subdir)
6419	target, issues, testruns = '', [], []
6420	desc = {'host':[],'mode':[],'kernel':[]}
6421	for dirname, dirnames, filenames in os.walk(subdir):
6422		for filename in filenames:
6423			if(not re.match('.*.html', filename)):
6424				continue
6425			data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6426			if(not data):
6427				continue
6428			if 'target' in data:
6429				target = data['target']
6430			testruns.append(data)
6431			for key in desc:
6432				if data[key] not in desc[key]:
6433					desc[key].append(data[key])
6434	pprint('Summary files:')
6435	if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6436		title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6437		if target:
6438			title += ' %s' % target
6439	else:
6440		title = inpath
6441	createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6442	pprint('   summary.html         - tabular list of test data found')
6443	createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6444	pprint('   summary-devices.html - kernel device list sorted by total execution time')
6445	createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6446	pprint('   summary-issues.html  - kernel issues found sorted by frequency')
6447
6448# Function: checkArgBool
6449# Description:
6450#	 check if a boolean string value is true or false
6451def checkArgBool(name, value):
6452	if value in switchvalues:
6453		if value in switchoff:
6454			return False
6455		return True
6456	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6457	return False
6458
6459# Function: configFromFile
6460# Description:
6461#	 Configure the script via the info in a config file
6462def configFromFile(file):
6463	Config = configparser.ConfigParser()
6464
6465	Config.read(file)
6466	sections = Config.sections()
6467	overridekprobes = False
6468	overridedevkprobes = False
6469	if 'Settings' in sections:
6470		for opt in Config.options('Settings'):
6471			value = Config.get('Settings', opt).lower()
6472			option = opt.lower()
6473			if(option == 'verbose'):
6474				sysvals.verbose = checkArgBool(option, value)
6475			elif(option == 'addlogs'):
6476				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6477			elif(option == 'dev'):
6478				sysvals.usedevsrc = checkArgBool(option, value)
6479			elif(option == 'proc'):
6480				sysvals.useprocmon = checkArgBool(option, value)
6481			elif(option == 'x2'):
6482				if checkArgBool(option, value):
6483					sysvals.execcount = 2
6484			elif(option == 'callgraph'):
6485				sysvals.usecallgraph = checkArgBool(option, value)
6486			elif(option == 'override-timeline-functions'):
6487				overridekprobes = checkArgBool(option, value)
6488			elif(option == 'override-dev-timeline-functions'):
6489				overridedevkprobes = checkArgBool(option, value)
6490			elif(option == 'skiphtml'):
6491				sysvals.skiphtml = checkArgBool(option, value)
6492			elif(option == 'sync'):
6493				sysvals.sync = checkArgBool(option, value)
6494			elif(option == 'rs' or option == 'runtimesuspend'):
6495				if value in switchvalues:
6496					if value in switchoff:
6497						sysvals.rs = -1
6498					else:
6499						sysvals.rs = 1
6500				else:
6501					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6502			elif(option == 'display'):
6503				disopt = ['on', 'off', 'standby', 'suspend']
6504				if value not in disopt:
6505					doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6506				sysvals.display = value
6507			elif(option == 'gzip'):
6508				sysvals.gzip = checkArgBool(option, value)
6509			elif(option == 'cgfilter'):
6510				sysvals.setCallgraphFilter(value)
6511			elif(option == 'cgskip'):
6512				if value in switchoff:
6513					sysvals.cgskip = ''
6514				else:
6515					sysvals.cgskip = sysvals.configFile(val)
6516					if(not sysvals.cgskip):
6517						doError('%s does not exist' % sysvals.cgskip)
6518			elif(option == 'cgtest'):
6519				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6520			elif(option == 'cgphase'):
6521				d = Data(0)
6522				if value not in d.phasedef:
6523					doError('invalid phase --> (%s: %s), valid phases are %s'\
6524						% (option, value, d.phasedef.keys()), True)
6525				sysvals.cgphase = value
6526			elif(option == 'fadd'):
6527				file = sysvals.configFile(value)
6528				if(not file):
6529					doError('%s does not exist' % value)
6530				sysvals.addFtraceFilterFunctions(file)
6531			elif(option == 'result'):
6532				sysvals.result = value
6533			elif(option == 'multi'):
6534				nums = value.split()
6535				if len(nums) != 2:
6536					doError('multi requires 2 integers (exec_count and delay)', True)
6537				sysvals.multiinit(nums[0], nums[1])
6538			elif(option == 'devicefilter'):
6539				sysvals.setDeviceFilter(value)
6540			elif(option == 'expandcg'):
6541				sysvals.cgexp = checkArgBool(option, value)
6542			elif(option == 'srgap'):
6543				if checkArgBool(option, value):
6544					sysvals.srgap = 5
6545			elif(option == 'mode'):
6546				sysvals.suspendmode = value
6547			elif(option == 'command' or option == 'cmd'):
6548				sysvals.testcommand = value
6549			elif(option == 'x2delay'):
6550				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6551			elif(option == 'predelay'):
6552				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6553			elif(option == 'postdelay'):
6554				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6555			elif(option == 'maxdepth'):
6556				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6557			elif(option == 'rtcwake'):
6558				if value in switchoff:
6559					sysvals.rtcwake = False
6560				else:
6561					sysvals.rtcwake = True
6562					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6563			elif(option == 'timeprec'):
6564				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6565			elif(option == 'mindev'):
6566				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6567			elif(option == 'callloop-maxgap'):
6568				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6569			elif(option == 'callloop-maxlen'):
6570				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6571			elif(option == 'mincg'):
6572				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6573			elif(option == 'bufsize'):
6574				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6575			elif(option == 'output-dir'):
6576				sysvals.outdir = sysvals.setOutputFolder(value)
6577
6578	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6579		doError('No command supplied for mode "command"')
6580
6581	# compatibility errors
6582	if sysvals.usedevsrc and sysvals.usecallgraph:
6583		doError('-dev is not compatible with -f')
6584	if sysvals.usecallgraph and sysvals.useprocmon:
6585		doError('-proc is not compatible with -f')
6586
6587	if overridekprobes:
6588		sysvals.tracefuncs = dict()
6589	if overridedevkprobes:
6590		sysvals.dev_tracefuncs = dict()
6591
6592	kprobes = dict()
6593	kprobesec = 'dev_timeline_functions_'+platform.machine()
6594	if kprobesec in sections:
6595		for name in Config.options(kprobesec):
6596			text = Config.get(kprobesec, name)
6597			kprobes[name] = (text, True)
6598	kprobesec = 'timeline_functions_'+platform.machine()
6599	if kprobesec in sections:
6600		for name in Config.options(kprobesec):
6601			if name in kprobes:
6602				doError('Duplicate timeline function found "%s"' % (name))
6603			text = Config.get(kprobesec, name)
6604			kprobes[name] = (text, False)
6605
6606	for name in kprobes:
6607		function = name
6608		format = name
6609		color = ''
6610		args = dict()
6611		text, dev = kprobes[name]
6612		data = text.split()
6613		i = 0
6614		for val in data:
6615			# bracketted strings are special formatting, read them separately
6616			if val[0] == '[' and val[-1] == ']':
6617				for prop in val[1:-1].split(','):
6618					p = prop.split('=')
6619					if p[0] == 'color':
6620						try:
6621							color = int(p[1], 16)
6622							color = '#'+p[1]
6623						except:
6624							color = p[1]
6625				continue
6626			# first real arg should be the format string
6627			if i == 0:
6628				format = val
6629			# all other args are actual function args
6630			else:
6631				d = val.split('=')
6632				args[d[0]] = d[1]
6633			i += 1
6634		if not function or not format:
6635			doError('Invalid kprobe: %s' % name)
6636		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6637			if arg not in args:
6638				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6639		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6640			doError('Duplicate timeline function found "%s"' % (name))
6641
6642		kp = {
6643			'name': name,
6644			'func': function,
6645			'format': format,
6646			sysvals.archargs: args
6647		}
6648		if color:
6649			kp['color'] = color
6650		if dev:
6651			sysvals.dev_tracefuncs[name] = kp
6652		else:
6653			sysvals.tracefuncs[name] = kp
6654
6655# Function: printHelp
6656# Description:
6657#	 print out the help text
6658def printHelp():
6659	pprint('\n%s v%s\n'\
6660	'Usage: sudo sleepgraph <options> <commands>\n'\
6661	'\n'\
6662	'Description:\n'\
6663	'  This tool is designed to assist kernel and OS developers in optimizing\n'\
6664	'  their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6665	'  with a few extra options enabled, the tool will execute a suspend and\n'\
6666	'  capture dmesg and ftrace data until resume is complete. This data is\n'\
6667	'  transformed into a device timeline and an optional callgraph to give\n'\
6668	'  a detailed view of which devices/subsystems are taking the most\n'\
6669	'  time in suspend/resume.\n'\
6670	'\n'\
6671	'  If no specific command is given, the default behavior is to initiate\n'\
6672	'  a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6673	'\n'\
6674	'  Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6675	'   HTML output:                    <hostname>_<mode>.html\n'\
6676	'   raw dmesg output:               <hostname>_<mode>_dmesg.txt\n'\
6677	'   raw ftrace output:              <hostname>_<mode>_ftrace.txt\n'\
6678	'\n'\
6679	'Options:\n'\
6680	'   -h           Print this help text\n'\
6681	'   -v           Print the current tool version\n'\
6682	'   -config fn   Pull arguments and config options from file fn\n'\
6683	'   -verbose     Print extra information during execution and analysis\n'\
6684	'   -m mode      Mode to initiate for suspend (default: %s)\n'\
6685	'   -o name      Overrides the output subdirectory name when running a new test\n'\
6686	'                default: suspend-{date}-{time}\n'\
6687	'   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6688	'   -addlogs     Add the dmesg and ftrace logs to the html output\n'\
6689	'   -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6690	'   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6691	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6692	'   -result fn   Export a results table to a text file for parsing.\n'\
6693	'   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
6694	'   -wifitrace   Trace kernel execution through wifi reconnect.\n'\
6695	'   -netfix      Use netfix to reset the network in the event it fails to resume.\n'\
6696	'  [testprep]\n'\
6697	'   -sync        Sync the filesystems before starting the test\n'\
6698	'   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
6699	'   -display m   Change the display mode to m for the test (on/off/standby/suspend)\n'\
6700	'  [advanced]\n'\
6701	'   -gzip        Gzip the trace and dmesg logs to save space\n'\
6702	'   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"\n'\
6703	'   -proc        Add usermode process info into the timeline (default: disabled)\n'\
6704	'   -dev         Add kernel function calls and threads to the timeline (default: disabled)\n'\
6705	'   -x2          Run two suspend/resumes back to back (default: disabled)\n'\
6706	'   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)\n'\
6707	'   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)\n'\
6708	'   -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6709	'   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6710	'   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6711	'                by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6712	'                The outputs will be created in a new subdirectory with a summary page.\n'\
6713	'   -maxfail n   Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6714	'  [debug]\n'\
6715	'   -f           Use ftrace to create device callgraphs (default: disabled)\n'\
6716	'   -ftop        Use ftrace on the top level call: "%s" (default: disabled)\n'\
6717	'   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)\n'\
6718	'   -expandcg    pre-expand the callgraph data in the html output (default: disabled)\n'\
6719	'   -fadd file   Add functions to be graphed in the timeline from a list in a text file\n'\
6720	'   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6721	'   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6722	'   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)\n'\
6723	'   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6724	'   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6725	'   -cgfilter S  Filter the callgraph output in the timeline\n'\
6726	'   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6727	'   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6728	'   -devdump     Print out all the raw device data for each phase\n'\
6729	'   -cgdump      Print out all the raw callgraph data\n'\
6730	'\n'\
6731	'Other commands:\n'\
6732	'   -modes       List available suspend modes\n'\
6733	'   -status      Test to see if the system is enabled to run this tool\n'\
6734	'   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table\n'\
6735	'   -wificheck   Print out wifi connection info\n'\
6736	'   -x<mode>     Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6737	'   -sysinfo     Print out system info extracted from BIOS\n'\
6738	'   -devinfo     Print out the pm settings of all devices which support runtime suspend\n'\
6739	'   -cmdinfo     Print out all the platform info collected before and after suspend/resume\n'\
6740	'   -flist       Print the list of functions currently being captured in ftrace\n'\
6741	'   -flistall    Print all functions capable of being captured in ftrace\n'\
6742	'   -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6743	'  [redo]\n'\
6744	'   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)\n'\
6745	'   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)\n'\
6746	'' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6747	return True
6748
6749# ----------------- MAIN --------------------
6750# exec start (skipped if script is loaded as library)
6751if __name__ == '__main__':
6752	genhtml = False
6753	cmd = ''
6754	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6755		'-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6756		'-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6757	if '-f' in sys.argv:
6758		sysvals.cgskip = sysvals.configFile('cgskip.txt')
6759	# loop through the command line arguments
6760	args = iter(sys.argv[1:])
6761	for arg in args:
6762		if(arg == '-m'):
6763			try:
6764				val = next(args)
6765			except:
6766				doError('No mode supplied', True)
6767			if val == 'command' and not sysvals.testcommand:
6768				doError('No command supplied for mode "command"', True)
6769			sysvals.suspendmode = val
6770		elif(arg in simplecmds):
6771			cmd = arg[1:]
6772		elif(arg == '-h'):
6773			printHelp()
6774			sys.exit(0)
6775		elif(arg == '-v'):
6776			pprint("Version %s" % sysvals.version)
6777			sys.exit(0)
6778		elif(arg == '-debugtiming'):
6779			debugtiming = True
6780		elif(arg == '-x2'):
6781			sysvals.execcount = 2
6782		elif(arg == '-x2delay'):
6783			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6784		elif(arg == '-predelay'):
6785			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6786		elif(arg == '-postdelay'):
6787			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6788		elif(arg == '-f'):
6789			sysvals.usecallgraph = True
6790		elif(arg == '-ftop'):
6791			sysvals.usecallgraph = True
6792			sysvals.ftop = True
6793			sysvals.usekprobes = False
6794		elif(arg == '-skiphtml'):
6795			sysvals.skiphtml = True
6796		elif(arg == '-cgdump'):
6797			sysvals.cgdump = True
6798		elif(arg == '-devdump'):
6799			sysvals.devdump = True
6800		elif(arg == '-genhtml'):
6801			genhtml = True
6802		elif(arg == '-addlogs'):
6803			sysvals.dmesglog = sysvals.ftracelog = True
6804		elif(arg == '-nologs'):
6805			sysvals.dmesglog = sysvals.ftracelog = False
6806		elif(arg == '-addlogdmesg'):
6807			sysvals.dmesglog = True
6808		elif(arg == '-addlogftrace'):
6809			sysvals.ftracelog = True
6810		elif(arg == '-noturbostat'):
6811			sysvals.tstat = False
6812		elif(arg == '-verbose'):
6813			sysvals.verbose = True
6814		elif(arg == '-proc'):
6815			sysvals.useprocmon = True
6816		elif(arg == '-dev'):
6817			sysvals.usedevsrc = True
6818		elif(arg == '-sync'):
6819			sysvals.sync = True
6820		elif(arg == '-wifi'):
6821			sysvals.wifi = True
6822		elif(arg == '-wifitrace'):
6823			sysvals.wifitrace = True
6824		elif(arg == '-netfix'):
6825			sysvals.netfix = True
6826		elif(arg == '-gzip'):
6827			sysvals.gzip = True
6828		elif(arg == '-info'):
6829			try:
6830				val = next(args)
6831			except:
6832				doError('-info requires one string argument', True)
6833		elif(arg == '-desc'):
6834			try:
6835				val = next(args)
6836			except:
6837				doError('-desc requires one string argument', True)
6838		elif(arg == '-rs'):
6839			try:
6840				val = next(args)
6841			except:
6842				doError('-rs requires "enable" or "disable"', True)
6843			if val.lower() in switchvalues:
6844				if val.lower() in switchoff:
6845					sysvals.rs = -1
6846				else:
6847					sysvals.rs = 1
6848			else:
6849				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6850		elif(arg == '-display'):
6851			try:
6852				val = next(args)
6853			except:
6854				doError('-display requires an mode value', True)
6855			disopt = ['on', 'off', 'standby', 'suspend']
6856			if val.lower() not in disopt:
6857				doError('valid display mode values are %s' % disopt, True)
6858			sysvals.display = val.lower()
6859		elif(arg == '-maxdepth'):
6860			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6861		elif(arg == '-rtcwake'):
6862			try:
6863				val = next(args)
6864			except:
6865				doError('No rtcwake time supplied', True)
6866			if val.lower() in switchoff:
6867				sysvals.rtcwake = False
6868			else:
6869				sysvals.rtcwake = True
6870				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6871		elif(arg == '-timeprec'):
6872			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6873		elif(arg == '-mindev'):
6874			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6875		elif(arg == '-mincg'):
6876			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6877		elif(arg == '-bufsize'):
6878			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6879		elif(arg == '-cgtest'):
6880			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6881		elif(arg == '-cgphase'):
6882			try:
6883				val = next(args)
6884			except:
6885				doError('No phase name supplied', True)
6886			d = Data(0)
6887			if val not in d.phasedef:
6888				doError('invalid phase --> (%s: %s), valid phases are %s'\
6889					% (arg, val, d.phasedef.keys()), True)
6890			sysvals.cgphase = val
6891		elif(arg == '-cgfilter'):
6892			try:
6893				val = next(args)
6894			except:
6895				doError('No callgraph functions supplied', True)
6896			sysvals.setCallgraphFilter(val)
6897		elif(arg == '-skipkprobe'):
6898			try:
6899				val = next(args)
6900			except:
6901				doError('No kprobe functions supplied', True)
6902			sysvals.skipKprobes(val)
6903		elif(arg == '-cgskip'):
6904			try:
6905				val = next(args)
6906			except:
6907				doError('No file supplied', True)
6908			if val.lower() in switchoff:
6909				sysvals.cgskip = ''
6910			else:
6911				sysvals.cgskip = sysvals.configFile(val)
6912				if(not sysvals.cgskip):
6913					doError('%s does not exist' % sysvals.cgskip)
6914		elif(arg == '-callloop-maxgap'):
6915			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6916		elif(arg == '-callloop-maxlen'):
6917			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6918		elif(arg == '-cmd'):
6919			try:
6920				val = next(args)
6921			except:
6922				doError('No command string supplied', True)
6923			sysvals.testcommand = val
6924			sysvals.suspendmode = 'command'
6925		elif(arg == '-expandcg'):
6926			sysvals.cgexp = True
6927		elif(arg == '-srgap'):
6928			sysvals.srgap = 5
6929		elif(arg == '-maxfail'):
6930			sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
6931		elif(arg == '-multi'):
6932			try:
6933				c, d = next(args), next(args)
6934			except:
6935				doError('-multi requires two values', True)
6936			sysvals.multiinit(c, d)
6937		elif(arg == '-o'):
6938			try:
6939				val = next(args)
6940			except:
6941				doError('No subdirectory name supplied', True)
6942			sysvals.outdir = sysvals.setOutputFolder(val)
6943		elif(arg == '-config'):
6944			try:
6945				val = next(args)
6946			except:
6947				doError('No text file supplied', True)
6948			file = sysvals.configFile(val)
6949			if(not file):
6950				doError('%s does not exist' % val)
6951			configFromFile(file)
6952		elif(arg == '-fadd'):
6953			try:
6954				val = next(args)
6955			except:
6956				doError('No text file supplied', True)
6957			file = sysvals.configFile(val)
6958			if(not file):
6959				doError('%s does not exist' % val)
6960			sysvals.addFtraceFilterFunctions(file)
6961		elif(arg == '-dmesg'):
6962			try:
6963				val = next(args)
6964			except:
6965				doError('No dmesg file supplied', True)
6966			sysvals.notestrun = True
6967			sysvals.dmesgfile = val
6968			if(os.path.exists(sysvals.dmesgfile) == False):
6969				doError('%s does not exist' % sysvals.dmesgfile)
6970		elif(arg == '-ftrace'):
6971			try:
6972				val = next(args)
6973			except:
6974				doError('No ftrace file supplied', True)
6975			sysvals.notestrun = True
6976			sysvals.ftracefile = val
6977			if(os.path.exists(sysvals.ftracefile) == False):
6978				doError('%s does not exist' % sysvals.ftracefile)
6979		elif(arg == '-summary'):
6980			try:
6981				val = next(args)
6982			except:
6983				doError('No directory supplied', True)
6984			cmd = 'summary'
6985			sysvals.outdir = val
6986			sysvals.notestrun = True
6987			if(os.path.isdir(val) == False):
6988				doError('%s is not accesible' % val)
6989		elif(arg == '-filter'):
6990			try:
6991				val = next(args)
6992			except:
6993				doError('No devnames supplied', True)
6994			sysvals.setDeviceFilter(val)
6995		elif(arg == '-result'):
6996			try:
6997				val = next(args)
6998			except:
6999				doError('No result file supplied', True)
7000			sysvals.result = val
7001			sysvals.signalHandlerInit()
7002		else:
7003			doError('Invalid argument: '+arg, True)
7004
7005	# compatibility errors
7006	if(sysvals.usecallgraph and sysvals.usedevsrc):
7007		doError('-dev is not compatible with -f')
7008	if(sysvals.usecallgraph and sysvals.useprocmon):
7009		doError('-proc is not compatible with -f')
7010
7011	if sysvals.usecallgraph and sysvals.cgskip:
7012		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
7013		sysvals.setCallgraphBlacklist(sysvals.cgskip)
7014
7015	# callgraph size cannot exceed device size
7016	if sysvals.mincglen < sysvals.mindevlen:
7017		sysvals.mincglen = sysvals.mindevlen
7018
7019	# remove existing buffers before calculating memory
7020	if(sysvals.usecallgraph or sysvals.usedevsrc):
7021		sysvals.fsetVal('16', 'buffer_size_kb')
7022	sysvals.cpuInfo()
7023
7024	# just run a utility command and exit
7025	if(cmd != ''):
7026		ret = 0
7027		if(cmd == 'status'):
7028			if not statusCheck(True):
7029				ret = 1
7030		elif(cmd == 'fpdt'):
7031			if not getFPDT(True):
7032				ret = 1
7033		elif(cmd == 'sysinfo'):
7034			sysvals.printSystemInfo(True)
7035		elif(cmd == 'devinfo'):
7036			deviceInfo()
7037		elif(cmd == 'modes'):
7038			pprint(getModes())
7039		elif(cmd == 'flist'):
7040			sysvals.getFtraceFilterFunctions(True)
7041		elif(cmd == 'flistall'):
7042			sysvals.getFtraceFilterFunctions(False)
7043		elif(cmd == 'summary'):
7044			runSummary(sysvals.outdir, True, genhtml)
7045		elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
7046			sysvals.verbose = True
7047			ret = sysvals.displayControl(cmd[1:])
7048		elif(cmd == 'xstat'):
7049			pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
7050		elif(cmd == 'wificheck'):
7051			dev = sysvals.checkWifi()
7052			if dev:
7053				print('%s is connected' % sysvals.wifiDetails(dev))
7054			else:
7055				print('No wifi connection found')
7056		elif(cmd == 'cmdinfo'):
7057			for out in sysvals.cmdinfo(False, True):
7058				print('[%s - %s]\n%s\n' % out)
7059		sys.exit(ret)
7060
7061	# if instructed, re-analyze existing data files
7062	if(sysvals.notestrun):
7063		stamp = rerunTest(sysvals.outdir)
7064		sysvals.outputResult(stamp)
7065		sys.exit(0)
7066
7067	# verify that we can run a test
7068	error = statusCheck()
7069	if(error):
7070		doError(error)
7071
7072	# extract mem/disk extra modes and convert
7073	mode = sysvals.suspendmode
7074	if mode.startswith('mem'):
7075		memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
7076		if memmode == 'shallow':
7077			mode = 'standby'
7078		elif memmode ==  's2idle':
7079			mode = 'freeze'
7080		else:
7081			mode = 'mem'
7082		sysvals.memmode = memmode
7083		sysvals.suspendmode = mode
7084	if mode.startswith('disk-'):
7085		sysvals.diskmode = mode.split('-', 1)[-1]
7086		sysvals.suspendmode = 'disk'
7087	sysvals.systemInfo(dmidecode(sysvals.mempath))
7088
7089	failcnt, ret = 0, 0
7090	if sysvals.multitest['run']:
7091		# run multiple tests in a separate subdirectory
7092		if not sysvals.outdir:
7093			if 'time' in sysvals.multitest:
7094				s = '-%dm' % sysvals.multitest['time']
7095			else:
7096				s = '-x%d' % sysvals.multitest['count']
7097			sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
7098		if not os.path.isdir(sysvals.outdir):
7099			os.makedirs(sysvals.outdir)
7100		sysvals.sudoUserchown(sysvals.outdir)
7101		finish = datetime.now()
7102		if 'time' in sysvals.multitest:
7103			finish += timedelta(minutes=sysvals.multitest['time'])
7104		for i in range(sysvals.multitest['count']):
7105			sysvals.multistat(True, i, finish)
7106			if i != 0 and sysvals.multitest['delay'] > 0:
7107				pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
7108				time.sleep(sysvals.multitest['delay'])
7109			fmt = 'suspend-%y%m%d-%H%M%S'
7110			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
7111			ret = runTest(i+1, not sysvals.verbose)
7112			failcnt = 0 if not ret else failcnt + 1
7113			if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
7114				pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
7115				break
7116			sysvals.resetlog()
7117			sysvals.multistat(False, i, finish)
7118			if 'time' in sysvals.multitest and datetime.now() >= finish:
7119				break
7120		if not sysvals.skiphtml:
7121			runSummary(sysvals.outdir, False, False)
7122		sysvals.sudoUserchown(sysvals.outdir)
7123	else:
7124		if sysvals.outdir:
7125			sysvals.testdir = sysvals.outdir
7126		# run the test in the current directory
7127		ret = runTest()
7128
7129	# reset to default values after testing
7130	if sysvals.display:
7131		sysvals.displayControl('reset')
7132	if sysvals.rs != 0:
7133		sysvals.setRuntimeSuspend(False)
7134	sys.exit(ret)
7135