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