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