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