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