0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
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
0082
0083
0084
0085
0086
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
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
0241 'acpi_resume_power_resources': {},
0242 'acpi_ps_execute_method': { 'args_x86_64': {
0243 'fullpath':'+0(+40(%di)):string',
0244 }},
0245
0246 'mei_reset': {},
0247
0248 'ext4_sync_fs': {},
0249
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
0273 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
0274
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
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
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
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
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
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
0673 rejects = []
0674
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
0696 for name in rejects:
0697 self.kprobes.pop(name)
0698
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
0782 self.fsetVal('0', 'tracing_on')
0783 self.cleanupFtrace()
0784
0785 self.fsetVal('global', 'trace_clock')
0786 self.fsetVal('nop', 'current_tracer')
0787
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
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
0805 if(self.usecallgraph):
0806
0807 self.fsetVal('function_graph', 'current_tracer')
0808 self.fsetVal('', 'set_ftrace_filter')
0809
0810 fp = open(self.tpath+'set_ftrace_notrace', 'w')
0811 fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
0812 fp.close()
0813
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
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
0848 events = iter(self.traceevents)
0849 for e in events:
0850 self.fsetVal('1', 'events/power/'+e+'/enable')
0851
0852 self.fsetVal('', 'trace')
0853 def verifyFtrace(self):
0854
0855 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
0856 'trace_marker', 'trace_options', 'tracing_on']
0857
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
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
0968 if not os.path.exists(self.ftracefile):
0969 return False
0970 footer = '#\n'
0971
0972
0973 if self.suspendmode == 'command' and self.testcommand:
0974 footer += '# platform-testcmd: %s\n' % (self.testcommand)
0975
0976
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
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
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
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
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
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
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
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
1361
1362
1363
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
1393
1394
1395
1396 class DeviceNode:
1397 def __init__(self, nodename, nodedepth):
1398 self.name = nodename
1399 self.children = []
1400 self.depth = nodedepth
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
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
1460 self.end = 0.0
1461 self.hwstart = 0
1462 self.hwend = 0
1463 self.tSuspended = 0.0
1464 self.tResumed = 0.0
1465 self.tKernSus = 0.0
1466 self.tKernRes = 0.0
1467 self.fwValid = False
1468 self.fwSuspend = 0
1469 self.fwResume = 0
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()
1479 self.testnumber = num
1480 self.idstr = idchar[num]
1481 self.dmesgtext = []
1482 self.dmesg = dict()
1483 self.errorinfo = {'suspend':[],'resume':[]}
1484 self.tLow = []
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
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
1597 if dev['pid'] != pid:
1598 continue
1599 devS = dev['start']
1600 devE = dev['end']
1601 if type == 'device':
1602
1603 if(start < devS or start >= devE or end <= devS or end > devE):
1604 continue
1605 elif type == 'thread':
1606
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
1616 phases = self.sortedPhases()
1617 tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1618
1619
1620 if not tgtdev and pid in self.devpids:
1621 return False
1622
1623 if not tgtdev:
1624 tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1625
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
1903 o = max(0, min(end, pend) - max(start, pstart))
1904 if o > 0:
1905 myphases.append(phase)
1906
1907 if o > overlap:
1908 if overlap > 0 and phase == 'post_resume':
1909 continue
1910 targetphase = phase
1911 overlap = o
1912
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
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
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
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
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
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
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
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
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
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
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
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
2163 if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2164 dm['suspend_prepare']['end'] = time
2165
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
2171 if self.tKernRes == 0.0:
2172 self.tKernRes = time
2173
2174 if self.tKernSus == 0.0:
2175 self.tKernSus = time
2176
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
2212
2213
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
2248 dt = self.time - tgt.end
2249
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
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
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
2284 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2285 if(d == 'traceevent'):
2286
2287 msg = m
2288 else:
2289
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
2314 if(d):
2315 self.length = float(d)/1000000
2316
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
2323 if(m[0] == '}'):
2324 self.freturn = True
2325 if(len(m) > 1):
2326
2327 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2328 if(match):
2329 self.name = match.group('n').strip()
2330
2331 else:
2332 self.fcall = True
2333
2334 if(m[-1] == '{'):
2335 match = re.match('^(?P<n>.*) *\(.*', m)
2336 if(match):
2337 self.name = match.group('n').strip()
2338
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
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
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
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
2393
2394
2395
2396
2397
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
2414 if(self.invalid):
2415 if(line.depth == 0 and line.freturn):
2416 return 1
2417 return 0
2418
2419 if(self.depth < 0):
2420 self.invalidate(line)
2421 return 0
2422
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
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
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
2448 mismatch = prelinedep - self.depth
2449 warning = self.sv.verbose and abs(mismatch) > 1
2450 info = []
2451 if mismatch < 0:
2452 idx = 0
2453
2454 while prelinedep < self.depth:
2455 self.depth -= 1
2456 if idx == 0 and last and last.isCall():
2457
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
2477 elif mismatch > 0:
2478 idx = 0
2479 if warning:
2480 info.append(('', last))
2481
2482 while prelinedep > self.depth:
2483 if idx == 0 and line.isReturn():
2484
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
2510 skipadd = False
2511 md = self.sv.max_graph_depth
2512 if line.isCall():
2513
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
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
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
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
2610
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
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
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
2640 return self.repair(cnt)
2641 def deviceMatch(self, pid, data):
2642 found = ''
2643
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
2722
2723
2724
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}"> {2}</div>\n'
2731 def __init__(self, rowheight, scaleheight):
2732 self.html = ''
2733 self.height = 0
2734 self.scaleH = scaleheight
2735 self.rowH = rowheight
2736 self.bodyH = 0
2737 self.rows = 0
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
2760
2761
2762
2763
2764
2765
2766 def getDeviceRows(self, rawlist):
2767
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
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
2800
2801
2802
2803
2804
2805
2806
2807 def getPhaseRows(self, devlist, row=0, sortby='length'):
2808
2809 remaining = len(devlist)
2810 rowdata = dict()
2811 sortdict = dict()
2812 myphases = []
2813
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
2822 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2823 else:
2824
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
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
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
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
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
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
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
2923
2924
2925
2926
2927
2928
2929
2930
2931
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
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
2963
2964
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
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
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
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
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
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
3153
3154
3155
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
3214
3215
3216
3217
3218
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
3227 if not sysvals.usekprobes:
3228 for i in kpcheck:
3229 if i in line:
3230 sysvals.usekprobes = True
3231
3232 check = techeck[:]
3233 for i in techeck:
3234 if i in line:
3235 check.remove(i)
3236 techeck = check
3237
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
3248
3249
3250
3251
3252
3253 def appendIncompleteTraceLog(testruns):
3254
3255 testcnt = len(testruns)
3256 testidx = 0
3257 testrun = []
3258 for data in testruns:
3259 testrun.append(TestRun(data))
3260
3261
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
3269 line = line.replace('\r\n', '')
3270 if tp.stampInfo(line, sysvals):
3271 continue
3272
3273 m = re.match(tp.ftrace_line_fmt, line)
3274 if(not m):
3275 continue
3276
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
3290 if(not t.fcall and not t.freturn and not t.fevent):
3291 continue
3292
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
3301 if(t.endMarker()):
3302 data.setEnd(t.time, t.name)
3303 testidx += 1
3304 if(testidx >= testcnt):
3305 break
3306 continue
3307
3308 if(t.fevent):
3309 continue
3310
3311 elif sysvals.usecallgraph:
3312
3313 if(pid not in testrun[testidx].ftemp):
3314 testrun[testidx].ftemp[pid] = []
3315 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3316
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
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
3351
3352
3353
3354
3355 def loadTraceLog():
3356 tp, data, lines, trace = TestProps(), dict(), [], []
3357 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3358 for line in tf:
3359
3360 line = line.replace('\r\n', '')
3361 if tp.stampInfo(line, sysvals):
3362 continue
3363
3364 if line[0] == '#':
3365 continue
3366
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
3374 t = float(info[0])
3375 if t in data:
3376 data[t].append(info)
3377 else:
3378 data[t] = [info]
3379
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
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
3406
3407
3408
3409
3410
3411
3412
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
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
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
3442 if(not t.fcall and not t.freturn and not t.fevent):
3443 continue
3444
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
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
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
3492
3493 if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3494
3495 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3496 limbo = True
3497 continue
3498
3499 if(t.fevent):
3500 if(t.type == 'suspend_resume'):
3501
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
3514 if(name.split('[')[0] in tracewatch):
3515 continue
3516
3517
3518 if(re.match('suspend_enter\[.*', t.name)):
3519 if(isbegin and data.tKernSus == 0):
3520 data.tKernSus = t.time
3521 continue
3522
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
3532 elif(re.match('dpm_suspend\[.*', t.name)):
3533 phase = data.setPhase('suspend', t.time, isbegin)
3534 continue
3535
3536 elif(re.match('dpm_suspend_late\[.*', t.name)):
3537 phase = data.setPhase('suspend_late', t.time, isbegin)
3538 continue
3539
3540 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3541 phase = data.setPhase('suspend_noirq', t.time, isbegin)
3542 continue
3543
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
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
3577 elif(re.match('dpm_resume_noirq\[.*', t.name)):
3578 phase = data.setPhase('resume_noirq', t.time, isbegin)
3579 continue
3580
3581 elif(re.match('dpm_resume_early\[.*', t.name)):
3582 phase = data.setPhase('resume_early', t.time, isbegin)
3583 continue
3584
3585 elif(re.match('dpm_resume\[.*', t.name)):
3586 phase = data.setPhase('resume', t.time, isbegin)
3587 continue
3588
3589 elif(re.match('dpm_complete\[.*', t.name)):
3590 phase = data.setPhase('resume_complete', t.time, isbegin)
3591 continue
3592
3593 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3594 continue
3595
3596 if(name not in testrun.ttemp):
3597 testrun.ttemp[name] = []
3598
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
3613 testrun.ttemp[name].append(\
3614 {'begin': t.time, 'end': t.time, 'pid': pid})
3615 else:
3616 if(len(testrun.ttemp[name]) > 0):
3617
3618 testrun.ttemp[name][-1]['end'] = t.time
3619
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
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
3647 elif(t.fkprobe):
3648 kprobename = t.type
3649 kprobedata = t.name
3650 key = (kprobename, pid)
3651
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
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
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
3686 elif sysvals.usecallgraph:
3687
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
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
3719 if sysvals.usedevsrc or sysvals.useprocmon:
3720 sysvals.mixedphaseheight = False
3721
3722
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
3734 tlb, tle = data.start, data.end
3735 if i < len(testruns) - 1:
3736 tle = testruns[i+1].data.start
3737
3738 if sysvals.useprocmon:
3739 data.createProcessUsageEvents()
3740
3741 if(sysvals.usetraceevents):
3742
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
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
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
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
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
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
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
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
3866
3867
3868
3869
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
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
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
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
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
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
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
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
4015 if t0 < 0:
4016 data.setStart(ktime)
4017 t0 = ktime
4018 else:
4019 continue
4020
4021
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
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
4104 if(phase in data.sortedPhases()):
4105
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
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
4120 if(not sysvals.usetraceevents):
4121
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
4131 if(re.match('Disabling non-boot CPUs .*', msg)):
4132
4133 cpu_start = ktime
4134 elif(re.match('Enabling non-boot CPUs .*', msg)):
4135
4136 cpu_start = ktime
4137 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
4138
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
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
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
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
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+' → '+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
4282
4283
4284
4285
4286 def createHTMLSummarySimple(testruns, htmlfile, title):
4287
4288 html = summaryCSS('Summary - SleepGraph')
4289
4290
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
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
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
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
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
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
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))
4430 html += td.format(mode)
4431 html += td.format(d[0])
4432 html += td.format(d[1])
4433 html += td.format(d[2])
4434 html += td.format(d[6])
4435 html += td.format(d[7])
4436 html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('')
4437 html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('')
4438 html += td.format(d[8])
4439 html += td.format('%.3f ms' % d[9]) if d[9] else td.format('')
4440 html += td.format(d[10])
4441 html += td.format('%.3f ms' % d[11]) if d[11] else td.format('')
4442 if useturbo:
4443 html += td.format(d[12])
4444 html += td.format(d[13])
4445 if usewifi:
4446 html += td.format(d[14])
4447 html += tdlink.format(d[5]) if d[5] else td.format('')
4448 html += '</tr>\n'
4449 num += 1
4450
4451
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
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
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
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
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'])
4507 html += td.format('%.3f ms' % data['average'])
4508 html += td.format(data['count'])
4509 html += td.format('%.3f ms' % data['worst'])
4510 html += td.format(data['host'])
4511 html += tdlink.format(data['url'])
4512 html += '</tr>\n'
4513 num += 1
4514 html += '</table>\n'
4515
4516
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
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
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'])
4551 html += td.format('center', e['count'])
4552 if multihost:
4553 html += td.format('center', len(e['urls']))
4554 html += td.format('center', testtotal)
4555 html += td.format('center', rate)
4556 html += td.format('center nowrap', '<br>'.join(links))
4557 html += '</tr>\n'
4558 num += 1
4559
4560
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
4578
4579
4580
4581
4582
4583
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
4599 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</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
4621 scaleH = 20
4622 if kerror:
4623 scaleH = 40
4624
4625
4626 devtl = Timeline(30, scaleH)
4627
4628
4629 devtl.createHeader(sysvals, testruns[0].stamp)
4630
4631
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
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
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
4699 t0 = testruns[0].start
4700 tMax = testruns[-1].end
4701 tTotal = tMax - t0
4702
4703
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
4744 devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4745 for data in testruns:
4746
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
4754 for dir in phases:
4755
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
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
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
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
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
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
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
4857 devtl.createTimeScale(m0, mMax, tTotal, dir)
4858 devtl.html += '</div>\n'
4859
4860
4861 devtl.html += '</div>\n</div>\n'
4862
4863
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('_', ' ')
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
4882 hf.write(devtl.html)
4883 hf.write('<div id="devicedetailtitle"></div>\n')
4884 hf.write('<div id="devicedetail" style="display:none;">\n')
4885
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
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
4914 if sysvals.testlog and sysvals.logmsg:
4915 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4916
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('<', '<').replace('>', '>')
4922 hf.write(line)
4923 lf.close()
4924 hf.write('</div>\n')
4925
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
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
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
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
5037
5038
5039
5040
5041
5042 def addScriptCode(hf, testruns):
5043 t0 = testruns[0].start * 1000
5044 tMax = testruns[-1].end * 1000
5045
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
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→</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>←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
5433
5434
5435
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
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
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
5468 for count in range(1,sv.execcount+1):
5469
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
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
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
5489 sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
5490
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
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
5514 turbo = sv.turbostat()
5515 if turbo:
5516 tdata['turbo'] = turbo
5517 else:
5518 pf = open(sv.powerfile, 'w')
5519 pf.write(mode)
5520
5521 try:
5522 pf.close()
5523 except Exception as e:
5524 tdata['error'] = str(e)
5525 sv.dlog('system returned from resume')
5526
5527 sv.testVal('restoreall')
5528 if(sv.rtcwake):
5529 sv.dlog('disable RTC wake alarm')
5530 sv.rtcWakeAlarmOff()
5531
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
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
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
5562 if not quiet:
5563 pprint('CAPTURING DMESG')
5564 sysvals.dlog('EXECUTION TRACE END')
5565 sv.getdmesg(testdata)
5566
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
5585
5586
5587
5588
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
5608
5609
5610
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
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
5667
5668
5669
5670
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
5697
5698
5699
5700
5701
5702
5703
5704 def dmidecode(mempath, fatal=False):
5705 out = dict()
5706
5707
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
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
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
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
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
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
5820
5821
5822
5823
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
5957
5958
5959
5960
5961
5962 def statusCheck(probecheck=False):
5963 status = ''
5964
5965 pprint('Checking this system (%s)...' % platform.node())
5966
5967
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
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
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
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
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
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
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
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
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
6067
6068
6069
6070
6071
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
6080
6081
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
6099
6100
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
6182
6183
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
6203
6204
6205 def runTest(n=0, quiet=False):
6206
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
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
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
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
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
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
6421
6422
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
6459
6460
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
6470
6471
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
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
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
6637 if i == 0:
6638 format = val
6639
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
6666
6667
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
6759
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
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
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
7023 if sysvals.mincglen < sysvals.mindevlen:
7024 sysvals.mincglen = sysvals.mindevlen
7025
7026
7027 if(sysvals.usecallgraph or sysvals.usedevsrc):
7028 sysvals.fsetVal('16', 'buffer_size_kb')
7029 sysvals.cpuInfo()
7030
7031
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
7069 if(sysvals.notestrun):
7070 stamp = rerunTest(sysvals.outdir)
7071 sysvals.outputResult(stamp)
7072 sys.exit(0)
7073
7074
7075 error = statusCheck()
7076 if(error):
7077 doError(error)
7078
7079
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
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
7134 ret = runTest()
7135
7136
7137 if sysvals.display:
7138 sysvals.displayControl('reset')
7139 if sysvals.rs != 0:
7140 sysvals.setRuntimeSuspend(False)
7141 sys.exit(ret)