Linux Audio

Check our new training course

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