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