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