Linux Audio

Check our new training course

Loading...
v5.14.15
   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.8'
  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	acpidebug = True
  96	tstat = True
  97	mindevlen = 0.0001
  98	mincglen = 0.0
  99	cgphase = ''
 100	cgtest = -1
 101	cgskip = ''
 102	maxfail = 0
 103	multitest = {'run': False, 'count': 1000000, 'delay': 0}
 104	max_graph_depth = 0
 105	callloopmaxgap = 0.0001
 106	callloopmaxlen = 0.005
 107	bufsize = 0
 108	cpucount = 0
 109	memtotal = 204800
 110	memfree = 204800
 111	srgap = 0
 112	cgexp = False
 113	testdir = ''
 114	outdir = ''
 115	tpath = '/sys/kernel/debug/tracing/'
 116	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
 117	epath = '/sys/kernel/debug/tracing/events/power/'
 118	pmdpath = '/sys/power/pm_debug_messages'
 119	acpipath='/sys/module/acpi/parameters/debug_level'
 120	traceevents = [
 121		'suspend_resume',
 122		'wakeup_source_activate',
 123		'wakeup_source_deactivate',
 124		'device_pm_callback_end',
 125		'device_pm_callback_start'
 126	]
 127	logmsg = ''
 128	testcommand = ''
 129	mempath = '/dev/mem'
 130	powerfile = '/sys/power/state'
 131	mempowerfile = '/sys/power/mem_sleep'
 132	diskpowerfile = '/sys/power/disk'
 133	suspendmode = 'mem'
 134	memmode = ''
 135	diskmode = ''
 136	hostname = 'localhost'
 137	prefix = 'test'
 138	teststamp = ''
 139	sysstamp = ''
 140	dmesgstart = 0.0
 141	dmesgfile = ''
 142	ftracefile = ''
 143	htmlfile = 'output.html'
 144	result = ''
 145	rtcwake = True
 146	rtcwaketime = 15
 147	rtcpath = ''
 148	devicefilter = []
 149	cgfilter = []
 150	stamp = 0
 151	execcount = 1
 152	x2delay = 0
 153	skiphtml = False
 154	usecallgraph = False
 155	ftopfunc = 'pm_suspend'
 156	ftop = False
 157	usetraceevents = False
 158	usetracemarkers = True
 159	usekprobes = True
 160	usedevsrc = False
 161	useprocmon = False
 162	notestrun = False
 163	cgdump = False
 164	devdump = False
 165	mixedphaseheight = True
 166	devprops = dict()
 167	cfgdef = dict()
 168	platinfo = []
 169	predelay = 0
 170	postdelay = 0
 171	tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
 172	tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
 
 
 173	tracefuncs = {
 174		'sys_sync': {},
 175		'ksys_sync': {},
 176		'__pm_notifier_call_chain': {},
 177		'pm_prepare_console': {},
 178		'pm_notifier_call_chain': {},
 179		'freeze_processes': {},
 180		'freeze_kernel_threads': {},
 181		'pm_restrict_gfp_mask': {},
 182		'acpi_suspend_begin': {},
 183		'acpi_hibernation_begin': {},
 184		'acpi_hibernation_enter': {},
 185		'acpi_hibernation_leave': {},
 186		'acpi_pm_freeze': {},
 187		'acpi_pm_thaw': {},
 188		'acpi_s2idle_end': {},
 189		'acpi_s2idle_sync': {},
 190		'acpi_s2idle_begin': {},
 191		'acpi_s2idle_prepare': {},
 192		'acpi_s2idle_prepare_late': {},
 193		'acpi_s2idle_wake': {},
 194		'acpi_s2idle_wakeup': {},
 195		'acpi_s2idle_restore': {},
 196		'acpi_s2idle_restore_early': {},
 197		'hibernate_preallocate_memory': {},
 198		'create_basic_memory_bitmaps': {},
 199		'swsusp_write': {},
 200		'suspend_console': {},
 201		'acpi_pm_prepare': {},
 202		'syscore_suspend': {},
 203		'arch_enable_nonboot_cpus_end': {},
 204		'syscore_resume': {},
 205		'acpi_pm_finish': {},
 206		'resume_console': {},
 207		'acpi_pm_end': {},
 208		'pm_restore_gfp_mask': {},
 209		'thaw_processes': {},
 210		'pm_restore_console': {},
 211		'CPU_OFF': {
 212			'func':'_cpu_down',
 213			'args_x86_64': {'cpu':'%di:s32'},
 214			'format': 'CPU_OFF[{cpu}]'
 215		},
 216		'CPU_ON': {
 217			'func':'_cpu_up',
 218			'args_x86_64': {'cpu':'%di:s32'},
 219			'format': 'CPU_ON[{cpu}]'
 220		},
 221	}
 222	dev_tracefuncs = {
 223		# general wait/delay/sleep
 224		'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
 
 225		'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
 226		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
 227		'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
 228		'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
 229		'acpi_os_stall': {'ub': 1},
 230		'rt_mutex_slowlock': {'ub': 1},
 231		# ACPI
 232		'acpi_resume_power_resources': {},
 233		'acpi_ps_execute_method': { 'args_x86_64': {
 234			'fullpath':'+0(+40(%di)):string',
 235		}},
 236		# mei_me
 237		'mei_reset': {},
 238		# filesystem
 239		'ext4_sync_fs': {},
 240		# 80211
 241		'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 242		'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
 243		'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
 244		'iwlagn_mac_start': {},
 245		'iwlagn_alloc_bcast_station': {},
 246		'iwl_trans_pcie_start_hw': {},
 247		'iwl_trans_pcie_start_fw': {},
 248		'iwl_run_init_ucode': {},
 249		'iwl_load_ucode_wait_alive': {},
 250		'iwl_alive_start': {},
 251		'iwlagn_mac_stop': {},
 252		'iwlagn_mac_suspend': {},
 253		'iwlagn_mac_resume': {},
 254		'iwlagn_mac_add_interface': {},
 255		'iwlagn_mac_remove_interface': {},
 256		'iwlagn_mac_change_interface': {},
 257		'iwlagn_mac_config': {},
 258		'iwlagn_configure_filter': {},
 259		'iwlagn_mac_hw_scan': {},
 260		'iwlagn_bss_info_changed': {},
 261		'iwlagn_mac_channel_switch': {},
 262		'iwlagn_mac_flush': {},
 263		# ATA
 264		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
 265		# i915
 266		'i915_gem_resume': {},
 267		'i915_restore_state': {},
 268		'intel_opregion_setup': {},
 269		'g4x_pre_enable_dp': {},
 270		'vlv_pre_enable_dp': {},
 271		'chv_pre_enable_dp': {},
 272		'g4x_enable_dp': {},
 273		'vlv_enable_dp': {},
 274		'intel_hpd_init': {},
 275		'intel_opregion_register': {},
 276		'intel_dp_detect': {},
 277		'intel_hdmi_detect': {},
 278		'intel_opregion_init': {},
 279		'intel_fbdev_set_suspend': {},
 280	}
 281	infocmds = [
 282		[0, 'kparams', 'cat', '/proc/cmdline'],
 283		[0, 'mcelog', 'mcelog'],
 284		[0, 'pcidevices', 'lspci', '-tv'],
 285		[0, 'usbdevices', 'lsusb', '-t'],
 286		[1, 'interrupts', 'cat', '/proc/interrupts'],
 287		[1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
 288		[2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
 289		[2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
 290		[2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
 291		[2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
 292	]
 293	cgblacklist = []
 294	kprobes = dict()
 295	timeformat = '%.3f'
 296	cmdline = '%s %s' % \
 297			(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
 298	sudouser = ''
 299	def __init__(self):
 300		self.archargs = 'args_'+platform.machine()
 301		self.hostname = platform.node()
 302		if(self.hostname == ''):
 303			self.hostname = 'localhost'
 304		rtc = "rtc0"
 305		if os.path.exists('/dev/rtc'):
 306			rtc = os.readlink('/dev/rtc')
 307		rtc = '/sys/class/rtc/'+rtc
 308		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 309			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 310			self.rtcpath = rtc
 311		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
 312			self.ansi = True
 313		self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
 314		if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
 315			os.environ['SUDO_USER']:
 316			self.sudouser = os.environ['SUDO_USER']
 317	def resetlog(self):
 318		self.logmsg = ''
 319		self.platinfo = []
 320	def vprint(self, msg):
 321		self.logmsg += msg+'\n'
 322		if self.verbose or msg.startswith('WARNING:'):
 323			pprint(msg)
 324	def signalHandler(self, signum, frame):
 325		if not self.result:
 326			return
 327		signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
 328		msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
 329		self.outputResult({'error':msg})
 330		sys.exit(3)
 331	def signalHandlerInit(self):
 332		capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
 333			'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM']
 334		self.signames = dict()
 335		for i in capture:
 336			s = 'SIG'+i
 337			try:
 338				signum = getattr(signal, s)
 339				signal.signal(signum, self.signalHandler)
 340			except:
 341				continue
 342			self.signames[signum] = s
 343	def rootCheck(self, fatal=True):
 344		if(os.access(self.powerfile, os.W_OK)):
 345			return True
 346		if fatal:
 347			msg = 'This command requires sysfs mount and root access'
 348			pprint('ERROR: %s\n' % msg)
 349			self.outputResult({'error':msg})
 350			sys.exit(1)
 351		return False
 352	def rootUser(self, fatal=False):
 353		if 'USER' in os.environ and os.environ['USER'] == 'root':
 354			return True
 355		if fatal:
 356			msg = 'This command must be run as root'
 357			pprint('ERROR: %s\n' % msg)
 358			self.outputResult({'error':msg})
 359			sys.exit(1)
 360		return False
 361	def usable(self, file):
 362		return (os.path.exists(file) and os.path.getsize(file) > 0)
 363	def getExec(self, cmd):
 364		try:
 365			fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
 366			out = ascii(fp.read()).strip()
 367			fp.close()
 368		except:
 369			out = ''
 370		if out:
 371			return out
 372		for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
 373			'/usr/local/sbin', '/usr/local/bin']:
 374			cmdfull = os.path.join(path, cmd)
 375			if os.path.exists(cmdfull):
 376				return cmdfull
 377		return out
 378	def setPrecision(self, num):
 379		if num < 0 or num > 6:
 380			return
 381		self.timeformat = '%.{0}f'.format(num)
 382	def setOutputFolder(self, value):
 383		args = dict()
 384		n = datetime.now()
 385		args['date'] = n.strftime('%y%m%d')
 386		args['time'] = n.strftime('%H%M%S')
 387		args['hostname'] = args['host'] = self.hostname
 388		args['mode'] = self.suspendmode
 389		return value.format(**args)
 390	def setOutputFile(self):
 391		if self.dmesgfile != '':
 392			m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
 393			if(m):
 394				self.htmlfile = m.group('name')+'.html'
 395		if self.ftracefile != '':
 396			m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
 397			if(m):
 398				self.htmlfile = m.group('name')+'.html'
 399	def systemInfo(self, info):
 400		p = m = ''
 401		if 'baseboard-manufacturer' in info:
 402			m = info['baseboard-manufacturer']
 403		elif 'system-manufacturer' in info:
 404			m = info['system-manufacturer']
 405		if 'system-product-name' in info:
 406			p = info['system-product-name']
 407		elif 'baseboard-product-name' in info:
 408			p = info['baseboard-product-name']
 409		if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
 410			p = info['baseboard-product-name']
 411		c = info['processor-version'] if 'processor-version' in info else ''
 412		b = info['bios-version'] if 'bios-version' in info else ''
 413		r = info['bios-release-date'] if 'bios-release-date' in info else ''
 414		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
 415			(m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
 
 
 
 416	def printSystemInfo(self, fatal=False):
 417		self.rootCheck(True)
 418		out = dmidecode(self.mempath, fatal)
 419		if len(out) < 1:
 420			return
 421		fmt = '%-24s: %s'
 422		for name in sorted(out):
 423			print(fmt % (name, out[name]))
 424		print(fmt % ('cpucount', ('%d' % self.cpucount)))
 425		print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
 426		print(fmt % ('memfree', ('%d kB' % self.memfree)))
 427	def cpuInfo(self):
 428		self.cpucount = 0
 429		fp = open('/proc/cpuinfo', 'r')
 430		for line in fp:
 431			if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
 432				self.cpucount += 1
 433		fp.close()
 434		fp = open('/proc/meminfo', 'r')
 435		for line in fp:
 436			m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
 437			if m:
 438				self.memtotal = int(m.group('sz'))
 439			m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
 440			if m:
 441				self.memfree = int(m.group('sz'))
 442		fp.close()
 443	def initTestOutput(self, name):
 444		self.prefix = self.hostname
 445		v = open('/proc/version', 'r').read().strip()
 446		kver = v.split()[2]
 447		fmt = name+'-%m%d%y-%H%M%S'
 448		testtime = datetime.now().strftime(fmt)
 449		self.teststamp = \
 450			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 451		ext = ''
 452		if self.gzip:
 453			ext = '.gz'
 454		self.dmesgfile = \
 455			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
 456		self.ftracefile = \
 457			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
 458		self.htmlfile = \
 459			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 460		if not os.path.isdir(self.testdir):
 461			os.makedirs(self.testdir)
 462		self.sudoUserchown(self.testdir)
 463	def getValueList(self, value):
 464		out = []
 465		for i in value.split(','):
 466			if i.strip():
 467				out.append(i.strip())
 468		return out
 469	def setDeviceFilter(self, value):
 470		self.devicefilter = self.getValueList(value)
 471	def setCallgraphFilter(self, value):
 472		self.cgfilter = self.getValueList(value)
 473	def skipKprobes(self, value):
 474		for k in self.getValueList(value):
 475			if k in self.tracefuncs:
 476				del self.tracefuncs[k]
 477			if k in self.dev_tracefuncs:
 478				del self.dev_tracefuncs[k]
 479	def setCallgraphBlacklist(self, file):
 480		self.cgblacklist = self.listFromFile(file)
 481	def rtcWakeAlarmOn(self):
 482		call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
 483		nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
 484		if nowtime:
 485			nowtime = int(nowtime)
 486		else:
 487			# if hardware time fails, use the software time
 488			nowtime = int(datetime.now().strftime('%s'))
 489		alarm = nowtime + self.rtcwaketime
 490		call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
 491	def rtcWakeAlarmOff(self):
 492		call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
 493	def initdmesg(self):
 494		# get the latest time stamp from the dmesg log
 495		lines = Popen('dmesg', stdout=PIPE).stdout.readlines()
 496		ktime = '0'
 497		for line in reversed(lines):
 498			line = ascii(line).replace('\r\n', '')
 499			idx = line.find('[')
 500			if idx > 1:
 501				line = line[idx:]
 502			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 503			if(m):
 504				ktime = m.group('ktime')
 505				break
 506		self.dmesgstart = float(ktime)
 507	def getdmesg(self, testdata):
 508		op = self.writeDatafileHeader(self.dmesgfile, testdata)
 509		# store all new dmesg lines since initdmesg was called
 510		fp = Popen('dmesg', stdout=PIPE).stdout
 511		for line in fp:
 512			line = ascii(line).replace('\r\n', '')
 513			idx = line.find('[')
 514			if idx > 1:
 515				line = line[idx:]
 516			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 517			if(not m):
 518				continue
 519			ktime = float(m.group('ktime'))
 520			if ktime > self.dmesgstart:
 521				op.write(line)
 522		fp.close()
 523		op.close()
 524	def listFromFile(self, file):
 525		list = []
 526		fp = open(file)
 527		for i in fp.read().split('\n'):
 528			i = i.strip()
 529			if i and i[0] != '#':
 530				list.append(i)
 531		fp.close()
 532		return list
 533	def addFtraceFilterFunctions(self, file):
 534		for i in self.listFromFile(file):
 535			if len(i) < 2:
 536				continue
 537			self.tracefuncs[i] = dict()
 538	def getFtraceFilterFunctions(self, current):
 539		self.rootCheck(True)
 540		if not current:
 541			call('cat '+self.tpath+'available_filter_functions', shell=True)
 542			return
 543		master = self.listFromFile(self.tpath+'available_filter_functions')
 544		for i in sorted(self.tracefuncs):
 545			if 'func' in self.tracefuncs[i]:
 546				i = self.tracefuncs[i]['func']
 547			if i in master:
 548				print(i)
 549			else:
 550				print(self.colorText(i))
 551	def setFtraceFilterFunctions(self, list):
 552		master = self.listFromFile(self.tpath+'available_filter_functions')
 553		flist = ''
 554		for i in list:
 555			if i not in master:
 556				continue
 557			if ' [' in i:
 558				flist += i.split(' ')[0]+'\n'
 559			else:
 560				flist += i+'\n'
 561		fp = open(self.tpath+'set_graph_function', 'w')
 562		fp.write(flist)
 563		fp.close()
 564	def basicKprobe(self, name):
 565		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
 566	def defaultKprobe(self, name, kdata):
 567		k = kdata
 568		for field in ['name', 'format', 'func']:
 569			if field not in k:
 570				k[field] = name
 571		if self.archargs in k:
 572			k['args'] = k[self.archargs]
 573		else:
 574			k['args'] = dict()
 575			k['format'] = name
 576		self.kprobes[name] = k
 577	def kprobeColor(self, name):
 578		if name not in self.kprobes or 'color' not in self.kprobes[name]:
 579			return ''
 580		return self.kprobes[name]['color']
 581	def kprobeDisplayName(self, name, dataraw):
 582		if name not in self.kprobes:
 583			self.basicKprobe(name)
 584		data = ''
 585		quote=0
 586		# first remvoe any spaces inside quotes, and the quotes
 587		for c in dataraw:
 588			if c == '"':
 589				quote = (quote + 1) % 2
 590			if quote and c == ' ':
 591				data += '_'
 592			elif c != '"':
 593				data += c
 594		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
 595		arglist = dict()
 596		# now process the args
 597		for arg in sorted(args):
 598			arglist[arg] = ''
 599			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
 600			if m:
 601				arglist[arg] = m.group('arg')
 602			else:
 603				m = re.match('.* '+arg+'=(?P<arg>.*)', data);
 604				if m:
 605					arglist[arg] = m.group('arg')
 606		out = fmt.format(**arglist)
 607		out = out.replace(' ', '_').replace('"', '')
 608		return out
 609	def kprobeText(self, kname, kprobe):
 610		name = fmt = func = kname
 611		args = dict()
 612		if 'name' in kprobe:
 613			name = kprobe['name']
 614		if 'format' in kprobe:
 615			fmt = kprobe['format']
 616		if 'func' in kprobe:
 617			func = kprobe['func']
 618		if self.archargs in kprobe:
 619			args = kprobe[self.archargs]
 620		if 'args' in kprobe:
 621			args = kprobe['args']
 622		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
 623			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
 624		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
 625			if arg not in args:
 626				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
 627		val = 'p:%s_cal %s' % (name, func)
 628		for i in sorted(args):
 629			val += ' %s=%s' % (i, args[i])
 630		val += '\nr:%s_ret %s $retval\n' % (name, func)
 631		return val
 632	def addKprobes(self, output=False):
 633		if len(self.kprobes) < 1:
 634			return
 635		if output:
 636			pprint('    kprobe functions in this kernel:')
 637		# first test each kprobe
 638		rejects = []
 639		# sort kprobes: trace, ub-dev, custom, dev
 640		kpl = [[], [], [], []]
 641		linesout = len(self.kprobes)
 642		for name in sorted(self.kprobes):
 643			res = self.colorText('YES', 32)
 644			if not self.testKprobe(name, self.kprobes[name]):
 645				res = self.colorText('NO')
 646				rejects.append(name)
 647			else:
 648				if name in self.tracefuncs:
 649					kpl[0].append(name)
 650				elif name in self.dev_tracefuncs:
 651					if 'ub' in self.dev_tracefuncs[name]:
 652						kpl[1].append(name)
 653					else:
 654						kpl[3].append(name)
 655				else:
 656					kpl[2].append(name)
 657			if output:
 658				pprint('         %s: %s' % (name, res))
 659		kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
 660		# remove all failed ones from the list
 661		for name in rejects:
 662			self.kprobes.pop(name)
 663		# set the kprobes all at once
 664		self.fsetVal('', 'kprobe_events')
 665		kprobeevents = ''
 666		for kp in kplist:
 667			kprobeevents += self.kprobeText(kp, self.kprobes[kp])
 668		self.fsetVal(kprobeevents, 'kprobe_events')
 669		if output:
 670			check = self.fgetVal('kprobe_events')
 671			linesack = (len(check.split('\n')) - 1) // 2
 672			pprint('    kprobe functions enabled: %d/%d' % (linesack, linesout))
 673		self.fsetVal('1', 'events/kprobes/enable')
 674	def testKprobe(self, kname, kprobe):
 675		self.fsetVal('0', 'events/kprobes/enable')
 676		kprobeevents = self.kprobeText(kname, kprobe)
 677		if not kprobeevents:
 678			return False
 679		try:
 680			self.fsetVal(kprobeevents, 'kprobe_events')
 681			check = self.fgetVal('kprobe_events')
 682		except:
 683			return False
 684		linesout = len(kprobeevents.split('\n'))
 685		linesack = len(check.split('\n'))
 686		if linesack < linesout:
 687			return False
 688		return True
 689	def setVal(self, val, file):
 690		if not os.path.exists(file):
 691			return False
 692		try:
 693			fp = open(file, 'wb', 0)
 694			fp.write(val.encode())
 695			fp.flush()
 696			fp.close()
 697		except:
 698			return False
 699		return True
 700	def fsetVal(self, val, path):
 701		return self.setVal(val, self.tpath+path)
 702	def getVal(self, file):
 703		res = ''
 704		if not os.path.exists(file):
 705			return res
 706		try:
 707			fp = open(file, 'r')
 708			res = fp.read()
 709			fp.close()
 710		except:
 711			pass
 712		return res
 713	def fgetVal(self, path):
 714		return self.getVal(self.tpath+path)
 715	def cleanupFtrace(self):
 716		if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
 717			self.fsetVal('0', 'events/kprobes/enable')
 718			self.fsetVal('', 'kprobe_events')
 719			self.fsetVal('1024', 'buffer_size_kb')
 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		self.testVal(self.pmdpath, 'basic', '1')
 744		# set the trace clock to global
 745		self.fsetVal('global', 'trace_clock')
 746		self.fsetVal('nop', 'current_tracer')
 747		# set trace buffer to an appropriate value
 748		cpus = max(1, self.cpucount)
 749		if self.bufsize > 0:
 750			tgtsize = self.bufsize
 751		elif self.usecallgraph or self.usedevsrc:
 752			bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
 753				else (3*1024*1024)
 754			tgtsize = min(self.memfree, bmax)
 755		else:
 756			tgtsize = 65536
 757		while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
 758			# if the size failed to set, lower it and keep trying
 759			tgtsize -= 65536
 760			if tgtsize < 65536:
 761				tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
 762				break
 763		self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
 764		# initialize the callgraph trace
 765		if(self.usecallgraph):
 766			# set trace type
 767			self.fsetVal('function_graph', 'current_tracer')
 768			self.fsetVal('', 'set_ftrace_filter')
 769			# set trace format options
 770			self.fsetVal('print-parent', 'trace_options')
 771			self.fsetVal('funcgraph-abstime', 'trace_options')
 772			self.fsetVal('funcgraph-cpu', 'trace_options')
 773			self.fsetVal('funcgraph-duration', 'trace_options')
 774			self.fsetVal('funcgraph-proc', 'trace_options')
 775			self.fsetVal('funcgraph-tail', 'trace_options')
 776			self.fsetVal('nofuncgraph-overhead', 'trace_options')
 777			self.fsetVal('context-info', 'trace_options')
 778			self.fsetVal('graph-time', 'trace_options')
 779			self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
 780			cf = ['dpm_run_callback']
 781			if(self.usetraceevents):
 782				cf += ['dpm_prepare', 'dpm_complete']
 783			for fn in self.tracefuncs:
 784				if 'func' in self.tracefuncs[fn]:
 785					cf.append(self.tracefuncs[fn]['func'])
 786				else:
 787					cf.append(fn)
 788			if self.ftop:
 789				self.setFtraceFilterFunctions([self.ftopfunc])
 790			else:
 791				self.setFtraceFilterFunctions(cf)
 792		# initialize the kprobe trace
 793		elif self.usekprobes:
 794			for name in self.tracefuncs:
 795				self.defaultKprobe(name, self.tracefuncs[name])
 796			if self.usedevsrc:
 797				for name in self.dev_tracefuncs:
 798					self.defaultKprobe(name, self.dev_tracefuncs[name])
 799			if not quiet:
 800				pprint('INITIALIZING KPROBES...')
 801			self.addKprobes(self.verbose)
 802		if(self.usetraceevents):
 803			# turn trace events on
 804			events = iter(self.traceevents)
 805			for e in events:
 806				self.fsetVal('1', 'events/power/'+e+'/enable')
 807		# clear the trace buffer
 808		self.fsetVal('', 'trace')
 809	def verifyFtrace(self):
 810		# files needed for any trace data
 811		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 812				 'trace_marker', 'trace_options', 'tracing_on']
 813		# files needed for callgraph trace data
 814		tp = self.tpath
 815		if(self.usecallgraph):
 816			files += [
 817				'available_filter_functions',
 818				'set_ftrace_filter',
 819				'set_graph_function'
 820			]
 821		for f in files:
 822			if(os.path.exists(tp+f) == False):
 823				return False
 824		return True
 825	def verifyKprobes(self):
 826		# files needed for kprobes to work
 827		files = ['kprobe_events', 'events']
 828		tp = self.tpath
 829		for f in files:
 830			if(os.path.exists(tp+f) == False):
 831				return False
 832		return True
 833	def colorText(self, str, color=31):
 834		if not self.ansi:
 835			return str
 836		return '\x1B[%d;40m%s\x1B[m' % (color, str)
 837	def writeDatafileHeader(self, filename, testdata):
 838		fp = self.openlog(filename, 'w')
 839		fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
 840		for test in testdata:
 841			if 'fw' in test:
 842				fw = test['fw']
 843				if(fw):
 844					fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
 845			if 'turbo' in test:
 846				fp.write('# turbostat %s\n' % test['turbo'])
 847			if 'wifi' in test:
 848				fp.write('# wifi %s\n' % test['wifi'])
 849			if test['error'] or len(testdata) > 1:
 850				fp.write('# enter_sleep_error %s\n' % test['error'])
 851		return fp
 852	def sudoUserchown(self, dir):
 853		if os.path.exists(dir) and self.sudouser:
 
 854			cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
 855			call(cmd.format(self.sudouser, dir), shell=True)
 856	def outputResult(self, testdata, num=0):
 857		if not self.result:
 858			return
 859		n = ''
 860		if num > 0:
 861			n = '%d' % num
 862		fp = open(self.result, 'a')
 863		if 'error' in testdata:
 864			fp.write('result%s: fail\n' % n)
 865			fp.write('error%s: %s\n' % (n, testdata['error']))
 866		else:
 867			fp.write('result%s: pass\n' % n)
 868		for v in ['suspend', 'resume', 'boot', 'lastinit']:
 869			if v in testdata:
 870				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
 871		for v in ['fwsuspend', 'fwresume']:
 872			if v in testdata:
 873				fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
 874		if 'bugurl' in testdata:
 875			fp.write('url%s: %s\n' % (n, testdata['bugurl']))
 876		fp.close()
 877		self.sudoUserchown(self.result)
 878	def configFile(self, file):
 879		dir = os.path.dirname(os.path.realpath(__file__))
 880		if os.path.exists(file):
 881			return file
 882		elif os.path.exists(dir+'/'+file):
 883			return dir+'/'+file
 884		elif os.path.exists(dir+'/config/'+file):
 885			return dir+'/config/'+file
 886		return ''
 887	def openlog(self, filename, mode):
 888		isgz = self.gzip
 889		if mode == 'r':
 890			try:
 891				with gzip.open(filename, mode+'t') as fp:
 892					test = fp.read(64)
 893				isgz = True
 894			except:
 895				isgz = False
 896		if isgz:
 897			return gzip.open(filename, mode+'t')
 898		return open(filename, mode)
 899	def putlog(self, filename, text):
 900		with self.openlog(filename, 'a') as fp:
 901			fp.write(text)
 902			fp.close()
 903	def dlog(self, text):
 904		self.putlog(self.dmesgfile, '# %s\n' % text)
 905	def flog(self, text):
 906		self.putlog(self.ftracefile, text)
 907	def b64unzip(self, data):
 908		try:
 909			out = codecs.decode(base64.b64decode(data), 'zlib').decode()
 910		except:
 911			out = data
 912		return out
 913	def b64zip(self, data):
 914		out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
 915		return out
 916	def platforminfo(self, cmdafter):
 917		# add platform info on to a completed ftrace file
 918		if not os.path.exists(self.ftracefile):
 919			return False
 920		footer = '#\n'
 921
 922		# add test command string line if need be
 923		if self.suspendmode == 'command' and self.testcommand:
 924			footer += '# platform-testcmd: %s\n' % (self.testcommand)
 925
 926		# get a list of target devices from the ftrace file
 927		props = dict()
 928		tp = TestProps()
 929		tf = self.openlog(self.ftracefile, 'r')
 930		for line in tf:
 931			if tp.stampInfo(line, self):
 932				continue
 933			# parse only valid lines, if this is not one move on
 934			m = re.match(tp.ftrace_line_fmt, line)
 935			if(not m or 'device_pm_callback_start' not in line):
 936				continue
 937			m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
 938			if(not m):
 939				continue
 940			dev = m.group('d')
 941			if dev not in props:
 942				props[dev] = DevProps()
 943		tf.close()
 944
 945		# now get the syspath for each target device
 946		for dirname, dirnames, filenames in os.walk('/sys/devices'):
 947			if(re.match('.*/power', dirname) and 'async' in filenames):
 948				dev = dirname.split('/')[-2]
 949				if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
 950					props[dev].syspath = dirname[:-6]
 951
 952		# now fill in the properties for our target devices
 953		for dev in sorted(props):
 954			dirname = props[dev].syspath
 955			if not dirname or not os.path.exists(dirname):
 956				continue
 957			with open(dirname+'/power/async') as fp:
 958				text = fp.read()
 959				props[dev].isasync = False
 960				if 'enabled' in text:
 961					props[dev].isasync = True
 962			fields = os.listdir(dirname)
 963			if 'product' in fields:
 964				with open(dirname+'/product', 'rb') as fp:
 965					props[dev].altname = ascii(fp.read())
 966			elif 'name' in fields:
 967				with open(dirname+'/name', 'rb') as fp:
 968					props[dev].altname = ascii(fp.read())
 969			elif 'model' in fields:
 970				with open(dirname+'/model', 'rb') as fp:
 971					props[dev].altname = ascii(fp.read())
 972			elif 'description' in fields:
 973				with open(dirname+'/description', 'rb') as fp:
 974					props[dev].altname = ascii(fp.read())
 975			elif 'id' in fields:
 976				with open(dirname+'/id', 'rb') as fp:
 977					props[dev].altname = ascii(fp.read())
 978			elif 'idVendor' in fields and 'idProduct' in fields:
 979				idv, idp = '', ''
 980				with open(dirname+'/idVendor', 'rb') as fp:
 981					idv = ascii(fp.read()).strip()
 982				with open(dirname+'/idProduct', 'rb') as fp:
 983					idp = ascii(fp.read()).strip()
 984				props[dev].altname = '%s:%s' % (idv, idp)
 985			if props[dev].altname:
 986				out = props[dev].altname.strip().replace('\n', ' ')\
 987					.replace(',', ' ').replace(';', ' ')
 988				props[dev].altname = out
 989
 990		# add a devinfo line to the bottom of ftrace
 991		out = ''
 992		for dev in sorted(props):
 993			out += props[dev].out(dev)
 994		footer += '# platform-devinfo: %s\n' % self.b64zip(out)
 995
 996		# add a line for each of these commands with their outputs
 997		for name, cmdline, info in cmdafter:
 998			footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
 999		self.flog(footer)
1000		return True
1001	def commonPrefix(self, list):
1002		if len(list) < 2:
1003			return ''
1004		prefix = list[0]
1005		for s in list[1:]:
1006			while s[:len(prefix)] != prefix and prefix:
1007				prefix = prefix[:len(prefix)-1]
1008			if not prefix:
1009				break
1010		if '/' in prefix and prefix[-1] != '/':
1011			prefix = prefix[0:prefix.rfind('/')+1]
1012		return prefix
1013	def dictify(self, text, format):
1014		out = dict()
1015		header = True if format == 1 else False
1016		delim = ' ' if format == 1 else ':'
1017		for line in text.split('\n'):
1018			if header:
1019				header, out['@'] = False, line
1020				continue
1021			line = line.strip()
1022			if delim in line:
1023				data = line.split(delim, 1)
1024				num = re.search(r'[\d]+', data[1])
1025				if format == 2 and num:
1026					out[data[0].strip()] = num.group()
1027				else:
1028					out[data[0].strip()] = data[1]
1029		return out
1030	def cmdinfo(self, begin, debug=False):
1031		out = []
1032		if begin:
1033			self.cmd1 = dict()
1034		for cargs in self.infocmds:
1035			delta, name = cargs[0], cargs[1]
1036			cmdline, cmdpath = ' '.join(cargs[2:]), self.getExec(cargs[2])
1037			if not cmdpath or (begin and not delta):
1038				continue
1039			self.dlog('[%s]' % cmdline)
1040			try:
1041				fp = Popen([cmdpath]+cargs[3:], stdout=PIPE, stderr=PIPE).stdout
1042				info = ascii(fp.read()).strip()
1043				fp.close()
1044			except:
1045				continue
1046			if not debug and begin:
1047				self.cmd1[name] = self.dictify(info, delta)
1048			elif not debug and delta and name in self.cmd1:
1049				before, after = self.cmd1[name], self.dictify(info, delta)
1050				dinfo = ('\t%s\n' % before['@']) if '@' in before else ''
1051				prefix = self.commonPrefix(list(before.keys()))
1052				for key in sorted(before):
1053					if key in after and before[key] != after[key]:
1054						title = key.replace(prefix, '')
1055						if delta == 2:
1056							dinfo += '\t%s : %s -> %s\n' % \
1057								(title, before[key].strip(), after[key].strip())
1058						else:
1059							dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1060								(title, before[key], title, after[key])
1061				dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1062				out.append((name, cmdline, dinfo))
1063			else:
1064				out.append((name, cmdline, '\tnothing' if not info else info))
1065		return out
1066	def testVal(self, file, fmt='basic', value=''):
1067		if file == 'restoreall':
1068			for f in self.cfgdef:
1069				if os.path.exists(f):
1070					fp = open(f, 'w')
1071					fp.write(self.cfgdef[f])
1072					fp.close()
1073			self.cfgdef = dict()
1074		elif value and os.path.exists(file):
1075			fp = open(file, 'r+')
1076			if fmt == 'radio':
1077				m = re.match('.*\[(?P<v>.*)\].*', fp.read())
1078				if m:
1079					self.cfgdef[file] = m.group('v')
1080			elif fmt == 'acpi':
1081				line = fp.read().strip().split('\n')[-1]
1082				m = re.match('.* (?P<v>[0-9A-Fx]*) .*', line)
1083				if m:
1084					self.cfgdef[file] = m.group('v')
1085			else:
1086				self.cfgdef[file] = fp.read().strip()
1087			fp.write(value)
1088			fp.close()
1089	def haveTurbostat(self):
1090		if not self.tstat:
1091			return False
1092		cmd = self.getExec('turbostat')
1093		if not cmd:
1094			return False
1095		fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1096		out = ascii(fp.read()).strip()
1097		fp.close()
1098		if re.match('turbostat version .*', out):
1099			self.vprint(out)
1100			return True
1101		return False
1102	def turbostat(self):
1103		cmd = self.getExec('turbostat')
1104		rawout = keyline = valline = ''
1105		fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1106		fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr
1107		for line in fp:
1108			line = ascii(line)
1109			rawout += line
1110			if keyline and valline:
1111				continue
1112			if re.match('(?i)Avg_MHz.*', line):
1113				keyline = line.strip().split()
1114			elif keyline:
1115				valline = line.strip().split()
1116		fp.close()
1117		if not keyline or not valline or len(keyline) != len(valline):
1118			errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1119			self.vprint(errmsg)
1120			if not self.verbose:
1121				pprint(errmsg)
1122			return ''
1123		if self.verbose:
1124			pprint(rawout.strip())
1125		out = []
1126		for key in keyline:
1127			idx = keyline.index(key)
1128			val = valline[idx]
1129			out.append('%s=%s' % (key, val))
1130		return '|'.join(out)
1131	def wifiDetails(self, dev):
1132		try:
1133			info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1134		except:
1135			return dev
1136		vals = [dev]
1137		for prop in info.split('\n'):
1138			if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1139				vals.append(prop.split('=')[-1])
1140		return ':'.join(vals)
1141	def checkWifi(self, dev=''):
1142		try:
1143			w = open('/proc/net/wireless', 'r').read().strip()
1144		except:
1145			return ''
1146		for line in reversed(w.split('\n')):
1147			m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', w.split('\n')[-1])
1148			if not m or (dev and dev != m.group('dev')):
1149				continue
1150			return m.group('dev')
1151		return ''
1152	def pollWifi(self, dev, timeout=60):
1153		start = time.time()
1154		while (time.time() - start) < timeout:
1155			w = self.checkWifi(dev)
1156			if w:
1157				return '%s reconnected %.2f' % \
1158					(self.wifiDetails(dev), max(0, time.time() - start))
1159			time.sleep(0.01)
1160		return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1161	def errorSummary(self, errinfo, msg):
1162		found = False
1163		for entry in errinfo:
1164			if re.match(entry['match'], msg):
1165				entry['count'] += 1
1166				if self.hostname not in entry['urls']:
1167					entry['urls'][self.hostname] = [self.htmlfile]
1168				elif self.htmlfile not in entry['urls'][self.hostname]:
1169					entry['urls'][self.hostname].append(self.htmlfile)
1170				found = True
1171				break
1172		if found:
1173			return
1174		arr = msg.split()
1175		for j in range(len(arr)):
1176			if re.match('^[0-9,\-\.]*$', arr[j]):
1177				arr[j] = '[0-9,\-\.]*'
1178			else:
1179				arr[j] = arr[j]\
1180					.replace('\\', '\\\\').replace(']', '\]').replace('[', '\[')\
1181					.replace('.', '\.').replace('+', '\+').replace('*', '\*')\
1182					.replace('(', '\(').replace(')', '\)').replace('}', '\}')\
1183					.replace('{', '\{')
1184		mstr = ' *'.join(arr)
1185		entry = {
1186			'line': msg,
1187			'match': mstr,
1188			'count': 1,
1189			'urls': {self.hostname: [self.htmlfile]}
1190		}
1191		errinfo.append(entry)
1192	def multistat(self, start, idx, finish):
1193		if 'time' in self.multitest:
1194			id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1195		else:
1196			id = '%d/%d' % (idx+1, self.multitest['count'])
1197		t = time.time()
1198		if 'start' not in self.multitest:
1199			self.multitest['start'] = self.multitest['last'] = t
1200			self.multitest['total'] = 0.0
1201			pprint('TEST (%s) START' % id)
1202			return
1203		dt = t - self.multitest['last']
1204		if not start:
1205			if idx == 0 and self.multitest['delay'] > 0:
1206				self.multitest['total'] += self.multitest['delay']
1207			pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1208			return
1209		self.multitest['total'] += dt
1210		self.multitest['last'] = t
1211		avg = self.multitest['total'] / idx
1212		if 'time' in self.multitest:
1213			left = finish - datetime.now()
1214			left -= timedelta(microseconds=left.microseconds)
1215		else:
1216			left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1217		pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1218			(id, avg, str(left)))
1219	def multiinit(self, c, d):
1220		sz, unit = 'count', 'm'
1221		if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1222			sz, unit, c = 'time', c[-1], c[:-1]
1223		self.multitest['run'] = True
1224		self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1225		self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1226		if unit == 'd':
1227			self.multitest[sz] *= 1440
1228		elif unit == 'h':
1229			self.multitest[sz] *= 60
1230	def displayControl(self, cmd):
1231		xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
1232		if self.sudouser:
1233			xset = 'sudo -u %s %s' % (self.sudouser, xset)
1234		if cmd == 'init':
1235			ret = call(xset.format('dpms 0 0 0'), shell=True)
1236			if not ret:
1237				ret = call(xset.format('s off'), shell=True)
1238		elif cmd == 'reset':
1239			ret = call(xset.format('s reset'), shell=True)
1240		elif cmd in ['on', 'off', 'standby', 'suspend']:
1241			b4 = self.displayControl('stat')
1242			ret = call(xset.format('dpms force %s' % cmd), shell=True)
1243			if not ret:
1244				curr = self.displayControl('stat')
1245				self.vprint('Display Switched: %s -> %s' % (b4, curr))
1246				if curr != cmd:
1247					self.vprint('WARNING: Display failed to change to %s' % cmd)
1248			if ret:
1249				self.vprint('WARNING: Display failed to change to %s with xset' % cmd)
1250				return ret
1251		elif cmd == 'stat':
1252			fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
1253			ret = 'unknown'
1254			for line in fp:
1255				m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))
1256				if(m and len(m.group('m')) >= 2):
1257					out = m.group('m').lower()
1258					ret = out[3:] if out[0:2] == 'in' else out
1259					break
1260			fp.close()
1261		return ret
1262	def setRuntimeSuspend(self, before=True):
1263		if before:
1264			# runtime suspend disable or enable
1265			if self.rs > 0:
1266				self.rstgt, self.rsval, self.rsdir = 'on', 'auto', 'enabled'
1267			else:
1268				self.rstgt, self.rsval, self.rsdir = 'auto', 'on', 'disabled'
1269			pprint('CONFIGURING RUNTIME SUSPEND...')
1270			self.rslist = deviceInfo(self.rstgt)
1271			for i in self.rslist:
1272				self.setVal(self.rsval, i)
1273			pprint('runtime suspend %s on all devices (%d changed)' % (self.rsdir, len(self.rslist)))
1274			pprint('waiting 5 seconds...')
1275			time.sleep(5)
1276		else:
1277			# runtime suspend re-enable or re-disable
1278			for i in self.rslist:
1279				self.setVal(self.rstgt, i)
1280			pprint('runtime suspend settings restored on %d devices' % len(self.rslist))
1281
1282sysvals = SystemValues()
1283switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1284switchoff = ['disable', 'off', 'false', '0']
1285suspendmodename = {
1286	'freeze': 'Freeze (S0)',
1287	'standby': 'Standby (S1)',
1288	'mem': 'Suspend (S3)',
1289	'disk': 'Hibernate (S4)'
1290}
1291
1292# Class: DevProps
1293# Description:
1294#	 Simple class which holds property values collected
1295#	 for all the devices used in the timeline.
1296class DevProps:
1297	def __init__(self):
1298		self.syspath = ''
1299		self.altname = ''
1300		self.isasync = True
1301		self.xtraclass = ''
1302		self.xtrainfo = ''
1303	def out(self, dev):
1304		return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1305	def debug(self, dev):
1306		pprint('%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.isasync))
1307	def altName(self, dev):
1308		if not self.altname or self.altname == dev:
1309			return dev
1310		return '%s [%s]' % (self.altname, dev)
1311	def xtraClass(self):
1312		if self.xtraclass:
1313			return ' '+self.xtraclass
1314		if not self.isasync:
1315			return ' sync'
1316		return ''
1317	def xtraInfo(self):
1318		if self.xtraclass:
1319			return ' '+self.xtraclass
1320		if self.isasync:
1321			return ' (async)'
1322		return ' (sync)'
1323
1324# Class: DeviceNode
1325# Description:
1326#	 A container used to create a device hierachy, with a single root node
1327#	 and a tree of child nodes. Used by Data.deviceTopology()
1328class DeviceNode:
 
 
 
1329	def __init__(self, nodename, nodedepth):
1330		self.name = nodename
1331		self.children = []
1332		self.depth = nodedepth
1333
1334# Class: Data
1335# Description:
1336#	 The primary container for suspend/resume test data. There is one for
1337#	 each test run. The data is organized into a cronological hierarchy:
1338#	 Data.dmesg {
1339#		phases {
1340#			10 sequential, non-overlapping phases of S/R
1341#			contents: times for phase start/end, order/color data for html
1342#			devlist {
1343#				device callback or action list for this phase
1344#				device {
1345#					a single device callback or generic action
1346#					contents: start/stop times, pid/cpu/driver info
1347#						parents/children, html id for timeline/callgraph
1348#						optionally includes an ftrace callgraph
1349#						optionally includes dev/ps data
1350#				}
1351#			}
1352#		}
1353#	}
1354#
1355class Data:
1356	phasedef = {
1357		'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1358		        'suspend': {'order': 1, 'color': '#88FF88'},
1359		   'suspend_late': {'order': 2, 'color': '#00AA00'},
1360		  'suspend_noirq': {'order': 3, 'color': '#008888'},
1361		'suspend_machine': {'order': 4, 'color': '#0000FF'},
1362		 'resume_machine': {'order': 5, 'color': '#FF0000'},
1363		   'resume_noirq': {'order': 6, 'color': '#FF9900'},
1364		   'resume_early': {'order': 7, 'color': '#FFCC00'},
1365		         'resume': {'order': 8, 'color': '#FFFF88'},
1366		'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1367	}
1368	errlist = {
1369		'HWERROR' : r'.*\[ *Hardware Error *\].*',
1370		'FWBUG'   : r'.*\[ *Firmware Bug *\].*',
1371		'BUG'     : r'(?i).*\bBUG\b.*',
1372		'ERROR'   : r'(?i).*\bERROR\b.*',
1373		'WARNING' : r'(?i).*\bWARNING\b.*',
1374		'FAULT'   : r'(?i).*\bFAULT\b.*',
1375		'FAIL'    : r'(?i).*\bFAILED\b.*',
1376		'INVALID' : r'(?i).*\bINVALID\b.*',
1377		'CRASH'   : r'(?i).*\bCRASHED\b.*',
1378		'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
1379		'IRQ'     : r'.*\bgenirq: .*',
1380		'TASKFAIL': r'.*Freezing of tasks *.*',
1381		'ACPI'    : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1382		'DISKFULL': r'.*\bNo space left on device.*',
1383		'USBERR'  : r'.*usb .*device .*, error [0-9-]*',
1384		'ATAERR'  : r' *ata[0-9\.]*: .*failed.*',
1385		'MEIERR'  : r' *mei.*: .*failed.*',
1386		'TPMERR'  : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1387	}
1388	def __init__(self, num):
1389		idchar = 'abcdefghij'
1390		self.start = 0.0 # test start
1391		self.end = 0.0   # test end
1392		self.hwstart = 0 # rtc test start
1393		self.hwend = 0   # rtc test end
1394		self.tSuspended = 0.0 # low-level suspend start
1395		self.tResumed = 0.0   # low-level resume start
1396		self.tKernSus = 0.0   # kernel level suspend start
1397		self.tKernRes = 0.0   # kernel level resume end
1398		self.fwValid = False  # is firmware data available
1399		self.fwSuspend = 0    # time spent in firmware suspend
1400		self.fwResume = 0     # time spent in firmware resume
1401		self.html_device_id = 0
1402		self.stamp = 0
1403		self.outfile = ''
1404		self.kerror = False
1405		self.wifi = dict()
1406		self.turbostat = 0
1407		self.enterfail = ''
1408		self.currphase = ''
1409		self.pstl = dict()    # process timeline
1410		self.testnumber = num
1411		self.idstr = idchar[num]
1412		self.dmesgtext = []   # dmesg text file in memory
1413		self.dmesg = dict()   # root data structure
1414		self.errorinfo = {'suspend':[],'resume':[]}
1415		self.tLow = []        # time spent in low-level suspends (standby/freeze)
1416		self.devpids = []
1417		self.devicegroups = 0
1418	def sortedPhases(self):
1419		return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1420	def initDevicegroups(self):
1421		# called when phases are all finished being added
1422		for phase in sorted(self.dmesg.keys()):
1423			if '*' in phase:
1424				p = phase.split('*')
1425				pnew = '%s%d' % (p[0], len(p))
1426				self.dmesg[pnew] = self.dmesg.pop(phase)
 
 
 
 
 
 
 
 
 
 
1427		self.devicegroups = []
1428		for phase in self.sortedPhases():
1429			self.devicegroups.append([phase])
1430	def nextPhase(self, phase, offset):
1431		order = self.dmesg[phase]['order'] + offset
1432		for p in self.dmesg:
1433			if self.dmesg[p]['order'] == order:
1434				return p
1435		return ''
1436	def lastPhase(self, depth=1):
1437		plist = self.sortedPhases()
1438		if len(plist) < depth:
1439			return ''
1440		return plist[-1*depth]
1441	def turbostatInfo(self):
1442		tp = TestProps()
1443		out = {'syslpi':'N/A','pkgpc10':'N/A'}
1444		for line in self.dmesgtext:
1445			m = re.match(tp.tstatfmt, line)
1446			if not m:
1447				continue
1448			for i in m.group('t').split('|'):
1449				if 'SYS%LPI' in i:
1450					out['syslpi'] = i.split('=')[-1]+'%'
1451				elif 'pc10' in i:
1452					out['pkgpc10'] = i.split('=')[-1]+'%'
1453			break
1454		return out
1455	def extractErrorInfo(self):
1456		lf = self.dmesgtext
1457		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1458			lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1459		i = 0
1460		tp = TestProps()
1461		list = []
 
 
 
1462		for line in lf:
1463			i += 1
1464			if tp.stampInfo(line, sysvals):
1465				continue
1466			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1467			if not m:
1468				continue
1469			t = float(m.group('ktime'))
1470			if t < self.start or t > self.end:
1471				continue
1472			dir = 'suspend' if t < self.tSuspended else 'resume'
 
 
 
1473			msg = m.group('msg')
1474			if re.match('capability: warning: .*', msg):
1475				continue
1476			for err in self.errlist:
1477				if re.match(self.errlist[err], msg):
1478					list.append((msg, err, dir, t, i, i))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1479					self.kerror = True
1480					break
1481		tp.msglist = []
1482		for msg, type, dir, t, idx1, idx2 in list:
1483			tp.msglist.append(msg)
 
 
 
 
 
 
 
 
 
 
 
1484			self.errorinfo[dir].append((type, t, idx1, idx2))
1485		if self.kerror:
1486			sysvals.dmesglog = True
1487		if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1488			lf.close()
1489		return tp
1490	def setStart(self, time, msg=''):
1491		self.start = time
1492		if msg:
1493			try:
1494				self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1495			except:
1496				self.hwstart = 0
1497	def setEnd(self, time, msg=''):
1498		self.end = time
1499		if msg:
1500			try:
1501				self.hwend = datetime.strptime(msg, sysvals.tmend)
1502			except:
1503				self.hwend = 0
1504	def isTraceEventOutsideDeviceCalls(self, pid, time):
1505		for phase in self.sortedPhases():
1506			list = self.dmesg[phase]['list']
1507			for dev in list:
1508				d = list[dev]
1509				if(d['pid'] == pid and time >= d['start'] and
1510					time < d['end']):
1511					return False
1512		return True
 
 
 
 
 
 
 
 
1513	def sourcePhase(self, start):
1514		for phase in self.sortedPhases():
1515			if 'machine' in phase:
1516				continue
1517			pend = self.dmesg[phase]['end']
1518			if start <= pend:
1519				return phase
1520		return 'resume_complete'
1521	def sourceDevice(self, phaselist, start, end, pid, type):
1522		tgtdev = ''
1523		for phase in phaselist:
1524			list = self.dmesg[phase]['list']
1525			for devname in list:
1526				dev = list[devname]
1527				# pid must match
1528				if dev['pid'] != pid:
1529					continue
1530				devS = dev['start']
1531				devE = dev['end']
1532				if type == 'device':
1533					# device target event is entirely inside the source boundary
1534					if(start < devS or start >= devE or end <= devS or end > devE):
1535						continue
1536				elif type == 'thread':
1537					# thread target event will expand the source boundary
1538					if start < devS:
1539						dev['start'] = start
1540					if end > devE:
1541						dev['end'] = end
1542				tgtdev = dev
1543				break
1544		return tgtdev
1545	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1546		# try to place the call in a device
1547		phases = self.sortedPhases()
1548		tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1549		# calls with device pids that occur outside device bounds are dropped
1550		# TODO: include these somehow
1551		if not tgtdev and pid in self.devpids:
1552			return False
1553		# try to place the call in a thread
1554		if not tgtdev:
1555			tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1556		# create new thread blocks, expand as new calls are found
1557		if not tgtdev:
1558			if proc == '<...>':
1559				threadname = 'kthread-%d' % (pid)
1560			else:
1561				threadname = '%s-%d' % (proc, pid)
1562			tgtphase = self.sourcePhase(start)
1563			self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1564			return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1565		# this should not happen
1566		if not tgtdev:
1567			sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1568				(start, end, proc, pid, kprobename, cdata, rdata))
1569			return False
1570		# place the call data inside the src element of the tgtdev
1571		if('src' not in tgtdev):
1572			tgtdev['src'] = []
1573		dtf = sysvals.dev_tracefuncs
1574		ubiquitous = False
1575		if kprobename in dtf and 'ub' in dtf[kprobename]:
1576			ubiquitous = True
1577		title = cdata+' '+rdata
1578		mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1579		m = re.match(mstr, title)
1580		if m:
1581			c = m.group('caller')
1582			a = m.group('args').strip()
1583			r = m.group('ret')
1584			if len(r) > 6:
1585				r = ''
1586			else:
1587				r = 'ret=%s ' % r
1588			if ubiquitous and c in dtf and 'ub' in dtf[c]:
1589				return False
1590		color = sysvals.kprobeColor(kprobename)
1591		e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1592		tgtdev['src'].append(e)
1593		return True
1594	def overflowDevices(self):
1595		# get a list of devices that extend beyond the end of this test run
1596		devlist = []
1597		for phase in self.sortedPhases():
1598			list = self.dmesg[phase]['list']
1599			for devname in list:
1600				dev = list[devname]
1601				if dev['end'] > self.end:
1602					devlist.append(dev)
1603		return devlist
1604	def mergeOverlapDevices(self, devlist):
1605		# merge any devices that overlap devlist
1606		for dev in devlist:
1607			devname = dev['name']
1608			for phase in self.sortedPhases():
1609				list = self.dmesg[phase]['list']
1610				if devname not in list:
1611					continue
1612				tdev = list[devname]
1613				o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1614				if o <= 0:
1615					continue
1616				dev['end'] = tdev['end']
1617				if 'src' not in dev or 'src' not in tdev:
1618					continue
1619				dev['src'] += tdev['src']
1620				del list[devname]
1621	def usurpTouchingThread(self, name, dev):
1622		# the caller test has priority of this thread, give it to him
1623		for phase in self.sortedPhases():
1624			list = self.dmesg[phase]['list']
1625			if name in list:
1626				tdev = list[name]
1627				if tdev['start'] - dev['end'] < 0.1:
1628					dev['end'] = tdev['end']
1629					if 'src' not in dev:
1630						dev['src'] = []
1631					if 'src' in tdev:
1632						dev['src'] += tdev['src']
1633					del list[name]
1634				break
1635	def stitchTouchingThreads(self, testlist):
1636		# merge any threads between tests that touch
1637		for phase in self.sortedPhases():
1638			list = self.dmesg[phase]['list']
1639			for devname in list:
1640				dev = list[devname]
1641				if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1642					continue
1643				for data in testlist:
1644					data.usurpTouchingThread(devname, dev)
1645	def optimizeDevSrc(self):
1646		# merge any src call loops to reduce timeline size
1647		for phase in self.sortedPhases():
1648			list = self.dmesg[phase]['list']
1649			for dev in list:
1650				if 'src' not in list[dev]:
1651					continue
1652				src = list[dev]['src']
1653				p = 0
1654				for e in sorted(src, key=lambda event: event.time):
1655					if not p or not e.repeat(p):
1656						p = e
1657						continue
1658					# e is another iteration of p, move it into p
1659					p.end = e.end
1660					p.length = p.end - p.time
1661					p.count += 1
1662					src.remove(e)
1663	def trimTimeVal(self, t, t0, dT, left):
1664		if left:
1665			if(t > t0):
1666				if(t - dT < t0):
1667					return t0
1668				return t - dT
1669			else:
1670				return t
1671		else:
1672			if(t < t0 + dT):
1673				if(t > t0):
1674					return t0 + dT
1675				return t + dT
1676			else:
1677				return t
1678	def trimTime(self, t0, dT, left):
1679		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1680		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1681		self.start = self.trimTimeVal(self.start, t0, dT, left)
1682		self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1683		self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1684		self.end = self.trimTimeVal(self.end, t0, dT, left)
1685		for phase in self.sortedPhases():
1686			p = self.dmesg[phase]
1687			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1688			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1689			list = p['list']
1690			for name in list:
1691				d = list[name]
1692				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1693				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1694				d['length'] = d['end'] - d['start']
1695				if('ftrace' in d):
1696					cg = d['ftrace']
1697					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1698					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1699					for line in cg.list:
1700						line.time = self.trimTimeVal(line.time, t0, dT, left)
1701				if('src' in d):
1702					for e in d['src']:
1703						e.time = self.trimTimeVal(e.time, t0, dT, left)
1704						e.end = self.trimTimeVal(e.end, t0, dT, left)
1705						e.length = e.end - e.time
1706		for dir in ['suspend', 'resume']:
1707			list = []
1708			for e in self.errorinfo[dir]:
1709				type, tm, idx1, idx2 = e
1710				tm = self.trimTimeVal(tm, t0, dT, left)
1711				list.append((type, tm, idx1, idx2))
1712			self.errorinfo[dir] = list
1713	def trimFreezeTime(self, tZero):
1714		# trim out any standby or freeze clock time
1715		lp = ''
1716		for phase in self.sortedPhases():
1717			if 'resume_machine' in phase and 'suspend_machine' in lp:
1718				tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1719				tL = tR - tS
1720				if tL <= 0:
1721					continue
1722				left = True if tR > tZero else False
1723				self.trimTime(tS, tL, left)
1724				if 'waking' in self.dmesg[lp]:
1725					tCnt = self.dmesg[lp]['waking'][0]
1726					if self.dmesg[lp]['waking'][1] >= 0.001:
1727						tTry = '-%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
1728					else:
1729						tTry = '-%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
1730					text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
1731				else:
1732					text = '%.0f' % (tL * 1000)
1733				self.tLow.append(text)
1734			lp = phase
1735	def getMemTime(self):
1736		if not self.hwstart or not self.hwend:
1737			return
1738		stime = (self.tSuspended - self.start) * 1000000
1739		rtime = (self.end - self.tResumed) * 1000000
1740		hws = self.hwstart + timedelta(microseconds=stime)
1741		hwr = self.hwend - timedelta(microseconds=rtime)
1742		self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1743	def getTimeValues(self):
1744		sktime = (self.tSuspended - self.tKernSus) * 1000
1745		rktime = (self.tKernRes - self.tResumed) * 1000
 
 
1746		return (sktime, rktime)
1747	def setPhase(self, phase, ktime, isbegin, order=-1):
1748		if(isbegin):
1749			# phase start over current phase
1750			if self.currphase:
1751				if 'resume_machine' not in self.currphase:
1752					sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1753				self.dmesg[self.currphase]['end'] = ktime
1754			phases = self.dmesg.keys()
1755			color = self.phasedef[phase]['color']
1756			count = len(phases) if order < 0 else order
1757			# create unique name for every new phase
1758			while phase in phases:
1759				phase += '*'
1760			self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1761				'row': 0, 'color': color, 'order': count}
1762			self.dmesg[phase]['start'] = ktime
1763			self.currphase = phase
1764		else:
1765			# phase end without a start
1766			if phase not in self.currphase:
1767				if self.currphase:
1768					sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1769				else:
1770					sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1771					return phase
1772			phase = self.currphase
1773			self.dmesg[phase]['end'] = ktime
1774			self.currphase = ''
1775		return phase
 
 
1776	def sortedDevices(self, phase):
1777		list = self.dmesg[phase]['list']
1778		return sorted(list, key=lambda k:list[k]['start'])
 
 
 
 
 
 
 
 
 
1779	def fixupInitcalls(self, phase):
1780		# if any calls never returned, clip them at system resume end
1781		phaselist = self.dmesg[phase]['list']
1782		for devname in phaselist:
1783			dev = phaselist[devname]
1784			if(dev['end'] < 0):
1785				for p in self.sortedPhases():
1786					if self.dmesg[p]['end'] > dev['start']:
1787						dev['end'] = self.dmesg[p]['end']
1788						break
1789				sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1790	def deviceFilter(self, devicefilter):
1791		for phase in self.sortedPhases():
1792			list = self.dmesg[phase]['list']
1793			rmlist = []
1794			for name in list:
1795				keep = False
1796				for filter in devicefilter:
1797					if filter in name or \
1798						('drv' in list[name] and filter in list[name]['drv']):
1799						keep = True
1800				if not keep:
1801					rmlist.append(name)
1802			for name in rmlist:
1803				del list[name]
1804	def fixupInitcallsThatDidntReturn(self):
1805		# if any calls never returned, clip them at system resume end
1806		for phase in self.sortedPhases():
1807			self.fixupInitcalls(phase)
1808	def phaseOverlap(self, phases):
1809		rmgroups = []
1810		newgroup = []
1811		for group in self.devicegroups:
1812			for phase in phases:
1813				if phase not in group:
1814					continue
1815				for p in group:
1816					if p not in newgroup:
1817						newgroup.append(p)
1818				if group not in rmgroups:
1819					rmgroups.append(group)
1820		for group in rmgroups:
1821			self.devicegroups.remove(group)
1822		self.devicegroups.append(newgroup)
1823	def newActionGlobal(self, name, start, end, pid=-1, color=''):
1824		# which phase is this device callback or action in
1825		phases = self.sortedPhases()
1826		targetphase = 'none'
1827		htmlclass = ''
1828		overlap = 0.0
1829		myphases = []
1830		for phase in phases:
1831			pstart = self.dmesg[phase]['start']
1832			pend = self.dmesg[phase]['end']
1833			# see if the action overlaps this phase
1834			o = max(0, min(end, pend) - max(start, pstart))
1835			if o > 0:
1836				myphases.append(phase)
1837			# set the target phase to the one that overlaps most
1838			if o > overlap:
1839				if overlap > 0 and phase == 'post_resume':
1840					continue
1841				targetphase = phase
1842				overlap = o
1843		# if no target phase was found, pin it to the edge
1844		if targetphase == 'none':
1845			p0start = self.dmesg[phases[0]]['start']
1846			if start <= p0start:
1847				targetphase = phases[0]
1848			else:
1849				targetphase = phases[-1]
1850		if pid == -2:
1851			htmlclass = ' bg'
1852		elif pid == -3:
1853			htmlclass = ' ps'
1854		if len(myphases) > 1:
1855			htmlclass = ' bg'
1856			self.phaseOverlap(myphases)
1857		if targetphase in phases:
1858			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1859			return (targetphase, newname)
1860		return False
1861	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1862		# new device callback for a specific phase
1863		self.html_device_id += 1
1864		devid = '%s%d' % (self.idstr, self.html_device_id)
1865		list = self.dmesg[phase]['list']
1866		length = -1.0
1867		if(start >= 0 and end >= 0):
1868			length = end - start
1869		if pid == -2 or name not in sysvals.tracefuncs.keys():
1870			i = 2
1871			origname = name
1872			while(name in list):
1873				name = '%s[%d]' % (origname, i)
1874				i += 1
1875		list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1876			'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1877		if htmlclass:
1878			list[name]['htmlclass'] = htmlclass
1879		if color:
1880			list[name]['color'] = color
1881		return name
1882	def findDevice(self, phase, name):
1883		list = self.dmesg[phase]['list']
1884		mydev = ''
1885		for devname in sorted(list):
1886			if name == devname or re.match('^%s\[(?P<num>[0-9]*)\]$' % name, devname):
1887				mydev = devname
1888		if mydev:
1889			return list[mydev]
1890		return False
1891	def deviceChildren(self, devname, phase):
1892		devlist = []
1893		list = self.dmesg[phase]['list']
1894		for child in list:
1895			if(list[child]['par'] == devname):
1896				devlist.append(child)
1897		return devlist
1898	def maxDeviceNameSize(self, phase):
1899		size = 0
1900		for name in self.dmesg[phase]['list']:
1901			if len(name) > size:
1902				size = len(name)
1903		return size
1904	def printDetails(self):
1905		sysvals.vprint('Timeline Details:')
1906		sysvals.vprint('          test start: %f' % self.start)
1907		sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1908		tS = tR = False
1909		for phase in self.sortedPhases():
1910			devlist = self.dmesg[phase]['list']
1911			dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
1912			if not tS and ps >= self.tSuspended:
1913				sysvals.vprint('   machine suspended: %f' % self.tSuspended)
1914				tS = True
1915			if not tR and ps >= self.tResumed:
1916				sysvals.vprint('     machine resumed: %f' % self.tResumed)
1917				tR = True
1918			sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
1919			if sysvals.devdump:
1920				sysvals.vprint(''.join('-' for i in range(80)))
1921				maxname = '%d' % self.maxDeviceNameSize(phase)
1922				fmt = '%3d) %'+maxname+'s - %f - %f'
1923				c = 1
1924				for name in sorted(devlist):
1925					s = devlist[name]['start']
1926					e = devlist[name]['end']
1927					sysvals.vprint(fmt % (c, name, s, e))
1928					c += 1
1929				sysvals.vprint(''.join('-' for i in range(80)))
1930		sysvals.vprint('   kernel resume end: %f' % self.tKernRes)
1931		sysvals.vprint('            test end: %f' % self.end)
1932	def deviceChildrenAllPhases(self, devname):
1933		devlist = []
1934		for phase in self.sortedPhases():
1935			list = self.deviceChildren(devname, phase)
1936			for dev in sorted(list):
1937				if dev not in devlist:
1938					devlist.append(dev)
1939		return devlist
1940	def masterTopology(self, name, list, depth):
1941		node = DeviceNode(name, depth)
1942		for cname in list:
1943			# avoid recursions
1944			if name == cname:
1945				continue
1946			clist = self.deviceChildrenAllPhases(cname)
1947			cnode = self.masterTopology(cname, clist, depth+1)
1948			node.children.append(cnode)
1949		return node
1950	def printTopology(self, node):
1951		html = ''
1952		if node.name:
1953			info = ''
1954			drv = ''
1955			for phase in self.sortedPhases():
1956				list = self.dmesg[phase]['list']
1957				if node.name in list:
1958					s = list[node.name]['start']
1959					e = list[node.name]['end']
1960					if list[node.name]['drv']:
1961						drv = ' {'+list[node.name]['drv']+'}'
1962					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1963			html += '<li><b>'+node.name+drv+'</b>'
1964			if info:
1965				html += '<ul>'+info+'</ul>'
1966			html += '</li>'
1967		if len(node.children) > 0:
1968			html += '<ul>'
1969			for cnode in node.children:
1970				html += self.printTopology(cnode)
1971			html += '</ul>'
1972		return html
1973	def rootDeviceList(self):
1974		# list of devices graphed
1975		real = []
1976		for phase in self.sortedPhases():
1977			list = self.dmesg[phase]['list']
1978			for dev in sorted(list):
1979				if list[dev]['pid'] >= 0 and dev not in real:
1980					real.append(dev)
1981		# list of top-most root devices
1982		rootlist = []
1983		for phase in self.sortedPhases():
1984			list = self.dmesg[phase]['list']
1985			for dev in sorted(list):
1986				pdev = list[dev]['par']
1987				pid = list[dev]['pid']
1988				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1989					continue
1990				if pdev and pdev not in real and pdev not in rootlist:
1991					rootlist.append(pdev)
1992		return rootlist
1993	def deviceTopology(self):
1994		rootlist = self.rootDeviceList()
1995		master = self.masterTopology('', rootlist, 0)
1996		return self.printTopology(master)
1997	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1998		# only select devices that will actually show up in html
1999		self.tdevlist = dict()
2000		for phase in self.dmesg:
2001			devlist = []
2002			list = self.dmesg[phase]['list']
2003			for dev in list:
2004				length = (list[dev]['end'] - list[dev]['start']) * 1000
2005				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
2006				if length >= mindevlen:
2007					devlist.append(dev)
2008			self.tdevlist[phase] = devlist
2009	def addHorizontalDivider(self, devname, devend):
2010		phase = 'suspend_prepare'
2011		self.newAction(phase, devname, -2, '', \
2012			self.start, devend, '', ' sec', '')
2013		if phase not in self.tdevlist:
2014			self.tdevlist[phase] = []
2015		self.tdevlist[phase].append(devname)
2016		d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
2017		return d
2018	def addProcessUsageEvent(self, name, times):
2019		# get the start and end times for this process
2020		maxC = 0
2021		tlast = 0
2022		start = -1
2023		end = -1
2024		for t in sorted(times):
2025			if tlast == 0:
2026				tlast = t
2027				continue
2028			if name in self.pstl[t]:
2029				if start == -1 or tlast < start:
2030					start = tlast
2031				if end == -1 or t > end:
2032					end = t
2033			tlast = t
2034		if start == -1 or end == -1:
2035			return 0
2036		# add a new action for this process and get the object
2037		out = self.newActionGlobal(name, start, end, -3)
2038		if not out:
2039			return 0
2040		phase, devname = out
2041		dev = self.dmesg[phase]['list'][devname]
2042		# get the cpu exec data
2043		tlast = 0
2044		clast = 0
2045		cpuexec = dict()
2046		for t in sorted(times):
2047			if tlast == 0 or t <= start or t > end:
2048				tlast = t
2049				continue
2050			list = self.pstl[t]
2051			c = 0
2052			if name in list:
2053				c = list[name]
2054			if c > maxC:
2055				maxC = c
2056			if c != clast:
2057				key = (tlast, t)
2058				cpuexec[key] = c
2059				tlast = t
2060				clast = c
2061		dev['cpuexec'] = cpuexec
2062		return maxC
2063	def createProcessUsageEvents(self):
2064		# get an array of process names
2065		proclist = []
2066		for t in sorted(self.pstl):
2067			pslist = self.pstl[t]
2068			for ps in sorted(pslist):
2069				if ps not in proclist:
2070					proclist.append(ps)
2071		# get a list of data points for suspend and resume
2072		tsus = []
2073		tres = []
2074		for t in sorted(self.pstl):
2075			if t < self.tSuspended:
2076				tsus.append(t)
2077			else:
2078				tres.append(t)
2079		# process the events for suspend and resume
2080		if len(proclist) > 0:
2081			sysvals.vprint('Process Execution:')
2082		for ps in proclist:
2083			c = self.addProcessUsageEvent(ps, tsus)
2084			if c > 0:
2085				sysvals.vprint('%25s (sus): %d' % (ps, c))
2086			c = self.addProcessUsageEvent(ps, tres)
2087			if c > 0:
2088				sysvals.vprint('%25s (res): %d' % (ps, c))
2089	def handleEndMarker(self, time, msg=''):
2090		dm = self.dmesg
2091		self.setEnd(time, msg)
2092		self.initDevicegroups()
2093		# give suspend_prepare an end if needed
2094		if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2095			dm['suspend_prepare']['end'] = time
2096		# assume resume machine ends at next phase start
2097		if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2098			np = self.nextPhase('resume_machine', 1)
2099			if np:
2100				dm['resume_machine']['end'] = dm[np]['start']
2101		# if kernel resume end not found, assume its the end marker
2102		if self.tKernRes == 0.0:
2103			self.tKernRes = time
2104		# if kernel suspend start not found, assume its the end marker
2105		if self.tKernSus == 0.0:
2106			self.tKernSus = time
2107		# set resume complete to end at end marker
2108		if 'resume_complete' in dm:
2109			dm['resume_complete']['end'] = time
2110	def debugPrint(self):
2111		for p in self.sortedPhases():
2112			list = self.dmesg[p]['list']
2113			for devname in sorted(list):
2114				dev = list[devname]
2115				if 'ftrace' in dev:
2116					dev['ftrace'].debugPrint(' [%s]' % devname)
2117
2118# Class: DevFunction
2119# Description:
2120#	 A container for kprobe function data we want in the dev timeline
2121class DevFunction:
 
 
2122	def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2123		self.row = 0
2124		self.count = 1
2125		self.name = name
2126		self.args = args
2127		self.caller = caller
2128		self.ret = ret
2129		self.time = start
2130		self.length = end - start
2131		self.end = end
2132		self.ubiquitous = u
2133		self.proc = proc
2134		self.pid = pid
2135		self.color = color
2136	def title(self):
2137		cnt = ''
2138		if self.count > 1:
2139			cnt = '(x%d)' % self.count
2140		l = '%0.3fms' % (self.length * 1000)
2141		if self.ubiquitous:
2142			title = '%s(%s)%s <- %s, %s(%s)' % \
2143				(self.name, self.args, cnt, self.caller, self.ret, l)
2144		else:
2145			title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2146		return title.replace('"', '')
2147	def text(self):
2148		if self.count > 1:
2149			text = '%s(x%d)' % (self.name, self.count)
2150		else:
2151			text = self.name
2152		return text
2153	def repeat(self, tgt):
2154		# is the tgt call just a repeat of this call (e.g. are we in a loop)
2155		dt = self.time - tgt.end
2156		# only combine calls if -all- attributes are identical
2157		if tgt.caller == self.caller and \
2158			tgt.name == self.name and tgt.args == self.args and \
2159			tgt.proc == self.proc and tgt.pid == self.pid and \
2160			tgt.ret == self.ret and dt >= 0 and \
2161			dt <= sysvals.callloopmaxgap and \
2162			self.length < sysvals.callloopmaxlen:
2163			return True
2164		return False
2165
2166# Class: FTraceLine
2167# Description:
2168#	 A container for a single line of ftrace data. There are six basic types:
2169#		 callgraph line:
2170#			  call: "  dpm_run_callback() {"
2171#			return: "  }"
2172#			  leaf: " dpm_run_callback();"
2173#		 trace event:
2174#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
2175#			 suspend_resume: phase or custom exec block data
2176#			 device_pm_callback: device callback info
2177class FTraceLine:
 
 
 
 
 
 
 
 
 
2178	def __init__(self, t, m='', d=''):
2179		self.length = 0.0
2180		self.fcall = False
2181		self.freturn = False
2182		self.fevent = False
2183		self.fkprobe = False
2184		self.depth = 0
2185		self.name = ''
2186		self.type = ''
2187		self.time = float(t)
2188		if not m and not d:
2189			return
2190		# is this a trace event
2191		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2192			if(d == 'traceevent'):
2193				# nop format trace event
2194				msg = m
2195			else:
2196				# function_graph format trace event
2197				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2198				msg = em.group('msg')
2199
2200			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
2201			if(emm):
2202				self.name = emm.group('msg')
2203				self.type = emm.group('call')
2204			else:
2205				self.name = msg
2206			km = re.match('^(?P<n>.*)_cal$', self.type)
2207			if km:
2208				self.fcall = True
2209				self.fkprobe = True
2210				self.type = km.group('n')
2211				return
2212			km = re.match('^(?P<n>.*)_ret$', self.type)
2213			if km:
2214				self.freturn = True
2215				self.fkprobe = True
2216				self.type = km.group('n')
2217				return
2218			self.fevent = True
2219			return
2220		# convert the duration to seconds
2221		if(d):
2222			self.length = float(d)/1000000
2223		# the indentation determines the depth
2224		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
2225		if(not match):
2226			return
2227		self.depth = self.getDepth(match.group('d'))
2228		m = match.group('o')
2229		# function return
2230		if(m[0] == '}'):
2231			self.freturn = True
2232			if(len(m) > 1):
2233				# includes comment with function name
2234				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2235				if(match):
2236					self.name = match.group('n').strip()
2237		# function call
2238		else:
2239			self.fcall = True
2240			# function call with children
2241			if(m[-1] == '{'):
2242				match = re.match('^(?P<n>.*) *\(.*', m)
2243				if(match):
2244					self.name = match.group('n').strip()
2245			# function call with no children (leaf)
2246			elif(m[-1] == ';'):
2247				self.freturn = True
2248				match = re.match('^(?P<n>.*) *\(.*', m)
2249				if(match):
2250					self.name = match.group('n').strip()
2251			# something else (possibly a trace marker)
2252			else:
2253				self.name = m
2254	def isCall(self):
2255		return self.fcall and not self.freturn
2256	def isReturn(self):
2257		return self.freturn and not self.fcall
2258	def isLeaf(self):
2259		return self.fcall and self.freturn
2260	def getDepth(self, str):
2261		return len(str)/2
2262	def debugPrint(self, info=''):
2263		if self.isLeaf():
2264			pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2265				self.depth, self.name, self.length*1000000, info))
2266		elif self.freturn:
2267			pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2268				self.depth, self.name, self.length*1000000, info))
2269		else:
2270			pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2271				self.depth, self.name, self.length*1000000, info))
2272	def startMarker(self):
2273		# Is this the starting line of a suspend?
2274		if not self.fevent:
2275			return False
2276		if sysvals.usetracemarkers:
2277			if(self.name.startswith('SUSPEND START')):
2278				return True
2279			return False
2280		else:
2281			if(self.type == 'suspend_resume' and
2282				re.match('suspend_enter\[.*\] begin', self.name)):
2283				return True
2284			return False
2285	def endMarker(self):
2286		# Is this the ending line of a resume?
2287		if not self.fevent:
2288			return False
2289		if sysvals.usetracemarkers:
2290			if(self.name.startswith('RESUME COMPLETE')):
2291				return True
2292			return False
2293		else:
2294			if(self.type == 'suspend_resume' and
2295				re.match('thaw_processes\[.*\] end', self.name)):
2296				return True
2297			return False
2298
2299# Class: FTraceCallGraph
2300# Description:
2301#	 A container for the ftrace callgraph of a single recursive function.
2302#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2303#	 Each instance is tied to a single device in a single phase, and is
2304#	 comprised of an ordered list of FTraceLine objects
2305class FTraceCallGraph:
 
 
 
 
 
 
 
 
 
2306	vfname = 'missing_function_name'
 
 
2307	def __init__(self, pid, sv):
2308		self.id = ''
2309		self.invalid = False
2310		self.name = ''
2311		self.partial = False
2312		self.ignore = False
2313		self.start = -1.0
2314		self.end = -1.0
2315		self.list = []
2316		self.depth = 0
2317		self.pid = pid
2318		self.sv = sv
2319	def addLine(self, line):
2320		# if this is already invalid, just leave
2321		if(self.invalid):
2322			if(line.depth == 0 and line.freturn):
2323				return 1
2324			return 0
2325		# invalidate on bad depth
2326		if(self.depth < 0):
2327			self.invalidate(line)
2328			return 0
2329		# ignore data til we return to the current depth
2330		if self.ignore:
2331			if line.depth > self.depth:
2332				return 0
2333			else:
2334				self.list[-1].freturn = True
2335				self.list[-1].length = line.time - self.list[-1].time
2336				self.ignore = False
2337				# if this is a return at self.depth, no more work is needed
2338				if line.depth == self.depth and line.isReturn():
2339					if line.depth == 0:
2340						self.end = line.time
2341						return 1
2342					return 0
2343		# compare current depth with this lines pre-call depth
2344		prelinedep = line.depth
2345		if line.isReturn():
2346			prelinedep += 1
2347		last = 0
2348		lasttime = line.time
2349		if len(self.list) > 0:
2350			last = self.list[-1]
2351			lasttime = last.time
2352			if last.isLeaf():
2353				lasttime += last.length
2354		# handle low misalignments by inserting returns
2355		mismatch = prelinedep - self.depth
2356		warning = self.sv.verbose and abs(mismatch) > 1
2357		info = []
2358		if mismatch < 0:
2359			idx = 0
2360			# add return calls to get the depth down
2361			while prelinedep < self.depth:
2362				self.depth -= 1
2363				if idx == 0 and last and last.isCall():
2364					# special case, turn last call into a leaf
2365					last.depth = self.depth
2366					last.freturn = True
2367					last.length = line.time - last.time
2368					if warning:
2369						info.append(('[make leaf]', last))
2370				else:
2371					vline = FTraceLine(lasttime)
2372					vline.depth = self.depth
2373					vline.name = self.vfname
2374					vline.freturn = True
2375					self.list.append(vline)
2376					if warning:
2377						if idx == 0:
2378							info.append(('', last))
2379						info.append(('[add return]', vline))
2380				idx += 1
2381			if warning:
2382				info.append(('', line))
2383		# handle high misalignments by inserting calls
2384		elif mismatch > 0:
2385			idx = 0
2386			if warning:
2387				info.append(('', last))
2388			# add calls to get the depth up
2389			while prelinedep > self.depth:
2390				if idx == 0 and line.isReturn():
2391					# special case, turn this return into a leaf
2392					line.fcall = True
2393					prelinedep -= 1
2394					if warning:
2395						info.append(('[make leaf]', line))
2396				else:
2397					vline = FTraceLine(lasttime)
2398					vline.depth = self.depth
2399					vline.name = self.vfname
2400					vline.fcall = True
2401					self.list.append(vline)
2402					self.depth += 1
2403					if not last:
2404						self.start = vline.time
2405					if warning:
2406						info.append(('[add call]', vline))
2407				idx += 1
2408			if warning and ('[make leaf]', line) not in info:
2409				info.append(('', line))
2410		if warning:
2411			pprint('WARNING: ftrace data missing, corrections made:')
2412			for i in info:
2413				t, obj = i
2414				if obj:
2415					obj.debugPrint(t)
2416		# process the call and set the new depth
2417		skipadd = False
2418		md = self.sv.max_graph_depth
2419		if line.isCall():
2420			# ignore blacklisted/overdepth funcs
2421			if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2422				self.ignore = True
2423			else:
2424				self.depth += 1
2425		elif line.isReturn():
2426			self.depth -= 1
2427			# remove blacklisted/overdepth/empty funcs that slipped through
2428			if (last and last.isCall() and last.depth == line.depth) or \
2429				(md and last and last.depth >= md) or \
2430				(line.name in self.sv.cgblacklist):
2431				while len(self.list) > 0 and self.list[-1].depth > line.depth:
2432					self.list.pop(-1)
2433				if len(self.list) == 0:
2434					self.invalid = True
2435					return 1
2436				self.list[-1].freturn = True
2437				self.list[-1].length = line.time - self.list[-1].time
2438				self.list[-1].name = line.name
2439				skipadd = True
2440		if len(self.list) < 1:
2441			self.start = line.time
2442		# check for a mismatch that returned all the way to callgraph end
2443		res = 1
2444		if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2445			line = self.list[-1]
2446			skipadd = True
2447			res = -1
2448		if not skipadd:
2449			self.list.append(line)
2450		if(line.depth == 0 and line.freturn):
2451			if(self.start < 0):
2452				self.start = line.time
2453			self.end = line.time
2454			if line.fcall:
2455				self.end += line.length
2456			if self.list[0].name == self.vfname:
2457				self.invalid = True
2458			if res == -1:
2459				self.partial = True
2460			return res
2461		return 0
2462	def invalidate(self, line):
2463		if(len(self.list) > 0):
2464			first = self.list[0]
2465			self.list = []
2466			self.list.append(first)
2467		self.invalid = True
2468		id = 'task %s' % (self.pid)
2469		window = '(%f - %f)' % (self.start, line.time)
2470		if(self.depth < 0):
2471			pprint('Data misalignment for '+id+\
2472				' (buffer overflow), ignoring this callback')
2473		else:
2474			pprint('Too much data for '+id+\
2475				' '+window+', ignoring this callback')
2476	def slice(self, dev):
2477		minicg = FTraceCallGraph(dev['pid'], self.sv)
2478		minicg.name = self.name
2479		mydepth = -1
2480		good = False
2481		for l in self.list:
2482			if(l.time < dev['start'] or l.time > dev['end']):
2483				continue
2484			if mydepth < 0:
2485				if l.name == 'mutex_lock' and l.freturn:
2486					mydepth = l.depth
2487				continue
2488			elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2489				good = True
2490				break
2491			l.depth -= mydepth
2492			minicg.addLine(l)
2493		if not good or len(minicg.list) < 1:
2494			return 0
2495		return minicg
2496	def repair(self, enddepth):
2497		# bring the depth back to 0 with additional returns
2498		fixed = False
2499		last = self.list[-1]
2500		for i in reversed(range(enddepth)):
2501			t = FTraceLine(last.time)
2502			t.depth = i
2503			t.freturn = True
2504			fixed = self.addLine(t)
2505			if fixed != 0:
2506				self.end = last.time
2507				return True
2508		return False
2509	def postProcess(self):
2510		if len(self.list) > 0:
2511			self.name = self.list[0].name
2512		stack = dict()
2513		cnt = 0
2514		last = 0
2515		for l in self.list:
2516			# ftrace bug: reported duration is not reliable
2517			# check each leaf and clip it at max possible length
2518			if last and last.isLeaf():
2519				if last.length > l.time - last.time:
2520					last.length = l.time - last.time
2521			if l.isCall():
2522				stack[l.depth] = l
2523				cnt += 1
2524			elif l.isReturn():
2525				if(l.depth not in stack):
2526					if self.sv.verbose:
2527						pprint('Post Process Error: Depth missing')
2528						l.debugPrint()
2529					return False
2530				# calculate call length from call/return lines
2531				cl = stack[l.depth]
2532				cl.length = l.time - cl.time
2533				if cl.name == self.vfname:
2534					cl.name = l.name
2535				stack.pop(l.depth)
2536				l.length = 0
2537				cnt -= 1
2538			last = l
2539		if(cnt == 0):
2540			# trace caught the whole call tree
2541			return True
2542		elif(cnt < 0):
2543			if self.sv.verbose:
2544				pprint('Post Process Error: Depth is less than 0')
2545			return False
2546		# trace ended before call tree finished
2547		return self.repair(cnt)
2548	def deviceMatch(self, pid, data):
2549		found = ''
2550		# add the callgraph data to the device hierarchy
2551		borderphase = {
2552			'dpm_prepare': 'suspend_prepare',
2553			'dpm_complete': 'resume_complete'
2554		}
2555		if(self.name in borderphase):
2556			p = borderphase[self.name]
2557			list = data.dmesg[p]['list']
2558			for devname in list:
2559				dev = list[devname]
2560				if(pid == dev['pid'] and
2561					self.start <= dev['start'] and
2562					self.end >= dev['end']):
2563					cg = self.slice(dev)
2564					if cg:
2565						dev['ftrace'] = cg
2566					found = devname
2567			return found
2568		for p in data.sortedPhases():
2569			if(data.dmesg[p]['start'] <= self.start and
2570				self.start <= data.dmesg[p]['end']):
2571				list = data.dmesg[p]['list']
2572				for devname in sorted(list, key=lambda k:list[k]['start']):
2573					dev = list[devname]
2574					if(pid == dev['pid'] and
2575						self.start <= dev['start'] and
2576						self.end >= dev['end']):
2577						dev['ftrace'] = self
2578						found = devname
2579						break
2580				break
2581		return found
2582	def newActionFromFunction(self, data):
2583		name = self.name
2584		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2585			return
2586		fs = self.start
2587		fe = self.end
2588		if fs < data.start or fe > data.end:
2589			return
2590		phase = ''
2591		for p in data.sortedPhases():
2592			if(data.dmesg[p]['start'] <= self.start and
2593				self.start < data.dmesg[p]['end']):
2594				phase = p
2595				break
2596		if not phase:
2597			return
2598		out = data.newActionGlobal(name, fs, fe, -2)
2599		if out:
2600			phase, myname = out
2601			data.dmesg[phase]['list'][myname]['ftrace'] = self
2602	def debugPrint(self, info=''):
2603		pprint('%s pid=%d [%f - %f] %.3f us' % \
2604			(self.name, self.pid, self.start, self.end,
2605			(self.end - self.start)*1000000))
2606		for l in self.list:
2607			if l.isLeaf():
2608				pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2609					l.depth, l.name, l.length*1000000, info))
2610			elif l.freturn:
2611				pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2612					l.depth, l.name, l.length*1000000, info))
2613			else:
2614				pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2615					l.depth, l.name, l.length*1000000, info))
2616		pprint(' ')
2617
2618class DevItem:
2619	def __init__(self, test, phase, dev):
2620		self.test = test
2621		self.phase = phase
2622		self.dev = dev
2623	def isa(self, cls):
2624		if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2625			return True
2626		return False
2627
2628# Class: Timeline
2629# Description:
2630#	 A container for a device timeline which calculates
2631#	 all the html properties to display it correctly
2632class Timeline:
 
 
 
 
 
 
 
 
2633	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2634	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'
2635	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2636	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2637	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2638	def __init__(self, rowheight, scaleheight):
 
 
2639		self.html = ''
2640		self.height = 0  # total timeline height
2641		self.scaleH = scaleheight # timescale (top) row height
2642		self.rowH = rowheight     # device row height
2643		self.bodyH = 0   # body height
2644		self.rows = 0    # total timeline rows
2645		self.rowlines = dict()
2646		self.rowheight = dict()
2647	def createHeader(self, sv, stamp):
2648		if(not stamp['time']):
2649			return
2650		self.html += '<div class="version"><a href="https://01.org/pm-graph">%s v%s</a></div>' \
2651			% (sv.title, sv.version)
2652		if sv.logmsg and sv.testlog:
2653			self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2654		if sv.dmesglog:
2655			self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2656		if sv.ftracelog:
2657			self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2658		headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2659		self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2660			stamp['mode'], stamp['time'])
2661		if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2662			stamp['man'] and stamp['plat'] and stamp['cpu']:
2663			headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2664			self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2665
2666	# Function: getDeviceRows
2667	# Description:
2668	#    determine how may rows the device funcs will take
2669	# Arguments:
2670	#	 rawlist: the list of devices/actions for a single phase
2671	# Output:
2672	#	 The total number of rows needed to display this phase of the timeline
2673	def getDeviceRows(self, rawlist):
2674		# clear all rows and set them to undefined
2675		sortdict = dict()
2676		for item in rawlist:
2677			item.row = -1
2678			sortdict[item] = item.length
2679		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2680		remaining = len(sortlist)
2681		rowdata = dict()
2682		row = 1
2683		# try to pack each row with as many ranges as possible
2684		while(remaining > 0):
2685			if(row not in rowdata):
2686				rowdata[row] = []
2687			for i in sortlist:
2688				if(i.row >= 0):
2689					continue
2690				s = i.time
2691				e = i.time + i.length
2692				valid = True
2693				for ritem in rowdata[row]:
2694					rs = ritem.time
2695					re = ritem.time + ritem.length
2696					if(not (((s <= rs) and (e <= rs)) or
2697						((s >= re) and (e >= re)))):
2698						valid = False
2699						break
2700				if(valid):
2701					rowdata[row].append(i)
2702					i.row = row
2703					remaining -= 1
2704			row += 1
2705		return row
2706	# Function: getPhaseRows
2707	# Description:
2708	#	 Organize the timeline entries into the smallest
2709	#	 number of rows possible, with no entry overlapping
2710	# Arguments:
2711	#	 devlist: the list of devices/actions in a group of contiguous phases
2712	# Output:
2713	#	 The total number of rows needed to display this phase of the timeline
2714	def getPhaseRows(self, devlist, row=0, sortby='length'):
2715		# clear all rows and set them to undefined
2716		remaining = len(devlist)
2717		rowdata = dict()
2718		sortdict = dict()
2719		myphases = []
2720		# initialize all device rows to -1 and calculate devrows
2721		for item in devlist:
2722			dev = item.dev
2723			tp = (item.test, item.phase)
2724			if tp not in myphases:
2725				myphases.append(tp)
2726			dev['row'] = -1
2727			if sortby == 'start':
2728				# sort by start 1st, then length 2nd
2729				sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2730			else:
2731				# sort by length 1st, then name 2nd
2732				sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2733			if 'src' in dev:
2734				dev['devrows'] = self.getDeviceRows(dev['src'])
2735		# sort the devlist by length so that large items graph on top
2736		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2737		orderedlist = []
2738		for item in sortlist:
2739			if item.dev['pid'] == -2:
2740				orderedlist.append(item)
2741		for item in sortlist:
2742			if item not in orderedlist:
2743				orderedlist.append(item)
2744		# try to pack each row with as many devices as possible
2745		while(remaining > 0):
2746			rowheight = 1
2747			if(row not in rowdata):
2748				rowdata[row] = []
2749			for item in orderedlist:
2750				dev = item.dev
2751				if(dev['row'] < 0):
2752					s = dev['start']
2753					e = dev['end']
2754					valid = True
2755					for ritem in rowdata[row]:
2756						rs = ritem.dev['start']
2757						re = ritem.dev['end']
2758						if(not (((s <= rs) and (e <= rs)) or
2759							((s >= re) and (e >= re)))):
2760							valid = False
2761							break
2762					if(valid):
2763						rowdata[row].append(item)
2764						dev['row'] = row
2765						remaining -= 1
2766						if 'devrows' in dev and dev['devrows'] > rowheight:
2767							rowheight = dev['devrows']
2768			for t, p in myphases:
2769				if t not in self.rowlines or t not in self.rowheight:
2770					self.rowlines[t] = dict()
2771					self.rowheight[t] = dict()
2772				if p not in self.rowlines[t] or p not in self.rowheight[t]:
2773					self.rowlines[t][p] = dict()
2774					self.rowheight[t][p] = dict()
2775				rh = self.rowH
2776				# section headers should use a different row height
2777				if len(rowdata[row]) == 1 and \
2778					'htmlclass' in rowdata[row][0].dev and \
2779					'sec' in rowdata[row][0].dev['htmlclass']:
2780					rh = 15
2781				self.rowlines[t][p][row] = rowheight
2782				self.rowheight[t][p][row] = rowheight * rh
2783			row += 1
2784		if(row > self.rows):
2785			self.rows = int(row)
2786		return row
2787	def phaseRowHeight(self, test, phase, row):
2788		return self.rowheight[test][phase][row]
2789	def phaseRowTop(self, test, phase, row):
2790		top = 0
2791		for i in sorted(self.rowheight[test][phase]):
2792			if i >= row:
2793				break
2794			top += self.rowheight[test][phase][i]
2795		return top
2796	def calcTotalRows(self):
2797		# Calculate the heights and offsets for the header and rows
2798		maxrows = 0
2799		standardphases = []
2800		for t in self.rowlines:
2801			for p in self.rowlines[t]:
2802				total = 0
2803				for i in sorted(self.rowlines[t][p]):
2804					total += self.rowlines[t][p][i]
2805				if total > maxrows:
2806					maxrows = total
2807				if total == len(self.rowlines[t][p]):
2808					standardphases.append((t, p))
2809		self.height = self.scaleH + (maxrows*self.rowH)
2810		self.bodyH = self.height - self.scaleH
2811		# if there is 1 line per row, draw them the standard way
2812		for t, p in standardphases:
2813			for i in sorted(self.rowheight[t][p]):
2814				self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2815	def createZoomBox(self, mode='command', testcount=1):
2816		# Create bounding box, add buttons
2817		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2818		html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2819		html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2820		html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2821		if mode != 'command':
2822			if testcount > 1:
2823				self.html += html_devlist2
2824				self.html += html_devlist1.format('1')
2825			else:
2826				self.html += html_devlist1.format('')
2827		self.html += html_zoombox
2828		self.html += html_timeline.format('dmesg', self.height)
2829	# Function: createTimeScale
2830	# Description:
2831	#	 Create the timescale for a timeline block
2832	# Arguments:
2833	#	 m0: start time (mode begin)
2834	#	 mMax: end time (mode end)
2835	#	 tTotal: total timeline time
2836	#	 mode: suspend or resume
2837	# Output:
2838	#	 The html code needed to display the time scale
2839	def createTimeScale(self, m0, mMax, tTotal, mode):
2840		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2841		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2842		output = '<div class="timescale">\n'
2843		# set scale for timeline
2844		mTotal = mMax - m0
2845		tS = 0.1
2846		if(tTotal <= 0):
2847			return output+'</div>\n'
2848		if(tTotal > 4):
2849			tS = 1
2850		divTotal = int(mTotal/tS) + 1
2851		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2852		for i in range(divTotal):
2853			htmlline = ''
2854			if(mode == 'suspend'):
2855				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2856				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2857				if(i == divTotal - 1):
2858					val = mode
2859				htmlline = timescale.format(pos, val)
2860			else:
2861				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2862				val = '%0.fms' % (float(i)*tS*1000)
2863				htmlline = timescale.format(pos, val)
2864				if(i == 0):
2865					htmlline = rline.format(mode)
2866			output += htmlline
2867		self.html += output+'</div>\n'
2868
2869# Class: TestProps
2870# Description:
2871#	 A list of values describing the properties of these test runs
2872class TestProps:
 
 
 
 
 
 
2873	stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2874				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2875				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2876	wififmt    = '^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
2877	tstatfmt   = '^# turbostat (?P<t>\S*)'
2878	testerrfmt = '^# enter_sleep_error (?P<e>.*)'
2879	sysinfofmt = '^# sysinfo .*'
2880	cmdlinefmt = '^# command \| (?P<cmd>.*)'
2881	kparamsfmt = '^# kparams \| (?P<kp>.*)'
2882	devpropfmt = '# Device Properties: .*'
2883	pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
2884	tracertypefmt = '# tracer: (?P<t>.*)'
2885	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
2886	procexecfmt = 'ps - (?P<ps>.*)$'
2887	ftrace_line_fmt_fg = \
2888		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2889		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2890		'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
2891	ftrace_line_fmt_nop = \
2892		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2893		'(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
2894		'(?P<msg>.*)'
2895	machinesuspend = 'machine_suspend\[.*'
 
 
 
2896	def __init__(self):
2897		self.stamp = ''
2898		self.sysinfo = ''
2899		self.cmdline = ''
2900		self.testerror = []
2901		self.turbostat = []
2902		self.wifi = []
2903		self.fwdata = []
2904		self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2905		self.cgformat = False
2906		self.data = 0
2907		self.ktemp = dict()
2908	def setTracerType(self, tracer):
2909		if(tracer == 'function_graph'):
2910			self.cgformat = True
2911			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2912		elif(tracer == 'nop'):
2913			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2914		else:
2915			doError('Invalid tracer format: [%s]' % tracer)
2916	def stampInfo(self, line, sv):
2917		if re.match(self.stampfmt, line):
2918			self.stamp = line
2919			return True
2920		elif re.match(self.sysinfofmt, line):
2921			self.sysinfo = line
2922			return True
2923		elif re.match(self.tstatfmt, line):
2924			self.turbostat.append(line)
2925			return True
2926		elif re.match(self.wififmt, line):
2927			self.wifi.append(line)
2928			return True
2929		elif re.match(self.testerrfmt, line):
2930			self.testerror.append(line)
2931			return True
2932		elif re.match(self.firmwarefmt, line):
2933			self.fwdata.append(line)
2934			return True
2935		elif(re.match(self.devpropfmt, line)):
2936			self.parseDevprops(line, sv)
2937			return True
2938		elif(re.match(self.pinfofmt, line)):
2939			self.parsePlatformInfo(line, sv)
2940			return True
2941		m = re.match(self.cmdlinefmt, line)
2942		if m:
2943			self.cmdline = m.group('cmd')
2944			return True
2945		m = re.match(self.tracertypefmt, line)
2946		if(m):
2947			self.setTracerType(m.group('t'))
2948			return True
2949		return False
2950	def parseStamp(self, data, sv):
2951		# global test data
2952		m = re.match(self.stampfmt, self.stamp)
2953		if not self.stamp or not m:
2954			doError('data does not include the expected stamp')
2955		data.stamp = {'time': '', 'host': '', 'mode': ''}
2956		dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2957			int(m.group('d')), int(m.group('H')), int(m.group('M')),
2958			int(m.group('S')))
2959		data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2960		data.stamp['host'] = m.group('host')
2961		data.stamp['mode'] = m.group('mode')
2962		data.stamp['kernel'] = m.group('kernel')
2963		if re.match(self.sysinfofmt, self.sysinfo):
2964			for f in self.sysinfo.split('|'):
2965				if '#' in f:
2966					continue
2967				tmp = f.strip().split(':', 1)
2968				key = tmp[0]
2969				val = tmp[1]
2970				data.stamp[key] = val
2971		sv.hostname = data.stamp['host']
2972		sv.suspendmode = data.stamp['mode']
2973		if sv.suspendmode == 'freeze':
2974			self.machinesuspend = 'timekeeping_freeze\[.*'
2975		else:
2976			self.machinesuspend = 'machine_suspend\[.*'
2977		if sv.suspendmode == 'command' and sv.ftracefile != '':
2978			modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2979			fp = sv.openlog(sv.ftracefile, 'r')
2980			for line in fp:
2981				m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
2982				if m and m.group('mode') in ['1', '2', '3', '4']:
2983					sv.suspendmode = modes[int(m.group('mode'))]
2984					data.stamp['mode'] = sv.suspendmode
2985					break
2986			fp.close()
2987		sv.cmdline = self.cmdline
 
 
 
 
2988		if not sv.stamp:
2989			sv.stamp = data.stamp
2990		# firmware data
2991		if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
2992			m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
2993			if m:
2994				data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
2995				if(data.fwSuspend > 0 or data.fwResume > 0):
2996					data.fwValid = True
2997		# turbostat data
2998		if len(self.turbostat) > data.testnumber:
2999			m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
3000			if m:
3001				data.turbostat = m.group('t')
3002		# wifi data
3003		if len(self.wifi) > data.testnumber:
3004			m = re.match(self.wififmt, self.wifi[data.testnumber])
3005			if m:
3006				data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
3007					'time': float(m.group('t'))}
3008				data.stamp['wifi'] = m.group('d')
3009		# sleep mode enter errors
3010		if len(self.testerror) > data.testnumber:
3011			m = re.match(self.testerrfmt, self.testerror[data.testnumber])
3012			if m:
3013				data.enterfail = m.group('e')
3014	def devprops(self, data):
3015		props = dict()
3016		devlist = data.split(';')
3017		for dev in devlist:
3018			f = dev.split(',')
3019			if len(f) < 3:
3020				continue
3021			dev = f[0]
3022			props[dev] = DevProps()
3023			props[dev].altname = f[1]
3024			if int(f[2]):
3025				props[dev].isasync = True
3026			else:
3027				props[dev].isasync = False
3028		return props
3029	def parseDevprops(self, line, sv):
3030		idx = line.index(': ') + 2
3031		if idx >= len(line):
3032			return
3033		props = self.devprops(line[idx:])
3034		if sv.suspendmode == 'command' and 'testcommandstring' in props:
3035			sv.testcommand = props['testcommandstring'].altname
3036		sv.devprops = props
3037	def parsePlatformInfo(self, line, sv):
3038		m = re.match(self.pinfofmt, line)
3039		if not m:
3040			return
3041		name, info = m.group('val'), m.group('info')
3042		if name == 'devinfo':
3043			sv.devprops = self.devprops(sv.b64unzip(info))
3044			return
3045		elif name == 'testcmd':
3046			sv.testcommand = info
3047			return
3048		field = info.split('|')
3049		if len(field) < 2:
3050			return
3051		cmdline = field[0].strip()
3052		output = sv.b64unzip(field[1].strip())
3053		sv.platinfo.append([name, cmdline, output])
3054
3055# Class: TestRun
3056# Description:
3057#	 A container for a suspend/resume test run. This is necessary as
3058#	 there could be more than one, and they need to be separate.
3059class TestRun:
 
 
 
3060	def __init__(self, dataobj):
3061		self.data = dataobj
3062		self.ftemp = dict()
3063		self.ttemp = dict()
3064
3065class ProcessMonitor:
3066	def __init__(self):
3067		self.proclist = dict()
3068		self.running = False
3069	def procstat(self):
3070		c = ['cat /proc/[1-9]*/stat 2>/dev/null']
3071		process = Popen(c, shell=True, stdout=PIPE)
3072		running = dict()
3073		for line in process.stdout:
3074			data = ascii(line).split()
3075			pid = data[0]
3076			name = re.sub('[()]', '', data[1])
3077			user = int(data[13])
3078			kern = int(data[14])
3079			kjiff = ujiff = 0
3080			if pid not in self.proclist:
3081				self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3082			else:
3083				val = self.proclist[pid]
3084				ujiff = user - val['user']
3085				kjiff = kern - val['kern']
3086				val['user'] = user
3087				val['kern'] = kern
3088			if ujiff > 0 or kjiff > 0:
3089				running[pid] = ujiff + kjiff
3090		process.wait()
3091		out = ''
3092		for pid in running:
3093			jiffies = running[pid]
3094			val = self.proclist[pid]
3095			if out:
3096				out += ','
3097			out += '%s-%s %d' % (val['name'], pid, jiffies)
3098		return 'ps - '+out
3099	def processMonitor(self, tid):
3100		while self.running:
3101			out = self.procstat()
3102			if out:
3103				sysvals.fsetVal(out, 'trace_marker')
3104	def start(self):
3105		self.thread = Thread(target=self.processMonitor, args=(0,))
3106		self.running = True
3107		self.thread.start()
3108	def stop(self):
3109		self.running = False
3110
3111# ----------------- FUNCTIONS --------------------
3112
3113# Function: doesTraceLogHaveTraceEvents
3114# Description:
3115#	 Quickly determine if the ftrace log has all of the trace events,
3116#	 markers, and/or kprobes required for primary parsing.
3117def doesTraceLogHaveTraceEvents():
3118	kpcheck = ['_cal: (', '_ret: (']
3119	techeck = ['suspend_resume', 'device_pm_callback']
3120	tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3121	sysvals.usekprobes = False
3122	fp = sysvals.openlog(sysvals.ftracefile, 'r')
3123	for line in fp:
3124		# check for kprobes
3125		if not sysvals.usekprobes:
3126			for i in kpcheck:
3127				if i in line:
3128					sysvals.usekprobes = True
3129		# check for all necessary trace events
3130		check = techeck[:]
3131		for i in techeck:
3132			if i in line:
3133				check.remove(i)
3134		techeck = check
3135		# check for all necessary trace markers
3136		check = tmcheck[:]
3137		for i in tmcheck:
3138			if i in line:
3139				check.remove(i)
3140		tmcheck = check
3141	fp.close()
3142	sysvals.usetraceevents = True if len(techeck) < 2 else False
3143	sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
 
 
 
 
 
 
3144
3145# Function: appendIncompleteTraceLog
3146# Description:
3147#	 [deprecated for kernel 3.15 or newer]
3148#	 Adds callgraph data which lacks trace event data. This is only
3149#	 for timelines generated from 3.15 or older
 
 
 
3150# Arguments:
3151#	 testruns: the array of Data objects obtained from parseKernelLog
3152def appendIncompleteTraceLog(testruns):
3153	# create TestRun vessels for ftrace parsing
3154	testcnt = len(testruns)
3155	testidx = 0
3156	testrun = []
3157	for data in testruns:
3158		testrun.append(TestRun(data))
3159
3160	# extract the callgraph and traceevent data
3161	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3162		os.path.basename(sysvals.ftracefile))
3163	tp = TestProps()
3164	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3165	data = 0
3166	for line in tf:
3167		# remove any latent carriage returns
3168		line = line.replace('\r\n', '')
3169		if tp.stampInfo(line, sysvals):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3170			continue
3171		# parse only valid lines, if this is not one move on
3172		m = re.match(tp.ftrace_line_fmt, line)
3173		if(not m):
3174			continue
3175		# gather the basic message data from the line
3176		m_time = m.group('time')
3177		m_pid = m.group('pid')
3178		m_msg = m.group('msg')
3179		if(tp.cgformat):
3180			m_param3 = m.group('dur')
3181		else:
3182			m_param3 = 'traceevent'
3183		if(m_time and m_pid and m_msg):
3184			t = FTraceLine(m_time, m_msg, m_param3)
3185			pid = int(m_pid)
3186		else:
3187			continue
3188		# the line should be a call, return, or event
3189		if(not t.fcall and not t.freturn and not t.fevent):
3190			continue
3191		# look for the suspend start marker
3192		if(t.startMarker()):
3193			data = testrun[testidx].data
3194			tp.parseStamp(data, sysvals)
3195			data.setStart(t.time, t.name)
3196			continue
3197		if(not data):
3198			continue
3199		# find the end of resume
3200		if(t.endMarker()):
3201			data.setEnd(t.time, t.name)
3202			testidx += 1
3203			if(testidx >= testcnt):
3204				break
3205			continue
3206		# trace event processing
3207		if(t.fevent):
3208			continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3209		# call/return processing
3210		elif sysvals.usecallgraph:
3211			# create a callgraph object for the data
3212			if(pid not in testrun[testidx].ftemp):
3213				testrun[testidx].ftemp[pid] = []
3214				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3215			# when the call is finished, see which device matches it
3216			cg = testrun[testidx].ftemp[pid][-1]
3217			res = cg.addLine(t)
3218			if(res != 0):
3219				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3220			if(res == -1):
3221				testrun[testidx].ftemp[pid][-1].addLine(t)
3222	tf.close()
3223
3224	for test in testrun:
 
 
 
 
 
 
3225		# add the callgraph data to the device hierarchy
3226		for pid in test.ftemp:
3227			for cg in test.ftemp[pid]:
3228				if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3229					continue
3230				if(not cg.postProcess()):
3231					id = 'task %s cpu %s' % (pid, m.group('cpu'))
3232					sysvals.vprint('Sanity check failed for '+\
3233						id+', ignoring this callback')
3234					continue
3235				callstart = cg.start
3236				callend = cg.end
3237				for p in test.data.sortedPhases():
3238					if(test.data.dmesg[p]['start'] <= callstart and
3239						callstart <= test.data.dmesg[p]['end']):
3240						list = test.data.dmesg[p]['list']
3241						for devname in list:
3242							dev = list[devname]
3243							if(pid == dev['pid'] and
3244								callstart <= dev['start'] and
3245								callend >= dev['end']):
3246								dev['ftrace'] = cg
3247						break
3248
3249# Function: parseTraceLog
3250# Description:
3251#	 Analyze an ftrace log output file generated from this app during
3252#	 the execution phase. Used when the ftrace log is the primary data source
3253#	 and includes the suspend_resume and device_pm_callback trace events
3254#	 The ftrace filename is taken from sysvals
3255# Output:
3256#	 An array of Data objects
3257def parseTraceLog(live=False):
3258	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3259		os.path.basename(sysvals.ftracefile))
3260	if(os.path.exists(sysvals.ftracefile) == False):
3261		doError('%s does not exist' % sysvals.ftracefile)
3262	if not live:
3263		sysvals.setupAllKprobes()
3264	ksuscalls = ['ksys_sync', 'pm_prepare_console']
3265	krescalls = ['pm_restore_console']
3266	tracewatch = ['irq_wakeup']
3267	if sysvals.usekprobes:
3268		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3269			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3270			'CPU_OFF', 'acpi_suspend']
3271
3272	# extract the callgraph and traceevent data
3273	s2idle_enter = hwsus = False
3274	tp = TestProps()
3275	testruns, testdata = [], []
3276	testrun, data, limbo = 0, 0, True
 
 
3277	tf = sysvals.openlog(sysvals.ftracefile, 'r')
3278	phase = 'suspend_prepare'
3279	for line in tf:
3280		# remove any latent carriage returns
3281		line = line.replace('\r\n', '')
3282		if tp.stampInfo(line, sysvals):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3283			continue
3284		# ignore all other commented lines
3285		if line[0] == '#':
3286			continue
3287		# ftrace line: parse only valid lines
3288		m = re.match(tp.ftrace_line_fmt, line)
3289		if(not m):
3290			continue
3291		# gather the basic message data from the line
3292		m_time = m.group('time')
3293		m_proc = m.group('proc')
3294		m_pid = m.group('pid')
3295		m_msg = m.group('msg')
3296		if(tp.cgformat):
3297			m_param3 = m.group('dur')
3298		else:
3299			m_param3 = 'traceevent'
3300		if(m_time and m_pid and m_msg):
3301			t = FTraceLine(m_time, m_msg, m_param3)
3302			pid = int(m_pid)
3303		else:
3304			continue
3305		# the line should be a call, return, or event
3306		if(not t.fcall and not t.freturn and not t.fevent):
3307			continue
3308		# find the start of suspend
3309		if(t.startMarker()):
3310			data, limbo = Data(len(testdata)), False
 
3311			testdata.append(data)
3312			testrun = TestRun(data)
3313			testruns.append(testrun)
3314			tp.parseStamp(data, sysvals)
3315			data.setStart(t.time, t.name)
3316			data.first_suspend_prepare = True
3317			phase = data.setPhase('suspend_prepare', t.time, True)
3318			continue
3319		if(not data or limbo):
3320			continue
3321		# process cpu exec line
3322		if t.type == 'tracing_mark_write':
3323			m = re.match(tp.procexecfmt, t.name)
3324			if(m):
3325				proclist = dict()
3326				for ps in m.group('ps').split(','):
3327					val = ps.split()
3328					if not val:
3329						continue
3330					name = val[0].replace('--', '-')
3331					proclist[name] = int(val[1])
3332				data.pstl[t.time] = proclist
3333				continue
3334		# find the end of resume
3335		if(t.endMarker()):
3336			if data.tKernRes == 0:
 
3337				data.tKernRes = t.time
3338			data.handleEndMarker(t.time, t.name)
 
 
 
 
 
 
3339			if(not sysvals.usetracemarkers):
3340				# no trace markers? then quit and be sure to finish recording
3341				# the event we used to trigger resume end
3342				if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3343					# if an entry exists, assume this is its end
3344					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3345			limbo = True
3346			continue
3347		# trace event processing
3348		if(t.fevent):
 
 
3349			if(t.type == 'suspend_resume'):
3350				# suspend_resume trace events have two types, begin and end
3351				if(re.match('(?P<name>.*) begin$', t.name)):
3352					isbegin = True
3353				elif(re.match('(?P<name>.*) end$', t.name)):
3354					isbegin = False
3355				else:
3356					continue
3357				if '[' in t.name:
3358					m = re.match('(?P<name>.*)\[.*', t.name)
 
 
 
 
 
3359				else:
3360					m = re.match('(?P<name>.*) .*', t.name)
3361				name = m.group('name')
3362				# ignore these events
3363				if(name.split('[')[0] in tracewatch):
3364					continue
3365				# -- phase changes --
3366				# start of kernel suspend
3367				if(re.match('suspend_enter\[.*', t.name)):
3368					if(isbegin and data.tKernSus == 0):
 
3369						data.tKernSus = t.time
3370					continue
3371				# suspend_prepare start
3372				elif(re.match('dpm_prepare\[.*', t.name)):
3373					if isbegin and data.first_suspend_prepare:
3374						data.first_suspend_prepare = False
3375						if data.tKernSus == 0:
3376							data.tKernSus = t.time
3377						continue
3378					phase = data.setPhase('suspend_prepare', t.time, isbegin)
3379					continue
3380				# suspend start
3381				elif(re.match('dpm_suspend\[.*', t.name)):
3382					phase = data.setPhase('suspend', t.time, isbegin)
 
3383					continue
3384				# suspend_late start
3385				elif(re.match('dpm_suspend_late\[.*', t.name)):
3386					phase = data.setPhase('suspend_late', t.time, isbegin)
 
3387					continue
3388				# suspend_noirq start
3389				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3390					phase = data.setPhase('suspend_noirq', t.time, isbegin)
 
 
 
 
 
 
3391					continue
3392				# suspend_machine/resume_machine
3393				elif(re.match(tp.machinesuspend, t.name)):
3394					lp = data.lastPhase()
3395					if(isbegin):
3396						hwsus = True
3397						if lp.startswith('resume_machine'):
3398							# trim out s2idle loops, track time trying to freeze
3399							llp = data.lastPhase(2)
3400							if llp.startswith('suspend_machine'):
3401								if 'waking' not in data.dmesg[llp]:
3402									data.dmesg[llp]['waking'] = [0, 0.0]
3403								data.dmesg[llp]['waking'][0] += 1
3404								data.dmesg[llp]['waking'][1] += \
3405									t.time - data.dmesg[lp]['start']
3406							data.currphase = ''
3407							del data.dmesg[lp]
3408							continue
3409						phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3410						data.setPhase(phase, t.time, False)
3411						if data.tSuspended == 0:
3412							data.tSuspended = t.time
3413					else:
3414						if lp.startswith('resume_machine'):
3415							data.dmesg[lp]['end'] = t.time
3416							continue
3417						phase = data.setPhase('resume_machine', t.time, True)
3418						if(sysvals.suspendmode in ['mem', 'disk']):
3419							susp = phase.replace('resume', 'suspend')
3420							if susp in data.dmesg:
3421								data.dmesg[susp]['end'] = t.time
3422							data.tSuspended = t.time
 
 
3423						data.tResumed = t.time
 
 
 
 
 
 
 
 
 
 
3424					continue
3425				# resume_noirq start
3426				elif(re.match('dpm_resume_noirq\[.*', t.name)):
3427					phase = data.setPhase('resume_noirq', t.time, isbegin)
 
 
 
 
 
3428					continue
3429				# resume_early start
3430				elif(re.match('dpm_resume_early\[.*', t.name)):
3431					phase = data.setPhase('resume_early', t.time, isbegin)
 
3432					continue
3433				# resume start
3434				elif(re.match('dpm_resume\[.*', t.name)):
3435					phase = data.setPhase('resume', t.time, isbegin)
 
3436					continue
3437				# resume complete start
3438				elif(re.match('dpm_complete\[.*', t.name)):
3439					phase = data.setPhase('resume_complete', t.time, isbegin)
 
 
3440					continue
3441				# skip trace events inside devices calls
3442				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3443					continue
3444				# global events (outside device calls) are graphed
3445				if(name not in testrun.ttemp):
3446					testrun.ttemp[name] = []
3447				# special handling for s2idle_enter
3448				if name == 'machine_suspend':
3449					if hwsus:
3450						s2idle_enter = hwsus = False
3451					elif s2idle_enter and not isbegin:
3452						if(len(testrun.ttemp[name]) > 0):
3453							testrun.ttemp[name][-1]['end'] = t.time
3454							testrun.ttemp[name][-1]['loop'] += 1
3455					elif not s2idle_enter and isbegin:
3456						s2idle_enter = True
3457						testrun.ttemp[name].append({'begin': t.time,
3458							'end': t.time, 'pid': pid, 'loop': 0})
3459					continue
3460				if(isbegin):
3461					# create a new list entry
3462					testrun.ttemp[name].append(\
3463						{'begin': t.time, 'end': t.time, 'pid': pid})
3464				else:
3465					if(len(testrun.ttemp[name]) > 0):
3466						# if an entry exists, assume this is its end
3467						testrun.ttemp[name][-1]['end'] = t.time
 
 
 
 
 
3468			# device callback start
3469			elif(t.type == 'device_pm_callback_start'):
3470				if phase not in data.dmesg:
3471					continue
3472				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3473					t.name);
3474				if(not m):
3475					continue
3476				drv = m.group('drv')
3477				n = m.group('d')
3478				p = m.group('p')
3479				if(n and p):
3480					data.newAction(phase, n, pid, p, t.time, -1, drv)
3481					if pid not in data.devpids:
3482						data.devpids.append(pid)
3483			# device callback finish
3484			elif(t.type == 'device_pm_callback_end'):
3485				if phase not in data.dmesg:
3486					continue
3487				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
3488				if(not m):
3489					continue
3490				n = m.group('d')
3491				dev = data.findDevice(phase, n)
3492				if dev:
 
3493					dev['length'] = t.time - dev['start']
3494					dev['end'] = t.time
3495		# kprobe event processing
3496		elif(t.fkprobe):
3497			kprobename = t.type
3498			kprobedata = t.name
3499			key = (kprobename, pid)
3500			# displayname is generated from kprobe data
3501			displayname = ''
3502			if(t.fcall):
3503				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3504				if not displayname:
3505					continue
3506				if(key not in tp.ktemp):
3507					tp.ktemp[key] = []
3508				tp.ktemp[key].append({
3509					'pid': pid,
3510					'begin': t.time,
3511					'end': -1,
3512					'name': displayname,
3513					'cdata': kprobedata,
3514					'proc': m_proc,
3515				})
3516				# start of kernel resume
3517				if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3518					and kprobename in ksuscalls):
3519					data.tKernSus = t.time
3520			elif(t.freturn):
3521				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3522					continue
3523				e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3524				if not e:
3525					continue
3526				e['end'] = t.time
3527				e['rdata'] = kprobedata
 
3528				# end of kernel resume
3529				if(phase != 'suspend_prepare' and kprobename in krescalls):
3530					if phase in data.dmesg:
3531						data.dmesg[phase]['end'] = t.time
3532					data.tKernRes = t.time
3533
3534		# callgraph processing
3535		elif sysvals.usecallgraph:
3536			# create a callgraph object for the data
3537			key = (m_proc, pid)
3538			if(key not in testrun.ftemp):
3539				testrun.ftemp[key] = []
3540				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3541			# when the call is finished, see which device matches it
3542			cg = testrun.ftemp[key][-1]
3543			res = cg.addLine(t)
3544			if(res != 0):
3545				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3546			if(res == -1):
3547				testrun.ftemp[key][-1].addLine(t)
3548	tf.close()
3549	if len(testdata) < 1:
3550		sysvals.vprint('WARNING: ftrace start marker is missing')
3551	if data and not data.devicegroups:
3552		sysvals.vprint('WARNING: ftrace end marker is missing')
3553		data.handleEndMarker(t.time, t.name)
3554
3555	if sysvals.suspendmode == 'command':
3556		for test in testruns:
3557			for p in test.data.sortedPhases():
3558				if p == 'suspend_prepare':
3559					test.data.dmesg[p]['start'] = test.data.start
3560					test.data.dmesg[p]['end'] = test.data.end
3561				else:
3562					test.data.dmesg[p]['start'] = test.data.end
3563					test.data.dmesg[p]['end'] = test.data.end
3564			test.data.tSuspended = test.data.end
3565			test.data.tResumed = test.data.end
 
3566			test.data.fwValid = False
3567
3568	# dev source and procmon events can be unreadable with mixed phase height
3569	if sysvals.usedevsrc or sysvals.useprocmon:
3570		sysvals.mixedphaseheight = False
3571
3572	# expand phase boundaries so there are no gaps
3573	for data in testdata:
3574		lp = data.sortedPhases()[0]
3575		for p in data.sortedPhases():
3576			if(p != lp and not ('machine' in p and 'machine' in lp)):
3577				data.dmesg[lp]['end'] = data.dmesg[p]['start']
3578			lp = p
3579
3580	for i in range(len(testruns)):
3581		test = testruns[i]
3582		data = test.data
3583		# find the total time range for this test (begin, end)
3584		tlb, tle = data.start, data.end
3585		if i < len(testruns) - 1:
3586			tle = testruns[i+1].data.start
3587		# add the process usage data to the timeline
3588		if sysvals.useprocmon:
3589			data.createProcessUsageEvents()
3590		# add the traceevent data to the device hierarchy
3591		if(sysvals.usetraceevents):
3592			# add actual trace funcs
3593			for name in sorted(test.ttemp):
3594				for event in test.ttemp[name]:
3595					if event['end'] - event['begin'] <= 0:
3596						continue
3597					title = name
3598					if name == 'machine_suspend' and 'loop' in event:
3599						title = 's2idle_enter_%dx' % event['loop']
3600					data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3601			# add the kprobe based virtual tracefuncs as actual devices
3602			for key in sorted(tp.ktemp):
3603				name, pid = key
3604				if name not in sysvals.tracefuncs:
3605					continue
3606				if pid not in data.devpids:
3607					data.devpids.append(pid)
3608				for e in tp.ktemp[key]:
3609					kb, ke = e['begin'], e['end']
3610					if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3611						continue
3612					color = sysvals.kprobeColor(name)
3613					data.newActionGlobal(e['name'], kb, ke, pid, color)
3614			# add config base kprobes and dev kprobes
3615			if sysvals.usedevsrc:
3616				for key in sorted(tp.ktemp):
3617					name, pid = key
3618					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3619						continue
3620					for e in tp.ktemp[key]:
3621						kb, ke = e['begin'], e['end']
3622						if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3623							continue
3624						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3625							ke, e['cdata'], e['rdata'])
3626		if sysvals.usecallgraph:
3627			# add the callgraph data to the device hierarchy
3628			sortlist = dict()
3629			for key in sorted(test.ftemp):
3630				proc, pid = key
3631				for cg in test.ftemp[key]:
3632					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3633						continue
3634					if(not cg.postProcess()):
3635						id = 'task %s' % (pid)
3636						sysvals.vprint('Sanity check failed for '+\
3637							id+', ignoring this callback')
3638						continue
3639					# match cg data to devices
3640					devname = ''
3641					if sysvals.suspendmode != 'command':
3642						devname = cg.deviceMatch(pid, data)
3643					if not devname:
3644						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3645						sortlist[sortkey] = cg
3646					elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3647						sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3648							(devname, len(cg.list)))
3649			# create blocks for orphan cg data
3650			for sortkey in sorted(sortlist):
3651				cg = sortlist[sortkey]
3652				name = cg.name
3653				if sysvals.isCallgraphFunc(name):
3654					sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3655					cg.newActionFromFunction(data)
3656	if sysvals.suspendmode == 'command':
3657		return (testdata, '')
3658
3659	# fill in any missing phases
3660	error = []
3661	for data in testdata:
3662		tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3663		terr = ''
3664		phasedef = data.phasedef
3665		lp = 'suspend_prepare'
3666		for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3667			if p not in data.dmesg:
3668				if not terr:
3669					ph = p if 'machine' in p else lp
3670					terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
3671					pprint('TEST%s FAILED: %s' % (tn, terr))
3672					error.append(terr)
3673					if data.tSuspended == 0:
3674						data.tSuspended = data.dmesg[lp]['end']
3675					if data.tResumed == 0:
3676						data.tResumed = data.dmesg[lp]['end']
3677					data.fwValid = False
3678				sysvals.vprint('WARNING: phase "%s" is missing!' % p)
 
 
 
 
 
 
 
 
 
 
3679			lp = p
3680		if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3681			terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3682				(sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3683			error.append(terr)
3684		if not terr and data.enterfail:
3685			pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3686			terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3687			error.append(terr)
3688		if data.tSuspended == 0:
3689			data.tSuspended = data.tKernRes
3690		if data.tResumed == 0:
3691			data.tResumed = data.tSuspended
3692
3693		if(len(sysvals.devicefilter) > 0):
3694			data.deviceFilter(sysvals.devicefilter)
3695		data.fixupInitcallsThatDidntReturn()
3696		if sysvals.usedevsrc:
3697			data.optimizeDevSrc()
3698
3699	# x2: merge any overlapping devices between test runs
3700	if sysvals.usedevsrc and len(testdata) > 1:
3701		tc = len(testdata)
3702		for i in range(tc - 1):
3703			devlist = testdata[i].overflowDevices()
3704			for j in range(i + 1, tc):
3705				testdata[j].mergeOverlapDevices(devlist)
3706		testdata[0].stitchTouchingThreads(testdata[1:])
3707	return (testdata, ', '.join(error))
3708
3709# Function: loadKernelLog
3710# Description:
3711#	 [deprecated for kernel 3.15.0 or newer]
3712#	 load the dmesg file into memory and fix up any ordering issues
3713#	 The dmesg filename is taken from sysvals
3714# Output:
3715#	 An array of empty Data objects with only their dmesgtext attributes set
3716def loadKernelLog():
3717	sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3718		os.path.basename(sysvals.dmesgfile))
3719	if(os.path.exists(sysvals.dmesgfile) == False):
3720		doError('%s does not exist' % sysvals.dmesgfile)
3721
3722	# there can be multiple test runs in a single file
3723	tp = TestProps()
3724	tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3725	testruns = []
3726	data = 0
3727	lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3728	for line in lf:
3729		line = line.replace('\r\n', '')
3730		idx = line.find('[')
3731		if idx > 1:
3732			line = line[idx:]
3733		if tp.stampInfo(line, sysvals):
 
 
 
 
 
 
 
 
 
 
 
 
3734			continue
3735		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3736		if(not m):
3737			continue
3738		msg = m.group("msg")
3739		if(re.match('PM: Syncing filesystems.*', msg)):
3740			if(data):
3741				testruns.append(data)
3742			data = Data(len(testruns))
3743			tp.parseStamp(data, sysvals)
 
 
 
 
3744		if(not data):
3745			continue
3746		m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3747		if(m):
3748			sysvals.stamp['kernel'] = m.group('k')
3749		m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3750		if(m):
3751			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3752		data.dmesgtext.append(line)
3753	lf.close()
3754
3755	if data:
3756		testruns.append(data)
3757	if len(testruns) < 1:
3758		doError('dmesg log has no suspend/resume data: %s' \
3759			% sysvals.dmesgfile)
3760
3761	# fix lines with same timestamp/function with the call and return swapped
3762	for data in testruns:
3763		last = ''
3764		for line in data.dmesgtext:
3765			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
3766				'(?P<f>.*)\+ @ .*, parent: .*', line)
3767			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3768				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3769			if(mc and mr and (mc.group('t') == mr.group('t')) and
3770				(mc.group('f') == mr.group('f'))):
3771				i = data.dmesgtext.index(last)
3772				j = data.dmesgtext.index(line)
3773				data.dmesgtext[i] = line
3774				data.dmesgtext[j] = last
3775			last = line
3776	return testruns
3777
3778# Function: parseKernelLog
3779# Description:
3780#	 [deprecated for kernel 3.15.0 or newer]
3781#	 Analyse a dmesg log output file generated from this app during
3782#	 the execution phase. Create a set of device structures in memory
3783#	 for subsequent formatting in the html output file
3784#	 This call is only for legacy support on kernels where the ftrace
3785#	 data lacks the suspend_resume or device_pm_callbacks trace events.
3786# Arguments:
3787#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3788# Output:
3789#	 The filled Data object
3790def parseKernelLog(data):
3791	phase = 'suspend_runtime'
3792
3793	if(data.fwValid):
3794		sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3795			(data.fwSuspend, data.fwResume))
3796
3797	# dmesg phase match table
3798	dm = {
3799		'suspend_prepare': ['PM: Syncing filesystems.*'],
3800		        'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
3801		   'suspend_late': ['PM: suspend of devices complete after.*'],
3802		  'suspend_noirq': ['PM: late suspend of devices complete after.*'],
3803		'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
3804		 'resume_machine': ['ACPI: Low-level resume complete.*'],
3805		   'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
3806		   'resume_early': ['PM: noirq resume of devices complete after.*'],
3807		         'resume': ['PM: early resume of devices complete after.*'],
3808		'resume_complete': ['PM: resume of devices complete after.*'],
3809		    'post_resume': ['.*Restarting tasks \.\.\..*'],
3810	}
3811	if(sysvals.suspendmode == 'standby'):
3812		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3813	elif(sysvals.suspendmode == 'disk'):
3814		dm['suspend_late'] = ['PM: freeze of devices complete after.*']
3815		dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
3816		dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
3817		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3818		dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
3819		dm['resume'] = ['PM: early restore of devices complete after.*']
3820		dm['resume_complete'] = ['PM: restore of devices complete after.*']
3821	elif(sysvals.suspendmode == 'freeze'):
3822		dm['resume_machine'] = ['ACPI: resume from mwait']
3823
3824	# action table (expected events that occur and show up in dmesg)
3825	at = {
3826		'sync_filesystems': {
3827			'smsg': 'PM: Syncing filesystems.*',
3828			'emsg': 'PM: Preparing system for mem sleep.*' },
3829		'freeze_user_processes': {
3830			'smsg': 'Freezing user space processes .*',
3831			'emsg': 'Freezing remaining freezable tasks.*' },
3832		'freeze_tasks': {
3833			'smsg': 'Freezing remaining freezable tasks.*',
3834			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3835		'ACPI prepare': {
3836			'smsg': 'ACPI: Preparing to enter system sleep state.*',
3837			'emsg': 'PM: Saving platform NVS memory.*' },
3838		'PM vns': {
3839			'smsg': 'PM: Saving platform NVS memory.*',
3840			'emsg': 'Disabling non-boot CPUs .*' },
3841	}
3842
3843	t0 = -1.0
3844	cpu_start = -1.0
3845	prevktime = -1.0
3846	actions = dict()
3847	for line in data.dmesgtext:
3848		# parse each dmesg line into the time and message
3849		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3850		if(m):
3851			val = m.group('ktime')
3852			try:
3853				ktime = float(val)
3854			except:
3855				continue
3856			msg = m.group('msg')
3857			# initialize data start to first line time
3858			if t0 < 0:
3859				data.setStart(ktime)
3860				t0 = ktime
3861		else:
3862			continue
3863
3864		# check for a phase change line
3865		phasechange = False
3866		for p in dm:
3867			for s in dm[p]:
3868				if(re.match(s, msg)):
3869					phasechange, phase = True, p
3870					break
3871
3872		# hack for determining resume_machine end for freeze
3873		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3874			and phase == 'resume_machine' and \
3875			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3876			data.setPhase(phase, ktime, False)
3877			phase = 'resume_noirq'
3878			data.setPhase(phase, ktime, True)
3879
3880		if phasechange:
3881			if phase == 'suspend_prepare':
3882				data.setPhase(phase, ktime, True)
3883				data.setStart(ktime)
3884				data.tKernSus = ktime
3885			elif phase == 'suspend':
3886				lp = data.lastPhase()
3887				if lp:
3888					data.setPhase(lp, ktime, False)
3889				data.setPhase(phase, ktime, True)
3890			elif phase == 'suspend_late':
3891				lp = data.lastPhase()
3892				if lp:
3893					data.setPhase(lp, ktime, False)
3894				data.setPhase(phase, ktime, True)
3895			elif phase == 'suspend_noirq':
3896				lp = data.lastPhase()
3897				if lp:
3898					data.setPhase(lp, ktime, False)
3899				data.setPhase(phase, ktime, True)
3900			elif phase == 'suspend_machine':
3901				lp = data.lastPhase()
3902				if lp:
3903					data.setPhase(lp, ktime, False)
3904				data.setPhase(phase, ktime, True)
3905			elif phase == 'resume_machine':
3906				lp = data.lastPhase()
3907				if(sysvals.suspendmode in ['freeze', 'standby']):
3908					data.tSuspended = prevktime
3909					if lp:
3910						data.setPhase(lp, prevktime, False)
3911				else:
3912					data.tSuspended = ktime
3913					if lp:
3914						data.setPhase(lp, prevktime, False)
3915				data.tResumed = ktime
3916				data.setPhase(phase, ktime, True)
3917			elif phase == 'resume_noirq':
3918				lp = data.lastPhase()
3919				if lp:
3920					data.setPhase(lp, ktime, False)
3921				data.setPhase(phase, ktime, True)
3922			elif phase == 'resume_early':
3923				lp = data.lastPhase()
3924				if lp:
3925					data.setPhase(lp, ktime, False)
3926				data.setPhase(phase, ktime, True)
3927			elif phase == 'resume':
3928				lp = data.lastPhase()
3929				if lp:
3930					data.setPhase(lp, ktime, False)
3931				data.setPhase(phase, ktime, True)
3932			elif phase == 'resume_complete':
3933				lp = data.lastPhase()
3934				if lp:
3935					data.setPhase(lp, ktime, False)
3936				data.setPhase(phase, ktime, True)
3937			elif phase == 'post_resume':
3938				lp = data.lastPhase()
3939				if lp:
3940					data.setPhase(lp, ktime, False)
3941				data.setEnd(ktime)
3942				data.tKernRes = ktime
3943				break
3944
3945		# -- device callbacks --
3946		if(phase in data.sortedPhases()):
3947			# device init call
3948			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3949				sm = re.match('calling  (?P<f>.*)\+ @ '+\
3950					'(?P<n>.*), parent: (?P<p>.*)', msg);
3951				f = sm.group('f')
3952				n = sm.group('n')
3953				p = sm.group('p')
3954				if(f and n and p):
3955					data.newAction(phase, f, int(n), p, ktime, -1, '')
3956			# device init return
3957			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3958				'(?P<t>.*) usecs', msg)):
3959				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3960					'(?P<t>.*) usecs(?P<a>.*)', msg);
3961				f = sm.group('f')
3962				t = sm.group('t')
3963				list = data.dmesg[phase]['list']
3964				if(f in list):
3965					dev = list[f]
3966					dev['length'] = int(t)
3967					dev['end'] = ktime
3968
3969		# if trace events are not available, these are better than nothing
3970		if(not sysvals.usetraceevents):
3971			# look for known actions
3972			for a in sorted(at):
3973				if(re.match(at[a]['smsg'], msg)):
3974					if(a not in actions):
3975						actions[a] = []
3976					actions[a].append({'begin': ktime, 'end': ktime})
3977				if(re.match(at[a]['emsg'], msg)):
3978					if(a in actions):
3979						actions[a][-1]['end'] = ktime
3980			# now look for CPU on/off events
3981			if(re.match('Disabling non-boot CPUs .*', msg)):
3982				# start of first cpu suspend
3983				cpu_start = ktime
3984			elif(re.match('Enabling non-boot CPUs .*', msg)):
3985				# start of first cpu resume
3986				cpu_start = ktime
3987			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3988				# end of a cpu suspend, start of the next
3989				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3990				cpu = 'CPU'+m.group('cpu')
3991				if(cpu not in actions):
3992					actions[cpu] = []
3993				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3994				cpu_start = ktime
3995			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3996				# end of a cpu resume, start of the next
3997				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3998				cpu = 'CPU'+m.group('cpu')
3999				if(cpu not in actions):
4000					actions[cpu] = []
4001				actions[cpu].append({'begin': cpu_start, 'end': ktime})
4002				cpu_start = ktime
4003		prevktime = ktime
4004	data.initDevicegroups()
4005
4006	# fill in any missing phases
4007	phasedef = data.phasedef
4008	terr, lp = '', 'suspend_prepare'
4009	for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4010		if p not in data.dmesg:
4011			if not terr:
4012				pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
4013				terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
4014				if data.tSuspended == 0:
4015					data.tSuspended = data.dmesg[lp]['end']
4016				if data.tResumed == 0:
4017					data.tResumed = data.dmesg[lp]['end']
4018			sysvals.vprint('WARNING: phase "%s" is missing!' % p)
4019		lp = p
4020	lp = data.sortedPhases()[0]
4021	for p in data.sortedPhases():
4022		if(p != lp and not ('machine' in p and 'machine' in lp)):
4023			data.dmesg[lp]['end'] = data.dmesg[p]['start']
4024		lp = p
4025	if data.tSuspended == 0:
4026		data.tSuspended = data.tKernRes
4027	if data.tResumed == 0:
4028		data.tResumed = data.tSuspended
4029
4030	# fill in any actions we've found
4031	for name in sorted(actions):
4032		for event in actions[name]:
4033			data.newActionGlobal(name, event['begin'], event['end'])
4034
4035	if(len(sysvals.devicefilter) > 0):
4036		data.deviceFilter(sysvals.devicefilter)
4037	data.fixupInitcallsThatDidntReturn()
4038	return True
4039
4040def callgraphHTML(sv, hf, num, cg, title, color, devid):
4041	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'
4042	html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
4043	html_func_end = '</article>\n'
4044	html_func_leaf = '<article>{0} {1}</article>\n'
4045
4046	cgid = devid
4047	if cg.id:
4048		cgid += cg.id
4049	cglen = (cg.end - cg.start) * 1000
4050	if cglen < sv.mincglen:
4051		return num
4052
4053	fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
4054	flen = fmt % (cglen, cg.start, cg.end)
4055	hf.write(html_func_top.format(cgid, color, num, title, flen))
4056	num += 1
4057	for line in cg.list:
4058		if(line.length < 0.000000001):
4059			flen = ''
4060		else:
4061			fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
4062			flen = fmt % (line.length*1000, line.time)
4063		if line.isLeaf():
4064			hf.write(html_func_leaf.format(line.name, flen))
4065		elif line.freturn:
4066			hf.write(html_func_end)
4067		else:
4068			hf.write(html_func_start.format(num, line.name, flen))
4069			num += 1
4070	hf.write(html_func_end)
4071	return num
4072
4073def addCallgraphs(sv, hf, data):
4074	hf.write('<section id="callgraphs" class="callgraph">\n')
4075	# write out the ftrace data converted to html
4076	num = 0
4077	for p in data.sortedPhases():
4078		if sv.cgphase and p != sv.cgphase:
4079			continue
4080		list = data.dmesg[p]['list']
4081		for d in data.sortedDevices(p):
4082			if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4083				continue
4084			dev = list[d]
4085			color = 'white'
4086			if 'color' in data.dmesg[p]:
4087				color = data.dmesg[p]['color']
4088			if 'color' in dev:
4089				color = dev['color']
4090			name = d if '[' not in d else d.split('[')[0]
4091			if(d in sv.devprops):
4092				name = sv.devprops[d].altName(d)
4093			if 'drv' in dev and dev['drv']:
4094				name += ' {%s}' % dev['drv']
4095			if sv.suspendmode in suspendmodename:
4096				name += ' '+p
4097			if('ftrace' in dev):
4098				cg = dev['ftrace']
4099				if cg.name == sv.ftopfunc:
4100					name = 'top level suspend/resume call'
4101				num = callgraphHTML(sv, hf, num, cg,
4102					name, color, dev['id'])
4103			if('ftraces' in dev):
4104				for cg in dev['ftraces']:
4105					num = callgraphHTML(sv, hf, num, cg,
4106						name+' &rarr; '+cg.name, color, dev['id'])
4107	hf.write('\n\n    </section>\n')
4108
4109def summaryCSS(title, center=True):
4110	tdcenter = 'text-align:center;' if center else ''
4111	out = '<!DOCTYPE html>\n<html>\n<head>\n\
4112	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4113	<title>'+title+'</title>\n\
4114	<style type=\'text/css\'>\n\
4115		.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4116		table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4117		th {border: 1px solid black;background:#222;color:white;}\n\
4118		td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4119		tr.head td {border: 1px solid black;background:#aaa;}\n\
4120		tr.alt {background-color:#ddd;}\n\
4121		tr.notice {color:red;}\n\
4122		.minval {background-color:#BBFFBB;}\n\
4123		.medval {background-color:#BBBBFF;}\n\
4124		.maxval {background-color:#FFBBBB;}\n\
4125		.head a {color:#000;text-decoration: none;}\n\
4126	</style>\n</head>\n<body>\n'
4127	return out
4128
4129# Function: createHTMLSummarySimple
4130# Description:
4131#	 Create summary html file for a series of tests
4132# Arguments:
4133#	 testruns: array of Data objects from parseTraceLog
4134def createHTMLSummarySimple(testruns, htmlfile, title):
4135	# write the html header first (html head, css code, up to body start)
4136	html = summaryCSS('Summary - SleepGraph')
4137
4138	# extract the test data into list
4139	list = dict()
4140	tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4141	iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4142	num = 0
4143	useturbo = usewifi = False
4144	lastmode = ''
4145	cnt = dict()
4146	for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4147		mode = data['mode']
4148		if mode not in list:
4149			list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4150		if lastmode and lastmode != mode and num > 0:
4151			for i in range(2):
4152				s = sorted(tMed[i])
4153				list[lastmode]['med'][i] = s[int(len(s)//2)]
4154				iMed[i] = tMed[i][list[lastmode]['med'][i]]
4155			list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4156			list[lastmode]['min'] = tMin
4157			list[lastmode]['max'] = tMax
4158			list[lastmode]['idx'] = (iMin, iMed, iMax)
4159			tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4160			iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4161			num = 0
4162		pkgpc10 = syslpi = wifi = ''
4163		if 'pkgpc10' in data and 'syslpi' in data:
4164			pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4165		if 'wifi' in data:
4166			wifi, usewifi = data['wifi'], True
4167		res = data['result']
4168		tVal = [float(data['suspend']), float(data['resume'])]
4169		list[mode]['data'].append([data['host'], data['kernel'],
4170			data['time'], tVal[0], tVal[1], data['url'], res,
4171			data['issues'], data['sus_worst'], data['sus_worsttime'],
4172			data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi])
4173		idx = len(list[mode]['data']) - 1
4174		if res.startswith('fail in'):
4175			res = 'fail'
4176		if res not in cnt:
4177			cnt[res] = 1
4178		else:
4179			cnt[res] += 1
4180		if res == 'pass':
4181			for i in range(2):
4182				tMed[i][tVal[i]] = idx
4183				tAvg[i] += tVal[i]
4184				if tMin[i] == 0 or tVal[i] < tMin[i]:
4185					iMin[i] = idx
4186					tMin[i] = tVal[i]
4187				if tMax[i] == 0 or tVal[i] > tMax[i]:
4188					iMax[i] = idx
4189					tMax[i] = tVal[i]
4190			num += 1
4191		lastmode = mode
4192	if lastmode and num > 0:
4193		for i in range(2):
4194			s = sorted(tMed[i])
4195			list[lastmode]['med'][i] = s[int(len(s)//2)]
4196			iMed[i] = tMed[i][list[lastmode]['med'][i]]
4197		list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4198		list[lastmode]['min'] = tMin
4199		list[lastmode]['max'] = tMax
4200		list[lastmode]['idx'] = (iMin, iMed, iMax)
4201
4202	# group test header
4203	desc = []
4204	for ilk in sorted(cnt, reverse=True):
4205		if cnt[ilk] > 0:
4206			desc.append('%d %s' % (cnt[ilk], ilk))
4207	html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4208	th = '\t<th>{0}</th>\n'
4209	td = '\t<td>{0}</td>\n'
4210	tdh = '\t<td{1}>{0}</td>\n'
4211	tdlink = '\t<td><a href="{0}">html</a></td>\n'
4212	cols = 12
4213	if useturbo:
4214		cols += 2
4215	if usewifi:
4216		cols += 1
4217	colspan = '%d' % cols
4218
4219	# table header
4220	html += '<table>\n<tr>\n' + th.format('#') +\
4221		th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4222		th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4223		th.format('Suspend') + th.format('Resume') +\
4224		th.format('Worst Suspend Device') + th.format('SD Time') +\
4225		th.format('Worst Resume Device') + th.format('RD Time')
4226	if useturbo:
4227		html += th.format('PkgPC10') + th.format('SysLPI')
4228	if usewifi:
4229		html += th.format('Wifi')
4230	html += th.format('Detail')+'</tr>\n'
4231	# export list into html
4232	head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4233		'<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4234		'<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4235		'<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4236		'<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4237		'Resume Avg={6} '+\
4238		'<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4239		'<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4240		'<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4241		'</tr>\n'
4242	headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4243		colspan+'></td></tr>\n'
4244	for mode in sorted(list):
4245		# header line for each suspend mode
4246		num = 0
4247		tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4248			list[mode]['max'], list[mode]['med']
4249		count = len(list[mode]['data'])
4250		if 'idx' in list[mode]:
4251			iMin, iMed, iMax = list[mode]['idx']
4252			html += head.format('%d' % count, mode.upper(),
4253				'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4254				'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4255				mode.lower()
4256			)
4257		else:
4258			iMin = iMed = iMax = [-1, -1, -1]
4259			html += headnone.format('%d' % count, mode.upper())
4260		for d in list[mode]['data']:
4261			# row classes - alternate row color
4262			rcls = ['alt'] if num % 2 == 1 else []
4263			if d[6] != 'pass':
4264				rcls.append('notice')
4265			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4266			# figure out if the line has sus or res highlighted
4267			idx = list[mode]['data'].index(d)
4268			tHigh = ['', '']
4269			for i in range(2):
4270				tag = 's%s' % mode if i == 0 else 'r%s' % mode
4271				if idx == iMin[i]:
4272					tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4273				elif idx == iMax[i]:
4274					tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4275				elif idx == iMed[i]:
4276					tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4277			html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4278			html += td.format(mode)										# mode
4279			html += td.format(d[0])										# host
4280			html += td.format(d[1])										# kernel
4281			html += td.format(d[2])										# time
4282			html += td.format(d[6])										# result
4283			html += td.format(d[7])										# issues
4284			html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('')	# suspend
4285			html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('')	# resume
4286			html += td.format(d[8])										# sus_worst
4287			html += td.format('%.3f ms' % d[9])	if d[9] else td.format('')		# sus_worst time
4288			html += td.format(d[10])									# res_worst
4289			html += td.format('%.3f ms' % d[11]) if d[11] else td.format('')	# res_worst time
4290			if useturbo:
4291				html += td.format(d[12])								# pkg_pc10
4292				html += td.format(d[13])								# syslpi
4293			if usewifi:
4294				html += td.format(d[14])								# wifi
4295			html += tdlink.format(d[5]) if d[5] else td.format('')		# url
4296			html += '</tr>\n'
4297			num += 1
4298
4299	# flush the data to file
4300	hf = open(htmlfile, 'w')
4301	hf.write(html+'</table>\n</body>\n</html>\n')
4302	hf.close()
4303
4304def createHTMLDeviceSummary(testruns, htmlfile, title):
4305	html = summaryCSS('Device Summary - SleepGraph', False)
4306
4307	# create global device list from all tests
4308	devall = dict()
4309	for data in testruns:
4310		host, url, devlist = data['host'], data['url'], data['devlist']
4311		for type in devlist:
4312			if type not in devall:
4313				devall[type] = dict()
4314			mdevlist, devlist = devall[type], data['devlist'][type]
4315			for name in devlist:
4316				length = devlist[name]
4317				if name not in mdevlist:
4318					mdevlist[name] = {'name': name, 'host': host,
4319						'worst': length, 'total': length, 'count': 1,
4320						'url': url}
4321				else:
4322					if length > mdevlist[name]['worst']:
4323						mdevlist[name]['worst'] = length
4324						mdevlist[name]['url'] = url
4325						mdevlist[name]['host'] = host
4326					mdevlist[name]['total'] += length
4327					mdevlist[name]['count'] += 1
4328
4329	# generate the html
4330	th = '\t<th>{0}</th>\n'
4331	td = '\t<td align=center>{0}</td>\n'
4332	tdr = '\t<td align=right>{0}</td>\n'
4333	tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4334	limit = 1
4335	for type in sorted(devall, reverse=True):
4336		num = 0
4337		devlist = devall[type]
4338		# table header
4339		html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4340			(title, type.upper(), limit)
4341		html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4342			th.format('Average Time') + th.format('Count') +\
4343			th.format('Worst Time') + th.format('Host (worst time)') +\
4344			th.format('Link (worst time)') + '</tr>\n'
4345		for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4346			devlist[k]['total'], devlist[k]['name']), reverse=True):
4347			data = devall[type][name]
4348			data['average'] = data['total'] / data['count']
4349			if data['average'] < limit:
4350				continue
4351			# row classes - alternate row color
4352			rcls = ['alt'] if num % 2 == 1 else []
4353			html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4354			html += tdr.format(data['name'])				# name
4355			html += td.format('%.3f ms' % data['average'])	# average
4356			html += td.format(data['count'])				# count
4357			html += td.format('%.3f ms' % data['worst'])	# worst
4358			html += td.format(data['host'])					# host
4359			html += tdlink.format(data['url'])				# url
4360			html += '</tr>\n'
4361			num += 1
4362		html += '</table>\n'
4363
4364	# flush the data to file
4365	hf = open(htmlfile, 'w')
4366	hf.write(html+'</body>\n</html>\n')
4367	hf.close()
4368	return devall
4369
4370def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4371	multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4372	html = summaryCSS('Issues Summary - SleepGraph', False)
4373	total = len(testruns)
4374
4375	# generate the html
4376	th = '\t<th>{0}</th>\n'
4377	td = '\t<td align={0}>{1}</td>\n'
4378	tdlink = '<a href="{1}">{0}</a>'
4379	subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4380	html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4381	html += '<tr>\n' + th.format('Issue') + th.format('Count')
4382	if multihost:
4383		html += th.format('Hosts')
4384	html += th.format('Tests') + th.format('Fail Rate') +\
4385		th.format('First Instance') + '</tr>\n'
4386
 
 
 
 
 
4387	num = 0
4388	for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4389		testtotal = 0
4390		links = []
4391		for host in sorted(e['urls']):
4392			links.append(tdlink.format(host, e['urls'][host][0]))
4393			testtotal += len(e['urls'][host])
4394		rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4395		# row classes - alternate row color
4396		rcls = ['alt'] if num % 2 == 1 else []
4397		html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4398		html += td.format('left', e['line'])		# issue
4399		html += td.format('center', e['count'])		# count
4400		if multihost:
4401			html += td.format('center', len(e['urls']))	# hosts
4402		html += td.format('center', testtotal)		# test count
4403		html += td.format('center', rate)			# test rate
4404		html += td.format('center nowrap', '<br>'.join(links))	# links
4405		html += '</tr>\n'
4406		num += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4407
4408	# flush the data to file
4409	hf = open(htmlfile, 'w')
4410	hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4411	hf.close()
4412	return issues
4413
4414def ordinal(value):
4415	suffix = 'th'
4416	if value < 10 or value > 19:
4417		if value % 10 == 1:
4418			suffix = 'st'
4419		elif value % 10 == 2:
4420			suffix = 'nd'
4421		elif value % 10 == 3:
4422			suffix = 'rd'
4423	return '%d%s' % (value, suffix)
4424
4425# Function: createHTML
4426# Description:
4427#	 Create the output html file from the resident test data
4428# Arguments:
4429#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4430# Output:
4431#	 True if the html file was created, false if it failed
4432def createHTML(testruns, testfail):
4433	if len(testruns) < 1:
4434		pprint('ERROR: Not enough test data to build a timeline')
4435		return
4436
4437	kerror = False
4438	for data in testruns:
4439		if data.kerror:
4440			kerror = True
4441		if(sysvals.suspendmode in ['freeze', 'standby']):
4442			data.trimFreezeTime(testruns[-1].tSuspended)
4443		else:
4444			data.getMemTime()
4445
4446	# html function templates
4447	html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
4448	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'
4449	html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4450	html_timetotal = '<table class="time1">\n<tr>'\
4451		'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4452		'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4453		'</tr>\n</table>\n'
4454	html_timetotal2 = '<table class="time1">\n<tr>'\
4455		'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4456		'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4457		'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4458		'</tr>\n</table>\n'
4459	html_timetotal3 = '<table class="time1">\n<tr>'\
4460		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
4461		'<td class="yellow">Command: <b>{1}</b></td>'\
4462		'</tr>\n</table>\n'
4463	html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4464	html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4465	html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4466	html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
 
 
4467
4468	# html format variables
4469	scaleH = 20
4470	if kerror:
4471		scaleH = 40
4472
4473	# device timeline
4474	devtl = Timeline(30, scaleH)
4475
4476	# write the test title and general info header
4477	devtl.createHeader(sysvals, testruns[0].stamp)
4478
4479	# Generate the header for this timeline
4480	for data in testruns:
4481		tTotal = data.end - data.start
 
4482		if(tTotal == 0):
4483			doError('No timeline data')
 
 
4484		if sysvals.suspendmode == 'command':
4485			run_time = '%.0f' % (tTotal * 1000)
4486			if sysvals.testcommand:
4487				testdesc = sysvals.testcommand
4488			else:
4489				testdesc = 'unknown'
4490			if(len(testruns) > 1):
4491				testdesc = ordinal(data.testnumber+1)+' '+testdesc
4492			thtml = html_timetotal3.format(run_time, testdesc)
4493			devtl.html += thtml
4494			continue
4495		# typical full suspend/resume header
4496		stot, rtot = sktime, rktime = data.getTimeValues()
4497		ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4498		if data.fwValid:
4499			stot += (data.fwSuspend/1000000.0)
4500			rtot += (data.fwResume/1000000.0)
4501			ssrc.append('firmware')
4502			rsrc.append('firmware')
4503			testdesc = 'Total'
4504		if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4505			rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4506			rsrc.append('wifi')
4507			testdesc = 'Total'
4508		suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4509		stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4510			(sysvals.suspendmode, ' & '.join(ssrc))
4511		rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4512			(sysvals.suspendmode, ' & '.join(rsrc))
4513		if(len(testruns) > 1):
4514			testdesc = testdesc2 = ordinal(data.testnumber+1)
4515			testdesc2 += ' '
4516		if(len(data.tLow) == 0):
4517			thtml = html_timetotal.format(suspend_time, \
4518				resume_time, testdesc, stitle, rtitle)
4519		else:
4520			low_time = '+'.join(data.tLow)
4521			thtml = html_timetotal2.format(suspend_time, low_time, \
4522				resume_time, testdesc, stitle, rtitle)
4523		devtl.html += thtml
4524		if not data.fwValid and 'dev' not in data.wifi:
4525			continue
4526		# extra detail when the times come from multiple sources
4527		thtml = '<table class="time2">\n<tr>'
4528		thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4529		if data.fwValid:
4530			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4531			rftime = '%.3f'%(data.fwResume / 1000000.0)
4532			thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4533			thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4534		thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4535		if 'time' in data.wifi:
4536			if data.wifi['stat'] != 'timeout':
4537				wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
 
 
 
 
 
 
 
4538			else:
4539				wtime = 'TIMEOUT'
4540			thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4541		thtml += '</tr>\n</table>\n'
4542		devtl.html += thtml
4543	if testfail:
4544		devtl.html += html_fail.format(testfail)
4545
4546	# time scale for potentially multiple datasets
4547	t0 = testruns[0].start
4548	tMax = testruns[-1].end
4549	tTotal = tMax - t0
4550
4551	# determine the maximum number of rows we need to draw
4552	fulllist = []
4553	threadlist = []
4554	pscnt = 0
4555	devcnt = 0
4556	for data in testruns:
4557		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4558		for group in data.devicegroups:
4559			devlist = []
4560			for phase in group:
4561				for devname in sorted(data.tdevlist[phase]):
4562					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4563					devlist.append(d)
4564					if d.isa('kth'):
4565						threadlist.append(d)
4566					else:
4567						if d.isa('ps'):
4568							pscnt += 1
4569						else:
4570							devcnt += 1
4571						fulllist.append(d)
4572			if sysvals.mixedphaseheight:
4573				devtl.getPhaseRows(devlist)
4574	if not sysvals.mixedphaseheight:
4575		if len(threadlist) > 0 and len(fulllist) > 0:
4576			if pscnt > 0 and devcnt > 0:
4577				msg = 'user processes & device pm callbacks'
4578			elif pscnt > 0:
4579				msg = 'user processes'
4580			else:
4581				msg = 'device pm callbacks'
4582			d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4583			fulllist.insert(0, d)
4584		devtl.getPhaseRows(fulllist)
4585		if len(threadlist) > 0:
4586			d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4587			threadlist.insert(0, d)
4588			devtl.getPhaseRows(threadlist, devtl.rows)
4589	devtl.calcTotalRows()
4590
4591	# draw the full timeline
4592	devtl.createZoomBox(sysvals.suspendmode, len(testruns))
 
 
 
 
 
 
 
 
4593	for data in testruns:
4594		# draw each test run and block chronologically
4595		phases = {'suspend':[],'resume':[]}
4596		for phase in data.sortedPhases():
4597			if data.dmesg[phase]['start'] >= data.tSuspended:
4598				phases['resume'].append(phase)
4599			else:
4600				phases['suspend'].append(phase)
4601		# now draw the actual timeline blocks
4602		for dir in phases:
4603			# draw suspend and resume blocks separately
4604			bname = '%s%d' % (dir[0], data.testnumber)
4605			if dir == 'suspend':
4606				m0 = data.start
4607				mMax = data.tSuspended
4608				left = '%f' % (((m0-t0)*100.0)/tTotal)
4609			else:
4610				m0 = data.tSuspended
4611				mMax = data.end
4612				# in an x2 run, remove any gap between blocks
4613				if len(testruns) > 1 and data.testnumber == 0:
4614					mMax = testruns[1].start
4615				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4616			mTotal = mMax - m0
4617			# if a timeline block is 0 length, skip altogether
4618			if mTotal == 0:
4619				continue
4620			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4621			devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4622			for b in phases[dir]:
4623				# draw the phase color background
4624				phase = data.dmesg[b]
4625				length = phase['end']-phase['start']
4626				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4627				width = '%f' % ((length*100.0)/mTotal)
4628				devtl.html += devtl.html_phase.format(left, width, \
4629					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4630					data.dmesg[b]['color'], '')
4631			for e in data.errorinfo[dir]:
4632				# draw red lines for any kernel errors found
4633				type, t, idx1, idx2 = e
4634				id = '%d_%d' % (idx1, idx2)
4635				right = '%f' % (((mMax-t)*100.0)/mTotal)
4636				devtl.html += html_error.format(right, id, type)
4637			for b in phases[dir]:
4638				# draw the devices for this phase
4639				phaselist = data.dmesg[b]['list']
4640				for d in sorted(data.tdevlist[b]):
4641					dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0]
4642					name, dev = dname, phaselist[d]
4643					drv = xtraclass = xtrainfo = xtrastyle = ''
 
 
 
4644					if 'htmlclass' in dev:
4645						xtraclass = dev['htmlclass']
4646					if 'color' in dev:
4647						xtrastyle = 'background:%s;' % dev['color']
4648					if(d in sysvals.devprops):
4649						name = sysvals.devprops[d].altName(d)
4650						xtraclass = sysvals.devprops[d].xtraClass()
4651						xtrainfo = sysvals.devprops[d].xtraInfo()
4652					elif xtraclass == ' kth':
4653						xtrainfo = ' kernel_thread'
4654					if('drv' in dev and dev['drv']):
4655						drv = ' {%s}' % dev['drv']
4656					rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4657					rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4658					top = '%.3f' % (rowtop + devtl.scaleH)
4659					left = '%f' % (((dev['start']-m0)*100)/mTotal)
4660					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4661					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4662					title = name+drv+xtrainfo+length
4663					if sysvals.suspendmode == 'command':
4664						title += sysvals.testcommand
4665					elif xtraclass == ' ps':
4666						if 'suspend' in b:
4667							title += 'pre_suspend_process'
4668						else:
4669							title += 'post_resume_process'
4670					else:
4671						title += b
4672					devtl.html += devtl.html_device.format(dev['id'], \
4673						title, left, top, '%.3f'%rowheight, width, \
4674						dname+drv, xtraclass, xtrastyle)
4675					if('cpuexec' in dev):
4676						for t in sorted(dev['cpuexec']):
4677							start, end = t
4678							j = float(dev['cpuexec'][t]) / 5
4679							if j > 1.0:
4680								j = 1.0
4681							height = '%.3f' % (rowheight/3)
4682							top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4683							left = '%f' % (((start-m0)*100)/mTotal)
4684							width = '%f' % ((end-start)*100/mTotal)
4685							color = 'rgba(255, 0, 0, %f)' % j
4686							devtl.html += \
4687								html_cpuexec.format(left, top, height, width, color)
4688					if('src' not in dev):
4689						continue
4690					# draw any trace events for this device
4691					for e in dev['src']:
4692						if e.length == 0:
4693							continue
4694						height = '%.3f' % devtl.rowH
4695						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4696						left = '%f' % (((e.time-m0)*100)/mTotal)
4697						width = '%f' % (e.length*100/mTotal)
4698						xtrastyle = ''
4699						if e.color:
4700							xtrastyle = 'background:%s;' % e.color
4701						devtl.html += \
4702							html_traceevent.format(e.title(), \
4703								left, top, height, width, e.text(), '', xtrastyle)
4704			# draw the time scale, try to make the number of labels readable
4705			devtl.createTimeScale(m0, mMax, tTotal, dir)
4706			devtl.html += '</div>\n'
4707
4708	# timeline is finished
4709	devtl.html += '</div>\n</div>\n'
4710
4711	# draw a legend which describes the phases by color
4712	if sysvals.suspendmode != 'command':
4713		phasedef = testruns[-1].phasedef
4714		devtl.html += '<div class="legend">\n'
4715		pdelta = 100.0/len(phasedef.keys())
4716		pmargin = pdelta / 4.0
4717		for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4718			id, p = '', phasedef[phase]
4719			for word in phase.split('_'):
4720				id += word[0]
4721			order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4722			name = phase.replace('_', ' &nbsp;')
4723			devtl.html += devtl.html_legend.format(order, p['color'], name, id)
 
 
4724		devtl.html += '</div>\n'
4725
4726	hf = open(sysvals.htmlfile, 'w')
4727	addCSS(hf, sysvals, len(testruns), kerror)
4728
4729	# write the device timeline
4730	hf.write(devtl.html)
4731	hf.write('<div id="devicedetailtitle"></div>\n')
4732	hf.write('<div id="devicedetail" style="display:none;">\n')
4733	# draw the colored boxes for the device detail section
4734	for data in testruns:
4735		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4736		pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4737		hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4738			'0', '0', pscolor))
4739		for b in data.sortedPhases():
4740			phase = data.dmesg[b]
4741			length = phase['end']-phase['start']
4742			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4743			width = '%.3f' % ((length*100.0)/tTotal)
4744			hf.write(devtl.html_phaselet.format(b, left, width, \
4745				data.dmesg[b]['color']))
4746		hf.write(devtl.html_phaselet.format('post_resume_process', \
4747			'0', '0', pscolor))
4748		if sysvals.suspendmode == 'command':
4749			hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4750		hf.write('</div>\n')
4751	hf.write('</div>\n')
4752
4753	# write the ftrace data (callgraph)
4754	if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4755		data = testruns[sysvals.cgtest]
4756	else:
4757		data = testruns[-1]
4758	if sysvals.usecallgraph:
4759		addCallgraphs(sysvals, hf, data)
4760
4761	# add the test log as a hidden div
4762	if sysvals.testlog and sysvals.logmsg:
4763		hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4764	# add the dmesg log as a hidden div
4765	if sysvals.dmesglog and sysvals.dmesgfile:
4766		hf.write('<div id="dmesglog" style="display:none;">\n')
4767		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4768		for line in lf:
4769			line = line.replace('<', '&lt').replace('>', '&gt')
4770			hf.write(line)
4771		lf.close()
4772		hf.write('</div>\n')
4773	# add the ftrace log as a hidden div
4774	if sysvals.ftracelog and sysvals.ftracefile:
4775		hf.write('<div id="ftracelog" style="display:none;">\n')
4776		lf = sysvals.openlog(sysvals.ftracefile, 'r')
4777		for line in lf:
4778			hf.write(line)
4779		lf.close()
4780		hf.write('</div>\n')
4781
4782	# write the footer and close
4783	addScriptCode(hf, testruns)
4784	hf.write('</body>\n</html>\n')
4785	hf.close()
4786	return True
4787
4788def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4789	kernel = sv.stamp['kernel']
4790	host = sv.hostname[0].upper()+sv.hostname[1:]
4791	mode = sv.suspendmode
4792	if sv.suspendmode in suspendmodename:
4793		mode = suspendmodename[sv.suspendmode]
4794	title = host+' '+mode+' '+kernel
4795
4796	# various format changes by flags
4797	cgchk = 'checked'
4798	cgnchk = 'not(:checked)'
4799	if sv.cgexp:
4800		cgchk = 'not(:checked)'
4801		cgnchk = 'checked'
4802
4803	hoverZ = 'z-index:8;'
4804	if sv.usedevsrc:
4805		hoverZ = ''
4806
4807	devlistpos = 'absolute'
4808	if testcount > 1:
4809		devlistpos = 'relative'
4810
4811	scaleTH = 20
4812	if kerror:
4813		scaleTH = 60
4814
4815	# write the html header first (html head, css code, up to body start)
4816	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4817	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4818	<title>'+title+'</title>\n\
4819	<style type=\'text/css\'>\n\
4820		body {overflow-y:scroll;}\n\
4821		.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4822		.stamp.sysinfo {font:10px Arial;}\n\
4823		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4824		.callgraph article * {padding-left:28px;}\n\
4825		h1 {color:black;font:bold 30px Times;}\n\
4826		t0 {color:black;font:bold 30px Times;}\n\
4827		t1 {color:black;font:30px Times;}\n\
4828		t2 {color:black;font:25px Times;}\n\
4829		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
4830		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4831		cS {font:bold 13px Times;}\n\
4832		table {width:100%;}\n\
4833		.gray {background:rgba(80,80,80,0.1);}\n\
4834		.green {background:rgba(204,255,204,0.4);}\n\
4835		.purple {background:rgba(128,0,128,0.2);}\n\
4836		.yellow {background:rgba(255,255,204,0.4);}\n\
4837		.blue {background:rgba(169,208,245,0.4);}\n\
4838		.time1 {font:22px Arial;border:1px solid;}\n\
4839		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4840		.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
4841		td {text-align:center;}\n\
4842		r {color:#500000;font:15px Tahoma;}\n\
4843		n {color:#505050;font:15px Tahoma;}\n\
4844		.tdhl {color:red;}\n\
4845		.hide {display:none;}\n\
4846		.pf {display:none;}\n\
4847		.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\
4848		.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\
4849		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4850		.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4851		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4852		.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\
4853		.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4854		.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4855		.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4856		.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4857		.hover.sync {background:white;}\n\
4858		.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4859		.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4860		.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\
4861		.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4862		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4863		.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4864		.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4865		.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4866		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4867		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4868		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4869		.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4870		.devlist {position:'+devlistpos+';width:190px;}\n\
4871		a:link {color:white;text-decoration:none;}\n\
4872		a:visited {color:white;}\n\
4873		a:hover {color:white;}\n\
4874		a:active {color:white;}\n\
4875		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4876		#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4877		.tblock {position:absolute;height:100%;background:#ddd;}\n\
4878		.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4879		.bg {z-index:1;}\n\
4880'+extra+'\
4881	</style>\n</head>\n<body>\n'
4882	hf.write(html_header)
4883
4884# Function: addScriptCode
4885# Description:
4886#	 Adds the javascript code to the output html
4887# Arguments:
4888#	 hf: the open html file pointer
4889#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4890def addScriptCode(hf, testruns):
4891	t0 = testruns[0].start * 1000
4892	tMax = testruns[-1].end * 1000
4893	# create an array in javascript memory with the device details
4894	detail = '	var devtable = [];\n'
4895	for data in testruns:
4896		topo = data.deviceTopology()
4897		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
4898	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
4899	# add the code which will manipulate the data in the browser
4900	script_code = \
4901	'<script type="text/javascript">\n'+detail+\
4902	'	var resolution = -1;\n'\
4903	'	var dragval = [0, 0];\n'\
4904	'	function redrawTimescale(t0, tMax, tS) {\n'\
4905	'		var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4906	'		var tTotal = tMax - t0;\n'\
4907	'		var list = document.getElementsByClassName("tblock");\n'\
4908	'		for (var i = 0; i < list.length; i++) {\n'\
4909	'			var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4910	'			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4911	'			var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4912	'			var mMax = m0 + mTotal;\n'\
4913	'			var html = "";\n'\
4914	'			var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4915	'			if(divTotal > 1000) continue;\n'\
4916	'			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4917	'			var pos = 0.0, val = 0.0;\n'\
4918	'			for (var j = 0; j < divTotal; j++) {\n'\
4919	'				var htmlline = "";\n'\
4920	'				var mode = list[i].id[5];\n'\
4921	'				if(mode == "s") {\n'\
4922	'					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4923	'					val = (j-divTotal+1)*tS;\n'\
4924	'					if(j == divTotal - 1)\n'\
4925	'						htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S&rarr;</cS></div>\';\n'\
4926	'					else\n'\
4927	'						htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4928	'				} else {\n'\
4929	'					pos = 100 - (((j)*tS*100)/mTotal);\n'\
4930	'					val = (j)*tS;\n'\
4931	'					htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4932	'					if(j == 0)\n'\
4933	'						if(mode == "r")\n'\
4934	'							htmlline = rline+"<cS>&larr;R</cS></div>";\n'\
4935	'						else\n'\
4936	'							htmlline = rline+"<cS>0ms</div>";\n'\
4937	'				}\n'\
4938	'				html += htmlline;\n'\
4939	'			}\n'\
4940	'			timescale.innerHTML = html;\n'\
4941	'		}\n'\
4942	'	}\n'\
4943	'	function zoomTimeline() {\n'\
4944	'		var dmesg = document.getElementById("dmesg");\n'\
4945	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4946	'		var left = zoombox.scrollLeft;\n'\
4947	'		var val = parseFloat(dmesg.style.width);\n'\
4948	'		var newval = 100;\n'\
4949	'		var sh = window.outerWidth / 2;\n'\
4950	'		if(this.id == "zoomin") {\n'\
4951	'			newval = val * 1.2;\n'\
4952	'			if(newval > 910034) newval = 910034;\n'\
4953	'			dmesg.style.width = newval+"%";\n'\
4954	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4955	'		} else if (this.id == "zoomout") {\n'\
4956	'			newval = val / 1.2;\n'\
4957	'			if(newval < 100) newval = 100;\n'\
4958	'			dmesg.style.width = newval+"%";\n'\
4959	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4960	'		} else {\n'\
4961	'			zoombox.scrollLeft = 0;\n'\
4962	'			dmesg.style.width = "100%";\n'\
4963	'		}\n'\
4964	'		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4965	'		var t0 = bounds[0];\n'\
4966	'		var tMax = bounds[1];\n'\
4967	'		var tTotal = tMax - t0;\n'\
4968	'		var wTotal = tTotal * 100.0 / newval;\n'\
4969	'		var idx = 7*window.innerWidth/1100;\n'\
4970	'		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4971	'		if(i >= tS.length) i = tS.length - 1;\n'\
4972	'		if(tS[i] == resolution) return;\n'\
4973	'		resolution = tS[i];\n'\
4974	'		redrawTimescale(t0, tMax, tS[i]);\n'\
4975	'	}\n'\
4976	'	function deviceName(title) {\n'\
4977	'		var name = title.slice(0, title.indexOf(" ("));\n'\
4978	'		return name;\n'\
4979	'	}\n'\
4980	'	function deviceHover() {\n'\
4981	'		var name = deviceName(this.title);\n'\
4982	'		var dmesg = document.getElementById("dmesg");\n'\
4983	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4984	'		var cpu = -1;\n'\
4985	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4986	'			cpu = parseInt(name.slice(7));\n'\
4987	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4988	'			cpu = parseInt(name.slice(8));\n'\
4989	'		for (var i = 0; i < dev.length; i++) {\n'\
4990	'			dname = deviceName(dev[i].title);\n'\
4991	'			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4992	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4993	'				(name == dname))\n'\
4994	'			{\n'\
4995	'				dev[i].className = "hover "+cname;\n'\
4996	'			} else {\n'\
4997	'				dev[i].className = cname;\n'\
4998	'			}\n'\
4999	'		}\n'\
5000	'	}\n'\
5001	'	function deviceUnhover() {\n'\
5002	'		var dmesg = document.getElementById("dmesg");\n'\
5003	'		var dev = dmesg.getElementsByClassName("thread");\n'\
5004	'		for (var i = 0; i < dev.length; i++) {\n'\
5005	'			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
5006	'		}\n'\
5007	'	}\n'\
5008	'	function deviceTitle(title, total, cpu) {\n'\
5009	'		var prefix = "Total";\n'\
5010	'		if(total.length > 3) {\n'\
5011	'			prefix = "Average";\n'\
5012	'			total[1] = (total[1]+total[3])/2;\n'\
5013	'			total[2] = (total[2]+total[4])/2;\n'\
5014	'		}\n'\
5015	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
5016	'		var name = deviceName(title);\n'\
5017	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
5018	'		var driver = "";\n'\
5019	'		var tS = "<t2>(</t2>";\n'\
5020	'		var tR = "<t2>)</t2>";\n'\
5021	'		if(total[1] > 0)\n'\
5022	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
5023	'		if(total[2] > 0)\n'\
5024	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
5025	'		var s = title.indexOf("{");\n'\
5026	'		var e = title.indexOf("}");\n'\
5027	'		if((s >= 0) && (e >= 0))\n'\
5028	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
5029	'		if(total[1] > 0 && total[2] > 0)\n'\
5030	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
5031	'		else\n'\
5032	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
5033	'		return name;\n'\
5034	'	}\n'\
5035	'	function deviceDetail() {\n'\
5036	'		var devinfo = document.getElementById("devicedetail");\n'\
5037	'		devinfo.style.display = "block";\n'\
5038	'		var name = deviceName(this.title);\n'\
5039	'		var cpu = -1;\n'\
5040	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
5041	'			cpu = parseInt(name.slice(7));\n'\
5042	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
5043	'			cpu = parseInt(name.slice(8));\n'\
5044	'		var dmesg = document.getElementById("dmesg");\n'\
5045	'		var dev = dmesg.getElementsByClassName("thread");\n'\
5046	'		var idlist = [];\n'\
5047	'		var pdata = [[]];\n'\
5048	'		if(document.getElementById("devicedetail1"))\n'\
5049	'			pdata = [[], []];\n'\
5050	'		var pd = pdata[0];\n'\
5051	'		var total = [0.0, 0.0, 0.0];\n'\
5052	'		for (var i = 0; i < dev.length; i++) {\n'\
5053	'			dname = deviceName(dev[i].title);\n'\
5054	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
5055	'				(name == dname))\n'\
5056	'			{\n'\
5057	'				idlist[idlist.length] = dev[i].id;\n'\
5058	'				var tidx = 1;\n'\
5059	'				if(dev[i].id[0] == "a") {\n'\
5060	'					pd = pdata[0];\n'\
5061	'				} else {\n'\
5062	'					if(pdata.length == 1) pdata[1] = [];\n'\
5063	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
5064	'					pd = pdata[1];\n'\
5065	'					tidx = 3;\n'\
5066	'				}\n'\
5067	'				var info = dev[i].title.split(" ");\n'\
5068	'				var pname = info[info.length-1];\n'\
5069	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
5070	'				total[0] += pd[pname];\n'\
5071	'				if(pname.indexOf("suspend") >= 0)\n'\
5072	'					total[tidx] += pd[pname];\n'\
5073	'				else\n'\
5074	'					total[tidx+1] += pd[pname];\n'\
5075	'			}\n'\
5076	'		}\n'\
5077	'		var devname = deviceTitle(this.title, total, cpu);\n'\
5078	'		var left = 0.0;\n'\
5079	'		for (var t = 0; t < pdata.length; t++) {\n'\
5080	'			pd = pdata[t];\n'\
5081	'			devinfo = document.getElementById("devicedetail"+t);\n'\
5082	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
5083	'			for (var i = 0; i < phases.length; i++) {\n'\
5084	'				if(phases[i].id in pd) {\n'\
5085	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
5086	'					var fs = 32;\n'\
5087	'					if(w < 8) fs = 4*w | 0;\n'\
5088	'					var fs2 = fs*3/4;\n'\
5089	'					phases[i].style.width = w+"%";\n'\
5090	'					phases[i].style.left = left+"%";\n'\
5091	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
5092	'					left += w;\n'\
5093	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
5094	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
5095	'					phases[i].innerHTML = time+pname;\n'\
5096	'				} else {\n'\
5097	'					phases[i].style.width = "0%";\n'\
5098	'					phases[i].style.left = left+"%";\n'\
5099	'				}\n'\
5100	'			}\n'\
5101	'		}\n'\
5102	'		if(typeof devstats !== \'undefined\')\n'\
5103	'			callDetail(this.id, this.title);\n'\
5104	'		var cglist = document.getElementById("callgraphs");\n'\
5105	'		if(!cglist) return;\n'\
5106	'		var cg = cglist.getElementsByClassName("atop");\n'\
5107	'		if(cg.length < 10) return;\n'\
5108	'		for (var i = 0; i < cg.length; i++) {\n'\
5109	'			cgid = cg[i].id.split("x")[0]\n'\
5110	'			if(idlist.indexOf(cgid) >= 0) {\n'\
5111	'				cg[i].style.display = "block";\n'\
5112	'			} else {\n'\
5113	'				cg[i].style.display = "none";\n'\
5114	'			}\n'\
5115	'		}\n'\
5116	'	}\n'\
5117	'	function callDetail(devid, devtitle) {\n'\
5118	'		if(!(devid in devstats) || devstats[devid].length < 1)\n'\
5119	'			return;\n'\
5120	'		var list = devstats[devid];\n'\
5121	'		var tmp = devtitle.split(" ");\n'\
5122	'		var name = tmp[0], phase = tmp[tmp.length-1];\n'\
5123	'		var dd = document.getElementById(phase);\n'\
5124	'		var total = parseFloat(tmp[1].slice(1));\n'\
5125	'		var mlist = [];\n'\
5126	'		var maxlen = 0;\n'\
5127	'		var info = []\n'\
5128	'		for(var i in list) {\n'\
5129	'			if(list[i][0] == "@") {\n'\
5130	'				info = list[i].split("|");\n'\
5131	'				continue;\n'\
5132	'			}\n'\
5133	'			var tmp = list[i].split("|");\n'\
5134	'			var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
5135	'			var p = (t*100.0/total).toFixed(2);\n'\
5136	'			mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
5137	'			if(f.length > maxlen)\n'\
5138	'				maxlen = f.length;\n'\
5139	'		}\n'\
5140	'		var pad = 5;\n'\
5141	'		if(mlist.length == 0) pad = 30;\n'\
5142	'		var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
5143	'		if(info.length > 2)\n'\
5144	'			html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
5145	'		if(info.length > 3)\n'\
5146	'			html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
5147	'		if(info.length > 4)\n'\
5148	'			html += ", return=<b>"+info[4]+"</b>";\n'\
5149	'		html += "</t3></div>";\n'\
5150	'		if(mlist.length > 0) {\n'\
5151	'			html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
5152	'			for(var i in mlist)\n'\
5153	'				html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
5154	'			html += "</tr><tr><th>Calls</th>";\n'\
5155	'			for(var i in mlist)\n'\
5156	'				html += "<td>"+mlist[i][1]+"</td>";\n'\
5157	'			html += "</tr><tr><th>Time(ms)</th>";\n'\
5158	'			for(var i in mlist)\n'\
5159	'				html += "<td>"+mlist[i][2]+"</td>";\n'\
5160	'			html += "</tr><tr><th>Percent</th>";\n'\
5161	'			for(var i in mlist)\n'\
5162	'				html += "<td>"+mlist[i][3]+"</td>";\n'\
5163	'			html += "</tr></table>";\n'\
5164	'		}\n'\
5165	'		dd.innerHTML = html;\n'\
5166	'		var height = (maxlen*5)+100;\n'\
5167	'		dd.style.height = height+"px";\n'\
5168	'		document.getElementById("devicedetail").style.height = height+"px";\n'\
5169	'	}\n'\
5170	'	function callSelect() {\n'\
5171	'		var cglist = document.getElementById("callgraphs");\n'\
5172	'		if(!cglist) return;\n'\
5173	'		var cg = cglist.getElementsByClassName("atop");\n'\
5174	'		for (var i = 0; i < cg.length; i++) {\n'\
5175	'			if(this.id == cg[i].id) {\n'\
5176	'				cg[i].style.display = "block";\n'\
5177	'			} else {\n'\
5178	'				cg[i].style.display = "none";\n'\
5179	'			}\n'\
5180	'		}\n'\
5181	'	}\n'\
5182	'	function devListWindow(e) {\n'\
5183	'		var win = window.open();\n'\
5184	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
5185	'			"<style type=\\"text/css\\">"+\n'\
5186	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
5187	'			"</style>"\n'\
5188	'		var dt = devtable[0];\n'\
5189	'		if(e.target.id != "devlist1")\n'\
5190	'			dt = devtable[1];\n'\
5191	'		win.document.write(html+dt);\n'\
5192	'	}\n'\
5193	'	function errWindow() {\n'\
5194	'		var range = this.id.split("_");\n'\
5195	'		var idx1 = parseInt(range[0]);\n'\
5196	'		var idx2 = parseInt(range[1]);\n'\
5197	'		var win = window.open();\n'\
5198	'		var log = document.getElementById("dmesglog");\n'\
5199	'		var title = "<title>dmesg log</title>";\n'\
5200	'		var text = log.innerHTML.split("\\n");\n'\
5201	'		var html = "";\n'\
5202	'		for(var i = 0; i < text.length; i++) {\n'\
5203	'			if(i == idx1) {\n'\
5204	'				html += "<e id=target>"+text[i]+"</e>\\n";\n'\
5205	'			} else if(i > idx1 && i <= idx2) {\n'\
5206	'				html += "<e>"+text[i]+"</e>\\n";\n'\
5207	'			} else {\n'\
5208	'				html += text[i]+"\\n";\n'\
5209	'			}\n'\
5210	'		}\n'\
5211	'		win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
5212	'		win.location.hash = "#target";\n'\
5213	'		win.document.close();\n'\
5214	'	}\n'\
5215	'	function logWindow(e) {\n'\
5216	'		var name = e.target.id.slice(4);\n'\
5217	'		var win = window.open();\n'\
5218	'		var log = document.getElementById(name+"log");\n'\
5219	'		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
5220	'		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
5221	'		win.document.close();\n'\
5222	'	}\n'\
5223	'	function onMouseDown(e) {\n'\
5224	'		dragval[0] = e.clientX;\n'\
5225	'		dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
5226	'		document.onmousemove = onMouseMove;\n'\
5227	'	}\n'\
5228	'	function onMouseMove(e) {\n'\
5229	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
5230	'		zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
5231	'	}\n'\
5232	'	function onMouseUp(e) {\n'\
5233	'		document.onmousemove = null;\n'\
5234	'	}\n'\
5235	'	function onKeyPress(e) {\n'\
5236	'		var c = e.charCode;\n'\
5237	'		if(c != 42 && c != 43 && c != 45) return;\n'\
5238	'		var click = document.createEvent("Events");\n'\
5239	'		click.initEvent("click", true, false);\n'\
5240	'		if(c == 43)  \n'\
5241	'			document.getElementById("zoomin").dispatchEvent(click);\n'\
5242	'		else if(c == 45)\n'\
5243	'			document.getElementById("zoomout").dispatchEvent(click);\n'\
5244	'		else if(c == 42)\n'\
5245	'			document.getElementById("zoomdef").dispatchEvent(click);\n'\
5246	'	}\n'\
5247	'	window.addEventListener("resize", function () {zoomTimeline();});\n'\
5248	'	window.addEventListener("load", function () {\n'\
5249	'		var dmesg = document.getElementById("dmesg");\n'\
5250	'		dmesg.style.width = "100%"\n'\
5251	'		dmesg.onmousedown = onMouseDown;\n'\
5252	'		document.onmouseup = onMouseUp;\n'\
5253	'		document.onkeypress = onKeyPress;\n'\
5254	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
5255	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
5256	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
5257	'		var list = document.getElementsByClassName("err");\n'\
5258	'		for (var i = 0; i < list.length; i++)\n'\
5259	'			list[i].onclick = errWindow;\n'\
5260	'		var list = document.getElementsByClassName("logbtn");\n'\
5261	'		for (var i = 0; i < list.length; i++)\n'\
5262	'			list[i].onclick = logWindow;\n'\
5263	'		list = document.getElementsByClassName("devlist");\n'\
5264	'		for (var i = 0; i < list.length; i++)\n'\
5265	'			list[i].onclick = devListWindow;\n'\
5266	'		var dev = dmesg.getElementsByClassName("thread");\n'\
5267	'		for (var i = 0; i < dev.length; i++) {\n'\
5268	'			dev[i].onclick = deviceDetail;\n'\
5269	'			dev[i].onmouseover = deviceHover;\n'\
5270	'			dev[i].onmouseout = deviceUnhover;\n'\
5271	'		}\n'\
5272	'		var dev = dmesg.getElementsByClassName("srccall");\n'\
5273	'		for (var i = 0; i < dev.length; i++)\n'\
5274	'			dev[i].onclick = callSelect;\n'\
5275	'		zoomTimeline();\n'\
5276	'	});\n'\
5277	'</script>\n'
5278	hf.write(script_code);
5279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5280# Function: executeSuspend
5281# Description:
5282#	 Execute system suspend through the sysfs interface, then copy the output
5283#	 dmesg and ftrace files to the test output directory.
5284def executeSuspend(quiet=False):
5285	sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor()
5286	if sv.wifi:
5287		wifi = sv.checkWifi()
5288		sv.dlog('wifi check, connected device is "%s"' % wifi)
5289	testdata = []
5290	# run these commands to prepare the system for suspend
5291	if sv.display:
5292		if not quiet:
5293			pprint('SET DISPLAY TO %s' % sv.display.upper())
5294		ret = sv.displayControl(sv.display)
5295		sv.dlog('xset display %s, ret = %d' % (sv.display, ret))
 
 
 
5296		time.sleep(1)
5297	if sv.sync:
5298		if not quiet:
5299			pprint('SYNCING FILESYSTEMS')
5300		sv.dlog('syncing filesystems')
5301		call('sync', shell=True)
5302	sv.dlog('read dmesg')
5303	sv.initdmesg()
5304	# start ftrace
5305	if(sv.usecallgraph or sv.usetraceevents):
5306		if not quiet:
5307			pprint('START TRACING')
5308		sv.dlog('start ftrace tracing')
5309		sv.fsetVal('1', 'tracing_on')
5310		if sv.useprocmon:
5311			sv.dlog('start the process monitor')
5312			pm.start()
5313	sv.dlog('run the cmdinfo list before')
5314	sv.cmdinfo(True)
5315	# execute however many s/r runs requested
5316	for count in range(1,sv.execcount+1):
5317		# x2delay in between test runs
5318		if(count > 1 and sv.x2delay > 0):
5319			sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker')
5320			time.sleep(sv.x2delay/1000.0)
5321			sv.fsetVal('WAIT END', 'trace_marker')
5322		# start message
5323		if sv.testcommand != '':
5324			pprint('COMMAND START')
5325		else:
5326			if(sv.rtcwake):
5327				pprint('SUSPEND START')
5328			else:
5329				pprint('SUSPEND START (press a key to resume)')
5330		# set rtcwake
5331		if(sv.rtcwake):
5332			if not quiet:
5333				pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime)
5334			sv.dlog('enable RTC wake alarm')
5335			sv.rtcWakeAlarmOn()
5336		# start of suspend trace marker
5337		if(sv.usecallgraph or sv.usetraceevents):
5338			sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
5339		# predelay delay
5340		if(count == 1 and sv.predelay > 0):
5341			sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
5342			time.sleep(sv.predelay/1000.0)
5343			sv.fsetVal('WAIT END', 'trace_marker')
5344		# initiate suspend or command
5345		sv.dlog('system executing a suspend')
5346		tdata = {'error': ''}
5347		if sv.testcommand != '':
5348			res = call(sv.testcommand+' 2>&1', shell=True);
5349			if res != 0:
5350				tdata['error'] = 'cmd returned %d' % res
5351		else:
5352			mode = sv.suspendmode
5353			if sv.memmode and os.path.exists(sv.mempowerfile):
5354				mode = 'mem'
5355				sv.testVal(sv.mempowerfile, 'radio', sv.memmode)
5356			if sv.diskmode and os.path.exists(sv.diskpowerfile):
5357				mode = 'disk'
5358				sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode)
5359			if sv.acpidebug:
5360				sv.testVal(sv.acpipath, 'acpi', '0xe')
5361			if mode == 'freeze' and sv.haveTurbostat():
5362				# execution will pause here
5363				turbo = sv.turbostat()
5364				if turbo:
5365					tdata['turbo'] = turbo
5366			else:
5367				pf = open(sv.powerfile, 'w')
5368				pf.write(mode)
5369				# execution will pause here
5370				try:
5371					pf.close()
5372				except Exception as e:
5373					tdata['error'] = str(e)
5374		sv.dlog('system returned from resume')
5375		# reset everything
5376		sv.testVal('restoreall')
5377		if(sv.rtcwake):
5378			sv.dlog('disable RTC wake alarm')
5379			sv.rtcWakeAlarmOff()
5380		# postdelay delay
5381		if(count == sv.execcount and sv.postdelay > 0):
5382			sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker')
5383			time.sleep(sv.postdelay/1000.0)
5384			sv.fsetVal('WAIT END', 'trace_marker')
5385		# return from suspend
5386		pprint('RESUME COMPLETE')
5387		if(sv.usecallgraph or sv.usetraceevents):
5388			sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5389		if sv.wifi and wifi:
5390			tdata['wifi'] = sv.pollWifi(wifi)
5391			sv.dlog('wifi check, %s' % tdata['wifi'])
5392		if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
5393			sv.dlog('read the ACPI FPDT')
5394			tdata['fw'] = getFPDT(False)
5395		testdata.append(tdata)
5396	sv.dlog('run the cmdinfo list after')
5397	cmdafter = sv.cmdinfo(False)
5398	# stop ftrace
5399	if(sv.usecallgraph or sv.usetraceevents):
5400		if sv.useprocmon:
5401			sv.dlog('stop the process monitor')
5402			pm.stop()
5403		sv.fsetVal('0', 'tracing_on')
5404	# grab a copy of the dmesg output
5405	if not quiet:
5406		pprint('CAPTURING DMESG')
5407	sysvals.dlog('EXECUTION TRACE END')
5408	sv.getdmesg(testdata)
5409	# grab a copy of the ftrace output
5410	if(sv.usecallgraph or sv.usetraceevents):
5411		if not quiet:
5412			pprint('CAPTURING TRACE')
5413		op = sv.writeDatafileHeader(sv.ftracefile, testdata)
5414		fp = open(tp+'trace', 'r')
5415		for line in fp:
5416			op.write(line)
5417		op.close()
5418		sv.fsetVal('', 'trace')
5419		sv.platforminfo(cmdafter)
 
 
 
5420
5421def readFile(file):
5422	if os.path.islink(file):
5423		return os.readlink(file).split('/')[-1]
5424	else:
5425		return sysvals.getVal(file).strip()
5426
5427# Function: ms2nice
5428# Description:
5429#	 Print out a very concise time string in minutes and seconds
5430# Output:
5431#	 The time string, e.g. "1901m16s"
5432def ms2nice(val):
5433	val = int(val)
5434	h = val // 3600000
5435	m = (val // 60000) % 60
5436	s = (val // 1000) % 60
5437	if h > 0:
5438		return '%d:%02d:%02d' % (h, m, s)
5439	if m > 0:
5440		return '%02d:%02d' % (m, s)
5441	return '%ds' % s
5442
5443def yesno(val):
5444	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5445		'active':'A', 'suspended':'S', 'suspending':'S'}
5446	if val not in list:
5447		return ' '
5448	return list[val]
5449
5450# Function: deviceInfo
5451# Description:
5452#	 Detect all the USB hosts and devices currently connected and add
5453#	 a list of USB device names to sysvals for better timeline readability
5454def deviceInfo(output=''):
5455	if not output:
5456		pprint('LEGEND\n'\
5457		'---------------------------------------------------------------------------------------------\n'\
5458		'  A = async/sync PM queue (A/S)               C = runtime active children\n'\
5459		'  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)\n'\
5460		'  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)\n'\
5461		'  U = runtime usage count\n'\
5462		'---------------------------------------------------------------------------------------------\n'\
5463		'DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND\n'\
5464		'---------------------------------------------------------------------------------------------')
5465
5466	res = []
5467	tgtval = 'runtime_status'
5468	lines = dict()
5469	for dirname, dirnames, filenames in os.walk('/sys/devices'):
5470		if(not re.match('.*/power', dirname) or
5471			'control' not in filenames or
5472			tgtval not in filenames):
5473			continue
5474		name = ''
5475		dirname = dirname[:-6]
5476		device = dirname.split('/')[-1]
5477		power = dict()
5478		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5479		# only list devices which support runtime suspend
5480		if power[tgtval] not in ['active', 'suspended', 'suspending']:
5481			continue
5482		for i in ['product', 'driver', 'subsystem']:
5483			file = '%s/%s' % (dirname, i)
5484			if os.path.exists(file):
5485				name = readFile(file)
5486				break
5487		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5488			'runtime_active_kids', 'runtime_active_time',
5489			'runtime_suspended_time']:
5490			if i in filenames:
5491				power[i] = readFile('%s/power/%s' % (dirname, i))
5492		if output:
5493			if power['control'] == output:
5494				res.append('%s/power/control' % dirname)
5495			continue
5496		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5497			(device[:26], name[:26],
5498			yesno(power['async']), \
5499			yesno(power['control']), \
5500			yesno(power['runtime_status']), \
5501			power['runtime_usage'], \
5502			power['runtime_active_kids'], \
5503			ms2nice(power['runtime_active_time']), \
5504			ms2nice(power['runtime_suspended_time']))
5505	for i in sorted(lines):
5506		print(lines[i])
5507	return res
5508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5509# Function: getModes
5510# Description:
5511#	 Determine the supported power modes on this system
5512# Output:
5513#	 A string list of the available modes
5514def getModes():
5515	modes = []
5516	if(os.path.exists(sysvals.powerfile)):
5517		fp = open(sysvals.powerfile, 'r')
5518		modes = fp.read().split()
5519		fp.close()
5520	if(os.path.exists(sysvals.mempowerfile)):
5521		deep = False
5522		fp = open(sysvals.mempowerfile, 'r')
5523		for m in fp.read().split():
5524			memmode = m.strip('[]')
5525			if memmode == 'deep':
5526				deep = True
5527			else:
5528				modes.append('mem-%s' % memmode)
5529		fp.close()
5530		if 'mem' in modes and not deep:
5531			modes.remove('mem')
5532	if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5533		fp = open(sysvals.diskpowerfile, 'r')
5534		for m in fp.read().split():
5535			modes.append('disk-%s' % m.strip('[]'))
5536		fp.close()
5537	return modes
5538
5539# Function: dmidecode
5540# Description:
5541#	 Read the bios tables and pull out system info
5542# Arguments:
5543#	 mempath: /dev/mem or custom mem path
5544#	 fatal: True to exit on error, False to return empty dict
5545# Output:
5546#	 A dict object with all available key/values
5547def dmidecode(mempath, fatal=False):
5548	out = dict()
5549
5550	# the list of values to retrieve, with hardcoded (type, idx)
5551	info = {
5552		'bios-vendor': (0, 4),
5553		'bios-version': (0, 5),
5554		'bios-release-date': (0, 8),
5555		'system-manufacturer': (1, 4),
5556		'system-product-name': (1, 5),
5557		'system-version': (1, 6),
5558		'system-serial-number': (1, 7),
5559		'baseboard-manufacturer': (2, 4),
5560		'baseboard-product-name': (2, 5),
5561		'baseboard-version': (2, 6),
5562		'baseboard-serial-number': (2, 7),
5563		'chassis-manufacturer': (3, 4),
5564		'chassis-type': (3, 5),
5565		'chassis-version': (3, 6),
5566		'chassis-serial-number': (3, 7),
5567		'processor-manufacturer': (4, 7),
5568		'processor-version': (4, 16),
5569	}
5570	if(not os.path.exists(mempath)):
5571		if(fatal):
5572			doError('file does not exist: %s' % mempath)
5573		return out
5574	if(not os.access(mempath, os.R_OK)):
5575		if(fatal):
5576			doError('file is not readable: %s' % mempath)
5577		return out
5578
5579	# by default use legacy scan, but try to use EFI first
5580	memaddr = 0xf0000
5581	memsize = 0x10000
5582	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5583		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5584			continue
5585		fp = open(ep, 'r')
5586		buf = fp.read()
5587		fp.close()
5588		i = buf.find('SMBIOS=')
5589		if i >= 0:
5590			try:
5591				memaddr = int(buf[i+7:], 16)
5592				memsize = 0x20
5593			except:
5594				continue
5595
5596	# read in the memory for scanning
 
5597	try:
5598		fp = open(mempath, 'rb')
5599		fp.seek(memaddr)
5600		buf = fp.read(memsize)
5601	except:
5602		if(fatal):
5603			doError('DMI table is unreachable, sorry')
5604		else:
5605			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5606			return out
5607	fp.close()
5608
5609	# search for either an SM table or DMI table
5610	i = base = length = num = 0
5611	while(i < memsize):
5612		if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5613			length = struct.unpack('H', buf[i+22:i+24])[0]
5614			base, num = struct.unpack('IH', buf[i+24:i+30])
5615			break
5616		elif buf[i:i+5] == b'_DMI_':
5617			length = struct.unpack('H', buf[i+6:i+8])[0]
5618			base, num = struct.unpack('IH', buf[i+8:i+14])
5619			break
5620		i += 16
5621	if base == 0 and length == 0 and num == 0:
5622		if(fatal):
5623			doError('Neither SMBIOS nor DMI were found')
5624		else:
5625			return out
5626
5627	# read in the SM or DMI table
 
5628	try:
5629		fp = open(mempath, 'rb')
5630		fp.seek(base)
5631		buf = fp.read(length)
5632	except:
5633		if(fatal):
5634			doError('DMI table is unreachable, sorry')
5635		else:
5636			pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5637			return out
5638	fp.close()
5639
5640	# scan the table for the values we want
5641	count = i = 0
5642	while(count < num and i <= len(buf) - 4):
5643		type, size, handle = struct.unpack('BBH', buf[i:i+4])
5644		n = i + size
5645		while n < len(buf) - 1:
5646			if 0 == struct.unpack('H', buf[n:n+2])[0]:
5647				break
5648			n += 1
5649		data = buf[i+size:n+2].split(b'\0')
5650		for name in info:
5651			itype, idxadr = info[name]
5652			if itype == type:
5653				idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5654				if idx > 0 and idx < len(data) - 1:
5655					s = data[idx-1].decode('utf-8')
5656					if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5657						out[name] = s
5658		i = n + 2
5659		count += 1
5660	return out
5661
5662# Function: getFPDT
5663# Description:
5664#	 Read the acpi bios tables and pull out FPDT, the firmware data
5665# Arguments:
5666#	 output: True to output the info to stdout, False otherwise
5667def getFPDT(output):
5668	rectype = {}
5669	rectype[0] = 'Firmware Basic Boot Performance Record'
5670	rectype[1] = 'S3 Performance Table Record'
5671	prectype = {}
5672	prectype[0] = 'Basic S3 Resume Performance Record'
5673	prectype[1] = 'Basic S3 Suspend Performance Record'
5674
5675	sysvals.rootCheck(True)
5676	if(not os.path.exists(sysvals.fpdtpath)):
5677		if(output):
5678			doError('file does not exist: %s' % sysvals.fpdtpath)
5679		return False
5680	if(not os.access(sysvals.fpdtpath, os.R_OK)):
5681		if(output):
5682			doError('file is not readable: %s' % sysvals.fpdtpath)
5683		return False
5684	if(not os.path.exists(sysvals.mempath)):
5685		if(output):
5686			doError('file does not exist: %s' % sysvals.mempath)
5687		return False
5688	if(not os.access(sysvals.mempath, os.R_OK)):
5689		if(output):
5690			doError('file is not readable: %s' % sysvals.mempath)
5691		return False
5692
5693	fp = open(sysvals.fpdtpath, 'rb')
5694	buf = fp.read()
5695	fp.close()
5696
5697	if(len(buf) < 36):
5698		if(output):
5699			doError('Invalid FPDT table data, should '+\
5700				'be at least 36 bytes')
5701		return False
5702
5703	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5704	if(output):
5705		pprint('\n'\
5706		'Firmware Performance Data Table (%s)\n'\
5707		'                  Signature : %s\n'\
5708		'               Table Length : %u\n'\
5709		'                   Revision : %u\n'\
5710		'                   Checksum : 0x%x\n'\
5711		'                     OEM ID : %s\n'\
5712		'               OEM Table ID : %s\n'\
5713		'               OEM Revision : %u\n'\
5714		'                 Creator ID : %s\n'\
5715		'           Creator Revision : 0x%x\n'\
5716		'' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5717			table[3], ascii(table[4]), ascii(table[5]), table[6],
5718			ascii(table[7]), table[8]))
5719
5720	if(table[0] != b'FPDT'):
5721		if(output):
5722			doError('Invalid FPDT table')
5723		return False
5724	if(len(buf) <= 36):
5725		return False
5726	i = 0
5727	fwData = [0, 0]
5728	records = buf[36:]
5729	try:
5730		fp = open(sysvals.mempath, 'rb')
5731	except:
5732		pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5733		return False
5734	while(i < len(records)):
5735		header = struct.unpack('HBB', records[i:i+4])
5736		if(header[0] not in rectype):
5737			i += header[1]
5738			continue
5739		if(header[1] != 16):
5740			i += header[1]
5741			continue
5742		addr = struct.unpack('Q', records[i+8:i+16])[0]
5743		try:
5744			fp.seek(addr)
5745			first = fp.read(8)
5746		except:
5747			if(output):
5748				pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5749			return [0, 0]
5750		rechead = struct.unpack('4sI', first)
5751		recdata = fp.read(rechead[1]-8)
5752		if(rechead[0] == b'FBPT'):
5753			record = struct.unpack('HBBIQQQQQ', recdata[:48])
5754			if(output):
5755				pprint('%s (%s)\n'\
5756				'                  Reset END : %u ns\n'\
5757				'  OS Loader LoadImage Start : %u ns\n'\
5758				' OS Loader StartImage Start : %u ns\n'\
5759				'     ExitBootServices Entry : %u ns\n'\
5760				'      ExitBootServices Exit : %u ns'\
5761				'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5762					record[6], record[7], record[8]))
5763		elif(rechead[0] == b'S3PT'):
5764			if(output):
5765				pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5766			j = 0
5767			while(j < len(recdata)):
5768				prechead = struct.unpack('HBB', recdata[j:j+4])
5769				if(prechead[0] not in prectype):
5770					continue
5771				if(prechead[0] == 0):
5772					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5773					fwData[1] = record[2]
5774					if(output):
5775						pprint('    %s\n'\
5776						'               Resume Count : %u\n'\
5777						'                 FullResume : %u ns\n'\
5778						'              AverageResume : %u ns'\
5779						'' % (prectype[prechead[0]], record[1],
5780								record[2], record[3]))
 
5781				elif(prechead[0] == 1):
5782					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5783					fwData[0] = record[1] - record[0]
5784					if(output):
5785						pprint('    %s\n'\
5786						'               SuspendStart : %u ns\n'\
5787						'                 SuspendEnd : %u ns\n'\
5788						'                SuspendTime : %u ns'\
5789						'' % (prectype[prechead[0]], record[0],
5790								record[1], fwData[0]))
5791
5792				j += prechead[1]
5793		if(output):
5794			pprint('')
5795		i += header[1]
5796	fp.close()
5797	return fwData
5798
5799# Function: statusCheck
5800# Description:
5801#	 Verify that the requested command and options will work, and
5802#	 print the results to the terminal
5803# Output:
5804#	 True if the test will work, False if not
5805def statusCheck(probecheck=False):
5806	status = ''
5807
5808	pprint('Checking this system (%s)...' % platform.node())
5809
5810	# check we have root access
5811	res = sysvals.colorText('NO (No features of this tool will work!)')
5812	if(sysvals.rootCheck(False)):
5813		res = 'YES'
5814	pprint('    have root access: %s' % res)
5815	if(res != 'YES'):
5816		pprint('    Try running this script with sudo')
5817		return 'missing root access'
5818
5819	# check sysfs is mounted
5820	res = sysvals.colorText('NO (No features of this tool will work!)')
5821	if(os.path.exists(sysvals.powerfile)):
5822		res = 'YES'
5823	pprint('    is sysfs mounted: %s' % res)
5824	if(res != 'YES'):
5825		return 'sysfs is missing'
5826
5827	# check target mode is a valid mode
5828	if sysvals.suspendmode != 'command':
5829		res = sysvals.colorText('NO')
5830		modes = getModes()
5831		if(sysvals.suspendmode in modes):
5832			res = 'YES'
5833		else:
5834			status = '%s mode is not supported' % sysvals.suspendmode
5835		pprint('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5836		if(res == 'NO'):
5837			pprint('      valid power modes are: %s' % modes)
5838			pprint('      please choose one with -m')
5839
5840	# check if ftrace is available
5841	res = sysvals.colorText('NO')
5842	ftgood = sysvals.verifyFtrace()
5843	if(ftgood):
5844		res = 'YES'
5845	elif(sysvals.usecallgraph):
5846		status = 'ftrace is not properly supported'
5847	pprint('    is ftrace supported: %s' % res)
5848
5849	# check if kprobes are available
5850	if sysvals.usekprobes:
5851		res = sysvals.colorText('NO')
5852		sysvals.usekprobes = sysvals.verifyKprobes()
5853		if(sysvals.usekprobes):
5854			res = 'YES'
5855		else:
5856			sysvals.usedevsrc = False
5857		pprint('    are kprobes supported: %s' % res)
5858
5859	# what data source are we using
5860	res = 'DMESG'
5861	if(ftgood):
5862		sysvals.usetraceevents = True
5863		for e in sysvals.traceevents:
5864			if not os.path.exists(sysvals.epath+e):
5865				sysvals.usetraceevents = False
5866		if(sysvals.usetraceevents):
5867			res = 'FTRACE (all trace events found)'
5868	pprint('    timeline data source: %s' % res)
5869
5870	# check if rtcwake
5871	res = sysvals.colorText('NO')
5872	if(sysvals.rtcpath != ''):
5873		res = 'YES'
5874	elif(sysvals.rtcwake):
5875		status = 'rtcwake is not properly supported'
5876	pprint('    is rtcwake supported: %s' % res)
5877
5878	# check info commands
5879	pprint('    optional commands this tool may use for info:')
5880	no = sysvals.colorText('MISSING')
5881	yes = sysvals.colorText('FOUND', 32)
5882	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb']:
5883		if c == 'turbostat':
5884			res = yes if sysvals.haveTurbostat() else no
5885		else:
5886			res = yes if sysvals.getExec(c) else no
5887		pprint('        %s: %s' % (c, res))
5888
5889	if not probecheck:
5890		return status
5891
5892	# verify kprobes
5893	if sysvals.usekprobes:
5894		for name in sysvals.tracefuncs:
5895			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5896		if sysvals.usedevsrc:
5897			for name in sysvals.dev_tracefuncs:
5898				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5899		sysvals.addKprobes(True)
5900
5901	return status
5902
5903# Function: doError
5904# Description:
5905#	 generic error function for catastrphic failures
5906# Arguments:
5907#	 msg: the error message to print
5908#	 help: True if printHelp should be called after, False otherwise
5909def doError(msg, help=False):
5910	if(help == True):
5911		printHelp()
5912	pprint('ERROR: %s\n' % msg)
5913	sysvals.outputResult({'error':msg})
5914	sys.exit(1)
5915
5916# Function: getArgInt
5917# Description:
5918#	 pull out an integer argument from the command line with checks
5919def getArgInt(name, args, min, max, main=True):
5920	if main:
5921		try:
5922			arg = next(args)
5923		except:
5924			doError(name+': no argument supplied', True)
5925	else:
5926		arg = args
5927	try:
5928		val = int(arg)
5929	except:
5930		doError(name+': non-integer value given', True)
5931	if(val < min or val > max):
5932		doError(name+': value should be between %d and %d' % (min, max), True)
5933	return val
5934
5935# Function: getArgFloat
5936# Description:
5937#	 pull out a float argument from the command line with checks
5938def getArgFloat(name, args, min, max, main=True):
5939	if main:
5940		try:
5941			arg = next(args)
5942		except:
5943			doError(name+': no argument supplied', True)
5944	else:
5945		arg = args
5946	try:
5947		val = float(arg)
5948	except:
5949		doError(name+': non-numerical value given', True)
5950	if(val < min or val > max):
5951		doError(name+': value should be between %f and %f' % (min, max), True)
5952	return val
5953
5954def processData(live=False, quiet=False):
5955	if not quiet:
5956		pprint('PROCESSING: %s' % sysvals.htmlfile)
5957	sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
5958		(sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
5959	error = ''
5960	if(sysvals.usetraceevents):
5961		testruns, error = parseTraceLog(live)
5962		if sysvals.dmesgfile:
5963			for data in testruns:
5964				data.extractErrorInfo()
5965	else:
5966		testruns = loadKernelLog()
5967		for data in testruns:
5968			parseKernelLog(data)
5969		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5970			appendIncompleteTraceLog(testruns)
5971	if not sysvals.stamp:
5972		pprint('ERROR: data does not include the expected stamp')
5973		return (testruns, {'error': 'timeline generation failed'})
5974	shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
5975			'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
5976	sysvals.vprint('System Info:')
5977	for key in sorted(sysvals.stamp):
5978		if key in shown:
5979			sysvals.vprint('    %-8s : %s' % (key.upper(), sysvals.stamp[key]))
5980	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
5981	for data in testruns:
5982		if data.turbostat:
5983			idx, s = 0, 'Turbostat:\n    '
5984			for val in data.turbostat.split('|'):
5985				idx += len(val) + 1
5986				if idx >= 80:
5987					idx = 0
5988					s += '\n    '
5989				s += val + ' '
5990			sysvals.vprint(s)
5991		data.printDetails()
5992	if len(sysvals.platinfo) > 0:
5993		sysvals.vprint('\nPlatform Info:')
5994		for info in sysvals.platinfo:
5995			sysvals.vprint('[%s - %s]' % (info[0], info[1]))
5996			sysvals.vprint(info[2])
5997		sysvals.vprint('')
5998	if sysvals.cgdump:
5999		for data in testruns:
6000			data.debugPrint()
6001		sys.exit(0)
6002	if len(testruns) < 1:
6003		pprint('ERROR: Not enough test data to build a timeline')
6004		return (testruns, {'error': 'timeline generation failed'})
6005	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6006	createHTML(testruns, error)
6007	if not quiet:
6008		pprint('DONE:       %s' % sysvals.htmlfile)
6009	data = testruns[0]
6010	stamp = data.stamp
6011	stamp['suspend'], stamp['resume'] = data.getTimeValues()
6012	if data.fwValid:
6013		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6014	if error:
6015		stamp['error'] = error
6016	return (testruns, stamp)
6017
6018# Function: rerunTest
6019# Description:
6020#	 generate an output from an existing set of ftrace/dmesg logs
6021def rerunTest(htmlfile=''):
6022	if sysvals.ftracefile:
6023		doesTraceLogHaveTraceEvents()
6024	if not sysvals.dmesgfile and not sysvals.usetraceevents:
6025		doError('recreating this html output requires a dmesg file')
6026	if htmlfile:
6027		sysvals.htmlfile = htmlfile
6028	else:
6029		sysvals.setOutputFile()
6030	if os.path.exists(sysvals.htmlfile):
6031		if not os.path.isfile(sysvals.htmlfile):
6032			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6033		elif not os.access(sysvals.htmlfile, os.W_OK):
6034			doError('missing permission to write to %s' % sysvals.htmlfile)
6035	testruns, stamp = processData()
6036	sysvals.resetlog()
6037	return stamp
6038
6039# Function: runTest
6040# Description:
6041#	 execute a suspend/resume, gather the logs, and generate the output
6042def runTest(n=0, quiet=False):
6043	# prepare for the test
 
6044	sysvals.initTestOutput('suspend')
6045	op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6046	op.write('# EXECUTION TRACE START\n')
6047	op.close()
6048	if n <= 1:
6049		if sysvals.rs != 0:
6050			sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6051			sysvals.setRuntimeSuspend(True)
6052		if sysvals.display:
6053			ret = sysvals.displayControl('init')
6054			sysvals.dlog('xset display init, ret = %d' % ret)
6055	sysvals.dlog('initialize ftrace')
6056	sysvals.initFtrace(quiet)
6057
6058	# execute the test
6059	executeSuspend(quiet)
6060	sysvals.cleanupFtrace()
6061	if sysvals.skiphtml:
6062		sysvals.outputResult({}, n)
6063		sysvals.sudoUserchown(sysvals.testdir)
6064		return
6065	testruns, stamp = processData(True, quiet)
6066	for data in testruns:
6067		del data
6068	sysvals.sudoUserchown(sysvals.testdir)
6069	sysvals.outputResult(stamp, n)
6070	if 'error' in stamp:
6071		return 2
6072	return 0
6073
6074def find_in_html(html, start, end, firstonly=True):
6075	cnt, out, list = len(html), [], []
6076	if firstonly:
6077		m = re.search(start, html)
6078		if m:
6079			list.append(m)
6080	else:
6081		list = re.finditer(start, html)
6082	for match in list:
6083		s = match.end()
6084		e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6085		m = re.search(end, html[s:e])
6086		if not m:
6087			break
6088		e = s + m.start()
6089		str = html[s:e]
6090		if end == 'ms':
6091			num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6092			str = num.group() if num else 'NaN'
6093		if firstonly:
6094			return str
6095		out.append(str)
6096	if firstonly:
6097		return ''
6098	return out
6099
6100def data_from_html(file, outpath, issues, fulldetail=False):
6101	html = open(file, 'r').read()
6102	sysvals.htmlfile = os.path.relpath(file, outpath)
6103	# extract general info
6104	suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6105	resume = find_in_html(html, 'Kernel Resume', 'ms')
6106	sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6107	line = find_in_html(html, '<div class="stamp">', '</div>')
6108	stmp = line.split()
6109	if not suspend or not resume or len(stmp) != 8:
6110		return False
6111	try:
6112		dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6113	except:
6114		return False
6115	sysvals.hostname = stmp[0]
6116	tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6117	error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6118	if error:
6119		m = re.match('[a-z0-9]* failed in (?P<p>\S*).*', error)
6120		if m:
6121			result = 'fail in %s' % m.group('p')
6122		else:
6123			result = 'fail'
6124	else:
6125		result = 'pass'
6126	# extract error info
6127	tp, ilist = False, []
6128	extra = dict()
6129	log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6130		'</div>').strip()
6131	if log:
6132		d = Data(0)
6133		d.end = 999999999
6134		d.dmesgtext = log.split('\n')
6135		tp = d.extractErrorInfo()
6136		for msg in tp.msglist:
6137			sysvals.errorSummary(issues, msg)
6138		if stmp[2] == 'freeze':
6139			extra = d.turbostatInfo()
6140		elist = dict()
6141		for dir in d.errorinfo:
6142			for err in d.errorinfo[dir]:
6143				if err[0] not in elist:
6144					elist[err[0]] = 0
6145				elist[err[0]] += 1
6146		for i in elist:
6147			ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6148	wifi = find_in_html(html, 'Wifi Resume: ', '</td>')
6149	if wifi:
6150		extra['wifi'] = wifi
6151	low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6152	for lowstr in ['waking', '+']:
6153		if not low:
6154			break
6155		if lowstr not in low:
6156			continue
6157		if lowstr == '+':
6158			issue = 'S2LOOPx%d' % len(low.split('+'))
6159		else:
6160			m = re.match('.*waking *(?P<n>[0-9]*) *times.*', low)
6161			issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6162		match = [i for i in issues if i['match'] == issue]
6163		if len(match) > 0:
6164			match[0]['count'] += 1
6165			if sysvals.hostname not in match[0]['urls']:
6166				match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6167			elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6168				match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6169		else:
6170			issues.append({
6171				'match': issue, 'count': 1, 'line': issue,
6172				'urls': {sysvals.hostname: [sysvals.htmlfile]},
6173			})
6174		ilist.append(issue)
6175	# extract device info
6176	devices = dict()
6177	for line in html.split('\n'):
6178		m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6179		if not m or 'thread kth' in line or 'thread sec' in line:
6180			continue
6181		m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6182		if not m:
6183			continue
6184		name, time, phase = m.group('n'), m.group('t'), m.group('p')
6185		if ' async' in name or ' sync' in name:
6186			name = ' '.join(name.split(' ')[:-1])
6187		if phase.startswith('suspend'):
6188			d = 'suspend'
6189		elif phase.startswith('resume'):
6190			d = 'resume'
6191		else:
6192			continue
6193		if d not in devices:
6194			devices[d] = dict()
6195		if name not in devices[d]:
6196			devices[d][name] = 0.0
6197		devices[d][name] += float(time)
6198	# create worst device info
6199	worst = dict()
6200	for d in ['suspend', 'resume']:
6201		worst[d] = {'name':'', 'time': 0.0}
6202		dev = devices[d] if d in devices else 0
6203		if dev and len(dev.keys()) > 0:
6204			n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6205			worst[d]['name'], worst[d]['time'] = n, dev[n]
6206	data = {
6207		'mode': stmp[2],
6208		'host': stmp[0],
6209		'kernel': stmp[1],
6210		'sysinfo': sysinfo,
6211		'time': tstr,
6212		'result': result,
6213		'issues': ' '.join(ilist),
6214		'suspend': suspend,
6215		'resume': resume,
6216		'devlist': devices,
6217		'sus_worst': worst['suspend']['name'],
6218		'sus_worsttime': worst['suspend']['time'],
6219		'res_worst': worst['resume']['name'],
6220		'res_worsttime': worst['resume']['time'],
6221		'url': sysvals.htmlfile,
6222	}
6223	for key in extra:
6224		data[key] = extra[key]
6225	if fulldetail:
6226		data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6227	if tp:
6228		for arg in ['-multi ', '-info ']:
6229			if arg in tp.cmdline:
6230				data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6231				break
6232	return data
6233
6234def genHtml(subdir, force=False):
6235	for dirname, dirnames, filenames in os.walk(subdir):
6236		sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6237		for filename in filenames:
6238			file = os.path.join(dirname, filename)
6239			if sysvals.usable(file):
6240				if(re.match('.*_dmesg.txt', filename)):
6241					sysvals.dmesgfile = file
6242				elif(re.match('.*_ftrace.txt', filename)):
6243					sysvals.ftracefile = file
6244		sysvals.setOutputFile()
6245		if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6246			(force or not sysvals.usable(sysvals.htmlfile)):
6247			pprint('FTRACE: %s' % sysvals.ftracefile)
6248			if sysvals.dmesgfile:
6249				pprint('DMESG : %s' % sysvals.dmesgfile)
6250			rerunTest()
6251
6252# Function: runSummary
6253# Description:
6254#	 create a summary of tests in a sub-directory
6255def runSummary(subdir, local=True, genhtml=False):
6256	inpath = os.path.abspath(subdir)
6257	outpath = os.path.abspath('.') if local else inpath
6258	pprint('Generating a summary of folder:\n   %s' % inpath)
6259	if genhtml:
6260		genHtml(subdir)
6261	target, issues, testruns = '', [], []
6262	desc = {'host':[],'mode':[],'kernel':[]}
6263	for dirname, dirnames, filenames in os.walk(subdir):
6264		for filename in filenames:
6265			if(not re.match('.*.html', filename)):
6266				continue
6267			data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6268			if(not data):
 
 
 
 
 
 
 
6269				continue
6270			if 'target' in data:
6271				target = data['target']
 
 
 
 
 
 
 
 
 
 
 
6272			testruns.append(data)
6273			for key in desc:
6274				if data[key] not in desc[key]:
6275					desc[key].append(data[key])
6276	pprint('Summary files:')
6277	if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6278		title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6279		if target:
6280			title += ' %s' % target
6281	else:
6282		title = inpath
6283	createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6284	pprint('   summary.html         - tabular list of test data found')
6285	createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6286	pprint('   summary-devices.html - kernel device list sorted by total execution time')
6287	createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6288	pprint('   summary-issues.html  - kernel issues found sorted by frequency')
6289
6290# Function: checkArgBool
6291# Description:
6292#	 check if a boolean string value is true or false
6293def checkArgBool(name, value):
6294	if value in switchvalues:
6295		if value in switchoff:
6296			return False
6297		return True
6298	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6299	return False
6300
6301# Function: configFromFile
6302# Description:
6303#	 Configure the script via the info in a config file
6304def configFromFile(file):
6305	Config = configparser.ConfigParser()
6306
6307	Config.read(file)
6308	sections = Config.sections()
6309	overridekprobes = False
6310	overridedevkprobes = False
6311	if 'Settings' in sections:
6312		for opt in Config.options('Settings'):
6313			value = Config.get('Settings', opt).lower()
6314			option = opt.lower()
6315			if(option == 'verbose'):
6316				sysvals.verbose = checkArgBool(option, value)
6317			elif(option == 'addlogs'):
6318				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6319			elif(option == 'dev'):
6320				sysvals.usedevsrc = checkArgBool(option, value)
6321			elif(option == 'proc'):
6322				sysvals.useprocmon = checkArgBool(option, value)
6323			elif(option == 'x2'):
6324				if checkArgBool(option, value):
6325					sysvals.execcount = 2
6326			elif(option == 'callgraph'):
6327				sysvals.usecallgraph = checkArgBool(option, value)
6328			elif(option == 'override-timeline-functions'):
6329				overridekprobes = checkArgBool(option, value)
6330			elif(option == 'override-dev-timeline-functions'):
6331				overridedevkprobes = checkArgBool(option, value)
6332			elif(option == 'skiphtml'):
6333				sysvals.skiphtml = checkArgBool(option, value)
6334			elif(option == 'sync'):
6335				sysvals.sync = checkArgBool(option, value)
6336			elif(option == 'rs' or option == 'runtimesuspend'):
6337				if value in switchvalues:
6338					if value in switchoff:
6339						sysvals.rs = -1
6340					else:
6341						sysvals.rs = 1
6342				else:
6343					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6344			elif(option == 'display'):
6345				disopt = ['on', 'off', 'standby', 'suspend']
6346				if value not in disopt:
6347					doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6348				sysvals.display = value
 
 
 
6349			elif(option == 'gzip'):
6350				sysvals.gzip = checkArgBool(option, value)
6351			elif(option == 'cgfilter'):
6352				sysvals.setCallgraphFilter(value)
6353			elif(option == 'cgskip'):
6354				if value in switchoff:
6355					sysvals.cgskip = ''
6356				else:
6357					sysvals.cgskip = sysvals.configFile(val)
6358					if(not sysvals.cgskip):
6359						doError('%s does not exist' % sysvals.cgskip)
6360			elif(option == 'cgtest'):
6361				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6362			elif(option == 'cgphase'):
6363				d = Data(0)
6364				if value not in d.phasedef:
6365					doError('invalid phase --> (%s: %s), valid phases are %s'\
6366						% (option, value, d.phasedef.keys()), True)
6367				sysvals.cgphase = value
6368			elif(option == 'fadd'):
6369				file = sysvals.configFile(value)
6370				if(not file):
6371					doError('%s does not exist' % value)
6372				sysvals.addFtraceFilterFunctions(file)
6373			elif(option == 'result'):
6374				sysvals.result = value
6375			elif(option == 'multi'):
6376				nums = value.split()
6377				if len(nums) != 2:
6378					doError('multi requires 2 integers (exec_count and delay)', True)
6379				sysvals.multiinit(nums[0], nums[1])
 
 
6380			elif(option == 'devicefilter'):
6381				sysvals.setDeviceFilter(value)
6382			elif(option == 'expandcg'):
6383				sysvals.cgexp = checkArgBool(option, value)
6384			elif(option == 'srgap'):
6385				if checkArgBool(option, value):
6386					sysvals.srgap = 5
6387			elif(option == 'mode'):
6388				sysvals.suspendmode = value
6389			elif(option == 'command' or option == 'cmd'):
6390				sysvals.testcommand = value
6391			elif(option == 'x2delay'):
6392				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6393			elif(option == 'predelay'):
6394				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6395			elif(option == 'postdelay'):
6396				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6397			elif(option == 'maxdepth'):
6398				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6399			elif(option == 'rtcwake'):
6400				if value in switchoff:
6401					sysvals.rtcwake = False
6402				else:
6403					sysvals.rtcwake = True
6404					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6405			elif(option == 'timeprec'):
6406				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6407			elif(option == 'mindev'):
6408				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6409			elif(option == 'callloop-maxgap'):
6410				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6411			elif(option == 'callloop-maxlen'):
6412				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6413			elif(option == 'mincg'):
6414				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6415			elif(option == 'bufsize'):
6416				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6417			elif(option == 'output-dir'):
6418				sysvals.outdir = sysvals.setOutputFolder(value)
6419
6420	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6421		doError('No command supplied for mode "command"')
6422
6423	# compatibility errors
6424	if sysvals.usedevsrc and sysvals.usecallgraph:
6425		doError('-dev is not compatible with -f')
6426	if sysvals.usecallgraph and sysvals.useprocmon:
6427		doError('-proc is not compatible with -f')
6428
6429	if overridekprobes:
6430		sysvals.tracefuncs = dict()
6431	if overridedevkprobes:
6432		sysvals.dev_tracefuncs = dict()
6433
6434	kprobes = dict()
6435	kprobesec = 'dev_timeline_functions_'+platform.machine()
6436	if kprobesec in sections:
6437		for name in Config.options(kprobesec):
6438			text = Config.get(kprobesec, name)
6439			kprobes[name] = (text, True)
6440	kprobesec = 'timeline_functions_'+platform.machine()
6441	if kprobesec in sections:
6442		for name in Config.options(kprobesec):
6443			if name in kprobes:
6444				doError('Duplicate timeline function found "%s"' % (name))
6445			text = Config.get(kprobesec, name)
6446			kprobes[name] = (text, False)
6447
6448	for name in kprobes:
6449		function = name
6450		format = name
6451		color = ''
6452		args = dict()
6453		text, dev = kprobes[name]
6454		data = text.split()
6455		i = 0
6456		for val in data:
6457			# bracketted strings are special formatting, read them separately
6458			if val[0] == '[' and val[-1] == ']':
6459				for prop in val[1:-1].split(','):
6460					p = prop.split('=')
6461					if p[0] == 'color':
6462						try:
6463							color = int(p[1], 16)
6464							color = '#'+p[1]
6465						except:
6466							color = p[1]
6467				continue
6468			# first real arg should be the format string
6469			if i == 0:
6470				format = val
6471			# all other args are actual function args
6472			else:
6473				d = val.split('=')
6474				args[d[0]] = d[1]
6475			i += 1
6476		if not function or not format:
6477			doError('Invalid kprobe: %s' % name)
6478		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6479			if arg not in args:
6480				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6481		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6482			doError('Duplicate timeline function found "%s"' % (name))
6483
6484		kp = {
6485			'name': name,
6486			'func': function,
6487			'format': format,
6488			sysvals.archargs: args
6489		}
6490		if color:
6491			kp['color'] = color
6492		if dev:
6493			sysvals.dev_tracefuncs[name] = kp
6494		else:
6495			sysvals.tracefuncs[name] = kp
6496
6497# Function: printHelp
6498# Description:
6499#	 print out the help text
6500def printHelp():
6501	pprint('\n%s v%s\n'\
6502	'Usage: sudo sleepgraph <options> <commands>\n'\
6503	'\n'\
6504	'Description:\n'\
6505	'  This tool is designed to assist kernel and OS developers in optimizing\n'\
6506	'  their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6507	'  with a few extra options enabled, the tool will execute a suspend and\n'\
6508	'  capture dmesg and ftrace data until resume is complete. This data is\n'\
6509	'  transformed into a device timeline and an optional callgraph to give\n'\
6510	'  a detailed view of which devices/subsystems are taking the most\n'\
6511	'  time in suspend/resume.\n'\
6512	'\n'\
6513	'  If no specific command is given, the default behavior is to initiate\n'\
6514	'  a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6515	'\n'\
6516	'  Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6517	'   HTML output:                    <hostname>_<mode>.html\n'\
6518	'   raw dmesg output:               <hostname>_<mode>_dmesg.txt\n'\
6519	'   raw ftrace output:              <hostname>_<mode>_ftrace.txt\n'\
6520	'\n'\
6521	'Options:\n'\
6522	'   -h           Print this help text\n'\
6523	'   -v           Print the current tool version\n'\
6524	'   -config fn   Pull arguments and config options from file fn\n'\
6525	'   -verbose     Print extra information during execution and analysis\n'\
6526	'   -m mode      Mode to initiate for suspend (default: %s)\n'\
6527	'   -o name      Overrides the output subdirectory name when running a new test\n'\
6528	'                default: suspend-{date}-{time}\n'\
6529	'   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6530	'   -addlogs     Add the dmesg and ftrace logs to the html output\n'\
6531	'   -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6532	'   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6533	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6534	'   -result fn   Export a results table to a text file for parsing.\n'\
6535	'   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
6536	'  [testprep]\n'\
6537	'   -sync        Sync the filesystems before starting the test\n'\
6538	'   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
6539	'   -display m   Change the display mode to m for the test (on/off/standby/suspend)\n'\
6540	'  [advanced]\n'\
6541	'   -gzip        Gzip the trace and dmesg logs to save space\n'\
6542	'   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"\n'\
6543	'   -proc        Add usermode process info into the timeline (default: disabled)\n'\
6544	'   -dev         Add kernel function calls and threads to the timeline (default: disabled)\n'\
6545	'   -x2          Run two suspend/resumes back to back (default: disabled)\n'\
6546	'   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)\n'\
6547	'   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)\n'\
6548	'   -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6549	'   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6550	'   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6551	'                by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6552	'                The outputs will be created in a new subdirectory with a summary page.\n'\
6553	'   -maxfail n   Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6554	'  [debug]\n'\
6555	'   -f           Use ftrace to create device callgraphs (default: disabled)\n'\
6556	'   -ftop        Use ftrace on the top level call: "%s" (default: disabled)\n'\
6557	'   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)\n'\
6558	'   -expandcg    pre-expand the callgraph data in the html output (default: disabled)\n'\
6559	'   -fadd file   Add functions to be graphed in the timeline from a list in a text file\n'\
6560	'   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6561	'   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6562	'   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)\n'\
6563	'   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6564	'   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6565	'   -cgfilter S  Filter the callgraph output in the timeline\n'\
6566	'   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6567	'   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6568	'   -devdump     Print out all the raw device data for each phase\n'\
6569	'   -cgdump      Print out all the raw callgraph data\n'\
6570	'\n'\
6571	'Other commands:\n'\
6572	'   -modes       List available suspend modes\n'\
6573	'   -status      Test to see if the system is enabled to run this tool\n'\
6574	'   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table\n'\
6575	'   -wificheck   Print out wifi connection info\n'\
6576	'   -x<mode>     Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6577	'   -sysinfo     Print out system info extracted from BIOS\n'\
6578	'   -devinfo     Print out the pm settings of all devices which support runtime suspend\n'\
6579	'   -cmdinfo     Print out all the platform info collected before and after suspend/resume\n'\
6580	'   -flist       Print the list of functions currently being captured in ftrace\n'\
6581	'   -flistall    Print all functions capable of being captured in ftrace\n'\
6582	'   -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6583	'  [redo]\n'\
6584	'   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)\n'\
6585	'   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)\n'\
6586	'' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6587	return True
6588
6589# ----------------- MAIN --------------------
6590# exec start (skipped if script is loaded as library)
6591if __name__ == '__main__':
6592	genhtml = False
6593	cmd = ''
6594	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6595		'-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6596		'-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6597	if '-f' in sys.argv:
6598		sysvals.cgskip = sysvals.configFile('cgskip.txt')
6599	# loop through the command line arguments
6600	args = iter(sys.argv[1:])
6601	for arg in args:
6602		if(arg == '-m'):
6603			try:
6604				val = next(args)
6605			except:
6606				doError('No mode supplied', True)
6607			if val == 'command' and not sysvals.testcommand:
6608				doError('No command supplied for mode "command"', True)
6609			sysvals.suspendmode = val
6610		elif(arg in simplecmds):
6611			cmd = arg[1:]
6612		elif(arg == '-h'):
6613			printHelp()
6614			sys.exit(0)
6615		elif(arg == '-v'):
6616			pprint("Version %s" % sysvals.version)
6617			sys.exit(0)
6618		elif(arg == '-x2'):
6619			sysvals.execcount = 2
6620		elif(arg == '-x2delay'):
6621			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6622		elif(arg == '-predelay'):
6623			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6624		elif(arg == '-postdelay'):
6625			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6626		elif(arg == '-f'):
6627			sysvals.usecallgraph = True
6628		elif(arg == '-ftop'):
6629			sysvals.usecallgraph = True
6630			sysvals.ftop = True
6631			sysvals.usekprobes = False
6632		elif(arg == '-skiphtml'):
6633			sysvals.skiphtml = True
6634		elif(arg == '-cgdump'):
6635			sysvals.cgdump = True
6636		elif(arg == '-devdump'):
6637			sysvals.devdump = True
6638		elif(arg == '-genhtml'):
6639			genhtml = True
6640		elif(arg == '-addlogs'):
6641			sysvals.dmesglog = sysvals.ftracelog = True
6642		elif(arg == '-nologs'):
6643			sysvals.dmesglog = sysvals.ftracelog = False
6644		elif(arg == '-addlogdmesg'):
6645			sysvals.dmesglog = True
6646		elif(arg == '-addlogftrace'):
6647			sysvals.ftracelog = True
6648		elif(arg == '-noturbostat'):
6649			sysvals.tstat = False
6650		elif(arg == '-verbose'):
6651			sysvals.verbose = True
6652		elif(arg == '-proc'):
6653			sysvals.useprocmon = True
6654		elif(arg == '-dev'):
6655			sysvals.usedevsrc = True
6656		elif(arg == '-sync'):
6657			sysvals.sync = True
6658		elif(arg == '-wifi'):
6659			sysvals.wifi = True
6660		elif(arg == '-gzip'):
6661			sysvals.gzip = True
6662		elif(arg == '-info'):
6663			try:
6664				val = next(args)
6665			except:
6666				doError('-info requires one string argument', True)
6667		elif(arg == '-desc'):
6668			try:
6669				val = next(args)
6670			except:
6671				doError('-desc requires one string argument', True)
6672		elif(arg == '-rs'):
6673			try:
6674				val = next(args)
6675			except:
6676				doError('-rs requires "enable" or "disable"', True)
6677			if val.lower() in switchvalues:
6678				if val.lower() in switchoff:
6679					sysvals.rs = -1
6680				else:
6681					sysvals.rs = 1
6682			else:
6683				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6684		elif(arg == '-display'):
6685			try:
6686				val = next(args)
6687			except:
6688				doError('-display requires an mode value', True)
6689			disopt = ['on', 'off', 'standby', 'suspend']
6690			if val.lower() not in disopt:
6691				doError('valid display mode values are %s' % disopt, True)
6692			sysvals.display = val.lower()
 
 
 
6693		elif(arg == '-maxdepth'):
6694			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6695		elif(arg == '-rtcwake'):
6696			try:
6697				val = next(args)
6698			except:
6699				doError('No rtcwake time supplied', True)
6700			if val.lower() in switchoff:
6701				sysvals.rtcwake = False
6702			else:
6703				sysvals.rtcwake = True
6704				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6705		elif(arg == '-timeprec'):
6706			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6707		elif(arg == '-mindev'):
6708			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6709		elif(arg == '-mincg'):
6710			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6711		elif(arg == '-bufsize'):
6712			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6713		elif(arg == '-cgtest'):
6714			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6715		elif(arg == '-cgphase'):
6716			try:
6717				val = next(args)
6718			except:
6719				doError('No phase name supplied', True)
6720			d = Data(0)
6721			if val not in d.phasedef:
6722				doError('invalid phase --> (%s: %s), valid phases are %s'\
6723					% (arg, val, d.phasedef.keys()), True)
6724			sysvals.cgphase = val
6725		elif(arg == '-cgfilter'):
6726			try:
6727				val = next(args)
6728			except:
6729				doError('No callgraph functions supplied', True)
6730			sysvals.setCallgraphFilter(val)
6731		elif(arg == '-skipkprobe'):
6732			try:
6733				val = next(args)
6734			except:
6735				doError('No kprobe functions supplied', True)
6736			sysvals.skipKprobes(val)
6737		elif(arg == '-cgskip'):
6738			try:
6739				val = next(args)
6740			except:
6741				doError('No file supplied', True)
6742			if val.lower() in switchoff:
6743				sysvals.cgskip = ''
6744			else:
6745				sysvals.cgskip = sysvals.configFile(val)
6746				if(not sysvals.cgskip):
6747					doError('%s does not exist' % sysvals.cgskip)
6748		elif(arg == '-callloop-maxgap'):
6749			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6750		elif(arg == '-callloop-maxlen'):
6751			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6752		elif(arg == '-cmd'):
6753			try:
6754				val = next(args)
6755			except:
6756				doError('No command string supplied', True)
6757			sysvals.testcommand = val
6758			sysvals.suspendmode = 'command'
6759		elif(arg == '-expandcg'):
6760			sysvals.cgexp = True
6761		elif(arg == '-srgap'):
6762			sysvals.srgap = 5
6763		elif(arg == '-maxfail'):
6764			sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
6765		elif(arg == '-multi'):
6766			try:
6767				c, d = next(args), next(args)
6768			except:
6769				doError('-multi requires two values', True)
6770			sysvals.multiinit(c, d)
6771		elif(arg == '-o'):
6772			try:
6773				val = next(args)
6774			except:
6775				doError('No subdirectory name supplied', True)
6776			sysvals.outdir = sysvals.setOutputFolder(val)
6777		elif(arg == '-config'):
6778			try:
6779				val = next(args)
6780			except:
6781				doError('No text file supplied', True)
6782			file = sysvals.configFile(val)
6783			if(not file):
6784				doError('%s does not exist' % val)
6785			configFromFile(file)
6786		elif(arg == '-fadd'):
6787			try:
6788				val = next(args)
6789			except:
6790				doError('No text file supplied', True)
6791			file = sysvals.configFile(val)
6792			if(not file):
6793				doError('%s does not exist' % val)
6794			sysvals.addFtraceFilterFunctions(file)
6795		elif(arg == '-dmesg'):
6796			try:
6797				val = next(args)
6798			except:
6799				doError('No dmesg file supplied', True)
6800			sysvals.notestrun = True
6801			sysvals.dmesgfile = val
6802			if(os.path.exists(sysvals.dmesgfile) == False):
6803				doError('%s does not exist' % sysvals.dmesgfile)
6804		elif(arg == '-ftrace'):
6805			try:
6806				val = next(args)
6807			except:
6808				doError('No ftrace file supplied', True)
6809			sysvals.notestrun = True
6810			sysvals.ftracefile = val
6811			if(os.path.exists(sysvals.ftracefile) == False):
6812				doError('%s does not exist' % sysvals.ftracefile)
6813		elif(arg == '-summary'):
6814			try:
6815				val = next(args)
6816			except:
6817				doError('No directory supplied', True)
6818			cmd = 'summary'
6819			sysvals.outdir = val
6820			sysvals.notestrun = True
6821			if(os.path.isdir(val) == False):
6822				doError('%s is not accessible' % val)
6823		elif(arg == '-filter'):
6824			try:
6825				val = next(args)
6826			except:
6827				doError('No devnames supplied', True)
6828			sysvals.setDeviceFilter(val)
6829		elif(arg == '-result'):
6830			try:
6831				val = next(args)
6832			except:
6833				doError('No result file supplied', True)
6834			sysvals.result = val
6835			sysvals.signalHandlerInit()
6836		else:
6837			doError('Invalid argument: '+arg, True)
6838
6839	# compatibility errors
6840	if(sysvals.usecallgraph and sysvals.usedevsrc):
6841		doError('-dev is not compatible with -f')
6842	if(sysvals.usecallgraph and sysvals.useprocmon):
6843		doError('-proc is not compatible with -f')
6844
6845	if sysvals.usecallgraph and sysvals.cgskip:
6846		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
6847		sysvals.setCallgraphBlacklist(sysvals.cgskip)
6848
6849	# callgraph size cannot exceed device size
6850	if sysvals.mincglen < sysvals.mindevlen:
6851		sysvals.mincglen = sysvals.mindevlen
6852
6853	# remove existing buffers before calculating memory
6854	if(sysvals.usecallgraph or sysvals.usedevsrc):
6855		sysvals.fsetVal('16', 'buffer_size_kb')
6856	sysvals.cpuInfo()
6857
6858	# just run a utility command and exit
6859	if(cmd != ''):
6860		ret = 0
6861		if(cmd == 'status'):
6862			if not statusCheck(True):
6863				ret = 1
6864		elif(cmd == 'fpdt'):
6865			if not getFPDT(True):
6866				ret = 1
6867		elif(cmd == 'sysinfo'):
6868			sysvals.printSystemInfo(True)
6869		elif(cmd == 'devinfo'):
6870			deviceInfo()
6871		elif(cmd == 'modes'):
6872			pprint(getModes())
6873		elif(cmd == 'flist'):
6874			sysvals.getFtraceFilterFunctions(True)
6875		elif(cmd == 'flistall'):
6876			sysvals.getFtraceFilterFunctions(False)
6877		elif(cmd == 'summary'):
6878			runSummary(sysvals.outdir, True, genhtml)
6879		elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
6880			sysvals.verbose = True
6881			ret = sysvals.displayControl(cmd[1:])
6882		elif(cmd == 'xstat'):
6883			pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
6884		elif(cmd == 'wificheck'):
6885			dev = sysvals.checkWifi()
6886			if dev:
6887				print('%s is connected' % sysvals.wifiDetails(dev))
6888			else:
6889				print('No wifi connection found')
6890		elif(cmd == 'cmdinfo'):
6891			for out in sysvals.cmdinfo(False, True):
6892				print('[%s - %s]\n%s\n' % out)
6893		sys.exit(ret)
6894
6895	# if instructed, re-analyze existing data files
6896	if(sysvals.notestrun):
6897		stamp = rerunTest(sysvals.outdir)
6898		sysvals.outputResult(stamp)
6899		sys.exit(0)
6900
6901	# verify that we can run a test
6902	error = statusCheck()
6903	if(error):
6904		doError(error)
6905
6906	# extract mem/disk extra modes and convert
6907	mode = sysvals.suspendmode
6908	if mode.startswith('mem'):
6909		memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
 
 
 
6910		if memmode == 'shallow':
6911			mode = 'standby'
6912		elif memmode ==  's2idle':
6913			mode = 'freeze'
6914		else:
6915			mode = 'mem'
6916		sysvals.memmode = memmode
6917		sysvals.suspendmode = mode
6918	if mode.startswith('disk-'):
6919		sysvals.diskmode = mode.split('-', 1)[-1]
6920		sysvals.suspendmode = 'disk'
6921	sysvals.systemInfo(dmidecode(sysvals.mempath))
6922
6923	failcnt, ret = 0, 0
 
 
 
6924	if sysvals.multitest['run']:
6925		# run multiple tests in a separate subdirectory
6926		if not sysvals.outdir:
6927			if 'time' in sysvals.multitest:
6928				s = '-%dm' % sysvals.multitest['time']
6929			else:
6930				s = '-x%d' % sysvals.multitest['count']
6931			sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
6932		if not os.path.isdir(sysvals.outdir):
6933			os.makedirs(sysvals.outdir)
6934		sysvals.sudoUserchown(sysvals.outdir)
6935		finish = datetime.now()
6936		if 'time' in sysvals.multitest:
6937			finish += timedelta(minutes=sysvals.multitest['time'])
6938		for i in range(sysvals.multitest['count']):
6939			sysvals.multistat(True, i, finish)
6940			if i != 0 and sysvals.multitest['delay'] > 0:
6941				pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
6942				time.sleep(sysvals.multitest['delay'])
 
6943			fmt = 'suspend-%y%m%d-%H%M%S'
6944			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
6945			ret = runTest(i+1, True)
6946			failcnt = 0 if not ret else failcnt + 1
6947			if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
6948				pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
6949				break
6950			time.sleep(5)
6951			sysvals.resetlog()
6952			sysvals.multistat(False, i, finish)
6953			if 'time' in sysvals.multitest and datetime.now() >= finish:
6954				break
6955		if not sysvals.skiphtml:
6956			runSummary(sysvals.outdir, False, False)
6957		sysvals.sudoUserchown(sysvals.outdir)
6958	else:
6959		if sysvals.outdir:
6960			sysvals.testdir = sysvals.outdir
6961		# run the test in the current directory
6962		ret = runTest()
6963
6964	# reset to default values after testing
6965	if sysvals.display:
6966		sysvals.displayControl('reset')
6967	if sysvals.rs != 0:
6968		sysvals.setRuntimeSuspend(False)
6969	sys.exit(ret)
v4.17
   1#!/usr/bin/python
 
   2#
   3# Tool for analyzing suspend/resume timing
   4# Copyright (c) 2013, Intel Corporation.
   5#
   6# This program is free software; you can redistribute it and/or modify it
   7# under the terms and conditions of the GNU General Public License,
   8# version 2, as published by the Free Software Foundation.
   9#
  10# This program is distributed in the hope it will be useful, but WITHOUT
  11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  13# more details.
  14#
  15# Authors:
  16#	 Todd Brandt <todd.e.brandt@linux.intel.com>
  17#
  18# Links:
  19#	 Home Page
  20#	   https://01.org/suspendresume
  21#	 Source repo
  22#	   git@github.com:01org/pm-graph
  23#
  24# Description:
  25#	 This tool is designed to assist kernel and OS developers in optimizing
  26#	 their linux stack's suspend/resume time. Using a kernel image built
  27#	 with a few extra options enabled, the tool will execute a suspend and
  28#	 will capture dmesg and ftrace data until resume is complete. This data
  29#	 is transformed into a device timeline and a callgraph to give a quick
  30#	 and detailed view of which devices and callbacks are taking the most
  31#	 time in suspend/resume. The output is a single html file which can be
  32#	 viewed in firefox or chrome.
  33#
  34#	 The following kernel build options are required:
 
  35#		 CONFIG_PM_DEBUG=y
  36#		 CONFIG_PM_SLEEP_DEBUG=y
  37#		 CONFIG_FTRACE=y
  38#		 CONFIG_FUNCTION_TRACER=y
  39#		 CONFIG_FUNCTION_GRAPH_TRACER=y
  40#		 CONFIG_KPROBES=y
  41#		 CONFIG_KPROBES_ON_FTRACE=y
  42#
  43#	 For kernel versions older than 3.15:
  44#	 The following additional kernel parameters are required:
  45#		 (e.g. in file /etc/default/grub)
  46#		 GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
  47#
  48
  49# ----------------- LIBRARIES --------------------
  50
  51import sys
  52import time
  53import os
  54import string
  55import re
  56import platform
  57from datetime import datetime
 
 
  58import struct
  59import ConfigParser
  60import gzip
  61from threading import Thread
  62from subprocess import call, Popen, PIPE
 
 
 
 
 
 
 
 
  63
  64# ----------------- CLASSES --------------------
  65
  66# Class: SystemValues
  67# Description:
  68#	 A global, single-instance container used to
  69#	 store system values and test parameters
  70class SystemValues:
  71	title = 'SleepGraph'
  72	version = '5.0'
  73	ansi = False
  74	rs = 0
  75	display = 0
  76	gzip = False
  77	sync = False
 
  78	verbose = False
  79	testlog = True
  80	dmesglog = False
  81	ftracelog = False
  82	mindevlen = 0.0
 
 
  83	mincglen = 0.0
  84	cgphase = ''
  85	cgtest = -1
  86	cgskip = ''
  87	multitest = {'run': False, 'count': 0, 'delay': 0}
 
  88	max_graph_depth = 0
  89	callloopmaxgap = 0.0001
  90	callloopmaxlen = 0.005
  91	bufsize = 0
  92	cpucount = 0
  93	memtotal = 204800
  94	memfree = 204800
  95	srgap = 0
  96	cgexp = False
  97	testdir = ''
  98	outdir = ''
  99	tpath = '/sys/kernel/debug/tracing/'
 100	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
 101	epath = '/sys/kernel/debug/tracing/events/power/'
 
 
 102	traceevents = [
 103		'suspend_resume',
 
 
 104		'device_pm_callback_end',
 105		'device_pm_callback_start'
 106	]
 107	logmsg = ''
 108	testcommand = ''
 109	mempath = '/dev/mem'
 110	powerfile = '/sys/power/state'
 111	mempowerfile = '/sys/power/mem_sleep'
 
 112	suspendmode = 'mem'
 113	memmode = ''
 
 114	hostname = 'localhost'
 115	prefix = 'test'
 116	teststamp = ''
 117	sysstamp = ''
 118	dmesgstart = 0.0
 119	dmesgfile = ''
 120	ftracefile = ''
 121	htmlfile = 'output.html'
 122	result = ''
 123	rtcwake = True
 124	rtcwaketime = 15
 125	rtcpath = ''
 126	devicefilter = []
 127	cgfilter = []
 128	stamp = 0
 129	execcount = 1
 130	x2delay = 0
 131	skiphtml = False
 132	usecallgraph = False
 
 
 133	usetraceevents = False
 134	usetracemarkers = True
 135	usekprobes = True
 136	usedevsrc = False
 137	useprocmon = False
 138	notestrun = False
 139	cgdump = False
 
 140	mixedphaseheight = True
 141	devprops = dict()
 
 
 142	predelay = 0
 143	postdelay = 0
 144	procexecfmt = 'ps - (?P<ps>.*)$'
 145	devpropfmt = '# Device Properties: .*'
 146	tracertypefmt = '# tracer: (?P<t>.*)'
 147	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
 148	tracefuncs = {
 149		'sys_sync': {},
 
 150		'__pm_notifier_call_chain': {},
 151		'pm_prepare_console': {},
 152		'pm_notifier_call_chain': {},
 153		'freeze_processes': {},
 154		'freeze_kernel_threads': {},
 155		'pm_restrict_gfp_mask': {},
 156		'acpi_suspend_begin': {},
 157		'acpi_hibernation_begin': {},
 158		'acpi_hibernation_enter': {},
 159		'acpi_hibernation_leave': {},
 160		'acpi_pm_freeze': {},
 161		'acpi_pm_thaw': {},
 
 
 
 
 
 
 
 
 
 162		'hibernate_preallocate_memory': {},
 163		'create_basic_memory_bitmaps': {},
 164		'swsusp_write': {},
 165		'suspend_console': {},
 166		'acpi_pm_prepare': {},
 167		'syscore_suspend': {},
 168		'arch_enable_nonboot_cpus_end': {},
 169		'syscore_resume': {},
 170		'acpi_pm_finish': {},
 171		'resume_console': {},
 172		'acpi_pm_end': {},
 173		'pm_restore_gfp_mask': {},
 174		'thaw_processes': {},
 175		'pm_restore_console': {},
 176		'CPU_OFF': {
 177			'func':'_cpu_down',
 178			'args_x86_64': {'cpu':'%di:s32'},
 179			'format': 'CPU_OFF[{cpu}]'
 180		},
 181		'CPU_ON': {
 182			'func':'_cpu_up',
 183			'args_x86_64': {'cpu':'%di:s32'},
 184			'format': 'CPU_ON[{cpu}]'
 185		},
 186	}
 187	dev_tracefuncs = {
 188		# general wait/delay/sleep
 189		'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
 190		'schedule_timeout_uninterruptible': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
 191		'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
 192		'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
 193		'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
 194		'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
 195		'acpi_os_stall': {'ub': 1},
 
 196		# ACPI
 197		'acpi_resume_power_resources': {},
 198		'acpi_ps_parse_aml': {},
 
 
 
 
 199		# filesystem
 200		'ext4_sync_fs': {},
 201		# 80211
 
 
 
 202		'iwlagn_mac_start': {},
 203		'iwlagn_alloc_bcast_station': {},
 204		'iwl_trans_pcie_start_hw': {},
 205		'iwl_trans_pcie_start_fw': {},
 206		'iwl_run_init_ucode': {},
 207		'iwl_load_ucode_wait_alive': {},
 208		'iwl_alive_start': {},
 209		'iwlagn_mac_stop': {},
 210		'iwlagn_mac_suspend': {},
 211		'iwlagn_mac_resume': {},
 212		'iwlagn_mac_add_interface': {},
 213		'iwlagn_mac_remove_interface': {},
 214		'iwlagn_mac_change_interface': {},
 215		'iwlagn_mac_config': {},
 216		'iwlagn_configure_filter': {},
 217		'iwlagn_mac_hw_scan': {},
 218		'iwlagn_bss_info_changed': {},
 219		'iwlagn_mac_channel_switch': {},
 220		'iwlagn_mac_flush': {},
 221		# ATA
 222		'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
 223		# i915
 224		'i915_gem_resume': {},
 225		'i915_restore_state': {},
 226		'intel_opregion_setup': {},
 227		'g4x_pre_enable_dp': {},
 228		'vlv_pre_enable_dp': {},
 229		'chv_pre_enable_dp': {},
 230		'g4x_enable_dp': {},
 231		'vlv_enable_dp': {},
 232		'intel_hpd_init': {},
 233		'intel_opregion_register': {},
 234		'intel_dp_detect': {},
 235		'intel_hdmi_detect': {},
 236		'intel_opregion_init': {},
 237		'intel_fbdev_set_suspend': {},
 238	}
 
 
 
 
 
 
 
 
 
 
 
 
 239	cgblacklist = []
 240	kprobes = dict()
 241	timeformat = '%.3f'
 242	cmdline = '%s %s' % \
 243			(os.path.basename(sys.argv[0]), string.join(sys.argv[1:], ' '))
 
 244	def __init__(self):
 245		self.archargs = 'args_'+platform.machine()
 246		self.hostname = platform.node()
 247		if(self.hostname == ''):
 248			self.hostname = 'localhost'
 249		rtc = "rtc0"
 250		if os.path.exists('/dev/rtc'):
 251			rtc = os.readlink('/dev/rtc')
 252		rtc = '/sys/class/rtc/'+rtc
 253		if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
 254			os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
 255			self.rtcpath = rtc
 256		if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
 257			self.ansi = True
 258		self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
 
 
 
 
 
 
 259	def vprint(self, msg):
 260		self.logmsg += msg+'\n'
 261		if(self.verbose):
 262			print(msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 263	def rootCheck(self, fatal=True):
 264		if(os.access(self.powerfile, os.W_OK)):
 265			return True
 266		if fatal:
 267			msg = 'This command requires sysfs mount and root access'
 268			print('ERROR: %s\n') % msg
 269			self.outputResult({'error':msg})
 270			sys.exit()
 271		return False
 272	def rootUser(self, fatal=False):
 273		if 'USER' in os.environ and os.environ['USER'] == 'root':
 274			return True
 275		if fatal:
 276			msg = 'This command must be run as root'
 277			print('ERROR: %s\n') % msg
 278			self.outputResult({'error':msg})
 279			sys.exit()
 280		return False
 
 
 281	def getExec(self, cmd):
 282		dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
 283			'/usr/local/sbin', '/usr/local/bin']
 284		for path in dirlist:
 
 
 
 
 
 
 
 285			cmdfull = os.path.join(path, cmd)
 286			if os.path.exists(cmdfull):
 287				return cmdfull
 288		return ''
 289	def setPrecision(self, num):
 290		if num < 0 or num > 6:
 291			return
 292		self.timeformat = '%.{0}f'.format(num)
 293	def setOutputFolder(self, value):
 294		args = dict()
 295		n = datetime.now()
 296		args['date'] = n.strftime('%y%m%d')
 297		args['time'] = n.strftime('%H%M%S')
 298		args['hostname'] = args['host'] = self.hostname
 
 299		return value.format(**args)
 300	def setOutputFile(self):
 301		if self.dmesgfile != '':
 302			m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
 303			if(m):
 304				self.htmlfile = m.group('name')+'.html'
 305		if self.ftracefile != '':
 306			m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
 307			if(m):
 308				self.htmlfile = m.group('name')+'.html'
 309	def systemInfo(self, info):
 310		p = c = m = b = ''
 311		if 'baseboard-manufacturer' in info:
 312			m = info['baseboard-manufacturer']
 313		elif 'system-manufacturer' in info:
 314			m = info['system-manufacturer']
 315		if 'baseboard-product-name' in info:
 
 
 
 
 316			p = info['baseboard-product-name']
 317		elif 'system-product-name' in info:
 318			p = info['system-product-name']
 319		if 'processor-version' in info:
 320			c = info['processor-version']
 321		if 'bios-version' in info:
 322			b = info['bios-version']
 323		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | numcpu:%d | memsz:%d | memfr:%d' % \
 324			(m, p, c, b, self.cpucount, self.memtotal, self.memfree)
 325	def printSystemInfo(self, fatal=False):
 326		self.rootCheck(True)
 327		out = dmidecode(self.mempath, fatal)
 328		if len(out) < 1:
 329			return
 330		fmt = '%-24s: %s'
 331		for name in sorted(out):
 332			print fmt % (name, out[name])
 333		print fmt % ('cpucount', ('%d' % self.cpucount))
 334		print fmt % ('memtotal', ('%d kB' % self.memtotal))
 335		print fmt % ('memfree', ('%d kB' % self.memfree))
 336	def cpuInfo(self):
 337		self.cpucount = 0
 338		fp = open('/proc/cpuinfo', 'r')
 339		for line in fp:
 340			if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
 341				self.cpucount += 1
 342		fp.close()
 343		fp = open('/proc/meminfo', 'r')
 344		for line in fp:
 345			m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
 346			if m:
 347				self.memtotal = int(m.group('sz'))
 348			m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
 349			if m:
 350				self.memfree = int(m.group('sz'))
 351		fp.close()
 352	def initTestOutput(self, name):
 353		self.prefix = self.hostname
 354		v = open('/proc/version', 'r').read().strip()
 355		kver = string.split(v)[2]
 356		fmt = name+'-%m%d%y-%H%M%S'
 357		testtime = datetime.now().strftime(fmt)
 358		self.teststamp = \
 359			'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
 360		ext = ''
 361		if self.gzip:
 362			ext = '.gz'
 363		self.dmesgfile = \
 364			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
 365		self.ftracefile = \
 366			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
 367		self.htmlfile = \
 368			self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
 369		if not os.path.isdir(self.testdir):
 370			os.mkdir(self.testdir)
 
 371	def getValueList(self, value):
 372		out = []
 373		for i in value.split(','):
 374			if i.strip():
 375				out.append(i.strip())
 376		return out
 377	def setDeviceFilter(self, value):
 378		self.devicefilter = self.getValueList(value)
 379	def setCallgraphFilter(self, value):
 380		self.cgfilter = self.getValueList(value)
 
 
 
 
 
 
 381	def setCallgraphBlacklist(self, file):
 382		self.cgblacklist = self.listFromFile(file)
 383	def rtcWakeAlarmOn(self):
 384		call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
 385		nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
 386		if nowtime:
 387			nowtime = int(nowtime)
 388		else:
 389			# if hardware time fails, use the software time
 390			nowtime = int(datetime.now().strftime('%s'))
 391		alarm = nowtime + self.rtcwaketime
 392		call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
 393	def rtcWakeAlarmOff(self):
 394		call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
 395	def initdmesg(self):
 396		# get the latest time stamp from the dmesg log
 397		fp = Popen('dmesg', stdout=PIPE).stdout
 398		ktime = '0'
 399		for line in fp:
 400			line = line.replace('\r\n', '')
 401			idx = line.find('[')
 402			if idx > 1:
 403				line = line[idx:]
 404			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 405			if(m):
 406				ktime = m.group('ktime')
 407		fp.close()
 408		self.dmesgstart = float(ktime)
 409	def getdmesg(self, fwdata=[]):
 410		op = self.writeDatafileHeader(sysvals.dmesgfile, fwdata)
 411		# store all new dmesg lines since initdmesg was called
 412		fp = Popen('dmesg', stdout=PIPE).stdout
 413		for line in fp:
 414			line = line.replace('\r\n', '')
 415			idx = line.find('[')
 416			if idx > 1:
 417				line = line[idx:]
 418			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 419			if(not m):
 420				continue
 421			ktime = float(m.group('ktime'))
 422			if ktime > self.dmesgstart:
 423				op.write(line)
 424		fp.close()
 425		op.close()
 426	def listFromFile(self, file):
 427		list = []
 428		fp = open(file)
 429		for i in fp.read().split('\n'):
 430			i = i.strip()
 431			if i and i[0] != '#':
 432				list.append(i)
 433		fp.close()
 434		return list
 435	def addFtraceFilterFunctions(self, file):
 436		for i in self.listFromFile(file):
 437			if len(i) < 2:
 438				continue
 439			self.tracefuncs[i] = dict()
 440	def getFtraceFilterFunctions(self, current):
 441		self.rootCheck(True)
 442		if not current:
 443			call('cat '+self.tpath+'available_filter_functions', shell=True)
 444			return
 445		master = self.listFromFile(self.tpath+'available_filter_functions')
 446		for i in self.tracefuncs:
 447			if 'func' in self.tracefuncs[i]:
 448				i = self.tracefuncs[i]['func']
 449			if i in master:
 450				print i
 451			else:
 452				print self.colorText(i)
 453	def setFtraceFilterFunctions(self, list):
 454		master = self.listFromFile(self.tpath+'available_filter_functions')
 455		flist = ''
 456		for i in list:
 457			if i not in master:
 458				continue
 459			if ' [' in i:
 460				flist += i.split(' ')[0]+'\n'
 461			else:
 462				flist += i+'\n'
 463		fp = open(self.tpath+'set_graph_function', 'w')
 464		fp.write(flist)
 465		fp.close()
 466	def basicKprobe(self, name):
 467		self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
 468	def defaultKprobe(self, name, kdata):
 469		k = kdata
 470		for field in ['name', 'format', 'func']:
 471			if field not in k:
 472				k[field] = name
 473		if self.archargs in k:
 474			k['args'] = k[self.archargs]
 475		else:
 476			k['args'] = dict()
 477			k['format'] = name
 478		self.kprobes[name] = k
 479	def kprobeColor(self, name):
 480		if name not in self.kprobes or 'color' not in self.kprobes[name]:
 481			return ''
 482		return self.kprobes[name]['color']
 483	def kprobeDisplayName(self, name, dataraw):
 484		if name not in self.kprobes:
 485			self.basicKprobe(name)
 486		data = ''
 487		quote=0
 488		# first remvoe any spaces inside quotes, and the quotes
 489		for c in dataraw:
 490			if c == '"':
 491				quote = (quote + 1) % 2
 492			if quote and c == ' ':
 493				data += '_'
 494			elif c != '"':
 495				data += c
 496		fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
 497		arglist = dict()
 498		# now process the args
 499		for arg in sorted(args):
 500			arglist[arg] = ''
 501			m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
 502			if m:
 503				arglist[arg] = m.group('arg')
 504			else:
 505				m = re.match('.* '+arg+'=(?P<arg>.*)', data);
 506				if m:
 507					arglist[arg] = m.group('arg')
 508		out = fmt.format(**arglist)
 509		out = out.replace(' ', '_').replace('"', '')
 510		return out
 511	def kprobeText(self, kname, kprobe):
 512		name = fmt = func = kname
 513		args = dict()
 514		if 'name' in kprobe:
 515			name = kprobe['name']
 516		if 'format' in kprobe:
 517			fmt = kprobe['format']
 518		if 'func' in kprobe:
 519			func = kprobe['func']
 520		if self.archargs in kprobe:
 521			args = kprobe[self.archargs]
 522		if 'args' in kprobe:
 523			args = kprobe['args']
 524		if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
 525			doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
 526		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
 527			if arg not in args:
 528				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
 529		val = 'p:%s_cal %s' % (name, func)
 530		for i in sorted(args):
 531			val += ' %s=%s' % (i, args[i])
 532		val += '\nr:%s_ret %s $retval\n' % (name, func)
 533		return val
 534	def addKprobes(self, output=False):
 535		if len(self.kprobes) < 1:
 536			return
 537		if output:
 538			print('    kprobe functions in this kernel:')
 539		# first test each kprobe
 540		rejects = []
 541		# sort kprobes: trace, ub-dev, custom, dev
 542		kpl = [[], [], [], []]
 543		linesout = len(self.kprobes)
 544		for name in sorted(self.kprobes):
 545			res = self.colorText('YES', 32)
 546			if not self.testKprobe(name, self.kprobes[name]):
 547				res = self.colorText('NO')
 548				rejects.append(name)
 549			else:
 550				if name in self.tracefuncs:
 551					kpl[0].append(name)
 552				elif name in self.dev_tracefuncs:
 553					if 'ub' in self.dev_tracefuncs[name]:
 554						kpl[1].append(name)
 555					else:
 556						kpl[3].append(name)
 557				else:
 558					kpl[2].append(name)
 559			if output:
 560				print('         %s: %s' % (name, res))
 561		kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
 562		# remove all failed ones from the list
 563		for name in rejects:
 564			self.kprobes.pop(name)
 565		# set the kprobes all at once
 566		self.fsetVal('', 'kprobe_events')
 567		kprobeevents = ''
 568		for kp in kplist:
 569			kprobeevents += self.kprobeText(kp, self.kprobes[kp])
 570		self.fsetVal(kprobeevents, 'kprobe_events')
 571		if output:
 572			check = self.fgetVal('kprobe_events')
 573			linesack = (len(check.split('\n')) - 1) / 2
 574			print('    kprobe functions enabled: %d/%d' % (linesack, linesout))
 575		self.fsetVal('1', 'events/kprobes/enable')
 576	def testKprobe(self, kname, kprobe):
 577		self.fsetVal('0', 'events/kprobes/enable')
 578		kprobeevents = self.kprobeText(kname, kprobe)
 579		if not kprobeevents:
 580			return False
 581		try:
 582			self.fsetVal(kprobeevents, 'kprobe_events')
 583			check = self.fgetVal('kprobe_events')
 584		except:
 585			return False
 586		linesout = len(kprobeevents.split('\n'))
 587		linesack = len(check.split('\n'))
 588		if linesack < linesout:
 589			return False
 590		return True
 591	def setVal(self, val, file, mode='w'):
 592		if not os.path.exists(file):
 593			return False
 594		try:
 595			fp = open(file, mode, 0)
 596			fp.write(val)
 597			fp.flush()
 598			fp.close()
 599		except:
 600			return False
 601		return True
 602	def fsetVal(self, val, path, mode='w'):
 603		return self.setVal(val, self.tpath+path, mode)
 604	def getVal(self, file):
 605		res = ''
 606		if not os.path.exists(file):
 607			return res
 608		try:
 609			fp = open(file, 'r')
 610			res = fp.read()
 611			fp.close()
 612		except:
 613			pass
 614		return res
 615	def fgetVal(self, path):
 616		return self.getVal(self.tpath+path)
 617	def cleanupFtrace(self):
 618		if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
 619			self.fsetVal('0', 'events/kprobes/enable')
 620			self.fsetVal('', 'kprobe_events')
 621			self.fsetVal('1024', 'buffer_size_kb')
 622	def setupAllKprobes(self):
 623		for name in self.tracefuncs:
 624			self.defaultKprobe(name, self.tracefuncs[name])
 625		for name in self.dev_tracefuncs:
 626			self.defaultKprobe(name, self.dev_tracefuncs[name])
 627	def isCallgraphFunc(self, name):
 628		if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
 629			return True
 630		for i in self.tracefuncs:
 631			if 'func' in self.tracefuncs[i]:
 632				f = self.tracefuncs[i]['func']
 633			else:
 634				f = i
 635			if name == f:
 636				return True
 637		return False
 638	def initFtrace(self):
 639		self.printSystemInfo(False)
 640		print('INITIALIZING FTRACE...')
 
 641		# turn trace off
 642		self.fsetVal('0', 'tracing_on')
 643		self.cleanupFtrace()
 
 644		# set the trace clock to global
 645		self.fsetVal('global', 'trace_clock')
 646		self.fsetVal('nop', 'current_tracer')
 647		# set trace buffer to an appropriate value
 648		cpus = max(1, self.cpucount)
 649		if self.bufsize > 0:
 650			tgtsize = self.bufsize
 651		elif self.usecallgraph or self.usedevsrc:
 652			tgtsize = min(self.memfree, 3*1024*1024)
 
 
 653		else:
 654			tgtsize = 65536
 655		while not self.fsetVal('%d' % (tgtsize / cpus), 'buffer_size_kb'):
 656			# if the size failed to set, lower it and keep trying
 657			tgtsize -= 65536
 658			if tgtsize < 65536:
 659				tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
 660				break
 661		print 'Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus)
 662		# initialize the callgraph trace
 663		if(self.usecallgraph):
 664			# set trace type
 665			self.fsetVal('function_graph', 'current_tracer')
 666			self.fsetVal('', 'set_ftrace_filter')
 667			# set trace format options
 668			self.fsetVal('print-parent', 'trace_options')
 669			self.fsetVal('funcgraph-abstime', 'trace_options')
 670			self.fsetVal('funcgraph-cpu', 'trace_options')
 671			self.fsetVal('funcgraph-duration', 'trace_options')
 672			self.fsetVal('funcgraph-proc', 'trace_options')
 673			self.fsetVal('funcgraph-tail', 'trace_options')
 674			self.fsetVal('nofuncgraph-overhead', 'trace_options')
 675			self.fsetVal('context-info', 'trace_options')
 676			self.fsetVal('graph-time', 'trace_options')
 677			self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
 678			cf = ['dpm_run_callback']
 679			if(self.usetraceevents):
 680				cf += ['dpm_prepare', 'dpm_complete']
 681			for fn in self.tracefuncs:
 682				if 'func' in self.tracefuncs[fn]:
 683					cf.append(self.tracefuncs[fn]['func'])
 684				else:
 685					cf.append(fn)
 686			self.setFtraceFilterFunctions(cf)
 
 
 
 687		# initialize the kprobe trace
 688		elif self.usekprobes:
 689			for name in self.tracefuncs:
 690				self.defaultKprobe(name, self.tracefuncs[name])
 691			if self.usedevsrc:
 692				for name in self.dev_tracefuncs:
 693					self.defaultKprobe(name, self.dev_tracefuncs[name])
 694			print('INITIALIZING KPROBES...')
 
 695			self.addKprobes(self.verbose)
 696		if(self.usetraceevents):
 697			# turn trace events on
 698			events = iter(self.traceevents)
 699			for e in events:
 700				self.fsetVal('1', 'events/power/'+e+'/enable')
 701		# clear the trace buffer
 702		self.fsetVal('', 'trace')
 703	def verifyFtrace(self):
 704		# files needed for any trace data
 705		files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
 706				 'trace_marker', 'trace_options', 'tracing_on']
 707		# files needed for callgraph trace data
 708		tp = self.tpath
 709		if(self.usecallgraph):
 710			files += [
 711				'available_filter_functions',
 712				'set_ftrace_filter',
 713				'set_graph_function'
 714			]
 715		for f in files:
 716			if(os.path.exists(tp+f) == False):
 717				return False
 718		return True
 719	def verifyKprobes(self):
 720		# files needed for kprobes to work
 721		files = ['kprobe_events', 'events']
 722		tp = self.tpath
 723		for f in files:
 724			if(os.path.exists(tp+f) == False):
 725				return False
 726		return True
 727	def colorText(self, str, color=31):
 728		if not self.ansi:
 729			return str
 730		return '\x1B[%d;40m%s\x1B[m' % (color, str)
 731	def writeDatafileHeader(self, filename, fwdata=[]):
 732		fp = self.openlog(filename, 'w')
 733		fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
 734		if(self.suspendmode == 'mem' or self.suspendmode == 'command'):
 735			for fw in fwdata:
 
 736				if(fw):
 737					fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
 
 
 
 
 
 
 738		return fp
 739	def sudouser(self, dir):
 740		if os.path.exists(dir) and os.getuid() == 0 and \
 741			'SUDO_USER' in os.environ:
 742			cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
 743			call(cmd.format(os.environ['SUDO_USER'], dir), shell=True)
 744	def outputResult(self, testdata, num=0):
 745		if not self.result:
 746			return
 747		n = ''
 748		if num > 0:
 749			n = '%d' % num
 750		fp = open(self.result, 'a')
 751		if 'error' in testdata:
 752			fp.write('result%s: fail\n' % n)
 753			fp.write('error%s: %s\n' % (n, testdata['error']))
 754		else:
 755			fp.write('result%s: pass\n' % n)
 756		for v in ['suspend', 'resume', 'boot', 'lastinit']:
 757			if v in testdata:
 758				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
 759		for v in ['fwsuspend', 'fwresume']:
 760			if v in testdata:
 761				fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
 762		if 'bugurl' in testdata:
 763			fp.write('url%s: %s\n' % (n, testdata['bugurl']))
 764		fp.close()
 765		self.sudouser(self.result)
 766	def configFile(self, file):
 767		dir = os.path.dirname(os.path.realpath(__file__))
 768		if os.path.exists(file):
 769			return file
 770		elif os.path.exists(dir+'/'+file):
 771			return dir+'/'+file
 772		elif os.path.exists(dir+'/config/'+file):
 773			return dir+'/config/'+file
 774		return ''
 775	def openlog(self, filename, mode):
 776		isgz = self.gzip
 777		if mode == 'r':
 778			try:
 779				with gzip.open(filename, mode+'b') as fp:
 780					test = fp.read(64)
 781				isgz = True
 782			except:
 783				isgz = False
 784		if isgz:
 785			return gzip.open(filename, mode+'b')
 786		return open(filename, mode)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 787
 788sysvals = SystemValues()
 789switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
 790switchoff = ['disable', 'off', 'false', '0']
 791suspendmodename = {
 792	'freeze': 'Freeze (S0)',
 793	'standby': 'Standby (S1)',
 794	'mem': 'Suspend (S3)',
 795	'disk': 'Hibernate (S4)'
 796}
 797
 798# Class: DevProps
 799# Description:
 800#	 Simple class which holds property values collected
 801#	 for all the devices used in the timeline.
 802class DevProps:
 803	syspath = ''
 804	altname = ''
 805	async = True
 806	xtraclass = ''
 807	xtrainfo = ''
 
 808	def out(self, dev):
 809		return '%s,%s,%d;' % (dev, self.altname, self.async)
 810	def debug(self, dev):
 811		print '%s:\n\taltname = %s\n\t  async = %s' % (dev, self.altname, self.async)
 812	def altName(self, dev):
 813		if not self.altname or self.altname == dev:
 814			return dev
 815		return '%s [%s]' % (self.altname, dev)
 816	def xtraClass(self):
 817		if self.xtraclass:
 818			return ' '+self.xtraclass
 819		if not self.async:
 820			return ' sync'
 821		return ''
 822	def xtraInfo(self):
 823		if self.xtraclass:
 824			return ' '+self.xtraclass
 825		if self.async:
 826			return ' async_device'
 827		return ' sync_device'
 828
 829# Class: DeviceNode
 830# Description:
 831#	 A container used to create a device hierachy, with a single root node
 832#	 and a tree of child nodes. Used by Data.deviceTopology()
 833class DeviceNode:
 834	name = ''
 835	children = 0
 836	depth = 0
 837	def __init__(self, nodename, nodedepth):
 838		self.name = nodename
 839		self.children = []
 840		self.depth = nodedepth
 841
 842# Class: Data
 843# Description:
 844#	 The primary container for suspend/resume test data. There is one for
 845#	 each test run. The data is organized into a cronological hierarchy:
 846#	 Data.dmesg {
 847#		phases {
 848#			10 sequential, non-overlapping phases of S/R
 849#			contents: times for phase start/end, order/color data for html
 850#			devlist {
 851#				device callback or action list for this phase
 852#				device {
 853#					a single device callback or generic action
 854#					contents: start/stop times, pid/cpu/driver info
 855#						parents/children, html id for timeline/callgraph
 856#						optionally includes an ftrace callgraph
 857#						optionally includes dev/ps data
 858#				}
 859#			}
 860#		}
 861#	}
 862#
 863class Data:
 864	dmesg = {}  # root data structure
 865	phases = [] # ordered list of phases
 866	start = 0.0 # test start
 867	end = 0.0   # test end
 868	tSuspended = 0.0 # low-level suspend start
 869	tResumed = 0.0   # low-level resume start
 870	tKernSus = 0.0   # kernel level suspend start
 871	tKernRes = 0.0   # kernel level resume end
 872	tLow = 0.0       # time spent in low-level suspend (standby/freeze)
 873	fwValid = False  # is firmware data available
 874	fwSuspend = 0    # time spent in firmware suspend
 875	fwResume = 0     # time spent in firmware resume
 876	dmesgtext = []   # dmesg text file in memory
 877	pstl = 0         # process timeline
 878	testnumber = 0
 879	idstr = ''
 880	html_device_id = 0
 881	stamp = 0
 882	outfile = ''
 883	devpids = []
 884	kerror = False
 
 
 
 
 
 
 
 
 
 
 
 885	def __init__(self, num):
 886		idchar = 'abcdefghij'
 887		self.pstl = dict()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 888		self.testnumber = num
 889		self.idstr = idchar[num]
 890		self.dmesgtext = []
 891		self.phases = []
 892		self.dmesg = { # fixed list of 10 phases
 893			'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
 894								'row': 0, 'color': '#CCFFCC', 'order': 0},
 895			        'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
 896								'row': 0, 'color': '#88FF88', 'order': 1},
 897			   'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
 898								'row': 0, 'color': '#00AA00', 'order': 2},
 899			  'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 900								'row': 0, 'color': '#008888', 'order': 3},
 901		    'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 902								'row': 0, 'color': '#0000FF', 'order': 4},
 903			 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
 904								'row': 0, 'color': '#FF0000', 'order': 5},
 905			   'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
 906								'row': 0, 'color': '#FF9900', 'order': 6},
 907			   'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
 908								'row': 0, 'color': '#FFCC00', 'order': 7},
 909			         'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
 910								'row': 0, 'color': '#FFFF88', 'order': 8},
 911			'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
 912								'row': 0, 'color': '#FFFFCC', 'order': 9}
 913		}
 914		self.phases = self.sortedPhases()
 915		self.devicegroups = []
 916		for phase in self.phases:
 917			self.devicegroups.append([phase])
 918		self.errorinfo = {'suspend':[],'resume':[]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 919	def extractErrorInfo(self):
 920		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
 
 
 921		i = 0
 
 922		list = []
 923		# sl = start line, et = error time, el = error line
 924		type = 'ERROR'
 925		sl = et = el = -1
 926		for line in lf:
 927			i += 1
 
 
 928			m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
 929			if not m:
 930				continue
 931			t = float(m.group('ktime'))
 932			if t < self.start or t > self.end:
 933				continue
 934			if t < self.tSuspended:
 935				dir = 'suspend'
 936			else:
 937				dir = 'resume'
 938			msg = m.group('msg')
 939			if re.match('-*\[ *cut here *\]-*', msg):
 940				type = 'WARNING'
 941				sl = i
 942			elif re.match('genirq: .*', msg):
 943				type = 'IRQ'
 944				sl = i
 945			elif re.match('BUG: .*', msg) or re.match('kernel BUG .*', msg):
 946				type = 'BUG'
 947				sl = i
 948			elif re.match('-*\[ *end trace .*\]-*', msg) or \
 949				re.match('R13: .*', msg):
 950				if et >= 0 and sl >= 0:
 951					list.append((type, dir, et, sl, i))
 952					self.kerror = True
 953					sl = et = el = -1
 954					type = 'ERROR'
 955			elif 'Call Trace:' in msg:
 956				if el >= 0 and et >= 0:
 957					list.append((type, dir, et, el, el))
 958					self.kerror = True
 959				et, el = t, i
 960				if sl < 0 or type == 'BUG':
 961					slval = i
 962					if sl >= 0:
 963						slval = sl
 964					list.append((type, dir, et, slval, i))
 965					self.kerror = True
 966					sl = et = el = -1
 967					type = 'ERROR'
 968		if el >= 0 and et >= 0:
 969			list.append((type, dir, et, el, el))
 970			self.kerror = True
 971		for e in list:
 972			type, dir, t, idx1, idx2 = e
 973			sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t))
 974			self.errorinfo[dir].append((type, t, idx1, idx2))
 975		if self.kerror:
 976			sysvals.dmesglog = True
 977		lf.close()
 978	def setStart(self, time):
 
 
 979		self.start = time
 980	def setEnd(self, time):
 
 
 
 
 
 981		self.end = time
 
 
 
 
 
 982	def isTraceEventOutsideDeviceCalls(self, pid, time):
 983		for phase in self.phases:
 984			list = self.dmesg[phase]['list']
 985			for dev in list:
 986				d = list[dev]
 987				if(d['pid'] == pid and time >= d['start'] and
 988					time < d['end']):
 989					return False
 990		return True
 991	def phaseCollision(self, phase, isbegin, line):
 992		key = 'end'
 993		if isbegin:
 994			key = 'start'
 995		if self.dmesg[phase][key] >= 0:
 996			sysvals.vprint('IGNORE: %s' % line.strip())
 997			return True
 998		return False
 999	def sourcePhase(self, start):
1000		for phase in self.phases:
 
 
1001			pend = self.dmesg[phase]['end']
1002			if start <= pend:
1003				return phase
1004		return 'resume_complete'
1005	def sourceDevice(self, phaselist, start, end, pid, type):
1006		tgtdev = ''
1007		for phase in phaselist:
1008			list = self.dmesg[phase]['list']
1009			for devname in list:
1010				dev = list[devname]
1011				# pid must match
1012				if dev['pid'] != pid:
1013					continue
1014				devS = dev['start']
1015				devE = dev['end']
1016				if type == 'device':
1017					# device target event is entirely inside the source boundary
1018					if(start < devS or start >= devE or end <= devS or end > devE):
1019						continue
1020				elif type == 'thread':
1021					# thread target event will expand the source boundary
1022					if start < devS:
1023						dev['start'] = start
1024					if end > devE:
1025						dev['end'] = end
1026				tgtdev = dev
1027				break
1028		return tgtdev
1029	def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1030		# try to place the call in a device
1031		tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device')
 
1032		# calls with device pids that occur outside device bounds are dropped
1033		# TODO: include these somehow
1034		if not tgtdev and pid in self.devpids:
1035			return False
1036		# try to place the call in a thread
1037		if not tgtdev:
1038			tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread')
1039		# create new thread blocks, expand as new calls are found
1040		if not tgtdev:
1041			if proc == '<...>':
1042				threadname = 'kthread-%d' % (pid)
1043			else:
1044				threadname = '%s-%d' % (proc, pid)
1045			tgtphase = self.sourcePhase(start)
1046			self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1047			return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1048		# this should not happen
1049		if not tgtdev:
1050			sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1051				(start, end, proc, pid, kprobename, cdata, rdata))
1052			return False
1053		# place the call data inside the src element of the tgtdev
1054		if('src' not in tgtdev):
1055			tgtdev['src'] = []
1056		dtf = sysvals.dev_tracefuncs
1057		ubiquitous = False
1058		if kprobename in dtf and 'ub' in dtf[kprobename]:
1059			ubiquitous = True
1060		title = cdata+' '+rdata
1061		mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1062		m = re.match(mstr, title)
1063		if m:
1064			c = m.group('caller')
1065			a = m.group('args').strip()
1066			r = m.group('ret')
1067			if len(r) > 6:
1068				r = ''
1069			else:
1070				r = 'ret=%s ' % r
1071			if ubiquitous and c in dtf and 'ub' in dtf[c]:
1072				return False
1073		color = sysvals.kprobeColor(kprobename)
1074		e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1075		tgtdev['src'].append(e)
1076		return True
1077	def overflowDevices(self):
1078		# get a list of devices that extend beyond the end of this test run
1079		devlist = []
1080		for phase in self.phases:
1081			list = self.dmesg[phase]['list']
1082			for devname in list:
1083				dev = list[devname]
1084				if dev['end'] > self.end:
1085					devlist.append(dev)
1086		return devlist
1087	def mergeOverlapDevices(self, devlist):
1088		# merge any devices that overlap devlist
1089		for dev in devlist:
1090			devname = dev['name']
1091			for phase in self.phases:
1092				list = self.dmesg[phase]['list']
1093				if devname not in list:
1094					continue
1095				tdev = list[devname]
1096				o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1097				if o <= 0:
1098					continue
1099				dev['end'] = tdev['end']
1100				if 'src' not in dev or 'src' not in tdev:
1101					continue
1102				dev['src'] += tdev['src']
1103				del list[devname]
1104	def usurpTouchingThread(self, name, dev):
1105		# the caller test has priority of this thread, give it to him
1106		for phase in self.phases:
1107			list = self.dmesg[phase]['list']
1108			if name in list:
1109				tdev = list[name]
1110				if tdev['start'] - dev['end'] < 0.1:
1111					dev['end'] = tdev['end']
1112					if 'src' not in dev:
1113						dev['src'] = []
1114					if 'src' in tdev:
1115						dev['src'] += tdev['src']
1116					del list[name]
1117				break
1118	def stitchTouchingThreads(self, testlist):
1119		# merge any threads between tests that touch
1120		for phase in self.phases:
1121			list = self.dmesg[phase]['list']
1122			for devname in list:
1123				dev = list[devname]
1124				if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1125					continue
1126				for data in testlist:
1127					data.usurpTouchingThread(devname, dev)
1128	def optimizeDevSrc(self):
1129		# merge any src call loops to reduce timeline size
1130		for phase in self.phases:
1131			list = self.dmesg[phase]['list']
1132			for dev in list:
1133				if 'src' not in list[dev]:
1134					continue
1135				src = list[dev]['src']
1136				p = 0
1137				for e in sorted(src, key=lambda event: event.time):
1138					if not p or not e.repeat(p):
1139						p = e
1140						continue
1141					# e is another iteration of p, move it into p
1142					p.end = e.end
1143					p.length = p.end - p.time
1144					p.count += 1
1145					src.remove(e)
1146	def trimTimeVal(self, t, t0, dT, left):
1147		if left:
1148			if(t > t0):
1149				if(t - dT < t0):
1150					return t0
1151				return t - dT
1152			else:
1153				return t
1154		else:
1155			if(t < t0 + dT):
1156				if(t > t0):
1157					return t0 + dT
1158				return t + dT
1159			else:
1160				return t
1161	def trimTime(self, t0, dT, left):
1162		self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1163		self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1164		self.start = self.trimTimeVal(self.start, t0, dT, left)
1165		self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1166		self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1167		self.end = self.trimTimeVal(self.end, t0, dT, left)
1168		for phase in self.phases:
1169			p = self.dmesg[phase]
1170			p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1171			p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1172			list = p['list']
1173			for name in list:
1174				d = list[name]
1175				d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1176				d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
 
1177				if('ftrace' in d):
1178					cg = d['ftrace']
1179					cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1180					cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1181					for line in cg.list:
1182						line.time = self.trimTimeVal(line.time, t0, dT, left)
1183				if('src' in d):
1184					for e in d['src']:
1185						e.time = self.trimTimeVal(e.time, t0, dT, left)
 
 
1186		for dir in ['suspend', 'resume']:
1187			list = []
1188			for e in self.errorinfo[dir]:
1189				type, tm, idx1, idx2 = e
1190				tm = self.trimTimeVal(tm, t0, dT, left)
1191				list.append((type, tm, idx1, idx2))
1192			self.errorinfo[dir] = list
1193	def normalizeTime(self, tZero):
1194		# trim out any standby or freeze clock time
1195		if(self.tSuspended != self.tResumed):
1196			if(self.tResumed > tZero):
1197				self.trimTime(self.tSuspended, \
1198					self.tResumed-self.tSuspended, True)
1199			else:
1200				self.trimTime(self.tSuspended, \
1201					self.tResumed-self.tSuspended, False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202	def getTimeValues(self):
1203		sktime = (self.dmesg['suspend_machine']['end'] - \
1204			self.tKernSus) * 1000
1205		rktime = (self.dmesg['resume_complete']['end'] - \
1206			self.dmesg['resume_machine']['start']) * 1000
1207		return (sktime, rktime)
1208	def setPhase(self, phase, ktime, isbegin):
1209		if(isbegin):
 
 
 
 
 
 
 
 
 
 
 
 
 
1210			self.dmesg[phase]['start'] = ktime
 
1211		else:
 
 
 
 
 
 
 
 
1212			self.dmesg[phase]['end'] = ktime
1213	def dmesgSortVal(self, phase):
1214		return self.dmesg[phase]['order']
1215	def sortedPhases(self):
1216		return sorted(self.dmesg, key=self.dmesgSortVal)
1217	def sortedDevices(self, phase):
1218		list = self.dmesg[phase]['list']
1219		slist = []
1220		tmp = dict()
1221		for devname in list:
1222			dev = list[devname]
1223			if dev['length'] == 0:
1224				continue
1225			tmp[dev['start']] = devname
1226		for t in sorted(tmp):
1227			slist.append(tmp[t])
1228		return slist
1229	def fixupInitcalls(self, phase):
1230		# if any calls never returned, clip them at system resume end
1231		phaselist = self.dmesg[phase]['list']
1232		for devname in phaselist:
1233			dev = phaselist[devname]
1234			if(dev['end'] < 0):
1235				for p in self.phases:
1236					if self.dmesg[p]['end'] > dev['start']:
1237						dev['end'] = self.dmesg[p]['end']
1238						break
1239				sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1240	def deviceFilter(self, devicefilter):
1241		for phase in self.phases:
1242			list = self.dmesg[phase]['list']
1243			rmlist = []
1244			for name in list:
1245				keep = False
1246				for filter in devicefilter:
1247					if filter in name or \
1248						('drv' in list[name] and filter in list[name]['drv']):
1249						keep = True
1250				if not keep:
1251					rmlist.append(name)
1252			for name in rmlist:
1253				del list[name]
1254	def fixupInitcallsThatDidntReturn(self):
1255		# if any calls never returned, clip them at system resume end
1256		for phase in self.phases:
1257			self.fixupInitcalls(phase)
1258	def phaseOverlap(self, phases):
1259		rmgroups = []
1260		newgroup = []
1261		for group in self.devicegroups:
1262			for phase in phases:
1263				if phase not in group:
1264					continue
1265				for p in group:
1266					if p not in newgroup:
1267						newgroup.append(p)
1268				if group not in rmgroups:
1269					rmgroups.append(group)
1270		for group in rmgroups:
1271			self.devicegroups.remove(group)
1272		self.devicegroups.append(newgroup)
1273	def newActionGlobal(self, name, start, end, pid=-1, color=''):
1274		# which phase is this device callback or action in
 
1275		targetphase = 'none'
1276		htmlclass = ''
1277		overlap = 0.0
1278		phases = []
1279		for phase in self.phases:
1280			pstart = self.dmesg[phase]['start']
1281			pend = self.dmesg[phase]['end']
1282			# see if the action overlaps this phase
1283			o = max(0, min(end, pend) - max(start, pstart))
1284			if o > 0:
1285				phases.append(phase)
1286			# set the target phase to the one that overlaps most
1287			if o > overlap:
1288				if overlap > 0 and phase == 'post_resume':
1289					continue
1290				targetphase = phase
1291				overlap = o
1292		# if no target phase was found, pin it to the edge
1293		if targetphase == 'none':
1294			p0start = self.dmesg[self.phases[0]]['start']
1295			if start <= p0start:
1296				targetphase = self.phases[0]
1297			else:
1298				targetphase = self.phases[-1]
1299		if pid == -2:
1300			htmlclass = ' bg'
1301		elif pid == -3:
1302			htmlclass = ' ps'
1303		if len(phases) > 1:
1304			htmlclass = ' bg'
1305			self.phaseOverlap(phases)
1306		if targetphase in self.phases:
1307			newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1308			return (targetphase, newname)
1309		return False
1310	def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1311		# new device callback for a specific phase
1312		self.html_device_id += 1
1313		devid = '%s%d' % (self.idstr, self.html_device_id)
1314		list = self.dmesg[phase]['list']
1315		length = -1.0
1316		if(start >= 0 and end >= 0):
1317			length = end - start
1318		if pid == -2:
1319			i = 2
1320			origname = name
1321			while(name in list):
1322				name = '%s[%d]' % (origname, i)
1323				i += 1
1324		list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1325			'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1326		if htmlclass:
1327			list[name]['htmlclass'] = htmlclass
1328		if color:
1329			list[name]['color'] = color
1330		return name
 
 
 
 
 
 
 
 
 
1331	def deviceChildren(self, devname, phase):
1332		devlist = []
1333		list = self.dmesg[phase]['list']
1334		for child in list:
1335			if(list[child]['par'] == devname):
1336				devlist.append(child)
1337		return devlist
 
 
 
 
 
 
1338	def printDetails(self):
1339		sysvals.vprint('Timeline Details:')
1340		sysvals.vprint('          test start: %f' % self.start)
1341		sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1342		for phase in self.phases:
1343			dc = len(self.dmesg[phase]['list'])
1344			sysvals.vprint('    %16s: %f - %f (%d devices)' % (phase, \
1345				self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1346		sysvals.vprint('   kernel resume end: %f' % self.tKernRes)
1347		sysvals.vprint('            test end: %f' % self.end)
1348	def deviceChildrenAllPhases(self, devname):
1349		devlist = []
1350		for phase in self.phases:
1351			list = self.deviceChildren(devname, phase)
1352			for dev in list:
1353				if dev not in devlist:
1354					devlist.append(dev)
1355		return devlist
1356	def masterTopology(self, name, list, depth):
1357		node = DeviceNode(name, depth)
1358		for cname in list:
1359			# avoid recursions
1360			if name == cname:
1361				continue
1362			clist = self.deviceChildrenAllPhases(cname)
1363			cnode = self.masterTopology(cname, clist, depth+1)
1364			node.children.append(cnode)
1365		return node
1366	def printTopology(self, node):
1367		html = ''
1368		if node.name:
1369			info = ''
1370			drv = ''
1371			for phase in self.phases:
1372				list = self.dmesg[phase]['list']
1373				if node.name in list:
1374					s = list[node.name]['start']
1375					e = list[node.name]['end']
1376					if list[node.name]['drv']:
1377						drv = ' {'+list[node.name]['drv']+'}'
1378					info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1379			html += '<li><b>'+node.name+drv+'</b>'
1380			if info:
1381				html += '<ul>'+info+'</ul>'
1382			html += '</li>'
1383		if len(node.children) > 0:
1384			html += '<ul>'
1385			for cnode in node.children:
1386				html += self.printTopology(cnode)
1387			html += '</ul>'
1388		return html
1389	def rootDeviceList(self):
1390		# list of devices graphed
1391		real = []
1392		for phase in self.dmesg:
1393			list = self.dmesg[phase]['list']
1394			for dev in list:
1395				if list[dev]['pid'] >= 0 and dev not in real:
1396					real.append(dev)
1397		# list of top-most root devices
1398		rootlist = []
1399		for phase in self.dmesg:
1400			list = self.dmesg[phase]['list']
1401			for dev in list:
1402				pdev = list[dev]['par']
1403				pid = list[dev]['pid']
1404				if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1405					continue
1406				if pdev and pdev not in real and pdev not in rootlist:
1407					rootlist.append(pdev)
1408		return rootlist
1409	def deviceTopology(self):
1410		rootlist = self.rootDeviceList()
1411		master = self.masterTopology('', rootlist, 0)
1412		return self.printTopology(master)
1413	def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1414		# only select devices that will actually show up in html
1415		self.tdevlist = dict()
1416		for phase in self.dmesg:
1417			devlist = []
1418			list = self.dmesg[phase]['list']
1419			for dev in list:
1420				length = (list[dev]['end'] - list[dev]['start']) * 1000
1421				width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1422				if width != '0.000000' and length >= mindevlen:
1423					devlist.append(dev)
1424			self.tdevlist[phase] = devlist
1425	def addHorizontalDivider(self, devname, devend):
1426		phase = 'suspend_prepare'
1427		self.newAction(phase, devname, -2, '', \
1428			self.start, devend, '', ' sec', '')
1429		if phase not in self.tdevlist:
1430			self.tdevlist[phase] = []
1431		self.tdevlist[phase].append(devname)
1432		d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1433		return d
1434	def addProcessUsageEvent(self, name, times):
1435		# get the start and end times for this process
1436		maxC = 0
1437		tlast = 0
1438		start = -1
1439		end = -1
1440		for t in sorted(times):
1441			if tlast == 0:
1442				tlast = t
1443				continue
1444			if name in self.pstl[t]:
1445				if start == -1 or tlast < start:
1446					start = tlast
1447				if end == -1 or t > end:
1448					end = t
1449			tlast = t
1450		if start == -1 or end == -1:
1451			return 0
1452		# add a new action for this process and get the object
1453		out = self.newActionGlobal(name, start, end, -3)
1454		if not out:
1455			return 0
1456		phase, devname = out
1457		dev = self.dmesg[phase]['list'][devname]
1458		# get the cpu exec data
1459		tlast = 0
1460		clast = 0
1461		cpuexec = dict()
1462		for t in sorted(times):
1463			if tlast == 0 or t <= start or t > end:
1464				tlast = t
1465				continue
1466			list = self.pstl[t]
1467			c = 0
1468			if name in list:
1469				c = list[name]
1470			if c > maxC:
1471				maxC = c
1472			if c != clast:
1473				key = (tlast, t)
1474				cpuexec[key] = c
1475				tlast = t
1476				clast = c
1477		dev['cpuexec'] = cpuexec
1478		return maxC
1479	def createProcessUsageEvents(self):
1480		# get an array of process names
1481		proclist = []
1482		for t in self.pstl:
1483			pslist = self.pstl[t]
1484			for ps in pslist:
1485				if ps not in proclist:
1486					proclist.append(ps)
1487		# get a list of data points for suspend and resume
1488		tsus = []
1489		tres = []
1490		for t in sorted(self.pstl):
1491			if t < self.tSuspended:
1492				tsus.append(t)
1493			else:
1494				tres.append(t)
1495		# process the events for suspend and resume
1496		if len(proclist) > 0:
1497			sysvals.vprint('Process Execution:')
1498		for ps in proclist:
1499			c = self.addProcessUsageEvent(ps, tsus)
1500			if c > 0:
1501				sysvals.vprint('%25s (sus): %d' % (ps, c))
1502			c = self.addProcessUsageEvent(ps, tres)
1503			if c > 0:
1504				sysvals.vprint('%25s (res): %d' % (ps, c))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1505	def debugPrint(self):
1506		for p in self.phases:
1507			list = self.dmesg[p]['list']
1508			for devname in list:
1509				dev = list[devname]
1510				if 'ftrace' in dev:
1511					dev['ftrace'].debugPrint(' [%s]' % devname)
1512
1513# Class: DevFunction
1514# Description:
1515#	 A container for kprobe function data we want in the dev timeline
1516class DevFunction:
1517	row = 0
1518	count = 1
1519	def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
 
 
1520		self.name = name
1521		self.args = args
1522		self.caller = caller
1523		self.ret = ret
1524		self.time = start
1525		self.length = end - start
1526		self.end = end
1527		self.ubiquitous = u
1528		self.proc = proc
1529		self.pid = pid
1530		self.color = color
1531	def title(self):
1532		cnt = ''
1533		if self.count > 1:
1534			cnt = '(x%d)' % self.count
1535		l = '%0.3fms' % (self.length * 1000)
1536		if self.ubiquitous:
1537			title = '%s(%s)%s <- %s, %s(%s)' % \
1538				(self.name, self.args, cnt, self.caller, self.ret, l)
1539		else:
1540			title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
1541		return title.replace('"', '')
1542	def text(self):
1543		if self.count > 1:
1544			text = '%s(x%d)' % (self.name, self.count)
1545		else:
1546			text = self.name
1547		return text
1548	def repeat(self, tgt):
1549		# is the tgt call just a repeat of this call (e.g. are we in a loop)
1550		dt = self.time - tgt.end
1551		# only combine calls if -all- attributes are identical
1552		if tgt.caller == self.caller and \
1553			tgt.name == self.name and tgt.args == self.args and \
1554			tgt.proc == self.proc and tgt.pid == self.pid and \
1555			tgt.ret == self.ret and dt >= 0 and \
1556			dt <= sysvals.callloopmaxgap and \
1557			self.length < sysvals.callloopmaxlen:
1558			return True
1559		return False
1560
1561# Class: FTraceLine
1562# Description:
1563#	 A container for a single line of ftrace data. There are six basic types:
1564#		 callgraph line:
1565#			  call: "  dpm_run_callback() {"
1566#			return: "  }"
1567#			  leaf: " dpm_run_callback();"
1568#		 trace event:
1569#			 tracing_mark_write: SUSPEND START or RESUME COMPLETE
1570#			 suspend_resume: phase or custom exec block data
1571#			 device_pm_callback: device callback info
1572class FTraceLine:
1573	time = 0.0
1574	length = 0.0
1575	fcall = False
1576	freturn = False
1577	fevent = False
1578	fkprobe = False
1579	depth = 0
1580	name = ''
1581	type = ''
1582	def __init__(self, t, m='', d=''):
 
 
 
 
 
 
 
 
1583		self.time = float(t)
1584		if not m and not d:
1585			return
1586		# is this a trace event
1587		if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1588			if(d == 'traceevent'):
1589				# nop format trace event
1590				msg = m
1591			else:
1592				# function_graph format trace event
1593				em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1594				msg = em.group('msg')
1595
1596			emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1597			if(emm):
1598				self.name = emm.group('msg')
1599				self.type = emm.group('call')
1600			else:
1601				self.name = msg
1602			km = re.match('^(?P<n>.*)_cal$', self.type)
1603			if km:
1604				self.fcall = True
1605				self.fkprobe = True
1606				self.type = km.group('n')
1607				return
1608			km = re.match('^(?P<n>.*)_ret$', self.type)
1609			if km:
1610				self.freturn = True
1611				self.fkprobe = True
1612				self.type = km.group('n')
1613				return
1614			self.fevent = True
1615			return
1616		# convert the duration to seconds
1617		if(d):
1618			self.length = float(d)/1000000
1619		# the indentation determines the depth
1620		match = re.match('^(?P<d> *)(?P<o>.*)$', m)
1621		if(not match):
1622			return
1623		self.depth = self.getDepth(match.group('d'))
1624		m = match.group('o')
1625		# function return
1626		if(m[0] == '}'):
1627			self.freturn = True
1628			if(len(m) > 1):
1629				# includes comment with function name
1630				match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
1631				if(match):
1632					self.name = match.group('n').strip()
1633		# function call
1634		else:
1635			self.fcall = True
1636			# function call with children
1637			if(m[-1] == '{'):
1638				match = re.match('^(?P<n>.*) *\(.*', m)
1639				if(match):
1640					self.name = match.group('n').strip()
1641			# function call with no children (leaf)
1642			elif(m[-1] == ';'):
1643				self.freturn = True
1644				match = re.match('^(?P<n>.*) *\(.*', m)
1645				if(match):
1646					self.name = match.group('n').strip()
1647			# something else (possibly a trace marker)
1648			else:
1649				self.name = m
1650	def isCall(self):
1651		return self.fcall and not self.freturn
1652	def isReturn(self):
1653		return self.freturn and not self.fcall
1654	def isLeaf(self):
1655		return self.fcall and self.freturn
1656	def getDepth(self, str):
1657		return len(str)/2
1658	def debugPrint(self, info=''):
1659		if self.isLeaf():
1660			print(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
1661				self.depth, self.name, self.length*1000000, info))
1662		elif self.freturn:
1663			print(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
1664				self.depth, self.name, self.length*1000000, info))
1665		else:
1666			print(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
1667				self.depth, self.name, self.length*1000000, info))
1668	def startMarker(self):
1669		# Is this the starting line of a suspend?
1670		if not self.fevent:
1671			return False
1672		if sysvals.usetracemarkers:
1673			if(self.name == 'SUSPEND START'):
1674				return True
1675			return False
1676		else:
1677			if(self.type == 'suspend_resume' and
1678				re.match('suspend_enter\[.*\] begin', self.name)):
1679				return True
1680			return False
1681	def endMarker(self):
1682		# Is this the ending line of a resume?
1683		if not self.fevent:
1684			return False
1685		if sysvals.usetracemarkers:
1686			if(self.name == 'RESUME COMPLETE'):
1687				return True
1688			return False
1689		else:
1690			if(self.type == 'suspend_resume' and
1691				re.match('thaw_processes\[.*\] end', self.name)):
1692				return True
1693			return False
1694
1695# Class: FTraceCallGraph
1696# Description:
1697#	 A container for the ftrace callgraph of a single recursive function.
1698#	 This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
1699#	 Each instance is tied to a single device in a single phase, and is
1700#	 comprised of an ordered list of FTraceLine objects
1701class FTraceCallGraph:
1702	id = ''
1703	start = -1.0
1704	end = -1.0
1705	list = []
1706	invalid = False
1707	depth = 0
1708	pid = 0
1709	name = ''
1710	partial = False
1711	vfname = 'missing_function_name'
1712	ignore = False
1713	sv = 0
1714	def __init__(self, pid, sv):
 
 
 
 
 
1715		self.start = -1.0
1716		self.end = -1.0
1717		self.list = []
1718		self.depth = 0
1719		self.pid = pid
1720		self.sv = sv
1721	def addLine(self, line):
1722		# if this is already invalid, just leave
1723		if(self.invalid):
1724			if(line.depth == 0 and line.freturn):
1725				return 1
1726			return 0
1727		# invalidate on bad depth
1728		if(self.depth < 0):
1729			self.invalidate(line)
1730			return 0
1731		# ignore data til we return to the current depth
1732		if self.ignore:
1733			if line.depth > self.depth:
1734				return 0
1735			else:
1736				self.list[-1].freturn = True
1737				self.list[-1].length = line.time - self.list[-1].time
1738				self.ignore = False
1739				# if this is a return at self.depth, no more work is needed
1740				if line.depth == self.depth and line.isReturn():
1741					if line.depth == 0:
1742						self.end = line.time
1743						return 1
1744					return 0
1745		# compare current depth with this lines pre-call depth
1746		prelinedep = line.depth
1747		if line.isReturn():
1748			prelinedep += 1
1749		last = 0
1750		lasttime = line.time
1751		if len(self.list) > 0:
1752			last = self.list[-1]
1753			lasttime = last.time
1754			if last.isLeaf():
1755				lasttime += last.length
1756		# handle low misalignments by inserting returns
1757		mismatch = prelinedep - self.depth
1758		warning = self.sv.verbose and abs(mismatch) > 1
1759		info = []
1760		if mismatch < 0:
1761			idx = 0
1762			# add return calls to get the depth down
1763			while prelinedep < self.depth:
1764				self.depth -= 1
1765				if idx == 0 and last and last.isCall():
1766					# special case, turn last call into a leaf
1767					last.depth = self.depth
1768					last.freturn = True
1769					last.length = line.time - last.time
1770					if warning:
1771						info.append(('[make leaf]', last))
1772				else:
1773					vline = FTraceLine(lasttime)
1774					vline.depth = self.depth
1775					vline.name = self.vfname
1776					vline.freturn = True
1777					self.list.append(vline)
1778					if warning:
1779						if idx == 0:
1780							info.append(('', last))
1781						info.append(('[add return]', vline))
1782				idx += 1
1783			if warning:
1784				info.append(('', line))
1785		# handle high misalignments by inserting calls
1786		elif mismatch > 0:
1787			idx = 0
1788			if warning:
1789				info.append(('', last))
1790			# add calls to get the depth up
1791			while prelinedep > self.depth:
1792				if idx == 0 and line.isReturn():
1793					# special case, turn this return into a leaf
1794					line.fcall = True
1795					prelinedep -= 1
1796					if warning:
1797						info.append(('[make leaf]', line))
1798				else:
1799					vline = FTraceLine(lasttime)
1800					vline.depth = self.depth
1801					vline.name = self.vfname
1802					vline.fcall = True
1803					self.list.append(vline)
1804					self.depth += 1
1805					if not last:
1806						self.start = vline.time
1807					if warning:
1808						info.append(('[add call]', vline))
1809				idx += 1
1810			if warning and ('[make leaf]', line) not in info:
1811				info.append(('', line))
1812		if warning:
1813			print 'WARNING: ftrace data missing, corrections made:'
1814			for i in info:
1815				t, obj = i
1816				if obj:
1817					obj.debugPrint(t)
1818		# process the call and set the new depth
1819		skipadd = False
1820		md = self.sv.max_graph_depth
1821		if line.isCall():
1822			# ignore blacklisted/overdepth funcs
1823			if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
1824				self.ignore = True
1825			else:
1826				self.depth += 1
1827		elif line.isReturn():
1828			self.depth -= 1
1829			# remove blacklisted/overdepth/empty funcs that slipped through
1830			if (last and last.isCall() and last.depth == line.depth) or \
1831				(md and last and last.depth >= md) or \
1832				(line.name in self.sv.cgblacklist):
1833				while len(self.list) > 0 and self.list[-1].depth > line.depth:
1834					self.list.pop(-1)
1835				if len(self.list) == 0:
1836					self.invalid = True
1837					return 1
1838				self.list[-1].freturn = True
1839				self.list[-1].length = line.time - self.list[-1].time
1840				self.list[-1].name = line.name
1841				skipadd = True
1842		if len(self.list) < 1:
1843			self.start = line.time
1844		# check for a mismatch that returned all the way to callgraph end
1845		res = 1
1846		if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
1847			line = self.list[-1]
1848			skipadd = True
1849			res = -1
1850		if not skipadd:
1851			self.list.append(line)
1852		if(line.depth == 0 and line.freturn):
1853			if(self.start < 0):
1854				self.start = line.time
1855			self.end = line.time
1856			if line.fcall:
1857				self.end += line.length
1858			if self.list[0].name == self.vfname:
1859				self.invalid = True
1860			if res == -1:
1861				self.partial = True
1862			return res
1863		return 0
1864	def invalidate(self, line):
1865		if(len(self.list) > 0):
1866			first = self.list[0]
1867			self.list = []
1868			self.list.append(first)
1869		self.invalid = True
1870		id = 'task %s' % (self.pid)
1871		window = '(%f - %f)' % (self.start, line.time)
1872		if(self.depth < 0):
1873			print('Data misalignment for '+id+\
1874				' (buffer overflow), ignoring this callback')
1875		else:
1876			print('Too much data for '+id+\
1877				' '+window+', ignoring this callback')
1878	def slice(self, dev):
1879		minicg = FTraceCallGraph(dev['pid'], self.sv)
1880		minicg.name = self.name
1881		mydepth = -1
1882		good = False
1883		for l in self.list:
1884			if(l.time < dev['start'] or l.time > dev['end']):
1885				continue
1886			if mydepth < 0:
1887				if l.name == 'mutex_lock' and l.freturn:
1888					mydepth = l.depth
1889				continue
1890			elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
1891				good = True
1892				break
1893			l.depth -= mydepth
1894			minicg.addLine(l)
1895		if not good or len(minicg.list) < 1:
1896			return 0
1897		return minicg
1898	def repair(self, enddepth):
1899		# bring the depth back to 0 with additional returns
1900		fixed = False
1901		last = self.list[-1]
1902		for i in reversed(range(enddepth)):
1903			t = FTraceLine(last.time)
1904			t.depth = i
1905			t.freturn = True
1906			fixed = self.addLine(t)
1907			if fixed != 0:
1908				self.end = last.time
1909				return True
1910		return False
1911	def postProcess(self):
1912		if len(self.list) > 0:
1913			self.name = self.list[0].name
1914		stack = dict()
1915		cnt = 0
1916		last = 0
1917		for l in self.list:
1918			# ftrace bug: reported duration is not reliable
1919			# check each leaf and clip it at max possible length
1920			if last and last.isLeaf():
1921				if last.length > l.time - last.time:
1922					last.length = l.time - last.time
1923			if l.isCall():
1924				stack[l.depth] = l
1925				cnt += 1
1926			elif l.isReturn():
1927				if(l.depth not in stack):
1928					if self.sv.verbose:
1929						print 'Post Process Error: Depth missing'
1930						l.debugPrint()
1931					return False
1932				# calculate call length from call/return lines
1933				cl = stack[l.depth]
1934				cl.length = l.time - cl.time
1935				if cl.name == self.vfname:
1936					cl.name = l.name
1937				stack.pop(l.depth)
1938				l.length = 0
1939				cnt -= 1
1940			last = l
1941		if(cnt == 0):
1942			# trace caught the whole call tree
1943			return True
1944		elif(cnt < 0):
1945			if self.sv.verbose:
1946				print 'Post Process Error: Depth is less than 0'
1947			return False
1948		# trace ended before call tree finished
1949		return self.repair(cnt)
1950	def deviceMatch(self, pid, data):
1951		found = ''
1952		# add the callgraph data to the device hierarchy
1953		borderphase = {
1954			'dpm_prepare': 'suspend_prepare',
1955			'dpm_complete': 'resume_complete'
1956		}
1957		if(self.name in borderphase):
1958			p = borderphase[self.name]
1959			list = data.dmesg[p]['list']
1960			for devname in list:
1961				dev = list[devname]
1962				if(pid == dev['pid'] and
1963					self.start <= dev['start'] and
1964					self.end >= dev['end']):
1965					cg = self.slice(dev)
1966					if cg:
1967						dev['ftrace'] = cg
1968					found = devname
1969			return found
1970		for p in data.phases:
1971			if(data.dmesg[p]['start'] <= self.start and
1972				self.start <= data.dmesg[p]['end']):
1973				list = data.dmesg[p]['list']
1974				for devname in list:
1975					dev = list[devname]
1976					if(pid == dev['pid'] and
1977						self.start <= dev['start'] and
1978						self.end >= dev['end']):
1979						dev['ftrace'] = self
1980						found = devname
1981						break
1982				break
1983		return found
1984	def newActionFromFunction(self, data):
1985		name = self.name
1986		if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
1987			return
1988		fs = self.start
1989		fe = self.end
1990		if fs < data.start or fe > data.end:
1991			return
1992		phase = ''
1993		for p in data.phases:
1994			if(data.dmesg[p]['start'] <= self.start and
1995				self.start < data.dmesg[p]['end']):
1996				phase = p
1997				break
1998		if not phase:
1999			return
2000		out = data.newActionGlobal(name, fs, fe, -2)
2001		if out:
2002			phase, myname = out
2003			data.dmesg[phase]['list'][myname]['ftrace'] = self
2004	def debugPrint(self, info=''):
2005		print('%s pid=%d [%f - %f] %.3f us') % \
2006			(self.name, self.pid, self.start, self.end,
2007			(self.end - self.start)*1000000)
2008		for l in self.list:
2009			if l.isLeaf():
2010				print('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2011					l.depth, l.name, l.length*1000000, info))
2012			elif l.freturn:
2013				print('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2014					l.depth, l.name, l.length*1000000, info))
2015			else:
2016				print('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2017					l.depth, l.name, l.length*1000000, info))
2018		print(' ')
2019
2020class DevItem:
2021	def __init__(self, test, phase, dev):
2022		self.test = test
2023		self.phase = phase
2024		self.dev = dev
2025	def isa(self, cls):
2026		if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2027			return True
2028		return False
2029
2030# Class: Timeline
2031# Description:
2032#	 A container for a device timeline which calculates
2033#	 all the html properties to display it correctly
2034class Timeline:
2035	html = ''
2036	height = 0	# total timeline height
2037	scaleH = 20	# timescale (top) row height
2038	rowH = 30	# device row height
2039	bodyH = 0	# body height
2040	rows = 0	# total timeline rows
2041	rowlines = dict()
2042	rowheight = dict()
2043	html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2044	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'
2045	html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2046	html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2047	html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2048	def __init__(self, rowheight, scaleheight):
2049		self.rowH = rowheight
2050		self.scaleH = scaleheight
2051		self.html = ''
 
 
 
 
 
 
 
2052	def createHeader(self, sv, stamp):
2053		if(not stamp['time']):
2054			return
2055		self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \
2056			% (sv.title, sv.version)
2057		if sv.logmsg and sv.testlog:
2058			self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2059		if sv.dmesglog:
2060			self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2061		if sv.ftracelog:
2062			self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2063		headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2064		self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2065			stamp['mode'], stamp['time'])
2066		if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2067			stamp['man'] and stamp['plat'] and stamp['cpu']:
2068			headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2069			self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2070
2071	# Function: getDeviceRows
2072	# Description:
2073	#    determine how may rows the device funcs will take
2074	# Arguments:
2075	#	 rawlist: the list of devices/actions for a single phase
2076	# Output:
2077	#	 The total number of rows needed to display this phase of the timeline
2078	def getDeviceRows(self, rawlist):
2079		# clear all rows and set them to undefined
2080		sortdict = dict()
2081		for item in rawlist:
2082			item.row = -1
2083			sortdict[item] = item.length
2084		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2085		remaining = len(sortlist)
2086		rowdata = dict()
2087		row = 1
2088		# try to pack each row with as many ranges as possible
2089		while(remaining > 0):
2090			if(row not in rowdata):
2091				rowdata[row] = []
2092			for i in sortlist:
2093				if(i.row >= 0):
2094					continue
2095				s = i.time
2096				e = i.time + i.length
2097				valid = True
2098				for ritem in rowdata[row]:
2099					rs = ritem.time
2100					re = ritem.time + ritem.length
2101					if(not (((s <= rs) and (e <= rs)) or
2102						((s >= re) and (e >= re)))):
2103						valid = False
2104						break
2105				if(valid):
2106					rowdata[row].append(i)
2107					i.row = row
2108					remaining -= 1
2109			row += 1
2110		return row
2111	# Function: getPhaseRows
2112	# Description:
2113	#	 Organize the timeline entries into the smallest
2114	#	 number of rows possible, with no entry overlapping
2115	# Arguments:
2116	#	 devlist: the list of devices/actions in a group of contiguous phases
2117	# Output:
2118	#	 The total number of rows needed to display this phase of the timeline
2119	def getPhaseRows(self, devlist, row=0, sortby='length'):
2120		# clear all rows and set them to undefined
2121		remaining = len(devlist)
2122		rowdata = dict()
2123		sortdict = dict()
2124		myphases = []
2125		# initialize all device rows to -1 and calculate devrows
2126		for item in devlist:
2127			dev = item.dev
2128			tp = (item.test, item.phase)
2129			if tp not in myphases:
2130				myphases.append(tp)
2131			dev['row'] = -1
2132			if sortby == 'start':
2133				# sort by start 1st, then length 2nd
2134				sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2135			else:
2136				# sort by length 1st, then name 2nd
2137				sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2138			if 'src' in dev:
2139				dev['devrows'] = self.getDeviceRows(dev['src'])
2140		# sort the devlist by length so that large items graph on top
2141		sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2142		orderedlist = []
2143		for item in sortlist:
2144			if item.dev['pid'] == -2:
2145				orderedlist.append(item)
2146		for item in sortlist:
2147			if item not in orderedlist:
2148				orderedlist.append(item)
2149		# try to pack each row with as many devices as possible
2150		while(remaining > 0):
2151			rowheight = 1
2152			if(row not in rowdata):
2153				rowdata[row] = []
2154			for item in orderedlist:
2155				dev = item.dev
2156				if(dev['row'] < 0):
2157					s = dev['start']
2158					e = dev['end']
2159					valid = True
2160					for ritem in rowdata[row]:
2161						rs = ritem.dev['start']
2162						re = ritem.dev['end']
2163						if(not (((s <= rs) and (e <= rs)) or
2164							((s >= re) and (e >= re)))):
2165							valid = False
2166							break
2167					if(valid):
2168						rowdata[row].append(item)
2169						dev['row'] = row
2170						remaining -= 1
2171						if 'devrows' in dev and dev['devrows'] > rowheight:
2172							rowheight = dev['devrows']
2173			for t, p in myphases:
2174				if t not in self.rowlines or t not in self.rowheight:
2175					self.rowlines[t] = dict()
2176					self.rowheight[t] = dict()
2177				if p not in self.rowlines[t] or p not in self.rowheight[t]:
2178					self.rowlines[t][p] = dict()
2179					self.rowheight[t][p] = dict()
2180				rh = self.rowH
2181				# section headers should use a different row height
2182				if len(rowdata[row]) == 1 and \
2183					'htmlclass' in rowdata[row][0].dev and \
2184					'sec' in rowdata[row][0].dev['htmlclass']:
2185					rh = 15
2186				self.rowlines[t][p][row] = rowheight
2187				self.rowheight[t][p][row] = rowheight * rh
2188			row += 1
2189		if(row > self.rows):
2190			self.rows = int(row)
2191		return row
2192	def phaseRowHeight(self, test, phase, row):
2193		return self.rowheight[test][phase][row]
2194	def phaseRowTop(self, test, phase, row):
2195		top = 0
2196		for i in sorted(self.rowheight[test][phase]):
2197			if i >= row:
2198				break
2199			top += self.rowheight[test][phase][i]
2200		return top
2201	def calcTotalRows(self):
2202		# Calculate the heights and offsets for the header and rows
2203		maxrows = 0
2204		standardphases = []
2205		for t in self.rowlines:
2206			for p in self.rowlines[t]:
2207				total = 0
2208				for i in sorted(self.rowlines[t][p]):
2209					total += self.rowlines[t][p][i]
2210				if total > maxrows:
2211					maxrows = total
2212				if total == len(self.rowlines[t][p]):
2213					standardphases.append((t, p))
2214		self.height = self.scaleH + (maxrows*self.rowH)
2215		self.bodyH = self.height - self.scaleH
2216		# if there is 1 line per row, draw them the standard way
2217		for t, p in standardphases:
2218			for i in sorted(self.rowheight[t][p]):
2219				self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p])
2220	def createZoomBox(self, mode='command', testcount=1):
2221		# Create bounding box, add buttons
2222		html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2223		html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2224		html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2225		html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2226		if mode != 'command':
2227			if testcount > 1:
2228				self.html += html_devlist2
2229				self.html += html_devlist1.format('1')
2230			else:
2231				self.html += html_devlist1.format('')
2232		self.html += html_zoombox
2233		self.html += html_timeline.format('dmesg', self.height)
2234	# Function: createTimeScale
2235	# Description:
2236	#	 Create the timescale for a timeline block
2237	# Arguments:
2238	#	 m0: start time (mode begin)
2239	#	 mMax: end time (mode end)
2240	#	 tTotal: total timeline time
2241	#	 mode: suspend or resume
2242	# Output:
2243	#	 The html code needed to display the time scale
2244	def createTimeScale(self, m0, mMax, tTotal, mode):
2245		timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2246		rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2247		output = '<div class="timescale">\n'
2248		# set scale for timeline
2249		mTotal = mMax - m0
2250		tS = 0.1
2251		if(tTotal <= 0):
2252			return output+'</div>\n'
2253		if(tTotal > 4):
2254			tS = 1
2255		divTotal = int(mTotal/tS) + 1
2256		divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2257		for i in range(divTotal):
2258			htmlline = ''
2259			if(mode == 'suspend'):
2260				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2261				val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2262				if(i == divTotal - 1):
2263					val = mode
2264				htmlline = timescale.format(pos, val)
2265			else:
2266				pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2267				val = '%0.fms' % (float(i)*tS*1000)
2268				htmlline = timescale.format(pos, val)
2269				if(i == 0):
2270					htmlline = rline.format(mode)
2271			output += htmlline
2272		self.html += output+'</div>\n'
2273
2274# Class: TestProps
2275# Description:
2276#	 A list of values describing the properties of these test runs
2277class TestProps:
2278	stamp = ''
2279	sysinfo = ''
2280	cmdline = ''
2281	kparams = ''
2282	S0i3 = False
2283	fwdata = []
2284	stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2285				'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2286				' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
 
 
 
2287	sysinfofmt = '^# sysinfo .*'
2288	cmdlinefmt = '^# command \| (?P<cmd>.*)'
2289	kparamsfmt = '^# kparams \| (?P<kp>.*)'
 
 
 
 
 
2290	ftrace_line_fmt_fg = \
2291		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2292		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2293		'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\|  (?P<msg>.*)'
2294	ftrace_line_fmt_nop = \
2295		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2296		'(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
2297		'(?P<msg>.*)'
2298	ftrace_line_fmt = ftrace_line_fmt_nop
2299	cgformat = False
2300	data = 0
2301	ktemp = dict()
2302	def __init__(self):
 
 
 
 
 
 
 
 
 
 
2303		self.ktemp = dict()
2304	def setTracerType(self, tracer):
2305		if(tracer == 'function_graph'):
2306			self.cgformat = True
2307			self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2308		elif(tracer == 'nop'):
2309			self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2310		else:
2311			doError('Invalid tracer format: [%s]' % tracer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2312	def parseStamp(self, data, sv):
 
2313		m = re.match(self.stampfmt, self.stamp)
 
 
2314		data.stamp = {'time': '', 'host': '', 'mode': ''}
2315		dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2316			int(m.group('d')), int(m.group('H')), int(m.group('M')),
2317			int(m.group('S')))
2318		data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2319		data.stamp['host'] = m.group('host')
2320		data.stamp['mode'] = m.group('mode')
2321		data.stamp['kernel'] = m.group('kernel')
2322		if re.match(self.sysinfofmt, self.sysinfo):
2323			for f in self.sysinfo.split('|'):
2324				if '#' in f:
2325					continue
2326				tmp = f.strip().split(':', 1)
2327				key = tmp[0]
2328				val = tmp[1]
2329				data.stamp[key] = val
2330		sv.hostname = data.stamp['host']
2331		sv.suspendmode = data.stamp['mode']
 
 
 
 
2332		if sv.suspendmode == 'command' and sv.ftracefile != '':
2333			modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2334			out = Popen(['grep', 'machine_suspend', sv.ftracefile],
2335				stderr=PIPE, stdout=PIPE).stdout.read()
2336			m = re.match('.* machine_suspend\[(?P<mode>.*)\]', out)
2337			if m and m.group('mode') in ['1', '2', '3', '4']:
2338				sv.suspendmode = modes[int(m.group('mode'))]
2339				data.stamp['mode'] = sv.suspendmode
2340		m = re.match(self.cmdlinefmt, self.cmdline)
2341		if m:
2342			sv.cmdline = m.group('cmd')
2343		if self.kparams:
2344			m = re.match(self.kparamsfmt, self.kparams)
2345			if m:
2346				sv.kparams = m.group('kp')
2347		if not sv.stamp:
2348			sv.stamp = data.stamp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2349
2350# Class: TestRun
2351# Description:
2352#	 A container for a suspend/resume test run. This is necessary as
2353#	 there could be more than one, and they need to be separate.
2354class TestRun:
2355	ftemp = dict()
2356	ttemp = dict()
2357	data = 0
2358	def __init__(self, dataobj):
2359		self.data = dataobj
2360		self.ftemp = dict()
2361		self.ttemp = dict()
2362
2363class ProcessMonitor:
2364	proclist = dict()
2365	running = False
 
2366	def procstat(self):
2367		c = ['cat /proc/[1-9]*/stat 2>/dev/null']
2368		process = Popen(c, shell=True, stdout=PIPE)
2369		running = dict()
2370		for line in process.stdout:
2371			data = line.split()
2372			pid = data[0]
2373			name = re.sub('[()]', '', data[1])
2374			user = int(data[13])
2375			kern = int(data[14])
2376			kjiff = ujiff = 0
2377			if pid not in self.proclist:
2378				self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
2379			else:
2380				val = self.proclist[pid]
2381				ujiff = user - val['user']
2382				kjiff = kern - val['kern']
2383				val['user'] = user
2384				val['kern'] = kern
2385			if ujiff > 0 or kjiff > 0:
2386				running[pid] = ujiff + kjiff
2387		process.wait()
2388		out = ''
2389		for pid in running:
2390			jiffies = running[pid]
2391			val = self.proclist[pid]
2392			if out:
2393				out += ','
2394			out += '%s-%s %d' % (val['name'], pid, jiffies)
2395		return 'ps - '+out
2396	def processMonitor(self, tid):
2397		while self.running:
2398			out = self.procstat()
2399			if out:
2400				sysvals.fsetVal(out, 'trace_marker')
2401	def start(self):
2402		self.thread = Thread(target=self.processMonitor, args=(0,))
2403		self.running = True
2404		self.thread.start()
2405	def stop(self):
2406		self.running = False
2407
2408# ----------------- FUNCTIONS --------------------
2409
2410# Function: doesTraceLogHaveTraceEvents
2411# Description:
2412#	 Quickly determine if the ftrace log has all of the trace events,
2413#	 markers, and/or kprobes required for primary parsing.
2414def doesTraceLogHaveTraceEvents():
2415	kpcheck = ['_cal: (', '_cpu_down()']
2416	techeck = sysvals.traceevents[:]
2417	tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
2418	sysvals.usekprobes = False
2419	fp = sysvals.openlog(sysvals.ftracefile, 'r')
2420	for line in fp:
2421		# check for kprobes
2422		if not sysvals.usekprobes:
2423			for i in kpcheck:
2424				if i in line:
2425					sysvals.usekprobes = True
2426		# check for all necessary trace events
2427		check = techeck[:]
2428		for i in techeck:
2429			if i in line:
2430				check.remove(i)
2431		techeck = check
2432		# check for all necessary trace markers
2433		check = tmcheck[:]
2434		for i in tmcheck:
2435			if i in line:
2436				check.remove(i)
2437		tmcheck = check
2438	fp.close()
2439	if len(techeck) == 0:
2440		sysvals.usetraceevents = True
2441	else:
2442		sysvals.usetraceevents = False
2443	if len(tmcheck) == 0:
2444		sysvals.usetracemarkers = True
2445	else:
2446		sysvals.usetracemarkers = False
2447
2448# Function: appendIncompleteTraceLog
2449# Description:
2450#	 [deprecated for kernel 3.15 or newer]
2451#	 Legacy support of ftrace outputs that lack the device_pm_callback
2452#	 and/or suspend_resume trace events. The primary data should be
2453#	 taken from dmesg, and this ftrace is used only for callgraph data
2454#	 or custom actions in the timeline. The data is appended to the Data
2455#	 objects provided.
2456# Arguments:
2457#	 testruns: the array of Data objects obtained from parseKernelLog
2458def appendIncompleteTraceLog(testruns):
2459	# create TestRun vessels for ftrace parsing
2460	testcnt = len(testruns)
2461	testidx = 0
2462	testrun = []
2463	for data in testruns:
2464		testrun.append(TestRun(data))
2465
2466	# extract the callgraph and traceevent data
2467	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2468		os.path.basename(sysvals.ftracefile))
2469	tp = TestProps()
2470	tf = sysvals.openlog(sysvals.ftracefile, 'r')
2471	data = 0
2472	for line in tf:
2473		# remove any latent carriage returns
2474		line = line.replace('\r\n', '')
2475		# grab the stamp and sysinfo
2476		if re.match(tp.stampfmt, line):
2477			tp.stamp = line
2478			continue
2479		elif re.match(tp.sysinfofmt, line):
2480			tp.sysinfo = line
2481			continue
2482		elif re.match(tp.cmdlinefmt, line):
2483			tp.cmdline = line
2484			continue
2485		# determine the trace data type (required for further parsing)
2486		m = re.match(sysvals.tracertypefmt, line)
2487		if(m):
2488			tp.setTracerType(m.group('t'))
2489			continue
2490		# device properties line
2491		if(re.match(sysvals.devpropfmt, line)):
2492			devProps(line)
2493			continue
2494		# parse only valid lines, if this is not one move on
2495		m = re.match(tp.ftrace_line_fmt, line)
2496		if(not m):
2497			continue
2498		# gather the basic message data from the line
2499		m_time = m.group('time')
2500		m_pid = m.group('pid')
2501		m_msg = m.group('msg')
2502		if(tp.cgformat):
2503			m_param3 = m.group('dur')
2504		else:
2505			m_param3 = 'traceevent'
2506		if(m_time and m_pid and m_msg):
2507			t = FTraceLine(m_time, m_msg, m_param3)
2508			pid = int(m_pid)
2509		else:
2510			continue
2511		# the line should be a call, return, or event
2512		if(not t.fcall and not t.freturn and not t.fevent):
2513			continue
2514		# look for the suspend start marker
2515		if(t.startMarker()):
2516			data = testrun[testidx].data
2517			tp.parseStamp(data, sysvals)
2518			data.setStart(t.time)
2519			continue
2520		if(not data):
2521			continue
2522		# find the end of resume
2523		if(t.endMarker()):
2524			data.setEnd(t.time)
2525			testidx += 1
2526			if(testidx >= testcnt):
2527				break
2528			continue
2529		# trace event processing
2530		if(t.fevent):
2531			# general trace events have two types, begin and end
2532			if(re.match('(?P<name>.*) begin$', t.name)):
2533				isbegin = True
2534			elif(re.match('(?P<name>.*) end$', t.name)):
2535				isbegin = False
2536			else:
2537				continue
2538			m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2539			if(m):
2540				val = m.group('val')
2541				if val == '0':
2542					name = m.group('name')
2543				else:
2544					name = m.group('name')+'['+val+']'
2545			else:
2546				m = re.match('(?P<name>.*) .*', t.name)
2547				name = m.group('name')
2548			# special processing for trace events
2549			if re.match('dpm_prepare\[.*', name):
2550				continue
2551			elif re.match('machine_suspend.*', name):
2552				continue
2553			elif re.match('suspend_enter\[.*', name):
2554				if(not isbegin):
2555					data.dmesg['suspend_prepare']['end'] = t.time
2556				continue
2557			elif re.match('dpm_suspend\[.*', name):
2558				if(not isbegin):
2559					data.dmesg['suspend']['end'] = t.time
2560				continue
2561			elif re.match('dpm_suspend_late\[.*', name):
2562				if(isbegin):
2563					data.dmesg['suspend_late']['start'] = t.time
2564				else:
2565					data.dmesg['suspend_late']['end'] = t.time
2566				continue
2567			elif re.match('dpm_suspend_noirq\[.*', name):
2568				if(isbegin):
2569					data.dmesg['suspend_noirq']['start'] = t.time
2570				else:
2571					data.dmesg['suspend_noirq']['end'] = t.time
2572				continue
2573			elif re.match('dpm_resume_noirq\[.*', name):
2574				if(isbegin):
2575					data.dmesg['resume_machine']['end'] = t.time
2576					data.dmesg['resume_noirq']['start'] = t.time
2577				else:
2578					data.dmesg['resume_noirq']['end'] = t.time
2579				continue
2580			elif re.match('dpm_resume_early\[.*', name):
2581				if(isbegin):
2582					data.dmesg['resume_early']['start'] = t.time
2583				else:
2584					data.dmesg['resume_early']['end'] = t.time
2585				continue
2586			elif re.match('dpm_resume\[.*', name):
2587				if(isbegin):
2588					data.dmesg['resume']['start'] = t.time
2589				else:
2590					data.dmesg['resume']['end'] = t.time
2591				continue
2592			elif re.match('dpm_complete\[.*', name):
2593				if(isbegin):
2594					data.dmesg['resume_complete']['start'] = t.time
2595				else:
2596					data.dmesg['resume_complete']['end'] = t.time
2597				continue
2598			# skip trace events inside devices calls
2599			if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2600				continue
2601			# global events (outside device calls) are simply graphed
2602			if(isbegin):
2603				# store each trace event in ttemp
2604				if(name not in testrun[testidx].ttemp):
2605					testrun[testidx].ttemp[name] = []
2606				testrun[testidx].ttemp[name].append(\
2607					{'begin': t.time, 'end': t.time})
2608			else:
2609				# finish off matching trace event in ttemp
2610				if(name in testrun[testidx].ttemp):
2611					testrun[testidx].ttemp[name][-1]['end'] = t.time
2612		# call/return processing
2613		elif sysvals.usecallgraph:
2614			# create a callgraph object for the data
2615			if(pid not in testrun[testidx].ftemp):
2616				testrun[testidx].ftemp[pid] = []
2617				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2618			# when the call is finished, see which device matches it
2619			cg = testrun[testidx].ftemp[pid][-1]
2620			res = cg.addLine(t)
2621			if(res != 0):
2622				testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2623			if(res == -1):
2624				testrun[testidx].ftemp[pid][-1].addLine(t)
2625	tf.close()
2626
2627	for test in testrun:
2628		# add the traceevent data to the device hierarchy
2629		if(sysvals.usetraceevents):
2630			for name in test.ttemp:
2631				for event in test.ttemp[name]:
2632					test.data.newActionGlobal(name, event['begin'], event['end'])
2633
2634		# add the callgraph data to the device hierarchy
2635		for pid in test.ftemp:
2636			for cg in test.ftemp[pid]:
2637				if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
2638					continue
2639				if(not cg.postProcess()):
2640					id = 'task %s cpu %s' % (pid, m.group('cpu'))
2641					sysvals.vprint('Sanity check failed for '+\
2642						id+', ignoring this callback')
2643					continue
2644				callstart = cg.start
2645				callend = cg.end
2646				for p in test.data.phases:
2647					if(test.data.dmesg[p]['start'] <= callstart and
2648						callstart <= test.data.dmesg[p]['end']):
2649						list = test.data.dmesg[p]['list']
2650						for devname in list:
2651							dev = list[devname]
2652							if(pid == dev['pid'] and
2653								callstart <= dev['start'] and
2654								callend >= dev['end']):
2655								dev['ftrace'] = cg
2656						break
2657
2658# Function: parseTraceLog
2659# Description:
2660#	 Analyze an ftrace log output file generated from this app during
2661#	 the execution phase. Used when the ftrace log is the primary data source
2662#	 and includes the suspend_resume and device_pm_callback trace events
2663#	 The ftrace filename is taken from sysvals
2664# Output:
2665#	 An array of Data objects
2666def parseTraceLog(live=False):
2667	sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2668		os.path.basename(sysvals.ftracefile))
2669	if(os.path.exists(sysvals.ftracefile) == False):
2670		doError('%s does not exist' % sysvals.ftracefile)
2671	if not live:
2672		sysvals.setupAllKprobes()
2673	tracewatch = []
 
 
2674	if sysvals.usekprobes:
2675		tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
2676			'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', 'CPU_OFF']
 
2677
2678	# extract the callgraph and traceevent data
 
2679	tp = TestProps()
2680	testruns = []
2681	testdata = []
2682	testrun = 0
2683	data = 0
2684	tf = sysvals.openlog(sysvals.ftracefile, 'r')
2685	phase = 'suspend_prepare'
2686	for line in tf:
2687		# remove any latent carriage returns
2688		line = line.replace('\r\n', '')
2689		# stamp and sysinfo lines
2690		if re.match(tp.stampfmt, line):
2691			tp.stamp = line
2692			continue
2693		elif re.match(tp.sysinfofmt, line):
2694			tp.sysinfo = line
2695			continue
2696		elif re.match(tp.cmdlinefmt, line):
2697			tp.cmdline = line
2698			continue
2699		# firmware line: pull out any firmware data
2700		m = re.match(sysvals.firmwarefmt, line)
2701		if(m):
2702			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
2703			continue
2704		# tracer type line: determine the trace data type
2705		m = re.match(sysvals.tracertypefmt, line)
2706		if(m):
2707			tp.setTracerType(m.group('t'))
2708			continue
2709		# device properties line
2710		if(re.match(sysvals.devpropfmt, line)):
2711			devProps(line)
2712			continue
2713		# ignore all other commented lines
2714		if line[0] == '#':
2715			continue
2716		# ftrace line: parse only valid lines
2717		m = re.match(tp.ftrace_line_fmt, line)
2718		if(not m):
2719			continue
2720		# gather the basic message data from the line
2721		m_time = m.group('time')
2722		m_proc = m.group('proc')
2723		m_pid = m.group('pid')
2724		m_msg = m.group('msg')
2725		if(tp.cgformat):
2726			m_param3 = m.group('dur')
2727		else:
2728			m_param3 = 'traceevent'
2729		if(m_time and m_pid and m_msg):
2730			t = FTraceLine(m_time, m_msg, m_param3)
2731			pid = int(m_pid)
2732		else:
2733			continue
2734		# the line should be a call, return, or event
2735		if(not t.fcall and not t.freturn and not t.fevent):
2736			continue
2737		# find the start of suspend
2738		if(t.startMarker()):
2739			phase = 'suspend_prepare'
2740			data = Data(len(testdata))
2741			testdata.append(data)
2742			testrun = TestRun(data)
2743			testruns.append(testrun)
2744			tp.parseStamp(data, sysvals)
2745			data.setStart(t.time)
2746			data.tKernSus = t.time
 
2747			continue
2748		if(not data):
2749			continue
2750		# process cpu exec line
2751		if t.type == 'tracing_mark_write':
2752			m = re.match(sysvals.procexecfmt, t.name)
2753			if(m):
2754				proclist = dict()
2755				for ps in m.group('ps').split(','):
2756					val = ps.split()
2757					if not val:
2758						continue
2759					name = val[0].replace('--', '-')
2760					proclist[name] = int(val[1])
2761				data.pstl[t.time] = proclist
2762				continue
2763		# find the end of resume
2764		if(t.endMarker()):
2765			data.setEnd(t.time)
2766			if data.tKernRes == 0.0:
2767				data.tKernRes = t.time
2768			if data.dmesg['resume_complete']['end'] < 0:
2769				data.dmesg['resume_complete']['end'] = t.time
2770			if sysvals.suspendmode == 'mem' and len(tp.fwdata) > data.testnumber:
2771				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2772				if(data.tSuspended != 0 and data.tResumed != 0 and \
2773					(data.fwSuspend > 0 or data.fwResume > 0)):
2774					data.fwValid = True
2775			if(not sysvals.usetracemarkers):
2776				# no trace markers? then quit and be sure to finish recording
2777				# the event we used to trigger resume end
2778				if(len(testrun.ttemp['thaw_processes']) > 0):
2779					# if an entry exists, assume this is its end
2780					testrun.ttemp['thaw_processes'][-1]['end'] = t.time
2781				break
2782			continue
2783		# trace event processing
2784		if(t.fevent):
2785			if(phase == 'post_resume'):
2786				data.setEnd(t.time)
2787			if(t.type == 'suspend_resume'):
2788				# suspend_resume trace events have two types, begin and end
2789				if(re.match('(?P<name>.*) begin$', t.name)):
2790					isbegin = True
2791				elif(re.match('(?P<name>.*) end$', t.name)):
2792					isbegin = False
2793				else:
2794					continue
2795				m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2796				if(m):
2797					val = m.group('val')
2798					if val == '0':
2799						name = m.group('name')
2800					else:
2801						name = m.group('name')+'['+val+']'
2802				else:
2803					m = re.match('(?P<name>.*) .*', t.name)
2804					name = m.group('name')
2805				# ignore these events
2806				if(name.split('[')[0] in tracewatch):
2807					continue
2808				# -- phase changes --
2809				# start of kernel suspend
2810				if(re.match('suspend_enter\[.*', t.name)):
2811					if(isbegin):
2812						data.dmesg[phase]['start'] = t.time
2813						data.tKernSus = t.time
2814					continue
2815				# suspend_prepare start
2816				elif(re.match('dpm_prepare\[.*', t.name)):
2817					phase = 'suspend_prepare'
2818					if(not isbegin):
2819						data.dmesg[phase]['end'] = t.time
2820						if data.dmesg[phase]['start'] < 0:
2821							data.dmesg[phase]['start'] = data.start
 
2822					continue
2823				# suspend start
2824				elif(re.match('dpm_suspend\[.*', t.name)):
2825					phase = 'suspend'
2826					data.setPhase(phase, t.time, isbegin)
2827					continue
2828				# suspend_late start
2829				elif(re.match('dpm_suspend_late\[.*', t.name)):
2830					phase = 'suspend_late'
2831					data.setPhase(phase, t.time, isbegin)
2832					continue
2833				# suspend_noirq start
2834				elif(re.match('dpm_suspend_noirq\[.*', t.name)):
2835					if data.phaseCollision('suspend_noirq', isbegin, line):
2836						continue
2837					phase = 'suspend_noirq'
2838					data.setPhase(phase, t.time, isbegin)
2839					if(not isbegin):
2840						phase = 'suspend_machine'
2841						data.dmesg[phase]['start'] = t.time
2842					continue
2843				# suspend_machine/resume_machine
2844				elif(re.match('machine_suspend\[.*', t.name)):
 
2845					if(isbegin):
2846						phase = 'suspend_machine'
2847						data.dmesg[phase]['end'] = t.time
2848						data.tSuspended = t.time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2849					else:
2850						if(sysvals.suspendmode in ['mem', 'disk'] and not tp.S0i3):
2851							data.dmesg['suspend_machine']['end'] = t.time
 
 
 
 
 
 
2852							data.tSuspended = t.time
2853						phase = 'resume_machine'
2854						data.dmesg[phase]['start'] = t.time
2855						data.tResumed = t.time
2856						data.tLow = data.tResumed - data.tSuspended
2857					continue
2858				# acpi_suspend
2859				elif(re.match('acpi_suspend\[.*', t.name)):
2860					# acpi_suspend[0] S0i3
2861					if(re.match('acpi_suspend\[0\] begin', t.name)):
2862						if(sysvals.suspendmode == 'mem'):
2863							tp.S0i3 = True
2864							data.dmesg['suspend_machine']['end'] = t.time
2865							data.tSuspended = t.time
2866					continue
2867				# resume_noirq start
2868				elif(re.match('dpm_resume_noirq\[.*', t.name)):
2869					if data.phaseCollision('resume_noirq', isbegin, line):
2870						continue
2871					phase = 'resume_noirq'
2872					data.setPhase(phase, t.time, isbegin)
2873					if(isbegin):
2874						data.dmesg['resume_machine']['end'] = t.time
2875					continue
2876				# resume_early start
2877				elif(re.match('dpm_resume_early\[.*', t.name)):
2878					phase = 'resume_early'
2879					data.setPhase(phase, t.time, isbegin)
2880					continue
2881				# resume start
2882				elif(re.match('dpm_resume\[.*', t.name)):
2883					phase = 'resume'
2884					data.setPhase(phase, t.time, isbegin)
2885					continue
2886				# resume complete start
2887				elif(re.match('dpm_complete\[.*', t.name)):
2888					phase = 'resume_complete'
2889					if(isbegin):
2890						data.dmesg[phase]['start'] = t.time
2891					continue
2892				# skip trace events inside devices calls
2893				if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2894					continue
2895				# global events (outside device calls) are graphed
2896				if(name not in testrun.ttemp):
2897					testrun.ttemp[name] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
2898				if(isbegin):
2899					# create a new list entry
2900					testrun.ttemp[name].append(\
2901						{'begin': t.time, 'end': t.time, 'pid': pid})
2902				else:
2903					if(len(testrun.ttemp[name]) > 0):
2904						# if an entry exists, assume this is its end
2905						testrun.ttemp[name][-1]['end'] = t.time
2906					elif(phase == 'post_resume'):
2907						# post resume events can just have ends
2908						testrun.ttemp[name].append({
2909							'begin': data.dmesg[phase]['start'],
2910							'end': t.time})
2911			# device callback start
2912			elif(t.type == 'device_pm_callback_start'):
 
 
2913				m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
2914					t.name);
2915				if(not m):
2916					continue
2917				drv = m.group('drv')
2918				n = m.group('d')
2919				p = m.group('p')
2920				if(n and p):
2921					data.newAction(phase, n, pid, p, t.time, -1, drv)
2922					if pid not in data.devpids:
2923						data.devpids.append(pid)
2924			# device callback finish
2925			elif(t.type == 'device_pm_callback_end'):
 
 
2926				m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
2927				if(not m):
2928					continue
2929				n = m.group('d')
2930				list = data.dmesg[phase]['list']
2931				if(n in list):
2932					dev = list[n]
2933					dev['length'] = t.time - dev['start']
2934					dev['end'] = t.time
2935		# kprobe event processing
2936		elif(t.fkprobe):
2937			kprobename = t.type
2938			kprobedata = t.name
2939			key = (kprobename, pid)
2940			# displayname is generated from kprobe data
2941			displayname = ''
2942			if(t.fcall):
2943				displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
2944				if not displayname:
2945					continue
2946				if(key not in tp.ktemp):
2947					tp.ktemp[key] = []
2948				tp.ktemp[key].append({
2949					'pid': pid,
2950					'begin': t.time,
2951					'end': t.time,
2952					'name': displayname,
2953					'cdata': kprobedata,
2954					'proc': m_proc,
2955				})
 
 
 
 
2956			elif(t.freturn):
2957				if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
2958					continue
2959				e = tp.ktemp[key][-1]
2960				if e['begin'] < 0.0 or t.time - e['begin'] < 0.000001:
2961					tp.ktemp[key].pop()
2962				else:
2963					e['end'] = t.time
2964					e['rdata'] = kprobedata
2965				# end of kernel resume
2966				if(kprobename == 'pm_notifier_call_chain' or \
2967					kprobename == 'pm_restore_console'):
2968					data.dmesg[phase]['end'] = t.time
2969					data.tKernRes = t.time
2970
2971		# callgraph processing
2972		elif sysvals.usecallgraph:
2973			# create a callgraph object for the data
2974			key = (m_proc, pid)
2975			if(key not in testrun.ftemp):
2976				testrun.ftemp[key] = []
2977				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2978			# when the call is finished, see which device matches it
2979			cg = testrun.ftemp[key][-1]
2980			res = cg.addLine(t)
2981			if(res != 0):
2982				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2983			if(res == -1):
2984				testrun.ftemp[key][-1].addLine(t)
2985	tf.close()
 
 
 
 
 
2986
2987	if sysvals.suspendmode == 'command':
2988		for test in testruns:
2989			for p in test.data.phases:
2990				if p == 'suspend_prepare':
2991					test.data.dmesg[p]['start'] = test.data.start
2992					test.data.dmesg[p]['end'] = test.data.end
2993				else:
2994					test.data.dmesg[p]['start'] = test.data.end
2995					test.data.dmesg[p]['end'] = test.data.end
2996			test.data.tSuspended = test.data.end
2997			test.data.tResumed = test.data.end
2998			test.data.tLow = 0
2999			test.data.fwValid = False
3000
3001	# dev source and procmon events can be unreadable with mixed phase height
3002	if sysvals.usedevsrc or sysvals.useprocmon:
3003		sysvals.mixedphaseheight = False
3004
 
 
 
 
 
 
 
 
3005	for i in range(len(testruns)):
3006		test = testruns[i]
3007		data = test.data
3008		# find the total time range for this test (begin, end)
3009		tlb, tle = data.start, data.end
3010		if i < len(testruns) - 1:
3011			tle = testruns[i+1].data.start
3012		# add the process usage data to the timeline
3013		if sysvals.useprocmon:
3014			data.createProcessUsageEvents()
3015		# add the traceevent data to the device hierarchy
3016		if(sysvals.usetraceevents):
3017			# add actual trace funcs
3018			for name in test.ttemp:
3019				for event in test.ttemp[name]:
3020					data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
 
 
 
 
 
3021			# add the kprobe based virtual tracefuncs as actual devices
3022			for key in tp.ktemp:
3023				name, pid = key
3024				if name not in sysvals.tracefuncs:
3025					continue
 
 
3026				for e in tp.ktemp[key]:
3027					kb, ke = e['begin'], e['end']
3028					if kb == ke or tlb > kb or tle <= kb:
3029						continue
3030					color = sysvals.kprobeColor(name)
3031					data.newActionGlobal(e['name'], kb, ke, pid, color)
3032			# add config base kprobes and dev kprobes
3033			if sysvals.usedevsrc:
3034				for key in tp.ktemp:
3035					name, pid = key
3036					if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3037						continue
3038					for e in tp.ktemp[key]:
3039						kb, ke = e['begin'], e['end']
3040						if kb == ke or tlb > kb or tle <= kb:
3041							continue
3042						data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3043							ke, e['cdata'], e['rdata'])
3044		if sysvals.usecallgraph:
3045			# add the callgraph data to the device hierarchy
3046			sortlist = dict()
3047			for key in test.ftemp:
3048				proc, pid = key
3049				for cg in test.ftemp[key]:
3050					if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3051						continue
3052					if(not cg.postProcess()):
3053						id = 'task %s' % (pid)
3054						sysvals.vprint('Sanity check failed for '+\
3055							id+', ignoring this callback')
3056						continue
3057					# match cg data to devices
3058					devname = ''
3059					if sysvals.suspendmode != 'command':
3060						devname = cg.deviceMatch(pid, data)
3061					if not devname:
3062						sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3063						sortlist[sortkey] = cg
3064					elif len(cg.list) > 1000000:
3065						print 'WARNING: the callgraph for %s is massive (%d lines)' %\
3066							(devname, len(cg.list))
3067			# create blocks for orphan cg data
3068			for sortkey in sorted(sortlist):
3069				cg = sortlist[sortkey]
3070				name = cg.name
3071				if sysvals.isCallgraphFunc(name):
3072					sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3073					cg.newActionFromFunction(data)
3074	if sysvals.suspendmode == 'command':
3075		return testdata
3076
3077	# fill in any missing phases
 
3078	for data in testdata:
3079		lp = data.phases[0]
3080		for p in data.phases:
3081			if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
 
 
 
 
 
 
 
 
 
 
 
 
 
3082				sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3083			if(data.dmesg[p]['start'] < 0):
3084				data.dmesg[p]['start'] = data.dmesg[lp]['end']
3085				if(p == 'resume_machine'):
3086					data.tSuspended = data.dmesg[lp]['end']
3087					data.tResumed = data.dmesg[lp]['end']
3088					data.tLow = 0
3089			if(data.dmesg[p]['end'] < 0):
3090				data.dmesg[p]['end'] = data.dmesg[p]['start']
3091			if(p != lp and not ('machine' in p and 'machine' in lp)):
3092				data.dmesg[lp]['end'] = data.dmesg[p]['start']
3093			lp = p
 
 
 
 
 
 
 
 
 
 
 
 
3094
3095		if(len(sysvals.devicefilter) > 0):
3096			data.deviceFilter(sysvals.devicefilter)
3097		data.fixupInitcallsThatDidntReturn()
3098		if sysvals.usedevsrc:
3099			data.optimizeDevSrc()
3100
3101	# x2: merge any overlapping devices between test runs
3102	if sysvals.usedevsrc and len(testdata) > 1:
3103		tc = len(testdata)
3104		for i in range(tc - 1):
3105			devlist = testdata[i].overflowDevices()
3106			for j in range(i + 1, tc):
3107				testdata[j].mergeOverlapDevices(devlist)
3108		testdata[0].stitchTouchingThreads(testdata[1:])
3109	return testdata
3110
3111# Function: loadKernelLog
3112# Description:
3113#	 [deprecated for kernel 3.15.0 or newer]
3114#	 load the dmesg file into memory and fix up any ordering issues
3115#	 The dmesg filename is taken from sysvals
3116# Output:
3117#	 An array of empty Data objects with only their dmesgtext attributes set
3118def loadKernelLog():
3119	sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3120		os.path.basename(sysvals.dmesgfile))
3121	if(os.path.exists(sysvals.dmesgfile) == False):
3122		doError('%s does not exist' % sysvals.dmesgfile)
3123
3124	# there can be multiple test runs in a single file
3125	tp = TestProps()
3126	tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3127	testruns = []
3128	data = 0
3129	lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3130	for line in lf:
3131		line = line.replace('\r\n', '')
3132		idx = line.find('[')
3133		if idx > 1:
3134			line = line[idx:]
3135		# grab the stamp and sysinfo
3136		if re.match(tp.stampfmt, line):
3137			tp.stamp = line
3138			continue
3139		elif re.match(tp.sysinfofmt, line):
3140			tp.sysinfo = line
3141			continue
3142		elif re.match(tp.cmdlinefmt, line):
3143			tp.cmdline = line
3144			continue
3145		m = re.match(sysvals.firmwarefmt, line)
3146		if(m):
3147			tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
3148			continue
3149		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3150		if(not m):
3151			continue
3152		msg = m.group("msg")
3153		if(re.match('PM: Syncing filesystems.*', msg)):
3154			if(data):
3155				testruns.append(data)
3156			data = Data(len(testruns))
3157			tp.parseStamp(data, sysvals)
3158			if len(tp.fwdata) > data.testnumber:
3159				data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
3160				if(data.fwSuspend > 0 or data.fwResume > 0):
3161					data.fwValid = True
3162		if(not data):
3163			continue
3164		m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3165		if(m):
3166			sysvals.stamp['kernel'] = m.group('k')
3167		m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3168		if(m):
3169			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3170		data.dmesgtext.append(line)
3171	lf.close()
3172
3173	if data:
3174		testruns.append(data)
3175	if len(testruns) < 1:
3176		doError(' dmesg log has no suspend/resume data: %s' \
3177			% sysvals.dmesgfile)
3178
3179	# fix lines with same timestamp/function with the call and return swapped
3180	for data in testruns:
3181		last = ''
3182		for line in data.dmesgtext:
3183			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
3184				'(?P<f>.*)\+ @ .*, parent: .*', line)
3185			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3186				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3187			if(mc and mr and (mc.group('t') == mr.group('t')) and
3188				(mc.group('f') == mr.group('f'))):
3189				i = data.dmesgtext.index(last)
3190				j = data.dmesgtext.index(line)
3191				data.dmesgtext[i] = line
3192				data.dmesgtext[j] = last
3193			last = line
3194	return testruns
3195
3196# Function: parseKernelLog
3197# Description:
3198#	 [deprecated for kernel 3.15.0 or newer]
3199#	 Analyse a dmesg log output file generated from this app during
3200#	 the execution phase. Create a set of device structures in memory
3201#	 for subsequent formatting in the html output file
3202#	 This call is only for legacy support on kernels where the ftrace
3203#	 data lacks the suspend_resume or device_pm_callbacks trace events.
3204# Arguments:
3205#	 data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3206# Output:
3207#	 The filled Data object
3208def parseKernelLog(data):
3209	phase = 'suspend_runtime'
3210
3211	if(data.fwValid):
3212		sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3213			(data.fwSuspend, data.fwResume))
3214
3215	# dmesg phase match table
3216	dm = {
3217		'suspend_prepare': 'PM: Syncing filesystems.*',
3218		        'suspend': 'PM: Entering [a-z]* sleep.*',
3219		   'suspend_late': 'PM: suspend of devices complete after.*',
3220		  'suspend_noirq': 'PM: late suspend of devices complete after.*',
3221		'suspend_machine': 'PM: noirq suspend of devices complete after.*',
3222		 'resume_machine': 'ACPI: Low-level resume complete.*',
3223		   'resume_noirq': 'ACPI: Waking up from system sleep state.*',
3224		   'resume_early': 'PM: noirq resume of devices complete after.*',
3225		         'resume': 'PM: early resume of devices complete after.*',
3226		'resume_complete': 'PM: resume of devices complete after.*',
3227		    'post_resume': '.*Restarting tasks \.\.\..*',
3228	}
3229	if(sysvals.suspendmode == 'standby'):
3230		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3231	elif(sysvals.suspendmode == 'disk'):
3232		dm['suspend_late'] = 'PM: freeze of devices complete after.*'
3233		dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
3234		dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
3235		dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3236		dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
3237		dm['resume'] = 'PM: early restore of devices complete after.*'
3238		dm['resume_complete'] = 'PM: restore of devices complete after.*'
3239	elif(sysvals.suspendmode == 'freeze'):
3240		dm['resume_machine'] = 'ACPI: resume from mwait'
3241
3242	# action table (expected events that occur and show up in dmesg)
3243	at = {
3244		'sync_filesystems': {
3245			'smsg': 'PM: Syncing filesystems.*',
3246			'emsg': 'PM: Preparing system for mem sleep.*' },
3247		'freeze_user_processes': {
3248			'smsg': 'Freezing user space processes .*',
3249			'emsg': 'Freezing remaining freezable tasks.*' },
3250		'freeze_tasks': {
3251			'smsg': 'Freezing remaining freezable tasks.*',
3252			'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3253		'ACPI prepare': {
3254			'smsg': 'ACPI: Preparing to enter system sleep state.*',
3255			'emsg': 'PM: Saving platform NVS memory.*' },
3256		'PM vns': {
3257			'smsg': 'PM: Saving platform NVS memory.*',
3258			'emsg': 'Disabling non-boot CPUs .*' },
3259	}
3260
3261	t0 = -1.0
3262	cpu_start = -1.0
3263	prevktime = -1.0
3264	actions = dict()
3265	for line in data.dmesgtext:
3266		# parse each dmesg line into the time and message
3267		m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3268		if(m):
3269			val = m.group('ktime')
3270			try:
3271				ktime = float(val)
3272			except:
3273				continue
3274			msg = m.group('msg')
3275			# initialize data start to first line time
3276			if t0 < 0:
3277				data.setStart(ktime)
3278				t0 = ktime
3279		else:
3280			continue
3281
 
 
 
 
 
 
 
 
3282		# hack for determining resume_machine end for freeze
3283		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3284			and phase == 'resume_machine' and \
3285			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3286			data.dmesg['resume_machine']['end'] = ktime
3287			phase = 'resume_noirq'
3288			data.dmesg[phase]['start'] = ktime
3289
3290		# suspend start
3291		if(re.match(dm['suspend_prepare'], msg)):
3292			phase = 'suspend_prepare'
3293			data.dmesg[phase]['start'] = ktime
3294			data.setStart(ktime)
3295			data.tKernSus = ktime
3296		# suspend start
3297		elif(re.match(dm['suspend'], msg)):
3298			data.dmesg['suspend_prepare']['end'] = ktime
3299			phase = 'suspend'
3300			data.dmesg[phase]['start'] = ktime
3301		# suspend_late start
3302		elif(re.match(dm['suspend_late'], msg)):
3303			data.dmesg['suspend']['end'] = ktime
3304			phase = 'suspend_late'
3305			data.dmesg[phase]['start'] = ktime
3306		# suspend_noirq start
3307		elif(re.match(dm['suspend_noirq'], msg)):
3308			data.dmesg['suspend_late']['end'] = ktime
3309			phase = 'suspend_noirq'
3310			data.dmesg[phase]['start'] = ktime
3311		# suspend_machine start
3312		elif(re.match(dm['suspend_machine'], msg)):
3313			data.dmesg['suspend_noirq']['end'] = ktime
3314			phase = 'suspend_machine'
3315			data.dmesg[phase]['start'] = ktime
3316		# resume_machine start
3317		elif(re.match(dm['resume_machine'], msg)):
3318			if(sysvals.suspendmode in ['freeze', 'standby']):
3319				data.tSuspended = prevktime
3320				data.dmesg['suspend_machine']['end'] = prevktime
3321			else:
3322				data.tSuspended = ktime
3323				data.dmesg['suspend_machine']['end'] = ktime
3324			phase = 'resume_machine'
3325			data.tResumed = ktime
3326			data.tLow = data.tResumed - data.tSuspended
3327			data.dmesg[phase]['start'] = ktime
3328		# resume_noirq start
3329		elif(re.match(dm['resume_noirq'], msg)):
3330			data.dmesg['resume_machine']['end'] = ktime
3331			phase = 'resume_noirq'
3332			data.dmesg[phase]['start'] = ktime
3333		# resume_early start
3334		elif(re.match(dm['resume_early'], msg)):
3335			data.dmesg['resume_noirq']['end'] = ktime
3336			phase = 'resume_early'
3337			data.dmesg[phase]['start'] = ktime
3338		# resume start
3339		elif(re.match(dm['resume'], msg)):
3340			data.dmesg['resume_early']['end'] = ktime
3341			phase = 'resume'
3342			data.dmesg[phase]['start'] = ktime
3343		# resume complete start
3344		elif(re.match(dm['resume_complete'], msg)):
3345			data.dmesg['resume']['end'] = ktime
3346			phase = 'resume_complete'
3347			data.dmesg[phase]['start'] = ktime
3348		# post resume start
3349		elif(re.match(dm['post_resume'], msg)):
3350			data.dmesg['resume_complete']['end'] = ktime
3351			data.setEnd(ktime)
3352			data.tKernRes = ktime
3353			break
3354
3355		# -- device callbacks --
3356		if(phase in data.phases):
3357			# device init call
3358			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
3359				sm = re.match('calling  (?P<f>.*)\+ @ '+\
3360					'(?P<n>.*), parent: (?P<p>.*)', msg);
3361				f = sm.group('f')
3362				n = sm.group('n')
3363				p = sm.group('p')
3364				if(f and n and p):
3365					data.newAction(phase, f, int(n), p, ktime, -1, '')
3366			# device init return
3367			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3368				'(?P<t>.*) usecs', msg)):
3369				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3370					'(?P<t>.*) usecs(?P<a>.*)', msg);
3371				f = sm.group('f')
3372				t = sm.group('t')
3373				list = data.dmesg[phase]['list']
3374				if(f in list):
3375					dev = list[f]
3376					dev['length'] = int(t)
3377					dev['end'] = ktime
3378
3379		# if trace events are not available, these are better than nothing
3380		if(not sysvals.usetraceevents):
3381			# look for known actions
3382			for a in at:
3383				if(re.match(at[a]['smsg'], msg)):
3384					if(a not in actions):
3385						actions[a] = []
3386					actions[a].append({'begin': ktime, 'end': ktime})
3387				if(re.match(at[a]['emsg'], msg)):
3388					if(a in actions):
3389						actions[a][-1]['end'] = ktime
3390			# now look for CPU on/off events
3391			if(re.match('Disabling non-boot CPUs .*', msg)):
3392				# start of first cpu suspend
3393				cpu_start = ktime
3394			elif(re.match('Enabling non-boot CPUs .*', msg)):
3395				# start of first cpu resume
3396				cpu_start = ktime
3397			elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3398				# end of a cpu suspend, start of the next
3399				m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3400				cpu = 'CPU'+m.group('cpu')
3401				if(cpu not in actions):
3402					actions[cpu] = []
3403				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3404				cpu_start = ktime
3405			elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3406				# end of a cpu resume, start of the next
3407				m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3408				cpu = 'CPU'+m.group('cpu')
3409				if(cpu not in actions):
3410					actions[cpu] = []
3411				actions[cpu].append({'begin': cpu_start, 'end': ktime})
3412				cpu_start = ktime
3413		prevktime = ktime
 
3414
3415	# fill in any missing phases
3416	lp = data.phases[0]
3417	for p in data.phases:
3418		if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3419			print('WARNING: phase "%s" is missing, something went wrong!' % p)
3420			print('    In %s, this dmesg line denotes the start of %s:' % \
3421				(sysvals.suspendmode, p))
3422			print('        "%s"' % dm[p])
3423		if(data.dmesg[p]['start'] < 0):
3424			data.dmesg[p]['start'] = data.dmesg[lp]['end']
3425			if(p == 'resume_machine'):
3426				data.tSuspended = data.dmesg[lp]['end']
3427				data.tResumed = data.dmesg[lp]['end']
3428				data.tLow = 0
3429		if(data.dmesg[p]['end'] < 0):
3430			data.dmesg[p]['end'] = data.dmesg[p]['start']
 
 
3431		lp = p
 
 
 
 
3432
3433	# fill in any actions we've found
3434	for name in actions:
3435		for event in actions[name]:
3436			data.newActionGlobal(name, event['begin'], event['end'])
3437
3438	if(len(sysvals.devicefilter) > 0):
3439		data.deviceFilter(sysvals.devicefilter)
3440	data.fixupInitcallsThatDidntReturn()
3441	return True
3442
3443def callgraphHTML(sv, hf, num, cg, title, color, devid):
3444	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'
3445	html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3446	html_func_end = '</article>\n'
3447	html_func_leaf = '<article>{0} {1}</article>\n'
3448
3449	cgid = devid
3450	if cg.id:
3451		cgid += cg.id
3452	cglen = (cg.end - cg.start) * 1000
3453	if cglen < sv.mincglen:
3454		return num
3455
3456	fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
3457	flen = fmt % (cglen, cg.start, cg.end)
3458	hf.write(html_func_top.format(cgid, color, num, title, flen))
3459	num += 1
3460	for line in cg.list:
3461		if(line.length < 0.000000001):
3462			flen = ''
3463		else:
3464			fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
3465			flen = fmt % (line.length*1000, line.time)
3466		if line.isLeaf():
3467			hf.write(html_func_leaf.format(line.name, flen))
3468		elif line.freturn:
3469			hf.write(html_func_end)
3470		else:
3471			hf.write(html_func_start.format(num, line.name, flen))
3472			num += 1
3473	hf.write(html_func_end)
3474	return num
3475
3476def addCallgraphs(sv, hf, data):
3477	hf.write('<section id="callgraphs" class="callgraph">\n')
3478	# write out the ftrace data converted to html
3479	num = 0
3480	for p in data.phases:
3481		if sv.cgphase and p != sv.cgphase:
3482			continue
3483		list = data.dmesg[p]['list']
3484		for devname in data.sortedDevices(p):
3485			if len(sv.cgfilter) > 0 and devname not in sv.cgfilter:
3486				continue
3487			dev = list[devname]
3488			color = 'white'
3489			if 'color' in data.dmesg[p]:
3490				color = data.dmesg[p]['color']
3491			if 'color' in dev:
3492				color = dev['color']
3493			name = devname
3494			if(devname in sv.devprops):
3495				name = sv.devprops[devname].altName(devname)
 
 
3496			if sv.suspendmode in suspendmodename:
3497				name += ' '+p
3498			if('ftrace' in dev):
3499				cg = dev['ftrace']
 
 
3500				num = callgraphHTML(sv, hf, num, cg,
3501					name, color, dev['id'])
3502			if('ftraces' in dev):
3503				for cg in dev['ftraces']:
3504					num = callgraphHTML(sv, hf, num, cg,
3505						name+' &rarr; '+cg.name, color, dev['id'])
3506	hf.write('\n\n    </section>\n')
3507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3508# Function: createHTMLSummarySimple
3509# Description:
3510#	 Create summary html file for a series of tests
3511# Arguments:
3512#	 testruns: array of Data objects from parseTraceLog
3513def createHTMLSummarySimple(testruns, htmlfile, folder):
3514	# write the html header first (html head, css code, up to body start)
3515	html = '<!DOCTYPE html>\n<html>\n<head>\n\
3516	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3517	<title>SleepGraph Summary</title>\n\
3518	<style type=\'text/css\'>\n\
3519		.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
3520		table {width:100%;border-collapse: collapse;}\n\
3521		.summary {border:1px solid;}\n\
3522		th {border: 1px solid black;background:#222;color:white;}\n\
3523		td {font: 16px "Times New Roman";text-align: center;}\n\
3524		tr.alt td {background:#ddd;}\n\
3525		tr.avg td {background:#aaa;}\n\
3526	</style>\n</head>\n<body>\n'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3527
3528	# group test header
3529	html += '<div class="stamp">%s (%d tests)</div>\n' % (folder, len(testruns))
 
 
 
 
3530	th = '\t<th>{0}</th>\n'
3531	td = '\t<td>{0}</td>\n'
 
3532	tdlink = '\t<td><a href="{0}">html</a></td>\n'
 
 
 
 
 
 
3533
3534	# table header
3535	html += '<table class="summary">\n<tr>\n' + th.format('#') +\
3536		th.format('Mode') + th.format('Host') + th.format('Kernel') +\
3537		th.format('Test Time') + th.format('Suspend') + th.format('Resume') +\
3538		th.format('Detail') + '</tr>\n'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3539
3540	# test data, 1 row per test
3541	avg = '<tr class="avg"><td></td><td></td><td></td><td></td>'+\
3542		'<td>Average of {0} {1} tests</td><td>{2}</td><td>{3}</td><td></td></tr>\n'
3543	sTimeAvg = rTimeAvg = 0.0
3544	mode = ''
3545	num = 0
3546	for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
3547		if mode != data['mode']:
3548			# test average line
3549			if(num > 0):
3550				sTimeAvg /= (num - 1)
3551				rTimeAvg /= (num - 1)
3552				html += avg.format('%d' % (num - 1), mode,
3553					'%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg)
3554			sTimeAvg = rTimeAvg = 0.0
3555			mode = data['mode']
3556			num = 1
3557		# alternate row color
3558		if num % 2 == 1:
3559			html += '<tr class="alt">\n'
3560		else:
3561			html += '<tr>\n'
3562		html += td.format("%d" % num)
 
3563		num += 1
3564		# basic info
3565		for item in ['mode', 'host', 'kernel', 'time']:
3566			val = "unknown"
3567			if(item in data):
3568				val = data[item]
3569			html += td.format(val)
3570		# suspend time
3571		sTime = float(data['suspend'])
3572		sTimeAvg += sTime
3573		html += td.format('%.3f ms' % sTime)
3574		# resume time
3575		rTime = float(data['resume'])
3576		rTimeAvg += rTime
3577		html += td.format('%.3f ms' % rTime)
3578		# link to the output html
3579		html += tdlink.format(data['url']) + '</tr>\n'
3580	# last test average line
3581	if(num > 0):
3582		sTimeAvg /= (num - 1)
3583		rTimeAvg /= (num - 1)
3584		html += avg.format('%d' % (num - 1), mode,
3585			'%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg)
3586
3587	# flush the data to file
3588	hf = open(htmlfile, 'w')
3589	hf.write(html+'</table>\n</body>\n</html>\n')
3590	hf.close()
 
3591
3592def ordinal(value):
3593	suffix = 'th'
3594	if value < 10 or value > 19:
3595		if value % 10 == 1:
3596			suffix = 'st'
3597		elif value % 10 == 2:
3598			suffix = 'nd'
3599		elif value % 10 == 3:
3600			suffix = 'rd'
3601	return '%d%s' % (value, suffix)
3602
3603# Function: createHTML
3604# Description:
3605#	 Create the output html file from the resident test data
3606# Arguments:
3607#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
3608# Output:
3609#	 True if the html file was created, false if it failed
3610def createHTML(testruns):
3611	if len(testruns) < 1:
3612		print('ERROR: Not enough test data to build a timeline')
3613		return
3614
3615	kerror = False
3616	for data in testruns:
3617		if data.kerror:
3618			kerror = True
3619		data.normalizeTime(testruns[-1].tSuspended)
 
 
 
3620
3621	# html function templates
3622	html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
3623	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'
3624	html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
3625	html_timetotal = '<table class="time1">\n<tr>'\
3626		'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
3627		'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
3628		'</tr>\n</table>\n'
3629	html_timetotal2 = '<table class="time1">\n<tr>'\
3630		'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
3631		'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
3632		'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
3633		'</tr>\n</table>\n'
3634	html_timetotal3 = '<table class="time1">\n<tr>'\
3635		'<td class="green">Execution Time: <b>{0} ms</b></td>'\
3636		'<td class="yellow">Command: <b>{1}</b></td>'\
3637		'</tr>\n</table>\n'
3638	html_timegroups = '<table class="time2">\n<tr>'\
3639		'<td class="green" title="time from kernel enter_state({5}) to firmware mode [kernel time only]">{4}Kernel Suspend: {0} ms</td>'\
3640		'<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
3641		'<td class="purple">{4}Firmware Resume: {2} ms</td>'\
3642		'<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
3643		'</tr>\n</table>\n'
3644
3645	# html format variables
3646	scaleH = 20
3647	if kerror:
3648		scaleH = 40
3649
3650	# device timeline
3651	devtl = Timeline(30, scaleH)
3652
3653	# write the test title and general info header
3654	devtl.createHeader(sysvals, testruns[0].stamp)
3655
3656	# Generate the header for this timeline
3657	for data in testruns:
3658		tTotal = data.end - data.start
3659		sktime, rktime = data.getTimeValues()
3660		if(tTotal == 0):
3661			doError('No timeline data')
3662		if(data.tLow > 0):
3663			low_time = '%.0f'%(data.tLow*1000)
3664		if sysvals.suspendmode == 'command':
3665			run_time = '%.0f'%((data.end-data.start)*1000)
3666			if sysvals.testcommand:
3667				testdesc = sysvals.testcommand
3668			else:
3669				testdesc = 'unknown'
3670			if(len(testruns) > 1):
3671				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3672			thtml = html_timetotal3.format(run_time, testdesc)
3673			devtl.html += thtml
3674		elif data.fwValid:
3675			suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0))
3676			resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0))
3677			testdesc1 = 'Total'
3678			testdesc2 = ''
3679			stitle = 'time from kernel enter_state(%s) to low-power mode [kernel & firmware time]' % sysvals.suspendmode
3680			rtitle = 'time from low-power mode to return from kernel enter_state(%s) [firmware & kernel time]' % sysvals.suspendmode
3681			if(len(testruns) > 1):
3682				testdesc1 = testdesc2 = ordinal(data.testnumber+1)
3683				testdesc2 += ' '
3684			if(data.tLow == 0):
3685				thtml = html_timetotal.format(suspend_time, \
3686					resume_time, testdesc1, stitle, rtitle)
3687			else:
3688				thtml = html_timetotal2.format(suspend_time, low_time, \
3689					resume_time, testdesc1, stitle, rtitle)
3690			devtl.html += thtml
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3691			sftime = '%.3f'%(data.fwSuspend / 1000000.0)
3692			rftime = '%.3f'%(data.fwResume / 1000000.0)
3693			devtl.html += html_timegroups.format('%.3f'%sktime, \
3694				sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode)
3695		else:
3696			suspend_time = '%.3f' % sktime
3697			resume_time = '%.3f' % rktime
3698			testdesc = 'Kernel'
3699			stitle = 'time from kernel enter_state(%s) to firmware mode [kernel time only]' % sysvals.suspendmode
3700			rtitle = 'time from firmware mode to return from kernel enter_state(%s) [kernel time only]' % sysvals.suspendmode
3701			if(len(testruns) > 1):
3702				testdesc = ordinal(data.testnumber+1)+' '+testdesc
3703			if(data.tLow == 0):
3704				thtml = html_timetotal.format(suspend_time, \
3705					resume_time, testdesc, stitle, rtitle)
3706			else:
3707				thtml = html_timetotal2.format(suspend_time, low_time, \
3708					resume_time, testdesc, stitle, rtitle)
3709			devtl.html += thtml
 
 
 
3710
3711	# time scale for potentially multiple datasets
3712	t0 = testruns[0].start
3713	tMax = testruns[-1].end
3714	tTotal = tMax - t0
3715
3716	# determine the maximum number of rows we need to draw
3717	fulllist = []
3718	threadlist = []
3719	pscnt = 0
3720	devcnt = 0
3721	for data in testruns:
3722		data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
3723		for group in data.devicegroups:
3724			devlist = []
3725			for phase in group:
3726				for devname in data.tdevlist[phase]:
3727					d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
3728					devlist.append(d)
3729					if d.isa('kth'):
3730						threadlist.append(d)
3731					else:
3732						if d.isa('ps'):
3733							pscnt += 1
3734						else:
3735							devcnt += 1
3736						fulllist.append(d)
3737			if sysvals.mixedphaseheight:
3738				devtl.getPhaseRows(devlist)
3739	if not sysvals.mixedphaseheight:
3740		if len(threadlist) > 0 and len(fulllist) > 0:
3741			if pscnt > 0 and devcnt > 0:
3742				msg = 'user processes & device pm callbacks'
3743			elif pscnt > 0:
3744				msg = 'user processes'
3745			else:
3746				msg = 'device pm callbacks'
3747			d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
3748			fulllist.insert(0, d)
3749		devtl.getPhaseRows(fulllist)
3750		if len(threadlist) > 0:
3751			d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
3752			threadlist.insert(0, d)
3753			devtl.getPhaseRows(threadlist, devtl.rows)
3754	devtl.calcTotalRows()
3755
3756	# draw the full timeline
3757	devtl.createZoomBox(sysvals.suspendmode, len(testruns))
3758	phases = {'suspend':[],'resume':[]}
3759	for phase in data.dmesg:
3760		if 'resume' in phase:
3761			phases['resume'].append(phase)
3762		else:
3763			phases['suspend'].append(phase)
3764
3765	# draw each test run chronologically
3766	for data in testruns:
 
 
 
 
 
 
 
3767		# now draw the actual timeline blocks
3768		for dir in phases:
3769			# draw suspend and resume blocks separately
3770			bname = '%s%d' % (dir[0], data.testnumber)
3771			if dir == 'suspend':
3772				m0 = data.start
3773				mMax = data.tSuspended
3774				left = '%f' % (((m0-t0)*100.0)/tTotal)
3775			else:
3776				m0 = data.tSuspended
3777				mMax = data.end
3778				# in an x2 run, remove any gap between blocks
3779				if len(testruns) > 1 and data.testnumber == 0:
3780					mMax = testruns[1].start
3781				left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
3782			mTotal = mMax - m0
3783			# if a timeline block is 0 length, skip altogether
3784			if mTotal == 0:
3785				continue
3786			width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
3787			devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
3788			for b in sorted(phases[dir]):
3789				# draw the phase color background
3790				phase = data.dmesg[b]
3791				length = phase['end']-phase['start']
3792				left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
3793				width = '%f' % ((length*100.0)/mTotal)
3794				devtl.html += devtl.html_phase.format(left, width, \
3795					'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
3796					data.dmesg[b]['color'], '')
3797			for e in data.errorinfo[dir]:
3798				# draw red lines for any kernel errors found
3799				type, t, idx1, idx2 = e
3800				id = '%d_%d' % (idx1, idx2)
3801				right = '%f' % (((mMax-t)*100.0)/mTotal)
3802				devtl.html += html_error.format(right, id, type)
3803			for b in sorted(phases[dir]):
3804				# draw the devices for this phase
3805				phaselist = data.dmesg[b]['list']
3806				for d in data.tdevlist[b]:
3807					name = d
3808					drv = ''
3809					dev = phaselist[d]
3810					xtraclass = ''
3811					xtrainfo = ''
3812					xtrastyle = ''
3813					if 'htmlclass' in dev:
3814						xtraclass = dev['htmlclass']
3815					if 'color' in dev:
3816						xtrastyle = 'background:%s;' % dev['color']
3817					if(d in sysvals.devprops):
3818						name = sysvals.devprops[d].altName(d)
3819						xtraclass = sysvals.devprops[d].xtraClass()
3820						xtrainfo = sysvals.devprops[d].xtraInfo()
3821					elif xtraclass == ' kth':
3822						xtrainfo = ' kernel_thread'
3823					if('drv' in dev and dev['drv']):
3824						drv = ' {%s}' % dev['drv']
3825					rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
3826					rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
3827					top = '%.3f' % (rowtop + devtl.scaleH)
3828					left = '%f' % (((dev['start']-m0)*100)/mTotal)
3829					width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
3830					length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
3831					title = name+drv+xtrainfo+length
3832					if sysvals.suspendmode == 'command':
3833						title += sysvals.testcommand
3834					elif xtraclass == ' ps':
3835						if 'suspend' in b:
3836							title += 'pre_suspend_process'
3837						else:
3838							title += 'post_resume_process'
3839					else:
3840						title += b
3841					devtl.html += devtl.html_device.format(dev['id'], \
3842						title, left, top, '%.3f'%rowheight, width, \
3843						d+drv, xtraclass, xtrastyle)
3844					if('cpuexec' in dev):
3845						for t in sorted(dev['cpuexec']):
3846							start, end = t
3847							j = float(dev['cpuexec'][t]) / 5
3848							if j > 1.0:
3849								j = 1.0
3850							height = '%.3f' % (rowheight/3)
3851							top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
3852							left = '%f' % (((start-m0)*100)/mTotal)
3853							width = '%f' % ((end-start)*100/mTotal)
3854							color = 'rgba(255, 0, 0, %f)' % j
3855							devtl.html += \
3856								html_cpuexec.format(left, top, height, width, color)
3857					if('src' not in dev):
3858						continue
3859					# draw any trace events for this device
3860					for e in dev['src']:
 
 
3861						height = '%.3f' % devtl.rowH
3862						top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
3863						left = '%f' % (((e.time-m0)*100)/mTotal)
3864						width = '%f' % (e.length*100/mTotal)
3865						xtrastyle = ''
3866						if e.color:
3867							xtrastyle = 'background:%s;' % e.color
3868						devtl.html += \
3869							html_traceevent.format(e.title(), \
3870								left, top, height, width, e.text(), '', xtrastyle)
3871			# draw the time scale, try to make the number of labels readable
3872			devtl.createTimeScale(m0, mMax, tTotal, dir)
3873			devtl.html += '</div>\n'
3874
3875	# timeline is finished
3876	devtl.html += '</div>\n</div>\n'
3877
3878	# draw a legend which describes the phases by color
3879	if sysvals.suspendmode != 'command':
3880		data = testruns[-1]
3881		devtl.html += '<div class="legend">\n'
3882		pdelta = 100.0/len(data.phases)
3883		pmargin = pdelta / 4.0
3884		for phase in data.phases:
3885			tmp = phase.split('_')
3886			id = tmp[0][0]
3887			if(len(tmp) > 1):
3888				id += tmp[1][0]
3889			order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3890			name = string.replace(phase, '_', ' &nbsp;')
3891			devtl.html += devtl.html_legend.format(order, \
3892				data.dmesg[phase]['color'], name, id)
3893		devtl.html += '</div>\n'
3894
3895	hf = open(sysvals.htmlfile, 'w')
3896	addCSS(hf, sysvals, len(testruns), kerror)
3897
3898	# write the device timeline
3899	hf.write(devtl.html)
3900	hf.write('<div id="devicedetailtitle"></div>\n')
3901	hf.write('<div id="devicedetail" style="display:none;">\n')
3902	# draw the colored boxes for the device detail section
3903	for data in testruns:
3904		hf.write('<div id="devicedetail%d">\n' % data.testnumber)
3905		pscolor = 'linear-gradient(to top left, #ccc, #eee)'
3906		hf.write(devtl.html_phaselet.format('pre_suspend_process', \
3907			'0', '0', pscolor))
3908		for b in data.phases:
3909			phase = data.dmesg[b]
3910			length = phase['end']-phase['start']
3911			left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
3912			width = '%.3f' % ((length*100.0)/tTotal)
3913			hf.write(devtl.html_phaselet.format(b, left, width, \
3914				data.dmesg[b]['color']))
3915		hf.write(devtl.html_phaselet.format('post_resume_process', \
3916			'0', '0', pscolor))
3917		if sysvals.suspendmode == 'command':
3918			hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
3919		hf.write('</div>\n')
3920	hf.write('</div>\n')
3921
3922	# write the ftrace data (callgraph)
3923	if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
3924		data = testruns[sysvals.cgtest]
3925	else:
3926		data = testruns[-1]
3927	if sysvals.usecallgraph:
3928		addCallgraphs(sysvals, hf, data)
3929
3930	# add the test log as a hidden div
3931	if sysvals.testlog and sysvals.logmsg:
3932		hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
3933	# add the dmesg log as a hidden div
3934	if sysvals.dmesglog and sysvals.dmesgfile:
3935		hf.write('<div id="dmesglog" style="display:none;">\n')
3936		lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3937		for line in lf:
3938			line = line.replace('<', '&lt').replace('>', '&gt')
3939			hf.write(line)
3940		lf.close()
3941		hf.write('</div>\n')
3942	# add the ftrace log as a hidden div
3943	if sysvals.ftracelog and sysvals.ftracefile:
3944		hf.write('<div id="ftracelog" style="display:none;">\n')
3945		lf = sysvals.openlog(sysvals.ftracefile, 'r')
3946		for line in lf:
3947			hf.write(line)
3948		lf.close()
3949		hf.write('</div>\n')
3950
3951	# write the footer and close
3952	addScriptCode(hf, testruns)
3953	hf.write('</body>\n</html>\n')
3954	hf.close()
3955	return True
3956
3957def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
3958	kernel = sv.stamp['kernel']
3959	host = sv.hostname[0].upper()+sv.hostname[1:]
3960	mode = sv.suspendmode
3961	if sv.suspendmode in suspendmodename:
3962		mode = suspendmodename[sv.suspendmode]
3963	title = host+' '+mode+' '+kernel
3964
3965	# various format changes by flags
3966	cgchk = 'checked'
3967	cgnchk = 'not(:checked)'
3968	if sv.cgexp:
3969		cgchk = 'not(:checked)'
3970		cgnchk = 'checked'
3971
3972	hoverZ = 'z-index:8;'
3973	if sv.usedevsrc:
3974		hoverZ = ''
3975
3976	devlistpos = 'absolute'
3977	if testcount > 1:
3978		devlistpos = 'relative'
3979
3980	scaleTH = 20
3981	if kerror:
3982		scaleTH = 60
3983
3984	# write the html header first (html head, css code, up to body start)
3985	html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
3986	<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3987	<title>'+title+'</title>\n\
3988	<style type=\'text/css\'>\n\
3989		body {overflow-y:scroll;}\n\
3990		.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
3991		.stamp.sysinfo {font:10px Arial;}\n\
3992		.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
3993		.callgraph article * {padding-left:28px;}\n\
3994		h1 {color:black;font:bold 30px Times;}\n\
3995		t0 {color:black;font:bold 30px Times;}\n\
3996		t1 {color:black;font:30px Times;}\n\
3997		t2 {color:black;font:25px Times;}\n\
3998		t3 {color:black;font:20px Times;white-space:nowrap;}\n\
3999		t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4000		cS {font:bold 13px Times;}\n\
4001		table {width:100%;}\n\
4002		.gray {background:rgba(80,80,80,0.1);}\n\
4003		.green {background:rgba(204,255,204,0.4);}\n\
4004		.purple {background:rgba(128,0,128,0.2);}\n\
4005		.yellow {background:rgba(255,255,204,0.4);}\n\
4006		.blue {background:rgba(169,208,245,0.4);}\n\
4007		.time1 {font:22px Arial;border:1px solid;}\n\
4008		.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
 
4009		td {text-align:center;}\n\
4010		r {color:#500000;font:15px Tahoma;}\n\
4011		n {color:#505050;font:15px Tahoma;}\n\
4012		.tdhl {color:red;}\n\
4013		.hide {display:none;}\n\
4014		.pf {display:none;}\n\
4015		.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\
4016		.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\
4017		.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4018		.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4019		.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4020		.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\
4021		.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4022		.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4023		.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4024		.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4025		.hover.sync {background:white;}\n\
4026		.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4027		.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4028		.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\
4029		.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4030		.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4031		.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4032		.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4033		.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4034		.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4035		.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4036		button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4037		.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4038		.devlist {position:'+devlistpos+';width:190px;}\n\
4039		a:link {color:white;text-decoration:none;}\n\
4040		a:visited {color:white;}\n\
4041		a:hover {color:white;}\n\
4042		a:active {color:white;}\n\
4043		.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4044		#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4045		.tblock {position:absolute;height:100%;background:#ddd;}\n\
4046		.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4047		.bg {z-index:1;}\n\
4048'+extra+'\
4049	</style>\n</head>\n<body>\n'
4050	hf.write(html_header)
4051
4052# Function: addScriptCode
4053# Description:
4054#	 Adds the javascript code to the output html
4055# Arguments:
4056#	 hf: the open html file pointer
4057#	 testruns: array of Data objects from parseKernelLog or parseTraceLog
4058def addScriptCode(hf, testruns):
4059	t0 = testruns[0].start * 1000
4060	tMax = testruns[-1].end * 1000
4061	# create an array in javascript memory with the device details
4062	detail = '	var devtable = [];\n'
4063	for data in testruns:
4064		topo = data.deviceTopology()
4065		detail += '	devtable[%d] = "%s";\n' % (data.testnumber, topo)
4066	detail += '	var bounds = [%f,%f];\n' % (t0, tMax)
4067	# add the code which will manipulate the data in the browser
4068	script_code = \
4069	'<script type="text/javascript">\n'+detail+\
4070	'	var resolution = -1;\n'\
4071	'	var dragval = [0, 0];\n'\
4072	'	function redrawTimescale(t0, tMax, tS) {\n'\
4073	'		var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4074	'		var tTotal = tMax - t0;\n'\
4075	'		var list = document.getElementsByClassName("tblock");\n'\
4076	'		for (var i = 0; i < list.length; i++) {\n'\
4077	'			var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4078	'			var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4079	'			var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4080	'			var mMax = m0 + mTotal;\n'\
4081	'			var html = "";\n'\
4082	'			var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4083	'			if(divTotal > 1000) continue;\n'\
4084	'			var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4085	'			var pos = 0.0, val = 0.0;\n'\
4086	'			for (var j = 0; j < divTotal; j++) {\n'\
4087	'				var htmlline = "";\n'\
4088	'				var mode = list[i].id[5];\n'\
4089	'				if(mode == "s") {\n'\
4090	'					pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4091	'					val = (j-divTotal+1)*tS;\n'\
4092	'					if(j == divTotal - 1)\n'\
4093	'						htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S&rarr;</cS></div>\';\n'\
4094	'					else\n'\
4095	'						htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4096	'				} else {\n'\
4097	'					pos = 100 - (((j)*tS*100)/mTotal);\n'\
4098	'					val = (j)*tS;\n'\
4099	'					htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4100	'					if(j == 0)\n'\
4101	'						if(mode == "r")\n'\
4102	'							htmlline = rline+"<cS>&larr;R</cS></div>";\n'\
4103	'						else\n'\
4104	'							htmlline = rline+"<cS>0ms</div>";\n'\
4105	'				}\n'\
4106	'				html += htmlline;\n'\
4107	'			}\n'\
4108	'			timescale.innerHTML = html;\n'\
4109	'		}\n'\
4110	'	}\n'\
4111	'	function zoomTimeline() {\n'\
4112	'		var dmesg = document.getElementById("dmesg");\n'\
4113	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4114	'		var left = zoombox.scrollLeft;\n'\
4115	'		var val = parseFloat(dmesg.style.width);\n'\
4116	'		var newval = 100;\n'\
4117	'		var sh = window.outerWidth / 2;\n'\
4118	'		if(this.id == "zoomin") {\n'\
4119	'			newval = val * 1.2;\n'\
4120	'			if(newval > 910034) newval = 910034;\n'\
4121	'			dmesg.style.width = newval+"%";\n'\
4122	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4123	'		} else if (this.id == "zoomout") {\n'\
4124	'			newval = val / 1.2;\n'\
4125	'			if(newval < 100) newval = 100;\n'\
4126	'			dmesg.style.width = newval+"%";\n'\
4127	'			zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4128	'		} else {\n'\
4129	'			zoombox.scrollLeft = 0;\n'\
4130	'			dmesg.style.width = "100%";\n'\
4131	'		}\n'\
4132	'		var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4133	'		var t0 = bounds[0];\n'\
4134	'		var tMax = bounds[1];\n'\
4135	'		var tTotal = tMax - t0;\n'\
4136	'		var wTotal = tTotal * 100.0 / newval;\n'\
4137	'		var idx = 7*window.innerWidth/1100;\n'\
4138	'		for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4139	'		if(i >= tS.length) i = tS.length - 1;\n'\
4140	'		if(tS[i] == resolution) return;\n'\
4141	'		resolution = tS[i];\n'\
4142	'		redrawTimescale(t0, tMax, tS[i]);\n'\
4143	'	}\n'\
4144	'	function deviceName(title) {\n'\
4145	'		var name = title.slice(0, title.indexOf(" ("));\n'\
4146	'		return name;\n'\
4147	'	}\n'\
4148	'	function deviceHover() {\n'\
4149	'		var name = deviceName(this.title);\n'\
4150	'		var dmesg = document.getElementById("dmesg");\n'\
4151	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4152	'		var cpu = -1;\n'\
4153	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4154	'			cpu = parseInt(name.slice(7));\n'\
4155	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4156	'			cpu = parseInt(name.slice(8));\n'\
4157	'		for (var i = 0; i < dev.length; i++) {\n'\
4158	'			dname = deviceName(dev[i].title);\n'\
4159	'			var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4160	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4161	'				(name == dname))\n'\
4162	'			{\n'\
4163	'				dev[i].className = "hover "+cname;\n'\
4164	'			} else {\n'\
4165	'				dev[i].className = cname;\n'\
4166	'			}\n'\
4167	'		}\n'\
4168	'	}\n'\
4169	'	function deviceUnhover() {\n'\
4170	'		var dmesg = document.getElementById("dmesg");\n'\
4171	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4172	'		for (var i = 0; i < dev.length; i++) {\n'\
4173	'			dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4174	'		}\n'\
4175	'	}\n'\
4176	'	function deviceTitle(title, total, cpu) {\n'\
4177	'		var prefix = "Total";\n'\
4178	'		if(total.length > 3) {\n'\
4179	'			prefix = "Average";\n'\
4180	'			total[1] = (total[1]+total[3])/2;\n'\
4181	'			total[2] = (total[2]+total[4])/2;\n'\
4182	'		}\n'\
4183	'		var devtitle = document.getElementById("devicedetailtitle");\n'\
4184	'		var name = deviceName(title);\n'\
4185	'		if(cpu >= 0) name = "CPU"+cpu;\n'\
4186	'		var driver = "";\n'\
4187	'		var tS = "<t2>(</t2>";\n'\
4188	'		var tR = "<t2>)</t2>";\n'\
4189	'		if(total[1] > 0)\n'\
4190	'			tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
4191	'		if(total[2] > 0)\n'\
4192	'			tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
4193	'		var s = title.indexOf("{");\n'\
4194	'		var e = title.indexOf("}");\n'\
4195	'		if((s >= 0) && (e >= 0))\n'\
4196	'			driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
4197	'		if(total[1] > 0 && total[2] > 0)\n'\
4198	'			devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
4199	'		else\n'\
4200	'			devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
4201	'		return name;\n'\
4202	'	}\n'\
4203	'	function deviceDetail() {\n'\
4204	'		var devinfo = document.getElementById("devicedetail");\n'\
4205	'		devinfo.style.display = "block";\n'\
4206	'		var name = deviceName(this.title);\n'\
4207	'		var cpu = -1;\n'\
4208	'		if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4209	'			cpu = parseInt(name.slice(7));\n'\
4210	'		else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4211	'			cpu = parseInt(name.slice(8));\n'\
4212	'		var dmesg = document.getElementById("dmesg");\n'\
4213	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4214	'		var idlist = [];\n'\
4215	'		var pdata = [[]];\n'\
4216	'		if(document.getElementById("devicedetail1"))\n'\
4217	'			pdata = [[], []];\n'\
4218	'		var pd = pdata[0];\n'\
4219	'		var total = [0.0, 0.0, 0.0];\n'\
4220	'		for (var i = 0; i < dev.length; i++) {\n'\
4221	'			dname = deviceName(dev[i].title);\n'\
4222	'			if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4223	'				(name == dname))\n'\
4224	'			{\n'\
4225	'				idlist[idlist.length] = dev[i].id;\n'\
4226	'				var tidx = 1;\n'\
4227	'				if(dev[i].id[0] == "a") {\n'\
4228	'					pd = pdata[0];\n'\
4229	'				} else {\n'\
4230	'					if(pdata.length == 1) pdata[1] = [];\n'\
4231	'					if(total.length == 3) total[3]=total[4]=0.0;\n'\
4232	'					pd = pdata[1];\n'\
4233	'					tidx = 3;\n'\
4234	'				}\n'\
4235	'				var info = dev[i].title.split(" ");\n'\
4236	'				var pname = info[info.length-1];\n'\
4237	'				pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
4238	'				total[0] += pd[pname];\n'\
4239	'				if(pname.indexOf("suspend") >= 0)\n'\
4240	'					total[tidx] += pd[pname];\n'\
4241	'				else\n'\
4242	'					total[tidx+1] += pd[pname];\n'\
4243	'			}\n'\
4244	'		}\n'\
4245	'		var devname = deviceTitle(this.title, total, cpu);\n'\
4246	'		var left = 0.0;\n'\
4247	'		for (var t = 0; t < pdata.length; t++) {\n'\
4248	'			pd = pdata[t];\n'\
4249	'			devinfo = document.getElementById("devicedetail"+t);\n'\
4250	'			var phases = devinfo.getElementsByClassName("phaselet");\n'\
4251	'			for (var i = 0; i < phases.length; i++) {\n'\
4252	'				if(phases[i].id in pd) {\n'\
4253	'					var w = 100.0*pd[phases[i].id]/total[0];\n'\
4254	'					var fs = 32;\n'\
4255	'					if(w < 8) fs = 4*w | 0;\n'\
4256	'					var fs2 = fs*3/4;\n'\
4257	'					phases[i].style.width = w+"%";\n'\
4258	'					phases[i].style.left = left+"%";\n'\
4259	'					phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
4260	'					left += w;\n'\
4261	'					var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
4262	'					var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
4263	'					phases[i].innerHTML = time+pname;\n'\
4264	'				} else {\n'\
4265	'					phases[i].style.width = "0%";\n'\
4266	'					phases[i].style.left = left+"%";\n'\
4267	'				}\n'\
4268	'			}\n'\
4269	'		}\n'\
4270	'		if(typeof devstats !== \'undefined\')\n'\
4271	'			callDetail(this.id, this.title);\n'\
4272	'		var cglist = document.getElementById("callgraphs");\n'\
4273	'		if(!cglist) return;\n'\
4274	'		var cg = cglist.getElementsByClassName("atop");\n'\
4275	'		if(cg.length < 10) return;\n'\
4276	'		for (var i = 0; i < cg.length; i++) {\n'\
4277	'			cgid = cg[i].id.split("x")[0]\n'\
4278	'			if(idlist.indexOf(cgid) >= 0) {\n'\
4279	'				cg[i].style.display = "block";\n'\
4280	'			} else {\n'\
4281	'				cg[i].style.display = "none";\n'\
4282	'			}\n'\
4283	'		}\n'\
4284	'	}\n'\
4285	'	function callDetail(devid, devtitle) {\n'\
4286	'		if(!(devid in devstats) || devstats[devid].length < 1)\n'\
4287	'			return;\n'\
4288	'		var list = devstats[devid];\n'\
4289	'		var tmp = devtitle.split(" ");\n'\
4290	'		var name = tmp[0], phase = tmp[tmp.length-1];\n'\
4291	'		var dd = document.getElementById(phase);\n'\
4292	'		var total = parseFloat(tmp[1].slice(1));\n'\
4293	'		var mlist = [];\n'\
4294	'		var maxlen = 0;\n'\
4295	'		var info = []\n'\
4296	'		for(var i in list) {\n'\
4297	'			if(list[i][0] == "@") {\n'\
4298	'				info = list[i].split("|");\n'\
4299	'				continue;\n'\
4300	'			}\n'\
4301	'			var tmp = list[i].split("|");\n'\
4302	'			var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
4303	'			var p = (t*100.0/total).toFixed(2);\n'\
4304	'			mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
4305	'			if(f.length > maxlen)\n'\
4306	'				maxlen = f.length;\n'\
4307	'		}\n'\
4308	'		var pad = 5;\n'\
4309	'		if(mlist.length == 0) pad = 30;\n'\
4310	'		var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
4311	'		if(info.length > 2)\n'\
4312	'			html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
4313	'		if(info.length > 3)\n'\
4314	'			html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
4315	'		if(info.length > 4)\n'\
4316	'			html += ", return=<b>"+info[4]+"</b>";\n'\
4317	'		html += "</t3></div>";\n'\
4318	'		if(mlist.length > 0) {\n'\
4319	'			html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
4320	'			for(var i in mlist)\n'\
4321	'				html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
4322	'			html += "</tr><tr><th>Calls</th>";\n'\
4323	'			for(var i in mlist)\n'\
4324	'				html += "<td>"+mlist[i][1]+"</td>";\n'\
4325	'			html += "</tr><tr><th>Time(ms)</th>";\n'\
4326	'			for(var i in mlist)\n'\
4327	'				html += "<td>"+mlist[i][2]+"</td>";\n'\
4328	'			html += "</tr><tr><th>Percent</th>";\n'\
4329	'			for(var i in mlist)\n'\
4330	'				html += "<td>"+mlist[i][3]+"</td>";\n'\
4331	'			html += "</tr></table>";\n'\
4332	'		}\n'\
4333	'		dd.innerHTML = html;\n'\
4334	'		var height = (maxlen*5)+100;\n'\
4335	'		dd.style.height = height+"px";\n'\
4336	'		document.getElementById("devicedetail").style.height = height+"px";\n'\
4337	'	}\n'\
4338	'	function callSelect() {\n'\
4339	'		var cglist = document.getElementById("callgraphs");\n'\
4340	'		if(!cglist) return;\n'\
4341	'		var cg = cglist.getElementsByClassName("atop");\n'\
4342	'		for (var i = 0; i < cg.length; i++) {\n'\
4343	'			if(this.id == cg[i].id) {\n'\
4344	'				cg[i].style.display = "block";\n'\
4345	'			} else {\n'\
4346	'				cg[i].style.display = "none";\n'\
4347	'			}\n'\
4348	'		}\n'\
4349	'	}\n'\
4350	'	function devListWindow(e) {\n'\
4351	'		var win = window.open();\n'\
4352	'		var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
4353	'			"<style type=\\"text/css\\">"+\n'\
4354	'			"   ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
4355	'			"</style>"\n'\
4356	'		var dt = devtable[0];\n'\
4357	'		if(e.target.id != "devlist1")\n'\
4358	'			dt = devtable[1];\n'\
4359	'		win.document.write(html+dt);\n'\
4360	'	}\n'\
4361	'	function errWindow() {\n'\
4362	'		var range = this.id.split("_");\n'\
4363	'		var idx1 = parseInt(range[0]);\n'\
4364	'		var idx2 = parseInt(range[1]);\n'\
4365	'		var win = window.open();\n'\
4366	'		var log = document.getElementById("dmesglog");\n'\
4367	'		var title = "<title>dmesg log</title>";\n'\
4368	'		var text = log.innerHTML.split("\\n");\n'\
4369	'		var html = "";\n'\
4370	'		for(var i = 0; i < text.length; i++) {\n'\
4371	'			if(i == idx1) {\n'\
4372	'				html += "<e id=target>"+text[i]+"</e>\\n";\n'\
4373	'			} else if(i > idx1 && i <= idx2) {\n'\
4374	'				html += "<e>"+text[i]+"</e>\\n";\n'\
4375	'			} else {\n'\
4376	'				html += text[i]+"\\n";\n'\
4377	'			}\n'\
4378	'		}\n'\
4379	'		win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
4380	'		win.location.hash = "#target";\n'\
4381	'		win.document.close();\n'\
4382	'	}\n'\
4383	'	function logWindow(e) {\n'\
4384	'		var name = e.target.id.slice(4);\n'\
4385	'		var win = window.open();\n'\
4386	'		var log = document.getElementById(name+"log");\n'\
4387	'		var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
4388	'		win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4389	'		win.document.close();\n'\
4390	'	}\n'\
4391	'	function onMouseDown(e) {\n'\
4392	'		dragval[0] = e.clientX;\n'\
4393	'		dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
4394	'		document.onmousemove = onMouseMove;\n'\
4395	'	}\n'\
4396	'	function onMouseMove(e) {\n'\
4397	'		var zoombox = document.getElementById("dmesgzoombox");\n'\
4398	'		zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
4399	'	}\n'\
4400	'	function onMouseUp(e) {\n'\
4401	'		document.onmousemove = null;\n'\
4402	'	}\n'\
4403	'	function onKeyPress(e) {\n'\
4404	'		var c = e.charCode;\n'\
4405	'		if(c != 42 && c != 43 && c != 45) return;\n'\
4406	'		var click = document.createEvent("Events");\n'\
4407	'		click.initEvent("click", true, false);\n'\
4408	'		if(c == 43)  \n'\
4409	'			document.getElementById("zoomin").dispatchEvent(click);\n'\
4410	'		else if(c == 45)\n'\
4411	'			document.getElementById("zoomout").dispatchEvent(click);\n'\
4412	'		else if(c == 42)\n'\
4413	'			document.getElementById("zoomdef").dispatchEvent(click);\n'\
4414	'	}\n'\
4415	'	window.addEventListener("resize", function () {zoomTimeline();});\n'\
4416	'	window.addEventListener("load", function () {\n'\
4417	'		var dmesg = document.getElementById("dmesg");\n'\
4418	'		dmesg.style.width = "100%"\n'\
4419	'		dmesg.onmousedown = onMouseDown;\n'\
4420	'		document.onmouseup = onMouseUp;\n'\
4421	'		document.onkeypress = onKeyPress;\n'\
4422	'		document.getElementById("zoomin").onclick = zoomTimeline;\n'\
4423	'		document.getElementById("zoomout").onclick = zoomTimeline;\n'\
4424	'		document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
4425	'		var list = document.getElementsByClassName("err");\n'\
4426	'		for (var i = 0; i < list.length; i++)\n'\
4427	'			list[i].onclick = errWindow;\n'\
4428	'		var list = document.getElementsByClassName("logbtn");\n'\
4429	'		for (var i = 0; i < list.length; i++)\n'\
4430	'			list[i].onclick = logWindow;\n'\
4431	'		list = document.getElementsByClassName("devlist");\n'\
4432	'		for (var i = 0; i < list.length; i++)\n'\
4433	'			list[i].onclick = devListWindow;\n'\
4434	'		var dev = dmesg.getElementsByClassName("thread");\n'\
4435	'		for (var i = 0; i < dev.length; i++) {\n'\
4436	'			dev[i].onclick = deviceDetail;\n'\
4437	'			dev[i].onmouseover = deviceHover;\n'\
4438	'			dev[i].onmouseout = deviceUnhover;\n'\
4439	'		}\n'\
4440	'		var dev = dmesg.getElementsByClassName("srccall");\n'\
4441	'		for (var i = 0; i < dev.length; i++)\n'\
4442	'			dev[i].onclick = callSelect;\n'\
4443	'		zoomTimeline();\n'\
4444	'	});\n'\
4445	'</script>\n'
4446	hf.write(script_code);
4447
4448def setRuntimeSuspend(before=True):
4449	global sysvals
4450	sv = sysvals
4451	if sv.rs == 0:
4452		return
4453	if before:
4454		# runtime suspend disable or enable
4455		if sv.rs > 0:
4456			sv.rstgt, sv.rsval, sv.rsdir = 'on', 'auto', 'enabled'
4457		else:
4458			sv.rstgt, sv.rsval, sv.rsdir = 'auto', 'on', 'disabled'
4459		print('CONFIGURING RUNTIME SUSPEND...')
4460		sv.rslist = deviceInfo(sv.rstgt)
4461		for i in sv.rslist:
4462			sv.setVal(sv.rsval, i)
4463		print('runtime suspend %s on all devices (%d changed)' % (sv.rsdir, len(sv.rslist)))
4464		print('waiting 5 seconds...')
4465		time.sleep(5)
4466	else:
4467		# runtime suspend re-enable or re-disable
4468		for i in sv.rslist:
4469			sv.setVal(sv.rstgt, i)
4470		print('runtime suspend settings restored on %d devices' % len(sv.rslist))
4471
4472# Function: executeSuspend
4473# Description:
4474#	 Execute system suspend through the sysfs interface, then copy the output
4475#	 dmesg and ftrace files to the test output directory.
4476def executeSuspend():
4477	pm = ProcessMonitor()
4478	tp = sysvals.tpath
4479	fwdata = []
 
 
4480	# run these commands to prepare the system for suspend
4481	if sysvals.display:
4482		if sysvals.display > 0:
4483			print('TURN DISPLAY ON')
4484			call('xset -d :0.0 dpms force suspend', shell=True)
4485			call('xset -d :0.0 dpms force on', shell=True)
4486		else:
4487			print('TURN DISPLAY OFF')
4488			call('xset -d :0.0 dpms force suspend', shell=True)
4489		time.sleep(1)
4490	if sysvals.sync:
4491		print('SYNCING FILESYSTEMS')
 
 
4492		call('sync', shell=True)
4493	# mark the start point in the kernel ring buffer just as we start
4494	sysvals.initdmesg()
4495	# start ftrace
4496	if(sysvals.usecallgraph or sysvals.usetraceevents):
4497		print('START TRACING')
4498		sysvals.fsetVal('1', 'tracing_on')
4499		if sysvals.useprocmon:
 
 
 
4500			pm.start()
 
 
4501	# execute however many s/r runs requested
4502	for count in range(1,sysvals.execcount+1):
4503		# x2delay in between test runs
4504		if(count > 1 and sysvals.x2delay > 0):
4505			sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
4506			time.sleep(sysvals.x2delay/1000.0)
4507			sysvals.fsetVal('WAIT END', 'trace_marker')
4508		# start message
4509		if sysvals.testcommand != '':
4510			print('COMMAND START')
4511		else:
4512			if(sysvals.rtcwake):
4513				print('SUSPEND START')
4514			else:
4515				print('SUSPEND START (press a key to resume)')
4516		# set rtcwake
4517		if(sysvals.rtcwake):
4518			print('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
4519			sysvals.rtcWakeAlarmOn()
 
 
4520		# start of suspend trace marker
4521		if(sysvals.usecallgraph or sysvals.usetraceevents):
4522			sysvals.fsetVal('SUSPEND START', 'trace_marker')
4523		# predelay delay
4524		if(count == 1 and sysvals.predelay > 0):
4525			sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
4526			time.sleep(sysvals.predelay/1000.0)
4527			sysvals.fsetVal('WAIT END', 'trace_marker')
4528		# initiate suspend or command
4529		if sysvals.testcommand != '':
4530			call(sysvals.testcommand+' 2>&1', shell=True);
 
 
 
 
4531		else:
4532			mode = sysvals.suspendmode
4533			if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
4534				mode = 'mem'
4535				pf = open(sysvals.mempowerfile, 'w')
4536				pf.write(sysvals.memmode)
4537				pf.close()
4538			pf = open(sysvals.powerfile, 'w')
4539			pf.write(mode)
4540			# execution will pause here
4541			try:
4542				pf.close()
4543			except:
4544				pass
4545		if(sysvals.rtcwake):
4546			sysvals.rtcWakeAlarmOff()
 
 
 
 
 
 
 
 
 
 
 
 
 
4547		# postdelay delay
4548		if(count == sysvals.execcount and sysvals.postdelay > 0):
4549			sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
4550			time.sleep(sysvals.postdelay/1000.0)
4551			sysvals.fsetVal('WAIT END', 'trace_marker')
4552		# return from suspend
4553		print('RESUME COMPLETE')
4554		if(sysvals.usecallgraph or sysvals.usetraceevents):
4555			sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
4556		if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4557			fwdata.append(getFPDT(False))
 
 
 
 
 
 
 
4558	# stop ftrace
4559	if(sysvals.usecallgraph or sysvals.usetraceevents):
4560		if sysvals.useprocmon:
 
4561			pm.stop()
4562		sysvals.fsetVal('0', 'tracing_on')
4563		print('CAPTURING TRACE')
4564		op = sysvals.writeDatafileHeader(sysvals.ftracefile, fwdata)
 
 
 
 
 
 
 
 
4565		fp = open(tp+'trace', 'r')
4566		for line in fp:
4567			op.write(line)
4568		op.close()
4569		sysvals.fsetVal('', 'trace')
4570		devProps()
4571	# grab a copy of the dmesg output
4572	print('CAPTURING DMESG')
4573	sysvals.getdmesg(fwdata)
4574
4575def readFile(file):
4576	if os.path.islink(file):
4577		return os.readlink(file).split('/')[-1]
4578	else:
4579		return sysvals.getVal(file).strip()
4580
4581# Function: ms2nice
4582# Description:
4583#	 Print out a very concise time string in minutes and seconds
4584# Output:
4585#	 The time string, e.g. "1901m16s"
4586def ms2nice(val):
4587	val = int(val)
4588	h = val / 3600000
4589	m = (val / 60000) % 60
4590	s = (val / 1000) % 60
4591	if h > 0:
4592		return '%d:%02d:%02d' % (h, m, s)
4593	if m > 0:
4594		return '%02d:%02d' % (m, s)
4595	return '%ds' % s
4596
4597def yesno(val):
4598	list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
4599		'active':'A', 'suspended':'S', 'suspending':'S'}
4600	if val not in list:
4601		return ' '
4602	return list[val]
4603
4604# Function: deviceInfo
4605# Description:
4606#	 Detect all the USB hosts and devices currently connected and add
4607#	 a list of USB device names to sysvals for better timeline readability
4608def deviceInfo(output=''):
4609	if not output:
4610		print('LEGEND')
4611		print('---------------------------------------------------------------------------------------------')
4612		print('  A = async/sync PM queue (A/S)               C = runtime active children')
4613		print('  R = runtime suspend enabled/disabled (E/D)  rACTIVE = runtime active (min/sec)')
4614		print('  S = runtime status active/suspended (A/S)   rSUSPEND = runtime suspend (min/sec)')
4615		print('  U = runtime usage count')
4616		print('---------------------------------------------------------------------------------------------')
4617		print('DEVICE                     NAME                       A R S U C    rACTIVE   rSUSPEND')
4618		print('---------------------------------------------------------------------------------------------')
4619
4620	res = []
4621	tgtval = 'runtime_status'
4622	lines = dict()
4623	for dirname, dirnames, filenames in os.walk('/sys/devices'):
4624		if(not re.match('.*/power', dirname) or
4625			'control' not in filenames or
4626			tgtval not in filenames):
4627			continue
4628		name = ''
4629		dirname = dirname[:-6]
4630		device = dirname.split('/')[-1]
4631		power = dict()
4632		power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
4633		# only list devices which support runtime suspend
4634		if power[tgtval] not in ['active', 'suspended', 'suspending']:
4635			continue
4636		for i in ['product', 'driver', 'subsystem']:
4637			file = '%s/%s' % (dirname, i)
4638			if os.path.exists(file):
4639				name = readFile(file)
4640				break
4641		for i in ['async', 'control', 'runtime_status', 'runtime_usage',
4642			'runtime_active_kids', 'runtime_active_time',
4643			'runtime_suspended_time']:
4644			if i in filenames:
4645				power[i] = readFile('%s/power/%s' % (dirname, i))
4646		if output:
4647			if power['control'] == output:
4648				res.append('%s/power/control' % dirname)
4649			continue
4650		lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
4651			(device[:26], name[:26],
4652			yesno(power['async']), \
4653			yesno(power['control']), \
4654			yesno(power['runtime_status']), \
4655			power['runtime_usage'], \
4656			power['runtime_active_kids'], \
4657			ms2nice(power['runtime_active_time']), \
4658			ms2nice(power['runtime_suspended_time']))
4659	for i in sorted(lines):
4660		print lines[i]
4661	return res
4662
4663# Function: devProps
4664# Description:
4665#	 Retrieve a list of properties for all devices in the trace log
4666def devProps(data=0):
4667	props = dict()
4668
4669	if data:
4670		idx = data.index(': ') + 2
4671		if idx >= len(data):
4672			return
4673		devlist = data[idx:].split(';')
4674		for dev in devlist:
4675			f = dev.split(',')
4676			if len(f) < 3:
4677				continue
4678			dev = f[0]
4679			props[dev] = DevProps()
4680			props[dev].altname = f[1]
4681			if int(f[2]):
4682				props[dev].async = True
4683			else:
4684				props[dev].async = False
4685			sysvals.devprops = props
4686		if sysvals.suspendmode == 'command' and 'testcommandstring' in props:
4687			sysvals.testcommand = props['testcommandstring'].altname
4688		return
4689
4690	if(os.path.exists(sysvals.ftracefile) == False):
4691		doError('%s does not exist' % sysvals.ftracefile)
4692
4693	# first get the list of devices we need properties for
4694	msghead = 'Additional data added by AnalyzeSuspend'
4695	alreadystamped = False
4696	tp = TestProps()
4697	tf = sysvals.openlog(sysvals.ftracefile, 'r')
4698	for line in tf:
4699		if msghead in line:
4700			alreadystamped = True
4701			continue
4702		# determine the trace data type (required for further parsing)
4703		m = re.match(sysvals.tracertypefmt, line)
4704		if(m):
4705			tp.setTracerType(m.group('t'))
4706			continue
4707		# parse only valid lines, if this is not one move on
4708		m = re.match(tp.ftrace_line_fmt, line)
4709		if(not m or 'device_pm_callback_start' not in line):
4710			continue
4711		m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
4712		if(not m):
4713			continue
4714		dev = m.group('d')
4715		if dev not in props:
4716			props[dev] = DevProps()
4717	tf.close()
4718
4719	if not alreadystamped and sysvals.suspendmode == 'command':
4720		out = '#\n# '+msghead+'\n# Device Properties: '
4721		out += 'testcommandstring,%s,0;' % (sysvals.testcommand)
4722		with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4723			fp.write(out+'\n')
4724		sysvals.devprops = props
4725		return
4726
4727	# now get the syspath for each of our target devices
4728	for dirname, dirnames, filenames in os.walk('/sys/devices'):
4729		if(re.match('.*/power', dirname) and 'async' in filenames):
4730			dev = dirname.split('/')[-2]
4731			if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
4732				props[dev].syspath = dirname[:-6]
4733
4734	# now fill in the properties for our target devices
4735	for dev in props:
4736		dirname = props[dev].syspath
4737		if not dirname or not os.path.exists(dirname):
4738			continue
4739		with open(dirname+'/power/async') as fp:
4740			text = fp.read()
4741			props[dev].async = False
4742			if 'enabled' in text:
4743				props[dev].async = True
4744		fields = os.listdir(dirname)
4745		if 'product' in fields:
4746			with open(dirname+'/product') as fp:
4747				props[dev].altname = fp.read()
4748		elif 'name' in fields:
4749			with open(dirname+'/name') as fp:
4750				props[dev].altname = fp.read()
4751		elif 'model' in fields:
4752			with open(dirname+'/model') as fp:
4753				props[dev].altname = fp.read()
4754		elif 'description' in fields:
4755			with open(dirname+'/description') as fp:
4756				props[dev].altname = fp.read()
4757		elif 'id' in fields:
4758			with open(dirname+'/id') as fp:
4759				props[dev].altname = fp.read()
4760		elif 'idVendor' in fields and 'idProduct' in fields:
4761			idv, idp = '', ''
4762			with open(dirname+'/idVendor') as fp:
4763				idv = fp.read().strip()
4764			with open(dirname+'/idProduct') as fp:
4765				idp = fp.read().strip()
4766			props[dev].altname = '%s:%s' % (idv, idp)
4767
4768		if props[dev].altname:
4769			out = props[dev].altname.strip().replace('\n', ' ')
4770			out = out.replace(',', ' ')
4771			out = out.replace(';', ' ')
4772			props[dev].altname = out
4773
4774	# and now write the data to the ftrace file
4775	if not alreadystamped:
4776		out = '#\n# '+msghead+'\n# Device Properties: '
4777		for dev in sorted(props):
4778			out += props[dev].out(dev)
4779		with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4780			fp.write(out+'\n')
4781
4782	sysvals.devprops = props
4783
4784# Function: getModes
4785# Description:
4786#	 Determine the supported power modes on this system
4787# Output:
4788#	 A string list of the available modes
4789def getModes():
4790	modes = []
4791	if(os.path.exists(sysvals.powerfile)):
4792		fp = open(sysvals.powerfile, 'r')
4793		modes = string.split(fp.read())
4794		fp.close()
4795	if(os.path.exists(sysvals.mempowerfile)):
4796		deep = False
4797		fp = open(sysvals.mempowerfile, 'r')
4798		for m in string.split(fp.read()):
4799			memmode = m.strip('[]')
4800			if memmode == 'deep':
4801				deep = True
4802			else:
4803				modes.append('mem-%s' % memmode)
4804		fp.close()
4805		if 'mem' in modes and not deep:
4806			modes.remove('mem')
 
 
 
 
 
4807	return modes
4808
4809# Function: dmidecode
4810# Description:
4811#	 Read the bios tables and pull out system info
4812# Arguments:
4813#	 mempath: /dev/mem or custom mem path
4814#	 fatal: True to exit on error, False to return empty dict
4815# Output:
4816#	 A dict object with all available key/values
4817def dmidecode(mempath, fatal=False):
4818	out = dict()
4819
4820	# the list of values to retrieve, with hardcoded (type, idx)
4821	info = {
4822		'bios-vendor': (0, 4),
4823		'bios-version': (0, 5),
4824		'bios-release-date': (0, 8),
4825		'system-manufacturer': (1, 4),
4826		'system-product-name': (1, 5),
4827		'system-version': (1, 6),
4828		'system-serial-number': (1, 7),
4829		'baseboard-manufacturer': (2, 4),
4830		'baseboard-product-name': (2, 5),
4831		'baseboard-version': (2, 6),
4832		'baseboard-serial-number': (2, 7),
4833		'chassis-manufacturer': (3, 4),
4834		'chassis-type': (3, 5),
4835		'chassis-version': (3, 6),
4836		'chassis-serial-number': (3, 7),
4837		'processor-manufacturer': (4, 7),
4838		'processor-version': (4, 16),
4839	}
4840	if(not os.path.exists(mempath)):
4841		if(fatal):
4842			doError('file does not exist: %s' % mempath)
4843		return out
4844	if(not os.access(mempath, os.R_OK)):
4845		if(fatal):
4846			doError('file is not readable: %s' % mempath)
4847		return out
4848
4849	# by default use legacy scan, but try to use EFI first
4850	memaddr = 0xf0000
4851	memsize = 0x10000
4852	for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
4853		if not os.path.exists(ep) or not os.access(ep, os.R_OK):
4854			continue
4855		fp = open(ep, 'r')
4856		buf = fp.read()
4857		fp.close()
4858		i = buf.find('SMBIOS=')
4859		if i >= 0:
4860			try:
4861				memaddr = int(buf[i+7:], 16)
4862				memsize = 0x20
4863			except:
4864				continue
4865
4866	# read in the memory for scanning
4867	fp = open(mempath, 'rb')
4868	try:
 
4869		fp.seek(memaddr)
4870		buf = fp.read(memsize)
4871	except:
4872		if(fatal):
4873			doError('DMI table is unreachable, sorry')
4874		else:
 
4875			return out
4876	fp.close()
4877
4878	# search for either an SM table or DMI table
4879	i = base = length = num = 0
4880	while(i < memsize):
4881		if buf[i:i+4] == '_SM_' and i < memsize - 16:
4882			length = struct.unpack('H', buf[i+22:i+24])[0]
4883			base, num = struct.unpack('IH', buf[i+24:i+30])
4884			break
4885		elif buf[i:i+5] == '_DMI_':
4886			length = struct.unpack('H', buf[i+6:i+8])[0]
4887			base, num = struct.unpack('IH', buf[i+8:i+14])
4888			break
4889		i += 16
4890	if base == 0 and length == 0 and num == 0:
4891		if(fatal):
4892			doError('Neither SMBIOS nor DMI were found')
4893		else:
4894			return out
4895
4896	# read in the SM or DMI table
4897	fp = open(mempath, 'rb')
4898	try:
 
4899		fp.seek(base)
4900		buf = fp.read(length)
4901	except:
4902		if(fatal):
4903			doError('DMI table is unreachable, sorry')
4904		else:
 
4905			return out
4906	fp.close()
4907
4908	# scan the table for the values we want
4909	count = i = 0
4910	while(count < num and i <= len(buf) - 4):
4911		type, size, handle = struct.unpack('BBH', buf[i:i+4])
4912		n = i + size
4913		while n < len(buf) - 1:
4914			if 0 == struct.unpack('H', buf[n:n+2])[0]:
4915				break
4916			n += 1
4917		data = buf[i+size:n+2].split('\0')
4918		for name in info:
4919			itype, idxadr = info[name]
4920			if itype == type:
4921				idx = struct.unpack('B', buf[i+idxadr])[0]
4922				if idx > 0 and idx < len(data) - 1:
4923					s = data[idx-1].strip()
4924					if s and s.lower() != 'to be filled by o.e.m.':
4925						out[name] = data[idx-1]
4926		i = n + 2
4927		count += 1
4928	return out
4929
4930# Function: getFPDT
4931# Description:
4932#	 Read the acpi bios tables and pull out FPDT, the firmware data
4933# Arguments:
4934#	 output: True to output the info to stdout, False otherwise
4935def getFPDT(output):
4936	rectype = {}
4937	rectype[0] = 'Firmware Basic Boot Performance Record'
4938	rectype[1] = 'S3 Performance Table Record'
4939	prectype = {}
4940	prectype[0] = 'Basic S3 Resume Performance Record'
4941	prectype[1] = 'Basic S3 Suspend Performance Record'
4942
4943	sysvals.rootCheck(True)
4944	if(not os.path.exists(sysvals.fpdtpath)):
4945		if(output):
4946			doError('file does not exist: %s' % sysvals.fpdtpath)
4947		return False
4948	if(not os.access(sysvals.fpdtpath, os.R_OK)):
4949		if(output):
4950			doError('file is not readable: %s' % sysvals.fpdtpath)
4951		return False
4952	if(not os.path.exists(sysvals.mempath)):
4953		if(output):
4954			doError('file does not exist: %s' % sysvals.mempath)
4955		return False
4956	if(not os.access(sysvals.mempath, os.R_OK)):
4957		if(output):
4958			doError('file is not readable: %s' % sysvals.mempath)
4959		return False
4960
4961	fp = open(sysvals.fpdtpath, 'rb')
4962	buf = fp.read()
4963	fp.close()
4964
4965	if(len(buf) < 36):
4966		if(output):
4967			doError('Invalid FPDT table data, should '+\
4968				'be at least 36 bytes')
4969		return False
4970
4971	table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
4972	if(output):
4973		print('')
4974		print('Firmware Performance Data Table (%s)' % table[0])
4975		print('                  Signature : %s' % table[0])
4976		print('               Table Length : %u' % table[1])
4977		print('                   Revision : %u' % table[2])
4978		print('                   Checksum : 0x%x' % table[3])
4979		print('                     OEM ID : %s' % table[4])
4980		print('               OEM Table ID : %s' % table[5])
4981		print('               OEM Revision : %u' % table[6])
4982		print('                 Creator ID : %s' % table[7])
4983		print('           Creator Revision : 0x%x' % table[8])
4984		print('')
 
 
4985
4986	if(table[0] != 'FPDT'):
4987		if(output):
4988			doError('Invalid FPDT table')
4989		return False
4990	if(len(buf) <= 36):
4991		return False
4992	i = 0
4993	fwData = [0, 0]
4994	records = buf[36:]
4995	fp = open(sysvals.mempath, 'rb')
 
 
 
 
4996	while(i < len(records)):
4997		header = struct.unpack('HBB', records[i:i+4])
4998		if(header[0] not in rectype):
4999			i += header[1]
5000			continue
5001		if(header[1] != 16):
5002			i += header[1]
5003			continue
5004		addr = struct.unpack('Q', records[i+8:i+16])[0]
5005		try:
5006			fp.seek(addr)
5007			first = fp.read(8)
5008		except:
5009			if(output):
5010				print('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5011			return [0, 0]
5012		rechead = struct.unpack('4sI', first)
5013		recdata = fp.read(rechead[1]-8)
5014		if(rechead[0] == 'FBPT'):
5015			record = struct.unpack('HBBIQQQQQ', recdata)
5016			if(output):
5017				print('%s (%s)' % (rectype[header[0]], rechead[0]))
5018				print('                  Reset END : %u ns' % record[4])
5019				print('  OS Loader LoadImage Start : %u ns' % record[5])
5020				print(' OS Loader StartImage Start : %u ns' % record[6])
5021				print('     ExitBootServices Entry : %u ns' % record[7])
5022				print('      ExitBootServices Exit : %u ns' % record[8])
5023		elif(rechead[0] == 'S3PT'):
 
 
5024			if(output):
5025				print('%s (%s)' % (rectype[header[0]], rechead[0]))
5026			j = 0
5027			while(j < len(recdata)):
5028				prechead = struct.unpack('HBB', recdata[j:j+4])
5029				if(prechead[0] not in prectype):
5030					continue
5031				if(prechead[0] == 0):
5032					record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5033					fwData[1] = record[2]
5034					if(output):
5035						print('    %s' % prectype[prechead[0]])
5036						print('               Resume Count : %u' % \
5037							record[1])
5038						print('                 FullResume : %u ns' % \
5039							record[2])
5040						print('              AverageResume : %u ns' % \
5041							record[3])
5042				elif(prechead[0] == 1):
5043					record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5044					fwData[0] = record[1] - record[0]
5045					if(output):
5046						print('    %s' % prectype[prechead[0]])
5047						print('               SuspendStart : %u ns' % \
5048							record[0])
5049						print('                 SuspendEnd : %u ns' % \
5050							record[1])
5051						print('                SuspendTime : %u ns' % \
5052							fwData[0])
5053				j += prechead[1]
5054		if(output):
5055			print('')
5056		i += header[1]
5057	fp.close()
5058	return fwData
5059
5060# Function: statusCheck
5061# Description:
5062#	 Verify that the requested command and options will work, and
5063#	 print the results to the terminal
5064# Output:
5065#	 True if the test will work, False if not
5066def statusCheck(probecheck=False):
5067	status = True
5068
5069	print('Checking this system (%s)...' % platform.node())
5070
5071	# check we have root access
5072	res = sysvals.colorText('NO (No features of this tool will work!)')
5073	if(sysvals.rootCheck(False)):
5074		res = 'YES'
5075	print('    have root access: %s' % res)
5076	if(res != 'YES'):
5077		print('    Try running this script with sudo')
5078		return False
5079
5080	# check sysfs is mounted
5081	res = sysvals.colorText('NO (No features of this tool will work!)')
5082	if(os.path.exists(sysvals.powerfile)):
5083		res = 'YES'
5084	print('    is sysfs mounted: %s' % res)
5085	if(res != 'YES'):
5086		return False
5087
5088	# check target mode is a valid mode
5089	if sysvals.suspendmode != 'command':
5090		res = sysvals.colorText('NO')
5091		modes = getModes()
5092		if(sysvals.suspendmode in modes):
5093			res = 'YES'
5094		else:
5095			status = False
5096		print('    is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5097		if(res == 'NO'):
5098			print('      valid power modes are: %s' % modes)
5099			print('      please choose one with -m')
5100
5101	# check if ftrace is available
5102	res = sysvals.colorText('NO')
5103	ftgood = sysvals.verifyFtrace()
5104	if(ftgood):
5105		res = 'YES'
5106	elif(sysvals.usecallgraph):
5107		status = False
5108	print('    is ftrace supported: %s' % res)
5109
5110	# check if kprobes are available
5111	res = sysvals.colorText('NO')
5112	sysvals.usekprobes = sysvals.verifyKprobes()
5113	if(sysvals.usekprobes):
5114		res = 'YES'
5115	else:
5116		sysvals.usedevsrc = False
5117	print('    are kprobes supported: %s' % res)
 
5118
5119	# what data source are we using
5120	res = 'DMESG'
5121	if(ftgood):
5122		sysvals.usetraceevents = True
5123		for e in sysvals.traceevents:
5124			if not os.path.exists(sysvals.epath+e):
5125				sysvals.usetraceevents = False
5126		if(sysvals.usetraceevents):
5127			res = 'FTRACE (all trace events found)'
5128	print('    timeline data source: %s' % res)
5129
5130	# check if rtcwake
5131	res = sysvals.colorText('NO')
5132	if(sysvals.rtcpath != ''):
5133		res = 'YES'
5134	elif(sysvals.rtcwake):
5135		status = False
5136	print('    is rtcwake supported: %s' % res)
 
 
 
 
 
 
 
 
 
 
 
5137
5138	if not probecheck:
5139		return status
5140
5141	# verify kprobes
5142	if sysvals.usekprobes:
5143		for name in sysvals.tracefuncs:
5144			sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5145		if sysvals.usedevsrc:
5146			for name in sysvals.dev_tracefuncs:
5147				sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5148		sysvals.addKprobes(True)
5149
5150	return status
5151
5152# Function: doError
5153# Description:
5154#	 generic error function for catastrphic failures
5155# Arguments:
5156#	 msg: the error message to print
5157#	 help: True if printHelp should be called after, False otherwise
5158def doError(msg, help=False):
5159	if(help == True):
5160		printHelp()
5161	print('ERROR: %s\n') % msg
5162	sysvals.outputResult({'error':msg})
5163	sys.exit()
5164
5165# Function: getArgInt
5166# Description:
5167#	 pull out an integer argument from the command line with checks
5168def getArgInt(name, args, min, max, main=True):
5169	if main:
5170		try:
5171			arg = args.next()
5172		except:
5173			doError(name+': no argument supplied', True)
5174	else:
5175		arg = args
5176	try:
5177		val = int(arg)
5178	except:
5179		doError(name+': non-integer value given', True)
5180	if(val < min or val > max):
5181		doError(name+': value should be between %d and %d' % (min, max), True)
5182	return val
5183
5184# Function: getArgFloat
5185# Description:
5186#	 pull out a float argument from the command line with checks
5187def getArgFloat(name, args, min, max, main=True):
5188	if main:
5189		try:
5190			arg = args.next()
5191		except:
5192			doError(name+': no argument supplied', True)
5193	else:
5194		arg = args
5195	try:
5196		val = float(arg)
5197	except:
5198		doError(name+': non-numerical value given', True)
5199	if(val < min or val > max):
5200		doError(name+': value should be between %f and %f' % (min, max), True)
5201	return val
5202
5203def processData(live=False):
5204	print('PROCESSING DATA')
 
 
 
 
5205	if(sysvals.usetraceevents):
5206		testruns = parseTraceLog(live)
5207		if sysvals.dmesgfile:
5208			for data in testruns:
5209				data.extractErrorInfo()
5210	else:
5211		testruns = loadKernelLog()
5212		for data in testruns:
5213			parseKernelLog(data)
5214		if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5215			appendIncompleteTraceLog(testruns)
 
 
 
 
 
 
 
 
 
5216	sysvals.vprint('Command:\n    %s' % sysvals.cmdline)
5217	for data in testruns:
 
 
 
 
 
 
 
 
 
5218		data.printDetails()
 
 
 
 
 
 
5219	if sysvals.cgdump:
5220		for data in testruns:
5221			data.debugPrint()
5222		sys.exit()
5223
 
 
5224	sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
5225	createHTML(testruns)
5226	print('DONE')
 
5227	data = testruns[0]
5228	stamp = data.stamp
5229	stamp['suspend'], stamp['resume'] = data.getTimeValues()
5230	if data.fwValid:
5231		stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
 
 
5232	return (testruns, stamp)
5233
5234# Function: rerunTest
5235# Description:
5236#	 generate an output from an existing set of ftrace/dmesg logs
5237def rerunTest():
5238	if sysvals.ftracefile:
5239		doesTraceLogHaveTraceEvents()
5240	if not sysvals.dmesgfile and not sysvals.usetraceevents:
5241		doError('recreating this html output requires a dmesg file')
5242	sysvals.setOutputFile()
 
 
 
5243	if os.path.exists(sysvals.htmlfile):
5244		if not os.path.isfile(sysvals.htmlfile):
5245			doError('a directory already exists with this name: %s' % sysvals.htmlfile)
5246		elif not os.access(sysvals.htmlfile, os.W_OK):
5247			doError('missing permission to write to %s' % sysvals.htmlfile)
5248	testruns, stamp = processData(False)
 
5249	return stamp
5250
5251# Function: runTest
5252# Description:
5253#	 execute a suspend/resume, gather the logs, and generate the output
5254def runTest(n=0):
5255	# prepare for the test
5256	sysvals.initFtrace()
5257	sysvals.initTestOutput('suspend')
 
 
 
 
 
 
 
 
 
 
 
 
5258
5259	# execute the test
5260	executeSuspend()
5261	sysvals.cleanupFtrace()
5262	if sysvals.skiphtml:
5263		sysvals.sudouser(sysvals.testdir)
 
5264		return
5265	testruns, stamp = processData(True)
5266	for data in testruns:
5267		del data
5268	sysvals.sudouser(sysvals.testdir)
5269	sysvals.outputResult(stamp, n)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5270
5271def find_in_html(html, strs, div=False):
5272	for str in strs:
5273		l = len(str)
5274		i = html.find(str)
5275		if i >= 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5276			break
5277	if i < 0:
5278		return ''
5279	if not div:
5280		return re.search(r'[-+]?\d*\.\d+|\d+', html[i+l:i+l+50]).group()
5281	n = html[i+l:].find('</div>')
5282	if n < 0:
5283		return ''
5284	return html[i+l:i+l+n]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5285
5286# Function: runSummary
5287# Description:
5288#	 create a summary of tests in a sub-directory
5289def runSummary(subdir, local=True):
5290	inpath = os.path.abspath(subdir)
5291	outpath = inpath
5292	if local:
5293		outpath = os.path.abspath('.')
5294	print('Generating a summary of folder "%s"' % inpath)
5295	testruns = []
 
5296	for dirname, dirnames, filenames in os.walk(subdir):
5297		for filename in filenames:
5298			if(not re.match('.*.html', filename)):
5299				continue
5300			file = os.path.join(dirname, filename)
5301			html = open(file, 'r').read(10000)
5302			suspend = find_in_html(html,
5303				['Kernel Suspend: ', 'Kernel Suspend Time: '])
5304			resume = find_in_html(html,
5305				['Kernel Resume: ', 'Kernel Resume Time: '])
5306			line = find_in_html(html, ['<div class="stamp">'], True)
5307			stmp = line.split()
5308			if not suspend or not resume or len(stmp) < 4:
5309				continue
5310			data = {
5311				'host': stmp[0],
5312				'kernel': stmp[1],
5313				'mode': stmp[2],
5314				'time': string.join(stmp[3:], ' '),
5315				'suspend': suspend,
5316				'resume': resume,
5317				'url': os.path.relpath(file, outpath),
5318			}
5319			if len(stmp) == 7:
5320				data['kernel'] = 'unknown'
5321				data['mode'] = stmp[1]
5322				data['time'] = string.join(stmp[2:], ' ')
5323			testruns.append(data)
5324	outfile = os.path.join(outpath, 'summary.html')
5325	print('Summary file: %s' % outfile)
5326	createHTMLSummarySimple(testruns, outfile, inpath)
 
 
 
 
 
 
 
 
 
 
 
 
 
5327
5328# Function: checkArgBool
5329# Description:
5330#	 check if a boolean string value is true or false
5331def checkArgBool(name, value):
5332	if value in switchvalues:
5333		if value in switchoff:
5334			return False
5335		return True
5336	doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
5337	return False
5338
5339# Function: configFromFile
5340# Description:
5341#	 Configure the script via the info in a config file
5342def configFromFile(file):
5343	Config = ConfigParser.ConfigParser()
5344
5345	Config.read(file)
5346	sections = Config.sections()
5347	overridekprobes = False
5348	overridedevkprobes = False
5349	if 'Settings' in sections:
5350		for opt in Config.options('Settings'):
5351			value = Config.get('Settings', opt).lower()
5352			option = opt.lower()
5353			if(option == 'verbose'):
5354				sysvals.verbose = checkArgBool(option, value)
5355			elif(option == 'addlogs'):
5356				sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
5357			elif(option == 'dev'):
5358				sysvals.usedevsrc = checkArgBool(option, value)
5359			elif(option == 'proc'):
5360				sysvals.useprocmon = checkArgBool(option, value)
5361			elif(option == 'x2'):
5362				if checkArgBool(option, value):
5363					sysvals.execcount = 2
5364			elif(option == 'callgraph'):
5365				sysvals.usecallgraph = checkArgBool(option, value)
5366			elif(option == 'override-timeline-functions'):
5367				overridekprobes = checkArgBool(option, value)
5368			elif(option == 'override-dev-timeline-functions'):
5369				overridedevkprobes = checkArgBool(option, value)
5370			elif(option == 'skiphtml'):
5371				sysvals.skiphtml = checkArgBool(option, value)
5372			elif(option == 'sync'):
5373				sysvals.sync = checkArgBool(option, value)
5374			elif(option == 'rs' or option == 'runtimesuspend'):
5375				if value in switchvalues:
5376					if value in switchoff:
5377						sysvals.rs = -1
5378					else:
5379						sysvals.rs = 1
5380				else:
5381					doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
5382			elif(option == 'display'):
5383				if value in switchvalues:
5384					if value in switchoff:
5385						sysvals.display = -1
5386					else:
5387						sysvals.display = 1
5388				else:
5389					doError('invalid value --> (%s: %s), use "on/off"' % (option, value), True)
5390			elif(option == 'gzip'):
5391				sysvals.gzip = checkArgBool(option, value)
5392			elif(option == 'cgfilter'):
5393				sysvals.setCallgraphFilter(value)
5394			elif(option == 'cgskip'):
5395				if value in switchoff:
5396					sysvals.cgskip = ''
5397				else:
5398					sysvals.cgskip = sysvals.configFile(val)
5399					if(not sysvals.cgskip):
5400						doError('%s does not exist' % sysvals.cgskip)
5401			elif(option == 'cgtest'):
5402				sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
5403			elif(option == 'cgphase'):
5404				d = Data(0)
5405				if value not in d.phases:
5406					doError('invalid phase --> (%s: %s), valid phases are %s'\
5407						% (option, value, d.phases), True)
5408				sysvals.cgphase = value
5409			elif(option == 'fadd'):
5410				file = sysvals.configFile(value)
5411				if(not file):
5412					doError('%s does not exist' % value)
5413				sysvals.addFtraceFilterFunctions(file)
5414			elif(option == 'result'):
5415				sysvals.result = value
5416			elif(option == 'multi'):
5417				nums = value.split()
5418				if len(nums) != 2:
5419					doError('multi requires 2 integers (exec_count and delay)', True)
5420				sysvals.multitest['run'] = True
5421				sysvals.multitest['count'] = getArgInt('multi: n d (exec count)', nums[0], 2, 1000000, False)
5422				sysvals.multitest['delay'] = getArgInt('multi: n d (delay between tests)', nums[1], 0, 3600, False)
5423			elif(option == 'devicefilter'):
5424				sysvals.setDeviceFilter(value)
5425			elif(option == 'expandcg'):
5426				sysvals.cgexp = checkArgBool(option, value)
5427			elif(option == 'srgap'):
5428				if checkArgBool(option, value):
5429					sysvals.srgap = 5
5430			elif(option == 'mode'):
5431				sysvals.suspendmode = value
5432			elif(option == 'command' or option == 'cmd'):
5433				sysvals.testcommand = value
5434			elif(option == 'x2delay'):
5435				sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
5436			elif(option == 'predelay'):
5437				sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
5438			elif(option == 'postdelay'):
5439				sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
5440			elif(option == 'maxdepth'):
5441				sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
5442			elif(option == 'rtcwake'):
5443				if value in switchoff:
5444					sysvals.rtcwake = False
5445				else:
5446					sysvals.rtcwake = True
5447					sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
5448			elif(option == 'timeprec'):
5449				sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
5450			elif(option == 'mindev'):
5451				sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
5452			elif(option == 'callloop-maxgap'):
5453				sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
5454			elif(option == 'callloop-maxlen'):
5455				sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
5456			elif(option == 'mincg'):
5457				sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
5458			elif(option == 'bufsize'):
5459				sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
5460			elif(option == 'output-dir'):
5461				sysvals.outdir = sysvals.setOutputFolder(value)
5462
5463	if sysvals.suspendmode == 'command' and not sysvals.testcommand:
5464		doError('No command supplied for mode "command"')
5465
5466	# compatibility errors
5467	if sysvals.usedevsrc and sysvals.usecallgraph:
5468		doError('-dev is not compatible with -f')
5469	if sysvals.usecallgraph and sysvals.useprocmon:
5470		doError('-proc is not compatible with -f')
5471
5472	if overridekprobes:
5473		sysvals.tracefuncs = dict()
5474	if overridedevkprobes:
5475		sysvals.dev_tracefuncs = dict()
5476
5477	kprobes = dict()
5478	kprobesec = 'dev_timeline_functions_'+platform.machine()
5479	if kprobesec in sections:
5480		for name in Config.options(kprobesec):
5481			text = Config.get(kprobesec, name)
5482			kprobes[name] = (text, True)
5483	kprobesec = 'timeline_functions_'+platform.machine()
5484	if kprobesec in sections:
5485		for name in Config.options(kprobesec):
5486			if name in kprobes:
5487				doError('Duplicate timeline function found "%s"' % (name))
5488			text = Config.get(kprobesec, name)
5489			kprobes[name] = (text, False)
5490
5491	for name in kprobes:
5492		function = name
5493		format = name
5494		color = ''
5495		args = dict()
5496		text, dev = kprobes[name]
5497		data = text.split()
5498		i = 0
5499		for val in data:
5500			# bracketted strings are special formatting, read them separately
5501			if val[0] == '[' and val[-1] == ']':
5502				for prop in val[1:-1].split(','):
5503					p = prop.split('=')
5504					if p[0] == 'color':
5505						try:
5506							color = int(p[1], 16)
5507							color = '#'+p[1]
5508						except:
5509							color = p[1]
5510				continue
5511			# first real arg should be the format string
5512			if i == 0:
5513				format = val
5514			# all other args are actual function args
5515			else:
5516				d = val.split('=')
5517				args[d[0]] = d[1]
5518			i += 1
5519		if not function or not format:
5520			doError('Invalid kprobe: %s' % name)
5521		for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
5522			if arg not in args:
5523				doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
5524		if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
5525			doError('Duplicate timeline function found "%s"' % (name))
5526
5527		kp = {
5528			'name': name,
5529			'func': function,
5530			'format': format,
5531			sysvals.archargs: args
5532		}
5533		if color:
5534			kp['color'] = color
5535		if dev:
5536			sysvals.dev_tracefuncs[name] = kp
5537		else:
5538			sysvals.tracefuncs[name] = kp
5539
5540# Function: printHelp
5541# Description:
5542#	 print out the help text
5543def printHelp():
5544	print('')
5545	print('%s v%s' % (sysvals.title, sysvals.version))
5546	print('Usage: sudo sleepgraph <options> <commands>')
5547	print('')
5548	print('Description:')
5549	print('  This tool is designed to assist kernel and OS developers in optimizing')
5550	print('  their linux stack\'s suspend/resume time. Using a kernel image built')
5551	print('  with a few extra options enabled, the tool will execute a suspend and')
5552	print('  capture dmesg and ftrace data until resume is complete. This data is')
5553	print('  transformed into a device timeline and an optional callgraph to give')
5554	print('  a detailed view of which devices/subsystems are taking the most')
5555	print('  time in suspend/resume.')
5556	print('')
5557	print('  If no specific command is given, the default behavior is to initiate')
5558	print('  a suspend/resume and capture the dmesg/ftrace output as an html timeline.')
5559	print('')
5560	print('  Generates output files in subdirectory: suspend-yymmdd-HHMMSS')
5561	print('   HTML output:                    <hostname>_<mode>.html')
5562	print('   raw dmesg output:               <hostname>_<mode>_dmesg.txt')
5563	print('   raw ftrace output:              <hostname>_<mode>_ftrace.txt')
5564	print('')
5565	print('Options:')
5566	print('   -h           Print this help text')
5567	print('   -v           Print the current tool version')
5568	print('   -config fn   Pull arguments and config options from file fn')
5569	print('   -verbose     Print extra information during execution and analysis')
5570	print('   -m mode      Mode to initiate for suspend (default: %s)') % (sysvals.suspendmode)
5571	print('   -o name      Overrides the output subdirectory name when running a new test')
5572	print('                default: suspend-{date}-{time}')
5573	print('   -rtcwake t   Wakeup t seconds after suspend, set t to "off" to disable (default: 15)')
5574	print('   -addlogs     Add the dmesg and ftrace logs to the html output')
5575	print('   -srgap       Add a visible gap in the timeline between sus/res (default: disabled)')
5576	print('   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)')
5577	print('   -result fn   Export a results table to a text file for parsing.')
5578	print('  [testprep]')
5579	print('   -sync        Sync the filesystems before starting the test')
5580	print('   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test')
5581	print('   -display on/off  Turn the display on or off for the test')
5582	print('  [advanced]')
5583	print('   -gzip        Gzip the trace and dmesg logs to save space')
5584	print('   -cmd {s}     Run the timeline over a custom command, e.g. "sync -d"')
5585	print('   -proc        Add usermode process info into the timeline (default: disabled)')
5586	print('   -dev         Add kernel function calls and threads to the timeline (default: disabled)')
5587	print('   -x2          Run two suspend/resumes back to back (default: disabled)')
5588	print('   -x2delay t   Include t ms delay between multiple test runs (default: 0 ms)')
5589	print('   -predelay t  Include t ms delay before 1st suspend (default: 0 ms)')
5590	print('   -postdelay t Include t ms delay after last resume (default: 0 ms)')
5591	print('   -mindev ms   Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)')
5592	print('   -multi n d   Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
5593	print('                be created in a new subdirectory with a summary page.')
5594	print('  [debug]')
5595	print('   -f           Use ftrace to create device callgraphs (default: disabled)')
5596	print('   -maxdepth N  limit the callgraph data to N call levels (default: 0=all)')
5597	print('   -expandcg    pre-expand the callgraph data in the html output (default: disabled)')
5598	print('   -fadd file   Add functions to be graphed in the timeline from a list in a text file')
5599	print('   -filter "d1,d2,..." Filter out all but this comma-delimited list of device names')
5600	print('   -mincg  ms   Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)')
5601	print('   -cgphase P   Only show callgraph data for phase P (e.g. suspend_late)')
5602	print('   -cgtest N    Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)')
5603	print('   -timeprec N  Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
5604	print('   -cgfilter S  Filter the callgraph output in the timeline')
5605	print('   -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)')
5606	print('   -bufsize N   Set trace buffer size to N kilo-bytes (default: all of free memory)')
5607	print('')
5608	print('Other commands:')
5609	print('   -modes       List available suspend modes')
5610	print('   -status      Test to see if the system is enabled to run this tool')
5611	print('   -fpdt        Print out the contents of the ACPI Firmware Performance Data Table')
5612	print('   -sysinfo     Print out system info extracted from BIOS')
5613	print('   -devinfo     Print out the pm settings of all devices which support runtime suspend')
5614	print('   -flist       Print the list of functions currently being captured in ftrace')
5615	print('   -flistall    Print all functions capable of being captured in ftrace')
5616	print('   -summary directory  Create a summary of all test in this dir')
5617	print('  [redo]')
5618	print('   -ftrace ftracefile  Create HTML output using ftrace input (used with -dmesg)')
5619	print('   -dmesg dmesgfile    Create HTML output using dmesg (used with -ftrace)')
5620	print('')
 
 
 
 
 
 
 
 
 
5621	return True
5622
5623# ----------------- MAIN --------------------
5624# exec start (skipped if script is loaded as library)
5625if __name__ == '__main__':
 
5626	cmd = ''
5627	simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-devinfo', '-status']
 
 
5628	if '-f' in sys.argv:
5629		sysvals.cgskip = sysvals.configFile('cgskip.txt')
5630	# loop through the command line arguments
5631	args = iter(sys.argv[1:])
5632	for arg in args:
5633		if(arg == '-m'):
5634			try:
5635				val = args.next()
5636			except:
5637				doError('No mode supplied', True)
5638			if val == 'command' and not sysvals.testcommand:
5639				doError('No command supplied for mode "command"', True)
5640			sysvals.suspendmode = val
5641		elif(arg in simplecmds):
5642			cmd = arg[1:]
5643		elif(arg == '-h'):
5644			printHelp()
5645			sys.exit()
5646		elif(arg == '-v'):
5647			print("Version %s" % sysvals.version)
5648			sys.exit()
5649		elif(arg == '-x2'):
5650			sysvals.execcount = 2
5651		elif(arg == '-x2delay'):
5652			sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
5653		elif(arg == '-predelay'):
5654			sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
5655		elif(arg == '-postdelay'):
5656			sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
5657		elif(arg == '-f'):
5658			sysvals.usecallgraph = True
 
 
 
 
5659		elif(arg == '-skiphtml'):
5660			sysvals.skiphtml = True
5661		elif(arg == '-cgdump'):
5662			sysvals.cgdump = True
 
 
 
 
5663		elif(arg == '-addlogs'):
5664			sysvals.dmesglog = sysvals.ftracelog = True
 
 
 
 
 
 
 
 
5665		elif(arg == '-verbose'):
5666			sysvals.verbose = True
5667		elif(arg == '-proc'):
5668			sysvals.useprocmon = True
5669		elif(arg == '-dev'):
5670			sysvals.usedevsrc = True
5671		elif(arg == '-sync'):
5672			sysvals.sync = True
 
 
5673		elif(arg == '-gzip'):
5674			sysvals.gzip = True
 
 
 
 
 
 
 
 
 
 
5675		elif(arg == '-rs'):
5676			try:
5677				val = args.next()
5678			except:
5679				doError('-rs requires "enable" or "disable"', True)
5680			if val.lower() in switchvalues:
5681				if val.lower() in switchoff:
5682					sysvals.rs = -1
5683				else:
5684					sysvals.rs = 1
5685			else:
5686				doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
5687		elif(arg == '-display'):
5688			try:
5689				val = args.next()
5690			except:
5691				doError('-display requires "on" or "off"', True)
5692			if val.lower() in switchvalues:
5693				if val.lower() in switchoff:
5694					sysvals.display = -1
5695				else:
5696					sysvals.display = 1
5697			else:
5698				doError('invalid option: %s, use "on/off"' % val, True)
5699		elif(arg == '-maxdepth'):
5700			sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
5701		elif(arg == '-rtcwake'):
5702			try:
5703				val = args.next()
5704			except:
5705				doError('No rtcwake time supplied', True)
5706			if val.lower() in switchoff:
5707				sysvals.rtcwake = False
5708			else:
5709				sysvals.rtcwake = True
5710				sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
5711		elif(arg == '-timeprec'):
5712			sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
5713		elif(arg == '-mindev'):
5714			sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
5715		elif(arg == '-mincg'):
5716			sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
5717		elif(arg == '-bufsize'):
5718			sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
5719		elif(arg == '-cgtest'):
5720			sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
5721		elif(arg == '-cgphase'):
5722			try:
5723				val = args.next()
5724			except:
5725				doError('No phase name supplied', True)
5726			d = Data(0)
5727			if val not in d.phases:
5728				doError('invalid phase --> (%s: %s), valid phases are %s'\
5729					% (arg, val, d.phases), True)
5730			sysvals.cgphase = val
5731		elif(arg == '-cgfilter'):
5732			try:
5733				val = args.next()
5734			except:
5735				doError('No callgraph functions supplied', True)
5736			sysvals.setCallgraphFilter(val)
 
 
 
 
 
 
5737		elif(arg == '-cgskip'):
5738			try:
5739				val = args.next()
5740			except:
5741				doError('No file supplied', True)
5742			if val.lower() in switchoff:
5743				sysvals.cgskip = ''
5744			else:
5745				sysvals.cgskip = sysvals.configFile(val)
5746				if(not sysvals.cgskip):
5747					doError('%s does not exist' % sysvals.cgskip)
5748		elif(arg == '-callloop-maxgap'):
5749			sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
5750		elif(arg == '-callloop-maxlen'):
5751			sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
5752		elif(arg == '-cmd'):
5753			try:
5754				val = args.next()
5755			except:
5756				doError('No command string supplied', True)
5757			sysvals.testcommand = val
5758			sysvals.suspendmode = 'command'
5759		elif(arg == '-expandcg'):
5760			sysvals.cgexp = True
5761		elif(arg == '-srgap'):
5762			sysvals.srgap = 5
 
 
5763		elif(arg == '-multi'):
5764			sysvals.multitest['run'] = True
5765			sysvals.multitest['count'] = getArgInt('-multi n d (exec count)', args, 2, 1000000)
5766			sysvals.multitest['delay'] = getArgInt('-multi n d (delay between tests)', args, 0, 3600)
 
 
5767		elif(arg == '-o'):
5768			try:
5769				val = args.next()
5770			except:
5771				doError('No subdirectory name supplied', True)
5772			sysvals.outdir = sysvals.setOutputFolder(val)
5773		elif(arg == '-config'):
5774			try:
5775				val = args.next()
5776			except:
5777				doError('No text file supplied', True)
5778			file = sysvals.configFile(val)
5779			if(not file):
5780				doError('%s does not exist' % val)
5781			configFromFile(file)
5782		elif(arg == '-fadd'):
5783			try:
5784				val = args.next()
5785			except:
5786				doError('No text file supplied', True)
5787			file = sysvals.configFile(val)
5788			if(not file):
5789				doError('%s does not exist' % val)
5790			sysvals.addFtraceFilterFunctions(file)
5791		elif(arg == '-dmesg'):
5792			try:
5793				val = args.next()
5794			except:
5795				doError('No dmesg file supplied', True)
5796			sysvals.notestrun = True
5797			sysvals.dmesgfile = val
5798			if(os.path.exists(sysvals.dmesgfile) == False):
5799				doError('%s does not exist' % sysvals.dmesgfile)
5800		elif(arg == '-ftrace'):
5801			try:
5802				val = args.next()
5803			except:
5804				doError('No ftrace file supplied', True)
5805			sysvals.notestrun = True
5806			sysvals.ftracefile = val
5807			if(os.path.exists(sysvals.ftracefile) == False):
5808				doError('%s does not exist' % sysvals.ftracefile)
5809		elif(arg == '-summary'):
5810			try:
5811				val = args.next()
5812			except:
5813				doError('No directory supplied', True)
5814			cmd = 'summary'
5815			sysvals.outdir = val
5816			sysvals.notestrun = True
5817			if(os.path.isdir(val) == False):
5818				doError('%s is not accesible' % val)
5819		elif(arg == '-filter'):
5820			try:
5821				val = args.next()
5822			except:
5823				doError('No devnames supplied', True)
5824			sysvals.setDeviceFilter(val)
5825		elif(arg == '-result'):
5826			try:
5827				val = args.next()
5828			except:
5829				doError('No result file supplied', True)
5830			sysvals.result = val
 
5831		else:
5832			doError('Invalid argument: '+arg, True)
5833
5834	# compatibility errors
5835	if(sysvals.usecallgraph and sysvals.usedevsrc):
5836		doError('-dev is not compatible with -f')
5837	if(sysvals.usecallgraph and sysvals.useprocmon):
5838		doError('-proc is not compatible with -f')
5839
5840	if sysvals.usecallgraph and sysvals.cgskip:
5841		sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
5842		sysvals.setCallgraphBlacklist(sysvals.cgskip)
5843
5844	# callgraph size cannot exceed device size
5845	if sysvals.mincglen < sysvals.mindevlen:
5846		sysvals.mincglen = sysvals.mindevlen
5847
5848	# remove existing buffers before calculating memory
5849	if(sysvals.usecallgraph or sysvals.usedevsrc):
5850		sysvals.fsetVal('16', 'buffer_size_kb')
5851	sysvals.cpuInfo()
5852
5853	# just run a utility command and exit
5854	if(cmd != ''):
 
5855		if(cmd == 'status'):
5856			statusCheck(True)
 
5857		elif(cmd == 'fpdt'):
5858			getFPDT(True)
 
5859		elif(cmd == 'sysinfo'):
5860			sysvals.printSystemInfo(True)
5861		elif(cmd == 'devinfo'):
5862			deviceInfo()
5863		elif(cmd == 'modes'):
5864			print getModes()
5865		elif(cmd == 'flist'):
5866			sysvals.getFtraceFilterFunctions(True)
5867		elif(cmd == 'flistall'):
5868			sysvals.getFtraceFilterFunctions(False)
5869		elif(cmd == 'summary'):
5870			runSummary(sysvals.outdir, True)
5871		sys.exit()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5872
5873	# if instructed, re-analyze existing data files
5874	if(sysvals.notestrun):
5875		stamp = rerunTest()
5876		sysvals.outputResult(stamp)
5877		sys.exit()
5878
5879	# verify that we can run a test
5880	if(not statusCheck()):
5881		doError('Check FAILED, aborting the test run!')
 
5882
5883	# extract mem modes and convert
5884	mode = sysvals.suspendmode
5885	if 'mem' == mode[:3]:
5886		if '-' in mode:
5887			memmode = mode.split('-')[-1]
5888		else:
5889			memmode = 'deep'
5890		if memmode == 'shallow':
5891			mode = 'standby'
5892		elif memmode ==  's2idle':
5893			mode = 'freeze'
5894		else:
5895			mode = 'mem'
5896		sysvals.memmode = memmode
5897		sysvals.suspendmode = mode
5898
 
 
5899	sysvals.systemInfo(dmidecode(sysvals.mempath))
5900
5901	setRuntimeSuspend(True)
5902	if sysvals.display:
5903		call('xset -d :0.0 dpms 0 0 0', shell=True)
5904		call('xset -d :0.0 s off', shell=True)
5905	if sysvals.multitest['run']:
5906		# run multiple tests in a separate subdirectory
5907		if not sysvals.outdir:
5908			s = 'suspend-x%d' % sysvals.multitest['count']
5909			sysvals.outdir = datetime.now().strftime(s+'-%y%m%d-%H%M%S')
 
 
 
5910		if not os.path.isdir(sysvals.outdir):
5911			os.mkdir(sysvals.outdir)
 
 
 
 
5912		for i in range(sysvals.multitest['count']):
5913			if(i != 0):
5914				print('Waiting %d seconds...' % (sysvals.multitest['delay']))
 
5915				time.sleep(sysvals.multitest['delay'])
5916			print('TEST (%d/%d) START' % (i+1, sysvals.multitest['count']))
5917			fmt = 'suspend-%y%m%d-%H%M%S'
5918			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
5919			runTest(i+1)
5920			print('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count']))
5921			sysvals.logmsg = ''
 
 
 
 
 
 
 
5922		if not sysvals.skiphtml:
5923			runSummary(sysvals.outdir, False)
5924		sysvals.sudouser(sysvals.outdir)
5925	else:
5926		if sysvals.outdir:
5927			sysvals.testdir = sysvals.outdir
5928		# run the test in the current directory
5929		runTest()
 
 
5930	if sysvals.display:
5931		call('xset -d :0.0 s reset', shell=True)
5932	setRuntimeSuspend(False)