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