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