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