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