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