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