]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blob - scripts/analyze_suspend.py
Merge tag 'staging-4.11-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[mirror_ubuntu-artful-kernel.git] / scripts / analyze_suspend.py
1 #!/usr/bin/python
2 #
3 # Tool for analyzing suspend/resume timing
4 # Copyright (c) 2013, Intel Corporation.
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms and conditions of the GNU General Public License,
8 # version 2, as published by the Free Software Foundation.
9 #
10 # This program is distributed in the hope it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
18 #
19 # Authors:
20 # Todd Brandt <todd.e.brandt@linux.intel.com>
21 #
22 # Links:
23 # Home Page
24 # https://01.org/suspendresume
25 # Source repo
26 # https://github.com/01org/suspendresume
27 #
28 # Description:
29 # This tool is designed to assist kernel and OS developers in optimizing
30 # their linux stack's suspend/resume time. Using a kernel image built
31 # with a few extra options enabled, the tool will execute a suspend and
32 # will capture dmesg and ftrace data until resume is complete. This data
33 # is transformed into a device timeline and a callgraph to give a quick
34 # and detailed view of which devices and callbacks are taking the most
35 # time in suspend/resume. The output is a single html file which can be
36 # viewed in firefox or chrome.
37 #
38 # The following kernel build options are required:
39 # CONFIG_PM_DEBUG=y
40 # CONFIG_PM_SLEEP_DEBUG=y
41 # CONFIG_FTRACE=y
42 # CONFIG_FUNCTION_TRACER=y
43 # CONFIG_FUNCTION_GRAPH_TRACER=y
44 # CONFIG_KPROBES=y
45 # CONFIG_KPROBES_ON_FTRACE=y
46 #
47 # For kernel versions older than 3.15:
48 # The following additional kernel parameters are required:
49 # (e.g. in file /etc/default/grub)
50 # GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
51 #
52
53 # ----------------- LIBRARIES --------------------
54
55 import sys
56 import time
57 import os
58 import string
59 import re
60 import platform
61 from datetime import datetime
62 import struct
63 import ConfigParser
64 from threading import Thread
65 from subprocess import call, Popen, PIPE
66
67 # ----------------- CLASSES --------------------
68
69 # Class: SystemValues
70 # Description:
71 # A global, single-instance container used to
72 # store system values and test parameters
73 class SystemValues:
74 ansi = False
75 version = '4.5'
76 verbose = False
77 addlogs = False
78 mindevlen = 0.0
79 mincglen = 0.0
80 cgphase = ''
81 cgtest = -1
82 callloopmaxgap = 0.0001
83 callloopmaxlen = 0.005
84 srgap = 0
85 cgexp = False
86 outdir = ''
87 testdir = '.'
88 tpath = '/sys/kernel/debug/tracing/'
89 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
90 epath = '/sys/kernel/debug/tracing/events/power/'
91 traceevents = [
92 'suspend_resume',
93 'device_pm_callback_end',
94 'device_pm_callback_start'
95 ]
96 logmsg = ''
97 testcommand = ''
98 mempath = '/dev/mem'
99 powerfile = '/sys/power/state'
100 suspendmode = 'mem'
101 hostname = 'localhost'
102 prefix = 'test'
103 teststamp = ''
104 dmesgstart = 0.0
105 dmesgfile = ''
106 ftracefile = ''
107 htmlfile = ''
108 embedded = False
109 rtcwake = False
110 rtcwaketime = 10
111 rtcpath = ''
112 devicefilter = []
113 stamp = 0
114 execcount = 1
115 x2delay = 0
116 usecallgraph = False
117 usetraceevents = False
118 usetraceeventsonly = False
119 usetracemarkers = True
120 usekprobes = True
121 usedevsrc = False
122 useprocmon = False
123 notestrun = False
124 mixedphaseheight = True
125 devprops = dict()
126 predelay = 0
127 postdelay = 0
128 procexecfmt = 'ps - (?P<ps>.*)$'
129 devpropfmt = '# Device Properties: .*'
130 tracertypefmt = '# tracer: (?P<t>.*)'
131 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
132 stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
133 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
134 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
135 tracefuncs = {
136 'sys_sync': dict(),
137 'pm_prepare_console': dict(),
138 'pm_notifier_call_chain': dict(),
139 'freeze_processes': dict(),
140 'freeze_kernel_threads': dict(),
141 'pm_restrict_gfp_mask': dict(),
142 'acpi_suspend_begin': dict(),
143 'suspend_console': dict(),
144 'acpi_pm_prepare': dict(),
145 'syscore_suspend': dict(),
146 'arch_enable_nonboot_cpus_end': dict(),
147 'syscore_resume': dict(),
148 'acpi_pm_finish': dict(),
149 'resume_console': dict(),
150 'acpi_pm_end': dict(),
151 'pm_restore_gfp_mask': dict(),
152 'thaw_processes': dict(),
153 'pm_restore_console': dict(),
154 'CPU_OFF': {
155 'func':'_cpu_down',
156 'args_x86_64': {'cpu':'%di:s32'},
157 'format': 'CPU_OFF[{cpu}]'
158 },
159 'CPU_ON': {
160 'func':'_cpu_up',
161 'args_x86_64': {'cpu':'%di:s32'},
162 'format': 'CPU_ON[{cpu}]'
163 },
164 }
165 dev_tracefuncs = {
166 # general wait/delay/sleep
167 'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
168 'schedule_timeout_uninterruptible': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
169 'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
170 'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
171 'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
172 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
173 'acpi_os_stall': {'ub': 1},
174 # ACPI
175 'acpi_resume_power_resources': dict(),
176 'acpi_ps_parse_aml': dict(),
177 # filesystem
178 'ext4_sync_fs': dict(),
179 # 80211
180 'iwlagn_mac_start': dict(),
181 'iwlagn_alloc_bcast_station': dict(),
182 'iwl_trans_pcie_start_hw': dict(),
183 'iwl_trans_pcie_start_fw': dict(),
184 'iwl_run_init_ucode': dict(),
185 'iwl_load_ucode_wait_alive': dict(),
186 'iwl_alive_start': dict(),
187 'iwlagn_mac_stop': dict(),
188 'iwlagn_mac_suspend': dict(),
189 'iwlagn_mac_resume': dict(),
190 'iwlagn_mac_add_interface': dict(),
191 'iwlagn_mac_remove_interface': dict(),
192 'iwlagn_mac_change_interface': dict(),
193 'iwlagn_mac_config': dict(),
194 'iwlagn_configure_filter': dict(),
195 'iwlagn_mac_hw_scan': dict(),
196 'iwlagn_bss_info_changed': dict(),
197 'iwlagn_mac_channel_switch': dict(),
198 'iwlagn_mac_flush': dict(),
199 # ATA
200 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
201 # i915
202 'i915_gem_resume': dict(),
203 'i915_restore_state': dict(),
204 'intel_opregion_setup': dict(),
205 'g4x_pre_enable_dp': dict(),
206 'vlv_pre_enable_dp': dict(),
207 'chv_pre_enable_dp': dict(),
208 'g4x_enable_dp': dict(),
209 'vlv_enable_dp': dict(),
210 'intel_hpd_init': dict(),
211 'intel_opregion_register': dict(),
212 'intel_dp_detect': dict(),
213 'intel_hdmi_detect': dict(),
214 'intel_opregion_init': dict(),
215 'intel_fbdev_set_suspend': dict(),
216 }
217 kprobes = dict()
218 timeformat = '%.3f'
219 def __init__(self):
220 # if this is a phoronix test run, set some default options
221 if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ):
222 self.embedded = True
223 self.addlogs = True
224 self.htmlfile = os.environ['LOG_FILE']
225 self.archargs = 'args_'+platform.machine()
226 self.hostname = platform.node()
227 if(self.hostname == ''):
228 self.hostname = 'localhost'
229 rtc = "rtc0"
230 if os.path.exists('/dev/rtc'):
231 rtc = os.readlink('/dev/rtc')
232 rtc = '/sys/class/rtc/'+rtc
233 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
234 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
235 self.rtcpath = rtc
236 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
237 self.ansi = True
238 def setPrecision(self, num):
239 if num < 0 or num > 6:
240 return
241 self.timeformat = '%.{0}f'.format(num)
242 def setOutputFolder(self, value):
243 args = dict()
244 n = datetime.now()
245 args['date'] = n.strftime('%y%m%d')
246 args['time'] = n.strftime('%H%M%S')
247 args['hostname'] = self.hostname
248 self.outdir = value.format(**args)
249 def setOutputFile(self):
250 if((self.htmlfile == '') and (self.dmesgfile != '')):
251 m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)
252 if(m):
253 self.htmlfile = m.group('name')+'.html'
254 if((self.htmlfile == '') and (self.ftracefile != '')):
255 m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)
256 if(m):
257 self.htmlfile = m.group('name')+'.html'
258 if(self.htmlfile == ''):
259 self.htmlfile = 'output.html'
260 def initTestOutput(self, subdir, testpath=''):
261 self.prefix = self.hostname
262 v = open('/proc/version', 'r').read().strip()
263 kver = string.split(v)[2]
264 n = datetime.now()
265 testtime = n.strftime('suspend-%m%d%y-%H%M%S')
266 if not testpath:
267 testpath = n.strftime('suspend-%y%m%d-%H%M%S')
268 if(subdir != "."):
269 self.testdir = subdir+"/"+testpath
270 else:
271 self.testdir = testpath
272 self.teststamp = \
273 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
274 if(self.embedded):
275 self.dmesgfile = \
276 '/tmp/'+testtime+'_'+self.suspendmode+'_dmesg.txt'
277 self.ftracefile = \
278 '/tmp/'+testtime+'_'+self.suspendmode+'_ftrace.txt'
279 return
280 self.dmesgfile = \
281 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'
282 self.ftracefile = \
283 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'
284 self.htmlfile = \
285 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
286 if not os.path.isdir(self.testdir):
287 os.mkdir(self.testdir)
288 def setDeviceFilter(self, value):
289 self.devicefilter = []
290 if value:
291 value = value.split(',')
292 for i in value:
293 self.devicefilter.append(i.strip())
294 def rtcWakeAlarmOn(self):
295 call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
296 outD = open(self.rtcpath+'/date', 'r').read().strip()
297 outT = open(self.rtcpath+'/time', 'r').read().strip()
298 mD = re.match('^(?P<y>[0-9]*)-(?P<m>[0-9]*)-(?P<d>[0-9]*)', outD)
299 mT = re.match('^(?P<h>[0-9]*):(?P<m>[0-9]*):(?P<s>[0-9]*)', outT)
300 if(mD and mT):
301 # get the current time from hardware
302 utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds())
303 dt = datetime(\
304 int(mD.group('y')), int(mD.group('m')), int(mD.group('d')),
305 int(mT.group('h')), int(mT.group('m')), int(mT.group('s')))
306 nowtime = int(dt.strftime('%s')) + utcoffset
307 else:
308 # if hardware time fails, use the software time
309 nowtime = int(datetime.now().strftime('%s'))
310 alarm = nowtime + self.rtcwaketime
311 call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
312 def rtcWakeAlarmOff(self):
313 call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
314 def initdmesg(self):
315 # get the latest time stamp from the dmesg log
316 fp = Popen('dmesg', stdout=PIPE).stdout
317 ktime = '0'
318 for line in fp:
319 line = line.replace('\r\n', '')
320 idx = line.find('[')
321 if idx > 1:
322 line = line[idx:]
323 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
324 if(m):
325 ktime = m.group('ktime')
326 fp.close()
327 self.dmesgstart = float(ktime)
328 def getdmesg(self):
329 # store all new dmesg lines since initdmesg was called
330 fp = Popen('dmesg', stdout=PIPE).stdout
331 op = open(self.dmesgfile, 'a')
332 for line in fp:
333 line = line.replace('\r\n', '')
334 idx = line.find('[')
335 if idx > 1:
336 line = line[idx:]
337 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
338 if(not m):
339 continue
340 ktime = float(m.group('ktime'))
341 if ktime > self.dmesgstart:
342 op.write(line)
343 fp.close()
344 op.close()
345 def addFtraceFilterFunctions(self, file):
346 fp = open(file)
347 list = fp.read().split('\n')
348 fp.close()
349 for i in list:
350 if len(i) < 2:
351 continue
352 self.tracefuncs[i] = dict()
353 def getFtraceFilterFunctions(self, current):
354 rootCheck(True)
355 if not current:
356 call('cat '+self.tpath+'available_filter_functions', shell=True)
357 return
358 fp = open(self.tpath+'available_filter_functions')
359 master = fp.read().split('\n')
360 fp.close()
361 for i in self.tracefuncs:
362 if 'func' in self.tracefuncs[i]:
363 i = self.tracefuncs[i]['func']
364 if i in master:
365 print i
366 else:
367 print self.colorText(i)
368 def setFtraceFilterFunctions(self, list):
369 fp = open(self.tpath+'available_filter_functions')
370 master = fp.read().split('\n')
371 fp.close()
372 flist = ''
373 for i in list:
374 if i not in master:
375 continue
376 if ' [' in i:
377 flist += i.split(' ')[0]+'\n'
378 else:
379 flist += i+'\n'
380 fp = open(self.tpath+'set_graph_function', 'w')
381 fp.write(flist)
382 fp.close()
383 def basicKprobe(self, name):
384 self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
385 def defaultKprobe(self, name, kdata):
386 k = kdata
387 for field in ['name', 'format', 'func']:
388 if field not in k:
389 k[field] = name
390 if self.archargs in k:
391 k['args'] = k[self.archargs]
392 else:
393 k['args'] = dict()
394 k['format'] = name
395 self.kprobes[name] = k
396 def kprobeColor(self, name):
397 if name not in self.kprobes or 'color' not in self.kprobes[name]:
398 return ''
399 return self.kprobes[name]['color']
400 def kprobeDisplayName(self, name, dataraw):
401 if name not in self.kprobes:
402 self.basicKprobe(name)
403 data = ''
404 quote=0
405 # first remvoe any spaces inside quotes, and the quotes
406 for c in dataraw:
407 if c == '"':
408 quote = (quote + 1) % 2
409 if quote and c == ' ':
410 data += '_'
411 elif c != '"':
412 data += c
413 fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
414 arglist = dict()
415 # now process the args
416 for arg in sorted(args):
417 arglist[arg] = ''
418 m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
419 if m:
420 arglist[arg] = m.group('arg')
421 else:
422 m = re.match('.* '+arg+'=(?P<arg>.*)', data);
423 if m:
424 arglist[arg] = m.group('arg')
425 out = fmt.format(**arglist)
426 out = out.replace(' ', '_').replace('"', '')
427 return out
428 def kprobeText(self, kname, kprobe):
429 name = fmt = func = kname
430 args = dict()
431 if 'name' in kprobe:
432 name = kprobe['name']
433 if 'format' in kprobe:
434 fmt = kprobe['format']
435 if 'func' in kprobe:
436 func = kprobe['func']
437 if self.archargs in kprobe:
438 args = kprobe[self.archargs]
439 if 'args' in kprobe:
440 args = kprobe['args']
441 if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
442 doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
443 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
444 if arg not in args:
445 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
446 val = 'p:%s_cal %s' % (name, func)
447 for i in sorted(args):
448 val += ' %s=%s' % (i, args[i])
449 val += '\nr:%s_ret %s $retval\n' % (name, func)
450 return val
451 def addKprobes(self, output=False):
452 if len(sysvals.kprobes) < 1:
453 return
454 if output:
455 print(' kprobe functions in this kernel:')
456 # first test each kprobe
457 rejects = []
458 # sort kprobes: trace, ub-dev, custom, dev
459 kpl = [[], [], [], []]
460 for name in sorted(self.kprobes):
461 res = self.colorText('YES', 32)
462 if not self.testKprobe(name, self.kprobes[name]):
463 res = self.colorText('NO')
464 rejects.append(name)
465 else:
466 if name in self.tracefuncs:
467 kpl[0].append(name)
468 elif name in self.dev_tracefuncs:
469 if 'ub' in self.dev_tracefuncs[name]:
470 kpl[1].append(name)
471 else:
472 kpl[3].append(name)
473 else:
474 kpl[2].append(name)
475 if output:
476 print(' %s: %s' % (name, res))
477 kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
478 # remove all failed ones from the list
479 for name in rejects:
480 self.kprobes.pop(name)
481 # set the kprobes all at once
482 self.fsetVal('', 'kprobe_events')
483 kprobeevents = ''
484 for kp in kplist:
485 kprobeevents += self.kprobeText(kp, self.kprobes[kp])
486 self.fsetVal(kprobeevents, 'kprobe_events')
487 # verify that the kprobes were set as ordered
488 check = self.fgetVal('kprobe_events')
489 linesout = len(kprobeevents.split('\n')) - 1
490 linesack = len(check.split('\n')) - 1
491 if output:
492 res = '%d/%d' % (linesack, linesout)
493 if linesack < linesout:
494 res = self.colorText(res, 31)
495 else:
496 res = self.colorText(res, 32)
497 print(' working kprobe functions enabled: %s' % res)
498 self.fsetVal('1', 'events/kprobes/enable')
499 def testKprobe(self, kname, kprobe):
500 self.fsetVal('0', 'events/kprobes/enable')
501 kprobeevents = self.kprobeText(kname, kprobe)
502 if not kprobeevents:
503 return False
504 try:
505 self.fsetVal(kprobeevents, 'kprobe_events')
506 check = self.fgetVal('kprobe_events')
507 except:
508 return False
509 linesout = len(kprobeevents.split('\n'))
510 linesack = len(check.split('\n'))
511 if linesack < linesout:
512 return False
513 return True
514 def fsetVal(self, val, path, mode='w'):
515 file = self.tpath+path
516 if not os.path.exists(file):
517 return False
518 try:
519 fp = open(file, mode, 0)
520 fp.write(val)
521 fp.flush()
522 fp.close()
523 except:
524 pass
525 return True
526 def fgetVal(self, path):
527 file = self.tpath+path
528 res = ''
529 if not os.path.exists(file):
530 return res
531 try:
532 fp = open(file, 'r')
533 res = fp.read()
534 fp.close()
535 except:
536 pass
537 return res
538 def cleanupFtrace(self):
539 if(self.usecallgraph or self.usetraceevents):
540 self.fsetVal('0', 'events/kprobes/enable')
541 self.fsetVal('', 'kprobe_events')
542 def setupAllKprobes(self):
543 for name in self.tracefuncs:
544 self.defaultKprobe(name, self.tracefuncs[name])
545 for name in self.dev_tracefuncs:
546 self.defaultKprobe(name, self.dev_tracefuncs[name])
547 def isCallgraphFunc(self, name):
548 if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
549 return True
550 for i in self.tracefuncs:
551 if 'func' in self.tracefuncs[i]:
552 f = self.tracefuncs[i]['func']
553 else:
554 f = i
555 if name == f:
556 return True
557 return False
558 def initFtrace(self, testing=False):
559 print('INITIALIZING FTRACE...')
560 # turn trace off
561 self.fsetVal('0', 'tracing_on')
562 self.cleanupFtrace()
563 # set the trace clock to global
564 self.fsetVal('global', 'trace_clock')
565 # set trace buffer to a huge value
566 self.fsetVal('nop', 'current_tracer')
567 self.fsetVal('100000', 'buffer_size_kb')
568 # go no further if this is just a status check
569 if testing:
570 return
571 # initialize the callgraph trace
572 if(self.usecallgraph):
573 # set trace type
574 self.fsetVal('function_graph', 'current_tracer')
575 self.fsetVal('', 'set_ftrace_filter')
576 # set trace format options
577 self.fsetVal('print-parent', 'trace_options')
578 self.fsetVal('funcgraph-abstime', 'trace_options')
579 self.fsetVal('funcgraph-cpu', 'trace_options')
580 self.fsetVal('funcgraph-duration', 'trace_options')
581 self.fsetVal('funcgraph-proc', 'trace_options')
582 self.fsetVal('funcgraph-tail', 'trace_options')
583 self.fsetVal('nofuncgraph-overhead', 'trace_options')
584 self.fsetVal('context-info', 'trace_options')
585 self.fsetVal('graph-time', 'trace_options')
586 self.fsetVal('0', 'max_graph_depth')
587 cf = ['dpm_run_callback']
588 if(self.usetraceeventsonly):
589 cf += ['dpm_prepare', 'dpm_complete']
590 for fn in self.tracefuncs:
591 if 'func' in self.tracefuncs[fn]:
592 cf.append(self.tracefuncs[fn]['func'])
593 else:
594 cf.append(fn)
595 self.setFtraceFilterFunctions(cf)
596 # initialize the kprobe trace
597 elif self.usekprobes:
598 for name in self.tracefuncs:
599 self.defaultKprobe(name, self.tracefuncs[name])
600 if self.usedevsrc:
601 for name in self.dev_tracefuncs:
602 self.defaultKprobe(name, self.dev_tracefuncs[name])
603 print('INITIALIZING KPROBES...')
604 self.addKprobes(self.verbose)
605 if(self.usetraceevents):
606 # turn trace events on
607 events = iter(self.traceevents)
608 for e in events:
609 self.fsetVal('1', 'events/power/'+e+'/enable')
610 # clear the trace buffer
611 self.fsetVal('', 'trace')
612 def verifyFtrace(self):
613 # files needed for any trace data
614 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
615 'trace_marker', 'trace_options', 'tracing_on']
616 # files needed for callgraph trace data
617 tp = self.tpath
618 if(self.usecallgraph):
619 files += [
620 'available_filter_functions',
621 'set_ftrace_filter',
622 'set_graph_function'
623 ]
624 for f in files:
625 if(os.path.exists(tp+f) == False):
626 return False
627 return True
628 def verifyKprobes(self):
629 # files needed for kprobes to work
630 files = ['kprobe_events', 'events']
631 tp = self.tpath
632 for f in files:
633 if(os.path.exists(tp+f) == False):
634 return False
635 return True
636 def colorText(self, str, color=31):
637 if not self.ansi:
638 return str
639 return '\x1B[%d;40m%s\x1B[m' % (color, str)
640
641 sysvals = SystemValues()
642
643 # Class: DevProps
644 # Description:
645 # Simple class which holds property values collected
646 # for all the devices used in the timeline.
647 class DevProps:
648 syspath = ''
649 altname = ''
650 async = True
651 xtraclass = ''
652 xtrainfo = ''
653 def out(self, dev):
654 return '%s,%s,%d;' % (dev, self.altname, self.async)
655 def debug(self, dev):
656 print '%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.async)
657 def altName(self, dev):
658 if not self.altname or self.altname == dev:
659 return dev
660 return '%s [%s]' % (self.altname, dev)
661 def xtraClass(self):
662 if self.xtraclass:
663 return ' '+self.xtraclass
664 if not self.async:
665 return ' sync'
666 return ''
667 def xtraInfo(self):
668 if self.xtraclass:
669 return ' '+self.xtraclass
670 if self.async:
671 return ' async_device'
672 return ' sync_device'
673
674 # Class: DeviceNode
675 # Description:
676 # A container used to create a device hierachy, with a single root node
677 # and a tree of child nodes. Used by Data.deviceTopology()
678 class DeviceNode:
679 name = ''
680 children = 0
681 depth = 0
682 def __init__(self, nodename, nodedepth):
683 self.name = nodename
684 self.children = []
685 self.depth = nodedepth
686
687 # Class: Data
688 # Description:
689 # The primary container for suspend/resume test data. There is one for
690 # each test run. The data is organized into a cronological hierarchy:
691 # Data.dmesg {
692 # phases {
693 # 10 sequential, non-overlapping phases of S/R
694 # contents: times for phase start/end, order/color data for html
695 # devlist {
696 # device callback or action list for this phase
697 # device {
698 # a single device callback or generic action
699 # contents: start/stop times, pid/cpu/driver info
700 # parents/children, html id for timeline/callgraph
701 # optionally includes an ftrace callgraph
702 # optionally includes dev/ps data
703 # }
704 # }
705 # }
706 # }
707 #
708 class Data:
709 dmesg = {} # root data structure
710 phases = [] # ordered list of phases
711 start = 0.0 # test start
712 end = 0.0 # test end
713 tSuspended = 0.0 # low-level suspend start
714 tResumed = 0.0 # low-level resume start
715 tKernSus = 0.0 # kernel level suspend start
716 tKernRes = 0.0 # kernel level resume end
717 tLow = 0.0 # time spent in low-level suspend (standby/freeze)
718 fwValid = False # is firmware data available
719 fwSuspend = 0 # time spent in firmware suspend
720 fwResume = 0 # time spent in firmware resume
721 dmesgtext = [] # dmesg text file in memory
722 pstl = 0 # process timeline
723 testnumber = 0
724 idstr = ''
725 html_device_id = 0
726 stamp = 0
727 outfile = ''
728 devpids = []
729 kerror = False
730 def __init__(self, num):
731 idchar = 'abcdefghij'
732 self.pstl = dict()
733 self.testnumber = num
734 self.idstr = idchar[num]
735 self.dmesgtext = []
736 self.phases = []
737 self.dmesg = { # fixed list of 10 phases
738 'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
739 'row': 0, 'color': '#CCFFCC', 'order': 0},
740 'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
741 'row': 0, 'color': '#88FF88', 'order': 1},
742 'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
743 'row': 0, 'color': '#00AA00', 'order': 2},
744 'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
745 'row': 0, 'color': '#008888', 'order': 3},
746 'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
747 'row': 0, 'color': '#0000FF', 'order': 4},
748 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
749 'row': 0, 'color': '#FF0000', 'order': 5},
750 'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
751 'row': 0, 'color': '#FF9900', 'order': 6},
752 'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
753 'row': 0, 'color': '#FFCC00', 'order': 7},
754 'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
755 'row': 0, 'color': '#FFFF88', 'order': 8},
756 'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
757 'row': 0, 'color': '#FFFFCC', 'order': 9}
758 }
759 self.phases = self.sortedPhases()
760 self.devicegroups = []
761 for phase in self.phases:
762 self.devicegroups.append([phase])
763 self.errorinfo = {'suspend':[],'resume':[]}
764 def extractErrorInfo(self, dmesg):
765 error = ''
766 tm = 0.0
767 for i in range(len(dmesg)):
768 if 'Call Trace:' in dmesg[i]:
769 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) .*', dmesg[i])
770 if not m:
771 continue
772 tm = float(m.group('ktime'))
773 if tm < self.start or tm > self.end:
774 continue
775 for j in range(i-10, i+1):
776 error += dmesg[j]
777 continue
778 if error:
779 m = re.match('[ \t]*\[ *[0-9\.]*\] \[\<[0-9a-fA-F]*\>\] .*', dmesg[i])
780 if m:
781 error += dmesg[i]
782 else:
783 if tm < self.tSuspended:
784 dir = 'suspend'
785 else:
786 dir = 'resume'
787 error = error.replace('<', '&lt').replace('>', '&gt')
788 vprint('kernel error found in %s at %f' % (dir, tm))
789 self.errorinfo[dir].append((tm, error))
790 self.kerror = True
791 error = ''
792 def setStart(self, time):
793 self.start = time
794 def setEnd(self, time):
795 self.end = time
796 def isTraceEventOutsideDeviceCalls(self, pid, time):
797 for phase in self.phases:
798 list = self.dmesg[phase]['list']
799 for dev in list:
800 d = list[dev]
801 if(d['pid'] == pid and time >= d['start'] and
802 time < d['end']):
803 return False
804 return True
805 def sourcePhase(self, start):
806 for phase in self.phases:
807 pend = self.dmesg[phase]['end']
808 if start <= pend:
809 return phase
810 return 'resume_complete'
811 def sourceDevice(self, phaselist, start, end, pid, type):
812 tgtdev = ''
813 for phase in phaselist:
814 list = self.dmesg[phase]['list']
815 for devname in list:
816 dev = list[devname]
817 # pid must match
818 if dev['pid'] != pid:
819 continue
820 devS = dev['start']
821 devE = dev['end']
822 if type == 'device':
823 # device target event is entirely inside the source boundary
824 if(start < devS or start >= devE or end <= devS or end > devE):
825 continue
826 elif type == 'thread':
827 # thread target event will expand the source boundary
828 if start < devS:
829 dev['start'] = start
830 if end > devE:
831 dev['end'] = end
832 tgtdev = dev
833 break
834 return tgtdev
835 def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
836 # try to place the call in a device
837 tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device')
838 # calls with device pids that occur outside device bounds are dropped
839 # TODO: include these somehow
840 if not tgtdev and pid in self.devpids:
841 return False
842 # try to place the call in a thread
843 if not tgtdev:
844 tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread')
845 # create new thread blocks, expand as new calls are found
846 if not tgtdev:
847 if proc == '<...>':
848 threadname = 'kthread-%d' % (pid)
849 else:
850 threadname = '%s-%d' % (proc, pid)
851 tgtphase = self.sourcePhase(start)
852 self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
853 return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
854 # this should not happen
855 if not tgtdev:
856 vprint('[%f - %f] %s-%d %s %s %s' % \
857 (start, end, proc, pid, kprobename, cdata, rdata))
858 return False
859 # place the call data inside the src element of the tgtdev
860 if('src' not in tgtdev):
861 tgtdev['src'] = []
862 dtf = sysvals.dev_tracefuncs
863 ubiquitous = False
864 if kprobename in dtf and 'ub' in dtf[kprobename]:
865 ubiquitous = True
866 title = cdata+' '+rdata
867 mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
868 m = re.match(mstr, title)
869 if m:
870 c = m.group('caller')
871 a = m.group('args').strip()
872 r = m.group('ret')
873 if len(r) > 6:
874 r = ''
875 else:
876 r = 'ret=%s ' % r
877 if ubiquitous and c in dtf and 'ub' in dtf[c]:
878 return False
879 color = sysvals.kprobeColor(kprobename)
880 e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
881 tgtdev['src'].append(e)
882 return True
883 def overflowDevices(self):
884 # get a list of devices that extend beyond the end of this test run
885 devlist = []
886 for phase in self.phases:
887 list = self.dmesg[phase]['list']
888 for devname in list:
889 dev = list[devname]
890 if dev['end'] > self.end:
891 devlist.append(dev)
892 return devlist
893 def mergeOverlapDevices(self, devlist):
894 # merge any devices that overlap devlist
895 for dev in devlist:
896 devname = dev['name']
897 for phase in self.phases:
898 list = self.dmesg[phase]['list']
899 if devname not in list:
900 continue
901 tdev = list[devname]
902 o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
903 if o <= 0:
904 continue
905 dev['end'] = tdev['end']
906 if 'src' not in dev or 'src' not in tdev:
907 continue
908 dev['src'] += tdev['src']
909 del list[devname]
910 def usurpTouchingThread(self, name, dev):
911 # the caller test has priority of this thread, give it to him
912 for phase in self.phases:
913 list = self.dmesg[phase]['list']
914 if name in list:
915 tdev = list[name]
916 if tdev['start'] - dev['end'] < 0.1:
917 dev['end'] = tdev['end']
918 if 'src' not in dev:
919 dev['src'] = []
920 if 'src' in tdev:
921 dev['src'] += tdev['src']
922 del list[name]
923 break
924 def stitchTouchingThreads(self, testlist):
925 # merge any threads between tests that touch
926 for phase in self.phases:
927 list = self.dmesg[phase]['list']
928 for devname in list:
929 dev = list[devname]
930 if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
931 continue
932 for data in testlist:
933 data.usurpTouchingThread(devname, dev)
934 def optimizeDevSrc(self):
935 # merge any src call loops to reduce timeline size
936 for phase in self.phases:
937 list = self.dmesg[phase]['list']
938 for dev in list:
939 if 'src' not in list[dev]:
940 continue
941 src = list[dev]['src']
942 p = 0
943 for e in sorted(src, key=lambda event: event.time):
944 if not p or not e.repeat(p):
945 p = e
946 continue
947 # e is another iteration of p, move it into p
948 p.end = e.end
949 p.length = p.end - p.time
950 p.count += 1
951 src.remove(e)
952 def trimTimeVal(self, t, t0, dT, left):
953 if left:
954 if(t > t0):
955 if(t - dT < t0):
956 return t0
957 return t - dT
958 else:
959 return t
960 else:
961 if(t < t0 + dT):
962 if(t > t0):
963 return t0 + dT
964 return t + dT
965 else:
966 return t
967 def trimTime(self, t0, dT, left):
968 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
969 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
970 self.start = self.trimTimeVal(self.start, t0, dT, left)
971 self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
972 self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
973 self.end = self.trimTimeVal(self.end, t0, dT, left)
974 for phase in self.phases:
975 p = self.dmesg[phase]
976 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
977 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
978 list = p['list']
979 for name in list:
980 d = list[name]
981 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
982 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
983 if('ftrace' in d):
984 cg = d['ftrace']
985 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
986 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
987 for line in cg.list:
988 line.time = self.trimTimeVal(line.time, t0, dT, left)
989 if('src' in d):
990 for e in d['src']:
991 e.time = self.trimTimeVal(e.time, t0, dT, left)
992 def normalizeTime(self, tZero):
993 # trim out any standby or freeze clock time
994 if(self.tSuspended != self.tResumed):
995 if(self.tResumed > tZero):
996 self.trimTime(self.tSuspended, \
997 self.tResumed-self.tSuspended, True)
998 else:
999 self.trimTime(self.tSuspended, \
1000 self.tResumed-self.tSuspended, False)
1001 def setPhase(self, phase, ktime, isbegin):
1002 if(isbegin):
1003 self.dmesg[phase]['start'] = ktime
1004 else:
1005 self.dmesg[phase]['end'] = ktime
1006 def dmesgSortVal(self, phase):
1007 return self.dmesg[phase]['order']
1008 def sortedPhases(self):
1009 return sorted(self.dmesg, key=self.dmesgSortVal)
1010 def sortedDevices(self, phase):
1011 list = self.dmesg[phase]['list']
1012 slist = []
1013 tmp = dict()
1014 for devname in list:
1015 dev = list[devname]
1016 tmp[dev['start']] = devname
1017 for t in sorted(tmp):
1018 slist.append(tmp[t])
1019 return slist
1020 def fixupInitcalls(self, phase):
1021 # if any calls never returned, clip them at system resume end
1022 phaselist = self.dmesg[phase]['list']
1023 for devname in phaselist:
1024 dev = phaselist[devname]
1025 if(dev['end'] < 0):
1026 for p in self.phases:
1027 if self.dmesg[p]['end'] > dev['start']:
1028 dev['end'] = self.dmesg[p]['end']
1029 break
1030 vprint('%s (%s): callback didnt return' % (devname, phase))
1031 def deviceFilter(self, devicefilter):
1032 for phase in self.phases:
1033 list = self.dmesg[phase]['list']
1034 rmlist = []
1035 for name in list:
1036 keep = False
1037 for filter in devicefilter:
1038 if filter in name or \
1039 ('drv' in list[name] and filter in list[name]['drv']):
1040 keep = True
1041 if not keep:
1042 rmlist.append(name)
1043 for name in rmlist:
1044 del list[name]
1045 def fixupInitcallsThatDidntReturn(self):
1046 # if any calls never returned, clip them at system resume end
1047 for phase in self.phases:
1048 self.fixupInitcalls(phase)
1049 def phaseOverlap(self, phases):
1050 rmgroups = []
1051 newgroup = []
1052 for group in self.devicegroups:
1053 for phase in phases:
1054 if phase not in group:
1055 continue
1056 for p in group:
1057 if p not in newgroup:
1058 newgroup.append(p)
1059 if group not in rmgroups:
1060 rmgroups.append(group)
1061 for group in rmgroups:
1062 self.devicegroups.remove(group)
1063 self.devicegroups.append(newgroup)
1064 def newActionGlobal(self, name, start, end, pid=-1, color=''):
1065 # which phase is this device callback or action in
1066 targetphase = 'none'
1067 htmlclass = ''
1068 overlap = 0.0
1069 phases = []
1070 for phase in self.phases:
1071 pstart = self.dmesg[phase]['start']
1072 pend = self.dmesg[phase]['end']
1073 # see if the action overlaps this phase
1074 o = max(0, min(end, pend) - max(start, pstart))
1075 if o > 0:
1076 phases.append(phase)
1077 # set the target phase to the one that overlaps most
1078 if o > overlap:
1079 if overlap > 0 and phase == 'post_resume':
1080 continue
1081 targetphase = phase
1082 overlap = o
1083 # if no target phase was found, pin it to the edge
1084 if targetphase == 'none':
1085 p0start = self.dmesg[self.phases[0]]['start']
1086 if start <= p0start:
1087 targetphase = self.phases[0]
1088 else:
1089 targetphase = self.phases[-1]
1090 if pid == -2:
1091 htmlclass = ' bg'
1092 elif pid == -3:
1093 htmlclass = ' ps'
1094 if len(phases) > 1:
1095 htmlclass = ' bg'
1096 self.phaseOverlap(phases)
1097 if targetphase in self.phases:
1098 newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1099 return (targetphase, newname)
1100 return False
1101 def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1102 # new device callback for a specific phase
1103 self.html_device_id += 1
1104 devid = '%s%d' % (self.idstr, self.html_device_id)
1105 list = self.dmesg[phase]['list']
1106 length = -1.0
1107 if(start >= 0 and end >= 0):
1108 length = end - start
1109 if pid == -2:
1110 i = 2
1111 origname = name
1112 while(name in list):
1113 name = '%s[%d]' % (origname, i)
1114 i += 1
1115 list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1116 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1117 if htmlclass:
1118 list[name]['htmlclass'] = htmlclass
1119 if color:
1120 list[name]['color'] = color
1121 return name
1122 def deviceChildren(self, devname, phase):
1123 devlist = []
1124 list = self.dmesg[phase]['list']
1125 for child in list:
1126 if(list[child]['par'] == devname):
1127 devlist.append(child)
1128 return devlist
1129 def printDetails(self):
1130 vprint('Timeline Details:')
1131 vprint(' test start: %f' % self.start)
1132 vprint('kernel suspend start: %f' % self.tKernSus)
1133 for phase in self.phases:
1134 dc = len(self.dmesg[phase]['list'])
1135 vprint(' %16s: %f - %f (%d devices)' % (phase, \
1136 self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
1137 vprint(' kernel resume end: %f' % self.tKernRes)
1138 vprint(' test end: %f' % self.end)
1139 def deviceChildrenAllPhases(self, devname):
1140 devlist = []
1141 for phase in self.phases:
1142 list = self.deviceChildren(devname, phase)
1143 for dev in list:
1144 if dev not in devlist:
1145 devlist.append(dev)
1146 return devlist
1147 def masterTopology(self, name, list, depth):
1148 node = DeviceNode(name, depth)
1149 for cname in list:
1150 # avoid recursions
1151 if name == cname:
1152 continue
1153 clist = self.deviceChildrenAllPhases(cname)
1154 cnode = self.masterTopology(cname, clist, depth+1)
1155 node.children.append(cnode)
1156 return node
1157 def printTopology(self, node):
1158 html = ''
1159 if node.name:
1160 info = ''
1161 drv = ''
1162 for phase in self.phases:
1163 list = self.dmesg[phase]['list']
1164 if node.name in list:
1165 s = list[node.name]['start']
1166 e = list[node.name]['end']
1167 if list[node.name]['drv']:
1168 drv = ' {'+list[node.name]['drv']+'}'
1169 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1170 html += '<li><b>'+node.name+drv+'</b>'
1171 if info:
1172 html += '<ul>'+info+'</ul>'
1173 html += '</li>'
1174 if len(node.children) > 0:
1175 html += '<ul>'
1176 for cnode in node.children:
1177 html += self.printTopology(cnode)
1178 html += '</ul>'
1179 return html
1180 def rootDeviceList(self):
1181 # list of devices graphed
1182 real = []
1183 for phase in self.dmesg:
1184 list = self.dmesg[phase]['list']
1185 for dev in list:
1186 if list[dev]['pid'] >= 0 and dev not in real:
1187 real.append(dev)
1188 # list of top-most root devices
1189 rootlist = []
1190 for phase in self.dmesg:
1191 list = self.dmesg[phase]['list']
1192 for dev in list:
1193 pdev = list[dev]['par']
1194 pid = list[dev]['pid']
1195 if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1196 continue
1197 if pdev and pdev not in real and pdev not in rootlist:
1198 rootlist.append(pdev)
1199 return rootlist
1200 def deviceTopology(self):
1201 rootlist = self.rootDeviceList()
1202 master = self.masterTopology('', rootlist, 0)
1203 return self.printTopology(master)
1204 def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1205 # only select devices that will actually show up in html
1206 self.tdevlist = dict()
1207 for phase in self.dmesg:
1208 devlist = []
1209 list = self.dmesg[phase]['list']
1210 for dev in list:
1211 length = (list[dev]['end'] - list[dev]['start']) * 1000
1212 width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1213 if width != '0.000000' and length >= mindevlen:
1214 devlist.append(dev)
1215 self.tdevlist[phase] = devlist
1216 def addHorizontalDivider(self, devname, devend):
1217 phase = 'suspend_prepare'
1218 self.newAction(phase, devname, -2, '', \
1219 self.start, devend, '', ' sec', '')
1220 if phase not in self.tdevlist:
1221 self.tdevlist[phase] = []
1222 self.tdevlist[phase].append(devname)
1223 d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1224 return d
1225 def addProcessUsageEvent(self, name, times):
1226 # get the start and end times for this process
1227 maxC = 0
1228 tlast = 0
1229 start = -1
1230 end = -1
1231 for t in sorted(times):
1232 if tlast == 0:
1233 tlast = t
1234 continue
1235 if name in self.pstl[t]:
1236 if start == -1 or tlast < start:
1237 start = tlast
1238 if end == -1 or t > end:
1239 end = t
1240 tlast = t
1241 if start == -1 or end == -1:
1242 return 0
1243 # add a new action for this process and get the object
1244 out = self.newActionGlobal(name, start, end, -3)
1245 if not out:
1246 return 0
1247 phase, devname = out
1248 dev = self.dmesg[phase]['list'][devname]
1249 # get the cpu exec data
1250 tlast = 0
1251 clast = 0
1252 cpuexec = dict()
1253 for t in sorted(times):
1254 if tlast == 0 or t <= start or t > end:
1255 tlast = t
1256 continue
1257 list = self.pstl[t]
1258 c = 0
1259 if name in list:
1260 c = list[name]
1261 if c > maxC:
1262 maxC = c
1263 if c != clast:
1264 key = (tlast, t)
1265 cpuexec[key] = c
1266 tlast = t
1267 clast = c
1268 dev['cpuexec'] = cpuexec
1269 return maxC
1270 def createProcessUsageEvents(self):
1271 # get an array of process names
1272 proclist = []
1273 for t in self.pstl:
1274 pslist = self.pstl[t]
1275 for ps in pslist:
1276 if ps not in proclist:
1277 proclist.append(ps)
1278 # get a list of data points for suspend and resume
1279 tsus = []
1280 tres = []
1281 for t in sorted(self.pstl):
1282 if t < self.tSuspended:
1283 tsus.append(t)
1284 else:
1285 tres.append(t)
1286 # process the events for suspend and resume
1287 if len(proclist) > 0:
1288 vprint('Process Execution:')
1289 for ps in proclist:
1290 c = self.addProcessUsageEvent(ps, tsus)
1291 if c > 0:
1292 vprint('%25s (sus): %d' % (ps, c))
1293 c = self.addProcessUsageEvent(ps, tres)
1294 if c > 0:
1295 vprint('%25s (res): %d' % (ps, c))
1296
1297 # Class: DevFunction
1298 # Description:
1299 # A container for kprobe function data we want in the dev timeline
1300 class DevFunction:
1301 row = 0
1302 count = 1
1303 def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
1304 self.name = name
1305 self.args = args
1306 self.caller = caller
1307 self.ret = ret
1308 self.time = start
1309 self.length = end - start
1310 self.end = end
1311 self.ubiquitous = u
1312 self.proc = proc
1313 self.pid = pid
1314 self.color = color
1315 def title(self):
1316 cnt = ''
1317 if self.count > 1:
1318 cnt = '(x%d)' % self.count
1319 l = '%0.3fms' % (self.length * 1000)
1320 if self.ubiquitous:
1321 title = '%s(%s)%s <- %s, %s(%s)' % \
1322 (self.name, self.args, cnt, self.caller, self.ret, l)
1323 else:
1324 title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
1325 return title.replace('"', '')
1326 def text(self):
1327 if self.count > 1:
1328 text = '%s(x%d)' % (self.name, self.count)
1329 else:
1330 text = self.name
1331 return text
1332 def repeat(self, tgt):
1333 # is the tgt call just a repeat of this call (e.g. are we in a loop)
1334 dt = self.time - tgt.end
1335 # only combine calls if -all- attributes are identical
1336 if tgt.caller == self.caller and \
1337 tgt.name == self.name and tgt.args == self.args and \
1338 tgt.proc == self.proc and tgt.pid == self.pid and \
1339 tgt.ret == self.ret and dt >= 0 and \
1340 dt <= sysvals.callloopmaxgap and \
1341 self.length < sysvals.callloopmaxlen:
1342 return True
1343 return False
1344
1345 # Class: FTraceLine
1346 # Description:
1347 # A container for a single line of ftrace data. There are six basic types:
1348 # callgraph line:
1349 # call: " dpm_run_callback() {"
1350 # return: " }"
1351 # leaf: " dpm_run_callback();"
1352 # trace event:
1353 # tracing_mark_write: SUSPEND START or RESUME COMPLETE
1354 # suspend_resume: phase or custom exec block data
1355 # device_pm_callback: device callback info
1356 class FTraceLine:
1357 time = 0.0
1358 length = 0.0
1359 fcall = False
1360 freturn = False
1361 fevent = False
1362 fkprobe = False
1363 depth = 0
1364 name = ''
1365 type = ''
1366 def __init__(self, t, m='', d=''):
1367 self.time = float(t)
1368 if not m and not d:
1369 return
1370 # is this a trace event
1371 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1372 if(d == 'traceevent'):
1373 # nop format trace event
1374 msg = m
1375 else:
1376 # function_graph format trace event
1377 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1378 msg = em.group('msg')
1379
1380 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1381 if(emm):
1382 self.name = emm.group('msg')
1383 self.type = emm.group('call')
1384 else:
1385 self.name = msg
1386 km = re.match('^(?P<n>.*)_cal$', self.type)
1387 if km:
1388 self.fcall = True
1389 self.fkprobe = True
1390 self.type = km.group('n')
1391 return
1392 km = re.match('^(?P<n>.*)_ret$', self.type)
1393 if km:
1394 self.freturn = True
1395 self.fkprobe = True
1396 self.type = km.group('n')
1397 return
1398 self.fevent = True
1399 return
1400 # convert the duration to seconds
1401 if(d):
1402 self.length = float(d)/1000000
1403 # the indentation determines the depth
1404 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
1405 if(not match):
1406 return
1407 self.depth = self.getDepth(match.group('d'))
1408 m = match.group('o')
1409 # function return
1410 if(m[0] == '}'):
1411 self.freturn = True
1412 if(len(m) > 1):
1413 # includes comment with function name
1414 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
1415 if(match):
1416 self.name = match.group('n').strip()
1417 # function call
1418 else:
1419 self.fcall = True
1420 # function call with children
1421 if(m[-1] == '{'):
1422 match = re.match('^(?P<n>.*) *\(.*', m)
1423 if(match):
1424 self.name = match.group('n').strip()
1425 # function call with no children (leaf)
1426 elif(m[-1] == ';'):
1427 self.freturn = True
1428 match = re.match('^(?P<n>.*) *\(.*', m)
1429 if(match):
1430 self.name = match.group('n').strip()
1431 # something else (possibly a trace marker)
1432 else:
1433 self.name = m
1434 def getDepth(self, str):
1435 return len(str)/2
1436 def debugPrint(self, dev=''):
1437 if(self.freturn and self.fcall):
1438 print('%s -- %f (%02d): %s(); (%.3f us)' % (dev, self.time, \
1439 self.depth, self.name, self.length*1000000))
1440 elif(self.freturn):
1441 print('%s -- %f (%02d): %s} (%.3f us)' % (dev, self.time, \
1442 self.depth, self.name, self.length*1000000))
1443 else:
1444 print('%s -- %f (%02d): %s() { (%.3f us)' % (dev, self.time, \
1445 self.depth, self.name, self.length*1000000))
1446 def startMarker(self):
1447 # Is this the starting line of a suspend?
1448 if not self.fevent:
1449 return False
1450 if sysvals.usetracemarkers:
1451 if(self.name == 'SUSPEND START'):
1452 return True
1453 return False
1454 else:
1455 if(self.type == 'suspend_resume' and
1456 re.match('suspend_enter\[.*\] begin', self.name)):
1457 return True
1458 return False
1459 def endMarker(self):
1460 # Is this the ending line of a resume?
1461 if not self.fevent:
1462 return False
1463 if sysvals.usetracemarkers:
1464 if(self.name == 'RESUME COMPLETE'):
1465 return True
1466 return False
1467 else:
1468 if(self.type == 'suspend_resume' and
1469 re.match('thaw_processes\[.*\] end', self.name)):
1470 return True
1471 return False
1472
1473 # Class: FTraceCallGraph
1474 # Description:
1475 # A container for the ftrace callgraph of a single recursive function.
1476 # This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
1477 # Each instance is tied to a single device in a single phase, and is
1478 # comprised of an ordered list of FTraceLine objects
1479 class FTraceCallGraph:
1480 start = -1.0
1481 end = -1.0
1482 list = []
1483 invalid = False
1484 depth = 0
1485 pid = 0
1486 def __init__(self, pid):
1487 self.start = -1.0
1488 self.end = -1.0
1489 self.list = []
1490 self.depth = 0
1491 self.pid = pid
1492 def addLine(self, line, debug=False):
1493 # if this is already invalid, just leave
1494 if(self.invalid):
1495 return False
1496 # invalidate on too much data or bad depth
1497 if(len(self.list) >= 1000000 or self.depth < 0):
1498 self.invalidate(line)
1499 return False
1500 # compare current depth with this lines pre-call depth
1501 prelinedep = line.depth
1502 if(line.freturn and not line.fcall):
1503 prelinedep += 1
1504 last = 0
1505 lasttime = line.time
1506 virtualfname = 'execution_misalignment'
1507 if len(self.list) > 0:
1508 last = self.list[-1]
1509 lasttime = last.time
1510 # handle low misalignments by inserting returns
1511 if prelinedep < self.depth:
1512 if debug and last:
1513 print '-------- task %d --------' % self.pid
1514 last.debugPrint()
1515 idx = 0
1516 # add return calls to get the depth down
1517 while prelinedep < self.depth:
1518 if debug:
1519 print 'MISALIGN LOW (add returns): C%d - eC%d' % (self.depth, prelinedep)
1520 self.depth -= 1
1521 if idx == 0 and last and last.fcall and not last.freturn:
1522 # special case, turn last call into a leaf
1523 last.depth = self.depth
1524 last.freturn = True
1525 last.length = line.time - last.time
1526 if debug:
1527 last.debugPrint()
1528 else:
1529 vline = FTraceLine(lasttime)
1530 vline.depth = self.depth
1531 vline.name = virtualfname
1532 vline.freturn = True
1533 self.list.append(vline)
1534 if debug:
1535 vline.debugPrint()
1536 idx += 1
1537 if debug:
1538 line.debugPrint()
1539 print ''
1540 # handle high misalignments by inserting calls
1541 elif prelinedep > self.depth:
1542 if debug and last:
1543 print '-------- task %d --------' % self.pid
1544 last.debugPrint()
1545 idx = 0
1546 # add calls to get the depth up
1547 while prelinedep > self.depth:
1548 if debug:
1549 print 'MISALIGN HIGH (add calls): C%d - eC%d' % (self.depth, prelinedep)
1550 if idx == 0 and line.freturn and not line.fcall:
1551 # special case, turn this return into a leaf
1552 line.fcall = True
1553 prelinedep -= 1
1554 else:
1555 vline = FTraceLine(lasttime)
1556 vline.depth = self.depth
1557 vline.name = virtualfname
1558 vline.fcall = True
1559 if debug:
1560 vline.debugPrint()
1561 self.list.append(vline)
1562 self.depth += 1
1563 if not last:
1564 self.start = vline.time
1565 idx += 1
1566 if debug:
1567 line.debugPrint()
1568 print ''
1569 # process the call and set the new depth
1570 if(line.fcall and not line.freturn):
1571 self.depth += 1
1572 elif(line.freturn and not line.fcall):
1573 self.depth -= 1
1574 if len(self.list) < 1:
1575 self.start = line.time
1576 self.list.append(line)
1577 if(line.depth == 0 and line.freturn):
1578 if(self.start < 0):
1579 self.start = line.time
1580 self.end = line.time
1581 if line.fcall:
1582 self.end += line.length
1583 if self.list[0].name == virtualfname:
1584 self.invalid = True
1585 return True
1586 return False
1587 def invalidate(self, line):
1588 if(len(self.list) > 0):
1589 first = self.list[0]
1590 self.list = []
1591 self.list.append(first)
1592 self.invalid = True
1593 id = 'task %s' % (self.pid)
1594 window = '(%f - %f)' % (self.start, line.time)
1595 if(self.depth < 0):
1596 vprint('Too much data for '+id+\
1597 ' (buffer overflow), ignoring this callback')
1598 else:
1599 vprint('Too much data for '+id+\
1600 ' '+window+', ignoring this callback')
1601 def slice(self, t0, tN):
1602 minicg = FTraceCallGraph(0)
1603 count = -1
1604 firstdepth = 0
1605 for l in self.list:
1606 if(l.time < t0 or l.time > tN):
1607 continue
1608 if(count < 0):
1609 if(not l.fcall or l.name == 'dev_driver_string'):
1610 continue
1611 firstdepth = l.depth
1612 count = 0
1613 l.depth -= firstdepth
1614 minicg.addLine(l)
1615 if((count == 0 and l.freturn and l.fcall) or
1616 (count > 0 and l.depth <= 0)):
1617 break
1618 count += 1
1619 return minicg
1620 def repair(self, enddepth):
1621 # bring the depth back to 0 with additional returns
1622 fixed = False
1623 last = self.list[-1]
1624 for i in reversed(range(enddepth)):
1625 t = FTraceLine(last.time)
1626 t.depth = i
1627 t.freturn = True
1628 fixed = self.addLine(t)
1629 if fixed:
1630 self.end = last.time
1631 return True
1632 return False
1633 def postProcess(self, debug=False):
1634 stack = dict()
1635 cnt = 0
1636 for l in self.list:
1637 if(l.fcall and not l.freturn):
1638 stack[l.depth] = l
1639 cnt += 1
1640 elif(l.freturn and not l.fcall):
1641 if(l.depth not in stack):
1642 if debug:
1643 print 'Post Process Error: Depth missing'
1644 l.debugPrint()
1645 return False
1646 # transfer total time from return line to call line
1647 stack[l.depth].length = l.length
1648 stack.pop(l.depth)
1649 l.length = 0
1650 cnt -= 1
1651 if(cnt == 0):
1652 # trace caught the whole call tree
1653 return True
1654 elif(cnt < 0):
1655 if debug:
1656 print 'Post Process Error: Depth is less than 0'
1657 return False
1658 # trace ended before call tree finished
1659 return self.repair(cnt)
1660 def deviceMatch(self, pid, data):
1661 found = False
1662 # add the callgraph data to the device hierarchy
1663 borderphase = {
1664 'dpm_prepare': 'suspend_prepare',
1665 'dpm_complete': 'resume_complete'
1666 }
1667 if(self.list[0].name in borderphase):
1668 p = borderphase[self.list[0].name]
1669 list = data.dmesg[p]['list']
1670 for devname in list:
1671 dev = list[devname]
1672 if(pid == dev['pid'] and
1673 self.start <= dev['start'] and
1674 self.end >= dev['end']):
1675 dev['ftrace'] = self.slice(dev['start'], dev['end'])
1676 found = True
1677 return found
1678 for p in data.phases:
1679 if(data.dmesg[p]['start'] <= self.start and
1680 self.start <= data.dmesg[p]['end']):
1681 list = data.dmesg[p]['list']
1682 for devname in list:
1683 dev = list[devname]
1684 if(pid == dev['pid'] and
1685 self.start <= dev['start'] and
1686 self.end >= dev['end']):
1687 dev['ftrace'] = self
1688 found = True
1689 break
1690 break
1691 return found
1692 def newActionFromFunction(self, data):
1693 name = self.list[0].name
1694 if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
1695 return
1696 fs = self.start
1697 fe = self.end
1698 if fs < data.start or fe > data.end:
1699 return
1700 phase = ''
1701 for p in data.phases:
1702 if(data.dmesg[p]['start'] <= self.start and
1703 self.start < data.dmesg[p]['end']):
1704 phase = p
1705 break
1706 if not phase:
1707 return
1708 out = data.newActionGlobal(name, fs, fe, -2)
1709 if out:
1710 phase, myname = out
1711 data.dmesg[phase]['list'][myname]['ftrace'] = self
1712 def debugPrint(self):
1713 print('[%f - %f] %s (%d)') % (self.start, self.end, self.list[0].name, self.pid)
1714 for l in self.list:
1715 if(l.freturn and l.fcall):
1716 print('%f (%02d): %s(); (%.3f us)' % (l.time, \
1717 l.depth, l.name, l.length*1000000))
1718 elif(l.freturn):
1719 print('%f (%02d): %s} (%.3f us)' % (l.time, \
1720 l.depth, l.name, l.length*1000000))
1721 else:
1722 print('%f (%02d): %s() { (%.3f us)' % (l.time, \
1723 l.depth, l.name, l.length*1000000))
1724 print(' ')
1725
1726 class DevItem:
1727 def __init__(self, test, phase, dev):
1728 self.test = test
1729 self.phase = phase
1730 self.dev = dev
1731 def isa(self, cls):
1732 if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
1733 return True
1734 return False
1735
1736 # Class: Timeline
1737 # Description:
1738 # A container for a device timeline which calculates
1739 # all the html properties to display it correctly
1740 class Timeline:
1741 html = {}
1742 height = 0 # total timeline height
1743 scaleH = 20 # timescale (top) row height
1744 rowH = 30 # device row height
1745 bodyH = 0 # body height
1746 rows = 0 # total timeline rows
1747 rowlines = dict()
1748 rowheight = dict()
1749 def __init__(self, rowheight, scaleheight):
1750 self.rowH = rowheight
1751 self.scaleH = scaleheight
1752 self.html = {
1753 'header': '',
1754 'timeline': '',
1755 'legend': '',
1756 }
1757 # Function: getDeviceRows
1758 # Description:
1759 # determine how may rows the device funcs will take
1760 # Arguments:
1761 # rawlist: the list of devices/actions for a single phase
1762 # Output:
1763 # The total number of rows needed to display this phase of the timeline
1764 def getDeviceRows(self, rawlist):
1765 # clear all rows and set them to undefined
1766 sortdict = dict()
1767 for item in rawlist:
1768 item.row = -1
1769 sortdict[item] = item.length
1770 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
1771 remaining = len(sortlist)
1772 rowdata = dict()
1773 row = 1
1774 # try to pack each row with as many ranges as possible
1775 while(remaining > 0):
1776 if(row not in rowdata):
1777 rowdata[row] = []
1778 for i in sortlist:
1779 if(i.row >= 0):
1780 continue
1781 s = i.time
1782 e = i.time + i.length
1783 valid = True
1784 for ritem in rowdata[row]:
1785 rs = ritem.time
1786 re = ritem.time + ritem.length
1787 if(not (((s <= rs) and (e <= rs)) or
1788 ((s >= re) and (e >= re)))):
1789 valid = False
1790 break
1791 if(valid):
1792 rowdata[row].append(i)
1793 i.row = row
1794 remaining -= 1
1795 row += 1
1796 return row
1797 # Function: getPhaseRows
1798 # Description:
1799 # Organize the timeline entries into the smallest
1800 # number of rows possible, with no entry overlapping
1801 # Arguments:
1802 # devlist: the list of devices/actions in a group of contiguous phases
1803 # Output:
1804 # The total number of rows needed to display this phase of the timeline
1805 def getPhaseRows(self, devlist, row=0):
1806 # clear all rows and set them to undefined
1807 remaining = len(devlist)
1808 rowdata = dict()
1809 sortdict = dict()
1810 myphases = []
1811 # initialize all device rows to -1 and calculate devrows
1812 for item in devlist:
1813 dev = item.dev
1814 tp = (item.test, item.phase)
1815 if tp not in myphases:
1816 myphases.append(tp)
1817 dev['row'] = -1
1818 # sort by length 1st, then name 2nd
1819 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
1820 if 'src' in dev:
1821 dev['devrows'] = self.getDeviceRows(dev['src'])
1822 # sort the devlist by length so that large items graph on top
1823 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
1824 orderedlist = []
1825 for item in sortlist:
1826 if item.dev['pid'] == -2:
1827 orderedlist.append(item)
1828 for item in sortlist:
1829 if item not in orderedlist:
1830 orderedlist.append(item)
1831 # try to pack each row with as many devices as possible
1832 while(remaining > 0):
1833 rowheight = 1
1834 if(row not in rowdata):
1835 rowdata[row] = []
1836 for item in orderedlist:
1837 dev = item.dev
1838 if(dev['row'] < 0):
1839 s = dev['start']
1840 e = dev['end']
1841 valid = True
1842 for ritem in rowdata[row]:
1843 rs = ritem.dev['start']
1844 re = ritem.dev['end']
1845 if(not (((s <= rs) and (e <= rs)) or
1846 ((s >= re) and (e >= re)))):
1847 valid = False
1848 break
1849 if(valid):
1850 rowdata[row].append(item)
1851 dev['row'] = row
1852 remaining -= 1
1853 if 'devrows' in dev and dev['devrows'] > rowheight:
1854 rowheight = dev['devrows']
1855 for t, p in myphases:
1856 if t not in self.rowlines or t not in self.rowheight:
1857 self.rowlines[t] = dict()
1858 self.rowheight[t] = dict()
1859 if p not in self.rowlines[t] or p not in self.rowheight[t]:
1860 self.rowlines[t][p] = dict()
1861 self.rowheight[t][p] = dict()
1862 rh = self.rowH
1863 # section headers should use a different row height
1864 if len(rowdata[row]) == 1 and \
1865 'htmlclass' in rowdata[row][0].dev and \
1866 'sec' in rowdata[row][0].dev['htmlclass']:
1867 rh = 15
1868 self.rowlines[t][p][row] = rowheight
1869 self.rowheight[t][p][row] = rowheight * rh
1870 row += 1
1871 if(row > self.rows):
1872 self.rows = int(row)
1873 return row
1874 def phaseRowHeight(self, test, phase, row):
1875 return self.rowheight[test][phase][row]
1876 def phaseRowTop(self, test, phase, row):
1877 top = 0
1878 for i in sorted(self.rowheight[test][phase]):
1879 if i >= row:
1880 break
1881 top += self.rowheight[test][phase][i]
1882 return top
1883 # Function: calcTotalRows
1884 # Description:
1885 # Calculate the heights and offsets for the header and rows
1886 def calcTotalRows(self):
1887 maxrows = 0
1888 standardphases = []
1889 for t in self.rowlines:
1890 for p in self.rowlines[t]:
1891 total = 0
1892 for i in sorted(self.rowlines[t][p]):
1893 total += self.rowlines[t][p][i]
1894 if total > maxrows:
1895 maxrows = total
1896 if total == len(self.rowlines[t][p]):
1897 standardphases.append((t, p))
1898 self.height = self.scaleH + (maxrows*self.rowH)
1899 self.bodyH = self.height - self.scaleH
1900 # if there is 1 line per row, draw them the standard way
1901 for t, p in standardphases:
1902 for i in sorted(self.rowheight[t][p]):
1903 self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p])
1904 # Function: createTimeScale
1905 # Description:
1906 # Create the timescale for a timeline block
1907 # Arguments:
1908 # m0: start time (mode begin)
1909 # mMax: end time (mode end)
1910 # tTotal: total timeline time
1911 # mode: suspend or resume
1912 # Output:
1913 # The html code needed to display the time scale
1914 def createTimeScale(self, m0, mMax, tTotal, mode):
1915 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
1916 rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">Resume</div>\n'
1917 output = '<div class="timescale">\n'
1918 # set scale for timeline
1919 mTotal = mMax - m0
1920 tS = 0.1
1921 if(tTotal <= 0):
1922 return output+'</div>\n'
1923 if(tTotal > 4):
1924 tS = 1
1925 divTotal = int(mTotal/tS) + 1
1926 divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
1927 for i in range(divTotal):
1928 htmlline = ''
1929 if(mode == 'resume'):
1930 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
1931 val = '%0.fms' % (float(i)*tS*1000)
1932 htmlline = timescale.format(pos, val)
1933 if(i == 0):
1934 htmlline = rline
1935 else:
1936 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
1937 val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
1938 if(i == divTotal - 1):
1939 val = 'Suspend'
1940 htmlline = timescale.format(pos, val)
1941 output += htmlline
1942 output += '</div>\n'
1943 return output
1944
1945 # Class: TestProps
1946 # Description:
1947 # A list of values describing the properties of these test runs
1948 class TestProps:
1949 stamp = ''
1950 S0i3 = False
1951 fwdata = []
1952 ftrace_line_fmt_fg = \
1953 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
1954 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
1955 '[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
1956 ftrace_line_fmt_nop = \
1957 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
1958 '(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
1959 '(?P<msg>.*)'
1960 ftrace_line_fmt = ftrace_line_fmt_nop
1961 cgformat = False
1962 data = 0
1963 ktemp = dict()
1964 def __init__(self):
1965 self.ktemp = dict()
1966 def setTracerType(self, tracer):
1967 if(tracer == 'function_graph'):
1968 self.cgformat = True
1969 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
1970 elif(tracer == 'nop'):
1971 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
1972 else:
1973 doError('Invalid tracer format: [%s]' % tracer)
1974
1975 # Class: TestRun
1976 # Description:
1977 # A container for a suspend/resume test run. This is necessary as
1978 # there could be more than one, and they need to be separate.
1979 class TestRun:
1980 ftemp = dict()
1981 ttemp = dict()
1982 data = 0
1983 def __init__(self, dataobj):
1984 self.data = dataobj
1985 self.ftemp = dict()
1986 self.ttemp = dict()
1987
1988 class ProcessMonitor:
1989 proclist = dict()
1990 running = False
1991 def procstat(self):
1992 c = ['cat /proc/[1-9]*/stat 2>/dev/null']
1993 process = Popen(c, shell=True, stdout=PIPE)
1994 running = dict()
1995 for line in process.stdout:
1996 data = line.split()
1997 pid = data[0]
1998 name = re.sub('[()]', '', data[1])
1999 user = int(data[13])
2000 kern = int(data[14])
2001 kjiff = ujiff = 0
2002 if pid not in self.proclist:
2003 self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
2004 else:
2005 val = self.proclist[pid]
2006 ujiff = user - val['user']
2007 kjiff = kern - val['kern']
2008 val['user'] = user
2009 val['kern'] = kern
2010 if ujiff > 0 or kjiff > 0:
2011 running[pid] = ujiff + kjiff
2012 result = process.wait()
2013 out = ''
2014 for pid in running:
2015 jiffies = running[pid]
2016 val = self.proclist[pid]
2017 if out:
2018 out += ','
2019 out += '%s-%s %d' % (val['name'], pid, jiffies)
2020 return 'ps - '+out
2021 def processMonitor(self, tid):
2022 while self.running:
2023 out = self.procstat()
2024 if out:
2025 sysvals.fsetVal(out, 'trace_marker')
2026 def start(self):
2027 self.thread = Thread(target=self.processMonitor, args=(0,))
2028 self.running = True
2029 self.thread.start()
2030 def stop(self):
2031 self.running = False
2032
2033 # ----------------- FUNCTIONS --------------------
2034
2035 # Function: vprint
2036 # Description:
2037 # verbose print (prints only with -verbose option)
2038 # Arguments:
2039 # msg: the debug/log message to print
2040 def vprint(msg):
2041 sysvals.logmsg += msg+'\n'
2042 if(sysvals.verbose):
2043 print(msg)
2044
2045 # Function: parseStamp
2046 # Description:
2047 # Pull in the stamp comment line from the data file(s),
2048 # create the stamp, and add it to the global sysvals object
2049 # Arguments:
2050 # m: the valid re.match output for the stamp line
2051 def parseStamp(line, data):
2052 m = re.match(sysvals.stampfmt, line)
2053 data.stamp = {'time': '', 'host': '', 'mode': ''}
2054 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2055 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2056 int(m.group('S')))
2057 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2058 data.stamp['host'] = m.group('host')
2059 data.stamp['mode'] = m.group('mode')
2060 data.stamp['kernel'] = m.group('kernel')
2061 sysvals.hostname = data.stamp['host']
2062 sysvals.suspendmode = data.stamp['mode']
2063 if sysvals.suspendmode == 'command' and sysvals.ftracefile != '':
2064 modes = ['on', 'freeze', 'standby', 'mem']
2065 out = Popen(['grep', 'suspend_enter', sysvals.ftracefile],
2066 stderr=PIPE, stdout=PIPE).stdout.read()
2067 m = re.match('.* suspend_enter\[(?P<mode>.*)\]', out)
2068 if m and m.group('mode') in ['1', '2', '3']:
2069 sysvals.suspendmode = modes[int(m.group('mode'))]
2070 data.stamp['mode'] = sysvals.suspendmode
2071 if not sysvals.stamp:
2072 sysvals.stamp = data.stamp
2073
2074 # Function: diffStamp
2075 # Description:
2076 # compare the host, kernel, and mode fields in 3 stamps
2077 # Arguments:
2078 # stamp1: string array with mode, kernel, and host
2079 # stamp2: string array with mode, kernel, and host
2080 # Return:
2081 # True if stamps differ, False if they're the same
2082 def diffStamp(stamp1, stamp2):
2083 if 'host' in stamp1 and 'host' in stamp2:
2084 if stamp1['host'] != stamp2['host']:
2085 return True
2086 if 'kernel' in stamp1 and 'kernel' in stamp2:
2087 if stamp1['kernel'] != stamp2['kernel']:
2088 return True
2089 if 'mode' in stamp1 and 'mode' in stamp2:
2090 if stamp1['mode'] != stamp2['mode']:
2091 return True
2092 return False
2093
2094 # Function: doesTraceLogHaveTraceEvents
2095 # Description:
2096 # Quickly determine if the ftrace log has some or all of the trace events
2097 # required for primary parsing. Set the usetraceevents and/or
2098 # usetraceeventsonly flags in the global sysvals object
2099 def doesTraceLogHaveTraceEvents():
2100 # check for kprobes
2101 sysvals.usekprobes = False
2102 out = call('grep -q "_cal: (" '+sysvals.ftracefile, shell=True)
2103 if(out == 0):
2104 sysvals.usekprobes = True
2105 # check for callgraph data on trace event blocks
2106 out = call('grep -q "_cpu_down()" '+sysvals.ftracefile, shell=True)
2107 if(out == 0):
2108 sysvals.usekprobes = True
2109 out = Popen(['head', '-1', sysvals.ftracefile],
2110 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
2111 m = re.match(sysvals.stampfmt, out)
2112 if m and m.group('mode') == 'command':
2113 sysvals.usetraceeventsonly = True
2114 sysvals.usetraceevents = True
2115 return
2116 # figure out what level of trace events are supported
2117 sysvals.usetraceeventsonly = True
2118 sysvals.usetraceevents = False
2119 for e in sysvals.traceevents:
2120 out = call('grep -q "'+e+': " '+sysvals.ftracefile, shell=True)
2121 if(out != 0):
2122 sysvals.usetraceeventsonly = False
2123 if(e == 'suspend_resume' and out == 0):
2124 sysvals.usetraceevents = True
2125 # determine is this log is properly formatted
2126 for e in ['SUSPEND START', 'RESUME COMPLETE']:
2127 out = call('grep -q "'+e+'" '+sysvals.ftracefile, shell=True)
2128 if(out != 0):
2129 sysvals.usetracemarkers = False
2130
2131 # Function: appendIncompleteTraceLog
2132 # Description:
2133 # [deprecated for kernel 3.15 or newer]
2134 # Legacy support of ftrace outputs that lack the device_pm_callback
2135 # and/or suspend_resume trace events. The primary data should be
2136 # taken from dmesg, and this ftrace is used only for callgraph data
2137 # or custom actions in the timeline. The data is appended to the Data
2138 # objects provided.
2139 # Arguments:
2140 # testruns: the array of Data objects obtained from parseKernelLog
2141 def appendIncompleteTraceLog(testruns):
2142 # create TestRun vessels for ftrace parsing
2143 testcnt = len(testruns)
2144 testidx = 0
2145 testrun = []
2146 for data in testruns:
2147 testrun.append(TestRun(data))
2148
2149 # extract the callgraph and traceevent data
2150 vprint('Analyzing the ftrace data...')
2151 tp = TestProps()
2152 tf = open(sysvals.ftracefile, 'r')
2153 data = 0
2154 for line in tf:
2155 # remove any latent carriage returns
2156 line = line.replace('\r\n', '')
2157 # grab the time stamp
2158 m = re.match(sysvals.stampfmt, line)
2159 if(m):
2160 tp.stamp = line
2161 continue
2162 # determine the trace data type (required for further parsing)
2163 m = re.match(sysvals.tracertypefmt, line)
2164 if(m):
2165 tp.setTracerType(m.group('t'))
2166 continue
2167 # device properties line
2168 if(re.match(sysvals.devpropfmt, line)):
2169 devProps(line)
2170 continue
2171 # parse only valid lines, if this is not one move on
2172 m = re.match(tp.ftrace_line_fmt, line)
2173 if(not m):
2174 continue
2175 # gather the basic message data from the line
2176 m_time = m.group('time')
2177 m_pid = m.group('pid')
2178 m_msg = m.group('msg')
2179 if(tp.cgformat):
2180 m_param3 = m.group('dur')
2181 else:
2182 m_param3 = 'traceevent'
2183 if(m_time and m_pid and m_msg):
2184 t = FTraceLine(m_time, m_msg, m_param3)
2185 pid = int(m_pid)
2186 else:
2187 continue
2188 # the line should be a call, return, or event
2189 if(not t.fcall and not t.freturn and not t.fevent):
2190 continue
2191 # look for the suspend start marker
2192 if(t.startMarker()):
2193 data = testrun[testidx].data
2194 parseStamp(tp.stamp, data)
2195 data.setStart(t.time)
2196 continue
2197 if(not data):
2198 continue
2199 # find the end of resume
2200 if(t.endMarker()):
2201 data.setEnd(t.time)
2202 testidx += 1
2203 if(testidx >= testcnt):
2204 break
2205 continue
2206 # trace event processing
2207 if(t.fevent):
2208 # general trace events have two types, begin and end
2209 if(re.match('(?P<name>.*) begin$', t.name)):
2210 isbegin = True
2211 elif(re.match('(?P<name>.*) end$', t.name)):
2212 isbegin = False
2213 else:
2214 continue
2215 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2216 if(m):
2217 val = m.group('val')
2218 if val == '0':
2219 name = m.group('name')
2220 else:
2221 name = m.group('name')+'['+val+']'
2222 else:
2223 m = re.match('(?P<name>.*) .*', t.name)
2224 name = m.group('name')
2225 # special processing for trace events
2226 if re.match('dpm_prepare\[.*', name):
2227 continue
2228 elif re.match('machine_suspend.*', name):
2229 continue
2230 elif re.match('suspend_enter\[.*', name):
2231 if(not isbegin):
2232 data.dmesg['suspend_prepare']['end'] = t.time
2233 continue
2234 elif re.match('dpm_suspend\[.*', name):
2235 if(not isbegin):
2236 data.dmesg['suspend']['end'] = t.time
2237 continue
2238 elif re.match('dpm_suspend_late\[.*', name):
2239 if(isbegin):
2240 data.dmesg['suspend_late']['start'] = t.time
2241 else:
2242 data.dmesg['suspend_late']['end'] = t.time
2243 continue
2244 elif re.match('dpm_suspend_noirq\[.*', name):
2245 if(isbegin):
2246 data.dmesg['suspend_noirq']['start'] = t.time
2247 else:
2248 data.dmesg['suspend_noirq']['end'] = t.time
2249 continue
2250 elif re.match('dpm_resume_noirq\[.*', name):
2251 if(isbegin):
2252 data.dmesg['resume_machine']['end'] = t.time
2253 data.dmesg['resume_noirq']['start'] = t.time
2254 else:
2255 data.dmesg['resume_noirq']['end'] = t.time
2256 continue
2257 elif re.match('dpm_resume_early\[.*', name):
2258 if(isbegin):
2259 data.dmesg['resume_early']['start'] = t.time
2260 else:
2261 data.dmesg['resume_early']['end'] = t.time
2262 continue
2263 elif re.match('dpm_resume\[.*', name):
2264 if(isbegin):
2265 data.dmesg['resume']['start'] = t.time
2266 else:
2267 data.dmesg['resume']['end'] = t.time
2268 continue
2269 elif re.match('dpm_complete\[.*', name):
2270 if(isbegin):
2271 data.dmesg['resume_complete']['start'] = t.time
2272 else:
2273 data.dmesg['resume_complete']['end'] = t.time
2274 continue
2275 # skip trace events inside devices calls
2276 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2277 continue
2278 # global events (outside device calls) are simply graphed
2279 if(isbegin):
2280 # store each trace event in ttemp
2281 if(name not in testrun[testidx].ttemp):
2282 testrun[testidx].ttemp[name] = []
2283 testrun[testidx].ttemp[name].append(\
2284 {'begin': t.time, 'end': t.time})
2285 else:
2286 # finish off matching trace event in ttemp
2287 if(name in testrun[testidx].ttemp):
2288 testrun[testidx].ttemp[name][-1]['end'] = t.time
2289 # call/return processing
2290 elif sysvals.usecallgraph:
2291 # create a callgraph object for the data
2292 if(pid not in testrun[testidx].ftemp):
2293 testrun[testidx].ftemp[pid] = []
2294 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid))
2295 # when the call is finished, see which device matches it
2296 cg = testrun[testidx].ftemp[pid][-1]
2297 if(cg.addLine(t)):
2298 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid))
2299 tf.close()
2300
2301 for test in testrun:
2302 # add the traceevent data to the device hierarchy
2303 if(sysvals.usetraceevents):
2304 for name in test.ttemp:
2305 for event in test.ttemp[name]:
2306 test.data.newActionGlobal(name, event['begin'], event['end'])
2307
2308 # add the callgraph data to the device hierarchy
2309 for pid in test.ftemp:
2310 for cg in test.ftemp[pid]:
2311 if len(cg.list) < 1 or cg.invalid:
2312 continue
2313 if(not cg.postProcess()):
2314 id = 'task %s cpu %s' % (pid, m.group('cpu'))
2315 vprint('Sanity check failed for '+\
2316 id+', ignoring this callback')
2317 continue
2318 callstart = cg.start
2319 callend = cg.end
2320 for p in test.data.phases:
2321 if(test.data.dmesg[p]['start'] <= callstart and
2322 callstart <= test.data.dmesg[p]['end']):
2323 list = test.data.dmesg[p]['list']
2324 for devname in list:
2325 dev = list[devname]
2326 if(pid == dev['pid'] and
2327 callstart <= dev['start'] and
2328 callend >= dev['end']):
2329 dev['ftrace'] = cg
2330 break
2331
2332 test.data.printDetails()
2333
2334 # Function: parseTraceLog
2335 # Description:
2336 # Analyze an ftrace log output file generated from this app during
2337 # the execution phase. Used when the ftrace log is the primary data source
2338 # and includes the suspend_resume and device_pm_callback trace events
2339 # The ftrace filename is taken from sysvals
2340 # Output:
2341 # An array of Data objects
2342 def parseTraceLog():
2343 vprint('Analyzing the ftrace data...')
2344 if(os.path.exists(sysvals.ftracefile) == False):
2345 doError('%s does not exist' % sysvals.ftracefile)
2346
2347 sysvals.setupAllKprobes()
2348 tracewatch = []
2349 if sysvals.usekprobes:
2350 tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
2351 'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', 'CPU_OFF']
2352
2353 # extract the callgraph and traceevent data
2354 tp = TestProps()
2355 testruns = []
2356 testdata = []
2357 testrun = 0
2358 data = 0
2359 tf = open(sysvals.ftracefile, 'r')
2360 phase = 'suspend_prepare'
2361 for line in tf:
2362 # remove any latent carriage returns
2363 line = line.replace('\r\n', '')
2364 # stamp line: each stamp means a new test run
2365 m = re.match(sysvals.stampfmt, line)
2366 if(m):
2367 tp.stamp = line
2368 continue
2369 # firmware line: pull out any firmware data
2370 m = re.match(sysvals.firmwarefmt, line)
2371 if(m):
2372 tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
2373 continue
2374 # tracer type line: determine the trace data type
2375 m = re.match(sysvals.tracertypefmt, line)
2376 if(m):
2377 tp.setTracerType(m.group('t'))
2378 continue
2379 # device properties line
2380 if(re.match(sysvals.devpropfmt, line)):
2381 devProps(line)
2382 continue
2383 # ignore all other commented lines
2384 if line[0] == '#':
2385 continue
2386 # ftrace line: parse only valid lines
2387 m = re.match(tp.ftrace_line_fmt, line)
2388 if(not m):
2389 continue
2390 # gather the basic message data from the line
2391 m_time = m.group('time')
2392 m_proc = m.group('proc')
2393 m_pid = m.group('pid')
2394 m_msg = m.group('msg')
2395 if(tp.cgformat):
2396 m_param3 = m.group('dur')
2397 else:
2398 m_param3 = 'traceevent'
2399 if(m_time and m_pid and m_msg):
2400 t = FTraceLine(m_time, m_msg, m_param3)
2401 pid = int(m_pid)
2402 else:
2403 continue
2404 # the line should be a call, return, or event
2405 if(not t.fcall and not t.freturn and not t.fevent):
2406 continue
2407 # find the start of suspend
2408 if(t.startMarker()):
2409 phase = 'suspend_prepare'
2410 data = Data(len(testdata))
2411 testdata.append(data)
2412 testrun = TestRun(data)
2413 testruns.append(testrun)
2414 parseStamp(tp.stamp, data)
2415 data.setStart(t.time)
2416 data.tKernSus = t.time
2417 continue
2418 if(not data):
2419 continue
2420 # process cpu exec line
2421 if t.type == 'tracing_mark_write':
2422 m = re.match(sysvals.procexecfmt, t.name)
2423 if(m):
2424 proclist = dict()
2425 for ps in m.group('ps').split(','):
2426 val = ps.split()
2427 if not val:
2428 continue
2429 name = val[0].replace('--', '-')
2430 proclist[name] = int(val[1])
2431 data.pstl[t.time] = proclist
2432 continue
2433 # find the end of resume
2434 if(t.endMarker()):
2435 data.setEnd(t.time)
2436 if data.tKernRes == 0.0:
2437 data.tKernRes = t.time
2438 if data.dmesg['resume_complete']['end'] < 0:
2439 data.dmesg['resume_complete']['end'] = t.time
2440 if sysvals.suspendmode == 'mem' and len(tp.fwdata) > data.testnumber:
2441 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2442 if(data.tSuspended != 0 and data.tResumed != 0 and \
2443 (data.fwSuspend > 0 or data.fwResume > 0)):
2444 data.fwValid = True
2445 if(not sysvals.usetracemarkers):
2446 # no trace markers? then quit and be sure to finish recording
2447 # the event we used to trigger resume end
2448 if(len(testrun.ttemp['thaw_processes']) > 0):
2449 # if an entry exists, assume this is its end
2450 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
2451 break
2452 continue
2453 # trace event processing
2454 if(t.fevent):
2455 if(phase == 'post_resume'):
2456 data.setEnd(t.time)
2457 if(t.type == 'suspend_resume'):
2458 # suspend_resume trace events have two types, begin and end
2459 if(re.match('(?P<name>.*) begin$', t.name)):
2460 isbegin = True
2461 elif(re.match('(?P<name>.*) end$', t.name)):
2462 isbegin = False
2463 else:
2464 continue
2465 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2466 if(m):
2467 val = m.group('val')
2468 if val == '0':
2469 name = m.group('name')
2470 else:
2471 name = m.group('name')+'['+val+']'
2472 else:
2473 m = re.match('(?P<name>.*) .*', t.name)
2474 name = m.group('name')
2475 # ignore these events
2476 if(name.split('[')[0] in tracewatch):
2477 continue
2478 # -- phase changes --
2479 # start of kernel suspend
2480 if(re.match('suspend_enter\[.*', t.name)):
2481 if(isbegin):
2482 data.dmesg[phase]['start'] = t.time
2483 data.tKernSus = t.time
2484 continue
2485 # suspend_prepare start
2486 elif(re.match('dpm_prepare\[.*', t.name)):
2487 phase = 'suspend_prepare'
2488 if(not isbegin):
2489 data.dmesg[phase]['end'] = t.time
2490 continue
2491 # suspend start
2492 elif(re.match('dpm_suspend\[.*', t.name)):
2493 phase = 'suspend'
2494 data.setPhase(phase, t.time, isbegin)
2495 continue
2496 # suspend_late start
2497 elif(re.match('dpm_suspend_late\[.*', t.name)):
2498 phase = 'suspend_late'
2499 data.setPhase(phase, t.time, isbegin)
2500 continue
2501 # suspend_noirq start
2502 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
2503 phase = 'suspend_noirq'
2504 data.setPhase(phase, t.time, isbegin)
2505 if(not isbegin):
2506 phase = 'suspend_machine'
2507 data.dmesg[phase]['start'] = t.time
2508 continue
2509 # suspend_machine/resume_machine
2510 elif(re.match('machine_suspend\[.*', t.name)):
2511 if(isbegin):
2512 phase = 'suspend_machine'
2513 data.dmesg[phase]['end'] = t.time
2514 data.tSuspended = t.time
2515 else:
2516 if(sysvals.suspendmode in ['mem', 'disk'] and not tp.S0i3):
2517 data.dmesg['suspend_machine']['end'] = t.time
2518 data.tSuspended = t.time
2519 phase = 'resume_machine'
2520 data.dmesg[phase]['start'] = t.time
2521 data.tResumed = t.time
2522 data.tLow = data.tResumed - data.tSuspended
2523 continue
2524 # acpi_suspend
2525 elif(re.match('acpi_suspend\[.*', t.name)):
2526 # acpi_suspend[0] S0i3
2527 if(re.match('acpi_suspend\[0\] begin', t.name)):
2528 if(sysvals.suspendmode == 'mem'):
2529 tp.S0i3 = True
2530 data.dmesg['suspend_machine']['end'] = t.time
2531 data.tSuspended = t.time
2532 continue
2533 # resume_noirq start
2534 elif(re.match('dpm_resume_noirq\[.*', t.name)):
2535 phase = 'resume_noirq'
2536 data.setPhase(phase, t.time, isbegin)
2537 if(isbegin):
2538 data.dmesg['resume_machine']['end'] = t.time
2539 continue
2540 # resume_early start
2541 elif(re.match('dpm_resume_early\[.*', t.name)):
2542 phase = 'resume_early'
2543 data.setPhase(phase, t.time, isbegin)
2544 continue
2545 # resume start
2546 elif(re.match('dpm_resume\[.*', t.name)):
2547 phase = 'resume'
2548 data.setPhase(phase, t.time, isbegin)
2549 continue
2550 # resume complete start
2551 elif(re.match('dpm_complete\[.*', t.name)):
2552 phase = 'resume_complete'
2553 if(isbegin):
2554 data.dmesg[phase]['start'] = t.time
2555 continue
2556 # skip trace events inside devices calls
2557 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2558 continue
2559 # global events (outside device calls) are graphed
2560 if(name not in testrun.ttemp):
2561 testrun.ttemp[name] = []
2562 if(isbegin):
2563 # create a new list entry
2564 testrun.ttemp[name].append(\
2565 {'begin': t.time, 'end': t.time, 'pid': pid})
2566 else:
2567 if(len(testrun.ttemp[name]) > 0):
2568 # if an entry exists, assume this is its end
2569 testrun.ttemp[name][-1]['end'] = t.time
2570 elif(phase == 'post_resume'):
2571 # post resume events can just have ends
2572 testrun.ttemp[name].append({
2573 'begin': data.dmesg[phase]['start'],
2574 'end': t.time})
2575 # device callback start
2576 elif(t.type == 'device_pm_callback_start'):
2577 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
2578 t.name);
2579 if(not m):
2580 continue
2581 drv = m.group('drv')
2582 n = m.group('d')
2583 p = m.group('p')
2584 if(n and p):
2585 data.newAction(phase, n, pid, p, t.time, -1, drv)
2586 if pid not in data.devpids:
2587 data.devpids.append(pid)
2588 # device callback finish
2589 elif(t.type == 'device_pm_callback_end'):
2590 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
2591 if(not m):
2592 continue
2593 n = m.group('d')
2594 list = data.dmesg[phase]['list']
2595 if(n in list):
2596 dev = list[n]
2597 dev['length'] = t.time - dev['start']
2598 dev['end'] = t.time
2599 # kprobe event processing
2600 elif(t.fkprobe):
2601 kprobename = t.type
2602 kprobedata = t.name
2603 key = (kprobename, pid)
2604 # displayname is generated from kprobe data
2605 displayname = ''
2606 if(t.fcall):
2607 displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
2608 if not displayname:
2609 continue
2610 if(key not in tp.ktemp):
2611 tp.ktemp[key] = []
2612 tp.ktemp[key].append({
2613 'pid': pid,
2614 'begin': t.time,
2615 'end': t.time,
2616 'name': displayname,
2617 'cdata': kprobedata,
2618 'proc': m_proc,
2619 })
2620 elif(t.freturn):
2621 if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
2622 continue
2623 e = tp.ktemp[key][-1]
2624 if e['begin'] < 0.0 or t.time - e['begin'] < 0.000001:
2625 tp.ktemp[key].pop()
2626 else:
2627 e['end'] = t.time
2628 e['rdata'] = kprobedata
2629 # end of kernel resume
2630 if(kprobename == 'pm_notifier_call_chain' or \
2631 kprobename == 'pm_restore_console'):
2632 data.dmesg[phase]['end'] = t.time
2633 data.tKernRes = t.time
2634
2635 # callgraph processing
2636 elif sysvals.usecallgraph:
2637 # create a callgraph object for the data
2638 key = (m_proc, pid)
2639 if(key not in testrun.ftemp):
2640 testrun.ftemp[key] = []
2641 testrun.ftemp[key].append(FTraceCallGraph(pid))
2642 # when the call is finished, see which device matches it
2643 cg = testrun.ftemp[key][-1]
2644 if(cg.addLine(t)):
2645 testrun.ftemp[key].append(FTraceCallGraph(pid))
2646 tf.close()
2647
2648 if sysvals.suspendmode == 'command':
2649 for test in testruns:
2650 for p in test.data.phases:
2651 if p == 'suspend_prepare':
2652 test.data.dmesg[p]['start'] = test.data.start
2653 test.data.dmesg[p]['end'] = test.data.end
2654 else:
2655 test.data.dmesg[p]['start'] = test.data.end
2656 test.data.dmesg[p]['end'] = test.data.end
2657 test.data.tSuspended = test.data.end
2658 test.data.tResumed = test.data.end
2659 test.data.tLow = 0
2660 test.data.fwValid = False
2661
2662 # dev source and procmon events can be unreadable with mixed phase height
2663 if sysvals.usedevsrc or sysvals.useprocmon:
2664 sysvals.mixedphaseheight = False
2665
2666 for i in range(len(testruns)):
2667 test = testruns[i]
2668 data = test.data
2669 # find the total time range for this test (begin, end)
2670 tlb, tle = data.start, data.end
2671 if i < len(testruns) - 1:
2672 tle = testruns[i+1].data.start
2673 # add the process usage data to the timeline
2674 if sysvals.useprocmon:
2675 data.createProcessUsageEvents()
2676 # add the traceevent data to the device hierarchy
2677 if(sysvals.usetraceevents):
2678 # add actual trace funcs
2679 for name in test.ttemp:
2680 for event in test.ttemp[name]:
2681 data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
2682 # add the kprobe based virtual tracefuncs as actual devices
2683 for key in tp.ktemp:
2684 name, pid = key
2685 if name not in sysvals.tracefuncs:
2686 continue
2687 for e in tp.ktemp[key]:
2688 kb, ke = e['begin'], e['end']
2689 if kb == ke or tlb > kb or tle <= kb:
2690 continue
2691 color = sysvals.kprobeColor(name)
2692 data.newActionGlobal(e['name'], kb, ke, pid, color)
2693 # add config base kprobes and dev kprobes
2694 if sysvals.usedevsrc:
2695 for key in tp.ktemp:
2696 name, pid = key
2697 if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
2698 continue
2699 for e in tp.ktemp[key]:
2700 kb, ke = e['begin'], e['end']
2701 if kb == ke or tlb > kb or tle <= kb:
2702 continue
2703 data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
2704 ke, e['cdata'], e['rdata'])
2705 if sysvals.usecallgraph:
2706 # add the callgraph data to the device hierarchy
2707 sortlist = dict()
2708 for key in test.ftemp:
2709 proc, pid = key
2710 for cg in test.ftemp[key]:
2711 if len(cg.list) < 1 or cg.invalid:
2712 continue
2713 if(not cg.postProcess()):
2714 id = 'task %s' % (pid)
2715 vprint('Sanity check failed for '+\
2716 id+', ignoring this callback')
2717 continue
2718 # match cg data to devices
2719 if sysvals.suspendmode == 'command' or not cg.deviceMatch(pid, data):
2720 sortkey = '%f%f%d' % (cg.start, cg.end, pid)
2721 sortlist[sortkey] = cg
2722 # create blocks for orphan cg data
2723 for sortkey in sorted(sortlist):
2724 cg = sortlist[sortkey]
2725 name = cg.list[0].name
2726 if sysvals.isCallgraphFunc(name):
2727 vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
2728 cg.newActionFromFunction(data)
2729
2730 if sysvals.suspendmode == 'command':
2731 for data in testdata:
2732 data.printDetails()
2733 return testdata
2734
2735 # fill in any missing phases
2736 for data in testdata:
2737 lp = data.phases[0]
2738 for p in data.phases:
2739 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
2740 vprint('WARNING: phase "%s" is missing!' % p)
2741 if(data.dmesg[p]['start'] < 0):
2742 data.dmesg[p]['start'] = data.dmesg[lp]['end']
2743 if(p == 'resume_machine'):
2744 data.tSuspended = data.dmesg[lp]['end']
2745 data.tResumed = data.dmesg[lp]['end']
2746 data.tLow = 0
2747 if(data.dmesg[p]['end'] < 0):
2748 data.dmesg[p]['end'] = data.dmesg[p]['start']
2749 if(p != lp and not ('machine' in p and 'machine' in lp)):
2750 data.dmesg[lp]['end'] = data.dmesg[p]['start']
2751 lp = p
2752
2753 if(len(sysvals.devicefilter) > 0):
2754 data.deviceFilter(sysvals.devicefilter)
2755 data.fixupInitcallsThatDidntReturn()
2756 if sysvals.usedevsrc:
2757 data.optimizeDevSrc()
2758 data.printDetails()
2759
2760 # x2: merge any overlapping devices between test runs
2761 if sysvals.usedevsrc and len(testdata) > 1:
2762 tc = len(testdata)
2763 for i in range(tc - 1):
2764 devlist = testdata[i].overflowDevices()
2765 for j in range(i + 1, tc):
2766 testdata[j].mergeOverlapDevices(devlist)
2767 testdata[0].stitchTouchingThreads(testdata[1:])
2768 return testdata
2769
2770 # Function: loadKernelLog
2771 # Description:
2772 # [deprecated for kernel 3.15.0 or newer]
2773 # load the dmesg file into memory and fix up any ordering issues
2774 # The dmesg filename is taken from sysvals
2775 # Output:
2776 # An array of empty Data objects with only their dmesgtext attributes set
2777 def loadKernelLog(justtext=False):
2778 vprint('Analyzing the dmesg data...')
2779 if(os.path.exists(sysvals.dmesgfile) == False):
2780 doError('%s does not exist' % sysvals.dmesgfile)
2781
2782 if justtext:
2783 dmesgtext = []
2784 # there can be multiple test runs in a single file
2785 tp = TestProps()
2786 tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
2787 testruns = []
2788 data = 0
2789 lf = open(sysvals.dmesgfile, 'r')
2790 for line in lf:
2791 line = line.replace('\r\n', '')
2792 idx = line.find('[')
2793 if idx > 1:
2794 line = line[idx:]
2795 m = re.match(sysvals.stampfmt, line)
2796 if(m):
2797 tp.stamp = line
2798 continue
2799 m = re.match(sysvals.firmwarefmt, line)
2800 if(m):
2801 tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
2802 continue
2803 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
2804 if(not m):
2805 continue
2806 msg = m.group("msg")
2807 if justtext:
2808 dmesgtext.append(line)
2809 continue
2810 if(re.match('PM: Syncing filesystems.*', msg)):
2811 if(data):
2812 testruns.append(data)
2813 data = Data(len(testruns))
2814 parseStamp(tp.stamp, data)
2815 if len(tp.fwdata) > data.testnumber:
2816 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2817 if(data.fwSuspend > 0 or data.fwResume > 0):
2818 data.fwValid = True
2819 if(not data):
2820 continue
2821 m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
2822 if(m):
2823 sysvals.stamp['kernel'] = m.group('k')
2824 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
2825 if(m):
2826 sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
2827 data.dmesgtext.append(line)
2828 lf.close()
2829
2830 if justtext:
2831 return dmesgtext
2832 if data:
2833 testruns.append(data)
2834 if len(testruns) < 1:
2835 doError(' dmesg log has no suspend/resume data: %s' \
2836 % sysvals.dmesgfile)
2837
2838 # fix lines with same timestamp/function with the call and return swapped
2839 for data in testruns:
2840 last = ''
2841 for line in data.dmesgtext:
2842 mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
2843 '(?P<f>.*)\+ @ .*, parent: .*', line)
2844 mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
2845 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
2846 if(mc and mr and (mc.group('t') == mr.group('t')) and
2847 (mc.group('f') == mr.group('f'))):
2848 i = data.dmesgtext.index(last)
2849 j = data.dmesgtext.index(line)
2850 data.dmesgtext[i] = line
2851 data.dmesgtext[j] = last
2852 last = line
2853 return testruns
2854
2855 # Function: parseKernelLog
2856 # Description:
2857 # [deprecated for kernel 3.15.0 or newer]
2858 # Analyse a dmesg log output file generated from this app during
2859 # the execution phase. Create a set of device structures in memory
2860 # for subsequent formatting in the html output file
2861 # This call is only for legacy support on kernels where the ftrace
2862 # data lacks the suspend_resume or device_pm_callbacks trace events.
2863 # Arguments:
2864 # data: an empty Data object (with dmesgtext) obtained from loadKernelLog
2865 # Output:
2866 # The filled Data object
2867 def parseKernelLog(data):
2868 phase = 'suspend_runtime'
2869
2870 if(data.fwValid):
2871 vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
2872 (data.fwSuspend, data.fwResume))
2873
2874 # dmesg phase match table
2875 dm = {
2876 'suspend_prepare': 'PM: Syncing filesystems.*',
2877 'suspend': 'PM: Entering [a-z]* sleep.*',
2878 'suspend_late': 'PM: suspend of devices complete after.*',
2879 'suspend_noirq': 'PM: late suspend of devices complete after.*',
2880 'suspend_machine': 'PM: noirq suspend of devices complete after.*',
2881 'resume_machine': 'ACPI: Low-level resume complete.*',
2882 'resume_noirq': 'ACPI: Waking up from system sleep state.*',
2883 'resume_early': 'PM: noirq resume of devices complete after.*',
2884 'resume': 'PM: early resume of devices complete after.*',
2885 'resume_complete': 'PM: resume of devices complete after.*',
2886 'post_resume': '.*Restarting tasks \.\.\..*',
2887 }
2888 if(sysvals.suspendmode == 'standby'):
2889 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
2890 elif(sysvals.suspendmode == 'disk'):
2891 dm['suspend_late'] = 'PM: freeze of devices complete after.*'
2892 dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
2893 dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
2894 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
2895 dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
2896 dm['resume'] = 'PM: early restore of devices complete after.*'
2897 dm['resume_complete'] = 'PM: restore of devices complete after.*'
2898 elif(sysvals.suspendmode == 'freeze'):
2899 dm['resume_machine'] = 'ACPI: resume from mwait'
2900
2901 # action table (expected events that occur and show up in dmesg)
2902 at = {
2903 'sync_filesystems': {
2904 'smsg': 'PM: Syncing filesystems.*',
2905 'emsg': 'PM: Preparing system for mem sleep.*' },
2906 'freeze_user_processes': {
2907 'smsg': 'Freezing user space processes .*',
2908 'emsg': 'Freezing remaining freezable tasks.*' },
2909 'freeze_tasks': {
2910 'smsg': 'Freezing remaining freezable tasks.*',
2911 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
2912 'ACPI prepare': {
2913 'smsg': 'ACPI: Preparing to enter system sleep state.*',
2914 'emsg': 'PM: Saving platform NVS memory.*' },
2915 'PM vns': {
2916 'smsg': 'PM: Saving platform NVS memory.*',
2917 'emsg': 'Disabling non-boot CPUs .*' },
2918 }
2919
2920 t0 = -1.0
2921 cpu_start = -1.0
2922 prevktime = -1.0
2923 actions = dict()
2924 for line in data.dmesgtext:
2925 # parse each dmesg line into the time and message
2926 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
2927 if(m):
2928 val = m.group('ktime')
2929 try:
2930 ktime = float(val)
2931 except:
2932 continue
2933 msg = m.group('msg')
2934 # initialize data start to first line time
2935 if t0 < 0:
2936 data.setStart(ktime)
2937 t0 = ktime
2938 else:
2939 continue
2940
2941 # hack for determining resume_machine end for freeze
2942 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
2943 and phase == 'resume_machine' and \
2944 re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
2945 data.dmesg['resume_machine']['end'] = ktime
2946 phase = 'resume_noirq'
2947 data.dmesg[phase]['start'] = ktime
2948
2949 # suspend start
2950 if(re.match(dm['suspend_prepare'], msg)):
2951 phase = 'suspend_prepare'
2952 data.dmesg[phase]['start'] = ktime
2953 data.setStart(ktime)
2954 data.tKernSus = ktime
2955 # suspend start
2956 elif(re.match(dm['suspend'], msg)):
2957 data.dmesg['suspend_prepare']['end'] = ktime
2958 phase = 'suspend'
2959 data.dmesg[phase]['start'] = ktime
2960 # suspend_late start
2961 elif(re.match(dm['suspend_late'], msg)):
2962 data.dmesg['suspend']['end'] = ktime
2963 phase = 'suspend_late'
2964 data.dmesg[phase]['start'] = ktime
2965 # suspend_noirq start
2966 elif(re.match(dm['suspend_noirq'], msg)):
2967 data.dmesg['suspend_late']['end'] = ktime
2968 phase = 'suspend_noirq'
2969 data.dmesg[phase]['start'] = ktime
2970 # suspend_machine start
2971 elif(re.match(dm['suspend_machine'], msg)):
2972 data.dmesg['suspend_noirq']['end'] = ktime
2973 phase = 'suspend_machine'
2974 data.dmesg[phase]['start'] = ktime
2975 # resume_machine start
2976 elif(re.match(dm['resume_machine'], msg)):
2977 if(sysvals.suspendmode in ['freeze', 'standby']):
2978 data.tSuspended = prevktime
2979 data.dmesg['suspend_machine']['end'] = prevktime
2980 else:
2981 data.tSuspended = ktime
2982 data.dmesg['suspend_machine']['end'] = ktime
2983 phase = 'resume_machine'
2984 data.tResumed = ktime
2985 data.tLow = data.tResumed - data.tSuspended
2986 data.dmesg[phase]['start'] = ktime
2987 # resume_noirq start
2988 elif(re.match(dm['resume_noirq'], msg)):
2989 data.dmesg['resume_machine']['end'] = ktime
2990 phase = 'resume_noirq'
2991 data.dmesg[phase]['start'] = ktime
2992 # resume_early start
2993 elif(re.match(dm['resume_early'], msg)):
2994 data.dmesg['resume_noirq']['end'] = ktime
2995 phase = 'resume_early'
2996 data.dmesg[phase]['start'] = ktime
2997 # resume start
2998 elif(re.match(dm['resume'], msg)):
2999 data.dmesg['resume_early']['end'] = ktime
3000 phase = 'resume'
3001 data.dmesg[phase]['start'] = ktime
3002 # resume complete start
3003 elif(re.match(dm['resume_complete'], msg)):
3004 data.dmesg['resume']['end'] = ktime
3005 phase = 'resume_complete'
3006 data.dmesg[phase]['start'] = ktime
3007 # post resume start
3008 elif(re.match(dm['post_resume'], msg)):
3009 data.dmesg['resume_complete']['end'] = ktime
3010 data.setEnd(ktime)
3011 data.tKernRes = ktime
3012 break
3013
3014 # -- device callbacks --
3015 if(phase in data.phases):
3016 # device init call
3017 if(re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3018 sm = re.match('calling (?P<f>.*)\+ @ '+\
3019 '(?P<n>.*), parent: (?P<p>.*)', msg);
3020 f = sm.group('f')
3021 n = sm.group('n')
3022 p = sm.group('p')
3023 if(f and n and p):
3024 data.newAction(phase, f, int(n), p, ktime, -1, '')
3025 # device init return
3026 elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3027 '(?P<t>.*) usecs', msg)):
3028 sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3029 '(?P<t>.*) usecs(?P<a>.*)', msg);
3030 f = sm.group('f')
3031 t = sm.group('t')
3032 list = data.dmesg[phase]['list']
3033 if(f in list):
3034 dev = list[f]
3035 dev['length'] = int(t)
3036 dev['end'] = ktime
3037
3038 # if trace events are not available, these are better than nothing
3039 if(not sysvals.usetraceevents):
3040 # look for known actions
3041 for a in at:
3042 if(re.match(at[a]['smsg'], msg)):
3043 if(a not in actions):
3044 actions[a] = []
3045 actions[a].append({'begin': ktime, 'end': ktime})
3046 if(re.match(at[a]['emsg'], msg)):
3047 if(a in actions):
3048 actions[a][-1]['end'] = ktime
3049 # now look for CPU on/off events
3050 if(re.match('Disabling non-boot CPUs .*', msg)):
3051 # start of first cpu suspend
3052 cpu_start = ktime
3053 elif(re.match('Enabling non-boot CPUs .*', msg)):
3054 # start of first cpu resume
3055 cpu_start = ktime
3056 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3057 # end of a cpu suspend, start of the next
3058 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3059 cpu = 'CPU'+m.group('cpu')
3060 if(cpu not in actions):
3061 actions[cpu] = []
3062 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3063 cpu_start = ktime
3064 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3065 # end of a cpu resume, start of the next
3066 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3067 cpu = 'CPU'+m.group('cpu')
3068 if(cpu not in actions):
3069 actions[cpu] = []
3070 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3071 cpu_start = ktime
3072 prevktime = ktime
3073
3074 # fill in any missing phases
3075 lp = data.phases[0]
3076 for p in data.phases:
3077 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3078 print('WARNING: phase "%s" is missing, something went wrong!' % p)
3079 print(' In %s, this dmesg line denotes the start of %s:' % \
3080 (sysvals.suspendmode, p))
3081 print(' "%s"' % dm[p])
3082 if(data.dmesg[p]['start'] < 0):
3083 data.dmesg[p]['start'] = data.dmesg[lp]['end']
3084 if(p == 'resume_machine'):
3085 data.tSuspended = data.dmesg[lp]['end']
3086 data.tResumed = data.dmesg[lp]['end']
3087 data.tLow = 0
3088 if(data.dmesg[p]['end'] < 0):
3089 data.dmesg[p]['end'] = data.dmesg[p]['start']
3090 lp = p
3091
3092 # fill in any actions we've found
3093 for name in actions:
3094 for event in actions[name]:
3095 data.newActionGlobal(name, event['begin'], event['end'])
3096
3097 data.printDetails()
3098 if(len(sysvals.devicefilter) > 0):
3099 data.deviceFilter(sysvals.devicefilter)
3100 data.fixupInitcallsThatDidntReturn()
3101 return True
3102
3103 # Function: createHTMLSummarySimple
3104 # Description:
3105 # Create summary html file for a series of tests
3106 # Arguments:
3107 # testruns: array of Data objects from parseTraceLog
3108 def createHTMLSummarySimple(testruns, htmlfile):
3109 # print out the basic summary of all the tests
3110 hf = open(htmlfile, 'w')
3111
3112 # write the html header first (html head, css code, up to body start)
3113 html = '<!DOCTYPE html>\n<html>\n<head>\n\
3114 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3115 <title>AnalyzeSuspend Summary</title>\n\
3116 <style type=\'text/css\'>\n\
3117 body {overflow-y: scroll;}\n\
3118 .stamp {width: 100%;text-align:center;background-color:#495E09;line-height:30px;color:white;font: 25px Arial;}\n\
3119 table {width:100%;border-collapse: collapse;}\n\
3120 .summary {font: 22px Arial;border:1px solid;}\n\
3121 th {border: 1px solid black;background-color:#A7C942;color:white;}\n\
3122 td {text-align: center;}\n\
3123 tr.alt td {background-color:#EAF2D3;}\n\
3124 tr.avg td {background-color:#BDE34C;}\n\
3125 a:link {color: #90B521;}\n\
3126 a:visited {color: #495E09;}\n\
3127 a:hover {color: #B1DF28;}\n\
3128 a:active {color: #FFFFFF;}\n\
3129 </style>\n</head>\n<body>\n'
3130
3131 # group test header
3132 count = len(testruns)
3133 headline_stamp = '<div class="stamp">{0} {1} {2} {3} ({4} tests)</div>\n'
3134 html += headline_stamp.format(sysvals.stamp['host'],
3135 sysvals.stamp['kernel'], sysvals.stamp['mode'],
3136 sysvals.stamp['time'], count)
3137
3138 # check to see if all the tests have the same value
3139 stampcolumns = False
3140 for data in testruns:
3141 if diffStamp(sysvals.stamp, data.stamp):
3142 stampcolumns = True
3143 break
3144
3145 th = '\t<th>{0}</th>\n'
3146 td = '\t<td>{0}</td>\n'
3147 tdlink = '\t<td><a href="{0}">Click Here</a></td>\n'
3148
3149 # table header
3150 html += '<table class="summary">\n<tr>\n'
3151 html += th.format("Test #")
3152 if stampcolumns:
3153 html += th.format("Hostname")
3154 html += th.format("Kernel Version")
3155 html += th.format("Suspend Mode")
3156 html += th.format("Test Time")
3157 html += th.format("Suspend Time")
3158 html += th.format("Resume Time")
3159 html += th.format("Detail")
3160 html += '</tr>\n'
3161
3162 # test data, 1 row per test
3163 sTimeAvg = 0.0
3164 rTimeAvg = 0.0
3165 num = 1
3166 for data in testruns:
3167 # data.end is the end of post_resume
3168 resumeEnd = data.dmesg['resume_complete']['end']
3169 if num % 2 == 1:
3170 html += '<tr class="alt">\n'
3171 else:
3172 html += '<tr>\n'
3173
3174 # test num
3175 html += td.format("test %d" % num)
3176 num += 1
3177 if stampcolumns:
3178 # host name
3179 val = "unknown"
3180 if('host' in data.stamp):
3181 val = data.stamp['host']
3182 html += td.format(val)
3183 # host kernel
3184 val = "unknown"
3185 if('kernel' in data.stamp):
3186 val = data.stamp['kernel']
3187 html += td.format(val)
3188 # suspend mode
3189 val = "unknown"
3190 if('mode' in data.stamp):
3191 val = data.stamp['mode']
3192 html += td.format(val)
3193 # test time
3194 val = "unknown"
3195 if('time' in data.stamp):
3196 val = data.stamp['time']
3197 html += td.format(val)
3198 # suspend time
3199 sTime = (data.tSuspended - data.start)*1000
3200 sTimeAvg += sTime
3201 html += td.format("%3.3f ms" % sTime)
3202 # resume time
3203 rTime = (resumeEnd - data.tResumed)*1000
3204 rTimeAvg += rTime
3205 html += td.format("%3.3f ms" % rTime)
3206 # link to the output html
3207 html += tdlink.format(data.outfile)
3208
3209 html += '</tr>\n'
3210
3211 # last line: test average
3212 if(count > 0):
3213 sTimeAvg /= count
3214 rTimeAvg /= count
3215 html += '<tr class="avg">\n'
3216 html += td.format('Average') # name
3217 if stampcolumns:
3218 html += td.format('') # host
3219 html += td.format('') # kernel
3220 html += td.format('') # mode
3221 html += td.format('') # time
3222 html += td.format("%3.3f ms" % sTimeAvg) # suspend time
3223 html += td.format("%3.3f ms" % rTimeAvg) # resume time
3224 html += td.format('') # output link
3225 html += '</tr>\n'
3226
3227 # flush the data to file
3228 hf.write(html+'</table>\n')
3229 hf.write('</body>\n</html>\n')
3230 hf.close()
3231
3232 def htmlTitle():
3233 modename = {
3234 'freeze': 'Freeze (S0)',
3235 'standby': 'Standby (S1)',
3236 'mem': 'Suspend (S3)',
3237 'disk': 'Hibernate (S4)'
3238 }
3239 kernel = sysvals.stamp['kernel']
3240 host = sysvals.hostname[0].upper()+sysvals.hostname[1:]
3241 mode = sysvals.suspendmode
3242 if sysvals.suspendmode in modename:
3243 mode = modename[sysvals.suspendmode]
3244 return host+' '+mode+' '+kernel
3245
3246 def ordinal(value):
3247 suffix = 'th'
3248 if value < 10 or value > 19:
3249 if value % 10 == 1:
3250 suffix = 'st'
3251 elif value % 10 == 2:
3252 suffix = 'nd'
3253 elif value % 10 == 3:
3254 suffix = 'rd'
3255 return '%d%s' % (value, suffix)
3256
3257 # Function: createHTML
3258 # Description:
3259 # Create the output html file from the resident test data
3260 # Arguments:
3261 # testruns: array of Data objects from parseKernelLog or parseTraceLog
3262 # Output:
3263 # True if the html file was created, false if it failed
3264 def createHTML(testruns):
3265 if len(testruns) < 1:
3266 print('ERROR: Not enough test data to build a timeline')
3267 return
3268
3269 kerror = False
3270 for data in testruns:
3271 if data.kerror:
3272 kerror = True
3273 data.normalizeTime(testruns[-1].tSuspended)
3274
3275 x2changes = ['', 'absolute']
3276 if len(testruns) > 1:
3277 x2changes = ['1', 'relative']
3278 # html function templates
3279 headline_version = '<div class="version"><a href="https://01.org/suspendresume">AnalyzeSuspend v%s</a></div>' % sysvals.version
3280 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
3281 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail%s</button>' % x2changes[0]
3282 html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
3283 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
3284 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
3285 html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
3286 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'
3287 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">ERROR&rarr;</div>\n'
3288 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'
3289 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
3290 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background-color:{4}">{5}</div>\n'
3291 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
3292 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background-color:{1}">&nbsp;{2}</div>\n'
3293 html_timetotal = '<table class="time1">\n<tr>'\
3294 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
3295 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
3296 '</tr>\n</table>\n'
3297 html_timetotal2 = '<table class="time1">\n<tr>'\
3298 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
3299 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
3300 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
3301 '</tr>\n</table>\n'
3302 html_timetotal3 = '<table class="time1">\n<tr>'\
3303 '<td class="green">Execution Time: <b>{0} ms</b></td>'\
3304 '<td class="yellow">Command: <b>{1}</b></td>'\
3305 '</tr>\n</table>\n'
3306 html_timegroups = '<table class="time2">\n<tr>'\
3307 '<td class="green" title="time from kernel enter_state({5}) to firmware mode [kernel time only]">{4}Kernel Suspend: {0} ms</td>'\
3308 '<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
3309 '<td class="purple">{4}Firmware Resume: {2} ms</td>'\
3310 '<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
3311 '</tr>\n</table>\n'
3312
3313 # html format variables
3314 hoverZ = 'z-index:8;'
3315 if sysvals.usedevsrc:
3316 hoverZ = ''
3317 scaleH = 20
3318 scaleTH = 20
3319 if kerror:
3320 scaleH = 40
3321 scaleTH = 60
3322
3323 # device timeline
3324 vprint('Creating Device Timeline...')
3325
3326 devtl = Timeline(30, scaleH)
3327
3328 # Generate the header for this timeline
3329 for data in testruns:
3330 tTotal = data.end - data.start
3331 sktime = (data.dmesg['suspend_machine']['end'] - \
3332 data.tKernSus) * 1000
3333 rktime = (data.dmesg['resume_complete']['end'] - \
3334 data.dmesg['resume_machine']['start']) * 1000
3335 if(tTotal == 0):
3336 print('ERROR: No timeline data')
3337 sys.exit()
3338 if(data.tLow > 0):
3339 low_time = '%.0f'%(data.tLow*1000)
3340 if sysvals.suspendmode == 'command':
3341 run_time = '%.0f'%((data.end-data.start)*1000)
3342 if sysvals.testcommand:
3343 testdesc = sysvals.testcommand
3344 else:
3345 testdesc = 'unknown'
3346 if(len(testruns) > 1):
3347 testdesc = ordinal(data.testnumber+1)+' '+testdesc
3348 thtml = html_timetotal3.format(run_time, testdesc)
3349 devtl.html['header'] += thtml
3350 elif data.fwValid:
3351 suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0))
3352 resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0))
3353 testdesc1 = 'Total'
3354 testdesc2 = ''
3355 stitle = 'time from kernel enter_state(%s) to low-power mode [kernel & firmware time]' % sysvals.suspendmode
3356 rtitle = 'time from low-power mode to return from kernel enter_state(%s) [firmware & kernel time]' % sysvals.suspendmode
3357 if(len(testruns) > 1):
3358 testdesc1 = testdesc2 = ordinal(data.testnumber+1)
3359 testdesc2 += ' '
3360 if(data.tLow == 0):
3361 thtml = html_timetotal.format(suspend_time, \
3362 resume_time, testdesc1, stitle, rtitle)
3363 else:
3364 thtml = html_timetotal2.format(suspend_time, low_time, \
3365 resume_time, testdesc1, stitle, rtitle)
3366 devtl.html['header'] += thtml
3367 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
3368 rftime = '%.3f'%(data.fwResume / 1000000.0)
3369 devtl.html['header'] += html_timegroups.format('%.3f'%sktime, \
3370 sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode)
3371 else:
3372 suspend_time = '%.3f' % sktime
3373 resume_time = '%.3f' % rktime
3374 testdesc = 'Kernel'
3375 stitle = 'time from kernel enter_state(%s) to firmware mode [kernel time only]' % sysvals.suspendmode
3376 rtitle = 'time from firmware mode to return from kernel enter_state(%s) [kernel time only]' % sysvals.suspendmode
3377 if(len(testruns) > 1):
3378 testdesc = ordinal(data.testnumber+1)+' '+testdesc
3379 if(data.tLow == 0):
3380 thtml = html_timetotal.format(suspend_time, \
3381 resume_time, testdesc, stitle, rtitle)
3382 else:
3383 thtml = html_timetotal2.format(suspend_time, low_time, \
3384 resume_time, testdesc, stitle, rtitle)
3385 devtl.html['header'] += thtml
3386
3387 # time scale for potentially multiple datasets
3388 t0 = testruns[0].start
3389 tMax = testruns[-1].end
3390 tTotal = tMax - t0
3391
3392 # determine the maximum number of rows we need to draw
3393 fulllist = []
3394 threadlist = []
3395 pscnt = 0
3396 devcnt = 0
3397 for data in testruns:
3398 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
3399 for group in data.devicegroups:
3400 devlist = []
3401 for phase in group:
3402 for devname in data.tdevlist[phase]:
3403 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
3404 devlist.append(d)
3405 if d.isa('kth'):
3406 threadlist.append(d)
3407 else:
3408 if d.isa('ps'):
3409 pscnt += 1
3410 else:
3411 devcnt += 1
3412 fulllist.append(d)
3413 if sysvals.mixedphaseheight:
3414 devtl.getPhaseRows(devlist)
3415 if not sysvals.mixedphaseheight:
3416 if len(threadlist) > 0 and len(fulllist) > 0:
3417 if pscnt > 0 and devcnt > 0:
3418 msg = 'user processes & device pm callbacks'
3419 elif pscnt > 0:
3420 msg = 'user processes'
3421 else:
3422 msg = 'device pm callbacks'
3423 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
3424 fulllist.insert(0, d)
3425 devtl.getPhaseRows(fulllist)
3426 if len(threadlist) > 0:
3427 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
3428 threadlist.insert(0, d)
3429 devtl.getPhaseRows(threadlist, devtl.rows)
3430 devtl.calcTotalRows()
3431
3432 # create bounding box, add buttons
3433 if sysvals.suspendmode != 'command':
3434 devtl.html['timeline'] += html_devlist1
3435 if len(testruns) > 1:
3436 devtl.html['timeline'] += html_devlist2
3437 devtl.html['timeline'] += html_zoombox
3438 devtl.html['timeline'] += html_timeline.format('dmesg', devtl.height)
3439
3440 # draw the full timeline
3441 phases = {'suspend':[],'resume':[]}
3442 for phase in data.dmesg:
3443 if 'resume' in phase:
3444 phases['resume'].append(phase)
3445 else:
3446 phases['suspend'].append(phase)
3447
3448 # draw each test run chronologically
3449 for data in testruns:
3450 # now draw the actual timeline blocks
3451 for dir in phases:
3452 # draw suspend and resume blocks separately
3453 bname = '%s%d' % (dir[0], data.testnumber)
3454 if dir == 'suspend':
3455 m0 = testruns[data.testnumber].start
3456 mMax = testruns[data.testnumber].tSuspended
3457 mTotal = mMax - m0
3458 left = '%f' % (((m0-t0)*100.0)/tTotal)
3459 else:
3460 m0 = testruns[data.testnumber].tSuspended
3461 mMax = testruns[data.testnumber].end
3462 # in an x2 run, remove any gap between blocks
3463 if len(testruns) > 1 and data.testnumber == 0:
3464 mMax = testruns[1].start
3465 mTotal = mMax - m0
3466 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
3467 # if a timeline block is 0 length, skip altogether
3468 if mTotal == 0:
3469 continue
3470 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
3471 devtl.html['timeline'] += html_tblock.format(bname, left, width, devtl.scaleH)
3472 for b in sorted(phases[dir]):
3473 # draw the phase color background
3474 phase = data.dmesg[b]
3475 length = phase['end']-phase['start']
3476 left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
3477 width = '%f' % ((length*100.0)/mTotal)
3478 devtl.html['timeline'] += html_phase.format(left, width, \
3479 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
3480 data.dmesg[b]['color'], '')
3481 for e in data.errorinfo[dir]:
3482 # draw red lines for any kernel errors found
3483 t, err = e
3484 right = '%f' % (((mMax-t)*100.0)/mTotal)
3485 devtl.html['timeline'] += html_error.format(right, err)
3486 for b in sorted(phases[dir]):
3487 # draw the devices for this phase
3488 phaselist = data.dmesg[b]['list']
3489 for d in data.tdevlist[b]:
3490 name = d
3491 drv = ''
3492 dev = phaselist[d]
3493 xtraclass = ''
3494 xtrainfo = ''
3495 xtrastyle = ''
3496 if 'htmlclass' in dev:
3497 xtraclass = dev['htmlclass']
3498 if 'color' in dev:
3499 xtrastyle = 'background-color:%s;' % dev['color']
3500 if(d in sysvals.devprops):
3501 name = sysvals.devprops[d].altName(d)
3502 xtraclass = sysvals.devprops[d].xtraClass()
3503 xtrainfo = sysvals.devprops[d].xtraInfo()
3504 elif xtraclass == ' kth':
3505 xtrainfo = ' kernel_thread'
3506 if('drv' in dev and dev['drv']):
3507 drv = ' {%s}' % dev['drv']
3508 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
3509 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
3510 top = '%.3f' % (rowtop + devtl.scaleH)
3511 left = '%f' % (((dev['start']-m0)*100)/mTotal)
3512 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
3513 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
3514 title = name+drv+xtrainfo+length
3515 if sysvals.suspendmode == 'command':
3516 title += sysvals.testcommand
3517 elif xtraclass == ' ps':
3518 if 'suspend' in b:
3519 title += 'pre_suspend_process'
3520 else:
3521 title += 'post_resume_process'
3522 else:
3523 title += b
3524 devtl.html['timeline'] += html_device.format(dev['id'], \
3525 title, left, top, '%.3f'%rowheight, width, \
3526 d+drv, xtraclass, xtrastyle)
3527 if('cpuexec' in dev):
3528 for t in sorted(dev['cpuexec']):
3529 start, end = t
3530 j = float(dev['cpuexec'][t]) / 5
3531 if j > 1.0:
3532 j = 1.0
3533 height = '%.3f' % (rowheight/3)
3534 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
3535 left = '%f' % (((start-m0)*100)/mTotal)
3536 width = '%f' % ((end-start)*100/mTotal)
3537 color = 'rgba(255, 0, 0, %f)' % j
3538 devtl.html['timeline'] += \
3539 html_cpuexec.format(left, top, height, width, color)
3540 if('src' not in dev):
3541 continue
3542 # draw any trace events for this device
3543 for e in dev['src']:
3544 height = '%.3f' % devtl.rowH
3545 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
3546 left = '%f' % (((e.time-m0)*100)/mTotal)
3547 width = '%f' % (e.length*100/mTotal)
3548 xtrastyle = ''
3549 if e.color:
3550 xtrastyle = 'background:%s;' % e.color
3551 devtl.html['timeline'] += \
3552 html_traceevent.format(e.title(), \
3553 left, top, height, width, e.text(), '', xtrastyle)
3554 # draw the time scale, try to make the number of labels readable
3555 devtl.html['timeline'] += devtl.createTimeScale(m0, mMax, tTotal, dir)
3556 devtl.html['timeline'] += '</div>\n'
3557
3558 # timeline is finished
3559 devtl.html['timeline'] += '</div>\n</div>\n'
3560
3561 # draw a legend which describes the phases by color
3562 if sysvals.suspendmode != 'command':
3563 data = testruns[-1]
3564 devtl.html['legend'] = '<div class="legend">\n'
3565 pdelta = 100.0/len(data.phases)
3566 pmargin = pdelta / 4.0
3567 for phase in data.phases:
3568 tmp = phase.split('_')
3569 id = tmp[0][0]
3570 if(len(tmp) > 1):
3571 id += tmp[1][0]
3572 order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3573 name = string.replace(phase, '_', ' &nbsp;')
3574 devtl.html['legend'] += html_legend.format(order, \
3575 data.dmesg[phase]['color'], name, id)
3576 devtl.html['legend'] += '</div>\n'
3577
3578 hf = open(sysvals.htmlfile, 'w')
3579
3580 if not sysvals.cgexp:
3581 cgchk = 'checked'
3582 cgnchk = 'not(:checked)'
3583 else:
3584 cgchk = 'not(:checked)'
3585 cgnchk = 'checked'
3586
3587 # write the html header first (html head, css code, up to body start)
3588 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
3589 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3590 <title>'+htmlTitle()+'</title>\n\
3591 <style type=\'text/css\'>\n\
3592 body {overflow-y:scroll;}\n\
3593 .stamp {width:100%;text-align:center;background-color:gray;line-height:30px;color:white;font:25px Arial;}\n\
3594 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
3595 .callgraph article * {padding-left:28px;}\n\
3596 h1 {color:black;font:bold 30px Times;}\n\
3597 t0 {color:black;font:bold 30px Times;}\n\
3598 t1 {color:black;font:30px Times;}\n\
3599 t2 {color:black;font:25px Times;}\n\
3600 t3 {color:black;font:20px Times;white-space:nowrap;}\n\
3601 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
3602 cS {font:bold 13px Times;}\n\
3603 table {width:100%;}\n\
3604 .gray {background-color:rgba(80,80,80,0.1);}\n\
3605 .green {background-color:rgba(204,255,204,0.4);}\n\
3606 .purple {background-color:rgba(128,0,128,0.2);}\n\
3607 .yellow {background-color:rgba(255,255,204,0.4);}\n\
3608 .time1 {font:22px Arial;border:1px solid;}\n\
3609 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
3610 td {text-align:center;}\n\
3611 r {color:#500000;font:15px Tahoma;}\n\
3612 n {color:#505050;font:15px Tahoma;}\n\
3613 .tdhl {color:red;}\n\
3614 .hide {display:none;}\n\
3615 .pf {display:none;}\n\
3616 .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\
3617 .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\
3618 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
3619 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
3620 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
3621 .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\
3622 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
3623 .thread:hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\
3624 .thread.sec,.thread.sec:hover {background-color:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
3625 .hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\
3626 .hover.sync {background-color:white;}\n\
3627 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background-color:white;}\n\
3628 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
3629 .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\
3630 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
3631 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
3632 .phaselet {position:absolute;overflow:hidden;border:0px;text-align:center;height:100px;font-size:24px;}\n\
3633 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
3634 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
3635 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
3636 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
3637 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
3638 .logbtn {position:relative;float:right;height:25px;width:50px;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
3639 .devlist {position:'+x2changes[1]+';width:190px;}\n\
3640 a:link {color:white;text-decoration:none;}\n\
3641 a:visited {color:white;}\n\
3642 a:hover {color:white;}\n\
3643 a:active {color:white;}\n\
3644 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
3645 #devicedetail {height:100px;box-shadow:5px 5px 20px black;}\n\
3646 .tblock {position:absolute;height:100%;background-color:#ddd;}\n\
3647 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
3648 .bg {z-index:1;}\n\
3649 </style>\n</head>\n<body>\n'
3650
3651 # no header or css if its embedded
3652 if(sysvals.embedded):
3653 hf.write('pass True tSus %.3f tRes %.3f tLow %.3f fwvalid %s tSus %.3f tRes %.3f\n' %
3654 (data.tSuspended-data.start, data.end-data.tSuspended, data.tLow, data.fwValid, \
3655 data.fwSuspend/1000000, data.fwResume/1000000))
3656 else:
3657 hf.write(html_header)
3658
3659 # write the test title and general info header
3660 if(sysvals.stamp['time'] != ""):
3661 hf.write(headline_version)
3662 if sysvals.logmsg:
3663 hf.write('<button id="showtest" class="logbtn">log</button>')
3664 if sysvals.addlogs and sysvals.dmesgfile:
3665 hf.write('<button id="showdmesg" class="logbtn">dmesg</button>')
3666 if sysvals.addlogs and sysvals.ftracefile:
3667 hf.write('<button id="showftrace" class="logbtn">ftrace</button>')
3668 hf.write(headline_stamp.format(sysvals.stamp['host'],
3669 sysvals.stamp['kernel'], sysvals.stamp['mode'], \
3670 sysvals.stamp['time']))
3671
3672 # write the device timeline
3673 hf.write(devtl.html['header'])
3674 hf.write(devtl.html['timeline'])
3675 hf.write(devtl.html['legend'])
3676 hf.write('<div id="devicedetailtitle"></div>\n')
3677 hf.write('<div id="devicedetail" style="display:none;">\n')
3678 # draw the colored boxes for the device detail section
3679 for data in testruns:
3680 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
3681 pscolor = 'linear-gradient(to top left, #ccc, #eee)'
3682 hf.write(html_phaselet.format('pre_suspend_process', \
3683 '0', '0', pscolor))
3684 for b in data.phases:
3685 phase = data.dmesg[b]
3686 length = phase['end']-phase['start']
3687 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
3688 width = '%.3f' % ((length*100.0)/tTotal)
3689 hf.write(html_phaselet.format(b, left, width, \
3690 data.dmesg[b]['color']))
3691 hf.write(html_phaselet.format('post_resume_process', \
3692 '0', '0', pscolor))
3693 if sysvals.suspendmode == 'command':
3694 hf.write(html_phaselet.format('cmdexec', '0', '0', pscolor))
3695 hf.write('</div>\n')
3696 hf.write('</div>\n')
3697
3698 # write the ftrace data (callgraph)
3699 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
3700 data = testruns[sysvals.cgtest]
3701 else:
3702 data = testruns[-1]
3703 if(sysvals.usecallgraph and not sysvals.embedded):
3704 hf.write('<section id="callgraphs" class="callgraph">\n')
3705 # write out the ftrace data converted to html
3706 html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
3707 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3708 html_func_end = '</article>\n'
3709 html_func_leaf = '<article>{0} {1}</article>\n'
3710 num = 0
3711 for p in data.phases:
3712 if sysvals.cgphase and p != sysvals.cgphase:
3713 continue
3714 list = data.dmesg[p]['list']
3715 for devname in data.sortedDevices(p):
3716 if('ftrace' not in list[devname]):
3717 continue
3718 devid = list[devname]['id']
3719 cg = list[devname]['ftrace']
3720 clen = (cg.end - cg.start) * 1000
3721 if clen < sysvals.mincglen:
3722 continue
3723 fmt = '<r>(%.3f ms @ '+sysvals.timeformat+' to '+sysvals.timeformat+')</r>'
3724 flen = fmt % (clen, cg.start, cg.end)
3725 name = devname
3726 if(devname in sysvals.devprops):
3727 name = sysvals.devprops[devname].altName(devname)
3728 if sysvals.suspendmode == 'command':
3729 ftitle = name
3730 else:
3731 ftitle = name+' '+p
3732 hf.write(html_func_top.format(devid, data.dmesg[p]['color'], \
3733 num, ftitle, flen))
3734 num += 1
3735 for line in cg.list:
3736 if(line.length < 0.000000001):
3737 flen = ''
3738 else:
3739 fmt = '<n>(%.3f ms @ '+sysvals.timeformat+')</n>'
3740 flen = fmt % (line.length*1000, line.time)
3741 if(line.freturn and line.fcall):
3742 hf.write(html_func_leaf.format(line.name, flen))
3743 elif(line.freturn):
3744 hf.write(html_func_end)
3745 else:
3746 hf.write(html_func_start.format(num, line.name, flen))
3747 num += 1
3748 hf.write(html_func_end)
3749 hf.write('\n\n </section>\n')
3750
3751 # add the test log as a hidden div
3752 if sysvals.logmsg:
3753 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
3754 # add the dmesg log as a hidden div
3755 if sysvals.addlogs and sysvals.dmesgfile:
3756 hf.write('<div id="dmesglog" style="display:none;">\n')
3757 lf = open(sysvals.dmesgfile, 'r')
3758 for line in lf:
3759 line = line.replace('<', '&lt').replace('>', '&gt')
3760 hf.write(line)
3761 lf.close()
3762 hf.write('</div>\n')
3763 # add the ftrace log as a hidden div
3764 if sysvals.addlogs and sysvals.ftracefile:
3765 hf.write('<div id="ftracelog" style="display:none;">\n')
3766 lf = open(sysvals.ftracefile, 'r')
3767 for line in lf:
3768 hf.write(line)
3769 lf.close()
3770 hf.write('</div>\n')
3771
3772 if(not sysvals.embedded):
3773 # write the footer and close
3774 addScriptCode(hf, testruns)
3775 hf.write('</body>\n</html>\n')
3776 else:
3777 # embedded out will be loaded in a page, skip the js
3778 t0 = (testruns[0].start - testruns[-1].tSuspended) * 1000
3779 tMax = (testruns[-1].end - testruns[-1].tSuspended) * 1000
3780 # add js code in a div entry for later evaluation
3781 detail = 'var bounds = [%f,%f];\n' % (t0, tMax)
3782 detail += 'var devtable = [\n'
3783 for data in testruns:
3784 topo = data.deviceTopology()
3785 detail += '\t"%s",\n' % (topo)
3786 detail += '];\n'
3787 hf.write('<div id=customcode style=display:none>\n'+detail+'</div>\n')
3788 hf.close()
3789 return True
3790
3791 # Function: addScriptCode
3792 # Description:
3793 # Adds the javascript code to the output html
3794 # Arguments:
3795 # hf: the open html file pointer
3796 # testruns: array of Data objects from parseKernelLog or parseTraceLog
3797 def addScriptCode(hf, testruns):
3798 t0 = testruns[0].start * 1000
3799 tMax = testruns[-1].end * 1000
3800 # create an array in javascript memory with the device details
3801 detail = ' var devtable = [];\n'
3802 for data in testruns:
3803 topo = data.deviceTopology()
3804 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
3805 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
3806 # add the code which will manipulate the data in the browser
3807 script_code = \
3808 '<script type="text/javascript">\n'+detail+\
3809 ' var resolution = -1;\n'\
3810 ' var dragval = [0, 0];\n'\
3811 ' function redrawTimescale(t0, tMax, tS) {\n'\
3812 ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;"><cS>&larr;R</cS></div>\';\n'\
3813 ' var tTotal = tMax - t0;\n'\
3814 ' var list = document.getElementsByClassName("tblock");\n'\
3815 ' for (var i = 0; i < list.length; i++) {\n'\
3816 ' var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
3817 ' var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
3818 ' var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
3819 ' var mMax = m0 + mTotal;\n'\
3820 ' var html = "";\n'\
3821 ' var divTotal = Math.floor(mTotal/tS) + 1;\n'\
3822 ' if(divTotal > 1000) continue;\n'\
3823 ' var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
3824 ' var pos = 0.0, val = 0.0;\n'\
3825 ' for (var j = 0; j < divTotal; j++) {\n'\
3826 ' var htmlline = "";\n'\
3827 ' if(list[i].id[5] == "r") {\n'\
3828 ' pos = 100 - (((j)*tS*100)/mTotal);\n'\
3829 ' val = (j)*tS;\n'\
3830 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
3831 ' if(j == 0)\n'\
3832 ' htmlline = rline;\n'\
3833 ' } else {\n'\
3834 ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
3835 ' val = (j-divTotal+1)*tS;\n'\
3836 ' if(j == divTotal - 1)\n'\
3837 ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S&rarr;</cS></div>\';\n'\
3838 ' else\n'\
3839 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
3840 ' }\n'\
3841 ' html += htmlline;\n'\
3842 ' }\n'\
3843 ' timescale.innerHTML = html;\n'\
3844 ' }\n'\
3845 ' }\n'\
3846 ' function zoomTimeline() {\n'\
3847 ' var dmesg = document.getElementById("dmesg");\n'\
3848 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
3849 ' var left = zoombox.scrollLeft;\n'\
3850 ' var val = parseFloat(dmesg.style.width);\n'\
3851 ' var newval = 100;\n'\
3852 ' var sh = window.outerWidth / 2;\n'\
3853 ' if(this.id == "zoomin") {\n'\
3854 ' newval = val * 1.2;\n'\
3855 ' if(newval > 910034) newval = 910034;\n'\
3856 ' dmesg.style.width = newval+"%";\n'\
3857 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
3858 ' } else if (this.id == "zoomout") {\n'\
3859 ' newval = val / 1.2;\n'\
3860 ' if(newval < 100) newval = 100;\n'\
3861 ' dmesg.style.width = newval+"%";\n'\
3862 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
3863 ' } else {\n'\
3864 ' zoombox.scrollLeft = 0;\n'\
3865 ' dmesg.style.width = "100%";\n'\
3866 ' }\n'\
3867 ' var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
3868 ' var t0 = bounds[0];\n'\
3869 ' var tMax = bounds[1];\n'\
3870 ' var tTotal = tMax - t0;\n'\
3871 ' var wTotal = tTotal * 100.0 / newval;\n'\
3872 ' var idx = 7*window.innerWidth/1100;\n'\
3873 ' for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
3874 ' if(i >= tS.length) i = tS.length - 1;\n'\
3875 ' if(tS[i] == resolution) return;\n'\
3876 ' resolution = tS[i];\n'\
3877 ' redrawTimescale(t0, tMax, tS[i]);\n'\
3878 ' }\n'\
3879 ' function deviceName(title) {\n'\
3880 ' var name = title.slice(0, title.indexOf(" ("));\n'\
3881 ' return name;\n'\
3882 ' }\n'\
3883 ' function deviceHover() {\n'\
3884 ' var name = deviceName(this.title);\n'\
3885 ' var dmesg = document.getElementById("dmesg");\n'\
3886 ' var dev = dmesg.getElementsByClassName("thread");\n'\
3887 ' var cpu = -1;\n'\
3888 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
3889 ' cpu = parseInt(name.slice(7));\n'\
3890 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
3891 ' cpu = parseInt(name.slice(8));\n'\
3892 ' for (var i = 0; i < dev.length; i++) {\n'\
3893 ' dname = deviceName(dev[i].title);\n'\
3894 ' var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
3895 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
3896 ' (name == dname))\n'\
3897 ' {\n'\
3898 ' dev[i].className = "hover "+cname;\n'\
3899 ' } else {\n'\
3900 ' dev[i].className = cname;\n'\
3901 ' }\n'\
3902 ' }\n'\
3903 ' }\n'\
3904 ' function deviceUnhover() {\n'\
3905 ' var dmesg = document.getElementById("dmesg");\n'\
3906 ' var dev = dmesg.getElementsByClassName("thread");\n'\
3907 ' for (var i = 0; i < dev.length; i++) {\n'\
3908 ' dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
3909 ' }\n'\
3910 ' }\n'\
3911 ' function deviceTitle(title, total, cpu) {\n'\
3912 ' var prefix = "Total";\n'\
3913 ' if(total.length > 3) {\n'\
3914 ' prefix = "Average";\n'\
3915 ' total[1] = (total[1]+total[3])/2;\n'\
3916 ' total[2] = (total[2]+total[4])/2;\n'\
3917 ' }\n'\
3918 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
3919 ' var name = deviceName(title);\n'\
3920 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
3921 ' var driver = "";\n'\
3922 ' var tS = "<t2>(</t2>";\n'\
3923 ' var tR = "<t2>)</t2>";\n'\
3924 ' if(total[1] > 0)\n'\
3925 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
3926 ' if(total[2] > 0)\n'\
3927 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
3928 ' var s = title.indexOf("{");\n'\
3929 ' var e = title.indexOf("}");\n'\
3930 ' if((s >= 0) && (e >= 0))\n'\
3931 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
3932 ' if(total[1] > 0 && total[2] > 0)\n'\
3933 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
3934 ' else\n'\
3935 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
3936 ' return name;\n'\
3937 ' }\n'\
3938 ' function deviceDetail() {\n'\
3939 ' var devinfo = document.getElementById("devicedetail");\n'\
3940 ' devinfo.style.display = "block";\n'\
3941 ' var name = deviceName(this.title);\n'\
3942 ' var cpu = -1;\n'\
3943 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
3944 ' cpu = parseInt(name.slice(7));\n'\
3945 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
3946 ' cpu = parseInt(name.slice(8));\n'\
3947 ' var dmesg = document.getElementById("dmesg");\n'\
3948 ' var dev = dmesg.getElementsByClassName("thread");\n'\
3949 ' var idlist = [];\n'\
3950 ' var pdata = [[]];\n'\
3951 ' if(document.getElementById("devicedetail1"))\n'\
3952 ' pdata = [[], []];\n'\
3953 ' var pd = pdata[0];\n'\
3954 ' var total = [0.0, 0.0, 0.0];\n'\
3955 ' for (var i = 0; i < dev.length; i++) {\n'\
3956 ' dname = deviceName(dev[i].title);\n'\
3957 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
3958 ' (name == dname))\n'\
3959 ' {\n'\
3960 ' idlist[idlist.length] = dev[i].id;\n'\
3961 ' var tidx = 1;\n'\
3962 ' if(dev[i].id[0] == "a") {\n'\
3963 ' pd = pdata[0];\n'\
3964 ' } else {\n'\
3965 ' if(pdata.length == 1) pdata[1] = [];\n'\
3966 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
3967 ' pd = pdata[1];\n'\
3968 ' tidx = 3;\n'\
3969 ' }\n'\
3970 ' var info = dev[i].title.split(" ");\n'\
3971 ' var pname = info[info.length-1];\n'\
3972 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
3973 ' total[0] += pd[pname];\n'\
3974 ' if(pname.indexOf("suspend") >= 0)\n'\
3975 ' total[tidx] += pd[pname];\n'\
3976 ' else\n'\
3977 ' total[tidx+1] += pd[pname];\n'\
3978 ' }\n'\
3979 ' }\n'\
3980 ' var devname = deviceTitle(this.title, total, cpu);\n'\
3981 ' var left = 0.0;\n'\
3982 ' for (var t = 0; t < pdata.length; t++) {\n'\
3983 ' pd = pdata[t];\n'\
3984 ' devinfo = document.getElementById("devicedetail"+t);\n'\
3985 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
3986 ' for (var i = 0; i < phases.length; i++) {\n'\
3987 ' if(phases[i].id in pd) {\n'\
3988 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
3989 ' var fs = 32;\n'\
3990 ' if(w < 8) fs = 4*w | 0;\n'\
3991 ' var fs2 = fs*3/4;\n'\
3992 ' phases[i].style.width = w+"%";\n'\
3993 ' phases[i].style.left = left+"%";\n'\
3994 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
3995 ' left += w;\n'\
3996 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
3997 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
3998 ' phases[i].innerHTML = time+pname;\n'\
3999 ' } else {\n'\
4000 ' phases[i].style.width = "0%";\n'\
4001 ' phases[i].style.left = left+"%";\n'\
4002 ' }\n'\
4003 ' }\n'\
4004 ' }\n'\
4005 ' var cglist = document.getElementById("callgraphs");\n'\
4006 ' if(!cglist) return;\n'\
4007 ' var cg = cglist.getElementsByClassName("atop");\n'\
4008 ' if(cg.length < 10) return;\n'\
4009 ' for (var i = 0; i < cg.length; i++) {\n'\
4010 ' if(idlist.indexOf(cg[i].id) >= 0) {\n'\
4011 ' cg[i].style.display = "block";\n'\
4012 ' } else {\n'\
4013 ' cg[i].style.display = "none";\n'\
4014 ' }\n'\
4015 ' }\n'\
4016 ' }\n'\
4017 ' function devListWindow(e) {\n'\
4018 ' var win = window.open();\n'\
4019 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
4020 ' "<style type=\\"text/css\\">"+\n'\
4021 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
4022 ' "</style>"\n'\
4023 ' var dt = devtable[0];\n'\
4024 ' if(e.target.id != "devlist1")\n'\
4025 ' dt = devtable[1];\n'\
4026 ' win.document.write(html+dt);\n'\
4027 ' }\n'\
4028 ' function errWindow() {\n'\
4029 ' var text = this.id;\n'\
4030 ' var win = window.open();\n'\
4031 ' win.document.write("<pre>"+text+"</pre>");\n'\
4032 ' win.document.close();\n'\
4033 ' }\n'\
4034 ' function logWindow(e) {\n'\
4035 ' var name = e.target.id.slice(4);\n'\
4036 ' var win = window.open();\n'\
4037 ' var log = document.getElementById(name+"log");\n'\
4038 ' var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
4039 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4040 ' win.document.close();\n'\
4041 ' }\n'\
4042 ' function onClickPhase(e) {\n'\
4043 ' }\n'\
4044 ' function onMouseDown(e) {\n'\
4045 ' dragval[0] = e.clientX;\n'\
4046 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
4047 ' document.onmousemove = onMouseMove;\n'\
4048 ' }\n'\
4049 ' function onMouseMove(e) {\n'\
4050 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
4051 ' zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
4052 ' }\n'\
4053 ' function onMouseUp(e) {\n'\
4054 ' document.onmousemove = null;\n'\
4055 ' }\n'\
4056 ' function onKeyPress(e) {\n'\
4057 ' var c = e.charCode;\n'\
4058 ' if(c != 42 && c != 43 && c != 45) return;\n'\
4059 ' var click = document.createEvent("Events");\n'\
4060 ' click.initEvent("click", true, false);\n'\
4061 ' if(c == 43) \n'\
4062 ' document.getElementById("zoomin").dispatchEvent(click);\n'\
4063 ' else if(c == 45)\n'\
4064 ' document.getElementById("zoomout").dispatchEvent(click);\n'\
4065 ' else if(c == 42)\n'\
4066 ' document.getElementById("zoomdef").dispatchEvent(click);\n'\
4067 ' }\n'\
4068 ' window.addEventListener("resize", function () {zoomTimeline();});\n'\
4069 ' window.addEventListener("load", function () {\n'\
4070 ' var dmesg = document.getElementById("dmesg");\n'\
4071 ' dmesg.style.width = "100%"\n'\
4072 ' dmesg.onmousedown = onMouseDown;\n'\
4073 ' document.onmouseup = onMouseUp;\n'\
4074 ' document.onkeypress = onKeyPress;\n'\
4075 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
4076 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
4077 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
4078 ' var list = document.getElementsByClassName("square");\n'\
4079 ' for (var i = 0; i < list.length; i++)\n'\
4080 ' list[i].onclick = onClickPhase;\n'\
4081 ' var list = document.getElementsByClassName("err");\n'\
4082 ' for (var i = 0; i < list.length; i++)\n'\
4083 ' list[i].onclick = errWindow;\n'\
4084 ' var list = document.getElementsByClassName("logbtn");\n'\
4085 ' for (var i = 0; i < list.length; i++)\n'\
4086 ' list[i].onclick = logWindow;\n'\
4087 ' list = document.getElementsByClassName("devlist");\n'\
4088 ' for (var i = 0; i < list.length; i++)\n'\
4089 ' list[i].onclick = devListWindow;\n'\
4090 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4091 ' for (var i = 0; i < dev.length; i++) {\n'\
4092 ' dev[i].onclick = deviceDetail;\n'\
4093 ' dev[i].onmouseover = deviceHover;\n'\
4094 ' dev[i].onmouseout = deviceUnhover;\n'\
4095 ' }\n'\
4096 ' zoomTimeline();\n'\
4097 ' });\n'\
4098 '</script>\n'
4099 hf.write(script_code);
4100
4101 # Function: executeSuspend
4102 # Description:
4103 # Execute system suspend through the sysfs interface, then copy the output
4104 # dmesg and ftrace files to the test output directory.
4105 def executeSuspend():
4106 pm = ProcessMonitor()
4107 tp = sysvals.tpath
4108 fwdata = []
4109 # mark the start point in the kernel ring buffer just as we start
4110 sysvals.initdmesg()
4111 # start ftrace
4112 if(sysvals.usecallgraph or sysvals.usetraceevents):
4113 print('START TRACING')
4114 sysvals.fsetVal('1', 'tracing_on')
4115 if sysvals.useprocmon:
4116 pm.start()
4117 # execute however many s/r runs requested
4118 for count in range(1,sysvals.execcount+1):
4119 # x2delay in between test runs
4120 if(count > 1 and sysvals.x2delay > 0):
4121 sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
4122 time.sleep(sysvals.x2delay/1000.0)
4123 sysvals.fsetVal('WAIT END', 'trace_marker')
4124 # start message
4125 if sysvals.testcommand != '':
4126 print('COMMAND START')
4127 else:
4128 if(sysvals.rtcwake):
4129 print('SUSPEND START')
4130 else:
4131 print('SUSPEND START (press a key to resume)')
4132 # set rtcwake
4133 if(sysvals.rtcwake):
4134 print('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
4135 sysvals.rtcWakeAlarmOn()
4136 # start of suspend trace marker
4137 if(sysvals.usecallgraph or sysvals.usetraceevents):
4138 sysvals.fsetVal('SUSPEND START', 'trace_marker')
4139 # predelay delay
4140 if(count == 1 and sysvals.predelay > 0):
4141 sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
4142 time.sleep(sysvals.predelay/1000.0)
4143 sysvals.fsetVal('WAIT END', 'trace_marker')
4144 # initiate suspend or command
4145 if sysvals.testcommand != '':
4146 call(sysvals.testcommand+' 2>&1', shell=True);
4147 else:
4148 pf = open(sysvals.powerfile, 'w')
4149 pf.write(sysvals.suspendmode)
4150 # execution will pause here
4151 try:
4152 pf.close()
4153 except:
4154 pass
4155 if(sysvals.rtcwake):
4156 sysvals.rtcWakeAlarmOff()
4157 # postdelay delay
4158 if(count == sysvals.execcount and sysvals.postdelay > 0):
4159 sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
4160 time.sleep(sysvals.postdelay/1000.0)
4161 sysvals.fsetVal('WAIT END', 'trace_marker')
4162 # return from suspend
4163 print('RESUME COMPLETE')
4164 if(sysvals.usecallgraph or sysvals.usetraceevents):
4165 sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
4166 if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4167 fwdata.append(getFPDT(False))
4168 # stop ftrace
4169 if(sysvals.usecallgraph or sysvals.usetraceevents):
4170 if sysvals.useprocmon:
4171 pm.stop()
4172 sysvals.fsetVal('0', 'tracing_on')
4173 print('CAPTURING TRACE')
4174 writeDatafileHeader(sysvals.ftracefile, fwdata)
4175 call('cat '+tp+'trace >> '+sysvals.ftracefile, shell=True)
4176 sysvals.fsetVal('', 'trace')
4177 devProps()
4178 # grab a copy of the dmesg output
4179 print('CAPTURING DMESG')
4180 writeDatafileHeader(sysvals.dmesgfile, fwdata)
4181 sysvals.getdmesg()
4182
4183 def writeDatafileHeader(filename, fwdata):
4184 fp = open(filename, 'a')
4185 fp.write(sysvals.teststamp+'\n')
4186 if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4187 for fw in fwdata:
4188 if(fw):
4189 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
4190 fp.close()
4191
4192 # Function: setUSBDevicesAuto
4193 # Description:
4194 # Set the autosuspend control parameter of all USB devices to auto
4195 # This can be dangerous, so use at your own risk, most devices are set
4196 # to always-on since the kernel cant determine if the device can
4197 # properly autosuspend
4198 def setUSBDevicesAuto():
4199 rootCheck(True)
4200 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4201 if(re.match('.*/usb[0-9]*.*', dirname) and
4202 'idVendor' in filenames and 'idProduct' in filenames):
4203 call('echo auto > %s/power/control' % dirname, shell=True)
4204 name = dirname.split('/')[-1]
4205 desc = Popen(['cat', '%s/product' % dirname],
4206 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
4207 ctrl = Popen(['cat', '%s/power/control' % dirname],
4208 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
4209 print('control is %s for %6s: %s' % (ctrl, name, desc))
4210
4211 # Function: yesno
4212 # Description:
4213 # Print out an equivalent Y or N for a set of known parameter values
4214 # Output:
4215 # 'Y', 'N', or ' ' if the value is unknown
4216 def yesno(val):
4217 yesvals = ['auto', 'enabled', 'active', '1']
4218 novals = ['on', 'disabled', 'suspended', 'forbidden', 'unsupported']
4219 if val in yesvals:
4220 return 'Y'
4221 elif val in novals:
4222 return 'N'
4223 return ' '
4224
4225 # Function: ms2nice
4226 # Description:
4227 # Print out a very concise time string in minutes and seconds
4228 # Output:
4229 # The time string, e.g. "1901m16s"
4230 def ms2nice(val):
4231 ms = 0
4232 try:
4233 ms = int(val)
4234 except:
4235 return 0.0
4236 m = ms / 60000
4237 s = (ms / 1000) - (m * 60)
4238 return '%3dm%2ds' % (m, s)
4239
4240 # Function: detectUSB
4241 # Description:
4242 # Detect all the USB hosts and devices currently connected and add
4243 # a list of USB device names to sysvals for better timeline readability
4244 def detectUSB():
4245 field = {'idVendor':'', 'idProduct':'', 'product':'', 'speed':''}
4246 power = {'async':'', 'autosuspend':'', 'autosuspend_delay_ms':'',
4247 'control':'', 'persist':'', 'runtime_enabled':'',
4248 'runtime_status':'', 'runtime_usage':'',
4249 'runtime_active_time':'',
4250 'runtime_suspended_time':'',
4251 'active_duration':'',
4252 'connected_duration':''}
4253
4254 print('LEGEND')
4255 print('---------------------------------------------------------------------------------------------')
4256 print(' A = async/sync PM queue Y/N D = autosuspend delay (seconds)')
4257 print(' S = autosuspend Y/N rACTIVE = runtime active (min/sec)')
4258 print(' P = persist across suspend Y/N rSUSPEN = runtime suspend (min/sec)')
4259 print(' E = runtime suspend enabled/forbidden Y/N ACTIVE = active duration (min/sec)')
4260 print(' R = runtime status active/suspended Y/N CONNECT = connected duration (min/sec)')
4261 print(' U = runtime usage count')
4262 print('---------------------------------------------------------------------------------------------')
4263 print(' NAME ID DESCRIPTION SPEED A S P E R U D rACTIVE rSUSPEN ACTIVE CONNECT')
4264 print('---------------------------------------------------------------------------------------------')
4265
4266 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4267 if(re.match('.*/usb[0-9]*.*', dirname) and
4268 'idVendor' in filenames and 'idProduct' in filenames):
4269 for i in field:
4270 field[i] = Popen(['cat', '%s/%s' % (dirname, i)],
4271 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
4272 name = dirname.split('/')[-1]
4273 for i in power:
4274 power[i] = Popen(['cat', '%s/power/%s' % (dirname, i)],
4275 stderr=PIPE, stdout=PIPE).stdout.read().replace('\n', '')
4276 if(re.match('usb[0-9]*', name)):
4277 first = '%-8s' % name
4278 else:
4279 first = '%8s' % name
4280 print('%s [%s:%s] %-20s %-4s %1s %1s %1s %1s %1s %1s %1s %s %s %s %s' % \
4281 (first, field['idVendor'], field['idProduct'], \
4282 field['product'][0:20], field['speed'], \
4283 yesno(power['async']), \
4284 yesno(power['control']), \
4285 yesno(power['persist']), \
4286 yesno(power['runtime_enabled']), \
4287 yesno(power['runtime_status']), \
4288 power['runtime_usage'], \
4289 power['autosuspend'], \
4290 ms2nice(power['runtime_active_time']), \
4291 ms2nice(power['runtime_suspended_time']), \
4292 ms2nice(power['active_duration']), \
4293 ms2nice(power['connected_duration'])))
4294
4295 # Function: devProps
4296 # Description:
4297 # Retrieve a list of properties for all devices in the trace log
4298 def devProps(data=0):
4299 props = dict()
4300
4301 if data:
4302 idx = data.index(': ') + 2
4303 if idx >= len(data):
4304 return
4305 devlist = data[idx:].split(';')
4306 for dev in devlist:
4307 f = dev.split(',')
4308 if len(f) < 3:
4309 continue
4310 dev = f[0]
4311 props[dev] = DevProps()
4312 props[dev].altname = f[1]
4313 if int(f[2]):
4314 props[dev].async = True
4315 else:
4316 props[dev].async = False
4317 sysvals.devprops = props
4318 if sysvals.suspendmode == 'command' and 'testcommandstring' in props:
4319 sysvals.testcommand = props['testcommandstring'].altname
4320 return
4321
4322 if(os.path.exists(sysvals.ftracefile) == False):
4323 doError('%s does not exist' % sysvals.ftracefile)
4324
4325 # first get the list of devices we need properties for
4326 msghead = 'Additional data added by AnalyzeSuspend'
4327 alreadystamped = False
4328 tp = TestProps()
4329 tf = open(sysvals.ftracefile, 'r')
4330 for line in tf:
4331 if msghead in line:
4332 alreadystamped = True
4333 continue
4334 # determine the trace data type (required for further parsing)
4335 m = re.match(sysvals.tracertypefmt, line)
4336 if(m):
4337 tp.setTracerType(m.group('t'))
4338 continue
4339 # parse only valid lines, if this is not one move on
4340 m = re.match(tp.ftrace_line_fmt, line)
4341 if(not m or 'device_pm_callback_start' not in line):
4342 continue
4343 m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
4344 if(not m):
4345 continue
4346 dev = m.group('d')
4347 if dev not in props:
4348 props[dev] = DevProps()
4349 tf.close()
4350
4351 if not alreadystamped and sysvals.suspendmode == 'command':
4352 out = '#\n# '+msghead+'\n# Device Properties: '
4353 out += 'testcommandstring,%s,0;' % (sysvals.testcommand)
4354 with open(sysvals.ftracefile, 'a') as fp:
4355 fp.write(out+'\n')
4356 sysvals.devprops = props
4357 return
4358
4359 # now get the syspath for each of our target devices
4360 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4361 if(re.match('.*/power', dirname) and 'async' in filenames):
4362 dev = dirname.split('/')[-2]
4363 if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
4364 props[dev].syspath = dirname[:-6]
4365
4366 # now fill in the properties for our target devices
4367 for dev in props:
4368 dirname = props[dev].syspath
4369 if not dirname or not os.path.exists(dirname):
4370 continue
4371 with open(dirname+'/power/async') as fp:
4372 text = fp.read()
4373 props[dev].async = False
4374 if 'enabled' in text:
4375 props[dev].async = True
4376 fields = os.listdir(dirname)
4377 if 'product' in fields:
4378 with open(dirname+'/product') as fp:
4379 props[dev].altname = fp.read()
4380 elif 'name' in fields:
4381 with open(dirname+'/name') as fp:
4382 props[dev].altname = fp.read()
4383 elif 'model' in fields:
4384 with open(dirname+'/model') as fp:
4385 props[dev].altname = fp.read()
4386 elif 'description' in fields:
4387 with open(dirname+'/description') as fp:
4388 props[dev].altname = fp.read()
4389 elif 'id' in fields:
4390 with open(dirname+'/id') as fp:
4391 props[dev].altname = fp.read()
4392 elif 'idVendor' in fields and 'idProduct' in fields:
4393 idv, idp = '', ''
4394 with open(dirname+'/idVendor') as fp:
4395 idv = fp.read().strip()
4396 with open(dirname+'/idProduct') as fp:
4397 idp = fp.read().strip()
4398 props[dev].altname = '%s:%s' % (idv, idp)
4399
4400 if props[dev].altname:
4401 out = props[dev].altname.strip().replace('\n', ' ')
4402 out = out.replace(',', ' ')
4403 out = out.replace(';', ' ')
4404 props[dev].altname = out
4405
4406 # and now write the data to the ftrace file
4407 if not alreadystamped:
4408 out = '#\n# '+msghead+'\n# Device Properties: '
4409 for dev in sorted(props):
4410 out += props[dev].out(dev)
4411 with open(sysvals.ftracefile, 'a') as fp:
4412 fp.write(out+'\n')
4413
4414 sysvals.devprops = props
4415
4416 # Function: getModes
4417 # Description:
4418 # Determine the supported power modes on this system
4419 # Output:
4420 # A string list of the available modes
4421 def getModes():
4422 modes = ''
4423 if(os.path.exists(sysvals.powerfile)):
4424 fp = open(sysvals.powerfile, 'r')
4425 modes = string.split(fp.read())
4426 fp.close()
4427 return modes
4428
4429 # Function: getFPDT
4430 # Description:
4431 # Read the acpi bios tables and pull out FPDT, the firmware data
4432 # Arguments:
4433 # output: True to output the info to stdout, False otherwise
4434 def getFPDT(output):
4435 rectype = {}
4436 rectype[0] = 'Firmware Basic Boot Performance Record'
4437 rectype[1] = 'S3 Performance Table Record'
4438 prectype = {}
4439 prectype[0] = 'Basic S3 Resume Performance Record'
4440 prectype[1] = 'Basic S3 Suspend Performance Record'
4441
4442 rootCheck(True)
4443 if(not os.path.exists(sysvals.fpdtpath)):
4444 if(output):
4445 doError('file does not exist: %s' % sysvals.fpdtpath)
4446 return False
4447 if(not os.access(sysvals.fpdtpath, os.R_OK)):
4448 if(output):
4449 doError('file is not readable: %s' % sysvals.fpdtpath)
4450 return False
4451 if(not os.path.exists(sysvals.mempath)):
4452 if(output):
4453 doError('file does not exist: %s' % sysvals.mempath)
4454 return False
4455 if(not os.access(sysvals.mempath, os.R_OK)):
4456 if(output):
4457 doError('file is not readable: %s' % sysvals.mempath)
4458 return False
4459
4460 fp = open(sysvals.fpdtpath, 'rb')
4461 buf = fp.read()
4462 fp.close()
4463
4464 if(len(buf) < 36):
4465 if(output):
4466 doError('Invalid FPDT table data, should '+\
4467 'be at least 36 bytes')
4468 return False
4469
4470 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
4471 if(output):
4472 print('')
4473 print('Firmware Performance Data Table (%s)' % table[0])
4474 print(' Signature : %s' % table[0])
4475 print(' Table Length : %u' % table[1])
4476 print(' Revision : %u' % table[2])
4477 print(' Checksum : 0x%x' % table[3])
4478 print(' OEM ID : %s' % table[4])
4479 print(' OEM Table ID : %s' % table[5])
4480 print(' OEM Revision : %u' % table[6])
4481 print(' Creator ID : %s' % table[7])
4482 print(' Creator Revision : 0x%x' % table[8])
4483 print('')
4484
4485 if(table[0] != 'FPDT'):
4486 if(output):
4487 doError('Invalid FPDT table')
4488 return False
4489 if(len(buf) <= 36):
4490 return False
4491 i = 0
4492 fwData = [0, 0]
4493 records = buf[36:]
4494 fp = open(sysvals.mempath, 'rb')
4495 while(i < len(records)):
4496 header = struct.unpack('HBB', records[i:i+4])
4497 if(header[0] not in rectype):
4498 i += header[1]
4499 continue
4500 if(header[1] != 16):
4501 i += header[1]
4502 continue
4503 addr = struct.unpack('Q', records[i+8:i+16])[0]
4504 try:
4505 fp.seek(addr)
4506 first = fp.read(8)
4507 except:
4508 if(output):
4509 print('Bad address 0x%x in %s' % (addr, sysvals.mempath))
4510 return [0, 0]
4511 rechead = struct.unpack('4sI', first)
4512 recdata = fp.read(rechead[1]-8)
4513 if(rechead[0] == 'FBPT'):
4514 record = struct.unpack('HBBIQQQQQ', recdata)
4515 if(output):
4516 print('%s (%s)' % (rectype[header[0]], rechead[0]))
4517 print(' Reset END : %u ns' % record[4])
4518 print(' OS Loader LoadImage Start : %u ns' % record[5])
4519 print(' OS Loader StartImage Start : %u ns' % record[6])
4520 print(' ExitBootServices Entry : %u ns' % record[7])
4521 print(' ExitBootServices Exit : %u ns' % record[8])
4522 elif(rechead[0] == 'S3PT'):
4523 if(output):
4524 print('%s (%s)' % (rectype[header[0]], rechead[0]))
4525 j = 0
4526 while(j < len(recdata)):
4527 prechead = struct.unpack('HBB', recdata[j:j+4])
4528 if(prechead[0] not in prectype):
4529 continue
4530 if(prechead[0] == 0):
4531 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
4532 fwData[1] = record[2]
4533 if(output):
4534 print(' %s' % prectype[prechead[0]])
4535 print(' Resume Count : %u' % \
4536 record[1])
4537 print(' FullResume : %u ns' % \
4538 record[2])
4539 print(' AverageResume : %u ns' % \
4540 record[3])
4541 elif(prechead[0] == 1):
4542 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
4543 fwData[0] = record[1] - record[0]
4544 if(output):
4545 print(' %s' % prectype[prechead[0]])
4546 print(' SuspendStart : %u ns' % \
4547 record[0])
4548 print(' SuspendEnd : %u ns' % \
4549 record[1])
4550 print(' SuspendTime : %u ns' % \
4551 fwData[0])
4552 j += prechead[1]
4553 if(output):
4554 print('')
4555 i += header[1]
4556 fp.close()
4557 return fwData
4558
4559 # Function: statusCheck
4560 # Description:
4561 # Verify that the requested command and options will work, and
4562 # print the results to the terminal
4563 # Output:
4564 # True if the test will work, False if not
4565 def statusCheck(probecheck=False):
4566 status = True
4567
4568 print('Checking this system (%s)...' % platform.node())
4569
4570 # check we have root access
4571 res = sysvals.colorText('NO (No features of this tool will work!)')
4572 if(rootCheck(False)):
4573 res = 'YES'
4574 print(' have root access: %s' % res)
4575 if(res != 'YES'):
4576 print(' Try running this script with sudo')
4577 return False
4578
4579 # check sysfs is mounted
4580 res = sysvals.colorText('NO (No features of this tool will work!)')
4581 if(os.path.exists(sysvals.powerfile)):
4582 res = 'YES'
4583 print(' is sysfs mounted: %s' % res)
4584 if(res != 'YES'):
4585 return False
4586
4587 # check target mode is a valid mode
4588 if sysvals.suspendmode != 'command':
4589 res = sysvals.colorText('NO')
4590 modes = getModes()
4591 if(sysvals.suspendmode in modes):
4592 res = 'YES'
4593 else:
4594 status = False
4595 print(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
4596 if(res == 'NO'):
4597 print(' valid power modes are: %s' % modes)
4598 print(' please choose one with -m')
4599
4600 # check if ftrace is available
4601 res = sysvals.colorText('NO')
4602 ftgood = sysvals.verifyFtrace()
4603 if(ftgood):
4604 res = 'YES'
4605 elif(sysvals.usecallgraph):
4606 status = False
4607 print(' is ftrace supported: %s' % res)
4608
4609 # check if kprobes are available
4610 res = sysvals.colorText('NO')
4611 sysvals.usekprobes = sysvals.verifyKprobes()
4612 if(sysvals.usekprobes):
4613 res = 'YES'
4614 else:
4615 sysvals.usedevsrc = False
4616 print(' are kprobes supported: %s' % res)
4617
4618 # what data source are we using
4619 res = 'DMESG'
4620 if(ftgood):
4621 sysvals.usetraceeventsonly = True
4622 sysvals.usetraceevents = False
4623 for e in sysvals.traceevents:
4624 check = False
4625 if(os.path.exists(sysvals.epath+e)):
4626 check = True
4627 if(not check):
4628 sysvals.usetraceeventsonly = False
4629 if(e == 'suspend_resume' and check):
4630 sysvals.usetraceevents = True
4631 if(sysvals.usetraceevents and sysvals.usetraceeventsonly):
4632 res = 'FTRACE (all trace events found)'
4633 elif(sysvals.usetraceevents):
4634 res = 'DMESG and FTRACE (suspend_resume trace event found)'
4635 print(' timeline data source: %s' % res)
4636
4637 # check if rtcwake
4638 res = sysvals.colorText('NO')
4639 if(sysvals.rtcpath != ''):
4640 res = 'YES'
4641 elif(sysvals.rtcwake):
4642 status = False
4643 print(' is rtcwake supported: %s' % res)
4644
4645 if not probecheck:
4646 return status
4647
4648 # verify kprobes
4649 if sysvals.usekprobes:
4650 for name in sysvals.tracefuncs:
4651 sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
4652 if sysvals.usedevsrc:
4653 for name in sysvals.dev_tracefuncs:
4654 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
4655 sysvals.addKprobes(True)
4656
4657 return status
4658
4659 # Function: doError
4660 # Description:
4661 # generic error function for catastrphic failures
4662 # Arguments:
4663 # msg: the error message to print
4664 # help: True if printHelp should be called after, False otherwise
4665 def doError(msg, help=False):
4666 if(help == True):
4667 printHelp()
4668 print('ERROR: %s\n') % msg
4669 sys.exit()
4670
4671 # Function: rootCheck
4672 # Description:
4673 # quick check to see if we have root access
4674 def rootCheck(fatal):
4675 if(os.access(sysvals.powerfile, os.W_OK)):
4676 return True
4677 if fatal:
4678 doError('This command must be run as root')
4679 return False
4680
4681 # Function: getArgInt
4682 # Description:
4683 # pull out an integer argument from the command line with checks
4684 def getArgInt(name, args, min, max, main=True):
4685 if main:
4686 try:
4687 arg = args.next()
4688 except:
4689 doError(name+': no argument supplied', True)
4690 else:
4691 arg = args
4692 try:
4693 val = int(arg)
4694 except:
4695 doError(name+': non-integer value given', True)
4696 if(val < min or val > max):
4697 doError(name+': value should be between %d and %d' % (min, max), True)
4698 return val
4699
4700 # Function: getArgFloat
4701 # Description:
4702 # pull out a float argument from the command line with checks
4703 def getArgFloat(name, args, min, max, main=True):
4704 if main:
4705 try:
4706 arg = args.next()
4707 except:
4708 doError(name+': no argument supplied', True)
4709 else:
4710 arg = args
4711 try:
4712 val = float(arg)
4713 except:
4714 doError(name+': non-numerical value given', True)
4715 if(val < min or val > max):
4716 doError(name+': value should be between %f and %f' % (min, max), True)
4717 return val
4718
4719 def processData():
4720 print('PROCESSING DATA')
4721 if(sysvals.usetraceeventsonly):
4722 testruns = parseTraceLog()
4723 if sysvals.dmesgfile:
4724 dmesgtext = loadKernelLog(True)
4725 for data in testruns:
4726 data.extractErrorInfo(dmesgtext)
4727 else:
4728 testruns = loadKernelLog()
4729 for data in testruns:
4730 parseKernelLog(data)
4731 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
4732 appendIncompleteTraceLog(testruns)
4733 createHTML(testruns)
4734
4735 # Function: rerunTest
4736 # Description:
4737 # generate an output from an existing set of ftrace/dmesg logs
4738 def rerunTest():
4739 if sysvals.ftracefile:
4740 doesTraceLogHaveTraceEvents()
4741 if not sysvals.dmesgfile and not sysvals.usetraceeventsonly:
4742 doError('recreating this html output requires a dmesg file')
4743 sysvals.setOutputFile()
4744 vprint('Output file: %s' % sysvals.htmlfile)
4745 if(os.path.exists(sysvals.htmlfile) and not os.access(sysvals.htmlfile, os.W_OK)):
4746 doError('missing permission to write to %s' % sysvals.htmlfile)
4747 processData()
4748
4749 # Function: runTest
4750 # Description:
4751 # execute a suspend/resume, gather the logs, and generate the output
4752 def runTest(subdir, testpath=''):
4753 # prepare for the test
4754 sysvals.initFtrace()
4755 sysvals.initTestOutput(subdir, testpath)
4756 vprint('Output files:\n\t%s\n\t%s\n\t%s' % \
4757 (sysvals.dmesgfile, sysvals.ftracefile, sysvals.htmlfile))
4758
4759 # execute the test
4760 executeSuspend()
4761 sysvals.cleanupFtrace()
4762 processData()
4763
4764 # if running as root, change output dir owner to sudo_user
4765 if os.path.isdir(sysvals.testdir) and os.getuid() == 0 and \
4766 'SUDO_USER' in os.environ:
4767 cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
4768 call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True)
4769
4770 # Function: runSummary
4771 # Description:
4772 # create a summary of tests in a sub-directory
4773 def runSummary(subdir, output):
4774 # get a list of ftrace output files
4775 files = []
4776 for dirname, dirnames, filenames in os.walk(subdir):
4777 for filename in filenames:
4778 if(re.match('.*_ftrace.txt', filename)):
4779 files.append("%s/%s" % (dirname, filename))
4780
4781 # process the files in order and get an array of data objects
4782 testruns = []
4783 for file in sorted(files):
4784 if output:
4785 print("Test found in %s" % os.path.dirname(file))
4786 sysvals.ftracefile = file
4787 sysvals.dmesgfile = file.replace('_ftrace.txt', '_dmesg.txt')
4788 doesTraceLogHaveTraceEvents()
4789 sysvals.usecallgraph = False
4790 if not sysvals.usetraceeventsonly:
4791 if(not os.path.exists(sysvals.dmesgfile)):
4792 print("Skipping %s: not a valid test input" % file)
4793 continue
4794 else:
4795 if output:
4796 f = os.path.basename(sysvals.ftracefile)
4797 d = os.path.basename(sysvals.dmesgfile)
4798 print("\tInput files: %s and %s" % (f, d))
4799 testdata = loadKernelLog()
4800 data = testdata[0]
4801 parseKernelLog(data)
4802 testdata = [data]
4803 appendIncompleteTraceLog(testdata)
4804 else:
4805 if output:
4806 print("\tInput file: %s" % os.path.basename(sysvals.ftracefile))
4807 testdata = parseTraceLog()
4808 data = testdata[0]
4809 data.normalizeTime(data.tSuspended)
4810 link = file.replace(subdir+'/', '').replace('_ftrace.txt', '.html')
4811 data.outfile = link
4812 testruns.append(data)
4813
4814 createHTMLSummarySimple(testruns, subdir+'/summary.html')
4815
4816 # Function: checkArgBool
4817 # Description:
4818 # check if a boolean string value is true or false
4819 def checkArgBool(value):
4820 yes = ['1', 'true', 'yes', 'on']
4821 if value.lower() in yes:
4822 return True
4823 return False
4824
4825 # Function: configFromFile
4826 # Description:
4827 # Configure the script via the info in a config file
4828 def configFromFile(file):
4829 Config = ConfigParser.ConfigParser()
4830
4831 Config.read(file)
4832 sections = Config.sections()
4833 overridekprobes = False
4834 overridedevkprobes = False
4835 if 'Settings' in sections:
4836 for opt in Config.options('Settings'):
4837 value = Config.get('Settings', opt).lower()
4838 if(opt.lower() == 'verbose'):
4839 sysvals.verbose = checkArgBool(value)
4840 elif(opt.lower() == 'addlogs'):
4841 sysvals.addlogs = checkArgBool(value)
4842 elif(opt.lower() == 'dev'):
4843 sysvals.usedevsrc = checkArgBool(value)
4844 elif(opt.lower() == 'proc'):
4845 sysvals.useprocmon = checkArgBool(value)
4846 elif(opt.lower() == 'x2'):
4847 if checkArgBool(value):
4848 sysvals.execcount = 2
4849 elif(opt.lower() == 'callgraph'):
4850 sysvals.usecallgraph = checkArgBool(value)
4851 elif(opt.lower() == 'override-timeline-functions'):
4852 overridekprobes = checkArgBool(value)
4853 elif(opt.lower() == 'override-dev-timeline-functions'):
4854 overridedevkprobes = checkArgBool(value)
4855 elif(opt.lower() == 'devicefilter'):
4856 sysvals.setDeviceFilter(value)
4857 elif(opt.lower() == 'expandcg'):
4858 sysvals.cgexp = checkArgBool(value)
4859 elif(opt.lower() == 'srgap'):
4860 if checkArgBool(value):
4861 sysvals.srgap = 5
4862 elif(opt.lower() == 'mode'):
4863 sysvals.suspendmode = value
4864 elif(opt.lower() == 'command'):
4865 sysvals.testcommand = value
4866 elif(opt.lower() == 'x2delay'):
4867 sysvals.x2delay = getArgInt('-x2delay', value, 0, 60000, False)
4868 elif(opt.lower() == 'predelay'):
4869 sysvals.predelay = getArgInt('-predelay', value, 0, 60000, False)
4870 elif(opt.lower() == 'postdelay'):
4871 sysvals.postdelay = getArgInt('-postdelay', value, 0, 60000, False)
4872 elif(opt.lower() == 'rtcwake'):
4873 sysvals.rtcwake = True
4874 sysvals.rtcwaketime = getArgInt('-rtcwake', value, 0, 3600, False)
4875 elif(opt.lower() == 'timeprec'):
4876 sysvals.setPrecision(getArgInt('-timeprec', value, 0, 6, False))
4877 elif(opt.lower() == 'mindev'):
4878 sysvals.mindevlen = getArgFloat('-mindev', value, 0.0, 10000.0, False)
4879 elif(opt.lower() == 'callloop-maxgap'):
4880 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', value, 0.0, 1.0, False)
4881 elif(opt.lower() == 'callloop-maxlen'):
4882 sysvals.callloopmaxgap = getArgFloat('-callloop-maxlen', value, 0.0, 1.0, False)
4883 elif(opt.lower() == 'mincg'):
4884 sysvals.mincglen = getArgFloat('-mincg', value, 0.0, 10000.0, False)
4885 elif(opt.lower() == 'output-dir'):
4886 sysvals.setOutputFolder(value)
4887
4888 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
4889 doError('No command supplied for mode "command"')
4890
4891 # compatibility errors
4892 if sysvals.usedevsrc and sysvals.usecallgraph:
4893 doError('-dev is not compatible with -f')
4894 if sysvals.usecallgraph and sysvals.useprocmon:
4895 doError('-proc is not compatible with -f')
4896
4897 if overridekprobes:
4898 sysvals.tracefuncs = dict()
4899 if overridedevkprobes:
4900 sysvals.dev_tracefuncs = dict()
4901
4902 kprobes = dict()
4903 kprobesec = 'dev_timeline_functions_'+platform.machine()
4904 if kprobesec in sections:
4905 for name in Config.options(kprobesec):
4906 text = Config.get(kprobesec, name)
4907 kprobes[name] = (text, True)
4908 kprobesec = 'timeline_functions_'+platform.machine()
4909 if kprobesec in sections:
4910 for name in Config.options(kprobesec):
4911 if name in kprobes:
4912 doError('Duplicate timeline function found "%s"' % (name))
4913 text = Config.get(kprobesec, name)
4914 kprobes[name] = (text, False)
4915
4916 for name in kprobes:
4917 function = name
4918 format = name
4919 color = ''
4920 args = dict()
4921 text, dev = kprobes[name]
4922 data = text.split()
4923 i = 0
4924 for val in data:
4925 # bracketted strings are special formatting, read them separately
4926 if val[0] == '[' and val[-1] == ']':
4927 for prop in val[1:-1].split(','):
4928 p = prop.split('=')
4929 if p[0] == 'color':
4930 try:
4931 color = int(p[1], 16)
4932 color = '#'+p[1]
4933 except:
4934 color = p[1]
4935 continue
4936 # first real arg should be the format string
4937 if i == 0:
4938 format = val
4939 # all other args are actual function args
4940 else:
4941 d = val.split('=')
4942 args[d[0]] = d[1]
4943 i += 1
4944 if not function or not format:
4945 doError('Invalid kprobe: %s' % name)
4946 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
4947 if arg not in args:
4948 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
4949 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
4950 doError('Duplicate timeline function found "%s"' % (name))
4951
4952 kp = {
4953 'name': name,
4954 'func': function,
4955 'format': format,
4956 sysvals.archargs: args
4957 }
4958 if color:
4959 kp['color'] = color
4960 if dev:
4961 sysvals.dev_tracefuncs[name] = kp
4962 else:
4963 sysvals.tracefuncs[name] = kp
4964
4965 # Function: printHelp
4966 # Description:
4967 # print out the help text
4968 def printHelp():
4969 modes = getModes()
4970
4971 print('')
4972 print('AnalyzeSuspend v%s' % sysvals.version)
4973 print('Usage: sudo analyze_suspend.py <options>')
4974 print('')
4975 print('Description:')
4976 print(' This tool is designed to assist kernel and OS developers in optimizing')
4977 print(' their linux stack\'s suspend/resume time. Using a kernel image built')
4978 print(' with a few extra options enabled, the tool will execute a suspend and')
4979 print(' capture dmesg and ftrace data until resume is complete. This data is')
4980 print(' transformed into a device timeline and an optional callgraph to give')
4981 print(' a detailed view of which devices/subsystems are taking the most')
4982 print(' time in suspend/resume.')
4983 print('')
4984 print(' Generates output files in subdirectory: suspend-mmddyy-HHMMSS')
4985 print(' HTML output: <hostname>_<mode>.html')
4986 print(' raw dmesg output: <hostname>_<mode>_dmesg.txt')
4987 print(' raw ftrace output: <hostname>_<mode>_ftrace.txt')
4988 print('')
4989 print('Options:')
4990 print(' [general]')
4991 print(' -h Print this help text')
4992 print(' -v Print the current tool version')
4993 print(' -config fn Pull arguments and config options from file fn')
4994 print(' -verbose Print extra information during execution and analysis')
4995 print(' -status Test to see if the system is enabled to run this tool')
4996 print(' -modes List available suspend modes')
4997 print(' -m mode Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode)
4998 print(' -o subdir Override the output subdirectory')
4999 print(' -rtcwake t Use rtcwake to autoresume after <t> seconds (default: disabled)')
5000 print(' -addlogs Add the dmesg and ftrace logs to the html output')
5001 print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)')
5002 print(' [advanced]')
5003 print(' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"')
5004 print(' -proc Add usermode process info into the timeline (default: disabled)')
5005 print(' -dev Add kernel function calls and threads to the timeline (default: disabled)')
5006 print(' -x2 Run two suspend/resumes back to back (default: disabled)')
5007 print(' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)')
5008 print(' -predelay t Include t ms delay before 1st suspend (default: 0 ms)')
5009 print(' -postdelay t Include t ms delay after last resume (default: 0 ms)')
5010 print(' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)')
5011 print(' -multi n d Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
5012 print(' be created in a new subdirectory with a summary page.')
5013 print(' [debug]')
5014 print(' -f Use ftrace to create device callgraphs (default: disabled)')
5015 print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)')
5016 print(' -flist Print the list of functions currently being captured in ftrace')
5017 print(' -flistall Print all functions capable of being captured in ftrace')
5018 print(' -fadd file Add functions to be graphed in the timeline from a list in a text file')
5019 print(' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names')
5020 print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)')
5021 print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)')
5022 print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)')
5023 print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
5024 print(' [utilities]')
5025 print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table')
5026 print(' -usbtopo Print out the current USB topology with power info')
5027 print(' -usbauto Enable autosuspend for all connected USB devices')
5028 print(' [re-analyze data from previous runs]')
5029 print(' -ftrace ftracefile Create HTML output using ftrace input')
5030 print(' -dmesg dmesgfile Create HTML output using dmesg (not needed for kernel >= 3.15)')
5031 print(' -summary directory Create a summary of all test in this dir')
5032 print('')
5033 return True
5034
5035 # ----------------- MAIN --------------------
5036 # exec start (skipped if script is loaded as library)
5037 if __name__ == '__main__':
5038 cmd = ''
5039 cmdarg = ''
5040 multitest = {'run': False, 'count': 0, 'delay': 0}
5041 simplecmds = ['-modes', '-fpdt', '-flist', '-flistall', '-usbtopo', '-usbauto', '-status']
5042 # loop through the command line arguments
5043 args = iter(sys.argv[1:])
5044 for arg in args:
5045 if(arg == '-m'):
5046 try:
5047 val = args.next()
5048 except:
5049 doError('No mode supplied', True)
5050 if val == 'command' and not sysvals.testcommand:
5051 doError('No command supplied for mode "command"', True)
5052 sysvals.suspendmode = val
5053 elif(arg in simplecmds):
5054 cmd = arg[1:]
5055 elif(arg == '-h'):
5056 printHelp()
5057 sys.exit()
5058 elif(arg == '-v'):
5059 print("Version %s" % sysvals.version)
5060 sys.exit()
5061 elif(arg == '-x2'):
5062 sysvals.execcount = 2
5063 elif(arg == '-x2delay'):
5064 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
5065 elif(arg == '-predelay'):
5066 sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
5067 elif(arg == '-postdelay'):
5068 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
5069 elif(arg == '-f'):
5070 sysvals.usecallgraph = True
5071 elif(arg == '-addlogs'):
5072 sysvals.addlogs = True
5073 elif(arg == '-verbose'):
5074 sysvals.verbose = True
5075 elif(arg == '-proc'):
5076 sysvals.useprocmon = True
5077 elif(arg == '-dev'):
5078 sysvals.usedevsrc = True
5079 elif(arg == '-rtcwake'):
5080 sysvals.rtcwake = True
5081 sysvals.rtcwaketime = getArgInt('-rtcwake', args, 0, 3600)
5082 elif(arg == '-timeprec'):
5083 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
5084 elif(arg == '-mindev'):
5085 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
5086 elif(arg == '-mincg'):
5087 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
5088 elif(arg == '-cgtest'):
5089 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
5090 elif(arg == '-cgphase'):
5091 try:
5092 val = args.next()
5093 except:
5094 doError('No phase name supplied', True)
5095 d = Data(0)
5096 if val not in d.phases:
5097 doError('Invalid phase, valid phaess are %s' % d.phases, True)
5098 sysvals.cgphase = val
5099 elif(arg == '-callloop-maxgap'):
5100 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
5101 elif(arg == '-callloop-maxlen'):
5102 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
5103 elif(arg == '-cmd'):
5104 try:
5105 val = args.next()
5106 except:
5107 doError('No command string supplied', True)
5108 sysvals.testcommand = val
5109 sysvals.suspendmode = 'command'
5110 elif(arg == '-expandcg'):
5111 sysvals.cgexp = True
5112 elif(arg == '-srgap'):
5113 sysvals.srgap = 5
5114 elif(arg == '-multi'):
5115 multitest['run'] = True
5116 multitest['count'] = getArgInt('-multi n (exec count)', args, 2, 1000000)
5117 multitest['delay'] = getArgInt('-multi d (delay between tests)', args, 0, 3600)
5118 elif(arg == '-o'):
5119 try:
5120 val = args.next()
5121 except:
5122 doError('No subdirectory name supplied', True)
5123 sysvals.setOutputFolder(val)
5124 elif(arg == '-config'):
5125 try:
5126 val = args.next()
5127 except:
5128 doError('No text file supplied', True)
5129 if(os.path.exists(val) == False):
5130 doError('%s does not exist' % val)
5131 configFromFile(val)
5132 elif(arg == '-fadd'):
5133 try:
5134 val = args.next()
5135 except:
5136 doError('No text file supplied', True)
5137 if(os.path.exists(val) == False):
5138 doError('%s does not exist' % val)
5139 sysvals.addFtraceFilterFunctions(val)
5140 elif(arg == '-dmesg'):
5141 try:
5142 val = args.next()
5143 except:
5144 doError('No dmesg file supplied', True)
5145 sysvals.notestrun = True
5146 sysvals.dmesgfile = val
5147 if(os.path.exists(sysvals.dmesgfile) == False):
5148 doError('%s does not exist' % sysvals.dmesgfile)
5149 elif(arg == '-ftrace'):
5150 try:
5151 val = args.next()
5152 except:
5153 doError('No ftrace file supplied', True)
5154 sysvals.notestrun = True
5155 sysvals.ftracefile = val
5156 if(os.path.exists(sysvals.ftracefile) == False):
5157 doError('%s does not exist' % sysvals.ftracefile)
5158 elif(arg == '-summary'):
5159 try:
5160 val = args.next()
5161 except:
5162 doError('No directory supplied', True)
5163 cmd = 'summary'
5164 cmdarg = val
5165 sysvals.notestrun = True
5166 if(os.path.isdir(val) == False):
5167 doError('%s is not accesible' % val)
5168 elif(arg == '-filter'):
5169 try:
5170 val = args.next()
5171 except:
5172 doError('No devnames supplied', True)
5173 sysvals.setDeviceFilter(val)
5174 else:
5175 doError('Invalid argument: '+arg, True)
5176
5177 # compatibility errors
5178 if(sysvals.usecallgraph and sysvals.usedevsrc):
5179 doError('-dev is not compatible with -f')
5180 if(sysvals.usecallgraph and sysvals.useprocmon):
5181 doError('-proc is not compatible with -f')
5182
5183 # callgraph size cannot exceed device size
5184 if sysvals.mincglen < sysvals.mindevlen:
5185 sysvals.mincglen = sysvals.mindevlen
5186
5187 # just run a utility command and exit
5188 if(cmd != ''):
5189 if(cmd == 'status'):
5190 statusCheck(True)
5191 elif(cmd == 'fpdt'):
5192 getFPDT(True)
5193 elif(cmd == 'usbtopo'):
5194 detectUSB()
5195 elif(cmd == 'modes'):
5196 print getModes()
5197 elif(cmd == 'flist'):
5198 sysvals.getFtraceFilterFunctions(True)
5199 elif(cmd == 'flistall'):
5200 sysvals.getFtraceFilterFunctions(False)
5201 elif(cmd == 'usbauto'):
5202 setUSBDevicesAuto()
5203 elif(cmd == 'summary'):
5204 print("Generating a summary of folder \"%s\"" % cmdarg)
5205 runSummary(cmdarg, True)
5206 sys.exit()
5207
5208 # if instructed, re-analyze existing data files
5209 if(sysvals.notestrun):
5210 rerunTest()
5211 sys.exit()
5212
5213 # verify that we can run a test
5214 if(not statusCheck()):
5215 print('Check FAILED, aborting the test run!')
5216 sys.exit()
5217
5218 if multitest['run']:
5219 # run multiple tests in a separate subdirectory
5220 s = 'x%d' % multitest['count']
5221 if not sysvals.outdir:
5222 sysvals.outdir = datetime.now().strftime('suspend-'+s+'-%m%d%y-%H%M%S')
5223 if not os.path.isdir(sysvals.outdir):
5224 os.mkdir(sysvals.outdir)
5225 for i in range(multitest['count']):
5226 if(i != 0):
5227 print('Waiting %d seconds...' % (multitest['delay']))
5228 time.sleep(multitest['delay'])
5229 print('TEST (%d/%d) START' % (i+1, multitest['count']))
5230 runTest(sysvals.outdir)
5231 print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count']))
5232 runSummary(sysvals.outdir, False)
5233 else:
5234 # run the test in the current directory
5235 runTest('.', sysvals.outdir)