Linux Audio

Check our new training course

Loading...
v5.9
   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
 
 
  69def pprint(msg):
  70	print(msg)
 
 
 
  71	sys.stdout.flush()
  72
  73def ascii(text):
  74	return text.decode('ascii', 'ignore')
  75
  76# ----------------- CLASSES --------------------
  77
  78# Class: SystemValues
  79# Description:
  80#	 A global, single-instance container used to
  81#	 store system values and test parameters
  82class SystemValues:
  83	title = 'SleepGraph'
  84	version = '5.7'
  85	ansi = False
  86	rs = 0
  87	display = ''
  88	gzip = False
  89	sync = False
  90	wifi = False
 
  91	verbose = False
  92	testlog = True
  93	dmesglog = True
  94	ftracelog = False
 
  95	tstat = True
  96	mindevlen = 0.0
 
  97	mincglen = 0.0
  98	cgphase = ''
  99	cgtest = -1
 100	cgskip = ''
 101	maxfail = 0
 102	multitest = {'run': False, 'count': 1000000, 'delay': 0}
 103	max_graph_depth = 0
 104	callloopmaxgap = 0.0001
 105	callloopmaxlen = 0.005
 106	bufsize = 0
 107	cpucount = 0
 108	memtotal = 204800
 109	memfree = 204800
 
 110	srgap = 0
 111	cgexp = False
 112	testdir = ''
 113	outdir = ''
 114	tpath = '/sys/kernel/debug/tracing/'
 115	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
 116	epath = '/sys/kernel/debug/tracing/events/power/'
 117	pmdpath = '/sys/power/pm_debug_messages'
 
 
 
 118	traceevents = [
 119		'suspend_resume',
 120		'wakeup_source_activate',
 121		'wakeup_source_deactivate',
 122		'device_pm_callback_end',
 123		'device_pm_callback_start'
 124	]
 125	logmsg = ''
 126	testcommand = ''
 127	mempath = '/dev/mem'
 128	powerfile = '/sys/power/state'
 129	mempowerfile = '/sys/power/mem_sleep'
 130	diskpowerfile = '/sys/power/disk'
 131	suspendmode = 'mem'
 132	memmode = ''
 133	diskmode = ''
 134	hostname = 'localhost'
 135	prefix = 'test'
 136	teststamp = ''
 137	sysstamp = ''
 138	dmesgstart = 0.0
 139	dmesgfile = ''
 140	ftracefile = ''
 141	htmlfile = 'output.html'
 142	result = ''
 143	rtcwake = True
 144	rtcwaketime = 15
 145	rtcpath = ''
 146	devicefilter = []
 147	cgfilter = []
 148	stamp = 0
 149	execcount = 1
 150	x2delay = 0
 151	skiphtml = False
 152	usecallgraph = False
 153	ftopfunc = 'pm_suspend'
 154	ftop = False
 155	usetraceevents = False
 156	usetracemarkers = True
 
 157	usekprobes = True
 158	usedevsrc = False
 159	useprocmon = False
 160	notestrun = False
 161	cgdump = False
 162	devdump = False
 163	mixedphaseheight = True
 164	devprops = dict()
 
 165	platinfo = []
 166	predelay = 0
 167	postdelay = 0
 168	pmdebug = ''
 169	tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
 170	tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
 171	tracefuncs = {
 
 172		'sys_sync': {},
 173		'ksys_sync': {},
 174		'__pm_notifier_call_chain': {},
 175		'pm_prepare_console': {},
 176		'pm_notifier_call_chain': {},
 177		'freeze_processes': {},
 178		'freeze_kernel_threads': {},
 179		'pm_restrict_gfp_mask': {},
 180		'acpi_suspend_begin': {},
 181		'acpi_hibernation_begin': {},
 182		'acpi_hibernation_enter': {},
 183		'acpi_hibernation_leave': {},
 184		'acpi_pm_freeze': {},
 185		'acpi_pm_thaw': {},
 186		'acpi_s2idle_end': {},
 187		'acpi_s2idle_sync': {},
 188		'acpi_s2idle_begin': {},
 189		'acpi_s2idle_prepare': {},
 190		'acpi_s2idle_prepare_late': {},
 191		'acpi_s2idle_wake': {},
 192		'acpi_s2idle_wakeup': {},
 193		'acpi_s2idle_restore': {},
 194		'acpi_s2idle_restore_early': {},
 195		'hibernate_preallocate_memory': {},
 196		'create_basic_memory_bitmaps': {},
 197		'swsusp_write': {},
 198		'suspend_console': {},
 199		'acpi_pm_prepare': {},
 200		'syscore_suspend': {},
 201		'arch_enable_nonboot_cpus_end': {},
 202		'syscore_resume': {},
 203		'acpi_pm_finish': {},
 204		'resume_console': {},
 205		'acpi_pm_end': {},
 206		'pm_restore_gfp_mask': {},
 207		'thaw_processes': {},
 208		'pm_restore_console': {},
 209		'CPU_OFF': {
 210			'func':'_cpu_down',
 211			'args_x86_64': {'cpu':'%di:s32'},
 212			'format': 'CPU_OFF[{cpu}]'
 213		},
 214		'CPU_ON': {
 215			'func':'_cpu_up',
 216			'args_x86_64': {'cpu':'%di:s32'},
 217			'format': 'CPU_ON[{cpu}]'
 218		},
 219	}
 220	dev_tracefuncs = {
 221		# general wait/delay/sleep
 222		'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
 223		'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
 224		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
 225		'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
 
 
 
 
 226		'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
 227		'acpi_os_stall': {'ub': 1},
 228		'rt_mutex_slowlock': {'ub': 1},
 229		# ACPI
 230		'acpi_resume_power_resources': {},
 231		'acpi_ps_execute_method': { 'args_x86_64': {
 232			'fullpath':'+0(+40(%di)):string',
 233		}},
 234		# mei_me
 235		'mei_reset': {},
 236		# filesystem
 237		'ext4_sync_fs': {},
 238		# 80211
 239		'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 240		'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 241		'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
 242		'iwlagn_mac_start': {},
 243		'iwlagn_alloc_bcast_station': {},
 244		'iwl_trans_pcie_start_hw': {},
 245		'iwl_trans_pcie_start_fw': {},
 246		'iwl_run_init_ucode': {},
 247		'iwl_load_ucode_wait_alive': {},
 248		'iwl_alive_start': {},
 249		'iwlagn_mac_stop': {},
 250		'iwlagn_mac_suspend': {},
 251		'iwlagn_mac_resume': {},
 252		'iwlagn_mac_add_interface': {},
 253		'iwlagn_mac_remove_interface': {},
 254		'iwlagn_mac_change_interface': {},
 255		'iwlagn_mac_config': {},
 256		'iwlagn_configure_filter': {},
 257		'iwlagn_mac_hw_scan': {},
 258		'iwlagn_bss_info_changed': {},
 259		'iwlagn_mac_channel_switch': {},
 260		'iwlagn_mac_flush': {},
 261		# ATA
 262		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
 263		# i915
 264		'i915_gem_resume': {},
 265		'i915_restore_state': {},
 266		'intel_opregion_setup': {},
 267		'g4x_pre_enable_dp': {},
 268		'vlv_pre_enable_dp': {},
 269		'chv_pre_enable_dp': {},
 270		'g4x_enable_dp': {},
 271		'vlv_enable_dp': {},
 272		'intel_hpd_init': {},
 273		'intel_opregion_register': {},
 274		'intel_dp_detect': {},
 275		'intel_hdmi_detect': {},
 276		'intel_opregion_init': {},
 277		'intel_fbdev_set_suspend': {},
 278	}
 279	infocmds = [
 
 
 280		[0, 'kparams', 'cat', '/proc/cmdline'],
 281		[0, 'mcelog', 'mcelog'],
 282		[0, 'pcidevices', 'lspci', '-tv'],
 283		[0, 'usbdevices', 'lsusb', '-t'],
 
 
 
 
 
 284		[1, 'interrupts', 'cat', '/proc/interrupts'],
 285		[1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
 286		[2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
 287		[2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
 288		[2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
 289		[2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
 
 290	]
 291	cgblacklist = []
 292	kprobes = dict()
 293	timeformat = '%.3f'
 294	cmdline = '%s %s' % \
 295			(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
 296	sudouser = ''
 297	def __init__(self):
 298		self.archargs = 'args_'+platform.machine()
 299		self.hostname = platform.node()
 300		if(self.hostname == ''):
 301			self.hostname = 'localhost'
 302		rtc = "rtc0"
 303		if os.path.exists('/dev/rtc'):
 304			rtc = os.readlink('/dev/rtc')
 305		rtc = '/sys/class/rtc/'+rtc
 306		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 307			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 308			self.rtcpath = rtc
 309		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
 310			self.ansi = True
 311		self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
 312		if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
 313			os.environ['SUDO_USER']:
 314			self.sudouser = os.environ['SUDO_USER']
 315	def resetlog(self):
 316		self.logmsg = ''
 317		self.platinfo = []
 318	def vprint(self, msg):
 319		self.logmsg += msg+'\n'
 320		if self.verbose or msg.startswith('WARNING:'):
 321			pprint(msg)
 322	def signalHandler(self, signum, frame):
 323		if not self.result:
 324			return
 325		signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
 326		msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
 
 
 
 
 
 
 
 327		self.outputResult({'error':msg})
 
 328		sys.exit(3)
 329	def signalHandlerInit(self):
 330		capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
 331			'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM']
 332		self.signames = dict()
 333		for i in capture:
 334			s = 'SIG'+i
 335			try:
 336				signum = getattr(signal, s)
 337				signal.signal(signum, self.signalHandler)
 338			except:
 339				continue
 340			self.signames[signum] = s
 341	def rootCheck(self, fatal=True):
 342		if(os.access(self.powerfile, os.W_OK)):
 343			return True
 344		if fatal:
 345			msg = 'This command requires sysfs mount and root access'
 346			pprint('ERROR: %s\n' % msg)
 347			self.outputResult({'error':msg})
 348			sys.exit(1)
 349		return False
 350	def rootUser(self, fatal=False):
 351		if 'USER' in os.environ and os.environ['USER'] == 'root':
 352			return True
 353		if fatal:
 354			msg = 'This command must be run as root'
 355			pprint('ERROR: %s\n' % msg)
 356			self.outputResult({'error':msg})
 357			sys.exit(1)
 358		return False
 359	def usable(self, file):
 360		return (os.path.exists(file) and os.path.getsize(file) > 0)
 
 
 
 
 
 
 
 
 
 
 
 361	def getExec(self, cmd):
 362		try:
 363			fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
 364			out = ascii(fp.read()).strip()
 365			fp.close()
 366		except:
 367			out = ''
 368		if out:
 369			return out
 370		for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
 371			'/usr/local/sbin', '/usr/local/bin']:
 372			cmdfull = os.path.join(path, cmd)
 373			if os.path.exists(cmdfull):
 374				return cmdfull
 375		return out
 376	def setPrecision(self, num):
 377		if num < 0 or num > 6:
 378			return
 379		self.timeformat = '%.{0}f'.format(num)
 380	def setOutputFolder(self, value):
 381		args = dict()
 382		n = datetime.now()
 383		args['date'] = n.strftime('%y%m%d')
 384		args['time'] = n.strftime('%H%M%S')
 385		args['hostname'] = args['host'] = self.hostname
 386		args['mode'] = self.suspendmode
 387		return value.format(**args)
 388	def setOutputFile(self):
 389		if self.dmesgfile != '':
 390			m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
 391			if(m):
 392				self.htmlfile = m.group('name')+'.html'
 393		if self.ftracefile != '':
 394			m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
 395			if(m):
 396				self.htmlfile = m.group('name')+'.html'
 397	def systemInfo(self, info):
 398		p = m = ''
 399		if 'baseboard-manufacturer' in info:
 400			m = info['baseboard-manufacturer']
 401		elif 'system-manufacturer' in info:
 402			m = info['system-manufacturer']
 403		if 'system-product-name' in info:
 404			p = info['system-product-name']
 405		elif 'baseboard-product-name' in info:
 406			p = info['baseboard-product-name']
 407		if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
 408			p = info['baseboard-product-name']
 409		c = info['processor-version'] if 'processor-version' in info else ''
 410		b = info['bios-version'] if 'bios-version' in info else ''
 411		r = info['bios-release-date'] if 'bios-release-date' in info else ''
 412		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
 413			(m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
 
 
 414	def printSystemInfo(self, fatal=False):
 415		self.rootCheck(True)
 416		out = dmidecode(self.mempath, fatal)
 417		if len(out) < 1:
 418			return
 419		fmt = '%-24s: %s'
 
 
 420		for name in sorted(out):
 421			print(fmt % (name, out[name]))
 422		print(fmt % ('cpucount', ('%d' % self.cpucount)))
 423		print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
 424		print(fmt % ('memfree', ('%d kB' % self.memfree)))
 425	def cpuInfo(self):
 426		self.cpucount = 0
 427		fp = open('/proc/cpuinfo', 'r')
 428		for line in fp:
 429			if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
 430				self.cpucount += 1
 431		fp.close()
 432		fp = open('/proc/meminfo', 'r')
 433		for line in fp:
 434			m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
 435			if m:
 436				self.memtotal = int(m.group('sz'))
 437			m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
 438			if m:
 439				self.memfree = int(m.group('sz'))
 440		fp.close()
 
 
 
 
 
 441	def initTestOutput(self, name):
 442		self.prefix = self.hostname
 443		v = open('/proc/version', 'r').read().strip()
 444		kver = v.split()[2]
 445		fmt = name+'-%m%d%y-%H%M%S'
 446		testtime = datetime.now().strftime(fmt)
 447		self.teststamp = \
 448			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 449		ext = ''
 450		if self.gzip:
 451			ext = '.gz'
 452		self.dmesgfile = \
 453			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
 454		self.ftracefile = \
 455			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
 456		self.htmlfile = \
 457			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 458		if not os.path.isdir(self.testdir):
 459			os.makedirs(self.testdir)
 460		self.sudoUserchown(self.testdir)
 461	def getValueList(self, value):
 462		out = []
 463		for i in value.split(','):
 464			if i.strip():
 465				out.append(i.strip())
 466		return out
 467	def setDeviceFilter(self, value):
 468		self.devicefilter = self.getValueList(value)
 469	def setCallgraphFilter(self, value):
 470		self.cgfilter = self.getValueList(value)
 471	def skipKprobes(self, value):
 472		for k in self.getValueList(value):
 473			if k in self.tracefuncs:
 474				del self.tracefuncs[k]
 475			if k in self.dev_tracefuncs:
 476				del self.dev_tracefuncs[k]
 477	def setCallgraphBlacklist(self, file):
 478		self.cgblacklist = self.listFromFile(file)
 479	def rtcWakeAlarmOn(self):
 480		call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
 481		nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
 482		if nowtime:
 483			nowtime = int(nowtime)
 484		else:
 485			# if hardware time fails, use the software time
 486			nowtime = int(datetime.now().strftime('%s'))
 487		alarm = nowtime + self.rtcwaketime
 488		call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
 489	def rtcWakeAlarmOff(self):
 490		call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
 491	def initdmesg(self):
 492		# get the latest time stamp from the dmesg log
 493		fp = Popen('dmesg', stdout=PIPE).stdout
 494		ktime = '0'
 495		for line in fp:
 496			line = ascii(line).replace('\r\n', '')
 497			idx = line.find('[')
 498			if idx > 1:
 499				line = line[idx:]
 500			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 501			if(m):
 502				ktime = m.group('ktime')
 503		fp.close()
 504		self.dmesgstart = float(ktime)
 505	def getdmesg(self, testdata):
 506		op = self.writeDatafileHeader(self.dmesgfile, testdata)
 507		# store all new dmesg lines since initdmesg was called
 508		fp = Popen('dmesg', stdout=PIPE).stdout
 509		for line in fp:
 510			line = ascii(line).replace('\r\n', '')
 511			idx = line.find('[')
 512			if idx > 1:
 513				line = line[idx:]
 514			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 515			if(not m):
 516				continue
 517			ktime = float(m.group('ktime'))
 518			if ktime > self.dmesgstart:
 519				op.write(line)
 520		fp.close()
 521		op.close()
 522	def listFromFile(self, file):
 523		list = []
 524		fp = open(file)
 525		for i in fp.read().split('\n'):
 526			i = i.strip()
 527			if i and i[0] != '#':
 528				list.append(i)
 529		fp.close()
 530		return list
 531	def addFtraceFilterFunctions(self, file):
 532		for i in self.listFromFile(file):
 533			if len(i) < 2:
 534				continue
 535			self.tracefuncs[i] = dict()
 536	def getFtraceFilterFunctions(self, current):
 537		self.rootCheck(True)
 538		if not current:
 539			call('cat '+self.tpath+'available_filter_functions', shell=True)
 540			return
 541		master = self.listFromFile(self.tpath+'available_filter_functions')
 542		for i in sorted(self.tracefuncs):
 543			if 'func' in self.tracefuncs[i]:
 544				i = self.tracefuncs[i]['func']
 545			if i in master:
 546				print(i)
 547			else:
 548				print(self.colorText(i))
 549	def setFtraceFilterFunctions(self, list):
 550		master = self.listFromFile(self.tpath+'available_filter_functions')
 551		flist = ''
 552		for i in list:
 553			if i not in master:
 554				continue
 555			if ' [' in i:
 556				flist += i.split(' ')[0]+'\n'
 557			else:
 558				flist += i+'\n'
 559		fp = open(self.tpath+'set_graph_function', 'w')
 560		fp.write(flist)
 561		fp.close()
 562	def basicKprobe(self, name):
 563		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
 564	def defaultKprobe(self, name, kdata):
 565		k = kdata
 566		for field in ['name', 'format', 'func']:
 567			if field not in k:
 568				k[field] = name
 569		if self.archargs in k:
 570			k['args'] = k[self.archargs]
 571		else:
 572			k['args'] = dict()
 573			k['format'] = name
 574		self.kprobes[name] = k
 575	def kprobeColor(self, name):
 576		if name not in self.kprobes or 'color' not in self.kprobes[name]:
 577			return ''
 578		return self.kprobes[name]['color']
 579	def kprobeDisplayName(self, name, dataraw):
 580		if name not in self.kprobes:
 581			self.basicKprobe(name)
 582		data = ''
 583		quote=0
 584		# first remvoe any spaces inside quotes, and the quotes
 585		for c in dataraw:
 586			if c == '"':
 587				quote = (quote + 1) % 2
 588			if quote and c == ' ':
 589				data += '_'
 590			elif c != '"':
 591				data += c
 592		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
 593		arglist = dict()
 594		# now process the args
 595		for arg in sorted(args):
 596			arglist[arg] = ''
 597			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
 598			if m:
 599				arglist[arg] = m.group('arg')
 600			else:
 601				m = re.match('.* '+arg+'=(?P<arg>.*)', data);
 602				if m:
 603					arglist[arg] = m.group('arg')
 604		out = fmt.format(**arglist)
 605		out = out.replace(' ', '_').replace('"', '')
 606		return out
 607	def kprobeText(self, kname, kprobe):
 608		name = fmt = func = kname
 609		args = dict()
 610		if 'name' in kprobe:
 611			name = kprobe['name']
 612		if 'format' in kprobe:
 613			fmt = kprobe['format']
 614		if 'func' in kprobe:
 615			func = kprobe['func']
 616		if self.archargs in kprobe:
 617			args = kprobe[self.archargs]
 618		if 'args' in kprobe:
 619			args = kprobe['args']
 620		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
 621			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
 622		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
 623			if arg not in args:
 624				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
 625		val = 'p:%s_cal %s' % (name, func)
 626		for i in sorted(args):
 627			val += ' %s=%s' % (i, args[i])
 628		val += '\nr:%s_ret %s $retval\n' % (name, func)
 629		return val
 630	def addKprobes(self, output=False):
 631		if len(self.kprobes) < 1:
 632			return
 633		if output:
 634			pprint('    kprobe functions in this kernel:')
 635		# first test each kprobe
 636		rejects = []
 637		# sort kprobes: trace, ub-dev, custom, dev
 638		kpl = [[], [], [], []]
 639		linesout = len(self.kprobes)
 640		for name in sorted(self.kprobes):
 641			res = self.colorText('YES', 32)
 642			if not self.testKprobe(name, self.kprobes[name]):
 643				res = self.colorText('NO')
 644				rejects.append(name)
 645			else:
 646				if name in self.tracefuncs:
 647					kpl[0].append(name)
 648				elif name in self.dev_tracefuncs:
 649					if 'ub' in self.dev_tracefuncs[name]:
 650						kpl[1].append(name)
 651					else:
 652						kpl[3].append(name)
 653				else:
 654					kpl[2].append(name)
 655			if output:
 656				pprint('         %s: %s' % (name, res))
 657		kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
 658		# remove all failed ones from the list
 659		for name in rejects:
 660			self.kprobes.pop(name)
 661		# set the kprobes all at once
 662		self.fsetVal('', 'kprobe_events')
 663		kprobeevents = ''
 664		for kp in kplist:
 665			kprobeevents += self.kprobeText(kp, self.kprobes[kp])
 666		self.fsetVal(kprobeevents, 'kprobe_events')
 667		if output:
 668			check = self.fgetVal('kprobe_events')
 669			linesack = (len(check.split('\n')) - 1) // 2
 670			pprint('    kprobe functions enabled: %d/%d' % (linesack, linesout))
 671		self.fsetVal('1', 'events/kprobes/enable')
 672	def testKprobe(self, kname, kprobe):
 673		self.fsetVal('0', 'events/kprobes/enable')
 674		kprobeevents = self.kprobeText(kname, kprobe)
 675		if not kprobeevents:
 676			return False
 677		try:
 678			self.fsetVal(kprobeevents, 'kprobe_events')
 679			check = self.fgetVal('kprobe_events')
 680		except:
 681			return False
 682		linesout = len(kprobeevents.split('\n'))
 683		linesack = len(check.split('\n'))
 684		if linesack < linesout:
 685			return False
 686		return True
 687	def setVal(self, val, file):
 688		if not os.path.exists(file):
 689			return False
 690		try:
 691			fp = open(file, 'wb', 0)
 692			fp.write(val.encode())
 693			fp.flush()
 694			fp.close()
 695		except:
 696			return False
 697		return True
 698	def fsetVal(self, val, path):
 
 
 699		return self.setVal(val, self.tpath+path)
 700	def getVal(self, file):
 701		res = ''
 702		if not os.path.exists(file):
 703			return res
 704		try:
 705			fp = open(file, 'r')
 706			res = fp.read()
 707			fp.close()
 708		except:
 709			pass
 710		return res
 711	def fgetVal(self, path):
 
 
 712		return self.getVal(self.tpath+path)
 713	def cleanupFtrace(self):
 714		if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
 715			self.fsetVal('0', 'events/kprobes/enable')
 716			self.fsetVal('', 'kprobe_events')
 717			self.fsetVal('1024', 'buffer_size_kb')
 718		if self.pmdebug:
 719			self.setVal(self.pmdebug, self.pmdpath)
 720	def setupAllKprobes(self):
 721		for name in self.tracefuncs:
 722			self.defaultKprobe(name, self.tracefuncs[name])
 723		for name in self.dev_tracefuncs:
 724			self.defaultKprobe(name, self.dev_tracefuncs[name])
 725	def isCallgraphFunc(self, name):
 726		if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
 727			return True
 728		for i in self.tracefuncs:
 729			if 'func' in self.tracefuncs[i]:
 730				f = self.tracefuncs[i]['func']
 731			else:
 732				f = i
 733			if name == f:
 734				return True
 735		return False
 736	def initFtrace(self, quiet=False):
 
 
 737		if not quiet:
 738			sysvals.printSystemInfo(False)
 739			pprint('INITIALIZING FTRACE...')
 740		# turn trace off
 741		self.fsetVal('0', 'tracing_on')
 742		self.cleanupFtrace()
 743		# pm debug messages
 744		pv = self.getVal(self.pmdpath)
 745		if pv != '1':
 746			self.setVal('1', self.pmdpath)
 747			self.pmdebug = pv
 748		# set the trace clock to global
 749		self.fsetVal('global', 'trace_clock')
 750		self.fsetVal('nop', 'current_tracer')
 751		# set trace buffer to an appropriate value
 752		cpus = max(1, self.cpucount)
 753		if self.bufsize > 0:
 754			tgtsize = self.bufsize
 755		elif self.usecallgraph or self.usedevsrc:
 756			bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
 757				else (3*1024*1024)
 758			tgtsize = min(self.memfree, bmax)
 759		else:
 760			tgtsize = 65536
 761		while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
 762			# if the size failed to set, lower it and keep trying
 763			tgtsize -= 65536
 764			if tgtsize < 65536:
 765				tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
 766				break
 767		self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
 768		# initialize the callgraph trace
 769		if(self.usecallgraph):
 770			# set trace type
 771			self.fsetVal('function_graph', 'current_tracer')
 772			self.fsetVal('', 'set_ftrace_filter')
 
 
 
 
 773			# set trace format options
 774			self.fsetVal('print-parent', 'trace_options')
 775			self.fsetVal('funcgraph-abstime', 'trace_options')
 776			self.fsetVal('funcgraph-cpu', 'trace_options')
 777			self.fsetVal('funcgraph-duration', 'trace_options')
 778			self.fsetVal('funcgraph-proc', 'trace_options')
 779			self.fsetVal('funcgraph-tail', 'trace_options')
 780			self.fsetVal('nofuncgraph-overhead', 'trace_options')
 781			self.fsetVal('context-info', 'trace_options')
 782			self.fsetVal('graph-time', 'trace_options')
 783			self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
 784			cf = ['dpm_run_callback']
 785			if(self.usetraceevents):
 786				cf += ['dpm_prepare', 'dpm_complete']
 787			for fn in self.tracefuncs:
 788				if 'func' in self.tracefuncs[fn]:
 789					cf.append(self.tracefuncs[fn]['func'])
 790				else:
 791					cf.append(fn)
 792			if self.ftop:
 793				self.setFtraceFilterFunctions([self.ftopfunc])
 794			else:
 795				self.setFtraceFilterFunctions(cf)
 796		# initialize the kprobe trace
 797		elif self.usekprobes:
 798			for name in self.tracefuncs:
 799				self.defaultKprobe(name, self.tracefuncs[name])
 800			if self.usedevsrc:
 801				for name in self.dev_tracefuncs:
 802					self.defaultKprobe(name, self.dev_tracefuncs[name])
 803			if not quiet:
 804				pprint('INITIALIZING KPROBES...')
 805			self.addKprobes(self.verbose)
 806		if(self.usetraceevents):
 807			# turn trace events on
 808			events = iter(self.traceevents)
 809			for e in events:
 810				self.fsetVal('1', 'events/power/'+e+'/enable')
 811		# clear the trace buffer
 812		self.fsetVal('', 'trace')
 813	def verifyFtrace(self):
 814		# files needed for any trace data
 815		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 816				 'trace_marker', 'trace_options', 'tracing_on']
 
 
 
 
 
 817		# files needed for callgraph trace data
 818		tp = self.tpath
 819		if(self.usecallgraph):
 820			files += [
 821				'available_filter_functions',
 822				'set_ftrace_filter',
 823				'set_graph_function'
 824			]
 825		for f in files:
 826			if(os.path.exists(tp+f) == False):
 827				return False
 828		return True
 829	def verifyKprobes(self):
 830		# files needed for kprobes to work
 831		files = ['kprobe_events', 'events']
 832		tp = self.tpath
 833		for f in files:
 834			if(os.path.exists(tp+f) == False):
 835				return False
 836		return True
 837	def colorText(self, str, color=31):
 838		if not self.ansi:
 839			return str
 840		return '\x1B[%d;40m%s\x1B[m' % (color, str)
 841	def writeDatafileHeader(self, filename, testdata):
 842		fp = self.openlog(filename, 'w')
 843		fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
 844		for test in testdata:
 845			if 'fw' in test:
 846				fw = test['fw']
 847				if(fw):
 848					fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
 849			if 'turbo' in test:
 850				fp.write('# turbostat %s\n' % test['turbo'])
 851			if 'wifi' in test:
 852				fp.write('# wifi %s\n' % test['wifi'])
 
 
 853			if test['error'] or len(testdata) > 1:
 854				fp.write('# enter_sleep_error %s\n' % test['error'])
 855		return fp
 856	def sudoUserchown(self, dir):
 857		if os.path.exists(dir) and self.sudouser:
 858			cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
 859			call(cmd.format(self.sudouser, dir), shell=True)
 860	def outputResult(self, testdata, num=0):
 861		if not self.result:
 862			return
 863		n = ''
 864		if num > 0:
 865			n = '%d' % num
 866		fp = open(self.result, 'a')
 
 
 
 
 
 
 
 867		if 'error' in testdata:
 868			fp.write('result%s: fail\n' % n)
 869			fp.write('error%s: %s\n' % (n, testdata['error']))
 870		else:
 871			fp.write('result%s: pass\n' % n)
 
 
 872		for v in ['suspend', 'resume', 'boot', 'lastinit']:
 873			if v in testdata:
 874				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
 875		for v in ['fwsuspend', 'fwresume']:
 876			if v in testdata:
 877				fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
 878		if 'bugurl' in testdata:
 879			fp.write('url%s: %s\n' % (n, testdata['bugurl']))
 880		fp.close()
 881		self.sudoUserchown(self.result)
 882	def configFile(self, file):
 883		dir = os.path.dirname(os.path.realpath(__file__))
 884		if os.path.exists(file):
 885			return file
 886		elif os.path.exists(dir+'/'+file):
 887			return dir+'/'+file
 888		elif os.path.exists(dir+'/config/'+file):
 889			return dir+'/config/'+file
 890		return ''
 891	def openlog(self, filename, mode):
 892		isgz = self.gzip
 893		if mode == 'r':
 894			try:
 895				with gzip.open(filename, mode+'t') as fp:
 896					test = fp.read(64)
 897				isgz = True
 898			except:
 899				isgz = False
 900		if isgz:
 901			return gzip.open(filename, mode+'t')
 902		return open(filename, mode)
 
 
 
 
 
 
 
 
 
 
 903	def b64unzip(self, data):
 904		try:
 905			out = codecs.decode(base64.b64decode(data), 'zlib').decode()
 906		except:
 907			out = data
 908		return out
 909	def b64zip(self, data):
 910		out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
 911		return out
 912	def platforminfo(self, cmdafter):
 913		# add platform info on to a completed ftrace file
 914		if not os.path.exists(self.ftracefile):
 915			return False
 916		footer = '#\n'
 917
 918		# add test command string line if need be
 919		if self.suspendmode == 'command' and self.testcommand:
 920			footer += '# platform-testcmd: %s\n' % (self.testcommand)
 921
 922		# get a list of target devices from the ftrace file
 923		props = dict()
 924		tp = TestProps()
 925		tf = self.openlog(self.ftracefile, 'r')
 926		for line in tf:
 927			if tp.stampInfo(line, self):
 928				continue
 929			# parse only valid lines, if this is not one move on
 930			m = re.match(tp.ftrace_line_fmt, line)
 931			if(not m or 'device_pm_callback_start' not in line):
 932				continue
 933			m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
 934			if(not m):
 935				continue
 936			dev = m.group('d')
 937			if dev not in props:
 938				props[dev] = DevProps()
 939		tf.close()
 940
 941		# now get the syspath for each target device
 942		for dirname, dirnames, filenames in os.walk('/sys/devices'):
 943			if(re.match('.*/power', dirname) and 'async' in filenames):
 944				dev = dirname.split('/')[-2]
 945				if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
 946					props[dev].syspath = dirname[:-6]
 947
 948		# now fill in the properties for our target devices
 949		for dev in sorted(props):
 950			dirname = props[dev].syspath
 951			if not dirname or not os.path.exists(dirname):
 952				continue
 953			with open(dirname+'/power/async') as fp:
 954				text = fp.read()
 955				props[dev].isasync = False
 956				if 'enabled' in text:
 957					props[dev].isasync = True
 
 958			fields = os.listdir(dirname)
 959			if 'product' in fields:
 960				with open(dirname+'/product', 'rb') as fp:
 961					props[dev].altname = ascii(fp.read())
 962			elif 'name' in fields:
 963				with open(dirname+'/name', 'rb') as fp:
 964					props[dev].altname = ascii(fp.read())
 965			elif 'model' in fields:
 966				with open(dirname+'/model', 'rb') as fp:
 967					props[dev].altname = ascii(fp.read())
 968			elif 'description' in fields:
 969				with open(dirname+'/description', 'rb') as fp:
 970					props[dev].altname = ascii(fp.read())
 971			elif 'id' in fields:
 972				with open(dirname+'/id', 'rb') as fp:
 973					props[dev].altname = ascii(fp.read())
 974			elif 'idVendor' in fields and 'idProduct' in fields:
 975				idv, idp = '', ''
 976				with open(dirname+'/idVendor', 'rb') as fp:
 977					idv = ascii(fp.read()).strip()
 978				with open(dirname+'/idProduct', 'rb') as fp:
 979					idp = ascii(fp.read()).strip()
 980				props[dev].altname = '%s:%s' % (idv, idp)
 981			if props[dev].altname:
 982				out = props[dev].altname.strip().replace('\n', ' ')\
 983					.replace(',', ' ').replace(';', ' ')
 984				props[dev].altname = out
 985
 986		# add a devinfo line to the bottom of ftrace
 987		out = ''
 988		for dev in sorted(props):
 989			out += props[dev].out(dev)
 990		footer += '# platform-devinfo: %s\n' % self.b64zip(out)
 991
 992		# add a line for each of these commands with their outputs
 993		for name, cmdline, info in cmdafter:
 994			footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
 995
 996		with self.openlog(self.ftracefile, 'a') as fp:
 997			fp.write(footer)
 998		return True
 999	def commonPrefix(self, list):
1000		if len(list) < 2:
1001			return ''
1002		prefix = list[0]
1003		for s in list[1:]:
1004			while s[:len(prefix)] != prefix and prefix:
1005				prefix = prefix[:len(prefix)-1]
1006			if not prefix:
1007				break
1008		if '/' in prefix and prefix[-1] != '/':
1009			prefix = prefix[0:prefix.rfind('/')+1]
1010		return prefix
1011	def dictify(self, text, format):
1012		out = dict()
1013		header = True if format == 1 else False
1014		delim = ' ' if format == 1 else ':'
1015		for line in text.split('\n'):
1016			if header:
1017				header, out['@'] = False, line
1018				continue
1019			line = line.strip()
1020			if delim in line:
1021				data = line.split(delim, 1)
1022				num = re.search(r'[\d]+', data[1])
1023				if format == 2 and num:
1024					out[data[0].strip()] = num.group()
1025				else:
1026					out[data[0].strip()] = data[1]
1027		return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1028	def cmdinfo(self, begin, debug=False):
1029		out = []
1030		if begin:
1031			self.cmd1 = dict()
1032		for cargs in self.infocmds:
1033			delta, name = cargs[0], cargs[1]
1034			cmdline, cmdpath = ' '.join(cargs[2:]), self.getExec(cargs[2])
 
 
 
1035			if not cmdpath or (begin and not delta):
1036				continue
 
1037			try:
1038				fp = Popen([cmdpath]+cargs[3:], stdout=PIPE, stderr=PIPE).stdout
1039				info = ascii(fp.read()).strip()
1040				fp.close()
1041			except:
1042				continue
1043			if not debug and begin:
1044				self.cmd1[name] = self.dictify(info, delta)
1045			elif not debug and delta and name in self.cmd1:
1046				before, after = self.cmd1[name], self.dictify(info, delta)
1047				dinfo = ('\t%s\n' % before['@']) if '@' in before else ''
1048				prefix = self.commonPrefix(list(before.keys()))
1049				for key in sorted(before):
1050					if key in after and before[key] != after[key]:
1051						title = key.replace(prefix, '')
1052						if delta == 2:
1053							dinfo += '\t%s : %s -> %s\n' % \
1054								(title, before[key].strip(), after[key].strip())
1055						else:
1056							dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1057								(title, before[key], title, after[key])
1058				dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1059				out.append((name, cmdline, dinfo))
1060			else:
1061				out.append((name, cmdline, '\tnothing' if not info else info))
1062		return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063	def haveTurbostat(self):
1064		if not self.tstat:
1065			return False
1066		cmd = self.getExec('turbostat')
1067		if not cmd:
1068			return False
1069		fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1070		out = ascii(fp.read()).strip()
1071		fp.close()
1072		if re.match('turbostat version .*', out):
1073			self.vprint(out)
1074			return True
1075		return False
1076	def turbostat(self):
1077		cmd = self.getExec('turbostat')
1078		rawout = keyline = valline = ''
1079		fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1080		fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr
1081		for line in fp:
1082			line = ascii(line)
1083			rawout += line
1084			if keyline and valline:
1085				continue
1086			if re.match('(?i)Avg_MHz.*', line):
1087				keyline = line.strip().split()
1088			elif keyline:
1089				valline = line.strip().split()
1090		fp.close()
1091		if not keyline or not valline or len(keyline) != len(valline):
1092			errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1093			self.vprint(errmsg)
1094			if not self.verbose:
1095				pprint(errmsg)
1096			return ''
1097		if self.verbose:
1098			pprint(rawout.strip())
1099		out = []
1100		for key in keyline:
1101			idx = keyline.index(key)
1102			val = valline[idx]
 
 
1103			out.append('%s=%s' % (key, val))
1104		return '|'.join(out)
 
 
 
 
 
 
 
 
1105	def wifiDetails(self, dev):
1106		try:
1107			info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1108		except:
1109			return dev
1110		vals = [dev]
1111		for prop in info.split('\n'):
1112			if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1113				vals.append(prop.split('=')[-1])
1114		return ':'.join(vals)
1115	def checkWifi(self, dev=''):
1116		try:
1117			w = open('/proc/net/wireless', 'r').read().strip()
1118		except:
1119			return ''
1120		for line in reversed(w.split('\n')):
1121			m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', w.split('\n')[-1])
1122			if not m or (dev and dev != m.group('dev')):
1123				continue
1124			return m.group('dev')
1125		return ''
1126	def pollWifi(self, dev, timeout=60):
1127		start = time.time()
1128		while (time.time() - start) < timeout:
1129			w = self.checkWifi(dev)
1130			if w:
1131				return '%s reconnected %.2f' % \
1132					(self.wifiDetails(dev), max(0, time.time() - start))
1133			time.sleep(0.01)
1134		return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1135	def errorSummary(self, errinfo, msg):
1136		found = False
1137		for entry in errinfo:
1138			if re.match(entry['match'], msg):
1139				entry['count'] += 1
1140				if self.hostname not in entry['urls']:
1141					entry['urls'][self.hostname] = [self.htmlfile]
1142				elif self.htmlfile not in entry['urls'][self.hostname]:
1143					entry['urls'][self.hostname].append(self.htmlfile)
1144				found = True
1145				break
1146		if found:
1147			return
1148		arr = msg.split()
1149		for j in range(len(arr)):
1150			if re.match('^[0-9,\-\.]*$', arr[j]):
1151				arr[j] = '[0-9,\-\.]*'
1152			else:
1153				arr[j] = arr[j]\
1154					.replace('\\', '\\\\').replace(']', '\]').replace('[', '\[')\
1155					.replace('.', '\.').replace('+', '\+').replace('*', '\*')\
1156					.replace('(', '\(').replace(')', '\)').replace('}', '\}')\
1157					.replace('{', '\{')
1158		mstr = ' *'.join(arr)
1159		entry = {
1160			'line': msg,
1161			'match': mstr,
1162			'count': 1,
1163			'urls': {self.hostname: [self.htmlfile]}
1164		}
1165		errinfo.append(entry)
1166	def multistat(self, start, idx, finish):
1167		if 'time' in self.multitest:
1168			id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1169		else:
1170			id = '%d/%d' % (idx+1, self.multitest['count'])
1171		t = time.time()
1172		if 'start' not in self.multitest:
1173			self.multitest['start'] = self.multitest['last'] = t
1174			self.multitest['total'] = 0.0
1175			pprint('TEST (%s) START' % id)
1176			return
1177		dt = t - self.multitest['last']
1178		if not start:
1179			if idx == 0 and self.multitest['delay'] > 0:
1180				self.multitest['total'] += self.multitest['delay']
1181			pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1182			return
1183		self.multitest['total'] += dt
1184		self.multitest['last'] = t
1185		avg = self.multitest['total'] / idx
1186		if 'time' in self.multitest:
1187			left = finish - datetime.now()
1188			left -= timedelta(microseconds=left.microseconds)
1189		else:
1190			left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1191		pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1192			(id, avg, str(left)))
1193	def multiinit(self, c, d):
1194		sz, unit = 'count', 'm'
1195		if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1196			sz, unit, c = 'time', c[-1], c[:-1]
1197		self.multitest['run'] = True
1198		self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1199		self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1200		if unit == 'd':
1201			self.multitest[sz] *= 1440
1202		elif unit == 'h':
1203			self.multitest[sz] *= 60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1204
1205sysvals = SystemValues()
1206switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1207switchoff = ['disable', 'off', 'false', '0']
1208suspendmodename = {
1209	'freeze': 'Freeze (S0)',
1210	'standby': 'Standby (S1)',
1211	'mem': 'Suspend (S3)',
1212	'disk': 'Hibernate (S4)'
1213}
1214
1215# Class: DevProps
1216# Description:
1217#	 Simple class which holds property values collected
1218#	 for all the devices used in the timeline.
1219class DevProps:
1220	def __init__(self):
1221		self.syspath = ''
1222		self.altname = ''
1223		self.isasync = True
1224		self.xtraclass = ''
1225		self.xtrainfo = ''
1226	def out(self, dev):
1227		return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1228	def debug(self, dev):
1229		pprint('%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.isasync))
1230	def altName(self, dev):
1231		if not self.altname or self.altname == dev:
1232			return dev
1233		return '%s [%s]' % (self.altname, dev)
1234	def xtraClass(self):
1235		if self.xtraclass:
1236			return ' '+self.xtraclass
1237		if not self.isasync:
1238			return ' sync'
1239		return ''
1240	def xtraInfo(self):
1241		if self.xtraclass:
1242			return ' '+self.xtraclass
1243		if self.isasync:
1244			return ' (async)'
1245		return ' (sync)'
1246
1247# Class: DeviceNode
1248# Description:
1249#	 A container used to create a device hierachy, with a single root node
1250#	 and a tree of child nodes. Used by Data.deviceTopology()
1251class DeviceNode:
1252	def __init__(self, nodename, nodedepth):
1253		self.name = nodename
1254		self.children = []
1255		self.depth = nodedepth
1256
1257# Class: Data
1258# Description:
1259#	 The primary container for suspend/resume test data. There is one for
1260#	 each test run. The data is organized into a cronological hierarchy:
1261#	 Data.dmesg {
1262#		phases {
1263#			10 sequential, non-overlapping phases of S/R
1264#			contents: times for phase start/end, order/color data for html
1265#			devlist {
1266#				device callback or action list for this phase
1267#				device {
1268#					a single device callback or generic action
1269#					contents: start/stop times, pid/cpu/driver info
1270#						parents/children, html id for timeline/callgraph
1271#						optionally includes an ftrace callgraph
1272#						optionally includes dev/ps data
1273#				}
1274#			}
1275#		}
1276#	}
1277#
1278class Data:
1279	phasedef = {
1280		'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1281		        'suspend': {'order': 1, 'color': '#88FF88'},
1282		   'suspend_late': {'order': 2, 'color': '#00AA00'},
1283		  'suspend_noirq': {'order': 3, 'color': '#008888'},
1284		'suspend_machine': {'order': 4, 'color': '#0000FF'},
1285		 'resume_machine': {'order': 5, 'color': '#FF0000'},
1286		   'resume_noirq': {'order': 6, 'color': '#FF9900'},
1287		   'resume_early': {'order': 7, 'color': '#FFCC00'},
1288		         'resume': {'order': 8, 'color': '#FFFF88'},
1289		'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1290	}
1291	errlist = {
1292		'HWERROR' : r'.*\[ *Hardware Error *\].*',
1293		'FWBUG'   : r'.*\[ *Firmware Bug *\].*',
 
1294		'BUG'     : r'(?i).*\bBUG\b.*',
1295		'ERROR'   : r'(?i).*\bERROR\b.*',
1296		'WARNING' : r'(?i).*\bWARNING\b.*',
1297		'FAULT'   : r'(?i).*\bFAULT\b.*',
1298		'FAIL'    : r'(?i).*\bFAILED\b.*',
1299		'INVALID' : r'(?i).*\bINVALID\b.*',
1300		'CRASH'   : r'(?i).*\bCRASHED\b.*',
1301		'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
 
1302		'IRQ'     : r'.*\bgenirq: .*',
1303		'TASKFAIL': r'.*Freezing of tasks *.*',
1304		'ACPI'    : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1305		'DISKFULL': r'.*\bNo space left on device.*',
1306		'USBERR'  : r'.*usb .*device .*, error [0-9-]*',
1307		'ATAERR'  : r' *ata[0-9\.]*: .*failed.*',
1308		'MEIERR'  : r' *mei.*: .*failed.*',
1309		'TPMERR'  : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1310	}
1311	def __init__(self, num):
1312		idchar = 'abcdefghij'
1313		self.start = 0.0 # test start
1314		self.end = 0.0   # test end
1315		self.hwstart = 0 # rtc test start
1316		self.hwend = 0   # rtc test end
1317		self.tSuspended = 0.0 # low-level suspend start
1318		self.tResumed = 0.0   # low-level resume start
1319		self.tKernSus = 0.0   # kernel level suspend start
1320		self.tKernRes = 0.0   # kernel level resume end
1321		self.fwValid = False  # is firmware data available
1322		self.fwSuspend = 0    # time spent in firmware suspend
1323		self.fwResume = 0     # time spent in firmware resume
1324		self.html_device_id = 0
1325		self.stamp = 0
1326		self.outfile = ''
1327		self.kerror = False
1328		self.wifi = dict()
1329		self.turbostat = 0
1330		self.enterfail = ''
1331		self.currphase = ''
1332		self.pstl = dict()    # process timeline
1333		self.testnumber = num
1334		self.idstr = idchar[num]
1335		self.dmesgtext = []   # dmesg text file in memory
1336		self.dmesg = dict()   # root data structure
1337		self.errorinfo = {'suspend':[],'resume':[]}
1338		self.tLow = []        # time spent in low-level suspends (standby/freeze)
1339		self.devpids = []
1340		self.devicegroups = 0
1341	def sortedPhases(self):
1342		return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1343	def initDevicegroups(self):
1344		# called when phases are all finished being added
1345		for phase in sorted(self.dmesg.keys()):
1346			if '*' in phase:
1347				p = phase.split('*')
1348				pnew = '%s%d' % (p[0], len(p))
1349				self.dmesg[pnew] = self.dmesg.pop(phase)
1350		self.devicegroups = []
1351		for phase in self.sortedPhases():
1352			self.devicegroups.append([phase])
1353	def nextPhase(self, phase, offset):
1354		order = self.dmesg[phase]['order'] + offset
1355		for p in self.dmesg:
1356			if self.dmesg[p]['order'] == order:
1357				return p
1358		return ''
1359	def lastPhase(self, depth=1):
1360		plist = self.sortedPhases()
1361		if len(plist) < depth:
1362			return ''
1363		return plist[-1*depth]
1364	def turbostatInfo(self):
1365		tp = TestProps()
1366		out = {'syslpi':'N/A','pkgpc10':'N/A'}
1367		for line in self.dmesgtext:
1368			m = re.match(tp.tstatfmt, line)
1369			if not m:
1370				continue
1371			for i in m.group('t').split('|'):
1372				if 'SYS%LPI' in i:
1373					out['syslpi'] = i.split('=')[-1]+'%'
1374				elif 'pc10' in i:
1375					out['pkgpc10'] = i.split('=')[-1]+'%'
1376			break
1377		return out
1378	def extractErrorInfo(self):
1379		lf = self.dmesgtext
1380		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1381			lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1382		i = 0
1383		tp = TestProps()
1384		list = []
1385		for line in lf:
1386			i += 1
1387			if tp.stampInfo(line, sysvals):
1388				continue
1389			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1390			if not m:
1391				continue
1392			t = float(m.group('ktime'))
1393			if t < self.start or t > self.end:
1394				continue
1395			dir = 'suspend' if t < self.tSuspended else 'resume'
1396			msg = m.group('msg')
1397			if re.match('capability: warning: .*', msg):
1398				continue
1399			for err in self.errlist:
1400				if re.match(self.errlist[err], msg):
1401					list.append((msg, err, dir, t, i, i))
1402					self.kerror = True
1403					break
1404		tp.msglist = []
1405		for msg, type, dir, t, idx1, idx2 in list:
1406			tp.msglist.append(msg)
1407			self.errorinfo[dir].append((type, t, idx1, idx2))
1408		if self.kerror:
1409			sysvals.dmesglog = True
1410		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1411			lf.close()
1412		return tp
1413	def setStart(self, time, msg=''):
1414		self.start = time
1415		if msg:
1416			try:
1417				self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1418			except:
1419				self.hwstart = 0
1420	def setEnd(self, time, msg=''):
1421		self.end = time
1422		if msg:
1423			try:
1424				self.hwend = datetime.strptime(msg, sysvals.tmend)
1425			except:
1426				self.hwend = 0
1427	def isTraceEventOutsideDeviceCalls(self, pid, time):
1428		for phase in self.sortedPhases():
1429			list = self.dmesg[phase]['list']
1430			for dev in list:
1431				d = list[dev]
1432				if(d['pid'] == pid and time >= d['start'] and
1433					time < d['end']):
1434					return False
1435		return True
1436	def sourcePhase(self, start):
1437		for phase in self.sortedPhases():
1438			if 'machine' in phase:
1439				continue
1440			pend = self.dmesg[phase]['end']
1441			if start <= pend:
1442				return phase
1443		return 'resume_complete'
1444	def sourceDevice(self, phaselist, start, end, pid, type):
1445		tgtdev = ''
1446		for phase in phaselist:
1447			list = self.dmesg[phase]['list']
1448			for devname in list:
1449				dev = list[devname]
1450				# pid must match
1451				if dev['pid'] != pid:
1452					continue
1453				devS = dev['start']
1454				devE = dev['end']
1455				if type == 'device':
1456					# device target event is entirely inside the source boundary
1457					if(start < devS or start >= devE or end <= devS or end > devE):
1458						continue
1459				elif type == 'thread':
1460					# thread target event will expand the source boundary
1461					if start < devS:
1462						dev['start'] = start
1463					if end > devE:
1464						dev['end'] = end
1465				tgtdev = dev
1466				break
1467		return tgtdev
1468	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1469		# try to place the call in a device
1470		phases = self.sortedPhases()
1471		tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1472		# calls with device pids that occur outside device bounds are dropped
1473		# TODO: include these somehow
1474		if not tgtdev and pid in self.devpids:
1475			return False
1476		# try to place the call in a thread
1477		if not tgtdev:
1478			tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1479		# create new thread blocks, expand as new calls are found
1480		if not tgtdev:
1481			if proc == '<...>':
1482				threadname = 'kthread-%d' % (pid)
1483			else:
1484				threadname = '%s-%d' % (proc, pid)
1485			tgtphase = self.sourcePhase(start)
 
 
1486			self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1487			return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1488		# this should not happen
1489		if not tgtdev:
1490			sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1491				(start, end, proc, pid, kprobename, cdata, rdata))
1492			return False
1493		# place the call data inside the src element of the tgtdev
1494		if('src' not in tgtdev):
1495			tgtdev['src'] = []
1496		dtf = sysvals.dev_tracefuncs
1497		ubiquitous = False
1498		if kprobename in dtf and 'ub' in dtf[kprobename]:
1499			ubiquitous = True
1500		title = cdata+' '+rdata
1501		mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1502		m = re.match(mstr, title)
1503		if m:
1504			c = m.group('caller')
1505			a = m.group('args').strip()
1506			r = m.group('ret')
1507			if len(r) > 6:
1508				r = ''
1509			else:
1510				r = 'ret=%s ' % r
1511			if ubiquitous and c in dtf and 'ub' in dtf[c]:
1512				return False
 
 
1513		color = sysvals.kprobeColor(kprobename)
1514		e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1515		tgtdev['src'].append(e)
1516		return True
1517	def overflowDevices(self):
1518		# get a list of devices that extend beyond the end of this test run
1519		devlist = []
1520		for phase in self.sortedPhases():
1521			list = self.dmesg[phase]['list']
1522			for devname in list:
1523				dev = list[devname]
1524				if dev['end'] > self.end:
1525					devlist.append(dev)
1526		return devlist
1527	def mergeOverlapDevices(self, devlist):
1528		# merge any devices that overlap devlist
1529		for dev in devlist:
1530			devname = dev['name']
1531			for phase in self.sortedPhases():
1532				list = self.dmesg[phase]['list']
1533				if devname not in list:
1534					continue
1535				tdev = list[devname]
1536				o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1537				if o <= 0:
1538					continue
1539				dev['end'] = tdev['end']
1540				if 'src' not in dev or 'src' not in tdev:
1541					continue
1542				dev['src'] += tdev['src']
1543				del list[devname]
1544	def usurpTouchingThread(self, name, dev):
1545		# the caller test has priority of this thread, give it to him
1546		for phase in self.sortedPhases():
1547			list = self.dmesg[phase]['list']
1548			if name in list:
1549				tdev = list[name]
1550				if tdev['start'] - dev['end'] < 0.1:
1551					dev['end'] = tdev['end']
1552					if 'src' not in dev:
1553						dev['src'] = []
1554					if 'src' in tdev:
1555						dev['src'] += tdev['src']
1556					del list[name]
1557				break
1558	def stitchTouchingThreads(self, testlist):
1559		# merge any threads between tests that touch
1560		for phase in self.sortedPhases():
1561			list = self.dmesg[phase]['list']
1562			for devname in list:
1563				dev = list[devname]
1564				if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1565					continue
1566				for data in testlist:
1567					data.usurpTouchingThread(devname, dev)
1568	def optimizeDevSrc(self):
1569		# merge any src call loops to reduce timeline size
1570		for phase in self.sortedPhases():
1571			list = self.dmesg[phase]['list']
1572			for dev in list:
1573				if 'src' not in list[dev]:
1574					continue
1575				src = list[dev]['src']
1576				p = 0
1577				for e in sorted(src, key=lambda event: event.time):
1578					if not p or not e.repeat(p):
1579						p = e
1580						continue
1581					# e is another iteration of p, move it into p
1582					p.end = e.end
1583					p.length = p.end - p.time
1584					p.count += 1
1585					src.remove(e)
1586	def trimTimeVal(self, t, t0, dT, left):
1587		if left:
1588			if(t > t0):
1589				if(t - dT < t0):
1590					return t0
1591				return t - dT
1592			else:
1593				return t
1594		else:
1595			if(t < t0 + dT):
1596				if(t > t0):
1597					return t0 + dT
1598				return t + dT
1599			else:
1600				return t
1601	def trimTime(self, t0, dT, left):
1602		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1603		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1604		self.start = self.trimTimeVal(self.start, t0, dT, left)
1605		self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1606		self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1607		self.end = self.trimTimeVal(self.end, t0, dT, left)
1608		for phase in self.sortedPhases():
1609			p = self.dmesg[phase]
1610			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1611			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1612			list = p['list']
1613			for name in list:
1614				d = list[name]
1615				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1616				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1617				d['length'] = d['end'] - d['start']
1618				if('ftrace' in d):
1619					cg = d['ftrace']
1620					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1621					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1622					for line in cg.list:
1623						line.time = self.trimTimeVal(line.time, t0, dT, left)
1624				if('src' in d):
1625					for e in d['src']:
1626						e.time = self.trimTimeVal(e.time, t0, dT, left)
1627						e.end = self.trimTimeVal(e.end, t0, dT, left)
1628						e.length = e.end - e.time
 
 
 
 
 
 
 
 
1629		for dir in ['suspend', 'resume']:
1630			list = []
1631			for e in self.errorinfo[dir]:
1632				type, tm, idx1, idx2 = e
1633				tm = self.trimTimeVal(tm, t0, dT, left)
1634				list.append((type, tm, idx1, idx2))
1635			self.errorinfo[dir] = list
1636	def trimFreezeTime(self, tZero):
1637		# trim out any standby or freeze clock time
1638		lp = ''
1639		for phase in self.sortedPhases():
1640			if 'resume_machine' in phase and 'suspend_machine' in lp:
1641				tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1642				tL = tR - tS
1643				if tL > 0:
1644					left = True if tR > tZero else False
1645					self.trimTime(tS, tL, left)
1646					if 'trying' in self.dmesg[lp] and self.dmesg[lp]['trying'] >= 0.001:
1647						tTry = round(self.dmesg[lp]['trying'] * 1000)
1648						text = '%.0f (-%.0f waking)' % (tL * 1000, tTry)
 
 
1649					else:
1650						text = '%.0f' % (tL * 1000)
1651					self.tLow.append(text)
 
 
 
1652			lp = phase
1653	def getMemTime(self):
1654		if not self.hwstart or not self.hwend:
1655			return
1656		stime = (self.tSuspended - self.start) * 1000000
1657		rtime = (self.end - self.tResumed) * 1000000
1658		hws = self.hwstart + timedelta(microseconds=stime)
1659		hwr = self.hwend - timedelta(microseconds=rtime)
1660		self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1661	def getTimeValues(self):
1662		sktime = (self.tSuspended - self.tKernSus) * 1000
1663		rktime = (self.tKernRes - self.tResumed) * 1000
1664		return (sktime, rktime)
1665	def setPhase(self, phase, ktime, isbegin, order=-1):
1666		if(isbegin):
1667			# phase start over current phase
1668			if self.currphase:
1669				if 'resume_machine' not in self.currphase:
1670					sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1671				self.dmesg[self.currphase]['end'] = ktime
1672			phases = self.dmesg.keys()
1673			color = self.phasedef[phase]['color']
1674			count = len(phases) if order < 0 else order
1675			# create unique name for every new phase
1676			while phase in phases:
1677				phase += '*'
1678			self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1679				'row': 0, 'color': color, 'order': count}
1680			self.dmesg[phase]['start'] = ktime
1681			self.currphase = phase
1682		else:
1683			# phase end without a start
1684			if phase not in self.currphase:
1685				if self.currphase:
1686					sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1687				else:
1688					sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1689					return phase
1690			phase = self.currphase
1691			self.dmesg[phase]['end'] = ktime
1692			self.currphase = ''
1693		return phase
1694	def sortedDevices(self, phase):
1695		list = self.dmesg[phase]['list']
1696		return sorted(list, key=lambda k:list[k]['start'])
1697	def fixupInitcalls(self, phase):
1698		# if any calls never returned, clip them at system resume end
1699		phaselist = self.dmesg[phase]['list']
1700		for devname in phaselist:
1701			dev = phaselist[devname]
1702			if(dev['end'] < 0):
1703				for p in self.sortedPhases():
1704					if self.dmesg[p]['end'] > dev['start']:
1705						dev['end'] = self.dmesg[p]['end']
1706						break
1707				sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1708	def deviceFilter(self, devicefilter):
1709		for phase in self.sortedPhases():
1710			list = self.dmesg[phase]['list']
1711			rmlist = []
1712			for name in list:
1713				keep = False
1714				for filter in devicefilter:
1715					if filter in name or \
1716						('drv' in list[name] and filter in list[name]['drv']):
1717						keep = True
1718				if not keep:
1719					rmlist.append(name)
1720			for name in rmlist:
1721				del list[name]
1722	def fixupInitcallsThatDidntReturn(self):
1723		# if any calls never returned, clip them at system resume end
1724		for phase in self.sortedPhases():
1725			self.fixupInitcalls(phase)
1726	def phaseOverlap(self, phases):
1727		rmgroups = []
1728		newgroup = []
1729		for group in self.devicegroups:
1730			for phase in phases:
1731				if phase not in group:
1732					continue
1733				for p in group:
1734					if p not in newgroup:
1735						newgroup.append(p)
1736				if group not in rmgroups:
1737					rmgroups.append(group)
1738		for group in rmgroups:
1739			self.devicegroups.remove(group)
1740		self.devicegroups.append(newgroup)
1741	def newActionGlobal(self, name, start, end, pid=-1, color=''):
1742		# which phase is this device callback or action in
1743		phases = self.sortedPhases()
1744		targetphase = 'none'
1745		htmlclass = ''
1746		overlap = 0.0
1747		myphases = []
1748		for phase in phases:
1749			pstart = self.dmesg[phase]['start']
1750			pend = self.dmesg[phase]['end']
1751			# see if the action overlaps this phase
1752			o = max(0, min(end, pend) - max(start, pstart))
1753			if o > 0:
1754				myphases.append(phase)
1755			# set the target phase to the one that overlaps most
1756			if o > overlap:
1757				if overlap > 0 and phase == 'post_resume':
1758					continue
1759				targetphase = phase
1760				overlap = o
1761		# if no target phase was found, pin it to the edge
1762		if targetphase == 'none':
1763			p0start = self.dmesg[phases[0]]['start']
1764			if start <= p0start:
1765				targetphase = phases[0]
1766			else:
1767				targetphase = phases[-1]
1768		if pid == -2:
1769			htmlclass = ' bg'
1770		elif pid == -3:
1771			htmlclass = ' ps'
1772		if len(myphases) > 1:
1773			htmlclass = ' bg'
1774			self.phaseOverlap(myphases)
1775		if targetphase in phases:
1776			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1777			return (targetphase, newname)
1778		return False
1779	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1780		# new device callback for a specific phase
1781		self.html_device_id += 1
1782		devid = '%s%d' % (self.idstr, self.html_device_id)
1783		list = self.dmesg[phase]['list']
1784		length = -1.0
1785		if(start >= 0 and end >= 0):
1786			length = end - start
1787		if pid == -2 or name not in sysvals.tracefuncs.keys():
1788			i = 2
1789			origname = name
1790			while(name in list):
1791				name = '%s[%d]' % (origname, i)
1792				i += 1
1793		list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1794			'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1795		if htmlclass:
1796			list[name]['htmlclass'] = htmlclass
1797		if color:
1798			list[name]['color'] = color
1799		return name
1800	def findDevice(self, phase, name):
1801		list = self.dmesg[phase]['list']
1802		mydev = ''
1803		for devname in sorted(list):
1804			if name == devname or re.match('^%s\[(?P<num>[0-9]*)\]$' % name, devname):
1805				mydev = devname
1806		if mydev:
1807			return list[mydev]
1808		return False
1809	def deviceChildren(self, devname, phase):
1810		devlist = []
1811		list = self.dmesg[phase]['list']
1812		for child in list:
1813			if(list[child]['par'] == devname):
1814				devlist.append(child)
1815		return devlist
1816	def maxDeviceNameSize(self, phase):
1817		size = 0
1818		for name in self.dmesg[phase]['list']:
1819			if len(name) > size:
1820				size = len(name)
1821		return size
1822	def printDetails(self):
1823		sysvals.vprint('Timeline Details:')
1824		sysvals.vprint('          test start: %f' % self.start)
1825		sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1826		tS = tR = False
1827		for phase in self.sortedPhases():
1828			devlist = self.dmesg[phase]['list']
1829			dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
1830			if not tS and ps >= self.tSuspended:
1831				sysvals.vprint('   machine suspended: %f' % self.tSuspended)
1832				tS = True
1833			if not tR and ps >= self.tResumed:
1834				sysvals.vprint('     machine resumed: %f' % self.tResumed)
1835				tR = True
1836			sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
1837			if sysvals.devdump:
1838				sysvals.vprint(''.join('-' for i in range(80)))
1839				maxname = '%d' % self.maxDeviceNameSize(phase)
1840				fmt = '%3d) %'+maxname+'s - %f - %f'
1841				c = 1
1842				for name in sorted(devlist):
1843					s = devlist[name]['start']
1844					e = devlist[name]['end']
1845					sysvals.vprint(fmt % (c, name, s, e))
1846					c += 1
1847				sysvals.vprint(''.join('-' for i in range(80)))
1848		sysvals.vprint('   kernel resume end: %f' % self.tKernRes)
1849		sysvals.vprint('            test end: %f' % self.end)
1850	def deviceChildrenAllPhases(self, devname):
1851		devlist = []
1852		for phase in self.sortedPhases():
1853			list = self.deviceChildren(devname, phase)
1854			for dev in sorted(list):
1855				if dev not in devlist:
1856					devlist.append(dev)
1857		return devlist
1858	def masterTopology(self, name, list, depth):
1859		node = DeviceNode(name, depth)
1860		for cname in list:
1861			# avoid recursions
1862			if name == cname:
1863				continue
1864			clist = self.deviceChildrenAllPhases(cname)
1865			cnode = self.masterTopology(cname, clist, depth+1)
1866			node.children.append(cnode)
1867		return node
1868	def printTopology(self, node):
1869		html = ''
1870		if node.name:
1871			info = ''
1872			drv = ''
1873			for phase in self.sortedPhases():
1874				list = self.dmesg[phase]['list']
1875				if node.name in list:
1876					s = list[node.name]['start']
1877					e = list[node.name]['end']
1878					if list[node.name]['drv']:
1879						drv = ' {'+list[node.name]['drv']+'}'
1880					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1881			html += '<li><b>'+node.name+drv+'</b>'
1882			if info:
1883				html += '<ul>'+info+'</ul>'
1884			html += '</li>'
1885		if len(node.children) > 0:
1886			html += '<ul>'
1887			for cnode in node.children:
1888				html += self.printTopology(cnode)
1889			html += '</ul>'
1890		return html
1891	def rootDeviceList(self):
1892		# list of devices graphed
1893		real = []
1894		for phase in self.sortedPhases():
1895			list = self.dmesg[phase]['list']
1896			for dev in sorted(list):
1897				if list[dev]['pid'] >= 0 and dev not in real:
1898					real.append(dev)
1899		# list of top-most root devices
1900		rootlist = []
1901		for phase in self.sortedPhases():
1902			list = self.dmesg[phase]['list']
1903			for dev in sorted(list):
1904				pdev = list[dev]['par']
1905				pid = list[dev]['pid']
1906				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1907					continue
1908				if pdev and pdev not in real and pdev not in rootlist:
1909					rootlist.append(pdev)
1910		return rootlist
1911	def deviceTopology(self):
1912		rootlist = self.rootDeviceList()
1913		master = self.masterTopology('', rootlist, 0)
1914		return self.printTopology(master)
1915	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1916		# only select devices that will actually show up in html
1917		self.tdevlist = dict()
1918		for phase in self.dmesg:
1919			devlist = []
1920			list = self.dmesg[phase]['list']
1921			for dev in list:
1922				length = (list[dev]['end'] - list[dev]['start']) * 1000
1923				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1924				if width != '0.000000' and length >= mindevlen:
1925					devlist.append(dev)
1926			self.tdevlist[phase] = devlist
1927	def addHorizontalDivider(self, devname, devend):
1928		phase = 'suspend_prepare'
1929		self.newAction(phase, devname, -2, '', \
1930			self.start, devend, '', ' sec', '')
1931		if phase not in self.tdevlist:
1932			self.tdevlist[phase] = []
1933		self.tdevlist[phase].append(devname)
1934		d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1935		return d
1936	def addProcessUsageEvent(self, name, times):
1937		# get the start and end times for this process
1938		maxC = 0
1939		tlast = 0
1940		start = -1
1941		end = -1
1942		for t in sorted(times):
1943			if tlast == 0:
1944				tlast = t
1945				continue
1946			if name in self.pstl[t]:
1947				if start == -1 or tlast < start:
1948					start = tlast
1949				if end == -1 or t > end:
1950					end = t
 
1951			tlast = t
1952		if start == -1 or end == -1:
1953			return 0
1954		# add a new action for this process and get the object
1955		out = self.newActionGlobal(name, start, end, -3)
1956		if not out:
1957			return 0
1958		phase, devname = out
1959		dev = self.dmesg[phase]['list'][devname]
1960		# get the cpu exec data
1961		tlast = 0
1962		clast = 0
1963		cpuexec = dict()
1964		for t in sorted(times):
1965			if tlast == 0 or t <= start or t > end:
1966				tlast = t
1967				continue
1968			list = self.pstl[t]
1969			c = 0
1970			if name in list:
1971				c = list[name]
1972			if c > maxC:
1973				maxC = c
1974			if c != clast:
1975				key = (tlast, t)
1976				cpuexec[key] = c
1977				tlast = t
1978				clast = c
1979		dev['cpuexec'] = cpuexec
1980		return maxC
1981	def createProcessUsageEvents(self):
1982		# get an array of process names
1983		proclist = []
1984		for t in sorted(self.pstl):
1985			pslist = self.pstl[t]
1986			for ps in sorted(pslist):
1987				if ps not in proclist:
1988					proclist.append(ps)
1989		# get a list of data points for suspend and resume
1990		tsus = []
1991		tres = []
1992		for t in sorted(self.pstl):
1993			if t < self.tSuspended:
1994				tsus.append(t)
1995			else:
1996				tres.append(t)
 
1997		# process the events for suspend and resume
1998		if len(proclist) > 0:
1999			sysvals.vprint('Process Execution:')
2000		for ps in proclist:
2001			c = self.addProcessUsageEvent(ps, tsus)
2002			if c > 0:
2003				sysvals.vprint('%25s (sus): %d' % (ps, c))
2004			c = self.addProcessUsageEvent(ps, tres)
2005			if c > 0:
2006				sysvals.vprint('%25s (res): %d' % (ps, c))
2007	def handleEndMarker(self, time, msg=''):
2008		dm = self.dmesg
2009		self.setEnd(time, msg)
2010		self.initDevicegroups()
2011		# give suspend_prepare an end if needed
2012		if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2013			dm['suspend_prepare']['end'] = time
2014		# assume resume machine ends at next phase start
2015		if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2016			np = self.nextPhase('resume_machine', 1)
2017			if np:
2018				dm['resume_machine']['end'] = dm[np]['start']
2019		# if kernel resume end not found, assume its the end marker
2020		if self.tKernRes == 0.0:
2021			self.tKernRes = time
2022		# if kernel suspend start not found, assume its the end marker
2023		if self.tKernSus == 0.0:
2024			self.tKernSus = time
2025		# set resume complete to end at end marker
2026		if 'resume_complete' in dm:
2027			dm['resume_complete']['end'] = time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2028	def debugPrint(self):
2029		for p in self.sortedPhases():
2030			list = self.dmesg[p]['list']
2031			for devname in sorted(list):
2032				dev = list[devname]
2033				if 'ftrace' in dev:
2034					dev['ftrace'].debugPrint(' [%s]' % devname)
2035
2036# Class: DevFunction
2037# Description:
2038#	 A container for kprobe function data we want in the dev timeline
2039class DevFunction:
2040	def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2041		self.row = 0
2042		self.count = 1
2043		self.name = name
2044		self.args = args
2045		self.caller = caller
2046		self.ret = ret
2047		self.time = start
2048		self.length = end - start
2049		self.end = end
2050		self.ubiquitous = u
2051		self.proc = proc
2052		self.pid = pid
2053		self.color = color
2054	def title(self):
2055		cnt = ''
2056		if self.count > 1:
2057			cnt = '(x%d)' % self.count
2058		l = '%0.3fms' % (self.length * 1000)
2059		if self.ubiquitous:
2060			title = '%s(%s)%s <- %s, %s(%s)' % \
2061				(self.name, self.args, cnt, self.caller, self.ret, l)
2062		else:
2063			title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2064		return title.replace('"', '')
2065	def text(self):
2066		if self.count > 1:
2067			text = '%s(x%d)' % (self.name, self.count)
2068		else:
2069			text = self.name
2070		return text
2071	def repeat(self, tgt):
2072		# is the tgt call just a repeat of this call (e.g. are we in a loop)
2073		dt = self.time - tgt.end
2074		# only combine calls if -all- attributes are identical
2075		if tgt.caller == self.caller and \
2076			tgt.name == self.name and tgt.args == self.args and \
2077			tgt.proc == self.proc and tgt.pid == self.pid and \
2078			tgt.ret == self.ret and dt >= 0 and \
2079			dt <= sysvals.callloopmaxgap and \
2080			self.length < sysvals.callloopmaxlen:
2081			return True
2082		return False
2083
2084# Class: FTraceLine
2085# Description:
2086#	 A container for a single line of ftrace data. There are six basic types:
2087#		 callgraph line:
2088#			  call: "  dpm_run_callback() {"
2089#			return: "  }"
2090#			  leaf: " dpm_run_callback();"
2091#		 trace event:
2092#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
2093#			 suspend_resume: phase or custom exec block data
2094#			 device_pm_callback: device callback info
2095class FTraceLine:
2096	def __init__(self, t, m='', d=''):
2097		self.length = 0.0
2098		self.fcall = False
2099		self.freturn = False
2100		self.fevent = False
2101		self.fkprobe = False
2102		self.depth = 0
2103		self.name = ''
2104		self.type = ''
2105		self.time = float(t)
2106		if not m and not d:
2107			return
2108		# is this a trace event
2109		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2110			if(d == 'traceevent'):
2111				# nop format trace event
2112				msg = m
2113			else:
2114				# function_graph format trace event
2115				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2116				msg = em.group('msg')
2117
2118			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
2119			if(emm):
2120				self.name = emm.group('msg')
2121				self.type = emm.group('call')
2122			else:
2123				self.name = msg
2124			km = re.match('^(?P<n>.*)_cal$', self.type)
2125			if km:
2126				self.fcall = True
2127				self.fkprobe = True
2128				self.type = km.group('n')
2129				return
2130			km = re.match('^(?P<n>.*)_ret$', self.type)
2131			if km:
2132				self.freturn = True
2133				self.fkprobe = True
2134				self.type = km.group('n')
2135				return
2136			self.fevent = True
2137			return
2138		# convert the duration to seconds
2139		if(d):
2140			self.length = float(d)/1000000
2141		# the indentation determines the depth
2142		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
2143		if(not match):
2144			return
2145		self.depth = self.getDepth(match.group('d'))
2146		m = match.group('o')
2147		# function return
2148		if(m[0] == '}'):
2149			self.freturn = True
2150			if(len(m) > 1):
2151				# includes comment with function name
2152				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2153				if(match):
2154					self.name = match.group('n').strip()
2155		# function call
2156		else:
2157			self.fcall = True
2158			# function call with children
2159			if(m[-1] == '{'):
2160				match = re.match('^(?P<n>.*) *\(.*', m)
2161				if(match):
2162					self.name = match.group('n').strip()
2163			# function call with no children (leaf)
2164			elif(m[-1] == ';'):
2165				self.freturn = True
2166				match = re.match('^(?P<n>.*) *\(.*', m)
2167				if(match):
2168					self.name = match.group('n').strip()
2169			# something else (possibly a trace marker)
2170			else:
2171				self.name = m
2172	def isCall(self):
2173		return self.fcall and not self.freturn
2174	def isReturn(self):
2175		return self.freturn and not self.fcall
2176	def isLeaf(self):
2177		return self.fcall and self.freturn
2178	def getDepth(self, str):
2179		return len(str)/2
2180	def debugPrint(self, info=''):
2181		if self.isLeaf():
2182			pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2183				self.depth, self.name, self.length*1000000, info))
2184		elif self.freturn:
2185			pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2186				self.depth, self.name, self.length*1000000, info))
2187		else:
2188			pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2189				self.depth, self.name, self.length*1000000, info))
2190	def startMarker(self):
2191		# Is this the starting line of a suspend?
2192		if not self.fevent:
2193			return False
2194		if sysvals.usetracemarkers:
2195			if(self.name.startswith('SUSPEND START')):
2196				return True
2197			return False
2198		else:
2199			if(self.type == 'suspend_resume' and
2200				re.match('suspend_enter\[.*\] begin', self.name)):
2201				return True
2202			return False
2203	def endMarker(self):
2204		# Is this the ending line of a resume?
2205		if not self.fevent:
2206			return False
2207		if sysvals.usetracemarkers:
2208			if(self.name.startswith('RESUME COMPLETE')):
2209				return True
2210			return False
2211		else:
2212			if(self.type == 'suspend_resume' and
2213				re.match('thaw_processes\[.*\] end', self.name)):
2214				return True
2215			return False
2216
2217# Class: FTraceCallGraph
2218# Description:
2219#	 A container for the ftrace callgraph of a single recursive function.
2220#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2221#	 Each instance is tied to a single device in a single phase, and is
2222#	 comprised of an ordered list of FTraceLine objects
2223class FTraceCallGraph:
2224	vfname = 'missing_function_name'
2225	def __init__(self, pid, sv):
2226		self.id = ''
2227		self.invalid = False
2228		self.name = ''
2229		self.partial = False
2230		self.ignore = False
2231		self.start = -1.0
2232		self.end = -1.0
2233		self.list = []
2234		self.depth = 0
2235		self.pid = pid
2236		self.sv = sv
2237	def addLine(self, line):
2238		# if this is already invalid, just leave
2239		if(self.invalid):
2240			if(line.depth == 0 and line.freturn):
2241				return 1
2242			return 0
2243		# invalidate on bad depth
2244		if(self.depth < 0):
2245			self.invalidate(line)
2246			return 0
2247		# ignore data til we return to the current depth
2248		if self.ignore:
2249			if line.depth > self.depth:
2250				return 0
2251			else:
2252				self.list[-1].freturn = True
2253				self.list[-1].length = line.time - self.list[-1].time
2254				self.ignore = False
2255				# if this is a return at self.depth, no more work is needed
2256				if line.depth == self.depth and line.isReturn():
2257					if line.depth == 0:
2258						self.end = line.time
2259						return 1
2260					return 0
2261		# compare current depth with this lines pre-call depth
2262		prelinedep = line.depth
2263		if line.isReturn():
2264			prelinedep += 1
2265		last = 0
2266		lasttime = line.time
2267		if len(self.list) > 0:
2268			last = self.list[-1]
2269			lasttime = last.time
2270			if last.isLeaf():
2271				lasttime += last.length
2272		# handle low misalignments by inserting returns
2273		mismatch = prelinedep - self.depth
2274		warning = self.sv.verbose and abs(mismatch) > 1
2275		info = []
2276		if mismatch < 0:
2277			idx = 0
2278			# add return calls to get the depth down
2279			while prelinedep < self.depth:
2280				self.depth -= 1
2281				if idx == 0 and last and last.isCall():
2282					# special case, turn last call into a leaf
2283					last.depth = self.depth
2284					last.freturn = True
2285					last.length = line.time - last.time
2286					if warning:
2287						info.append(('[make leaf]', last))
2288				else:
2289					vline = FTraceLine(lasttime)
2290					vline.depth = self.depth
2291					vline.name = self.vfname
2292					vline.freturn = True
2293					self.list.append(vline)
2294					if warning:
2295						if idx == 0:
2296							info.append(('', last))
2297						info.append(('[add return]', vline))
2298				idx += 1
2299			if warning:
2300				info.append(('', line))
2301		# handle high misalignments by inserting calls
2302		elif mismatch > 0:
2303			idx = 0
2304			if warning:
2305				info.append(('', last))
2306			# add calls to get the depth up
2307			while prelinedep > self.depth:
2308				if idx == 0 and line.isReturn():
2309					# special case, turn this return into a leaf
2310					line.fcall = True
2311					prelinedep -= 1
2312					if warning:
2313						info.append(('[make leaf]', line))
2314				else:
2315					vline = FTraceLine(lasttime)
2316					vline.depth = self.depth
2317					vline.name = self.vfname
2318					vline.fcall = True
2319					self.list.append(vline)
2320					self.depth += 1
2321					if not last:
2322						self.start = vline.time
2323					if warning:
2324						info.append(('[add call]', vline))
2325				idx += 1
2326			if warning and ('[make leaf]', line) not in info:
2327				info.append(('', line))
2328		if warning:
2329			pprint('WARNING: ftrace data missing, corrections made:')
2330			for i in info:
2331				t, obj = i
2332				if obj:
2333					obj.debugPrint(t)
2334		# process the call and set the new depth
2335		skipadd = False
2336		md = self.sv.max_graph_depth
2337		if line.isCall():
2338			# ignore blacklisted/overdepth funcs
2339			if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2340				self.ignore = True
2341			else:
2342				self.depth += 1
2343		elif line.isReturn():
2344			self.depth -= 1
2345			# remove blacklisted/overdepth/empty funcs that slipped through
2346			if (last and last.isCall() and last.depth == line.depth) or \
2347				(md and last and last.depth >= md) or \
2348				(line.name in self.sv.cgblacklist):
2349				while len(self.list) > 0 and self.list[-1].depth > line.depth:
2350					self.list.pop(-1)
2351				if len(self.list) == 0:
2352					self.invalid = True
2353					return 1
2354				self.list[-1].freturn = True
2355				self.list[-1].length = line.time - self.list[-1].time
2356				self.list[-1].name = line.name
2357				skipadd = True
2358		if len(self.list) < 1:
2359			self.start = line.time
2360		# check for a mismatch that returned all the way to callgraph end
2361		res = 1
2362		if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2363			line = self.list[-1]
2364			skipadd = True
2365			res = -1
2366		if not skipadd:
2367			self.list.append(line)
2368		if(line.depth == 0 and line.freturn):
2369			if(self.start < 0):
2370				self.start = line.time
2371			self.end = line.time
2372			if line.fcall:
2373				self.end += line.length
2374			if self.list[0].name == self.vfname:
2375				self.invalid = True
2376			if res == -1:
2377				self.partial = True
2378			return res
2379		return 0
2380	def invalidate(self, line):
2381		if(len(self.list) > 0):
2382			first = self.list[0]
2383			self.list = []
2384			self.list.append(first)
2385		self.invalid = True
2386		id = 'task %s' % (self.pid)
2387		window = '(%f - %f)' % (self.start, line.time)
2388		if(self.depth < 0):
2389			pprint('Data misalignment for '+id+\
2390				' (buffer overflow), ignoring this callback')
2391		else:
2392			pprint('Too much data for '+id+\
2393				' '+window+', ignoring this callback')
2394	def slice(self, dev):
2395		minicg = FTraceCallGraph(dev['pid'], self.sv)
2396		minicg.name = self.name
2397		mydepth = -1
2398		good = False
2399		for l in self.list:
2400			if(l.time < dev['start'] or l.time > dev['end']):
2401				continue
2402			if mydepth < 0:
2403				if l.name == 'mutex_lock' and l.freturn:
2404					mydepth = l.depth
2405				continue
2406			elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2407				good = True
2408				break
2409			l.depth -= mydepth
2410			minicg.addLine(l)
2411		if not good or len(minicg.list) < 1:
2412			return 0
2413		return minicg
2414	def repair(self, enddepth):
2415		# bring the depth back to 0 with additional returns
2416		fixed = False
2417		last = self.list[-1]
2418		for i in reversed(range(enddepth)):
2419			t = FTraceLine(last.time)
2420			t.depth = i
2421			t.freturn = True
2422			fixed = self.addLine(t)
2423			if fixed != 0:
2424				self.end = last.time
2425				return True
2426		return False
2427	def postProcess(self):
2428		if len(self.list) > 0:
2429			self.name = self.list[0].name
2430		stack = dict()
2431		cnt = 0
2432		last = 0
2433		for l in self.list:
2434			# ftrace bug: reported duration is not reliable
2435			# check each leaf and clip it at max possible length
2436			if last and last.isLeaf():
2437				if last.length > l.time - last.time:
2438					last.length = l.time - last.time
2439			if l.isCall():
2440				stack[l.depth] = l
2441				cnt += 1
2442			elif l.isReturn():
2443				if(l.depth not in stack):
2444					if self.sv.verbose:
2445						pprint('Post Process Error: Depth missing')
2446						l.debugPrint()
2447					return False
2448				# calculate call length from call/return lines
2449				cl = stack[l.depth]
2450				cl.length = l.time - cl.time
2451				if cl.name == self.vfname:
2452					cl.name = l.name
2453				stack.pop(l.depth)
2454				l.length = 0
2455				cnt -= 1
2456			last = l
2457		if(cnt == 0):
2458			# trace caught the whole call tree
2459			return True
2460		elif(cnt < 0):
2461			if self.sv.verbose:
2462				pprint('Post Process Error: Depth is less than 0')
2463			return False
2464		# trace ended before call tree finished
2465		return self.repair(cnt)
2466	def deviceMatch(self, pid, data):
2467		found = ''
2468		# add the callgraph data to the device hierarchy
2469		borderphase = {
2470			'dpm_prepare': 'suspend_prepare',
2471			'dpm_complete': 'resume_complete'
2472		}
2473		if(self.name in borderphase):
2474			p = borderphase[self.name]
2475			list = data.dmesg[p]['list']
2476			for devname in list:
2477				dev = list[devname]
2478				if(pid == dev['pid'] and
2479					self.start <= dev['start'] and
2480					self.end >= dev['end']):
2481					cg = self.slice(dev)
2482					if cg:
2483						dev['ftrace'] = cg
2484					found = devname
2485			return found
2486		for p in data.sortedPhases():
2487			if(data.dmesg[p]['start'] <= self.start and
2488				self.start <= data.dmesg[p]['end']):
2489				list = data.dmesg[p]['list']
2490				for devname in sorted(list, key=lambda k:list[k]['start']):
2491					dev = list[devname]
2492					if(pid == dev['pid'] and
2493						self.start <= dev['start'] and
2494						self.end >= dev['end']):
2495						dev['ftrace'] = self
2496						found = devname
2497						break
2498				break
2499		return found
2500	def newActionFromFunction(self, data):
2501		name = self.name
2502		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2503			return
2504		fs = self.start
2505		fe = self.end
2506		if fs < data.start or fe > data.end:
2507			return
2508		phase = ''
2509		for p in data.sortedPhases():
2510			if(data.dmesg[p]['start'] <= self.start and
2511				self.start < data.dmesg[p]['end']):
2512				phase = p
2513				break
2514		if not phase:
2515			return
2516		out = data.newActionGlobal(name, fs, fe, -2)
2517		if out:
2518			phase, myname = out
2519			data.dmesg[phase]['list'][myname]['ftrace'] = self
2520	def debugPrint(self, info=''):
2521		pprint('%s pid=%d [%f - %f] %.3f us' % \
2522			(self.name, self.pid, self.start, self.end,
2523			(self.end - self.start)*1000000))
2524		for l in self.list:
2525			if l.isLeaf():
2526				pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2527					l.depth, l.name, l.length*1000000, info))
2528			elif l.freturn:
2529				pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2530					l.depth, l.name, l.length*1000000, info))
2531			else:
2532				pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2533					l.depth, l.name, l.length*1000000, info))
2534		pprint(' ')
2535
2536class DevItem:
2537	def __init__(self, test, phase, dev):
2538		self.test = test
2539		self.phase = phase
2540		self.dev = dev
2541	def isa(self, cls):
2542		if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2543			return True
2544		return False
2545
2546# Class: Timeline
2547# Description:
2548#	 A container for a device timeline which calculates
2549#	 all the html properties to display it correctly
2550class Timeline:
2551	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2552	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'
2553	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2554	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2555	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2556	def __init__(self, rowheight, scaleheight):
2557		self.html = ''
2558		self.height = 0  # total timeline height
2559		self.scaleH = scaleheight # timescale (top) row height
2560		self.rowH = rowheight     # device row height
2561		self.bodyH = 0   # body height
2562		self.rows = 0    # total timeline rows
2563		self.rowlines = dict()
2564		self.rowheight = dict()
2565	def createHeader(self, sv, stamp):
2566		if(not stamp['time']):
2567			return
2568		self.html += '<div class="version"><a href="https://01.org/pm-graph">%s v%s</a></div>' \
 
2569			% (sv.title, sv.version)
2570		if sv.logmsg and sv.testlog:
2571			self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2572		if sv.dmesglog:
2573			self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2574		if sv.ftracelog:
2575			self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2576		headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2577		self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2578			stamp['mode'], stamp['time'])
2579		if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2580			stamp['man'] and stamp['plat'] and stamp['cpu']:
2581			headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2582			self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2583
2584	# Function: getDeviceRows
2585	# Description:
2586	#    determine how may rows the device funcs will take
2587	# Arguments:
2588	#	 rawlist: the list of devices/actions for a single phase
2589	# Output:
2590	#	 The total number of rows needed to display this phase of the timeline
2591	def getDeviceRows(self, rawlist):
2592		# clear all rows and set them to undefined
2593		sortdict = dict()
2594		for item in rawlist:
2595			item.row = -1
2596			sortdict[item] = item.length
2597		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2598		remaining = len(sortlist)
2599		rowdata = dict()
2600		row = 1
2601		# try to pack each row with as many ranges as possible
2602		while(remaining > 0):
2603			if(row not in rowdata):
2604				rowdata[row] = []
2605			for i in sortlist:
2606				if(i.row >= 0):
2607					continue
2608				s = i.time
2609				e = i.time + i.length
2610				valid = True
2611				for ritem in rowdata[row]:
2612					rs = ritem.time
2613					re = ritem.time + ritem.length
2614					if(not (((s <= rs) and (e <= rs)) or
2615						((s >= re) and (e >= re)))):
2616						valid = False
2617						break
2618				if(valid):
2619					rowdata[row].append(i)
2620					i.row = row
2621					remaining -= 1
2622			row += 1
2623		return row
2624	# Function: getPhaseRows
2625	# Description:
2626	#	 Organize the timeline entries into the smallest
2627	#	 number of rows possible, with no entry overlapping
2628	# Arguments:
2629	#	 devlist: the list of devices/actions in a group of contiguous phases
2630	# Output:
2631	#	 The total number of rows needed to display this phase of the timeline
2632	def getPhaseRows(self, devlist, row=0, sortby='length'):
2633		# clear all rows and set them to undefined
2634		remaining = len(devlist)
2635		rowdata = dict()
2636		sortdict = dict()
2637		myphases = []
2638		# initialize all device rows to -1 and calculate devrows
2639		for item in devlist:
2640			dev = item.dev
2641			tp = (item.test, item.phase)
2642			if tp not in myphases:
2643				myphases.append(tp)
2644			dev['row'] = -1
2645			if sortby == 'start':
2646				# sort by start 1st, then length 2nd
2647				sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2648			else:
2649				# sort by length 1st, then name 2nd
2650				sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2651			if 'src' in dev:
2652				dev['devrows'] = self.getDeviceRows(dev['src'])
2653		# sort the devlist by length so that large items graph on top
2654		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2655		orderedlist = []
2656		for item in sortlist:
2657			if item.dev['pid'] == -2:
2658				orderedlist.append(item)
2659		for item in sortlist:
2660			if item not in orderedlist:
2661				orderedlist.append(item)
2662		# try to pack each row with as many devices as possible
2663		while(remaining > 0):
2664			rowheight = 1
2665			if(row not in rowdata):
2666				rowdata[row] = []
2667			for item in orderedlist:
2668				dev = item.dev
2669				if(dev['row'] < 0):
2670					s = dev['start']
2671					e = dev['end']
2672					valid = True
2673					for ritem in rowdata[row]:
2674						rs = ritem.dev['start']
2675						re = ritem.dev['end']
2676						if(not (((s <= rs) and (e <= rs)) or
2677							((s >= re) and (e >= re)))):
2678							valid = False
2679							break
2680					if(valid):
2681						rowdata[row].append(item)
2682						dev['row'] = row
2683						remaining -= 1
2684						if 'devrows' in dev and dev['devrows'] > rowheight:
2685							rowheight = dev['devrows']
2686			for t, p in myphases:
2687				if t not in self.rowlines or t not in self.rowheight:
2688					self.rowlines[t] = dict()
2689					self.rowheight[t] = dict()
2690				if p not in self.rowlines[t] or p not in self.rowheight[t]:
2691					self.rowlines[t][p] = dict()
2692					self.rowheight[t][p] = dict()
2693				rh = self.rowH
2694				# section headers should use a different row height
2695				if len(rowdata[row]) == 1 and \
2696					'htmlclass' in rowdata[row][0].dev and \
2697					'sec' in rowdata[row][0].dev['htmlclass']:
2698					rh = 15
2699				self.rowlines[t][p][row] = rowheight
2700				self.rowheight[t][p][row] = rowheight * rh
2701			row += 1
2702		if(row > self.rows):
2703			self.rows = int(row)
2704		return row
2705	def phaseRowHeight(self, test, phase, row):
2706		return self.rowheight[test][phase][row]
2707	def phaseRowTop(self, test, phase, row):
2708		top = 0
2709		for i in sorted(self.rowheight[test][phase]):
2710			if i >= row:
2711				break
2712			top += self.rowheight[test][phase][i]
2713		return top
2714	def calcTotalRows(self):
2715		# Calculate the heights and offsets for the header and rows
2716		maxrows = 0
2717		standardphases = []
2718		for t in self.rowlines:
2719			for p in self.rowlines[t]:
2720				total = 0
2721				for i in sorted(self.rowlines[t][p]):
2722					total += self.rowlines[t][p][i]
2723				if total > maxrows:
2724					maxrows = total
2725				if total == len(self.rowlines[t][p]):
2726					standardphases.append((t, p))
2727		self.height = self.scaleH + (maxrows*self.rowH)
2728		self.bodyH = self.height - self.scaleH
2729		# if there is 1 line per row, draw them the standard way
2730		for t, p in standardphases:
2731			for i in sorted(self.rowheight[t][p]):
2732				self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2733	def createZoomBox(self, mode='command', testcount=1):
2734		# Create bounding box, add buttons
2735		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2736		html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2737		html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2738		html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2739		if mode != 'command':
2740			if testcount > 1:
2741				self.html += html_devlist2
2742				self.html += html_devlist1.format('1')
2743			else:
2744				self.html += html_devlist1.format('')
2745		self.html += html_zoombox
2746		self.html += html_timeline.format('dmesg', self.height)
2747	# Function: createTimeScale
2748	# Description:
2749	#	 Create the timescale for a timeline block
2750	# Arguments:
2751	#	 m0: start time (mode begin)
2752	#	 mMax: end time (mode end)
2753	#	 tTotal: total timeline time
2754	#	 mode: suspend or resume
2755	# Output:
2756	#	 The html code needed to display the time scale
2757	def createTimeScale(self, m0, mMax, tTotal, mode):
2758		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2759		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2760		output = '<div class="timescale">\n'
2761		# set scale for timeline
2762		mTotal = mMax - m0
2763		tS = 0.1
2764		if(tTotal <= 0):
2765			return output+'</div>\n'
2766		if(tTotal > 4):
2767			tS = 1
2768		divTotal = int(mTotal/tS) + 1
2769		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2770		for i in range(divTotal):
2771			htmlline = ''
2772			if(mode == 'suspend'):
2773				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2774				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2775				if(i == divTotal - 1):
2776					val = mode
2777				htmlline = timescale.format(pos, val)
2778			else:
2779				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2780				val = '%0.fms' % (float(i)*tS*1000)
2781				htmlline = timescale.format(pos, val)
2782				if(i == 0):
2783					htmlline = rline.format(mode)
2784			output += htmlline
2785		self.html += output+'</div>\n'
2786
2787# Class: TestProps
2788# Description:
2789#	 A list of values describing the properties of these test runs
2790class TestProps:
2791	stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2792				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2793				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2794	wififmt    = '^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
2795	tstatfmt   = '^# turbostat (?P<t>\S*)'
2796	testerrfmt = '^# enter_sleep_error (?P<e>.*)'
2797	sysinfofmt = '^# sysinfo .*'
2798	cmdlinefmt = '^# command \| (?P<cmd>.*)'
2799	kparamsfmt = '^# kparams \| (?P<kp>.*)'
2800	devpropfmt = '# Device Properties: .*'
2801	pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
2802	tracertypefmt = '# tracer: (?P<t>.*)'
2803	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
2804	procexecfmt = 'ps - (?P<ps>.*)$'
 
2805	ftrace_line_fmt_fg = \
2806		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2807		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2808		'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
2809	ftrace_line_fmt_nop = \
2810		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2811		'(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
2812		'(?P<msg>.*)'
2813	machinesuspend = 'machine_suspend\[.*'
 
 
 
2814	def __init__(self):
2815		self.stamp = ''
2816		self.sysinfo = ''
2817		self.cmdline = ''
2818		self.testerror = []
2819		self.turbostat = []
2820		self.wifi = []
2821		self.fwdata = []
2822		self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2823		self.cgformat = False
2824		self.data = 0
2825		self.ktemp = dict()
2826	def setTracerType(self, tracer):
2827		if(tracer == 'function_graph'):
2828			self.cgformat = True
2829			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2830		elif(tracer == 'nop'):
2831			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2832		else:
2833			doError('Invalid tracer format: [%s]' % tracer)
2834	def stampInfo(self, line, sv):
2835		if re.match(self.stampfmt, line):
2836			self.stamp = line
2837			return True
2838		elif re.match(self.sysinfofmt, line):
2839			self.sysinfo = line
2840			return True
2841		elif re.match(self.tstatfmt, line):
2842			self.turbostat.append(line)
2843			return True
2844		elif re.match(self.wififmt, line):
2845			self.wifi.append(line)
2846			return True
2847		elif re.match(self.testerrfmt, line):
2848			self.testerror.append(line)
2849			return True
2850		elif re.match(self.firmwarefmt, line):
2851			self.fwdata.append(line)
2852			return True
2853		elif(re.match(self.devpropfmt, line)):
2854			self.parseDevprops(line, sv)
2855			return True
2856		elif(re.match(self.pinfofmt, line)):
2857			self.parsePlatformInfo(line, sv)
2858			return True
2859		m = re.match(self.cmdlinefmt, line)
2860		if m:
2861			self.cmdline = m.group('cmd')
2862			return True
2863		m = re.match(self.tracertypefmt, line)
2864		if(m):
2865			self.setTracerType(m.group('t'))
2866			return True
2867		return False
2868	def parseStamp(self, data, sv):
2869		# global test data
2870		m = re.match(self.stampfmt, self.stamp)
2871		if not self.stamp or not m:
2872			doError('data does not include the expected stamp')
2873		data.stamp = {'time': '', 'host': '', 'mode': ''}
2874		dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2875			int(m.group('d')), int(m.group('H')), int(m.group('M')),
2876			int(m.group('S')))
2877		data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2878		data.stamp['host'] = m.group('host')
2879		data.stamp['mode'] = m.group('mode')
2880		data.stamp['kernel'] = m.group('kernel')
2881		if re.match(self.sysinfofmt, self.sysinfo):
2882			for f in self.sysinfo.split('|'):
2883				if '#' in f:
2884					continue
2885				tmp = f.strip().split(':', 1)
2886				key = tmp[0]
2887				val = tmp[1]
2888				data.stamp[key] = val
2889		sv.hostname = data.stamp['host']
2890		sv.suspendmode = data.stamp['mode']
2891		if sv.suspendmode == 'freeze':
2892			self.machinesuspend = 'timekeeping_freeze\[.*'
2893		else:
2894			self.machinesuspend = 'machine_suspend\[.*'
2895		if sv.suspendmode == 'command' and sv.ftracefile != '':
2896			modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2897			fp = sv.openlog(sv.ftracefile, 'r')
2898			for line in fp:
2899				m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
2900				if m and m.group('mode') in ['1', '2', '3', '4']:
2901					sv.suspendmode = modes[int(m.group('mode'))]
2902					data.stamp['mode'] = sv.suspendmode
2903					break
2904			fp.close()
2905		sv.cmdline = self.cmdline
2906		if not sv.stamp:
2907			sv.stamp = data.stamp
2908		# firmware data
2909		if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
2910			m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
2911			if m:
2912				data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
2913				if(data.fwSuspend > 0 or data.fwResume > 0):
2914					data.fwValid = True
2915		# turbostat data
2916		if len(self.turbostat) > data.testnumber:
2917			m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
2918			if m:
2919				data.turbostat = m.group('t')
2920		# wifi data
2921		if len(self.wifi) > data.testnumber:
2922			m = re.match(self.wififmt, self.wifi[data.testnumber])
2923			if m:
2924				data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
2925					'time': float(m.group('t'))}
2926				data.stamp['wifi'] = m.group('d')
2927		# sleep mode enter errors
2928		if len(self.testerror) > data.testnumber:
2929			m = re.match(self.testerrfmt, self.testerror[data.testnumber])
2930			if m:
2931				data.enterfail = m.group('e')
2932	def devprops(self, data):
2933		props = dict()
2934		devlist = data.split(';')
2935		for dev in devlist:
2936			f = dev.split(',')
2937			if len(f) < 3:
2938				continue
2939			dev = f[0]
2940			props[dev] = DevProps()
2941			props[dev].altname = f[1]
2942			if int(f[2]):
2943				props[dev].isasync = True
2944			else:
2945				props[dev].isasync = False
2946		return props
2947	def parseDevprops(self, line, sv):
2948		idx = line.index(': ') + 2
2949		if idx >= len(line):
2950			return
2951		props = self.devprops(line[idx:])
2952		if sv.suspendmode == 'command' and 'testcommandstring' in props:
2953			sv.testcommand = props['testcommandstring'].altname
2954		sv.devprops = props
2955	def parsePlatformInfo(self, line, sv):
2956		m = re.match(self.pinfofmt, line)
2957		if not m:
2958			return
2959		name, info = m.group('val'), m.group('info')
2960		if name == 'devinfo':
2961			sv.devprops = self.devprops(sv.b64unzip(info))
2962			return
2963		elif name == 'testcmd':
2964			sv.testcommand = info
2965			return
2966		field = info.split('|')
2967		if len(field) < 2:
2968			return
2969		cmdline = field[0].strip()
2970		output = sv.b64unzip(field[1].strip())
2971		sv.platinfo.append([name, cmdline, output])
2972
2973# Class: TestRun
2974# Description:
2975#	 A container for a suspend/resume test run. This is necessary as
2976#	 there could be more than one, and they need to be separate.
2977class TestRun:
2978	def __init__(self, dataobj):
2979		self.data = dataobj
2980		self.ftemp = dict()
2981		self.ttemp = dict()
2982
2983class ProcessMonitor:
 
2984	def __init__(self):
2985		self.proclist = dict()
2986		self.running = False
2987	def procstat(self):
2988		c = ['cat /proc/[1-9]*/stat 2>/dev/null']
2989		process = Popen(c, shell=True, stdout=PIPE)
2990		running = dict()
2991		for line in process.stdout:
2992			data = ascii(line).split()
2993			pid = data[0]
2994			name = re.sub('[()]', '', data[1])
2995			user = int(data[13])
2996			kern = int(data[14])
2997			kjiff = ujiff = 0
2998			if pid not in self.proclist:
2999				self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3000			else:
3001				val = self.proclist[pid]
3002				ujiff = user - val['user']
3003				kjiff = kern - val['kern']
3004				val['user'] = user
3005				val['kern'] = kern
3006			if ujiff > 0 or kjiff > 0:
3007				running[pid] = ujiff + kjiff
3008		process.wait()
3009		out = ''
3010		for pid in running:
3011			jiffies = running[pid]
3012			val = self.proclist[pid]
3013			if out:
3014				out += ','
3015			out += '%s-%s %d' % (val['name'], pid, jiffies)
3016		return 'ps - '+out
 
 
 
 
 
 
3017	def processMonitor(self, tid):
3018		while self.running:
3019			out = self.procstat()
3020			if out:
3021				sysvals.fsetVal(out, 'trace_marker')
3022	def start(self):
3023		self.thread = Thread(target=self.processMonitor, args=(0,))
3024		self.running = True
3025		self.thread.start()
3026	def stop(self):
3027		self.running = False
3028
3029# ----------------- FUNCTIONS --------------------
3030
3031# Function: doesTraceLogHaveTraceEvents
3032# Description:
3033#	 Quickly determine if the ftrace log has all of the trace events,
3034#	 markers, and/or kprobes required for primary parsing.
3035def doesTraceLogHaveTraceEvents():
3036	kpcheck = ['_cal: (', '_ret: (']
3037	techeck = ['suspend_resume', 'device_pm_callback']
3038	tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3039	sysvals.usekprobes = False
3040	fp = sysvals.openlog(sysvals.ftracefile, 'r')
3041	for line in fp:
3042		# check for kprobes
3043		if not sysvals.usekprobes:
3044			for i in kpcheck:
3045				if i in line:
3046					sysvals.usekprobes = True
3047		# check for all necessary trace events
3048		check = techeck[:]
3049		for i in techeck:
3050			if i in line:
3051				check.remove(i)
3052		techeck = check
3053		# check for all necessary trace markers
3054		check = tmcheck[:]
3055		for i in tmcheck:
3056			if i in line:
3057				check.remove(i)
3058		tmcheck = check
3059	fp.close()
3060	sysvals.usetraceevents = True if len(techeck) < 2 else False
3061	sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
3062
3063# Function: appendIncompleteTraceLog
3064# Description:
3065#	 [deprecated for kernel 3.15 or newer]
3066#	 Adds callgraph data which lacks trace event data. This is only
3067#	 for timelines generated from 3.15 or older
3068# Arguments:
3069#	 testruns: the array of Data objects obtained from parseKernelLog
3070def appendIncompleteTraceLog(testruns):
3071	# create TestRun vessels for ftrace parsing
3072	testcnt = len(testruns)
3073	testidx = 0
3074	testrun = []
3075	for data in testruns:
3076		testrun.append(TestRun(data))
3077
3078	# extract the callgraph and traceevent data
3079	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3080		os.path.basename(sysvals.ftracefile))
3081	tp = TestProps()
3082	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3083	data = 0
3084	for line in tf:
3085		# remove any latent carriage returns
3086		line = line.replace('\r\n', '')
3087		if tp.stampInfo(line, sysvals):
3088			continue
3089		# parse only valid lines, if this is not one move on
3090		m = re.match(tp.ftrace_line_fmt, line)
3091		if(not m):
3092			continue
3093		# gather the basic message data from the line
3094		m_time = m.group('time')
3095		m_pid = m.group('pid')
3096		m_msg = m.group('msg')
3097		if(tp.cgformat):
3098			m_param3 = m.group('dur')
3099		else:
3100			m_param3 = 'traceevent'
3101		if(m_time and m_pid and m_msg):
3102			t = FTraceLine(m_time, m_msg, m_param3)
3103			pid = int(m_pid)
3104		else:
3105			continue
3106		# the line should be a call, return, or event
3107		if(not t.fcall and not t.freturn and not t.fevent):
3108			continue
3109		# look for the suspend start marker
3110		if(t.startMarker()):
3111			data = testrun[testidx].data
3112			tp.parseStamp(data, sysvals)
3113			data.setStart(t.time, t.name)
3114			continue
3115		if(not data):
3116			continue
3117		# find the end of resume
3118		if(t.endMarker()):
3119			data.setEnd(t.time, t.name)
3120			testidx += 1
3121			if(testidx >= testcnt):
3122				break
3123			continue
3124		# trace event processing
3125		if(t.fevent):
3126			continue
3127		# call/return processing
3128		elif sysvals.usecallgraph:
3129			# create a callgraph object for the data
3130			if(pid not in testrun[testidx].ftemp):
3131				testrun[testidx].ftemp[pid] = []
3132				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3133			# when the call is finished, see which device matches it
3134			cg = testrun[testidx].ftemp[pid][-1]
3135			res = cg.addLine(t)
3136			if(res != 0):
3137				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3138			if(res == -1):
3139				testrun[testidx].ftemp[pid][-1].addLine(t)
3140	tf.close()
3141
3142	for test in testrun:
3143		# add the callgraph data to the device hierarchy
3144		for pid in test.ftemp:
3145			for cg in test.ftemp[pid]:
3146				if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3147					continue
3148				if(not cg.postProcess()):
3149					id = 'task %s cpu %s' % (pid, m.group('cpu'))
3150					sysvals.vprint('Sanity check failed for '+\
3151						id+', ignoring this callback')
3152					continue
3153				callstart = cg.start
3154				callend = cg.end
3155				for p in test.data.sortedPhases():
3156					if(test.data.dmesg[p]['start'] <= callstart and
3157						callstart <= test.data.dmesg[p]['end']):
3158						list = test.data.dmesg[p]['list']
3159						for devname in list:
3160							dev = list[devname]
3161							if(pid == dev['pid'] and
3162								callstart <= dev['start'] and
3163								callend >= dev['end']):
3164								dev['ftrace'] = cg
3165						break
3166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3167# Function: parseTraceLog
3168# Description:
3169#	 Analyze an ftrace log output file generated from this app during
3170#	 the execution phase. Used when the ftrace log is the primary data source
3171#	 and includes the suspend_resume and device_pm_callback trace events
3172#	 The ftrace filename is taken from sysvals
3173# Output:
3174#	 An array of Data objects
3175def parseTraceLog(live=False):
3176	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3177		os.path.basename(sysvals.ftracefile))
3178	if(os.path.exists(sysvals.ftracefile) == False):
3179		doError('%s does not exist' % sysvals.ftracefile)
3180	if not live:
3181		sysvals.setupAllKprobes()
3182	ksuscalls = ['ksys_sync', 'pm_prepare_console']
3183	krescalls = ['pm_restore_console']
3184	tracewatch = ['irq_wakeup']
3185	if sysvals.usekprobes:
3186		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3187			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3188			'CPU_OFF', 'acpi_suspend']
3189
3190	# extract the callgraph and traceevent data
3191	s2idle_enter = hwsus = False
3192	tp = TestProps()
3193	testruns, testdata = [], []
3194	testrun, data, limbo = 0, 0, True
3195	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3196	phase = 'suspend_prepare'
3197	for line in tf:
3198		# remove any latent carriage returns
3199		line = line.replace('\r\n', '')
3200		if tp.stampInfo(line, sysvals):
3201			continue
3202		# ignore all other commented lines
3203		if line[0] == '#':
3204			continue
3205		# ftrace line: parse only valid lines
3206		m = re.match(tp.ftrace_line_fmt, line)
3207		if(not m):
3208			continue
3209		# gather the basic message data from the line
3210		m_time = m.group('time')
3211		m_proc = m.group('proc')
3212		m_pid = m.group('pid')
3213		m_msg = m.group('msg')
3214		if(tp.cgformat):
3215			m_param3 = m.group('dur')
3216		else:
3217			m_param3 = 'traceevent'
3218		if(m_time and m_pid and m_msg):
3219			t = FTraceLine(m_time, m_msg, m_param3)
3220			pid = int(m_pid)
3221		else:
3222			continue
3223		# the line should be a call, return, or event
3224		if(not t.fcall and not t.freturn and not t.fevent):
3225			continue
3226		# find the start of suspend
3227		if(t.startMarker()):
3228			data, limbo = Data(len(testdata)), False
3229			testdata.append(data)
3230			testrun = TestRun(data)
3231			testruns.append(testrun)
3232			tp.parseStamp(data, sysvals)
3233			data.setStart(t.time, t.name)
3234			data.first_suspend_prepare = True
3235			phase = data.setPhase('suspend_prepare', t.time, True)
3236			continue
3237		if(not data or limbo):
3238			continue
3239		# process cpu exec line
3240		if t.type == 'tracing_mark_write':
 
 
3241			m = re.match(tp.procexecfmt, t.name)
3242			if(m):
3243				proclist = dict()
3244				for ps in m.group('ps').split(','):
 
 
 
 
 
 
 
 
 
 
 
3245					val = ps.split()
3246					if not val:
3247						continue
3248					name = val[0].replace('--', '-')
3249					proclist[name] = int(val[1])
3250				data.pstl[t.time] = proclist
 
 
 
 
3251				continue
3252		# find the end of resume
3253		if(t.endMarker()):
3254			if data.tKernRes == 0:
3255				data.tKernRes = t.time
3256			data.handleEndMarker(t.time, t.name)
3257			if(not sysvals.usetracemarkers):
3258				# no trace markers? then quit and be sure to finish recording
3259				# the event we used to trigger resume end
3260				if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3261					# if an entry exists, assume this is its end
3262					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3263			limbo = True
3264			continue
3265		# trace event processing
3266		if(t.fevent):
3267			if(t.type == 'suspend_resume'):
3268				# suspend_resume trace events have two types, begin and end
3269				if(re.match('(?P<name>.*) begin$', t.name)):
3270					isbegin = True
3271				elif(re.match('(?P<name>.*) end$', t.name)):
3272					isbegin = False
3273				else:
3274					continue
3275				if '[' in t.name:
3276					m = re.match('(?P<name>.*)\[.*', t.name)
3277				else:
3278					m = re.match('(?P<name>.*) .*', t.name)
3279				name = m.group('name')
3280				# ignore these events
3281				if(name.split('[')[0] in tracewatch):
3282					continue
3283				# -- phase changes --
3284				# start of kernel suspend
3285				if(re.match('suspend_enter\[.*', t.name)):
3286					if(isbegin and data.tKernSus == 0):
3287						data.tKernSus = t.time
3288					continue
3289				# suspend_prepare start
3290				elif(re.match('dpm_prepare\[.*', t.name)):
3291					if isbegin and data.first_suspend_prepare:
3292						data.first_suspend_prepare = False
3293						if data.tKernSus == 0:
3294							data.tKernSus = t.time
3295						continue
3296					phase = data.setPhase('suspend_prepare', t.time, isbegin)
3297					continue
3298				# suspend start
3299				elif(re.match('dpm_suspend\[.*', t.name)):
3300					phase = data.setPhase('suspend', t.time, isbegin)
3301					continue
3302				# suspend_late start
3303				elif(re.match('dpm_suspend_late\[.*', t.name)):
3304					phase = data.setPhase('suspend_late', t.time, isbegin)
3305					continue
3306				# suspend_noirq start
3307				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3308					phase = data.setPhase('suspend_noirq', t.time, isbegin)
3309					continue
3310				# suspend_machine/resume_machine
3311				elif(re.match(tp.machinesuspend, t.name)):
3312					lp = data.lastPhase()
3313					if(isbegin):
3314						hwsus = True
3315						if lp.startswith('resume_machine'):
3316							# trim out s2idle loops, track time trying to freeze
3317							llp = data.lastPhase(2)
3318							if llp.startswith('suspend_machine'):
3319								if 'trying' not in data.dmesg[llp]:
3320									data.dmesg[llp]['trying'] = 0
3321								data.dmesg[llp]['trying'] += \
 
3322									t.time - data.dmesg[lp]['start']
3323							data.currphase = ''
3324							del data.dmesg[lp]
3325							continue
3326						phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3327						data.setPhase(phase, t.time, False)
3328						if data.tSuspended == 0:
3329							data.tSuspended = t.time
3330					else:
3331						if lp.startswith('resume_machine'):
3332							data.dmesg[lp]['end'] = t.time
3333							continue
3334						phase = data.setPhase('resume_machine', t.time, True)
3335						if(sysvals.suspendmode in ['mem', 'disk']):
3336							susp = phase.replace('resume', 'suspend')
3337							if susp in data.dmesg:
3338								data.dmesg[susp]['end'] = t.time
3339							data.tSuspended = t.time
3340						data.tResumed = t.time
3341					continue
3342				# resume_noirq start
3343				elif(re.match('dpm_resume_noirq\[.*', t.name)):
3344					phase = data.setPhase('resume_noirq', t.time, isbegin)
3345					continue
3346				# resume_early start
3347				elif(re.match('dpm_resume_early\[.*', t.name)):
3348					phase = data.setPhase('resume_early', t.time, isbegin)
3349					continue
3350				# resume start
3351				elif(re.match('dpm_resume\[.*', t.name)):
3352					phase = data.setPhase('resume', t.time, isbegin)
3353					continue
3354				# resume complete start
3355				elif(re.match('dpm_complete\[.*', t.name)):
3356					phase = data.setPhase('resume_complete', t.time, isbegin)
3357					continue
3358				# skip trace events inside devices calls
3359				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3360					continue
3361				# global events (outside device calls) are graphed
3362				if(name not in testrun.ttemp):
3363					testrun.ttemp[name] = []
3364				# special handling for s2idle_enter
3365				if name == 'machine_suspend':
3366					if hwsus:
3367						s2idle_enter = hwsus = False
3368					elif s2idle_enter and not isbegin:
3369						if(len(testrun.ttemp[name]) > 0):
3370							testrun.ttemp[name][-1]['end'] = t.time
3371							testrun.ttemp[name][-1]['loop'] += 1
3372					elif not s2idle_enter and isbegin:
3373						s2idle_enter = True
3374						testrun.ttemp[name].append({'begin': t.time,
3375							'end': t.time, 'pid': pid, 'loop': 0})
3376					continue
3377				if(isbegin):
3378					# create a new list entry
3379					testrun.ttemp[name].append(\
3380						{'begin': t.time, 'end': t.time, 'pid': pid})
3381				else:
3382					if(len(testrun.ttemp[name]) > 0):
3383						# if an entry exists, assume this is its end
3384						testrun.ttemp[name][-1]['end'] = t.time
3385			# device callback start
3386			elif(t.type == 'device_pm_callback_start'):
3387				if phase not in data.dmesg:
3388					continue
3389				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3390					t.name);
3391				if(not m):
3392					continue
3393				drv = m.group('drv')
3394				n = m.group('d')
3395				p = m.group('p')
3396				if(n and p):
3397					data.newAction(phase, n, pid, p, t.time, -1, drv)
3398					if pid not in data.devpids:
3399						data.devpids.append(pid)
3400			# device callback finish
3401			elif(t.type == 'device_pm_callback_end'):
3402				if phase not in data.dmesg:
3403					continue
3404				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
3405				if(not m):
3406					continue
3407				n = m.group('d')
3408				dev = data.findDevice(phase, n)
3409				if dev:
3410					dev['length'] = t.time - dev['start']
3411					dev['end'] = t.time
3412		# kprobe event processing
3413		elif(t.fkprobe):
3414			kprobename = t.type
3415			kprobedata = t.name
3416			key = (kprobename, pid)
3417			# displayname is generated from kprobe data
3418			displayname = ''
3419			if(t.fcall):
3420				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3421				if not displayname:
3422					continue
3423				if(key not in tp.ktemp):
3424					tp.ktemp[key] = []
3425				tp.ktemp[key].append({
3426					'pid': pid,
3427					'begin': t.time,
3428					'end': -1,
3429					'name': displayname,
3430					'cdata': kprobedata,
3431					'proc': m_proc,
3432				})
3433				# start of kernel resume
3434				if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3435					and kprobename in ksuscalls):
3436					data.tKernSus = t.time
3437			elif(t.freturn):
3438				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3439					continue
3440				e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3441				if not e:
3442					continue
 
 
 
3443				e['end'] = t.time
3444				e['rdata'] = kprobedata
3445				# end of kernel resume
3446				if(phase != 'suspend_prepare' and kprobename in krescalls):
3447					if phase in data.dmesg:
3448						data.dmesg[phase]['end'] = t.time
3449					data.tKernRes = t.time
3450
3451		# callgraph processing
3452		elif sysvals.usecallgraph:
3453			# create a callgraph object for the data
3454			key = (m_proc, pid)
3455			if(key not in testrun.ftemp):
3456				testrun.ftemp[key] = []
3457				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3458			# when the call is finished, see which device matches it
3459			cg = testrun.ftemp[key][-1]
3460			res = cg.addLine(t)
3461			if(res != 0):
3462				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3463			if(res == -1):
3464				testrun.ftemp[key][-1].addLine(t)
3465	tf.close()
3466	if len(testdata) < 1:
3467		sysvals.vprint('WARNING: ftrace start marker is missing')
3468	if data and not data.devicegroups:
3469		sysvals.vprint('WARNING: ftrace end marker is missing')
3470		data.handleEndMarker(t.time, t.name)
3471
3472	if sysvals.suspendmode == 'command':
3473		for test in testruns:
3474			for p in test.data.sortedPhases():
3475				if p == 'suspend_prepare':
3476					test.data.dmesg[p]['start'] = test.data.start
3477					test.data.dmesg[p]['end'] = test.data.end
3478				else:
3479					test.data.dmesg[p]['start'] = test.data.end
3480					test.data.dmesg[p]['end'] = test.data.end
3481			test.data.tSuspended = test.data.end
3482			test.data.tResumed = test.data.end
3483			test.data.fwValid = False
3484
3485	# dev source and procmon events can be unreadable with mixed phase height
3486	if sysvals.usedevsrc or sysvals.useprocmon:
3487		sysvals.mixedphaseheight = False
3488
3489	# expand phase boundaries so there are no gaps
3490	for data in testdata:
3491		lp = data.sortedPhases()[0]
3492		for p in data.sortedPhases():
3493			if(p != lp and not ('machine' in p and 'machine' in lp)):
3494				data.dmesg[lp]['end'] = data.dmesg[p]['start']
3495			lp = p
3496
3497	for i in range(len(testruns)):
3498		test = testruns[i]
3499		data = test.data
3500		# find the total time range for this test (begin, end)
3501		tlb, tle = data.start, data.end
3502		if i < len(testruns) - 1:
3503			tle = testruns[i+1].data.start
3504		# add the process usage data to the timeline
3505		if sysvals.useprocmon:
3506			data.createProcessUsageEvents()
3507		# add the traceevent data to the device hierarchy
3508		if(sysvals.usetraceevents):
3509			# add actual trace funcs
3510			for name in sorted(test.ttemp):
3511				for event in test.ttemp[name]:
3512					if event['end'] - event['begin'] <= 0:
3513						continue
3514					title = name
3515					if name == 'machine_suspend' and 'loop' in event:
3516						title = 's2idle_enter_%dx' % event['loop']
3517					data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3518			# add the kprobe based virtual tracefuncs as actual devices
3519			for key in sorted(tp.ktemp):
3520				name, pid = key
3521				if name not in sysvals.tracefuncs:
3522					continue
3523				if pid not in data.devpids:
3524					data.devpids.append(pid)
3525				for e in tp.ktemp[key]:
3526					kb, ke = e['begin'], e['end']
3527					if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3528						continue
3529					color = sysvals.kprobeColor(name)
3530					data.newActionGlobal(e['name'], kb, ke, pid, color)
3531			# add config base kprobes and dev kprobes
3532			if sysvals.usedevsrc:
3533				for key in sorted(tp.ktemp):
3534					name, pid = key
3535					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3536						continue
3537					for e in tp.ktemp[key]:
3538						kb, ke = e['begin'], e['end']
3539						if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3540							continue
3541						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3542							ke, e['cdata'], e['rdata'])
3543		if sysvals.usecallgraph:
3544			# add the callgraph data to the device hierarchy
3545			sortlist = dict()
3546			for key in sorted(test.ftemp):
3547				proc, pid = key
3548				for cg in test.ftemp[key]:
3549					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3550						continue
3551					if(not cg.postProcess()):
3552						id = 'task %s' % (pid)
3553						sysvals.vprint('Sanity check failed for '+\
3554							id+', ignoring this callback')
3555						continue
3556					# match cg data to devices
3557					devname = ''
3558					if sysvals.suspendmode != 'command':
3559						devname = cg.deviceMatch(pid, data)
3560					if not devname:
3561						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3562						sortlist[sortkey] = cg
3563					elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3564						sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3565							(devname, len(cg.list)))
3566			# create blocks for orphan cg data
3567			for sortkey in sorted(sortlist):
3568				cg = sortlist[sortkey]
3569				name = cg.name
3570				if sysvals.isCallgraphFunc(name):
3571					sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3572					cg.newActionFromFunction(data)
3573	if sysvals.suspendmode == 'command':
3574		return (testdata, '')
3575
3576	# fill in any missing phases
3577	error = []
3578	for data in testdata:
3579		tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3580		terr = ''
3581		phasedef = data.phasedef
3582		lp = 'suspend_prepare'
3583		for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3584			if p not in data.dmesg:
3585				if not terr:
3586					ph = p if 'machine' in p else lp
3587					terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
 
 
 
 
 
 
3588					pprint('TEST%s FAILED: %s' % (tn, terr))
3589					error.append(terr)
3590					if data.tSuspended == 0:
3591						data.tSuspended = data.dmesg[lp]['end']
3592					if data.tResumed == 0:
3593						data.tResumed = data.dmesg[lp]['end']
3594					data.fwValid = False
3595				sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3596			lp = p
3597		if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3598			terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3599				(sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3600			error.append(terr)
3601		if not terr and data.enterfail:
3602			pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3603			terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3604			error.append(terr)
3605		if data.tSuspended == 0:
3606			data.tSuspended = data.tKernRes
3607		if data.tResumed == 0:
3608			data.tResumed = data.tSuspended
3609
3610		if(len(sysvals.devicefilter) > 0):
3611			data.deviceFilter(sysvals.devicefilter)
3612		data.fixupInitcallsThatDidntReturn()
3613		if sysvals.usedevsrc:
3614			data.optimizeDevSrc()
3615
3616	# x2: merge any overlapping devices between test runs
3617	if sysvals.usedevsrc and len(testdata) > 1:
3618		tc = len(testdata)
3619		for i in range(tc - 1):
3620			devlist = testdata[i].overflowDevices()
3621			for j in range(i + 1, tc):
3622				testdata[j].mergeOverlapDevices(devlist)
3623		testdata[0].stitchTouchingThreads(testdata[1:])
3624	return (testdata, ', '.join(error))
3625
3626# Function: loadKernelLog
3627# Description:
3628#	 [deprecated for kernel 3.15.0 or newer]
3629#	 load the dmesg file into memory and fix up any ordering issues
3630#	 The dmesg filename is taken from sysvals
3631# Output:
3632#	 An array of empty Data objects with only their dmesgtext attributes set
3633def loadKernelLog():
3634	sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3635		os.path.basename(sysvals.dmesgfile))
3636	if(os.path.exists(sysvals.dmesgfile) == False):
3637		doError('%s does not exist' % sysvals.dmesgfile)
3638
3639	# there can be multiple test runs in a single file
3640	tp = TestProps()
3641	tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3642	testruns = []
3643	data = 0
3644	lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3645	for line in lf:
3646		line = line.replace('\r\n', '')
3647		idx = line.find('[')
3648		if idx > 1:
3649			line = line[idx:]
3650		if tp.stampInfo(line, sysvals):
3651			continue
3652		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3653		if(not m):
3654			continue
3655		msg = m.group("msg")
3656		if(re.match('PM: Syncing filesystems.*', msg)):
 
3657			if(data):
3658				testruns.append(data)
3659			data = Data(len(testruns))
3660			tp.parseStamp(data, sysvals)
3661		if(not data):
3662			continue
3663		m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3664		if(m):
3665			sysvals.stamp['kernel'] = m.group('k')
3666		m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3667		if(m):
 
 
3668			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3669		data.dmesgtext.append(line)
3670	lf.close()
3671
 
 
 
 
3672	if data:
3673		testruns.append(data)
3674	if len(testruns) < 1:
3675		doError('dmesg log has no suspend/resume data: %s' \
3676			% sysvals.dmesgfile)
3677
3678	# fix lines with same timestamp/function with the call and return swapped
3679	for data in testruns:
3680		last = ''
3681		for line in data.dmesgtext:
3682			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
3683				'(?P<f>.*)\+ @ .*, parent: .*', line)
3684			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3685				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3686			if(mc and mr and (mc.group('t') == mr.group('t')) and
3687				(mc.group('f') == mr.group('f'))):
3688				i = data.dmesgtext.index(last)
3689				j = data.dmesgtext.index(line)
3690				data.dmesgtext[i] = line
3691				data.dmesgtext[j] = last
3692			last = line
3693	return testruns
3694
3695# Function: parseKernelLog
3696# Description:
3697#	 [deprecated for kernel 3.15.0 or newer]
3698#	 Analyse a dmesg log output file generated from this app during
3699#	 the execution phase. Create a set of device structures in memory
3700#	 for subsequent formatting in the html output file
3701#	 This call is only for legacy support on kernels where the ftrace
3702#	 data lacks the suspend_resume or device_pm_callbacks trace events.
3703# Arguments:
3704#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3705# Output:
3706#	 The filled Data object
3707def parseKernelLog(data):
3708	phase = 'suspend_runtime'
3709
3710	if(data.fwValid):
3711		sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3712			(data.fwSuspend, data.fwResume))
3713
3714	# dmesg phase match table
3715	dm = {
3716		'suspend_prepare': ['PM: Syncing filesystems.*'],
3717		        'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
3718		   'suspend_late': ['PM: suspend of devices complete after.*'],
3719		  'suspend_noirq': ['PM: late suspend of devices complete after.*'],
3720		'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
3721		 'resume_machine': ['ACPI: Low-level resume complete.*'],
3722		   'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
3723		   'resume_early': ['PM: noirq resume of devices complete after.*'],
3724		         'resume': ['PM: early resume of devices complete after.*'],
3725		'resume_complete': ['PM: resume of devices complete after.*'],
3726		    'post_resume': ['.*Restarting tasks \.\.\..*'],
 
 
 
 
 
 
 
 
 
 
 
 
3727	}
3728	if(sysvals.suspendmode == 'standby'):
3729		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3730	elif(sysvals.suspendmode == 'disk'):
3731		dm['suspend_late'] = ['PM: freeze of devices complete after.*']
3732		dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
3733		dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
3734		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3735		dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
3736		dm['resume'] = ['PM: early restore of devices complete after.*']
3737		dm['resume_complete'] = ['PM: restore of devices complete after.*']
3738	elif(sysvals.suspendmode == 'freeze'):
3739		dm['resume_machine'] = ['ACPI: resume from mwait']
3740
3741	# action table (expected events that occur and show up in dmesg)
3742	at = {
3743		'sync_filesystems': {
3744			'smsg': 'PM: Syncing filesystems.*',
3745			'emsg': 'PM: Preparing system for mem sleep.*' },
3746		'freeze_user_processes': {
3747			'smsg': 'Freezing user space processes .*',
3748			'emsg': 'Freezing remaining freezable tasks.*' },
3749		'freeze_tasks': {
3750			'smsg': 'Freezing remaining freezable tasks.*',
3751			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3752		'ACPI prepare': {
3753			'smsg': 'ACPI: Preparing to enter system sleep state.*',
3754			'emsg': 'PM: Saving platform NVS memory.*' },
3755		'PM vns': {
3756			'smsg': 'PM: Saving platform NVS memory.*',
3757			'emsg': 'Disabling non-boot CPUs .*' },
3758	}
3759
3760	t0 = -1.0
3761	cpu_start = -1.0
3762	prevktime = -1.0
3763	actions = dict()
3764	for line in data.dmesgtext:
3765		# parse each dmesg line into the time and message
3766		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3767		if(m):
3768			val = m.group('ktime')
3769			try:
3770				ktime = float(val)
3771			except:
3772				continue
3773			msg = m.group('msg')
3774			# initialize data start to first line time
3775			if t0 < 0:
3776				data.setStart(ktime)
3777				t0 = ktime
3778		else:
3779			continue
3780
3781		# check for a phase change line
3782		phasechange = False
3783		for p in dm:
3784			for s in dm[p]:
3785				if(re.match(s, msg)):
3786					phasechange, phase = True, p
 
3787					break
3788
3789		# hack for determining resume_machine end for freeze
3790		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3791			and phase == 'resume_machine' and \
3792			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3793			data.setPhase(phase, ktime, False)
3794			phase = 'resume_noirq'
3795			data.setPhase(phase, ktime, True)
3796
3797		if phasechange:
3798			if phase == 'suspend_prepare':
3799				data.setPhase(phase, ktime, True)
3800				data.setStart(ktime)
3801				data.tKernSus = ktime
3802			elif phase == 'suspend':
3803				lp = data.lastPhase()
3804				if lp:
3805					data.setPhase(lp, ktime, False)
3806				data.setPhase(phase, ktime, True)
3807			elif phase == 'suspend_late':
3808				lp = data.lastPhase()
3809				if lp:
3810					data.setPhase(lp, ktime, False)
3811				data.setPhase(phase, ktime, True)
3812			elif phase == 'suspend_noirq':
3813				lp = data.lastPhase()
3814				if lp:
3815					data.setPhase(lp, ktime, False)
3816				data.setPhase(phase, ktime, True)
3817			elif phase == 'suspend_machine':
3818				lp = data.lastPhase()
3819				if lp:
3820					data.setPhase(lp, ktime, False)
3821				data.setPhase(phase, ktime, True)
3822			elif phase == 'resume_machine':
3823				lp = data.lastPhase()
3824				if(sysvals.suspendmode in ['freeze', 'standby']):
3825					data.tSuspended = prevktime
3826					if lp:
3827						data.setPhase(lp, prevktime, False)
3828				else:
3829					data.tSuspended = ktime
3830					if lp:
3831						data.setPhase(lp, prevktime, False)
3832				data.tResumed = ktime
3833				data.setPhase(phase, ktime, True)
3834			elif phase == 'resume_noirq':
3835				lp = data.lastPhase()
3836				if lp:
3837					data.setPhase(lp, ktime, False)
3838				data.setPhase(phase, ktime, True)
3839			elif phase == 'resume_early':
3840				lp = data.lastPhase()
3841				if lp:
3842					data.setPhase(lp, ktime, False)
3843				data.setPhase(phase, ktime, True)
3844			elif phase == 'resume':
3845				lp = data.lastPhase()
3846				if lp:
3847					data.setPhase(lp, ktime, False)
3848				data.setPhase(phase, ktime, True)
3849			elif phase == 'resume_complete':
3850				lp = data.lastPhase()
3851				if lp:
3852					data.setPhase(lp, ktime, False)
3853				data.setPhase(phase, ktime, True)
3854			elif phase == 'post_resume':
3855				lp = data.lastPhase()
3856				if lp:
3857					data.setPhase(lp, ktime, False)
3858				data.setEnd(ktime)
3859				data.tKernRes = ktime
3860				break
3861
3862		# -- device callbacks --
3863		if(phase in data.sortedPhases()):
3864			# device init call
3865			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3866				sm = re.match('calling  (?P<f>.*)\+ @ '+\
3867					'(?P<n>.*), parent: (?P<p>.*)', msg);
3868				f = sm.group('f')
3869				n = sm.group('n')
3870				p = sm.group('p')
3871				if(f and n and p):
3872					data.newAction(phase, f, int(n), p, ktime, -1, '')
3873			# device init return
3874			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3875				'(?P<t>.*) usecs', msg)):
3876				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3877					'(?P<t>.*) usecs(?P<a>.*)', msg);
3878				f = sm.group('f')
3879				t = sm.group('t')
3880				list = data.dmesg[phase]['list']
3881				if(f in list):
3882					dev = list[f]
3883					dev['length'] = int(t)
3884					dev['end'] = ktime
3885
3886		# if trace events are not available, these are better than nothing
3887		if(not sysvals.usetraceevents):
3888			# look for known actions
3889			for a in sorted(at):
3890				if(re.match(at[a]['smsg'], msg)):
3891					if(a not in actions):
3892						actions[a] = []
3893					actions[a].append({'begin': ktime, 'end': ktime})
3894				if(re.match(at[a]['emsg'], msg)):
3895					if(a in actions):
3896						actions[a][-1]['end'] = ktime
3897			# now look for CPU on/off events
3898			if(re.match('Disabling non-boot CPUs .*', msg)):
3899				# start of first cpu suspend
3900				cpu_start = ktime
3901			elif(re.match('Enabling non-boot CPUs .*', msg)):
3902				# start of first cpu resume
3903				cpu_start = ktime
3904			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
 
3905				# end of a cpu suspend, start of the next
3906				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
 
 
3907				cpu = 'CPU'+m.group('cpu')
3908				if(cpu not in actions):
3909					actions[cpu] = []
3910				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3911				cpu_start = ktime
3912			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3913				# end of a cpu resume, start of the next
3914				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3915				cpu = 'CPU'+m.group('cpu')
3916				if(cpu not in actions):
3917					actions[cpu] = []
3918				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3919				cpu_start = ktime
3920		prevktime = ktime
3921	data.initDevicegroups()
3922
3923	# fill in any missing phases
3924	phasedef = data.phasedef
3925	terr, lp = '', 'suspend_prepare'
 
 
3926	for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3927		if p not in data.dmesg:
3928			if not terr:
3929				pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
3930				terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
3931				if data.tSuspended == 0:
3932					data.tSuspended = data.dmesg[lp]['end']
3933				if data.tResumed == 0:
3934					data.tResumed = data.dmesg[lp]['end']
3935			sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3936		lp = p
3937	lp = data.sortedPhases()[0]
3938	for p in data.sortedPhases():
3939		if(p != lp and not ('machine' in p and 'machine' in lp)):
3940			data.dmesg[lp]['end'] = data.dmesg[p]['start']
3941		lp = p
3942	if data.tSuspended == 0:
3943		data.tSuspended = data.tKernRes
3944	if data.tResumed == 0:
3945		data.tResumed = data.tSuspended
3946
3947	# fill in any actions we've found
3948	for name in sorted(actions):
3949		for event in actions[name]:
3950			data.newActionGlobal(name, event['begin'], event['end'])
3951
3952	if(len(sysvals.devicefilter) > 0):
3953		data.deviceFilter(sysvals.devicefilter)
3954	data.fixupInitcallsThatDidntReturn()
3955	return True
3956
3957def callgraphHTML(sv, hf, num, cg, title, color, devid):
3958	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'
3959	html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3960	html_func_end = '</article>\n'
3961	html_func_leaf = '<article>{0} {1}</article>\n'
3962
3963	cgid = devid
3964	if cg.id:
3965		cgid += cg.id
3966	cglen = (cg.end - cg.start) * 1000
3967	if cglen < sv.mincglen:
3968		return num
3969
3970	fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
3971	flen = fmt % (cglen, cg.start, cg.end)
3972	hf.write(html_func_top.format(cgid, color, num, title, flen))
3973	num += 1
3974	for line in cg.list:
3975		if(line.length < 0.000000001):
3976			flen = ''
3977		else:
3978			fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
3979			flen = fmt % (line.length*1000, line.time)
3980		if line.isLeaf():
 
 
3981			hf.write(html_func_leaf.format(line.name, flen))
3982		elif line.freturn:
3983			hf.write(html_func_end)
3984		else:
3985			hf.write(html_func_start.format(num, line.name, flen))
3986			num += 1
3987	hf.write(html_func_end)
3988	return num
3989
3990def addCallgraphs(sv, hf, data):
3991	hf.write('<section id="callgraphs" class="callgraph">\n')
3992	# write out the ftrace data converted to html
3993	num = 0
3994	for p in data.sortedPhases():
3995		if sv.cgphase and p != sv.cgphase:
3996			continue
3997		list = data.dmesg[p]['list']
3998		for d in data.sortedDevices(p):
3999			if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4000				continue
4001			dev = list[d]
4002			color = 'white'
4003			if 'color' in data.dmesg[p]:
4004				color = data.dmesg[p]['color']
4005			if 'color' in dev:
4006				color = dev['color']
4007			name = d if '[' not in d else d.split('[')[0]
4008			if(d in sv.devprops):
4009				name = sv.devprops[d].altName(d)
4010			if 'drv' in dev and dev['drv']:
4011				name += ' {%s}' % dev['drv']
4012			if sv.suspendmode in suspendmodename:
4013				name += ' '+p
4014			if('ftrace' in dev):
4015				cg = dev['ftrace']
4016				if cg.name == sv.ftopfunc:
4017					name = 'top level suspend/resume call'
4018				num = callgraphHTML(sv, hf, num, cg,
4019					name, color, dev['id'])
4020			if('ftraces' in dev):
4021				for cg in dev['ftraces']:
4022					num = callgraphHTML(sv, hf, num, cg,
4023						name+' &rarr; '+cg.name, color, dev['id'])
4024	hf.write('\n\n    </section>\n')
4025
4026def summaryCSS(title, center=True):
4027	tdcenter = 'text-align:center;' if center else ''
4028	out = '<!DOCTYPE html>\n<html>\n<head>\n\
4029	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4030	<title>'+title+'</title>\n\
4031	<style type=\'text/css\'>\n\
4032		.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4033		table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4034		th {border: 1px solid black;background:#222;color:white;}\n\
4035		td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4036		tr.head td {border: 1px solid black;background:#aaa;}\n\
4037		tr.alt {background-color:#ddd;}\n\
4038		tr.notice {color:red;}\n\
4039		.minval {background-color:#BBFFBB;}\n\
4040		.medval {background-color:#BBBBFF;}\n\
4041		.maxval {background-color:#FFBBBB;}\n\
4042		.head a {color:#000;text-decoration: none;}\n\
4043	</style>\n</head>\n<body>\n'
4044	return out
4045
4046# Function: createHTMLSummarySimple
4047# Description:
4048#	 Create summary html file for a series of tests
4049# Arguments:
4050#	 testruns: array of Data objects from parseTraceLog
4051def createHTMLSummarySimple(testruns, htmlfile, title):
4052	# write the html header first (html head, css code, up to body start)
4053	html = summaryCSS('Summary - SleepGraph')
4054
4055	# extract the test data into list
4056	list = dict()
4057	tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4058	iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4059	num = 0
4060	useturbo = usewifi = False
4061	lastmode = ''
4062	cnt = dict()
4063	for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4064		mode = data['mode']
4065		if mode not in list:
4066			list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4067		if lastmode and lastmode != mode and num > 0:
4068			for i in range(2):
4069				s = sorted(tMed[i])
4070				list[lastmode]['med'][i] = s[int(len(s)//2)]
4071				iMed[i] = tMed[i][list[lastmode]['med'][i]]
4072			list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4073			list[lastmode]['min'] = tMin
4074			list[lastmode]['max'] = tMax
4075			list[lastmode]['idx'] = (iMin, iMed, iMax)
4076			tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4077			iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4078			num = 0
4079		pkgpc10 = syslpi = wifi = ''
4080		if 'pkgpc10' in data and 'syslpi' in data:
4081			pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4082		if 'wifi' in data:
4083			wifi, usewifi = data['wifi'], True
4084		res = data['result']
4085		tVal = [float(data['suspend']), float(data['resume'])]
4086		list[mode]['data'].append([data['host'], data['kernel'],
4087			data['time'], tVal[0], tVal[1], data['url'], res,
4088			data['issues'], data['sus_worst'], data['sus_worsttime'],
4089			data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi])
 
4090		idx = len(list[mode]['data']) - 1
4091		if res.startswith('fail in'):
4092			res = 'fail'
4093		if res not in cnt:
4094			cnt[res] = 1
4095		else:
4096			cnt[res] += 1
4097		if res == 'pass':
4098			for i in range(2):
4099				tMed[i][tVal[i]] = idx
4100				tAvg[i] += tVal[i]
4101				if tMin[i] == 0 or tVal[i] < tMin[i]:
4102					iMin[i] = idx
4103					tMin[i] = tVal[i]
4104				if tMax[i] == 0 or tVal[i] > tMax[i]:
4105					iMax[i] = idx
4106					tMax[i] = tVal[i]
4107			num += 1
4108		lastmode = mode
4109	if lastmode and num > 0:
4110		for i in range(2):
4111			s = sorted(tMed[i])
4112			list[lastmode]['med'][i] = s[int(len(s)//2)]
4113			iMed[i] = tMed[i][list[lastmode]['med'][i]]
4114		list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4115		list[lastmode]['min'] = tMin
4116		list[lastmode]['max'] = tMax
4117		list[lastmode]['idx'] = (iMin, iMed, iMax)
4118
4119	# group test header
4120	desc = []
4121	for ilk in sorted(cnt, reverse=True):
4122		if cnt[ilk] > 0:
4123			desc.append('%d %s' % (cnt[ilk], ilk))
4124	html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4125	th = '\t<th>{0}</th>\n'
4126	td = '\t<td>{0}</td>\n'
4127	tdh = '\t<td{1}>{0}</td>\n'
4128	tdlink = '\t<td><a href="{0}">html</a></td>\n'
4129	cols = 12
4130	if useturbo:
4131		cols += 2
4132	if usewifi:
4133		cols += 1
4134	colspan = '%d' % cols
4135
4136	# table header
4137	html += '<table>\n<tr>\n' + th.format('#') +\
4138		th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4139		th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4140		th.format('Suspend') + th.format('Resume') +\
4141		th.format('Worst Suspend Device') + th.format('SD Time') +\
4142		th.format('Worst Resume Device') + th.format('RD Time')
4143	if useturbo:
4144		html += th.format('PkgPC10') + th.format('SysLPI')
4145	if usewifi:
4146		html += th.format('Wifi')
4147	html += th.format('Detail')+'</tr>\n'
4148	# export list into html
4149	head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4150		'<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4151		'<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4152		'<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4153		'<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4154		'Resume Avg={6} '+\
4155		'<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4156		'<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4157		'<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4158		'</tr>\n'
4159	headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4160		colspan+'></td></tr>\n'
4161	for mode in sorted(list):
4162		# header line for each suspend mode
4163		num = 0
4164		tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4165			list[mode]['max'], list[mode]['med']
4166		count = len(list[mode]['data'])
4167		if 'idx' in list[mode]:
4168			iMin, iMed, iMax = list[mode]['idx']
4169			html += head.format('%d' % count, mode.upper(),
4170				'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4171				'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4172				mode.lower()
4173			)
4174		else:
4175			iMin = iMed = iMax = [-1, -1, -1]
4176			html += headnone.format('%d' % count, mode.upper())
4177		for d in list[mode]['data']:
4178			# row classes - alternate row color
4179			rcls = ['alt'] if num % 2 == 1 else []
4180			if d[6] != 'pass':
4181				rcls.append('notice')
4182			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4183			# figure out if the line has sus or res highlighted
4184			idx = list[mode]['data'].index(d)
4185			tHigh = ['', '']
4186			for i in range(2):
4187				tag = 's%s' % mode if i == 0 else 'r%s' % mode
4188				if idx == iMin[i]:
4189					tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4190				elif idx == iMax[i]:
4191					tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4192				elif idx == iMed[i]:
4193					tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4194			html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4195			html += td.format(mode)										# mode
4196			html += td.format(d[0])										# host
4197			html += td.format(d[1])										# kernel
4198			html += td.format(d[2])										# time
4199			html += td.format(d[6])										# result
4200			html += td.format(d[7])										# issues
4201			html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('')	# suspend
4202			html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('')	# resume
4203			html += td.format(d[8])										# sus_worst
4204			html += td.format('%.3f ms' % d[9])	if d[9] else td.format('')		# sus_worst time
4205			html += td.format(d[10])									# res_worst
4206			html += td.format('%.3f ms' % d[11]) if d[11] else td.format('')	# res_worst time
4207			if useturbo:
4208				html += td.format(d[12])								# pkg_pc10
4209				html += td.format(d[13])								# syslpi
4210			if usewifi:
4211				html += td.format(d[14])								# wifi
4212			html += tdlink.format(d[5]) if d[5] else td.format('')		# url
4213			html += '</tr>\n'
4214			num += 1
4215
4216	# flush the data to file
4217	hf = open(htmlfile, 'w')
4218	hf.write(html+'</table>\n</body>\n</html>\n')
4219	hf.close()
4220
4221def createHTMLDeviceSummary(testruns, htmlfile, title):
4222	html = summaryCSS('Device Summary - SleepGraph', False)
4223
4224	# create global device list from all tests
4225	devall = dict()
4226	for data in testruns:
4227		host, url, devlist = data['host'], data['url'], data['devlist']
4228		for type in devlist:
4229			if type not in devall:
4230				devall[type] = dict()
4231			mdevlist, devlist = devall[type], data['devlist'][type]
4232			for name in devlist:
4233				length = devlist[name]
4234				if name not in mdevlist:
4235					mdevlist[name] = {'name': name, 'host': host,
4236						'worst': length, 'total': length, 'count': 1,
4237						'url': url}
4238				else:
4239					if length > mdevlist[name]['worst']:
4240						mdevlist[name]['worst'] = length
4241						mdevlist[name]['url'] = url
4242						mdevlist[name]['host'] = host
4243					mdevlist[name]['total'] += length
4244					mdevlist[name]['count'] += 1
4245
4246	# generate the html
4247	th = '\t<th>{0}</th>\n'
4248	td = '\t<td align=center>{0}</td>\n'
4249	tdr = '\t<td align=right>{0}</td>\n'
4250	tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4251	limit = 1
4252	for type in sorted(devall, reverse=True):
4253		num = 0
4254		devlist = devall[type]
4255		# table header
4256		html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4257			(title, type.upper(), limit)
4258		html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4259			th.format('Average Time') + th.format('Count') +\
4260			th.format('Worst Time') + th.format('Host (worst time)') +\
4261			th.format('Link (worst time)') + '</tr>\n'
4262		for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4263			devlist[k]['total'], devlist[k]['name']), reverse=True):
4264			data = devall[type][name]
4265			data['average'] = data['total'] / data['count']
4266			if data['average'] < limit:
4267				continue
4268			# row classes - alternate row color
4269			rcls = ['alt'] if num % 2 == 1 else []
4270			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4271			html += tdr.format(data['name'])				# name
4272			html += td.format('%.3f ms' % data['average'])	# average
4273			html += td.format(data['count'])				# count
4274			html += td.format('%.3f ms' % data['worst'])	# worst
4275			html += td.format(data['host'])					# host
4276			html += tdlink.format(data['url'])				# url
4277			html += '</tr>\n'
4278			num += 1
4279		html += '</table>\n'
4280
4281	# flush the data to file
4282	hf = open(htmlfile, 'w')
4283	hf.write(html+'</body>\n</html>\n')
4284	hf.close()
4285	return devall
4286
4287def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4288	multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4289	html = summaryCSS('Issues Summary - SleepGraph', False)
4290	total = len(testruns)
4291
4292	# generate the html
4293	th = '\t<th>{0}</th>\n'
4294	td = '\t<td align={0}>{1}</td>\n'
4295	tdlink = '<a href="{1}">{0}</a>'
4296	subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4297	html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4298	html += '<tr>\n' + th.format('Issue') + th.format('Count')
4299	if multihost:
4300		html += th.format('Hosts')
4301	html += th.format('Tests') + th.format('Fail Rate') +\
4302		th.format('First Instance') + '</tr>\n'
4303
4304	num = 0
4305	for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4306		testtotal = 0
4307		links = []
4308		for host in sorted(e['urls']):
4309			links.append(tdlink.format(host, e['urls'][host][0]))
4310			testtotal += len(e['urls'][host])
4311		rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4312		# row classes - alternate row color
4313		rcls = ['alt'] if num % 2 == 1 else []
4314		html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4315		html += td.format('left', e['line'])		# issue
4316		html += td.format('center', e['count'])		# count
4317		if multihost:
4318			html += td.format('center', len(e['urls']))	# hosts
4319		html += td.format('center', testtotal)		# test count
4320		html += td.format('center', rate)			# test rate
4321		html += td.format('center nowrap', '<br>'.join(links))	# links
4322		html += '</tr>\n'
4323		num += 1
4324
4325	# flush the data to file
4326	hf = open(htmlfile, 'w')
4327	hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4328	hf.close()
4329	return issues
4330
4331def ordinal(value):
4332	suffix = 'th'
4333	if value < 10 or value > 19:
4334		if value % 10 == 1:
4335			suffix = 'st'
4336		elif value % 10 == 2:
4337			suffix = 'nd'
4338		elif value % 10 == 3:
4339			suffix = 'rd'
4340	return '%d%s' % (value, suffix)
4341
4342# Function: createHTML
4343# Description:
4344#	 Create the output html file from the resident test data
4345# Arguments:
4346#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4347# Output:
4348#	 True if the html file was created, false if it failed
4349def createHTML(testruns, testfail):
4350	if len(testruns) < 1:
4351		pprint('ERROR: Not enough test data to build a timeline')
4352		return
4353
4354	kerror = False
4355	for data in testruns:
4356		if data.kerror:
4357			kerror = True
4358		if(sysvals.suspendmode in ['freeze', 'standby']):
4359			data.trimFreezeTime(testruns[-1].tSuspended)
4360		else:
4361			data.getMemTime()
4362
4363	# html function templates
4364	html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
4365	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'
4366	html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4367	html_timetotal = '<table class="time1">\n<tr>'\
4368		'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4369		'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4370		'</tr>\n</table>\n'
4371	html_timetotal2 = '<table class="time1">\n<tr>'\
4372		'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4373		'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4374		'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4375		'</tr>\n</table>\n'
4376	html_timetotal3 = '<table class="time1">\n<tr>'\
4377		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
4378		'<td class="yellow">Command: <b>{1}</b></td>'\
4379		'</tr>\n</table>\n'
4380	html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4381	html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4382	html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4383	html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
4384
4385	# html format variables
4386	scaleH = 20
4387	if kerror:
4388		scaleH = 40
4389
4390	# device timeline
4391	devtl = Timeline(30, scaleH)
4392
4393	# write the test title and general info header
4394	devtl.createHeader(sysvals, testruns[0].stamp)
4395
4396	# Generate the header for this timeline
4397	for data in testruns:
4398		tTotal = data.end - data.start
4399		if(tTotal == 0):
4400			doError('No timeline data')
4401		if sysvals.suspendmode == 'command':
4402			run_time = '%.0f' % (tTotal * 1000)
4403			if sysvals.testcommand:
4404				testdesc = sysvals.testcommand
4405			else:
4406				testdesc = 'unknown'
4407			if(len(testruns) > 1):
4408				testdesc = ordinal(data.testnumber+1)+' '+testdesc
4409			thtml = html_timetotal3.format(run_time, testdesc)
4410			devtl.html += thtml
4411			continue
4412		# typical full suspend/resume header
4413		stot, rtot = sktime, rktime = data.getTimeValues()
4414		ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4415		if data.fwValid:
4416			stot += (data.fwSuspend/1000000.0)
4417			rtot += (data.fwResume/1000000.0)
4418			ssrc.append('firmware')
4419			rsrc.append('firmware')
4420			testdesc = 'Total'
4421		if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4422			rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4423			rsrc.append('wifi')
4424			testdesc = 'Total'
4425		suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4426		stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4427			(sysvals.suspendmode, ' & '.join(ssrc))
4428		rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4429			(sysvals.suspendmode, ' & '.join(rsrc))
4430		if(len(testruns) > 1):
4431			testdesc = testdesc2 = ordinal(data.testnumber+1)
4432			testdesc2 += ' '
4433		if(len(data.tLow) == 0):
4434			thtml = html_timetotal.format(suspend_time, \
4435				resume_time, testdesc, stitle, rtitle)
4436		else:
4437			low_time = '+'.join(data.tLow)
4438			thtml = html_timetotal2.format(suspend_time, low_time, \
4439				resume_time, testdesc, stitle, rtitle)
4440		devtl.html += thtml
4441		if not data.fwValid and 'dev' not in data.wifi:
4442			continue
4443		# extra detail when the times come from multiple sources
4444		thtml = '<table class="time2">\n<tr>'
4445		thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4446		if data.fwValid:
4447			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4448			rftime = '%.3f'%(data.fwResume / 1000000.0)
4449			thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4450			thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4451		thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4452		if 'time' in data.wifi:
4453			if data.wifi['stat'] != 'timeout':
4454				wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
4455			else:
4456				wtime = 'TIMEOUT'
4457			thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4458		thtml += '</tr>\n</table>\n'
4459		devtl.html += thtml
4460	if testfail:
4461		devtl.html += html_fail.format(testfail)
4462
4463	# time scale for potentially multiple datasets
4464	t0 = testruns[0].start
4465	tMax = testruns[-1].end
4466	tTotal = tMax - t0
4467
4468	# determine the maximum number of rows we need to draw
4469	fulllist = []
4470	threadlist = []
4471	pscnt = 0
4472	devcnt = 0
4473	for data in testruns:
4474		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4475		for group in data.devicegroups:
4476			devlist = []
4477			for phase in group:
4478				for devname in sorted(data.tdevlist[phase]):
4479					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4480					devlist.append(d)
4481					if d.isa('kth'):
4482						threadlist.append(d)
4483					else:
4484						if d.isa('ps'):
4485							pscnt += 1
4486						else:
4487							devcnt += 1
4488						fulllist.append(d)
4489			if sysvals.mixedphaseheight:
4490				devtl.getPhaseRows(devlist)
4491	if not sysvals.mixedphaseheight:
4492		if len(threadlist) > 0 and len(fulllist) > 0:
4493			if pscnt > 0 and devcnt > 0:
4494				msg = 'user processes & device pm callbacks'
4495			elif pscnt > 0:
4496				msg = 'user processes'
4497			else:
4498				msg = 'device pm callbacks'
4499			d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4500			fulllist.insert(0, d)
4501		devtl.getPhaseRows(fulllist)
4502		if len(threadlist) > 0:
4503			d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4504			threadlist.insert(0, d)
4505			devtl.getPhaseRows(threadlist, devtl.rows)
4506	devtl.calcTotalRows()
4507
4508	# draw the full timeline
4509	devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4510	for data in testruns:
4511		# draw each test run and block chronologically
4512		phases = {'suspend':[],'resume':[]}
4513		for phase in data.sortedPhases():
4514			if data.dmesg[phase]['start'] >= data.tSuspended:
4515				phases['resume'].append(phase)
4516			else:
4517				phases['suspend'].append(phase)
4518		# now draw the actual timeline blocks
4519		for dir in phases:
4520			# draw suspend and resume blocks separately
4521			bname = '%s%d' % (dir[0], data.testnumber)
4522			if dir == 'suspend':
4523				m0 = data.start
4524				mMax = data.tSuspended
4525				left = '%f' % (((m0-t0)*100.0)/tTotal)
4526			else:
4527				m0 = data.tSuspended
4528				mMax = data.end
4529				# in an x2 run, remove any gap between blocks
4530				if len(testruns) > 1 and data.testnumber == 0:
4531					mMax = testruns[1].start
4532				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4533			mTotal = mMax - m0
4534			# if a timeline block is 0 length, skip altogether
4535			if mTotal == 0:
4536				continue
4537			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4538			devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4539			for b in phases[dir]:
4540				# draw the phase color background
4541				phase = data.dmesg[b]
4542				length = phase['end']-phase['start']
4543				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4544				width = '%f' % ((length*100.0)/mTotal)
4545				devtl.html += devtl.html_phase.format(left, width, \
4546					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4547					data.dmesg[b]['color'], '')
4548			for e in data.errorinfo[dir]:
4549				# draw red lines for any kernel errors found
4550				type, t, idx1, idx2 = e
4551				id = '%d_%d' % (idx1, idx2)
4552				right = '%f' % (((mMax-t)*100.0)/mTotal)
4553				devtl.html += html_error.format(right, id, type)
4554			for b in phases[dir]:
4555				# draw the devices for this phase
4556				phaselist = data.dmesg[b]['list']
4557				for d in sorted(data.tdevlist[b]):
4558					dname = d if '[' not in d else d.split('[')[0]
4559					name, dev = dname, phaselist[d]
4560					drv = xtraclass = xtrainfo = xtrastyle = ''
4561					if 'htmlclass' in dev:
4562						xtraclass = dev['htmlclass']
4563					if 'color' in dev:
4564						xtrastyle = 'background:%s;' % dev['color']
4565					if(d in sysvals.devprops):
4566						name = sysvals.devprops[d].altName(d)
4567						xtraclass = sysvals.devprops[d].xtraClass()
4568						xtrainfo = sysvals.devprops[d].xtraInfo()
4569					elif xtraclass == ' kth':
4570						xtrainfo = ' kernel_thread'
4571					if('drv' in dev and dev['drv']):
4572						drv = ' {%s}' % dev['drv']
4573					rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4574					rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4575					top = '%.3f' % (rowtop + devtl.scaleH)
4576					left = '%f' % (((dev['start']-m0)*100)/mTotal)
4577					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4578					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4579					title = name+drv+xtrainfo+length
4580					if sysvals.suspendmode == 'command':
4581						title += sysvals.testcommand
4582					elif xtraclass == ' ps':
4583						if 'suspend' in b:
4584							title += 'pre_suspend_process'
4585						else:
4586							title += 'post_resume_process'
4587					else:
4588						title += b
4589					devtl.html += devtl.html_device.format(dev['id'], \
4590						title, left, top, '%.3f'%rowheight, width, \
4591						dname+drv, xtraclass, xtrastyle)
4592					if('cpuexec' in dev):
4593						for t in sorted(dev['cpuexec']):
4594							start, end = t
4595							j = float(dev['cpuexec'][t]) / 5
4596							if j > 1.0:
4597								j = 1.0
4598							height = '%.3f' % (rowheight/3)
4599							top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4600							left = '%f' % (((start-m0)*100)/mTotal)
4601							width = '%f' % ((end-start)*100/mTotal)
4602							color = 'rgba(255, 0, 0, %f)' % j
4603							devtl.html += \
4604								html_cpuexec.format(left, top, height, width, color)
4605					if('src' not in dev):
4606						continue
4607					# draw any trace events for this device
4608					for e in dev['src']:
4609						if e.length == 0:
4610							continue
4611						height = '%.3f' % devtl.rowH
4612						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4613						left = '%f' % (((e.time-m0)*100)/mTotal)
4614						width = '%f' % (e.length*100/mTotal)
4615						xtrastyle = ''
4616						if e.color:
4617							xtrastyle = 'background:%s;' % e.color
4618						devtl.html += \
4619							html_traceevent.format(e.title(), \
4620								left, top, height, width, e.text(), '', xtrastyle)
4621			# draw the time scale, try to make the number of labels readable
4622			devtl.createTimeScale(m0, mMax, tTotal, dir)
4623			devtl.html += '</div>\n'
4624
4625	# timeline is finished
4626	devtl.html += '</div>\n</div>\n'
4627
4628	# draw a legend which describes the phases by color
4629	if sysvals.suspendmode != 'command':
4630		phasedef = testruns[-1].phasedef
4631		devtl.html += '<div class="legend">\n'
4632		pdelta = 100.0/len(phasedef.keys())
4633		pmargin = pdelta / 4.0
4634		for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4635			id, p = '', phasedef[phase]
4636			for word in phase.split('_'):
4637				id += word[0]
4638			order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4639			name = phase.replace('_', ' &nbsp;')
4640			devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4641		devtl.html += '</div>\n'
4642
4643	hf = open(sysvals.htmlfile, 'w')
4644	addCSS(hf, sysvals, len(testruns), kerror)
4645
4646	# write the device timeline
4647	hf.write(devtl.html)
4648	hf.write('<div id="devicedetailtitle"></div>\n')
4649	hf.write('<div id="devicedetail" style="display:none;">\n')
4650	# draw the colored boxes for the device detail section
4651	for data in testruns:
4652		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4653		pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4654		hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4655			'0', '0', pscolor))
4656		for b in data.sortedPhases():
4657			phase = data.dmesg[b]
4658			length = phase['end']-phase['start']
4659			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4660			width = '%.3f' % ((length*100.0)/tTotal)
4661			hf.write(devtl.html_phaselet.format(b, left, width, \
4662				data.dmesg[b]['color']))
4663		hf.write(devtl.html_phaselet.format('post_resume_process', \
4664			'0', '0', pscolor))
4665		if sysvals.suspendmode == 'command':
4666			hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4667		hf.write('</div>\n')
4668	hf.write('</div>\n')
4669
4670	# write the ftrace data (callgraph)
4671	if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4672		data = testruns[sysvals.cgtest]
4673	else:
4674		data = testruns[-1]
4675	if sysvals.usecallgraph:
4676		addCallgraphs(sysvals, hf, data)
4677
4678	# add the test log as a hidden div
4679	if sysvals.testlog and sysvals.logmsg:
4680		hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4681	# add the dmesg log as a hidden div
4682	if sysvals.dmesglog and sysvals.dmesgfile:
4683		hf.write('<div id="dmesglog" style="display:none;">\n')
4684		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4685		for line in lf:
4686			line = line.replace('<', '&lt').replace('>', '&gt')
4687			hf.write(line)
4688		lf.close()
4689		hf.write('</div>\n')
4690	# add the ftrace log as a hidden div
4691	if sysvals.ftracelog and sysvals.ftracefile:
4692		hf.write('<div id="ftracelog" style="display:none;">\n')
4693		lf = sysvals.openlog(sysvals.ftracefile, 'r')
4694		for line in lf:
4695			hf.write(line)
4696		lf.close()
4697		hf.write('</div>\n')
4698
4699	# write the footer and close
4700	addScriptCode(hf, testruns)
4701	hf.write('</body>\n</html>\n')
4702	hf.close()
4703	return True
4704
4705def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4706	kernel = sv.stamp['kernel']
4707	host = sv.hostname[0].upper()+sv.hostname[1:]
4708	mode = sv.suspendmode
4709	if sv.suspendmode in suspendmodename:
4710		mode = suspendmodename[sv.suspendmode]
4711	title = host+' '+mode+' '+kernel
4712
4713	# various format changes by flags
4714	cgchk = 'checked'
4715	cgnchk = 'not(:checked)'
4716	if sv.cgexp:
4717		cgchk = 'not(:checked)'
4718		cgnchk = 'checked'
4719
4720	hoverZ = 'z-index:8;'
4721	if sv.usedevsrc:
4722		hoverZ = ''
4723
4724	devlistpos = 'absolute'
4725	if testcount > 1:
4726		devlistpos = 'relative'
4727
4728	scaleTH = 20
4729	if kerror:
4730		scaleTH = 60
4731
4732	# write the html header first (html head, css code, up to body start)
4733	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4734	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4735	<title>'+title+'</title>\n\
4736	<style type=\'text/css\'>\n\
4737		body {overflow-y:scroll;}\n\
4738		.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4739		.stamp.sysinfo {font:10px Arial;}\n\
4740		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4741		.callgraph article * {padding-left:28px;}\n\
4742		h1 {color:black;font:bold 30px Times;}\n\
4743		t0 {color:black;font:bold 30px Times;}\n\
4744		t1 {color:black;font:30px Times;}\n\
4745		t2 {color:black;font:25px Times;}\n\
4746		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
4747		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4748		cS {font:bold 13px Times;}\n\
4749		table {width:100%;}\n\
4750		.gray {background:rgba(80,80,80,0.1);}\n\
4751		.green {background:rgba(204,255,204,0.4);}\n\
4752		.purple {background:rgba(128,0,128,0.2);}\n\
4753		.yellow {background:rgba(255,255,204,0.4);}\n\
4754		.blue {background:rgba(169,208,245,0.4);}\n\
4755		.time1 {font:22px Arial;border:1px solid;}\n\
4756		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4757		.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
4758		td {text-align:center;}\n\
4759		r {color:#500000;font:15px Tahoma;}\n\
4760		n {color:#505050;font:15px Tahoma;}\n\
4761		.tdhl {color:red;}\n\
4762		.hide {display:none;}\n\
4763		.pf {display:none;}\n\
4764		.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\
4765		.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\
4766		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4767		.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4768		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4769		.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\
4770		.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4771		.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4772		.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4773		.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4774		.hover.sync {background:white;}\n\
4775		.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4776		.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4777		.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\
4778		.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4779		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4780		.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4781		.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4782		.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4783		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4784		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4785		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4786		.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4787		.devlist {position:'+devlistpos+';width:190px;}\n\
4788		a:link {color:white;text-decoration:none;}\n\
4789		a:visited {color:white;}\n\
4790		a:hover {color:white;}\n\
4791		a:active {color:white;}\n\
4792		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4793		#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4794		.tblock {position:absolute;height:100%;background:#ddd;}\n\
4795		.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4796		.bg {z-index:1;}\n\
4797'+extra+'\
4798	</style>\n</head>\n<body>\n'
4799	hf.write(html_header)
4800
4801# Function: addScriptCode
4802# Description:
4803#	 Adds the javascript code to the output html
4804# Arguments:
4805#	 hf: the open html file pointer
4806#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4807def addScriptCode(hf, testruns):
4808	t0 = testruns[0].start * 1000
4809	tMax = testruns[-1].end * 1000
 
4810	# create an array in javascript memory with the device details
4811	detail = '	var devtable = [];\n'
4812	for data in testruns:
4813		topo = data.deviceTopology()
4814		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
4815	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
4816	# add the code which will manipulate the data in the browser
4817	script_code = \
4818	'<script type="text/javascript">\n'+detail+\
4819	'	var resolution = -1;\n'\
4820	'	var dragval = [0, 0];\n'\
4821	'	function redrawTimescale(t0, tMax, tS) {\n'\
4822	'		var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4823	'		var tTotal = tMax - t0;\n'\
4824	'		var list = document.getElementsByClassName("tblock");\n'\
4825	'		for (var i = 0; i < list.length; i++) {\n'\
4826	'			var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4827	'			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4828	'			var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4829	'			var mMax = m0 + mTotal;\n'\
4830	'			var html = "";\n'\
4831	'			var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4832	'			if(divTotal > 1000) continue;\n'\
4833	'			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4834	'			var pos = 0.0, val = 0.0;\n'\
4835	'			for (var j = 0; j < divTotal; j++) {\n'\
4836	'				var htmlline = "";\n'\
4837	'				var mode = list[i].id[5];\n'\
4838	'				if(mode == "s") {\n'\
4839	'					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4840	'					val = (j-divTotal+1)*tS;\n'\
4841	'					if(j == divTotal - 1)\n'\
4842	'						htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S&rarr;</cS></div>\';\n'\
4843	'					else\n'\
4844	'						htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4845	'				} else {\n'\
4846	'					pos = 100 - (((j)*tS*100)/mTotal);\n'\
4847	'					val = (j)*tS;\n'\
4848	'					htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4849	'					if(j == 0)\n'\
4850	'						if(mode == "r")\n'\
4851	'							htmlline = rline+"<cS>&larr;R</cS></div>";\n'\
4852	'						else\n'\
4853	'							htmlline = rline+"<cS>0ms</div>";\n'\
4854	'				}\n'\
4855	'				html += htmlline;\n'\
4856	'			}\n'\
4857	'			timescale.innerHTML = html;\n'\
4858	'		}\n'\
4859	'	}\n'\
4860	'	function zoomTimeline() {\n'\
4861	'		var dmesg = document.getElementById("dmesg");\n'\
4862	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4863	'		var left = zoombox.scrollLeft;\n'\
4864	'		var val = parseFloat(dmesg.style.width);\n'\
4865	'		var newval = 100;\n'\
4866	'		var sh = window.outerWidth / 2;\n'\
4867	'		if(this.id == "zoomin") {\n'\
4868	'			newval = val * 1.2;\n'\
4869	'			if(newval > 910034) newval = 910034;\n'\
4870	'			dmesg.style.width = newval+"%";\n'\
4871	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4872	'		} else if (this.id == "zoomout") {\n'\
4873	'			newval = val / 1.2;\n'\
4874	'			if(newval < 100) newval = 100;\n'\
4875	'			dmesg.style.width = newval+"%";\n'\
4876	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4877	'		} else {\n'\
4878	'			zoombox.scrollLeft = 0;\n'\
4879	'			dmesg.style.width = "100%";\n'\
4880	'		}\n'\
4881	'		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4882	'		var t0 = bounds[0];\n'\
4883	'		var tMax = bounds[1];\n'\
4884	'		var tTotal = tMax - t0;\n'\
4885	'		var wTotal = tTotal * 100.0 / newval;\n'\
4886	'		var idx = 7*window.innerWidth/1100;\n'\
4887	'		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4888	'		if(i >= tS.length) i = tS.length - 1;\n'\
4889	'		if(tS[i] == resolution) return;\n'\
4890	'		resolution = tS[i];\n'\
4891	'		redrawTimescale(t0, tMax, tS[i]);\n'\
4892	'	}\n'\
4893	'	function deviceName(title) {\n'\
4894	'		var name = title.slice(0, title.indexOf(" ("));\n'\
4895	'		return name;\n'\
4896	'	}\n'\
4897	'	function deviceHover() {\n'\
4898	'		var name = deviceName(this.title);\n'\
4899	'		var dmesg = document.getElementById("dmesg");\n'\
4900	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4901	'		var cpu = -1;\n'\
4902	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4903	'			cpu = parseInt(name.slice(7));\n'\
4904	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4905	'			cpu = parseInt(name.slice(8));\n'\
4906	'		for (var i = 0; i < dev.length; i++) {\n'\
4907	'			dname = deviceName(dev[i].title);\n'\
4908	'			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4909	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4910	'				(name == dname))\n'\
4911	'			{\n'\
4912	'				dev[i].className = "hover "+cname;\n'\
4913	'			} else {\n'\
4914	'				dev[i].className = cname;\n'\
4915	'			}\n'\
4916	'		}\n'\
4917	'	}\n'\
4918	'	function deviceUnhover() {\n'\
4919	'		var dmesg = document.getElementById("dmesg");\n'\
4920	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4921	'		for (var i = 0; i < dev.length; i++) {\n'\
4922	'			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4923	'		}\n'\
4924	'	}\n'\
4925	'	function deviceTitle(title, total, cpu) {\n'\
4926	'		var prefix = "Total";\n'\
4927	'		if(total.length > 3) {\n'\
4928	'			prefix = "Average";\n'\
4929	'			total[1] = (total[1]+total[3])/2;\n'\
4930	'			total[2] = (total[2]+total[4])/2;\n'\
4931	'		}\n'\
4932	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
4933	'		var name = deviceName(title);\n'\
4934	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
4935	'		var driver = "";\n'\
4936	'		var tS = "<t2>(</t2>";\n'\
4937	'		var tR = "<t2>)</t2>";\n'\
4938	'		if(total[1] > 0)\n'\
4939	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
4940	'		if(total[2] > 0)\n'\
4941	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
4942	'		var s = title.indexOf("{");\n'\
4943	'		var e = title.indexOf("}");\n'\
4944	'		if((s >= 0) && (e >= 0))\n'\
4945	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
4946	'		if(total[1] > 0 && total[2] > 0)\n'\
4947	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
4948	'		else\n'\
4949	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
4950	'		return name;\n'\
4951	'	}\n'\
4952	'	function deviceDetail() {\n'\
4953	'		var devinfo = document.getElementById("devicedetail");\n'\
4954	'		devinfo.style.display = "block";\n'\
4955	'		var name = deviceName(this.title);\n'\
4956	'		var cpu = -1;\n'\
4957	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4958	'			cpu = parseInt(name.slice(7));\n'\
4959	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4960	'			cpu = parseInt(name.slice(8));\n'\
4961	'		var dmesg = document.getElementById("dmesg");\n'\
4962	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4963	'		var idlist = [];\n'\
4964	'		var pdata = [[]];\n'\
4965	'		if(document.getElementById("devicedetail1"))\n'\
4966	'			pdata = [[], []];\n'\
4967	'		var pd = pdata[0];\n'\
4968	'		var total = [0.0, 0.0, 0.0];\n'\
4969	'		for (var i = 0; i < dev.length; i++) {\n'\
4970	'			dname = deviceName(dev[i].title);\n'\
4971	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4972	'				(name == dname))\n'\
4973	'			{\n'\
4974	'				idlist[idlist.length] = dev[i].id;\n'\
4975	'				var tidx = 1;\n'\
4976	'				if(dev[i].id[0] == "a") {\n'\
4977	'					pd = pdata[0];\n'\
4978	'				} else {\n'\
4979	'					if(pdata.length == 1) pdata[1] = [];\n'\
4980	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
4981	'					pd = pdata[1];\n'\
4982	'					tidx = 3;\n'\
4983	'				}\n'\
4984	'				var info = dev[i].title.split(" ");\n'\
4985	'				var pname = info[info.length-1];\n'\
4986	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
4987	'				total[0] += pd[pname];\n'\
4988	'				if(pname.indexOf("suspend") >= 0)\n'\
4989	'					total[tidx] += pd[pname];\n'\
4990	'				else\n'\
4991	'					total[tidx+1] += pd[pname];\n'\
4992	'			}\n'\
4993	'		}\n'\
4994	'		var devname = deviceTitle(this.title, total, cpu);\n'\
4995	'		var left = 0.0;\n'\
4996	'		for (var t = 0; t < pdata.length; t++) {\n'\
4997	'			pd = pdata[t];\n'\
4998	'			devinfo = document.getElementById("devicedetail"+t);\n'\
4999	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
5000	'			for (var i = 0; i < phases.length; i++) {\n'\
5001	'				if(phases[i].id in pd) {\n'\
5002	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
5003	'					var fs = 32;\n'\
5004	'					if(w < 8) fs = 4*w | 0;\n'\
5005	'					var fs2 = fs*3/4;\n'\
5006	'					phases[i].style.width = w+"%";\n'\
5007	'					phases[i].style.left = left+"%";\n'\
5008	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
5009	'					left += w;\n'\
5010	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
5011	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
5012	'					phases[i].innerHTML = time+pname;\n'\
5013	'				} else {\n'\
5014	'					phases[i].style.width = "0%";\n'\
5015	'					phases[i].style.left = left+"%";\n'\
5016	'				}\n'\
5017	'			}\n'\
5018	'		}\n'\
5019	'		if(typeof devstats !== \'undefined\')\n'\
5020	'			callDetail(this.id, this.title);\n'\
5021	'		var cglist = document.getElementById("callgraphs");\n'\
5022	'		if(!cglist) return;\n'\
5023	'		var cg = cglist.getElementsByClassName("atop");\n'\
5024	'		if(cg.length < 10) return;\n'\
5025	'		for (var i = 0; i < cg.length; i++) {\n'\
5026	'			cgid = cg[i].id.split("x")[0]\n'\
5027	'			if(idlist.indexOf(cgid) >= 0) {\n'\
5028	'				cg[i].style.display = "block";\n'\
5029	'			} else {\n'\
5030	'				cg[i].style.display = "none";\n'\
5031	'			}\n'\
5032	'		}\n'\
5033	'	}\n'\
5034	'	function callDetail(devid, devtitle) {\n'\
5035	'		if(!(devid in devstats) || devstats[devid].length < 1)\n'\
5036	'			return;\n'\
5037	'		var list = devstats[devid];\n'\
5038	'		var tmp = devtitle.split(" ");\n'\
5039	'		var name = tmp[0], phase = tmp[tmp.length-1];\n'\
5040	'		var dd = document.getElementById(phase);\n'\
5041	'		var total = parseFloat(tmp[1].slice(1));\n'\
5042	'		var mlist = [];\n'\
5043	'		var maxlen = 0;\n'\
5044	'		var info = []\n'\
5045	'		for(var i in list) {\n'\
5046	'			if(list[i][0] == "@") {\n'\
5047	'				info = list[i].split("|");\n'\
5048	'				continue;\n'\
5049	'			}\n'\
5050	'			var tmp = list[i].split("|");\n'\
5051	'			var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
5052	'			var p = (t*100.0/total).toFixed(2);\n'\
5053	'			mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
5054	'			if(f.length > maxlen)\n'\
5055	'				maxlen = f.length;\n'\
5056	'		}\n'\
5057	'		var pad = 5;\n'\
5058	'		if(mlist.length == 0) pad = 30;\n'\
5059	'		var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
5060	'		if(info.length > 2)\n'\
5061	'			html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
5062	'		if(info.length > 3)\n'\
5063	'			html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
5064	'		if(info.length > 4)\n'\
5065	'			html += ", return=<b>"+info[4]+"</b>";\n'\
5066	'		html += "</t3></div>";\n'\
5067	'		if(mlist.length > 0) {\n'\
5068	'			html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
5069	'			for(var i in mlist)\n'\
5070	'				html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
5071	'			html += "</tr><tr><th>Calls</th>";\n'\
5072	'			for(var i in mlist)\n'\
5073	'				html += "<td>"+mlist[i][1]+"</td>";\n'\
5074	'			html += "</tr><tr><th>Time(ms)</th>";\n'\
5075	'			for(var i in mlist)\n'\
5076	'				html += "<td>"+mlist[i][2]+"</td>";\n'\
5077	'			html += "</tr><tr><th>Percent</th>";\n'\
5078	'			for(var i in mlist)\n'\
5079	'				html += "<td>"+mlist[i][3]+"</td>";\n'\
5080	'			html += "</tr></table>";\n'\
5081	'		}\n'\
5082	'		dd.innerHTML = html;\n'\
5083	'		var height = (maxlen*5)+100;\n'\
5084	'		dd.style.height = height+"px";\n'\
5085	'		document.getElementById("devicedetail").style.height = height+"px";\n'\
5086	'	}\n'\
5087	'	function callSelect() {\n'\
5088	'		var cglist = document.getElementById("callgraphs");\n'\
5089	'		if(!cglist) return;\n'\
5090	'		var cg = cglist.getElementsByClassName("atop");\n'\
5091	'		for (var i = 0; i < cg.length; i++) {\n'\
5092	'			if(this.id == cg[i].id) {\n'\
5093	'				cg[i].style.display = "block";\n'\
5094	'			} else {\n'\
5095	'				cg[i].style.display = "none";\n'\
5096	'			}\n'\
5097	'		}\n'\
5098	'	}\n'\
5099	'	function devListWindow(e) {\n'\
5100	'		var win = window.open();\n'\
5101	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
5102	'			"<style type=\\"text/css\\">"+\n'\
5103	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
5104	'			"</style>"\n'\
5105	'		var dt = devtable[0];\n'\
5106	'		if(e.target.id != "devlist1")\n'\
5107	'			dt = devtable[1];\n'\
5108	'		win.document.write(html+dt);\n'\
5109	'	}\n'\
5110	'	function errWindow() {\n'\
5111	'		var range = this.id.split("_");\n'\
5112	'		var idx1 = parseInt(range[0]);\n'\
5113	'		var idx2 = parseInt(range[1]);\n'\
5114	'		var win = window.open();\n'\
5115	'		var log = document.getElementById("dmesglog");\n'\
5116	'		var title = "<title>dmesg log</title>";\n'\
5117	'		var text = log.innerHTML.split("\\n");\n'\
5118	'		var html = "";\n'\
5119	'		for(var i = 0; i < text.length; i++) {\n'\
5120	'			if(i == idx1) {\n'\
5121	'				html += "<e id=target>"+text[i]+"</e>\\n";\n'\
5122	'			} else if(i > idx1 && i <= idx2) {\n'\
5123	'				html += "<e>"+text[i]+"</e>\\n";\n'\
5124	'			} else {\n'\
5125	'				html += text[i]+"\\n";\n'\
5126	'			}\n'\
5127	'		}\n'\
5128	'		win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
5129	'		win.location.hash = "#target";\n'\
5130	'		win.document.close();\n'\
5131	'	}\n'\
5132	'	function logWindow(e) {\n'\
5133	'		var name = e.target.id.slice(4);\n'\
5134	'		var win = window.open();\n'\
5135	'		var log = document.getElementById(name+"log");\n'\
5136	'		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
5137	'		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
5138	'		win.document.close();\n'\
5139	'	}\n'\
5140	'	function onMouseDown(e) {\n'\
5141	'		dragval[0] = e.clientX;\n'\
5142	'		dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
5143	'		document.onmousemove = onMouseMove;\n'\
5144	'	}\n'\
5145	'	function onMouseMove(e) {\n'\
5146	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
5147	'		zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
5148	'	}\n'\
5149	'	function onMouseUp(e) {\n'\
5150	'		document.onmousemove = null;\n'\
5151	'	}\n'\
5152	'	function onKeyPress(e) {\n'\
5153	'		var c = e.charCode;\n'\
5154	'		if(c != 42 && c != 43 && c != 45) return;\n'\
5155	'		var click = document.createEvent("Events");\n'\
5156	'		click.initEvent("click", true, false);\n'\
5157	'		if(c == 43)  \n'\
5158	'			document.getElementById("zoomin").dispatchEvent(click);\n'\
5159	'		else if(c == 45)\n'\
5160	'			document.getElementById("zoomout").dispatchEvent(click);\n'\
5161	'		else if(c == 42)\n'\
5162	'			document.getElementById("zoomdef").dispatchEvent(click);\n'\
5163	'	}\n'\
5164	'	window.addEventListener("resize", function () {zoomTimeline();});\n'\
5165	'	window.addEventListener("load", function () {\n'\
5166	'		var dmesg = document.getElementById("dmesg");\n'\
5167	'		dmesg.style.width = "100%"\n'\
5168	'		dmesg.onmousedown = onMouseDown;\n'\
5169	'		document.onmouseup = onMouseUp;\n'\
5170	'		document.onkeypress = onKeyPress;\n'\
5171	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
5172	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
5173	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
5174	'		var list = document.getElementsByClassName("err");\n'\
5175	'		for (var i = 0; i < list.length; i++)\n'\
5176	'			list[i].onclick = errWindow;\n'\
5177	'		var list = document.getElementsByClassName("logbtn");\n'\
5178	'		for (var i = 0; i < list.length; i++)\n'\
5179	'			list[i].onclick = logWindow;\n'\
5180	'		list = document.getElementsByClassName("devlist");\n'\
5181	'		for (var i = 0; i < list.length; i++)\n'\
5182	'			list[i].onclick = devListWindow;\n'\
5183	'		var dev = dmesg.getElementsByClassName("thread");\n'\
5184	'		for (var i = 0; i < dev.length; i++) {\n'\
5185	'			dev[i].onclick = deviceDetail;\n'\
5186	'			dev[i].onmouseover = deviceHover;\n'\
5187	'			dev[i].onmouseout = deviceUnhover;\n'\
5188	'		}\n'\
5189	'		var dev = dmesg.getElementsByClassName("srccall");\n'\
5190	'		for (var i = 0; i < dev.length; i++)\n'\
5191	'			dev[i].onclick = callSelect;\n'\
5192	'		zoomTimeline();\n'\
5193	'	});\n'\
5194	'</script>\n'
 
 
 
5195	hf.write(script_code);
5196
5197def setRuntimeSuspend(before=True):
5198	global sysvals
5199	sv = sysvals
5200	if sv.rs == 0:
5201		return
5202	if before:
5203		# runtime suspend disable or enable
5204		if sv.rs > 0:
5205			sv.rstgt, sv.rsval, sv.rsdir = 'on', 'auto', 'enabled'
5206		else:
5207			sv.rstgt, sv.rsval, sv.rsdir = 'auto', 'on', 'disabled'
5208		pprint('CONFIGURING RUNTIME SUSPEND...')
5209		sv.rslist = deviceInfo(sv.rstgt)
5210		for i in sv.rslist:
5211			sv.setVal(sv.rsval, i)
5212		pprint('runtime suspend %s on all devices (%d changed)' % (sv.rsdir, len(sv.rslist)))
5213		pprint('waiting 5 seconds...')
5214		time.sleep(5)
5215	else:
5216		# runtime suspend re-enable or re-disable
5217		for i in sv.rslist:
5218			sv.setVal(sv.rstgt, i)
5219		pprint('runtime suspend settings restored on %d devices' % len(sv.rslist))
5220
5221# Function: executeSuspend
5222# Description:
5223#	 Execute system suspend through the sysfs interface, then copy the output
5224#	 dmesg and ftrace files to the test output directory.
5225def executeSuspend(quiet=False):
5226	pm = ProcessMonitor()
5227	tp = sysvals.tpath
5228	if sysvals.wifi:
5229		wifi = sysvals.checkWifi()
5230	testdata = []
5231	# run these commands to prepare the system for suspend
5232	if sysvals.display:
5233		if not quiet:
5234			pprint('SET DISPLAY TO %s' % sysvals.display.upper())
5235		displayControl(sysvals.display)
 
5236		time.sleep(1)
5237	if sysvals.sync:
5238		if not quiet:
5239			pprint('SYNCING FILESYSTEMS')
 
5240		call('sync', shell=True)
5241	# mark the start point in the kernel ring buffer just as we start
5242	sysvals.initdmesg()
5243	# start ftrace
5244	if(sysvals.usecallgraph or sysvals.usetraceevents):
5245		if not quiet:
5246			pprint('START TRACING')
5247		sysvals.fsetVal('1', 'tracing_on')
5248		if sysvals.useprocmon:
5249			pm.start()
5250	sysvals.cmdinfo(True)
5251	# execute however many s/r runs requested
5252	for count in range(1,sysvals.execcount+1):
5253		# x2delay in between test runs
5254		if(count > 1 and sysvals.x2delay > 0):
5255			sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
5256			time.sleep(sysvals.x2delay/1000.0)
5257			sysvals.fsetVal('WAIT END', 'trace_marker')
5258		# start message
5259		if sysvals.testcommand != '':
5260			pprint('COMMAND START')
5261		else:
5262			if(sysvals.rtcwake):
5263				pprint('SUSPEND START')
5264			else:
5265				pprint('SUSPEND START (press a key to resume)')
5266		# set rtcwake
5267		if(sysvals.rtcwake):
5268			if not quiet:
5269				pprint('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
5270			sysvals.rtcWakeAlarmOn()
 
5271		# start of suspend trace marker
5272		if(sysvals.usecallgraph or sysvals.usetraceevents):
5273			sysvals.fsetVal(datetime.now().strftime(sysvals.tmstart), 'trace_marker')
5274		# predelay delay
5275		if(count == 1 and sysvals.predelay > 0):
5276			sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
5277			time.sleep(sysvals.predelay/1000.0)
5278			sysvals.fsetVal('WAIT END', 'trace_marker')
5279		# initiate suspend or command
 
5280		tdata = {'error': ''}
5281		if sysvals.testcommand != '':
5282			res = call(sysvals.testcommand+' 2>&1', shell=True);
5283			if res != 0:
5284				tdata['error'] = 'cmd returned %d' % res
5285		else:
5286			mode = sysvals.suspendmode
5287			if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
 
5288				mode = 'mem'
5289				pf = open(sysvals.mempowerfile, 'w')
5290				pf.write(sysvals.memmode)
5291				pf.close()
5292			if sysvals.diskmode and os.path.exists(sysvals.diskpowerfile):
5293				mode = 'disk'
5294				pf = open(sysvals.diskpowerfile, 'w')
5295				pf.write(sysvals.diskmode)
5296				pf.close()
5297			if mode == 'freeze' and sysvals.haveTurbostat():
 
5298				# execution will pause here
5299				turbo = sysvals.turbostat()
 
 
5300				if turbo:
5301					tdata['turbo'] = turbo
5302			else:
5303				pf = open(sysvals.powerfile, 'w')
5304				pf.write(mode)
5305				# execution will pause here
5306				try:
 
5307					pf.close()
5308				except Exception as e:
5309					tdata['error'] = str(e)
5310		if(sysvals.rtcwake):
5311			sysvals.rtcWakeAlarmOff()
 
 
 
 
 
5312		# postdelay delay
5313		if(count == sysvals.execcount and sysvals.postdelay > 0):
5314			sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
5315			time.sleep(sysvals.postdelay/1000.0)
5316			sysvals.fsetVal('WAIT END', 'trace_marker')
5317		# return from suspend
5318		pprint('RESUME COMPLETE')
5319		if(sysvals.usecallgraph or sysvals.usetraceevents):
5320			sysvals.fsetVal(datetime.now().strftime(sysvals.tmend), 'trace_marker')
5321		if sysvals.wifi and wifi:
5322			tdata['wifi'] = sysvals.pollWifi(wifi)
5323		if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
 
 
 
 
 
 
 
 
 
 
 
5324			tdata['fw'] = getFPDT(False)
5325		testdata.append(tdata)
5326	cmdafter = sysvals.cmdinfo(False)
5327	# stop ftrace
5328	if(sysvals.usecallgraph or sysvals.usetraceevents):
5329		if sysvals.useprocmon:
5330			pm.stop()
5331		sysvals.fsetVal('0', 'tracing_on')
5332	# grab a copy of the dmesg output
5333	if not quiet:
5334		pprint('CAPTURING DMESG')
5335	sysvals.getdmesg(testdata)
5336	# grab a copy of the ftrace output
5337	if(sysvals.usecallgraph or sysvals.usetraceevents):
5338		if not quiet:
5339			pprint('CAPTURING TRACE')
5340		op = sysvals.writeDatafileHeader(sysvals.ftracefile, testdata)
5341		fp = open(tp+'trace', 'r')
5342		for line in fp:
5343			op.write(line)
5344		op.close()
5345		sysvals.fsetVal('', 'trace')
5346		sysvals.platforminfo(cmdafter)
5347
5348def readFile(file):
5349	if os.path.islink(file):
5350		return os.readlink(file).split('/')[-1]
5351	else:
5352		return sysvals.getVal(file).strip()
5353
5354# Function: ms2nice
5355# Description:
5356#	 Print out a very concise time string in minutes and seconds
5357# Output:
5358#	 The time string, e.g. "1901m16s"
5359def ms2nice(val):
5360	val = int(val)
5361	h = val // 3600000
5362	m = (val // 60000) % 60
5363	s = (val // 1000) % 60
5364	if h > 0:
5365		return '%d:%02d:%02d' % (h, m, s)
5366	if m > 0:
5367		return '%02d:%02d' % (m, s)
5368	return '%ds' % s
5369
5370def yesno(val):
5371	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5372		'active':'A', 'suspended':'S', 'suspending':'S'}
5373	if val not in list:
5374		return ' '
5375	return list[val]
5376
5377# Function: deviceInfo
5378# Description:
5379#	 Detect all the USB hosts and devices currently connected and add
5380#	 a list of USB device names to sysvals for better timeline readability
5381def deviceInfo(output=''):
5382	if not output:
5383		pprint('LEGEND\n'\
5384		'---------------------------------------------------------------------------------------------\n'\
5385		'  A = async/sync PM queue (A/S)               C = runtime active children\n'\
5386		'  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)\n'\
5387		'  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)\n'\
5388		'  U = runtime usage count\n'\
5389		'---------------------------------------------------------------------------------------------\n'\
5390		'DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND\n'\
5391		'---------------------------------------------------------------------------------------------')
5392
5393	res = []
5394	tgtval = 'runtime_status'
5395	lines = dict()
5396	for dirname, dirnames, filenames in os.walk('/sys/devices'):
5397		if(not re.match('.*/power', dirname) or
5398			'control' not in filenames or
5399			tgtval not in filenames):
5400			continue
5401		name = ''
5402		dirname = dirname[:-6]
5403		device = dirname.split('/')[-1]
5404		power = dict()
5405		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5406		# only list devices which support runtime suspend
5407		if power[tgtval] not in ['active', 'suspended', 'suspending']:
5408			continue
5409		for i in ['product', 'driver', 'subsystem']:
5410			file = '%s/%s' % (dirname, i)
5411			if os.path.exists(file):
5412				name = readFile(file)
5413				break
5414		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5415			'runtime_active_kids', 'runtime_active_time',
5416			'runtime_suspended_time']:
5417			if i in filenames:
5418				power[i] = readFile('%s/power/%s' % (dirname, i))
5419		if output:
5420			if power['control'] == output:
5421				res.append('%s/power/control' % dirname)
5422			continue
5423		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5424			(device[:26], name[:26],
5425			yesno(power['async']), \
5426			yesno(power['control']), \
5427			yesno(power['runtime_status']), \
5428			power['runtime_usage'], \
5429			power['runtime_active_kids'], \
5430			ms2nice(power['runtime_active_time']), \
5431			ms2nice(power['runtime_suspended_time']))
5432	for i in sorted(lines):
5433		print(lines[i])
5434	return res
5435
5436# Function: getModes
5437# Description:
5438#	 Determine the supported power modes on this system
5439# Output:
5440#	 A string list of the available modes
5441def getModes():
5442	modes = []
5443	if(os.path.exists(sysvals.powerfile)):
5444		fp = open(sysvals.powerfile, 'r')
5445		modes = fp.read().split()
5446		fp.close()
5447	if(os.path.exists(sysvals.mempowerfile)):
5448		deep = False
5449		fp = open(sysvals.mempowerfile, 'r')
5450		for m in fp.read().split():
5451			memmode = m.strip('[]')
5452			if memmode == 'deep':
5453				deep = True
5454			else:
5455				modes.append('mem-%s' % memmode)
5456		fp.close()
5457		if 'mem' in modes and not deep:
5458			modes.remove('mem')
5459	if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5460		fp = open(sysvals.diskpowerfile, 'r')
5461		for m in fp.read().split():
5462			modes.append('disk-%s' % m.strip('[]'))
5463		fp.close()
5464	return modes
5465
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5466# Function: dmidecode
5467# Description:
5468#	 Read the bios tables and pull out system info
5469# Arguments:
5470#	 mempath: /dev/mem or custom mem path
5471#	 fatal: True to exit on error, False to return empty dict
5472# Output:
5473#	 A dict object with all available key/values
5474def dmidecode(mempath, fatal=False):
5475	out = dict()
 
 
5476
5477	# the list of values to retrieve, with hardcoded (type, idx)
5478	info = {
5479		'bios-vendor': (0, 4),
5480		'bios-version': (0, 5),
5481		'bios-release-date': (0, 8),
5482		'system-manufacturer': (1, 4),
5483		'system-product-name': (1, 5),
5484		'system-version': (1, 6),
5485		'system-serial-number': (1, 7),
5486		'baseboard-manufacturer': (2, 4),
5487		'baseboard-product-name': (2, 5),
5488		'baseboard-version': (2, 6),
5489		'baseboard-serial-number': (2, 7),
5490		'chassis-manufacturer': (3, 4),
5491		'chassis-type': (3, 5),
5492		'chassis-version': (3, 6),
5493		'chassis-serial-number': (3, 7),
5494		'processor-manufacturer': (4, 7),
5495		'processor-version': (4, 16),
5496	}
5497	if(not os.path.exists(mempath)):
5498		if(fatal):
5499			doError('file does not exist: %s' % mempath)
5500		return out
5501	if(not os.access(mempath, os.R_OK)):
5502		if(fatal):
5503			doError('file is not readable: %s' % mempath)
5504		return out
5505
5506	# by default use legacy scan, but try to use EFI first
5507	memaddr = 0xf0000
5508	memsize = 0x10000
5509	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5510		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5511			continue
5512		fp = open(ep, 'r')
5513		buf = fp.read()
5514		fp.close()
5515		i = buf.find('SMBIOS=')
5516		if i >= 0:
5517			try:
5518				memaddr = int(buf[i+7:], 16)
5519				memsize = 0x20
5520			except:
5521				continue
5522
5523	# read in the memory for scanning
5524	try:
5525		fp = open(mempath, 'rb')
5526		fp.seek(memaddr)
5527		buf = fp.read(memsize)
5528	except:
5529		if(fatal):
5530			doError('DMI table is unreachable, sorry')
5531		else:
5532			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5533			return out
5534	fp.close()
5535
5536	# search for either an SM table or DMI table
5537	i = base = length = num = 0
5538	while(i < memsize):
5539		if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5540			length = struct.unpack('H', buf[i+22:i+24])[0]
5541			base, num = struct.unpack('IH', buf[i+24:i+30])
5542			break
5543		elif buf[i:i+5] == b'_DMI_':
5544			length = struct.unpack('H', buf[i+6:i+8])[0]
5545			base, num = struct.unpack('IH', buf[i+8:i+14])
5546			break
5547		i += 16
5548	if base == 0 and length == 0 and num == 0:
5549		if(fatal):
5550			doError('Neither SMBIOS nor DMI were found')
5551		else:
5552			return out
5553
5554	# read in the SM or DMI table
5555	try:
5556		fp = open(mempath, 'rb')
5557		fp.seek(base)
5558		buf = fp.read(length)
5559	except:
5560		if(fatal):
5561			doError('DMI table is unreachable, sorry')
5562		else:
5563			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5564			return out
5565	fp.close()
5566
5567	# scan the table for the values we want
5568	count = i = 0
5569	while(count < num and i <= len(buf) - 4):
5570		type, size, handle = struct.unpack('BBH', buf[i:i+4])
5571		n = i + size
5572		while n < len(buf) - 1:
5573			if 0 == struct.unpack('H', buf[n:n+2])[0]:
5574				break
5575			n += 1
5576		data = buf[i+size:n+2].split(b'\0')
5577		for name in info:
5578			itype, idxadr = info[name]
5579			if itype == type:
5580				idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5581				if idx > 0 and idx < len(data) - 1:
5582					s = data[idx-1].decode('utf-8')
5583					if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5584						out[name] = s
5585		i = n + 2
5586		count += 1
5587	return out
5588
5589def displayControl(cmd):
5590	xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
5591	if sysvals.sudouser:
5592		xset = 'sudo -u %s %s' % (sysvals.sudouser, xset)
5593	if cmd == 'init':
5594		ret = call(xset.format('dpms 0 0 0'), shell=True)
5595		if not ret:
5596			ret = call(xset.format('s off'), shell=True)
5597	elif cmd == 'reset':
5598		ret = call(xset.format('s reset'), shell=True)
5599	elif cmd in ['on', 'off', 'standby', 'suspend']:
5600		b4 = displayControl('stat')
5601		ret = call(xset.format('dpms force %s' % cmd), shell=True)
5602		if not ret:
5603			curr = displayControl('stat')
5604			sysvals.vprint('Display Switched: %s -> %s' % (b4, curr))
5605			if curr != cmd:
5606				sysvals.vprint('WARNING: Display failed to change to %s' % cmd)
5607		if ret:
5608			sysvals.vprint('WARNING: Display failed to change to %s with xset' % cmd)
5609			return ret
5610	elif cmd == 'stat':
5611		fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
5612		ret = 'unknown'
5613		for line in fp:
5614			m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))
5615			if(m and len(m.group('m')) >= 2):
5616				out = m.group('m').lower()
5617				ret = out[3:] if out[0:2] == 'in' else out
5618				break
5619		fp.close()
5620	return ret
5621
5622# Function: getFPDT
5623# Description:
5624#	 Read the acpi bios tables and pull out FPDT, the firmware data
5625# Arguments:
5626#	 output: True to output the info to stdout, False otherwise
5627def getFPDT(output):
5628	rectype = {}
5629	rectype[0] = 'Firmware Basic Boot Performance Record'
5630	rectype[1] = 'S3 Performance Table Record'
5631	prectype = {}
5632	prectype[0] = 'Basic S3 Resume Performance Record'
5633	prectype[1] = 'Basic S3 Suspend Performance Record'
5634
5635	sysvals.rootCheck(True)
5636	if(not os.path.exists(sysvals.fpdtpath)):
5637		if(output):
5638			doError('file does not exist: %s' % sysvals.fpdtpath)
5639		return False
5640	if(not os.access(sysvals.fpdtpath, os.R_OK)):
5641		if(output):
5642			doError('file is not readable: %s' % sysvals.fpdtpath)
5643		return False
5644	if(not os.path.exists(sysvals.mempath)):
5645		if(output):
5646			doError('file does not exist: %s' % sysvals.mempath)
5647		return False
5648	if(not os.access(sysvals.mempath, os.R_OK)):
5649		if(output):
5650			doError('file is not readable: %s' % sysvals.mempath)
5651		return False
5652
5653	fp = open(sysvals.fpdtpath, 'rb')
5654	buf = fp.read()
5655	fp.close()
5656
5657	if(len(buf) < 36):
5658		if(output):
5659			doError('Invalid FPDT table data, should '+\
5660				'be at least 36 bytes')
5661		return False
5662
5663	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5664	if(output):
5665		pprint('\n'\
5666		'Firmware Performance Data Table (%s)\n'\
5667		'                  Signature : %s\n'\
5668		'               Table Length : %u\n'\
5669		'                   Revision : %u\n'\
5670		'                   Checksum : 0x%x\n'\
5671		'                     OEM ID : %s\n'\
5672		'               OEM Table ID : %s\n'\
5673		'               OEM Revision : %u\n'\
5674		'                 Creator ID : %s\n'\
5675		'           Creator Revision : 0x%x\n'\
5676		'' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5677			table[3], ascii(table[4]), ascii(table[5]), table[6],
5678			ascii(table[7]), table[8]))
5679
5680	if(table[0] != b'FPDT'):
5681		if(output):
5682			doError('Invalid FPDT table')
5683		return False
5684	if(len(buf) <= 36):
5685		return False
5686	i = 0
5687	fwData = [0, 0]
5688	records = buf[36:]
5689	try:
5690		fp = open(sysvals.mempath, 'rb')
5691	except:
5692		pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5693		return False
5694	while(i < len(records)):
5695		header = struct.unpack('HBB', records[i:i+4])
5696		if(header[0] not in rectype):
5697			i += header[1]
5698			continue
5699		if(header[1] != 16):
5700			i += header[1]
5701			continue
5702		addr = struct.unpack('Q', records[i+8:i+16])[0]
5703		try:
5704			fp.seek(addr)
5705			first = fp.read(8)
5706		except:
5707			if(output):
5708				pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5709			return [0, 0]
5710		rechead = struct.unpack('4sI', first)
5711		recdata = fp.read(rechead[1]-8)
5712		if(rechead[0] == b'FBPT'):
5713			record = struct.unpack('HBBIQQQQQ', recdata[:48])
5714			if(output):
5715				pprint('%s (%s)\n'\
5716				'                  Reset END : %u ns\n'\
5717				'  OS Loader LoadImage Start : %u ns\n'\
5718				' OS Loader StartImage Start : %u ns\n'\
5719				'     ExitBootServices Entry : %u ns\n'\
5720				'      ExitBootServices Exit : %u ns'\
5721				'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5722					record[6], record[7], record[8]))
5723		elif(rechead[0] == b'S3PT'):
5724			if(output):
5725				pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5726			j = 0
5727			while(j < len(recdata)):
5728				prechead = struct.unpack('HBB', recdata[j:j+4])
5729				if(prechead[0] not in prectype):
5730					continue
5731				if(prechead[0] == 0):
5732					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5733					fwData[1] = record[2]
5734					if(output):
5735						pprint('    %s\n'\
5736						'               Resume Count : %u\n'\
5737						'                 FullResume : %u ns\n'\
5738						'              AverageResume : %u ns'\
5739						'' % (prectype[prechead[0]], record[1],
5740								record[2], record[3]))
5741				elif(prechead[0] == 1):
5742					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5743					fwData[0] = record[1] - record[0]
5744					if(output):
5745						pprint('    %s\n'\
5746						'               SuspendStart : %u ns\n'\
5747						'                 SuspendEnd : %u ns\n'\
5748						'                SuspendTime : %u ns'\
5749						'' % (prectype[prechead[0]], record[0],
5750								record[1], fwData[0]))
5751
5752				j += prechead[1]
5753		if(output):
5754			pprint('')
5755		i += header[1]
5756	fp.close()
5757	return fwData
5758
5759# Function: statusCheck
5760# Description:
5761#	 Verify that the requested command and options will work, and
5762#	 print the results to the terminal
5763# Output:
5764#	 True if the test will work, False if not
5765def statusCheck(probecheck=False):
5766	status = ''
5767
5768	pprint('Checking this system (%s)...' % platform.node())
5769
5770	# check we have root access
5771	res = sysvals.colorText('NO (No features of this tool will work!)')
5772	if(sysvals.rootCheck(False)):
5773		res = 'YES'
5774	pprint('    have root access: %s' % res)
5775	if(res != 'YES'):
5776		pprint('    Try running this script with sudo')
5777		return 'missing root access'
5778
5779	# check sysfs is mounted
5780	res = sysvals.colorText('NO (No features of this tool will work!)')
5781	if(os.path.exists(sysvals.powerfile)):
5782		res = 'YES'
5783	pprint('    is sysfs mounted: %s' % res)
5784	if(res != 'YES'):
5785		return 'sysfs is missing'
5786
5787	# check target mode is a valid mode
5788	if sysvals.suspendmode != 'command':
5789		res = sysvals.colorText('NO')
5790		modes = getModes()
5791		if(sysvals.suspendmode in modes):
5792			res = 'YES'
5793		else:
5794			status = '%s mode is not supported' % sysvals.suspendmode
5795		pprint('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5796		if(res == 'NO'):
5797			pprint('      valid power modes are: %s' % modes)
5798			pprint('      please choose one with -m')
5799
5800	# check if ftrace is available
5801	res = sysvals.colorText('NO')
5802	ftgood = sysvals.verifyFtrace()
5803	if(ftgood):
5804		res = 'YES'
5805	elif(sysvals.usecallgraph):
5806		status = 'ftrace is not properly supported'
5807	pprint('    is ftrace supported: %s' % res)
 
 
 
 
 
 
5808
5809	# check if kprobes are available
5810	if sysvals.usekprobes:
5811		res = sysvals.colorText('NO')
5812		sysvals.usekprobes = sysvals.verifyKprobes()
5813		if(sysvals.usekprobes):
5814			res = 'YES'
5815		else:
5816			sysvals.usedevsrc = False
5817		pprint('    are kprobes supported: %s' % res)
5818
5819	# what data source are we using
5820	res = 'DMESG'
5821	if(ftgood):
5822		sysvals.usetraceevents = True
5823		for e in sysvals.traceevents:
5824			if not os.path.exists(sysvals.epath+e):
5825				sysvals.usetraceevents = False
5826		if(sysvals.usetraceevents):
5827			res = 'FTRACE (all trace events found)'
5828	pprint('    timeline data source: %s' % res)
5829
5830	# check if rtcwake
5831	res = sysvals.colorText('NO')
5832	if(sysvals.rtcpath != ''):
5833		res = 'YES'
5834	elif(sysvals.rtcwake):
5835		status = 'rtcwake is not properly supported'
5836	pprint('    is rtcwake supported: %s' % res)
5837
5838	# check info commands
5839	pprint('    optional commands this tool may use for info:')
5840	no = sysvals.colorText('MISSING')
5841	yes = sysvals.colorText('FOUND', 32)
5842	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb']:
5843		if c == 'turbostat':
5844			res = yes if sysvals.haveTurbostat() else no
5845		else:
5846			res = yes if sysvals.getExec(c) else no
5847		pprint('        %s: %s' % (c, res))
5848
5849	if not probecheck:
5850		return status
5851
5852	# verify kprobes
5853	if sysvals.usekprobes:
5854		for name in sysvals.tracefuncs:
5855			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5856		if sysvals.usedevsrc:
5857			for name in sysvals.dev_tracefuncs:
5858				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5859		sysvals.addKprobes(True)
5860
5861	return status
5862
5863# Function: doError
5864# Description:
5865#	 generic error function for catastrphic failures
5866# Arguments:
5867#	 msg: the error message to print
5868#	 help: True if printHelp should be called after, False otherwise
5869def doError(msg, help=False):
5870	if(help == True):
5871		printHelp()
5872	pprint('ERROR: %s\n' % msg)
5873	sysvals.outputResult({'error':msg})
5874	sys.exit(1)
5875
5876# Function: getArgInt
5877# Description:
5878#	 pull out an integer argument from the command line with checks
5879def getArgInt(name, args, min, max, main=True):
5880	if main:
5881		try:
5882			arg = next(args)
5883		except:
5884			doError(name+': no argument supplied', True)
5885	else:
5886		arg = args
5887	try:
5888		val = int(arg)
5889	except:
5890		doError(name+': non-integer value given', True)
5891	if(val < min or val > max):
5892		doError(name+': value should be between %d and %d' % (min, max), True)
5893	return val
5894
5895# Function: getArgFloat
5896# Description:
5897#	 pull out a float argument from the command line with checks
5898def getArgFloat(name, args, min, max, main=True):
5899	if main:
5900		try:
5901			arg = next(args)
5902		except:
5903			doError(name+': no argument supplied', True)
5904	else:
5905		arg = args
5906	try:
5907		val = float(arg)
5908	except:
5909		doError(name+': non-numerical value given', True)
5910	if(val < min or val > max):
5911		doError(name+': value should be between %f and %f' % (min, max), True)
5912	return val
5913
5914def processData(live=False, quiet=False):
5915	if not quiet:
5916		pprint('PROCESSING: %s' % sysvals.htmlfile)
5917	sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
5918		(sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
5919	error = ''
5920	if(sysvals.usetraceevents):
5921		testruns, error = parseTraceLog(live)
5922		if sysvals.dmesgfile:
5923			for data in testruns:
5924				data.extractErrorInfo()
5925	else:
5926		testruns = loadKernelLog()
5927		for data in testruns:
5928			parseKernelLog(data)
5929		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5930			appendIncompleteTraceLog(testruns)
5931	if not sysvals.stamp:
5932		pprint('ERROR: data does not include the expected stamp')
5933		return (testruns, {'error': 'timeline generation failed'})
5934	shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
5935			'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
5936	sysvals.vprint('System Info:')
5937	for key in sorted(sysvals.stamp):
5938		if key in shown:
5939			sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key]))
5940	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
5941	for data in testruns:
5942		if data.turbostat:
5943			idx, s = 0, 'Turbostat:\n    '
5944			for val in data.turbostat.split('|'):
5945				idx += len(val) + 1
5946				if idx >= 80:
5947					idx = 0
5948					s += '\n    '
5949				s += val + ' '
5950			sysvals.vprint(s)
5951		data.printDetails()
5952	if len(sysvals.platinfo) > 0:
5953		sysvals.vprint('\nPlatform Info:')
5954		for info in sysvals.platinfo:
5955			sysvals.vprint('[%s - %s]' % (info[0], info[1]))
5956			sysvals.vprint(info[2])
5957		sysvals.vprint('')
5958	if sysvals.cgdump:
5959		for data in testruns:
5960			data.debugPrint()
5961		sys.exit(0)
5962	if len(testruns) < 1:
5963		pprint('ERROR: Not enough test data to build a timeline')
5964		return (testruns, {'error': 'timeline generation failed'})
5965	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
5966	createHTML(testruns, error)
5967	if not quiet:
5968		pprint('DONE:       %s' % sysvals.htmlfile)
5969	data = testruns[0]
5970	stamp = data.stamp
5971	stamp['suspend'], stamp['resume'] = data.getTimeValues()
5972	if data.fwValid:
5973		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
5974	if error:
5975		stamp['error'] = error
5976	return (testruns, stamp)
5977
5978# Function: rerunTest
5979# Description:
5980#	 generate an output from an existing set of ftrace/dmesg logs
5981def rerunTest(htmlfile=''):
5982	if sysvals.ftracefile:
5983		doesTraceLogHaveTraceEvents()
5984	if not sysvals.dmesgfile and not sysvals.usetraceevents:
5985		doError('recreating this html output requires a dmesg file')
5986	if htmlfile:
5987		sysvals.htmlfile = htmlfile
5988	else:
5989		sysvals.setOutputFile()
5990	if os.path.exists(sysvals.htmlfile):
5991		if not os.path.isfile(sysvals.htmlfile):
5992			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
5993		elif not os.access(sysvals.htmlfile, os.W_OK):
5994			doError('missing permission to write to %s' % sysvals.htmlfile)
5995	testruns, stamp = processData()
5996	sysvals.resetlog()
5997	return stamp
5998
5999# Function: runTest
6000# Description:
6001#	 execute a suspend/resume, gather the logs, and generate the output
6002def runTest(n=0, quiet=False):
6003	# prepare for the test
6004	sysvals.initFtrace(quiet)
6005	sysvals.initTestOutput('suspend')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6006
6007	# execute the test
6008	executeSuspend(quiet)
6009	sysvals.cleanupFtrace()
6010	if sysvals.skiphtml:
6011		sysvals.outputResult({}, n)
6012		sysvals.sudoUserchown(sysvals.testdir)
6013		return
6014	testruns, stamp = processData(True, quiet)
6015	for data in testruns:
6016		del data
6017	sysvals.sudoUserchown(sysvals.testdir)
6018	sysvals.outputResult(stamp, n)
6019	if 'error' in stamp:
6020		return 2
6021	return 0
6022
6023def find_in_html(html, start, end, firstonly=True):
6024	cnt, out, list = len(html), [], []
6025	if firstonly:
6026		m = re.search(start, html)
6027		if m:
6028			list.append(m)
6029	else:
6030		list = re.finditer(start, html)
6031	for match in list:
6032		s = match.end()
6033		e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6034		m = re.search(end, html[s:e])
6035		if not m:
6036			break
6037		e = s + m.start()
6038		str = html[s:e]
6039		if end == 'ms':
6040			num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6041			str = num.group() if num else 'NaN'
6042		if firstonly:
6043			return str
6044		out.append(str)
6045	if firstonly:
6046		return ''
6047	return out
6048
6049def data_from_html(file, outpath, issues, fulldetail=False):
6050	html = open(file, 'r').read()
 
 
 
6051	sysvals.htmlfile = os.path.relpath(file, outpath)
6052	# extract general info
6053	suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6054	resume = find_in_html(html, 'Kernel Resume', 'ms')
6055	sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6056	line = find_in_html(html, '<div class="stamp">', '</div>')
6057	stmp = line.split()
6058	if not suspend or not resume or len(stmp) != 8:
6059		return False
6060	try:
6061		dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6062	except:
6063		return False
6064	sysvals.hostname = stmp[0]
6065	tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6066	error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6067	if error:
6068		m = re.match('[a-z0-9]* failed in (?P<p>\S*).*', error)
6069		if m:
6070			result = 'fail in %s' % m.group('p')
6071		else:
6072			result = 'fail'
6073	else:
6074		result = 'pass'
6075	# extract error info
6076	tp, ilist = False, []
6077	extra = dict()
6078	log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6079		'</div>').strip()
6080	if log:
6081		d = Data(0)
6082		d.end = 999999999
6083		d.dmesgtext = log.split('\n')
6084		tp = d.extractErrorInfo()
6085		for msg in tp.msglist:
6086			sysvals.errorSummary(issues, msg)
 
6087		if stmp[2] == 'freeze':
6088			extra = d.turbostatInfo()
6089		elist = dict()
6090		for dir in d.errorinfo:
6091			for err in d.errorinfo[dir]:
6092				if err[0] not in elist:
6093					elist[err[0]] = 0
6094				elist[err[0]] += 1
6095		for i in elist:
6096			ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6097	wifi = find_in_html(html, 'Wifi Resume: ', '</td>')
6098	if wifi:
6099		extra['wifi'] = wifi
 
 
 
 
 
 
 
 
6100	low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6101	if low and 'waking' in low:
6102		issue = 'FREEZEWAKE'
 
 
 
 
 
 
 
 
6103		match = [i for i in issues if i['match'] == issue]
6104		if len(match) > 0:
6105			match[0]['count'] += 1
6106			if sysvals.hostname not in match[0]['urls']:
6107				match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6108			elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6109				match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6110		else:
6111			issues.append({
6112				'match': issue, 'count': 1, 'line': issue,
6113				'urls': {sysvals.hostname: [sysvals.htmlfile]},
6114			})
6115		ilist.append(issue)
6116	# extract device info
6117	devices = dict()
6118	for line in html.split('\n'):
6119		m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6120		if not m or 'thread kth' in line or 'thread sec' in line:
6121			continue
6122		m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6123		if not m:
6124			continue
6125		name, time, phase = m.group('n'), m.group('t'), m.group('p')
 
 
6126		if ' async' in name or ' sync' in name:
6127			name = ' '.join(name.split(' ')[:-1])
6128		if phase.startswith('suspend'):
6129			d = 'suspend'
6130		elif phase.startswith('resume'):
6131			d = 'resume'
6132		else:
6133			continue
6134		if d not in devices:
6135			devices[d] = dict()
6136		if name not in devices[d]:
6137			devices[d][name] = 0.0
6138		devices[d][name] += float(time)
6139	# create worst device info
6140	worst = dict()
6141	for d in ['suspend', 'resume']:
6142		worst[d] = {'name':'', 'time': 0.0}
6143		dev = devices[d] if d in devices else 0
6144		if dev and len(dev.keys()) > 0:
6145			n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6146			worst[d]['name'], worst[d]['time'] = n, dev[n]
6147	data = {
6148		'mode': stmp[2],
6149		'host': stmp[0],
6150		'kernel': stmp[1],
6151		'sysinfo': sysinfo,
6152		'time': tstr,
6153		'result': result,
6154		'issues': ' '.join(ilist),
6155		'suspend': suspend,
6156		'resume': resume,
6157		'devlist': devices,
6158		'sus_worst': worst['suspend']['name'],
6159		'sus_worsttime': worst['suspend']['time'],
6160		'res_worst': worst['resume']['name'],
6161		'res_worsttime': worst['resume']['time'],
6162		'url': sysvals.htmlfile,
6163	}
6164	for key in extra:
6165		data[key] = extra[key]
6166	if fulldetail:
6167		data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6168	if tp:
6169		for arg in ['-multi ', '-info ']:
6170			if arg in tp.cmdline:
6171				data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6172				break
6173	return data
6174
6175def genHtml(subdir, force=False):
6176	for dirname, dirnames, filenames in os.walk(subdir):
6177		sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6178		for filename in filenames:
6179			file = os.path.join(dirname, filename)
6180			if sysvals.usable(file):
6181				if(re.match('.*_dmesg.txt', filename)):
6182					sysvals.dmesgfile = file
6183				elif(re.match('.*_ftrace.txt', filename)):
6184					sysvals.ftracefile = file
6185		sysvals.setOutputFile()
6186		if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6187			(force or not sysvals.usable(sysvals.htmlfile)):
6188			pprint('FTRACE: %s' % sysvals.ftracefile)
6189			if sysvals.dmesgfile:
6190				pprint('DMESG : %s' % sysvals.dmesgfile)
6191			rerunTest()
6192
6193# Function: runSummary
6194# Description:
6195#	 create a summary of tests in a sub-directory
6196def runSummary(subdir, local=True, genhtml=False):
6197	inpath = os.path.abspath(subdir)
6198	outpath = os.path.abspath('.') if local else inpath
6199	pprint('Generating a summary of folder:\n   %s' % inpath)
6200	if genhtml:
6201		genHtml(subdir)
6202	target, issues, testruns = '', [], []
6203	desc = {'host':[],'mode':[],'kernel':[]}
6204	for dirname, dirnames, filenames in os.walk(subdir):
6205		for filename in filenames:
6206			if(not re.match('.*.html', filename)):
6207				continue
6208			data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6209			if(not data):
6210				continue
6211			if 'target' in data:
6212				target = data['target']
6213			testruns.append(data)
6214			for key in desc:
6215				if data[key] not in desc[key]:
6216					desc[key].append(data[key])
6217	pprint('Summary files:')
6218	if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6219		title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6220		if target:
6221			title += ' %s' % target
6222	else:
6223		title = inpath
6224	createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6225	pprint('   summary.html         - tabular list of test data found')
6226	createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6227	pprint('   summary-devices.html - kernel device list sorted by total execution time')
6228	createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6229	pprint('   summary-issues.html  - kernel issues found sorted by frequency')
6230
6231# Function: checkArgBool
6232# Description:
6233#	 check if a boolean string value is true or false
6234def checkArgBool(name, value):
6235	if value in switchvalues:
6236		if value in switchoff:
6237			return False
6238		return True
6239	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6240	return False
6241
6242# Function: configFromFile
6243# Description:
6244#	 Configure the script via the info in a config file
6245def configFromFile(file):
6246	Config = configparser.ConfigParser()
6247
6248	Config.read(file)
6249	sections = Config.sections()
6250	overridekprobes = False
6251	overridedevkprobes = False
6252	if 'Settings' in sections:
6253		for opt in Config.options('Settings'):
6254			value = Config.get('Settings', opt).lower()
6255			option = opt.lower()
6256			if(option == 'verbose'):
6257				sysvals.verbose = checkArgBool(option, value)
6258			elif(option == 'addlogs'):
6259				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6260			elif(option == 'dev'):
6261				sysvals.usedevsrc = checkArgBool(option, value)
6262			elif(option == 'proc'):
6263				sysvals.useprocmon = checkArgBool(option, value)
6264			elif(option == 'x2'):
6265				if checkArgBool(option, value):
6266					sysvals.execcount = 2
6267			elif(option == 'callgraph'):
6268				sysvals.usecallgraph = checkArgBool(option, value)
6269			elif(option == 'override-timeline-functions'):
6270				overridekprobes = checkArgBool(option, value)
6271			elif(option == 'override-dev-timeline-functions'):
6272				overridedevkprobes = checkArgBool(option, value)
6273			elif(option == 'skiphtml'):
6274				sysvals.skiphtml = checkArgBool(option, value)
6275			elif(option == 'sync'):
6276				sysvals.sync = checkArgBool(option, value)
6277			elif(option == 'rs' or option == 'runtimesuspend'):
6278				if value in switchvalues:
6279					if value in switchoff:
6280						sysvals.rs = -1
6281					else:
6282						sysvals.rs = 1
6283				else:
6284					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6285			elif(option == 'display'):
6286				disopt = ['on', 'off', 'standby', 'suspend']
6287				if value not in disopt:
6288					doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6289				sysvals.display = value
6290			elif(option == 'gzip'):
6291				sysvals.gzip = checkArgBool(option, value)
6292			elif(option == 'cgfilter'):
6293				sysvals.setCallgraphFilter(value)
6294			elif(option == 'cgskip'):
6295				if value in switchoff:
6296					sysvals.cgskip = ''
6297				else:
6298					sysvals.cgskip = sysvals.configFile(val)
6299					if(not sysvals.cgskip):
6300						doError('%s does not exist' % sysvals.cgskip)
6301			elif(option == 'cgtest'):
6302				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6303			elif(option == 'cgphase'):
6304				d = Data(0)
6305				if value not in d.phasedef:
6306					doError('invalid phase --> (%s: %s), valid phases are %s'\
6307						% (option, value, d.phasedef.keys()), True)
6308				sysvals.cgphase = value
6309			elif(option == 'fadd'):
6310				file = sysvals.configFile(value)
6311				if(not file):
6312					doError('%s does not exist' % value)
6313				sysvals.addFtraceFilterFunctions(file)
6314			elif(option == 'result'):
6315				sysvals.result = value
6316			elif(option == 'multi'):
6317				nums = value.split()
6318				if len(nums) != 2:
6319					doError('multi requires 2 integers (exec_count and delay)', True)
6320				sysvals.multiinit(nums[0], nums[1])
6321			elif(option == 'devicefilter'):
6322				sysvals.setDeviceFilter(value)
6323			elif(option == 'expandcg'):
6324				sysvals.cgexp = checkArgBool(option, value)
6325			elif(option == 'srgap'):
6326				if checkArgBool(option, value):
6327					sysvals.srgap = 5
6328			elif(option == 'mode'):
6329				sysvals.suspendmode = value
6330			elif(option == 'command' or option == 'cmd'):
6331				sysvals.testcommand = value
6332			elif(option == 'x2delay'):
6333				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6334			elif(option == 'predelay'):
6335				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6336			elif(option == 'postdelay'):
6337				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6338			elif(option == 'maxdepth'):
6339				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6340			elif(option == 'rtcwake'):
6341				if value in switchoff:
6342					sysvals.rtcwake = False
6343				else:
6344					sysvals.rtcwake = True
6345					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6346			elif(option == 'timeprec'):
6347				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6348			elif(option == 'mindev'):
6349				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6350			elif(option == 'callloop-maxgap'):
6351				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6352			elif(option == 'callloop-maxlen'):
6353				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6354			elif(option == 'mincg'):
6355				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6356			elif(option == 'bufsize'):
6357				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6358			elif(option == 'output-dir'):
6359				sysvals.outdir = sysvals.setOutputFolder(value)
6360
6361	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6362		doError('No command supplied for mode "command"')
6363
6364	# compatibility errors
6365	if sysvals.usedevsrc and sysvals.usecallgraph:
6366		doError('-dev is not compatible with -f')
6367	if sysvals.usecallgraph and sysvals.useprocmon:
6368		doError('-proc is not compatible with -f')
6369
6370	if overridekprobes:
6371		sysvals.tracefuncs = dict()
6372	if overridedevkprobes:
6373		sysvals.dev_tracefuncs = dict()
6374
6375	kprobes = dict()
6376	kprobesec = 'dev_timeline_functions_'+platform.machine()
6377	if kprobesec in sections:
6378		for name in Config.options(kprobesec):
6379			text = Config.get(kprobesec, name)
6380			kprobes[name] = (text, True)
6381	kprobesec = 'timeline_functions_'+platform.machine()
6382	if kprobesec in sections:
6383		for name in Config.options(kprobesec):
6384			if name in kprobes:
6385				doError('Duplicate timeline function found "%s"' % (name))
6386			text = Config.get(kprobesec, name)
6387			kprobes[name] = (text, False)
6388
6389	for name in kprobes:
6390		function = name
6391		format = name
6392		color = ''
6393		args = dict()
6394		text, dev = kprobes[name]
6395		data = text.split()
6396		i = 0
6397		for val in data:
6398			# bracketted strings are special formatting, read them separately
6399			if val[0] == '[' and val[-1] == ']':
6400				for prop in val[1:-1].split(','):
6401					p = prop.split('=')
6402					if p[0] == 'color':
6403						try:
6404							color = int(p[1], 16)
6405							color = '#'+p[1]
6406						except:
6407							color = p[1]
6408				continue
6409			# first real arg should be the format string
6410			if i == 0:
6411				format = val
6412			# all other args are actual function args
6413			else:
6414				d = val.split('=')
6415				args[d[0]] = d[1]
6416			i += 1
6417		if not function or not format:
6418			doError('Invalid kprobe: %s' % name)
6419		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6420			if arg not in args:
6421				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6422		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6423			doError('Duplicate timeline function found "%s"' % (name))
6424
6425		kp = {
6426			'name': name,
6427			'func': function,
6428			'format': format,
6429			sysvals.archargs: args
6430		}
6431		if color:
6432			kp['color'] = color
6433		if dev:
6434			sysvals.dev_tracefuncs[name] = kp
6435		else:
6436			sysvals.tracefuncs[name] = kp
6437
6438# Function: printHelp
6439# Description:
6440#	 print out the help text
6441def printHelp():
6442	pprint('\n%s v%s\n'\
6443	'Usage: sudo sleepgraph <options> <commands>\n'\
6444	'\n'\
6445	'Description:\n'\
6446	'  This tool is designed to assist kernel and OS developers in optimizing\n'\
6447	'  their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6448	'  with a few extra options enabled, the tool will execute a suspend and\n'\
6449	'  capture dmesg and ftrace data until resume is complete. This data is\n'\
6450	'  transformed into a device timeline and an optional callgraph to give\n'\
6451	'  a detailed view of which devices/subsystems are taking the most\n'\
6452	'  time in suspend/resume.\n'\
6453	'\n'\
6454	'  If no specific command is given, the default behavior is to initiate\n'\
6455	'  a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6456	'\n'\
6457	'  Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6458	'   HTML output:                    <hostname>_<mode>.html\n'\
6459	'   raw dmesg output:               <hostname>_<mode>_dmesg.txt\n'\
6460	'   raw ftrace output:              <hostname>_<mode>_ftrace.txt\n'\
6461	'\n'\
6462	'Options:\n'\
6463	'   -h           Print this help text\n'\
6464	'   -v           Print the current tool version\n'\
6465	'   -config fn   Pull arguments and config options from file fn\n'\
6466	'   -verbose     Print extra information during execution and analysis\n'\
6467	'   -m mode      Mode to initiate for suspend (default: %s)\n'\
6468	'   -o name      Overrides the output subdirectory name when running a new test\n'\
6469	'                default: suspend-{date}-{time}\n'\
6470	'   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6471	'   -addlogs     Add the dmesg and ftrace logs to the html output\n'\
6472	'   -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6473	'   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6474	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6475	'   -result fn   Export a results table to a text file for parsing.\n'\
6476	'   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
 
 
 
6477	'  [testprep]\n'\
6478	'   -sync        Sync the filesystems before starting the test\n'\
6479	'   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
6480	'   -display m   Change the display mode to m for the test (on/off/standby/suspend)\n'\
6481	'  [advanced]\n'\
6482	'   -gzip        Gzip the trace and dmesg logs to save space\n'\
6483	'   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"\n'\
6484	'   -proc        Add usermode process info into the timeline (default: disabled)\n'\
6485	'   -dev         Add kernel function calls and threads to the timeline (default: disabled)\n'\
6486	'   -x2          Run two suspend/resumes back to back (default: disabled)\n'\
6487	'   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)\n'\
6488	'   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)\n'\
6489	'   -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6490	'   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6491	'   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6492	'                by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6493	'                The outputs will be created in a new subdirectory with a summary page.\n'\
6494	'   -maxfail n   Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6495	'  [debug]\n'\
6496	'   -f           Use ftrace to create device callgraphs (default: disabled)\n'\
6497	'   -ftop        Use ftrace on the top level call: "%s" (default: disabled)\n'\
6498	'   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)\n'\
6499	'   -expandcg    pre-expand the callgraph data in the html output (default: disabled)\n'\
6500	'   -fadd file   Add functions to be graphed in the timeline from a list in a text file\n'\
6501	'   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6502	'   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6503	'   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)\n'\
6504	'   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6505	'   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6506	'   -cgfilter S  Filter the callgraph output in the timeline\n'\
6507	'   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6508	'   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6509	'   -devdump     Print out all the raw device data for each phase\n'\
6510	'   -cgdump      Print out all the raw callgraph data\n'\
6511	'\n'\
6512	'Other commands:\n'\
6513	'   -modes       List available suspend modes\n'\
6514	'   -status      Test to see if the system is enabled to run this tool\n'\
6515	'   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table\n'\
6516	'   -wificheck   Print out wifi connection info\n'\
6517	'   -x<mode>     Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6518	'   -sysinfo     Print out system info extracted from BIOS\n'\
6519	'   -devinfo     Print out the pm settings of all devices which support runtime suspend\n'\
6520	'   -cmdinfo     Print out all the platform info collected before and after suspend/resume\n'\
6521	'   -flist       Print the list of functions currently being captured in ftrace\n'\
6522	'   -flistall    Print all functions capable of being captured in ftrace\n'\
6523	'   -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6524	'  [redo]\n'\
6525	'   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)\n'\
6526	'   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)\n'\
6527	'' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6528	return True
6529
6530# ----------------- MAIN --------------------
6531# exec start (skipped if script is loaded as library)
6532if __name__ == '__main__':
6533	genhtml = False
6534	cmd = ''
6535	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6536		'-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6537		'-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6538	if '-f' in sys.argv:
6539		sysvals.cgskip = sysvals.configFile('cgskip.txt')
6540	# loop through the command line arguments
6541	args = iter(sys.argv[1:])
6542	for arg in args:
6543		if(arg == '-m'):
6544			try:
6545				val = next(args)
6546			except:
6547				doError('No mode supplied', True)
6548			if val == 'command' and not sysvals.testcommand:
6549				doError('No command supplied for mode "command"', True)
6550			sysvals.suspendmode = val
6551		elif(arg in simplecmds):
6552			cmd = arg[1:]
6553		elif(arg == '-h'):
6554			printHelp()
6555			sys.exit(0)
6556		elif(arg == '-v'):
6557			pprint("Version %s" % sysvals.version)
6558			sys.exit(0)
 
 
6559		elif(arg == '-x2'):
6560			sysvals.execcount = 2
6561		elif(arg == '-x2delay'):
6562			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6563		elif(arg == '-predelay'):
6564			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6565		elif(arg == '-postdelay'):
6566			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6567		elif(arg == '-f'):
6568			sysvals.usecallgraph = True
6569		elif(arg == '-ftop'):
6570			sysvals.usecallgraph = True
6571			sysvals.ftop = True
6572			sysvals.usekprobes = False
6573		elif(arg == '-skiphtml'):
6574			sysvals.skiphtml = True
6575		elif(arg == '-cgdump'):
6576			sysvals.cgdump = True
6577		elif(arg == '-devdump'):
6578			sysvals.devdump = True
6579		elif(arg == '-genhtml'):
6580			genhtml = True
6581		elif(arg == '-addlogs'):
6582			sysvals.dmesglog = sysvals.ftracelog = True
6583		elif(arg == '-nologs'):
6584			sysvals.dmesglog = sysvals.ftracelog = False
6585		elif(arg == '-addlogdmesg'):
6586			sysvals.dmesglog = True
6587		elif(arg == '-addlogftrace'):
6588			sysvals.ftracelog = True
6589		elif(arg == '-noturbostat'):
6590			sysvals.tstat = False
6591		elif(arg == '-verbose'):
6592			sysvals.verbose = True
6593		elif(arg == '-proc'):
6594			sysvals.useprocmon = True
6595		elif(arg == '-dev'):
6596			sysvals.usedevsrc = True
6597		elif(arg == '-sync'):
6598			sysvals.sync = True
6599		elif(arg == '-wifi'):
6600			sysvals.wifi = True
 
 
 
 
6601		elif(arg == '-gzip'):
6602			sysvals.gzip = True
6603		elif(arg == '-info'):
6604			try:
6605				val = next(args)
6606			except:
6607				doError('-info requires one string argument', True)
 
 
 
 
 
6608		elif(arg == '-rs'):
6609			try:
6610				val = next(args)
6611			except:
6612				doError('-rs requires "enable" or "disable"', True)
6613			if val.lower() in switchvalues:
6614				if val.lower() in switchoff:
6615					sysvals.rs = -1
6616				else:
6617					sysvals.rs = 1
6618			else:
6619				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6620		elif(arg == '-display'):
6621			try:
6622				val = next(args)
6623			except:
6624				doError('-display requires an mode value', True)
6625			disopt = ['on', 'off', 'standby', 'suspend']
6626			if val.lower() not in disopt:
6627				doError('valid display mode values are %s' % disopt, True)
6628			sysvals.display = val.lower()
6629		elif(arg == '-maxdepth'):
6630			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6631		elif(arg == '-rtcwake'):
6632			try:
6633				val = next(args)
6634			except:
6635				doError('No rtcwake time supplied', True)
6636			if val.lower() in switchoff:
6637				sysvals.rtcwake = False
6638			else:
6639				sysvals.rtcwake = True
6640				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6641		elif(arg == '-timeprec'):
6642			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6643		elif(arg == '-mindev'):
6644			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6645		elif(arg == '-mincg'):
6646			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6647		elif(arg == '-bufsize'):
6648			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6649		elif(arg == '-cgtest'):
6650			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6651		elif(arg == '-cgphase'):
6652			try:
6653				val = next(args)
6654			except:
6655				doError('No phase name supplied', True)
6656			d = Data(0)
6657			if val not in d.phasedef:
6658				doError('invalid phase --> (%s: %s), valid phases are %s'\
6659					% (arg, val, d.phasedef.keys()), True)
6660			sysvals.cgphase = val
6661		elif(arg == '-cgfilter'):
6662			try:
6663				val = next(args)
6664			except:
6665				doError('No callgraph functions supplied', True)
6666			sysvals.setCallgraphFilter(val)
6667		elif(arg == '-skipkprobe'):
6668			try:
6669				val = next(args)
6670			except:
6671				doError('No kprobe functions supplied', True)
6672			sysvals.skipKprobes(val)
6673		elif(arg == '-cgskip'):
6674			try:
6675				val = next(args)
6676			except:
6677				doError('No file supplied', True)
6678			if val.lower() in switchoff:
6679				sysvals.cgskip = ''
6680			else:
6681				sysvals.cgskip = sysvals.configFile(val)
6682				if(not sysvals.cgskip):
6683					doError('%s does not exist' % sysvals.cgskip)
6684		elif(arg == '-callloop-maxgap'):
6685			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6686		elif(arg == '-callloop-maxlen'):
6687			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6688		elif(arg == '-cmd'):
6689			try:
6690				val = next(args)
6691			except:
6692				doError('No command string supplied', True)
6693			sysvals.testcommand = val
6694			sysvals.suspendmode = 'command'
6695		elif(arg == '-expandcg'):
6696			sysvals.cgexp = True
6697		elif(arg == '-srgap'):
6698			sysvals.srgap = 5
6699		elif(arg == '-maxfail'):
6700			sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
6701		elif(arg == '-multi'):
6702			try:
6703				c, d = next(args), next(args)
6704			except:
6705				doError('-multi requires two values', True)
6706			sysvals.multiinit(c, d)
6707		elif(arg == '-o'):
6708			try:
6709				val = next(args)
6710			except:
6711				doError('No subdirectory name supplied', True)
6712			sysvals.outdir = sysvals.setOutputFolder(val)
6713		elif(arg == '-config'):
6714			try:
6715				val = next(args)
6716			except:
6717				doError('No text file supplied', True)
6718			file = sysvals.configFile(val)
6719			if(not file):
6720				doError('%s does not exist' % val)
6721			configFromFile(file)
6722		elif(arg == '-fadd'):
6723			try:
6724				val = next(args)
6725			except:
6726				doError('No text file supplied', True)
6727			file = sysvals.configFile(val)
6728			if(not file):
6729				doError('%s does not exist' % val)
6730			sysvals.addFtraceFilterFunctions(file)
6731		elif(arg == '-dmesg'):
6732			try:
6733				val = next(args)
6734			except:
6735				doError('No dmesg file supplied', True)
6736			sysvals.notestrun = True
6737			sysvals.dmesgfile = val
6738			if(os.path.exists(sysvals.dmesgfile) == False):
6739				doError('%s does not exist' % sysvals.dmesgfile)
6740		elif(arg == '-ftrace'):
6741			try:
6742				val = next(args)
6743			except:
6744				doError('No ftrace file supplied', True)
6745			sysvals.notestrun = True
6746			sysvals.ftracefile = val
6747			if(os.path.exists(sysvals.ftracefile) == False):
6748				doError('%s does not exist' % sysvals.ftracefile)
6749		elif(arg == '-summary'):
6750			try:
6751				val = next(args)
6752			except:
6753				doError('No directory supplied', True)
6754			cmd = 'summary'
6755			sysvals.outdir = val
6756			sysvals.notestrun = True
6757			if(os.path.isdir(val) == False):
6758				doError('%s is not accesible' % val)
6759		elif(arg == '-filter'):
6760			try:
6761				val = next(args)
6762			except:
6763				doError('No devnames supplied', True)
6764			sysvals.setDeviceFilter(val)
6765		elif(arg == '-result'):
6766			try:
6767				val = next(args)
6768			except:
6769				doError('No result file supplied', True)
6770			sysvals.result = val
6771			sysvals.signalHandlerInit()
6772		else:
6773			doError('Invalid argument: '+arg, True)
6774
6775	# compatibility errors
6776	if(sysvals.usecallgraph and sysvals.usedevsrc):
6777		doError('-dev is not compatible with -f')
6778	if(sysvals.usecallgraph and sysvals.useprocmon):
6779		doError('-proc is not compatible with -f')
6780
 
6781	if sysvals.usecallgraph and sysvals.cgskip:
6782		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
6783		sysvals.setCallgraphBlacklist(sysvals.cgskip)
6784
6785	# callgraph size cannot exceed device size
6786	if sysvals.mincglen < sysvals.mindevlen:
6787		sysvals.mincglen = sysvals.mindevlen
6788
6789	# remove existing buffers before calculating memory
6790	if(sysvals.usecallgraph or sysvals.usedevsrc):
6791		sysvals.fsetVal('16', 'buffer_size_kb')
6792	sysvals.cpuInfo()
6793
6794	# just run a utility command and exit
6795	if(cmd != ''):
6796		ret = 0
6797		if(cmd == 'status'):
6798			if not statusCheck(True):
6799				ret = 1
6800		elif(cmd == 'fpdt'):
6801			if not getFPDT(True):
6802				ret = 1
6803		elif(cmd == 'sysinfo'):
6804			sysvals.printSystemInfo(True)
6805		elif(cmd == 'devinfo'):
6806			deviceInfo()
6807		elif(cmd == 'modes'):
6808			pprint(getModes())
6809		elif(cmd == 'flist'):
6810			sysvals.getFtraceFilterFunctions(True)
6811		elif(cmd == 'flistall'):
6812			sysvals.getFtraceFilterFunctions(False)
6813		elif(cmd == 'summary'):
6814			runSummary(sysvals.outdir, True, genhtml)
6815		elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
6816			sysvals.verbose = True
6817			ret = displayControl(cmd[1:])
6818		elif(cmd == 'xstat'):
6819			pprint('Display Status: %s' % displayControl('stat').upper())
6820		elif(cmd == 'wificheck'):
6821			dev = sysvals.checkWifi()
6822			if dev:
6823				print('%s is connected' % sysvals.wifiDetails(dev))
6824			else:
6825				print('No wifi connection found')
6826		elif(cmd == 'cmdinfo'):
6827			for out in sysvals.cmdinfo(False, True):
6828				print('[%s - %s]\n%s\n' % out)
6829		sys.exit(ret)
6830
6831	# if instructed, re-analyze existing data files
6832	if(sysvals.notestrun):
6833		stamp = rerunTest(sysvals.outdir)
6834		sysvals.outputResult(stamp)
6835		sys.exit(0)
6836
6837	# verify that we can run a test
6838	error = statusCheck()
6839	if(error):
6840		doError(error)
6841
6842	# extract mem/disk extra modes and convert
6843	mode = sysvals.suspendmode
6844	if mode.startswith('mem'):
6845		memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
6846		if memmode == 'shallow':
6847			mode = 'standby'
6848		elif memmode ==  's2idle':
6849			mode = 'freeze'
6850		else:
6851			mode = 'mem'
6852		sysvals.memmode = memmode
6853		sysvals.suspendmode = mode
6854	if mode.startswith('disk-'):
6855		sysvals.diskmode = mode.split('-', 1)[-1]
6856		sysvals.suspendmode = 'disk'
6857
6858	sysvals.systemInfo(dmidecode(sysvals.mempath))
6859
6860	setRuntimeSuspend(True)
6861	if sysvals.display:
6862		displayControl('init')
6863	failcnt, ret = 0, 0
6864	if sysvals.multitest['run']:
6865		# run multiple tests in a separate subdirectory
6866		if not sysvals.outdir:
6867			if 'time' in sysvals.multitest:
6868				s = '-%dm' % sysvals.multitest['time']
6869			else:
6870				s = '-x%d' % sysvals.multitest['count']
6871			sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
6872		if not os.path.isdir(sysvals.outdir):
6873			os.makedirs(sysvals.outdir)
6874		sysvals.sudoUserchown(sysvals.outdir)
6875		finish = datetime.now()
6876		if 'time' in sysvals.multitest:
6877			finish += timedelta(minutes=sysvals.multitest['time'])
6878		for i in range(sysvals.multitest['count']):
6879			sysvals.multistat(True, i, finish)
6880			if i != 0 and sysvals.multitest['delay'] > 0:
6881				pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
6882				time.sleep(sysvals.multitest['delay'])
6883			fmt = 'suspend-%y%m%d-%H%M%S'
6884			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
6885			ret = runTest(i+1, True)
6886			failcnt = 0 if not ret else failcnt + 1
6887			if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
6888				pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
6889				break
6890			time.sleep(5)
6891			sysvals.resetlog()
6892			sysvals.multistat(False, i, finish)
6893			if 'time' in sysvals.multitest and datetime.now() >= finish:
6894				break
6895		if not sysvals.skiphtml:
6896			runSummary(sysvals.outdir, False, False)
6897		sysvals.sudoUserchown(sysvals.outdir)
6898	else:
6899		if sysvals.outdir:
6900			sysvals.testdir = sysvals.outdir
6901		# run the test in the current directory
6902		ret = runTest()
 
 
6903	if sysvals.display:
6904		displayControl('reset')
6905	setRuntimeSuspend(False)
 
6906	sys.exit(ret)
v6.13.7
   1#!/usr/bin/env python3
   2# SPDX-License-Identifier: GPL-2.0-only
   3#
   4# Tool for analyzing suspend/resume timing
   5# Copyright (c) 2013, Intel Corporation.
   6#
   7# This program is free software; you can redistribute it and/or modify it
   8# under the terms and conditions of the GNU General Public License,
   9# version 2, as published by the Free Software Foundation.
  10#
  11# This program is distributed in the hope it will be useful, but WITHOUT
  12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  14# more details.
  15#
  16# Authors:
  17#	 Todd Brandt <todd.e.brandt@linux.intel.com>
  18#
  19# Links:
  20#	 Home Page
  21#	   https://www.intel.com/content/www/us/en/developer/topic-technology/open/pm-graph/overview.html
  22#	 Source repo
  23#	   git@github.com:intel/pm-graph
  24#
  25# Description:
  26#	 This tool is designed to assist kernel and OS developers in optimizing
  27#	 their linux stack's suspend/resume time. Using a kernel image built
  28#	 with a few extra options enabled, the tool will execute a suspend and
  29#	 will capture dmesg and ftrace data until resume is complete. This data
  30#	 is transformed into a device timeline and a callgraph to give a quick
  31#	 and detailed view of which devices and callbacks are taking the most
  32#	 time in suspend/resume. The output is a single html file which can be
  33#	 viewed in firefox or chrome.
  34#
  35#	 The following kernel build options are required:
  36#		 CONFIG_DEVMEM=y
  37#		 CONFIG_PM_DEBUG=y
  38#		 CONFIG_PM_SLEEP_DEBUG=y
  39#		 CONFIG_FTRACE=y
  40#		 CONFIG_FUNCTION_TRACER=y
  41#		 CONFIG_FUNCTION_GRAPH_TRACER=y
  42#		 CONFIG_KPROBES=y
  43#		 CONFIG_KPROBES_ON_FTRACE=y
  44#
  45#	 For kernel versions older than 3.15:
  46#	 The following additional kernel parameters are required:
  47#		 (e.g. in file /etc/default/grub)
  48#		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
  49#
  50
  51# ----------------- LIBRARIES --------------------
  52
  53import sys
  54import time
  55import os
  56import string
  57import re
  58import platform
  59import signal
  60import codecs
  61from datetime import datetime, timedelta
  62import struct
  63import configparser
  64import gzip
  65from threading import Thread
  66from subprocess import call, Popen, PIPE
  67import base64
  68import traceback
  69
  70debugtiming = False
  71mystarttime = time.time()
  72def pprint(msg):
  73	if debugtiming:
  74		print('[%09.3f] %s' % (time.time()-mystarttime, msg))
  75	else:
  76		print(msg)
  77	sys.stdout.flush()
  78
  79def ascii(text):
  80	return text.decode('ascii', 'ignore')
  81
  82# ----------------- CLASSES --------------------
  83
  84# Class: SystemValues
  85# Description:
  86#	 A global, single-instance container used to
  87#	 store system values and test parameters
  88class SystemValues:
  89	title = 'SleepGraph'
  90	version = '5.13'
  91	ansi = False
  92	rs = 0
  93	display = ''
  94	gzip = False
  95	sync = False
  96	wifi = False
  97	netfix = False
  98	verbose = False
  99	testlog = True
 100	dmesglog = True
 101	ftracelog = False
 102	acpidebug = True
 103	tstat = True
 104	wifitrace = False
 105	mindevlen = 0.0001
 106	mincglen = 0.0
 107	cgphase = ''
 108	cgtest = -1
 109	cgskip = ''
 110	maxfail = 0
 111	multitest = {'run': False, 'count': 1000000, 'delay': 0}
 112	max_graph_depth = 0
 113	callloopmaxgap = 0.0001
 114	callloopmaxlen = 0.005
 115	bufsize = 0
 116	cpucount = 0
 117	memtotal = 204800
 118	memfree = 204800
 119	osversion = ''
 120	srgap = 0
 121	cgexp = False
 122	testdir = ''
 123	outdir = ''
 124	tpath = '/sys/kernel/tracing/'
 125	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
 126	epath = '/sys/kernel/tracing/events/power/'
 127	pmdpath = '/sys/power/pm_debug_messages'
 128	s0ixpath = '/sys/module/intel_pmc_core/parameters/warn_on_s0ix_failures'
 129	s0ixres = '/sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us'
 130	acpipath='/sys/module/acpi/parameters/debug_level'
 131	traceevents = [
 132		'suspend_resume',
 133		'wakeup_source_activate',
 134		'wakeup_source_deactivate',
 135		'device_pm_callback_end',
 136		'device_pm_callback_start'
 137	]
 138	logmsg = ''
 139	testcommand = ''
 140	mempath = '/dev/mem'
 141	powerfile = '/sys/power/state'
 142	mempowerfile = '/sys/power/mem_sleep'
 143	diskpowerfile = '/sys/power/disk'
 144	suspendmode = 'mem'
 145	memmode = ''
 146	diskmode = ''
 147	hostname = 'localhost'
 148	prefix = 'test'
 149	teststamp = ''
 150	sysstamp = ''
 151	dmesgstart = 0.0
 152	dmesgfile = ''
 153	ftracefile = ''
 154	htmlfile = 'output.html'
 155	result = ''
 156	rtcwake = True
 157	rtcwaketime = 15
 158	rtcpath = ''
 159	devicefilter = []
 160	cgfilter = []
 161	stamp = 0
 162	execcount = 1
 163	x2delay = 0
 164	skiphtml = False
 165	usecallgraph = False
 166	ftopfunc = 'pm_suspend'
 167	ftop = False
 168	usetraceevents = False
 169	usetracemarkers = True
 170	useftrace = True
 171	usekprobes = True
 172	usedevsrc = False
 173	useprocmon = False
 174	notestrun = False
 175	cgdump = False
 176	devdump = False
 177	mixedphaseheight = True
 178	devprops = dict()
 179	cfgdef = dict()
 180	platinfo = []
 181	predelay = 0
 182	postdelay = 0
 
 183	tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
 184	tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
 185	tracefuncs = {
 186		'async_synchronize_full': {},
 187		'sys_sync': {},
 188		'ksys_sync': {},
 189		'__pm_notifier_call_chain': {},
 190		'pm_prepare_console': {},
 191		'pm_notifier_call_chain': {},
 192		'freeze_processes': {},
 193		'freeze_kernel_threads': {},
 194		'pm_restrict_gfp_mask': {},
 195		'acpi_suspend_begin': {},
 196		'acpi_hibernation_begin': {},
 197		'acpi_hibernation_enter': {},
 198		'acpi_hibernation_leave': {},
 199		'acpi_pm_freeze': {},
 200		'acpi_pm_thaw': {},
 201		'acpi_s2idle_end': {},
 202		'acpi_s2idle_sync': {},
 203		'acpi_s2idle_begin': {},
 204		'acpi_s2idle_prepare': {},
 205		'acpi_s2idle_prepare_late': {},
 206		'acpi_s2idle_wake': {},
 207		'acpi_s2idle_wakeup': {},
 208		'acpi_s2idle_restore': {},
 209		'acpi_s2idle_restore_early': {},
 210		'hibernate_preallocate_memory': {},
 211		'create_basic_memory_bitmaps': {},
 212		'swsusp_write': {},
 213		'suspend_console': {},
 214		'acpi_pm_prepare': {},
 215		'syscore_suspend': {},
 216		'arch_enable_nonboot_cpus_end': {},
 217		'syscore_resume': {},
 218		'acpi_pm_finish': {},
 219		'resume_console': {},
 220		'acpi_pm_end': {},
 221		'pm_restore_gfp_mask': {},
 222		'thaw_processes': {},
 223		'pm_restore_console': {},
 224		'CPU_OFF': {
 225			'func':'_cpu_down',
 226			'args_x86_64': {'cpu':'%di:s32'},
 227			'format': 'CPU_OFF[{cpu}]'
 228		},
 229		'CPU_ON': {
 230			'func':'_cpu_up',
 231			'args_x86_64': {'cpu':'%di:s32'},
 232			'format': 'CPU_ON[{cpu}]'
 233		},
 234	}
 235	dev_tracefuncs = {
 236		# general wait/delay/sleep
 237		'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
 238		'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
 239		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
 240		'usleep_range': {
 241			'func':'usleep_range_state',
 242			'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'},
 243			'ub': 1
 244		},
 245		'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
 246		'acpi_os_stall': {'ub': 1},
 247		'rt_mutex_slowlock': {'ub': 1},
 248		# ACPI
 249		'acpi_resume_power_resources': {},
 250		'acpi_ps_execute_method': { 'args_x86_64': {
 251			'fullpath':'+0(+40(%di)):string',
 252		}},
 253		# mei_me
 254		'mei_reset': {},
 255		# filesystem
 256		'ext4_sync_fs': {},
 257		# 80211
 258		'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 259		'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 260		'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
 261		'iwlagn_mac_start': {},
 262		'iwlagn_alloc_bcast_station': {},
 263		'iwl_trans_pcie_start_hw': {},
 264		'iwl_trans_pcie_start_fw': {},
 265		'iwl_run_init_ucode': {},
 266		'iwl_load_ucode_wait_alive': {},
 267		'iwl_alive_start': {},
 268		'iwlagn_mac_stop': {},
 269		'iwlagn_mac_suspend': {},
 270		'iwlagn_mac_resume': {},
 271		'iwlagn_mac_add_interface': {},
 272		'iwlagn_mac_remove_interface': {},
 273		'iwlagn_mac_change_interface': {},
 274		'iwlagn_mac_config': {},
 275		'iwlagn_configure_filter': {},
 276		'iwlagn_mac_hw_scan': {},
 277		'iwlagn_bss_info_changed': {},
 278		'iwlagn_mac_channel_switch': {},
 279		'iwlagn_mac_flush': {},
 280		# ATA
 281		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
 282		# i915
 283		'i915_gem_resume': {},
 284		'i915_restore_state': {},
 285		'intel_opregion_setup': {},
 286		'g4x_pre_enable_dp': {},
 287		'vlv_pre_enable_dp': {},
 288		'chv_pre_enable_dp': {},
 289		'g4x_enable_dp': {},
 290		'vlv_enable_dp': {},
 291		'intel_hpd_init': {},
 292		'intel_opregion_register': {},
 293		'intel_dp_detect': {},
 294		'intel_hdmi_detect': {},
 295		'intel_opregion_init': {},
 296		'intel_fbdev_set_suspend': {},
 297	}
 298	infocmds = [
 299		[0, 'sysinfo', 'uname', '-a'],
 300		[0, 'cpuinfo', 'head', '-7', '/proc/cpuinfo'],
 301		[0, 'kparams', 'cat', '/proc/cmdline'],
 302		[0, 'mcelog', 'mcelog'],
 303		[0, 'pcidevices', 'lspci', '-tv'],
 304		[0, 'usbdevices', 'lsusb', '-tv'],
 305		[0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'],
 306		[0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'],
 307		[0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'],
 308		[0, 'ethtool', 'ethtool', '{ethdev}'],
 309		[1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'],
 310		[1, 'interrupts', 'cat', '/proc/interrupts'],
 311		[1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
 312		[2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
 313		[2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
 314		[2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
 315		[2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
 316		[2, 'thermal', 'sh', '-c', 'grep . /sys/class/thermal/thermal_zone*/temp'],
 317	]
 318	cgblacklist = []
 319	kprobes = dict()
 320	timeformat = '%.3f'
 321	cmdline = '%s %s' % \
 322			(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
 323	sudouser = ''
 324	def __init__(self):
 325		self.archargs = 'args_'+platform.machine()
 326		self.hostname = platform.node()
 327		if(self.hostname == ''):
 328			self.hostname = 'localhost'
 329		rtc = "rtc0"
 330		if os.path.exists('/dev/rtc'):
 331			rtc = os.readlink('/dev/rtc')
 332		rtc = '/sys/class/rtc/'+rtc
 333		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 334			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 335			self.rtcpath = rtc
 336		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
 337			self.ansi = True
 338		self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
 339		if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
 340			os.environ['SUDO_USER']:
 341			self.sudouser = os.environ['SUDO_USER']
 342	def resetlog(self):
 343		self.logmsg = ''
 344		self.platinfo = []
 345	def vprint(self, msg):
 346		self.logmsg += msg+'\n'
 347		if self.verbose or msg.startswith('WARNING:'):
 348			pprint(msg)
 349	def signalHandler(self, signum, frame):
 
 
 350		signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
 351		if signame in ['SIGUSR1', 'SIGUSR2', 'SIGSEGV']:
 352			traceback.print_stack()
 353			stack = traceback.format_list(traceback.extract_stack())
 354			self.outputResult({'stack':stack})
 355			if signame == 'SIGUSR1':
 356				return
 357		msg = '%s caused a tool exit, line %d' % (signame, frame.f_lineno)
 358		pprint(msg)
 359		self.outputResult({'error':msg})
 360		os.kill(os.getpid(), signal.SIGKILL)
 361		sys.exit(3)
 362	def signalHandlerInit(self):
 363		capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
 364			'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM', 'USR1', 'USR2']
 365		self.signames = dict()
 366		for i in capture:
 367			s = 'SIG'+i
 368			try:
 369				signum = getattr(signal, s)
 370				signal.signal(signum, self.signalHandler)
 371			except:
 372				continue
 373			self.signames[signum] = s
 374	def rootCheck(self, fatal=True):
 375		if(os.access(self.powerfile, os.W_OK)):
 376			return True
 377		if fatal:
 378			msg = 'This command requires sysfs mount and root access'
 379			pprint('ERROR: %s\n' % msg)
 380			self.outputResult({'error':msg})
 381			sys.exit(1)
 382		return False
 383	def rootUser(self, fatal=False):
 384		if 'USER' in os.environ and os.environ['USER'] == 'root':
 385			return True
 386		if fatal:
 387			msg = 'This command must be run as root'
 388			pprint('ERROR: %s\n' % msg)
 389			self.outputResult({'error':msg})
 390			sys.exit(1)
 391		return False
 392	def usable(self, file, ishtml=False):
 393		if not os.path.exists(file) or os.path.getsize(file) < 1:
 394			return False
 395		if ishtml:
 396			try:
 397				fp = open(file, 'r')
 398				res = fp.read(1000)
 399				fp.close()
 400			except:
 401				return False
 402			if '<html>' not in res:
 403				return False
 404		return True
 405	def getExec(self, cmd):
 406		try:
 407			fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
 408			out = ascii(fp.read()).strip()
 409			fp.close()
 410		except:
 411			out = ''
 412		if out:
 413			return out
 414		for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
 415			'/usr/local/sbin', '/usr/local/bin']:
 416			cmdfull = os.path.join(path, cmd)
 417			if os.path.exists(cmdfull):
 418				return cmdfull
 419		return out
 420	def setPrecision(self, num):
 421		if num < 0 or num > 6:
 422			return
 423		self.timeformat = '%.{0}f'.format(num)
 424	def setOutputFolder(self, value):
 425		args = dict()
 426		n = datetime.now()
 427		args['date'] = n.strftime('%y%m%d')
 428		args['time'] = n.strftime('%H%M%S')
 429		args['hostname'] = args['host'] = self.hostname
 430		args['mode'] = self.suspendmode
 431		return value.format(**args)
 432	def setOutputFile(self):
 433		if self.dmesgfile != '':
 434			m = re.match(r'(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
 435			if(m):
 436				self.htmlfile = m.group('name')+'.html'
 437		if self.ftracefile != '':
 438			m = re.match(r'(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
 439			if(m):
 440				self.htmlfile = m.group('name')+'.html'
 441	def systemInfo(self, info):
 442		p = m = ''
 443		if 'baseboard-manufacturer' in info:
 444			m = info['baseboard-manufacturer']
 445		elif 'system-manufacturer' in info:
 446			m = info['system-manufacturer']
 447		if 'system-product-name' in info:
 448			p = info['system-product-name']
 449		elif 'baseboard-product-name' in info:
 450			p = info['baseboard-product-name']
 451		if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
 452			p = info['baseboard-product-name']
 453		c = info['processor-version'] if 'processor-version' in info else ''
 454		b = info['bios-version'] if 'bios-version' in info else ''
 455		r = info['bios-release-date'] if 'bios-release-date' in info else ''
 456		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
 457			(m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
 458		if self.osversion:
 459			self.sysstamp += ' | os:%s' % self.osversion
 460	def printSystemInfo(self, fatal=False):
 461		self.rootCheck(True)
 462		out = dmidecode(self.mempath, fatal)
 463		if len(out) < 1:
 464			return
 465		fmt = '%-24s: %s'
 466		if self.osversion:
 467			print(fmt % ('os-version', self.osversion))
 468		for name in sorted(out):
 469			print(fmt % (name, out[name]))
 470		print(fmt % ('cpucount', ('%d' % self.cpucount)))
 471		print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
 472		print(fmt % ('memfree', ('%d kB' % self.memfree)))
 473	def cpuInfo(self):
 474		self.cpucount = 0
 475		if os.path.exists('/proc/cpuinfo'):
 476			with open('/proc/cpuinfo', 'r') as fp:
 477				for line in fp:
 478					if re.match(r'^processor[ \t]*:[ \t]*[0-9]*', line):
 479						self.cpucount += 1
 480		if os.path.exists('/proc/meminfo'):
 481			with open('/proc/meminfo', 'r') as fp:
 482				for line in fp:
 483					m = re.match(r'^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
 484					if m:
 485						self.memtotal = int(m.group('sz'))
 486					m = re.match(r'^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
 487					if m:
 488						self.memfree = int(m.group('sz'))
 489		if os.path.exists('/etc/os-release'):
 490			with open('/etc/os-release', 'r') as fp:
 491				for line in fp:
 492					if line.startswith('PRETTY_NAME='):
 493						self.osversion = line[12:].strip().replace('"', '')
 494	def initTestOutput(self, name):
 495		self.prefix = self.hostname
 496		v = open('/proc/version', 'r').read().strip()
 497		kver = v.split()[2]
 498		fmt = name+'-%m%d%y-%H%M%S'
 499		testtime = datetime.now().strftime(fmt)
 500		self.teststamp = \
 501			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 502		ext = ''
 503		if self.gzip:
 504			ext = '.gz'
 505		self.dmesgfile = \
 506			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
 507		self.ftracefile = \
 508			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
 509		self.htmlfile = \
 510			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 511		if not os.path.isdir(self.testdir):
 512			os.makedirs(self.testdir)
 513		self.sudoUserchown(self.testdir)
 514	def getValueList(self, value):
 515		out = []
 516		for i in value.split(','):
 517			if i.strip():
 518				out.append(i.strip())
 519		return out
 520	def setDeviceFilter(self, value):
 521		self.devicefilter = self.getValueList(value)
 522	def setCallgraphFilter(self, value):
 523		self.cgfilter = self.getValueList(value)
 524	def skipKprobes(self, value):
 525		for k in self.getValueList(value):
 526			if k in self.tracefuncs:
 527				del self.tracefuncs[k]
 528			if k in self.dev_tracefuncs:
 529				del self.dev_tracefuncs[k]
 530	def setCallgraphBlacklist(self, file):
 531		self.cgblacklist = self.listFromFile(file)
 532	def rtcWakeAlarmOn(self):
 533		call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
 534		nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
 535		if nowtime:
 536			nowtime = int(nowtime)
 537		else:
 538			# if hardware time fails, use the software time
 539			nowtime = int(datetime.now().strftime('%s'))
 540		alarm = nowtime + self.rtcwaketime
 541		call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
 542	def rtcWakeAlarmOff(self):
 543		call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
 544	def initdmesg(self):
 545		# get the latest time stamp from the dmesg log
 546		lines = Popen('dmesg', stdout=PIPE).stdout.readlines()
 547		ktime = '0'
 548		for line in reversed(lines):
 549			line = ascii(line).replace('\r\n', '')
 550			idx = line.find('[')
 551			if idx > 1:
 552				line = line[idx:]
 553			m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 554			if(m):
 555				ktime = m.group('ktime')
 556				break
 557		self.dmesgstart = float(ktime)
 558	def getdmesg(self, testdata):
 559		op = self.writeDatafileHeader(self.dmesgfile, testdata)
 560		# store all new dmesg lines since initdmesg was called
 561		fp = Popen('dmesg', stdout=PIPE).stdout
 562		for line in fp:
 563			line = ascii(line).replace('\r\n', '')
 564			idx = line.find('[')
 565			if idx > 1:
 566				line = line[idx:]
 567			m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 568			if(not m):
 569				continue
 570			ktime = float(m.group('ktime'))
 571			if ktime > self.dmesgstart:
 572				op.write(line)
 573		fp.close()
 574		op.close()
 575	def listFromFile(self, file):
 576		list = []
 577		fp = open(file)
 578		for i in fp.read().split('\n'):
 579			i = i.strip()
 580			if i and i[0] != '#':
 581				list.append(i)
 582		fp.close()
 583		return list
 584	def addFtraceFilterFunctions(self, file):
 585		for i in self.listFromFile(file):
 586			if len(i) < 2:
 587				continue
 588			self.tracefuncs[i] = dict()
 589	def getFtraceFilterFunctions(self, current):
 590		self.rootCheck(True)
 591		if not current:
 592			call('cat '+self.tpath+'available_filter_functions', shell=True)
 593			return
 594		master = self.listFromFile(self.tpath+'available_filter_functions')
 595		for i in sorted(self.tracefuncs):
 596			if 'func' in self.tracefuncs[i]:
 597				i = self.tracefuncs[i]['func']
 598			if i in master:
 599				print(i)
 600			else:
 601				print(self.colorText(i))
 602	def setFtraceFilterFunctions(self, list):
 603		master = self.listFromFile(self.tpath+'available_filter_functions')
 604		flist = ''
 605		for i in list:
 606			if i not in master:
 607				continue
 608			if ' [' in i:
 609				flist += i.split(' ')[0]+'\n'
 610			else:
 611				flist += i+'\n'
 612		fp = open(self.tpath+'set_graph_function', 'w')
 613		fp.write(flist)
 614		fp.close()
 615	def basicKprobe(self, name):
 616		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
 617	def defaultKprobe(self, name, kdata):
 618		k = kdata
 619		for field in ['name', 'format', 'func']:
 620			if field not in k:
 621				k[field] = name
 622		if self.archargs in k:
 623			k['args'] = k[self.archargs]
 624		else:
 625			k['args'] = dict()
 626			k['format'] = name
 627		self.kprobes[name] = k
 628	def kprobeColor(self, name):
 629		if name not in self.kprobes or 'color' not in self.kprobes[name]:
 630			return ''
 631		return self.kprobes[name]['color']
 632	def kprobeDisplayName(self, name, dataraw):
 633		if name not in self.kprobes:
 634			self.basicKprobe(name)
 635		data = ''
 636		quote=0
 637		# first remvoe any spaces inside quotes, and the quotes
 638		for c in dataraw:
 639			if c == '"':
 640				quote = (quote + 1) % 2
 641			if quote and c == ' ':
 642				data += '_'
 643			elif c != '"':
 644				data += c
 645		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
 646		arglist = dict()
 647		# now process the args
 648		for arg in sorted(args):
 649			arglist[arg] = ''
 650			m = re.match(r'.* '+arg+'=(?P<arg>.*) ', data);
 651			if m:
 652				arglist[arg] = m.group('arg')
 653			else:
 654				m = re.match(r'.* '+arg+'=(?P<arg>.*)', data);
 655				if m:
 656					arglist[arg] = m.group('arg')
 657		out = fmt.format(**arglist)
 658		out = out.replace(' ', '_').replace('"', '')
 659		return out
 660	def kprobeText(self, kname, kprobe):
 661		name = fmt = func = kname
 662		args = dict()
 663		if 'name' in kprobe:
 664			name = kprobe['name']
 665		if 'format' in kprobe:
 666			fmt = kprobe['format']
 667		if 'func' in kprobe:
 668			func = kprobe['func']
 669		if self.archargs in kprobe:
 670			args = kprobe[self.archargs]
 671		if 'args' in kprobe:
 672			args = kprobe['args']
 673		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
 674			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
 675		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
 676			if arg not in args:
 677				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
 678		val = 'p:%s_cal %s' % (name, func)
 679		for i in sorted(args):
 680			val += ' %s=%s' % (i, args[i])
 681		val += '\nr:%s_ret %s $retval\n' % (name, func)
 682		return val
 683	def addKprobes(self, output=False):
 684		if len(self.kprobes) < 1:
 685			return
 686		if output:
 687			pprint('    kprobe functions in this kernel:')
 688		# first test each kprobe
 689		rejects = []
 690		# sort kprobes: trace, ub-dev, custom, dev
 691		kpl = [[], [], [], []]
 692		linesout = len(self.kprobes)
 693		for name in sorted(self.kprobes):
 694			res = self.colorText('YES', 32)
 695			if not self.testKprobe(name, self.kprobes[name]):
 696				res = self.colorText('NO')
 697				rejects.append(name)
 698			else:
 699				if name in self.tracefuncs:
 700					kpl[0].append(name)
 701				elif name in self.dev_tracefuncs:
 702					if 'ub' in self.dev_tracefuncs[name]:
 703						kpl[1].append(name)
 704					else:
 705						kpl[3].append(name)
 706				else:
 707					kpl[2].append(name)
 708			if output:
 709				pprint('         %s: %s' % (name, res))
 710		kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
 711		# remove all failed ones from the list
 712		for name in rejects:
 713			self.kprobes.pop(name)
 714		# set the kprobes all at once
 715		self.fsetVal('', 'kprobe_events')
 716		kprobeevents = ''
 717		for kp in kplist:
 718			kprobeevents += self.kprobeText(kp, self.kprobes[kp])
 719		self.fsetVal(kprobeevents, 'kprobe_events')
 720		if output:
 721			check = self.fgetVal('kprobe_events')
 722			linesack = (len(check.split('\n')) - 1) // 2
 723			pprint('    kprobe functions enabled: %d/%d' % (linesack, linesout))
 724		self.fsetVal('1', 'events/kprobes/enable')
 725	def testKprobe(self, kname, kprobe):
 726		self.fsetVal('0', 'events/kprobes/enable')
 727		kprobeevents = self.kprobeText(kname, kprobe)
 728		if not kprobeevents:
 729			return False
 730		try:
 731			self.fsetVal(kprobeevents, 'kprobe_events')
 732			check = self.fgetVal('kprobe_events')
 733		except:
 734			return False
 735		linesout = len(kprobeevents.split('\n'))
 736		linesack = len(check.split('\n'))
 737		if linesack < linesout:
 738			return False
 739		return True
 740	def setVal(self, val, file):
 741		if not os.path.exists(file):
 742			return False
 743		try:
 744			fp = open(file, 'wb', 0)
 745			fp.write(val.encode())
 746			fp.flush()
 747			fp.close()
 748		except:
 749			return False
 750		return True
 751	def fsetVal(self, val, path):
 752		if not self.useftrace:
 753			return False
 754		return self.setVal(val, self.tpath+path)
 755	def getVal(self, file):
 756		res = ''
 757		if not os.path.exists(file):
 758			return res
 759		try:
 760			fp = open(file, 'r')
 761			res = fp.read()
 762			fp.close()
 763		except:
 764			pass
 765		return res
 766	def fgetVal(self, path):
 767		if not self.useftrace:
 768			return ''
 769		return self.getVal(self.tpath+path)
 770	def cleanupFtrace(self):
 771		if self.useftrace:
 772			self.fsetVal('0', 'events/kprobes/enable')
 773			self.fsetVal('', 'kprobe_events')
 774			self.fsetVal('1024', 'buffer_size_kb')
 
 
 775	def setupAllKprobes(self):
 776		for name in self.tracefuncs:
 777			self.defaultKprobe(name, self.tracefuncs[name])
 778		for name in self.dev_tracefuncs:
 779			self.defaultKprobe(name, self.dev_tracefuncs[name])
 780	def isCallgraphFunc(self, name):
 781		if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
 782			return True
 783		for i in self.tracefuncs:
 784			if 'func' in self.tracefuncs[i]:
 785				f = self.tracefuncs[i]['func']
 786			else:
 787				f = i
 788			if name == f:
 789				return True
 790		return False
 791	def initFtrace(self, quiet=False):
 792		if not self.useftrace:
 793			return
 794		if not quiet:
 795			sysvals.printSystemInfo(False)
 796			pprint('INITIALIZING FTRACE')
 797		# turn trace off
 798		self.fsetVal('0', 'tracing_on')
 799		self.cleanupFtrace()
 
 
 
 
 
 800		# set the trace clock to global
 801		self.fsetVal('global', 'trace_clock')
 802		self.fsetVal('nop', 'current_tracer')
 803		# set trace buffer to an appropriate value
 804		cpus = max(1, self.cpucount)
 805		if self.bufsize > 0:
 806			tgtsize = self.bufsize
 807		elif self.usecallgraph or self.usedevsrc:
 808			bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
 809				else (3*1024*1024)
 810			tgtsize = min(self.memfree, bmax)
 811		else:
 812			tgtsize = 65536
 813		while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
 814			# if the size failed to set, lower it and keep trying
 815			tgtsize -= 65536
 816			if tgtsize < 65536:
 817				tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
 818				break
 819		self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
 820		# initialize the callgraph trace
 821		if(self.usecallgraph):
 822			# set trace type
 823			self.fsetVal('function_graph', 'current_tracer')
 824			self.fsetVal('', 'set_ftrace_filter')
 825			# temporary hack to fix https://bugzilla.kernel.org/show_bug.cgi?id=212761
 826			fp = open(self.tpath+'set_ftrace_notrace', 'w')
 827			fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
 828			fp.close()
 829			# set trace format options
 830			self.fsetVal('print-parent', 'trace_options')
 831			self.fsetVal('funcgraph-abstime', 'trace_options')
 832			self.fsetVal('funcgraph-cpu', 'trace_options')
 833			self.fsetVal('funcgraph-duration', 'trace_options')
 834			self.fsetVal('funcgraph-proc', 'trace_options')
 835			self.fsetVal('funcgraph-tail', 'trace_options')
 836			self.fsetVal('nofuncgraph-overhead', 'trace_options')
 837			self.fsetVal('context-info', 'trace_options')
 838			self.fsetVal('graph-time', 'trace_options')
 839			self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
 840			cf = ['dpm_run_callback']
 841			if(self.usetraceevents):
 842				cf += ['dpm_prepare', 'dpm_complete']
 843			for fn in self.tracefuncs:
 844				if 'func' in self.tracefuncs[fn]:
 845					cf.append(self.tracefuncs[fn]['func'])
 846				else:
 847					cf.append(fn)
 848			if self.ftop:
 849				self.setFtraceFilterFunctions([self.ftopfunc])
 850			else:
 851				self.setFtraceFilterFunctions(cf)
 852		# initialize the kprobe trace
 853		elif self.usekprobes:
 854			for name in self.tracefuncs:
 855				self.defaultKprobe(name, self.tracefuncs[name])
 856			if self.usedevsrc:
 857				for name in self.dev_tracefuncs:
 858					self.defaultKprobe(name, self.dev_tracefuncs[name])
 859			if not quiet:
 860				pprint('INITIALIZING KPROBES')
 861			self.addKprobes(self.verbose)
 862		if(self.usetraceevents):
 863			# turn trace events on
 864			events = iter(self.traceevents)
 865			for e in events:
 866				self.fsetVal('1', 'events/power/'+e+'/enable')
 867		# clear the trace buffer
 868		self.fsetVal('', 'trace')
 869	def verifyFtrace(self):
 870		# files needed for any trace data
 871		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 872				 'trace_marker', 'trace_options', 'tracing_on']
 873		# legacy check for old systems
 874		if not os.path.exists(self.tpath+'trace'):
 875			self.tpath = '/sys/kernel/debug/tracing/'
 876		if not os.path.exists(self.epath):
 877			self.epath = '/sys/kernel/debug/tracing/events/power/'
 878		# files needed for callgraph trace data
 879		tp = self.tpath
 880		if(self.usecallgraph):
 881			files += [
 882				'available_filter_functions',
 883				'set_ftrace_filter',
 884				'set_graph_function'
 885			]
 886		for f in files:
 887			if(os.path.exists(tp+f) == False):
 888				return False
 889		return True
 890	def verifyKprobes(self):
 891		# files needed for kprobes to work
 892		files = ['kprobe_events', 'events']
 893		tp = self.tpath
 894		for f in files:
 895			if(os.path.exists(tp+f) == False):
 896				return False
 897		return True
 898	def colorText(self, str, color=31):
 899		if not self.ansi:
 900			return str
 901		return '\x1B[%d;40m%s\x1B[m' % (color, str)
 902	def writeDatafileHeader(self, filename, testdata):
 903		fp = self.openlog(filename, 'w')
 904		fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
 905		for test in testdata:
 906			if 'fw' in test:
 907				fw = test['fw']
 908				if(fw):
 909					fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
 910			if 'turbo' in test:
 911				fp.write('# turbostat %s\n' % test['turbo'])
 912			if 'wifi' in test:
 913				fp.write('# wifi %s\n' % test['wifi'])
 914			if 'netfix' in test:
 915				fp.write('# netfix %s\n' % test['netfix'])
 916			if test['error'] or len(testdata) > 1:
 917				fp.write('# enter_sleep_error %s\n' % test['error'])
 918		return fp
 919	def sudoUserchown(self, dir):
 920		if os.path.exists(dir) and self.sudouser:
 921			cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
 922			call(cmd.format(self.sudouser, dir), shell=True)
 923	def outputResult(self, testdata, num=0):
 924		if not self.result:
 925			return
 926		n = ''
 927		if num > 0:
 928			n = '%d' % num
 929		fp = open(self.result, 'a')
 930		if 'stack' in testdata:
 931			fp.write('Printing stack trace:\n')
 932			for line in testdata['stack']:
 933				fp.write(line)
 934			fp.close()
 935			self.sudoUserchown(self.result)
 936			return
 937		if 'error' in testdata:
 938			fp.write('result%s: fail\n' % n)
 939			fp.write('error%s: %s\n' % (n, testdata['error']))
 940		else:
 941			fp.write('result%s: pass\n' % n)
 942		if 'mode' in testdata:
 943			fp.write('mode%s: %s\n' % (n, testdata['mode']))
 944		for v in ['suspend', 'resume', 'boot', 'lastinit']:
 945			if v in testdata:
 946				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
 947		for v in ['fwsuspend', 'fwresume']:
 948			if v in testdata:
 949				fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
 950		if 'bugurl' in testdata:
 951			fp.write('url%s: %s\n' % (n, testdata['bugurl']))
 952		fp.close()
 953		self.sudoUserchown(self.result)
 954	def configFile(self, file):
 955		dir = os.path.dirname(os.path.realpath(__file__))
 956		if os.path.exists(file):
 957			return file
 958		elif os.path.exists(dir+'/'+file):
 959			return dir+'/'+file
 960		elif os.path.exists(dir+'/config/'+file):
 961			return dir+'/config/'+file
 962		return ''
 963	def openlog(self, filename, mode):
 964		isgz = self.gzip
 965		if mode == 'r':
 966			try:
 967				with gzip.open(filename, mode+'t') as fp:
 968					test = fp.read(64)
 969				isgz = True
 970			except:
 971				isgz = False
 972		if isgz:
 973			return gzip.open(filename, mode+'t')
 974		return open(filename, mode)
 975	def putlog(self, filename, text):
 976		with self.openlog(filename, 'a') as fp:
 977			fp.write(text)
 978			fp.close()
 979	def dlog(self, text):
 980		if not self.dmesgfile:
 981			return
 982		self.putlog(self.dmesgfile, '# %s\n' % text)
 983	def flog(self, text):
 984		self.putlog(self.ftracefile, text)
 985	def b64unzip(self, data):
 986		try:
 987			out = codecs.decode(base64.b64decode(data), 'zlib').decode()
 988		except:
 989			out = data
 990		return out
 991	def b64zip(self, data):
 992		out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
 993		return out
 994	def platforminfo(self, cmdafter):
 995		# add platform info on to a completed ftrace file
 996		if not os.path.exists(self.ftracefile):
 997			return False
 998		footer = '#\n'
 999
1000		# add test command string line if need be
1001		if self.suspendmode == 'command' and self.testcommand:
1002			footer += '# platform-testcmd: %s\n' % (self.testcommand)
1003
1004		# get a list of target devices from the ftrace file
1005		props = dict()
1006		tp = TestProps()
1007		tf = self.openlog(self.ftracefile, 'r')
1008		for line in tf:
1009			if tp.stampInfo(line, self):
1010				continue
1011			# parse only valid lines, if this is not one move on
1012			m = re.match(tp.ftrace_line_fmt, line)
1013			if(not m or 'device_pm_callback_start' not in line):
1014				continue
1015			m = re.match(r'.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
1016			if(not m):
1017				continue
1018			dev = m.group('d')
1019			if dev not in props:
1020				props[dev] = DevProps()
1021		tf.close()
1022
1023		# now get the syspath for each target device
1024		for dirname, dirnames, filenames in os.walk('/sys/devices'):
1025			if(re.match(r'.*/power', dirname) and 'async' in filenames):
1026				dev = dirname.split('/')[-2]
1027				if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
1028					props[dev].syspath = dirname[:-6]
1029
1030		# now fill in the properties for our target devices
1031		for dev in sorted(props):
1032			dirname = props[dev].syspath
1033			if not dirname or not os.path.exists(dirname):
1034				continue
1035			props[dev].isasync = False
1036			if os.path.exists(dirname+'/power/async'):
1037				fp = open(dirname+'/power/async')
1038				if 'enabled' in fp.read():
1039					props[dev].isasync = True
1040				fp.close()
1041			fields = os.listdir(dirname)
1042			for file in ['product', 'name', 'model', 'description', 'id', 'idVendor']:
1043				if file not in fields:
1044					continue
1045				try:
1046					with open(os.path.join(dirname, file), 'rb') as fp:
1047						props[dev].altname = ascii(fp.read())
1048				except:
1049					continue
1050				if file == 'idVendor':
1051					idv, idp = props[dev].altname.strip(), ''
1052					try:
1053						with open(os.path.join(dirname, 'idProduct'), 'rb') as fp:
1054							idp = ascii(fp.read()).strip()
1055					except:
1056						props[dev].altname = ''
1057						break
1058					props[dev].altname = '%s:%s' % (idv, idp)
1059				break
 
 
 
 
1060			if props[dev].altname:
1061				out = props[dev].altname.strip().replace('\n', ' ')\
1062					.replace(',', ' ').replace(';', ' ')
1063				props[dev].altname = out
1064
1065		# add a devinfo line to the bottom of ftrace
1066		out = ''
1067		for dev in sorted(props):
1068			out += props[dev].out(dev)
1069		footer += '# platform-devinfo: %s\n' % self.b64zip(out)
1070
1071		# add a line for each of these commands with their outputs
1072		for name, cmdline, info in cmdafter:
1073			footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
1074		self.flog(footer)
 
 
1075		return True
1076	def commonPrefix(self, list):
1077		if len(list) < 2:
1078			return ''
1079		prefix = list[0]
1080		for s in list[1:]:
1081			while s[:len(prefix)] != prefix and prefix:
1082				prefix = prefix[:len(prefix)-1]
1083			if not prefix:
1084				break
1085		if '/' in prefix and prefix[-1] != '/':
1086			prefix = prefix[0:prefix.rfind('/')+1]
1087		return prefix
1088	def dictify(self, text, format):
1089		out = dict()
1090		header = True if format == 1 else False
1091		delim = ' ' if format == 1 else ':'
1092		for line in text.split('\n'):
1093			if header:
1094				header, out['@'] = False, line
1095				continue
1096			line = line.strip()
1097			if delim in line:
1098				data = line.split(delim, 1)
1099				num = re.search(r'[\d]+', data[1])
1100				if format == 2 and num:
1101					out[data[0].strip()] = num.group()
1102				else:
1103					out[data[0].strip()] = data[1]
1104		return out
1105	def cmdinfovar(self, arg):
1106		if arg == 'ethdev':
1107			try:
1108				cmd = [self.getExec('ip'), '-4', '-o', '-br', 'addr']
1109				fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout
1110				info = ascii(fp.read()).strip()
1111				fp.close()
1112			except:
1113				return 'iptoolcrash'
1114			for line in info.split('\n'):
1115				if line[0] == 'e' and 'UP' in line:
1116					return line.split()[0]
1117			return 'nodevicefound'
1118		return 'unknown'
1119	def cmdinfo(self, begin, debug=False):
1120		out = []
1121		if begin:
1122			self.cmd1 = dict()
1123		for cargs in self.infocmds:
1124			delta, name, args = cargs[0], cargs[1], cargs[2:]
1125			for i in range(len(args)):
1126				if args[i][0] == '{' and args[i][-1] == '}':
1127					args[i] = self.cmdinfovar(args[i][1:-1])
1128			cmdline, cmdpath = ' '.join(args[0:]), self.getExec(args[0])
1129			if not cmdpath or (begin and not delta):
1130				continue
1131			self.dlog('[%s]' % cmdline)
1132			try:
1133				fp = Popen([cmdpath]+args[1:], stdout=PIPE, stderr=PIPE).stdout
1134				info = ascii(fp.read()).strip()
1135				fp.close()
1136			except:
1137				continue
1138			if not debug and begin:
1139				self.cmd1[name] = self.dictify(info, delta)
1140			elif not debug and delta and name in self.cmd1:
1141				before, after = self.cmd1[name], self.dictify(info, delta)
1142				dinfo = ('\t%s\n' % before['@']) if '@' in before and len(before) > 1 else ''
1143				prefix = self.commonPrefix(list(before.keys()))
1144				for key in sorted(before):
1145					if key in after and before[key] != after[key]:
1146						title = key.replace(prefix, '')
1147						if delta == 2:
1148							dinfo += '\t%s : %s -> %s\n' % \
1149								(title, before[key].strip(), after[key].strip())
1150						else:
1151							dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1152								(title, before[key], title, after[key])
1153				dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1154				out.append((name, cmdline, dinfo))
1155			else:
1156				out.append((name, cmdline, '\tnothing' if not info else info))
1157		return out
1158	def testVal(self, file, fmt='basic', value=''):
1159		if file == 'restoreall':
1160			for f in self.cfgdef:
1161				if os.path.exists(f):
1162					fp = open(f, 'w')
1163					fp.write(self.cfgdef[f])
1164					fp.close()
1165			self.cfgdef = dict()
1166		elif value and os.path.exists(file):
1167			fp = open(file, 'r+')
1168			if fmt == 'radio':
1169				m = re.match(r'.*\[(?P<v>.*)\].*', fp.read())
1170				if m:
1171					self.cfgdef[file] = m.group('v')
1172			elif fmt == 'acpi':
1173				line = fp.read().strip().split('\n')[-1]
1174				m = re.match(r'.* (?P<v>[0-9A-Fx]*) .*', line)
1175				if m:
1176					self.cfgdef[file] = m.group('v')
1177			else:
1178				self.cfgdef[file] = fp.read().strip()
1179			fp.write(value)
1180			fp.close()
1181	def s0ixSupport(self):
1182		if not os.path.exists(self.s0ixres) or not os.path.exists(self.mempowerfile):
1183			return False
1184		fp = open(sysvals.mempowerfile, 'r')
1185		data = fp.read().strip()
1186		fp.close()
1187		if '[s2idle]' in data:
1188			return True
1189		return False
1190	def haveTurbostat(self):
1191		if not self.tstat:
1192			return False
1193		cmd = self.getExec('turbostat')
1194		if not cmd:
1195			return False
1196		fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1197		out = ascii(fp.read()).strip()
1198		fp.close()
1199		if re.match(r'turbostat version .*', out):
1200			self.vprint(out)
1201			return True
1202		return False
1203	def turbostat(self, s0ixready):
1204		cmd = self.getExec('turbostat')
1205		rawout = keyline = valline = ''
1206		fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1207		fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE)
1208		for line in fp.stderr:
1209			line = ascii(line)
1210			rawout += line
1211			if keyline and valline:
1212				continue
1213			if re.match(r'(?i)Avg_MHz.*', line):
1214				keyline = line.strip().split()
1215			elif keyline:
1216				valline = line.strip().split()
1217		fp.wait()
1218		if not keyline or not valline or len(keyline) != len(valline):
1219			errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1220			self.vprint(errmsg)
1221			if not self.verbose:
1222				pprint(errmsg)
1223			return (fp.returncode, '')
1224		if self.verbose:
1225			pprint(rawout.strip())
1226		out = []
1227		for key in keyline:
1228			idx = keyline.index(key)
1229			val = valline[idx]
1230			if key == 'SYS%LPI' and not s0ixready and re.match(r'^[0\.]*$', val):
1231				continue
1232			out.append('%s=%s' % (key, val))
1233		return (fp.returncode, '|'.join(out))
1234	def netfixon(self, net='both'):
1235		cmd = self.getExec('netfix')
1236		if not cmd:
1237			return ''
1238		fp = Popen([cmd, '-s', net, 'on'], stdout=PIPE, stderr=PIPE).stdout
1239		out = ascii(fp.read()).strip()
1240		fp.close()
1241		return out
1242	def wifiDetails(self, dev):
1243		try:
1244			info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1245		except:
1246			return dev
1247		vals = [dev]
1248		for prop in info.split('\n'):
1249			if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1250				vals.append(prop.split('=')[-1])
1251		return ':'.join(vals)
1252	def checkWifi(self, dev=''):
1253		try:
1254			w = open('/proc/net/wireless', 'r').read().strip()
1255		except:
1256			return ''
1257		for line in reversed(w.split('\n')):
1258			m = re.match(r' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', line)
1259			if not m or (dev and dev != m.group('dev')):
1260				continue
1261			return m.group('dev')
1262		return ''
1263	def pollWifi(self, dev, timeout=10):
1264		start = time.time()
1265		while (time.time() - start) < timeout:
1266			w = self.checkWifi(dev)
1267			if w:
1268				return '%s reconnected %.2f' % \
1269					(self.wifiDetails(dev), max(0, time.time() - start))
1270			time.sleep(0.01)
1271		return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1272	def errorSummary(self, errinfo, msg):
1273		found = False
1274		for entry in errinfo:
1275			if re.match(entry['match'], msg):
1276				entry['count'] += 1
1277				if self.hostname not in entry['urls']:
1278					entry['urls'][self.hostname] = [self.htmlfile]
1279				elif self.htmlfile not in entry['urls'][self.hostname]:
1280					entry['urls'][self.hostname].append(self.htmlfile)
1281				found = True
1282				break
1283		if found:
1284			return
1285		arr = msg.split()
1286		for j in range(len(arr)):
1287			if re.match(r'^[0-9,\-\.]*$', arr[j]):
1288				arr[j] = r'[0-9,\-\.]*'
1289			else:
1290				arr[j] = arr[j]\
1291					.replace('\\', r'\\\\').replace(']', r'\]').replace('[', r'\[')\
1292					.replace('.', r'\.').replace('+', r'\+').replace('*', r'\*')\
1293					.replace('(', r'\(').replace(')', r'\)').replace('}', r'\}')\
1294					.replace('{', r'\{')
1295		mstr = ' *'.join(arr)
1296		entry = {
1297			'line': msg,
1298			'match': mstr,
1299			'count': 1,
1300			'urls': {self.hostname: [self.htmlfile]}
1301		}
1302		errinfo.append(entry)
1303	def multistat(self, start, idx, finish):
1304		if 'time' in self.multitest:
1305			id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1306		else:
1307			id = '%d/%d' % (idx+1, self.multitest['count'])
1308		t = time.time()
1309		if 'start' not in self.multitest:
1310			self.multitest['start'] = self.multitest['last'] = t
1311			self.multitest['total'] = 0.0
1312			pprint('TEST (%s) START' % id)
1313			return
1314		dt = t - self.multitest['last']
1315		if not start:
1316			if idx == 0 and self.multitest['delay'] > 0:
1317				self.multitest['total'] += self.multitest['delay']
1318			pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1319			return
1320		self.multitest['total'] += dt
1321		self.multitest['last'] = t
1322		avg = self.multitest['total'] / idx
1323		if 'time' in self.multitest:
1324			left = finish - datetime.now()
1325			left -= timedelta(microseconds=left.microseconds)
1326		else:
1327			left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1328		pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1329			(id, avg, str(left)))
1330	def multiinit(self, c, d):
1331		sz, unit = 'count', 'm'
1332		if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1333			sz, unit, c = 'time', c[-1], c[:-1]
1334		self.multitest['run'] = True
1335		self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1336		self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1337		if unit == 'd':
1338			self.multitest[sz] *= 1440
1339		elif unit == 'h':
1340			self.multitest[sz] *= 60
1341	def displayControl(self, cmd):
1342		xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
1343		if self.sudouser:
1344			xset = 'sudo -u %s %s' % (self.sudouser, xset)
1345		if cmd == 'init':
1346			ret = call(xset.format('dpms 0 0 0'), shell=True)
1347			if not ret:
1348				ret = call(xset.format('s off'), shell=True)
1349		elif cmd == 'reset':
1350			ret = call(xset.format('s reset'), shell=True)
1351		elif cmd in ['on', 'off', 'standby', 'suspend']:
1352			b4 = self.displayControl('stat')
1353			ret = call(xset.format('dpms force %s' % cmd), shell=True)
1354			if not ret:
1355				curr = self.displayControl('stat')
1356				self.vprint('Display Switched: %s -> %s' % (b4, curr))
1357				if curr != cmd:
1358					self.vprint('WARNING: Display failed to change to %s' % cmd)
1359			if ret:
1360				self.vprint('WARNING: Display failed to change to %s with xset' % cmd)
1361				return ret
1362		elif cmd == 'stat':
1363			fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
1364			ret = 'unknown'
1365			for line in fp:
1366				m = re.match(r'[\s]*Monitor is (?P<m>.*)', ascii(line))
1367				if(m and len(m.group('m')) >= 2):
1368					out = m.group('m').lower()
1369					ret = out[3:] if out[0:2] == 'in' else out
1370					break
1371			fp.close()
1372		return ret
1373	def setRuntimeSuspend(self, before=True):
1374		if before:
1375			# runtime suspend disable or enable
1376			if self.rs > 0:
1377				self.rstgt, self.rsval, self.rsdir = 'on', 'auto', 'enabled'
1378			else:
1379				self.rstgt, self.rsval, self.rsdir = 'auto', 'on', 'disabled'
1380			pprint('CONFIGURING RUNTIME SUSPEND...')
1381			self.rslist = deviceInfo(self.rstgt)
1382			for i in self.rslist:
1383				self.setVal(self.rsval, i)
1384			pprint('runtime suspend %s on all devices (%d changed)' % (self.rsdir, len(self.rslist)))
1385			pprint('waiting 5 seconds...')
1386			time.sleep(5)
1387		else:
1388			# runtime suspend re-enable or re-disable
1389			for i in self.rslist:
1390				self.setVal(self.rstgt, i)
1391			pprint('runtime suspend settings restored on %d devices' % len(self.rslist))
1392	def start(self, pm):
1393		if self.useftrace:
1394			self.dlog('start ftrace tracing')
1395			self.fsetVal('1', 'tracing_on')
1396			if self.useprocmon:
1397				self.dlog('start the process monitor')
1398				pm.start()
1399	def stop(self, pm):
1400		if self.useftrace:
1401			if self.useprocmon:
1402				self.dlog('stop the process monitor')
1403				pm.stop()
1404			self.dlog('stop ftrace tracing')
1405			self.fsetVal('0', 'tracing_on')
1406
1407sysvals = SystemValues()
1408switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1409switchoff = ['disable', 'off', 'false', '0']
1410suspendmodename = {
1411	'standby': 'standby (S1)',
1412	'freeze': 'freeze (S2idle)',
1413	'mem': 'suspend (S3)',
1414	'disk': 'hibernate (S4)'
1415}
1416
1417# Class: DevProps
1418# Description:
1419#	 Simple class which holds property values collected
1420#	 for all the devices used in the timeline.
1421class DevProps:
1422	def __init__(self):
1423		self.syspath = ''
1424		self.altname = ''
1425		self.isasync = True
1426		self.xtraclass = ''
1427		self.xtrainfo = ''
1428	def out(self, dev):
1429		return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1430	def debug(self, dev):
1431		pprint('%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.isasync))
1432	def altName(self, dev):
1433		if not self.altname or self.altname == dev:
1434			return dev
1435		return '%s [%s]' % (self.altname, dev)
1436	def xtraClass(self):
1437		if self.xtraclass:
1438			return ' '+self.xtraclass
1439		if not self.isasync:
1440			return ' sync'
1441		return ''
1442	def xtraInfo(self):
1443		if self.xtraclass:
1444			return ' '+self.xtraclass
1445		if self.isasync:
1446			return ' (async)'
1447		return ' (sync)'
1448
1449# Class: DeviceNode
1450# Description:
1451#	 A container used to create a device hierachy, with a single root node
1452#	 and a tree of child nodes. Used by Data.deviceTopology()
1453class DeviceNode:
1454	def __init__(self, nodename, nodedepth):
1455		self.name = nodename
1456		self.children = []
1457		self.depth = nodedepth
1458
1459# Class: Data
1460# Description:
1461#	 The primary container for suspend/resume test data. There is one for
1462#	 each test run. The data is organized into a cronological hierarchy:
1463#	 Data.dmesg {
1464#		phases {
1465#			10 sequential, non-overlapping phases of S/R
1466#			contents: times for phase start/end, order/color data for html
1467#			devlist {
1468#				device callback or action list for this phase
1469#				device {
1470#					a single device callback or generic action
1471#					contents: start/stop times, pid/cpu/driver info
1472#						parents/children, html id for timeline/callgraph
1473#						optionally includes an ftrace callgraph
1474#						optionally includes dev/ps data
1475#				}
1476#			}
1477#		}
1478#	}
1479#
1480class Data:
1481	phasedef = {
1482		'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1483		        'suspend': {'order': 1, 'color': '#88FF88'},
1484		   'suspend_late': {'order': 2, 'color': '#00AA00'},
1485		  'suspend_noirq': {'order': 3, 'color': '#008888'},
1486		'suspend_machine': {'order': 4, 'color': '#0000FF'},
1487		 'resume_machine': {'order': 5, 'color': '#FF0000'},
1488		   'resume_noirq': {'order': 6, 'color': '#FF9900'},
1489		   'resume_early': {'order': 7, 'color': '#FFCC00'},
1490		         'resume': {'order': 8, 'color': '#FFFF88'},
1491		'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1492	}
1493	errlist = {
1494		'HWERROR' : r'.*\[ *Hardware Error *\].*',
1495		'FWBUG'   : r'.*\[ *Firmware Bug *\].*',
1496		'TASKFAIL': r'.*Freezing .*after *.*',
1497		'BUG'     : r'(?i).*\bBUG\b.*',
1498		'ERROR'   : r'(?i).*\bERROR\b.*',
1499		'WARNING' : r'(?i).*\bWARNING\b.*',
1500		'FAULT'   : r'(?i).*\bFAULT\b.*',
1501		'FAIL'    : r'(?i).*\bFAILED\b.*',
1502		'INVALID' : r'(?i).*\bINVALID\b.*',
1503		'CRASH'   : r'(?i).*\bCRASHED\b.*',
1504		'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
1505		'ABORT'   : r'(?i).*\bABORT\b.*',
1506		'IRQ'     : r'.*\bgenirq: .*',
 
1507		'ACPI'    : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1508		'DISKFULL': r'.*\bNo space left on device.*',
1509		'USBERR'  : r'.*usb .*device .*, error [0-9-]*',
1510		'ATAERR'  : r' *ata[0-9\.]*: .*failed.*',
1511		'MEIERR'  : r' *mei.*: .*failed.*',
1512		'TPMERR'  : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1513	}
1514	def __init__(self, num):
1515		idchar = 'abcdefghij'
1516		self.start = 0.0 # test start
1517		self.end = 0.0   # test end
1518		self.hwstart = 0 # rtc test start
1519		self.hwend = 0   # rtc test end
1520		self.tSuspended = 0.0 # low-level suspend start
1521		self.tResumed = 0.0   # low-level resume start
1522		self.tKernSus = 0.0   # kernel level suspend start
1523		self.tKernRes = 0.0   # kernel level resume end
1524		self.fwValid = False  # is firmware data available
1525		self.fwSuspend = 0    # time spent in firmware suspend
1526		self.fwResume = 0     # time spent in firmware resume
1527		self.html_device_id = 0
1528		self.stamp = 0
1529		self.outfile = ''
1530		self.kerror = False
1531		self.wifi = dict()
1532		self.turbostat = 0
1533		self.enterfail = ''
1534		self.currphase = ''
1535		self.pstl = dict()    # process timeline
1536		self.testnumber = num
1537		self.idstr = idchar[num]
1538		self.dmesgtext = []   # dmesg text file in memory
1539		self.dmesg = dict()   # root data structure
1540		self.errorinfo = {'suspend':[],'resume':[]}
1541		self.tLow = []        # time spent in low-level suspends (standby/freeze)
1542		self.devpids = []
1543		self.devicegroups = 0
1544	def sortedPhases(self):
1545		return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1546	def initDevicegroups(self):
1547		# called when phases are all finished being added
1548		for phase in sorted(self.dmesg.keys()):
1549			if '*' in phase:
1550				p = phase.split('*')
1551				pnew = '%s%d' % (p[0], len(p))
1552				self.dmesg[pnew] = self.dmesg.pop(phase)
1553		self.devicegroups = []
1554		for phase in self.sortedPhases():
1555			self.devicegroups.append([phase])
1556	def nextPhase(self, phase, offset):
1557		order = self.dmesg[phase]['order'] + offset
1558		for p in self.dmesg:
1559			if self.dmesg[p]['order'] == order:
1560				return p
1561		return ''
1562	def lastPhase(self, depth=1):
1563		plist = self.sortedPhases()
1564		if len(plist) < depth:
1565			return ''
1566		return plist[-1*depth]
1567	def turbostatInfo(self):
1568		tp = TestProps()
1569		out = {'syslpi':'N/A','pkgpc10':'N/A'}
1570		for line in self.dmesgtext:
1571			m = re.match(tp.tstatfmt, line)
1572			if not m:
1573				continue
1574			for i in m.group('t').split('|'):
1575				if 'SYS%LPI' in i:
1576					out['syslpi'] = i.split('=')[-1]+'%'
1577				elif 'pc10' in i:
1578					out['pkgpc10'] = i.split('=')[-1]+'%'
1579			break
1580		return out
1581	def extractErrorInfo(self):
1582		lf = self.dmesgtext
1583		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1584			lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1585		i = 0
1586		tp = TestProps()
1587		list = []
1588		for line in lf:
1589			i += 1
1590			if tp.stampInfo(line, sysvals):
1591				continue
1592			m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1593			if not m:
1594				continue
1595			t = float(m.group('ktime'))
1596			if t < self.start or t > self.end:
1597				continue
1598			dir = 'suspend' if t < self.tSuspended else 'resume'
1599			msg = m.group('msg')
1600			if re.match(r'capability: warning: .*', msg):
1601				continue
1602			for err in self.errlist:
1603				if re.match(self.errlist[err], msg):
1604					list.append((msg, err, dir, t, i, i))
1605					self.kerror = True
1606					break
1607		tp.msglist = []
1608		for msg, type, dir, t, idx1, idx2 in list:
1609			tp.msglist.append(msg)
1610			self.errorinfo[dir].append((type, t, idx1, idx2))
1611		if self.kerror:
1612			sysvals.dmesglog = True
1613		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1614			lf.close()
1615		return tp
1616	def setStart(self, time, msg=''):
1617		self.start = time
1618		if msg:
1619			try:
1620				self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1621			except:
1622				self.hwstart = 0
1623	def setEnd(self, time, msg=''):
1624		self.end = time
1625		if msg:
1626			try:
1627				self.hwend = datetime.strptime(msg, sysvals.tmend)
1628			except:
1629				self.hwend = 0
1630	def isTraceEventOutsideDeviceCalls(self, pid, time):
1631		for phase in self.sortedPhases():
1632			list = self.dmesg[phase]['list']
1633			for dev in list:
1634				d = list[dev]
1635				if(d['pid'] == pid and time >= d['start'] and
1636					time < d['end']):
1637					return False
1638		return True
1639	def sourcePhase(self, start):
1640		for phase in self.sortedPhases():
1641			if 'machine' in phase:
1642				continue
1643			pend = self.dmesg[phase]['end']
1644			if start <= pend:
1645				return phase
1646		return 'resume_complete' if 'resume_complete' in self.dmesg else ''
1647	def sourceDevice(self, phaselist, start, end, pid, type):
1648		tgtdev = ''
1649		for phase in phaselist:
1650			list = self.dmesg[phase]['list']
1651			for devname in list:
1652				dev = list[devname]
1653				# pid must match
1654				if dev['pid'] != pid:
1655					continue
1656				devS = dev['start']
1657				devE = dev['end']
1658				if type == 'device':
1659					# device target event is entirely inside the source boundary
1660					if(start < devS or start >= devE or end <= devS or end > devE):
1661						continue
1662				elif type == 'thread':
1663					# thread target event will expand the source boundary
1664					if start < devS:
1665						dev['start'] = start
1666					if end > devE:
1667						dev['end'] = end
1668				tgtdev = dev
1669				break
1670		return tgtdev
1671	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1672		# try to place the call in a device
1673		phases = self.sortedPhases()
1674		tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1675		# calls with device pids that occur outside device bounds are dropped
1676		# TODO: include these somehow
1677		if not tgtdev and pid in self.devpids:
1678			return False
1679		# try to place the call in a thread
1680		if not tgtdev:
1681			tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1682		# create new thread blocks, expand as new calls are found
1683		if not tgtdev:
1684			if proc == '<...>':
1685				threadname = 'kthread-%d' % (pid)
1686			else:
1687				threadname = '%s-%d' % (proc, pid)
1688			tgtphase = self.sourcePhase(start)
1689			if not tgtphase:
1690				return False
1691			self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1692			return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1693		# this should not happen
1694		if not tgtdev:
1695			sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1696				(start, end, proc, pid, kprobename, cdata, rdata))
1697			return False
1698		# place the call data inside the src element of the tgtdev
1699		if('src' not in tgtdev):
1700			tgtdev['src'] = []
1701		dtf = sysvals.dev_tracefuncs
1702		ubiquitous = False
1703		if kprobename in dtf and 'ub' in dtf[kprobename]:
1704			ubiquitous = True
1705		mc = re.match(r'\(.*\) *(?P<args>.*)', cdata)
1706		mr = re.match(r'\((?P<caller>\S*).* arg1=(?P<ret>.*)', rdata)
1707		if mc and mr:
1708			c = mr.group('caller').split('+')[0]
1709			a = mc.group('args').strip()
1710			r = mr.group('ret')
 
1711			if len(r) > 6:
1712				r = ''
1713			else:
1714				r = 'ret=%s ' % r
1715			if ubiquitous and c in dtf and 'ub' in dtf[c]:
1716				return False
1717		else:
1718			return False
1719		color = sysvals.kprobeColor(kprobename)
1720		e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1721		tgtdev['src'].append(e)
1722		return True
1723	def overflowDevices(self):
1724		# get a list of devices that extend beyond the end of this test run
1725		devlist = []
1726		for phase in self.sortedPhases():
1727			list = self.dmesg[phase]['list']
1728			for devname in list:
1729				dev = list[devname]
1730				if dev['end'] > self.end:
1731					devlist.append(dev)
1732		return devlist
1733	def mergeOverlapDevices(self, devlist):
1734		# merge any devices that overlap devlist
1735		for dev in devlist:
1736			devname = dev['name']
1737			for phase in self.sortedPhases():
1738				list = self.dmesg[phase]['list']
1739				if devname not in list:
1740					continue
1741				tdev = list[devname]
1742				o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1743				if o <= 0:
1744					continue
1745				dev['end'] = tdev['end']
1746				if 'src' not in dev or 'src' not in tdev:
1747					continue
1748				dev['src'] += tdev['src']
1749				del list[devname]
1750	def usurpTouchingThread(self, name, dev):
1751		# the caller test has priority of this thread, give it to him
1752		for phase in self.sortedPhases():
1753			list = self.dmesg[phase]['list']
1754			if name in list:
1755				tdev = list[name]
1756				if tdev['start'] - dev['end'] < 0.1:
1757					dev['end'] = tdev['end']
1758					if 'src' not in dev:
1759						dev['src'] = []
1760					if 'src' in tdev:
1761						dev['src'] += tdev['src']
1762					del list[name]
1763				break
1764	def stitchTouchingThreads(self, testlist):
1765		# merge any threads between tests that touch
1766		for phase in self.sortedPhases():
1767			list = self.dmesg[phase]['list']
1768			for devname in list:
1769				dev = list[devname]
1770				if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1771					continue
1772				for data in testlist:
1773					data.usurpTouchingThread(devname, dev)
1774	def optimizeDevSrc(self):
1775		# merge any src call loops to reduce timeline size
1776		for phase in self.sortedPhases():
1777			list = self.dmesg[phase]['list']
1778			for dev in list:
1779				if 'src' not in list[dev]:
1780					continue
1781				src = list[dev]['src']
1782				p = 0
1783				for e in sorted(src, key=lambda event: event.time):
1784					if not p or not e.repeat(p):
1785						p = e
1786						continue
1787					# e is another iteration of p, move it into p
1788					p.end = e.end
1789					p.length = p.end - p.time
1790					p.count += 1
1791					src.remove(e)
1792	def trimTimeVal(self, t, t0, dT, left):
1793		if left:
1794			if(t > t0):
1795				if(t - dT < t0):
1796					return t0
1797				return t - dT
1798			else:
1799				return t
1800		else:
1801			if(t < t0 + dT):
1802				if(t > t0):
1803					return t0 + dT
1804				return t + dT
1805			else:
1806				return t
1807	def trimTime(self, t0, dT, left):
1808		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1809		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1810		self.start = self.trimTimeVal(self.start, t0, dT, left)
1811		self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1812		self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1813		self.end = self.trimTimeVal(self.end, t0, dT, left)
1814		for phase in self.sortedPhases():
1815			p = self.dmesg[phase]
1816			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1817			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1818			list = p['list']
1819			for name in list:
1820				d = list[name]
1821				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1822				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1823				d['length'] = d['end'] - d['start']
1824				if('ftrace' in d):
1825					cg = d['ftrace']
1826					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1827					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1828					for line in cg.list:
1829						line.time = self.trimTimeVal(line.time, t0, dT, left)
1830				if('src' in d):
1831					for e in d['src']:
1832						e.time = self.trimTimeVal(e.time, t0, dT, left)
1833						e.end = self.trimTimeVal(e.end, t0, dT, left)
1834						e.length = e.end - e.time
1835				if('cpuexec' in d):
1836					cpuexec = dict()
1837					for e in d['cpuexec']:
1838						c0, cN = e
1839						c0 = self.trimTimeVal(c0, t0, dT, left)
1840						cN = self.trimTimeVal(cN, t0, dT, left)
1841						cpuexec[(c0, cN)] = d['cpuexec'][e]
1842					d['cpuexec'] = cpuexec
1843		for dir in ['suspend', 'resume']:
1844			list = []
1845			for e in self.errorinfo[dir]:
1846				type, tm, idx1, idx2 = e
1847				tm = self.trimTimeVal(tm, t0, dT, left)
1848				list.append((type, tm, idx1, idx2))
1849			self.errorinfo[dir] = list
1850	def trimFreezeTime(self, tZero):
1851		# trim out any standby or freeze clock time
1852		lp = ''
1853		for phase in self.sortedPhases():
1854			if 'resume_machine' in phase and 'suspend_machine' in lp:
1855				tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1856				tL = tR - tS
1857				if tL <= 0:
1858					continue
1859				left = True if tR > tZero else False
1860				self.trimTime(tS, tL, left)
1861				if 'waking' in self.dmesg[lp]:
1862					tCnt = self.dmesg[lp]['waking'][0]
1863					if self.dmesg[lp]['waking'][1] >= 0.001:
1864						tTry = '%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
1865					else:
1866						tTry = '%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
1867					text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
1868				else:
1869					text = '%.0f' % (tL * 1000)
1870				self.tLow.append(text)
1871			lp = phase
1872	def getMemTime(self):
1873		if not self.hwstart or not self.hwend:
1874			return
1875		stime = (self.tSuspended - self.start) * 1000000
1876		rtime = (self.end - self.tResumed) * 1000000
1877		hws = self.hwstart + timedelta(microseconds=stime)
1878		hwr = self.hwend - timedelta(microseconds=rtime)
1879		self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1880	def getTimeValues(self):
1881		s = (self.tSuspended - self.tKernSus) * 1000
1882		r = (self.tKernRes - self.tResumed) * 1000
1883		return (max(s, 0), max(r, 0))
1884	def setPhase(self, phase, ktime, isbegin, order=-1):
1885		if(isbegin):
1886			# phase start over current phase
1887			if self.currphase:
1888				if 'resume_machine' not in self.currphase:
1889					sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1890				self.dmesg[self.currphase]['end'] = ktime
1891			phases = self.dmesg.keys()
1892			color = self.phasedef[phase]['color']
1893			count = len(phases) if order < 0 else order
1894			# create unique name for every new phase
1895			while phase in phases:
1896				phase += '*'
1897			self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1898				'row': 0, 'color': color, 'order': count}
1899			self.dmesg[phase]['start'] = ktime
1900			self.currphase = phase
1901		else:
1902			# phase end without a start
1903			if phase not in self.currphase:
1904				if self.currphase:
1905					sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1906				else:
1907					sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1908					return phase
1909			phase = self.currphase
1910			self.dmesg[phase]['end'] = ktime
1911			self.currphase = ''
1912		return phase
1913	def sortedDevices(self, phase):
1914		list = self.dmesg[phase]['list']
1915		return sorted(list, key=lambda k:list[k]['start'])
1916	def fixupInitcalls(self, phase):
1917		# if any calls never returned, clip them at system resume end
1918		phaselist = self.dmesg[phase]['list']
1919		for devname in phaselist:
1920			dev = phaselist[devname]
1921			if(dev['end'] < 0):
1922				for p in self.sortedPhases():
1923					if self.dmesg[p]['end'] > dev['start']:
1924						dev['end'] = self.dmesg[p]['end']
1925						break
1926				sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1927	def deviceFilter(self, devicefilter):
1928		for phase in self.sortedPhases():
1929			list = self.dmesg[phase]['list']
1930			rmlist = []
1931			for name in list:
1932				keep = False
1933				for filter in devicefilter:
1934					if filter in name or \
1935						('drv' in list[name] and filter in list[name]['drv']):
1936						keep = True
1937				if not keep:
1938					rmlist.append(name)
1939			for name in rmlist:
1940				del list[name]
1941	def fixupInitcallsThatDidntReturn(self):
1942		# if any calls never returned, clip them at system resume end
1943		for phase in self.sortedPhases():
1944			self.fixupInitcalls(phase)
1945	def phaseOverlap(self, phases):
1946		rmgroups = []
1947		newgroup = []
1948		for group in self.devicegroups:
1949			for phase in phases:
1950				if phase not in group:
1951					continue
1952				for p in group:
1953					if p not in newgroup:
1954						newgroup.append(p)
1955				if group not in rmgroups:
1956					rmgroups.append(group)
1957		for group in rmgroups:
1958			self.devicegroups.remove(group)
1959		self.devicegroups.append(newgroup)
1960	def newActionGlobal(self, name, start, end, pid=-1, color=''):
1961		# which phase is this device callback or action in
1962		phases = self.sortedPhases()
1963		targetphase = 'none'
1964		htmlclass = ''
1965		overlap = 0.0
1966		myphases = []
1967		for phase in phases:
1968			pstart = self.dmesg[phase]['start']
1969			pend = self.dmesg[phase]['end']
1970			# see if the action overlaps this phase
1971			o = max(0, min(end, pend) - max(start, pstart))
1972			if o > 0:
1973				myphases.append(phase)
1974			# set the target phase to the one that overlaps most
1975			if o > overlap:
1976				if overlap > 0 and phase == 'post_resume':
1977					continue
1978				targetphase = phase
1979				overlap = o
1980		# if no target phase was found, pin it to the edge
1981		if targetphase == 'none':
1982			p0start = self.dmesg[phases[0]]['start']
1983			if start <= p0start:
1984				targetphase = phases[0]
1985			else:
1986				targetphase = phases[-1]
1987		if pid == -2:
1988			htmlclass = ' bg'
1989		elif pid == -3:
1990			htmlclass = ' ps'
1991		if len(myphases) > 1:
1992			htmlclass = ' bg'
1993			self.phaseOverlap(myphases)
1994		if targetphase in phases:
1995			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1996			return (targetphase, newname)
1997		return False
1998	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1999		# new device callback for a specific phase
2000		self.html_device_id += 1
2001		devid = '%s%d' % (self.idstr, self.html_device_id)
2002		list = self.dmesg[phase]['list']
2003		length = -1.0
2004		if(start >= 0 and end >= 0):
2005			length = end - start
2006		if pid >= -2:
2007			i = 2
2008			origname = name
2009			while(name in list):
2010				name = '%s[%d]' % (origname, i)
2011				i += 1
2012		list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
2013			'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
2014		if htmlclass:
2015			list[name]['htmlclass'] = htmlclass
2016		if color:
2017			list[name]['color'] = color
2018		return name
2019	def findDevice(self, phase, name):
2020		list = self.dmesg[phase]['list']
2021		mydev = ''
2022		for devname in sorted(list):
2023			if name == devname or re.match(r'^%s\[(?P<num>[0-9]*)\]$' % name, devname):
2024				mydev = devname
2025		if mydev:
2026			return list[mydev]
2027		return False
2028	def deviceChildren(self, devname, phase):
2029		devlist = []
2030		list = self.dmesg[phase]['list']
2031		for child in list:
2032			if(list[child]['par'] == devname):
2033				devlist.append(child)
2034		return devlist
2035	def maxDeviceNameSize(self, phase):
2036		size = 0
2037		for name in self.dmesg[phase]['list']:
2038			if len(name) > size:
2039				size = len(name)
2040		return size
2041	def printDetails(self):
2042		sysvals.vprint('Timeline Details:')
2043		sysvals.vprint('          test start: %f' % self.start)
2044		sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
2045		tS = tR = False
2046		for phase in self.sortedPhases():
2047			devlist = self.dmesg[phase]['list']
2048			dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
2049			if not tS and ps >= self.tSuspended:
2050				sysvals.vprint('   machine suspended: %f' % self.tSuspended)
2051				tS = True
2052			if not tR and ps >= self.tResumed:
2053				sysvals.vprint('     machine resumed: %f' % self.tResumed)
2054				tR = True
2055			sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
2056			if sysvals.devdump:
2057				sysvals.vprint(''.join('-' for i in range(80)))
2058				maxname = '%d' % self.maxDeviceNameSize(phase)
2059				fmt = '%3d) %'+maxname+'s - %f - %f'
2060				c = 1
2061				for name in sorted(devlist):
2062					s = devlist[name]['start']
2063					e = devlist[name]['end']
2064					sysvals.vprint(fmt % (c, name, s, e))
2065					c += 1
2066				sysvals.vprint(''.join('-' for i in range(80)))
2067		sysvals.vprint('   kernel resume end: %f' % self.tKernRes)
2068		sysvals.vprint('            test end: %f' % self.end)
2069	def deviceChildrenAllPhases(self, devname):
2070		devlist = []
2071		for phase in self.sortedPhases():
2072			list = self.deviceChildren(devname, phase)
2073			for dev in sorted(list):
2074				if dev not in devlist:
2075					devlist.append(dev)
2076		return devlist
2077	def masterTopology(self, name, list, depth):
2078		node = DeviceNode(name, depth)
2079		for cname in list:
2080			# avoid recursions
2081			if name == cname:
2082				continue
2083			clist = self.deviceChildrenAllPhases(cname)
2084			cnode = self.masterTopology(cname, clist, depth+1)
2085			node.children.append(cnode)
2086		return node
2087	def printTopology(self, node):
2088		html = ''
2089		if node.name:
2090			info = ''
2091			drv = ''
2092			for phase in self.sortedPhases():
2093				list = self.dmesg[phase]['list']
2094				if node.name in list:
2095					s = list[node.name]['start']
2096					e = list[node.name]['end']
2097					if list[node.name]['drv']:
2098						drv = ' {'+list[node.name]['drv']+'}'
2099					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
2100			html += '<li><b>'+node.name+drv+'</b>'
2101			if info:
2102				html += '<ul>'+info+'</ul>'
2103			html += '</li>'
2104		if len(node.children) > 0:
2105			html += '<ul>'
2106			for cnode in node.children:
2107				html += self.printTopology(cnode)
2108			html += '</ul>'
2109		return html
2110	def rootDeviceList(self):
2111		# list of devices graphed
2112		real = []
2113		for phase in self.sortedPhases():
2114			list = self.dmesg[phase]['list']
2115			for dev in sorted(list):
2116				if list[dev]['pid'] >= 0 and dev not in real:
2117					real.append(dev)
2118		# list of top-most root devices
2119		rootlist = []
2120		for phase in self.sortedPhases():
2121			list = self.dmesg[phase]['list']
2122			for dev in sorted(list):
2123				pdev = list[dev]['par']
2124				pid = list[dev]['pid']
2125				if(pid < 0 or re.match(r'[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
2126					continue
2127				if pdev and pdev not in real and pdev not in rootlist:
2128					rootlist.append(pdev)
2129		return rootlist
2130	def deviceTopology(self):
2131		rootlist = self.rootDeviceList()
2132		master = self.masterTopology('', rootlist, 0)
2133		return self.printTopology(master)
2134	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
2135		# only select devices that will actually show up in html
2136		self.tdevlist = dict()
2137		for phase in self.dmesg:
2138			devlist = []
2139			list = self.dmesg[phase]['list']
2140			for dev in list:
2141				length = (list[dev]['end'] - list[dev]['start']) * 1000
2142				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
2143				if length >= mindevlen:
2144					devlist.append(dev)
2145			self.tdevlist[phase] = devlist
2146	def addHorizontalDivider(self, devname, devend):
2147		phase = 'suspend_prepare'
2148		self.newAction(phase, devname, -2, '', \
2149			self.start, devend, '', ' sec', '')
2150		if phase not in self.tdevlist:
2151			self.tdevlist[phase] = []
2152		self.tdevlist[phase].append(devname)
2153		d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
2154		return d
2155	def addProcessUsageEvent(self, name, times):
2156		# get the start and end times for this process
2157		cpuexec = dict()
2158		tlast = start = end = -1
 
 
2159		for t in sorted(times):
2160			if tlast < 0:
2161				tlast = t
2162				continue
2163			if name in self.pstl[t] and self.pstl[t][name] > 0:
2164				if start < 0:
2165					start = tlast
2166				end, key = t, (tlast, t)
2167				maxj = (t - tlast) * 1024.0
2168				cpuexec[key] = min(1.0, float(self.pstl[t][name]) / maxj)
2169			tlast = t
2170		if start < 0 or end < 0:
2171			return
2172		# add a new action for this process and get the object
2173		out = self.newActionGlobal(name, start, end, -3)
2174		if out:
2175			phase, devname = out
2176			dev = self.dmesg[phase]['list'][devname]
2177			dev['cpuexec'] = cpuexec
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2178	def createProcessUsageEvents(self):
2179		# get an array of process names and times
2180		proclist = {'sus': dict(), 'res': dict()}
2181		tdata = {'sus': [], 'res': []}
 
 
 
 
 
 
 
2182		for t in sorted(self.pstl):
2183			dir = 'sus' if t < self.tSuspended else 'res'
2184			for ps in sorted(self.pstl[t]):
2185				if ps not in proclist[dir]:
2186					proclist[dir][ps] = 0
2187			tdata[dir].append(t)
2188		# process the events for suspend and resume
2189		if len(proclist['sus']) > 0 or len(proclist['res']) > 0:
2190			sysvals.vprint('Process Execution:')
2191		for dir in ['sus', 'res']:
2192			for ps in sorted(proclist[dir]):
2193				self.addProcessUsageEvent(ps, tdata[dir])
 
 
 
 
2194	def handleEndMarker(self, time, msg=''):
2195		dm = self.dmesg
2196		self.setEnd(time, msg)
2197		self.initDevicegroups()
2198		# give suspend_prepare an end if needed
2199		if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2200			dm['suspend_prepare']['end'] = time
2201		# assume resume machine ends at next phase start
2202		if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2203			np = self.nextPhase('resume_machine', 1)
2204			if np:
2205				dm['resume_machine']['end'] = dm[np]['start']
2206		# if kernel resume end not found, assume its the end marker
2207		if self.tKernRes == 0.0:
2208			self.tKernRes = time
2209		# if kernel suspend start not found, assume its the end marker
2210		if self.tKernSus == 0.0:
2211			self.tKernSus = time
2212		# set resume complete to end at end marker
2213		if 'resume_complete' in dm:
2214			dm['resume_complete']['end'] = time
2215	def initcall_debug_call(self, line, quick=False):
2216		m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2217			r'PM: *calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2218		if not m:
2219			m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2220				r'calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2221		if not m:
2222			m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
2223				r'(?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)', line)
2224		if m:
2225			return True if quick else m.group('t', 'f', 'n', 'p')
2226		return False if quick else ('', '', '', '')
2227	def initcall_debug_return(self, line, quick=False):
2228		m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: PM: '+\
2229			r'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2230		if not m:
2231			m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2232				r'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2233		if not m:
2234			m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
2235				r'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', line)
2236		if m:
2237			return True if quick else m.group('t', 'f', 'dt')
2238		return False if quick else ('', '', '')
2239	def debugPrint(self):
2240		for p in self.sortedPhases():
2241			list = self.dmesg[p]['list']
2242			for devname in sorted(list):
2243				dev = list[devname]
2244				if 'ftrace' in dev:
2245					dev['ftrace'].debugPrint(' [%s]' % devname)
2246
2247# Class: DevFunction
2248# Description:
2249#	 A container for kprobe function data we want in the dev timeline
2250class DevFunction:
2251	def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2252		self.row = 0
2253		self.count = 1
2254		self.name = name
2255		self.args = args
2256		self.caller = caller
2257		self.ret = ret
2258		self.time = start
2259		self.length = end - start
2260		self.end = end
2261		self.ubiquitous = u
2262		self.proc = proc
2263		self.pid = pid
2264		self.color = color
2265	def title(self):
2266		cnt = ''
2267		if self.count > 1:
2268			cnt = '(x%d)' % self.count
2269		l = '%0.3fms' % (self.length * 1000)
2270		if self.ubiquitous:
2271			title = '%s(%s)%s <- %s, %s(%s)' % \
2272				(self.name, self.args, cnt, self.caller, self.ret, l)
2273		else:
2274			title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2275		return title.replace('"', '')
2276	def text(self):
2277		if self.count > 1:
2278			text = '%s(x%d)' % (self.name, self.count)
2279		else:
2280			text = self.name
2281		return text
2282	def repeat(self, tgt):
2283		# is the tgt call just a repeat of this call (e.g. are we in a loop)
2284		dt = self.time - tgt.end
2285		# only combine calls if -all- attributes are identical
2286		if tgt.caller == self.caller and \
2287			tgt.name == self.name and tgt.args == self.args and \
2288			tgt.proc == self.proc and tgt.pid == self.pid and \
2289			tgt.ret == self.ret and dt >= 0 and \
2290			dt <= sysvals.callloopmaxgap and \
2291			self.length < sysvals.callloopmaxlen:
2292			return True
2293		return False
2294
2295# Class: FTraceLine
2296# Description:
2297#	 A container for a single line of ftrace data. There are six basic types:
2298#		 callgraph line:
2299#			  call: "  dpm_run_callback() {"
2300#			return: "  }"
2301#			  leaf: " dpm_run_callback();"
2302#		 trace event:
2303#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
2304#			 suspend_resume: phase or custom exec block data
2305#			 device_pm_callback: device callback info
2306class FTraceLine:
2307	def __init__(self, t, m='', d=''):
2308		self.length = 0.0
2309		self.fcall = False
2310		self.freturn = False
2311		self.fevent = False
2312		self.fkprobe = False
2313		self.depth = 0
2314		self.name = ''
2315		self.type = ''
2316		self.time = float(t)
2317		if not m and not d:
2318			return
2319		# is this a trace event
2320		if(d == 'traceevent' or re.match(r'^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2321			if(d == 'traceevent'):
2322				# nop format trace event
2323				msg = m
2324			else:
2325				# function_graph format trace event
2326				em = re.match(r'^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2327				msg = em.group('msg')
2328
2329			emm = re.match(r'^(?P<call>.*?): (?P<msg>.*)', msg)
2330			if(emm):
2331				self.name = emm.group('msg')
2332				self.type = emm.group('call')
2333			else:
2334				self.name = msg
2335			km = re.match(r'^(?P<n>.*)_cal$', self.type)
2336			if km:
2337				self.fcall = True
2338				self.fkprobe = True
2339				self.type = km.group('n')
2340				return
2341			km = re.match(r'^(?P<n>.*)_ret$', self.type)
2342			if km:
2343				self.freturn = True
2344				self.fkprobe = True
2345				self.type = km.group('n')
2346				return
2347			self.fevent = True
2348			return
2349		# convert the duration to seconds
2350		if(d):
2351			self.length = float(d)/1000000
2352		# the indentation determines the depth
2353		match = re.match(r'^(?P<d> *)(?P<o>.*)$', m)
2354		if(not match):
2355			return
2356		self.depth = self.getDepth(match.group('d'))
2357		m = match.group('o')
2358		# function return
2359		if(m[0] == '}'):
2360			self.freturn = True
2361			if(len(m) > 1):
2362				# includes comment with function name
2363				match = re.match(r'^} *\/\* *(?P<n>.*) *\*\/$', m)
2364				if(match):
2365					self.name = match.group('n').strip()
2366		# function call
2367		else:
2368			self.fcall = True
2369			# function call with children
2370			if(m[-1] == '{'):
2371				match = re.match(r'^(?P<n>.*) *\(.*', m)
2372				if(match):
2373					self.name = match.group('n').strip()
2374			# function call with no children (leaf)
2375			elif(m[-1] == ';'):
2376				self.freturn = True
2377				match = re.match(r'^(?P<n>.*) *\(.*', m)
2378				if(match):
2379					self.name = match.group('n').strip()
2380			# something else (possibly a trace marker)
2381			else:
2382				self.name = m
2383	def isCall(self):
2384		return self.fcall and not self.freturn
2385	def isReturn(self):
2386		return self.freturn and not self.fcall
2387	def isLeaf(self):
2388		return self.fcall and self.freturn
2389	def getDepth(self, str):
2390		return len(str)/2
2391	def debugPrint(self, info=''):
2392		if self.isLeaf():
2393			pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2394				self.depth, self.name, self.length*1000000, info))
2395		elif self.freturn:
2396			pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2397				self.depth, self.name, self.length*1000000, info))
2398		else:
2399			pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2400				self.depth, self.name, self.length*1000000, info))
2401	def startMarker(self):
2402		# Is this the starting line of a suspend?
2403		if not self.fevent:
2404			return False
2405		if sysvals.usetracemarkers:
2406			if(self.name.startswith('SUSPEND START')):
2407				return True
2408			return False
2409		else:
2410			if(self.type == 'suspend_resume' and
2411				re.match(r'suspend_enter\[.*\] begin', self.name)):
2412				return True
2413			return False
2414	def endMarker(self):
2415		# Is this the ending line of a resume?
2416		if not self.fevent:
2417			return False
2418		if sysvals.usetracemarkers:
2419			if(self.name.startswith('RESUME COMPLETE')):
2420				return True
2421			return False
2422		else:
2423			if(self.type == 'suspend_resume' and
2424				re.match(r'thaw_processes\[.*\] end', self.name)):
2425				return True
2426			return False
2427
2428# Class: FTraceCallGraph
2429# Description:
2430#	 A container for the ftrace callgraph of a single recursive function.
2431#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2432#	 Each instance is tied to a single device in a single phase, and is
2433#	 comprised of an ordered list of FTraceLine objects
2434class FTraceCallGraph:
2435	vfname = 'missing_function_name'
2436	def __init__(self, pid, sv):
2437		self.id = ''
2438		self.invalid = False
2439		self.name = ''
2440		self.partial = False
2441		self.ignore = False
2442		self.start = -1.0
2443		self.end = -1.0
2444		self.list = []
2445		self.depth = 0
2446		self.pid = pid
2447		self.sv = sv
2448	def addLine(self, line):
2449		# if this is already invalid, just leave
2450		if(self.invalid):
2451			if(line.depth == 0 and line.freturn):
2452				return 1
2453			return 0
2454		# invalidate on bad depth
2455		if(self.depth < 0):
2456			self.invalidate(line)
2457			return 0
2458		# ignore data til we return to the current depth
2459		if self.ignore:
2460			if line.depth > self.depth:
2461				return 0
2462			else:
2463				self.list[-1].freturn = True
2464				self.list[-1].length = line.time - self.list[-1].time
2465				self.ignore = False
2466				# if this is a return at self.depth, no more work is needed
2467				if line.depth == self.depth and line.isReturn():
2468					if line.depth == 0:
2469						self.end = line.time
2470						return 1
2471					return 0
2472		# compare current depth with this lines pre-call depth
2473		prelinedep = line.depth
2474		if line.isReturn():
2475			prelinedep += 1
2476		last = 0
2477		lasttime = line.time
2478		if len(self.list) > 0:
2479			last = self.list[-1]
2480			lasttime = last.time
2481			if last.isLeaf():
2482				lasttime += last.length
2483		# handle low misalignments by inserting returns
2484		mismatch = prelinedep - self.depth
2485		warning = self.sv.verbose and abs(mismatch) > 1
2486		info = []
2487		if mismatch < 0:
2488			idx = 0
2489			# add return calls to get the depth down
2490			while prelinedep < self.depth:
2491				self.depth -= 1
2492				if idx == 0 and last and last.isCall():
2493					# special case, turn last call into a leaf
2494					last.depth = self.depth
2495					last.freturn = True
2496					last.length = line.time - last.time
2497					if warning:
2498						info.append(('[make leaf]', last))
2499				else:
2500					vline = FTraceLine(lasttime)
2501					vline.depth = self.depth
2502					vline.name = self.vfname
2503					vline.freturn = True
2504					self.list.append(vline)
2505					if warning:
2506						if idx == 0:
2507							info.append(('', last))
2508						info.append(('[add return]', vline))
2509				idx += 1
2510			if warning:
2511				info.append(('', line))
2512		# handle high misalignments by inserting calls
2513		elif mismatch > 0:
2514			idx = 0
2515			if warning:
2516				info.append(('', last))
2517			# add calls to get the depth up
2518			while prelinedep > self.depth:
2519				if idx == 0 and line.isReturn():
2520					# special case, turn this return into a leaf
2521					line.fcall = True
2522					prelinedep -= 1
2523					if warning:
2524						info.append(('[make leaf]', line))
2525				else:
2526					vline = FTraceLine(lasttime)
2527					vline.depth = self.depth
2528					vline.name = self.vfname
2529					vline.fcall = True
2530					self.list.append(vline)
2531					self.depth += 1
2532					if not last:
2533						self.start = vline.time
2534					if warning:
2535						info.append(('[add call]', vline))
2536				idx += 1
2537			if warning and ('[make leaf]', line) not in info:
2538				info.append(('', line))
2539		if warning:
2540			pprint('WARNING: ftrace data missing, corrections made:')
2541			for i in info:
2542				t, obj = i
2543				if obj:
2544					obj.debugPrint(t)
2545		# process the call and set the new depth
2546		skipadd = False
2547		md = self.sv.max_graph_depth
2548		if line.isCall():
2549			# ignore blacklisted/overdepth funcs
2550			if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2551				self.ignore = True
2552			else:
2553				self.depth += 1
2554		elif line.isReturn():
2555			self.depth -= 1
2556			# remove blacklisted/overdepth/empty funcs that slipped through
2557			if (last and last.isCall() and last.depth == line.depth) or \
2558				(md and last and last.depth >= md) or \
2559				(line.name in self.sv.cgblacklist):
2560				while len(self.list) > 0 and self.list[-1].depth > line.depth:
2561					self.list.pop(-1)
2562				if len(self.list) == 0:
2563					self.invalid = True
2564					return 1
2565				self.list[-1].freturn = True
2566				self.list[-1].length = line.time - self.list[-1].time
2567				self.list[-1].name = line.name
2568				skipadd = True
2569		if len(self.list) < 1:
2570			self.start = line.time
2571		# check for a mismatch that returned all the way to callgraph end
2572		res = 1
2573		if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2574			line = self.list[-1]
2575			skipadd = True
2576			res = -1
2577		if not skipadd:
2578			self.list.append(line)
2579		if(line.depth == 0 and line.freturn):
2580			if(self.start < 0):
2581				self.start = line.time
2582			self.end = line.time
2583			if line.fcall:
2584				self.end += line.length
2585			if self.list[0].name == self.vfname:
2586				self.invalid = True
2587			if res == -1:
2588				self.partial = True
2589			return res
2590		return 0
2591	def invalidate(self, line):
2592		if(len(self.list) > 0):
2593			first = self.list[0]
2594			self.list = []
2595			self.list.append(first)
2596		self.invalid = True
2597		id = 'task %s' % (self.pid)
2598		window = '(%f - %f)' % (self.start, line.time)
2599		if(self.depth < 0):
2600			pprint('Data misalignment for '+id+\
2601				' (buffer overflow), ignoring this callback')
2602		else:
2603			pprint('Too much data for '+id+\
2604				' '+window+', ignoring this callback')
2605	def slice(self, dev):
2606		minicg = FTraceCallGraph(dev['pid'], self.sv)
2607		minicg.name = self.name
2608		mydepth = -1
2609		good = False
2610		for l in self.list:
2611			if(l.time < dev['start'] or l.time > dev['end']):
2612				continue
2613			if mydepth < 0:
2614				if l.name == 'mutex_lock' and l.freturn:
2615					mydepth = l.depth
2616				continue
2617			elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2618				good = True
2619				break
2620			l.depth -= mydepth
2621			minicg.addLine(l)
2622		if not good or len(minicg.list) < 1:
2623			return 0
2624		return minicg
2625	def repair(self, enddepth):
2626		# bring the depth back to 0 with additional returns
2627		fixed = False
2628		last = self.list[-1]
2629		for i in reversed(range(enddepth)):
2630			t = FTraceLine(last.time)
2631			t.depth = i
2632			t.freturn = True
2633			fixed = self.addLine(t)
2634			if fixed != 0:
2635				self.end = last.time
2636				return True
2637		return False
2638	def postProcess(self):
2639		if len(self.list) > 0:
2640			self.name = self.list[0].name
2641		stack = dict()
2642		cnt = 0
2643		last = 0
2644		for l in self.list:
2645			# ftrace bug: reported duration is not reliable
2646			# check each leaf and clip it at max possible length
2647			if last and last.isLeaf():
2648				if last.length > l.time - last.time:
2649					last.length = l.time - last.time
2650			if l.isCall():
2651				stack[l.depth] = l
2652				cnt += 1
2653			elif l.isReturn():
2654				if(l.depth not in stack):
2655					if self.sv.verbose:
2656						pprint('Post Process Error: Depth missing')
2657						l.debugPrint()
2658					return False
2659				# calculate call length from call/return lines
2660				cl = stack[l.depth]
2661				cl.length = l.time - cl.time
2662				if cl.name == self.vfname:
2663					cl.name = l.name
2664				stack.pop(l.depth)
2665				l.length = 0
2666				cnt -= 1
2667			last = l
2668		if(cnt == 0):
2669			# trace caught the whole call tree
2670			return True
2671		elif(cnt < 0):
2672			if self.sv.verbose:
2673				pprint('Post Process Error: Depth is less than 0')
2674			return False
2675		# trace ended before call tree finished
2676		return self.repair(cnt)
2677	def deviceMatch(self, pid, data):
2678		found = ''
2679		# add the callgraph data to the device hierarchy
2680		borderphase = {
2681			'dpm_prepare': 'suspend_prepare',
2682			'dpm_complete': 'resume_complete'
2683		}
2684		if(self.name in borderphase):
2685			p = borderphase[self.name]
2686			list = data.dmesg[p]['list']
2687			for devname in list:
2688				dev = list[devname]
2689				if(pid == dev['pid'] and
2690					self.start <= dev['start'] and
2691					self.end >= dev['end']):
2692					cg = self.slice(dev)
2693					if cg:
2694						dev['ftrace'] = cg
2695					found = devname
2696			return found
2697		for p in data.sortedPhases():
2698			if(data.dmesg[p]['start'] <= self.start and
2699				self.start <= data.dmesg[p]['end']):
2700				list = data.dmesg[p]['list']
2701				for devname in sorted(list, key=lambda k:list[k]['start']):
2702					dev = list[devname]
2703					if(pid == dev['pid'] and
2704						self.start <= dev['start'] and
2705						self.end >= dev['end']):
2706						dev['ftrace'] = self
2707						found = devname
2708						break
2709				break
2710		return found
2711	def newActionFromFunction(self, data):
2712		name = self.name
2713		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2714			return
2715		fs = self.start
2716		fe = self.end
2717		if fs < data.start or fe > data.end:
2718			return
2719		phase = ''
2720		for p in data.sortedPhases():
2721			if(data.dmesg[p]['start'] <= self.start and
2722				self.start < data.dmesg[p]['end']):
2723				phase = p
2724				break
2725		if not phase:
2726			return
2727		out = data.newActionGlobal(name, fs, fe, -2)
2728		if out:
2729			phase, myname = out
2730			data.dmesg[phase]['list'][myname]['ftrace'] = self
2731	def debugPrint(self, info=''):
2732		pprint('%s pid=%d [%f - %f] %.3f us' % \
2733			(self.name, self.pid, self.start, self.end,
2734			(self.end - self.start)*1000000))
2735		for l in self.list:
2736			if l.isLeaf():
2737				pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2738					l.depth, l.name, l.length*1000000, info))
2739			elif l.freturn:
2740				pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2741					l.depth, l.name, l.length*1000000, info))
2742			else:
2743				pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2744					l.depth, l.name, l.length*1000000, info))
2745		pprint(' ')
2746
2747class DevItem:
2748	def __init__(self, test, phase, dev):
2749		self.test = test
2750		self.phase = phase
2751		self.dev = dev
2752	def isa(self, cls):
2753		if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2754			return True
2755		return False
2756
2757# Class: Timeline
2758# Description:
2759#	 A container for a device timeline which calculates
2760#	 all the html properties to display it correctly
2761class Timeline:
2762	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2763	html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2764	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2765	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2766	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2767	def __init__(self, rowheight, scaleheight):
2768		self.html = ''
2769		self.height = 0  # total timeline height
2770		self.scaleH = scaleheight # timescale (top) row height
2771		self.rowH = rowheight     # device row height
2772		self.bodyH = 0   # body height
2773		self.rows = 0    # total timeline rows
2774		self.rowlines = dict()
2775		self.rowheight = dict()
2776	def createHeader(self, sv, stamp):
2777		if(not stamp['time']):
2778			return
2779		self.html += '<div class="version"><a href="https://www.intel.com/content/www/'+\
2780			'us/en/developer/topic-technology/open/pm-graph/overview.html">%s v%s</a></div>' \
2781			% (sv.title, sv.version)
2782		if sv.logmsg and sv.testlog:
2783			self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2784		if sv.dmesglog:
2785			self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2786		if sv.ftracelog:
2787			self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2788		headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2789		self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2790			stamp['mode'], stamp['time'])
2791		if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2792			stamp['man'] and stamp['plat'] and stamp['cpu']:
2793			headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2794			self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2795
2796	# Function: getDeviceRows
2797	# Description:
2798	#    determine how may rows the device funcs will take
2799	# Arguments:
2800	#	 rawlist: the list of devices/actions for a single phase
2801	# Output:
2802	#	 The total number of rows needed to display this phase of the timeline
2803	def getDeviceRows(self, rawlist):
2804		# clear all rows and set them to undefined
2805		sortdict = dict()
2806		for item in rawlist:
2807			item.row = -1
2808			sortdict[item] = item.length
2809		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2810		remaining = len(sortlist)
2811		rowdata = dict()
2812		row = 1
2813		# try to pack each row with as many ranges as possible
2814		while(remaining > 0):
2815			if(row not in rowdata):
2816				rowdata[row] = []
2817			for i in sortlist:
2818				if(i.row >= 0):
2819					continue
2820				s = i.time
2821				e = i.time + i.length
2822				valid = True
2823				for ritem in rowdata[row]:
2824					rs = ritem.time
2825					re = ritem.time + ritem.length
2826					if(not (((s <= rs) and (e <= rs)) or
2827						((s >= re) and (e >= re)))):
2828						valid = False
2829						break
2830				if(valid):
2831					rowdata[row].append(i)
2832					i.row = row
2833					remaining -= 1
2834			row += 1
2835		return row
2836	# Function: getPhaseRows
2837	# Description:
2838	#	 Organize the timeline entries into the smallest
2839	#	 number of rows possible, with no entry overlapping
2840	# Arguments:
2841	#	 devlist: the list of devices/actions in a group of contiguous phases
2842	# Output:
2843	#	 The total number of rows needed to display this phase of the timeline
2844	def getPhaseRows(self, devlist, row=0, sortby='length'):
2845		# clear all rows and set them to undefined
2846		remaining = len(devlist)
2847		rowdata = dict()
2848		sortdict = dict()
2849		myphases = []
2850		# initialize all device rows to -1 and calculate devrows
2851		for item in devlist:
2852			dev = item.dev
2853			tp = (item.test, item.phase)
2854			if tp not in myphases:
2855				myphases.append(tp)
2856			dev['row'] = -1
2857			if sortby == 'start':
2858				# sort by start 1st, then length 2nd
2859				sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2860			else:
2861				# sort by length 1st, then name 2nd
2862				sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2863			if 'src' in dev:
2864				dev['devrows'] = self.getDeviceRows(dev['src'])
2865		# sort the devlist by length so that large items graph on top
2866		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2867		orderedlist = []
2868		for item in sortlist:
2869			if item.dev['pid'] == -2:
2870				orderedlist.append(item)
2871		for item in sortlist:
2872			if item not in orderedlist:
2873				orderedlist.append(item)
2874		# try to pack each row with as many devices as possible
2875		while(remaining > 0):
2876			rowheight = 1
2877			if(row not in rowdata):
2878				rowdata[row] = []
2879			for item in orderedlist:
2880				dev = item.dev
2881				if(dev['row'] < 0):
2882					s = dev['start']
2883					e = dev['end']
2884					valid = True
2885					for ritem in rowdata[row]:
2886						rs = ritem.dev['start']
2887						re = ritem.dev['end']
2888						if(not (((s <= rs) and (e <= rs)) or
2889							((s >= re) and (e >= re)))):
2890							valid = False
2891							break
2892					if(valid):
2893						rowdata[row].append(item)
2894						dev['row'] = row
2895						remaining -= 1
2896						if 'devrows' in dev and dev['devrows'] > rowheight:
2897							rowheight = dev['devrows']
2898			for t, p in myphases:
2899				if t not in self.rowlines or t not in self.rowheight:
2900					self.rowlines[t] = dict()
2901					self.rowheight[t] = dict()
2902				if p not in self.rowlines[t] or p not in self.rowheight[t]:
2903					self.rowlines[t][p] = dict()
2904					self.rowheight[t][p] = dict()
2905				rh = self.rowH
2906				# section headers should use a different row height
2907				if len(rowdata[row]) == 1 and \
2908					'htmlclass' in rowdata[row][0].dev and \
2909					'sec' in rowdata[row][0].dev['htmlclass']:
2910					rh = 15
2911				self.rowlines[t][p][row] = rowheight
2912				self.rowheight[t][p][row] = rowheight * rh
2913			row += 1
2914		if(row > self.rows):
2915			self.rows = int(row)
2916		return row
2917	def phaseRowHeight(self, test, phase, row):
2918		return self.rowheight[test][phase][row]
2919	def phaseRowTop(self, test, phase, row):
2920		top = 0
2921		for i in sorted(self.rowheight[test][phase]):
2922			if i >= row:
2923				break
2924			top += self.rowheight[test][phase][i]
2925		return top
2926	def calcTotalRows(self):
2927		# Calculate the heights and offsets for the header and rows
2928		maxrows = 0
2929		standardphases = []
2930		for t in self.rowlines:
2931			for p in self.rowlines[t]:
2932				total = 0
2933				for i in sorted(self.rowlines[t][p]):
2934					total += self.rowlines[t][p][i]
2935				if total > maxrows:
2936					maxrows = total
2937				if total == len(self.rowlines[t][p]):
2938					standardphases.append((t, p))
2939		self.height = self.scaleH + (maxrows*self.rowH)
2940		self.bodyH = self.height - self.scaleH
2941		# if there is 1 line per row, draw them the standard way
2942		for t, p in standardphases:
2943			for i in sorted(self.rowheight[t][p]):
2944				self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2945	def createZoomBox(self, mode='command', testcount=1):
2946		# Create bounding box, add buttons
2947		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2948		html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2949		html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2950		html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2951		if mode != 'command':
2952			if testcount > 1:
2953				self.html += html_devlist2
2954				self.html += html_devlist1.format('1')
2955			else:
2956				self.html += html_devlist1.format('')
2957		self.html += html_zoombox
2958		self.html += html_timeline.format('dmesg', self.height)
2959	# Function: createTimeScale
2960	# Description:
2961	#	 Create the timescale for a timeline block
2962	# Arguments:
2963	#	 m0: start time (mode begin)
2964	#	 mMax: end time (mode end)
2965	#	 tTotal: total timeline time
2966	#	 mode: suspend or resume
2967	# Output:
2968	#	 The html code needed to display the time scale
2969	def createTimeScale(self, m0, mMax, tTotal, mode):
2970		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2971		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2972		output = '<div class="timescale">\n'
2973		# set scale for timeline
2974		mTotal = mMax - m0
2975		tS = 0.1
2976		if(tTotal <= 0):
2977			return output+'</div>\n'
2978		if(tTotal > 4):
2979			tS = 1
2980		divTotal = int(mTotal/tS) + 1
2981		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2982		for i in range(divTotal):
2983			htmlline = ''
2984			if(mode == 'suspend'):
2985				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2986				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2987				if(i == divTotal - 1):
2988					val = mode
2989				htmlline = timescale.format(pos, val)
2990			else:
2991				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2992				val = '%0.fms' % (float(i)*tS*1000)
2993				htmlline = timescale.format(pos, val)
2994				if(i == 0):
2995					htmlline = rline.format(mode)
2996			output += htmlline
2997		self.html += output+'</div>\n'
2998
2999# Class: TestProps
3000# Description:
3001#	 A list of values describing the properties of these test runs
3002class TestProps:
3003	stampfmt = r'# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
3004				r'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
3005				r' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
3006	wififmt    = r'^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
3007	tstatfmt   = r'^# turbostat (?P<t>\S*)'
3008	testerrfmt = r'^# enter_sleep_error (?P<e>.*)'
3009	sysinfofmt = r'^# sysinfo .*'
3010	cmdlinefmt = r'^# command \| (?P<cmd>.*)'
3011	kparamsfmt = r'^# kparams \| (?P<kp>.*)'
3012	devpropfmt = r'# Device Properties: .*'
3013	pinfofmt   = r'# platform-(?P<val>[a-z,A-Z,0-9,_]*): (?P<info>.*)'
3014	tracertypefmt = r'# tracer: (?P<t>.*)'
3015	firmwarefmt = r'# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
3016	procexecfmt = r'ps - (?P<ps>.*)$'
3017	procmultifmt = r'@(?P<n>[0-9]*)\|(?P<ps>.*)$'
3018	ftrace_line_fmt_fg = \
3019		r'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
3020		r' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
3021		r'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
3022	ftrace_line_fmt_nop = \
3023		r' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
3024		r'(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
3025		r'(?P<msg>.*)'
3026	machinesuspend = r'machine_suspend\[.*'
3027	multiproclist = dict()
3028	multiproctime = 0.0
3029	multiproccnt = 0
3030	def __init__(self):
3031		self.stamp = ''
3032		self.sysinfo = ''
3033		self.cmdline = ''
3034		self.testerror = []
3035		self.turbostat = []
3036		self.wifi = []
3037		self.fwdata = []
3038		self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3039		self.cgformat = False
3040		self.data = 0
3041		self.ktemp = dict()
3042	def setTracerType(self, tracer):
3043		if(tracer == 'function_graph'):
3044			self.cgformat = True
3045			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
3046		elif(tracer == 'nop'):
3047			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3048		else:
3049			doError('Invalid tracer format: [%s]' % tracer)
3050	def stampInfo(self, line, sv):
3051		if re.match(self.stampfmt, line):
3052			self.stamp = line
3053			return True
3054		elif re.match(self.sysinfofmt, line):
3055			self.sysinfo = line
3056			return True
3057		elif re.match(self.tstatfmt, line):
3058			self.turbostat.append(line)
3059			return True
3060		elif re.match(self.wififmt, line):
3061			self.wifi.append(line)
3062			return True
3063		elif re.match(self.testerrfmt, line):
3064			self.testerror.append(line)
3065			return True
3066		elif re.match(self.firmwarefmt, line):
3067			self.fwdata.append(line)
3068			return True
3069		elif(re.match(self.devpropfmt, line)):
3070			self.parseDevprops(line, sv)
3071			return True
3072		elif(re.match(self.pinfofmt, line)):
3073			self.parsePlatformInfo(line, sv)
3074			return True
3075		m = re.match(self.cmdlinefmt, line)
3076		if m:
3077			self.cmdline = m.group('cmd')
3078			return True
3079		m = re.match(self.tracertypefmt, line)
3080		if(m):
3081			self.setTracerType(m.group('t'))
3082			return True
3083		return False
3084	def parseStamp(self, data, sv):
3085		# global test data
3086		m = re.match(self.stampfmt, self.stamp)
3087		if not self.stamp or not m:
3088			doError('data does not include the expected stamp')
3089		data.stamp = {'time': '', 'host': '', 'mode': ''}
3090		dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
3091			int(m.group('d')), int(m.group('H')), int(m.group('M')),
3092			int(m.group('S')))
3093		data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
3094		data.stamp['host'] = m.group('host')
3095		data.stamp['mode'] = m.group('mode')
3096		data.stamp['kernel'] = m.group('kernel')
3097		if re.match(self.sysinfofmt, self.sysinfo):
3098			for f in self.sysinfo.split('|'):
3099				if '#' in f:
3100					continue
3101				tmp = f.strip().split(':', 1)
3102				key = tmp[0]
3103				val = tmp[1]
3104				data.stamp[key] = val
3105		sv.hostname = data.stamp['host']
3106		sv.suspendmode = data.stamp['mode']
3107		if sv.suspendmode == 'freeze':
3108			self.machinesuspend = r'timekeeping_freeze\[.*'
3109		else:
3110			self.machinesuspend = r'machine_suspend\[.*'
3111		if sv.suspendmode == 'command' and sv.ftracefile != '':
3112			modes = ['on', 'freeze', 'standby', 'mem', 'disk']
3113			fp = sv.openlog(sv.ftracefile, 'r')
3114			for line in fp:
3115				m = re.match(r'.* machine_suspend\[(?P<mode>.*)\]', line)
3116				if m and m.group('mode') in ['1', '2', '3', '4']:
3117					sv.suspendmode = modes[int(m.group('mode'))]
3118					data.stamp['mode'] = sv.suspendmode
3119					break
3120			fp.close()
3121		sv.cmdline = self.cmdline
3122		if not sv.stamp:
3123			sv.stamp = data.stamp
3124		# firmware data
3125		if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
3126			m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
3127			if m:
3128				data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
3129				if(data.fwSuspend > 0 or data.fwResume > 0):
3130					data.fwValid = True
3131		# turbostat data
3132		if len(self.turbostat) > data.testnumber:
3133			m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
3134			if m:
3135				data.turbostat = m.group('t')
3136		# wifi data
3137		if len(self.wifi) > data.testnumber:
3138			m = re.match(self.wififmt, self.wifi[data.testnumber])
3139			if m:
3140				data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
3141					'time': float(m.group('t'))}
3142				data.stamp['wifi'] = m.group('d')
3143		# sleep mode enter errors
3144		if len(self.testerror) > data.testnumber:
3145			m = re.match(self.testerrfmt, self.testerror[data.testnumber])
3146			if m:
3147				data.enterfail = m.group('e')
3148	def devprops(self, data):
3149		props = dict()
3150		devlist = data.split(';')
3151		for dev in devlist:
3152			f = dev.split(',')
3153			if len(f) < 3:
3154				continue
3155			dev = f[0]
3156			props[dev] = DevProps()
3157			props[dev].altname = f[1]
3158			if int(f[2]):
3159				props[dev].isasync = True
3160			else:
3161				props[dev].isasync = False
3162		return props
3163	def parseDevprops(self, line, sv):
3164		idx = line.index(': ') + 2
3165		if idx >= len(line):
3166			return
3167		props = self.devprops(line[idx:])
3168		if sv.suspendmode == 'command' and 'testcommandstring' in props:
3169			sv.testcommand = props['testcommandstring'].altname
3170		sv.devprops = props
3171	def parsePlatformInfo(self, line, sv):
3172		m = re.match(self.pinfofmt, line)
3173		if not m:
3174			return
3175		name, info = m.group('val'), m.group('info')
3176		if name == 'devinfo':
3177			sv.devprops = self.devprops(sv.b64unzip(info))
3178			return
3179		elif name == 'testcmd':
3180			sv.testcommand = info
3181			return
3182		field = info.split('|')
3183		if len(field) < 2:
3184			return
3185		cmdline = field[0].strip()
3186		output = sv.b64unzip(field[1].strip())
3187		sv.platinfo.append([name, cmdline, output])
3188
3189# Class: TestRun
3190# Description:
3191#	 A container for a suspend/resume test run. This is necessary as
3192#	 there could be more than one, and they need to be separate.
3193class TestRun:
3194	def __init__(self, dataobj):
3195		self.data = dataobj
3196		self.ftemp = dict()
3197		self.ttemp = dict()
3198
3199class ProcessMonitor:
3200	maxchars = 512
3201	def __init__(self):
3202		self.proclist = dict()
3203		self.running = False
3204	def procstat(self):
3205		c = ['cat /proc/[1-9]*/stat 2>/dev/null']
3206		process = Popen(c, shell=True, stdout=PIPE)
3207		running = dict()
3208		for line in process.stdout:
3209			data = ascii(line).split()
3210			pid = data[0]
3211			name = re.sub('[()]', '', data[1])
3212			user = int(data[13])
3213			kern = int(data[14])
3214			kjiff = ujiff = 0
3215			if pid not in self.proclist:
3216				self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3217			else:
3218				val = self.proclist[pid]
3219				ujiff = user - val['user']
3220				kjiff = kern - val['kern']
3221				val['user'] = user
3222				val['kern'] = kern
3223			if ujiff > 0 or kjiff > 0:
3224				running[pid] = ujiff + kjiff
3225		process.wait()
3226		out = ['']
3227		for pid in running:
3228			jiffies = running[pid]
3229			val = self.proclist[pid]
3230			if len(out[-1]) > self.maxchars:
3231				out.append('')
3232			elif len(out[-1]) > 0:
3233				out[-1] += ','
3234			out[-1] += '%s-%s %d' % (val['name'], pid, jiffies)
3235		if len(out) > 1:
3236			for line in out:
3237				sysvals.fsetVal('ps - @%d|%s' % (len(out), line), 'trace_marker')
3238		else:
3239			sysvals.fsetVal('ps - %s' % out[0], 'trace_marker')
3240	def processMonitor(self, tid):
3241		while self.running:
3242			self.procstat()
 
 
3243	def start(self):
3244		self.thread = Thread(target=self.processMonitor, args=(0,))
3245		self.running = True
3246		self.thread.start()
3247	def stop(self):
3248		self.running = False
3249
3250# ----------------- FUNCTIONS --------------------
3251
3252# Function: doesTraceLogHaveTraceEvents
3253# Description:
3254#	 Quickly determine if the ftrace log has all of the trace events,
3255#	 markers, and/or kprobes required for primary parsing.
3256def doesTraceLogHaveTraceEvents():
3257	kpcheck = ['_cal: (', '_ret: (']
3258	techeck = ['suspend_resume', 'device_pm_callback', 'tracing_mark_write']
3259	tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3260	sysvals.usekprobes = False
3261	fp = sysvals.openlog(sysvals.ftracefile, 'r')
3262	for line in fp:
3263		# check for kprobes
3264		if not sysvals.usekprobes:
3265			for i in kpcheck:
3266				if i in line:
3267					sysvals.usekprobes = True
3268		# check for all necessary trace events
3269		check = techeck[:]
3270		for i in techeck:
3271			if i in line:
3272				check.remove(i)
3273		techeck = check
3274		# check for all necessary trace markers
3275		check = tmcheck[:]
3276		for i in tmcheck:
3277			if i in line:
3278				check.remove(i)
3279		tmcheck = check
3280	fp.close()
3281	sysvals.usetraceevents = True if len(techeck) < 3 else False
3282	sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
3283
3284# Function: appendIncompleteTraceLog
3285# Description:
 
3286#	 Adds callgraph data which lacks trace event data. This is only
3287#	 for timelines generated from 3.15 or older
3288# Arguments:
3289#	 testruns: the array of Data objects obtained from parseKernelLog
3290def appendIncompleteTraceLog(testruns):
3291	# create TestRun vessels for ftrace parsing
3292	testcnt = len(testruns)
3293	testidx = 0
3294	testrun = []
3295	for data in testruns:
3296		testrun.append(TestRun(data))
3297
3298	# extract the callgraph and traceevent data
3299	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3300		os.path.basename(sysvals.ftracefile))
3301	tp = TestProps()
3302	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3303	data = 0
3304	for line in tf:
3305		# remove any latent carriage returns
3306		line = line.replace('\r\n', '')
3307		if tp.stampInfo(line, sysvals):
3308			continue
3309		# parse only valid lines, if this is not one move on
3310		m = re.match(tp.ftrace_line_fmt, line)
3311		if(not m):
3312			continue
3313		# gather the basic message data from the line
3314		m_time = m.group('time')
3315		m_pid = m.group('pid')
3316		m_msg = m.group('msg')
3317		if(tp.cgformat):
3318			m_param3 = m.group('dur')
3319		else:
3320			m_param3 = 'traceevent'
3321		if(m_time and m_pid and m_msg):
3322			t = FTraceLine(m_time, m_msg, m_param3)
3323			pid = int(m_pid)
3324		else:
3325			continue
3326		# the line should be a call, return, or event
3327		if(not t.fcall and not t.freturn and not t.fevent):
3328			continue
3329		# look for the suspend start marker
3330		if(t.startMarker()):
3331			data = testrun[testidx].data
3332			tp.parseStamp(data, sysvals)
3333			data.setStart(t.time, t.name)
3334			continue
3335		if(not data):
3336			continue
3337		# find the end of resume
3338		if(t.endMarker()):
3339			data.setEnd(t.time, t.name)
3340			testidx += 1
3341			if(testidx >= testcnt):
3342				break
3343			continue
3344		# trace event processing
3345		if(t.fevent):
3346			continue
3347		# call/return processing
3348		elif sysvals.usecallgraph:
3349			# create a callgraph object for the data
3350			if(pid not in testrun[testidx].ftemp):
3351				testrun[testidx].ftemp[pid] = []
3352				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3353			# when the call is finished, see which device matches it
3354			cg = testrun[testidx].ftemp[pid][-1]
3355			res = cg.addLine(t)
3356			if(res != 0):
3357				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3358			if(res == -1):
3359				testrun[testidx].ftemp[pid][-1].addLine(t)
3360	tf.close()
3361
3362	for test in testrun:
3363		# add the callgraph data to the device hierarchy
3364		for pid in test.ftemp:
3365			for cg in test.ftemp[pid]:
3366				if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3367					continue
3368				if(not cg.postProcess()):
3369					id = 'task %s cpu %s' % (pid, m.group('cpu'))
3370					sysvals.vprint('Sanity check failed for '+\
3371						id+', ignoring this callback')
3372					continue
3373				callstart = cg.start
3374				callend = cg.end
3375				for p in test.data.sortedPhases():
3376					if(test.data.dmesg[p]['start'] <= callstart and
3377						callstart <= test.data.dmesg[p]['end']):
3378						list = test.data.dmesg[p]['list']
3379						for devname in list:
3380							dev = list[devname]
3381							if(pid == dev['pid'] and
3382								callstart <= dev['start'] and
3383								callend >= dev['end']):
3384								dev['ftrace'] = cg
3385						break
3386
3387# Function: loadTraceLog
3388# Description:
3389#	 load the ftrace file into memory and fix up any ordering issues
3390# Output:
3391#	 TestProps instance and an array of lines in proper order
3392def loadTraceLog():
3393	tp, data, lines, trace = TestProps(), dict(), [], []
3394	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3395	for line in tf:
3396		# remove any latent carriage returns
3397		line = line.replace('\r\n', '')
3398		if tp.stampInfo(line, sysvals):
3399			continue
3400		# ignore all other commented lines
3401		if line[0] == '#':
3402			continue
3403		# ftrace line: parse only valid lines
3404		m = re.match(tp.ftrace_line_fmt, line)
3405		if(not m):
3406			continue
3407		dur = m.group('dur') if tp.cgformat else 'traceevent'
3408		info = (m.group('time'), m.group('proc'), m.group('pid'),
3409			m.group('msg'), dur)
3410		# group the data by timestamp
3411		t = float(info[0])
3412		if t in data:
3413			data[t].append(info)
3414		else:
3415			data[t] = [info]
3416		# we only care about trace event ordering
3417		if (info[3].startswith('suspend_resume:') or \
3418			info[3].startswith('tracing_mark_write:')) and t not in trace:
3419				trace.append(t)
3420	tf.close()
3421	for t in sorted(data):
3422		first, last, blk = [], [], data[t]
3423		if len(blk) > 1 and t in trace:
3424			# move certain lines to the start or end of a timestamp block
3425			for i in range(len(blk)):
3426				if 'SUSPEND START' in blk[i][3]:
3427					first.append(i)
3428				elif re.match(r'.* timekeeping_freeze.*begin', blk[i][3]):
3429					last.append(i)
3430				elif re.match(r'.* timekeeping_freeze.*end', blk[i][3]):
3431					first.append(i)
3432				elif 'RESUME COMPLETE' in blk[i][3]:
3433					last.append(i)
3434			if len(first) == 1 and len(last) == 0:
3435				blk.insert(0, blk.pop(first[0]))
3436			elif len(last) == 1 and len(first) == 0:
3437				blk.append(blk.pop(last[0]))
3438		for info in blk:
3439			lines.append(info)
3440	return (tp, lines)
3441
3442# Function: parseTraceLog
3443# Description:
3444#	 Analyze an ftrace log output file generated from this app during
3445#	 the execution phase. Used when the ftrace log is the primary data source
3446#	 and includes the suspend_resume and device_pm_callback trace events
3447#	 The ftrace filename is taken from sysvals
3448# Output:
3449#	 An array of Data objects
3450def parseTraceLog(live=False):
3451	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3452		os.path.basename(sysvals.ftracefile))
3453	if(os.path.exists(sysvals.ftracefile) == False):
3454		doError('%s does not exist' % sysvals.ftracefile)
3455	if not live:
3456		sysvals.setupAllKprobes()
3457	ksuscalls = ['ksys_sync', 'pm_prepare_console']
3458	krescalls = ['pm_restore_console']
3459	tracewatch = ['irq_wakeup']
3460	if sysvals.usekprobes:
3461		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3462			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3463			'CPU_OFF', 'acpi_suspend']
3464
3465	# extract the callgraph and traceevent data
3466	s2idle_enter = hwsus = False
 
3467	testruns, testdata = [], []
3468	testrun, data, limbo = 0, 0, True
 
3469	phase = 'suspend_prepare'
3470	tp, tf = loadTraceLog()
3471	for m_time, m_proc, m_pid, m_msg, m_param3 in tf:
 
 
 
 
 
 
 
 
 
 
3472		# gather the basic message data from the line
 
 
 
 
 
 
 
 
3473		if(m_time and m_pid and m_msg):
3474			t = FTraceLine(m_time, m_msg, m_param3)
3475			pid = int(m_pid)
3476		else:
3477			continue
3478		# the line should be a call, return, or event
3479		if(not t.fcall and not t.freturn and not t.fevent):
3480			continue
3481		# find the start of suspend
3482		if(t.startMarker()):
3483			data, limbo = Data(len(testdata)), False
3484			testdata.append(data)
3485			testrun = TestRun(data)
3486			testruns.append(testrun)
3487			tp.parseStamp(data, sysvals)
3488			data.setStart(t.time, t.name)
3489			data.first_suspend_prepare = True
3490			phase = data.setPhase('suspend_prepare', t.time, True)
3491			continue
3492		if(not data or limbo):
3493			continue
3494		# process cpu exec line
3495		if t.type == 'tracing_mark_write':
3496			if t.name == 'CMD COMPLETE' and data.tKernRes == 0:
3497				data.tKernRes = t.time
3498			m = re.match(tp.procexecfmt, t.name)
3499			if(m):
3500				parts, msg = 1, m.group('ps')
3501				m = re.match(tp.procmultifmt, msg)
3502				if(m):
3503					parts, msg = int(m.group('n')), m.group('ps')
3504					if tp.multiproccnt == 0:
3505						tp.multiproctime = t.time
3506						tp.multiproclist = dict()
3507					proclist = tp.multiproclist
3508					tp.multiproccnt += 1
3509				else:
3510					proclist = dict()
3511					tp.multiproccnt = 0
3512				for ps in msg.split(','):
3513					val = ps.split()
3514					if not val or len(val) != 2:
3515						continue
3516					name = val[0].replace('--', '-')
3517					proclist[name] = int(val[1])
3518				if parts == 1:
3519					data.pstl[t.time] = proclist
3520				elif parts == tp.multiproccnt:
3521					data.pstl[tp.multiproctime] = proclist
3522					tp.multiproccnt = 0
3523				continue
3524		# find the end of resume
3525		if(t.endMarker()):
3526			if data.tKernRes == 0:
3527				data.tKernRes = t.time
3528			data.handleEndMarker(t.time, t.name)
3529			if(not sysvals.usetracemarkers):
3530				# no trace markers? then quit and be sure to finish recording
3531				# the event we used to trigger resume end
3532				if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3533					# if an entry exists, assume this is its end
3534					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3535			limbo = True
3536			continue
3537		# trace event processing
3538		if(t.fevent):
3539			if(t.type == 'suspend_resume'):
3540				# suspend_resume trace events have two types, begin and end
3541				if(re.match(r'(?P<name>.*) begin$', t.name)):
3542					isbegin = True
3543				elif(re.match(r'(?P<name>.*) end$', t.name)):
3544					isbegin = False
3545				else:
3546					continue
3547				if '[' in t.name:
3548					m = re.match(r'(?P<name>.*)\[.*', t.name)
3549				else:
3550					m = re.match(r'(?P<name>.*) .*', t.name)
3551				name = m.group('name')
3552				# ignore these events
3553				if(name.split('[')[0] in tracewatch):
3554					continue
3555				# -- phase changes --
3556				# start of kernel suspend
3557				if(re.match(r'suspend_enter\[.*', t.name)):
3558					if(isbegin and data.tKernSus == 0):
3559						data.tKernSus = t.time
3560					continue
3561				# suspend_prepare start
3562				elif(re.match(r'dpm_prepare\[.*', t.name)):
3563					if isbegin and data.first_suspend_prepare:
3564						data.first_suspend_prepare = False
3565						if data.tKernSus == 0:
3566							data.tKernSus = t.time
3567						continue
3568					phase = data.setPhase('suspend_prepare', t.time, isbegin)
3569					continue
3570				# suspend start
3571				elif(re.match(r'dpm_suspend\[.*', t.name)):
3572					phase = data.setPhase('suspend', t.time, isbegin)
3573					continue
3574				# suspend_late start
3575				elif(re.match(r'dpm_suspend_late\[.*', t.name)):
3576					phase = data.setPhase('suspend_late', t.time, isbegin)
3577					continue
3578				# suspend_noirq start
3579				elif(re.match(r'dpm_suspend_noirq\[.*', t.name)):
3580					phase = data.setPhase('suspend_noirq', t.time, isbegin)
3581					continue
3582				# suspend_machine/resume_machine
3583				elif(re.match(tp.machinesuspend, t.name)):
3584					lp = data.lastPhase()
3585					if(isbegin):
3586						hwsus = True
3587						if lp.startswith('resume_machine'):
3588							# trim out s2idle loops, track time trying to freeze
3589							llp = data.lastPhase(2)
3590							if llp.startswith('suspend_machine'):
3591								if 'waking' not in data.dmesg[llp]:
3592									data.dmesg[llp]['waking'] = [0, 0.0]
3593								data.dmesg[llp]['waking'][0] += 1
3594								data.dmesg[llp]['waking'][1] += \
3595									t.time - data.dmesg[lp]['start']
3596							data.currphase = ''
3597							del data.dmesg[lp]
3598							continue
3599						phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3600						data.setPhase(phase, t.time, False)
3601						if data.tSuspended == 0:
3602							data.tSuspended = t.time
3603					else:
3604						if lp.startswith('resume_machine'):
3605							data.dmesg[lp]['end'] = t.time
3606							continue
3607						phase = data.setPhase('resume_machine', t.time, True)
3608						if(sysvals.suspendmode in ['mem', 'disk']):
3609							susp = phase.replace('resume', 'suspend')
3610							if susp in data.dmesg:
3611								data.dmesg[susp]['end'] = t.time
3612							data.tSuspended = t.time
3613						data.tResumed = t.time
3614					continue
3615				# resume_noirq start
3616				elif(re.match(r'dpm_resume_noirq\[.*', t.name)):
3617					phase = data.setPhase('resume_noirq', t.time, isbegin)
3618					continue
3619				# resume_early start
3620				elif(re.match(r'dpm_resume_early\[.*', t.name)):
3621					phase = data.setPhase('resume_early', t.time, isbegin)
3622					continue
3623				# resume start
3624				elif(re.match(r'dpm_resume\[.*', t.name)):
3625					phase = data.setPhase('resume', t.time, isbegin)
3626					continue
3627				# resume complete start
3628				elif(re.match(r'dpm_complete\[.*', t.name)):
3629					phase = data.setPhase('resume_complete', t.time, isbegin)
3630					continue
3631				# skip trace events inside devices calls
3632				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3633					continue
3634				# global events (outside device calls) are graphed
3635				if(name not in testrun.ttemp):
3636					testrun.ttemp[name] = []
3637				# special handling for s2idle_enter
3638				if name == 'machine_suspend':
3639					if hwsus:
3640						s2idle_enter = hwsus = False
3641					elif s2idle_enter and not isbegin:
3642						if(len(testrun.ttemp[name]) > 0):
3643							testrun.ttemp[name][-1]['end'] = t.time
3644							testrun.ttemp[name][-1]['loop'] += 1
3645					elif not s2idle_enter and isbegin:
3646						s2idle_enter = True
3647						testrun.ttemp[name].append({'begin': t.time,
3648							'end': t.time, 'pid': pid, 'loop': 0})
3649					continue
3650				if(isbegin):
3651					# create a new list entry
3652					testrun.ttemp[name].append(\
3653						{'begin': t.time, 'end': t.time, 'pid': pid})
3654				else:
3655					if(len(testrun.ttemp[name]) > 0):
3656						# if an entry exists, assume this is its end
3657						testrun.ttemp[name][-1]['end'] = t.time
3658			# device callback start
3659			elif(t.type == 'device_pm_callback_start'):
3660				if phase not in data.dmesg:
3661					continue
3662				m = re.match(r'(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3663					t.name);
3664				if(not m):
3665					continue
3666				drv = m.group('drv')
3667				n = m.group('d')
3668				p = m.group('p')
3669				if(n and p):
3670					data.newAction(phase, n, pid, p, t.time, -1, drv)
3671					if pid not in data.devpids:
3672						data.devpids.append(pid)
3673			# device callback finish
3674			elif(t.type == 'device_pm_callback_end'):
3675				if phase not in data.dmesg:
3676					continue
3677				m = re.match(r'(?P<drv>.*) (?P<d>.*), err.*', t.name);
3678				if(not m):
3679					continue
3680				n = m.group('d')
3681				dev = data.findDevice(phase, n)
3682				if dev:
3683					dev['length'] = t.time - dev['start']
3684					dev['end'] = t.time
3685		# kprobe event processing
3686		elif(t.fkprobe):
3687			kprobename = t.type
3688			kprobedata = t.name
3689			key = (kprobename, pid)
3690			# displayname is generated from kprobe data
3691			displayname = ''
3692			if(t.fcall):
3693				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3694				if not displayname:
3695					continue
3696				if(key not in tp.ktemp):
3697					tp.ktemp[key] = []
3698				tp.ktemp[key].append({
3699					'pid': pid,
3700					'begin': t.time,
3701					'end': -1,
3702					'name': displayname,
3703					'cdata': kprobedata,
3704					'proc': m_proc,
3705				})
3706				# start of kernel resume
3707				if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3708					and kprobename in ksuscalls):
3709					data.tKernSus = t.time
3710			elif(t.freturn):
3711				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3712					continue
3713				e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3714				if not e:
3715					continue
3716				if (t.time - e['begin']) * 1000 < sysvals.mindevlen:
3717					tp.ktemp[key].pop()
3718					continue
3719				e['end'] = t.time
3720				e['rdata'] = kprobedata
3721				# end of kernel resume
3722				if(phase != 'suspend_prepare' and kprobename in krescalls):
3723					if phase in data.dmesg:
3724						data.dmesg[phase]['end'] = t.time
3725					data.tKernRes = t.time
3726
3727		# callgraph processing
3728		elif sysvals.usecallgraph:
3729			# create a callgraph object for the data
3730			key = (m_proc, pid)
3731			if(key not in testrun.ftemp):
3732				testrun.ftemp[key] = []
3733				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3734			# when the call is finished, see which device matches it
3735			cg = testrun.ftemp[key][-1]
3736			res = cg.addLine(t)
3737			if(res != 0):
3738				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3739			if(res == -1):
3740				testrun.ftemp[key][-1].addLine(t)
 
3741	if len(testdata) < 1:
3742		sysvals.vprint('WARNING: ftrace start marker is missing')
3743	if data and not data.devicegroups:
3744		sysvals.vprint('WARNING: ftrace end marker is missing')
3745		data.handleEndMarker(t.time, t.name)
3746
3747	if sysvals.suspendmode == 'command':
3748		for test in testruns:
3749			for p in test.data.sortedPhases():
3750				if p == 'suspend_prepare':
3751					test.data.dmesg[p]['start'] = test.data.start
3752					test.data.dmesg[p]['end'] = test.data.end
3753				else:
3754					test.data.dmesg[p]['start'] = test.data.end
3755					test.data.dmesg[p]['end'] = test.data.end
3756			test.data.tSuspended = test.data.end
3757			test.data.tResumed = test.data.end
3758			test.data.fwValid = False
3759
3760	# dev source and procmon events can be unreadable with mixed phase height
3761	if sysvals.usedevsrc or sysvals.useprocmon:
3762		sysvals.mixedphaseheight = False
3763
3764	# expand phase boundaries so there are no gaps
3765	for data in testdata:
3766		lp = data.sortedPhases()[0]
3767		for p in data.sortedPhases():
3768			if(p != lp and not ('machine' in p and 'machine' in lp)):
3769				data.dmesg[lp]['end'] = data.dmesg[p]['start']
3770			lp = p
3771
3772	for i in range(len(testruns)):
3773		test = testruns[i]
3774		data = test.data
3775		# find the total time range for this test (begin, end)
3776		tlb, tle = data.start, data.end
3777		if i < len(testruns) - 1:
3778			tle = testruns[i+1].data.start
3779		# add the process usage data to the timeline
3780		if sysvals.useprocmon:
3781			data.createProcessUsageEvents()
3782		# add the traceevent data to the device hierarchy
3783		if(sysvals.usetraceevents):
3784			# add actual trace funcs
3785			for name in sorted(test.ttemp):
3786				for event in test.ttemp[name]:
3787					if event['end'] - event['begin'] <= 0:
3788						continue
3789					title = name
3790					if name == 'machine_suspend' and 'loop' in event:
3791						title = 's2idle_enter_%dx' % event['loop']
3792					data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3793			# add the kprobe based virtual tracefuncs as actual devices
3794			for key in sorted(tp.ktemp):
3795				name, pid = key
3796				if name not in sysvals.tracefuncs:
3797					continue
3798				if pid not in data.devpids:
3799					data.devpids.append(pid)
3800				for e in tp.ktemp[key]:
3801					kb, ke = e['begin'], e['end']
3802					if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3803						continue
3804					color = sysvals.kprobeColor(name)
3805					data.newActionGlobal(e['name'], kb, ke, pid, color)
3806			# add config base kprobes and dev kprobes
3807			if sysvals.usedevsrc:
3808				for key in sorted(tp.ktemp):
3809					name, pid = key
3810					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3811						continue
3812					for e in tp.ktemp[key]:
3813						kb, ke = e['begin'], e['end']
3814						if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3815							continue
3816						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3817							ke, e['cdata'], e['rdata'])
3818		if sysvals.usecallgraph:
3819			# add the callgraph data to the device hierarchy
3820			sortlist = dict()
3821			for key in sorted(test.ftemp):
3822				proc, pid = key
3823				for cg in test.ftemp[key]:
3824					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3825						continue
3826					if(not cg.postProcess()):
3827						id = 'task %s' % (pid)
3828						sysvals.vprint('Sanity check failed for '+\
3829							id+', ignoring this callback')
3830						continue
3831					# match cg data to devices
3832					devname = ''
3833					if sysvals.suspendmode != 'command':
3834						devname = cg.deviceMatch(pid, data)
3835					if not devname:
3836						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3837						sortlist[sortkey] = cg
3838					elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3839						sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3840							(devname, len(cg.list)))
3841			# create blocks for orphan cg data
3842			for sortkey in sorted(sortlist):
3843				cg = sortlist[sortkey]
3844				name = cg.name
3845				if sysvals.isCallgraphFunc(name):
3846					sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3847					cg.newActionFromFunction(data)
3848	if sysvals.suspendmode == 'command':
3849		return (testdata, '')
3850
3851	# fill in any missing phases
3852	error = []
3853	for data in testdata:
3854		tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3855		terr = ''
3856		phasedef = data.phasedef
3857		lp = 'suspend_prepare'
3858		for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3859			if p not in data.dmesg:
3860				if not terr:
3861					ph = p if 'machine' in p else lp
3862					if p == 'suspend_machine':
3863						sm = sysvals.suspendmode
3864						if sm in suspendmodename:
3865							sm = suspendmodename[sm]
3866						terr = 'test%s did not enter %s power mode' % (tn, sm)
3867					else:
3868						terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
3869					pprint('TEST%s FAILED: %s' % (tn, terr))
3870					error.append(terr)
3871					if data.tSuspended == 0:
3872						data.tSuspended = data.dmesg[lp]['end']
3873					if data.tResumed == 0:
3874						data.tResumed = data.dmesg[lp]['end']
3875					data.fwValid = False
3876				sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3877			lp = p
3878		if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3879			terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3880				(sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3881			error.append(terr)
3882		if not terr and data.enterfail:
3883			pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3884			terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3885			error.append(terr)
3886		if data.tSuspended == 0:
3887			data.tSuspended = data.tKernRes
3888		if data.tResumed == 0:
3889			data.tResumed = data.tSuspended
3890
3891		if(len(sysvals.devicefilter) > 0):
3892			data.deviceFilter(sysvals.devicefilter)
3893		data.fixupInitcallsThatDidntReturn()
3894		if sysvals.usedevsrc:
3895			data.optimizeDevSrc()
3896
3897	# x2: merge any overlapping devices between test runs
3898	if sysvals.usedevsrc and len(testdata) > 1:
3899		tc = len(testdata)
3900		for i in range(tc - 1):
3901			devlist = testdata[i].overflowDevices()
3902			for j in range(i + 1, tc):
3903				testdata[j].mergeOverlapDevices(devlist)
3904		testdata[0].stitchTouchingThreads(testdata[1:])
3905	return (testdata, ', '.join(error))
3906
3907# Function: loadKernelLog
3908# Description:
 
3909#	 load the dmesg file into memory and fix up any ordering issues
 
3910# Output:
3911#	 An array of empty Data objects with only their dmesgtext attributes set
3912def loadKernelLog():
3913	sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3914		os.path.basename(sysvals.dmesgfile))
3915	if(os.path.exists(sysvals.dmesgfile) == False):
3916		doError('%s does not exist' % sysvals.dmesgfile)
3917
3918	# there can be multiple test runs in a single file
3919	tp = TestProps()
3920	tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3921	testruns = []
3922	data = 0
3923	lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3924	for line in lf:
3925		line = line.replace('\r\n', '')
3926		idx = line.find('[')
3927		if idx > 1:
3928			line = line[idx:]
3929		if tp.stampInfo(line, sysvals):
3930			continue
3931		m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3932		if(not m):
3933			continue
3934		msg = m.group("msg")
3935		if re.match(r'PM: Syncing filesystems.*', msg) or \
3936			re.match(r'PM: suspend entry.*', msg):
3937			if(data):
3938				testruns.append(data)
3939			data = Data(len(testruns))
3940			tp.parseStamp(data, sysvals)
3941		if(not data):
3942			continue
3943		m = re.match(r'.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3944		if(m):
3945			sysvals.stamp['kernel'] = m.group('k')
3946		m = re.match(r'PM: Preparing system for (?P<m>.*) sleep', msg)
3947		if not m:
3948			m = re.match(r'PM: Preparing system for sleep \((?P<m>.*)\)', msg)
3949		if m:
3950			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3951		data.dmesgtext.append(line)
3952	lf.close()
3953
3954	if sysvals.suspendmode == 's2idle':
3955		sysvals.suspendmode = 'freeze'
3956	elif sysvals.suspendmode == 'deep':
3957		sysvals.suspendmode = 'mem'
3958	if data:
3959		testruns.append(data)
3960	if len(testruns) < 1:
3961		doError('dmesg log has no suspend/resume data: %s' \
3962			% sysvals.dmesgfile)
3963
3964	# fix lines with same timestamp/function with the call and return swapped
3965	for data in testruns:
3966		last = ''
3967		for line in data.dmesgtext:
3968			ct, cf, n, p = data.initcall_debug_call(line)
3969			rt, rf, l = data.initcall_debug_return(last)
3970			if ct and rt and ct == rt and cf == rf:
 
 
 
3971				i = data.dmesgtext.index(last)
3972				j = data.dmesgtext.index(line)
3973				data.dmesgtext[i] = line
3974				data.dmesgtext[j] = last
3975			last = line
3976	return testruns
3977
3978# Function: parseKernelLog
3979# Description:
 
3980#	 Analyse a dmesg log output file generated from this app during
3981#	 the execution phase. Create a set of device structures in memory
3982#	 for subsequent formatting in the html output file
3983#	 This call is only for legacy support on kernels where the ftrace
3984#	 data lacks the suspend_resume or device_pm_callbacks trace events.
3985# Arguments:
3986#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3987# Output:
3988#	 The filled Data object
3989def parseKernelLog(data):
3990	phase = 'suspend_runtime'
3991
3992	if(data.fwValid):
3993		sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3994			(data.fwSuspend, data.fwResume))
3995
3996	# dmesg phase match table
3997	dm = {
3998		'suspend_prepare': ['PM: Syncing filesystems.*', 'PM: suspend entry.*'],
3999		        'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*',
4000		                    'PM: Suspending system .*'],
4001		   'suspend_late': ['PM: suspend of devices complete after.*',
4002							'PM: freeze of devices complete after.*'],
4003		  'suspend_noirq': ['PM: late suspend of devices complete after.*',
4004							'PM: late freeze of devices complete after.*'],
4005		'suspend_machine': ['PM: suspend-to-idle',
4006							'PM: noirq suspend of devices complete after.*',
4007							'PM: noirq freeze of devices complete after.*'],
4008		 'resume_machine': ['[PM: ]*Timekeeping suspended for.*',
4009							'ACPI: Low-level resume complete.*',
4010							'ACPI: resume from mwait',
4011							r'Suspended for [0-9\.]* seconds'],
4012		   'resume_noirq': ['PM: resume from suspend-to-idle',
4013							'ACPI: Waking up from system sleep state.*'],
4014		   'resume_early': ['PM: noirq resume of devices complete after.*',
4015							'PM: noirq restore of devices complete after.*'],
4016		         'resume': ['PM: early resume of devices complete after.*',
4017							'PM: early restore of devices complete after.*'],
4018		'resume_complete': ['PM: resume of devices complete after.*',
4019							'PM: restore of devices complete after.*'],
4020		    'post_resume': [r'.*Restarting tasks \.\.\..*'],
4021	}
 
 
 
 
 
 
 
 
 
 
 
 
4022
4023	# action table (expected events that occur and show up in dmesg)
4024	at = {
4025		'sync_filesystems': {
4026			'smsg': '.*[Ff]+ilesystems.*',
4027			'emsg': 'PM: Preparing system for[a-z]* sleep.*' },
4028		'freeze_user_processes': {
4029			'smsg': 'Freezing user space processes.*',
4030			'emsg': 'Freezing remaining freezable tasks.*' },
4031		'freeze_tasks': {
4032			'smsg': 'Freezing remaining freezable tasks.*',
4033			'emsg': 'PM: Suspending system.*' },
4034		'ACPI prepare': {
4035			'smsg': 'ACPI: Preparing to enter system sleep state.*',
4036			'emsg': 'PM: Saving platform NVS memory.*' },
4037		'PM vns': {
4038			'smsg': 'PM: Saving platform NVS memory.*',
4039			'emsg': 'Disabling non-boot CPUs .*' },
4040	}
4041
4042	t0 = -1.0
4043	cpu_start = -1.0
4044	prevktime = -1.0
4045	actions = dict()
4046	for line in data.dmesgtext:
4047		# parse each dmesg line into the time and message
4048		m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
4049		if(m):
4050			val = m.group('ktime')
4051			try:
4052				ktime = float(val)
4053			except:
4054				continue
4055			msg = m.group('msg')
4056			# initialize data start to first line time
4057			if t0 < 0:
4058				data.setStart(ktime)
4059				t0 = ktime
4060		else:
4061			continue
4062
4063		# check for a phase change line
4064		phasechange = False
4065		for p in dm:
4066			for s in dm[p]:
4067				if(re.match(s, msg)):
4068					phasechange, phase = True, p
4069					dm[p] = [s]
4070					break
4071
4072		# hack for determining resume_machine end for freeze
4073		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
4074			and phase == 'resume_machine' and \
4075			data.initcall_debug_call(line, True)):
4076			data.setPhase(phase, ktime, False)
4077			phase = 'resume_noirq'
4078			data.setPhase(phase, ktime, True)
4079
4080		if phasechange:
4081			if phase == 'suspend_prepare':
4082				data.setPhase(phase, ktime, True)
4083				data.setStart(ktime)
4084				data.tKernSus = ktime
4085			elif phase == 'suspend':
4086				lp = data.lastPhase()
4087				if lp:
4088					data.setPhase(lp, ktime, False)
4089				data.setPhase(phase, ktime, True)
4090			elif phase == 'suspend_late':
4091				lp = data.lastPhase()
4092				if lp:
4093					data.setPhase(lp, ktime, False)
4094				data.setPhase(phase, ktime, True)
4095			elif phase == 'suspend_noirq':
4096				lp = data.lastPhase()
4097				if lp:
4098					data.setPhase(lp, ktime, False)
4099				data.setPhase(phase, ktime, True)
4100			elif phase == 'suspend_machine':
4101				lp = data.lastPhase()
4102				if lp:
4103					data.setPhase(lp, ktime, False)
4104				data.setPhase(phase, ktime, True)
4105			elif phase == 'resume_machine':
4106				lp = data.lastPhase()
4107				if(sysvals.suspendmode in ['freeze', 'standby']):
4108					data.tSuspended = prevktime
4109					if lp:
4110						data.setPhase(lp, prevktime, False)
4111				else:
4112					data.tSuspended = ktime
4113					if lp:
4114						data.setPhase(lp, prevktime, False)
4115				data.tResumed = ktime
4116				data.setPhase(phase, ktime, True)
4117			elif phase == 'resume_noirq':
4118				lp = data.lastPhase()
4119				if lp:
4120					data.setPhase(lp, ktime, False)
4121				data.setPhase(phase, ktime, True)
4122			elif phase == 'resume_early':
4123				lp = data.lastPhase()
4124				if lp:
4125					data.setPhase(lp, ktime, False)
4126				data.setPhase(phase, ktime, True)
4127			elif phase == 'resume':
4128				lp = data.lastPhase()
4129				if lp:
4130					data.setPhase(lp, ktime, False)
4131				data.setPhase(phase, ktime, True)
4132			elif phase == 'resume_complete':
4133				lp = data.lastPhase()
4134				if lp:
4135					data.setPhase(lp, ktime, False)
4136				data.setPhase(phase, ktime, True)
4137			elif phase == 'post_resume':
4138				lp = data.lastPhase()
4139				if lp:
4140					data.setPhase(lp, ktime, False)
4141				data.setEnd(ktime)
4142				data.tKernRes = ktime
4143				break
4144
4145		# -- device callbacks --
4146		if(phase in data.sortedPhases()):
4147			# device init call
4148			t, f, n, p = data.initcall_debug_call(line)
4149			if t and f and n and p:
4150				data.newAction(phase, f, int(n), p, ktime, -1, '')
4151			else:
4152				# device init return
4153				t, f, l = data.initcall_debug_return(line)
4154				if t and f and l:
4155					list = data.dmesg[phase]['list']
4156					if(f in list):
4157						dev = list[f]
4158						dev['length'] = int(l)
4159						dev['end'] = ktime
 
 
 
 
 
 
 
 
4160
4161		# if trace events are not available, these are better than nothing
4162		if(not sysvals.usetraceevents):
4163			# look for known actions
4164			for a in sorted(at):
4165				if(re.match(at[a]['smsg'], msg)):
4166					if(a not in actions):
4167						actions[a] = [{'begin': ktime, 'end': ktime}]
 
4168				if(re.match(at[a]['emsg'], msg)):
4169					if(a in actions and actions[a][-1]['begin'] == actions[a][-1]['end']):
4170						actions[a][-1]['end'] = ktime
4171			# now look for CPU on/off events
4172			if(re.match(r'Disabling non-boot CPUs .*', msg)):
4173				# start of first cpu suspend
4174				cpu_start = ktime
4175			elif(re.match(r'Enabling non-boot CPUs .*', msg)):
4176				# start of first cpu resume
4177				cpu_start = ktime
4178			elif(re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) \
4179				or re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg)):
4180				# end of a cpu suspend, start of the next
4181				m = re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
4182				if(not m):
4183					m = re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg)
4184				cpu = 'CPU'+m.group('cpu')
4185				if(cpu not in actions):
4186					actions[cpu] = []
4187				actions[cpu].append({'begin': cpu_start, 'end': ktime})
4188				cpu_start = ktime
4189			elif(re.match(r'CPU(?P<cpu>[0-9]*) is up', msg)):
4190				# end of a cpu resume, start of the next
4191				m = re.match(r'CPU(?P<cpu>[0-9]*) is up', msg)
4192				cpu = 'CPU'+m.group('cpu')
4193				if(cpu not in actions):
4194					actions[cpu] = []
4195				actions[cpu].append({'begin': cpu_start, 'end': ktime})
4196				cpu_start = ktime
4197		prevktime = ktime
4198	data.initDevicegroups()
4199
4200	# fill in any missing phases
4201	phasedef = data.phasedef
4202	terr, lp = '', 'suspend_prepare'
4203	if lp not in data.dmesg:
4204		doError('dmesg log format has changed, could not find start of suspend')
4205	for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4206		if p not in data.dmesg:
4207			if not terr:
4208				pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
4209				terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
4210				if data.tSuspended == 0:
4211					data.tSuspended = data.dmesg[lp]['end']
4212				if data.tResumed == 0:
4213					data.tResumed = data.dmesg[lp]['end']
4214			sysvals.vprint('WARNING: phase "%s" is missing!' % p)
4215		lp = p
4216	lp = data.sortedPhases()[0]
4217	for p in data.sortedPhases():
4218		if(p != lp and not ('machine' in p and 'machine' in lp)):
4219			data.dmesg[lp]['end'] = data.dmesg[p]['start']
4220		lp = p
4221	if data.tSuspended == 0:
4222		data.tSuspended = data.tKernRes
4223	if data.tResumed == 0:
4224		data.tResumed = data.tSuspended
4225
4226	# fill in any actions we've found
4227	for name in sorted(actions):
4228		for event in actions[name]:
4229			data.newActionGlobal(name, event['begin'], event['end'])
4230
4231	if(len(sysvals.devicefilter) > 0):
4232		data.deviceFilter(sysvals.devicefilter)
4233	data.fixupInitcallsThatDidntReturn()
4234	return True
4235
4236def callgraphHTML(sv, hf, num, cg, title, color, devid):
4237	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'
4238	html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
4239	html_func_end = '</article>\n'
4240	html_func_leaf = '<article>{0} {1}</article>\n'
4241
4242	cgid = devid
4243	if cg.id:
4244		cgid += cg.id
4245	cglen = (cg.end - cg.start) * 1000
4246	if cglen < sv.mincglen:
4247		return num
4248
4249	fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
4250	flen = fmt % (cglen, cg.start, cg.end)
4251	hf.write(html_func_top.format(cgid, color, num, title, flen))
4252	num += 1
4253	for line in cg.list:
4254		if(line.length < 0.000000001):
4255			flen = ''
4256		else:
4257			fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
4258			flen = fmt % (line.length*1000, line.time)
4259		if line.isLeaf():
4260			if line.length * 1000 < sv.mincglen:
4261				continue
4262			hf.write(html_func_leaf.format(line.name, flen))
4263		elif line.freturn:
4264			hf.write(html_func_end)
4265		else:
4266			hf.write(html_func_start.format(num, line.name, flen))
4267			num += 1
4268	hf.write(html_func_end)
4269	return num
4270
4271def addCallgraphs(sv, hf, data):
4272	hf.write('<section id="callgraphs" class="callgraph">\n')
4273	# write out the ftrace data converted to html
4274	num = 0
4275	for p in data.sortedPhases():
4276		if sv.cgphase and p != sv.cgphase:
4277			continue
4278		list = data.dmesg[p]['list']
4279		for d in data.sortedDevices(p):
4280			if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4281				continue
4282			dev = list[d]
4283			color = 'white'
4284			if 'color' in data.dmesg[p]:
4285				color = data.dmesg[p]['color']
4286			if 'color' in dev:
4287				color = dev['color']
4288			name = d if '[' not in d else d.split('[')[0]
4289			if(d in sv.devprops):
4290				name = sv.devprops[d].altName(d)
4291			if 'drv' in dev and dev['drv']:
4292				name += ' {%s}' % dev['drv']
4293			if sv.suspendmode in suspendmodename:
4294				name += ' '+p
4295			if('ftrace' in dev):
4296				cg = dev['ftrace']
4297				if cg.name == sv.ftopfunc:
4298					name = 'top level suspend/resume call'
4299				num = callgraphHTML(sv, hf, num, cg,
4300					name, color, dev['id'])
4301			if('ftraces' in dev):
4302				for cg in dev['ftraces']:
4303					num = callgraphHTML(sv, hf, num, cg,
4304						name+' &rarr; '+cg.name, color, dev['id'])
4305	hf.write('\n\n    </section>\n')
4306
4307def summaryCSS(title, center=True):
4308	tdcenter = 'text-align:center;' if center else ''
4309	out = '<!DOCTYPE html>\n<html>\n<head>\n\
4310	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4311	<title>'+title+'</title>\n\
4312	<style type=\'text/css\'>\n\
4313		.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4314		table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4315		th {border: 1px solid black;background:#222;color:white;}\n\
4316		td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4317		tr.head td {border: 1px solid black;background:#aaa;}\n\
4318		tr.alt {background-color:#ddd;}\n\
4319		tr.notice {color:red;}\n\
4320		.minval {background-color:#BBFFBB;}\n\
4321		.medval {background-color:#BBBBFF;}\n\
4322		.maxval {background-color:#FFBBBB;}\n\
4323		.head a {color:#000;text-decoration: none;}\n\
4324	</style>\n</head>\n<body>\n'
4325	return out
4326
4327# Function: createHTMLSummarySimple
4328# Description:
4329#	 Create summary html file for a series of tests
4330# Arguments:
4331#	 testruns: array of Data objects from parseTraceLog
4332def createHTMLSummarySimple(testruns, htmlfile, title):
4333	# write the html header first (html head, css code, up to body start)
4334	html = summaryCSS('Summary - SleepGraph')
4335
4336	# extract the test data into list
4337	list = dict()
4338	tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4339	iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4340	num = 0
4341	useturbo = usewifi = False
4342	lastmode = ''
4343	cnt = dict()
4344	for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4345		mode = data['mode']
4346		if mode not in list:
4347			list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4348		if lastmode and lastmode != mode and num > 0:
4349			for i in range(2):
4350				s = sorted(tMed[i])
4351				list[lastmode]['med'][i] = s[int(len(s)//2)]
4352				iMed[i] = tMed[i][list[lastmode]['med'][i]]
4353			list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4354			list[lastmode]['min'] = tMin
4355			list[lastmode]['max'] = tMax
4356			list[lastmode]['idx'] = (iMin, iMed, iMax)
4357			tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4358			iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4359			num = 0
4360		pkgpc10 = syslpi = wifi = ''
4361		if 'pkgpc10' in data and 'syslpi' in data:
4362			pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4363		if 'wifi' in data:
4364			wifi, usewifi = data['wifi'], True
4365		res = data['result']
4366		tVal = [float(data['suspend']), float(data['resume'])]
4367		list[mode]['data'].append([data['host'], data['kernel'],
4368			data['time'], tVal[0], tVal[1], data['url'], res,
4369			data['issues'], data['sus_worst'], data['sus_worsttime'],
4370			data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi,
4371			(data['fullmode'] if 'fullmode' in data else mode)])
4372		idx = len(list[mode]['data']) - 1
4373		if res.startswith('fail in'):
4374			res = 'fail'
4375		if res not in cnt:
4376			cnt[res] = 1
4377		else:
4378			cnt[res] += 1
4379		if res == 'pass':
4380			for i in range(2):
4381				tMed[i][tVal[i]] = idx
4382				tAvg[i] += tVal[i]
4383				if tMin[i] == 0 or tVal[i] < tMin[i]:
4384					iMin[i] = idx
4385					tMin[i] = tVal[i]
4386				if tMax[i] == 0 or tVal[i] > tMax[i]:
4387					iMax[i] = idx
4388					tMax[i] = tVal[i]
4389			num += 1
4390		lastmode = mode
4391	if lastmode and num > 0:
4392		for i in range(2):
4393			s = sorted(tMed[i])
4394			list[lastmode]['med'][i] = s[int(len(s)//2)]
4395			iMed[i] = tMed[i][list[lastmode]['med'][i]]
4396		list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4397		list[lastmode]['min'] = tMin
4398		list[lastmode]['max'] = tMax
4399		list[lastmode]['idx'] = (iMin, iMed, iMax)
4400
4401	# group test header
4402	desc = []
4403	for ilk in sorted(cnt, reverse=True):
4404		if cnt[ilk] > 0:
4405			desc.append('%d %s' % (cnt[ilk], ilk))
4406	html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4407	th = '\t<th>{0}</th>\n'
4408	td = '\t<td>{0}</td>\n'
4409	tdh = '\t<td{1}>{0}</td>\n'
4410	tdlink = '\t<td><a href="{0}">html</a></td>\n'
4411	cols = 12
4412	if useturbo:
4413		cols += 2
4414	if usewifi:
4415		cols += 1
4416	colspan = '%d' % cols
4417
4418	# table header
4419	html += '<table>\n<tr>\n' + th.format('#') +\
4420		th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4421		th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4422		th.format('Suspend') + th.format('Resume') +\
4423		th.format('Worst Suspend Device') + th.format('SD Time') +\
4424		th.format('Worst Resume Device') + th.format('RD Time')
4425	if useturbo:
4426		html += th.format('PkgPC10') + th.format('SysLPI')
4427	if usewifi:
4428		html += th.format('Wifi')
4429	html += th.format('Detail')+'</tr>\n'
4430	# export list into html
4431	head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4432		'<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4433		'<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4434		'<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4435		'<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4436		'Resume Avg={6} '+\
4437		'<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4438		'<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4439		'<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4440		'</tr>\n'
4441	headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4442		colspan+'></td></tr>\n'
4443	for mode in sorted(list):
4444		# header line for each suspend mode
4445		num = 0
4446		tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4447			list[mode]['max'], list[mode]['med']
4448		count = len(list[mode]['data'])
4449		if 'idx' in list[mode]:
4450			iMin, iMed, iMax = list[mode]['idx']
4451			html += head.format('%d' % count, mode.upper(),
4452				'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4453				'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4454				mode.lower()
4455			)
4456		else:
4457			iMin = iMed = iMax = [-1, -1, -1]
4458			html += headnone.format('%d' % count, mode.upper())
4459		for d in list[mode]['data']:
4460			# row classes - alternate row color
4461			rcls = ['alt'] if num % 2 == 1 else []
4462			if d[6] != 'pass':
4463				rcls.append('notice')
4464			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4465			# figure out if the line has sus or res highlighted
4466			idx = list[mode]['data'].index(d)
4467			tHigh = ['', '']
4468			for i in range(2):
4469				tag = 's%s' % mode if i == 0 else 'r%s' % mode
4470				if idx == iMin[i]:
4471					tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4472				elif idx == iMax[i]:
4473					tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4474				elif idx == iMed[i]:
4475					tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4476			html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4477			html += td.format(d[15])									# mode
4478			html += td.format(d[0])										# host
4479			html += td.format(d[1])										# kernel
4480			html += td.format(d[2])										# time
4481			html += td.format(d[6])										# result
4482			html += td.format(d[7])										# issues
4483			html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('')	# suspend
4484			html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('')	# resume
4485			html += td.format(d[8])										# sus_worst
4486			html += td.format('%.3f ms' % d[9])	if d[9] else td.format('')		# sus_worst time
4487			html += td.format(d[10])									# res_worst
4488			html += td.format('%.3f ms' % d[11]) if d[11] else td.format('')	# res_worst time
4489			if useturbo:
4490				html += td.format(d[12])								# pkg_pc10
4491				html += td.format(d[13])								# syslpi
4492			if usewifi:
4493				html += td.format(d[14])								# wifi
4494			html += tdlink.format(d[5]) if d[5] else td.format('')		# url
4495			html += '</tr>\n'
4496			num += 1
4497
4498	# flush the data to file
4499	hf = open(htmlfile, 'w')
4500	hf.write(html+'</table>\n</body>\n</html>\n')
4501	hf.close()
4502
4503def createHTMLDeviceSummary(testruns, htmlfile, title):
4504	html = summaryCSS('Device Summary - SleepGraph', False)
4505
4506	# create global device list from all tests
4507	devall = dict()
4508	for data in testruns:
4509		host, url, devlist = data['host'], data['url'], data['devlist']
4510		for type in devlist:
4511			if type not in devall:
4512				devall[type] = dict()
4513			mdevlist, devlist = devall[type], data['devlist'][type]
4514			for name in devlist:
4515				length = devlist[name]
4516				if name not in mdevlist:
4517					mdevlist[name] = {'name': name, 'host': host,
4518						'worst': length, 'total': length, 'count': 1,
4519						'url': url}
4520				else:
4521					if length > mdevlist[name]['worst']:
4522						mdevlist[name]['worst'] = length
4523						mdevlist[name]['url'] = url
4524						mdevlist[name]['host'] = host
4525					mdevlist[name]['total'] += length
4526					mdevlist[name]['count'] += 1
4527
4528	# generate the html
4529	th = '\t<th>{0}</th>\n'
4530	td = '\t<td align=center>{0}</td>\n'
4531	tdr = '\t<td align=right>{0}</td>\n'
4532	tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4533	limit = 1
4534	for type in sorted(devall, reverse=True):
4535		num = 0
4536		devlist = devall[type]
4537		# table header
4538		html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4539			(title, type.upper(), limit)
4540		html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4541			th.format('Average Time') + th.format('Count') +\
4542			th.format('Worst Time') + th.format('Host (worst time)') +\
4543			th.format('Link (worst time)') + '</tr>\n'
4544		for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4545			devlist[k]['total'], devlist[k]['name']), reverse=True):
4546			data = devall[type][name]
4547			data['average'] = data['total'] / data['count']
4548			if data['average'] < limit:
4549				continue
4550			# row classes - alternate row color
4551			rcls = ['alt'] if num % 2 == 1 else []
4552			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4553			html += tdr.format(data['name'])				# name
4554			html += td.format('%.3f ms' % data['average'])	# average
4555			html += td.format(data['count'])				# count
4556			html += td.format('%.3f ms' % data['worst'])	# worst
4557			html += td.format(data['host'])					# host
4558			html += tdlink.format(data['url'])				# url
4559			html += '</tr>\n'
4560			num += 1
4561		html += '</table>\n'
4562
4563	# flush the data to file
4564	hf = open(htmlfile, 'w')
4565	hf.write(html+'</body>\n</html>\n')
4566	hf.close()
4567	return devall
4568
4569def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4570	multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4571	html = summaryCSS('Issues Summary - SleepGraph', False)
4572	total = len(testruns)
4573
4574	# generate the html
4575	th = '\t<th>{0}</th>\n'
4576	td = '\t<td align={0}>{1}</td>\n'
4577	tdlink = '<a href="{1}">{0}</a>'
4578	subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4579	html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4580	html += '<tr>\n' + th.format('Issue') + th.format('Count')
4581	if multihost:
4582		html += th.format('Hosts')
4583	html += th.format('Tests') + th.format('Fail Rate') +\
4584		th.format('First Instance') + '</tr>\n'
4585
4586	num = 0
4587	for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4588		testtotal = 0
4589		links = []
4590		for host in sorted(e['urls']):
4591			links.append(tdlink.format(host, e['urls'][host][0]))
4592			testtotal += len(e['urls'][host])
4593		rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4594		# row classes - alternate row color
4595		rcls = ['alt'] if num % 2 == 1 else []
4596		html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4597		html += td.format('left', e['line'])		# issue
4598		html += td.format('center', e['count'])		# count
4599		if multihost:
4600			html += td.format('center', len(e['urls']))	# hosts
4601		html += td.format('center', testtotal)		# test count
4602		html += td.format('center', rate)			# test rate
4603		html += td.format('center nowrap', '<br>'.join(links))	# links
4604		html += '</tr>\n'
4605		num += 1
4606
4607	# flush the data to file
4608	hf = open(htmlfile, 'w')
4609	hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4610	hf.close()
4611	return issues
4612
4613def ordinal(value):
4614	suffix = 'th'
4615	if value < 10 or value > 19:
4616		if value % 10 == 1:
4617			suffix = 'st'
4618		elif value % 10 == 2:
4619			suffix = 'nd'
4620		elif value % 10 == 3:
4621			suffix = 'rd'
4622	return '%d%s' % (value, suffix)
4623
4624# Function: createHTML
4625# Description:
4626#	 Create the output html file from the resident test data
4627# Arguments:
4628#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4629# Output:
4630#	 True if the html file was created, false if it failed
4631def createHTML(testruns, testfail):
4632	if len(testruns) < 1:
4633		pprint('ERROR: Not enough test data to build a timeline')
4634		return
4635
4636	kerror = False
4637	for data in testruns:
4638		if data.kerror:
4639			kerror = True
4640		if(sysvals.suspendmode in ['freeze', 'standby']):
4641			data.trimFreezeTime(testruns[-1].tSuspended)
4642		else:
4643			data.getMemTime()
4644
4645	# html function templates
4646	html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
4647	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'
4648	html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4649	html_timetotal = '<table class="time1">\n<tr>'\
4650		'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4651		'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4652		'</tr>\n</table>\n'
4653	html_timetotal2 = '<table class="time1">\n<tr>'\
4654		'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4655		'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4656		'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4657		'</tr>\n</table>\n'
4658	html_timetotal3 = '<table class="time1">\n<tr>'\
4659		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
4660		'<td class="yellow">Command: <b>{1}</b></td>'\
4661		'</tr>\n</table>\n'
4662	html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4663	html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4664	html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4665	html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
4666
4667	# html format variables
4668	scaleH = 20
4669	if kerror:
4670		scaleH = 40
4671
4672	# device timeline
4673	devtl = Timeline(30, scaleH)
4674
4675	# write the test title and general info header
4676	devtl.createHeader(sysvals, testruns[0].stamp)
4677
4678	# Generate the header for this timeline
4679	for data in testruns:
4680		tTotal = data.end - data.start
4681		if(tTotal == 0):
4682			doError('No timeline data')
4683		if sysvals.suspendmode == 'command':
4684			run_time = '%.0f' % (tTotal * 1000)
4685			if sysvals.testcommand:
4686				testdesc = sysvals.testcommand
4687			else:
4688				testdesc = 'unknown'
4689			if(len(testruns) > 1):
4690				testdesc = ordinal(data.testnumber+1)+' '+testdesc
4691			thtml = html_timetotal3.format(run_time, testdesc)
4692			devtl.html += thtml
4693			continue
4694		# typical full suspend/resume header
4695		stot, rtot = sktime, rktime = data.getTimeValues()
4696		ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4697		if data.fwValid:
4698			stot += (data.fwSuspend/1000000.0)
4699			rtot += (data.fwResume/1000000.0)
4700			ssrc.append('firmware')
4701			rsrc.append('firmware')
4702			testdesc = 'Total'
4703		if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4704			rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4705			rsrc.append('wifi')
4706			testdesc = 'Total'
4707		suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4708		stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4709			(sysvals.suspendmode, ' & '.join(ssrc))
4710		rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4711			(sysvals.suspendmode, ' & '.join(rsrc))
4712		if(len(testruns) > 1):
4713			testdesc = testdesc2 = ordinal(data.testnumber+1)
4714			testdesc2 += ' '
4715		if(len(data.tLow) == 0):
4716			thtml = html_timetotal.format(suspend_time, \
4717				resume_time, testdesc, stitle, rtitle)
4718		else:
4719			low_time = '+'.join(data.tLow)
4720			thtml = html_timetotal2.format(suspend_time, low_time, \
4721				resume_time, testdesc, stitle, rtitle)
4722		devtl.html += thtml
4723		if not data.fwValid and 'dev' not in data.wifi:
4724			continue
4725		# extra detail when the times come from multiple sources
4726		thtml = '<table class="time2">\n<tr>'
4727		thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4728		if data.fwValid:
4729			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4730			rftime = '%.3f'%(data.fwResume / 1000000.0)
4731			thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4732			thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4733		thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4734		if 'time' in data.wifi:
4735			if data.wifi['stat'] != 'timeout':
4736				wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
4737			else:
4738				wtime = 'TIMEOUT'
4739			thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4740		thtml += '</tr>\n</table>\n'
4741		devtl.html += thtml
4742	if testfail:
4743		devtl.html += html_fail.format(testfail)
4744
4745	# time scale for potentially multiple datasets
4746	t0 = testruns[0].start
4747	tMax = testruns[-1].end
4748	tTotal = tMax - t0
4749
4750	# determine the maximum number of rows we need to draw
4751	fulllist = []
4752	threadlist = []
4753	pscnt = 0
4754	devcnt = 0
4755	for data in testruns:
4756		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4757		for group in data.devicegroups:
4758			devlist = []
4759			for phase in group:
4760				for devname in sorted(data.tdevlist[phase]):
4761					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4762					devlist.append(d)
4763					if d.isa('kth'):
4764						threadlist.append(d)
4765					else:
4766						if d.isa('ps'):
4767							pscnt += 1
4768						else:
4769							devcnt += 1
4770						fulllist.append(d)
4771			if sysvals.mixedphaseheight:
4772				devtl.getPhaseRows(devlist)
4773	if not sysvals.mixedphaseheight:
4774		if len(threadlist) > 0 and len(fulllist) > 0:
4775			if pscnt > 0 and devcnt > 0:
4776				msg = 'user processes & device pm callbacks'
4777			elif pscnt > 0:
4778				msg = 'user processes'
4779			else:
4780				msg = 'device pm callbacks'
4781			d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4782			fulllist.insert(0, d)
4783		devtl.getPhaseRows(fulllist)
4784		if len(threadlist) > 0:
4785			d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4786			threadlist.insert(0, d)
4787			devtl.getPhaseRows(threadlist, devtl.rows)
4788	devtl.calcTotalRows()
4789
4790	# draw the full timeline
4791	devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4792	for data in testruns:
4793		# draw each test run and block chronologically
4794		phases = {'suspend':[],'resume':[]}
4795		for phase in data.sortedPhases():
4796			if data.dmesg[phase]['start'] >= data.tSuspended:
4797				phases['resume'].append(phase)
4798			else:
4799				phases['suspend'].append(phase)
4800		# now draw the actual timeline blocks
4801		for dir in phases:
4802			# draw suspend and resume blocks separately
4803			bname = '%s%d' % (dir[0], data.testnumber)
4804			if dir == 'suspend':
4805				m0 = data.start
4806				mMax = data.tSuspended
4807				left = '%f' % (((m0-t0)*100.0)/tTotal)
4808			else:
4809				m0 = data.tSuspended
4810				mMax = data.end
4811				# in an x2 run, remove any gap between blocks
4812				if len(testruns) > 1 and data.testnumber == 0:
4813					mMax = testruns[1].start
4814				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4815			mTotal = mMax - m0
4816			# if a timeline block is 0 length, skip altogether
4817			if mTotal == 0:
4818				continue
4819			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4820			devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4821			for b in phases[dir]:
4822				# draw the phase color background
4823				phase = data.dmesg[b]
4824				length = phase['end']-phase['start']
4825				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4826				width = '%f' % ((length*100.0)/mTotal)
4827				devtl.html += devtl.html_phase.format(left, width, \
4828					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4829					data.dmesg[b]['color'], '')
4830			for e in data.errorinfo[dir]:
4831				# draw red lines for any kernel errors found
4832				type, t, idx1, idx2 = e
4833				id = '%d_%d' % (idx1, idx2)
4834				right = '%f' % (((mMax-t)*100.0)/mTotal)
4835				devtl.html += html_error.format(right, id, type)
4836			for b in phases[dir]:
4837				# draw the devices for this phase
4838				phaselist = data.dmesg[b]['list']
4839				for d in sorted(data.tdevlist[b]):
4840					dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0]
4841					name, dev = dname, phaselist[d]
4842					drv = xtraclass = xtrainfo = xtrastyle = ''
4843					if 'htmlclass' in dev:
4844						xtraclass = dev['htmlclass']
4845					if 'color' in dev:
4846						xtrastyle = 'background:%s;' % dev['color']
4847					if(d in sysvals.devprops):
4848						name = sysvals.devprops[d].altName(d)
4849						xtraclass = sysvals.devprops[d].xtraClass()
4850						xtrainfo = sysvals.devprops[d].xtraInfo()
4851					elif xtraclass == ' kth':
4852						xtrainfo = ' kernel_thread'
4853					if('drv' in dev and dev['drv']):
4854						drv = ' {%s}' % dev['drv']
4855					rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4856					rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4857					top = '%.3f' % (rowtop + devtl.scaleH)
4858					left = '%f' % (((dev['start']-m0)*100)/mTotal)
4859					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4860					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4861					title = name+drv+xtrainfo+length
4862					if sysvals.suspendmode == 'command':
4863						title += sysvals.testcommand
4864					elif xtraclass == ' ps':
4865						if 'suspend' in b:
4866							title += 'pre_suspend_process'
4867						else:
4868							title += 'post_resume_process'
4869					else:
4870						title += b
4871					devtl.html += devtl.html_device.format(dev['id'], \
4872						title, left, top, '%.3f'%rowheight, width, \
4873						dname+drv, xtraclass, xtrastyle)
4874					if('cpuexec' in dev):
4875						for t in sorted(dev['cpuexec']):
4876							start, end = t
 
 
 
4877							height = '%.3f' % (rowheight/3)
4878							top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4879							left = '%f' % (((start-m0)*100)/mTotal)
4880							width = '%f' % ((end-start)*100/mTotal)
4881							color = 'rgba(255, 0, 0, %f)' % dev['cpuexec'][t]
4882							devtl.html += \
4883								html_cpuexec.format(left, top, height, width, color)
4884					if('src' not in dev):
4885						continue
4886					# draw any trace events for this device
4887					for e in dev['src']:
4888						if e.length == 0:
4889							continue
4890						height = '%.3f' % devtl.rowH
4891						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4892						left = '%f' % (((e.time-m0)*100)/mTotal)
4893						width = '%f' % (e.length*100/mTotal)
4894						xtrastyle = ''
4895						if e.color:
4896							xtrastyle = 'background:%s;' % e.color
4897						devtl.html += \
4898							html_traceevent.format(e.title(), \
4899								left, top, height, width, e.text(), '', xtrastyle)
4900			# draw the time scale, try to make the number of labels readable
4901			devtl.createTimeScale(m0, mMax, tTotal, dir)
4902			devtl.html += '</div>\n'
4903
4904	# timeline is finished
4905	devtl.html += '</div>\n</div>\n'
4906
4907	# draw a legend which describes the phases by color
4908	if sysvals.suspendmode != 'command':
4909		phasedef = testruns[-1].phasedef
4910		devtl.html += '<div class="legend">\n'
4911		pdelta = 100.0/len(phasedef.keys())
4912		pmargin = pdelta / 4.0
4913		for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4914			id, p = '', phasedef[phase]
4915			for word in phase.split('_'):
4916				id += word[0]
4917			order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4918			name = phase.replace('_', ' &nbsp;')
4919			devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4920		devtl.html += '</div>\n'
4921
4922	hf = open(sysvals.htmlfile, 'w')
4923	addCSS(hf, sysvals, len(testruns), kerror)
4924
4925	# write the device timeline
4926	hf.write(devtl.html)
4927	hf.write('<div id="devicedetailtitle"></div>\n')
4928	hf.write('<div id="devicedetail" style="display:none;">\n')
4929	# draw the colored boxes for the device detail section
4930	for data in testruns:
4931		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4932		pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4933		hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4934			'0', '0', pscolor))
4935		for b in data.sortedPhases():
4936			phase = data.dmesg[b]
4937			length = phase['end']-phase['start']
4938			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4939			width = '%.3f' % ((length*100.0)/tTotal)
4940			hf.write(devtl.html_phaselet.format(b, left, width, \
4941				data.dmesg[b]['color']))
4942		hf.write(devtl.html_phaselet.format('post_resume_process', \
4943			'0', '0', pscolor))
4944		if sysvals.suspendmode == 'command':
4945			hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4946		hf.write('</div>\n')
4947	hf.write('</div>\n')
4948
4949	# write the ftrace data (callgraph)
4950	if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4951		data = testruns[sysvals.cgtest]
4952	else:
4953		data = testruns[-1]
4954	if sysvals.usecallgraph:
4955		addCallgraphs(sysvals, hf, data)
4956
4957	# add the test log as a hidden div
4958	if sysvals.testlog and sysvals.logmsg:
4959		hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4960	# add the dmesg log as a hidden div
4961	if sysvals.dmesglog and sysvals.dmesgfile:
4962		hf.write('<div id="dmesglog" style="display:none;">\n')
4963		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4964		for line in lf:
4965			line = line.replace('<', '&lt').replace('>', '&gt')
4966			hf.write(line)
4967		lf.close()
4968		hf.write('</div>\n')
4969	# add the ftrace log as a hidden div
4970	if sysvals.ftracelog and sysvals.ftracefile:
4971		hf.write('<div id="ftracelog" style="display:none;">\n')
4972		lf = sysvals.openlog(sysvals.ftracefile, 'r')
4973		for line in lf:
4974			hf.write(line)
4975		lf.close()
4976		hf.write('</div>\n')
4977
4978	# write the footer and close
4979	addScriptCode(hf, testruns)
4980	hf.write('</body>\n</html>\n')
4981	hf.close()
4982	return True
4983
4984def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4985	kernel = sv.stamp['kernel']
4986	host = sv.hostname[0].upper()+sv.hostname[1:]
4987	mode = sv.suspendmode
4988	if sv.suspendmode in suspendmodename:
4989		mode = suspendmodename[sv.suspendmode]
4990	title = host+' '+mode+' '+kernel
4991
4992	# various format changes by flags
4993	cgchk = 'checked'
4994	cgnchk = 'not(:checked)'
4995	if sv.cgexp:
4996		cgchk = 'not(:checked)'
4997		cgnchk = 'checked'
4998
4999	hoverZ = 'z-index:8;'
5000	if sv.usedevsrc:
5001		hoverZ = ''
5002
5003	devlistpos = 'absolute'
5004	if testcount > 1:
5005		devlistpos = 'relative'
5006
5007	scaleTH = 20
5008	if kerror:
5009		scaleTH = 60
5010
5011	# write the html header first (html head, css code, up to body start)
5012	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
5013	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
5014	<title>'+title+'</title>\n\
5015	<style type=\'text/css\'>\n\
5016		body {overflow-y:scroll;}\n\
5017		.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
5018		.stamp.sysinfo {font:10px Arial;}\n\
5019		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
5020		.callgraph article * {padding-left:28px;}\n\
5021		h1 {color:black;font:bold 30px Times;}\n\
5022		t0 {color:black;font:bold 30px Times;}\n\
5023		t1 {color:black;font:30px Times;}\n\
5024		t2 {color:black;font:25px Times;}\n\
5025		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
5026		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
5027		cS {font:bold 13px Times;}\n\
5028		table {width:100%;}\n\
5029		.gray {background:rgba(80,80,80,0.1);}\n\
5030		.green {background:rgba(204,255,204,0.4);}\n\
5031		.purple {background:rgba(128,0,128,0.2);}\n\
5032		.yellow {background:rgba(255,255,204,0.4);}\n\
5033		.blue {background:rgba(169,208,245,0.4);}\n\
5034		.time1 {font:22px Arial;border:1px solid;}\n\
5035		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
5036		.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
5037		td {text-align:center;}\n\
5038		r {color:#500000;font:15px Tahoma;}\n\
5039		n {color:#505050;font:15px Tahoma;}\n\
5040		.tdhl {color:red;}\n\
5041		.hide {display:none;}\n\
5042		.pf {display:none;}\n\
5043		.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\
5044		.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\
5045		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
5046		.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
5047		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
5048		.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\
5049		.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
5050		.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5051		.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
5052		.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5053		.hover.sync {background:white;}\n\
5054		.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
5055		.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
5056		.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\
5057		.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
5058		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
5059		.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
5060		.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
5061		.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
5062		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
5063		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
5064		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
5065		.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
5066		.devlist {position:'+devlistpos+';width:190px;}\n\
5067		a:link {color:white;text-decoration:none;}\n\
5068		a:visited {color:white;}\n\
5069		a:hover {color:white;}\n\
5070		a:active {color:white;}\n\
5071		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
5072		#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
5073		.tblock {position:absolute;height:100%;background:#ddd;}\n\
5074		.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
5075		.bg {z-index:1;}\n\
5076'+extra+'\
5077	</style>\n</head>\n<body>\n'
5078	hf.write(html_header)
5079
5080# Function: addScriptCode
5081# Description:
5082#	 Adds the javascript code to the output html
5083# Arguments:
5084#	 hf: the open html file pointer
5085#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
5086def addScriptCode(hf, testruns):
5087	t0 = testruns[0].start * 1000
5088	tMax = testruns[-1].end * 1000
5089	hf.write('<script type="text/javascript">\n');
5090	# create an array in javascript memory with the device details
5091	detail = '	var devtable = [];\n'
5092	for data in testruns:
5093		topo = data.deviceTopology()
5094		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
5095	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
5096	# add the code which will manipulate the data in the browser
5097	hf.write(detail);
5098	script_code = r"""	var resolution = -1;
5099	var dragval = [0, 0];
5100	function redrawTimescale(t0, tMax, tS) {
5101		var rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">';
5102		var tTotal = tMax - t0;
5103		var list = document.getElementsByClassName("tblock");
5104		for (var i = 0; i < list.length; i++) {
5105			var timescale = list[i].getElementsByClassName("timescale")[0];
5106			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);
5107			var mTotal = tTotal*parseFloat(list[i].style.width)/100;
5108			var mMax = m0 + mTotal;
5109			var html = "";
5110			var divTotal = Math.floor(mTotal/tS) + 1;
5111			if(divTotal > 1000) continue;
5112			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;
5113			var pos = 0.0, val = 0.0;
5114			for (var j = 0; j < divTotal; j++) {
5115				var htmlline = "";
5116				var mode = list[i].id[5];
5117				if(mode == "s") {
5118					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;
5119					val = (j-divTotal+1)*tS;
5120					if(j == divTotal - 1)
5121						htmlline = '<div class="t" style="right:'+pos+'%"><cS>S&rarr;</cS></div>';
5122					else
5123						htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>';
5124				} else {
5125					pos = 100 - (((j)*tS*100)/mTotal);
5126					val = (j)*tS;
5127					htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>';
5128					if(j == 0)
5129						if(mode == "r")
5130							htmlline = rline+"<cS>&larr;R</cS></div>";
5131						else
5132							htmlline = rline+"<cS>0ms</div>";
5133				}
5134				html += htmlline;
5135			}
5136			timescale.innerHTML = html;
5137		}
5138	}
5139	function zoomTimeline() {
5140		var dmesg = document.getElementById("dmesg");
5141		var zoombox = document.getElementById("dmesgzoombox");
5142		var left = zoombox.scrollLeft;
5143		var val = parseFloat(dmesg.style.width);
5144		var newval = 100;
5145		var sh = window.outerWidth / 2;
5146		if(this.id == "zoomin") {
5147			newval = val * 1.2;
5148			if(newval > 910034) newval = 910034;
5149			dmesg.style.width = newval+"%";
5150			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;
5151		} else if (this.id == "zoomout") {
5152			newval = val / 1.2;
5153			if(newval < 100) newval = 100;
5154			dmesg.style.width = newval+"%";
5155			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;
5156		} else {
5157			zoombox.scrollLeft = 0;
5158			dmesg.style.width = "100%";
5159		}
5160		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];
5161		var t0 = bounds[0];
5162		var tMax = bounds[1];
5163		var tTotal = tMax - t0;
5164		var wTotal = tTotal * 100.0 / newval;
5165		var idx = 7*window.innerWidth/1100;
5166		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);
5167		if(i >= tS.length) i = tS.length - 1;
5168		if(tS[i] == resolution) return;
5169		resolution = tS[i];
5170		redrawTimescale(t0, tMax, tS[i]);
5171	}
5172	function deviceName(title) {
5173		var name = title.slice(0, title.indexOf(" ("));
5174		return name;
5175	}
5176	function deviceHover() {
5177		var name = deviceName(this.title);
5178		var dmesg = document.getElementById("dmesg");
5179		var dev = dmesg.getElementsByClassName("thread");
5180		var cpu = -1;
5181		if(name.match("CPU_ON\[[0-9]*\]"))
5182			cpu = parseInt(name.slice(7));
5183		else if(name.match("CPU_OFF\[[0-9]*\]"))
5184			cpu = parseInt(name.slice(8));
5185		for (var i = 0; i < dev.length; i++) {
5186			dname = deviceName(dev[i].title);
5187			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));
5188			if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) ||
5189				(name == dname))
5190			{
5191				dev[i].className = "hover "+cname;
5192			} else {
5193				dev[i].className = cname;
5194			}
5195		}
5196	}
5197	function deviceUnhover() {
5198		var dmesg = document.getElementById("dmesg");
5199		var dev = dmesg.getElementsByClassName("thread");
5200		for (var i = 0; i < dev.length; i++) {
5201			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));
5202		}
5203	}
5204	function deviceTitle(title, total, cpu) {
5205		var prefix = "Total";
5206		if(total.length > 3) {
5207			prefix = "Average";
5208			total[1] = (total[1]+total[3])/2;
5209			total[2] = (total[2]+total[4])/2;
5210		}
5211		var devtitle = document.getElementById("devicedetailtitle");
5212		var name = deviceName(title);
5213		if(cpu >= 0) name = "CPU"+cpu;
5214		var driver = "";
5215		var tS = "<t2>(</t2>";
5216		var tR = "<t2>)</t2>";
5217		if(total[1] > 0)
5218			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";
5219		if(total[2] > 0)
5220			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";
5221		var s = title.indexOf("{");
5222		var e = title.indexOf("}");
5223		if((s >= 0) && (e >= 0))
5224			driver = title.slice(s+1, e) + " <t1>@</t1> ";
5225		if(total[1] > 0 && total[2] > 0)
5226			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;
5227		else
5228			devtitle.innerHTML = "<t0>"+title+"</t0>";
5229		return name;
5230	}
5231	function deviceDetail() {
5232		var devinfo = document.getElementById("devicedetail");
5233		devinfo.style.display = "block";
5234		var name = deviceName(this.title);
5235		var cpu = -1;
5236		if(name.match("CPU_ON\[[0-9]*\]"))
5237			cpu = parseInt(name.slice(7));
5238		else if(name.match("CPU_OFF\[[0-9]*\]"))
5239			cpu = parseInt(name.slice(8));
5240		var dmesg = document.getElementById("dmesg");
5241		var dev = dmesg.getElementsByClassName("thread");
5242		var idlist = [];
5243		var pdata = [[]];
5244		if(document.getElementById("devicedetail1"))
5245			pdata = [[], []];
5246		var pd = pdata[0];
5247		var total = [0.0, 0.0, 0.0];
5248		for (var i = 0; i < dev.length; i++) {
5249			dname = deviceName(dev[i].title);
5250			if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) ||
5251				(name == dname))
5252			{
5253				idlist[idlist.length] = dev[i].id;
5254				var tidx = 1;
5255				if(dev[i].id[0] == "a") {
5256					pd = pdata[0];
5257				} else {
5258					if(pdata.length == 1) pdata[1] = [];
5259					if(total.length == 3) total[3]=total[4]=0.0;
5260					pd = pdata[1];
5261					tidx = 3;
5262				}
5263				var info = dev[i].title.split(" ");
5264				var pname = info[info.length-1];
5265				var length = parseFloat(info[info.length-3].slice(1));
5266				if (pname in pd)
5267					pd[pname] += length;
5268				else
5269					pd[pname] = length;
5270				total[0] += length;
5271				if(pname.indexOf("suspend") >= 0)
5272					total[tidx] += length;
5273				else
5274					total[tidx+1] += length;
5275			}
5276		}
5277		var devname = deviceTitle(this.title, total, cpu);
5278		var left = 0.0;
5279		for (var t = 0; t < pdata.length; t++) {
5280			pd = pdata[t];
5281			devinfo = document.getElementById("devicedetail"+t);
5282			var phases = devinfo.getElementsByClassName("phaselet");
5283			for (var i = 0; i < phases.length; i++) {
5284				if(phases[i].id in pd) {
5285					var w = 100.0*pd[phases[i].id]/total[0];
5286					var fs = 32;
5287					if(w < 8) fs = 4*w | 0;
5288					var fs2 = fs*3/4;
5289					phases[i].style.width = w+"%";
5290					phases[i].style.left = left+"%";
5291					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";
5292					left += w;
5293					var time = "<t4 style=\"font-size:"+fs+"px\">"+pd[phases[i].id].toFixed(3)+" ms<br></t4>";
5294					var pname = "<t3 style=\"font-size:"+fs2+"px\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";
5295					phases[i].innerHTML = time+pname;
5296				} else {
5297					phases[i].style.width = "0%";
5298					phases[i].style.left = left+"%";
5299				}
5300			}
5301		}
5302		if(typeof devstats !== 'undefined')
5303			callDetail(this.id, this.title);
5304		var cglist = document.getElementById("callgraphs");
5305		if(!cglist) return;
5306		var cg = cglist.getElementsByClassName("atop");
5307		if(cg.length < 10) return;
5308		for (var i = 0; i < cg.length; i++) {
5309			cgid = cg[i].id.split("x")[0]
5310			if(idlist.indexOf(cgid) >= 0) {
5311				cg[i].style.display = "block";
5312			} else {
5313				cg[i].style.display = "none";
5314			}
5315		}
5316	}
5317	function callDetail(devid, devtitle) {
5318		if(!(devid in devstats) || devstats[devid].length < 1)
5319			return;
5320		var list = devstats[devid];
5321		var tmp = devtitle.split(" ");
5322		var name = tmp[0], phase = tmp[tmp.length-1];
5323		var dd = document.getElementById(phase);
5324		var total = parseFloat(tmp[1].slice(1));
5325		var mlist = [];
5326		var maxlen = 0;
5327		var info = []
5328		for(var i in list) {
5329			if(list[i][0] == "@") {
5330				info = list[i].split("|");
5331				continue;
5332			}
5333			var tmp = list[i].split("|");
5334			var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);
5335			var p = (t*100.0/total).toFixed(2);
5336			mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];
5337			if(f.length > maxlen)
5338				maxlen = f.length;
5339		}
5340		var pad = 5;
5341		if(mlist.length == 0) pad = 30;
5342		var html = '<div style="padding-top:'+pad+'px"><t3> <b>'+name+':</b>';
5343		if(info.length > 2)
5344			html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";
5345		if(info.length > 3)
5346			html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";
5347		if(info.length > 4)
5348			html += ", return=<b>"+info[4]+"</b>";
5349		html += "</t3></div>";
5350		if(mlist.length > 0) {
5351			html += '<table class=fstat style="padding-top:'+(maxlen*5)+'px;"><tr><th>Function</th>';
5352			for(var i in mlist)
5353				html += "<td class=vt>"+mlist[i][0]+"</td>";
5354			html += "</tr><tr><th>Calls</th>";
5355			for(var i in mlist)
5356				html += "<td>"+mlist[i][1]+"</td>";
5357			html += "</tr><tr><th>Time(ms)</th>";
5358			for(var i in mlist)
5359				html += "<td>"+mlist[i][2]+"</td>";
5360			html += "</tr><tr><th>Percent</th>";
5361			for(var i in mlist)
5362				html += "<td>"+mlist[i][3]+"</td>";
5363			html += "</tr></table>";
5364		}
5365		dd.innerHTML = html;
5366		var height = (maxlen*5)+100;
5367		dd.style.height = height+"px";
5368		document.getElementById("devicedetail").style.height = height+"px";
5369	}
5370	function callSelect() {
5371		var cglist = document.getElementById("callgraphs");
5372		if(!cglist) return;
5373		var cg = cglist.getElementsByClassName("atop");
5374		for (var i = 0; i < cg.length; i++) {
5375			if(this.id == cg[i].id) {
5376				cg[i].style.display = "block";
5377			} else {
5378				cg[i].style.display = "none";
5379			}
5380		}
5381	}
5382	function devListWindow(e) {
5383		var win = window.open();
5384		var html = "<title>"+e.target.innerHTML+"</title>"+
5385			"<style type=\"text/css\">"+
5386			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+
5387			"</style>"
5388		var dt = devtable[0];
5389		if(e.target.id != "devlist1")
5390			dt = devtable[1];
5391		win.document.write(html+dt);
5392	}
5393	function errWindow() {
5394		var range = this.id.split("_");
5395		var idx1 = parseInt(range[0]);
5396		var idx2 = parseInt(range[1]);
5397		var win = window.open();
5398		var log = document.getElementById("dmesglog");
5399		var title = "<title>dmesg log</title>";
5400		var text = log.innerHTML.split("\n");
5401		var html = "";
5402		for(var i = 0; i < text.length; i++) {
5403			if(i == idx1) {
5404				html += "<e id=target>"+text[i]+"</e>\n";
5405			} else if(i > idx1 && i <= idx2) {
5406				html += "<e>"+text[i]+"</e>\n";
5407			} else {
5408				html += text[i]+"\n";
5409			}
5410		}
5411		win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");
5412		win.location.hash = "#target";
5413		win.document.close();
5414	}
5415	function logWindow(e) {
5416		var name = e.target.id.slice(4);
5417		var win = window.open();
5418		var log = document.getElementById(name+"log");
5419		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";
5420		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");
5421		win.document.close();
5422	}
5423	function onMouseDown(e) {
5424		dragval[0] = e.clientX;
5425		dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;
5426		document.onmousemove = onMouseMove;
5427	}
5428	function onMouseMove(e) {
5429		var zoombox = document.getElementById("dmesgzoombox");
5430		zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;
5431	}
5432	function onMouseUp(e) {
5433		document.onmousemove = null;
5434	}
5435	function onKeyPress(e) {
5436		var c = e.charCode;
5437		if(c != 42 && c != 43 && c != 45) return;
5438		var click = document.createEvent("Events");
5439		click.initEvent("click", true, false);
5440		if(c == 43)
5441			document.getElementById("zoomin").dispatchEvent(click);
5442		else if(c == 45)
5443			document.getElementById("zoomout").dispatchEvent(click);
5444		else if(c == 42)
5445			document.getElementById("zoomdef").dispatchEvent(click);
5446	}
5447	window.addEventListener("resize", function () {zoomTimeline();});
5448	window.addEventListener("load", function () {
5449		var dmesg = document.getElementById("dmesg");
5450		dmesg.style.width = "100%"
5451		dmesg.onmousedown = onMouseDown;
5452		document.onmouseup = onMouseUp;
5453		document.onkeypress = onKeyPress;
5454		document.getElementById("zoomin").onclick = zoomTimeline;
5455		document.getElementById("zoomout").onclick = zoomTimeline;
5456		document.getElementById("zoomdef").onclick = zoomTimeline;
5457		var list = document.getElementsByClassName("err");
5458		for (var i = 0; i < list.length; i++)
5459			list[i].onclick = errWindow;
5460		var list = document.getElementsByClassName("logbtn");
5461		for (var i = 0; i < list.length; i++)
5462			list[i].onclick = logWindow;
5463		list = document.getElementsByClassName("devlist");
5464		for (var i = 0; i < list.length; i++)
5465			list[i].onclick = devListWindow;
5466		var dev = dmesg.getElementsByClassName("thread");
5467		for (var i = 0; i < dev.length; i++) {
5468			dev[i].onclick = deviceDetail;
5469			dev[i].onmouseover = deviceHover;
5470			dev[i].onmouseout = deviceUnhover;
5471		}
5472		var dev = dmesg.getElementsByClassName("srccall");
5473		for (var i = 0; i < dev.length; i++)
5474			dev[i].onclick = callSelect;
5475		zoomTimeline();
5476	});
5477</script> """
5478	hf.write(script_code);
5479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5480# Function: executeSuspend
5481# Description:
5482#	 Execute system suspend through the sysfs interface, then copy the output
5483#	 dmesg and ftrace files to the test output directory.
5484def executeSuspend(quiet=False):
5485	sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor()
5486	if sv.wifi:
5487		wifi = sv.checkWifi()
5488		sv.dlog('wifi check, connected device is "%s"' % wifi)
5489	testdata = []
5490	# run these commands to prepare the system for suspend
5491	if sv.display:
5492		if not quiet:
5493			pprint('SET DISPLAY TO %s' % sv.display.upper())
5494		ret = sv.displayControl(sv.display)
5495		sv.dlog('xset display %s, ret = %d' % (sv.display, ret))
5496		time.sleep(1)
5497	if sv.sync:
5498		if not quiet:
5499			pprint('SYNCING FILESYSTEMS')
5500		sv.dlog('syncing filesystems')
5501		call('sync', shell=True)
5502	sv.dlog('read dmesg')
5503	sv.initdmesg()
5504	sv.dlog('cmdinfo before')
5505	sv.cmdinfo(True)
5506	sv.start(pm)
 
 
 
 
 
5507	# execute however many s/r runs requested
5508	for count in range(1,sv.execcount+1):
5509		# x2delay in between test runs
5510		if(count > 1 and sv.x2delay > 0):
5511			sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker')
5512			time.sleep(sv.x2delay/1000.0)
5513			sv.fsetVal('WAIT END', 'trace_marker')
5514		# start message
5515		if sv.testcommand != '':
5516			pprint('COMMAND START')
5517		else:
5518			if(sv.rtcwake):
5519				pprint('SUSPEND START')
5520			else:
5521				pprint('SUSPEND START (press a key to resume)')
5522		# set rtcwake
5523		if(sv.rtcwake):
5524			if not quiet:
5525				pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime)
5526			sv.dlog('enable RTC wake alarm')
5527			sv.rtcWakeAlarmOn()
5528		# start of suspend trace marker
5529		sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
 
5530		# predelay delay
5531		if(count == 1 and sv.predelay > 0):
5532			sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
5533			time.sleep(sv.predelay/1000.0)
5534			sv.fsetVal('WAIT END', 'trace_marker')
5535		# initiate suspend or command
5536		sv.dlog('system executing a suspend')
5537		tdata = {'error': ''}
5538		if sv.testcommand != '':
5539			res = call(sv.testcommand+' 2>&1', shell=True);
5540			if res != 0:
5541				tdata['error'] = 'cmd returned %d' % res
5542		else:
5543			s0ixready = sv.s0ixSupport()
5544			mode = sv.suspendmode
5545			if sv.memmode and os.path.exists(sv.mempowerfile):
5546				mode = 'mem'
5547				sv.testVal(sv.mempowerfile, 'radio', sv.memmode)
5548			if sv.diskmode and os.path.exists(sv.diskpowerfile):
 
 
5549				mode = 'disk'
5550				sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode)
5551			if sv.acpidebug:
5552				sv.testVal(sv.acpipath, 'acpi', '0xe')
5553			if ((mode == 'freeze') or (sv.memmode == 's2idle')) \
5554				and sv.haveTurbostat():
5555				# execution will pause here
5556				retval, turbo = sv.turbostat(s0ixready)
5557				if retval != 0:
5558					tdata['error'] ='turbostat returned %d' % retval
5559				if turbo:
5560					tdata['turbo'] = turbo
5561			else:
5562				pf = open(sv.powerfile, 'w')
5563				pf.write(mode)
5564				# execution will pause here
5565				try:
5566					pf.flush()
5567					pf.close()
5568				except Exception as e:
5569					tdata['error'] = str(e)
5570		sv.fsetVal('CMD COMPLETE', 'trace_marker')
5571		sv.dlog('system returned')
5572		# reset everything
5573		sv.testVal('restoreall')
5574		if(sv.rtcwake):
5575			sv.dlog('disable RTC wake alarm')
5576			sv.rtcWakeAlarmOff()
5577		# postdelay delay
5578		if(count == sv.execcount and sv.postdelay > 0):
5579			sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker')
5580			time.sleep(sv.postdelay/1000.0)
5581			sv.fsetVal('WAIT END', 'trace_marker')
5582		# return from suspend
5583		pprint('RESUME COMPLETE')
5584		if(count < sv.execcount):
5585			sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5586		elif(not sv.wifitrace):
5587			sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5588			sv.stop(pm)
5589		if sv.wifi and wifi:
5590			tdata['wifi'] = sv.pollWifi(wifi)
5591			sv.dlog('wifi check, %s' % tdata['wifi'])
5592		if(count == sv.execcount and sv.wifitrace):
5593			sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5594			sv.stop(pm)
5595		if sv.netfix:
5596			tdata['netfix'] = sv.netfixon()
5597			sv.dlog('netfix, %s' % tdata['netfix'])
5598		if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
5599			sv.dlog('read the ACPI FPDT')
5600			tdata['fw'] = getFPDT(False)
5601		testdata.append(tdata)
5602	sv.dlog('cmdinfo after')
5603	cmdafter = sv.cmdinfo(False)
 
 
 
 
5604	# grab a copy of the dmesg output
5605	if not quiet:
5606		pprint('CAPTURING DMESG')
5607	sv.getdmesg(testdata)
5608	# grab a copy of the ftrace output
5609	if sv.useftrace:
5610		if not quiet:
5611			pprint('CAPTURING TRACE')
5612		op = sv.writeDatafileHeader(sv.ftracefile, testdata)
5613		fp = open(tp+'trace', 'rb')
5614		op.write(ascii(fp.read()))
 
5615		op.close()
5616		sv.fsetVal('', 'trace')
5617		sv.platforminfo(cmdafter)
5618
5619def readFile(file):
5620	if os.path.islink(file):
5621		return os.readlink(file).split('/')[-1]
5622	else:
5623		return sysvals.getVal(file).strip()
5624
5625# Function: ms2nice
5626# Description:
5627#	 Print out a very concise time string in minutes and seconds
5628# Output:
5629#	 The time string, e.g. "1901m16s"
5630def ms2nice(val):
5631	val = int(val)
5632	h = val // 3600000
5633	m = (val // 60000) % 60
5634	s = (val // 1000) % 60
5635	if h > 0:
5636		return '%d:%02d:%02d' % (h, m, s)
5637	if m > 0:
5638		return '%02d:%02d' % (m, s)
5639	return '%ds' % s
5640
5641def yesno(val):
5642	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5643		'active':'A', 'suspended':'S', 'suspending':'S'}
5644	if val not in list:
5645		return ' '
5646	return list[val]
5647
5648# Function: deviceInfo
5649# Description:
5650#	 Detect all the USB hosts and devices currently connected and add
5651#	 a list of USB device names to sysvals for better timeline readability
5652def deviceInfo(output=''):
5653	if not output:
5654		pprint('LEGEND\n'\
5655		'---------------------------------------------------------------------------------------------\n'\
5656		'  A = async/sync PM queue (A/S)               C = runtime active children\n'\
5657		'  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)\n'\
5658		'  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)\n'\
5659		'  U = runtime usage count\n'\
5660		'---------------------------------------------------------------------------------------------\n'\
5661		'DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND\n'\
5662		'---------------------------------------------------------------------------------------------')
5663
5664	res = []
5665	tgtval = 'runtime_status'
5666	lines = dict()
5667	for dirname, dirnames, filenames in os.walk('/sys/devices'):
5668		if(not re.match(r'.*/power', dirname) or
5669			'control' not in filenames or
5670			tgtval not in filenames):
5671			continue
5672		name = ''
5673		dirname = dirname[:-6]
5674		device = dirname.split('/')[-1]
5675		power = dict()
5676		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5677		# only list devices which support runtime suspend
5678		if power[tgtval] not in ['active', 'suspended', 'suspending']:
5679			continue
5680		for i in ['product', 'driver', 'subsystem']:
5681			file = '%s/%s' % (dirname, i)
5682			if os.path.exists(file):
5683				name = readFile(file)
5684				break
5685		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5686			'runtime_active_kids', 'runtime_active_time',
5687			'runtime_suspended_time']:
5688			if i in filenames:
5689				power[i] = readFile('%s/power/%s' % (dirname, i))
5690		if output:
5691			if power['control'] == output:
5692				res.append('%s/power/control' % dirname)
5693			continue
5694		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5695			(device[:26], name[:26],
5696			yesno(power['async']), \
5697			yesno(power['control']), \
5698			yesno(power['runtime_status']), \
5699			power['runtime_usage'], \
5700			power['runtime_active_kids'], \
5701			ms2nice(power['runtime_active_time']), \
5702			ms2nice(power['runtime_suspended_time']))
5703	for i in sorted(lines):
5704		print(lines[i])
5705	return res
5706
5707# Function: getModes
5708# Description:
5709#	 Determine the supported power modes on this system
5710# Output:
5711#	 A string list of the available modes
5712def getModes():
5713	modes = []
5714	if(os.path.exists(sysvals.powerfile)):
5715		fp = open(sysvals.powerfile, 'r')
5716		modes = fp.read().split()
5717		fp.close()
5718	if(os.path.exists(sysvals.mempowerfile)):
5719		deep = False
5720		fp = open(sysvals.mempowerfile, 'r')
5721		for m in fp.read().split():
5722			memmode = m.strip('[]')
5723			if memmode == 'deep':
5724				deep = True
5725			else:
5726				modes.append('mem-%s' % memmode)
5727		fp.close()
5728		if 'mem' in modes and not deep:
5729			modes.remove('mem')
5730	if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5731		fp = open(sysvals.diskpowerfile, 'r')
5732		for m in fp.read().split():
5733			modes.append('disk-%s' % m.strip('[]'))
5734		fp.close()
5735	return modes
5736
5737def dmidecode_backup(out, fatal=False):
5738	cpath, spath, info = '/proc/cpuinfo', '/sys/class/dmi/id', {
5739		'bios-vendor': 'bios_vendor',
5740		'bios-version': 'bios_version',
5741		'bios-release-date': 'bios_date',
5742		'system-manufacturer': 'sys_vendor',
5743		'system-product-name': 'product_name',
5744		'system-version': 'product_version',
5745		'system-serial-number': 'product_serial',
5746		'baseboard-manufacturer': 'board_vendor',
5747		'baseboard-product-name': 'board_name',
5748		'baseboard-version': 'board_version',
5749		'baseboard-serial-number': 'board_serial',
5750		'chassis-manufacturer': 'chassis_vendor',
5751		'chassis-version': 'chassis_version',
5752		'chassis-serial-number': 'chassis_serial',
5753	}
5754	for key in info:
5755		if key not in out:
5756			val = sysvals.getVal(os.path.join(spath, info[key])).strip()
5757			if val and val.lower() != 'to be filled by o.e.m.':
5758				out[key] = val
5759	if 'processor-version' not in out and os.path.exists(cpath):
5760		with open(cpath, 'r') as fp:
5761			for line in fp:
5762				m = re.match(r'^model\s*name\s*\:\s*(?P<c>.*)', line)
5763				if m:
5764					out['processor-version'] = m.group('c').strip()
5765					break
5766	if fatal and len(out) < 1:
5767		doError('dmidecode failed to get info from %s or %s' % \
5768			(sysvals.mempath, spath))
5769	return out
5770
5771# Function: dmidecode
5772# Description:
5773#	 Read the bios tables and pull out system info
5774# Arguments:
5775#	 mempath: /dev/mem or custom mem path
5776#	 fatal: True to exit on error, False to return empty dict
5777# Output:
5778#	 A dict object with all available key/values
5779def dmidecode(mempath, fatal=False):
5780	out = dict()
5781	if(not (os.path.exists(mempath) and os.access(mempath, os.R_OK))):
5782		return dmidecode_backup(out, fatal)
5783
5784	# the list of values to retrieve, with hardcoded (type, idx)
5785	info = {
5786		'bios-vendor': (0, 4),
5787		'bios-version': (0, 5),
5788		'bios-release-date': (0, 8),
5789		'system-manufacturer': (1, 4),
5790		'system-product-name': (1, 5),
5791		'system-version': (1, 6),
5792		'system-serial-number': (1, 7),
5793		'baseboard-manufacturer': (2, 4),
5794		'baseboard-product-name': (2, 5),
5795		'baseboard-version': (2, 6),
5796		'baseboard-serial-number': (2, 7),
5797		'chassis-manufacturer': (3, 4),
 
5798		'chassis-version': (3, 6),
5799		'chassis-serial-number': (3, 7),
5800		'processor-manufacturer': (4, 7),
5801		'processor-version': (4, 16),
5802	}
 
 
 
 
 
 
 
 
5803
5804	# by default use legacy scan, but try to use EFI first
5805	memaddr, memsize = 0xf0000, 0x10000
 
5806	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5807		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5808			continue
5809		fp = open(ep, 'r')
5810		buf = fp.read()
5811		fp.close()
5812		i = buf.find('SMBIOS=')
5813		if i >= 0:
5814			try:
5815				memaddr = int(buf[i+7:], 16)
5816				memsize = 0x20
5817			except:
5818				continue
5819
5820	# read in the memory for scanning
5821	try:
5822		fp = open(mempath, 'rb')
5823		fp.seek(memaddr)
5824		buf = fp.read(memsize)
5825	except:
5826		return dmidecode_backup(out, fatal)
 
 
 
 
5827	fp.close()
5828
5829	# search for either an SM table or DMI table
5830	i = base = length = num = 0
5831	while(i < memsize):
5832		if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5833			length = struct.unpack('H', buf[i+22:i+24])[0]
5834			base, num = struct.unpack('IH', buf[i+24:i+30])
5835			break
5836		elif buf[i:i+5] == b'_DMI_':
5837			length = struct.unpack('H', buf[i+6:i+8])[0]
5838			base, num = struct.unpack('IH', buf[i+8:i+14])
5839			break
5840		i += 16
5841	if base == 0 and length == 0 and num == 0:
5842		return dmidecode_backup(out, fatal)
 
 
 
5843
5844	# read in the SM or DMI table
5845	try:
5846		fp = open(mempath, 'rb')
5847		fp.seek(base)
5848		buf = fp.read(length)
5849	except:
5850		return dmidecode_backup(out, fatal)
 
 
 
 
5851	fp.close()
5852
5853	# scan the table for the values we want
5854	count = i = 0
5855	while(count < num and i <= len(buf) - 4):
5856		type, size, handle = struct.unpack('BBH', buf[i:i+4])
5857		n = i + size
5858		while n < len(buf) - 1:
5859			if 0 == struct.unpack('H', buf[n:n+2])[0]:
5860				break
5861			n += 1
5862		data = buf[i+size:n+2].split(b'\0')
5863		for name in info:
5864			itype, idxadr = info[name]
5865			if itype == type:
5866				idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5867				if idx > 0 and idx < len(data) - 1:
5868					s = data[idx-1].decode('utf-8')
5869					if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5870						out[name] = s
5871		i = n + 2
5872		count += 1
5873	return out
5874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5875# Function: getFPDT
5876# Description:
5877#	 Read the acpi bios tables and pull out FPDT, the firmware data
5878# Arguments:
5879#	 output: True to output the info to stdout, False otherwise
5880def getFPDT(output):
5881	rectype = {}
5882	rectype[0] = 'Firmware Basic Boot Performance Record'
5883	rectype[1] = 'S3 Performance Table Record'
5884	prectype = {}
5885	prectype[0] = 'Basic S3 Resume Performance Record'
5886	prectype[1] = 'Basic S3 Suspend Performance Record'
5887
5888	sysvals.rootCheck(True)
5889	if(not os.path.exists(sysvals.fpdtpath)):
5890		if(output):
5891			doError('file does not exist: %s' % sysvals.fpdtpath)
5892		return False
5893	if(not os.access(sysvals.fpdtpath, os.R_OK)):
5894		if(output):
5895			doError('file is not readable: %s' % sysvals.fpdtpath)
5896		return False
5897	if(not os.path.exists(sysvals.mempath)):
5898		if(output):
5899			doError('file does not exist: %s' % sysvals.mempath)
5900		return False
5901	if(not os.access(sysvals.mempath, os.R_OK)):
5902		if(output):
5903			doError('file is not readable: %s' % sysvals.mempath)
5904		return False
5905
5906	fp = open(sysvals.fpdtpath, 'rb')
5907	buf = fp.read()
5908	fp.close()
5909
5910	if(len(buf) < 36):
5911		if(output):
5912			doError('Invalid FPDT table data, should '+\
5913				'be at least 36 bytes')
5914		return False
5915
5916	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5917	if(output):
5918		pprint('\n'\
5919		'Firmware Performance Data Table (%s)\n'\
5920		'                  Signature : %s\n'\
5921		'               Table Length : %u\n'\
5922		'                   Revision : %u\n'\
5923		'                   Checksum : 0x%x\n'\
5924		'                     OEM ID : %s\n'\
5925		'               OEM Table ID : %s\n'\
5926		'               OEM Revision : %u\n'\
5927		'                 Creator ID : %s\n'\
5928		'           Creator Revision : 0x%x\n'\
5929		'' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5930			table[3], ascii(table[4]), ascii(table[5]), table[6],
5931			ascii(table[7]), table[8]))
5932
5933	if(table[0] != b'FPDT'):
5934		if(output):
5935			doError('Invalid FPDT table')
5936		return False
5937	if(len(buf) <= 36):
5938		return False
5939	i = 0
5940	fwData = [0, 0]
5941	records = buf[36:]
5942	try:
5943		fp = open(sysvals.mempath, 'rb')
5944	except:
5945		pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5946		return False
5947	while(i < len(records)):
5948		header = struct.unpack('HBB', records[i:i+4])
5949		if(header[0] not in rectype):
5950			i += header[1]
5951			continue
5952		if(header[1] != 16):
5953			i += header[1]
5954			continue
5955		addr = struct.unpack('Q', records[i+8:i+16])[0]
5956		try:
5957			fp.seek(addr)
5958			first = fp.read(8)
5959		except:
5960			if(output):
5961				pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5962			return [0, 0]
5963		rechead = struct.unpack('4sI', first)
5964		recdata = fp.read(rechead[1]-8)
5965		if(rechead[0] == b'FBPT'):
5966			record = struct.unpack('HBBIQQQQQ', recdata[:48])
5967			if(output):
5968				pprint('%s (%s)\n'\
5969				'                  Reset END : %u ns\n'\
5970				'  OS Loader LoadImage Start : %u ns\n'\
5971				' OS Loader StartImage Start : %u ns\n'\
5972				'     ExitBootServices Entry : %u ns\n'\
5973				'      ExitBootServices Exit : %u ns'\
5974				'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5975					record[6], record[7], record[8]))
5976		elif(rechead[0] == b'S3PT'):
5977			if(output):
5978				pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5979			j = 0
5980			while(j < len(recdata)):
5981				prechead = struct.unpack('HBB', recdata[j:j+4])
5982				if(prechead[0] not in prectype):
5983					continue
5984				if(prechead[0] == 0):
5985					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5986					fwData[1] = record[2]
5987					if(output):
5988						pprint('    %s\n'\
5989						'               Resume Count : %u\n'\
5990						'                 FullResume : %u ns\n'\
5991						'              AverageResume : %u ns'\
5992						'' % (prectype[prechead[0]], record[1],
5993								record[2], record[3]))
5994				elif(prechead[0] == 1):
5995					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5996					fwData[0] = record[1] - record[0]
5997					if(output):
5998						pprint('    %s\n'\
5999						'               SuspendStart : %u ns\n'\
6000						'                 SuspendEnd : %u ns\n'\
6001						'                SuspendTime : %u ns'\
6002						'' % (prectype[prechead[0]], record[0],
6003								record[1], fwData[0]))
6004
6005				j += prechead[1]
6006		if(output):
6007			pprint('')
6008		i += header[1]
6009	fp.close()
6010	return fwData
6011
6012# Function: statusCheck
6013# Description:
6014#	 Verify that the requested command and options will work, and
6015#	 print the results to the terminal
6016# Output:
6017#	 True if the test will work, False if not
6018def statusCheck(probecheck=False):
6019	status = ''
6020
6021	pprint('Checking this system (%s)...' % platform.node())
6022
6023	# check we have root access
6024	res = sysvals.colorText('NO (No features of this tool will work!)')
6025	if(sysvals.rootCheck(False)):
6026		res = 'YES'
6027	pprint('    have root access: %s' % res)
6028	if(res != 'YES'):
6029		pprint('    Try running this script with sudo')
6030		return 'missing root access'
6031
6032	# check sysfs is mounted
6033	res = sysvals.colorText('NO (No features of this tool will work!)')
6034	if(os.path.exists(sysvals.powerfile)):
6035		res = 'YES'
6036	pprint('    is sysfs mounted: %s' % res)
6037	if(res != 'YES'):
6038		return 'sysfs is missing'
6039
6040	# check target mode is a valid mode
6041	if sysvals.suspendmode != 'command':
6042		res = sysvals.colorText('NO')
6043		modes = getModes()
6044		if(sysvals.suspendmode in modes):
6045			res = 'YES'
6046		else:
6047			status = '%s mode is not supported' % sysvals.suspendmode
6048		pprint('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
6049		if(res == 'NO'):
6050			pprint('      valid power modes are: %s' % modes)
6051			pprint('      please choose one with -m')
6052
6053	# check if ftrace is available
6054	if sysvals.useftrace:
6055		res = sysvals.colorText('NO')
6056		sysvals.useftrace = sysvals.verifyFtrace()
6057		efmt = '"{0}" uses ftrace, and it is not properly supported'
6058		if sysvals.useftrace:
6059			res = 'YES'
6060		elif sysvals.usecallgraph:
6061			status = efmt.format('-f')
6062		elif sysvals.usedevsrc:
6063			status = efmt.format('-dev')
6064		elif sysvals.useprocmon:
6065			status = efmt.format('-proc')
6066		pprint('    is ftrace supported: %s' % res)
6067
6068	# check if kprobes are available
6069	if sysvals.usekprobes:
6070		res = sysvals.colorText('NO')
6071		sysvals.usekprobes = sysvals.verifyKprobes()
6072		if(sysvals.usekprobes):
6073			res = 'YES'
6074		else:
6075			sysvals.usedevsrc = False
6076		pprint('    are kprobes supported: %s' % res)
6077
6078	# what data source are we using
6079	res = 'DMESG (very limited, ftrace is preferred)'
6080	if sysvals.useftrace:
6081		sysvals.usetraceevents = True
6082		for e in sysvals.traceevents:
6083			if not os.path.exists(sysvals.epath+e):
6084				sysvals.usetraceevents = False
6085		if(sysvals.usetraceevents):
6086			res = 'FTRACE (all trace events found)'
6087	pprint('    timeline data source: %s' % res)
6088
6089	# check if rtcwake
6090	res = sysvals.colorText('NO')
6091	if(sysvals.rtcpath != ''):
6092		res = 'YES'
6093	elif(sysvals.rtcwake):
6094		status = 'rtcwake is not properly supported'
6095	pprint('    is rtcwake supported: %s' % res)
6096
6097	# check info commands
6098	pprint('    optional commands this tool may use for info:')
6099	no = sysvals.colorText('MISSING')
6100	yes = sysvals.colorText('FOUND', 32)
6101	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
6102		if c == 'turbostat':
6103			res = yes if sysvals.haveTurbostat() else no
6104		else:
6105			res = yes if sysvals.getExec(c) else no
6106		pprint('        %s: %s' % (c, res))
6107
6108	if not probecheck:
6109		return status
6110
6111	# verify kprobes
6112	if sysvals.usekprobes:
6113		for name in sysvals.tracefuncs:
6114			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
6115		if sysvals.usedevsrc:
6116			for name in sysvals.dev_tracefuncs:
6117				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
6118		sysvals.addKprobes(True)
6119
6120	return status
6121
6122# Function: doError
6123# Description:
6124#	 generic error function for catastrphic failures
6125# Arguments:
6126#	 msg: the error message to print
6127#	 help: True if printHelp should be called after, False otherwise
6128def doError(msg, help=False):
6129	if(help == True):
6130		printHelp()
6131	pprint('ERROR: %s\n' % msg)
6132	sysvals.outputResult({'error':msg})
6133	sys.exit(1)
6134
6135# Function: getArgInt
6136# Description:
6137#	 pull out an integer argument from the command line with checks
6138def getArgInt(name, args, min, max, main=True):
6139	if main:
6140		try:
6141			arg = next(args)
6142		except:
6143			doError(name+': no argument supplied', True)
6144	else:
6145		arg = args
6146	try:
6147		val = int(arg)
6148	except:
6149		doError(name+': non-integer value given', True)
6150	if(val < min or val > max):
6151		doError(name+': value should be between %d and %d' % (min, max), True)
6152	return val
6153
6154# Function: getArgFloat
6155# Description:
6156#	 pull out a float argument from the command line with checks
6157def getArgFloat(name, args, min, max, main=True):
6158	if main:
6159		try:
6160			arg = next(args)
6161		except:
6162			doError(name+': no argument supplied', True)
6163	else:
6164		arg = args
6165	try:
6166		val = float(arg)
6167	except:
6168		doError(name+': non-numerical value given', True)
6169	if(val < min or val > max):
6170		doError(name+': value should be between %f and %f' % (min, max), True)
6171	return val
6172
6173def processData(live=False, quiet=False):
6174	if not quiet:
6175		pprint('PROCESSING: %s' % sysvals.htmlfile)
6176	sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
6177		(sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
6178	error = ''
6179	if(sysvals.usetraceevents):
6180		testruns, error = parseTraceLog(live)
6181		if sysvals.dmesgfile:
6182			for data in testruns:
6183				data.extractErrorInfo()
6184	else:
6185		testruns = loadKernelLog()
6186		for data in testruns:
6187			parseKernelLog(data)
6188		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
6189			appendIncompleteTraceLog(testruns)
6190	if not sysvals.stamp:
6191		pprint('ERROR: data does not include the expected stamp')
6192		return (testruns, {'error': 'timeline generation failed'})
6193	shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
6194			'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
6195	sysvals.vprint('System Info:')
6196	for key in sorted(sysvals.stamp):
6197		if key in shown:
6198			sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key]))
6199	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
6200	for data in testruns:
6201		if data.turbostat:
6202			idx, s = 0, 'Turbostat:\n    '
6203			for val in data.turbostat.split('|'):
6204				idx += len(val) + 1
6205				if idx >= 80:
6206					idx = 0
6207					s += '\n    '
6208				s += val + ' '
6209			sysvals.vprint(s)
6210		data.printDetails()
6211	if len(sysvals.platinfo) > 0:
6212		sysvals.vprint('\nPlatform Info:')
6213		for info in sysvals.platinfo:
6214			sysvals.vprint('[%s - %s]' % (info[0], info[1]))
6215			sysvals.vprint(info[2])
6216		sysvals.vprint('')
6217	if sysvals.cgdump:
6218		for data in testruns:
6219			data.debugPrint()
6220		sys.exit(0)
6221	if len(testruns) < 1:
6222		pprint('ERROR: Not enough test data to build a timeline')
6223		return (testruns, {'error': 'timeline generation failed'})
6224	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6225	createHTML(testruns, error)
6226	if not quiet:
6227		pprint('DONE:       %s' % sysvals.htmlfile)
6228	data = testruns[0]
6229	stamp = data.stamp
6230	stamp['suspend'], stamp['resume'] = data.getTimeValues()
6231	if data.fwValid:
6232		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6233	if error:
6234		stamp['error'] = error
6235	return (testruns, stamp)
6236
6237# Function: rerunTest
6238# Description:
6239#	 generate an output from an existing set of ftrace/dmesg logs
6240def rerunTest(htmlfile=''):
6241	if sysvals.ftracefile:
6242		doesTraceLogHaveTraceEvents()
6243	if not sysvals.dmesgfile and not sysvals.usetraceevents:
6244		doError('recreating this html output requires a dmesg file')
6245	if htmlfile:
6246		sysvals.htmlfile = htmlfile
6247	else:
6248		sysvals.setOutputFile()
6249	if os.path.exists(sysvals.htmlfile):
6250		if not os.path.isfile(sysvals.htmlfile):
6251			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6252		elif not os.access(sysvals.htmlfile, os.W_OK):
6253			doError('missing permission to write to %s' % sysvals.htmlfile)
6254	testruns, stamp = processData()
6255	sysvals.resetlog()
6256	return stamp
6257
6258# Function: runTest
6259# Description:
6260#	 execute a suspend/resume, gather the logs, and generate the output
6261def runTest(n=0, quiet=False):
6262	# prepare for the test
 
6263	sysvals.initTestOutput('suspend')
6264	op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6265	op.write('# EXECUTION TRACE START\n')
6266	op.close()
6267	if n <= 1:
6268		if sysvals.rs != 0:
6269			sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6270			sysvals.setRuntimeSuspend(True)
6271		if sysvals.display:
6272			ret = sysvals.displayControl('init')
6273			sysvals.dlog('xset display init, ret = %d' % ret)
6274	sysvals.testVal(sysvals.pmdpath, 'basic', '1')
6275	sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
6276	sysvals.dlog('initialize ftrace')
6277	sysvals.initFtrace(quiet)
6278
6279	# execute the test
6280	executeSuspend(quiet)
6281	sysvals.cleanupFtrace()
6282	if sysvals.skiphtml:
6283		sysvals.outputResult({}, n)
6284		sysvals.sudoUserchown(sysvals.testdir)
6285		return
6286	testruns, stamp = processData(True, quiet)
6287	for data in testruns:
6288		del data
6289	sysvals.sudoUserchown(sysvals.testdir)
6290	sysvals.outputResult(stamp, n)
6291	if 'error' in stamp:
6292		return 2
6293	return 0
6294
6295def find_in_html(html, start, end, firstonly=True):
6296	cnt, out, list = len(html), [], []
6297	if firstonly:
6298		m = re.search(start, html)
6299		if m:
6300			list.append(m)
6301	else:
6302		list = re.finditer(start, html)
6303	for match in list:
6304		s = match.end()
6305		e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6306		m = re.search(end, html[s:e])
6307		if not m:
6308			break
6309		e = s + m.start()
6310		str = html[s:e]
6311		if end == 'ms':
6312			num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6313			str = num.group() if num else 'NaN'
6314		if firstonly:
6315			return str
6316		out.append(str)
6317	if firstonly:
6318		return ''
6319	return out
6320
6321def data_from_html(file, outpath, issues, fulldetail=False):
6322	try:
6323		html = open(file, 'r').read()
6324	except:
6325		html = ascii(open(file, 'rb').read())
6326	sysvals.htmlfile = os.path.relpath(file, outpath)
6327	# extract general info
6328	suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6329	resume = find_in_html(html, 'Kernel Resume', 'ms')
6330	sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6331	line = find_in_html(html, '<div class="stamp">', '</div>')
6332	stmp = line.split()
6333	if not suspend or not resume or len(stmp) != 8:
6334		return False
6335	try:
6336		dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6337	except:
6338		return False
6339	sysvals.hostname = stmp[0]
6340	tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6341	error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6342	if error:
6343		m = re.match(r'[a-z0-9]* failed in (?P<p>\S*).*', error)
6344		if m:
6345			result = 'fail in %s' % m.group('p')
6346		else:
6347			result = 'fail'
6348	else:
6349		result = 'pass'
6350	# extract error info
6351	tp, ilist = False, []
6352	extra = dict()
6353	log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6354		'</div>').strip()
6355	if log:
6356		d = Data(0)
6357		d.end = 999999999
6358		d.dmesgtext = log.split('\n')
6359		tp = d.extractErrorInfo()
6360		if len(issues) < 100:
6361			for msg in tp.msglist:
6362				sysvals.errorSummary(issues, msg)
6363		if stmp[2] == 'freeze':
6364			extra = d.turbostatInfo()
6365		elist = dict()
6366		for dir in d.errorinfo:
6367			for err in d.errorinfo[dir]:
6368				if err[0] not in elist:
6369					elist[err[0]] = 0
6370				elist[err[0]] += 1
6371		for i in elist:
6372			ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6373		line = find_in_html(log, '# wifi ', '\n')
6374		if line:
6375			extra['wifi'] = line
6376		line = find_in_html(log, '# netfix ', '\n')
6377		if line:
6378			extra['netfix'] = line
6379		line = find_in_html(log, '# command ', '\n')
6380		if line:
6381			m = re.match(r'.* -m (?P<m>\S*).*', line)
6382			if m:
6383				extra['fullmode'] = m.group('m')
6384	low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6385	for lowstr in ['waking', '+']:
6386		if not low:
6387			break
6388		if lowstr not in low:
6389			continue
6390		if lowstr == '+':
6391			issue = 'S2LOOPx%d' % len(low.split('+'))
6392		else:
6393			m = re.match(r'.*waking *(?P<n>[0-9]*) *times.*', low)
6394			issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6395		match = [i for i in issues if i['match'] == issue]
6396		if len(match) > 0:
6397			match[0]['count'] += 1
6398			if sysvals.hostname not in match[0]['urls']:
6399				match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6400			elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6401				match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6402		else:
6403			issues.append({
6404				'match': issue, 'count': 1, 'line': issue,
6405				'urls': {sysvals.hostname: [sysvals.htmlfile]},
6406			})
6407		ilist.append(issue)
6408	# extract device info
6409	devices = dict()
6410	for line in html.split('\n'):
6411		m = re.match(r' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6412		if not m or 'thread kth' in line or 'thread sec' in line:
6413			continue
6414		m = re.match(r'(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6415		if not m:
6416			continue
6417		name, time, phase = m.group('n'), m.group('t'), m.group('p')
6418		if name == 'async_synchronize_full':
6419			continue
6420		if ' async' in name or ' sync' in name:
6421			name = ' '.join(name.split(' ')[:-1])
6422		if phase.startswith('suspend'):
6423			d = 'suspend'
6424		elif phase.startswith('resume'):
6425			d = 'resume'
6426		else:
6427			continue
6428		if d not in devices:
6429			devices[d] = dict()
6430		if name not in devices[d]:
6431			devices[d][name] = 0.0
6432		devices[d][name] += float(time)
6433	# create worst device info
6434	worst = dict()
6435	for d in ['suspend', 'resume']:
6436		worst[d] = {'name':'', 'time': 0.0}
6437		dev = devices[d] if d in devices else 0
6438		if dev and len(dev.keys()) > 0:
6439			n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6440			worst[d]['name'], worst[d]['time'] = n, dev[n]
6441	data = {
6442		'mode': stmp[2],
6443		'host': stmp[0],
6444		'kernel': stmp[1],
6445		'sysinfo': sysinfo,
6446		'time': tstr,
6447		'result': result,
6448		'issues': ' '.join(ilist),
6449		'suspend': suspend,
6450		'resume': resume,
6451		'devlist': devices,
6452		'sus_worst': worst['suspend']['name'],
6453		'sus_worsttime': worst['suspend']['time'],
6454		'res_worst': worst['resume']['name'],
6455		'res_worsttime': worst['resume']['time'],
6456		'url': sysvals.htmlfile,
6457	}
6458	for key in extra:
6459		data[key] = extra[key]
6460	if fulldetail:
6461		data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6462	if tp:
6463		for arg in ['-multi ', '-info ']:
6464			if arg in tp.cmdline:
6465				data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6466				break
6467	return data
6468
6469def genHtml(subdir, force=False):
6470	for dirname, dirnames, filenames in os.walk(subdir):
6471		sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6472		for filename in filenames:
6473			file = os.path.join(dirname, filename)
6474			if sysvals.usable(file):
6475				if(re.match(r'.*_dmesg.txt', filename)):
6476					sysvals.dmesgfile = file
6477				elif(re.match(r'.*_ftrace.txt', filename)):
6478					sysvals.ftracefile = file
6479		sysvals.setOutputFile()
6480		if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6481			(force or not sysvals.usable(sysvals.htmlfile, True)):
6482			pprint('FTRACE: %s' % sysvals.ftracefile)
6483			if sysvals.dmesgfile:
6484				pprint('DMESG : %s' % sysvals.dmesgfile)
6485			rerunTest()
6486
6487# Function: runSummary
6488# Description:
6489#	 create a summary of tests in a sub-directory
6490def runSummary(subdir, local=True, genhtml=False):
6491	inpath = os.path.abspath(subdir)
6492	outpath = os.path.abspath('.') if local else inpath
6493	pprint('Generating a summary of folder:\n   %s' % inpath)
6494	if genhtml:
6495		genHtml(subdir)
6496	target, issues, testruns = '', [], []
6497	desc = {'host':[],'mode':[],'kernel':[]}
6498	for dirname, dirnames, filenames in os.walk(subdir):
6499		for filename in filenames:
6500			if(not re.match(r'.*.html', filename)):
6501				continue
6502			data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6503			if(not data):
6504				continue
6505			if 'target' in data:
6506				target = data['target']
6507			testruns.append(data)
6508			for key in desc:
6509				if data[key] not in desc[key]:
6510					desc[key].append(data[key])
6511	pprint('Summary files:')
6512	if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6513		title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6514		if target:
6515			title += ' %s' % target
6516	else:
6517		title = inpath
6518	createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6519	pprint('   summary.html         - tabular list of test data found')
6520	createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6521	pprint('   summary-devices.html - kernel device list sorted by total execution time')
6522	createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6523	pprint('   summary-issues.html  - kernel issues found sorted by frequency')
6524
6525# Function: checkArgBool
6526# Description:
6527#	 check if a boolean string value is true or false
6528def checkArgBool(name, value):
6529	if value in switchvalues:
6530		if value in switchoff:
6531			return False
6532		return True
6533	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6534	return False
6535
6536# Function: configFromFile
6537# Description:
6538#	 Configure the script via the info in a config file
6539def configFromFile(file):
6540	Config = configparser.ConfigParser()
6541
6542	Config.read(file)
6543	sections = Config.sections()
6544	overridekprobes = False
6545	overridedevkprobes = False
6546	if 'Settings' in sections:
6547		for opt in Config.options('Settings'):
6548			value = Config.get('Settings', opt).lower()
6549			option = opt.lower()
6550			if(option == 'verbose'):
6551				sysvals.verbose = checkArgBool(option, value)
6552			elif(option == 'addlogs'):
6553				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6554			elif(option == 'dev'):
6555				sysvals.usedevsrc = checkArgBool(option, value)
6556			elif(option == 'proc'):
6557				sysvals.useprocmon = checkArgBool(option, value)
6558			elif(option == 'x2'):
6559				if checkArgBool(option, value):
6560					sysvals.execcount = 2
6561			elif(option == 'callgraph'):
6562				sysvals.usecallgraph = checkArgBool(option, value)
6563			elif(option == 'override-timeline-functions'):
6564				overridekprobes = checkArgBool(option, value)
6565			elif(option == 'override-dev-timeline-functions'):
6566				overridedevkprobes = checkArgBool(option, value)
6567			elif(option == 'skiphtml'):
6568				sysvals.skiphtml = checkArgBool(option, value)
6569			elif(option == 'sync'):
6570				sysvals.sync = checkArgBool(option, value)
6571			elif(option == 'rs' or option == 'runtimesuspend'):
6572				if value in switchvalues:
6573					if value in switchoff:
6574						sysvals.rs = -1
6575					else:
6576						sysvals.rs = 1
6577				else:
6578					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6579			elif(option == 'display'):
6580				disopt = ['on', 'off', 'standby', 'suspend']
6581				if value not in disopt:
6582					doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6583				sysvals.display = value
6584			elif(option == 'gzip'):
6585				sysvals.gzip = checkArgBool(option, value)
6586			elif(option == 'cgfilter'):
6587				sysvals.setCallgraphFilter(value)
6588			elif(option == 'cgskip'):
6589				if value in switchoff:
6590					sysvals.cgskip = ''
6591				else:
6592					sysvals.cgskip = sysvals.configFile(val)
6593					if(not sysvals.cgskip):
6594						doError('%s does not exist' % sysvals.cgskip)
6595			elif(option == 'cgtest'):
6596				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6597			elif(option == 'cgphase'):
6598				d = Data(0)
6599				if value not in d.phasedef:
6600					doError('invalid phase --> (%s: %s), valid phases are %s'\
6601						% (option, value, d.phasedef.keys()), True)
6602				sysvals.cgphase = value
6603			elif(option == 'fadd'):
6604				file = sysvals.configFile(value)
6605				if(not file):
6606					doError('%s does not exist' % value)
6607				sysvals.addFtraceFilterFunctions(file)
6608			elif(option == 'result'):
6609				sysvals.result = value
6610			elif(option == 'multi'):
6611				nums = value.split()
6612				if len(nums) != 2:
6613					doError('multi requires 2 integers (exec_count and delay)', True)
6614				sysvals.multiinit(nums[0], nums[1])
6615			elif(option == 'devicefilter'):
6616				sysvals.setDeviceFilter(value)
6617			elif(option == 'expandcg'):
6618				sysvals.cgexp = checkArgBool(option, value)
6619			elif(option == 'srgap'):
6620				if checkArgBool(option, value):
6621					sysvals.srgap = 5
6622			elif(option == 'mode'):
6623				sysvals.suspendmode = value
6624			elif(option == 'command' or option == 'cmd'):
6625				sysvals.testcommand = value
6626			elif(option == 'x2delay'):
6627				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6628			elif(option == 'predelay'):
6629				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6630			elif(option == 'postdelay'):
6631				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6632			elif(option == 'maxdepth'):
6633				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6634			elif(option == 'rtcwake'):
6635				if value in switchoff:
6636					sysvals.rtcwake = False
6637				else:
6638					sysvals.rtcwake = True
6639					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6640			elif(option == 'timeprec'):
6641				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6642			elif(option == 'mindev'):
6643				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6644			elif(option == 'callloop-maxgap'):
6645				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6646			elif(option == 'callloop-maxlen'):
6647				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6648			elif(option == 'mincg'):
6649				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6650			elif(option == 'bufsize'):
6651				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6652			elif(option == 'output-dir'):
6653				sysvals.outdir = sysvals.setOutputFolder(value)
6654
6655	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6656		doError('No command supplied for mode "command"')
6657
6658	# compatibility errors
6659	if sysvals.usedevsrc and sysvals.usecallgraph:
6660		doError('-dev is not compatible with -f')
6661	if sysvals.usecallgraph and sysvals.useprocmon:
6662		doError('-proc is not compatible with -f')
6663
6664	if overridekprobes:
6665		sysvals.tracefuncs = dict()
6666	if overridedevkprobes:
6667		sysvals.dev_tracefuncs = dict()
6668
6669	kprobes = dict()
6670	kprobesec = 'dev_timeline_functions_'+platform.machine()
6671	if kprobesec in sections:
6672		for name in Config.options(kprobesec):
6673			text = Config.get(kprobesec, name)
6674			kprobes[name] = (text, True)
6675	kprobesec = 'timeline_functions_'+platform.machine()
6676	if kprobesec in sections:
6677		for name in Config.options(kprobesec):
6678			if name in kprobes:
6679				doError('Duplicate timeline function found "%s"' % (name))
6680			text = Config.get(kprobesec, name)
6681			kprobes[name] = (text, False)
6682
6683	for name in kprobes:
6684		function = name
6685		format = name
6686		color = ''
6687		args = dict()
6688		text, dev = kprobes[name]
6689		data = text.split()
6690		i = 0
6691		for val in data:
6692			# bracketted strings are special formatting, read them separately
6693			if val[0] == '[' and val[-1] == ']':
6694				for prop in val[1:-1].split(','):
6695					p = prop.split('=')
6696					if p[0] == 'color':
6697						try:
6698							color = int(p[1], 16)
6699							color = '#'+p[1]
6700						except:
6701							color = p[1]
6702				continue
6703			# first real arg should be the format string
6704			if i == 0:
6705				format = val
6706			# all other args are actual function args
6707			else:
6708				d = val.split('=')
6709				args[d[0]] = d[1]
6710			i += 1
6711		if not function or not format:
6712			doError('Invalid kprobe: %s' % name)
6713		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6714			if arg not in args:
6715				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6716		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6717			doError('Duplicate timeline function found "%s"' % (name))
6718
6719		kp = {
6720			'name': name,
6721			'func': function,
6722			'format': format,
6723			sysvals.archargs: args
6724		}
6725		if color:
6726			kp['color'] = color
6727		if dev:
6728			sysvals.dev_tracefuncs[name] = kp
6729		else:
6730			sysvals.tracefuncs[name] = kp
6731
6732# Function: printHelp
6733# Description:
6734#	 print out the help text
6735def printHelp():
6736	pprint('\n%s v%s\n'\
6737	'Usage: sudo sleepgraph <options> <commands>\n'\
6738	'\n'\
6739	'Description:\n'\
6740	'  This tool is designed to assist kernel and OS developers in optimizing\n'\
6741	'  their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6742	'  with a few extra options enabled, the tool will execute a suspend and\n'\
6743	'  capture dmesg and ftrace data until resume is complete. This data is\n'\
6744	'  transformed into a device timeline and an optional callgraph to give\n'\
6745	'  a detailed view of which devices/subsystems are taking the most\n'\
6746	'  time in suspend/resume.\n'\
6747	'\n'\
6748	'  If no specific command is given, the default behavior is to initiate\n'\
6749	'  a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6750	'\n'\
6751	'  Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6752	'   HTML output:                    <hostname>_<mode>.html\n'\
6753	'   raw dmesg output:               <hostname>_<mode>_dmesg.txt\n'\
6754	'   raw ftrace output:              <hostname>_<mode>_ftrace.txt\n'\
6755	'\n'\
6756	'Options:\n'\
6757	'   -h           Print this help text\n'\
6758	'   -v           Print the current tool version\n'\
6759	'   -config fn   Pull arguments and config options from file fn\n'\
6760	'   -verbose     Print extra information during execution and analysis\n'\
6761	'   -m mode      Mode to initiate for suspend (default: %s)\n'\
6762	'   -o name      Overrides the output subdirectory name when running a new test\n'\
6763	'                default: suspend-{date}-{time}\n'\
6764	'   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6765	'   -addlogs     Add the dmesg and ftrace logs to the html output\n'\
6766	'   -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6767	'   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6768	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6769	'   -result fn   Export a results table to a text file for parsing.\n'\
6770	'   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
6771	'   -wifitrace   Trace kernel execution through wifi reconnect.\n'\
6772	'   -netfix      Use netfix to reset the network in the event it fails to resume.\n'\
6773	'   -debugtiming Add timestamp to each printed line\n'\
6774	'  [testprep]\n'\
6775	'   -sync        Sync the filesystems before starting the test\n'\
6776	'   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
6777	'   -display m   Change the display mode to m for the test (on/off/standby/suspend)\n'\
6778	'  [advanced]\n'\
6779	'   -gzip        Gzip the trace and dmesg logs to save space\n'\
6780	'   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"\n'\
6781	'   -proc        Add usermode process info into the timeline (default: disabled)\n'\
6782	'   -dev         Add kernel function calls and threads to the timeline (default: disabled)\n'\
6783	'   -x2          Run two suspend/resumes back to back (default: disabled)\n'\
6784	'   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)\n'\
6785	'   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)\n'\
6786	'   -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6787	'   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6788	'   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6789	'                by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6790	'                The outputs will be created in a new subdirectory with a summary page.\n'\
6791	'   -maxfail n   Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6792	'  [debug]\n'\
6793	'   -f           Use ftrace to create device callgraphs (default: disabled)\n'\
6794	'   -ftop        Use ftrace on the top level call: "%s" (default: disabled)\n'\
6795	'   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)\n'\
6796	'   -expandcg    pre-expand the callgraph data in the html output (default: disabled)\n'\
6797	'   -fadd file   Add functions to be graphed in the timeline from a list in a text file\n'\
6798	'   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6799	'   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6800	'   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)\n'\
6801	'   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6802	'   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6803	'   -cgfilter S  Filter the callgraph output in the timeline\n'\
6804	'   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6805	'   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6806	'   -devdump     Print out all the raw device data for each phase\n'\
6807	'   -cgdump      Print out all the raw callgraph data\n'\
6808	'\n'\
6809	'Other commands:\n'\
6810	'   -modes       List available suspend modes\n'\
6811	'   -status      Test to see if the system is enabled to run this tool\n'\
6812	'   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table\n'\
6813	'   -wificheck   Print out wifi connection info\n'\
6814	'   -x<mode>     Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6815	'   -sysinfo     Print out system info extracted from BIOS\n'\
6816	'   -devinfo     Print out the pm settings of all devices which support runtime suspend\n'\
6817	'   -cmdinfo     Print out all the platform info collected before and after suspend/resume\n'\
6818	'   -flist       Print the list of functions currently being captured in ftrace\n'\
6819	'   -flistall    Print all functions capable of being captured in ftrace\n'\
6820	'   -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6821	'  [redo]\n'\
6822	'   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)\n'\
6823	'   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)\n'\
6824	'' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6825	return True
6826
6827# ----------------- MAIN --------------------
6828# exec start (skipped if script is loaded as library)
6829if __name__ == '__main__':
6830	genhtml = False
6831	cmd = ''
6832	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6833		'-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6834		'-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6835	if '-f' in sys.argv:
6836		sysvals.cgskip = sysvals.configFile('cgskip.txt')
6837	# loop through the command line arguments
6838	args = iter(sys.argv[1:])
6839	for arg in args:
6840		if(arg == '-m'):
6841			try:
6842				val = next(args)
6843			except:
6844				doError('No mode supplied', True)
6845			if val == 'command' and not sysvals.testcommand:
6846				doError('No command supplied for mode "command"', True)
6847			sysvals.suspendmode = val
6848		elif(arg in simplecmds):
6849			cmd = arg[1:]
6850		elif(arg == '-h'):
6851			printHelp()
6852			sys.exit(0)
6853		elif(arg == '-v'):
6854			pprint("Version %s" % sysvals.version)
6855			sys.exit(0)
6856		elif(arg == '-debugtiming'):
6857			debugtiming = True
6858		elif(arg == '-x2'):
6859			sysvals.execcount = 2
6860		elif(arg == '-x2delay'):
6861			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6862		elif(arg == '-predelay'):
6863			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6864		elif(arg == '-postdelay'):
6865			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6866		elif(arg == '-f'):
6867			sysvals.usecallgraph = True
6868		elif(arg == '-ftop'):
6869			sysvals.usecallgraph = True
6870			sysvals.ftop = True
6871			sysvals.usekprobes = False
6872		elif(arg == '-skiphtml'):
6873			sysvals.skiphtml = True
6874		elif(arg == '-cgdump'):
6875			sysvals.cgdump = True
6876		elif(arg == '-devdump'):
6877			sysvals.devdump = True
6878		elif(arg == '-genhtml'):
6879			genhtml = True
6880		elif(arg == '-addlogs'):
6881			sysvals.dmesglog = sysvals.ftracelog = True
6882		elif(arg == '-nologs'):
6883			sysvals.dmesglog = sysvals.ftracelog = False
6884		elif(arg == '-addlogdmesg'):
6885			sysvals.dmesglog = True
6886		elif(arg == '-addlogftrace'):
6887			sysvals.ftracelog = True
6888		elif(arg == '-noturbostat'):
6889			sysvals.tstat = False
6890		elif(arg == '-verbose'):
6891			sysvals.verbose = True
6892		elif(arg == '-proc'):
6893			sysvals.useprocmon = True
6894		elif(arg == '-dev'):
6895			sysvals.usedevsrc = True
6896		elif(arg == '-sync'):
6897			sysvals.sync = True
6898		elif(arg == '-wifi'):
6899			sysvals.wifi = True
6900		elif(arg == '-wifitrace'):
6901			sysvals.wifitrace = True
6902		elif(arg == '-netfix'):
6903			sysvals.netfix = True
6904		elif(arg == '-gzip'):
6905			sysvals.gzip = True
6906		elif(arg == '-info'):
6907			try:
6908				val = next(args)
6909			except:
6910				doError('-info requires one string argument', True)
6911		elif(arg == '-desc'):
6912			try:
6913				val = next(args)
6914			except:
6915				doError('-desc requires one string argument', True)
6916		elif(arg == '-rs'):
6917			try:
6918				val = next(args)
6919			except:
6920				doError('-rs requires "enable" or "disable"', True)
6921			if val.lower() in switchvalues:
6922				if val.lower() in switchoff:
6923					sysvals.rs = -1
6924				else:
6925					sysvals.rs = 1
6926			else:
6927				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6928		elif(arg == '-display'):
6929			try:
6930				val = next(args)
6931			except:
6932				doError('-display requires an mode value', True)
6933			disopt = ['on', 'off', 'standby', 'suspend']
6934			if val.lower() not in disopt:
6935				doError('valid display mode values are %s' % disopt, True)
6936			sysvals.display = val.lower()
6937		elif(arg == '-maxdepth'):
6938			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6939		elif(arg == '-rtcwake'):
6940			try:
6941				val = next(args)
6942			except:
6943				doError('No rtcwake time supplied', True)
6944			if val.lower() in switchoff:
6945				sysvals.rtcwake = False
6946			else:
6947				sysvals.rtcwake = True
6948				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6949		elif(arg == '-timeprec'):
6950			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6951		elif(arg == '-mindev'):
6952			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6953		elif(arg == '-mincg'):
6954			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6955		elif(arg == '-bufsize'):
6956			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6957		elif(arg == '-cgtest'):
6958			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6959		elif(arg == '-cgphase'):
6960			try:
6961				val = next(args)
6962			except:
6963				doError('No phase name supplied', True)
6964			d = Data(0)
6965			if val not in d.phasedef:
6966				doError('invalid phase --> (%s: %s), valid phases are %s'\
6967					% (arg, val, d.phasedef.keys()), True)
6968			sysvals.cgphase = val
6969		elif(arg == '-cgfilter'):
6970			try:
6971				val = next(args)
6972			except:
6973				doError('No callgraph functions supplied', True)
6974			sysvals.setCallgraphFilter(val)
6975		elif(arg == '-skipkprobe'):
6976			try:
6977				val = next(args)
6978			except:
6979				doError('No kprobe functions supplied', True)
6980			sysvals.skipKprobes(val)
6981		elif(arg == '-cgskip'):
6982			try:
6983				val = next(args)
6984			except:
6985				doError('No file supplied', True)
6986			if val.lower() in switchoff:
6987				sysvals.cgskip = ''
6988			else:
6989				sysvals.cgskip = sysvals.configFile(val)
6990				if(not sysvals.cgskip):
6991					doError('%s does not exist' % sysvals.cgskip)
6992		elif(arg == '-callloop-maxgap'):
6993			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6994		elif(arg == '-callloop-maxlen'):
6995			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6996		elif(arg == '-cmd'):
6997			try:
6998				val = next(args)
6999			except:
7000				doError('No command string supplied', True)
7001			sysvals.testcommand = val
7002			sysvals.suspendmode = 'command'
7003		elif(arg == '-expandcg'):
7004			sysvals.cgexp = True
7005		elif(arg == '-srgap'):
7006			sysvals.srgap = 5
7007		elif(arg == '-maxfail'):
7008			sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
7009		elif(arg == '-multi'):
7010			try:
7011				c, d = next(args), next(args)
7012			except:
7013				doError('-multi requires two values', True)
7014			sysvals.multiinit(c, d)
7015		elif(arg == '-o'):
7016			try:
7017				val = next(args)
7018			except:
7019				doError('No subdirectory name supplied', True)
7020			sysvals.outdir = sysvals.setOutputFolder(val)
7021		elif(arg == '-config'):
7022			try:
7023				val = next(args)
7024			except:
7025				doError('No text file supplied', True)
7026			file = sysvals.configFile(val)
7027			if(not file):
7028				doError('%s does not exist' % val)
7029			configFromFile(file)
7030		elif(arg == '-fadd'):
7031			try:
7032				val = next(args)
7033			except:
7034				doError('No text file supplied', True)
7035			file = sysvals.configFile(val)
7036			if(not file):
7037				doError('%s does not exist' % val)
7038			sysvals.addFtraceFilterFunctions(file)
7039		elif(arg == '-dmesg'):
7040			try:
7041				val = next(args)
7042			except:
7043				doError('No dmesg file supplied', True)
7044			sysvals.notestrun = True
7045			sysvals.dmesgfile = val
7046			if(os.path.exists(sysvals.dmesgfile) == False):
7047				doError('%s does not exist' % sysvals.dmesgfile)
7048		elif(arg == '-ftrace'):
7049			try:
7050				val = next(args)
7051			except:
7052				doError('No ftrace file supplied', True)
7053			sysvals.notestrun = True
7054			sysvals.ftracefile = val
7055			if(os.path.exists(sysvals.ftracefile) == False):
7056				doError('%s does not exist' % sysvals.ftracefile)
7057		elif(arg == '-summary'):
7058			try:
7059				val = next(args)
7060			except:
7061				doError('No directory supplied', True)
7062			cmd = 'summary'
7063			sysvals.outdir = val
7064			sysvals.notestrun = True
7065			if(os.path.isdir(val) == False):
7066				doError('%s is not accesible' % val)
7067		elif(arg == '-filter'):
7068			try:
7069				val = next(args)
7070			except:
7071				doError('No devnames supplied', True)
7072			sysvals.setDeviceFilter(val)
7073		elif(arg == '-result'):
7074			try:
7075				val = next(args)
7076			except:
7077				doError('No result file supplied', True)
7078			sysvals.result = val
 
7079		else:
7080			doError('Invalid argument: '+arg, True)
7081
7082	# compatibility errors
7083	if(sysvals.usecallgraph and sysvals.usedevsrc):
7084		doError('-dev is not compatible with -f')
7085	if(sysvals.usecallgraph and sysvals.useprocmon):
7086		doError('-proc is not compatible with -f')
7087
7088	sysvals.signalHandlerInit()
7089	if sysvals.usecallgraph and sysvals.cgskip:
7090		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
7091		sysvals.setCallgraphBlacklist(sysvals.cgskip)
7092
7093	# callgraph size cannot exceed device size
7094	if sysvals.mincglen < sysvals.mindevlen:
7095		sysvals.mincglen = sysvals.mindevlen
7096
7097	# remove existing buffers before calculating memory
7098	if(sysvals.usecallgraph or sysvals.usedevsrc):
7099		sysvals.fsetVal('16', 'buffer_size_kb')
7100	sysvals.cpuInfo()
7101
7102	# just run a utility command and exit
7103	if(cmd != ''):
7104		ret = 0
7105		if(cmd == 'status'):
7106			if not statusCheck(True):
7107				ret = 1
7108		elif(cmd == 'fpdt'):
7109			if not getFPDT(True):
7110				ret = 1
7111		elif(cmd == 'sysinfo'):
7112			sysvals.printSystemInfo(True)
7113		elif(cmd == 'devinfo'):
7114			deviceInfo()
7115		elif(cmd == 'modes'):
7116			pprint(getModes())
7117		elif(cmd == 'flist'):
7118			sysvals.getFtraceFilterFunctions(True)
7119		elif(cmd == 'flistall'):
7120			sysvals.getFtraceFilterFunctions(False)
7121		elif(cmd == 'summary'):
7122			runSummary(sysvals.outdir, True, genhtml)
7123		elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
7124			sysvals.verbose = True
7125			ret = sysvals.displayControl(cmd[1:])
7126		elif(cmd == 'xstat'):
7127			pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
7128		elif(cmd == 'wificheck'):
7129			dev = sysvals.checkWifi()
7130			if dev:
7131				print('%s is connected' % sysvals.wifiDetails(dev))
7132			else:
7133				print('No wifi connection found')
7134		elif(cmd == 'cmdinfo'):
7135			for out in sysvals.cmdinfo(False, True):
7136				print('[%s - %s]\n%s\n' % out)
7137		sys.exit(ret)
7138
7139	# if instructed, re-analyze existing data files
7140	if(sysvals.notestrun):
7141		stamp = rerunTest(sysvals.outdir)
7142		sysvals.outputResult(stamp)
7143		sys.exit(0)
7144
7145	# verify that we can run a test
7146	error = statusCheck()
7147	if(error):
7148		doError(error)
7149
7150	# extract mem/disk extra modes and convert
7151	mode = sysvals.suspendmode
7152	if mode.startswith('mem'):
7153		memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
7154		if memmode == 'shallow':
7155			mode = 'standby'
7156		elif memmode ==  's2idle':
7157			mode = 'freeze'
7158		else:
7159			mode = 'mem'
7160		sysvals.memmode = memmode
7161		sysvals.suspendmode = mode
7162	if mode.startswith('disk-'):
7163		sysvals.diskmode = mode.split('-', 1)[-1]
7164		sysvals.suspendmode = 'disk'
 
7165	sysvals.systemInfo(dmidecode(sysvals.mempath))
7166
 
 
 
7167	failcnt, ret = 0, 0
7168	if sysvals.multitest['run']:
7169		# run multiple tests in a separate subdirectory
7170		if not sysvals.outdir:
7171			if 'time' in sysvals.multitest:
7172				s = '-%dm' % sysvals.multitest['time']
7173			else:
7174				s = '-x%d' % sysvals.multitest['count']
7175			sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
7176		if not os.path.isdir(sysvals.outdir):
7177			os.makedirs(sysvals.outdir)
7178		sysvals.sudoUserchown(sysvals.outdir)
7179		finish = datetime.now()
7180		if 'time' in sysvals.multitest:
7181			finish += timedelta(minutes=sysvals.multitest['time'])
7182		for i in range(sysvals.multitest['count']):
7183			sysvals.multistat(True, i, finish)
7184			if i != 0 and sysvals.multitest['delay'] > 0:
7185				pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
7186				time.sleep(sysvals.multitest['delay'])
7187			fmt = 'suspend-%y%m%d-%H%M%S'
7188			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
7189			ret = runTest(i+1, not sysvals.verbose)
7190			failcnt = 0 if not ret else failcnt + 1
7191			if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
7192				pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
7193				break
 
7194			sysvals.resetlog()
7195			sysvals.multistat(False, i, finish)
7196			if 'time' in sysvals.multitest and datetime.now() >= finish:
7197				break
7198		if not sysvals.skiphtml:
7199			runSummary(sysvals.outdir, False, False)
7200		sysvals.sudoUserchown(sysvals.outdir)
7201	else:
7202		if sysvals.outdir:
7203			sysvals.testdir = sysvals.outdir
7204		# run the test in the current directory
7205		ret = runTest()
7206
7207	# reset to default values after testing
7208	if sysvals.display:
7209		sysvals.displayControl('reset')
7210	if sysvals.rs != 0:
7211		sysvals.setRuntimeSuspend(False)
7212	sys.exit(ret)