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