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 'suspend_console': {}, 214 'acpi_pm_prepare': {}, 215 'syscore_suspend': {}, 216 'arch_enable_nonboot_cpus_end': {}, 217 'syscore_resume': {}, 218 'acpi_pm_finish': {}, 219 'resume_console': {}, 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', 'resume_console', '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 } 4022 4023 # action table (expected events that occur and show up in dmesg) 4024 at = { 4025 'sync_filesystems': { 4026 'smsg': '.*[Ff]+ilesystems.*', 4027 'emsg': 'PM: Preparing system for[a-z]* sleep.*' }, 4028 'freeze_user_processes': { 4029 'smsg': 'Freezing user space processes.*', 4030 'emsg': 'Freezing remaining freezable tasks.*' }, 4031 'freeze_tasks': { 4032 'smsg': 'Freezing remaining freezable tasks.*', 4033 'emsg': 'PM: Suspending system.*' }, 4034 'ACPI prepare': { 4035 'smsg': 'ACPI: Preparing to enter system sleep state.*', 4036 'emsg': 'PM: Saving platform NVS memory.*' }, 4037 'PM vns': { 4038 'smsg': 'PM: Saving platform NVS memory.*', 4039 'emsg': 'Disabling non-boot CPUs .*' }, 4040 } 4041 4042 t0 = -1.0 4043 cpu_start = -1.0 4044 prevktime = -1.0 4045 actions = dict() 4046 for line in data.dmesgtext: 4047 # parse each dmesg line into the time and message 4048 m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line) 4049 if(m): 4050 val = m.group('ktime') 4051 try: 4052 ktime = float(val) 4053 except: 4054 continue 4055 msg = m.group('msg') 4056 # initialize data start to first line time 4057 if t0 < 0: 4058 data.setStart(ktime) 4059 t0 = ktime 4060 else: 4061 continue 4062 4063 # check for a phase change line 4064 phasechange = False 4065 for p in dm: 4066 for s in dm[p]: 4067 if(re.match(s, msg)): 4068 phasechange, phase = True, p 4069 dm[p] = [s] 4070 break 4071 4072 # hack for determining resume_machine end for freeze 4073 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \ 4074 and phase == 'resume_machine' and \ 4075 data.initcall_debug_call(line, True)): 4076 data.setPhase(phase, ktime, False) 4077 phase = 'resume_noirq' 4078 data.setPhase(phase, ktime, True) 4079 4080 if phasechange: 4081 if phase == 'suspend_prepare': 4082 data.setPhase(phase, ktime, True) 4083 data.setStart(ktime) 4084 data.tKernSus = ktime 4085 elif phase == 'suspend': 4086 lp = data.lastPhase() 4087 if lp: 4088 data.setPhase(lp, ktime, False) 4089 data.setPhase(phase, ktime, True) 4090 elif phase == 'suspend_late': 4091 lp = data.lastPhase() 4092 if lp: 4093 data.setPhase(lp, ktime, False) 4094 data.setPhase(phase, ktime, True) 4095 elif phase == 'suspend_noirq': 4096 lp = data.lastPhase() 4097 if lp: 4098 data.setPhase(lp, ktime, False) 4099 data.setPhase(phase, ktime, True) 4100 elif phase == 'suspend_machine': 4101 lp = data.lastPhase() 4102 if lp: 4103 data.setPhase(lp, ktime, False) 4104 data.setPhase(phase, ktime, True) 4105 elif phase == 'resume_machine': 4106 lp = data.lastPhase() 4107 if(sysvals.suspendmode in ['freeze', 'standby']): 4108 data.tSuspended = prevktime 4109 if lp: 4110 data.setPhase(lp, prevktime, False) 4111 else: 4112 data.tSuspended = ktime 4113 if lp: 4114 data.setPhase(lp, prevktime, False) 4115 data.tResumed = ktime 4116 data.setPhase(phase, ktime, True) 4117 elif phase == 'resume_noirq': 4118 lp = data.lastPhase() 4119 if lp: 4120 data.setPhase(lp, ktime, False) 4121 data.setPhase(phase, ktime, True) 4122 elif phase == 'resume_early': 4123 lp = data.lastPhase() 4124 if lp: 4125 data.setPhase(lp, ktime, False) 4126 data.setPhase(phase, ktime, True) 4127 elif phase == 'resume': 4128 lp = data.lastPhase() 4129 if lp: 4130 data.setPhase(lp, ktime, False) 4131 data.setPhase(phase, ktime, True) 4132 elif phase == 'resume_complete': 4133 lp = data.lastPhase() 4134 if lp: 4135 data.setPhase(lp, ktime, False) 4136 data.setPhase(phase, ktime, True) 4137 elif phase == 'post_resume': 4138 lp = data.lastPhase() 4139 if lp: 4140 data.setPhase(lp, ktime, False) 4141 data.setEnd(ktime) 4142 data.tKernRes = ktime 4143 break 4144 4145 # -- device callbacks -- 4146 if(phase in data.sortedPhases()): 4147 # device init call 4148 t, f, n, p = data.initcall_debug_call(line) 4149 if t and f and n and p: 4150 data.newAction(phase, f, int(n), p, ktime, -1, '') 4151 else: 4152 # device init return 4153 t, f, l = data.initcall_debug_return(line) 4154 if t and f and l: 4155 list = data.dmesg[phase]['list'] 4156 if(f in list): 4157 dev = list[f] 4158 dev['length'] = int(l) 4159 dev['end'] = ktime 4160 4161 # if trace events are not available, these are better than nothing 4162 if(not sysvals.usetraceevents): 4163 # look for known actions 4164 for a in sorted(at): 4165 if(re.match(at[a]['smsg'], msg)): 4166 if(a not in actions): 4167 actions[a] = [{'begin': ktime, 'end': ktime}] 4168 if(re.match(at[a]['emsg'], msg)): 4169 if(a in actions and actions[a][-1]['begin'] == actions[a][-1]['end']): 4170 actions[a][-1]['end'] = ktime 4171 # now look for CPU on/off events 4172 if(re.match(r'Disabling non-boot CPUs .*', msg)): 4173 # start of first cpu suspend 4174 cpu_start = ktime 4175 elif(re.match(r'Enabling non-boot CPUs .*', msg)): 4176 # start of first cpu resume 4177 cpu_start = ktime 4178 elif(re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) \ 4179 or re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg)): 4180 # end of a cpu suspend, start of the next 4181 m = re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) 4182 if(not m): 4183 m = re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg) 4184 cpu = 'CPU'+m.group('cpu') 4185 if(cpu not in actions): 4186 actions[cpu] = [] 4187 actions[cpu].append({'begin': cpu_start, 'end': ktime}) 4188 cpu_start = ktime 4189 elif(re.match(r'CPU(?P<cpu>[0-9]*) is up', msg)): 4190 # end of a cpu resume, start of the next 4191 m = re.match(r'CPU(?P<cpu>[0-9]*) is up', msg) 4192 cpu = 'CPU'+m.group('cpu') 4193 if(cpu not in actions): 4194 actions[cpu] = [] 4195 actions[cpu].append({'begin': cpu_start, 'end': ktime}) 4196 cpu_start = ktime 4197 prevktime = ktime 4198 data.initDevicegroups() 4199 4200 # fill in any missing phases 4201 phasedef = data.phasedef 4202 terr, lp = '', 'suspend_prepare' 4203 if lp not in data.dmesg: 4204 doError('dmesg log format has changed, could not find start of suspend') 4205 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']): 4206 if p not in data.dmesg: 4207 if not terr: 4208 pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp)) 4209 terr = '%s failed in %s phase' % (sysvals.suspendmode, lp) 4210 if data.tSuspended == 0: 4211 data.tSuspended = data.dmesg[lp]['end'] 4212 if data.tResumed == 0: 4213 data.tResumed = data.dmesg[lp]['end'] 4214 sysvals.vprint('WARNING: phase "%s" is missing!' % p) 4215 lp = p 4216 lp = data.sortedPhases()[0] 4217 for p in data.sortedPhases(): 4218 if(p != lp and not ('machine' in p and 'machine' in lp)): 4219 data.dmesg[lp]['end'] = data.dmesg[p]['start'] 4220 lp = p 4221 if data.tSuspended == 0: 4222 data.tSuspended = data.tKernRes 4223 if data.tResumed == 0: 4224 data.tResumed = data.tSuspended 4225 4226 # fill in any actions we've found 4227 for name in sorted(actions): 4228 for event in actions[name]: 4229 data.newActionGlobal(name, event['begin'], event['end']) 4230 4231 if(len(sysvals.devicefilter) > 0): 4232 data.deviceFilter(sysvals.devicefilter) 4233 data.fixupInitcallsThatDidntReturn() 4234 return True 4235 4236def callgraphHTML(sv, hf, num, cg, title, color, devid): 4237 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' 4238 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n' 4239 html_func_end = '</article>\n' 4240 html_func_leaf = '<article>{0} {1}</article>\n' 4241 4242 cgid = devid 4243 if cg.id: 4244 cgid += cg.id 4245 cglen = (cg.end - cg.start) * 1000 4246 if cglen < sv.mincglen: 4247 return num 4248 4249 fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>' 4250 flen = fmt % (cglen, cg.start, cg.end) 4251 hf.write(html_func_top.format(cgid, color, num, title, flen)) 4252 num += 1 4253 for line in cg.list: 4254 if(line.length < 0.000000001): 4255 flen = '' 4256 else: 4257 fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>' 4258 flen = fmt % (line.length*1000, line.time) 4259 if line.isLeaf(): 4260 if line.length * 1000 < sv.mincglen: 4261 continue 4262 hf.write(html_func_leaf.format(line.name, flen)) 4263 elif line.freturn: 4264 hf.write(html_func_end) 4265 else: 4266 hf.write(html_func_start.format(num, line.name, flen)) 4267 num += 1 4268 hf.write(html_func_end) 4269 return num 4270 4271def addCallgraphs(sv, hf, data): 4272 hf.write('<section id="callgraphs" class="callgraph">\n') 4273 # write out the ftrace data converted to html 4274 num = 0 4275 for p in data.sortedPhases(): 4276 if sv.cgphase and p != sv.cgphase: 4277 continue 4278 list = data.dmesg[p]['list'] 4279 for d in data.sortedDevices(p): 4280 if len(sv.cgfilter) > 0 and d not in sv.cgfilter: 4281 continue 4282 dev = list[d] 4283 color = 'white' 4284 if 'color' in data.dmesg[p]: 4285 color = data.dmesg[p]['color'] 4286 if 'color' in dev: 4287 color = dev['color'] 4288 name = d if '[' not in d else d.split('[')[0] 4289 if(d in sv.devprops): 4290 name = sv.devprops[d].altName(d) 4291 if 'drv' in dev and dev['drv']: 4292 name += ' {%s}' % dev['drv'] 4293 if sv.suspendmode in suspendmodename: 4294 name += ' '+p 4295 if('ftrace' in dev): 4296 cg = dev['ftrace'] 4297 if cg.name == sv.ftopfunc: 4298 name = 'top level suspend/resume call' 4299 num = callgraphHTML(sv, hf, num, cg, 4300 name, color, dev['id']) 4301 if('ftraces' in dev): 4302 for cg in dev['ftraces']: 4303 num = callgraphHTML(sv, hf, num, cg, 4304 name+' → '+cg.name, color, dev['id']) 4305 hf.write('\n\n </section>\n') 4306 4307def summaryCSS(title, center=True): 4308 tdcenter = 'text-align:center;' if center else '' 4309 out = '<!DOCTYPE html>\n<html>\n<head>\n\ 4310 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ 4311 <title>'+title+'</title>\n\ 4312 <style type=\'text/css\'>\n\ 4313 .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\ 4314 table {width:100%;border-collapse: collapse;border:1px solid;}\n\ 4315 th {border: 1px solid black;background:#222;color:white;}\n\ 4316 td {font: 14px "Times New Roman";'+tdcenter+'}\n\ 4317 tr.head td {border: 1px solid black;background:#aaa;}\n\ 4318 tr.alt {background-color:#ddd;}\n\ 4319 tr.notice {color:red;}\n\ 4320 .minval {background-color:#BBFFBB;}\n\ 4321 .medval {background-color:#BBBBFF;}\n\ 4322 .maxval {background-color:#FFBBBB;}\n\ 4323 .head a {color:#000;text-decoration: none;}\n\ 4324 </style>\n</head>\n<body>\n' 4325 return out 4326 4327# Function: createHTMLSummarySimple 4328# Description: 4329# Create summary html file for a series of tests 4330# Arguments: 4331# testruns: array of Data objects from parseTraceLog 4332def createHTMLSummarySimple(testruns, htmlfile, title): 4333 # write the html header first (html head, css code, up to body start) 4334 html = summaryCSS('Summary - SleepGraph') 4335 4336 # extract the test data into list 4337 list = dict() 4338 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()] 4339 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0] 4340 num = 0 4341 useturbo = usewifi = False 4342 lastmode = '' 4343 cnt = dict() 4344 for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])): 4345 mode = data['mode'] 4346 if mode not in list: 4347 list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]} 4348 if lastmode and lastmode != mode and num > 0: 4349 for i in range(2): 4350 s = sorted(tMed[i]) 4351 list[lastmode]['med'][i] = s[int(len(s)//2)] 4352 iMed[i] = tMed[i][list[lastmode]['med'][i]] 4353 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num] 4354 list[lastmode]['min'] = tMin 4355 list[lastmode]['max'] = tMax 4356 list[lastmode]['idx'] = (iMin, iMed, iMax) 4357 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()] 4358 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0] 4359 num = 0 4360 pkgpc10 = syslpi = wifi = '' 4361 if 'pkgpc10' in data and 'syslpi' in data: 4362 pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True 4363 if 'wifi' in data: 4364 wifi, usewifi = data['wifi'], True 4365 res = data['result'] 4366 tVal = [float(data['suspend']), float(data['resume'])] 4367 list[mode]['data'].append([data['host'], data['kernel'], 4368 data['time'], tVal[0], tVal[1], data['url'], res, 4369 data['issues'], data['sus_worst'], data['sus_worsttime'], 4370 data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi, 4371 (data['fullmode'] if 'fullmode' in data else mode)]) 4372 idx = len(list[mode]['data']) - 1 4373 if res.startswith('fail in'): 4374 res = 'fail' 4375 if res not in cnt: 4376 cnt[res] = 1 4377 else: 4378 cnt[res] += 1 4379 if res == 'pass': 4380 for i in range(2): 4381 tMed[i][tVal[i]] = idx 4382 tAvg[i] += tVal[i] 4383 if tMin[i] == 0 or tVal[i] < tMin[i]: 4384 iMin[i] = idx 4385 tMin[i] = tVal[i] 4386 if tMax[i] == 0 or tVal[i] > tMax[i]: 4387 iMax[i] = idx 4388 tMax[i] = tVal[i] 4389 num += 1 4390 lastmode = mode 4391 if lastmode and num > 0: 4392 for i in range(2): 4393 s = sorted(tMed[i]) 4394 list[lastmode]['med'][i] = s[int(len(s)//2)] 4395 iMed[i] = tMed[i][list[lastmode]['med'][i]] 4396 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num] 4397 list[lastmode]['min'] = tMin 4398 list[lastmode]['max'] = tMax 4399 list[lastmode]['idx'] = (iMin, iMed, iMax) 4400 4401 # group test header 4402 desc = [] 4403 for ilk in sorted(cnt, reverse=True): 4404 if cnt[ilk] > 0: 4405 desc.append('%d %s' % (cnt[ilk], ilk)) 4406 html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc)) 4407 th = '\t<th>{0}</th>\n' 4408 td = '\t<td>{0}</td>\n' 4409 tdh = '\t<td{1}>{0}</td>\n' 4410 tdlink = '\t<td><a href="{0}">html</a></td>\n' 4411 cols = 12 4412 if useturbo: 4413 cols += 2 4414 if usewifi: 4415 cols += 1 4416 colspan = '%d' % cols 4417 4418 # table header 4419 html += '<table>\n<tr>\n' + th.format('#') +\ 4420 th.format('Mode') + th.format('Host') + th.format('Kernel') +\ 4421 th.format('Test Time') + th.format('Result') + th.format('Issues') +\ 4422 th.format('Suspend') + th.format('Resume') +\ 4423 th.format('Worst Suspend Device') + th.format('SD Time') +\ 4424 th.format('Worst Resume Device') + th.format('RD Time') 4425 if useturbo: 4426 html += th.format('PkgPC10') + th.format('SysLPI') 4427 if usewifi: 4428 html += th.format('Wifi') 4429 html += th.format('Detail')+'</tr>\n' 4430 # export list into html 4431 head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\ 4432 '<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\ 4433 '<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\ 4434 '<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\ 4435 '<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\ 4436 'Resume Avg={6} '+\ 4437 '<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\ 4438 '<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\ 4439 '<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\ 4440 '</tr>\n' 4441 headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\ 4442 colspan+'></td></tr>\n' 4443 for mode in sorted(list): 4444 # header line for each suspend mode 4445 num = 0 4446 tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\ 4447 list[mode]['max'], list[mode]['med'] 4448 count = len(list[mode]['data']) 4449 if 'idx' in list[mode]: 4450 iMin, iMed, iMax = list[mode]['idx'] 4451 html += head.format('%d' % count, mode.upper(), 4452 '%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0], 4453 '%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1], 4454 mode.lower() 4455 ) 4456 else: 4457 iMin = iMed = iMax = [-1, -1, -1] 4458 html += headnone.format('%d' % count, mode.upper()) 4459 for d in list[mode]['data']: 4460 # row classes - alternate row color 4461 rcls = ['alt'] if num % 2 == 1 else [] 4462 if d[6] != 'pass': 4463 rcls.append('notice') 4464 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n' 4465 # figure out if the line has sus or res highlighted 4466 idx = list[mode]['data'].index(d) 4467 tHigh = ['', ''] 4468 for i in range(2): 4469 tag = 's%s' % mode if i == 0 else 'r%s' % mode 4470 if idx == iMin[i]: 4471 tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag 4472 elif idx == iMax[i]: 4473 tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag 4474 elif idx == iMed[i]: 4475 tHigh[i] = ' id="%smed" class=medval title="Median"' % tag 4476 html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row 4477 html += td.format(d[15]) # mode 4478 html += td.format(d[0]) # host 4479 html += td.format(d[1]) # kernel 4480 html += td.format(d[2]) # time 4481 html += td.format(d[6]) # result 4482 html += td.format(d[7]) # issues 4483 html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend 4484 html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume 4485 html += td.format(d[8]) # sus_worst 4486 html += td.format('%.3f ms' % d[9]) if d[9] else td.format('') # sus_worst time 4487 html += td.format(d[10]) # res_worst 4488 html += td.format('%.3f ms' % d[11]) if d[11] else td.format('') # res_worst time 4489 if useturbo: 4490 html += td.format(d[12]) # pkg_pc10 4491 html += td.format(d[13]) # syslpi 4492 if usewifi: 4493 html += td.format(d[14]) # wifi 4494 html += tdlink.format(d[5]) if d[5] else td.format('') # url 4495 html += '</tr>\n' 4496 num += 1 4497 4498 # flush the data to file 4499 hf = open(htmlfile, 'w') 4500 hf.write(html+'</table>\n</body>\n</html>\n') 4501 hf.close() 4502 4503def createHTMLDeviceSummary(testruns, htmlfile, title): 4504 html = summaryCSS('Device Summary - SleepGraph', False) 4505 4506 # create global device list from all tests 4507 devall = dict() 4508 for data in testruns: 4509 host, url, devlist = data['host'], data['url'], data['devlist'] 4510 for type in devlist: 4511 if type not in devall: 4512 devall[type] = dict() 4513 mdevlist, devlist = devall[type], data['devlist'][type] 4514 for name in devlist: 4515 length = devlist[name] 4516 if name not in mdevlist: 4517 mdevlist[name] = {'name': name, 'host': host, 4518 'worst': length, 'total': length, 'count': 1, 4519 'url': url} 4520 else: 4521 if length > mdevlist[name]['worst']: 4522 mdevlist[name]['worst'] = length 4523 mdevlist[name]['url'] = url 4524 mdevlist[name]['host'] = host 4525 mdevlist[name]['total'] += length 4526 mdevlist[name]['count'] += 1 4527 4528 # generate the html 4529 th = '\t<th>{0}</th>\n' 4530 td = '\t<td align=center>{0}</td>\n' 4531 tdr = '\t<td align=right>{0}</td>\n' 4532 tdlink = '\t<td align=center><a href="{0}">html</a></td>\n' 4533 limit = 1 4534 for type in sorted(devall, reverse=True): 4535 num = 0 4536 devlist = devall[type] 4537 # table header 4538 html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \ 4539 (title, type.upper(), limit) 4540 html += '<tr>\n' + '<th align=right>Device Name</th>' +\ 4541 th.format('Average Time') + th.format('Count') +\ 4542 th.format('Worst Time') + th.format('Host (worst time)') +\ 4543 th.format('Link (worst time)') + '</tr>\n' 4544 for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \ 4545 devlist[k]['total'], devlist[k]['name']), reverse=True): 4546 data = devall[type][name] 4547 data['average'] = data['total'] / data['count'] 4548 if data['average'] < limit: 4549 continue 4550 # row classes - alternate row color 4551 rcls = ['alt'] if num % 2 == 1 else [] 4552 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n' 4553 html += tdr.format(data['name']) # name 4554 html += td.format('%.3f ms' % data['average']) # average 4555 html += td.format(data['count']) # count 4556 html += td.format('%.3f ms' % data['worst']) # worst 4557 html += td.format(data['host']) # host 4558 html += tdlink.format(data['url']) # url 4559 html += '</tr>\n' 4560 num += 1 4561 html += '</table>\n' 4562 4563 # flush the data to file 4564 hf = open(htmlfile, 'w') 4565 hf.write(html+'</body>\n</html>\n') 4566 hf.close() 4567 return devall 4568 4569def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''): 4570 multihost = len([e for e in issues if len(e['urls']) > 1]) > 0 4571 html = summaryCSS('Issues Summary - SleepGraph', False) 4572 total = len(testruns) 4573 4574 # generate the html 4575 th = '\t<th>{0}</th>\n' 4576 td = '\t<td align={0}>{1}</td>\n' 4577 tdlink = '<a href="{1}">{0}</a>' 4578 subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues' 4579 html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle) 4580 html += '<tr>\n' + th.format('Issue') + th.format('Count') 4581 if multihost: 4582 html += th.format('Hosts') 4583 html += th.format('Tests') + th.format('Fail Rate') +\ 4584 th.format('First Instance') + '</tr>\n' 4585 4586 num = 0 4587 for e in sorted(issues, key=lambda v:v['count'], reverse=True): 4588 testtotal = 0 4589 links = [] 4590 for host in sorted(e['urls']): 4591 links.append(tdlink.format(host, e['urls'][host][0])) 4592 testtotal += len(e['urls'][host]) 4593 rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total)) 4594 # row classes - alternate row color 4595 rcls = ['alt'] if num % 2 == 1 else [] 4596 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n' 4597 html += td.format('left', e['line']) # issue 4598 html += td.format('center', e['count']) # count 4599 if multihost: 4600 html += td.format('center', len(e['urls'])) # hosts 4601 html += td.format('center', testtotal) # test count 4602 html += td.format('center', rate) # test rate 4603 html += td.format('center nowrap', '<br>'.join(links)) # links 4604 html += '</tr>\n' 4605 num += 1 4606 4607 # flush the data to file 4608 hf = open(htmlfile, 'w') 4609 hf.write(html+'</table>\n'+extra+'</body>\n</html>\n') 4610 hf.close() 4611 return issues 4612 4613def ordinal(value): 4614 suffix = 'th' 4615 if value < 10 or value > 19: 4616 if value % 10 == 1: 4617 suffix = 'st' 4618 elif value % 10 == 2: 4619 suffix = 'nd' 4620 elif value % 10 == 3: 4621 suffix = 'rd' 4622 return '%d%s' % (value, suffix) 4623 4624# Function: createHTML 4625# Description: 4626# Create the output html file from the resident test data 4627# Arguments: 4628# testruns: array of Data objects from parseKernelLog or parseTraceLog 4629# Output: 4630# True if the html file was created, false if it failed 4631def createHTML(testruns, testfail): 4632 if len(testruns) < 1: 4633 pprint('ERROR: Not enough test data to build a timeline') 4634 return 4635 4636 kerror = False 4637 for data in testruns: 4638 if data.kerror: 4639 kerror = True 4640 if(sysvals.suspendmode in ['freeze', 'standby']): 4641 data.trimFreezeTime(testruns[-1].tSuspended) 4642 else: 4643 data.getMemTime() 4644 4645 # html function templates 4646 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</div>\n' 4647 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' 4648 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n' 4649 html_timetotal = '<table class="time1">\n<tr>'\ 4650 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\ 4651 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\ 4652 '</tr>\n</table>\n' 4653 html_timetotal2 = '<table class="time1">\n<tr>'\ 4654 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\ 4655 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\ 4656 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\ 4657 '</tr>\n</table>\n' 4658 html_timetotal3 = '<table class="time1">\n<tr>'\ 4659 '<td class="green">Execution Time: <b>{0} ms</b></td>'\ 4660 '<td class="yellow">Command: <b>{1}</b></td>'\ 4661 '</tr>\n</table>\n' 4662 html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n' 4663 html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>' 4664 html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>' 4665 html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>' 4666 4667 # html format variables 4668 scaleH = 20 4669 if kerror: 4670 scaleH = 40 4671 4672 # device timeline 4673 devtl = Timeline(30, scaleH) 4674 4675 # write the test title and general info header 4676 devtl.createHeader(sysvals, testruns[0].stamp) 4677 4678 # Generate the header for this timeline 4679 for data in testruns: 4680 tTotal = data.end - data.start 4681 if(tTotal == 0): 4682 doError('No timeline data') 4683 if sysvals.suspendmode == 'command': 4684 run_time = '%.0f' % (tTotal * 1000) 4685 if sysvals.testcommand: 4686 testdesc = sysvals.testcommand 4687 else: 4688 testdesc = 'unknown' 4689 if(len(testruns) > 1): 4690 testdesc = ordinal(data.testnumber+1)+' '+testdesc 4691 thtml = html_timetotal3.format(run_time, testdesc) 4692 devtl.html += thtml 4693 continue 4694 # typical full suspend/resume header 4695 stot, rtot = sktime, rktime = data.getTimeValues() 4696 ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', '' 4697 if data.fwValid: 4698 stot += (data.fwSuspend/1000000.0) 4699 rtot += (data.fwResume/1000000.0) 4700 ssrc.append('firmware') 4701 rsrc.append('firmware') 4702 testdesc = 'Total' 4703 if 'time' in data.wifi and data.wifi['stat'] != 'timeout': 4704 rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0) 4705 rsrc.append('wifi') 4706 testdesc = 'Total' 4707 suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot 4708 stitle = 'time from kernel suspend start to %s mode [%s time]' % \ 4709 (sysvals.suspendmode, ' & '.join(ssrc)) 4710 rtitle = 'time from %s mode to kernel resume complete [%s time]' % \ 4711 (sysvals.suspendmode, ' & '.join(rsrc)) 4712 if(len(testruns) > 1): 4713 testdesc = testdesc2 = ordinal(data.testnumber+1) 4714 testdesc2 += ' ' 4715 if(len(data.tLow) == 0): 4716 thtml = html_timetotal.format(suspend_time, \ 4717 resume_time, testdesc, stitle, rtitle) 4718 else: 4719 low_time = '+'.join(data.tLow) 4720 thtml = html_timetotal2.format(suspend_time, low_time, \ 4721 resume_time, testdesc, stitle, rtitle) 4722 devtl.html += thtml 4723 if not data.fwValid and 'dev' not in data.wifi: 4724 continue 4725 # extra detail when the times come from multiple sources 4726 thtml = '<table class="time2">\n<tr>' 4727 thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green') 4728 if data.fwValid: 4729 sftime = '%.3f'%(data.fwSuspend / 1000000.0) 4730 rftime = '%.3f'%(data.fwResume / 1000000.0) 4731 thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green') 4732 thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow') 4733 thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow') 4734 if 'time' in data.wifi: 4735 if data.wifi['stat'] != 'timeout': 4736 wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0)) 4737 else: 4738 wtime = 'TIMEOUT' 4739 thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev']) 4740 thtml += '</tr>\n</table>\n' 4741 devtl.html += thtml 4742 if testfail: 4743 devtl.html += html_fail.format(testfail) 4744 4745 # time scale for potentially multiple datasets 4746 t0 = testruns[0].start 4747 tMax = testruns[-1].end 4748 tTotal = tMax - t0 4749 4750 # determine the maximum number of rows we need to draw 4751 fulllist = [] 4752 threadlist = [] 4753 pscnt = 0 4754 devcnt = 0 4755 for data in testruns: 4756 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen) 4757 for group in data.devicegroups: 4758 devlist = [] 4759 for phase in group: 4760 for devname in sorted(data.tdevlist[phase]): 4761 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname]) 4762 devlist.append(d) 4763 if d.isa('kth'): 4764 threadlist.append(d) 4765 else: 4766 if d.isa('ps'): 4767 pscnt += 1 4768 else: 4769 devcnt += 1 4770 fulllist.append(d) 4771 if sysvals.mixedphaseheight: 4772 devtl.getPhaseRows(devlist) 4773 if not sysvals.mixedphaseheight: 4774 if len(threadlist) > 0 and len(fulllist) > 0: 4775 if pscnt > 0 and devcnt > 0: 4776 msg = 'user processes & device pm callbacks' 4777 elif pscnt > 0: 4778 msg = 'user processes' 4779 else: 4780 msg = 'device pm callbacks' 4781 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end) 4782 fulllist.insert(0, d) 4783 devtl.getPhaseRows(fulllist) 4784 if len(threadlist) > 0: 4785 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end) 4786 threadlist.insert(0, d) 4787 devtl.getPhaseRows(threadlist, devtl.rows) 4788 devtl.calcTotalRows() 4789 4790 # draw the full timeline 4791 devtl.createZoomBox(sysvals.suspendmode, len(testruns)) 4792 for data in testruns: 4793 # draw each test run and block chronologically 4794 phases = {'suspend':[],'resume':[]} 4795 for phase in data.sortedPhases(): 4796 if data.dmesg[phase]['start'] >= data.tSuspended: 4797 phases['resume'].append(phase) 4798 else: 4799 phases['suspend'].append(phase) 4800 # now draw the actual timeline blocks 4801 for dir in phases: 4802 # draw suspend and resume blocks separately 4803 bname = '%s%d' % (dir[0], data.testnumber) 4804 if dir == 'suspend': 4805 m0 = data.start 4806 mMax = data.tSuspended 4807 left = '%f' % (((m0-t0)*100.0)/tTotal) 4808 else: 4809 m0 = data.tSuspended 4810 mMax = data.end 4811 # in an x2 run, remove any gap between blocks 4812 if len(testruns) > 1 and data.testnumber == 0: 4813 mMax = testruns[1].start 4814 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal) 4815 mTotal = mMax - m0 4816 # if a timeline block is 0 length, skip altogether 4817 if mTotal == 0: 4818 continue 4819 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal) 4820 devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH) 4821 for b in phases[dir]: 4822 # draw the phase color background 4823 phase = data.dmesg[b] 4824 length = phase['end']-phase['start'] 4825 left = '%f' % (((phase['start']-m0)*100.0)/mTotal) 4826 width = '%f' % ((length*100.0)/mTotal) 4827 devtl.html += devtl.html_phase.format(left, width, \ 4828 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ 4829 data.dmesg[b]['color'], '') 4830 for e in data.errorinfo[dir]: 4831 # draw red lines for any kernel errors found 4832 type, t, idx1, idx2 = e 4833 id = '%d_%d' % (idx1, idx2) 4834 right = '%f' % (((mMax-t)*100.0)/mTotal) 4835 devtl.html += html_error.format(right, id, type) 4836 for b in phases[dir]: 4837 # draw the devices for this phase 4838 phaselist = data.dmesg[b]['list'] 4839 for d in sorted(data.tdevlist[b]): 4840 dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0] 4841 name, dev = dname, phaselist[d] 4842 drv = xtraclass = xtrainfo = xtrastyle = '' 4843 if 'htmlclass' in dev: 4844 xtraclass = dev['htmlclass'] 4845 if 'color' in dev: 4846 xtrastyle = 'background:%s;' % dev['color'] 4847 if(d in sysvals.devprops): 4848 name = sysvals.devprops[d].altName(d) 4849 xtraclass = sysvals.devprops[d].xtraClass() 4850 xtrainfo = sysvals.devprops[d].xtraInfo() 4851 elif xtraclass == ' kth': 4852 xtrainfo = ' kernel_thread' 4853 if('drv' in dev and dev['drv']): 4854 drv = ' {%s}' % dev['drv'] 4855 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row']) 4856 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row']) 4857 top = '%.3f' % (rowtop + devtl.scaleH) 4858 left = '%f' % (((dev['start']-m0)*100)/mTotal) 4859 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal) 4860 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) 4861 title = name+drv+xtrainfo+length 4862 if sysvals.suspendmode == 'command': 4863 title += sysvals.testcommand 4864 elif xtraclass == ' ps': 4865 if 'suspend' in b: 4866 title += 'pre_suspend_process' 4867 else: 4868 title += 'post_resume_process' 4869 else: 4870 title += b 4871 devtl.html += devtl.html_device.format(dev['id'], \ 4872 title, left, top, '%.3f'%rowheight, width, \ 4873 dname+drv, xtraclass, xtrastyle) 4874 if('cpuexec' in dev): 4875 for t in sorted(dev['cpuexec']): 4876 start, end = t 4877 height = '%.3f' % (rowheight/3) 4878 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3) 4879 left = '%f' % (((start-m0)*100)/mTotal) 4880 width = '%f' % ((end-start)*100/mTotal) 4881 color = 'rgba(255, 0, 0, %f)' % dev['cpuexec'][t] 4882 devtl.html += \ 4883 html_cpuexec.format(left, top, height, width, color) 4884 if('src' not in dev): 4885 continue 4886 # draw any trace events for this device 4887 for e in dev['src']: 4888 if e.length == 0: 4889 continue 4890 height = '%.3f' % devtl.rowH 4891 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH)) 4892 left = '%f' % (((e.time-m0)*100)/mTotal) 4893 width = '%f' % (e.length*100/mTotal) 4894 xtrastyle = '' 4895 if e.color: 4896 xtrastyle = 'background:%s;' % e.color 4897 devtl.html += \ 4898 html_traceevent.format(e.title(), \ 4899 left, top, height, width, e.text(), '', xtrastyle) 4900 # draw the time scale, try to make the number of labels readable 4901 devtl.createTimeScale(m0, mMax, tTotal, dir) 4902 devtl.html += '</div>\n' 4903 4904 # timeline is finished 4905 devtl.html += '</div>\n</div>\n' 4906 4907 # draw a legend which describes the phases by color 4908 if sysvals.suspendmode != 'command': 4909 phasedef = testruns[-1].phasedef 4910 devtl.html += '<div class="legend">\n' 4911 pdelta = 100.0/len(phasedef.keys()) 4912 pmargin = pdelta / 4.0 4913 for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']): 4914 id, p = '', phasedef[phase] 4915 for word in phase.split('_'): 4916 id += word[0] 4917 order = '%.2f' % ((p['order'] * pdelta) + pmargin) 4918 name = phase.replace('_', ' ') 4919 devtl.html += devtl.html_legend.format(order, p['color'], name, id) 4920 devtl.html += '</div>\n' 4921 4922 hf = open(sysvals.htmlfile, 'w') 4923 addCSS(hf, sysvals, len(testruns), kerror) 4924 4925 # write the device timeline 4926 hf.write(devtl.html) 4927 hf.write('<div id="devicedetailtitle"></div>\n') 4928 hf.write('<div id="devicedetail" style="display:none;">\n') 4929 # draw the colored boxes for the device detail section 4930 for data in testruns: 4931 hf.write('<div id="devicedetail%d">\n' % data.testnumber) 4932 pscolor = 'linear-gradient(to top left, #ccc, #eee)' 4933 hf.write(devtl.html_phaselet.format('pre_suspend_process', \ 4934 '0', '0', pscolor)) 4935 for b in data.sortedPhases(): 4936 phase = data.dmesg[b] 4937 length = phase['end']-phase['start'] 4938 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) 4939 width = '%.3f' % ((length*100.0)/tTotal) 4940 hf.write(devtl.html_phaselet.format(b, left, width, \ 4941 data.dmesg[b]['color'])) 4942 hf.write(devtl.html_phaselet.format('post_resume_process', \ 4943 '0', '0', pscolor)) 4944 if sysvals.suspendmode == 'command': 4945 hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor)) 4946 hf.write('</div>\n') 4947 hf.write('</div>\n') 4948 4949 # write the ftrace data (callgraph) 4950 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest: 4951 data = testruns[sysvals.cgtest] 4952 else: 4953 data = testruns[-1] 4954 if sysvals.usecallgraph: 4955 addCallgraphs(sysvals, hf, data) 4956 4957 # add the test log as a hidden div 4958 if sysvals.testlog and sysvals.logmsg: 4959 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n') 4960 # add the dmesg log as a hidden div 4961 if sysvals.dmesglog and sysvals.dmesgfile: 4962 hf.write('<div id="dmesglog" style="display:none;">\n') 4963 lf = sysvals.openlog(sysvals.dmesgfile, 'r') 4964 for line in lf: 4965 line = line.replace('<', '<').replace('>', '>') 4966 hf.write(line) 4967 lf.close() 4968 hf.write('</div>\n') 4969 # add the ftrace log as a hidden div 4970 if sysvals.ftracelog and sysvals.ftracefile: 4971 hf.write('<div id="ftracelog" style="display:none;">\n') 4972 lf = sysvals.openlog(sysvals.ftracefile, 'r') 4973 for line in lf: 4974 hf.write(line) 4975 lf.close() 4976 hf.write('</div>\n') 4977 4978 # write the footer and close 4979 addScriptCode(hf, testruns) 4980 hf.write('</body>\n</html>\n') 4981 hf.close() 4982 return True 4983 4984def addCSS(hf, sv, testcount=1, kerror=False, extra=''): 4985 kernel = sv.stamp['kernel'] 4986 host = sv.hostname[0].upper()+sv.hostname[1:] 4987 mode = sv.suspendmode 4988 if sv.suspendmode in suspendmodename: 4989 mode = suspendmodename[sv.suspendmode] 4990 title = host+' '+mode+' '+kernel 4991 4992 # various format changes by flags 4993 cgchk = 'checked' 4994 cgnchk = 'not(:checked)' 4995 if sv.cgexp: 4996 cgchk = 'not(:checked)' 4997 cgnchk = 'checked' 4998 4999 hoverZ = 'z-index:8;' 5000 if sv.usedevsrc: 5001 hoverZ = '' 5002 5003 devlistpos = 'absolute' 5004 if testcount > 1: 5005 devlistpos = 'relative' 5006 5007 scaleTH = 20 5008 if kerror: 5009 scaleTH = 60 5010 5011 # write the html header first (html head, css code, up to body start) 5012 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\ 5013 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ 5014 <title>'+title+'</title>\n\ 5015 <style type=\'text/css\'>\n\ 5016 body {overflow-y:scroll;}\n\ 5017 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\ 5018 .stamp.sysinfo {font:10px Arial;}\n\ 5019 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\ 5020 .callgraph article * {padding-left:28px;}\n\ 5021 h1 {color:black;font:bold 30px Times;}\n\ 5022 t0 {color:black;font:bold 30px Times;}\n\ 5023 t1 {color:black;font:30px Times;}\n\ 5024 t2 {color:black;font:25px Times;}\n\ 5025 t3 {color:black;font:20px Times;white-space:nowrap;}\n\ 5026 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\ 5027 cS {font:bold 13px Times;}\n\ 5028 table {width:100%;}\n\ 5029 .gray {background:rgba(80,80,80,0.1);}\n\ 5030 .green {background:rgba(204,255,204,0.4);}\n\ 5031 .purple {background:rgba(128,0,128,0.2);}\n\ 5032 .yellow {background:rgba(255,255,204,0.4);}\n\ 5033 .blue {background:rgba(169,208,245,0.4);}\n\ 5034 .time1 {font:22px Arial;border:1px solid;}\n\ 5035 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\ 5036 .testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\ 5037 td {text-align:center;}\n\ 5038 r {color:#500000;font:15px Tahoma;}\n\ 5039 n {color:#505050;font:15px Tahoma;}\n\ 5040 .tdhl {color:red;}\n\ 5041 .hide {display:none;}\n\ 5042 .pf {display:none;}\n\ 5043 .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\ 5044 .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\ 5045 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\ 5046 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\ 5047 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\ 5048 .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\ 5049 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\ 5050 .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\ 5051 .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\ 5052 .hover {background:white;border:1px solid red;'+hoverZ+'}\n\ 5053 .hover.sync {background:white;}\n\ 5054 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\ 5055 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\ 5056 .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\ 5057 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\ 5058 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\ 5059 .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\ 5060 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\ 5061 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\ 5062 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\ 5063 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\ 5064 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\ 5065 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\ 5066 .devlist {position:'+devlistpos+';width:190px;}\n\ 5067 a:link {color:white;text-decoration:none;}\n\ 5068 a:visited {color:white;}\n\ 5069 a:hover {color:white;}\n\ 5070 a:active {color:white;}\n\ 5071 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\ 5072 #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\ 5073 .tblock {position:absolute;height:100%;background:#ddd;}\n\ 5074 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\ 5075 .bg {z-index:1;}\n\ 5076'+extra+'\ 5077 </style>\n</head>\n<body>\n' 5078 hf.write(html_header) 5079 5080# Function: addScriptCode 5081# Description: 5082# Adds the javascript code to the output html 5083# Arguments: 5084# hf: the open html file pointer 5085# testruns: array of Data objects from parseKernelLog or parseTraceLog 5086def addScriptCode(hf, testruns): 5087 t0 = testruns[0].start * 1000 5088 tMax = testruns[-1].end * 1000 5089 hf.write('<script type="text/javascript">\n'); 5090 # create an array in javascript memory with the device details 5091 detail = ' var devtable = [];\n' 5092 for data in testruns: 5093 topo = data.deviceTopology() 5094 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo) 5095 detail += ' var bounds = [%f,%f];\n' % (t0, tMax) 5096 # add the code which will manipulate the data in the browser 5097 hf.write(detail); 5098 script_code = r""" var resolution = -1; 5099 var dragval = [0, 0]; 5100 function redrawTimescale(t0, tMax, tS) { 5101 var rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">'; 5102 var tTotal = tMax - t0; 5103 var list = document.getElementsByClassName("tblock"); 5104 for (var i = 0; i < list.length; i++) { 5105 var timescale = list[i].getElementsByClassName("timescale")[0]; 5106 var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100); 5107 var mTotal = tTotal*parseFloat(list[i].style.width)/100; 5108 var mMax = m0 + mTotal; 5109 var html = ""; 5110 var divTotal = Math.floor(mTotal/tS) + 1; 5111 if(divTotal > 1000) continue; 5112 var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal; 5113 var pos = 0.0, val = 0.0; 5114 for (var j = 0; j < divTotal; j++) { 5115 var htmlline = ""; 5116 var mode = list[i].id[5]; 5117 if(mode == "s") { 5118 pos = 100 - (((j)*tS*100)/mTotal) - divEdge; 5119 val = (j-divTotal+1)*tS; 5120 if(j == divTotal - 1) 5121 htmlline = '<div class="t" style="right:'+pos+'%"><cS>S→</cS></div>'; 5122 else 5123 htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>'; 5124 } else { 5125 pos = 100 - (((j)*tS*100)/mTotal); 5126 val = (j)*tS; 5127 htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>'; 5128 if(j == 0) 5129 if(mode == "r") 5130 htmlline = rline+"<cS>←R</cS></div>"; 5131 else 5132 htmlline = rline+"<cS>0ms</div>"; 5133 } 5134 html += htmlline; 5135 } 5136 timescale.innerHTML = html; 5137 } 5138 } 5139 function zoomTimeline() { 5140 var dmesg = document.getElementById("dmesg"); 5141 var zoombox = document.getElementById("dmesgzoombox"); 5142 var left = zoombox.scrollLeft; 5143 var val = parseFloat(dmesg.style.width); 5144 var newval = 100; 5145 var sh = window.outerWidth / 2; 5146 if(this.id == "zoomin") { 5147 newval = val * 1.2; 5148 if(newval > 910034) newval = 910034; 5149 dmesg.style.width = newval+"%"; 5150 zoombox.scrollLeft = ((left + sh) * newval / val) - sh; 5151 } else if (this.id == "zoomout") { 5152 newval = val / 1.2; 5153 if(newval < 100) newval = 100; 5154 dmesg.style.width = newval+"%"; 5155 zoombox.scrollLeft = ((left + sh) * newval / val) - sh; 5156 } else { 5157 zoombox.scrollLeft = 0; 5158 dmesg.style.width = "100%"; 5159 } 5160 var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]; 5161 var t0 = bounds[0]; 5162 var tMax = bounds[1]; 5163 var tTotal = tMax - t0; 5164 var wTotal = tTotal * 100.0 / newval; 5165 var idx = 7*window.innerWidth/1100; 5166 for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++); 5167 if(i >= tS.length) i = tS.length - 1; 5168 if(tS[i] == resolution) return; 5169 resolution = tS[i]; 5170 redrawTimescale(t0, tMax, tS[i]); 5171 } 5172 function deviceName(title) { 5173 var name = title.slice(0, title.indexOf(" (")); 5174 return name; 5175 } 5176 function deviceHover() { 5177 var name = deviceName(this.title); 5178 var dmesg = document.getElementById("dmesg"); 5179 var dev = dmesg.getElementsByClassName("thread"); 5180 var cpu = -1; 5181 if(name.match("CPU_ON\[[0-9]*\]")) 5182 cpu = parseInt(name.slice(7)); 5183 else if(name.match("CPU_OFF\[[0-9]*\]")) 5184 cpu = parseInt(name.slice(8)); 5185 for (var i = 0; i < dev.length; i++) { 5186 dname = deviceName(dev[i].title); 5187 var cname = dev[i].className.slice(dev[i].className.indexOf("thread")); 5188 if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) || 5189 (name == dname)) 5190 { 5191 dev[i].className = "hover "+cname; 5192 } else { 5193 dev[i].className = cname; 5194 } 5195 } 5196 } 5197 function deviceUnhover() { 5198 var dmesg = document.getElementById("dmesg"); 5199 var dev = dmesg.getElementsByClassName("thread"); 5200 for (var i = 0; i < dev.length; i++) { 5201 dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread")); 5202 } 5203 } 5204 function deviceTitle(title, total, cpu) { 5205 var prefix = "Total"; 5206 if(total.length > 3) { 5207 prefix = "Average"; 5208 total[1] = (total[1]+total[3])/2; 5209 total[2] = (total[2]+total[4])/2; 5210 } 5211 var devtitle = document.getElementById("devicedetailtitle"); 5212 var name = deviceName(title); 5213 if(cpu >= 0) name = "CPU"+cpu; 5214 var driver = ""; 5215 var tS = "<t2>(</t2>"; 5216 var tR = "<t2>)</t2>"; 5217 if(total[1] > 0) 5218 tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> "; 5219 if(total[2] > 0) 5220 tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>"; 5221 var s = title.indexOf("{"); 5222 var e = title.indexOf("}"); 5223 if((s >= 0) && (e >= 0)) 5224 driver = title.slice(s+1, e) + " <t1>@</t1> "; 5225 if(total[1] > 0 && total[2] > 0) 5226 devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR; 5227 else 5228 devtitle.innerHTML = "<t0>"+title+"</t0>"; 5229 return name; 5230 } 5231 function deviceDetail() { 5232 var devinfo = document.getElementById("devicedetail"); 5233 devinfo.style.display = "block"; 5234 var name = deviceName(this.title); 5235 var cpu = -1; 5236 if(name.match("CPU_ON\[[0-9]*\]")) 5237 cpu = parseInt(name.slice(7)); 5238 else if(name.match("CPU_OFF\[[0-9]*\]")) 5239 cpu = parseInt(name.slice(8)); 5240 var dmesg = document.getElementById("dmesg"); 5241 var dev = dmesg.getElementsByClassName("thread"); 5242 var idlist = []; 5243 var pdata = [[]]; 5244 if(document.getElementById("devicedetail1")) 5245 pdata = [[], []]; 5246 var pd = pdata[0]; 5247 var total = [0.0, 0.0, 0.0]; 5248 for (var i = 0; i < dev.length; i++) { 5249 dname = deviceName(dev[i].title); 5250 if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) || 5251 (name == dname)) 5252 { 5253 idlist[idlist.length] = dev[i].id; 5254 var tidx = 1; 5255 if(dev[i].id[0] == "a") { 5256 pd = pdata[0]; 5257 } else { 5258 if(pdata.length == 1) pdata[1] = []; 5259 if(total.length == 3) total[3]=total[4]=0.0; 5260 pd = pdata[1]; 5261 tidx = 3; 5262 } 5263 var info = dev[i].title.split(" "); 5264 var pname = info[info.length-1]; 5265 var length = parseFloat(info[info.length-3].slice(1)); 5266 if (pname in pd) 5267 pd[pname] += length; 5268 else 5269 pd[pname] = length; 5270 total[0] += length; 5271 if(pname.indexOf("suspend") >= 0) 5272 total[tidx] += length; 5273 else 5274 total[tidx+1] += length; 5275 } 5276 } 5277 var devname = deviceTitle(this.title, total, cpu); 5278 var left = 0.0; 5279 for (var t = 0; t < pdata.length; t++) { 5280 pd = pdata[t]; 5281 devinfo = document.getElementById("devicedetail"+t); 5282 var phases = devinfo.getElementsByClassName("phaselet"); 5283 for (var i = 0; i < phases.length; i++) { 5284 if(phases[i].id in pd) { 5285 var w = 100.0*pd[phases[i].id]/total[0]; 5286 var fs = 32; 5287 if(w < 8) fs = 4*w | 0; 5288 var fs2 = fs*3/4; 5289 phases[i].style.width = w+"%"; 5290 phases[i].style.left = left+"%"; 5291 phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms"; 5292 left += w; 5293 var time = "<t4 style=\"font-size:"+fs+"px\">"+pd[phases[i].id].toFixed(3)+" ms<br></t4>"; 5294 var pname = "<t3 style=\"font-size:"+fs2+"px\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>"; 5295 phases[i].innerHTML = time+pname; 5296 } else { 5297 phases[i].style.width = "0%"; 5298 phases[i].style.left = left+"%"; 5299 } 5300 } 5301 } 5302 if(typeof devstats !== 'undefined') 5303 callDetail(this.id, this.title); 5304 var cglist = document.getElementById("callgraphs"); 5305 if(!cglist) return; 5306 var cg = cglist.getElementsByClassName("atop"); 5307 if(cg.length < 10) return; 5308 for (var i = 0; i < cg.length; i++) { 5309 cgid = cg[i].id.split("x")[0] 5310 if(idlist.indexOf(cgid) >= 0) { 5311 cg[i].style.display = "block"; 5312 } else { 5313 cg[i].style.display = "none"; 5314 } 5315 } 5316 } 5317 function callDetail(devid, devtitle) { 5318 if(!(devid in devstats) || devstats[devid].length < 1) 5319 return; 5320 var list = devstats[devid]; 5321 var tmp = devtitle.split(" "); 5322 var name = tmp[0], phase = tmp[tmp.length-1]; 5323 var dd = document.getElementById(phase); 5324 var total = parseFloat(tmp[1].slice(1)); 5325 var mlist = []; 5326 var maxlen = 0; 5327 var info = [] 5328 for(var i in list) { 5329 if(list[i][0] == "@") { 5330 info = list[i].split("|"); 5331 continue; 5332 } 5333 var tmp = list[i].split("|"); 5334 var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]); 5335 var p = (t*100.0/total).toFixed(2); 5336 mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"]; 5337 if(f.length > maxlen) 5338 maxlen = f.length; 5339 } 5340 var pad = 5; 5341 if(mlist.length == 0) pad = 30; 5342 var html = '<div style="padding-top:'+pad+'px"><t3> <b>'+name+':</b>'; 5343 if(info.length > 2) 5344 html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>"; 5345 if(info.length > 3) 5346 html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>"; 5347 if(info.length > 4) 5348 html += ", return=<b>"+info[4]+"</b>"; 5349 html += "</t3></div>"; 5350 if(mlist.length > 0) { 5351 html += '<table class=fstat style="padding-top:'+(maxlen*5)+'px;"><tr><th>Function</th>'; 5352 for(var i in mlist) 5353 html += "<td class=vt>"+mlist[i][0]+"</td>"; 5354 html += "</tr><tr><th>Calls</th>"; 5355 for(var i in mlist) 5356 html += "<td>"+mlist[i][1]+"</td>"; 5357 html += "</tr><tr><th>Time(ms)</th>"; 5358 for(var i in mlist) 5359 html += "<td>"+mlist[i][2]+"</td>"; 5360 html += "</tr><tr><th>Percent</th>"; 5361 for(var i in mlist) 5362 html += "<td>"+mlist[i][3]+"</td>"; 5363 html += "</tr></table>"; 5364 } 5365 dd.innerHTML = html; 5366 var height = (maxlen*5)+100; 5367 dd.style.height = height+"px"; 5368 document.getElementById("devicedetail").style.height = height+"px"; 5369 } 5370 function callSelect() { 5371 var cglist = document.getElementById("callgraphs"); 5372 if(!cglist) return; 5373 var cg = cglist.getElementsByClassName("atop"); 5374 for (var i = 0; i < cg.length; i++) { 5375 if(this.id == cg[i].id) { 5376 cg[i].style.display = "block"; 5377 } else { 5378 cg[i].style.display = "none"; 5379 } 5380 } 5381 } 5382 function devListWindow(e) { 5383 var win = window.open(); 5384 var html = "<title>"+e.target.innerHTML+"</title>"+ 5385 "<style type=\"text/css\">"+ 5386 " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+ 5387 "</style>" 5388 var dt = devtable[0]; 5389 if(e.target.id != "devlist1") 5390 dt = devtable[1]; 5391 win.document.write(html+dt); 5392 } 5393 function errWindow() { 5394 var range = this.id.split("_"); 5395 var idx1 = parseInt(range[0]); 5396 var idx2 = parseInt(range[1]); 5397 var win = window.open(); 5398 var log = document.getElementById("dmesglog"); 5399 var title = "<title>dmesg log</title>"; 5400 var text = log.innerHTML.split("\n"); 5401 var html = ""; 5402 for(var i = 0; i < text.length; i++) { 5403 if(i == idx1) { 5404 html += "<e id=target>"+text[i]+"</e>\n"; 5405 } else if(i > idx1 && i <= idx2) { 5406 html += "<e>"+text[i]+"</e>\n"; 5407 } else { 5408 html += text[i]+"\n"; 5409 } 5410 } 5411 win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>"); 5412 win.location.hash = "#target"; 5413 win.document.close(); 5414 } 5415 function logWindow(e) { 5416 var name = e.target.id.slice(4); 5417 var win = window.open(); 5418 var log = document.getElementById(name+"log"); 5419 var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>"; 5420 win.document.write(title+"<pre>"+log.innerHTML+"</pre>"); 5421 win.document.close(); 5422 } 5423 function onMouseDown(e) { 5424 dragval[0] = e.clientX; 5425 dragval[1] = document.getElementById("dmesgzoombox").scrollLeft; 5426 document.onmousemove = onMouseMove; 5427 } 5428 function onMouseMove(e) { 5429 var zoombox = document.getElementById("dmesgzoombox"); 5430 zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX; 5431 } 5432 function onMouseUp(e) { 5433 document.onmousemove = null; 5434 } 5435 function onKeyPress(e) { 5436 var c = e.charCode; 5437 if(c != 42 && c != 43 && c != 45) return; 5438 var click = document.createEvent("Events"); 5439 click.initEvent("click", true, false); 5440 if(c == 43) 5441 document.getElementById("zoomin").dispatchEvent(click); 5442 else if(c == 45) 5443 document.getElementById("zoomout").dispatchEvent(click); 5444 else if(c == 42) 5445 document.getElementById("zoomdef").dispatchEvent(click); 5446 } 5447 window.addEventListener("resize", function () {zoomTimeline();}); 5448 window.addEventListener("load", function () { 5449 var dmesg = document.getElementById("dmesg"); 5450 dmesg.style.width = "100%" 5451 dmesg.onmousedown = onMouseDown; 5452 document.onmouseup = onMouseUp; 5453 document.onkeypress = onKeyPress; 5454 document.getElementById("zoomin").onclick = zoomTimeline; 5455 document.getElementById("zoomout").onclick = zoomTimeline; 5456 document.getElementById("zoomdef").onclick = zoomTimeline; 5457 var list = document.getElementsByClassName("err"); 5458 for (var i = 0; i < list.length; i++) 5459 list[i].onclick = errWindow; 5460 var list = document.getElementsByClassName("logbtn"); 5461 for (var i = 0; i < list.length; i++) 5462 list[i].onclick = logWindow; 5463 list = document.getElementsByClassName("devlist"); 5464 for (var i = 0; i < list.length; i++) 5465 list[i].onclick = devListWindow; 5466 var dev = dmesg.getElementsByClassName("thread"); 5467 for (var i = 0; i < dev.length; i++) { 5468 dev[i].onclick = deviceDetail; 5469 dev[i].onmouseover = deviceHover; 5470 dev[i].onmouseout = deviceUnhover; 5471 } 5472 var dev = dmesg.getElementsByClassName("srccall"); 5473 for (var i = 0; i < dev.length; i++) 5474 dev[i].onclick = callSelect; 5475 zoomTimeline(); 5476 }); 5477</script> """ 5478 hf.write(script_code); 5479 5480# Function: executeSuspend 5481# Description: 5482# Execute system suspend through the sysfs interface, then copy the output 5483# dmesg and ftrace files to the test output directory. 5484def executeSuspend(quiet=False): 5485 sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor() 5486 if sv.wifi: 5487 wifi = sv.checkWifi() 5488 sv.dlog('wifi check, connected device is "%s"' % wifi) 5489 testdata = [] 5490 # run these commands to prepare the system for suspend 5491 if sv.display: 5492 if not quiet: 5493 pprint('SET DISPLAY TO %s' % sv.display.upper()) 5494 ret = sv.displayControl(sv.display) 5495 sv.dlog('xset display %s, ret = %d' % (sv.display, ret)) 5496 time.sleep(1) 5497 if sv.sync: 5498 if not quiet: 5499 pprint('SYNCING FILESYSTEMS') 5500 sv.dlog('syncing filesystems') 5501 call('sync', shell=True) 5502 sv.dlog('read dmesg') 5503 sv.initdmesg() 5504 sv.dlog('cmdinfo before') 5505 sv.cmdinfo(True) 5506 sv.start(pm) 5507 # execute however many s/r runs requested 5508 for count in range(1,sv.execcount+1): 5509 # x2delay in between test runs 5510 if(count > 1 and sv.x2delay > 0): 5511 sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker') 5512 time.sleep(sv.x2delay/1000.0) 5513 sv.fsetVal('WAIT END', 'trace_marker') 5514 # start message 5515 if sv.testcommand != '': 5516 pprint('COMMAND START') 5517 else: 5518 if(sv.rtcwake): 5519 pprint('SUSPEND START') 5520 else: 5521 pprint('SUSPEND START (press a key to resume)') 5522 # set rtcwake 5523 if(sv.rtcwake): 5524 if not quiet: 5525 pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime) 5526 sv.dlog('enable RTC wake alarm') 5527 sv.rtcWakeAlarmOn() 5528 # start of suspend trace marker 5529 sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker') 5530 # predelay delay 5531 if(count == 1 and sv.predelay > 0): 5532 sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker') 5533 time.sleep(sv.predelay/1000.0) 5534 sv.fsetVal('WAIT END', 'trace_marker') 5535 # initiate suspend or command 5536 sv.dlog('system executing a suspend') 5537 tdata = {'error': ''} 5538 if sv.testcommand != '': 5539 res = call(sv.testcommand+' 2>&1', shell=True); 5540 if res != 0: 5541 tdata['error'] = 'cmd returned %d' % res 5542 else: 5543 s0ixready = sv.s0ixSupport() 5544 mode = sv.suspendmode 5545 if sv.memmode and os.path.exists(sv.mempowerfile): 5546 mode = 'mem' 5547 sv.testVal(sv.mempowerfile, 'radio', sv.memmode) 5548 if sv.diskmode and os.path.exists(sv.diskpowerfile): 5549 mode = 'disk' 5550 sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode) 5551 if sv.acpidebug: 5552 sv.testVal(sv.acpipath, 'acpi', '0xe') 5553 if ((mode == 'freeze') or (sv.memmode == 's2idle')) \ 5554 and sv.haveTurbostat(): 5555 # execution will pause here 5556 retval, turbo = sv.turbostat(s0ixready) 5557 if retval != 0: 5558 tdata['error'] ='turbostat returned %d' % retval 5559 if turbo: 5560 tdata['turbo'] = turbo 5561 else: 5562 pf = open(sv.powerfile, 'w') 5563 pf.write(mode) 5564 # execution will pause here 5565 try: 5566 pf.flush() 5567 pf.close() 5568 except Exception as e: 5569 tdata['error'] = str(e) 5570 sv.fsetVal('CMD COMPLETE', 'trace_marker') 5571 sv.dlog('system returned') 5572 # reset everything 5573 sv.testVal('restoreall') 5574 if(sv.rtcwake): 5575 sv.dlog('disable RTC wake alarm') 5576 sv.rtcWakeAlarmOff() 5577 # postdelay delay 5578 if(count == sv.execcount and sv.postdelay > 0): 5579 sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker') 5580 time.sleep(sv.postdelay/1000.0) 5581 sv.fsetVal('WAIT END', 'trace_marker') 5582 # return from suspend 5583 pprint('RESUME COMPLETE') 5584 if(count < sv.execcount): 5585 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker') 5586 elif(not sv.wifitrace): 5587 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker') 5588 sv.stop(pm) 5589 if sv.wifi and wifi: 5590 tdata['wifi'] = sv.pollWifi(wifi) 5591 sv.dlog('wifi check, %s' % tdata['wifi']) 5592 if(count == sv.execcount and sv.wifitrace): 5593 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker') 5594 sv.stop(pm) 5595 if sv.netfix: 5596 tdata['netfix'] = sv.netfixon() 5597 sv.dlog('netfix, %s' % tdata['netfix']) 5598 if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'): 5599 sv.dlog('read the ACPI FPDT') 5600 tdata['fw'] = getFPDT(False) 5601 testdata.append(tdata) 5602 sv.dlog('cmdinfo after') 5603 cmdafter = sv.cmdinfo(False) 5604 # grab a copy of the dmesg output 5605 if not quiet: 5606 pprint('CAPTURING DMESG') 5607 sv.getdmesg(testdata) 5608 # grab a copy of the ftrace output 5609 if sv.useftrace: 5610 if not quiet: 5611 pprint('CAPTURING TRACE') 5612 op = sv.writeDatafileHeader(sv.ftracefile, testdata) 5613 fp = open(tp+'trace', 'rb') 5614 op.write(ascii(fp.read())) 5615 op.close() 5616 sv.fsetVal('', 'trace') 5617 sv.platforminfo(cmdafter) 5618 5619def readFile(file): 5620 if os.path.islink(file): 5621 return os.readlink(file).split('/')[-1] 5622 else: 5623 return sysvals.getVal(file).strip() 5624 5625# Function: ms2nice 5626# Description: 5627# Print out a very concise time string in minutes and seconds 5628# Output: 5629# The time string, e.g. "1901m16s" 5630def ms2nice(val): 5631 val = int(val) 5632 h = val // 3600000 5633 m = (val // 60000) % 60 5634 s = (val // 1000) % 60 5635 if h > 0: 5636 return '%d:%02d:%02d' % (h, m, s) 5637 if m > 0: 5638 return '%02d:%02d' % (m, s) 5639 return '%ds' % s 5640 5641def yesno(val): 5642 list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D', 5643 'active':'A', 'suspended':'S', 'suspending':'S'} 5644 if val not in list: 5645 return ' ' 5646 return list[val] 5647 5648# Function: deviceInfo 5649# Description: 5650# Detect all the USB hosts and devices currently connected and add 5651# a list of USB device names to sysvals for better timeline readability 5652def deviceInfo(output=''): 5653 if not output: 5654 pprint('LEGEND\n'\ 5655 '---------------------------------------------------------------------------------------------\n'\ 5656 ' A = async/sync PM queue (A/S) C = runtime active children\n'\ 5657 ' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)\n'\ 5658 ' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)\n'\ 5659 ' U = runtime usage count\n'\ 5660 '---------------------------------------------------------------------------------------------\n'\ 5661 'DEVICE NAME A R S U C rACTIVE rSUSPEND\n'\ 5662 '---------------------------------------------------------------------------------------------') 5663 5664 res = [] 5665 tgtval = 'runtime_status' 5666 lines = dict() 5667 for dirname, dirnames, filenames in os.walk('/sys/devices'): 5668 if(not re.match(r'.*/power', dirname) or 5669 'control' not in filenames or 5670 tgtval not in filenames): 5671 continue 5672 name = '' 5673 dirname = dirname[:-6] 5674 device = dirname.split('/')[-1] 5675 power = dict() 5676 power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval)) 5677 # only list devices which support runtime suspend 5678 if power[tgtval] not in ['active', 'suspended', 'suspending']: 5679 continue 5680 for i in ['product', 'driver', 'subsystem']: 5681 file = '%s/%s' % (dirname, i) 5682 if os.path.exists(file): 5683 name = readFile(file) 5684 break 5685 for i in ['async', 'control', 'runtime_status', 'runtime_usage', 5686 'runtime_active_kids', 'runtime_active_time', 5687 'runtime_suspended_time']: 5688 if i in filenames: 5689 power[i] = readFile('%s/power/%s' % (dirname, i)) 5690 if output: 5691 if power['control'] == output: 5692 res.append('%s/power/control' % dirname) 5693 continue 5694 lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \ 5695 (device[:26], name[:26], 5696 yesno(power['async']), \ 5697 yesno(power['control']), \ 5698 yesno(power['runtime_status']), \ 5699 power['runtime_usage'], \ 5700 power['runtime_active_kids'], \ 5701 ms2nice(power['runtime_active_time']), \ 5702 ms2nice(power['runtime_suspended_time'])) 5703 for i in sorted(lines): 5704 print(lines[i]) 5705 return res 5706 5707# Function: getModes 5708# Description: 5709# Determine the supported power modes on this system 5710# Output: 5711# A string list of the available modes 5712def getModes(): 5713 modes = [] 5714 if(os.path.exists(sysvals.powerfile)): 5715 fp = open(sysvals.powerfile, 'r') 5716 modes = fp.read().split() 5717 fp.close() 5718 if(os.path.exists(sysvals.mempowerfile)): 5719 deep = False 5720 fp = open(sysvals.mempowerfile, 'r') 5721 for m in fp.read().split(): 5722 memmode = m.strip('[]') 5723 if memmode == 'deep': 5724 deep = True 5725 else: 5726 modes.append('mem-%s' % memmode) 5727 fp.close() 5728 if 'mem' in modes and not deep: 5729 modes.remove('mem') 5730 if('disk' in modes and os.path.exists(sysvals.diskpowerfile)): 5731 fp = open(sysvals.diskpowerfile, 'r') 5732 for m in fp.read().split(): 5733 modes.append('disk-%s' % m.strip('[]')) 5734 fp.close() 5735 return modes 5736 5737def dmidecode_backup(out, fatal=False): 5738 cpath, spath, info = '/proc/cpuinfo', '/sys/class/dmi/id', { 5739 'bios-vendor': 'bios_vendor', 5740 'bios-version': 'bios_version', 5741 'bios-release-date': 'bios_date', 5742 'system-manufacturer': 'sys_vendor', 5743 'system-product-name': 'product_name', 5744 'system-version': 'product_version', 5745 'system-serial-number': 'product_serial', 5746 'baseboard-manufacturer': 'board_vendor', 5747 'baseboard-product-name': 'board_name', 5748 'baseboard-version': 'board_version', 5749 'baseboard-serial-number': 'board_serial', 5750 'chassis-manufacturer': 'chassis_vendor', 5751 'chassis-version': 'chassis_version', 5752 'chassis-serial-number': 'chassis_serial', 5753 } 5754 for key in info: 5755 if key not in out: 5756 val = sysvals.getVal(os.path.join(spath, info[key])).strip() 5757 if val and val.lower() != 'to be filled by o.e.m.': 5758 out[key] = val 5759 if 'processor-version' not in out and os.path.exists(cpath): 5760 with open(cpath, 'r') as fp: 5761 for line in fp: 5762 m = re.match(r'^model\s*name\s*\:\s*(?P<c>.*)', line) 5763 if m: 5764 out['processor-version'] = m.group('c').strip() 5765 break 5766 if fatal and len(out) < 1: 5767 doError('dmidecode failed to get info from %s or %s' % \ 5768 (sysvals.mempath, spath)) 5769 return out 5770 5771# Function: dmidecode 5772# Description: 5773# Read the bios tables and pull out system info 5774# Arguments: 5775# mempath: /dev/mem or custom mem path 5776# fatal: True to exit on error, False to return empty dict 5777# Output: 5778# A dict object with all available key/values 5779def dmidecode(mempath, fatal=False): 5780 out = dict() 5781 if(not (os.path.exists(mempath) and os.access(mempath, os.R_OK))): 5782 return dmidecode_backup(out, fatal) 5783 5784 # the list of values to retrieve, with hardcoded (type, idx) 5785 info = { 5786 'bios-vendor': (0, 4), 5787 'bios-version': (0, 5), 5788 'bios-release-date': (0, 8), 5789 'system-manufacturer': (1, 4), 5790 'system-product-name': (1, 5), 5791 'system-version': (1, 6), 5792 'system-serial-number': (1, 7), 5793 'baseboard-manufacturer': (2, 4), 5794 'baseboard-product-name': (2, 5), 5795 'baseboard-version': (2, 6), 5796 'baseboard-serial-number': (2, 7), 5797 'chassis-manufacturer': (3, 4), 5798 'chassis-version': (3, 6), 5799 'chassis-serial-number': (3, 7), 5800 'processor-manufacturer': (4, 7), 5801 'processor-version': (4, 16), 5802 } 5803 5804 # by default use legacy scan, but try to use EFI first 5805 memaddr, memsize = 0xf0000, 0x10000 5806 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']: 5807 if not os.path.exists(ep) or not os.access(ep, os.R_OK): 5808 continue 5809 fp = open(ep, 'r') 5810 buf = fp.read() 5811 fp.close() 5812 i = buf.find('SMBIOS=') 5813 if i >= 0: 5814 try: 5815 memaddr = int(buf[i+7:], 16) 5816 memsize = 0x20 5817 except: 5818 continue 5819 5820 # read in the memory for scanning 5821 try: 5822 fp = open(mempath, 'rb') 5823 fp.seek(memaddr) 5824 buf = fp.read(memsize) 5825 except: 5826 return dmidecode_backup(out, fatal) 5827 fp.close() 5828 5829 # search for either an SM table or DMI table 5830 i = base = length = num = 0 5831 while(i < memsize): 5832 if buf[i:i+4] == b'_SM_' and i < memsize - 16: 5833 length = struct.unpack('H', buf[i+22:i+24])[0] 5834 base, num = struct.unpack('IH', buf[i+24:i+30]) 5835 break 5836 elif buf[i:i+5] == b'_DMI_': 5837 length = struct.unpack('H', buf[i+6:i+8])[0] 5838 base, num = struct.unpack('IH', buf[i+8:i+14]) 5839 break 5840 i += 16 5841 if base == 0 and length == 0 and num == 0: 5842 return dmidecode_backup(out, fatal) 5843 5844 # read in the SM or DMI table 5845 try: 5846 fp = open(mempath, 'rb') 5847 fp.seek(base) 5848 buf = fp.read(length) 5849 except: 5850 return dmidecode_backup(out, fatal) 5851 fp.close() 5852 5853 # scan the table for the values we want 5854 count = i = 0 5855 while(count < num and i <= len(buf) - 4): 5856 type, size, handle = struct.unpack('BBH', buf[i:i+4]) 5857 n = i + size 5858 while n < len(buf) - 1: 5859 if 0 == struct.unpack('H', buf[n:n+2])[0]: 5860 break 5861 n += 1 5862 data = buf[i+size:n+2].split(b'\0') 5863 for name in info: 5864 itype, idxadr = info[name] 5865 if itype == type: 5866 idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0] 5867 if idx > 0 and idx < len(data) - 1: 5868 s = data[idx-1].decode('utf-8') 5869 if s.strip() and s.strip().lower() != 'to be filled by o.e.m.': 5870 out[name] = s 5871 i = n + 2 5872 count += 1 5873 return out 5874 5875# Function: getFPDT 5876# Description: 5877# Read the acpi bios tables and pull out FPDT, the firmware data 5878# Arguments: 5879# output: True to output the info to stdout, False otherwise 5880def getFPDT(output): 5881 rectype = {} 5882 rectype[0] = 'Firmware Basic Boot Performance Record' 5883 rectype[1] = 'S3 Performance Table Record' 5884 prectype = {} 5885 prectype[0] = 'Basic S3 Resume Performance Record' 5886 prectype[1] = 'Basic S3 Suspend Performance Record' 5887 5888 sysvals.rootCheck(True) 5889 if(not os.path.exists(sysvals.fpdtpath)): 5890 if(output): 5891 doError('file does not exist: %s' % sysvals.fpdtpath) 5892 return False 5893 if(not os.access(sysvals.fpdtpath, os.R_OK)): 5894 if(output): 5895 doError('file is not readable: %s' % sysvals.fpdtpath) 5896 return False 5897 if(not os.path.exists(sysvals.mempath)): 5898 if(output): 5899 doError('file does not exist: %s' % sysvals.mempath) 5900 return False 5901 if(not os.access(sysvals.mempath, os.R_OK)): 5902 if(output): 5903 doError('file is not readable: %s' % sysvals.mempath) 5904 return False 5905 5906 fp = open(sysvals.fpdtpath, 'rb') 5907 buf = fp.read() 5908 fp.close() 5909 5910 if(len(buf) < 36): 5911 if(output): 5912 doError('Invalid FPDT table data, should '+\ 5913 'be at least 36 bytes') 5914 return False 5915 5916 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36]) 5917 if(output): 5918 pprint('\n'\ 5919 'Firmware Performance Data Table (%s)\n'\ 5920 ' Signature : %s\n'\ 5921 ' Table Length : %u\n'\ 5922 ' Revision : %u\n'\ 5923 ' Checksum : 0x%x\n'\ 5924 ' OEM ID : %s\n'\ 5925 ' OEM Table ID : %s\n'\ 5926 ' OEM Revision : %u\n'\ 5927 ' Creator ID : %s\n'\ 5928 ' Creator Revision : 0x%x\n'\ 5929 '' % (ascii(table[0]), ascii(table[0]), table[1], table[2], 5930 table[3], ascii(table[4]), ascii(table[5]), table[6], 5931 ascii(table[7]), table[8])) 5932 5933 if(table[0] != b'FPDT'): 5934 if(output): 5935 doError('Invalid FPDT table') 5936 return False 5937 if(len(buf) <= 36): 5938 return False 5939 i = 0 5940 fwData = [0, 0] 5941 records = buf[36:] 5942 try: 5943 fp = open(sysvals.mempath, 'rb') 5944 except: 5945 pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data') 5946 return False 5947 while(i < len(records)): 5948 header = struct.unpack('HBB', records[i:i+4]) 5949 if(header[0] not in rectype): 5950 i += header[1] 5951 continue 5952 if(header[1] != 16): 5953 i += header[1] 5954 continue 5955 addr = struct.unpack('Q', records[i+8:i+16])[0] 5956 try: 5957 fp.seek(addr) 5958 first = fp.read(8) 5959 except: 5960 if(output): 5961 pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath)) 5962 return [0, 0] 5963 rechead = struct.unpack('4sI', first) 5964 recdata = fp.read(rechead[1]-8) 5965 if(rechead[0] == b'FBPT'): 5966 record = struct.unpack('HBBIQQQQQ', recdata[:48]) 5967 if(output): 5968 pprint('%s (%s)\n'\ 5969 ' Reset END : %u ns\n'\ 5970 ' OS Loader LoadImage Start : %u ns\n'\ 5971 ' OS Loader StartImage Start : %u ns\n'\ 5972 ' ExitBootServices Entry : %u ns\n'\ 5973 ' ExitBootServices Exit : %u ns'\ 5974 '' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5], 5975 record[6], record[7], record[8])) 5976 elif(rechead[0] == b'S3PT'): 5977 if(output): 5978 pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0]))) 5979 j = 0 5980 while(j < len(recdata)): 5981 prechead = struct.unpack('HBB', recdata[j:j+4]) 5982 if(prechead[0] not in prectype): 5983 continue 5984 if(prechead[0] == 0): 5985 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]]) 5986 fwData[1] = record[2] 5987 if(output): 5988 pprint(' %s\n'\ 5989 ' Resume Count : %u\n'\ 5990 ' FullResume : %u ns\n'\ 5991 ' AverageResume : %u ns'\ 5992 '' % (prectype[prechead[0]], record[1], 5993 record[2], record[3])) 5994 elif(prechead[0] == 1): 5995 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]]) 5996 fwData[0] = record[1] - record[0] 5997 if(output): 5998 pprint(' %s\n'\ 5999 ' SuspendStart : %u ns\n'\ 6000 ' SuspendEnd : %u ns\n'\ 6001 ' SuspendTime : %u ns'\ 6002 '' % (prectype[prechead[0]], record[0], 6003 record[1], fwData[0])) 6004 6005 j += prechead[1] 6006 if(output): 6007 pprint('') 6008 i += header[1] 6009 fp.close() 6010 return fwData 6011 6012# Function: statusCheck 6013# Description: 6014# Verify that the requested command and options will work, and 6015# print the results to the terminal 6016# Output: 6017# True if the test will work, False if not 6018def statusCheck(probecheck=False): 6019 status = '' 6020 6021 pprint('Checking this system (%s)...' % platform.node()) 6022 6023 # check we have root access 6024 res = sysvals.colorText('NO (No features of this tool will work!)') 6025 if(sysvals.rootCheck(False)): 6026 res = 'YES' 6027 pprint(' have root access: %s' % res) 6028 if(res != 'YES'): 6029 pprint(' Try running this script with sudo') 6030 return 'missing root access' 6031 6032 # check sysfs is mounted 6033 res = sysvals.colorText('NO (No features of this tool will work!)') 6034 if(os.path.exists(sysvals.powerfile)): 6035 res = 'YES' 6036 pprint(' is sysfs mounted: %s' % res) 6037 if(res != 'YES'): 6038 return 'sysfs is missing' 6039 6040 # check target mode is a valid mode 6041 if sysvals.suspendmode != 'command': 6042 res = sysvals.colorText('NO') 6043 modes = getModes() 6044 if(sysvals.suspendmode in modes): 6045 res = 'YES' 6046 else: 6047 status = '%s mode is not supported' % sysvals.suspendmode 6048 pprint(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res)) 6049 if(res == 'NO'): 6050 pprint(' valid power modes are: %s' % modes) 6051 pprint(' please choose one with -m') 6052 6053 # check if ftrace is available 6054 if sysvals.useftrace: 6055 res = sysvals.colorText('NO') 6056 sysvals.useftrace = sysvals.verifyFtrace() 6057 efmt = '"{0}" uses ftrace, and it is not properly supported' 6058 if sysvals.useftrace: 6059 res = 'YES' 6060 elif sysvals.usecallgraph: 6061 status = efmt.format('-f') 6062 elif sysvals.usedevsrc: 6063 status = efmt.format('-dev') 6064 elif sysvals.useprocmon: 6065 status = efmt.format('-proc') 6066 pprint(' is ftrace supported: %s' % res) 6067 6068 # check if kprobes are available 6069 if sysvals.usekprobes: 6070 res = sysvals.colorText('NO') 6071 sysvals.usekprobes = sysvals.verifyKprobes() 6072 if(sysvals.usekprobes): 6073 res = 'YES' 6074 else: 6075 sysvals.usedevsrc = False 6076 pprint(' are kprobes supported: %s' % res) 6077 6078 # what data source are we using 6079 res = 'DMESG (very limited, ftrace is preferred)' 6080 if sysvals.useftrace: 6081 sysvals.usetraceevents = True 6082 for e in sysvals.traceevents: 6083 if not os.path.exists(sysvals.epath+e): 6084 sysvals.usetraceevents = False 6085 if(sysvals.usetraceevents): 6086 res = 'FTRACE (all trace events found)' 6087 pprint(' timeline data source: %s' % res) 6088 6089 # check if rtcwake 6090 res = sysvals.colorText('NO') 6091 if(sysvals.rtcpath != ''): 6092 res = 'YES' 6093 elif(sysvals.rtcwake): 6094 status = 'rtcwake is not properly supported' 6095 pprint(' is rtcwake supported: %s' % res) 6096 6097 # check info commands 6098 pprint(' optional commands this tool may use for info:') 6099 no = sysvals.colorText('MISSING') 6100 yes = sysvals.colorText('FOUND', 32) 6101 for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']: 6102 if c == 'turbostat': 6103 res = yes if sysvals.haveTurbostat() else no 6104 else: 6105 res = yes if sysvals.getExec(c) else no 6106 pprint(' %s: %s' % (c, res)) 6107 6108 if not probecheck: 6109 return status 6110 6111 # verify kprobes 6112 if sysvals.usekprobes: 6113 for name in sysvals.tracefuncs: 6114 sysvals.defaultKprobe(name, sysvals.tracefuncs[name]) 6115 if sysvals.usedevsrc: 6116 for name in sysvals.dev_tracefuncs: 6117 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name]) 6118 sysvals.addKprobes(True) 6119 6120 return status 6121 6122# Function: doError 6123# Description: 6124# generic error function for catastrphic failures 6125# Arguments: 6126# msg: the error message to print 6127# help: True if printHelp should be called after, False otherwise 6128def doError(msg, help=False): 6129 if(help == True): 6130 printHelp() 6131 pprint('ERROR: %s\n' % msg) 6132 sysvals.outputResult({'error':msg}) 6133 sys.exit(1) 6134 6135# Function: getArgInt 6136# Description: 6137# pull out an integer argument from the command line with checks 6138def getArgInt(name, args, min, max, main=True): 6139 if main: 6140 try: 6141 arg = next(args) 6142 except: 6143 doError(name+': no argument supplied', True) 6144 else: 6145 arg = args 6146 try: 6147 val = int(arg) 6148 except: 6149 doError(name+': non-integer value given', True) 6150 if(val < min or val > max): 6151 doError(name+': value should be between %d and %d' % (min, max), True) 6152 return val 6153 6154# Function: getArgFloat 6155# Description: 6156# pull out a float argument from the command line with checks 6157def getArgFloat(name, args, min, max, main=True): 6158 if main: 6159 try: 6160 arg = next(args) 6161 except: 6162 doError(name+': no argument supplied', True) 6163 else: 6164 arg = args 6165 try: 6166 val = float(arg) 6167 except: 6168 doError(name+': non-numerical value given', True) 6169 if(val < min or val > max): 6170 doError(name+': value should be between %f and %f' % (min, max), True) 6171 return val 6172 6173def processData(live=False, quiet=False): 6174 if not quiet: 6175 pprint('PROCESSING: %s' % sysvals.htmlfile) 6176 sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \ 6177 (sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes)) 6178 error = '' 6179 if(sysvals.usetraceevents): 6180 testruns, error = parseTraceLog(live) 6181 if sysvals.dmesgfile: 6182 for data in testruns: 6183 data.extractErrorInfo() 6184 else: 6185 testruns = loadKernelLog() 6186 for data in testruns: 6187 parseKernelLog(data) 6188 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)): 6189 appendIncompleteTraceLog(testruns) 6190 if not sysvals.stamp: 6191 pprint('ERROR: data does not include the expected stamp') 6192 return (testruns, {'error': 'timeline generation failed'}) 6193 shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr', 6194 'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi'] 6195 sysvals.vprint('System Info:') 6196 for key in sorted(sysvals.stamp): 6197 if key in shown: 6198 sysvals.vprint(' %-8s : %s' % (key.upper(), sysvals.stamp[key])) 6199 sysvals.vprint('Command:\n %s' % sysvals.cmdline) 6200 for data in testruns: 6201 if data.turbostat: 6202 idx, s = 0, 'Turbostat:\n ' 6203 for val in data.turbostat.split('|'): 6204 idx += len(val) + 1 6205 if idx >= 80: 6206 idx = 0 6207 s += '\n ' 6208 s += val + ' ' 6209 sysvals.vprint(s) 6210 data.printDetails() 6211 if len(sysvals.platinfo) > 0: 6212 sysvals.vprint('\nPlatform Info:') 6213 for info in sysvals.platinfo: 6214 sysvals.vprint('[%s - %s]' % (info[0], info[1])) 6215 sysvals.vprint(info[2]) 6216 sysvals.vprint('') 6217 if sysvals.cgdump: 6218 for data in testruns: 6219 data.debugPrint() 6220 sys.exit(0) 6221 if len(testruns) < 1: 6222 pprint('ERROR: Not enough test data to build a timeline') 6223 return (testruns, {'error': 'timeline generation failed'}) 6224 sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile) 6225 createHTML(testruns, error) 6226 if not quiet: 6227 pprint('DONE: %s' % sysvals.htmlfile) 6228 data = testruns[0] 6229 stamp = data.stamp 6230 stamp['suspend'], stamp['resume'] = data.getTimeValues() 6231 if data.fwValid: 6232 stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume 6233 if error: 6234 stamp['error'] = error 6235 return (testruns, stamp) 6236 6237# Function: rerunTest 6238# Description: 6239# generate an output from an existing set of ftrace/dmesg logs 6240def rerunTest(htmlfile=''): 6241 if sysvals.ftracefile: 6242 doesTraceLogHaveTraceEvents() 6243 if not sysvals.dmesgfile and not sysvals.usetraceevents: 6244 doError('recreating this html output requires a dmesg file') 6245 if htmlfile: 6246 sysvals.htmlfile = htmlfile 6247 else: 6248 sysvals.setOutputFile() 6249 if os.path.exists(sysvals.htmlfile): 6250 if not os.path.isfile(sysvals.htmlfile): 6251 doError('a directory already exists with this name: %s' % sysvals.htmlfile) 6252 elif not os.access(sysvals.htmlfile, os.W_OK): 6253 doError('missing permission to write to %s' % sysvals.htmlfile) 6254 testruns, stamp = processData() 6255 sysvals.resetlog() 6256 return stamp 6257 6258# Function: runTest 6259# Description: 6260# execute a suspend/resume, gather the logs, and generate the output 6261def runTest(n=0, quiet=False): 6262 # prepare for the test 6263 sysvals.initTestOutput('suspend') 6264 op = sysvals.writeDatafileHeader(sysvals.dmesgfile, []) 6265 op.write('# EXECUTION TRACE START\n') 6266 op.close() 6267 if n <= 1: 6268 if sysvals.rs != 0: 6269 sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis')) 6270 sysvals.setRuntimeSuspend(True) 6271 if sysvals.display: 6272 ret = sysvals.displayControl('init') 6273 sysvals.dlog('xset display init, ret = %d' % ret) 6274 sysvals.testVal(sysvals.pmdpath, 'basic', '1') 6275 sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y') 6276 sysvals.dlog('initialize ftrace') 6277 sysvals.initFtrace(quiet) 6278 6279 # execute the test 6280 executeSuspend(quiet) 6281 sysvals.cleanupFtrace() 6282 if sysvals.skiphtml: 6283 sysvals.outputResult({}, n) 6284 sysvals.sudoUserchown(sysvals.testdir) 6285 return 6286 testruns, stamp = processData(True, quiet) 6287 for data in testruns: 6288 del data 6289 sysvals.sudoUserchown(sysvals.testdir) 6290 sysvals.outputResult(stamp, n) 6291 if 'error' in stamp: 6292 return 2 6293 return 0 6294 6295def find_in_html(html, start, end, firstonly=True): 6296 cnt, out, list = len(html), [], [] 6297 if firstonly: 6298 m = re.search(start, html) 6299 if m: 6300 list.append(m) 6301 else: 6302 list = re.finditer(start, html) 6303 for match in list: 6304 s = match.end() 6305 e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000 6306 m = re.search(end, html[s:e]) 6307 if not m: 6308 break 6309 e = s + m.start() 6310 str = html[s:e] 6311 if end == 'ms': 6312 num = re.search(r'[-+]?\d*\.\d+|\d+', str) 6313 str = num.group() if num else 'NaN' 6314 if firstonly: 6315 return str 6316 out.append(str) 6317 if firstonly: 6318 return '' 6319 return out 6320 6321def data_from_html(file, outpath, issues, fulldetail=False): 6322 try: 6323 html = open(file, 'r').read() 6324 except: 6325 html = ascii(open(file, 'rb').read()) 6326 sysvals.htmlfile = os.path.relpath(file, outpath) 6327 # extract general info 6328 suspend = find_in_html(html, 'Kernel Suspend', 'ms') 6329 resume = find_in_html(html, 'Kernel Resume', 'ms') 6330 sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>') 6331 line = find_in_html(html, '<div class="stamp">', '</div>') 6332 stmp = line.split() 6333 if not suspend or not resume or len(stmp) != 8: 6334 return False 6335 try: 6336 dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p') 6337 except: 6338 return False 6339 sysvals.hostname = stmp[0] 6340 tstr = dt.strftime('%Y/%m/%d %H:%M:%S') 6341 error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>') 6342 if error: 6343 m = re.match(r'[a-z0-9]* failed in (?P<p>\S*).*', error) 6344 if m: 6345 result = 'fail in %s' % m.group('p') 6346 else: 6347 result = 'fail' 6348 else: 6349 result = 'pass' 6350 # extract error info 6351 tp, ilist = False, [] 6352 extra = dict() 6353 log = find_in_html(html, '<div id="dmesglog" style="display:none;">', 6354 '</div>').strip() 6355 if log: 6356 d = Data(0) 6357 d.end = 999999999 6358 d.dmesgtext = log.split('\n') 6359 tp = d.extractErrorInfo() 6360 if len(issues) < 100: 6361 for msg in tp.msglist: 6362 sysvals.errorSummary(issues, msg) 6363 if stmp[2] == 'freeze': 6364 extra = d.turbostatInfo() 6365 elist = dict() 6366 for dir in d.errorinfo: 6367 for err in d.errorinfo[dir]: 6368 if err[0] not in elist: 6369 elist[err[0]] = 0 6370 elist[err[0]] += 1 6371 for i in elist: 6372 ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i) 6373 line = find_in_html(log, '# wifi ', '\n') 6374 if line: 6375 extra['wifi'] = line 6376 line = find_in_html(log, '# netfix ', '\n') 6377 if line: 6378 extra['netfix'] = line 6379 line = find_in_html(log, '# command ', '\n') 6380 if line: 6381 m = re.match(r'.* -m (?P<m>\S*).*', line) 6382 if m: 6383 extra['fullmode'] = m.group('m') 6384 low = find_in_html(html, 'freeze time: <b>', ' ms</b>') 6385 for lowstr in ['waking', '+']: 6386 if not low: 6387 break 6388 if lowstr not in low: 6389 continue 6390 if lowstr == '+': 6391 issue = 'S2LOOPx%d' % len(low.split('+')) 6392 else: 6393 m = re.match(r'.*waking *(?P<n>[0-9]*) *times.*', low) 6394 issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN' 6395 match = [i for i in issues if i['match'] == issue] 6396 if len(match) > 0: 6397 match[0]['count'] += 1 6398 if sysvals.hostname not in match[0]['urls']: 6399 match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile] 6400 elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]: 6401 match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile) 6402 else: 6403 issues.append({ 6404 'match': issue, 'count': 1, 'line': issue, 6405 'urls': {sysvals.hostname: [sysvals.htmlfile]}, 6406 }) 6407 ilist.append(issue) 6408 # extract device info 6409 devices = dict() 6410 for line in html.split('\n'): 6411 m = re.match(r' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line) 6412 if not m or 'thread kth' in line or 'thread sec' in line: 6413 continue 6414 m = re.match(r'(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title')) 6415 if not m: 6416 continue 6417 name, time, phase = m.group('n'), m.group('t'), m.group('p') 6418 if name == 'async_synchronize_full': 6419 continue 6420 if ' async' in name or ' sync' in name: 6421 name = ' '.join(name.split(' ')[:-1]) 6422 if phase.startswith('suspend'): 6423 d = 'suspend' 6424 elif phase.startswith('resume'): 6425 d = 'resume' 6426 else: 6427 continue 6428 if d not in devices: 6429 devices[d] = dict() 6430 if name not in devices[d]: 6431 devices[d][name] = 0.0 6432 devices[d][name] += float(time) 6433 # create worst device info 6434 worst = dict() 6435 for d in ['suspend', 'resume']: 6436 worst[d] = {'name':'', 'time': 0.0} 6437 dev = devices[d] if d in devices else 0 6438 if dev and len(dev.keys()) > 0: 6439 n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0] 6440 worst[d]['name'], worst[d]['time'] = n, dev[n] 6441 data = { 6442 'mode': stmp[2], 6443 'host': stmp[0], 6444 'kernel': stmp[1], 6445 'sysinfo': sysinfo, 6446 'time': tstr, 6447 'result': result, 6448 'issues': ' '.join(ilist), 6449 'suspend': suspend, 6450 'resume': resume, 6451 'devlist': devices, 6452 'sus_worst': worst['suspend']['name'], 6453 'sus_worsttime': worst['suspend']['time'], 6454 'res_worst': worst['resume']['name'], 6455 'res_worsttime': worst['resume']['time'], 6456 'url': sysvals.htmlfile, 6457 } 6458 for key in extra: 6459 data[key] = extra[key] 6460 if fulldetail: 6461 data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False) 6462 if tp: 6463 for arg in ['-multi ', '-info ']: 6464 if arg in tp.cmdline: 6465 data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1] 6466 break 6467 return data 6468 6469def genHtml(subdir, force=False): 6470 for dirname, dirnames, filenames in os.walk(subdir): 6471 sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = '' 6472 for filename in filenames: 6473 file = os.path.join(dirname, filename) 6474 if sysvals.usable(file): 6475 if(re.match(r'.*_dmesg.txt', filename)): 6476 sysvals.dmesgfile = file 6477 elif(re.match(r'.*_ftrace.txt', filename)): 6478 sysvals.ftracefile = file 6479 sysvals.setOutputFile() 6480 if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \ 6481 (force or not sysvals.usable(sysvals.htmlfile, True)): 6482 pprint('FTRACE: %s' % sysvals.ftracefile) 6483 if sysvals.dmesgfile: 6484 pprint('DMESG : %s' % sysvals.dmesgfile) 6485 rerunTest() 6486 6487# Function: runSummary 6488# Description: 6489# create a summary of tests in a sub-directory 6490def runSummary(subdir, local=True, genhtml=False): 6491 inpath = os.path.abspath(subdir) 6492 outpath = os.path.abspath('.') if local else inpath 6493 pprint('Generating a summary of folder:\n %s' % inpath) 6494 if genhtml: 6495 genHtml(subdir) 6496 target, issues, testruns = '', [], [] 6497 desc = {'host':[],'mode':[],'kernel':[]} 6498 for dirname, dirnames, filenames in os.walk(subdir): 6499 for filename in filenames: 6500 if(not re.match(r'.*.html', filename)): 6501 continue 6502 data = data_from_html(os.path.join(dirname, filename), outpath, issues) 6503 if(not data): 6504 continue 6505 if 'target' in data: 6506 target = data['target'] 6507 testruns.append(data) 6508 for key in desc: 6509 if data[key] not in desc[key]: 6510 desc[key].append(data[key]) 6511 pprint('Summary files:') 6512 if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1: 6513 title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0]) 6514 if target: 6515 title += ' %s' % target 6516 else: 6517 title = inpath 6518 createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title) 6519 pprint(' summary.html - tabular list of test data found') 6520 createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title) 6521 pprint(' summary-devices.html - kernel device list sorted by total execution time') 6522 createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title) 6523 pprint(' summary-issues.html - kernel issues found sorted by frequency') 6524 6525# Function: checkArgBool 6526# Description: 6527# check if a boolean string value is true or false 6528def checkArgBool(name, value): 6529 if value in switchvalues: 6530 if value in switchoff: 6531 return False 6532 return True 6533 doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True) 6534 return False 6535 6536# Function: configFromFile 6537# Description: 6538# Configure the script via the info in a config file 6539def configFromFile(file): 6540 Config = configparser.ConfigParser() 6541 6542 Config.read(file) 6543 sections = Config.sections() 6544 overridekprobes = False 6545 overridedevkprobes = False 6546 if 'Settings' in sections: 6547 for opt in Config.options('Settings'): 6548 value = Config.get('Settings', opt).lower() 6549 option = opt.lower() 6550 if(option == 'verbose'): 6551 sysvals.verbose = checkArgBool(option, value) 6552 elif(option == 'addlogs'): 6553 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value) 6554 elif(option == 'dev'): 6555 sysvals.usedevsrc = checkArgBool(option, value) 6556 elif(option == 'proc'): 6557 sysvals.useprocmon = checkArgBool(option, value) 6558 elif(option == 'x2'): 6559 if checkArgBool(option, value): 6560 sysvals.execcount = 2 6561 elif(option == 'callgraph'): 6562 sysvals.usecallgraph = checkArgBool(option, value) 6563 elif(option == 'override-timeline-functions'): 6564 overridekprobes = checkArgBool(option, value) 6565 elif(option == 'override-dev-timeline-functions'): 6566 overridedevkprobes = checkArgBool(option, value) 6567 elif(option == 'skiphtml'): 6568 sysvals.skiphtml = checkArgBool(option, value) 6569 elif(option == 'sync'): 6570 sysvals.sync = checkArgBool(option, value) 6571 elif(option == 'rs' or option == 'runtimesuspend'): 6572 if value in switchvalues: 6573 if value in switchoff: 6574 sysvals.rs = -1 6575 else: 6576 sysvals.rs = 1 6577 else: 6578 doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True) 6579 elif(option == 'display'): 6580 disopt = ['on', 'off', 'standby', 'suspend'] 6581 if value not in disopt: 6582 doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True) 6583 sysvals.display = value 6584 elif(option == 'gzip'): 6585 sysvals.gzip = checkArgBool(option, value) 6586 elif(option == 'cgfilter'): 6587 sysvals.setCallgraphFilter(value) 6588 elif(option == 'cgskip'): 6589 if value in switchoff: 6590 sysvals.cgskip = '' 6591 else: 6592 sysvals.cgskip = sysvals.configFile(val) 6593 if(not sysvals.cgskip): 6594 doError('%s does not exist' % sysvals.cgskip) 6595 elif(option == 'cgtest'): 6596 sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False) 6597 elif(option == 'cgphase'): 6598 d = Data(0) 6599 if value not in d.phasedef: 6600 doError('invalid phase --> (%s: %s), valid phases are %s'\ 6601 % (option, value, d.phasedef.keys()), True) 6602 sysvals.cgphase = value 6603 elif(option == 'fadd'): 6604 file = sysvals.configFile(value) 6605 if(not file): 6606 doError('%s does not exist' % value) 6607 sysvals.addFtraceFilterFunctions(file) 6608 elif(option == 'result'): 6609 sysvals.result = value 6610 elif(option == 'multi'): 6611 nums = value.split() 6612 if len(nums) != 2: 6613 doError('multi requires 2 integers (exec_count and delay)', True) 6614 sysvals.multiinit(nums[0], nums[1]) 6615 elif(option == 'devicefilter'): 6616 sysvals.setDeviceFilter(value) 6617 elif(option == 'expandcg'): 6618 sysvals.cgexp = checkArgBool(option, value) 6619 elif(option == 'srgap'): 6620 if checkArgBool(option, value): 6621 sysvals.srgap = 5 6622 elif(option == 'mode'): 6623 sysvals.suspendmode = value 6624 elif(option == 'command' or option == 'cmd'): 6625 sysvals.testcommand = value 6626 elif(option == 'x2delay'): 6627 sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False) 6628 elif(option == 'predelay'): 6629 sysvals.predelay = getArgInt('predelay', value, 0, 60000, False) 6630 elif(option == 'postdelay'): 6631 sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False) 6632 elif(option == 'maxdepth'): 6633 sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False) 6634 elif(option == 'rtcwake'): 6635 if value in switchoff: 6636 sysvals.rtcwake = False 6637 else: 6638 sysvals.rtcwake = True 6639 sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False) 6640 elif(option == 'timeprec'): 6641 sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False)) 6642 elif(option == 'mindev'): 6643 sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False) 6644 elif(option == 'callloop-maxgap'): 6645 sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False) 6646 elif(option == 'callloop-maxlen'): 6647 sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False) 6648 elif(option == 'mincg'): 6649 sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False) 6650 elif(option == 'bufsize'): 6651 sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False) 6652 elif(option == 'output-dir'): 6653 sysvals.outdir = sysvals.setOutputFolder(value) 6654 6655 if sysvals.suspendmode == 'command' and not sysvals.testcommand: 6656 doError('No command supplied for mode "command"') 6657 6658 # compatibility errors 6659 if sysvals.usedevsrc and sysvals.usecallgraph: 6660 doError('-dev is not compatible with -f') 6661 if sysvals.usecallgraph and sysvals.useprocmon: 6662 doError('-proc is not compatible with -f') 6663 6664 if overridekprobes: 6665 sysvals.tracefuncs = dict() 6666 if overridedevkprobes: 6667 sysvals.dev_tracefuncs = dict() 6668 6669 kprobes = dict() 6670 kprobesec = 'dev_timeline_functions_'+platform.machine() 6671 if kprobesec in sections: 6672 for name in Config.options(kprobesec): 6673 text = Config.get(kprobesec, name) 6674 kprobes[name] = (text, True) 6675 kprobesec = 'timeline_functions_'+platform.machine() 6676 if kprobesec in sections: 6677 for name in Config.options(kprobesec): 6678 if name in kprobes: 6679 doError('Duplicate timeline function found "%s"' % (name)) 6680 text = Config.get(kprobesec, name) 6681 kprobes[name] = (text, False) 6682 6683 for name in kprobes: 6684 function = name 6685 format = name 6686 color = '' 6687 args = dict() 6688 text, dev = kprobes[name] 6689 data = text.split() 6690 i = 0 6691 for val in data: 6692 # bracketted strings are special formatting, read them separately 6693 if val[0] == '[' and val[-1] == ']': 6694 for prop in val[1:-1].split(','): 6695 p = prop.split('=') 6696 if p[0] == 'color': 6697 try: 6698 color = int(p[1], 16) 6699 color = '#'+p[1] 6700 except: 6701 color = p[1] 6702 continue 6703 # first real arg should be the format string 6704 if i == 0: 6705 format = val 6706 # all other args are actual function args 6707 else: 6708 d = val.split('=') 6709 args[d[0]] = d[1] 6710 i += 1 6711 if not function or not format: 6712 doError('Invalid kprobe: %s' % name) 6713 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format): 6714 if arg not in args: 6715 doError('Kprobe "%s" is missing argument "%s"' % (name, arg)) 6716 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs): 6717 doError('Duplicate timeline function found "%s"' % (name)) 6718 6719 kp = { 6720 'name': name, 6721 'func': function, 6722 'format': format, 6723 sysvals.archargs: args 6724 } 6725 if color: 6726 kp['color'] = color 6727 if dev: 6728 sysvals.dev_tracefuncs[name] = kp 6729 else: 6730 sysvals.tracefuncs[name] = kp 6731 6732# Function: printHelp 6733# Description: 6734# print out the help text 6735def printHelp(): 6736 pprint('\n%s v%s\n'\ 6737 'Usage: sudo sleepgraph <options> <commands>\n'\ 6738 '\n'\ 6739 'Description:\n'\ 6740 ' This tool is designed to assist kernel and OS developers in optimizing\n'\ 6741 ' their linux stack\'s suspend/resume time. Using a kernel image built\n'\ 6742 ' with a few extra options enabled, the tool will execute a suspend and\n'\ 6743 ' capture dmesg and ftrace data until resume is complete. This data is\n'\ 6744 ' transformed into a device timeline and an optional callgraph to give\n'\ 6745 ' a detailed view of which devices/subsystems are taking the most\n'\ 6746 ' time in suspend/resume.\n'\ 6747 '\n'\ 6748 ' If no specific command is given, the default behavior is to initiate\n'\ 6749 ' a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\ 6750 '\n'\ 6751 ' Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\ 6752 ' HTML output: <hostname>_<mode>.html\n'\ 6753 ' raw dmesg output: <hostname>_<mode>_dmesg.txt\n'\ 6754 ' raw ftrace output: <hostname>_<mode>_ftrace.txt\n'\ 6755 '\n'\ 6756 'Options:\n'\ 6757 ' -h Print this help text\n'\ 6758 ' -v Print the current tool version\n'\ 6759 ' -config fn Pull arguments and config options from file fn\n'\ 6760 ' -verbose Print extra information during execution and analysis\n'\ 6761 ' -m mode Mode to initiate for suspend (default: %s)\n'\ 6762 ' -o name Overrides the output subdirectory name when running a new test\n'\ 6763 ' default: suspend-{date}-{time}\n'\ 6764 ' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\ 6765 ' -addlogs Add the dmesg and ftrace logs to the html output\n'\ 6766 ' -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\ 6767 ' -srgap Add a visible gap in the timeline between sus/res (default: disabled)\n'\ 6768 ' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\ 6769 ' -result fn Export a results table to a text file for parsing.\n'\ 6770 ' -wifi If a wifi connection is available, check that it reconnects after resume.\n'\ 6771 ' -wifitrace Trace kernel execution through wifi reconnect.\n'\ 6772 ' -netfix Use netfix to reset the network in the event it fails to resume.\n'\ 6773 ' -debugtiming Add timestamp to each printed line\n'\ 6774 ' [testprep]\n'\ 6775 ' -sync Sync the filesystems before starting the test\n'\ 6776 ' -rs on/off Enable/disable runtime suspend for all devices, restore all after test\n'\ 6777 ' -display m Change the display mode to m for the test (on/off/standby/suspend)\n'\ 6778 ' [advanced]\n'\ 6779 ' -gzip Gzip the trace and dmesg logs to save space\n'\ 6780 ' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"\n'\ 6781 ' -proc Add usermode process info into the timeline (default: disabled)\n'\ 6782 ' -dev Add kernel function calls and threads to the timeline (default: disabled)\n'\ 6783 ' -x2 Run two suspend/resumes back to back (default: disabled)\n'\ 6784 ' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)\n'\ 6785 ' -predelay t Include t ms delay before 1st suspend (default: 0 ms)\n'\ 6786 ' -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\ 6787 ' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\ 6788 ' -multi n d Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\ 6789 ' by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\ 6790 ' The outputs will be created in a new subdirectory with a summary page.\n'\ 6791 ' -maxfail n Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\ 6792 ' [debug]\n'\ 6793 ' -f Use ftrace to create device callgraphs (default: disabled)\n'\ 6794 ' -ftop Use ftrace on the top level call: "%s" (default: disabled)\n'\ 6795 ' -maxdepth N limit the callgraph data to N call levels (default: 0=all)\n'\ 6796 ' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\ 6797 ' -fadd file Add functions to be graphed in the timeline from a list in a text file\n'\ 6798 ' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\ 6799 ' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\ 6800 ' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)\n'\ 6801 ' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\ 6802 ' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\ 6803 ' -cgfilter S Filter the callgraph output in the timeline\n'\ 6804 ' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\ 6805 ' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\ 6806 ' -devdump Print out all the raw device data for each phase\n'\ 6807 ' -cgdump Print out all the raw callgraph data\n'\ 6808 '\n'\ 6809 'Other commands:\n'\ 6810 ' -modes List available suspend modes\n'\ 6811 ' -status Test to see if the system is enabled to run this tool\n'\ 6812 ' -fpdt Print out the contents of the ACPI Firmware Performance Data Table\n'\ 6813 ' -wificheck Print out wifi connection info\n'\ 6814 ' -x<mode> Test xset by toggling the given mode (on/off/standby/suspend)\n'\ 6815 ' -sysinfo Print out system info extracted from BIOS\n'\ 6816 ' -devinfo Print out the pm settings of all devices which support runtime suspend\n'\ 6817 ' -cmdinfo Print out all the platform info collected before and after suspend/resume\n'\ 6818 ' -flist Print the list of functions currently being captured in ftrace\n'\ 6819 ' -flistall Print all functions capable of being captured in ftrace\n'\ 6820 ' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\ 6821 ' [redo]\n'\ 6822 ' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)\n'\ 6823 ' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)\n'\ 6824 '' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc)) 6825 return True 6826 6827# ----------------- MAIN -------------------- 6828# exec start (skipped if script is loaded as library) 6829if __name__ == '__main__': 6830 genhtml = False 6831 cmd = '' 6832 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', 6833 '-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend', 6834 '-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo'] 6835 if '-f' in sys.argv: 6836 sysvals.cgskip = sysvals.configFile('cgskip.txt') 6837 # loop through the command line arguments 6838 args = iter(sys.argv[1:]) 6839 for arg in args: 6840 if(arg == '-m'): 6841 try: 6842 val = next(args) 6843 except: 6844 doError('No mode supplied', True) 6845 if val == 'command' and not sysvals.testcommand: 6846 doError('No command supplied for mode "command"', True) 6847 sysvals.suspendmode = val 6848 elif(arg in simplecmds): 6849 cmd = arg[1:] 6850 elif(arg == '-h'): 6851 printHelp() 6852 sys.exit(0) 6853 elif(arg == '-v'): 6854 pprint("Version %s" % sysvals.version) 6855 sys.exit(0) 6856 elif(arg == '-debugtiming'): 6857 debugtiming = True 6858 elif(arg == '-x2'): 6859 sysvals.execcount = 2 6860 elif(arg == '-x2delay'): 6861 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000) 6862 elif(arg == '-predelay'): 6863 sysvals.predelay = getArgInt('-predelay', args, 0, 60000) 6864 elif(arg == '-postdelay'): 6865 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000) 6866 elif(arg == '-f'): 6867 sysvals.usecallgraph = True 6868 elif(arg == '-ftop'): 6869 sysvals.usecallgraph = True 6870 sysvals.ftop = True 6871 sysvals.usekprobes = False 6872 elif(arg == '-skiphtml'): 6873 sysvals.skiphtml = True 6874 elif(arg == '-cgdump'): 6875 sysvals.cgdump = True 6876 elif(arg == '-devdump'): 6877 sysvals.devdump = True 6878 elif(arg == '-genhtml'): 6879 genhtml = True 6880 elif(arg == '-addlogs'): 6881 sysvals.dmesglog = sysvals.ftracelog = True 6882 elif(arg == '-nologs'): 6883 sysvals.dmesglog = sysvals.ftracelog = False 6884 elif(arg == '-addlogdmesg'): 6885 sysvals.dmesglog = True 6886 elif(arg == '-addlogftrace'): 6887 sysvals.ftracelog = True 6888 elif(arg == '-noturbostat'): 6889 sysvals.tstat = False 6890 elif(arg == '-verbose'): 6891 sysvals.verbose = True 6892 elif(arg == '-proc'): 6893 sysvals.useprocmon = True 6894 elif(arg == '-dev'): 6895 sysvals.usedevsrc = True 6896 elif(arg == '-sync'): 6897 sysvals.sync = True 6898 elif(arg == '-wifi'): 6899 sysvals.wifi = True 6900 elif(arg == '-wifitrace'): 6901 sysvals.wifitrace = True 6902 elif(arg == '-netfix'): 6903 sysvals.netfix = True 6904 elif(arg == '-gzip'): 6905 sysvals.gzip = True 6906 elif(arg == '-info'): 6907 try: 6908 val = next(args) 6909 except: 6910 doError('-info requires one string argument', True) 6911 elif(arg == '-desc'): 6912 try: 6913 val = next(args) 6914 except: 6915 doError('-desc requires one string argument', True) 6916 elif(arg == '-rs'): 6917 try: 6918 val = next(args) 6919 except: 6920 doError('-rs requires "enable" or "disable"', True) 6921 if val.lower() in switchvalues: 6922 if val.lower() in switchoff: 6923 sysvals.rs = -1 6924 else: 6925 sysvals.rs = 1 6926 else: 6927 doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True) 6928 elif(arg == '-display'): 6929 try: 6930 val = next(args) 6931 except: 6932 doError('-display requires an mode value', True) 6933 disopt = ['on', 'off', 'standby', 'suspend'] 6934 if val.lower() not in disopt: 6935 doError('valid display mode values are %s' % disopt, True) 6936 sysvals.display = val.lower() 6937 elif(arg == '-maxdepth'): 6938 sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000) 6939 elif(arg == '-rtcwake'): 6940 try: 6941 val = next(args) 6942 except: 6943 doError('No rtcwake time supplied', True) 6944 if val.lower() in switchoff: 6945 sysvals.rtcwake = False 6946 else: 6947 sysvals.rtcwake = True 6948 sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False) 6949 elif(arg == '-timeprec'): 6950 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6)) 6951 elif(arg == '-mindev'): 6952 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0) 6953 elif(arg == '-mincg'): 6954 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0) 6955 elif(arg == '-bufsize'): 6956 sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8) 6957 elif(arg == '-cgtest'): 6958 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1) 6959 elif(arg == '-cgphase'): 6960 try: 6961 val = next(args) 6962 except: 6963 doError('No phase name supplied', True) 6964 d = Data(0) 6965 if val not in d.phasedef: 6966 doError('invalid phase --> (%s: %s), valid phases are %s'\ 6967 % (arg, val, d.phasedef.keys()), True) 6968 sysvals.cgphase = val 6969 elif(arg == '-cgfilter'): 6970 try: 6971 val = next(args) 6972 except: 6973 doError('No callgraph functions supplied', True) 6974 sysvals.setCallgraphFilter(val) 6975 elif(arg == '-skipkprobe'): 6976 try: 6977 val = next(args) 6978 except: 6979 doError('No kprobe functions supplied', True) 6980 sysvals.skipKprobes(val) 6981 elif(arg == '-cgskip'): 6982 try: 6983 val = next(args) 6984 except: 6985 doError('No file supplied', True) 6986 if val.lower() in switchoff: 6987 sysvals.cgskip = '' 6988 else: 6989 sysvals.cgskip = sysvals.configFile(val) 6990 if(not sysvals.cgskip): 6991 doError('%s does not exist' % sysvals.cgskip) 6992 elif(arg == '-callloop-maxgap'): 6993 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0) 6994 elif(arg == '-callloop-maxlen'): 6995 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0) 6996 elif(arg == '-cmd'): 6997 try: 6998 val = next(args) 6999 except: 7000 doError('No command string supplied', True) 7001 sysvals.testcommand = val 7002 sysvals.suspendmode = 'command' 7003 elif(arg == '-expandcg'): 7004 sysvals.cgexp = True 7005 elif(arg == '-srgap'): 7006 sysvals.srgap = 5 7007 elif(arg == '-maxfail'): 7008 sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000) 7009 elif(arg == '-multi'): 7010 try: 7011 c, d = next(args), next(args) 7012 except: 7013 doError('-multi requires two values', True) 7014 sysvals.multiinit(c, d) 7015 elif(arg == '-o'): 7016 try: 7017 val = next(args) 7018 except: 7019 doError('No subdirectory name supplied', True) 7020 sysvals.outdir = sysvals.setOutputFolder(val) 7021 elif(arg == '-config'): 7022 try: 7023 val = next(args) 7024 except: 7025 doError('No text file supplied', True) 7026 file = sysvals.configFile(val) 7027 if(not file): 7028 doError('%s does not exist' % val) 7029 configFromFile(file) 7030 elif(arg == '-fadd'): 7031 try: 7032 val = next(args) 7033 except: 7034 doError('No text file supplied', True) 7035 file = sysvals.configFile(val) 7036 if(not file): 7037 doError('%s does not exist' % val) 7038 sysvals.addFtraceFilterFunctions(file) 7039 elif(arg == '-dmesg'): 7040 try: 7041 val = next(args) 7042 except: 7043 doError('No dmesg file supplied', True) 7044 sysvals.notestrun = True 7045 sysvals.dmesgfile = val 7046 if(os.path.exists(sysvals.dmesgfile) == False): 7047 doError('%s does not exist' % sysvals.dmesgfile) 7048 elif(arg == '-ftrace'): 7049 try: 7050 val = next(args) 7051 except: 7052 doError('No ftrace file supplied', True) 7053 sysvals.notestrun = True 7054 sysvals.ftracefile = val 7055 if(os.path.exists(sysvals.ftracefile) == False): 7056 doError('%s does not exist' % sysvals.ftracefile) 7057 elif(arg == '-summary'): 7058 try: 7059 val = next(args) 7060 except: 7061 doError('No directory supplied', True) 7062 cmd = 'summary' 7063 sysvals.outdir = val 7064 sysvals.notestrun = True 7065 if(os.path.isdir(val) == False): 7066 doError('%s is not accesible' % val) 7067 elif(arg == '-filter'): 7068 try: 7069 val = next(args) 7070 except: 7071 doError('No devnames supplied', True) 7072 sysvals.setDeviceFilter(val) 7073 elif(arg == '-result'): 7074 try: 7075 val = next(args) 7076 except: 7077 doError('No result file supplied', True) 7078 sysvals.result = val 7079 else: 7080 doError('Invalid argument: '+arg, True) 7081 7082 # compatibility errors 7083 if(sysvals.usecallgraph and sysvals.usedevsrc): 7084 doError('-dev is not compatible with -f') 7085 if(sysvals.usecallgraph and sysvals.useprocmon): 7086 doError('-proc is not compatible with -f') 7087 7088 sysvals.signalHandlerInit() 7089 if sysvals.usecallgraph and sysvals.cgskip: 7090 sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip) 7091 sysvals.setCallgraphBlacklist(sysvals.cgskip) 7092 7093 # callgraph size cannot exceed device size 7094 if sysvals.mincglen < sysvals.mindevlen: 7095 sysvals.mincglen = sysvals.mindevlen 7096 7097 # remove existing buffers before calculating memory 7098 if(sysvals.usecallgraph or sysvals.usedevsrc): 7099 sysvals.fsetVal('16', 'buffer_size_kb') 7100 sysvals.cpuInfo() 7101 7102 # just run a utility command and exit 7103 if(cmd != ''): 7104 ret = 0 7105 if(cmd == 'status'): 7106 if not statusCheck(True): 7107 ret = 1 7108 elif(cmd == 'fpdt'): 7109 if not getFPDT(True): 7110 ret = 1 7111 elif(cmd == 'sysinfo'): 7112 sysvals.printSystemInfo(True) 7113 elif(cmd == 'devinfo'): 7114 deviceInfo() 7115 elif(cmd == 'modes'): 7116 pprint(getModes()) 7117 elif(cmd == 'flist'): 7118 sysvals.getFtraceFilterFunctions(True) 7119 elif(cmd == 'flistall'): 7120 sysvals.getFtraceFilterFunctions(False) 7121 elif(cmd == 'summary'): 7122 runSummary(sysvals.outdir, True, genhtml) 7123 elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']): 7124 sysvals.verbose = True 7125 ret = sysvals.displayControl(cmd[1:]) 7126 elif(cmd == 'xstat'): 7127 pprint('Display Status: %s' % sysvals.displayControl('stat').upper()) 7128 elif(cmd == 'wificheck'): 7129 dev = sysvals.checkWifi() 7130 if dev: 7131 print('%s is connected' % sysvals.wifiDetails(dev)) 7132 else: 7133 print('No wifi connection found') 7134 elif(cmd == 'cmdinfo'): 7135 for out in sysvals.cmdinfo(False, True): 7136 print('[%s - %s]\n%s\n' % out) 7137 sys.exit(ret) 7138 7139 # if instructed, re-analyze existing data files 7140 if(sysvals.notestrun): 7141 stamp = rerunTest(sysvals.outdir) 7142 sysvals.outputResult(stamp) 7143 sys.exit(0) 7144 7145 # verify that we can run a test 7146 error = statusCheck() 7147 if(error): 7148 doError(error) 7149 7150 # extract mem/disk extra modes and convert 7151 mode = sysvals.suspendmode 7152 if mode.startswith('mem'): 7153 memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep' 7154 if memmode == 'shallow': 7155 mode = 'standby' 7156 elif memmode == 's2idle': 7157 mode = 'freeze' 7158 else: 7159 mode = 'mem' 7160 sysvals.memmode = memmode 7161 sysvals.suspendmode = mode 7162 if mode.startswith('disk-'): 7163 sysvals.diskmode = mode.split('-', 1)[-1] 7164 sysvals.suspendmode = 'disk' 7165 sysvals.systemInfo(dmidecode(sysvals.mempath)) 7166 7167 failcnt, ret = 0, 0 7168 if sysvals.multitest['run']: 7169 # run multiple tests in a separate subdirectory 7170 if not sysvals.outdir: 7171 if 'time' in sysvals.multitest: 7172 s = '-%dm' % sysvals.multitest['time'] 7173 else: 7174 s = '-x%d' % sysvals.multitest['count'] 7175 sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s) 7176 if not os.path.isdir(sysvals.outdir): 7177 os.makedirs(sysvals.outdir) 7178 sysvals.sudoUserchown(sysvals.outdir) 7179 finish = datetime.now() 7180 if 'time' in sysvals.multitest: 7181 finish += timedelta(minutes=sysvals.multitest['time']) 7182 for i in range(sysvals.multitest['count']): 7183 sysvals.multistat(True, i, finish) 7184 if i != 0 and sysvals.multitest['delay'] > 0: 7185 pprint('Waiting %d seconds...' % (sysvals.multitest['delay'])) 7186 time.sleep(sysvals.multitest['delay']) 7187 fmt = 'suspend-%y%m%d-%H%M%S' 7188 sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt)) 7189 ret = runTest(i+1, not sysvals.verbose) 7190 failcnt = 0 if not ret else failcnt + 1 7191 if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail: 7192 pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail)) 7193 break 7194 sysvals.resetlog() 7195 sysvals.multistat(False, i, finish) 7196 if 'time' in sysvals.multitest and datetime.now() >= finish: 7197 break 7198 if not sysvals.skiphtml: 7199 runSummary(sysvals.outdir, False, False) 7200 sysvals.sudoUserchown(sysvals.outdir) 7201 else: 7202 if sysvals.outdir: 7203 sysvals.testdir = sysvals.outdir 7204 # run the test in the current directory 7205 ret = runTest() 7206 7207 # reset to default values after testing 7208 if sysvals.display: 7209 sysvals.displayControl('reset') 7210 if sysvals.rs != 0: 7211 sysvals.setRuntimeSuspend(False) 7212 sys.exit(ret) 7213