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