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