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