Linux Audio

Check our new training course

In-person Linux kernel drivers training

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