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