Back to home page

OSCL-LXR

 
 

    


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