Linux Audio

Check our new training course

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