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