Loading...
1#!/usr/bin/python
2# SPDX-License-Identifier: GPL-2.0-only
3#
4# Tool for analyzing suspend/resume timing
5# Copyright (c) 2013, Intel Corporation.
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms and conditions of the GNU General Public License,
9# version 2, as published by the Free Software Foundation.
10#
11# This program is distributed in the hope it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14# more details.
15#
16# Authors:
17# Todd Brandt <todd.e.brandt@linux.intel.com>
18#
19# Links:
20# Home Page
21# https://01.org/pm-graph
22# Source repo
23# git@github.com:intel/pm-graph
24#
25# Description:
26# This tool is designed to assist kernel and OS developers in optimizing
27# their linux stack's suspend/resume time. Using a kernel image built
28# with a few extra options enabled, the tool will execute a suspend and
29# will capture dmesg and ftrace data until resume is complete. This data
30# is transformed into a device timeline and a callgraph to give a quick
31# and detailed view of which devices and callbacks are taking the most
32# time in suspend/resume. The output is a single html file which can be
33# viewed in firefox or chrome.
34#
35# The following kernel build options are required:
36# CONFIG_DEVMEM=y
37# CONFIG_PM_DEBUG=y
38# CONFIG_PM_SLEEP_DEBUG=y
39# CONFIG_FTRACE=y
40# CONFIG_FUNCTION_TRACER=y
41# CONFIG_FUNCTION_GRAPH_TRACER=y
42# CONFIG_KPROBES=y
43# CONFIG_KPROBES_ON_FTRACE=y
44#
45# For kernel versions older than 3.15:
46# The following additional kernel parameters are required:
47# (e.g. in file /etc/default/grub)
48# GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
49#
50
51# ----------------- LIBRARIES --------------------
52
53import sys
54import time
55import os
56import string
57import re
58import platform
59import signal
60import codecs
61from datetime import datetime
62import struct
63import configparser
64import gzip
65from threading import Thread
66from subprocess import call, Popen, PIPE
67import base64
68
69def pprint(msg):
70 print(msg)
71 sys.stdout.flush()
72
73def ascii(text):
74 return text.decode('ascii', 'ignore')
75
76# ----------------- CLASSES --------------------
77
78# Class: SystemValues
79# Description:
80# A global, single-instance container used to
81# store system values and test parameters
82class SystemValues:
83 title = 'SleepGraph'
84 version = '5.5'
85 ansi = False
86 rs = 0
87 display = ''
88 gzip = False
89 sync = False
90 verbose = False
91 testlog = True
92 dmesglog = True
93 ftracelog = False
94 tstat = True
95 mindevlen = 0.0
96 mincglen = 0.0
97 cgphase = ''
98 cgtest = -1
99 cgskip = ''
100 multitest = {'run': False, 'count': 0, 'delay': 0}
101 max_graph_depth = 0
102 callloopmaxgap = 0.0001
103 callloopmaxlen = 0.005
104 bufsize = 0
105 cpucount = 0
106 memtotal = 204800
107 memfree = 204800
108 srgap = 0
109 cgexp = False
110 testdir = ''
111 outdir = ''
112 tpath = '/sys/kernel/debug/tracing/'
113 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
114 epath = '/sys/kernel/debug/tracing/events/power/'
115 pmdpath = '/sys/power/pm_debug_messages'
116 traceevents = [
117 'suspend_resume',
118 'wakeup_source_activate',
119 'wakeup_source_deactivate',
120 'device_pm_callback_end',
121 'device_pm_callback_start'
122 ]
123 logmsg = ''
124 testcommand = ''
125 mempath = '/dev/mem'
126 powerfile = '/sys/power/state'
127 mempowerfile = '/sys/power/mem_sleep'
128 diskpowerfile = '/sys/power/disk'
129 suspendmode = 'mem'
130 memmode = ''
131 diskmode = ''
132 hostname = 'localhost'
133 prefix = 'test'
134 teststamp = ''
135 sysstamp = ''
136 dmesgstart = 0.0
137 dmesgfile = ''
138 ftracefile = ''
139 htmlfile = 'output.html'
140 result = ''
141 rtcwake = True
142 rtcwaketime = 15
143 rtcpath = ''
144 devicefilter = []
145 cgfilter = []
146 stamp = 0
147 execcount = 1
148 x2delay = 0
149 skiphtml = False
150 usecallgraph = False
151 ftopfunc = 'suspend_devices_and_enter'
152 ftop = False
153 usetraceevents = False
154 usetracemarkers = True
155 usekprobes = True
156 usedevsrc = False
157 useprocmon = False
158 notestrun = False
159 cgdump = False
160 devdump = False
161 mixedphaseheight = True
162 devprops = dict()
163 platinfo = []
164 predelay = 0
165 postdelay = 0
166 pmdebug = ''
167 tracefuncs = {
168 'sys_sync': {},
169 'ksys_sync': {},
170 '__pm_notifier_call_chain': {},
171 'pm_prepare_console': {},
172 'pm_notifier_call_chain': {},
173 'freeze_processes': {},
174 'freeze_kernel_threads': {},
175 'pm_restrict_gfp_mask': {},
176 'acpi_suspend_begin': {},
177 'acpi_hibernation_begin': {},
178 'acpi_hibernation_enter': {},
179 'acpi_hibernation_leave': {},
180 'acpi_pm_freeze': {},
181 'acpi_pm_thaw': {},
182 'acpi_s2idle_end': {},
183 'acpi_s2idle_sync': {},
184 'acpi_s2idle_begin': {},
185 'acpi_s2idle_prepare': {},
186 'acpi_s2idle_wake': {},
187 'acpi_s2idle_wakeup': {},
188 'acpi_s2idle_restore': {},
189 'hibernate_preallocate_memory': {},
190 'create_basic_memory_bitmaps': {},
191 'swsusp_write': {},
192 'suspend_console': {},
193 'acpi_pm_prepare': {},
194 'syscore_suspend': {},
195 'arch_enable_nonboot_cpus_end': {},
196 'syscore_resume': {},
197 'acpi_pm_finish': {},
198 'resume_console': {},
199 'acpi_pm_end': {},
200 'pm_restore_gfp_mask': {},
201 'thaw_processes': {},
202 'pm_restore_console': {},
203 'CPU_OFF': {
204 'func':'_cpu_down',
205 'args_x86_64': {'cpu':'%di:s32'},
206 'format': 'CPU_OFF[{cpu}]'
207 },
208 'CPU_ON': {
209 'func':'_cpu_up',
210 'args_x86_64': {'cpu':'%di:s32'},
211 'format': 'CPU_ON[{cpu}]'
212 },
213 }
214 dev_tracefuncs = {
215 # general wait/delay/sleep
216 'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
217 'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
218 'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
219 'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
220 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
221 'acpi_os_stall': {'ub': 1},
222 'rt_mutex_slowlock': {'ub': 1},
223 # ACPI
224 'acpi_resume_power_resources': {},
225 'acpi_ps_execute_method': { 'args_x86_64': {
226 'fullpath':'+0(+40(%di)):string',
227 }},
228 # mei_me
229 'mei_reset': {},
230 # filesystem
231 'ext4_sync_fs': {},
232 # 80211
233 'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
234 'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
235 'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
236 'iwlagn_mac_start': {},
237 'iwlagn_alloc_bcast_station': {},
238 'iwl_trans_pcie_start_hw': {},
239 'iwl_trans_pcie_start_fw': {},
240 'iwl_run_init_ucode': {},
241 'iwl_load_ucode_wait_alive': {},
242 'iwl_alive_start': {},
243 'iwlagn_mac_stop': {},
244 'iwlagn_mac_suspend': {},
245 'iwlagn_mac_resume': {},
246 'iwlagn_mac_add_interface': {},
247 'iwlagn_mac_remove_interface': {},
248 'iwlagn_mac_change_interface': {},
249 'iwlagn_mac_config': {},
250 'iwlagn_configure_filter': {},
251 'iwlagn_mac_hw_scan': {},
252 'iwlagn_bss_info_changed': {},
253 'iwlagn_mac_channel_switch': {},
254 'iwlagn_mac_flush': {},
255 # ATA
256 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
257 # i915
258 'i915_gem_resume': {},
259 'i915_restore_state': {},
260 'intel_opregion_setup': {},
261 'g4x_pre_enable_dp': {},
262 'vlv_pre_enable_dp': {},
263 'chv_pre_enable_dp': {},
264 'g4x_enable_dp': {},
265 'vlv_enable_dp': {},
266 'intel_hpd_init': {},
267 'intel_opregion_register': {},
268 'intel_dp_detect': {},
269 'intel_hdmi_detect': {},
270 'intel_opregion_init': {},
271 'intel_fbdev_set_suspend': {},
272 }
273 cgblacklist = []
274 kprobes = dict()
275 timeformat = '%.3f'
276 cmdline = '%s %s' % \
277 (os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
278 kparams = ''
279 sudouser = ''
280 def __init__(self):
281 self.archargs = 'args_'+platform.machine()
282 self.hostname = platform.node()
283 if(self.hostname == ''):
284 self.hostname = 'localhost'
285 rtc = "rtc0"
286 if os.path.exists('/dev/rtc'):
287 rtc = os.readlink('/dev/rtc')
288 rtc = '/sys/class/rtc/'+rtc
289 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
290 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
291 self.rtcpath = rtc
292 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
293 self.ansi = True
294 self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
295 if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
296 os.environ['SUDO_USER']:
297 self.sudouser = os.environ['SUDO_USER']
298 def vprint(self, msg):
299 self.logmsg += msg+'\n'
300 if self.verbose or msg.startswith('WARNING:'):
301 pprint(msg)
302 def signalHandler(self, signum, frame):
303 if not self.result:
304 return
305 signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
306 msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
307 sysvals.outputResult({'error':msg})
308 sys.exit(3)
309 def signalHandlerInit(self):
310 capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
311 'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM', 'TSTP']
312 self.signames = dict()
313 for i in capture:
314 s = 'SIG'+i
315 try:
316 signum = getattr(signal, s)
317 signal.signal(signum, self.signalHandler)
318 except:
319 continue
320 self.signames[signum] = s
321 def rootCheck(self, fatal=True):
322 if(os.access(self.powerfile, os.W_OK)):
323 return True
324 if fatal:
325 msg = 'This command requires sysfs mount and root access'
326 pprint('ERROR: %s\n' % msg)
327 self.outputResult({'error':msg})
328 sys.exit(1)
329 return False
330 def rootUser(self, fatal=False):
331 if 'USER' in os.environ and os.environ['USER'] == 'root':
332 return True
333 if fatal:
334 msg = 'This command must be run as root'
335 pprint('ERROR: %s\n' % msg)
336 self.outputResult({'error':msg})
337 sys.exit(1)
338 return False
339 def getExec(self, cmd):
340 try:
341 fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
342 out = ascii(fp.read()).strip()
343 fp.close()
344 except:
345 out = ''
346 if out:
347 return out
348 for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
349 '/usr/local/sbin', '/usr/local/bin']:
350 cmdfull = os.path.join(path, cmd)
351 if os.path.exists(cmdfull):
352 return cmdfull
353 return out
354 def setPrecision(self, num):
355 if num < 0 or num > 6:
356 return
357 self.timeformat = '%.{0}f'.format(num)
358 def setOutputFolder(self, value):
359 args = dict()
360 n = datetime.now()
361 args['date'] = n.strftime('%y%m%d')
362 args['time'] = n.strftime('%H%M%S')
363 args['hostname'] = args['host'] = self.hostname
364 args['mode'] = self.suspendmode
365 return value.format(**args)
366 def setOutputFile(self):
367 if self.dmesgfile != '':
368 m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
369 if(m):
370 self.htmlfile = m.group('name')+'.html'
371 if self.ftracefile != '':
372 m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
373 if(m):
374 self.htmlfile = m.group('name')+'.html'
375 def systemInfo(self, info):
376 p = m = ''
377 if 'baseboard-manufacturer' in info:
378 m = info['baseboard-manufacturer']
379 elif 'system-manufacturer' in info:
380 m = info['system-manufacturer']
381 if 'system-product-name' in info:
382 p = info['system-product-name']
383 elif 'baseboard-product-name' in info:
384 p = info['baseboard-product-name']
385 if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
386 p = info['baseboard-product-name']
387 c = info['processor-version'] if 'processor-version' in info else ''
388 b = info['bios-version'] if 'bios-version' in info else ''
389 r = info['bios-release-date'] if 'bios-release-date' in info else ''
390 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
391 (m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
392 try:
393 kcmd = open('/proc/cmdline', 'r').read().strip()
394 except:
395 kcmd = ''
396 if kcmd:
397 self.sysstamp += '\n# kparams | %s' % kcmd
398 def printSystemInfo(self, fatal=False):
399 self.rootCheck(True)
400 out = dmidecode(self.mempath, fatal)
401 if len(out) < 1:
402 return
403 fmt = '%-24s: %s'
404 for name in sorted(out):
405 print(fmt % (name, out[name]))
406 print(fmt % ('cpucount', ('%d' % self.cpucount)))
407 print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
408 print(fmt % ('memfree', ('%d kB' % self.memfree)))
409 def cpuInfo(self):
410 self.cpucount = 0
411 fp = open('/proc/cpuinfo', 'r')
412 for line in fp:
413 if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
414 self.cpucount += 1
415 fp.close()
416 fp = open('/proc/meminfo', 'r')
417 for line in fp:
418 m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
419 if m:
420 self.memtotal = int(m.group('sz'))
421 m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
422 if m:
423 self.memfree = int(m.group('sz'))
424 fp.close()
425 def initTestOutput(self, name):
426 self.prefix = self.hostname
427 v = open('/proc/version', 'r').read().strip()
428 kver = v.split()[2]
429 fmt = name+'-%m%d%y-%H%M%S'
430 testtime = datetime.now().strftime(fmt)
431 self.teststamp = \
432 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
433 ext = ''
434 if self.gzip:
435 ext = '.gz'
436 self.dmesgfile = \
437 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
438 self.ftracefile = \
439 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
440 self.htmlfile = \
441 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
442 if not os.path.isdir(self.testdir):
443 os.makedirs(self.testdir)
444 def getValueList(self, value):
445 out = []
446 for i in value.split(','):
447 if i.strip():
448 out.append(i.strip())
449 return out
450 def setDeviceFilter(self, value):
451 self.devicefilter = self.getValueList(value)
452 def setCallgraphFilter(self, value):
453 self.cgfilter = self.getValueList(value)
454 def skipKprobes(self, value):
455 for k in self.getValueList(value):
456 if k in self.tracefuncs:
457 del self.tracefuncs[k]
458 if k in self.dev_tracefuncs:
459 del self.dev_tracefuncs[k]
460 def setCallgraphBlacklist(self, file):
461 self.cgblacklist = self.listFromFile(file)
462 def rtcWakeAlarmOn(self):
463 call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
464 nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
465 if nowtime:
466 nowtime = int(nowtime)
467 else:
468 # if hardware time fails, use the software time
469 nowtime = int(datetime.now().strftime('%s'))
470 alarm = nowtime + self.rtcwaketime
471 call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
472 def rtcWakeAlarmOff(self):
473 call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
474 def initdmesg(self):
475 # get the latest time stamp from the dmesg log
476 fp = Popen('dmesg', stdout=PIPE).stdout
477 ktime = '0'
478 for line in fp:
479 line = ascii(line).replace('\r\n', '')
480 idx = line.find('[')
481 if idx > 1:
482 line = line[idx:]
483 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
484 if(m):
485 ktime = m.group('ktime')
486 fp.close()
487 self.dmesgstart = float(ktime)
488 def getdmesg(self, testdata):
489 op = self.writeDatafileHeader(sysvals.dmesgfile, testdata)
490 # store all new dmesg lines since initdmesg was called
491 fp = Popen('dmesg', stdout=PIPE).stdout
492 for line in fp:
493 line = ascii(line).replace('\r\n', '')
494 idx = line.find('[')
495 if idx > 1:
496 line = line[idx:]
497 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
498 if(not m):
499 continue
500 ktime = float(m.group('ktime'))
501 if ktime > self.dmesgstart:
502 op.write(line)
503 fp.close()
504 op.close()
505 def listFromFile(self, file):
506 list = []
507 fp = open(file)
508 for i in fp.read().split('\n'):
509 i = i.strip()
510 if i and i[0] != '#':
511 list.append(i)
512 fp.close()
513 return list
514 def addFtraceFilterFunctions(self, file):
515 for i in self.listFromFile(file):
516 if len(i) < 2:
517 continue
518 self.tracefuncs[i] = dict()
519 def getFtraceFilterFunctions(self, current):
520 self.rootCheck(True)
521 if not current:
522 call('cat '+self.tpath+'available_filter_functions', shell=True)
523 return
524 master = self.listFromFile(self.tpath+'available_filter_functions')
525 for i in sorted(self.tracefuncs):
526 if 'func' in self.tracefuncs[i]:
527 i = self.tracefuncs[i]['func']
528 if i in master:
529 print(i)
530 else:
531 print(self.colorText(i))
532 def setFtraceFilterFunctions(self, list):
533 master = self.listFromFile(self.tpath+'available_filter_functions')
534 flist = ''
535 for i in list:
536 if i not in master:
537 continue
538 if ' [' in i:
539 flist += i.split(' ')[0]+'\n'
540 else:
541 flist += i+'\n'
542 fp = open(self.tpath+'set_graph_function', 'w')
543 fp.write(flist)
544 fp.close()
545 def basicKprobe(self, name):
546 self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
547 def defaultKprobe(self, name, kdata):
548 k = kdata
549 for field in ['name', 'format', 'func']:
550 if field not in k:
551 k[field] = name
552 if self.archargs in k:
553 k['args'] = k[self.archargs]
554 else:
555 k['args'] = dict()
556 k['format'] = name
557 self.kprobes[name] = k
558 def kprobeColor(self, name):
559 if name not in self.kprobes or 'color' not in self.kprobes[name]:
560 return ''
561 return self.kprobes[name]['color']
562 def kprobeDisplayName(self, name, dataraw):
563 if name not in self.kprobes:
564 self.basicKprobe(name)
565 data = ''
566 quote=0
567 # first remvoe any spaces inside quotes, and the quotes
568 for c in dataraw:
569 if c == '"':
570 quote = (quote + 1) % 2
571 if quote and c == ' ':
572 data += '_'
573 elif c != '"':
574 data += c
575 fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
576 arglist = dict()
577 # now process the args
578 for arg in sorted(args):
579 arglist[arg] = ''
580 m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
581 if m:
582 arglist[arg] = m.group('arg')
583 else:
584 m = re.match('.* '+arg+'=(?P<arg>.*)', data);
585 if m:
586 arglist[arg] = m.group('arg')
587 out = fmt.format(**arglist)
588 out = out.replace(' ', '_').replace('"', '')
589 return out
590 def kprobeText(self, kname, kprobe):
591 name = fmt = func = kname
592 args = dict()
593 if 'name' in kprobe:
594 name = kprobe['name']
595 if 'format' in kprobe:
596 fmt = kprobe['format']
597 if 'func' in kprobe:
598 func = kprobe['func']
599 if self.archargs in kprobe:
600 args = kprobe[self.archargs]
601 if 'args' in kprobe:
602 args = kprobe['args']
603 if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
604 doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
605 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
606 if arg not in args:
607 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
608 val = 'p:%s_cal %s' % (name, func)
609 for i in sorted(args):
610 val += ' %s=%s' % (i, args[i])
611 val += '\nr:%s_ret %s $retval\n' % (name, func)
612 return val
613 def addKprobes(self, output=False):
614 if len(self.kprobes) < 1:
615 return
616 if output:
617 pprint(' kprobe functions in this kernel:')
618 # first test each kprobe
619 rejects = []
620 # sort kprobes: trace, ub-dev, custom, dev
621 kpl = [[], [], [], []]
622 linesout = len(self.kprobes)
623 for name in sorted(self.kprobes):
624 res = self.colorText('YES', 32)
625 if not self.testKprobe(name, self.kprobes[name]):
626 res = self.colorText('NO')
627 rejects.append(name)
628 else:
629 if name in self.tracefuncs:
630 kpl[0].append(name)
631 elif name in self.dev_tracefuncs:
632 if 'ub' in self.dev_tracefuncs[name]:
633 kpl[1].append(name)
634 else:
635 kpl[3].append(name)
636 else:
637 kpl[2].append(name)
638 if output:
639 pprint(' %s: %s' % (name, res))
640 kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
641 # remove all failed ones from the list
642 for name in rejects:
643 self.kprobes.pop(name)
644 # set the kprobes all at once
645 self.fsetVal('', 'kprobe_events')
646 kprobeevents = ''
647 for kp in kplist:
648 kprobeevents += self.kprobeText(kp, self.kprobes[kp])
649 self.fsetVal(kprobeevents, 'kprobe_events')
650 if output:
651 check = self.fgetVal('kprobe_events')
652 linesack = (len(check.split('\n')) - 1) // 2
653 pprint(' kprobe functions enabled: %d/%d' % (linesack, linesout))
654 self.fsetVal('1', 'events/kprobes/enable')
655 def testKprobe(self, kname, kprobe):
656 self.fsetVal('0', 'events/kprobes/enable')
657 kprobeevents = self.kprobeText(kname, kprobe)
658 if not kprobeevents:
659 return False
660 try:
661 self.fsetVal(kprobeevents, 'kprobe_events')
662 check = self.fgetVal('kprobe_events')
663 except:
664 return False
665 linesout = len(kprobeevents.split('\n'))
666 linesack = len(check.split('\n'))
667 if linesack < linesout:
668 return False
669 return True
670 def setVal(self, val, file):
671 if not os.path.exists(file):
672 return False
673 try:
674 fp = open(file, 'wb', 0)
675 fp.write(val.encode())
676 fp.flush()
677 fp.close()
678 except:
679 return False
680 return True
681 def fsetVal(self, val, path):
682 return self.setVal(val, self.tpath+path)
683 def getVal(self, file):
684 res = ''
685 if not os.path.exists(file):
686 return res
687 try:
688 fp = open(file, 'r')
689 res = fp.read()
690 fp.close()
691 except:
692 pass
693 return res
694 def fgetVal(self, path):
695 return self.getVal(self.tpath+path)
696 def cleanupFtrace(self):
697 if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
698 self.fsetVal('0', 'events/kprobes/enable')
699 self.fsetVal('', 'kprobe_events')
700 self.fsetVal('1024', 'buffer_size_kb')
701 if self.pmdebug:
702 self.setVal(self.pmdebug, self.pmdpath)
703 def setupAllKprobes(self):
704 for name in self.tracefuncs:
705 self.defaultKprobe(name, self.tracefuncs[name])
706 for name in self.dev_tracefuncs:
707 self.defaultKprobe(name, self.dev_tracefuncs[name])
708 def isCallgraphFunc(self, name):
709 if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
710 return True
711 for i in self.tracefuncs:
712 if 'func' in self.tracefuncs[i]:
713 f = self.tracefuncs[i]['func']
714 else:
715 f = i
716 if name == f:
717 return True
718 return False
719 def initFtrace(self):
720 self.printSystemInfo(False)
721 pprint('INITIALIZING FTRACE...')
722 # turn trace off
723 self.fsetVal('0', 'tracing_on')
724 self.cleanupFtrace()
725 # pm debug messages
726 pv = self.getVal(self.pmdpath)
727 if pv != '1':
728 self.setVal('1', self.pmdpath)
729 self.pmdebug = pv
730 # set the trace clock to global
731 self.fsetVal('global', 'trace_clock')
732 self.fsetVal('nop', 'current_tracer')
733 # set trace buffer to an appropriate value
734 cpus = max(1, self.cpucount)
735 if self.bufsize > 0:
736 tgtsize = self.bufsize
737 elif self.usecallgraph or self.usedevsrc:
738 bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
739 else (3*1024*1024)
740 tgtsize = min(self.memfree, bmax)
741 else:
742 tgtsize = 65536
743 while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
744 # if the size failed to set, lower it and keep trying
745 tgtsize -= 65536
746 if tgtsize < 65536:
747 tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
748 break
749 pprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
750 # initialize the callgraph trace
751 if(self.usecallgraph):
752 # set trace type
753 self.fsetVal('function_graph', 'current_tracer')
754 self.fsetVal('', 'set_ftrace_filter')
755 # set trace format options
756 self.fsetVal('print-parent', 'trace_options')
757 self.fsetVal('funcgraph-abstime', 'trace_options')
758 self.fsetVal('funcgraph-cpu', 'trace_options')
759 self.fsetVal('funcgraph-duration', 'trace_options')
760 self.fsetVal('funcgraph-proc', 'trace_options')
761 self.fsetVal('funcgraph-tail', 'trace_options')
762 self.fsetVal('nofuncgraph-overhead', 'trace_options')
763 self.fsetVal('context-info', 'trace_options')
764 self.fsetVal('graph-time', 'trace_options')
765 self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
766 cf = ['dpm_run_callback']
767 if(self.usetraceevents):
768 cf += ['dpm_prepare', 'dpm_complete']
769 for fn in self.tracefuncs:
770 if 'func' in self.tracefuncs[fn]:
771 cf.append(self.tracefuncs[fn]['func'])
772 else:
773 cf.append(fn)
774 if self.ftop:
775 self.setFtraceFilterFunctions([self.ftopfunc])
776 else:
777 self.setFtraceFilterFunctions(cf)
778 # initialize the kprobe trace
779 elif self.usekprobes:
780 for name in self.tracefuncs:
781 self.defaultKprobe(name, self.tracefuncs[name])
782 if self.usedevsrc:
783 for name in self.dev_tracefuncs:
784 self.defaultKprobe(name, self.dev_tracefuncs[name])
785 pprint('INITIALIZING KPROBES...')
786 self.addKprobes(self.verbose)
787 if(self.usetraceevents):
788 # turn trace events on
789 events = iter(self.traceevents)
790 for e in events:
791 self.fsetVal('1', 'events/power/'+e+'/enable')
792 # clear the trace buffer
793 self.fsetVal('', 'trace')
794 def verifyFtrace(self):
795 # files needed for any trace data
796 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
797 'trace_marker', 'trace_options', 'tracing_on']
798 # files needed for callgraph trace data
799 tp = self.tpath
800 if(self.usecallgraph):
801 files += [
802 'available_filter_functions',
803 'set_ftrace_filter',
804 'set_graph_function'
805 ]
806 for f in files:
807 if(os.path.exists(tp+f) == False):
808 return False
809 return True
810 def verifyKprobes(self):
811 # files needed for kprobes to work
812 files = ['kprobe_events', 'events']
813 tp = self.tpath
814 for f in files:
815 if(os.path.exists(tp+f) == False):
816 return False
817 return True
818 def colorText(self, str, color=31):
819 if not self.ansi:
820 return str
821 return '\x1B[%d;40m%s\x1B[m' % (color, str)
822 def writeDatafileHeader(self, filename, testdata):
823 fp = self.openlog(filename, 'w')
824 fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
825 for test in testdata:
826 if 'fw' in test:
827 fw = test['fw']
828 if(fw):
829 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
830 if 'mcelog' in test:
831 fp.write('# mcelog %s\n' % test['mcelog'])
832 if 'turbo' in test:
833 fp.write('# turbostat %s\n' % test['turbo'])
834 if 'bat' in test:
835 (a1, c1), (a2, c2) = test['bat']
836 fp.write('# battery %s %d %s %d\n' % (a1, c1, a2, c2))
837 if 'wifi' in test:
838 wstr = []
839 for wifi in test['wifi']:
840 tmp = []
841 for key in sorted(wifi):
842 tmp.append('%s:%s' % (key, wifi[key]))
843 wstr.append('|'.join(tmp))
844 fp.write('# wifi %s\n' % (','.join(wstr)))
845 if test['error'] or len(testdata) > 1:
846 fp.write('# enter_sleep_error %s\n' % test['error'])
847 return fp
848 def sudoUserchown(self, dir):
849 if os.path.exists(dir) and self.sudouser:
850 cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
851 call(cmd.format(self.sudouser, dir), shell=True)
852 def outputResult(self, testdata, num=0):
853 if not self.result:
854 return
855 n = ''
856 if num > 0:
857 n = '%d' % num
858 fp = open(self.result, 'a')
859 if 'error' in testdata:
860 fp.write('result%s: fail\n' % n)
861 fp.write('error%s: %s\n' % (n, testdata['error']))
862 else:
863 fp.write('result%s: pass\n' % n)
864 for v in ['suspend', 'resume', 'boot', 'lastinit']:
865 if v in testdata:
866 fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
867 for v in ['fwsuspend', 'fwresume']:
868 if v in testdata:
869 fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
870 if 'bugurl' in testdata:
871 fp.write('url%s: %s\n' % (n, testdata['bugurl']))
872 fp.close()
873 self.sudoUserchown(self.result)
874 def configFile(self, file):
875 dir = os.path.dirname(os.path.realpath(__file__))
876 if os.path.exists(file):
877 return file
878 elif os.path.exists(dir+'/'+file):
879 return dir+'/'+file
880 elif os.path.exists(dir+'/config/'+file):
881 return dir+'/config/'+file
882 return ''
883 def openlog(self, filename, mode):
884 isgz = self.gzip
885 if mode == 'r':
886 try:
887 with gzip.open(filename, mode+'t') as fp:
888 test = fp.read(64)
889 isgz = True
890 except:
891 isgz = False
892 if isgz:
893 return gzip.open(filename, mode+'t')
894 return open(filename, mode)
895 def b64unzip(self, data):
896 try:
897 out = codecs.decode(base64.b64decode(data), 'zlib').decode()
898 except:
899 out = data
900 return out
901 def b64zip(self, data):
902 out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
903 return out
904 def mcelog(self, clear=False):
905 cmd = self.getExec('mcelog')
906 if not cmd:
907 return ''
908 if clear:
909 call(cmd+' > /dev/null 2>&1', shell=True)
910 return ''
911 try:
912 fp = Popen([cmd], stdout=PIPE, stderr=PIPE).stdout
913 out = ascii(fp.read()).strip()
914 fp.close()
915 except:
916 return ''
917 if not out:
918 return ''
919 return self.b64zip(out)
920 def platforminfo(self):
921 # add platform info on to a completed ftrace file
922 if not os.path.exists(self.ftracefile):
923 return False
924 footer = '#\n'
925
926 # add test command string line if need be
927 if self.suspendmode == 'command' and self.testcommand:
928 footer += '# platform-testcmd: %s\n' % (self.testcommand)
929
930 # get a list of target devices from the ftrace file
931 props = dict()
932 tp = TestProps()
933 tf = self.openlog(self.ftracefile, 'r')
934 for line in tf:
935 # determine the trace data type (required for further parsing)
936 m = re.match(tp.tracertypefmt, line)
937 if(m):
938 tp.setTracerType(m.group('t'))
939 continue
940 # parse only valid lines, if this is not one move on
941 m = re.match(tp.ftrace_line_fmt, line)
942 if(not m or 'device_pm_callback_start' not in line):
943 continue
944 m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
945 if(not m):
946 continue
947 dev = m.group('d')
948 if dev not in props:
949 props[dev] = DevProps()
950 tf.close()
951
952 # now get the syspath for each target device
953 for dirname, dirnames, filenames in os.walk('/sys/devices'):
954 if(re.match('.*/power', dirname) and 'async' in filenames):
955 dev = dirname.split('/')[-2]
956 if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
957 props[dev].syspath = dirname[:-6]
958
959 # now fill in the properties for our target devices
960 for dev in sorted(props):
961 dirname = props[dev].syspath
962 if not dirname or not os.path.exists(dirname):
963 continue
964 with open(dirname+'/power/async') as fp:
965 text = fp.read()
966 props[dev].isasync = False
967 if 'enabled' in text:
968 props[dev].isasync = True
969 fields = os.listdir(dirname)
970 if 'product' in fields:
971 with open(dirname+'/product', 'rb') as fp:
972 props[dev].altname = ascii(fp.read())
973 elif 'name' in fields:
974 with open(dirname+'/name', 'rb') as fp:
975 props[dev].altname = ascii(fp.read())
976 elif 'model' in fields:
977 with open(dirname+'/model', 'rb') as fp:
978 props[dev].altname = ascii(fp.read())
979 elif 'description' in fields:
980 with open(dirname+'/description', 'rb') as fp:
981 props[dev].altname = ascii(fp.read())
982 elif 'id' in fields:
983 with open(dirname+'/id', 'rb') as fp:
984 props[dev].altname = ascii(fp.read())
985 elif 'idVendor' in fields and 'idProduct' in fields:
986 idv, idp = '', ''
987 with open(dirname+'/idVendor', 'rb') as fp:
988 idv = ascii(fp.read()).strip()
989 with open(dirname+'/idProduct', 'rb') as fp:
990 idp = ascii(fp.read()).strip()
991 props[dev].altname = '%s:%s' % (idv, idp)
992 if props[dev].altname:
993 out = props[dev].altname.strip().replace('\n', ' ')\
994 .replace(',', ' ').replace(';', ' ')
995 props[dev].altname = out
996
997 # add a devinfo line to the bottom of ftrace
998 out = ''
999 for dev in sorted(props):
1000 out += props[dev].out(dev)
1001 footer += '# platform-devinfo: %s\n' % self.b64zip(out)
1002
1003 # add a line for each of these commands with their outputs
1004 cmds = [
1005 ['pcidevices', 'lspci', '-tv'],
1006 ['interrupts', 'cat', '/proc/interrupts'],
1007 ['gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/gpe*'],
1008 ]
1009 for cargs in cmds:
1010 name = cargs[0]
1011 cmdline = ' '.join(cargs[1:])
1012 cmdpath = self.getExec(cargs[1])
1013 if not cmdpath:
1014 continue
1015 cmd = [cmdpath] + cargs[2:]
1016 try:
1017 fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout
1018 info = ascii(fp.read()).strip()
1019 fp.close()
1020 except:
1021 continue
1022 if not info:
1023 continue
1024 footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
1025
1026 with self.openlog(self.ftracefile, 'a') as fp:
1027 fp.write(footer)
1028 return True
1029 def haveTurbostat(self):
1030 if not self.tstat:
1031 return False
1032 cmd = self.getExec('turbostat')
1033 if not cmd:
1034 return False
1035 fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1036 out = ascii(fp.read()).strip()
1037 fp.close()
1038 if re.match('turbostat version [0-9\.]* .*', out):
1039 sysvals.vprint(out)
1040 return True
1041 return False
1042 def turbostat(self):
1043 cmd = self.getExec('turbostat')
1044 rawout = keyline = valline = ''
1045 fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1046 fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr
1047 for line in fp:
1048 line = ascii(line)
1049 rawout += line
1050 if keyline and valline:
1051 continue
1052 if re.match('(?i)Avg_MHz.*', line):
1053 keyline = line.strip().split()
1054 elif keyline:
1055 valline = line.strip().split()
1056 fp.close()
1057 if not keyline or not valline or len(keyline) != len(valline):
1058 errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1059 sysvals.vprint(errmsg)
1060 if not sysvals.verbose:
1061 pprint(errmsg)
1062 return ''
1063 if sysvals.verbose:
1064 pprint(rawout.strip())
1065 out = []
1066 for key in keyline:
1067 idx = keyline.index(key)
1068 val = valline[idx]
1069 out.append('%s=%s' % (key, val))
1070 return '|'.join(out)
1071 def checkWifi(self):
1072 out = dict()
1073 iwcmd, ifcmd = self.getExec('iwconfig'), self.getExec('ifconfig')
1074 if not iwcmd or not ifcmd:
1075 return out
1076 fp = Popen(iwcmd, stdout=PIPE, stderr=PIPE).stdout
1077 for line in fp:
1078 m = re.match('(?P<dev>\S*) .* ESSID:(?P<ess>\S*)', ascii(line))
1079 if not m:
1080 continue
1081 out['device'] = m.group('dev')
1082 if '"' in m.group('ess'):
1083 out['essid'] = m.group('ess').strip('"')
1084 break
1085 fp.close()
1086 if 'device' in out:
1087 fp = Popen([ifcmd, out['device']], stdout=PIPE, stderr=PIPE).stdout
1088 for line in fp:
1089 m = re.match('.* inet (?P<ip>[0-9\.]*)', ascii(line))
1090 if m:
1091 out['ip'] = m.group('ip')
1092 break
1093 fp.close()
1094 return out
1095 def errorSummary(self, errinfo, msg):
1096 found = False
1097 for entry in errinfo:
1098 if re.match(entry['match'], msg):
1099 entry['count'] += 1
1100 if self.hostname not in entry['urls']:
1101 entry['urls'][self.hostname] = [self.htmlfile]
1102 elif self.htmlfile not in entry['urls'][self.hostname]:
1103 entry['urls'][self.hostname].append(self.htmlfile)
1104 found = True
1105 break
1106 if found:
1107 return
1108 arr = msg.split()
1109 for j in range(len(arr)):
1110 if re.match('^[0-9,\-\.]*$', arr[j]):
1111 arr[j] = '[0-9,\-\.]*'
1112 else:
1113 arr[j] = arr[j]\
1114 .replace('\\', '\\\\').replace(']', '\]').replace('[', '\[')\
1115 .replace('.', '\.').replace('+', '\+').replace('*', '\*')\
1116 .replace('(', '\(').replace(')', '\)')
1117 mstr = ' '.join(arr)
1118 entry = {
1119 'line': msg,
1120 'match': mstr,
1121 'count': 1,
1122 'urls': {self.hostname: [self.htmlfile]}
1123 }
1124 errinfo.append(entry)
1125
1126sysvals = SystemValues()
1127switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1128switchoff = ['disable', 'off', 'false', '0']
1129suspendmodename = {
1130 'freeze': 'Freeze (S0)',
1131 'standby': 'Standby (S1)',
1132 'mem': 'Suspend (S3)',
1133 'disk': 'Hibernate (S4)'
1134}
1135
1136# Class: DevProps
1137# Description:
1138# Simple class which holds property values collected
1139# for all the devices used in the timeline.
1140class DevProps:
1141 def __init__(self):
1142 self.syspath = ''
1143 self.altname = ''
1144 self.isasync = True
1145 self.xtraclass = ''
1146 self.xtrainfo = ''
1147 def out(self, dev):
1148 return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1149 def debug(self, dev):
1150 pprint('%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.isasync))
1151 def altName(self, dev):
1152 if not self.altname or self.altname == dev:
1153 return dev
1154 return '%s [%s]' % (self.altname, dev)
1155 def xtraClass(self):
1156 if self.xtraclass:
1157 return ' '+self.xtraclass
1158 if not self.isasync:
1159 return ' sync'
1160 return ''
1161 def xtraInfo(self):
1162 if self.xtraclass:
1163 return ' '+self.xtraclass
1164 if self.isasync:
1165 return ' async_device'
1166 return ' sync_device'
1167
1168# Class: DeviceNode
1169# Description:
1170# A container used to create a device hierachy, with a single root node
1171# and a tree of child nodes. Used by Data.deviceTopology()
1172class DeviceNode:
1173 def __init__(self, nodename, nodedepth):
1174 self.name = nodename
1175 self.children = []
1176 self.depth = nodedepth
1177
1178# Class: Data
1179# Description:
1180# The primary container for suspend/resume test data. There is one for
1181# each test run. The data is organized into a cronological hierarchy:
1182# Data.dmesg {
1183# phases {
1184# 10 sequential, non-overlapping phases of S/R
1185# contents: times for phase start/end, order/color data for html
1186# devlist {
1187# device callback or action list for this phase
1188# device {
1189# a single device callback or generic action
1190# contents: start/stop times, pid/cpu/driver info
1191# parents/children, html id for timeline/callgraph
1192# optionally includes an ftrace callgraph
1193# optionally includes dev/ps data
1194# }
1195# }
1196# }
1197# }
1198#
1199class Data:
1200 phasedef = {
1201 'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1202 'suspend': {'order': 1, 'color': '#88FF88'},
1203 'suspend_late': {'order': 2, 'color': '#00AA00'},
1204 'suspend_noirq': {'order': 3, 'color': '#008888'},
1205 'suspend_machine': {'order': 4, 'color': '#0000FF'},
1206 'resume_machine': {'order': 5, 'color': '#FF0000'},
1207 'resume_noirq': {'order': 6, 'color': '#FF9900'},
1208 'resume_early': {'order': 7, 'color': '#FFCC00'},
1209 'resume': {'order': 8, 'color': '#FFFF88'},
1210 'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1211 }
1212 errlist = {
1213 'HWERROR' : '.*\[ *Hardware Error *\].*',
1214 'FWBUG' : '.*\[ *Firmware Bug *\].*',
1215 'BUG' : '.*BUG.*',
1216 'ERROR' : '.*ERROR.*',
1217 'WARNING' : '.*WARNING.*',
1218 'IRQ' : '.*genirq: .*',
1219 'TASKFAIL': '.*Freezing of tasks *.*',
1220 'ACPI' : '.*ACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1221 'DEVFAIL' : '.* failed to (?P<b>[a-z]*) async: .*',
1222 'DISKFULL': '.*No space left on device.*',
1223 'USBERR' : '.*usb .*device .*, error [0-9-]*',
1224 'ATAERR' : ' *ata[0-9\.]*: .*failed.*',
1225 'MEIERR' : ' *mei.*: .*failed.*',
1226 'TPMERR' : '(?i) *tpm *tpm[0-9]*: .*error.*',
1227 }
1228 def __init__(self, num):
1229 idchar = 'abcdefghij'
1230 self.start = 0.0 # test start
1231 self.end = 0.0 # test end
1232 self.tSuspended = 0.0 # low-level suspend start
1233 self.tResumed = 0.0 # low-level resume start
1234 self.tKernSus = 0.0 # kernel level suspend start
1235 self.tKernRes = 0.0 # kernel level resume end
1236 self.fwValid = False # is firmware data available
1237 self.fwSuspend = 0 # time spent in firmware suspend
1238 self.fwResume = 0 # time spent in firmware resume
1239 self.html_device_id = 0
1240 self.stamp = 0
1241 self.outfile = ''
1242 self.kerror = False
1243 self.battery = 0
1244 self.wifi = 0
1245 self.turbostat = 0
1246 self.mcelog = 0
1247 self.enterfail = ''
1248 self.currphase = ''
1249 self.pstl = dict() # process timeline
1250 self.testnumber = num
1251 self.idstr = idchar[num]
1252 self.dmesgtext = [] # dmesg text file in memory
1253 self.dmesg = dict() # root data structure
1254 self.errorinfo = {'suspend':[],'resume':[]}
1255 self.tLow = [] # time spent in low-level suspends (standby/freeze)
1256 self.devpids = []
1257 self.devicegroups = 0
1258 def sortedPhases(self):
1259 return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1260 def initDevicegroups(self):
1261 # called when phases are all finished being added
1262 for phase in sorted(self.dmesg.keys()):
1263 if '*' in phase:
1264 p = phase.split('*')
1265 pnew = '%s%d' % (p[0], len(p))
1266 self.dmesg[pnew] = self.dmesg.pop(phase)
1267 self.devicegroups = []
1268 for phase in self.sortedPhases():
1269 self.devicegroups.append([phase])
1270 def nextPhase(self, phase, offset):
1271 order = self.dmesg[phase]['order'] + offset
1272 for p in self.dmesg:
1273 if self.dmesg[p]['order'] == order:
1274 return p
1275 return ''
1276 def lastPhase(self):
1277 plist = self.sortedPhases()
1278 if len(plist) < 1:
1279 return ''
1280 return plist[-1]
1281 def turbostatInfo(self):
1282 tp = TestProps()
1283 out = {'syslpi':'N/A','pkgpc10':'N/A'}
1284 for line in self.dmesgtext:
1285 m = re.match(tp.tstatfmt, line)
1286 if not m:
1287 continue
1288 for i in m.group('t').split('|'):
1289 if 'SYS%LPI' in i:
1290 out['syslpi'] = i.split('=')[-1]+'%'
1291 elif 'pc10' in i:
1292 out['pkgpc10'] = i.split('=')[-1]+'%'
1293 break
1294 return out
1295 def extractErrorInfo(self):
1296 lf = self.dmesgtext
1297 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1298 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1299 i = 0
1300 list = []
1301 for line in lf:
1302 i += 1
1303 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1304 if not m:
1305 continue
1306 t = float(m.group('ktime'))
1307 if t < self.start or t > self.end:
1308 continue
1309 dir = 'suspend' if t < self.tSuspended else 'resume'
1310 msg = m.group('msg')
1311 for err in self.errlist:
1312 if re.match(self.errlist[err], msg):
1313 list.append((msg, err, dir, t, i, i))
1314 self.kerror = True
1315 break
1316 msglist = []
1317 for msg, type, dir, t, idx1, idx2 in list:
1318 msglist.append(msg)
1319 sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t))
1320 self.errorinfo[dir].append((type, t, idx1, idx2))
1321 if self.kerror:
1322 sysvals.dmesglog = True
1323 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1324 lf.close()
1325 return msglist
1326 def setStart(self, time):
1327 self.start = time
1328 def setEnd(self, time):
1329 self.end = time
1330 def isTraceEventOutsideDeviceCalls(self, pid, time):
1331 for phase in self.sortedPhases():
1332 list = self.dmesg[phase]['list']
1333 for dev in list:
1334 d = list[dev]
1335 if(d['pid'] == pid and time >= d['start'] and
1336 time < d['end']):
1337 return False
1338 return True
1339 def sourcePhase(self, start):
1340 for phase in self.sortedPhases():
1341 if 'machine' in phase:
1342 continue
1343 pend = self.dmesg[phase]['end']
1344 if start <= pend:
1345 return phase
1346 return 'resume_complete'
1347 def sourceDevice(self, phaselist, start, end, pid, type):
1348 tgtdev = ''
1349 for phase in phaselist:
1350 list = self.dmesg[phase]['list']
1351 for devname in list:
1352 dev = list[devname]
1353 # pid must match
1354 if dev['pid'] != pid:
1355 continue
1356 devS = dev['start']
1357 devE = dev['end']
1358 if type == 'device':
1359 # device target event is entirely inside the source boundary
1360 if(start < devS or start >= devE or end <= devS or end > devE):
1361 continue
1362 elif type == 'thread':
1363 # thread target event will expand the source boundary
1364 if start < devS:
1365 dev['start'] = start
1366 if end > devE:
1367 dev['end'] = end
1368 tgtdev = dev
1369 break
1370 return tgtdev
1371 def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1372 # try to place the call in a device
1373 phases = self.sortedPhases()
1374 tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1375 # calls with device pids that occur outside device bounds are dropped
1376 # TODO: include these somehow
1377 if not tgtdev and pid in self.devpids:
1378 return False
1379 # try to place the call in a thread
1380 if not tgtdev:
1381 tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1382 # create new thread blocks, expand as new calls are found
1383 if not tgtdev:
1384 if proc == '<...>':
1385 threadname = 'kthread-%d' % (pid)
1386 else:
1387 threadname = '%s-%d' % (proc, pid)
1388 tgtphase = self.sourcePhase(start)
1389 self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1390 return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1391 # this should not happen
1392 if not tgtdev:
1393 sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1394 (start, end, proc, pid, kprobename, cdata, rdata))
1395 return False
1396 # place the call data inside the src element of the tgtdev
1397 if('src' not in tgtdev):
1398 tgtdev['src'] = []
1399 dtf = sysvals.dev_tracefuncs
1400 ubiquitous = False
1401 if kprobename in dtf and 'ub' in dtf[kprobename]:
1402 ubiquitous = True
1403 title = cdata+' '+rdata
1404 mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1405 m = re.match(mstr, title)
1406 if m:
1407 c = m.group('caller')
1408 a = m.group('args').strip()
1409 r = m.group('ret')
1410 if len(r) > 6:
1411 r = ''
1412 else:
1413 r = 'ret=%s ' % r
1414 if ubiquitous and c in dtf and 'ub' in dtf[c]:
1415 return False
1416 color = sysvals.kprobeColor(kprobename)
1417 e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1418 tgtdev['src'].append(e)
1419 return True
1420 def overflowDevices(self):
1421 # get a list of devices that extend beyond the end of this test run
1422 devlist = []
1423 for phase in self.sortedPhases():
1424 list = self.dmesg[phase]['list']
1425 for devname in list:
1426 dev = list[devname]
1427 if dev['end'] > self.end:
1428 devlist.append(dev)
1429 return devlist
1430 def mergeOverlapDevices(self, devlist):
1431 # merge any devices that overlap devlist
1432 for dev in devlist:
1433 devname = dev['name']
1434 for phase in self.sortedPhases():
1435 list = self.dmesg[phase]['list']
1436 if devname not in list:
1437 continue
1438 tdev = list[devname]
1439 o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1440 if o <= 0:
1441 continue
1442 dev['end'] = tdev['end']
1443 if 'src' not in dev or 'src' not in tdev:
1444 continue
1445 dev['src'] += tdev['src']
1446 del list[devname]
1447 def usurpTouchingThread(self, name, dev):
1448 # the caller test has priority of this thread, give it to him
1449 for phase in self.sortedPhases():
1450 list = self.dmesg[phase]['list']
1451 if name in list:
1452 tdev = list[name]
1453 if tdev['start'] - dev['end'] < 0.1:
1454 dev['end'] = tdev['end']
1455 if 'src' not in dev:
1456 dev['src'] = []
1457 if 'src' in tdev:
1458 dev['src'] += tdev['src']
1459 del list[name]
1460 break
1461 def stitchTouchingThreads(self, testlist):
1462 # merge any threads between tests that touch
1463 for phase in self.sortedPhases():
1464 list = self.dmesg[phase]['list']
1465 for devname in list:
1466 dev = list[devname]
1467 if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1468 continue
1469 for data in testlist:
1470 data.usurpTouchingThread(devname, dev)
1471 def optimizeDevSrc(self):
1472 # merge any src call loops to reduce timeline size
1473 for phase in self.sortedPhases():
1474 list = self.dmesg[phase]['list']
1475 for dev in list:
1476 if 'src' not in list[dev]:
1477 continue
1478 src = list[dev]['src']
1479 p = 0
1480 for e in sorted(src, key=lambda event: event.time):
1481 if not p or not e.repeat(p):
1482 p = e
1483 continue
1484 # e is another iteration of p, move it into p
1485 p.end = e.end
1486 p.length = p.end - p.time
1487 p.count += 1
1488 src.remove(e)
1489 def trimTimeVal(self, t, t0, dT, left):
1490 if left:
1491 if(t > t0):
1492 if(t - dT < t0):
1493 return t0
1494 return t - dT
1495 else:
1496 return t
1497 else:
1498 if(t < t0 + dT):
1499 if(t > t0):
1500 return t0 + dT
1501 return t + dT
1502 else:
1503 return t
1504 def trimTime(self, t0, dT, left):
1505 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1506 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1507 self.start = self.trimTimeVal(self.start, t0, dT, left)
1508 self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1509 self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1510 self.end = self.trimTimeVal(self.end, t0, dT, left)
1511 for phase in self.sortedPhases():
1512 p = self.dmesg[phase]
1513 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1514 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1515 list = p['list']
1516 for name in list:
1517 d = list[name]
1518 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1519 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1520 d['length'] = d['end'] - d['start']
1521 if('ftrace' in d):
1522 cg = d['ftrace']
1523 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1524 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1525 for line in cg.list:
1526 line.time = self.trimTimeVal(line.time, t0, dT, left)
1527 if('src' in d):
1528 for e in d['src']:
1529 e.time = self.trimTimeVal(e.time, t0, dT, left)
1530 for dir in ['suspend', 'resume']:
1531 list = []
1532 for e in self.errorinfo[dir]:
1533 type, tm, idx1, idx2 = e
1534 tm = self.trimTimeVal(tm, t0, dT, left)
1535 list.append((type, tm, idx1, idx2))
1536 self.errorinfo[dir] = list
1537 def trimFreezeTime(self, tZero):
1538 # trim out any standby or freeze clock time
1539 lp = ''
1540 for phase in self.sortedPhases():
1541 if 'resume_machine' in phase and 'suspend_machine' in lp:
1542 tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1543 tL = tR - tS
1544 if tL > 0:
1545 left = True if tR > tZero else False
1546 self.trimTime(tS, tL, left)
1547 self.tLow.append('%.0f'%(tL*1000))
1548 lp = phase
1549 def getTimeValues(self):
1550 sktime = (self.tSuspended - self.tKernSus) * 1000
1551 rktime = (self.tKernRes - self.tResumed) * 1000
1552 return (sktime, rktime)
1553 def setPhase(self, phase, ktime, isbegin, order=-1):
1554 if(isbegin):
1555 # phase start over current phase
1556 if self.currphase:
1557 if 'resume_machine' not in self.currphase:
1558 sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1559 self.dmesg[self.currphase]['end'] = ktime
1560 phases = self.dmesg.keys()
1561 color = self.phasedef[phase]['color']
1562 count = len(phases) if order < 0 else order
1563 # create unique name for every new phase
1564 while phase in phases:
1565 phase += '*'
1566 self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1567 'row': 0, 'color': color, 'order': count}
1568 self.dmesg[phase]['start'] = ktime
1569 self.currphase = phase
1570 else:
1571 # phase end without a start
1572 if phase not in self.currphase:
1573 if self.currphase:
1574 sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1575 else:
1576 sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1577 return phase
1578 phase = self.currphase
1579 self.dmesg[phase]['end'] = ktime
1580 self.currphase = ''
1581 return phase
1582 def sortedDevices(self, phase):
1583 list = self.dmesg[phase]['list']
1584 return sorted(list, key=lambda k:list[k]['start'])
1585 def fixupInitcalls(self, phase):
1586 # if any calls never returned, clip them at system resume end
1587 phaselist = self.dmesg[phase]['list']
1588 for devname in phaselist:
1589 dev = phaselist[devname]
1590 if(dev['end'] < 0):
1591 for p in self.sortedPhases():
1592 if self.dmesg[p]['end'] > dev['start']:
1593 dev['end'] = self.dmesg[p]['end']
1594 break
1595 sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1596 def deviceFilter(self, devicefilter):
1597 for phase in self.sortedPhases():
1598 list = self.dmesg[phase]['list']
1599 rmlist = []
1600 for name in list:
1601 keep = False
1602 for filter in devicefilter:
1603 if filter in name or \
1604 ('drv' in list[name] and filter in list[name]['drv']):
1605 keep = True
1606 if not keep:
1607 rmlist.append(name)
1608 for name in rmlist:
1609 del list[name]
1610 def fixupInitcallsThatDidntReturn(self):
1611 # if any calls never returned, clip them at system resume end
1612 for phase in self.sortedPhases():
1613 self.fixupInitcalls(phase)
1614 def phaseOverlap(self, phases):
1615 rmgroups = []
1616 newgroup = []
1617 for group in self.devicegroups:
1618 for phase in phases:
1619 if phase not in group:
1620 continue
1621 for p in group:
1622 if p not in newgroup:
1623 newgroup.append(p)
1624 if group not in rmgroups:
1625 rmgroups.append(group)
1626 for group in rmgroups:
1627 self.devicegroups.remove(group)
1628 self.devicegroups.append(newgroup)
1629 def newActionGlobal(self, name, start, end, pid=-1, color=''):
1630 # which phase is this device callback or action in
1631 phases = self.sortedPhases()
1632 targetphase = 'none'
1633 htmlclass = ''
1634 overlap = 0.0
1635 myphases = []
1636 for phase in phases:
1637 pstart = self.dmesg[phase]['start']
1638 pend = self.dmesg[phase]['end']
1639 # see if the action overlaps this phase
1640 o = max(0, min(end, pend) - max(start, pstart))
1641 if o > 0:
1642 myphases.append(phase)
1643 # set the target phase to the one that overlaps most
1644 if o > overlap:
1645 if overlap > 0 and phase == 'post_resume':
1646 continue
1647 targetphase = phase
1648 overlap = o
1649 # if no target phase was found, pin it to the edge
1650 if targetphase == 'none':
1651 p0start = self.dmesg[phases[0]]['start']
1652 if start <= p0start:
1653 targetphase = phases[0]
1654 else:
1655 targetphase = phases[-1]
1656 if pid == -2:
1657 htmlclass = ' bg'
1658 elif pid == -3:
1659 htmlclass = ' ps'
1660 if len(myphases) > 1:
1661 htmlclass = ' bg'
1662 self.phaseOverlap(myphases)
1663 if targetphase in phases:
1664 newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1665 return (targetphase, newname)
1666 return False
1667 def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1668 # new device callback for a specific phase
1669 self.html_device_id += 1
1670 devid = '%s%d' % (self.idstr, self.html_device_id)
1671 list = self.dmesg[phase]['list']
1672 length = -1.0
1673 if(start >= 0 and end >= 0):
1674 length = end - start
1675 if pid == -2:
1676 i = 2
1677 origname = name
1678 while(name in list):
1679 name = '%s[%d]' % (origname, i)
1680 i += 1
1681 list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1682 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1683 if htmlclass:
1684 list[name]['htmlclass'] = htmlclass
1685 if color:
1686 list[name]['color'] = color
1687 return name
1688 def deviceChildren(self, devname, phase):
1689 devlist = []
1690 list = self.dmesg[phase]['list']
1691 for child in list:
1692 if(list[child]['par'] == devname):
1693 devlist.append(child)
1694 return devlist
1695 def maxDeviceNameSize(self, phase):
1696 size = 0
1697 for name in self.dmesg[phase]['list']:
1698 if len(name) > size:
1699 size = len(name)
1700 return size
1701 def printDetails(self):
1702 sysvals.vprint('Timeline Details:')
1703 sysvals.vprint(' test start: %f' % self.start)
1704 sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1705 tS = tR = False
1706 for phase in self.sortedPhases():
1707 devlist = self.dmesg[phase]['list']
1708 dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
1709 if not tS and ps >= self.tSuspended:
1710 sysvals.vprint(' machine suspended: %f' % self.tSuspended)
1711 tS = True
1712 if not tR and ps >= self.tResumed:
1713 sysvals.vprint(' machine resumed: %f' % self.tResumed)
1714 tR = True
1715 sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
1716 if sysvals.devdump:
1717 sysvals.vprint(''.join('-' for i in range(80)))
1718 maxname = '%d' % self.maxDeviceNameSize(phase)
1719 fmt = '%3d) %'+maxname+'s - %f - %f'
1720 c = 1
1721 for name in sorted(devlist):
1722 s = devlist[name]['start']
1723 e = devlist[name]['end']
1724 sysvals.vprint(fmt % (c, name, s, e))
1725 c += 1
1726 sysvals.vprint(''.join('-' for i in range(80)))
1727 sysvals.vprint(' kernel resume end: %f' % self.tKernRes)
1728 sysvals.vprint(' test end: %f' % self.end)
1729 def deviceChildrenAllPhases(self, devname):
1730 devlist = []
1731 for phase in self.sortedPhases():
1732 list = self.deviceChildren(devname, phase)
1733 for dev in sorted(list):
1734 if dev not in devlist:
1735 devlist.append(dev)
1736 return devlist
1737 def masterTopology(self, name, list, depth):
1738 node = DeviceNode(name, depth)
1739 for cname in list:
1740 # avoid recursions
1741 if name == cname:
1742 continue
1743 clist = self.deviceChildrenAllPhases(cname)
1744 cnode = self.masterTopology(cname, clist, depth+1)
1745 node.children.append(cnode)
1746 return node
1747 def printTopology(self, node):
1748 html = ''
1749 if node.name:
1750 info = ''
1751 drv = ''
1752 for phase in self.sortedPhases():
1753 list = self.dmesg[phase]['list']
1754 if node.name in list:
1755 s = list[node.name]['start']
1756 e = list[node.name]['end']
1757 if list[node.name]['drv']:
1758 drv = ' {'+list[node.name]['drv']+'}'
1759 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1760 html += '<li><b>'+node.name+drv+'</b>'
1761 if info:
1762 html += '<ul>'+info+'</ul>'
1763 html += '</li>'
1764 if len(node.children) > 0:
1765 html += '<ul>'
1766 for cnode in node.children:
1767 html += self.printTopology(cnode)
1768 html += '</ul>'
1769 return html
1770 def rootDeviceList(self):
1771 # list of devices graphed
1772 real = []
1773 for phase in self.sortedPhases():
1774 list = self.dmesg[phase]['list']
1775 for dev in sorted(list):
1776 if list[dev]['pid'] >= 0 and dev not in real:
1777 real.append(dev)
1778 # list of top-most root devices
1779 rootlist = []
1780 for phase in self.sortedPhases():
1781 list = self.dmesg[phase]['list']
1782 for dev in sorted(list):
1783 pdev = list[dev]['par']
1784 pid = list[dev]['pid']
1785 if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1786 continue
1787 if pdev and pdev not in real and pdev not in rootlist:
1788 rootlist.append(pdev)
1789 return rootlist
1790 def deviceTopology(self):
1791 rootlist = self.rootDeviceList()
1792 master = self.masterTopology('', rootlist, 0)
1793 return self.printTopology(master)
1794 def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1795 # only select devices that will actually show up in html
1796 self.tdevlist = dict()
1797 for phase in self.dmesg:
1798 devlist = []
1799 list = self.dmesg[phase]['list']
1800 for dev in list:
1801 length = (list[dev]['end'] - list[dev]['start']) * 1000
1802 width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1803 if width != '0.000000' and length >= mindevlen:
1804 devlist.append(dev)
1805 self.tdevlist[phase] = devlist
1806 def addHorizontalDivider(self, devname, devend):
1807 phase = 'suspend_prepare'
1808 self.newAction(phase, devname, -2, '', \
1809 self.start, devend, '', ' sec', '')
1810 if phase not in self.tdevlist:
1811 self.tdevlist[phase] = []
1812 self.tdevlist[phase].append(devname)
1813 d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1814 return d
1815 def addProcessUsageEvent(self, name, times):
1816 # get the start and end times for this process
1817 maxC = 0
1818 tlast = 0
1819 start = -1
1820 end = -1
1821 for t in sorted(times):
1822 if tlast == 0:
1823 tlast = t
1824 continue
1825 if name in self.pstl[t]:
1826 if start == -1 or tlast < start:
1827 start = tlast
1828 if end == -1 or t > end:
1829 end = t
1830 tlast = t
1831 if start == -1 or end == -1:
1832 return 0
1833 # add a new action for this process and get the object
1834 out = self.newActionGlobal(name, start, end, -3)
1835 if not out:
1836 return 0
1837 phase, devname = out
1838 dev = self.dmesg[phase]['list'][devname]
1839 # get the cpu exec data
1840 tlast = 0
1841 clast = 0
1842 cpuexec = dict()
1843 for t in sorted(times):
1844 if tlast == 0 or t <= start or t > end:
1845 tlast = t
1846 continue
1847 list = self.pstl[t]
1848 c = 0
1849 if name in list:
1850 c = list[name]
1851 if c > maxC:
1852 maxC = c
1853 if c != clast:
1854 key = (tlast, t)
1855 cpuexec[key] = c
1856 tlast = t
1857 clast = c
1858 dev['cpuexec'] = cpuexec
1859 return maxC
1860 def createProcessUsageEvents(self):
1861 # get an array of process names
1862 proclist = []
1863 for t in sorted(self.pstl):
1864 pslist = self.pstl[t]
1865 for ps in sorted(pslist):
1866 if ps not in proclist:
1867 proclist.append(ps)
1868 # get a list of data points for suspend and resume
1869 tsus = []
1870 tres = []
1871 for t in sorted(self.pstl):
1872 if t < self.tSuspended:
1873 tsus.append(t)
1874 else:
1875 tres.append(t)
1876 # process the events for suspend and resume
1877 if len(proclist) > 0:
1878 sysvals.vprint('Process Execution:')
1879 for ps in proclist:
1880 c = self.addProcessUsageEvent(ps, tsus)
1881 if c > 0:
1882 sysvals.vprint('%25s (sus): %d' % (ps, c))
1883 c = self.addProcessUsageEvent(ps, tres)
1884 if c > 0:
1885 sysvals.vprint('%25s (res): %d' % (ps, c))
1886 def handleEndMarker(self, time):
1887 dm = self.dmesg
1888 self.setEnd(time)
1889 self.initDevicegroups()
1890 # give suspend_prepare an end if needed
1891 if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
1892 dm['suspend_prepare']['end'] = time
1893 # assume resume machine ends at next phase start
1894 if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
1895 np = self.nextPhase('resume_machine', 1)
1896 if np:
1897 dm['resume_machine']['end'] = dm[np]['start']
1898 # if kernel resume end not found, assume its the end marker
1899 if self.tKernRes == 0.0:
1900 self.tKernRes = time
1901 # if kernel suspend start not found, assume its the end marker
1902 if self.tKernSus == 0.0:
1903 self.tKernSus = time
1904 # set resume complete to end at end marker
1905 if 'resume_complete' in dm:
1906 dm['resume_complete']['end'] = time
1907 def debugPrint(self):
1908 for p in self.sortedPhases():
1909 list = self.dmesg[p]['list']
1910 for devname in sorted(list):
1911 dev = list[devname]
1912 if 'ftrace' in dev:
1913 dev['ftrace'].debugPrint(' [%s]' % devname)
1914
1915# Class: DevFunction
1916# Description:
1917# A container for kprobe function data we want in the dev timeline
1918class DevFunction:
1919 def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
1920 self.row = 0
1921 self.count = 1
1922 self.name = name
1923 self.args = args
1924 self.caller = caller
1925 self.ret = ret
1926 self.time = start
1927 self.length = end - start
1928 self.end = end
1929 self.ubiquitous = u
1930 self.proc = proc
1931 self.pid = pid
1932 self.color = color
1933 def title(self):
1934 cnt = ''
1935 if self.count > 1:
1936 cnt = '(x%d)' % self.count
1937 l = '%0.3fms' % (self.length * 1000)
1938 if self.ubiquitous:
1939 title = '%s(%s)%s <- %s, %s(%s)' % \
1940 (self.name, self.args, cnt, self.caller, self.ret, l)
1941 else:
1942 title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
1943 return title.replace('"', '')
1944 def text(self):
1945 if self.count > 1:
1946 text = '%s(x%d)' % (self.name, self.count)
1947 else:
1948 text = self.name
1949 return text
1950 def repeat(self, tgt):
1951 # is the tgt call just a repeat of this call (e.g. are we in a loop)
1952 dt = self.time - tgt.end
1953 # only combine calls if -all- attributes are identical
1954 if tgt.caller == self.caller and \
1955 tgt.name == self.name and tgt.args == self.args and \
1956 tgt.proc == self.proc and tgt.pid == self.pid and \
1957 tgt.ret == self.ret and dt >= 0 and \
1958 dt <= sysvals.callloopmaxgap and \
1959 self.length < sysvals.callloopmaxlen:
1960 return True
1961 return False
1962
1963# Class: FTraceLine
1964# Description:
1965# A container for a single line of ftrace data. There are six basic types:
1966# callgraph line:
1967# call: " dpm_run_callback() {"
1968# return: " }"
1969# leaf: " dpm_run_callback();"
1970# trace event:
1971# tracing_mark_write: SUSPEND START or RESUME COMPLETE
1972# suspend_resume: phase or custom exec block data
1973# device_pm_callback: device callback info
1974class FTraceLine:
1975 def __init__(self, t, m='', d=''):
1976 self.length = 0.0
1977 self.fcall = False
1978 self.freturn = False
1979 self.fevent = False
1980 self.fkprobe = False
1981 self.depth = 0
1982 self.name = ''
1983 self.type = ''
1984 self.time = float(t)
1985 if not m and not d:
1986 return
1987 # is this a trace event
1988 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1989 if(d == 'traceevent'):
1990 # nop format trace event
1991 msg = m
1992 else:
1993 # function_graph format trace event
1994 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1995 msg = em.group('msg')
1996
1997 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1998 if(emm):
1999 self.name = emm.group('msg')
2000 self.type = emm.group('call')
2001 else:
2002 self.name = msg
2003 km = re.match('^(?P<n>.*)_cal$', self.type)
2004 if km:
2005 self.fcall = True
2006 self.fkprobe = True
2007 self.type = km.group('n')
2008 return
2009 km = re.match('^(?P<n>.*)_ret$', self.type)
2010 if km:
2011 self.freturn = True
2012 self.fkprobe = True
2013 self.type = km.group('n')
2014 return
2015 self.fevent = True
2016 return
2017 # convert the duration to seconds
2018 if(d):
2019 self.length = float(d)/1000000
2020 # the indentation determines the depth
2021 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
2022 if(not match):
2023 return
2024 self.depth = self.getDepth(match.group('d'))
2025 m = match.group('o')
2026 # function return
2027 if(m[0] == '}'):
2028 self.freturn = True
2029 if(len(m) > 1):
2030 # includes comment with function name
2031 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2032 if(match):
2033 self.name = match.group('n').strip()
2034 # function call
2035 else:
2036 self.fcall = True
2037 # function call with children
2038 if(m[-1] == '{'):
2039 match = re.match('^(?P<n>.*) *\(.*', m)
2040 if(match):
2041 self.name = match.group('n').strip()
2042 # function call with no children (leaf)
2043 elif(m[-1] == ';'):
2044 self.freturn = True
2045 match = re.match('^(?P<n>.*) *\(.*', m)
2046 if(match):
2047 self.name = match.group('n').strip()
2048 # something else (possibly a trace marker)
2049 else:
2050 self.name = m
2051 def isCall(self):
2052 return self.fcall and not self.freturn
2053 def isReturn(self):
2054 return self.freturn and not self.fcall
2055 def isLeaf(self):
2056 return self.fcall and self.freturn
2057 def getDepth(self, str):
2058 return len(str)/2
2059 def debugPrint(self, info=''):
2060 if self.isLeaf():
2061 pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2062 self.depth, self.name, self.length*1000000, info))
2063 elif self.freturn:
2064 pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2065 self.depth, self.name, self.length*1000000, info))
2066 else:
2067 pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2068 self.depth, self.name, self.length*1000000, info))
2069 def startMarker(self):
2070 # Is this the starting line of a suspend?
2071 if not self.fevent:
2072 return False
2073 if sysvals.usetracemarkers:
2074 if(self.name == 'SUSPEND START'):
2075 return True
2076 return False
2077 else:
2078 if(self.type == 'suspend_resume' and
2079 re.match('suspend_enter\[.*\] begin', self.name)):
2080 return True
2081 return False
2082 def endMarker(self):
2083 # Is this the ending line of a resume?
2084 if not self.fevent:
2085 return False
2086 if sysvals.usetracemarkers:
2087 if(self.name == 'RESUME COMPLETE'):
2088 return True
2089 return False
2090 else:
2091 if(self.type == 'suspend_resume' and
2092 re.match('thaw_processes\[.*\] end', self.name)):
2093 return True
2094 return False
2095
2096# Class: FTraceCallGraph
2097# Description:
2098# A container for the ftrace callgraph of a single recursive function.
2099# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2100# Each instance is tied to a single device in a single phase, and is
2101# comprised of an ordered list of FTraceLine objects
2102class FTraceCallGraph:
2103 vfname = 'missing_function_name'
2104 def __init__(self, pid, sv):
2105 self.id = ''
2106 self.invalid = False
2107 self.name = ''
2108 self.partial = False
2109 self.ignore = False
2110 self.start = -1.0
2111 self.end = -1.0
2112 self.list = []
2113 self.depth = 0
2114 self.pid = pid
2115 self.sv = sv
2116 def addLine(self, line):
2117 # if this is already invalid, just leave
2118 if(self.invalid):
2119 if(line.depth == 0 and line.freturn):
2120 return 1
2121 return 0
2122 # invalidate on bad depth
2123 if(self.depth < 0):
2124 self.invalidate(line)
2125 return 0
2126 # ignore data til we return to the current depth
2127 if self.ignore:
2128 if line.depth > self.depth:
2129 return 0
2130 else:
2131 self.list[-1].freturn = True
2132 self.list[-1].length = line.time - self.list[-1].time
2133 self.ignore = False
2134 # if this is a return at self.depth, no more work is needed
2135 if line.depth == self.depth and line.isReturn():
2136 if line.depth == 0:
2137 self.end = line.time
2138 return 1
2139 return 0
2140 # compare current depth with this lines pre-call depth
2141 prelinedep = line.depth
2142 if line.isReturn():
2143 prelinedep += 1
2144 last = 0
2145 lasttime = line.time
2146 if len(self.list) > 0:
2147 last = self.list[-1]
2148 lasttime = last.time
2149 if last.isLeaf():
2150 lasttime += last.length
2151 # handle low misalignments by inserting returns
2152 mismatch = prelinedep - self.depth
2153 warning = self.sv.verbose and abs(mismatch) > 1
2154 info = []
2155 if mismatch < 0:
2156 idx = 0
2157 # add return calls to get the depth down
2158 while prelinedep < self.depth:
2159 self.depth -= 1
2160 if idx == 0 and last and last.isCall():
2161 # special case, turn last call into a leaf
2162 last.depth = self.depth
2163 last.freturn = True
2164 last.length = line.time - last.time
2165 if warning:
2166 info.append(('[make leaf]', last))
2167 else:
2168 vline = FTraceLine(lasttime)
2169 vline.depth = self.depth
2170 vline.name = self.vfname
2171 vline.freturn = True
2172 self.list.append(vline)
2173 if warning:
2174 if idx == 0:
2175 info.append(('', last))
2176 info.append(('[add return]', vline))
2177 idx += 1
2178 if warning:
2179 info.append(('', line))
2180 # handle high misalignments by inserting calls
2181 elif mismatch > 0:
2182 idx = 0
2183 if warning:
2184 info.append(('', last))
2185 # add calls to get the depth up
2186 while prelinedep > self.depth:
2187 if idx == 0 and line.isReturn():
2188 # special case, turn this return into a leaf
2189 line.fcall = True
2190 prelinedep -= 1
2191 if warning:
2192 info.append(('[make leaf]', line))
2193 else:
2194 vline = FTraceLine(lasttime)
2195 vline.depth = self.depth
2196 vline.name = self.vfname
2197 vline.fcall = True
2198 self.list.append(vline)
2199 self.depth += 1
2200 if not last:
2201 self.start = vline.time
2202 if warning:
2203 info.append(('[add call]', vline))
2204 idx += 1
2205 if warning and ('[make leaf]', line) not in info:
2206 info.append(('', line))
2207 if warning:
2208 pprint('WARNING: ftrace data missing, corrections made:')
2209 for i in info:
2210 t, obj = i
2211 if obj:
2212 obj.debugPrint(t)
2213 # process the call and set the new depth
2214 skipadd = False
2215 md = self.sv.max_graph_depth
2216 if line.isCall():
2217 # ignore blacklisted/overdepth funcs
2218 if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2219 self.ignore = True
2220 else:
2221 self.depth += 1
2222 elif line.isReturn():
2223 self.depth -= 1
2224 # remove blacklisted/overdepth/empty funcs that slipped through
2225 if (last and last.isCall() and last.depth == line.depth) or \
2226 (md and last and last.depth >= md) or \
2227 (line.name in self.sv.cgblacklist):
2228 while len(self.list) > 0 and self.list[-1].depth > line.depth:
2229 self.list.pop(-1)
2230 if len(self.list) == 0:
2231 self.invalid = True
2232 return 1
2233 self.list[-1].freturn = True
2234 self.list[-1].length = line.time - self.list[-1].time
2235 self.list[-1].name = line.name
2236 skipadd = True
2237 if len(self.list) < 1:
2238 self.start = line.time
2239 # check for a mismatch that returned all the way to callgraph end
2240 res = 1
2241 if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2242 line = self.list[-1]
2243 skipadd = True
2244 res = -1
2245 if not skipadd:
2246 self.list.append(line)
2247 if(line.depth == 0 and line.freturn):
2248 if(self.start < 0):
2249 self.start = line.time
2250 self.end = line.time
2251 if line.fcall:
2252 self.end += line.length
2253 if self.list[0].name == self.vfname:
2254 self.invalid = True
2255 if res == -1:
2256 self.partial = True
2257 return res
2258 return 0
2259 def invalidate(self, line):
2260 if(len(self.list) > 0):
2261 first = self.list[0]
2262 self.list = []
2263 self.list.append(first)
2264 self.invalid = True
2265 id = 'task %s' % (self.pid)
2266 window = '(%f - %f)' % (self.start, line.time)
2267 if(self.depth < 0):
2268 pprint('Data misalignment for '+id+\
2269 ' (buffer overflow), ignoring this callback')
2270 else:
2271 pprint('Too much data for '+id+\
2272 ' '+window+', ignoring this callback')
2273 def slice(self, dev):
2274 minicg = FTraceCallGraph(dev['pid'], self.sv)
2275 minicg.name = self.name
2276 mydepth = -1
2277 good = False
2278 for l in self.list:
2279 if(l.time < dev['start'] or l.time > dev['end']):
2280 continue
2281 if mydepth < 0:
2282 if l.name == 'mutex_lock' and l.freturn:
2283 mydepth = l.depth
2284 continue
2285 elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2286 good = True
2287 break
2288 l.depth -= mydepth
2289 minicg.addLine(l)
2290 if not good or len(minicg.list) < 1:
2291 return 0
2292 return minicg
2293 def repair(self, enddepth):
2294 # bring the depth back to 0 with additional returns
2295 fixed = False
2296 last = self.list[-1]
2297 for i in reversed(range(enddepth)):
2298 t = FTraceLine(last.time)
2299 t.depth = i
2300 t.freturn = True
2301 fixed = self.addLine(t)
2302 if fixed != 0:
2303 self.end = last.time
2304 return True
2305 return False
2306 def postProcess(self):
2307 if len(self.list) > 0:
2308 self.name = self.list[0].name
2309 stack = dict()
2310 cnt = 0
2311 last = 0
2312 for l in self.list:
2313 # ftrace bug: reported duration is not reliable
2314 # check each leaf and clip it at max possible length
2315 if last and last.isLeaf():
2316 if last.length > l.time - last.time:
2317 last.length = l.time - last.time
2318 if l.isCall():
2319 stack[l.depth] = l
2320 cnt += 1
2321 elif l.isReturn():
2322 if(l.depth not in stack):
2323 if self.sv.verbose:
2324 pprint('Post Process Error: Depth missing')
2325 l.debugPrint()
2326 return False
2327 # calculate call length from call/return lines
2328 cl = stack[l.depth]
2329 cl.length = l.time - cl.time
2330 if cl.name == self.vfname:
2331 cl.name = l.name
2332 stack.pop(l.depth)
2333 l.length = 0
2334 cnt -= 1
2335 last = l
2336 if(cnt == 0):
2337 # trace caught the whole call tree
2338 return True
2339 elif(cnt < 0):
2340 if self.sv.verbose:
2341 pprint('Post Process Error: Depth is less than 0')
2342 return False
2343 # trace ended before call tree finished
2344 return self.repair(cnt)
2345 def deviceMatch(self, pid, data):
2346 found = ''
2347 # add the callgraph data to the device hierarchy
2348 borderphase = {
2349 'dpm_prepare': 'suspend_prepare',
2350 'dpm_complete': 'resume_complete'
2351 }
2352 if(self.name in borderphase):
2353 p = borderphase[self.name]
2354 list = data.dmesg[p]['list']
2355 for devname in list:
2356 dev = list[devname]
2357 if(pid == dev['pid'] and
2358 self.start <= dev['start'] and
2359 self.end >= dev['end']):
2360 cg = self.slice(dev)
2361 if cg:
2362 dev['ftrace'] = cg
2363 found = devname
2364 return found
2365 for p in data.sortedPhases():
2366 if(data.dmesg[p]['start'] <= self.start and
2367 self.start <= data.dmesg[p]['end']):
2368 list = data.dmesg[p]['list']
2369 for devname in sorted(list, key=lambda k:list[k]['start']):
2370 dev = list[devname]
2371 if(pid == dev['pid'] and
2372 self.start <= dev['start'] and
2373 self.end >= dev['end']):
2374 dev['ftrace'] = self
2375 found = devname
2376 break
2377 break
2378 return found
2379 def newActionFromFunction(self, data):
2380 name = self.name
2381 if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2382 return
2383 fs = self.start
2384 fe = self.end
2385 if fs < data.start or fe > data.end:
2386 return
2387 phase = ''
2388 for p in data.sortedPhases():
2389 if(data.dmesg[p]['start'] <= self.start and
2390 self.start < data.dmesg[p]['end']):
2391 phase = p
2392 break
2393 if not phase:
2394 return
2395 out = data.newActionGlobal(name, fs, fe, -2)
2396 if out:
2397 phase, myname = out
2398 data.dmesg[phase]['list'][myname]['ftrace'] = self
2399 def debugPrint(self, info=''):
2400 pprint('%s pid=%d [%f - %f] %.3f us' % \
2401 (self.name, self.pid, self.start, self.end,
2402 (self.end - self.start)*1000000))
2403 for l in self.list:
2404 if l.isLeaf():
2405 pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2406 l.depth, l.name, l.length*1000000, info))
2407 elif l.freturn:
2408 pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2409 l.depth, l.name, l.length*1000000, info))
2410 else:
2411 pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2412 l.depth, l.name, l.length*1000000, info))
2413 pprint(' ')
2414
2415class DevItem:
2416 def __init__(self, test, phase, dev):
2417 self.test = test
2418 self.phase = phase
2419 self.dev = dev
2420 def isa(self, cls):
2421 if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2422 return True
2423 return False
2424
2425# Class: Timeline
2426# Description:
2427# A container for a device timeline which calculates
2428# all the html properties to display it correctly
2429class Timeline:
2430 html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2431 html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2432 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2433 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2434 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}"> {2}</div>\n'
2435 def __init__(self, rowheight, scaleheight):
2436 self.html = ''
2437 self.height = 0 # total timeline height
2438 self.scaleH = scaleheight # timescale (top) row height
2439 self.rowH = rowheight # device row height
2440 self.bodyH = 0 # body height
2441 self.rows = 0 # total timeline rows
2442 self.rowlines = dict()
2443 self.rowheight = dict()
2444 def createHeader(self, sv, stamp):
2445 if(not stamp['time']):
2446 return
2447 self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \
2448 % (sv.title, sv.version)
2449 if sv.logmsg and sv.testlog:
2450 self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2451 if sv.dmesglog:
2452 self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2453 if sv.ftracelog:
2454 self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2455 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2456 self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2457 stamp['mode'], stamp['time'])
2458 if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2459 stamp['man'] and stamp['plat'] and stamp['cpu']:
2460 headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2461 self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2462
2463 # Function: getDeviceRows
2464 # Description:
2465 # determine how may rows the device funcs will take
2466 # Arguments:
2467 # rawlist: the list of devices/actions for a single phase
2468 # Output:
2469 # The total number of rows needed to display this phase of the timeline
2470 def getDeviceRows(self, rawlist):
2471 # clear all rows and set them to undefined
2472 sortdict = dict()
2473 for item in rawlist:
2474 item.row = -1
2475 sortdict[item] = item.length
2476 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2477 remaining = len(sortlist)
2478 rowdata = dict()
2479 row = 1
2480 # try to pack each row with as many ranges as possible
2481 while(remaining > 0):
2482 if(row not in rowdata):
2483 rowdata[row] = []
2484 for i in sortlist:
2485 if(i.row >= 0):
2486 continue
2487 s = i.time
2488 e = i.time + i.length
2489 valid = True
2490 for ritem in rowdata[row]:
2491 rs = ritem.time
2492 re = ritem.time + ritem.length
2493 if(not (((s <= rs) and (e <= rs)) or
2494 ((s >= re) and (e >= re)))):
2495 valid = False
2496 break
2497 if(valid):
2498 rowdata[row].append(i)
2499 i.row = row
2500 remaining -= 1
2501 row += 1
2502 return row
2503 # Function: getPhaseRows
2504 # Description:
2505 # Organize the timeline entries into the smallest
2506 # number of rows possible, with no entry overlapping
2507 # Arguments:
2508 # devlist: the list of devices/actions in a group of contiguous phases
2509 # Output:
2510 # The total number of rows needed to display this phase of the timeline
2511 def getPhaseRows(self, devlist, row=0, sortby='length'):
2512 # clear all rows and set them to undefined
2513 remaining = len(devlist)
2514 rowdata = dict()
2515 sortdict = dict()
2516 myphases = []
2517 # initialize all device rows to -1 and calculate devrows
2518 for item in devlist:
2519 dev = item.dev
2520 tp = (item.test, item.phase)
2521 if tp not in myphases:
2522 myphases.append(tp)
2523 dev['row'] = -1
2524 if sortby == 'start':
2525 # sort by start 1st, then length 2nd
2526 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2527 else:
2528 # sort by length 1st, then name 2nd
2529 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2530 if 'src' in dev:
2531 dev['devrows'] = self.getDeviceRows(dev['src'])
2532 # sort the devlist by length so that large items graph on top
2533 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2534 orderedlist = []
2535 for item in sortlist:
2536 if item.dev['pid'] == -2:
2537 orderedlist.append(item)
2538 for item in sortlist:
2539 if item not in orderedlist:
2540 orderedlist.append(item)
2541 # try to pack each row with as many devices as possible
2542 while(remaining > 0):
2543 rowheight = 1
2544 if(row not in rowdata):
2545 rowdata[row] = []
2546 for item in orderedlist:
2547 dev = item.dev
2548 if(dev['row'] < 0):
2549 s = dev['start']
2550 e = dev['end']
2551 valid = True
2552 for ritem in rowdata[row]:
2553 rs = ritem.dev['start']
2554 re = ritem.dev['end']
2555 if(not (((s <= rs) and (e <= rs)) or
2556 ((s >= re) and (e >= re)))):
2557 valid = False
2558 break
2559 if(valid):
2560 rowdata[row].append(item)
2561 dev['row'] = row
2562 remaining -= 1
2563 if 'devrows' in dev and dev['devrows'] > rowheight:
2564 rowheight = dev['devrows']
2565 for t, p in myphases:
2566 if t not in self.rowlines or t not in self.rowheight:
2567 self.rowlines[t] = dict()
2568 self.rowheight[t] = dict()
2569 if p not in self.rowlines[t] or p not in self.rowheight[t]:
2570 self.rowlines[t][p] = dict()
2571 self.rowheight[t][p] = dict()
2572 rh = self.rowH
2573 # section headers should use a different row height
2574 if len(rowdata[row]) == 1 and \
2575 'htmlclass' in rowdata[row][0].dev and \
2576 'sec' in rowdata[row][0].dev['htmlclass']:
2577 rh = 15
2578 self.rowlines[t][p][row] = rowheight
2579 self.rowheight[t][p][row] = rowheight * rh
2580 row += 1
2581 if(row > self.rows):
2582 self.rows = int(row)
2583 return row
2584 def phaseRowHeight(self, test, phase, row):
2585 return self.rowheight[test][phase][row]
2586 def phaseRowTop(self, test, phase, row):
2587 top = 0
2588 for i in sorted(self.rowheight[test][phase]):
2589 if i >= row:
2590 break
2591 top += self.rowheight[test][phase][i]
2592 return top
2593 def calcTotalRows(self):
2594 # Calculate the heights and offsets for the header and rows
2595 maxrows = 0
2596 standardphases = []
2597 for t in self.rowlines:
2598 for p in self.rowlines[t]:
2599 total = 0
2600 for i in sorted(self.rowlines[t][p]):
2601 total += self.rowlines[t][p][i]
2602 if total > maxrows:
2603 maxrows = total
2604 if total == len(self.rowlines[t][p]):
2605 standardphases.append((t, p))
2606 self.height = self.scaleH + (maxrows*self.rowH)
2607 self.bodyH = self.height - self.scaleH
2608 # if there is 1 line per row, draw them the standard way
2609 for t, p in standardphases:
2610 for i in sorted(self.rowheight[t][p]):
2611 self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2612 def createZoomBox(self, mode='command', testcount=1):
2613 # Create bounding box, add buttons
2614 html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2615 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2616 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2617 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2618 if mode != 'command':
2619 if testcount > 1:
2620 self.html += html_devlist2
2621 self.html += html_devlist1.format('1')
2622 else:
2623 self.html += html_devlist1.format('')
2624 self.html += html_zoombox
2625 self.html += html_timeline.format('dmesg', self.height)
2626 # Function: createTimeScale
2627 # Description:
2628 # Create the timescale for a timeline block
2629 # Arguments:
2630 # m0: start time (mode begin)
2631 # mMax: end time (mode end)
2632 # tTotal: total timeline time
2633 # mode: suspend or resume
2634 # Output:
2635 # The html code needed to display the time scale
2636 def createTimeScale(self, m0, mMax, tTotal, mode):
2637 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2638 rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2639 output = '<div class="timescale">\n'
2640 # set scale for timeline
2641 mTotal = mMax - m0
2642 tS = 0.1
2643 if(tTotal <= 0):
2644 return output+'</div>\n'
2645 if(tTotal > 4):
2646 tS = 1
2647 divTotal = int(mTotal/tS) + 1
2648 divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2649 for i in range(divTotal):
2650 htmlline = ''
2651 if(mode == 'suspend'):
2652 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2653 val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2654 if(i == divTotal - 1):
2655 val = mode
2656 htmlline = timescale.format(pos, val)
2657 else:
2658 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2659 val = '%0.fms' % (float(i)*tS*1000)
2660 htmlline = timescale.format(pos, val)
2661 if(i == 0):
2662 htmlline = rline.format(mode)
2663 output += htmlline
2664 self.html += output+'</div>\n'
2665
2666# Class: TestProps
2667# Description:
2668# A list of values describing the properties of these test runs
2669class TestProps:
2670 stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2671 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2672 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2673 batteryfmt = '^# battery (?P<a1>\w*) (?P<c1>\d*) (?P<a2>\w*) (?P<c2>\d*)'
2674 wififmt = '^# wifi (?P<w>.*)'
2675 tstatfmt = '^# turbostat (?P<t>\S*)'
2676 mcelogfmt = '^# mcelog (?P<m>\S*)'
2677 testerrfmt = '^# enter_sleep_error (?P<e>.*)'
2678 sysinfofmt = '^# sysinfo .*'
2679 cmdlinefmt = '^# command \| (?P<cmd>.*)'
2680 kparamsfmt = '^# kparams \| (?P<kp>.*)'
2681 devpropfmt = '# Device Properties: .*'
2682 pinfofmt = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
2683 tracertypefmt = '# tracer: (?P<t>.*)'
2684 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
2685 procexecfmt = 'ps - (?P<ps>.*)$'
2686 ftrace_line_fmt_fg = \
2687 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2688 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2689 '[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
2690 ftrace_line_fmt_nop = \
2691 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2692 '(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
2693 '(?P<msg>.*)'
2694 def __init__(self):
2695 self.stamp = ''
2696 self.sysinfo = ''
2697 self.cmdline = ''
2698 self.kparams = ''
2699 self.testerror = []
2700 self.mcelog = []
2701 self.turbostat = []
2702 self.battery = []
2703 self.wifi = []
2704 self.fwdata = []
2705 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2706 self.cgformat = False
2707 self.data = 0
2708 self.ktemp = dict()
2709 def setTracerType(self, tracer):
2710 if(tracer == 'function_graph'):
2711 self.cgformat = True
2712 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2713 elif(tracer == 'nop'):
2714 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2715 else:
2716 doError('Invalid tracer format: [%s]' % tracer)
2717 def stampInfo(self, line):
2718 if re.match(self.stampfmt, line):
2719 self.stamp = line
2720 return True
2721 elif re.match(self.sysinfofmt, line):
2722 self.sysinfo = line
2723 return True
2724 elif re.match(self.kparamsfmt, line):
2725 self.kparams = line
2726 return True
2727 elif re.match(self.cmdlinefmt, line):
2728 self.cmdline = line
2729 return True
2730 elif re.match(self.mcelogfmt, line):
2731 self.mcelog.append(line)
2732 return True
2733 elif re.match(self.tstatfmt, line):
2734 self.turbostat.append(line)
2735 return True
2736 elif re.match(self.batteryfmt, line):
2737 self.battery.append(line)
2738 return True
2739 elif re.match(self.wififmt, line):
2740 self.wifi.append(line)
2741 return True
2742 elif re.match(self.testerrfmt, line):
2743 self.testerror.append(line)
2744 return True
2745 elif re.match(self.firmwarefmt, line):
2746 self.fwdata.append(line)
2747 return True
2748 return False
2749 def parseStamp(self, data, sv):
2750 # global test data
2751 m = re.match(self.stampfmt, self.stamp)
2752 data.stamp = {'time': '', 'host': '', 'mode': ''}
2753 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2754 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2755 int(m.group('S')))
2756 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2757 data.stamp['host'] = m.group('host')
2758 data.stamp['mode'] = m.group('mode')
2759 data.stamp['kernel'] = m.group('kernel')
2760 if re.match(self.sysinfofmt, self.sysinfo):
2761 for f in self.sysinfo.split('|'):
2762 if '#' in f:
2763 continue
2764 tmp = f.strip().split(':', 1)
2765 key = tmp[0]
2766 val = tmp[1]
2767 data.stamp[key] = val
2768 sv.hostname = data.stamp['host']
2769 sv.suspendmode = data.stamp['mode']
2770 if sv.suspendmode == 'command' and sv.ftracefile != '':
2771 modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2772 fp = sysvals.openlog(sv.ftracefile, 'r')
2773 for line in fp:
2774 m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
2775 if m and m.group('mode') in ['1', '2', '3', '4']:
2776 sv.suspendmode = modes[int(m.group('mode'))]
2777 data.stamp['mode'] = sv.suspendmode
2778 break
2779 fp.close()
2780 m = re.match(self.cmdlinefmt, self.cmdline)
2781 if m:
2782 sv.cmdline = m.group('cmd')
2783 if self.kparams:
2784 m = re.match(self.kparamsfmt, self.kparams)
2785 if m:
2786 sv.kparams = m.group('kp')
2787 if not sv.stamp:
2788 sv.stamp = data.stamp
2789 # firmware data
2790 if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
2791 m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
2792 if m:
2793 data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
2794 if(data.fwSuspend > 0 or data.fwResume > 0):
2795 data.fwValid = True
2796 # mcelog data
2797 if len(self.mcelog) > data.testnumber:
2798 m = re.match(self.mcelogfmt, self.mcelog[data.testnumber])
2799 if m:
2800 data.mcelog = sv.b64unzip(m.group('m'))
2801 # turbostat data
2802 if len(self.turbostat) > data.testnumber:
2803 m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
2804 if m:
2805 data.turbostat = m.group('t')
2806 # battery data
2807 if len(self.battery) > data.testnumber:
2808 m = re.match(self.batteryfmt, self.battery[data.testnumber])
2809 if m:
2810 data.battery = m.groups()
2811 # wifi data
2812 if len(self.wifi) > data.testnumber:
2813 m = re.match(self.wififmt, self.wifi[data.testnumber])
2814 if m:
2815 data.wifi = m.group('w')
2816 # sleep mode enter errors
2817 if len(self.testerror) > data.testnumber:
2818 m = re.match(self.testerrfmt, self.testerror[data.testnumber])
2819 if m:
2820 data.enterfail = m.group('e')
2821 def devprops(self, data):
2822 props = dict()
2823 devlist = data.split(';')
2824 for dev in devlist:
2825 f = dev.split(',')
2826 if len(f) < 3:
2827 continue
2828 dev = f[0]
2829 props[dev] = DevProps()
2830 props[dev].altname = f[1]
2831 if int(f[2]):
2832 props[dev].isasync = True
2833 else:
2834 props[dev].isasync = False
2835 return props
2836 def parseDevprops(self, line, sv):
2837 idx = line.index(': ') + 2
2838 if idx >= len(line):
2839 return
2840 props = self.devprops(line[idx:])
2841 if sv.suspendmode == 'command' and 'testcommandstring' in props:
2842 sv.testcommand = props['testcommandstring'].altname
2843 sv.devprops = props
2844 def parsePlatformInfo(self, line, sv):
2845 m = re.match(self.pinfofmt, line)
2846 if not m:
2847 return
2848 name, info = m.group('val'), m.group('info')
2849 if name == 'devinfo':
2850 sv.devprops = self.devprops(sv.b64unzip(info))
2851 return
2852 elif name == 'testcmd':
2853 sv.testcommand = info
2854 return
2855 field = info.split('|')
2856 if len(field) < 2:
2857 return
2858 cmdline = field[0].strip()
2859 output = sv.b64unzip(field[1].strip())
2860 sv.platinfo.append([name, cmdline, output])
2861
2862# Class: TestRun
2863# Description:
2864# A container for a suspend/resume test run. This is necessary as
2865# there could be more than one, and they need to be separate.
2866class TestRun:
2867 def __init__(self, dataobj):
2868 self.data = dataobj
2869 self.ftemp = dict()
2870 self.ttemp = dict()
2871
2872class ProcessMonitor:
2873 def __init__(self):
2874 self.proclist = dict()
2875 self.running = False
2876 def procstat(self):
2877 c = ['cat /proc/[1-9]*/stat 2>/dev/null']
2878 process = Popen(c, shell=True, stdout=PIPE)
2879 running = dict()
2880 for line in process.stdout:
2881 data = ascii(line).split()
2882 pid = data[0]
2883 name = re.sub('[()]', '', data[1])
2884 user = int(data[13])
2885 kern = int(data[14])
2886 kjiff = ujiff = 0
2887 if pid not in self.proclist:
2888 self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
2889 else:
2890 val = self.proclist[pid]
2891 ujiff = user - val['user']
2892 kjiff = kern - val['kern']
2893 val['user'] = user
2894 val['kern'] = kern
2895 if ujiff > 0 or kjiff > 0:
2896 running[pid] = ujiff + kjiff
2897 process.wait()
2898 out = ''
2899 for pid in running:
2900 jiffies = running[pid]
2901 val = self.proclist[pid]
2902 if out:
2903 out += ','
2904 out += '%s-%s %d' % (val['name'], pid, jiffies)
2905 return 'ps - '+out
2906 def processMonitor(self, tid):
2907 while self.running:
2908 out = self.procstat()
2909 if out:
2910 sysvals.fsetVal(out, 'trace_marker')
2911 def start(self):
2912 self.thread = Thread(target=self.processMonitor, args=(0,))
2913 self.running = True
2914 self.thread.start()
2915 def stop(self):
2916 self.running = False
2917
2918# ----------------- FUNCTIONS --------------------
2919
2920# Function: doesTraceLogHaveTraceEvents
2921# Description:
2922# Quickly determine if the ftrace log has all of the trace events,
2923# markers, and/or kprobes required for primary parsing.
2924def doesTraceLogHaveTraceEvents():
2925 kpcheck = ['_cal: (', '_ret: (']
2926 techeck = ['suspend_resume', 'device_pm_callback']
2927 tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
2928 sysvals.usekprobes = False
2929 fp = sysvals.openlog(sysvals.ftracefile, 'r')
2930 for line in fp:
2931 # check for kprobes
2932 if not sysvals.usekprobes:
2933 for i in kpcheck:
2934 if i in line:
2935 sysvals.usekprobes = True
2936 # check for all necessary trace events
2937 check = techeck[:]
2938 for i in techeck:
2939 if i in line:
2940 check.remove(i)
2941 techeck = check
2942 # check for all necessary trace markers
2943 check = tmcheck[:]
2944 for i in tmcheck:
2945 if i in line:
2946 check.remove(i)
2947 tmcheck = check
2948 fp.close()
2949 sysvals.usetraceevents = True if len(techeck) < 2 else False
2950 sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
2951
2952# Function: appendIncompleteTraceLog
2953# Description:
2954# [deprecated for kernel 3.15 or newer]
2955# Adds callgraph data which lacks trace event data. This is only
2956# for timelines generated from 3.15 or older
2957# Arguments:
2958# testruns: the array of Data objects obtained from parseKernelLog
2959def appendIncompleteTraceLog(testruns):
2960 # create TestRun vessels for ftrace parsing
2961 testcnt = len(testruns)
2962 testidx = 0
2963 testrun = []
2964 for data in testruns:
2965 testrun.append(TestRun(data))
2966
2967 # extract the callgraph and traceevent data
2968 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2969 os.path.basename(sysvals.ftracefile))
2970 tp = TestProps()
2971 tf = sysvals.openlog(sysvals.ftracefile, 'r')
2972 data = 0
2973 for line in tf:
2974 # remove any latent carriage returns
2975 line = line.replace('\r\n', '')
2976 if tp.stampInfo(line):
2977 continue
2978 # determine the trace data type (required for further parsing)
2979 m = re.match(tp.tracertypefmt, line)
2980 if(m):
2981 tp.setTracerType(m.group('t'))
2982 continue
2983 # device properties line
2984 if(re.match(tp.devpropfmt, line)):
2985 tp.parseDevprops(line, sysvals)
2986 continue
2987 # platform info line
2988 if(re.match(tp.pinfofmt, line)):
2989 tp.parsePlatformInfo(line, sysvals)
2990 continue
2991 # parse only valid lines, if this is not one move on
2992 m = re.match(tp.ftrace_line_fmt, line)
2993 if(not m):
2994 continue
2995 # gather the basic message data from the line
2996 m_time = m.group('time')
2997 m_pid = m.group('pid')
2998 m_msg = m.group('msg')
2999 if(tp.cgformat):
3000 m_param3 = m.group('dur')
3001 else:
3002 m_param3 = 'traceevent'
3003 if(m_time and m_pid and m_msg):
3004 t = FTraceLine(m_time, m_msg, m_param3)
3005 pid = int(m_pid)
3006 else:
3007 continue
3008 # the line should be a call, return, or event
3009 if(not t.fcall and not t.freturn and not t.fevent):
3010 continue
3011 # look for the suspend start marker
3012 if(t.startMarker()):
3013 data = testrun[testidx].data
3014 tp.parseStamp(data, sysvals)
3015 data.setStart(t.time)
3016 continue
3017 if(not data):
3018 continue
3019 # find the end of resume
3020 if(t.endMarker()):
3021 data.setEnd(t.time)
3022 testidx += 1
3023 if(testidx >= testcnt):
3024 break
3025 continue
3026 # trace event processing
3027 if(t.fevent):
3028 continue
3029 # call/return processing
3030 elif sysvals.usecallgraph:
3031 # create a callgraph object for the data
3032 if(pid not in testrun[testidx].ftemp):
3033 testrun[testidx].ftemp[pid] = []
3034 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3035 # when the call is finished, see which device matches it
3036 cg = testrun[testidx].ftemp[pid][-1]
3037 res = cg.addLine(t)
3038 if(res != 0):
3039 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3040 if(res == -1):
3041 testrun[testidx].ftemp[pid][-1].addLine(t)
3042 tf.close()
3043
3044 for test in testrun:
3045 # add the callgraph data to the device hierarchy
3046 for pid in test.ftemp:
3047 for cg in test.ftemp[pid]:
3048 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3049 continue
3050 if(not cg.postProcess()):
3051 id = 'task %s cpu %s' % (pid, m.group('cpu'))
3052 sysvals.vprint('Sanity check failed for '+\
3053 id+', ignoring this callback')
3054 continue
3055 callstart = cg.start
3056 callend = cg.end
3057 for p in test.data.sortedPhases():
3058 if(test.data.dmesg[p]['start'] <= callstart and
3059 callstart <= test.data.dmesg[p]['end']):
3060 list = test.data.dmesg[p]['list']
3061 for devname in list:
3062 dev = list[devname]
3063 if(pid == dev['pid'] and
3064 callstart <= dev['start'] and
3065 callend >= dev['end']):
3066 dev['ftrace'] = cg
3067 break
3068
3069# Function: parseTraceLog
3070# Description:
3071# Analyze an ftrace log output file generated from this app during
3072# the execution phase. Used when the ftrace log is the primary data source
3073# and includes the suspend_resume and device_pm_callback trace events
3074# The ftrace filename is taken from sysvals
3075# Output:
3076# An array of Data objects
3077def parseTraceLog(live=False):
3078 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3079 os.path.basename(sysvals.ftracefile))
3080 if(os.path.exists(sysvals.ftracefile) == False):
3081 doError('%s does not exist' % sysvals.ftracefile)
3082 if not live:
3083 sysvals.setupAllKprobes()
3084 ksuscalls = ['pm_prepare_console']
3085 krescalls = ['pm_restore_console']
3086 tracewatch = ['irq_wakeup']
3087 if sysvals.usekprobes:
3088 tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3089 'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3090 'CPU_OFF', 'timekeeping_freeze', 'acpi_suspend']
3091
3092 # extract the callgraph and traceevent data
3093 tp = TestProps()
3094 testruns = []
3095 testdata = []
3096 testrun = 0
3097 data = 0
3098 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3099 phase = 'suspend_prepare'
3100 for line in tf:
3101 # remove any latent carriage returns
3102 line = line.replace('\r\n', '')
3103 if tp.stampInfo(line):
3104 continue
3105 # tracer type line: determine the trace data type
3106 m = re.match(tp.tracertypefmt, line)
3107 if(m):
3108 tp.setTracerType(m.group('t'))
3109 continue
3110 # device properties line
3111 if(re.match(tp.devpropfmt, line)):
3112 tp.parseDevprops(line, sysvals)
3113 continue
3114 # platform info line
3115 if(re.match(tp.pinfofmt, line)):
3116 tp.parsePlatformInfo(line, sysvals)
3117 continue
3118 # ignore all other commented lines
3119 if line[0] == '#':
3120 continue
3121 # ftrace line: parse only valid lines
3122 m = re.match(tp.ftrace_line_fmt, line)
3123 if(not m):
3124 continue
3125 # gather the basic message data from the line
3126 m_time = m.group('time')
3127 m_proc = m.group('proc')
3128 m_pid = m.group('pid')
3129 m_msg = m.group('msg')
3130 if(tp.cgformat):
3131 m_param3 = m.group('dur')
3132 else:
3133 m_param3 = 'traceevent'
3134 if(m_time and m_pid and m_msg):
3135 t = FTraceLine(m_time, m_msg, m_param3)
3136 pid = int(m_pid)
3137 else:
3138 continue
3139 # the line should be a call, return, or event
3140 if(not t.fcall and not t.freturn and not t.fevent):
3141 continue
3142 # find the start of suspend
3143 if(t.startMarker()):
3144 data = Data(len(testdata))
3145 testdata.append(data)
3146 testrun = TestRun(data)
3147 testruns.append(testrun)
3148 tp.parseStamp(data, sysvals)
3149 data.setStart(t.time)
3150 data.first_suspend_prepare = True
3151 phase = data.setPhase('suspend_prepare', t.time, True)
3152 continue
3153 if(not data):
3154 continue
3155 # process cpu exec line
3156 if t.type == 'tracing_mark_write':
3157 m = re.match(tp.procexecfmt, t.name)
3158 if(m):
3159 proclist = dict()
3160 for ps in m.group('ps').split(','):
3161 val = ps.split()
3162 if not val:
3163 continue
3164 name = val[0].replace('--', '-')
3165 proclist[name] = int(val[1])
3166 data.pstl[t.time] = proclist
3167 continue
3168 # find the end of resume
3169 if(t.endMarker()):
3170 data.handleEndMarker(t.time)
3171 if(not sysvals.usetracemarkers):
3172 # no trace markers? then quit and be sure to finish recording
3173 # the event we used to trigger resume end
3174 if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3175 # if an entry exists, assume this is its end
3176 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3177 break
3178 continue
3179 # trace event processing
3180 if(t.fevent):
3181 if(t.type == 'suspend_resume'):
3182 # suspend_resume trace events have two types, begin and end
3183 if(re.match('(?P<name>.*) begin$', t.name)):
3184 isbegin = True
3185 elif(re.match('(?P<name>.*) end$', t.name)):
3186 isbegin = False
3187 else:
3188 continue
3189 if '[' in t.name:
3190 m = re.match('(?P<name>.*)\[.*', t.name)
3191 else:
3192 m = re.match('(?P<name>.*) .*', t.name)
3193 name = m.group('name')
3194 # ignore these events
3195 if(name.split('[')[0] in tracewatch):
3196 continue
3197 # -- phase changes --
3198 # start of kernel suspend
3199 if(re.match('suspend_enter\[.*', t.name)):
3200 if(isbegin):
3201 data.tKernSus = t.time
3202 continue
3203 # suspend_prepare start
3204 elif(re.match('dpm_prepare\[.*', t.name)):
3205 if isbegin and data.first_suspend_prepare:
3206 data.first_suspend_prepare = False
3207 if data.tKernSus == 0:
3208 data.tKernSus = t.time
3209 continue
3210 phase = data.setPhase('suspend_prepare', t.time, isbegin)
3211 continue
3212 # suspend start
3213 elif(re.match('dpm_suspend\[.*', t.name)):
3214 phase = data.setPhase('suspend', t.time, isbegin)
3215 continue
3216 # suspend_late start
3217 elif(re.match('dpm_suspend_late\[.*', t.name)):
3218 phase = data.setPhase('suspend_late', t.time, isbegin)
3219 continue
3220 # suspend_noirq start
3221 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3222 phase = data.setPhase('suspend_noirq', t.time, isbegin)
3223 continue
3224 # suspend_machine/resume_machine
3225 elif(re.match('machine_suspend\[.*', t.name)):
3226 if(isbegin):
3227 lp = data.lastPhase()
3228 if lp == 'resume_machine':
3229 data.dmesg[lp]['end'] = t.time
3230 phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3231 data.setPhase(phase, t.time, False)
3232 if data.tSuspended == 0:
3233 data.tSuspended = t.time
3234 else:
3235 phase = data.setPhase('resume_machine', t.time, True)
3236 if(sysvals.suspendmode in ['mem', 'disk']):
3237 susp = phase.replace('resume', 'suspend')
3238 if susp in data.dmesg:
3239 data.dmesg[susp]['end'] = t.time
3240 data.tSuspended = t.time
3241 data.tResumed = t.time
3242 continue
3243 # resume_noirq start
3244 elif(re.match('dpm_resume_noirq\[.*', t.name)):
3245 phase = data.setPhase('resume_noirq', t.time, isbegin)
3246 continue
3247 # resume_early start
3248 elif(re.match('dpm_resume_early\[.*', t.name)):
3249 phase = data.setPhase('resume_early', t.time, isbegin)
3250 continue
3251 # resume start
3252 elif(re.match('dpm_resume\[.*', t.name)):
3253 phase = data.setPhase('resume', t.time, isbegin)
3254 continue
3255 # resume complete start
3256 elif(re.match('dpm_complete\[.*', t.name)):
3257 phase = data.setPhase('resume_complete', t.time, isbegin)
3258 continue
3259 # skip trace events inside devices calls
3260 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3261 continue
3262 # global events (outside device calls) are graphed
3263 if(name not in testrun.ttemp):
3264 testrun.ttemp[name] = []
3265 if(isbegin):
3266 # create a new list entry
3267 testrun.ttemp[name].append(\
3268 {'begin': t.time, 'end': t.time, 'pid': pid})
3269 else:
3270 if(len(testrun.ttemp[name]) > 0):
3271 # if an entry exists, assume this is its end
3272 testrun.ttemp[name][-1]['end'] = t.time
3273 # device callback start
3274 elif(t.type == 'device_pm_callback_start'):
3275 if phase not in data.dmesg:
3276 continue
3277 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3278 t.name);
3279 if(not m):
3280 continue
3281 drv = m.group('drv')
3282 n = m.group('d')
3283 p = m.group('p')
3284 if(n and p):
3285 data.newAction(phase, n, pid, p, t.time, -1, drv)
3286 if pid not in data.devpids:
3287 data.devpids.append(pid)
3288 # device callback finish
3289 elif(t.type == 'device_pm_callback_end'):
3290 if phase not in data.dmesg:
3291 continue
3292 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
3293 if(not m):
3294 continue
3295 n = m.group('d')
3296 list = data.dmesg[phase]['list']
3297 if(n in list):
3298 dev = list[n]
3299 dev['length'] = t.time - dev['start']
3300 dev['end'] = t.time
3301 # kprobe event processing
3302 elif(t.fkprobe):
3303 kprobename = t.type
3304 kprobedata = t.name
3305 key = (kprobename, pid)
3306 # displayname is generated from kprobe data
3307 displayname = ''
3308 if(t.fcall):
3309 displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3310 if not displayname:
3311 continue
3312 if(key not in tp.ktemp):
3313 tp.ktemp[key] = []
3314 tp.ktemp[key].append({
3315 'pid': pid,
3316 'begin': t.time,
3317 'end': -1,
3318 'name': displayname,
3319 'cdata': kprobedata,
3320 'proc': m_proc,
3321 })
3322 # start of kernel resume
3323 if(phase == 'suspend_prepare' and kprobename in ksuscalls):
3324 data.tKernSus = t.time
3325 elif(t.freturn):
3326 if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3327 continue
3328 e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3329 if not e:
3330 continue
3331 e['end'] = t.time
3332 e['rdata'] = kprobedata
3333 # end of kernel resume
3334 if(phase != 'suspend_prepare' and kprobename in krescalls):
3335 if phase in data.dmesg:
3336 data.dmesg[phase]['end'] = t.time
3337 data.tKernRes = t.time
3338
3339 # callgraph processing
3340 elif sysvals.usecallgraph:
3341 # create a callgraph object for the data
3342 key = (m_proc, pid)
3343 if(key not in testrun.ftemp):
3344 testrun.ftemp[key] = []
3345 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3346 # when the call is finished, see which device matches it
3347 cg = testrun.ftemp[key][-1]
3348 res = cg.addLine(t)
3349 if(res != 0):
3350 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3351 if(res == -1):
3352 testrun.ftemp[key][-1].addLine(t)
3353 tf.close()
3354 if len(testdata) < 1:
3355 sysvals.vprint('WARNING: ftrace start marker is missing')
3356 if data and not data.devicegroups:
3357 sysvals.vprint('WARNING: ftrace end marker is missing')
3358 data.handleEndMarker(t.time)
3359
3360 if sysvals.suspendmode == 'command':
3361 for test in testruns:
3362 for p in test.data.sortedPhases():
3363 if p == 'suspend_prepare':
3364 test.data.dmesg[p]['start'] = test.data.start
3365 test.data.dmesg[p]['end'] = test.data.end
3366 else:
3367 test.data.dmesg[p]['start'] = test.data.end
3368 test.data.dmesg[p]['end'] = test.data.end
3369 test.data.tSuspended = test.data.end
3370 test.data.tResumed = test.data.end
3371 test.data.fwValid = False
3372
3373 # dev source and procmon events can be unreadable with mixed phase height
3374 if sysvals.usedevsrc or sysvals.useprocmon:
3375 sysvals.mixedphaseheight = False
3376
3377 # expand phase boundaries so there are no gaps
3378 for data in testdata:
3379 lp = data.sortedPhases()[0]
3380 for p in data.sortedPhases():
3381 if(p != lp and not ('machine' in p and 'machine' in lp)):
3382 data.dmesg[lp]['end'] = data.dmesg[p]['start']
3383 lp = p
3384
3385 for i in range(len(testruns)):
3386 test = testruns[i]
3387 data = test.data
3388 # find the total time range for this test (begin, end)
3389 tlb, tle = data.start, data.end
3390 if i < len(testruns) - 1:
3391 tle = testruns[i+1].data.start
3392 # add the process usage data to the timeline
3393 if sysvals.useprocmon:
3394 data.createProcessUsageEvents()
3395 # add the traceevent data to the device hierarchy
3396 if(sysvals.usetraceevents):
3397 # add actual trace funcs
3398 for name in sorted(test.ttemp):
3399 for event in test.ttemp[name]:
3400 data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
3401 # add the kprobe based virtual tracefuncs as actual devices
3402 for key in sorted(tp.ktemp):
3403 name, pid = key
3404 if name not in sysvals.tracefuncs:
3405 continue
3406 if pid not in data.devpids:
3407 data.devpids.append(pid)
3408 for e in tp.ktemp[key]:
3409 kb, ke = e['begin'], e['end']
3410 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3411 continue
3412 color = sysvals.kprobeColor(name)
3413 data.newActionGlobal(e['name'], kb, ke, pid, color)
3414 # add config base kprobes and dev kprobes
3415 if sysvals.usedevsrc:
3416 for key in sorted(tp.ktemp):
3417 name, pid = key
3418 if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3419 continue
3420 for e in tp.ktemp[key]:
3421 kb, ke = e['begin'], e['end']
3422 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3423 continue
3424 data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3425 ke, e['cdata'], e['rdata'])
3426 if sysvals.usecallgraph:
3427 # add the callgraph data to the device hierarchy
3428 sortlist = dict()
3429 for key in sorted(test.ftemp):
3430 proc, pid = key
3431 for cg in test.ftemp[key]:
3432 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3433 continue
3434 if(not cg.postProcess()):
3435 id = 'task %s' % (pid)
3436 sysvals.vprint('Sanity check failed for '+\
3437 id+', ignoring this callback')
3438 continue
3439 # match cg data to devices
3440 devname = ''
3441 if sysvals.suspendmode != 'command':
3442 devname = cg.deviceMatch(pid, data)
3443 if not devname:
3444 sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3445 sortlist[sortkey] = cg
3446 elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3447 sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3448 (devname, len(cg.list)))
3449 # create blocks for orphan cg data
3450 for sortkey in sorted(sortlist):
3451 cg = sortlist[sortkey]
3452 name = cg.name
3453 if sysvals.isCallgraphFunc(name):
3454 sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3455 cg.newActionFromFunction(data)
3456 if sysvals.suspendmode == 'command':
3457 return (testdata, '')
3458
3459 # fill in any missing phases
3460 error = []
3461 for data in testdata:
3462 tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3463 terr = ''
3464 phasedef = data.phasedef
3465 lp = 'suspend_prepare'
3466 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3467 if p not in data.dmesg:
3468 if not terr:
3469 pprint('TEST%s FAILED: %s failed in %s phase' % (tn, sysvals.suspendmode, lp))
3470 terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, lp)
3471 error.append(terr)
3472 if data.tSuspended == 0:
3473 data.tSuspended = data.dmesg[lp]['end']
3474 if data.tResumed == 0:
3475 data.tResumed = data.dmesg[lp]['end']
3476 data.fwValid = False
3477 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3478 lp = p
3479 if not terr and data.enterfail:
3480 pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3481 terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3482 error.append(terr)
3483 if data.tSuspended == 0:
3484 data.tSuspended = data.tKernRes
3485 if data.tResumed == 0:
3486 data.tResumed = data.tSuspended
3487
3488 if(len(sysvals.devicefilter) > 0):
3489 data.deviceFilter(sysvals.devicefilter)
3490 data.fixupInitcallsThatDidntReturn()
3491 if sysvals.usedevsrc:
3492 data.optimizeDevSrc()
3493
3494 # x2: merge any overlapping devices between test runs
3495 if sysvals.usedevsrc and len(testdata) > 1:
3496 tc = len(testdata)
3497 for i in range(tc - 1):
3498 devlist = testdata[i].overflowDevices()
3499 for j in range(i + 1, tc):
3500 testdata[j].mergeOverlapDevices(devlist)
3501 testdata[0].stitchTouchingThreads(testdata[1:])
3502 return (testdata, ', '.join(error))
3503
3504# Function: loadKernelLog
3505# Description:
3506# [deprecated for kernel 3.15.0 or newer]
3507# load the dmesg file into memory and fix up any ordering issues
3508# The dmesg filename is taken from sysvals
3509# Output:
3510# An array of empty Data objects with only their dmesgtext attributes set
3511def loadKernelLog():
3512 sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3513 os.path.basename(sysvals.dmesgfile))
3514 if(os.path.exists(sysvals.dmesgfile) == False):
3515 doError('%s does not exist' % sysvals.dmesgfile)
3516
3517 # there can be multiple test runs in a single file
3518 tp = TestProps()
3519 tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3520 testruns = []
3521 data = 0
3522 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3523 for line in lf:
3524 line = line.replace('\r\n', '')
3525 idx = line.find('[')
3526 if idx > 1:
3527 line = line[idx:]
3528 if tp.stampInfo(line):
3529 continue
3530 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3531 if(not m):
3532 continue
3533 msg = m.group("msg")
3534 if(re.match('PM: Syncing filesystems.*', msg)):
3535 if(data):
3536 testruns.append(data)
3537 data = Data(len(testruns))
3538 tp.parseStamp(data, sysvals)
3539 if(not data):
3540 continue
3541 m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3542 if(m):
3543 sysvals.stamp['kernel'] = m.group('k')
3544 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3545 if(m):
3546 sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3547 data.dmesgtext.append(line)
3548 lf.close()
3549
3550 if data:
3551 testruns.append(data)
3552 if len(testruns) < 1:
3553 doError('dmesg log has no suspend/resume data: %s' \
3554 % sysvals.dmesgfile)
3555
3556 # fix lines with same timestamp/function with the call and return swapped
3557 for data in testruns:
3558 last = ''
3559 for line in data.dmesgtext:
3560 mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
3561 '(?P<f>.*)\+ @ .*, parent: .*', line)
3562 mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3563 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3564 if(mc and mr and (mc.group('t') == mr.group('t')) and
3565 (mc.group('f') == mr.group('f'))):
3566 i = data.dmesgtext.index(last)
3567 j = data.dmesgtext.index(line)
3568 data.dmesgtext[i] = line
3569 data.dmesgtext[j] = last
3570 last = line
3571 return testruns
3572
3573# Function: parseKernelLog
3574# Description:
3575# [deprecated for kernel 3.15.0 or newer]
3576# Analyse a dmesg log output file generated from this app during
3577# the execution phase. Create a set of device structures in memory
3578# for subsequent formatting in the html output file
3579# This call is only for legacy support on kernels where the ftrace
3580# data lacks the suspend_resume or device_pm_callbacks trace events.
3581# Arguments:
3582# data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3583# Output:
3584# The filled Data object
3585def parseKernelLog(data):
3586 phase = 'suspend_runtime'
3587
3588 if(data.fwValid):
3589 sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3590 (data.fwSuspend, data.fwResume))
3591
3592 # dmesg phase match table
3593 dm = {
3594 'suspend_prepare': ['PM: Syncing filesystems.*'],
3595 'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
3596 'suspend_late': ['PM: suspend of devices complete after.*'],
3597 'suspend_noirq': ['PM: late suspend of devices complete after.*'],
3598 'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
3599 'resume_machine': ['ACPI: Low-level resume complete.*'],
3600 'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
3601 'resume_early': ['PM: noirq resume of devices complete after.*'],
3602 'resume': ['PM: early resume of devices complete after.*'],
3603 'resume_complete': ['PM: resume of devices complete after.*'],
3604 'post_resume': ['.*Restarting tasks \.\.\..*'],
3605 }
3606 if(sysvals.suspendmode == 'standby'):
3607 dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3608 elif(sysvals.suspendmode == 'disk'):
3609 dm['suspend_late'] = ['PM: freeze of devices complete after.*']
3610 dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
3611 dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
3612 dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3613 dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
3614 dm['resume'] = ['PM: early restore of devices complete after.*']
3615 dm['resume_complete'] = ['PM: restore of devices complete after.*']
3616 elif(sysvals.suspendmode == 'freeze'):
3617 dm['resume_machine'] = ['ACPI: resume from mwait']
3618
3619 # action table (expected events that occur and show up in dmesg)
3620 at = {
3621 'sync_filesystems': {
3622 'smsg': 'PM: Syncing filesystems.*',
3623 'emsg': 'PM: Preparing system for mem sleep.*' },
3624 'freeze_user_processes': {
3625 'smsg': 'Freezing user space processes .*',
3626 'emsg': 'Freezing remaining freezable tasks.*' },
3627 'freeze_tasks': {
3628 'smsg': 'Freezing remaining freezable tasks.*',
3629 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3630 'ACPI prepare': {
3631 'smsg': 'ACPI: Preparing to enter system sleep state.*',
3632 'emsg': 'PM: Saving platform NVS memory.*' },
3633 'PM vns': {
3634 'smsg': 'PM: Saving platform NVS memory.*',
3635 'emsg': 'Disabling non-boot CPUs .*' },
3636 }
3637
3638 t0 = -1.0
3639 cpu_start = -1.0
3640 prevktime = -1.0
3641 actions = dict()
3642 for line in data.dmesgtext:
3643 # parse each dmesg line into the time and message
3644 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3645 if(m):
3646 val = m.group('ktime')
3647 try:
3648 ktime = float(val)
3649 except:
3650 continue
3651 msg = m.group('msg')
3652 # initialize data start to first line time
3653 if t0 < 0:
3654 data.setStart(ktime)
3655 t0 = ktime
3656 else:
3657 continue
3658
3659 # check for a phase change line
3660 phasechange = False
3661 for p in dm:
3662 for s in dm[p]:
3663 if(re.match(s, msg)):
3664 phasechange, phase = True, p
3665 break
3666
3667 # hack for determining resume_machine end for freeze
3668 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3669 and phase == 'resume_machine' and \
3670 re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3671 data.setPhase(phase, ktime, False)
3672 phase = 'resume_noirq'
3673 data.setPhase(phase, ktime, True)
3674
3675 if phasechange:
3676 if phase == 'suspend_prepare':
3677 data.setPhase(phase, ktime, True)
3678 data.setStart(ktime)
3679 data.tKernSus = ktime
3680 elif phase == 'suspend':
3681 lp = data.lastPhase()
3682 if lp:
3683 data.setPhase(lp, ktime, False)
3684 data.setPhase(phase, ktime, True)
3685 elif phase == 'suspend_late':
3686 lp = data.lastPhase()
3687 if lp:
3688 data.setPhase(lp, ktime, False)
3689 data.setPhase(phase, ktime, True)
3690 elif phase == 'suspend_noirq':
3691 lp = data.lastPhase()
3692 if lp:
3693 data.setPhase(lp, ktime, False)
3694 data.setPhase(phase, ktime, True)
3695 elif phase == 'suspend_machine':
3696 lp = data.lastPhase()
3697 if lp:
3698 data.setPhase(lp, ktime, False)
3699 data.setPhase(phase, ktime, True)
3700 elif phase == 'resume_machine':
3701 lp = data.lastPhase()
3702 if(sysvals.suspendmode in ['freeze', 'standby']):
3703 data.tSuspended = prevktime
3704 if lp:
3705 data.setPhase(lp, prevktime, False)
3706 else:
3707 data.tSuspended = ktime
3708 if lp:
3709 data.setPhase(lp, prevktime, False)
3710 data.tResumed = ktime
3711 data.setPhase(phase, ktime, True)
3712 elif phase == 'resume_noirq':
3713 lp = data.lastPhase()
3714 if lp:
3715 data.setPhase(lp, ktime, False)
3716 data.setPhase(phase, ktime, True)
3717 elif phase == 'resume_early':
3718 lp = data.lastPhase()
3719 if lp:
3720 data.setPhase(lp, ktime, False)
3721 data.setPhase(phase, ktime, True)
3722 elif phase == 'resume':
3723 lp = data.lastPhase()
3724 if lp:
3725 data.setPhase(lp, ktime, False)
3726 data.setPhase(phase, ktime, True)
3727 elif phase == 'resume_complete':
3728 lp = data.lastPhase()
3729 if lp:
3730 data.setPhase(lp, ktime, False)
3731 data.setPhase(phase, ktime, True)
3732 elif phase == 'post_resume':
3733 lp = data.lastPhase()
3734 if lp:
3735 data.setPhase(lp, ktime, False)
3736 data.setEnd(ktime)
3737 data.tKernRes = ktime
3738 break
3739
3740 # -- device callbacks --
3741 if(phase in data.sortedPhases()):
3742 # device init call
3743 if(re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3744 sm = re.match('calling (?P<f>.*)\+ @ '+\
3745 '(?P<n>.*), parent: (?P<p>.*)', msg);
3746 f = sm.group('f')
3747 n = sm.group('n')
3748 p = sm.group('p')
3749 if(f and n and p):
3750 data.newAction(phase, f, int(n), p, ktime, -1, '')
3751 # device init return
3752 elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3753 '(?P<t>.*) usecs', msg)):
3754 sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3755 '(?P<t>.*) usecs(?P<a>.*)', msg);
3756 f = sm.group('f')
3757 t = sm.group('t')
3758 list = data.dmesg[phase]['list']
3759 if(f in list):
3760 dev = list[f]
3761 dev['length'] = int(t)
3762 dev['end'] = ktime
3763
3764 # if trace events are not available, these are better than nothing
3765 if(not sysvals.usetraceevents):
3766 # look for known actions
3767 for a in sorted(at):
3768 if(re.match(at[a]['smsg'], msg)):
3769 if(a not in actions):
3770 actions[a] = []
3771 actions[a].append({'begin': ktime, 'end': ktime})
3772 if(re.match(at[a]['emsg'], msg)):
3773 if(a in actions):
3774 actions[a][-1]['end'] = ktime
3775 # now look for CPU on/off events
3776 if(re.match('Disabling non-boot CPUs .*', msg)):
3777 # start of first cpu suspend
3778 cpu_start = ktime
3779 elif(re.match('Enabling non-boot CPUs .*', msg)):
3780 # start of first cpu resume
3781 cpu_start = ktime
3782 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3783 # end of a cpu suspend, start of the next
3784 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3785 cpu = 'CPU'+m.group('cpu')
3786 if(cpu not in actions):
3787 actions[cpu] = []
3788 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3789 cpu_start = ktime
3790 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3791 # end of a cpu resume, start of the next
3792 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3793 cpu = 'CPU'+m.group('cpu')
3794 if(cpu not in actions):
3795 actions[cpu] = []
3796 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3797 cpu_start = ktime
3798 prevktime = ktime
3799 data.initDevicegroups()
3800
3801 # fill in any missing phases
3802 phasedef = data.phasedef
3803 terr, lp = '', 'suspend_prepare'
3804 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3805 if p not in data.dmesg:
3806 if not terr:
3807 pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
3808 terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
3809 if data.tSuspended == 0:
3810 data.tSuspended = data.dmesg[lp]['end']
3811 if data.tResumed == 0:
3812 data.tResumed = data.dmesg[lp]['end']
3813 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3814 lp = p
3815 lp = data.sortedPhases()[0]
3816 for p in data.sortedPhases():
3817 if(p != lp and not ('machine' in p and 'machine' in lp)):
3818 data.dmesg[lp]['end'] = data.dmesg[p]['start']
3819 lp = p
3820 if data.tSuspended == 0:
3821 data.tSuspended = data.tKernRes
3822 if data.tResumed == 0:
3823 data.tResumed = data.tSuspended
3824
3825 # fill in any actions we've found
3826 for name in sorted(actions):
3827 for event in actions[name]:
3828 data.newActionGlobal(name, event['begin'], event['end'])
3829
3830 if(len(sysvals.devicefilter) > 0):
3831 data.deviceFilter(sysvals.devicefilter)
3832 data.fixupInitcallsThatDidntReturn()
3833 return True
3834
3835def callgraphHTML(sv, hf, num, cg, title, color, devid):
3836 html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
3837 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3838 html_func_end = '</article>\n'
3839 html_func_leaf = '<article>{0} {1}</article>\n'
3840
3841 cgid = devid
3842 if cg.id:
3843 cgid += cg.id
3844 cglen = (cg.end - cg.start) * 1000
3845 if cglen < sv.mincglen:
3846 return num
3847
3848 fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
3849 flen = fmt % (cglen, cg.start, cg.end)
3850 hf.write(html_func_top.format(cgid, color, num, title, flen))
3851 num += 1
3852 for line in cg.list:
3853 if(line.length < 0.000000001):
3854 flen = ''
3855 else:
3856 fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
3857 flen = fmt % (line.length*1000, line.time)
3858 if line.isLeaf():
3859 hf.write(html_func_leaf.format(line.name, flen))
3860 elif line.freturn:
3861 hf.write(html_func_end)
3862 else:
3863 hf.write(html_func_start.format(num, line.name, flen))
3864 num += 1
3865 hf.write(html_func_end)
3866 return num
3867
3868def addCallgraphs(sv, hf, data):
3869 hf.write('<section id="callgraphs" class="callgraph">\n')
3870 # write out the ftrace data converted to html
3871 num = 0
3872 for p in data.sortedPhases():
3873 if sv.cgphase and p != sv.cgphase:
3874 continue
3875 list = data.dmesg[p]['list']
3876 for devname in data.sortedDevices(p):
3877 if len(sv.cgfilter) > 0 and devname not in sv.cgfilter:
3878 continue
3879 dev = list[devname]
3880 color = 'white'
3881 if 'color' in data.dmesg[p]:
3882 color = data.dmesg[p]['color']
3883 if 'color' in dev:
3884 color = dev['color']
3885 name = devname
3886 if(devname in sv.devprops):
3887 name = sv.devprops[devname].altName(devname)
3888 if sv.suspendmode in suspendmodename:
3889 name += ' '+p
3890 if('ftrace' in dev):
3891 cg = dev['ftrace']
3892 if cg.name == sv.ftopfunc:
3893 name = 'top level suspend/resume call'
3894 num = callgraphHTML(sv, hf, num, cg,
3895 name, color, dev['id'])
3896 if('ftraces' in dev):
3897 for cg in dev['ftraces']:
3898 num = callgraphHTML(sv, hf, num, cg,
3899 name+' → '+cg.name, color, dev['id'])
3900 hf.write('\n\n </section>\n')
3901
3902def summaryCSS(title, center=True):
3903 tdcenter = 'text-align:center;' if center else ''
3904 out = '<!DOCTYPE html>\n<html>\n<head>\n\
3905 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3906 <title>'+title+'</title>\n\
3907 <style type=\'text/css\'>\n\
3908 .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
3909 table {width:100%;border-collapse: collapse;border:1px solid;}\n\
3910 th {border: 1px solid black;background:#222;color:white;}\n\
3911 td {font: 14px "Times New Roman";'+tdcenter+'}\n\
3912 tr.head td {border: 1px solid black;background:#aaa;}\n\
3913 tr.alt {background-color:#ddd;}\n\
3914 tr.notice {color:red;}\n\
3915 .minval {background-color:#BBFFBB;}\n\
3916 .medval {background-color:#BBBBFF;}\n\
3917 .maxval {background-color:#FFBBBB;}\n\
3918 .head a {color:#000;text-decoration: none;}\n\
3919 </style>\n</head>\n<body>\n'
3920 return out
3921
3922# Function: createHTMLSummarySimple
3923# Description:
3924# Create summary html file for a series of tests
3925# Arguments:
3926# testruns: array of Data objects from parseTraceLog
3927def createHTMLSummarySimple(testruns, htmlfile, title):
3928 # write the html header first (html head, css code, up to body start)
3929 html = summaryCSS('Summary - SleepGraph')
3930
3931 # extract the test data into list
3932 list = dict()
3933 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
3934 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
3935 num = 0
3936 useturbo = False
3937 lastmode = ''
3938 cnt = dict()
3939 for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
3940 mode = data['mode']
3941 if mode not in list:
3942 list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
3943 if lastmode and lastmode != mode and num > 0:
3944 for i in range(2):
3945 s = sorted(tMed[i])
3946 list[lastmode]['med'][i] = s[int(len(s)//2)]
3947 iMed[i] = tMed[i][list[lastmode]['med'][i]]
3948 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
3949 list[lastmode]['min'] = tMin
3950 list[lastmode]['max'] = tMax
3951 list[lastmode]['idx'] = (iMin, iMed, iMax)
3952 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
3953 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
3954 num = 0
3955 pkgpc10 = syslpi = ''
3956 if 'pkgpc10' in data and 'syslpi' in data:
3957 pkgpc10 = data['pkgpc10']
3958 syslpi = data['syslpi']
3959 useturbo = True
3960 res = data['result']
3961 tVal = [float(data['suspend']), float(data['resume'])]
3962 list[mode]['data'].append([data['host'], data['kernel'],
3963 data['time'], tVal[0], tVal[1], data['url'], res,
3964 data['issues'], data['sus_worst'], data['sus_worsttime'],
3965 data['res_worst'], data['res_worsttime'], pkgpc10, syslpi])
3966 idx = len(list[mode]['data']) - 1
3967 if res.startswith('fail in'):
3968 res = 'fail'
3969 if res not in cnt:
3970 cnt[res] = 1
3971 else:
3972 cnt[res] += 1
3973 if res == 'pass':
3974 for i in range(2):
3975 tMed[i][tVal[i]] = idx
3976 tAvg[i] += tVal[i]
3977 if tMin[i] == 0 or tVal[i] < tMin[i]:
3978 iMin[i] = idx
3979 tMin[i] = tVal[i]
3980 if tMax[i] == 0 or tVal[i] > tMax[i]:
3981 iMax[i] = idx
3982 tMax[i] = tVal[i]
3983 num += 1
3984 lastmode = mode
3985 if lastmode and num > 0:
3986 for i in range(2):
3987 s = sorted(tMed[i])
3988 list[lastmode]['med'][i] = s[int(len(s)//2)]
3989 iMed[i] = tMed[i][list[lastmode]['med'][i]]
3990 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
3991 list[lastmode]['min'] = tMin
3992 list[lastmode]['max'] = tMax
3993 list[lastmode]['idx'] = (iMin, iMed, iMax)
3994
3995 # group test header
3996 desc = []
3997 for ilk in sorted(cnt, reverse=True):
3998 if cnt[ilk] > 0:
3999 desc.append('%d %s' % (cnt[ilk], ilk))
4000 html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4001 th = '\t<th>{0}</th>\n'
4002 td = '\t<td>{0}</td>\n'
4003 tdh = '\t<td{1}>{0}</td>\n'
4004 tdlink = '\t<td><a href="{0}">html</a></td>\n'
4005 colspan = '14' if useturbo else '12'
4006
4007 # table header
4008 html += '<table>\n<tr>\n' + th.format('#') +\
4009 th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4010 th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4011 th.format('Suspend') + th.format('Resume') +\
4012 th.format('Worst Suspend Device') + th.format('SD Time') +\
4013 th.format('Worst Resume Device') + th.format('RD Time')
4014 if useturbo:
4015 html += th.format('PkgPC10') + th.format('SysLPI')
4016 html += th.format('Detail')+'</tr>\n'
4017 # export list into html
4018 head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4019 '<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4020 '<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4021 '<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4022 '<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4023 'Resume Avg={6} '+\
4024 '<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4025 '<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4026 '<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4027 '</tr>\n'
4028 headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4029 colspan+'></td></tr>\n'
4030 for mode in sorted(list):
4031 # header line for each suspend mode
4032 num = 0
4033 tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4034 list[mode]['max'], list[mode]['med']
4035 count = len(list[mode]['data'])
4036 if 'idx' in list[mode]:
4037 iMin, iMed, iMax = list[mode]['idx']
4038 html += head.format('%d' % count, mode.upper(),
4039 '%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4040 '%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4041 mode.lower()
4042 )
4043 else:
4044 iMin = iMed = iMax = [-1, -1, -1]
4045 html += headnone.format('%d' % count, mode.upper())
4046 for d in list[mode]['data']:
4047 # row classes - alternate row color
4048 rcls = ['alt'] if num % 2 == 1 else []
4049 if d[6] != 'pass':
4050 rcls.append('notice')
4051 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4052 # figure out if the line has sus or res highlighted
4053 idx = list[mode]['data'].index(d)
4054 tHigh = ['', '']
4055 for i in range(2):
4056 tag = 's%s' % mode if i == 0 else 'r%s' % mode
4057 if idx == iMin[i]:
4058 tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4059 elif idx == iMax[i]:
4060 tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4061 elif idx == iMed[i]:
4062 tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4063 html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4064 html += td.format(mode) # mode
4065 html += td.format(d[0]) # host
4066 html += td.format(d[1]) # kernel
4067 html += td.format(d[2]) # time
4068 html += td.format(d[6]) # result
4069 html += td.format(d[7]) # issues
4070 html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend
4071 html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume
4072 html += td.format(d[8]) # sus_worst
4073 html += td.format('%.3f ms' % d[9]) if d[9] else td.format('') # sus_worst time
4074 html += td.format(d[10]) # res_worst
4075 html += td.format('%.3f ms' % d[11]) if d[11] else td.format('') # res_worst time
4076 if useturbo:
4077 html += td.format(d[12]) # pkg_pc10
4078 html += td.format(d[13]) # syslpi
4079 html += tdlink.format(d[5]) if d[5] else td.format('') # url
4080 html += '</tr>\n'
4081 num += 1
4082
4083 # flush the data to file
4084 hf = open(htmlfile, 'w')
4085 hf.write(html+'</table>\n</body>\n</html>\n')
4086 hf.close()
4087
4088def createHTMLDeviceSummary(testruns, htmlfile, title):
4089 html = summaryCSS('Device Summary - SleepGraph', False)
4090
4091 # create global device list from all tests
4092 devall = dict()
4093 for data in testruns:
4094 host, url, devlist = data['host'], data['url'], data['devlist']
4095 for type in devlist:
4096 if type not in devall:
4097 devall[type] = dict()
4098 mdevlist, devlist = devall[type], data['devlist'][type]
4099 for name in devlist:
4100 length = devlist[name]
4101 if name not in mdevlist:
4102 mdevlist[name] = {'name': name, 'host': host,
4103 'worst': length, 'total': length, 'count': 1,
4104 'url': url}
4105 else:
4106 if length > mdevlist[name]['worst']:
4107 mdevlist[name]['worst'] = length
4108 mdevlist[name]['url'] = url
4109 mdevlist[name]['host'] = host
4110 mdevlist[name]['total'] += length
4111 mdevlist[name]['count'] += 1
4112
4113 # generate the html
4114 th = '\t<th>{0}</th>\n'
4115 td = '\t<td align=center>{0}</td>\n'
4116 tdr = '\t<td align=right>{0}</td>\n'
4117 tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4118 limit = 1
4119 for type in sorted(devall, reverse=True):
4120 num = 0
4121 devlist = devall[type]
4122 # table header
4123 html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4124 (title, type.upper(), limit)
4125 html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4126 th.format('Average Time') + th.format('Count') +\
4127 th.format('Worst Time') + th.format('Host (worst time)') +\
4128 th.format('Link (worst time)') + '</tr>\n'
4129 for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4130 devlist[k]['total'], devlist[k]['name']), reverse=True):
4131 data = devall[type][name]
4132 data['average'] = data['total'] / data['count']
4133 if data['average'] < limit:
4134 continue
4135 # row classes - alternate row color
4136 rcls = ['alt'] if num % 2 == 1 else []
4137 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4138 html += tdr.format(data['name']) # name
4139 html += td.format('%.3f ms' % data['average']) # average
4140 html += td.format(data['count']) # count
4141 html += td.format('%.3f ms' % data['worst']) # worst
4142 html += td.format(data['host']) # host
4143 html += tdlink.format(data['url']) # url
4144 html += '</tr>\n'
4145 num += 1
4146 html += '</table>\n'
4147
4148 # flush the data to file
4149 hf = open(htmlfile, 'w')
4150 hf.write(html+'</body>\n</html>\n')
4151 hf.close()
4152 return devall
4153
4154def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4155 multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4156 html = summaryCSS('Issues Summary - SleepGraph', False)
4157 total = len(testruns)
4158
4159 # generate the html
4160 th = '\t<th>{0}</th>\n'
4161 td = '\t<td align={0}>{1}</td>\n'
4162 tdlink = '<a href="{1}">{0}</a>'
4163 subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4164 html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4165 html += '<tr>\n' + th.format('Issue') + th.format('Count')
4166 if multihost:
4167 html += th.format('Hosts')
4168 html += th.format('Tests') + th.format('Fail Rate') +\
4169 th.format('First Instance') + '</tr>\n'
4170
4171 num = 0
4172 for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4173 testtotal = 0
4174 links = []
4175 for host in sorted(e['urls']):
4176 links.append(tdlink.format(host, e['urls'][host][0]))
4177 testtotal += len(e['urls'][host])
4178 rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4179 # row classes - alternate row color
4180 rcls = ['alt'] if num % 2 == 1 else []
4181 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4182 html += td.format('left', e['line']) # issue
4183 html += td.format('center', e['count']) # count
4184 if multihost:
4185 html += td.format('center', len(e['urls'])) # hosts
4186 html += td.format('center', testtotal) # test count
4187 html += td.format('center', rate) # test rate
4188 html += td.format('center nowrap', '<br>'.join(links)) # links
4189 html += '</tr>\n'
4190 num += 1
4191
4192 # flush the data to file
4193 hf = open(htmlfile, 'w')
4194 hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4195 hf.close()
4196 return issues
4197
4198def ordinal(value):
4199 suffix = 'th'
4200 if value < 10 or value > 19:
4201 if value % 10 == 1:
4202 suffix = 'st'
4203 elif value % 10 == 2:
4204 suffix = 'nd'
4205 elif value % 10 == 3:
4206 suffix = 'rd'
4207 return '%d%s' % (value, suffix)
4208
4209# Function: createHTML
4210# Description:
4211# Create the output html file from the resident test data
4212# Arguments:
4213# testruns: array of Data objects from parseKernelLog or parseTraceLog
4214# Output:
4215# True if the html file was created, false if it failed
4216def createHTML(testruns, testfail):
4217 if len(testruns) < 1:
4218 pprint('ERROR: Not enough test data to build a timeline')
4219 return
4220
4221 kerror = False
4222 for data in testruns:
4223 if data.kerror:
4224 kerror = True
4225 if(sysvals.suspendmode in ['freeze', 'standby']):
4226 data.trimFreezeTime(testruns[-1].tSuspended)
4227
4228 # html function templates
4229 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</div>\n'
4230 html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n'
4231 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4232 html_timetotal = '<table class="time1">\n<tr>'\
4233 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4234 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4235 '</tr>\n</table>\n'
4236 html_timetotal2 = '<table class="time1">\n<tr>'\
4237 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4238 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4239 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4240 '</tr>\n</table>\n'
4241 html_timetotal3 = '<table class="time1">\n<tr>'\
4242 '<td class="green">Execution Time: <b>{0} ms</b></td>'\
4243 '<td class="yellow">Command: <b>{1}</b></td>'\
4244 '</tr>\n</table>\n'
4245 html_timegroups = '<table class="time2">\n<tr>'\
4246 '<td class="green" title="time from kernel enter_state({5}) to firmware mode [kernel time only]">{4}Kernel Suspend: {0} ms</td>'\
4247 '<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
4248 '<td class="purple">{4}Firmware Resume: {2} ms</td>'\
4249 '<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
4250 '</tr>\n</table>\n'
4251 html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4252
4253 # html format variables
4254 scaleH = 20
4255 if kerror:
4256 scaleH = 40
4257
4258 # device timeline
4259 devtl = Timeline(30, scaleH)
4260
4261 # write the test title and general info header
4262 devtl.createHeader(sysvals, testruns[0].stamp)
4263
4264 # Generate the header for this timeline
4265 for data in testruns:
4266 tTotal = data.end - data.start
4267 sktime, rktime = data.getTimeValues()
4268 if(tTotal == 0):
4269 doError('No timeline data')
4270 if(len(data.tLow) > 0):
4271 low_time = '+'.join(data.tLow)
4272 if sysvals.suspendmode == 'command':
4273 run_time = '%.0f'%((data.end-data.start)*1000)
4274 if sysvals.testcommand:
4275 testdesc = sysvals.testcommand
4276 else:
4277 testdesc = 'unknown'
4278 if(len(testruns) > 1):
4279 testdesc = ordinal(data.testnumber+1)+' '+testdesc
4280 thtml = html_timetotal3.format(run_time, testdesc)
4281 devtl.html += thtml
4282 elif data.fwValid:
4283 suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0))
4284 resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0))
4285 testdesc1 = 'Total'
4286 testdesc2 = ''
4287 stitle = 'time from kernel enter_state(%s) to low-power mode [kernel & firmware time]' % sysvals.suspendmode
4288 rtitle = 'time from low-power mode to return from kernel enter_state(%s) [firmware & kernel time]' % sysvals.suspendmode
4289 if(len(testruns) > 1):
4290 testdesc1 = testdesc2 = ordinal(data.testnumber+1)
4291 testdesc2 += ' '
4292 if(len(data.tLow) == 0):
4293 thtml = html_timetotal.format(suspend_time, \
4294 resume_time, testdesc1, stitle, rtitle)
4295 else:
4296 thtml = html_timetotal2.format(suspend_time, low_time, \
4297 resume_time, testdesc1, stitle, rtitle)
4298 devtl.html += thtml
4299 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4300 rftime = '%.3f'%(data.fwResume / 1000000.0)
4301 devtl.html += html_timegroups.format('%.3f'%sktime, \
4302 sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode)
4303 else:
4304 suspend_time = '%.3f' % sktime
4305 resume_time = '%.3f' % rktime
4306 testdesc = 'Kernel'
4307 stitle = 'time from kernel enter_state(%s) to firmware mode [kernel time only]' % sysvals.suspendmode
4308 rtitle = 'time from firmware mode to return from kernel enter_state(%s) [kernel time only]' % sysvals.suspendmode
4309 if(len(testruns) > 1):
4310 testdesc = ordinal(data.testnumber+1)+' '+testdesc
4311 if(len(data.tLow) == 0):
4312 thtml = html_timetotal.format(suspend_time, \
4313 resume_time, testdesc, stitle, rtitle)
4314 else:
4315 thtml = html_timetotal2.format(suspend_time, low_time, \
4316 resume_time, testdesc, stitle, rtitle)
4317 devtl.html += thtml
4318
4319 if testfail:
4320 devtl.html += html_fail.format(testfail)
4321
4322 # time scale for potentially multiple datasets
4323 t0 = testruns[0].start
4324 tMax = testruns[-1].end
4325 tTotal = tMax - t0
4326
4327 # determine the maximum number of rows we need to draw
4328 fulllist = []
4329 threadlist = []
4330 pscnt = 0
4331 devcnt = 0
4332 for data in testruns:
4333 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4334 for group in data.devicegroups:
4335 devlist = []
4336 for phase in group:
4337 for devname in sorted(data.tdevlist[phase]):
4338 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4339 devlist.append(d)
4340 if d.isa('kth'):
4341 threadlist.append(d)
4342 else:
4343 if d.isa('ps'):
4344 pscnt += 1
4345 else:
4346 devcnt += 1
4347 fulllist.append(d)
4348 if sysvals.mixedphaseheight:
4349 devtl.getPhaseRows(devlist)
4350 if not sysvals.mixedphaseheight:
4351 if len(threadlist) > 0 and len(fulllist) > 0:
4352 if pscnt > 0 and devcnt > 0:
4353 msg = 'user processes & device pm callbacks'
4354 elif pscnt > 0:
4355 msg = 'user processes'
4356 else:
4357 msg = 'device pm callbacks'
4358 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4359 fulllist.insert(0, d)
4360 devtl.getPhaseRows(fulllist)
4361 if len(threadlist) > 0:
4362 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4363 threadlist.insert(0, d)
4364 devtl.getPhaseRows(threadlist, devtl.rows)
4365 devtl.calcTotalRows()
4366
4367 # draw the full timeline
4368 devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4369 for data in testruns:
4370 # draw each test run and block chronologically
4371 phases = {'suspend':[],'resume':[]}
4372 for phase in data.sortedPhases():
4373 if data.dmesg[phase]['start'] >= data.tSuspended:
4374 phases['resume'].append(phase)
4375 else:
4376 phases['suspend'].append(phase)
4377 # now draw the actual timeline blocks
4378 for dir in phases:
4379 # draw suspend and resume blocks separately
4380 bname = '%s%d' % (dir[0], data.testnumber)
4381 if dir == 'suspend':
4382 m0 = data.start
4383 mMax = data.tSuspended
4384 left = '%f' % (((m0-t0)*100.0)/tTotal)
4385 else:
4386 m0 = data.tSuspended
4387 mMax = data.end
4388 # in an x2 run, remove any gap between blocks
4389 if len(testruns) > 1 and data.testnumber == 0:
4390 mMax = testruns[1].start
4391 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4392 mTotal = mMax - m0
4393 # if a timeline block is 0 length, skip altogether
4394 if mTotal == 0:
4395 continue
4396 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4397 devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4398 for b in phases[dir]:
4399 # draw the phase color background
4400 phase = data.dmesg[b]
4401 length = phase['end']-phase['start']
4402 left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4403 width = '%f' % ((length*100.0)/mTotal)
4404 devtl.html += devtl.html_phase.format(left, width, \
4405 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4406 data.dmesg[b]['color'], '')
4407 for e in data.errorinfo[dir]:
4408 # draw red lines for any kernel errors found
4409 type, t, idx1, idx2 = e
4410 id = '%d_%d' % (idx1, idx2)
4411 right = '%f' % (((mMax-t)*100.0)/mTotal)
4412 devtl.html += html_error.format(right, id, type)
4413 for b in phases[dir]:
4414 # draw the devices for this phase
4415 phaselist = data.dmesg[b]['list']
4416 for d in sorted(data.tdevlist[b]):
4417 name = d
4418 drv = ''
4419 dev = phaselist[d]
4420 xtraclass = ''
4421 xtrainfo = ''
4422 xtrastyle = ''
4423 if 'htmlclass' in dev:
4424 xtraclass = dev['htmlclass']
4425 if 'color' in dev:
4426 xtrastyle = 'background:%s;' % dev['color']
4427 if(d in sysvals.devprops):
4428 name = sysvals.devprops[d].altName(d)
4429 xtraclass = sysvals.devprops[d].xtraClass()
4430 xtrainfo = sysvals.devprops[d].xtraInfo()
4431 elif xtraclass == ' kth':
4432 xtrainfo = ' kernel_thread'
4433 if('drv' in dev and dev['drv']):
4434 drv = ' {%s}' % dev['drv']
4435 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4436 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4437 top = '%.3f' % (rowtop + devtl.scaleH)
4438 left = '%f' % (((dev['start']-m0)*100)/mTotal)
4439 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4440 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4441 title = name+drv+xtrainfo+length
4442 if sysvals.suspendmode == 'command':
4443 title += sysvals.testcommand
4444 elif xtraclass == ' ps':
4445 if 'suspend' in b:
4446 title += 'pre_suspend_process'
4447 else:
4448 title += 'post_resume_process'
4449 else:
4450 title += b
4451 devtl.html += devtl.html_device.format(dev['id'], \
4452 title, left, top, '%.3f'%rowheight, width, \
4453 d+drv, xtraclass, xtrastyle)
4454 if('cpuexec' in dev):
4455 for t in sorted(dev['cpuexec']):
4456 start, end = t
4457 j = float(dev['cpuexec'][t]) / 5
4458 if j > 1.0:
4459 j = 1.0
4460 height = '%.3f' % (rowheight/3)
4461 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4462 left = '%f' % (((start-m0)*100)/mTotal)
4463 width = '%f' % ((end-start)*100/mTotal)
4464 color = 'rgba(255, 0, 0, %f)' % j
4465 devtl.html += \
4466 html_cpuexec.format(left, top, height, width, color)
4467 if('src' not in dev):
4468 continue
4469 # draw any trace events for this device
4470 for e in dev['src']:
4471 height = '%.3f' % devtl.rowH
4472 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4473 left = '%f' % (((e.time-m0)*100)/mTotal)
4474 width = '%f' % (e.length*100/mTotal)
4475 xtrastyle = ''
4476 if e.color:
4477 xtrastyle = 'background:%s;' % e.color
4478 devtl.html += \
4479 html_traceevent.format(e.title(), \
4480 left, top, height, width, e.text(), '', xtrastyle)
4481 # draw the time scale, try to make the number of labels readable
4482 devtl.createTimeScale(m0, mMax, tTotal, dir)
4483 devtl.html += '</div>\n'
4484
4485 # timeline is finished
4486 devtl.html += '</div>\n</div>\n'
4487
4488 # draw a legend which describes the phases by color
4489 if sysvals.suspendmode != 'command':
4490 phasedef = testruns[-1].phasedef
4491 devtl.html += '<div class="legend">\n'
4492 pdelta = 100.0/len(phasedef.keys())
4493 pmargin = pdelta / 4.0
4494 for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4495 id, p = '', phasedef[phase]
4496 for word in phase.split('_'):
4497 id += word[0]
4498 order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4499 name = phase.replace('_', ' ')
4500 devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4501 devtl.html += '</div>\n'
4502
4503 hf = open(sysvals.htmlfile, 'w')
4504 addCSS(hf, sysvals, len(testruns), kerror)
4505
4506 # write the device timeline
4507 hf.write(devtl.html)
4508 hf.write('<div id="devicedetailtitle"></div>\n')
4509 hf.write('<div id="devicedetail" style="display:none;">\n')
4510 # draw the colored boxes for the device detail section
4511 for data in testruns:
4512 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4513 pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4514 hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4515 '0', '0', pscolor))
4516 for b in data.sortedPhases():
4517 phase = data.dmesg[b]
4518 length = phase['end']-phase['start']
4519 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4520 width = '%.3f' % ((length*100.0)/tTotal)
4521 hf.write(devtl.html_phaselet.format(b, left, width, \
4522 data.dmesg[b]['color']))
4523 hf.write(devtl.html_phaselet.format('post_resume_process', \
4524 '0', '0', pscolor))
4525 if sysvals.suspendmode == 'command':
4526 hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4527 hf.write('</div>\n')
4528 hf.write('</div>\n')
4529
4530 # write the ftrace data (callgraph)
4531 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4532 data = testruns[sysvals.cgtest]
4533 else:
4534 data = testruns[-1]
4535 if sysvals.usecallgraph:
4536 addCallgraphs(sysvals, hf, data)
4537
4538 # add the test log as a hidden div
4539 if sysvals.testlog and sysvals.logmsg:
4540 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4541 # add the dmesg log as a hidden div
4542 if sysvals.dmesglog and sysvals.dmesgfile:
4543 hf.write('<div id="dmesglog" style="display:none;">\n')
4544 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4545 for line in lf:
4546 line = line.replace('<', '<').replace('>', '>')
4547 hf.write(line)
4548 lf.close()
4549 hf.write('</div>\n')
4550 # add the ftrace log as a hidden div
4551 if sysvals.ftracelog and sysvals.ftracefile:
4552 hf.write('<div id="ftracelog" style="display:none;">\n')
4553 lf = sysvals.openlog(sysvals.ftracefile, 'r')
4554 for line in lf:
4555 hf.write(line)
4556 lf.close()
4557 hf.write('</div>\n')
4558
4559 # write the footer and close
4560 addScriptCode(hf, testruns)
4561 hf.write('</body>\n</html>\n')
4562 hf.close()
4563 return True
4564
4565def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4566 kernel = sv.stamp['kernel']
4567 host = sv.hostname[0].upper()+sv.hostname[1:]
4568 mode = sv.suspendmode
4569 if sv.suspendmode in suspendmodename:
4570 mode = suspendmodename[sv.suspendmode]
4571 title = host+' '+mode+' '+kernel
4572
4573 # various format changes by flags
4574 cgchk = 'checked'
4575 cgnchk = 'not(:checked)'
4576 if sv.cgexp:
4577 cgchk = 'not(:checked)'
4578 cgnchk = 'checked'
4579
4580 hoverZ = 'z-index:8;'
4581 if sv.usedevsrc:
4582 hoverZ = ''
4583
4584 devlistpos = 'absolute'
4585 if testcount > 1:
4586 devlistpos = 'relative'
4587
4588 scaleTH = 20
4589 if kerror:
4590 scaleTH = 60
4591
4592 # write the html header first (html head, css code, up to body start)
4593 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4594 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4595 <title>'+title+'</title>\n\
4596 <style type=\'text/css\'>\n\
4597 body {overflow-y:scroll;}\n\
4598 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4599 .stamp.sysinfo {font:10px Arial;}\n\
4600 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4601 .callgraph article * {padding-left:28px;}\n\
4602 h1 {color:black;font:bold 30px Times;}\n\
4603 t0 {color:black;font:bold 30px Times;}\n\
4604 t1 {color:black;font:30px Times;}\n\
4605 t2 {color:black;font:25px Times;}\n\
4606 t3 {color:black;font:20px Times;white-space:nowrap;}\n\
4607 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4608 cS {font:bold 13px Times;}\n\
4609 table {width:100%;}\n\
4610 .gray {background:rgba(80,80,80,0.1);}\n\
4611 .green {background:rgba(204,255,204,0.4);}\n\
4612 .purple {background:rgba(128,0,128,0.2);}\n\
4613 .yellow {background:rgba(255,255,204,0.4);}\n\
4614 .blue {background:rgba(169,208,245,0.4);}\n\
4615 .time1 {font:22px Arial;border:1px solid;}\n\
4616 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4617 .testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
4618 td {text-align:center;}\n\
4619 r {color:#500000;font:15px Tahoma;}\n\
4620 n {color:#505050;font:15px Tahoma;}\n\
4621 .tdhl {color:red;}\n\
4622 .hide {display:none;}\n\
4623 .pf {display:none;}\n\
4624 .pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4625 .pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4626 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4627 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4628 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4629 .thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\
4630 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4631 .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4632 .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4633 .hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4634 .hover.sync {background:white;}\n\
4635 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4636 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4637 .traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\
4638 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4639 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4640 .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4641 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4642 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4643 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4644 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4645 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4646 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4647 .devlist {position:'+devlistpos+';width:190px;}\n\
4648 a:link {color:white;text-decoration:none;}\n\
4649 a:visited {color:white;}\n\
4650 a:hover {color:white;}\n\
4651 a:active {color:white;}\n\
4652 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4653 #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4654 .tblock {position:absolute;height:100%;background:#ddd;}\n\
4655 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4656 .bg {z-index:1;}\n\
4657'+extra+'\
4658 </style>\n</head>\n<body>\n'
4659 hf.write(html_header)
4660
4661# Function: addScriptCode
4662# Description:
4663# Adds the javascript code to the output html
4664# Arguments:
4665# hf: the open html file pointer
4666# testruns: array of Data objects from parseKernelLog or parseTraceLog
4667def addScriptCode(hf, testruns):
4668 t0 = testruns[0].start * 1000
4669 tMax = testruns[-1].end * 1000
4670 # create an array in javascript memory with the device details
4671 detail = ' var devtable = [];\n'
4672 for data in testruns:
4673 topo = data.deviceTopology()
4674 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
4675 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
4676 # add the code which will manipulate the data in the browser
4677 script_code = \
4678 '<script type="text/javascript">\n'+detail+\
4679 ' var resolution = -1;\n'\
4680 ' var dragval = [0, 0];\n'\
4681 ' function redrawTimescale(t0, tMax, tS) {\n'\
4682 ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4683 ' var tTotal = tMax - t0;\n'\
4684 ' var list = document.getElementsByClassName("tblock");\n'\
4685 ' for (var i = 0; i < list.length; i++) {\n'\
4686 ' var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4687 ' var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4688 ' var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4689 ' var mMax = m0 + mTotal;\n'\
4690 ' var html = "";\n'\
4691 ' var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4692 ' if(divTotal > 1000) continue;\n'\
4693 ' var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4694 ' var pos = 0.0, val = 0.0;\n'\
4695 ' for (var j = 0; j < divTotal; j++) {\n'\
4696 ' var htmlline = "";\n'\
4697 ' var mode = list[i].id[5];\n'\
4698 ' if(mode == "s") {\n'\
4699 ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4700 ' val = (j-divTotal+1)*tS;\n'\
4701 ' if(j == divTotal - 1)\n'\
4702 ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\
4703 ' else\n'\
4704 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4705 ' } else {\n'\
4706 ' pos = 100 - (((j)*tS*100)/mTotal);\n'\
4707 ' val = (j)*tS;\n'\
4708 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4709 ' if(j == 0)\n'\
4710 ' if(mode == "r")\n'\
4711 ' htmlline = rline+"<cS>←R</cS></div>";\n'\
4712 ' else\n'\
4713 ' htmlline = rline+"<cS>0ms</div>";\n'\
4714 ' }\n'\
4715 ' html += htmlline;\n'\
4716 ' }\n'\
4717 ' timescale.innerHTML = html;\n'\
4718 ' }\n'\
4719 ' }\n'\
4720 ' function zoomTimeline() {\n'\
4721 ' var dmesg = document.getElementById("dmesg");\n'\
4722 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
4723 ' var left = zoombox.scrollLeft;\n'\
4724 ' var val = parseFloat(dmesg.style.width);\n'\
4725 ' var newval = 100;\n'\
4726 ' var sh = window.outerWidth / 2;\n'\
4727 ' if(this.id == "zoomin") {\n'\
4728 ' newval = val * 1.2;\n'\
4729 ' if(newval > 910034) newval = 910034;\n'\
4730 ' dmesg.style.width = newval+"%";\n'\
4731 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4732 ' } else if (this.id == "zoomout") {\n'\
4733 ' newval = val / 1.2;\n'\
4734 ' if(newval < 100) newval = 100;\n'\
4735 ' dmesg.style.width = newval+"%";\n'\
4736 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4737 ' } else {\n'\
4738 ' zoombox.scrollLeft = 0;\n'\
4739 ' dmesg.style.width = "100%";\n'\
4740 ' }\n'\
4741 ' var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4742 ' var t0 = bounds[0];\n'\
4743 ' var tMax = bounds[1];\n'\
4744 ' var tTotal = tMax - t0;\n'\
4745 ' var wTotal = tTotal * 100.0 / newval;\n'\
4746 ' var idx = 7*window.innerWidth/1100;\n'\
4747 ' for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4748 ' if(i >= tS.length) i = tS.length - 1;\n'\
4749 ' if(tS[i] == resolution) return;\n'\
4750 ' resolution = tS[i];\n'\
4751 ' redrawTimescale(t0, tMax, tS[i]);\n'\
4752 ' }\n'\
4753 ' function deviceName(title) {\n'\
4754 ' var name = title.slice(0, title.indexOf(" ("));\n'\
4755 ' return name;\n'\
4756 ' }\n'\
4757 ' function deviceHover() {\n'\
4758 ' var name = deviceName(this.title);\n'\
4759 ' var dmesg = document.getElementById("dmesg");\n'\
4760 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4761 ' var cpu = -1;\n'\
4762 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4763 ' cpu = parseInt(name.slice(7));\n'\
4764 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4765 ' cpu = parseInt(name.slice(8));\n'\
4766 ' for (var i = 0; i < dev.length; i++) {\n'\
4767 ' dname = deviceName(dev[i].title);\n'\
4768 ' var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4769 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4770 ' (name == dname))\n'\
4771 ' {\n'\
4772 ' dev[i].className = "hover "+cname;\n'\
4773 ' } else {\n'\
4774 ' dev[i].className = cname;\n'\
4775 ' }\n'\
4776 ' }\n'\
4777 ' }\n'\
4778 ' function deviceUnhover() {\n'\
4779 ' var dmesg = document.getElementById("dmesg");\n'\
4780 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4781 ' for (var i = 0; i < dev.length; i++) {\n'\
4782 ' dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4783 ' }\n'\
4784 ' }\n'\
4785 ' function deviceTitle(title, total, cpu) {\n'\
4786 ' var prefix = "Total";\n'\
4787 ' if(total.length > 3) {\n'\
4788 ' prefix = "Average";\n'\
4789 ' total[1] = (total[1]+total[3])/2;\n'\
4790 ' total[2] = (total[2]+total[4])/2;\n'\
4791 ' }\n'\
4792 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
4793 ' var name = deviceName(title);\n'\
4794 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
4795 ' var driver = "";\n'\
4796 ' var tS = "<t2>(</t2>";\n'\
4797 ' var tR = "<t2>)</t2>";\n'\
4798 ' if(total[1] > 0)\n'\
4799 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
4800 ' if(total[2] > 0)\n'\
4801 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
4802 ' var s = title.indexOf("{");\n'\
4803 ' var e = title.indexOf("}");\n'\
4804 ' if((s >= 0) && (e >= 0))\n'\
4805 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
4806 ' if(total[1] > 0 && total[2] > 0)\n'\
4807 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
4808 ' else\n'\
4809 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
4810 ' return name;\n'\
4811 ' }\n'\
4812 ' function deviceDetail() {\n'\
4813 ' var devinfo = document.getElementById("devicedetail");\n'\
4814 ' devinfo.style.display = "block";\n'\
4815 ' var name = deviceName(this.title);\n'\
4816 ' var cpu = -1;\n'\
4817 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4818 ' cpu = parseInt(name.slice(7));\n'\
4819 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4820 ' cpu = parseInt(name.slice(8));\n'\
4821 ' var dmesg = document.getElementById("dmesg");\n'\
4822 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4823 ' var idlist = [];\n'\
4824 ' var pdata = [[]];\n'\
4825 ' if(document.getElementById("devicedetail1"))\n'\
4826 ' pdata = [[], []];\n'\
4827 ' var pd = pdata[0];\n'\
4828 ' var total = [0.0, 0.0, 0.0];\n'\
4829 ' for (var i = 0; i < dev.length; i++) {\n'\
4830 ' dname = deviceName(dev[i].title);\n'\
4831 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4832 ' (name == dname))\n'\
4833 ' {\n'\
4834 ' idlist[idlist.length] = dev[i].id;\n'\
4835 ' var tidx = 1;\n'\
4836 ' if(dev[i].id[0] == "a") {\n'\
4837 ' pd = pdata[0];\n'\
4838 ' } else {\n'\
4839 ' if(pdata.length == 1) pdata[1] = [];\n'\
4840 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
4841 ' pd = pdata[1];\n'\
4842 ' tidx = 3;\n'\
4843 ' }\n'\
4844 ' var info = dev[i].title.split(" ");\n'\
4845 ' var pname = info[info.length-1];\n'\
4846 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
4847 ' total[0] += pd[pname];\n'\
4848 ' if(pname.indexOf("suspend") >= 0)\n'\
4849 ' total[tidx] += pd[pname];\n'\
4850 ' else\n'\
4851 ' total[tidx+1] += pd[pname];\n'\
4852 ' }\n'\
4853 ' }\n'\
4854 ' var devname = deviceTitle(this.title, total, cpu);\n'\
4855 ' var left = 0.0;\n'\
4856 ' for (var t = 0; t < pdata.length; t++) {\n'\
4857 ' pd = pdata[t];\n'\
4858 ' devinfo = document.getElementById("devicedetail"+t);\n'\
4859 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
4860 ' for (var i = 0; i < phases.length; i++) {\n'\
4861 ' if(phases[i].id in pd) {\n'\
4862 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
4863 ' var fs = 32;\n'\
4864 ' if(w < 8) fs = 4*w | 0;\n'\
4865 ' var fs2 = fs*3/4;\n'\
4866 ' phases[i].style.width = w+"%";\n'\
4867 ' phases[i].style.left = left+"%";\n'\
4868 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
4869 ' left += w;\n'\
4870 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
4871 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
4872 ' phases[i].innerHTML = time+pname;\n'\
4873 ' } else {\n'\
4874 ' phases[i].style.width = "0%";\n'\
4875 ' phases[i].style.left = left+"%";\n'\
4876 ' }\n'\
4877 ' }\n'\
4878 ' }\n'\
4879 ' if(typeof devstats !== \'undefined\')\n'\
4880 ' callDetail(this.id, this.title);\n'\
4881 ' var cglist = document.getElementById("callgraphs");\n'\
4882 ' if(!cglist) return;\n'\
4883 ' var cg = cglist.getElementsByClassName("atop");\n'\
4884 ' if(cg.length < 10) return;\n'\
4885 ' for (var i = 0; i < cg.length; i++) {\n'\
4886 ' cgid = cg[i].id.split("x")[0]\n'\
4887 ' if(idlist.indexOf(cgid) >= 0) {\n'\
4888 ' cg[i].style.display = "block";\n'\
4889 ' } else {\n'\
4890 ' cg[i].style.display = "none";\n'\
4891 ' }\n'\
4892 ' }\n'\
4893 ' }\n'\
4894 ' function callDetail(devid, devtitle) {\n'\
4895 ' if(!(devid in devstats) || devstats[devid].length < 1)\n'\
4896 ' return;\n'\
4897 ' var list = devstats[devid];\n'\
4898 ' var tmp = devtitle.split(" ");\n'\
4899 ' var name = tmp[0], phase = tmp[tmp.length-1];\n'\
4900 ' var dd = document.getElementById(phase);\n'\
4901 ' var total = parseFloat(tmp[1].slice(1));\n'\
4902 ' var mlist = [];\n'\
4903 ' var maxlen = 0;\n'\
4904 ' var info = []\n'\
4905 ' for(var i in list) {\n'\
4906 ' if(list[i][0] == "@") {\n'\
4907 ' info = list[i].split("|");\n'\
4908 ' continue;\n'\
4909 ' }\n'\
4910 ' var tmp = list[i].split("|");\n'\
4911 ' var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
4912 ' var p = (t*100.0/total).toFixed(2);\n'\
4913 ' mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
4914 ' if(f.length > maxlen)\n'\
4915 ' maxlen = f.length;\n'\
4916 ' }\n'\
4917 ' var pad = 5;\n'\
4918 ' if(mlist.length == 0) pad = 30;\n'\
4919 ' var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
4920 ' if(info.length > 2)\n'\
4921 ' html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
4922 ' if(info.length > 3)\n'\
4923 ' html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
4924 ' if(info.length > 4)\n'\
4925 ' html += ", return=<b>"+info[4]+"</b>";\n'\
4926 ' html += "</t3></div>";\n'\
4927 ' if(mlist.length > 0) {\n'\
4928 ' html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
4929 ' for(var i in mlist)\n'\
4930 ' html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
4931 ' html += "</tr><tr><th>Calls</th>";\n'\
4932 ' for(var i in mlist)\n'\
4933 ' html += "<td>"+mlist[i][1]+"</td>";\n'\
4934 ' html += "</tr><tr><th>Time(ms)</th>";\n'\
4935 ' for(var i in mlist)\n'\
4936 ' html += "<td>"+mlist[i][2]+"</td>";\n'\
4937 ' html += "</tr><tr><th>Percent</th>";\n'\
4938 ' for(var i in mlist)\n'\
4939 ' html += "<td>"+mlist[i][3]+"</td>";\n'\
4940 ' html += "</tr></table>";\n'\
4941 ' }\n'\
4942 ' dd.innerHTML = html;\n'\
4943 ' var height = (maxlen*5)+100;\n'\
4944 ' dd.style.height = height+"px";\n'\
4945 ' document.getElementById("devicedetail").style.height = height+"px";\n'\
4946 ' }\n'\
4947 ' function callSelect() {\n'\
4948 ' var cglist = document.getElementById("callgraphs");\n'\
4949 ' if(!cglist) return;\n'\
4950 ' var cg = cglist.getElementsByClassName("atop");\n'\
4951 ' for (var i = 0; i < cg.length; i++) {\n'\
4952 ' if(this.id == cg[i].id) {\n'\
4953 ' cg[i].style.display = "block";\n'\
4954 ' } else {\n'\
4955 ' cg[i].style.display = "none";\n'\
4956 ' }\n'\
4957 ' }\n'\
4958 ' }\n'\
4959 ' function devListWindow(e) {\n'\
4960 ' var win = window.open();\n'\
4961 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
4962 ' "<style type=\\"text/css\\">"+\n'\
4963 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
4964 ' "</style>"\n'\
4965 ' var dt = devtable[0];\n'\
4966 ' if(e.target.id != "devlist1")\n'\
4967 ' dt = devtable[1];\n'\
4968 ' win.document.write(html+dt);\n'\
4969 ' }\n'\
4970 ' function errWindow() {\n'\
4971 ' var range = this.id.split("_");\n'\
4972 ' var idx1 = parseInt(range[0]);\n'\
4973 ' var idx2 = parseInt(range[1]);\n'\
4974 ' var win = window.open();\n'\
4975 ' var log = document.getElementById("dmesglog");\n'\
4976 ' var title = "<title>dmesg log</title>";\n'\
4977 ' var text = log.innerHTML.split("\\n");\n'\
4978 ' var html = "";\n'\
4979 ' for(var i = 0; i < text.length; i++) {\n'\
4980 ' if(i == idx1) {\n'\
4981 ' html += "<e id=target>"+text[i]+"</e>\\n";\n'\
4982 ' } else if(i > idx1 && i <= idx2) {\n'\
4983 ' html += "<e>"+text[i]+"</e>\\n";\n'\
4984 ' } else {\n'\
4985 ' html += text[i]+"\\n";\n'\
4986 ' }\n'\
4987 ' }\n'\
4988 ' win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
4989 ' win.location.hash = "#target";\n'\
4990 ' win.document.close();\n'\
4991 ' }\n'\
4992 ' function logWindow(e) {\n'\
4993 ' var name = e.target.id.slice(4);\n'\
4994 ' var win = window.open();\n'\
4995 ' var log = document.getElementById(name+"log");\n'\
4996 ' var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
4997 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4998 ' win.document.close();\n'\
4999 ' }\n'\
5000 ' function onMouseDown(e) {\n'\
5001 ' dragval[0] = e.clientX;\n'\
5002 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
5003 ' document.onmousemove = onMouseMove;\n'\
5004 ' }\n'\
5005 ' function onMouseMove(e) {\n'\
5006 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
5007 ' zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
5008 ' }\n'\
5009 ' function onMouseUp(e) {\n'\
5010 ' document.onmousemove = null;\n'\
5011 ' }\n'\
5012 ' function onKeyPress(e) {\n'\
5013 ' var c = e.charCode;\n'\
5014 ' if(c != 42 && c != 43 && c != 45) return;\n'\
5015 ' var click = document.createEvent("Events");\n'\
5016 ' click.initEvent("click", true, false);\n'\
5017 ' if(c == 43) \n'\
5018 ' document.getElementById("zoomin").dispatchEvent(click);\n'\
5019 ' else if(c == 45)\n'\
5020 ' document.getElementById("zoomout").dispatchEvent(click);\n'\
5021 ' else if(c == 42)\n'\
5022 ' document.getElementById("zoomdef").dispatchEvent(click);\n'\
5023 ' }\n'\
5024 ' window.addEventListener("resize", function () {zoomTimeline();});\n'\
5025 ' window.addEventListener("load", function () {\n'\
5026 ' var dmesg = document.getElementById("dmesg");\n'\
5027 ' dmesg.style.width = "100%"\n'\
5028 ' dmesg.onmousedown = onMouseDown;\n'\
5029 ' document.onmouseup = onMouseUp;\n'\
5030 ' document.onkeypress = onKeyPress;\n'\
5031 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
5032 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
5033 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
5034 ' var list = document.getElementsByClassName("err");\n'\
5035 ' for (var i = 0; i < list.length; i++)\n'\
5036 ' list[i].onclick = errWindow;\n'\
5037 ' var list = document.getElementsByClassName("logbtn");\n'\
5038 ' for (var i = 0; i < list.length; i++)\n'\
5039 ' list[i].onclick = logWindow;\n'\
5040 ' list = document.getElementsByClassName("devlist");\n'\
5041 ' for (var i = 0; i < list.length; i++)\n'\
5042 ' list[i].onclick = devListWindow;\n'\
5043 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5044 ' for (var i = 0; i < dev.length; i++) {\n'\
5045 ' dev[i].onclick = deviceDetail;\n'\
5046 ' dev[i].onmouseover = deviceHover;\n'\
5047 ' dev[i].onmouseout = deviceUnhover;\n'\
5048 ' }\n'\
5049 ' var dev = dmesg.getElementsByClassName("srccall");\n'\
5050 ' for (var i = 0; i < dev.length; i++)\n'\
5051 ' dev[i].onclick = callSelect;\n'\
5052 ' zoomTimeline();\n'\
5053 ' });\n'\
5054 '</script>\n'
5055 hf.write(script_code);
5056
5057def setRuntimeSuspend(before=True):
5058 global sysvals
5059 sv = sysvals
5060 if sv.rs == 0:
5061 return
5062 if before:
5063 # runtime suspend disable or enable
5064 if sv.rs > 0:
5065 sv.rstgt, sv.rsval, sv.rsdir = 'on', 'auto', 'enabled'
5066 else:
5067 sv.rstgt, sv.rsval, sv.rsdir = 'auto', 'on', 'disabled'
5068 pprint('CONFIGURING RUNTIME SUSPEND...')
5069 sv.rslist = deviceInfo(sv.rstgt)
5070 for i in sv.rslist:
5071 sv.setVal(sv.rsval, i)
5072 pprint('runtime suspend %s on all devices (%d changed)' % (sv.rsdir, len(sv.rslist)))
5073 pprint('waiting 5 seconds...')
5074 time.sleep(5)
5075 else:
5076 # runtime suspend re-enable or re-disable
5077 for i in sv.rslist:
5078 sv.setVal(sv.rstgt, i)
5079 pprint('runtime suspend settings restored on %d devices' % len(sv.rslist))
5080
5081# Function: executeSuspend
5082# Description:
5083# Execute system suspend through the sysfs interface, then copy the output
5084# dmesg and ftrace files to the test output directory.
5085def executeSuspend():
5086 pm = ProcessMonitor()
5087 tp = sysvals.tpath
5088 wifi = sysvals.checkWifi()
5089 testdata = []
5090 battery = True if getBattery() else False
5091 # run these commands to prepare the system for suspend
5092 if sysvals.display:
5093 pprint('SET DISPLAY TO %s' % sysvals.display.upper())
5094 displayControl(sysvals.display)
5095 time.sleep(1)
5096 if sysvals.sync:
5097 pprint('SYNCING FILESYSTEMS')
5098 call('sync', shell=True)
5099 # mark the start point in the kernel ring buffer just as we start
5100 sysvals.initdmesg()
5101 # start ftrace
5102 if(sysvals.usecallgraph or sysvals.usetraceevents):
5103 pprint('START TRACING')
5104 sysvals.fsetVal('1', 'tracing_on')
5105 if sysvals.useprocmon:
5106 pm.start()
5107 # execute however many s/r runs requested
5108 for count in range(1,sysvals.execcount+1):
5109 # x2delay in between test runs
5110 if(count > 1 and sysvals.x2delay > 0):
5111 sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
5112 time.sleep(sysvals.x2delay/1000.0)
5113 sysvals.fsetVal('WAIT END', 'trace_marker')
5114 # start message
5115 if sysvals.testcommand != '':
5116 pprint('COMMAND START')
5117 else:
5118 if(sysvals.rtcwake):
5119 pprint('SUSPEND START')
5120 else:
5121 pprint('SUSPEND START (press a key to resume)')
5122 sysvals.mcelog(True)
5123 bat1 = getBattery() if battery else False
5124 # set rtcwake
5125 if(sysvals.rtcwake):
5126 pprint('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
5127 sysvals.rtcWakeAlarmOn()
5128 # start of suspend trace marker
5129 if(sysvals.usecallgraph or sysvals.usetraceevents):
5130 sysvals.fsetVal('SUSPEND START', 'trace_marker')
5131 # predelay delay
5132 if(count == 1 and sysvals.predelay > 0):
5133 sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
5134 time.sleep(sysvals.predelay/1000.0)
5135 sysvals.fsetVal('WAIT END', 'trace_marker')
5136 # initiate suspend or command
5137 tdata = {'error': ''}
5138 if sysvals.testcommand != '':
5139 res = call(sysvals.testcommand+' 2>&1', shell=True);
5140 if res != 0:
5141 tdata['error'] = 'cmd returned %d' % res
5142 else:
5143 mode = sysvals.suspendmode
5144 if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
5145 mode = 'mem'
5146 pf = open(sysvals.mempowerfile, 'w')
5147 pf.write(sysvals.memmode)
5148 pf.close()
5149 if sysvals.diskmode and os.path.exists(sysvals.diskpowerfile):
5150 mode = 'disk'
5151 pf = open(sysvals.diskpowerfile, 'w')
5152 pf.write(sysvals.diskmode)
5153 pf.close()
5154 if mode == 'freeze' and sysvals.haveTurbostat():
5155 # execution will pause here
5156 turbo = sysvals.turbostat()
5157 if turbo:
5158 tdata['turbo'] = turbo
5159 else:
5160 pf = open(sysvals.powerfile, 'w')
5161 pf.write(mode)
5162 # execution will pause here
5163 try:
5164 pf.close()
5165 except Exception as e:
5166 tdata['error'] = str(e)
5167 if(sysvals.rtcwake):
5168 sysvals.rtcWakeAlarmOff()
5169 # postdelay delay
5170 if(count == sysvals.execcount and sysvals.postdelay > 0):
5171 sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
5172 time.sleep(sysvals.postdelay/1000.0)
5173 sysvals.fsetVal('WAIT END', 'trace_marker')
5174 # return from suspend
5175 pprint('RESUME COMPLETE')
5176 if(sysvals.usecallgraph or sysvals.usetraceevents):
5177 sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
5178 if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
5179 tdata['fw'] = getFPDT(False)
5180 mcelog = sysvals.mcelog()
5181 if mcelog:
5182 tdata['mcelog'] = mcelog
5183 bat2 = getBattery() if battery else False
5184 if battery and bat1 and bat2:
5185 tdata['bat'] = (bat1, bat2)
5186 if 'device' in wifi and 'ip' in wifi:
5187 tdata['wifi'] = (wifi, sysvals.checkWifi())
5188 testdata.append(tdata)
5189 # stop ftrace
5190 if(sysvals.usecallgraph or sysvals.usetraceevents):
5191 if sysvals.useprocmon:
5192 pm.stop()
5193 sysvals.fsetVal('0', 'tracing_on')
5194 # grab a copy of the dmesg output
5195 pprint('CAPTURING DMESG')
5196 sysvals.getdmesg(testdata)
5197 # grab a copy of the ftrace output
5198 if(sysvals.usecallgraph or sysvals.usetraceevents):
5199 pprint('CAPTURING TRACE')
5200 op = sysvals.writeDatafileHeader(sysvals.ftracefile, testdata)
5201 fp = open(tp+'trace', 'r')
5202 for line in fp:
5203 op.write(line)
5204 op.close()
5205 sysvals.fsetVal('', 'trace')
5206 sysvals.platforminfo()
5207 return testdata
5208
5209def readFile(file):
5210 if os.path.islink(file):
5211 return os.readlink(file).split('/')[-1]
5212 else:
5213 return sysvals.getVal(file).strip()
5214
5215# Function: ms2nice
5216# Description:
5217# Print out a very concise time string in minutes and seconds
5218# Output:
5219# The time string, e.g. "1901m16s"
5220def ms2nice(val):
5221 val = int(val)
5222 h = val // 3600000
5223 m = (val // 60000) % 60
5224 s = (val // 1000) % 60
5225 if h > 0:
5226 return '%d:%02d:%02d' % (h, m, s)
5227 if m > 0:
5228 return '%02d:%02d' % (m, s)
5229 return '%ds' % s
5230
5231def yesno(val):
5232 list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5233 'active':'A', 'suspended':'S', 'suspending':'S'}
5234 if val not in list:
5235 return ' '
5236 return list[val]
5237
5238# Function: deviceInfo
5239# Description:
5240# Detect all the USB hosts and devices currently connected and add
5241# a list of USB device names to sysvals for better timeline readability
5242def deviceInfo(output=''):
5243 if not output:
5244 pprint('LEGEND\n'\
5245 '---------------------------------------------------------------------------------------------\n'\
5246 ' A = async/sync PM queue (A/S) C = runtime active children\n'\
5247 ' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)\n'\
5248 ' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)\n'\
5249 ' U = runtime usage count\n'\
5250 '---------------------------------------------------------------------------------------------\n'\
5251 'DEVICE NAME A R S U C rACTIVE rSUSPEND\n'\
5252 '---------------------------------------------------------------------------------------------')
5253
5254 res = []
5255 tgtval = 'runtime_status'
5256 lines = dict()
5257 for dirname, dirnames, filenames in os.walk('/sys/devices'):
5258 if(not re.match('.*/power', dirname) or
5259 'control' not in filenames or
5260 tgtval not in filenames):
5261 continue
5262 name = ''
5263 dirname = dirname[:-6]
5264 device = dirname.split('/')[-1]
5265 power = dict()
5266 power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5267 # only list devices which support runtime suspend
5268 if power[tgtval] not in ['active', 'suspended', 'suspending']:
5269 continue
5270 for i in ['product', 'driver', 'subsystem']:
5271 file = '%s/%s' % (dirname, i)
5272 if os.path.exists(file):
5273 name = readFile(file)
5274 break
5275 for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5276 'runtime_active_kids', 'runtime_active_time',
5277 'runtime_suspended_time']:
5278 if i in filenames:
5279 power[i] = readFile('%s/power/%s' % (dirname, i))
5280 if output:
5281 if power['control'] == output:
5282 res.append('%s/power/control' % dirname)
5283 continue
5284 lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5285 (device[:26], name[:26],
5286 yesno(power['async']), \
5287 yesno(power['control']), \
5288 yesno(power['runtime_status']), \
5289 power['runtime_usage'], \
5290 power['runtime_active_kids'], \
5291 ms2nice(power['runtime_active_time']), \
5292 ms2nice(power['runtime_suspended_time']))
5293 for i in sorted(lines):
5294 print(lines[i])
5295 return res
5296
5297# Function: getModes
5298# Description:
5299# Determine the supported power modes on this system
5300# Output:
5301# A string list of the available modes
5302def getModes():
5303 modes = []
5304 if(os.path.exists(sysvals.powerfile)):
5305 fp = open(sysvals.powerfile, 'r')
5306 modes = fp.read().split()
5307 fp.close()
5308 if(os.path.exists(sysvals.mempowerfile)):
5309 deep = False
5310 fp = open(sysvals.mempowerfile, 'r')
5311 for m in fp.read().split():
5312 memmode = m.strip('[]')
5313 if memmode == 'deep':
5314 deep = True
5315 else:
5316 modes.append('mem-%s' % memmode)
5317 fp.close()
5318 if 'mem' in modes and not deep:
5319 modes.remove('mem')
5320 if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5321 fp = open(sysvals.diskpowerfile, 'r')
5322 for m in fp.read().split():
5323 modes.append('disk-%s' % m.strip('[]'))
5324 fp.close()
5325 return modes
5326
5327# Function: dmidecode
5328# Description:
5329# Read the bios tables and pull out system info
5330# Arguments:
5331# mempath: /dev/mem or custom mem path
5332# fatal: True to exit on error, False to return empty dict
5333# Output:
5334# A dict object with all available key/values
5335def dmidecode(mempath, fatal=False):
5336 out = dict()
5337
5338 # the list of values to retrieve, with hardcoded (type, idx)
5339 info = {
5340 'bios-vendor': (0, 4),
5341 'bios-version': (0, 5),
5342 'bios-release-date': (0, 8),
5343 'system-manufacturer': (1, 4),
5344 'system-product-name': (1, 5),
5345 'system-version': (1, 6),
5346 'system-serial-number': (1, 7),
5347 'baseboard-manufacturer': (2, 4),
5348 'baseboard-product-name': (2, 5),
5349 'baseboard-version': (2, 6),
5350 'baseboard-serial-number': (2, 7),
5351 'chassis-manufacturer': (3, 4),
5352 'chassis-type': (3, 5),
5353 'chassis-version': (3, 6),
5354 'chassis-serial-number': (3, 7),
5355 'processor-manufacturer': (4, 7),
5356 'processor-version': (4, 16),
5357 }
5358 if(not os.path.exists(mempath)):
5359 if(fatal):
5360 doError('file does not exist: %s' % mempath)
5361 return out
5362 if(not os.access(mempath, os.R_OK)):
5363 if(fatal):
5364 doError('file is not readable: %s' % mempath)
5365 return out
5366
5367 # by default use legacy scan, but try to use EFI first
5368 memaddr = 0xf0000
5369 memsize = 0x10000
5370 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5371 if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5372 continue
5373 fp = open(ep, 'r')
5374 buf = fp.read()
5375 fp.close()
5376 i = buf.find('SMBIOS=')
5377 if i >= 0:
5378 try:
5379 memaddr = int(buf[i+7:], 16)
5380 memsize = 0x20
5381 except:
5382 continue
5383
5384 # read in the memory for scanning
5385 try:
5386 fp = open(mempath, 'rb')
5387 fp.seek(memaddr)
5388 buf = fp.read(memsize)
5389 except:
5390 if(fatal):
5391 doError('DMI table is unreachable, sorry')
5392 else:
5393 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5394 return out
5395 fp.close()
5396
5397 # search for either an SM table or DMI table
5398 i = base = length = num = 0
5399 while(i < memsize):
5400 if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5401 length = struct.unpack('H', buf[i+22:i+24])[0]
5402 base, num = struct.unpack('IH', buf[i+24:i+30])
5403 break
5404 elif buf[i:i+5] == b'_DMI_':
5405 length = struct.unpack('H', buf[i+6:i+8])[0]
5406 base, num = struct.unpack('IH', buf[i+8:i+14])
5407 break
5408 i += 16
5409 if base == 0 and length == 0 and num == 0:
5410 if(fatal):
5411 doError('Neither SMBIOS nor DMI were found')
5412 else:
5413 return out
5414
5415 # read in the SM or DMI table
5416 try:
5417 fp = open(mempath, 'rb')
5418 fp.seek(base)
5419 buf = fp.read(length)
5420 except:
5421 if(fatal):
5422 doError('DMI table is unreachable, sorry')
5423 else:
5424 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5425 return out
5426 fp.close()
5427
5428 # scan the table for the values we want
5429 count = i = 0
5430 while(count < num and i <= len(buf) - 4):
5431 type, size, handle = struct.unpack('BBH', buf[i:i+4])
5432 n = i + size
5433 while n < len(buf) - 1:
5434 if 0 == struct.unpack('H', buf[n:n+2])[0]:
5435 break
5436 n += 1
5437 data = buf[i+size:n+2].split(b'\0')
5438 for name in info:
5439 itype, idxadr = info[name]
5440 if itype == type:
5441 idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5442 if idx > 0 and idx < len(data) - 1:
5443 s = data[idx-1].decode('utf-8')
5444 if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5445 out[name] = s
5446 i = n + 2
5447 count += 1
5448 return out
5449
5450def getBattery():
5451 p, charge, bat = '/sys/class/power_supply', 0, {}
5452 if not os.path.exists(p):
5453 return False
5454 for d in os.listdir(p):
5455 type = sysvals.getVal(os.path.join(p, d, 'type')).strip().lower()
5456 if type != 'battery':
5457 continue
5458 for v in ['status', 'energy_now', 'capacity_now']:
5459 bat[v] = sysvals.getVal(os.path.join(p, d, v)).strip().lower()
5460 break
5461 if 'status' not in bat:
5462 return False
5463 ac = False if 'discharging' in bat['status'] else True
5464 for v in ['energy_now', 'capacity_now']:
5465 if v in bat and bat[v]:
5466 charge = int(bat[v])
5467 return (ac, charge)
5468
5469def displayControl(cmd):
5470 xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
5471 if sysvals.sudouser:
5472 xset = 'sudo -u %s %s' % (sysvals.sudouser, xset)
5473 if cmd == 'init':
5474 ret = call(xset.format('dpms 0 0 0'), shell=True)
5475 if not ret:
5476 ret = call(xset.format('s off'), shell=True)
5477 elif cmd == 'reset':
5478 ret = call(xset.format('s reset'), shell=True)
5479 elif cmd in ['on', 'off', 'standby', 'suspend']:
5480 b4 = displayControl('stat')
5481 ret = call(xset.format('dpms force %s' % cmd), shell=True)
5482 if not ret:
5483 curr = displayControl('stat')
5484 sysvals.vprint('Display Switched: %s -> %s' % (b4, curr))
5485 if curr != cmd:
5486 sysvals.vprint('WARNING: Display failed to change to %s' % cmd)
5487 if ret:
5488 sysvals.vprint('WARNING: Display failed to change to %s with xset' % cmd)
5489 return ret
5490 elif cmd == 'stat':
5491 fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
5492 ret = 'unknown'
5493 for line in fp:
5494 m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))
5495 if(m and len(m.group('m')) >= 2):
5496 out = m.group('m').lower()
5497 ret = out[3:] if out[0:2] == 'in' else out
5498 break
5499 fp.close()
5500 return ret
5501
5502# Function: getFPDT
5503# Description:
5504# Read the acpi bios tables and pull out FPDT, the firmware data
5505# Arguments:
5506# output: True to output the info to stdout, False otherwise
5507def getFPDT(output):
5508 rectype = {}
5509 rectype[0] = 'Firmware Basic Boot Performance Record'
5510 rectype[1] = 'S3 Performance Table Record'
5511 prectype = {}
5512 prectype[0] = 'Basic S3 Resume Performance Record'
5513 prectype[1] = 'Basic S3 Suspend Performance Record'
5514
5515 sysvals.rootCheck(True)
5516 if(not os.path.exists(sysvals.fpdtpath)):
5517 if(output):
5518 doError('file does not exist: %s' % sysvals.fpdtpath)
5519 return False
5520 if(not os.access(sysvals.fpdtpath, os.R_OK)):
5521 if(output):
5522 doError('file is not readable: %s' % sysvals.fpdtpath)
5523 return False
5524 if(not os.path.exists(sysvals.mempath)):
5525 if(output):
5526 doError('file does not exist: %s' % sysvals.mempath)
5527 return False
5528 if(not os.access(sysvals.mempath, os.R_OK)):
5529 if(output):
5530 doError('file is not readable: %s' % sysvals.mempath)
5531 return False
5532
5533 fp = open(sysvals.fpdtpath, 'rb')
5534 buf = fp.read()
5535 fp.close()
5536
5537 if(len(buf) < 36):
5538 if(output):
5539 doError('Invalid FPDT table data, should '+\
5540 'be at least 36 bytes')
5541 return False
5542
5543 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5544 if(output):
5545 pprint('\n'\
5546 'Firmware Performance Data Table (%s)\n'\
5547 ' Signature : %s\n'\
5548 ' Table Length : %u\n'\
5549 ' Revision : %u\n'\
5550 ' Checksum : 0x%x\n'\
5551 ' OEM ID : %s\n'\
5552 ' OEM Table ID : %s\n'\
5553 ' OEM Revision : %u\n'\
5554 ' Creator ID : %s\n'\
5555 ' Creator Revision : 0x%x\n'\
5556 '' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5557 table[3], ascii(table[4]), ascii(table[5]), table[6],
5558 ascii(table[7]), table[8]))
5559
5560 if(table[0] != b'FPDT'):
5561 if(output):
5562 doError('Invalid FPDT table')
5563 return False
5564 if(len(buf) <= 36):
5565 return False
5566 i = 0
5567 fwData = [0, 0]
5568 records = buf[36:]
5569 try:
5570 fp = open(sysvals.mempath, 'rb')
5571 except:
5572 pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5573 return False
5574 while(i < len(records)):
5575 header = struct.unpack('HBB', records[i:i+4])
5576 if(header[0] not in rectype):
5577 i += header[1]
5578 continue
5579 if(header[1] != 16):
5580 i += header[1]
5581 continue
5582 addr = struct.unpack('Q', records[i+8:i+16])[0]
5583 try:
5584 fp.seek(addr)
5585 first = fp.read(8)
5586 except:
5587 if(output):
5588 pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5589 return [0, 0]
5590 rechead = struct.unpack('4sI', first)
5591 recdata = fp.read(rechead[1]-8)
5592 if(rechead[0] == b'FBPT'):
5593 record = struct.unpack('HBBIQQQQQ', recdata[:48])
5594 if(output):
5595 pprint('%s (%s)\n'\
5596 ' Reset END : %u ns\n'\
5597 ' OS Loader LoadImage Start : %u ns\n'\
5598 ' OS Loader StartImage Start : %u ns\n'\
5599 ' ExitBootServices Entry : %u ns\n'\
5600 ' ExitBootServices Exit : %u ns'\
5601 '' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5602 record[6], record[7], record[8]))
5603 elif(rechead[0] == b'S3PT'):
5604 if(output):
5605 pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5606 j = 0
5607 while(j < len(recdata)):
5608 prechead = struct.unpack('HBB', recdata[j:j+4])
5609 if(prechead[0] not in prectype):
5610 continue
5611 if(prechead[0] == 0):
5612 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5613 fwData[1] = record[2]
5614 if(output):
5615 pprint(' %s\n'\
5616 ' Resume Count : %u\n'\
5617 ' FullResume : %u ns\n'\
5618 ' AverageResume : %u ns'\
5619 '' % (prectype[prechead[0]], record[1],
5620 record[2], record[3]))
5621 elif(prechead[0] == 1):
5622 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5623 fwData[0] = record[1] - record[0]
5624 if(output):
5625 pprint(' %s\n'\
5626 ' SuspendStart : %u ns\n'\
5627 ' SuspendEnd : %u ns\n'\
5628 ' SuspendTime : %u ns'\
5629 '' % (prectype[prechead[0]], record[0],
5630 record[1], fwData[0]))
5631
5632 j += prechead[1]
5633 if(output):
5634 pprint('')
5635 i += header[1]
5636 fp.close()
5637 return fwData
5638
5639# Function: statusCheck
5640# Description:
5641# Verify that the requested command and options will work, and
5642# print the results to the terminal
5643# Output:
5644# True if the test will work, False if not
5645def statusCheck(probecheck=False):
5646 status = ''
5647
5648 pprint('Checking this system (%s)...' % platform.node())
5649
5650 # check we have root access
5651 res = sysvals.colorText('NO (No features of this tool will work!)')
5652 if(sysvals.rootCheck(False)):
5653 res = 'YES'
5654 pprint(' have root access: %s' % res)
5655 if(res != 'YES'):
5656 pprint(' Try running this script with sudo')
5657 return 'missing root access'
5658
5659 # check sysfs is mounted
5660 res = sysvals.colorText('NO (No features of this tool will work!)')
5661 if(os.path.exists(sysvals.powerfile)):
5662 res = 'YES'
5663 pprint(' is sysfs mounted: %s' % res)
5664 if(res != 'YES'):
5665 return 'sysfs is missing'
5666
5667 # check target mode is a valid mode
5668 if sysvals.suspendmode != 'command':
5669 res = sysvals.colorText('NO')
5670 modes = getModes()
5671 if(sysvals.suspendmode in modes):
5672 res = 'YES'
5673 else:
5674 status = '%s mode is not supported' % sysvals.suspendmode
5675 pprint(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5676 if(res == 'NO'):
5677 pprint(' valid power modes are: %s' % modes)
5678 pprint(' please choose one with -m')
5679
5680 # check if ftrace is available
5681 res = sysvals.colorText('NO')
5682 ftgood = sysvals.verifyFtrace()
5683 if(ftgood):
5684 res = 'YES'
5685 elif(sysvals.usecallgraph):
5686 status = 'ftrace is not properly supported'
5687 pprint(' is ftrace supported: %s' % res)
5688
5689 # check if kprobes are available
5690 if sysvals.usekprobes:
5691 res = sysvals.colorText('NO')
5692 sysvals.usekprobes = sysvals.verifyKprobes()
5693 if(sysvals.usekprobes):
5694 res = 'YES'
5695 else:
5696 sysvals.usedevsrc = False
5697 pprint(' are kprobes supported: %s' % res)
5698
5699 # what data source are we using
5700 res = 'DMESG'
5701 if(ftgood):
5702 sysvals.usetraceevents = True
5703 for e in sysvals.traceevents:
5704 if not os.path.exists(sysvals.epath+e):
5705 sysvals.usetraceevents = False
5706 if(sysvals.usetraceevents):
5707 res = 'FTRACE (all trace events found)'
5708 pprint(' timeline data source: %s' % res)
5709
5710 # check if rtcwake
5711 res = sysvals.colorText('NO')
5712 if(sysvals.rtcpath != ''):
5713 res = 'YES'
5714 elif(sysvals.rtcwake):
5715 status = 'rtcwake is not properly supported'
5716 pprint(' is rtcwake supported: %s' % res)
5717
5718 if not probecheck:
5719 return status
5720
5721 # verify kprobes
5722 if sysvals.usekprobes:
5723 for name in sysvals.tracefuncs:
5724 sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5725 if sysvals.usedevsrc:
5726 for name in sysvals.dev_tracefuncs:
5727 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5728 sysvals.addKprobes(True)
5729
5730 return status
5731
5732# Function: doError
5733# Description:
5734# generic error function for catastrphic failures
5735# Arguments:
5736# msg: the error message to print
5737# help: True if printHelp should be called after, False otherwise
5738def doError(msg, help=False):
5739 if(help == True):
5740 printHelp()
5741 pprint('ERROR: %s\n' % msg)
5742 sysvals.outputResult({'error':msg})
5743 sys.exit(1)
5744
5745# Function: getArgInt
5746# Description:
5747# pull out an integer argument from the command line with checks
5748def getArgInt(name, args, min, max, main=True):
5749 if main:
5750 try:
5751 arg = next(args)
5752 except:
5753 doError(name+': no argument supplied', True)
5754 else:
5755 arg = args
5756 try:
5757 val = int(arg)
5758 except:
5759 doError(name+': non-integer value given', True)
5760 if(val < min or val > max):
5761 doError(name+': value should be between %d and %d' % (min, max), True)
5762 return val
5763
5764# Function: getArgFloat
5765# Description:
5766# pull out a float argument from the command line with checks
5767def getArgFloat(name, args, min, max, main=True):
5768 if main:
5769 try:
5770 arg = next(args)
5771 except:
5772 doError(name+': no argument supplied', True)
5773 else:
5774 arg = args
5775 try:
5776 val = float(arg)
5777 except:
5778 doError(name+': non-numerical value given', True)
5779 if(val < min or val > max):
5780 doError(name+': value should be between %f and %f' % (min, max), True)
5781 return val
5782
5783def processData(live=False):
5784 pprint('PROCESSING DATA')
5785 sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
5786 (sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
5787 error = ''
5788 if(sysvals.usetraceevents):
5789 testruns, error = parseTraceLog(live)
5790 if sysvals.dmesgfile:
5791 for data in testruns:
5792 data.extractErrorInfo()
5793 else:
5794 testruns = loadKernelLog()
5795 for data in testruns:
5796 parseKernelLog(data)
5797 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5798 appendIncompleteTraceLog(testruns)
5799 shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
5800 'memsz', 'mode', 'numcpu', 'plat', 'time']
5801 sysvals.vprint('System Info:')
5802 for key in sorted(sysvals.stamp):
5803 if key in shown:
5804 sysvals.vprint(' %-8s : %s' % (key.upper(), sysvals.stamp[key]))
5805 if sysvals.kparams:
5806 sysvals.vprint('Kparams:\n %s' % sysvals.kparams)
5807 sysvals.vprint('Command:\n %s' % sysvals.cmdline)
5808 for data in testruns:
5809 if data.mcelog:
5810 sysvals.vprint('MCELOG Data:')
5811 for line in data.mcelog.split('\n'):
5812 sysvals.vprint(' %s' % line)
5813 if data.turbostat:
5814 idx, s = 0, 'Turbostat:\n '
5815 for val in data.turbostat.split('|'):
5816 idx += len(val) + 1
5817 if idx >= 80:
5818 idx = 0
5819 s += '\n '
5820 s += val + ' '
5821 sysvals.vprint(s)
5822 if data.battery:
5823 a1, c1, a2, c2 = data.battery
5824 s = 'Battery:\n Before - AC: %s, Charge: %d\n After - AC: %s, Charge: %d' % \
5825 (a1, int(c1), a2, int(c2))
5826 sysvals.vprint(s)
5827 if data.wifi:
5828 w = data.wifi.replace('|', ' ').split(',')
5829 s = 'Wifi:\n Before %s\n After %s' % \
5830 (w[0], w[1])
5831 sysvals.vprint(s)
5832 data.printDetails()
5833 if len(sysvals.platinfo) > 0:
5834 sysvals.vprint('\nPlatform Info:')
5835 for info in sysvals.platinfo:
5836 sysvals.vprint(info[0]+' - '+info[1])
5837 sysvals.vprint(info[2])
5838 sysvals.vprint('')
5839 if sysvals.cgdump:
5840 for data in testruns:
5841 data.debugPrint()
5842 sys.exit(0)
5843 if len(testruns) < 1:
5844 pprint('ERROR: Not enough test data to build a timeline')
5845 return (testruns, {'error': 'timeline generation failed'})
5846 sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
5847 createHTML(testruns, error)
5848 pprint('DONE')
5849 data = testruns[0]
5850 stamp = data.stamp
5851 stamp['suspend'], stamp['resume'] = data.getTimeValues()
5852 if data.fwValid:
5853 stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
5854 if error:
5855 stamp['error'] = error
5856 return (testruns, stamp)
5857
5858# Function: rerunTest
5859# Description:
5860# generate an output from an existing set of ftrace/dmesg logs
5861def rerunTest(htmlfile=''):
5862 if sysvals.ftracefile:
5863 doesTraceLogHaveTraceEvents()
5864 if not sysvals.dmesgfile and not sysvals.usetraceevents:
5865 doError('recreating this html output requires a dmesg file')
5866 if htmlfile:
5867 sysvals.htmlfile = htmlfile
5868 else:
5869 sysvals.setOutputFile()
5870 if os.path.exists(sysvals.htmlfile):
5871 if not os.path.isfile(sysvals.htmlfile):
5872 doError('a directory already exists with this name: %s' % sysvals.htmlfile)
5873 elif not os.access(sysvals.htmlfile, os.W_OK):
5874 doError('missing permission to write to %s' % sysvals.htmlfile)
5875 testruns, stamp = processData(False)
5876 sysvals.logmsg = ''
5877 return stamp
5878
5879# Function: runTest
5880# Description:
5881# execute a suspend/resume, gather the logs, and generate the output
5882def runTest(n=0):
5883 # prepare for the test
5884 sysvals.initFtrace()
5885 sysvals.initTestOutput('suspend')
5886
5887 # execute the test
5888 testdata = executeSuspend()
5889 sysvals.cleanupFtrace()
5890 if sysvals.skiphtml:
5891 sysvals.sudoUserchown(sysvals.testdir)
5892 return
5893 if not testdata[0]['error']:
5894 testruns, stamp = processData(True)
5895 for data in testruns:
5896 del data
5897 else:
5898 stamp = testdata[0]
5899
5900 sysvals.sudoUserchown(sysvals.testdir)
5901 sysvals.outputResult(stamp, n)
5902 if 'error' in stamp:
5903 return 2
5904 return 0
5905
5906def find_in_html(html, start, end, firstonly=True):
5907 n, out = 0, []
5908 while n < len(html):
5909 m = re.search(start, html[n:])
5910 if not m:
5911 break
5912 i = m.end()
5913 m = re.search(end, html[n+i:])
5914 if not m:
5915 break
5916 j = m.start()
5917 str = html[n+i:n+i+j]
5918 if end == 'ms':
5919 num = re.search(r'[-+]?\d*\.\d+|\d+', str)
5920 str = num.group() if num else 'NaN'
5921 if firstonly:
5922 return str
5923 out.append(str)
5924 n += i+j
5925 if firstonly:
5926 return ''
5927 return out
5928
5929def data_from_html(file, outpath, issues, fulldetail=False):
5930 html = open(file, 'r').read()
5931 sysvals.htmlfile = os.path.relpath(file, outpath)
5932 # extract general info
5933 suspend = find_in_html(html, 'Kernel Suspend', 'ms')
5934 resume = find_in_html(html, 'Kernel Resume', 'ms')
5935 sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
5936 line = find_in_html(html, '<div class="stamp">', '</div>')
5937 stmp = line.split()
5938 if not suspend or not resume or len(stmp) != 8:
5939 return False
5940 try:
5941 dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
5942 except:
5943 return False
5944 sysvals.hostname = stmp[0]
5945 tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
5946 error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
5947 if error:
5948 m = re.match('[a-z]* failed in (?P<p>[a-z0-9_]*) phase', error)
5949 if m:
5950 result = 'fail in %s' % m.group('p')
5951 else:
5952 result = 'fail'
5953 else:
5954 result = 'pass'
5955 # extract error info
5956 ilist = []
5957 extra = dict()
5958 log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
5959 '</div>').strip()
5960 if log:
5961 d = Data(0)
5962 d.end = 999999999
5963 d.dmesgtext = log.split('\n')
5964 msglist = d.extractErrorInfo()
5965 for msg in msglist:
5966 sysvals.errorSummary(issues, msg)
5967 if stmp[2] == 'freeze':
5968 extra = d.turbostatInfo()
5969 elist = dict()
5970 for dir in d.errorinfo:
5971 for err in d.errorinfo[dir]:
5972 if err[0] not in elist:
5973 elist[err[0]] = 0
5974 elist[err[0]] += 1
5975 for i in elist:
5976 ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
5977 low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
5978 if low and '|' in low:
5979 issue = 'FREEZEx%d' % len(low.split('|'))
5980 match = [i for i in issues if i['match'] == issue]
5981 if len(match) > 0:
5982 match[0]['count'] += 1
5983 if sysvals.hostname not in match[0]['urls']:
5984 match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
5985 elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
5986 match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
5987 else:
5988 issues.append({
5989 'match': issue, 'count': 1, 'line': issue,
5990 'urls': {sysvals.hostname: [sysvals.htmlfile]},
5991 })
5992 ilist.append(issue)
5993 # extract device info
5994 devices = dict()
5995 for line in html.split('\n'):
5996 m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
5997 if not m or 'thread kth' in line or 'thread sec' in line:
5998 continue
5999 m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6000 if not m:
6001 continue
6002 name, time, phase = m.group('n'), m.group('t'), m.group('p')
6003 if ' async' in name or ' sync' in name:
6004 name = ' '.join(name.split(' ')[:-1])
6005 if phase.startswith('suspend'):
6006 d = 'suspend'
6007 elif phase.startswith('resume'):
6008 d = 'resume'
6009 else:
6010 continue
6011 if d not in devices:
6012 devices[d] = dict()
6013 if name not in devices[d]:
6014 devices[d][name] = 0.0
6015 devices[d][name] += float(time)
6016 # create worst device info
6017 worst = dict()
6018 for d in ['suspend', 'resume']:
6019 worst[d] = {'name':'', 'time': 0.0}
6020 dev = devices[d] if d in devices else 0
6021 if dev and len(dev.keys()) > 0:
6022 n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6023 worst[d]['name'], worst[d]['time'] = n, dev[n]
6024 data = {
6025 'mode': stmp[2],
6026 'host': stmp[0],
6027 'kernel': stmp[1],
6028 'sysinfo': sysinfo,
6029 'time': tstr,
6030 'result': result,
6031 'issues': ' '.join(ilist),
6032 'suspend': suspend,
6033 'resume': resume,
6034 'devlist': devices,
6035 'sus_worst': worst['suspend']['name'],
6036 'sus_worsttime': worst['suspend']['time'],
6037 'res_worst': worst['resume']['name'],
6038 'res_worsttime': worst['resume']['time'],
6039 'url': sysvals.htmlfile,
6040 }
6041 for key in extra:
6042 data[key] = extra[key]
6043 if fulldetail:
6044 data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6045 return data
6046
6047def genHtml(subdir, force=False):
6048 for dirname, dirnames, filenames in os.walk(subdir):
6049 sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6050 for filename in filenames:
6051 if(re.match('.*_dmesg.txt', filename)):
6052 sysvals.dmesgfile = os.path.join(dirname, filename)
6053 elif(re.match('.*_ftrace.txt', filename)):
6054 sysvals.ftracefile = os.path.join(dirname, filename)
6055 sysvals.setOutputFile()
6056 if sysvals.ftracefile and sysvals.htmlfile and \
6057 (force or not os.path.exists(sysvals.htmlfile)):
6058 pprint('FTRACE: %s' % sysvals.ftracefile)
6059 if sysvals.dmesgfile:
6060 pprint('DMESG : %s' % sysvals.dmesgfile)
6061 rerunTest()
6062
6063# Function: runSummary
6064# Description:
6065# create a summary of tests in a sub-directory
6066def runSummary(subdir, local=True, genhtml=False):
6067 inpath = os.path.abspath(subdir)
6068 outpath = os.path.abspath('.') if local else inpath
6069 pprint('Generating a summary of folder:\n %s' % inpath)
6070 if genhtml:
6071 genHtml(subdir)
6072 issues = []
6073 testruns = []
6074 desc = {'host':[],'mode':[],'kernel':[]}
6075 for dirname, dirnames, filenames in os.walk(subdir):
6076 for filename in filenames:
6077 if(not re.match('.*.html', filename)):
6078 continue
6079 data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6080 if(not data):
6081 continue
6082 testruns.append(data)
6083 for key in desc:
6084 if data[key] not in desc[key]:
6085 desc[key].append(data[key])
6086 pprint('Summary files:')
6087 if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6088 title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6089 else:
6090 title = inpath
6091 createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6092 pprint(' summary.html - tabular list of test data found')
6093 createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6094 pprint(' summary-devices.html - kernel device list sorted by total execution time')
6095 createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6096 pprint(' summary-issues.html - kernel issues found sorted by frequency')
6097
6098# Function: checkArgBool
6099# Description:
6100# check if a boolean string value is true or false
6101def checkArgBool(name, value):
6102 if value in switchvalues:
6103 if value in switchoff:
6104 return False
6105 return True
6106 doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6107 return False
6108
6109# Function: configFromFile
6110# Description:
6111# Configure the script via the info in a config file
6112def configFromFile(file):
6113 Config = configparser.ConfigParser()
6114
6115 Config.read(file)
6116 sections = Config.sections()
6117 overridekprobes = False
6118 overridedevkprobes = False
6119 if 'Settings' in sections:
6120 for opt in Config.options('Settings'):
6121 value = Config.get('Settings', opt).lower()
6122 option = opt.lower()
6123 if(option == 'verbose'):
6124 sysvals.verbose = checkArgBool(option, value)
6125 elif(option == 'addlogs'):
6126 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6127 elif(option == 'dev'):
6128 sysvals.usedevsrc = checkArgBool(option, value)
6129 elif(option == 'proc'):
6130 sysvals.useprocmon = checkArgBool(option, value)
6131 elif(option == 'x2'):
6132 if checkArgBool(option, value):
6133 sysvals.execcount = 2
6134 elif(option == 'callgraph'):
6135 sysvals.usecallgraph = checkArgBool(option, value)
6136 elif(option == 'override-timeline-functions'):
6137 overridekprobes = checkArgBool(option, value)
6138 elif(option == 'override-dev-timeline-functions'):
6139 overridedevkprobes = checkArgBool(option, value)
6140 elif(option == 'skiphtml'):
6141 sysvals.skiphtml = checkArgBool(option, value)
6142 elif(option == 'sync'):
6143 sysvals.sync = checkArgBool(option, value)
6144 elif(option == 'rs' or option == 'runtimesuspend'):
6145 if value in switchvalues:
6146 if value in switchoff:
6147 sysvals.rs = -1
6148 else:
6149 sysvals.rs = 1
6150 else:
6151 doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6152 elif(option == 'display'):
6153 disopt = ['on', 'off', 'standby', 'suspend']
6154 if value not in disopt:
6155 doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6156 sysvals.display = value
6157 elif(option == 'gzip'):
6158 sysvals.gzip = checkArgBool(option, value)
6159 elif(option == 'cgfilter'):
6160 sysvals.setCallgraphFilter(value)
6161 elif(option == 'cgskip'):
6162 if value in switchoff:
6163 sysvals.cgskip = ''
6164 else:
6165 sysvals.cgskip = sysvals.configFile(val)
6166 if(not sysvals.cgskip):
6167 doError('%s does not exist' % sysvals.cgskip)
6168 elif(option == 'cgtest'):
6169 sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6170 elif(option == 'cgphase'):
6171 d = Data(0)
6172 if value not in d.sortedPhases():
6173 doError('invalid phase --> (%s: %s), valid phases are %s'\
6174 % (option, value, d.sortedPhases()), True)
6175 sysvals.cgphase = value
6176 elif(option == 'fadd'):
6177 file = sysvals.configFile(value)
6178 if(not file):
6179 doError('%s does not exist' % value)
6180 sysvals.addFtraceFilterFunctions(file)
6181 elif(option == 'result'):
6182 sysvals.result = value
6183 elif(option == 'multi'):
6184 nums = value.split()
6185 if len(nums) != 2:
6186 doError('multi requires 2 integers (exec_count and delay)', True)
6187 sysvals.multitest['run'] = True
6188 sysvals.multitest['count'] = getArgInt('multi: n d (exec count)', nums[0], 2, 1000000, False)
6189 sysvals.multitest['delay'] = getArgInt('multi: n d (delay between tests)', nums[1], 0, 3600, False)
6190 elif(option == 'devicefilter'):
6191 sysvals.setDeviceFilter(value)
6192 elif(option == 'expandcg'):
6193 sysvals.cgexp = checkArgBool(option, value)
6194 elif(option == 'srgap'):
6195 if checkArgBool(option, value):
6196 sysvals.srgap = 5
6197 elif(option == 'mode'):
6198 sysvals.suspendmode = value
6199 elif(option == 'command' or option == 'cmd'):
6200 sysvals.testcommand = value
6201 elif(option == 'x2delay'):
6202 sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6203 elif(option == 'predelay'):
6204 sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6205 elif(option == 'postdelay'):
6206 sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6207 elif(option == 'maxdepth'):
6208 sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6209 elif(option == 'rtcwake'):
6210 if value in switchoff:
6211 sysvals.rtcwake = False
6212 else:
6213 sysvals.rtcwake = True
6214 sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6215 elif(option == 'timeprec'):
6216 sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6217 elif(option == 'mindev'):
6218 sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6219 elif(option == 'callloop-maxgap'):
6220 sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6221 elif(option == 'callloop-maxlen'):
6222 sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6223 elif(option == 'mincg'):
6224 sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6225 elif(option == 'bufsize'):
6226 sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6227 elif(option == 'output-dir'):
6228 sysvals.outdir = sysvals.setOutputFolder(value)
6229
6230 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6231 doError('No command supplied for mode "command"')
6232
6233 # compatibility errors
6234 if sysvals.usedevsrc and sysvals.usecallgraph:
6235 doError('-dev is not compatible with -f')
6236 if sysvals.usecallgraph and sysvals.useprocmon:
6237 doError('-proc is not compatible with -f')
6238
6239 if overridekprobes:
6240 sysvals.tracefuncs = dict()
6241 if overridedevkprobes:
6242 sysvals.dev_tracefuncs = dict()
6243
6244 kprobes = dict()
6245 kprobesec = 'dev_timeline_functions_'+platform.machine()
6246 if kprobesec in sections:
6247 for name in Config.options(kprobesec):
6248 text = Config.get(kprobesec, name)
6249 kprobes[name] = (text, True)
6250 kprobesec = 'timeline_functions_'+platform.machine()
6251 if kprobesec in sections:
6252 for name in Config.options(kprobesec):
6253 if name in kprobes:
6254 doError('Duplicate timeline function found "%s"' % (name))
6255 text = Config.get(kprobesec, name)
6256 kprobes[name] = (text, False)
6257
6258 for name in kprobes:
6259 function = name
6260 format = name
6261 color = ''
6262 args = dict()
6263 text, dev = kprobes[name]
6264 data = text.split()
6265 i = 0
6266 for val in data:
6267 # bracketted strings are special formatting, read them separately
6268 if val[0] == '[' and val[-1] == ']':
6269 for prop in val[1:-1].split(','):
6270 p = prop.split('=')
6271 if p[0] == 'color':
6272 try:
6273 color = int(p[1], 16)
6274 color = '#'+p[1]
6275 except:
6276 color = p[1]
6277 continue
6278 # first real arg should be the format string
6279 if i == 0:
6280 format = val
6281 # all other args are actual function args
6282 else:
6283 d = val.split('=')
6284 args[d[0]] = d[1]
6285 i += 1
6286 if not function or not format:
6287 doError('Invalid kprobe: %s' % name)
6288 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6289 if arg not in args:
6290 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6291 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6292 doError('Duplicate timeline function found "%s"' % (name))
6293
6294 kp = {
6295 'name': name,
6296 'func': function,
6297 'format': format,
6298 sysvals.archargs: args
6299 }
6300 if color:
6301 kp['color'] = color
6302 if dev:
6303 sysvals.dev_tracefuncs[name] = kp
6304 else:
6305 sysvals.tracefuncs[name] = kp
6306
6307# Function: printHelp
6308# Description:
6309# print out the help text
6310def printHelp():
6311 pprint('\n%s v%s\n'\
6312 'Usage: sudo sleepgraph <options> <commands>\n'\
6313 '\n'\
6314 'Description:\n'\
6315 ' This tool is designed to assist kernel and OS developers in optimizing\n'\
6316 ' their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6317 ' with a few extra options enabled, the tool will execute a suspend and\n'\
6318 ' capture dmesg and ftrace data until resume is complete. This data is\n'\
6319 ' transformed into a device timeline and an optional callgraph to give\n'\
6320 ' a detailed view of which devices/subsystems are taking the most\n'\
6321 ' time in suspend/resume.\n'\
6322 '\n'\
6323 ' If no specific command is given, the default behavior is to initiate\n'\
6324 ' a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6325 '\n'\
6326 ' Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6327 ' HTML output: <hostname>_<mode>.html\n'\
6328 ' raw dmesg output: <hostname>_<mode>_dmesg.txt\n'\
6329 ' raw ftrace output: <hostname>_<mode>_ftrace.txt\n'\
6330 '\n'\
6331 'Options:\n'\
6332 ' -h Print this help text\n'\
6333 ' -v Print the current tool version\n'\
6334 ' -config fn Pull arguments and config options from file fn\n'\
6335 ' -verbose Print extra information during execution and analysis\n'\
6336 ' -m mode Mode to initiate for suspend (default: %s)\n'\
6337 ' -o name Overrides the output subdirectory name when running a new test\n'\
6338 ' default: suspend-{date}-{time}\n'\
6339 ' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6340 ' -addlogs Add the dmesg and ftrace logs to the html output\n'\
6341 ' -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6342 ' -srgap Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6343 ' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6344 ' -result fn Export a results table to a text file for parsing.\n'\
6345 ' [testprep]\n'\
6346 ' -sync Sync the filesystems before starting the test\n'\
6347 ' -rs on/off Enable/disable runtime suspend for all devices, restore all after test\n'\
6348 ' -display m Change the display mode to m for the test (on/off/standby/suspend)\n'\
6349 ' [advanced]\n'\
6350 ' -gzip Gzip the trace and dmesg logs to save space\n'\
6351 ' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"\n'\
6352 ' -proc Add usermode process info into the timeline (default: disabled)\n'\
6353 ' -dev Add kernel function calls and threads to the timeline (default: disabled)\n'\
6354 ' -x2 Run two suspend/resumes back to back (default: disabled)\n'\
6355 ' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)\n'\
6356 ' -predelay t Include t ms delay before 1st suspend (default: 0 ms)\n'\
6357 ' -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6358 ' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6359 ' -multi n d Execute <n> consecutive tests at <d> seconds intervals. The outputs will\n'\
6360 ' be created in a new subdirectory with a summary page.\n'\
6361 ' [debug]\n'\
6362 ' -f Use ftrace to create device callgraphs (default: disabled)\n'\
6363 ' -ftop Use ftrace on the top level call: "%s" (default: disabled)\n'\
6364 ' -maxdepth N limit the callgraph data to N call levels (default: 0=all)\n'\
6365 ' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\
6366 ' -fadd file Add functions to be graphed in the timeline from a list in a text file\n'\
6367 ' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6368 ' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6369 ' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)\n'\
6370 ' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6371 ' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6372 ' -cgfilter S Filter the callgraph output in the timeline\n'\
6373 ' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6374 ' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6375 ' -devdump Print out all the raw device data for each phase\n'\
6376 ' -cgdump Print out all the raw callgraph data\n'\
6377 '\n'\
6378 'Other commands:\n'\
6379 ' -modes List available suspend modes\n'\
6380 ' -status Test to see if the system is enabled to run this tool\n'\
6381 ' -fpdt Print out the contents of the ACPI Firmware Performance Data Table\n'\
6382 ' -battery Print out battery info (if available)\n'\
6383 ' -wifi Print out wifi connection info (if wireless-tools and device exists)\n'\
6384 ' -x<mode> Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6385 ' -sysinfo Print out system info extracted from BIOS\n'\
6386 ' -devinfo Print out the pm settings of all devices which support runtime suspend\n'\
6387 ' -flist Print the list of functions currently being captured in ftrace\n'\
6388 ' -flistall Print all functions capable of being captured in ftrace\n'\
6389 ' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6390 ' [redo]\n'\
6391 ' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)\n'\
6392 ' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)\n'\
6393 '' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6394 return True
6395
6396# ----------------- MAIN --------------------
6397# exec start (skipped if script is loaded as library)
6398if __name__ == '__main__':
6399 genhtml = False
6400 cmd = ''
6401 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6402 '-devinfo', '-status', '-battery', '-xon', '-xoff', '-xstandby',
6403 '-xsuspend', '-xinit', '-xreset', '-xstat', '-wifi']
6404 if '-f' in sys.argv:
6405 sysvals.cgskip = sysvals.configFile('cgskip.txt')
6406 # loop through the command line arguments
6407 args = iter(sys.argv[1:])
6408 for arg in args:
6409 if(arg == '-m'):
6410 try:
6411 val = next(args)
6412 except:
6413 doError('No mode supplied', True)
6414 if val == 'command' and not sysvals.testcommand:
6415 doError('No command supplied for mode "command"', True)
6416 sysvals.suspendmode = val
6417 elif(arg in simplecmds):
6418 cmd = arg[1:]
6419 elif(arg == '-h'):
6420 printHelp()
6421 sys.exit(0)
6422 elif(arg == '-v'):
6423 pprint("Version %s" % sysvals.version)
6424 sys.exit(0)
6425 elif(arg == '-x2'):
6426 sysvals.execcount = 2
6427 elif(arg == '-x2delay'):
6428 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6429 elif(arg == '-predelay'):
6430 sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6431 elif(arg == '-postdelay'):
6432 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6433 elif(arg == '-f'):
6434 sysvals.usecallgraph = True
6435 elif(arg == '-ftop'):
6436 sysvals.usecallgraph = True
6437 sysvals.ftop = True
6438 sysvals.usekprobes = False
6439 elif(arg == '-skiphtml'):
6440 sysvals.skiphtml = True
6441 elif(arg == '-cgdump'):
6442 sysvals.cgdump = True
6443 elif(arg == '-devdump'):
6444 sysvals.devdump = True
6445 elif(arg == '-genhtml'):
6446 genhtml = True
6447 elif(arg == '-addlogs'):
6448 sysvals.dmesglog = sysvals.ftracelog = True
6449 elif(arg == '-nologs'):
6450 sysvals.dmesglog = sysvals.ftracelog = False
6451 elif(arg == '-addlogdmesg'):
6452 sysvals.dmesglog = True
6453 elif(arg == '-addlogftrace'):
6454 sysvals.ftracelog = True
6455 elif(arg == '-noturbostat'):
6456 sysvals.tstat = False
6457 elif(arg == '-verbose'):
6458 sysvals.verbose = True
6459 elif(arg == '-proc'):
6460 sysvals.useprocmon = True
6461 elif(arg == '-dev'):
6462 sysvals.usedevsrc = True
6463 elif(arg == '-sync'):
6464 sysvals.sync = True
6465 elif(arg == '-gzip'):
6466 sysvals.gzip = True
6467 elif(arg == '-rs'):
6468 try:
6469 val = next(args)
6470 except:
6471 doError('-rs requires "enable" or "disable"', True)
6472 if val.lower() in switchvalues:
6473 if val.lower() in switchoff:
6474 sysvals.rs = -1
6475 else:
6476 sysvals.rs = 1
6477 else:
6478 doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6479 elif(arg == '-display'):
6480 try:
6481 val = next(args)
6482 except:
6483 doError('-display requires an mode value', True)
6484 disopt = ['on', 'off', 'standby', 'suspend']
6485 if val.lower() not in disopt:
6486 doError('valid display mode values are %s' % disopt, True)
6487 sysvals.display = val.lower()
6488 elif(arg == '-maxdepth'):
6489 sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6490 elif(arg == '-rtcwake'):
6491 try:
6492 val = next(args)
6493 except:
6494 doError('No rtcwake time supplied', True)
6495 if val.lower() in switchoff:
6496 sysvals.rtcwake = False
6497 else:
6498 sysvals.rtcwake = True
6499 sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6500 elif(arg == '-timeprec'):
6501 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6502 elif(arg == '-mindev'):
6503 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6504 elif(arg == '-mincg'):
6505 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6506 elif(arg == '-bufsize'):
6507 sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6508 elif(arg == '-cgtest'):
6509 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6510 elif(arg == '-cgphase'):
6511 try:
6512 val = next(args)
6513 except:
6514 doError('No phase name supplied', True)
6515 d = Data(0)
6516 if val not in d.phasedef:
6517 doError('invalid phase --> (%s: %s), valid phases are %s'\
6518 % (arg, val, d.phasedef.keys()), True)
6519 sysvals.cgphase = val
6520 elif(arg == '-cgfilter'):
6521 try:
6522 val = next(args)
6523 except:
6524 doError('No callgraph functions supplied', True)
6525 sysvals.setCallgraphFilter(val)
6526 elif(arg == '-skipkprobe'):
6527 try:
6528 val = next(args)
6529 except:
6530 doError('No kprobe functions supplied', True)
6531 sysvals.skipKprobes(val)
6532 elif(arg == '-cgskip'):
6533 try:
6534 val = next(args)
6535 except:
6536 doError('No file supplied', True)
6537 if val.lower() in switchoff:
6538 sysvals.cgskip = ''
6539 else:
6540 sysvals.cgskip = sysvals.configFile(val)
6541 if(not sysvals.cgskip):
6542 doError('%s does not exist' % sysvals.cgskip)
6543 elif(arg == '-callloop-maxgap'):
6544 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6545 elif(arg == '-callloop-maxlen'):
6546 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6547 elif(arg == '-cmd'):
6548 try:
6549 val = next(args)
6550 except:
6551 doError('No command string supplied', True)
6552 sysvals.testcommand = val
6553 sysvals.suspendmode = 'command'
6554 elif(arg == '-expandcg'):
6555 sysvals.cgexp = True
6556 elif(arg == '-srgap'):
6557 sysvals.srgap = 5
6558 elif(arg == '-multi'):
6559 sysvals.multitest['run'] = True
6560 sysvals.multitest['count'] = getArgInt('-multi n d (exec count)', args, 2, 1000000)
6561 sysvals.multitest['delay'] = getArgInt('-multi n d (delay between tests)', args, 0, 3600)
6562 elif(arg == '-o'):
6563 try:
6564 val = next(args)
6565 except:
6566 doError('No subdirectory name supplied', True)
6567 sysvals.outdir = sysvals.setOutputFolder(val)
6568 elif(arg == '-config'):
6569 try:
6570 val = next(args)
6571 except:
6572 doError('No text file supplied', True)
6573 file = sysvals.configFile(val)
6574 if(not file):
6575 doError('%s does not exist' % val)
6576 configFromFile(file)
6577 elif(arg == '-fadd'):
6578 try:
6579 val = next(args)
6580 except:
6581 doError('No text file supplied', True)
6582 file = sysvals.configFile(val)
6583 if(not file):
6584 doError('%s does not exist' % val)
6585 sysvals.addFtraceFilterFunctions(file)
6586 elif(arg == '-dmesg'):
6587 try:
6588 val = next(args)
6589 except:
6590 doError('No dmesg file supplied', True)
6591 sysvals.notestrun = True
6592 sysvals.dmesgfile = val
6593 if(os.path.exists(sysvals.dmesgfile) == False):
6594 doError('%s does not exist' % sysvals.dmesgfile)
6595 elif(arg == '-ftrace'):
6596 try:
6597 val = next(args)
6598 except:
6599 doError('No ftrace file supplied', True)
6600 sysvals.notestrun = True
6601 sysvals.ftracefile = val
6602 if(os.path.exists(sysvals.ftracefile) == False):
6603 doError('%s does not exist' % sysvals.ftracefile)
6604 elif(arg == '-summary'):
6605 try:
6606 val = next(args)
6607 except:
6608 doError('No directory supplied', True)
6609 cmd = 'summary'
6610 sysvals.outdir = val
6611 sysvals.notestrun = True
6612 if(os.path.isdir(val) == False):
6613 doError('%s is not accesible' % val)
6614 elif(arg == '-filter'):
6615 try:
6616 val = next(args)
6617 except:
6618 doError('No devnames supplied', True)
6619 sysvals.setDeviceFilter(val)
6620 elif(arg == '-result'):
6621 try:
6622 val = next(args)
6623 except:
6624 doError('No result file supplied', True)
6625 sysvals.result = val
6626 sysvals.signalHandlerInit()
6627 else:
6628 doError('Invalid argument: '+arg, True)
6629
6630 # compatibility errors
6631 if(sysvals.usecallgraph and sysvals.usedevsrc):
6632 doError('-dev is not compatible with -f')
6633 if(sysvals.usecallgraph and sysvals.useprocmon):
6634 doError('-proc is not compatible with -f')
6635
6636 if sysvals.usecallgraph and sysvals.cgskip:
6637 sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
6638 sysvals.setCallgraphBlacklist(sysvals.cgskip)
6639
6640 # callgraph size cannot exceed device size
6641 if sysvals.mincglen < sysvals.mindevlen:
6642 sysvals.mincglen = sysvals.mindevlen
6643
6644 # remove existing buffers before calculating memory
6645 if(sysvals.usecallgraph or sysvals.usedevsrc):
6646 sysvals.fsetVal('16', 'buffer_size_kb')
6647 sysvals.cpuInfo()
6648
6649 # just run a utility command and exit
6650 if(cmd != ''):
6651 ret = 0
6652 if(cmd == 'status'):
6653 if not statusCheck(True):
6654 ret = 1
6655 elif(cmd == 'fpdt'):
6656 if not getFPDT(True):
6657 ret = 1
6658 elif(cmd == 'battery'):
6659 out = getBattery()
6660 if out:
6661 pprint('AC Connect : %s\nBattery Charge: %d' % out)
6662 else:
6663 pprint('no battery found')
6664 ret = 1
6665 elif(cmd == 'sysinfo'):
6666 sysvals.printSystemInfo(True)
6667 elif(cmd == 'devinfo'):
6668 deviceInfo()
6669 elif(cmd == 'modes'):
6670 pprint(getModes())
6671 elif(cmd == 'flist'):
6672 sysvals.getFtraceFilterFunctions(True)
6673 elif(cmd == 'flistall'):
6674 sysvals.getFtraceFilterFunctions(False)
6675 elif(cmd == 'summary'):
6676 runSummary(sysvals.outdir, True, genhtml)
6677 elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
6678 sysvals.verbose = True
6679 ret = displayControl(cmd[1:])
6680 elif(cmd == 'xstat'):
6681 pprint('Display Status: %s' % displayControl('stat').upper())
6682 elif(cmd == 'wifi'):
6683 out = sysvals.checkWifi()
6684 if 'device' not in out:
6685 pprint('WIFI interface not found')
6686 else:
6687 for key in sorted(out):
6688 pprint('%6s: %s' % (key.upper(), out[key]))
6689 sys.exit(ret)
6690
6691 # if instructed, re-analyze existing data files
6692 if(sysvals.notestrun):
6693 stamp = rerunTest(sysvals.outdir)
6694 sysvals.outputResult(stamp)
6695 sys.exit(0)
6696
6697 # verify that we can run a test
6698 error = statusCheck()
6699 if(error):
6700 doError(error)
6701
6702 # extract mem/disk extra modes and convert
6703 mode = sysvals.suspendmode
6704 if mode.startswith('mem'):
6705 memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
6706 if memmode == 'shallow':
6707 mode = 'standby'
6708 elif memmode == 's2idle':
6709 mode = 'freeze'
6710 else:
6711 mode = 'mem'
6712 sysvals.memmode = memmode
6713 sysvals.suspendmode = mode
6714 if mode.startswith('disk-'):
6715 sysvals.diskmode = mode.split('-', 1)[-1]
6716 sysvals.suspendmode = 'disk'
6717
6718 sysvals.systemInfo(dmidecode(sysvals.mempath))
6719
6720 setRuntimeSuspend(True)
6721 if sysvals.display:
6722 displayControl('init')
6723 ret = 0
6724 if sysvals.multitest['run']:
6725 # run multiple tests in a separate subdirectory
6726 if not sysvals.outdir:
6727 s = 'suspend-x%d' % sysvals.multitest['count']
6728 sysvals.outdir = datetime.now().strftime(s+'-%y%m%d-%H%M%S')
6729 if not os.path.isdir(sysvals.outdir):
6730 os.makedirs(sysvals.outdir)
6731 for i in range(sysvals.multitest['count']):
6732 if(i != 0):
6733 pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
6734 time.sleep(sysvals.multitest['delay'])
6735 pprint('TEST (%d/%d) START' % (i+1, sysvals.multitest['count']))
6736 fmt = 'suspend-%y%m%d-%H%M%S'
6737 sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
6738 ret = runTest(i+1)
6739 pprint('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count']))
6740 sysvals.logmsg = ''
6741 if not sysvals.skiphtml:
6742 runSummary(sysvals.outdir, False, False)
6743 sysvals.sudoUserchown(sysvals.outdir)
6744 else:
6745 if sysvals.outdir:
6746 sysvals.testdir = sysvals.outdir
6747 # run the test in the current directory
6748 ret = runTest()
6749 if sysvals.display:
6750 displayControl('reset')
6751 setRuntimeSuspend(False)
6752 sys.exit(ret)
1#!/usr/bin/python
2#
3# Tool for analyzing suspend/resume timing
4# Copyright (c) 2013, Intel Corporation.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms and conditions of the GNU General Public License,
8# version 2, as published by the Free Software Foundation.
9#
10# This program is distributed in the hope it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# Authors:
16# Todd Brandt <todd.e.brandt@linux.intel.com>
17#
18# Links:
19# Home Page
20# https://01.org/suspendresume
21# Source repo
22# git@github.com:01org/pm-graph
23#
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.
33#
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
42#
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 ..."
47#
48
49# ----------------- LIBRARIES --------------------
50
51import sys
52import time
53import os
54import string
55import re
56import platform
57from datetime import datetime
58import struct
59import ConfigParser
60import gzip
61from threading import Thread
62from subprocess import call, Popen, PIPE
63
64# ----------------- CLASSES --------------------
65
66# Class: SystemValues
67# Description:
68# A global, single-instance container used to
69# store system values and test parameters
70class SystemValues:
71 title = 'SleepGraph'
72 version = '5.0'
73 ansi = False
74 rs = 0
75 display = 0
76 gzip = False
77 sync = False
78 verbose = False
79 testlog = True
80 dmesglog = False
81 ftracelog = False
82 mindevlen = 0.0
83 mincglen = 0.0
84 cgphase = ''
85 cgtest = -1
86 cgskip = ''
87 multitest = {'run': False, 'count': 0, 'delay': 0}
88 max_graph_depth = 0
89 callloopmaxgap = 0.0001
90 callloopmaxlen = 0.005
91 bufsize = 0
92 cpucount = 0
93 memtotal = 204800
94 memfree = 204800
95 srgap = 0
96 cgexp = False
97 testdir = ''
98 outdir = ''
99 tpath = '/sys/kernel/debug/tracing/'
100 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
101 epath = '/sys/kernel/debug/tracing/events/power/'
102 traceevents = [
103 'suspend_resume',
104 'device_pm_callback_end',
105 'device_pm_callback_start'
106 ]
107 logmsg = ''
108 testcommand = ''
109 mempath = '/dev/mem'
110 powerfile = '/sys/power/state'
111 mempowerfile = '/sys/power/mem_sleep'
112 suspendmode = 'mem'
113 memmode = ''
114 hostname = 'localhost'
115 prefix = 'test'
116 teststamp = ''
117 sysstamp = ''
118 dmesgstart = 0.0
119 dmesgfile = ''
120 ftracefile = ''
121 htmlfile = 'output.html'
122 result = ''
123 rtcwake = True
124 rtcwaketime = 15
125 rtcpath = ''
126 devicefilter = []
127 cgfilter = []
128 stamp = 0
129 execcount = 1
130 x2delay = 0
131 skiphtml = False
132 usecallgraph = False
133 usetraceevents = False
134 usetracemarkers = True
135 usekprobes = True
136 usedevsrc = False
137 useprocmon = False
138 notestrun = False
139 cgdump = False
140 mixedphaseheight = True
141 devprops = dict()
142 predelay = 0
143 postdelay = 0
144 procexecfmt = 'ps - (?P<ps>.*)$'
145 devpropfmt = '# Device Properties: .*'
146 tracertypefmt = '# tracer: (?P<t>.*)'
147 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
148 tracefuncs = {
149 'sys_sync': {},
150 '__pm_notifier_call_chain': {},
151 'pm_prepare_console': {},
152 'pm_notifier_call_chain': {},
153 'freeze_processes': {},
154 'freeze_kernel_threads': {},
155 'pm_restrict_gfp_mask': {},
156 'acpi_suspend_begin': {},
157 'acpi_hibernation_begin': {},
158 'acpi_hibernation_enter': {},
159 'acpi_hibernation_leave': {},
160 'acpi_pm_freeze': {},
161 'acpi_pm_thaw': {},
162 'hibernate_preallocate_memory': {},
163 'create_basic_memory_bitmaps': {},
164 'swsusp_write': {},
165 'suspend_console': {},
166 'acpi_pm_prepare': {},
167 'syscore_suspend': {},
168 'arch_enable_nonboot_cpus_end': {},
169 'syscore_resume': {},
170 'acpi_pm_finish': {},
171 'resume_console': {},
172 'acpi_pm_end': {},
173 'pm_restore_gfp_mask': {},
174 'thaw_processes': {},
175 'pm_restore_console': {},
176 'CPU_OFF': {
177 'func':'_cpu_down',
178 'args_x86_64': {'cpu':'%di:s32'},
179 'format': 'CPU_OFF[{cpu}]'
180 },
181 'CPU_ON': {
182 'func':'_cpu_up',
183 'args_x86_64': {'cpu':'%di:s32'},
184 'format': 'CPU_ON[{cpu}]'
185 },
186 }
187 dev_tracefuncs = {
188 # general wait/delay/sleep
189 'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
190 'schedule_timeout_uninterruptible': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
191 'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
192 'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
193 'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
194 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
195 'acpi_os_stall': {'ub': 1},
196 # ACPI
197 'acpi_resume_power_resources': {},
198 'acpi_ps_parse_aml': {},
199 # filesystem
200 'ext4_sync_fs': {},
201 # 80211
202 'iwlagn_mac_start': {},
203 'iwlagn_alloc_bcast_station': {},
204 'iwl_trans_pcie_start_hw': {},
205 'iwl_trans_pcie_start_fw': {},
206 'iwl_run_init_ucode': {},
207 'iwl_load_ucode_wait_alive': {},
208 'iwl_alive_start': {},
209 'iwlagn_mac_stop': {},
210 'iwlagn_mac_suspend': {},
211 'iwlagn_mac_resume': {},
212 'iwlagn_mac_add_interface': {},
213 'iwlagn_mac_remove_interface': {},
214 'iwlagn_mac_change_interface': {},
215 'iwlagn_mac_config': {},
216 'iwlagn_configure_filter': {},
217 'iwlagn_mac_hw_scan': {},
218 'iwlagn_bss_info_changed': {},
219 'iwlagn_mac_channel_switch': {},
220 'iwlagn_mac_flush': {},
221 # ATA
222 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
223 # i915
224 'i915_gem_resume': {},
225 'i915_restore_state': {},
226 'intel_opregion_setup': {},
227 'g4x_pre_enable_dp': {},
228 'vlv_pre_enable_dp': {},
229 'chv_pre_enable_dp': {},
230 'g4x_enable_dp': {},
231 'vlv_enable_dp': {},
232 'intel_hpd_init': {},
233 'intel_opregion_register': {},
234 'intel_dp_detect': {},
235 'intel_hdmi_detect': {},
236 'intel_opregion_init': {},
237 'intel_fbdev_set_suspend': {},
238 }
239 cgblacklist = []
240 kprobes = dict()
241 timeformat = '%.3f'
242 cmdline = '%s %s' % \
243 (os.path.basename(sys.argv[0]), string.join(sys.argv[1:], ' '))
244 def __init__(self):
245 self.archargs = 'args_'+platform.machine()
246 self.hostname = platform.node()
247 if(self.hostname == ''):
248 self.hostname = 'localhost'
249 rtc = "rtc0"
250 if os.path.exists('/dev/rtc'):
251 rtc = os.readlink('/dev/rtc')
252 rtc = '/sys/class/rtc/'+rtc
253 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
254 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
255 self.rtcpath = rtc
256 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
257 self.ansi = True
258 self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
259 def vprint(self, msg):
260 self.logmsg += msg+'\n'
261 if(self.verbose):
262 print(msg)
263 def rootCheck(self, fatal=True):
264 if(os.access(self.powerfile, os.W_OK)):
265 return True
266 if fatal:
267 msg = 'This command requires sysfs mount and root access'
268 print('ERROR: %s\n') % msg
269 self.outputResult({'error':msg})
270 sys.exit()
271 return False
272 def rootUser(self, fatal=False):
273 if 'USER' in os.environ and os.environ['USER'] == 'root':
274 return True
275 if fatal:
276 msg = 'This command must be run as root'
277 print('ERROR: %s\n') % msg
278 self.outputResult({'error':msg})
279 sys.exit()
280 return False
281 def getExec(self, cmd):
282 dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
283 '/usr/local/sbin', '/usr/local/bin']
284 for path in dirlist:
285 cmdfull = os.path.join(path, cmd)
286 if os.path.exists(cmdfull):
287 return cmdfull
288 return ''
289 def setPrecision(self, num):
290 if num < 0 or num > 6:
291 return
292 self.timeformat = '%.{0}f'.format(num)
293 def setOutputFolder(self, value):
294 args = dict()
295 n = datetime.now()
296 args['date'] = n.strftime('%y%m%d')
297 args['time'] = n.strftime('%H%M%S')
298 args['hostname'] = args['host'] = self.hostname
299 return value.format(**args)
300 def setOutputFile(self):
301 if self.dmesgfile != '':
302 m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
303 if(m):
304 self.htmlfile = m.group('name')+'.html'
305 if self.ftracefile != '':
306 m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
307 if(m):
308 self.htmlfile = m.group('name')+'.html'
309 def systemInfo(self, info):
310 p = c = m = b = ''
311 if 'baseboard-manufacturer' in info:
312 m = info['baseboard-manufacturer']
313 elif 'system-manufacturer' in info:
314 m = info['system-manufacturer']
315 if 'baseboard-product-name' in info:
316 p = info['baseboard-product-name']
317 elif 'system-product-name' in info:
318 p = info['system-product-name']
319 if 'processor-version' in info:
320 c = info['processor-version']
321 if 'bios-version' in info:
322 b = info['bios-version']
323 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | numcpu:%d | memsz:%d | memfr:%d' % \
324 (m, p, c, b, self.cpucount, self.memtotal, self.memfree)
325 def printSystemInfo(self, fatal=False):
326 self.rootCheck(True)
327 out = dmidecode(self.mempath, fatal)
328 if len(out) < 1:
329 return
330 fmt = '%-24s: %s'
331 for name in sorted(out):
332 print fmt % (name, out[name])
333 print fmt % ('cpucount', ('%d' % self.cpucount))
334 print fmt % ('memtotal', ('%d kB' % self.memtotal))
335 print fmt % ('memfree', ('%d kB' % self.memfree))
336 def cpuInfo(self):
337 self.cpucount = 0
338 fp = open('/proc/cpuinfo', 'r')
339 for line in fp:
340 if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
341 self.cpucount += 1
342 fp.close()
343 fp = open('/proc/meminfo', 'r')
344 for line in fp:
345 m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
346 if m:
347 self.memtotal = int(m.group('sz'))
348 m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
349 if m:
350 self.memfree = int(m.group('sz'))
351 fp.close()
352 def initTestOutput(self, name):
353 self.prefix = self.hostname
354 v = open('/proc/version', 'r').read().strip()
355 kver = string.split(v)[2]
356 fmt = name+'-%m%d%y-%H%M%S'
357 testtime = datetime.now().strftime(fmt)
358 self.teststamp = \
359 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
360 ext = ''
361 if self.gzip:
362 ext = '.gz'
363 self.dmesgfile = \
364 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
365 self.ftracefile = \
366 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
367 self.htmlfile = \
368 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
369 if not os.path.isdir(self.testdir):
370 os.mkdir(self.testdir)
371 def getValueList(self, value):
372 out = []
373 for i in value.split(','):
374 if i.strip():
375 out.append(i.strip())
376 return out
377 def setDeviceFilter(self, value):
378 self.devicefilter = self.getValueList(value)
379 def setCallgraphFilter(self, value):
380 self.cgfilter = self.getValueList(value)
381 def setCallgraphBlacklist(self, file):
382 self.cgblacklist = self.listFromFile(file)
383 def rtcWakeAlarmOn(self):
384 call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
385 nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
386 if nowtime:
387 nowtime = int(nowtime)
388 else:
389 # if hardware time fails, use the software time
390 nowtime = int(datetime.now().strftime('%s'))
391 alarm = nowtime + self.rtcwaketime
392 call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
393 def rtcWakeAlarmOff(self):
394 call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
395 def initdmesg(self):
396 # get the latest time stamp from the dmesg log
397 fp = Popen('dmesg', stdout=PIPE).stdout
398 ktime = '0'
399 for line in fp:
400 line = line.replace('\r\n', '')
401 idx = line.find('[')
402 if idx > 1:
403 line = line[idx:]
404 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
405 if(m):
406 ktime = m.group('ktime')
407 fp.close()
408 self.dmesgstart = float(ktime)
409 def getdmesg(self, fwdata=[]):
410 op = self.writeDatafileHeader(sysvals.dmesgfile, fwdata)
411 # store all new dmesg lines since initdmesg was called
412 fp = Popen('dmesg', stdout=PIPE).stdout
413 for line in fp:
414 line = line.replace('\r\n', '')
415 idx = line.find('[')
416 if idx > 1:
417 line = line[idx:]
418 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
419 if(not m):
420 continue
421 ktime = float(m.group('ktime'))
422 if ktime > self.dmesgstart:
423 op.write(line)
424 fp.close()
425 op.close()
426 def listFromFile(self, file):
427 list = []
428 fp = open(file)
429 for i in fp.read().split('\n'):
430 i = i.strip()
431 if i and i[0] != '#':
432 list.append(i)
433 fp.close()
434 return list
435 def addFtraceFilterFunctions(self, file):
436 for i in self.listFromFile(file):
437 if len(i) < 2:
438 continue
439 self.tracefuncs[i] = dict()
440 def getFtraceFilterFunctions(self, current):
441 self.rootCheck(True)
442 if not current:
443 call('cat '+self.tpath+'available_filter_functions', shell=True)
444 return
445 master = self.listFromFile(self.tpath+'available_filter_functions')
446 for i in self.tracefuncs:
447 if 'func' in self.tracefuncs[i]:
448 i = self.tracefuncs[i]['func']
449 if i in master:
450 print i
451 else:
452 print self.colorText(i)
453 def setFtraceFilterFunctions(self, list):
454 master = self.listFromFile(self.tpath+'available_filter_functions')
455 flist = ''
456 for i in list:
457 if i not in master:
458 continue
459 if ' [' in i:
460 flist += i.split(' ')[0]+'\n'
461 else:
462 flist += i+'\n'
463 fp = open(self.tpath+'set_graph_function', 'w')
464 fp.write(flist)
465 fp.close()
466 def basicKprobe(self, name):
467 self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
468 def defaultKprobe(self, name, kdata):
469 k = kdata
470 for field in ['name', 'format', 'func']:
471 if field not in k:
472 k[field] = name
473 if self.archargs in k:
474 k['args'] = k[self.archargs]
475 else:
476 k['args'] = dict()
477 k['format'] = name
478 self.kprobes[name] = k
479 def kprobeColor(self, name):
480 if name not in self.kprobes or 'color' not in self.kprobes[name]:
481 return ''
482 return self.kprobes[name]['color']
483 def kprobeDisplayName(self, name, dataraw):
484 if name not in self.kprobes:
485 self.basicKprobe(name)
486 data = ''
487 quote=0
488 # first remvoe any spaces inside quotes, and the quotes
489 for c in dataraw:
490 if c == '"':
491 quote = (quote + 1) % 2
492 if quote and c == ' ':
493 data += '_'
494 elif c != '"':
495 data += c
496 fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
497 arglist = dict()
498 # now process the args
499 for arg in sorted(args):
500 arglist[arg] = ''
501 m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
502 if m:
503 arglist[arg] = m.group('arg')
504 else:
505 m = re.match('.* '+arg+'=(?P<arg>.*)', data);
506 if m:
507 arglist[arg] = m.group('arg')
508 out = fmt.format(**arglist)
509 out = out.replace(' ', '_').replace('"', '')
510 return out
511 def kprobeText(self, kname, kprobe):
512 name = fmt = func = kname
513 args = dict()
514 if 'name' in kprobe:
515 name = kprobe['name']
516 if 'format' in kprobe:
517 fmt = kprobe['format']
518 if 'func' in kprobe:
519 func = kprobe['func']
520 if self.archargs in kprobe:
521 args = kprobe[self.archargs]
522 if 'args' in kprobe:
523 args = kprobe['args']
524 if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
525 doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
526 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
527 if arg not in args:
528 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
529 val = 'p:%s_cal %s' % (name, func)
530 for i in sorted(args):
531 val += ' %s=%s' % (i, args[i])
532 val += '\nr:%s_ret %s $retval\n' % (name, func)
533 return val
534 def addKprobes(self, output=False):
535 if len(self.kprobes) < 1:
536 return
537 if output:
538 print(' kprobe functions in this kernel:')
539 # first test each kprobe
540 rejects = []
541 # sort kprobes: trace, ub-dev, custom, dev
542 kpl = [[], [], [], []]
543 linesout = len(self.kprobes)
544 for name in sorted(self.kprobes):
545 res = self.colorText('YES', 32)
546 if not self.testKprobe(name, self.kprobes[name]):
547 res = self.colorText('NO')
548 rejects.append(name)
549 else:
550 if name in self.tracefuncs:
551 kpl[0].append(name)
552 elif name in self.dev_tracefuncs:
553 if 'ub' in self.dev_tracefuncs[name]:
554 kpl[1].append(name)
555 else:
556 kpl[3].append(name)
557 else:
558 kpl[2].append(name)
559 if output:
560 print(' %s: %s' % (name, res))
561 kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
562 # remove all failed ones from the list
563 for name in rejects:
564 self.kprobes.pop(name)
565 # set the kprobes all at once
566 self.fsetVal('', 'kprobe_events')
567 kprobeevents = ''
568 for kp in kplist:
569 kprobeevents += self.kprobeText(kp, self.kprobes[kp])
570 self.fsetVal(kprobeevents, 'kprobe_events')
571 if output:
572 check = self.fgetVal('kprobe_events')
573 linesack = (len(check.split('\n')) - 1) / 2
574 print(' kprobe functions enabled: %d/%d' % (linesack, linesout))
575 self.fsetVal('1', 'events/kprobes/enable')
576 def testKprobe(self, kname, kprobe):
577 self.fsetVal('0', 'events/kprobes/enable')
578 kprobeevents = self.kprobeText(kname, kprobe)
579 if not kprobeevents:
580 return False
581 try:
582 self.fsetVal(kprobeevents, 'kprobe_events')
583 check = self.fgetVal('kprobe_events')
584 except:
585 return False
586 linesout = len(kprobeevents.split('\n'))
587 linesack = len(check.split('\n'))
588 if linesack < linesout:
589 return False
590 return True
591 def setVal(self, val, file, mode='w'):
592 if not os.path.exists(file):
593 return False
594 try:
595 fp = open(file, mode, 0)
596 fp.write(val)
597 fp.flush()
598 fp.close()
599 except:
600 return False
601 return True
602 def fsetVal(self, val, path, mode='w'):
603 return self.setVal(val, self.tpath+path, mode)
604 def getVal(self, file):
605 res = ''
606 if not os.path.exists(file):
607 return res
608 try:
609 fp = open(file, 'r')
610 res = fp.read()
611 fp.close()
612 except:
613 pass
614 return res
615 def fgetVal(self, path):
616 return self.getVal(self.tpath+path)
617 def cleanupFtrace(self):
618 if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
619 self.fsetVal('0', 'events/kprobes/enable')
620 self.fsetVal('', 'kprobe_events')
621 self.fsetVal('1024', 'buffer_size_kb')
622 def setupAllKprobes(self):
623 for name in self.tracefuncs:
624 self.defaultKprobe(name, self.tracefuncs[name])
625 for name in self.dev_tracefuncs:
626 self.defaultKprobe(name, self.dev_tracefuncs[name])
627 def isCallgraphFunc(self, name):
628 if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
629 return True
630 for i in self.tracefuncs:
631 if 'func' in self.tracefuncs[i]:
632 f = self.tracefuncs[i]['func']
633 else:
634 f = i
635 if name == f:
636 return True
637 return False
638 def initFtrace(self):
639 self.printSystemInfo(False)
640 print('INITIALIZING FTRACE...')
641 # turn trace off
642 self.fsetVal('0', 'tracing_on')
643 self.cleanupFtrace()
644 # set the trace clock to global
645 self.fsetVal('global', 'trace_clock')
646 self.fsetVal('nop', 'current_tracer')
647 # set trace buffer to an appropriate value
648 cpus = max(1, self.cpucount)
649 if self.bufsize > 0:
650 tgtsize = self.bufsize
651 elif self.usecallgraph or self.usedevsrc:
652 tgtsize = min(self.memfree, 3*1024*1024)
653 else:
654 tgtsize = 65536
655 while not self.fsetVal('%d' % (tgtsize / cpus), 'buffer_size_kb'):
656 # if the size failed to set, lower it and keep trying
657 tgtsize -= 65536
658 if tgtsize < 65536:
659 tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
660 break
661 print 'Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus)
662 # initialize the callgraph trace
663 if(self.usecallgraph):
664 # set trace type
665 self.fsetVal('function_graph', 'current_tracer')
666 self.fsetVal('', 'set_ftrace_filter')
667 # set trace format options
668 self.fsetVal('print-parent', 'trace_options')
669 self.fsetVal('funcgraph-abstime', 'trace_options')
670 self.fsetVal('funcgraph-cpu', 'trace_options')
671 self.fsetVal('funcgraph-duration', 'trace_options')
672 self.fsetVal('funcgraph-proc', 'trace_options')
673 self.fsetVal('funcgraph-tail', 'trace_options')
674 self.fsetVal('nofuncgraph-overhead', 'trace_options')
675 self.fsetVal('context-info', 'trace_options')
676 self.fsetVal('graph-time', 'trace_options')
677 self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
678 cf = ['dpm_run_callback']
679 if(self.usetraceevents):
680 cf += ['dpm_prepare', 'dpm_complete']
681 for fn in self.tracefuncs:
682 if 'func' in self.tracefuncs[fn]:
683 cf.append(self.tracefuncs[fn]['func'])
684 else:
685 cf.append(fn)
686 self.setFtraceFilterFunctions(cf)
687 # initialize the kprobe trace
688 elif self.usekprobes:
689 for name in self.tracefuncs:
690 self.defaultKprobe(name, self.tracefuncs[name])
691 if self.usedevsrc:
692 for name in self.dev_tracefuncs:
693 self.defaultKprobe(name, self.dev_tracefuncs[name])
694 print('INITIALIZING KPROBES...')
695 self.addKprobes(self.verbose)
696 if(self.usetraceevents):
697 # turn trace events on
698 events = iter(self.traceevents)
699 for e in events:
700 self.fsetVal('1', 'events/power/'+e+'/enable')
701 # clear the trace buffer
702 self.fsetVal('', 'trace')
703 def verifyFtrace(self):
704 # files needed for any trace data
705 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
706 'trace_marker', 'trace_options', 'tracing_on']
707 # files needed for callgraph trace data
708 tp = self.tpath
709 if(self.usecallgraph):
710 files += [
711 'available_filter_functions',
712 'set_ftrace_filter',
713 'set_graph_function'
714 ]
715 for f in files:
716 if(os.path.exists(tp+f) == False):
717 return False
718 return True
719 def verifyKprobes(self):
720 # files needed for kprobes to work
721 files = ['kprobe_events', 'events']
722 tp = self.tpath
723 for f in files:
724 if(os.path.exists(tp+f) == False):
725 return False
726 return True
727 def colorText(self, str, color=31):
728 if not self.ansi:
729 return str
730 return '\x1B[%d;40m%s\x1B[m' % (color, str)
731 def writeDatafileHeader(self, filename, fwdata=[]):
732 fp = self.openlog(filename, 'w')
733 fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
734 if(self.suspendmode == 'mem' or self.suspendmode == 'command'):
735 for fw in fwdata:
736 if(fw):
737 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
738 return fp
739 def sudouser(self, dir):
740 if os.path.exists(dir) and os.getuid() == 0 and \
741 'SUDO_USER' in os.environ:
742 cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
743 call(cmd.format(os.environ['SUDO_USER'], dir), shell=True)
744 def outputResult(self, testdata, num=0):
745 if not self.result:
746 return
747 n = ''
748 if num > 0:
749 n = '%d' % num
750 fp = open(self.result, 'a')
751 if 'error' in testdata:
752 fp.write('result%s: fail\n' % n)
753 fp.write('error%s: %s\n' % (n, testdata['error']))
754 else:
755 fp.write('result%s: pass\n' % n)
756 for v in ['suspend', 'resume', 'boot', 'lastinit']:
757 if v in testdata:
758 fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
759 for v in ['fwsuspend', 'fwresume']:
760 if v in testdata:
761 fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
762 if 'bugurl' in testdata:
763 fp.write('url%s: %s\n' % (n, testdata['bugurl']))
764 fp.close()
765 self.sudouser(self.result)
766 def configFile(self, file):
767 dir = os.path.dirname(os.path.realpath(__file__))
768 if os.path.exists(file):
769 return file
770 elif os.path.exists(dir+'/'+file):
771 return dir+'/'+file
772 elif os.path.exists(dir+'/config/'+file):
773 return dir+'/config/'+file
774 return ''
775 def openlog(self, filename, mode):
776 isgz = self.gzip
777 if mode == 'r':
778 try:
779 with gzip.open(filename, mode+'b') as fp:
780 test = fp.read(64)
781 isgz = True
782 except:
783 isgz = False
784 if isgz:
785 return gzip.open(filename, mode+'b')
786 return open(filename, mode)
787
788sysvals = SystemValues()
789switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
790switchoff = ['disable', 'off', 'false', '0']
791suspendmodename = {
792 'freeze': 'Freeze (S0)',
793 'standby': 'Standby (S1)',
794 'mem': 'Suspend (S3)',
795 'disk': 'Hibernate (S4)'
796}
797
798# Class: DevProps
799# Description:
800# Simple class which holds property values collected
801# for all the devices used in the timeline.
802class DevProps:
803 syspath = ''
804 altname = ''
805 async = True
806 xtraclass = ''
807 xtrainfo = ''
808 def out(self, dev):
809 return '%s,%s,%d;' % (dev, self.altname, self.async)
810 def debug(self, dev):
811 print '%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.async)
812 def altName(self, dev):
813 if not self.altname or self.altname == dev:
814 return dev
815 return '%s [%s]' % (self.altname, dev)
816 def xtraClass(self):
817 if self.xtraclass:
818 return ' '+self.xtraclass
819 if not self.async:
820 return ' sync'
821 return ''
822 def xtraInfo(self):
823 if self.xtraclass:
824 return ' '+self.xtraclass
825 if self.async:
826 return ' async_device'
827 return ' sync_device'
828
829# Class: DeviceNode
830# Description:
831# A container used to create a device hierachy, with a single root node
832# and a tree of child nodes. Used by Data.deviceTopology()
833class DeviceNode:
834 name = ''
835 children = 0
836 depth = 0
837 def __init__(self, nodename, nodedepth):
838 self.name = nodename
839 self.children = []
840 self.depth = nodedepth
841
842# Class: Data
843# Description:
844# The primary container for suspend/resume test data. There is one for
845# each test run. The data is organized into a cronological hierarchy:
846# Data.dmesg {
847# phases {
848# 10 sequential, non-overlapping phases of S/R
849# contents: times for phase start/end, order/color data for html
850# devlist {
851# device callback or action list for this phase
852# device {
853# a single device callback or generic action
854# contents: start/stop times, pid/cpu/driver info
855# parents/children, html id for timeline/callgraph
856# optionally includes an ftrace callgraph
857# optionally includes dev/ps data
858# }
859# }
860# }
861# }
862#
863class Data:
864 dmesg = {} # root data structure
865 phases = [] # ordered list of phases
866 start = 0.0 # test start
867 end = 0.0 # test end
868 tSuspended = 0.0 # low-level suspend start
869 tResumed = 0.0 # low-level resume start
870 tKernSus = 0.0 # kernel level suspend start
871 tKernRes = 0.0 # kernel level resume end
872 tLow = 0.0 # time spent in low-level suspend (standby/freeze)
873 fwValid = False # is firmware data available
874 fwSuspend = 0 # time spent in firmware suspend
875 fwResume = 0 # time spent in firmware resume
876 dmesgtext = [] # dmesg text file in memory
877 pstl = 0 # process timeline
878 testnumber = 0
879 idstr = ''
880 html_device_id = 0
881 stamp = 0
882 outfile = ''
883 devpids = []
884 kerror = False
885 def __init__(self, num):
886 idchar = 'abcdefghij'
887 self.pstl = dict()
888 self.testnumber = num
889 self.idstr = idchar[num]
890 self.dmesgtext = []
891 self.phases = []
892 self.dmesg = { # fixed list of 10 phases
893 'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
894 'row': 0, 'color': '#CCFFCC', 'order': 0},
895 'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
896 'row': 0, 'color': '#88FF88', 'order': 1},
897 'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
898 'row': 0, 'color': '#00AA00', 'order': 2},
899 'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
900 'row': 0, 'color': '#008888', 'order': 3},
901 'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
902 'row': 0, 'color': '#0000FF', 'order': 4},
903 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
904 'row': 0, 'color': '#FF0000', 'order': 5},
905 'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
906 'row': 0, 'color': '#FF9900', 'order': 6},
907 'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
908 'row': 0, 'color': '#FFCC00', 'order': 7},
909 'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
910 'row': 0, 'color': '#FFFF88', 'order': 8},
911 'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
912 'row': 0, 'color': '#FFFFCC', 'order': 9}
913 }
914 self.phases = self.sortedPhases()
915 self.devicegroups = []
916 for phase in self.phases:
917 self.devicegroups.append([phase])
918 self.errorinfo = {'suspend':[],'resume':[]}
919 def extractErrorInfo(self):
920 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
921 i = 0
922 list = []
923 # sl = start line, et = error time, el = error line
924 type = 'ERROR'
925 sl = et = el = -1
926 for line in lf:
927 i += 1
928 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
929 if not m:
930 continue
931 t = float(m.group('ktime'))
932 if t < self.start or t > self.end:
933 continue
934 if t < self.tSuspended:
935 dir = 'suspend'
936 else:
937 dir = 'resume'
938 msg = m.group('msg')
939 if re.match('-*\[ *cut here *\]-*', msg):
940 type = 'WARNING'
941 sl = i
942 elif re.match('genirq: .*', msg):
943 type = 'IRQ'
944 sl = i
945 elif re.match('BUG: .*', msg) or re.match('kernel BUG .*', msg):
946 type = 'BUG'
947 sl = i
948 elif re.match('-*\[ *end trace .*\]-*', msg) or \
949 re.match('R13: .*', msg):
950 if et >= 0 and sl >= 0:
951 list.append((type, dir, et, sl, i))
952 self.kerror = True
953 sl = et = el = -1
954 type = 'ERROR'
955 elif 'Call Trace:' in msg:
956 if el >= 0 and et >= 0:
957 list.append((type, dir, et, el, el))
958 self.kerror = True
959 et, el = t, i
960 if sl < 0 or type == 'BUG':
961 slval = i
962 if sl >= 0:
963 slval = sl
964 list.append((type, dir, et, slval, i))
965 self.kerror = True
966 sl = et = el = -1
967 type = 'ERROR'
968 if el >= 0 and et >= 0:
969 list.append((type, dir, et, el, el))
970 self.kerror = True
971 for e in list:
972 type, dir, t, idx1, idx2 = e
973 sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t))
974 self.errorinfo[dir].append((type, t, idx1, idx2))
975 if self.kerror:
976 sysvals.dmesglog = True
977 lf.close()
978 def setStart(self, time):
979 self.start = time
980 def setEnd(self, time):
981 self.end = time
982 def isTraceEventOutsideDeviceCalls(self, pid, time):
983 for phase in self.phases:
984 list = self.dmesg[phase]['list']
985 for dev in list:
986 d = list[dev]
987 if(d['pid'] == pid and time >= d['start'] and
988 time < d['end']):
989 return False
990 return True
991 def phaseCollision(self, phase, isbegin, line):
992 key = 'end'
993 if isbegin:
994 key = 'start'
995 if self.dmesg[phase][key] >= 0:
996 sysvals.vprint('IGNORE: %s' % line.strip())
997 return True
998 return False
999 def sourcePhase(self, start):
1000 for phase in self.phases:
1001 pend = self.dmesg[phase]['end']
1002 if start <= pend:
1003 return phase
1004 return 'resume_complete'
1005 def sourceDevice(self, phaselist, start, end, pid, type):
1006 tgtdev = ''
1007 for phase in phaselist:
1008 list = self.dmesg[phase]['list']
1009 for devname in list:
1010 dev = list[devname]
1011 # pid must match
1012 if dev['pid'] != pid:
1013 continue
1014 devS = dev['start']
1015 devE = dev['end']
1016 if type == 'device':
1017 # device target event is entirely inside the source boundary
1018 if(start < devS or start >= devE or end <= devS or end > devE):
1019 continue
1020 elif type == 'thread':
1021 # thread target event will expand the source boundary
1022 if start < devS:
1023 dev['start'] = start
1024 if end > devE:
1025 dev['end'] = end
1026 tgtdev = dev
1027 break
1028 return tgtdev
1029 def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1030 # try to place the call in a device
1031 tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device')
1032 # calls with device pids that occur outside device bounds are dropped
1033 # TODO: include these somehow
1034 if not tgtdev and pid in self.devpids:
1035 return False
1036 # try to place the call in a thread
1037 if not tgtdev:
1038 tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread')
1039 # create new thread blocks, expand as new calls are found
1040 if not tgtdev:
1041 if proc == '<...>':
1042 threadname = 'kthread-%d' % (pid)
1043 else:
1044 threadname = '%s-%d' % (proc, pid)
1045 tgtphase = self.sourcePhase(start)
1046 self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1047 return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1048 # this should not happen
1049 if not tgtdev:
1050 sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1051 (start, end, proc, pid, kprobename, cdata, rdata))
1052 return False
1053 # place the call data inside the src element of the tgtdev
1054 if('src' not in tgtdev):
1055 tgtdev['src'] = []
1056 dtf = sysvals.dev_tracefuncs
1057 ubiquitous = False
1058 if kprobename in dtf and 'ub' in dtf[kprobename]:
1059 ubiquitous = True
1060 title = cdata+' '+rdata
1061 mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1062 m = re.match(mstr, title)
1063 if m:
1064 c = m.group('caller')
1065 a = m.group('args').strip()
1066 r = m.group('ret')
1067 if len(r) > 6:
1068 r = ''
1069 else:
1070 r = 'ret=%s ' % r
1071 if ubiquitous and c in dtf and 'ub' in dtf[c]:
1072 return False
1073 color = sysvals.kprobeColor(kprobename)
1074 e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1075 tgtdev['src'].append(e)
1076 return True
1077 def overflowDevices(self):
1078 # get a list of devices that extend beyond the end of this test run
1079 devlist = []
1080 for phase in self.phases:
1081 list = self.dmesg[phase]['list']
1082 for devname in list:
1083 dev = list[devname]
1084 if dev['end'] > self.end:
1085 devlist.append(dev)
1086 return devlist
1087 def mergeOverlapDevices(self, devlist):
1088 # merge any devices that overlap devlist
1089 for dev in devlist:
1090 devname = dev['name']
1091 for phase in self.phases:
1092 list = self.dmesg[phase]['list']
1093 if devname not in list:
1094 continue
1095 tdev = list[devname]
1096 o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1097 if o <= 0:
1098 continue
1099 dev['end'] = tdev['end']
1100 if 'src' not in dev or 'src' not in tdev:
1101 continue
1102 dev['src'] += tdev['src']
1103 del list[devname]
1104 def usurpTouchingThread(self, name, dev):
1105 # the caller test has priority of this thread, give it to him
1106 for phase in self.phases:
1107 list = self.dmesg[phase]['list']
1108 if name in list:
1109 tdev = list[name]
1110 if tdev['start'] - dev['end'] < 0.1:
1111 dev['end'] = tdev['end']
1112 if 'src' not in dev:
1113 dev['src'] = []
1114 if 'src' in tdev:
1115 dev['src'] += tdev['src']
1116 del list[name]
1117 break
1118 def stitchTouchingThreads(self, testlist):
1119 # merge any threads between tests that touch
1120 for phase in self.phases:
1121 list = self.dmesg[phase]['list']
1122 for devname in list:
1123 dev = list[devname]
1124 if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1125 continue
1126 for data in testlist:
1127 data.usurpTouchingThread(devname, dev)
1128 def optimizeDevSrc(self):
1129 # merge any src call loops to reduce timeline size
1130 for phase in self.phases:
1131 list = self.dmesg[phase]['list']
1132 for dev in list:
1133 if 'src' not in list[dev]:
1134 continue
1135 src = list[dev]['src']
1136 p = 0
1137 for e in sorted(src, key=lambda event: event.time):
1138 if not p or not e.repeat(p):
1139 p = e
1140 continue
1141 # e is another iteration of p, move it into p
1142 p.end = e.end
1143 p.length = p.end - p.time
1144 p.count += 1
1145 src.remove(e)
1146 def trimTimeVal(self, t, t0, dT, left):
1147 if left:
1148 if(t > t0):
1149 if(t - dT < t0):
1150 return t0
1151 return t - dT
1152 else:
1153 return t
1154 else:
1155 if(t < t0 + dT):
1156 if(t > t0):
1157 return t0 + dT
1158 return t + dT
1159 else:
1160 return t
1161 def trimTime(self, t0, dT, left):
1162 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1163 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1164 self.start = self.trimTimeVal(self.start, t0, dT, left)
1165 self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1166 self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1167 self.end = self.trimTimeVal(self.end, t0, dT, left)
1168 for phase in self.phases:
1169 p = self.dmesg[phase]
1170 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1171 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1172 list = p['list']
1173 for name in list:
1174 d = list[name]
1175 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1176 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1177 if('ftrace' in d):
1178 cg = d['ftrace']
1179 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1180 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1181 for line in cg.list:
1182 line.time = self.trimTimeVal(line.time, t0, dT, left)
1183 if('src' in d):
1184 for e in d['src']:
1185 e.time = self.trimTimeVal(e.time, t0, dT, left)
1186 for dir in ['suspend', 'resume']:
1187 list = []
1188 for e in self.errorinfo[dir]:
1189 type, tm, idx1, idx2 = e
1190 tm = self.trimTimeVal(tm, t0, dT, left)
1191 list.append((type, tm, idx1, idx2))
1192 self.errorinfo[dir] = list
1193 def normalizeTime(self, tZero):
1194 # trim out any standby or freeze clock time
1195 if(self.tSuspended != self.tResumed):
1196 if(self.tResumed > tZero):
1197 self.trimTime(self.tSuspended, \
1198 self.tResumed-self.tSuspended, True)
1199 else:
1200 self.trimTime(self.tSuspended, \
1201 self.tResumed-self.tSuspended, False)
1202 def getTimeValues(self):
1203 sktime = (self.dmesg['suspend_machine']['end'] - \
1204 self.tKernSus) * 1000
1205 rktime = (self.dmesg['resume_complete']['end'] - \
1206 self.dmesg['resume_machine']['start']) * 1000
1207 return (sktime, rktime)
1208 def setPhase(self, phase, ktime, isbegin):
1209 if(isbegin):
1210 self.dmesg[phase]['start'] = ktime
1211 else:
1212 self.dmesg[phase]['end'] = ktime
1213 def dmesgSortVal(self, phase):
1214 return self.dmesg[phase]['order']
1215 def sortedPhases(self):
1216 return sorted(self.dmesg, key=self.dmesgSortVal)
1217 def sortedDevices(self, phase):
1218 list = self.dmesg[phase]['list']
1219 slist = []
1220 tmp = dict()
1221 for devname in list:
1222 dev = list[devname]
1223 if dev['length'] == 0:
1224 continue
1225 tmp[dev['start']] = devname
1226 for t in sorted(tmp):
1227 slist.append(tmp[t])
1228 return slist
1229 def fixupInitcalls(self, phase):
1230 # if any calls never returned, clip them at system resume end
1231 phaselist = self.dmesg[phase]['list']
1232 for devname in phaselist:
1233 dev = phaselist[devname]
1234 if(dev['end'] < 0):
1235 for p in self.phases:
1236 if self.dmesg[p]['end'] > dev['start']:
1237 dev['end'] = self.dmesg[p]['end']
1238 break
1239 sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1240 def deviceFilter(self, devicefilter):
1241 for phase in self.phases:
1242 list = self.dmesg[phase]['list']
1243 rmlist = []
1244 for name in list:
1245 keep = False
1246 for filter in devicefilter:
1247 if filter in name or \
1248 ('drv' in list[name] and filter in list[name]['drv']):
1249 keep = True
1250 if not keep:
1251 rmlist.append(name)
1252 for name in rmlist:
1253 del list[name]
1254 def fixupInitcallsThatDidntReturn(self):
1255 # if any calls never returned, clip them at system resume end
1256 for phase in self.phases:
1257 self.fixupInitcalls(phase)
1258 def phaseOverlap(self, phases):
1259 rmgroups = []
1260 newgroup = []
1261 for group in self.devicegroups:
1262 for phase in phases:
1263 if phase not in group:
1264 continue
1265 for p in group:
1266 if p not in newgroup:
1267 newgroup.append(p)
1268 if group not in rmgroups:
1269 rmgroups.append(group)
1270 for group in rmgroups:
1271 self.devicegroups.remove(group)
1272 self.devicegroups.append(newgroup)
1273 def newActionGlobal(self, name, start, end, pid=-1, color=''):
1274 # which phase is this device callback or action in
1275 targetphase = 'none'
1276 htmlclass = ''
1277 overlap = 0.0
1278 phases = []
1279 for phase in self.phases:
1280 pstart = self.dmesg[phase]['start']
1281 pend = self.dmesg[phase]['end']
1282 # see if the action overlaps this phase
1283 o = max(0, min(end, pend) - max(start, pstart))
1284 if o > 0:
1285 phases.append(phase)
1286 # set the target phase to the one that overlaps most
1287 if o > overlap:
1288 if overlap > 0 and phase == 'post_resume':
1289 continue
1290 targetphase = phase
1291 overlap = o
1292 # if no target phase was found, pin it to the edge
1293 if targetphase == 'none':
1294 p0start = self.dmesg[self.phases[0]]['start']
1295 if start <= p0start:
1296 targetphase = self.phases[0]
1297 else:
1298 targetphase = self.phases[-1]
1299 if pid == -2:
1300 htmlclass = ' bg'
1301 elif pid == -3:
1302 htmlclass = ' ps'
1303 if len(phases) > 1:
1304 htmlclass = ' bg'
1305 self.phaseOverlap(phases)
1306 if targetphase in self.phases:
1307 newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1308 return (targetphase, newname)
1309 return False
1310 def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1311 # new device callback for a specific phase
1312 self.html_device_id += 1
1313 devid = '%s%d' % (self.idstr, self.html_device_id)
1314 list = self.dmesg[phase]['list']
1315 length = -1.0
1316 if(start >= 0 and end >= 0):
1317 length = end - start
1318 if pid == -2:
1319 i = 2
1320 origname = name
1321 while(name in list):
1322 name = '%s[%d]' % (origname, i)
1323 i += 1
1324 list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1325 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1326 if htmlclass:
1327 list[name]['htmlclass'] = htmlclass
1328 if color:
1329 list[name]['color'] = color
1330 return name
1331 def deviceChildren(self, devname, phase):
1332 devlist = []
1333 list = self.dmesg[phase]['list']
1334 for child in list:
1335 if(list[child]['par'] == devname):
1336 devlist.append(child)
1337 return devlist
1338 def printDetails(self):
1339 sysvals.vprint('Timeline Details:')
1340 sysvals.vprint(' test start: %f' % self.start)
1341 sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1342 for phase in self.phases:
1343 dc = len(self.dmesg[phase]['list'])
1344 sysvals.vprint(' %16s: %f - %f (%d devices)' % (phase, \
1345 self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
1346 sysvals.vprint(' kernel resume end: %f' % self.tKernRes)
1347 sysvals.vprint(' test end: %f' % self.end)
1348 def deviceChildrenAllPhases(self, devname):
1349 devlist = []
1350 for phase in self.phases:
1351 list = self.deviceChildren(devname, phase)
1352 for dev in list:
1353 if dev not in devlist:
1354 devlist.append(dev)
1355 return devlist
1356 def masterTopology(self, name, list, depth):
1357 node = DeviceNode(name, depth)
1358 for cname in list:
1359 # avoid recursions
1360 if name == cname:
1361 continue
1362 clist = self.deviceChildrenAllPhases(cname)
1363 cnode = self.masterTopology(cname, clist, depth+1)
1364 node.children.append(cnode)
1365 return node
1366 def printTopology(self, node):
1367 html = ''
1368 if node.name:
1369 info = ''
1370 drv = ''
1371 for phase in self.phases:
1372 list = self.dmesg[phase]['list']
1373 if node.name in list:
1374 s = list[node.name]['start']
1375 e = list[node.name]['end']
1376 if list[node.name]['drv']:
1377 drv = ' {'+list[node.name]['drv']+'}'
1378 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1379 html += '<li><b>'+node.name+drv+'</b>'
1380 if info:
1381 html += '<ul>'+info+'</ul>'
1382 html += '</li>'
1383 if len(node.children) > 0:
1384 html += '<ul>'
1385 for cnode in node.children:
1386 html += self.printTopology(cnode)
1387 html += '</ul>'
1388 return html
1389 def rootDeviceList(self):
1390 # list of devices graphed
1391 real = []
1392 for phase in self.dmesg:
1393 list = self.dmesg[phase]['list']
1394 for dev in list:
1395 if list[dev]['pid'] >= 0 and dev not in real:
1396 real.append(dev)
1397 # list of top-most root devices
1398 rootlist = []
1399 for phase in self.dmesg:
1400 list = self.dmesg[phase]['list']
1401 for dev in list:
1402 pdev = list[dev]['par']
1403 pid = list[dev]['pid']
1404 if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1405 continue
1406 if pdev and pdev not in real and pdev not in rootlist:
1407 rootlist.append(pdev)
1408 return rootlist
1409 def deviceTopology(self):
1410 rootlist = self.rootDeviceList()
1411 master = self.masterTopology('', rootlist, 0)
1412 return self.printTopology(master)
1413 def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1414 # only select devices that will actually show up in html
1415 self.tdevlist = dict()
1416 for phase in self.dmesg:
1417 devlist = []
1418 list = self.dmesg[phase]['list']
1419 for dev in list:
1420 length = (list[dev]['end'] - list[dev]['start']) * 1000
1421 width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
1422 if width != '0.000000' and length >= mindevlen:
1423 devlist.append(dev)
1424 self.tdevlist[phase] = devlist
1425 def addHorizontalDivider(self, devname, devend):
1426 phase = 'suspend_prepare'
1427 self.newAction(phase, devname, -2, '', \
1428 self.start, devend, '', ' sec', '')
1429 if phase not in self.tdevlist:
1430 self.tdevlist[phase] = []
1431 self.tdevlist[phase].append(devname)
1432 d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
1433 return d
1434 def addProcessUsageEvent(self, name, times):
1435 # get the start and end times for this process
1436 maxC = 0
1437 tlast = 0
1438 start = -1
1439 end = -1
1440 for t in sorted(times):
1441 if tlast == 0:
1442 tlast = t
1443 continue
1444 if name in self.pstl[t]:
1445 if start == -1 or tlast < start:
1446 start = tlast
1447 if end == -1 or t > end:
1448 end = t
1449 tlast = t
1450 if start == -1 or end == -1:
1451 return 0
1452 # add a new action for this process and get the object
1453 out = self.newActionGlobal(name, start, end, -3)
1454 if not out:
1455 return 0
1456 phase, devname = out
1457 dev = self.dmesg[phase]['list'][devname]
1458 # get the cpu exec data
1459 tlast = 0
1460 clast = 0
1461 cpuexec = dict()
1462 for t in sorted(times):
1463 if tlast == 0 or t <= start or t > end:
1464 tlast = t
1465 continue
1466 list = self.pstl[t]
1467 c = 0
1468 if name in list:
1469 c = list[name]
1470 if c > maxC:
1471 maxC = c
1472 if c != clast:
1473 key = (tlast, t)
1474 cpuexec[key] = c
1475 tlast = t
1476 clast = c
1477 dev['cpuexec'] = cpuexec
1478 return maxC
1479 def createProcessUsageEvents(self):
1480 # get an array of process names
1481 proclist = []
1482 for t in self.pstl:
1483 pslist = self.pstl[t]
1484 for ps in pslist:
1485 if ps not in proclist:
1486 proclist.append(ps)
1487 # get a list of data points for suspend and resume
1488 tsus = []
1489 tres = []
1490 for t in sorted(self.pstl):
1491 if t < self.tSuspended:
1492 tsus.append(t)
1493 else:
1494 tres.append(t)
1495 # process the events for suspend and resume
1496 if len(proclist) > 0:
1497 sysvals.vprint('Process Execution:')
1498 for ps in proclist:
1499 c = self.addProcessUsageEvent(ps, tsus)
1500 if c > 0:
1501 sysvals.vprint('%25s (sus): %d' % (ps, c))
1502 c = self.addProcessUsageEvent(ps, tres)
1503 if c > 0:
1504 sysvals.vprint('%25s (res): %d' % (ps, c))
1505 def debugPrint(self):
1506 for p in self.phases:
1507 list = self.dmesg[p]['list']
1508 for devname in list:
1509 dev = list[devname]
1510 if 'ftrace' in dev:
1511 dev['ftrace'].debugPrint(' [%s]' % devname)
1512
1513# Class: DevFunction
1514# Description:
1515# A container for kprobe function data we want in the dev timeline
1516class DevFunction:
1517 row = 0
1518 count = 1
1519 def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
1520 self.name = name
1521 self.args = args
1522 self.caller = caller
1523 self.ret = ret
1524 self.time = start
1525 self.length = end - start
1526 self.end = end
1527 self.ubiquitous = u
1528 self.proc = proc
1529 self.pid = pid
1530 self.color = color
1531 def title(self):
1532 cnt = ''
1533 if self.count > 1:
1534 cnt = '(x%d)' % self.count
1535 l = '%0.3fms' % (self.length * 1000)
1536 if self.ubiquitous:
1537 title = '%s(%s)%s <- %s, %s(%s)' % \
1538 (self.name, self.args, cnt, self.caller, self.ret, l)
1539 else:
1540 title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
1541 return title.replace('"', '')
1542 def text(self):
1543 if self.count > 1:
1544 text = '%s(x%d)' % (self.name, self.count)
1545 else:
1546 text = self.name
1547 return text
1548 def repeat(self, tgt):
1549 # is the tgt call just a repeat of this call (e.g. are we in a loop)
1550 dt = self.time - tgt.end
1551 # only combine calls if -all- attributes are identical
1552 if tgt.caller == self.caller and \
1553 tgt.name == self.name and tgt.args == self.args and \
1554 tgt.proc == self.proc and tgt.pid == self.pid and \
1555 tgt.ret == self.ret and dt >= 0 and \
1556 dt <= sysvals.callloopmaxgap and \
1557 self.length < sysvals.callloopmaxlen:
1558 return True
1559 return False
1560
1561# Class: FTraceLine
1562# Description:
1563# A container for a single line of ftrace data. There are six basic types:
1564# callgraph line:
1565# call: " dpm_run_callback() {"
1566# return: " }"
1567# leaf: " dpm_run_callback();"
1568# trace event:
1569# tracing_mark_write: SUSPEND START or RESUME COMPLETE
1570# suspend_resume: phase or custom exec block data
1571# device_pm_callback: device callback info
1572class FTraceLine:
1573 time = 0.0
1574 length = 0.0
1575 fcall = False
1576 freturn = False
1577 fevent = False
1578 fkprobe = False
1579 depth = 0
1580 name = ''
1581 type = ''
1582 def __init__(self, t, m='', d=''):
1583 self.time = float(t)
1584 if not m and not d:
1585 return
1586 # is this a trace event
1587 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
1588 if(d == 'traceevent'):
1589 # nop format trace event
1590 msg = m
1591 else:
1592 # function_graph format trace event
1593 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
1594 msg = em.group('msg')
1595
1596 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
1597 if(emm):
1598 self.name = emm.group('msg')
1599 self.type = emm.group('call')
1600 else:
1601 self.name = msg
1602 km = re.match('^(?P<n>.*)_cal$', self.type)
1603 if km:
1604 self.fcall = True
1605 self.fkprobe = True
1606 self.type = km.group('n')
1607 return
1608 km = re.match('^(?P<n>.*)_ret$', self.type)
1609 if km:
1610 self.freturn = True
1611 self.fkprobe = True
1612 self.type = km.group('n')
1613 return
1614 self.fevent = True
1615 return
1616 # convert the duration to seconds
1617 if(d):
1618 self.length = float(d)/1000000
1619 # the indentation determines the depth
1620 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
1621 if(not match):
1622 return
1623 self.depth = self.getDepth(match.group('d'))
1624 m = match.group('o')
1625 # function return
1626 if(m[0] == '}'):
1627 self.freturn = True
1628 if(len(m) > 1):
1629 # includes comment with function name
1630 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
1631 if(match):
1632 self.name = match.group('n').strip()
1633 # function call
1634 else:
1635 self.fcall = True
1636 # function call with children
1637 if(m[-1] == '{'):
1638 match = re.match('^(?P<n>.*) *\(.*', m)
1639 if(match):
1640 self.name = match.group('n').strip()
1641 # function call with no children (leaf)
1642 elif(m[-1] == ';'):
1643 self.freturn = True
1644 match = re.match('^(?P<n>.*) *\(.*', m)
1645 if(match):
1646 self.name = match.group('n').strip()
1647 # something else (possibly a trace marker)
1648 else:
1649 self.name = m
1650 def isCall(self):
1651 return self.fcall and not self.freturn
1652 def isReturn(self):
1653 return self.freturn and not self.fcall
1654 def isLeaf(self):
1655 return self.fcall and self.freturn
1656 def getDepth(self, str):
1657 return len(str)/2
1658 def debugPrint(self, info=''):
1659 if self.isLeaf():
1660 print(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
1661 self.depth, self.name, self.length*1000000, info))
1662 elif self.freturn:
1663 print(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
1664 self.depth, self.name, self.length*1000000, info))
1665 else:
1666 print(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
1667 self.depth, self.name, self.length*1000000, info))
1668 def startMarker(self):
1669 # Is this the starting line of a suspend?
1670 if not self.fevent:
1671 return False
1672 if sysvals.usetracemarkers:
1673 if(self.name == 'SUSPEND START'):
1674 return True
1675 return False
1676 else:
1677 if(self.type == 'suspend_resume' and
1678 re.match('suspend_enter\[.*\] begin', self.name)):
1679 return True
1680 return False
1681 def endMarker(self):
1682 # Is this the ending line of a resume?
1683 if not self.fevent:
1684 return False
1685 if sysvals.usetracemarkers:
1686 if(self.name == 'RESUME COMPLETE'):
1687 return True
1688 return False
1689 else:
1690 if(self.type == 'suspend_resume' and
1691 re.match('thaw_processes\[.*\] end', self.name)):
1692 return True
1693 return False
1694
1695# Class: FTraceCallGraph
1696# Description:
1697# A container for the ftrace callgraph of a single recursive function.
1698# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
1699# Each instance is tied to a single device in a single phase, and is
1700# comprised of an ordered list of FTraceLine objects
1701class FTraceCallGraph:
1702 id = ''
1703 start = -1.0
1704 end = -1.0
1705 list = []
1706 invalid = False
1707 depth = 0
1708 pid = 0
1709 name = ''
1710 partial = False
1711 vfname = 'missing_function_name'
1712 ignore = False
1713 sv = 0
1714 def __init__(self, pid, sv):
1715 self.start = -1.0
1716 self.end = -1.0
1717 self.list = []
1718 self.depth = 0
1719 self.pid = pid
1720 self.sv = sv
1721 def addLine(self, line):
1722 # if this is already invalid, just leave
1723 if(self.invalid):
1724 if(line.depth == 0 and line.freturn):
1725 return 1
1726 return 0
1727 # invalidate on bad depth
1728 if(self.depth < 0):
1729 self.invalidate(line)
1730 return 0
1731 # ignore data til we return to the current depth
1732 if self.ignore:
1733 if line.depth > self.depth:
1734 return 0
1735 else:
1736 self.list[-1].freturn = True
1737 self.list[-1].length = line.time - self.list[-1].time
1738 self.ignore = False
1739 # if this is a return at self.depth, no more work is needed
1740 if line.depth == self.depth and line.isReturn():
1741 if line.depth == 0:
1742 self.end = line.time
1743 return 1
1744 return 0
1745 # compare current depth with this lines pre-call depth
1746 prelinedep = line.depth
1747 if line.isReturn():
1748 prelinedep += 1
1749 last = 0
1750 lasttime = line.time
1751 if len(self.list) > 0:
1752 last = self.list[-1]
1753 lasttime = last.time
1754 if last.isLeaf():
1755 lasttime += last.length
1756 # handle low misalignments by inserting returns
1757 mismatch = prelinedep - self.depth
1758 warning = self.sv.verbose and abs(mismatch) > 1
1759 info = []
1760 if mismatch < 0:
1761 idx = 0
1762 # add return calls to get the depth down
1763 while prelinedep < self.depth:
1764 self.depth -= 1
1765 if idx == 0 and last and last.isCall():
1766 # special case, turn last call into a leaf
1767 last.depth = self.depth
1768 last.freturn = True
1769 last.length = line.time - last.time
1770 if warning:
1771 info.append(('[make leaf]', last))
1772 else:
1773 vline = FTraceLine(lasttime)
1774 vline.depth = self.depth
1775 vline.name = self.vfname
1776 vline.freturn = True
1777 self.list.append(vline)
1778 if warning:
1779 if idx == 0:
1780 info.append(('', last))
1781 info.append(('[add return]', vline))
1782 idx += 1
1783 if warning:
1784 info.append(('', line))
1785 # handle high misalignments by inserting calls
1786 elif mismatch > 0:
1787 idx = 0
1788 if warning:
1789 info.append(('', last))
1790 # add calls to get the depth up
1791 while prelinedep > self.depth:
1792 if idx == 0 and line.isReturn():
1793 # special case, turn this return into a leaf
1794 line.fcall = True
1795 prelinedep -= 1
1796 if warning:
1797 info.append(('[make leaf]', line))
1798 else:
1799 vline = FTraceLine(lasttime)
1800 vline.depth = self.depth
1801 vline.name = self.vfname
1802 vline.fcall = True
1803 self.list.append(vline)
1804 self.depth += 1
1805 if not last:
1806 self.start = vline.time
1807 if warning:
1808 info.append(('[add call]', vline))
1809 idx += 1
1810 if warning and ('[make leaf]', line) not in info:
1811 info.append(('', line))
1812 if warning:
1813 print 'WARNING: ftrace data missing, corrections made:'
1814 for i in info:
1815 t, obj = i
1816 if obj:
1817 obj.debugPrint(t)
1818 # process the call and set the new depth
1819 skipadd = False
1820 md = self.sv.max_graph_depth
1821 if line.isCall():
1822 # ignore blacklisted/overdepth funcs
1823 if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
1824 self.ignore = True
1825 else:
1826 self.depth += 1
1827 elif line.isReturn():
1828 self.depth -= 1
1829 # remove blacklisted/overdepth/empty funcs that slipped through
1830 if (last and last.isCall() and last.depth == line.depth) or \
1831 (md and last and last.depth >= md) or \
1832 (line.name in self.sv.cgblacklist):
1833 while len(self.list) > 0 and self.list[-1].depth > line.depth:
1834 self.list.pop(-1)
1835 if len(self.list) == 0:
1836 self.invalid = True
1837 return 1
1838 self.list[-1].freturn = True
1839 self.list[-1].length = line.time - self.list[-1].time
1840 self.list[-1].name = line.name
1841 skipadd = True
1842 if len(self.list) < 1:
1843 self.start = line.time
1844 # check for a mismatch that returned all the way to callgraph end
1845 res = 1
1846 if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
1847 line = self.list[-1]
1848 skipadd = True
1849 res = -1
1850 if not skipadd:
1851 self.list.append(line)
1852 if(line.depth == 0 and line.freturn):
1853 if(self.start < 0):
1854 self.start = line.time
1855 self.end = line.time
1856 if line.fcall:
1857 self.end += line.length
1858 if self.list[0].name == self.vfname:
1859 self.invalid = True
1860 if res == -1:
1861 self.partial = True
1862 return res
1863 return 0
1864 def invalidate(self, line):
1865 if(len(self.list) > 0):
1866 first = self.list[0]
1867 self.list = []
1868 self.list.append(first)
1869 self.invalid = True
1870 id = 'task %s' % (self.pid)
1871 window = '(%f - %f)' % (self.start, line.time)
1872 if(self.depth < 0):
1873 print('Data misalignment for '+id+\
1874 ' (buffer overflow), ignoring this callback')
1875 else:
1876 print('Too much data for '+id+\
1877 ' '+window+', ignoring this callback')
1878 def slice(self, dev):
1879 minicg = FTraceCallGraph(dev['pid'], self.sv)
1880 minicg.name = self.name
1881 mydepth = -1
1882 good = False
1883 for l in self.list:
1884 if(l.time < dev['start'] or l.time > dev['end']):
1885 continue
1886 if mydepth < 0:
1887 if l.name == 'mutex_lock' and l.freturn:
1888 mydepth = l.depth
1889 continue
1890 elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
1891 good = True
1892 break
1893 l.depth -= mydepth
1894 minicg.addLine(l)
1895 if not good or len(minicg.list) < 1:
1896 return 0
1897 return minicg
1898 def repair(self, enddepth):
1899 # bring the depth back to 0 with additional returns
1900 fixed = False
1901 last = self.list[-1]
1902 for i in reversed(range(enddepth)):
1903 t = FTraceLine(last.time)
1904 t.depth = i
1905 t.freturn = True
1906 fixed = self.addLine(t)
1907 if fixed != 0:
1908 self.end = last.time
1909 return True
1910 return False
1911 def postProcess(self):
1912 if len(self.list) > 0:
1913 self.name = self.list[0].name
1914 stack = dict()
1915 cnt = 0
1916 last = 0
1917 for l in self.list:
1918 # ftrace bug: reported duration is not reliable
1919 # check each leaf and clip it at max possible length
1920 if last and last.isLeaf():
1921 if last.length > l.time - last.time:
1922 last.length = l.time - last.time
1923 if l.isCall():
1924 stack[l.depth] = l
1925 cnt += 1
1926 elif l.isReturn():
1927 if(l.depth not in stack):
1928 if self.sv.verbose:
1929 print 'Post Process Error: Depth missing'
1930 l.debugPrint()
1931 return False
1932 # calculate call length from call/return lines
1933 cl = stack[l.depth]
1934 cl.length = l.time - cl.time
1935 if cl.name == self.vfname:
1936 cl.name = l.name
1937 stack.pop(l.depth)
1938 l.length = 0
1939 cnt -= 1
1940 last = l
1941 if(cnt == 0):
1942 # trace caught the whole call tree
1943 return True
1944 elif(cnt < 0):
1945 if self.sv.verbose:
1946 print 'Post Process Error: Depth is less than 0'
1947 return False
1948 # trace ended before call tree finished
1949 return self.repair(cnt)
1950 def deviceMatch(self, pid, data):
1951 found = ''
1952 # add the callgraph data to the device hierarchy
1953 borderphase = {
1954 'dpm_prepare': 'suspend_prepare',
1955 'dpm_complete': 'resume_complete'
1956 }
1957 if(self.name in borderphase):
1958 p = borderphase[self.name]
1959 list = data.dmesg[p]['list']
1960 for devname in list:
1961 dev = list[devname]
1962 if(pid == dev['pid'] and
1963 self.start <= dev['start'] and
1964 self.end >= dev['end']):
1965 cg = self.slice(dev)
1966 if cg:
1967 dev['ftrace'] = cg
1968 found = devname
1969 return found
1970 for p in data.phases:
1971 if(data.dmesg[p]['start'] <= self.start and
1972 self.start <= data.dmesg[p]['end']):
1973 list = data.dmesg[p]['list']
1974 for devname in list:
1975 dev = list[devname]
1976 if(pid == dev['pid'] and
1977 self.start <= dev['start'] and
1978 self.end >= dev['end']):
1979 dev['ftrace'] = self
1980 found = devname
1981 break
1982 break
1983 return found
1984 def newActionFromFunction(self, data):
1985 name = self.name
1986 if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
1987 return
1988 fs = self.start
1989 fe = self.end
1990 if fs < data.start or fe > data.end:
1991 return
1992 phase = ''
1993 for p in data.phases:
1994 if(data.dmesg[p]['start'] <= self.start and
1995 self.start < data.dmesg[p]['end']):
1996 phase = p
1997 break
1998 if not phase:
1999 return
2000 out = data.newActionGlobal(name, fs, fe, -2)
2001 if out:
2002 phase, myname = out
2003 data.dmesg[phase]['list'][myname]['ftrace'] = self
2004 def debugPrint(self, info=''):
2005 print('%s pid=%d [%f - %f] %.3f us') % \
2006 (self.name, self.pid, self.start, self.end,
2007 (self.end - self.start)*1000000)
2008 for l in self.list:
2009 if l.isLeaf():
2010 print('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2011 l.depth, l.name, l.length*1000000, info))
2012 elif l.freturn:
2013 print('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2014 l.depth, l.name, l.length*1000000, info))
2015 else:
2016 print('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2017 l.depth, l.name, l.length*1000000, info))
2018 print(' ')
2019
2020class DevItem:
2021 def __init__(self, test, phase, dev):
2022 self.test = test
2023 self.phase = phase
2024 self.dev = dev
2025 def isa(self, cls):
2026 if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2027 return True
2028 return False
2029
2030# Class: Timeline
2031# Description:
2032# A container for a device timeline which calculates
2033# all the html properties to display it correctly
2034class Timeline:
2035 html = ''
2036 height = 0 # total timeline height
2037 scaleH = 20 # timescale (top) row height
2038 rowH = 30 # device row height
2039 bodyH = 0 # body height
2040 rows = 0 # total timeline rows
2041 rowlines = dict()
2042 rowheight = dict()
2043 html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2044 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'
2045 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2046 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2047 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}"> {2}</div>\n'
2048 def __init__(self, rowheight, scaleheight):
2049 self.rowH = rowheight
2050 self.scaleH = scaleheight
2051 self.html = ''
2052 def createHeader(self, sv, stamp):
2053 if(not stamp['time']):
2054 return
2055 self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \
2056 % (sv.title, sv.version)
2057 if sv.logmsg and sv.testlog:
2058 self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2059 if sv.dmesglog:
2060 self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2061 if sv.ftracelog:
2062 self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2063 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2064 self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2065 stamp['mode'], stamp['time'])
2066 if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2067 stamp['man'] and stamp['plat'] and stamp['cpu']:
2068 headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2069 self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2070
2071 # Function: getDeviceRows
2072 # Description:
2073 # determine how may rows the device funcs will take
2074 # Arguments:
2075 # rawlist: the list of devices/actions for a single phase
2076 # Output:
2077 # The total number of rows needed to display this phase of the timeline
2078 def getDeviceRows(self, rawlist):
2079 # clear all rows and set them to undefined
2080 sortdict = dict()
2081 for item in rawlist:
2082 item.row = -1
2083 sortdict[item] = item.length
2084 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2085 remaining = len(sortlist)
2086 rowdata = dict()
2087 row = 1
2088 # try to pack each row with as many ranges as possible
2089 while(remaining > 0):
2090 if(row not in rowdata):
2091 rowdata[row] = []
2092 for i in sortlist:
2093 if(i.row >= 0):
2094 continue
2095 s = i.time
2096 e = i.time + i.length
2097 valid = True
2098 for ritem in rowdata[row]:
2099 rs = ritem.time
2100 re = ritem.time + ritem.length
2101 if(not (((s <= rs) and (e <= rs)) or
2102 ((s >= re) and (e >= re)))):
2103 valid = False
2104 break
2105 if(valid):
2106 rowdata[row].append(i)
2107 i.row = row
2108 remaining -= 1
2109 row += 1
2110 return row
2111 # Function: getPhaseRows
2112 # Description:
2113 # Organize the timeline entries into the smallest
2114 # number of rows possible, with no entry overlapping
2115 # Arguments:
2116 # devlist: the list of devices/actions in a group of contiguous phases
2117 # Output:
2118 # The total number of rows needed to display this phase of the timeline
2119 def getPhaseRows(self, devlist, row=0, sortby='length'):
2120 # clear all rows and set them to undefined
2121 remaining = len(devlist)
2122 rowdata = dict()
2123 sortdict = dict()
2124 myphases = []
2125 # initialize all device rows to -1 and calculate devrows
2126 for item in devlist:
2127 dev = item.dev
2128 tp = (item.test, item.phase)
2129 if tp not in myphases:
2130 myphases.append(tp)
2131 dev['row'] = -1
2132 if sortby == 'start':
2133 # sort by start 1st, then length 2nd
2134 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2135 else:
2136 # sort by length 1st, then name 2nd
2137 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2138 if 'src' in dev:
2139 dev['devrows'] = self.getDeviceRows(dev['src'])
2140 # sort the devlist by length so that large items graph on top
2141 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2142 orderedlist = []
2143 for item in sortlist:
2144 if item.dev['pid'] == -2:
2145 orderedlist.append(item)
2146 for item in sortlist:
2147 if item not in orderedlist:
2148 orderedlist.append(item)
2149 # try to pack each row with as many devices as possible
2150 while(remaining > 0):
2151 rowheight = 1
2152 if(row not in rowdata):
2153 rowdata[row] = []
2154 for item in orderedlist:
2155 dev = item.dev
2156 if(dev['row'] < 0):
2157 s = dev['start']
2158 e = dev['end']
2159 valid = True
2160 for ritem in rowdata[row]:
2161 rs = ritem.dev['start']
2162 re = ritem.dev['end']
2163 if(not (((s <= rs) and (e <= rs)) or
2164 ((s >= re) and (e >= re)))):
2165 valid = False
2166 break
2167 if(valid):
2168 rowdata[row].append(item)
2169 dev['row'] = row
2170 remaining -= 1
2171 if 'devrows' in dev and dev['devrows'] > rowheight:
2172 rowheight = dev['devrows']
2173 for t, p in myphases:
2174 if t not in self.rowlines or t not in self.rowheight:
2175 self.rowlines[t] = dict()
2176 self.rowheight[t] = dict()
2177 if p not in self.rowlines[t] or p not in self.rowheight[t]:
2178 self.rowlines[t][p] = dict()
2179 self.rowheight[t][p] = dict()
2180 rh = self.rowH
2181 # section headers should use a different row height
2182 if len(rowdata[row]) == 1 and \
2183 'htmlclass' in rowdata[row][0].dev and \
2184 'sec' in rowdata[row][0].dev['htmlclass']:
2185 rh = 15
2186 self.rowlines[t][p][row] = rowheight
2187 self.rowheight[t][p][row] = rowheight * rh
2188 row += 1
2189 if(row > self.rows):
2190 self.rows = int(row)
2191 return row
2192 def phaseRowHeight(self, test, phase, row):
2193 return self.rowheight[test][phase][row]
2194 def phaseRowTop(self, test, phase, row):
2195 top = 0
2196 for i in sorted(self.rowheight[test][phase]):
2197 if i >= row:
2198 break
2199 top += self.rowheight[test][phase][i]
2200 return top
2201 def calcTotalRows(self):
2202 # Calculate the heights and offsets for the header and rows
2203 maxrows = 0
2204 standardphases = []
2205 for t in self.rowlines:
2206 for p in self.rowlines[t]:
2207 total = 0
2208 for i in sorted(self.rowlines[t][p]):
2209 total += self.rowlines[t][p][i]
2210 if total > maxrows:
2211 maxrows = total
2212 if total == len(self.rowlines[t][p]):
2213 standardphases.append((t, p))
2214 self.height = self.scaleH + (maxrows*self.rowH)
2215 self.bodyH = self.height - self.scaleH
2216 # if there is 1 line per row, draw them the standard way
2217 for t, p in standardphases:
2218 for i in sorted(self.rowheight[t][p]):
2219 self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p])
2220 def createZoomBox(self, mode='command', testcount=1):
2221 # Create bounding box, add buttons
2222 html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2223 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2224 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2225 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2226 if mode != 'command':
2227 if testcount > 1:
2228 self.html += html_devlist2
2229 self.html += html_devlist1.format('1')
2230 else:
2231 self.html += html_devlist1.format('')
2232 self.html += html_zoombox
2233 self.html += html_timeline.format('dmesg', self.height)
2234 # Function: createTimeScale
2235 # Description:
2236 # Create the timescale for a timeline block
2237 # Arguments:
2238 # m0: start time (mode begin)
2239 # mMax: end time (mode end)
2240 # tTotal: total timeline time
2241 # mode: suspend or resume
2242 # Output:
2243 # The html code needed to display the time scale
2244 def createTimeScale(self, m0, mMax, tTotal, mode):
2245 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2246 rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2247 output = '<div class="timescale">\n'
2248 # set scale for timeline
2249 mTotal = mMax - m0
2250 tS = 0.1
2251 if(tTotal <= 0):
2252 return output+'</div>\n'
2253 if(tTotal > 4):
2254 tS = 1
2255 divTotal = int(mTotal/tS) + 1
2256 divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2257 for i in range(divTotal):
2258 htmlline = ''
2259 if(mode == 'suspend'):
2260 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2261 val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2262 if(i == divTotal - 1):
2263 val = mode
2264 htmlline = timescale.format(pos, val)
2265 else:
2266 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2267 val = '%0.fms' % (float(i)*tS*1000)
2268 htmlline = timescale.format(pos, val)
2269 if(i == 0):
2270 htmlline = rline.format(mode)
2271 output += htmlline
2272 self.html += output+'</div>\n'
2273
2274# Class: TestProps
2275# Description:
2276# A list of values describing the properties of these test runs
2277class TestProps:
2278 stamp = ''
2279 sysinfo = ''
2280 cmdline = ''
2281 kparams = ''
2282 S0i3 = False
2283 fwdata = []
2284 stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2285 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2286 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2287 sysinfofmt = '^# sysinfo .*'
2288 cmdlinefmt = '^# command \| (?P<cmd>.*)'
2289 kparamsfmt = '^# kparams \| (?P<kp>.*)'
2290 ftrace_line_fmt_fg = \
2291 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2292 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2293 '[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
2294 ftrace_line_fmt_nop = \
2295 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2296 '(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
2297 '(?P<msg>.*)'
2298 ftrace_line_fmt = ftrace_line_fmt_nop
2299 cgformat = False
2300 data = 0
2301 ktemp = dict()
2302 def __init__(self):
2303 self.ktemp = dict()
2304 def setTracerType(self, tracer):
2305 if(tracer == 'function_graph'):
2306 self.cgformat = True
2307 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2308 elif(tracer == 'nop'):
2309 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2310 else:
2311 doError('Invalid tracer format: [%s]' % tracer)
2312 def parseStamp(self, data, sv):
2313 m = re.match(self.stampfmt, self.stamp)
2314 data.stamp = {'time': '', 'host': '', 'mode': ''}
2315 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2316 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2317 int(m.group('S')))
2318 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2319 data.stamp['host'] = m.group('host')
2320 data.stamp['mode'] = m.group('mode')
2321 data.stamp['kernel'] = m.group('kernel')
2322 if re.match(self.sysinfofmt, self.sysinfo):
2323 for f in self.sysinfo.split('|'):
2324 if '#' in f:
2325 continue
2326 tmp = f.strip().split(':', 1)
2327 key = tmp[0]
2328 val = tmp[1]
2329 data.stamp[key] = val
2330 sv.hostname = data.stamp['host']
2331 sv.suspendmode = data.stamp['mode']
2332 if sv.suspendmode == 'command' and sv.ftracefile != '':
2333 modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2334 out = Popen(['grep', 'machine_suspend', sv.ftracefile],
2335 stderr=PIPE, stdout=PIPE).stdout.read()
2336 m = re.match('.* machine_suspend\[(?P<mode>.*)\]', out)
2337 if m and m.group('mode') in ['1', '2', '3', '4']:
2338 sv.suspendmode = modes[int(m.group('mode'))]
2339 data.stamp['mode'] = sv.suspendmode
2340 m = re.match(self.cmdlinefmt, self.cmdline)
2341 if m:
2342 sv.cmdline = m.group('cmd')
2343 if self.kparams:
2344 m = re.match(self.kparamsfmt, self.kparams)
2345 if m:
2346 sv.kparams = m.group('kp')
2347 if not sv.stamp:
2348 sv.stamp = data.stamp
2349
2350# Class: TestRun
2351# Description:
2352# A container for a suspend/resume test run. This is necessary as
2353# there could be more than one, and they need to be separate.
2354class TestRun:
2355 ftemp = dict()
2356 ttemp = dict()
2357 data = 0
2358 def __init__(self, dataobj):
2359 self.data = dataobj
2360 self.ftemp = dict()
2361 self.ttemp = dict()
2362
2363class ProcessMonitor:
2364 proclist = dict()
2365 running = False
2366 def procstat(self):
2367 c = ['cat /proc/[1-9]*/stat 2>/dev/null']
2368 process = Popen(c, shell=True, stdout=PIPE)
2369 running = dict()
2370 for line in process.stdout:
2371 data = line.split()
2372 pid = data[0]
2373 name = re.sub('[()]', '', data[1])
2374 user = int(data[13])
2375 kern = int(data[14])
2376 kjiff = ujiff = 0
2377 if pid not in self.proclist:
2378 self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
2379 else:
2380 val = self.proclist[pid]
2381 ujiff = user - val['user']
2382 kjiff = kern - val['kern']
2383 val['user'] = user
2384 val['kern'] = kern
2385 if ujiff > 0 or kjiff > 0:
2386 running[pid] = ujiff + kjiff
2387 process.wait()
2388 out = ''
2389 for pid in running:
2390 jiffies = running[pid]
2391 val = self.proclist[pid]
2392 if out:
2393 out += ','
2394 out += '%s-%s %d' % (val['name'], pid, jiffies)
2395 return 'ps - '+out
2396 def processMonitor(self, tid):
2397 while self.running:
2398 out = self.procstat()
2399 if out:
2400 sysvals.fsetVal(out, 'trace_marker')
2401 def start(self):
2402 self.thread = Thread(target=self.processMonitor, args=(0,))
2403 self.running = True
2404 self.thread.start()
2405 def stop(self):
2406 self.running = False
2407
2408# ----------------- FUNCTIONS --------------------
2409
2410# Function: doesTraceLogHaveTraceEvents
2411# Description:
2412# Quickly determine if the ftrace log has all of the trace events,
2413# markers, and/or kprobes required for primary parsing.
2414def doesTraceLogHaveTraceEvents():
2415 kpcheck = ['_cal: (', '_cpu_down()']
2416 techeck = sysvals.traceevents[:]
2417 tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
2418 sysvals.usekprobes = False
2419 fp = sysvals.openlog(sysvals.ftracefile, 'r')
2420 for line in fp:
2421 # check for kprobes
2422 if not sysvals.usekprobes:
2423 for i in kpcheck:
2424 if i in line:
2425 sysvals.usekprobes = True
2426 # check for all necessary trace events
2427 check = techeck[:]
2428 for i in techeck:
2429 if i in line:
2430 check.remove(i)
2431 techeck = check
2432 # check for all necessary trace markers
2433 check = tmcheck[:]
2434 for i in tmcheck:
2435 if i in line:
2436 check.remove(i)
2437 tmcheck = check
2438 fp.close()
2439 if len(techeck) == 0:
2440 sysvals.usetraceevents = True
2441 else:
2442 sysvals.usetraceevents = False
2443 if len(tmcheck) == 0:
2444 sysvals.usetracemarkers = True
2445 else:
2446 sysvals.usetracemarkers = False
2447
2448# Function: appendIncompleteTraceLog
2449# Description:
2450# [deprecated for kernel 3.15 or newer]
2451# Legacy support of ftrace outputs that lack the device_pm_callback
2452# and/or suspend_resume trace events. The primary data should be
2453# taken from dmesg, and this ftrace is used only for callgraph data
2454# or custom actions in the timeline. The data is appended to the Data
2455# objects provided.
2456# Arguments:
2457# testruns: the array of Data objects obtained from parseKernelLog
2458def appendIncompleteTraceLog(testruns):
2459 # create TestRun vessels for ftrace parsing
2460 testcnt = len(testruns)
2461 testidx = 0
2462 testrun = []
2463 for data in testruns:
2464 testrun.append(TestRun(data))
2465
2466 # extract the callgraph and traceevent data
2467 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2468 os.path.basename(sysvals.ftracefile))
2469 tp = TestProps()
2470 tf = sysvals.openlog(sysvals.ftracefile, 'r')
2471 data = 0
2472 for line in tf:
2473 # remove any latent carriage returns
2474 line = line.replace('\r\n', '')
2475 # grab the stamp and sysinfo
2476 if re.match(tp.stampfmt, line):
2477 tp.stamp = line
2478 continue
2479 elif re.match(tp.sysinfofmt, line):
2480 tp.sysinfo = line
2481 continue
2482 elif re.match(tp.cmdlinefmt, line):
2483 tp.cmdline = line
2484 continue
2485 # determine the trace data type (required for further parsing)
2486 m = re.match(sysvals.tracertypefmt, line)
2487 if(m):
2488 tp.setTracerType(m.group('t'))
2489 continue
2490 # device properties line
2491 if(re.match(sysvals.devpropfmt, line)):
2492 devProps(line)
2493 continue
2494 # parse only valid lines, if this is not one move on
2495 m = re.match(tp.ftrace_line_fmt, line)
2496 if(not m):
2497 continue
2498 # gather the basic message data from the line
2499 m_time = m.group('time')
2500 m_pid = m.group('pid')
2501 m_msg = m.group('msg')
2502 if(tp.cgformat):
2503 m_param3 = m.group('dur')
2504 else:
2505 m_param3 = 'traceevent'
2506 if(m_time and m_pid and m_msg):
2507 t = FTraceLine(m_time, m_msg, m_param3)
2508 pid = int(m_pid)
2509 else:
2510 continue
2511 # the line should be a call, return, or event
2512 if(not t.fcall and not t.freturn and not t.fevent):
2513 continue
2514 # look for the suspend start marker
2515 if(t.startMarker()):
2516 data = testrun[testidx].data
2517 tp.parseStamp(data, sysvals)
2518 data.setStart(t.time)
2519 continue
2520 if(not data):
2521 continue
2522 # find the end of resume
2523 if(t.endMarker()):
2524 data.setEnd(t.time)
2525 testidx += 1
2526 if(testidx >= testcnt):
2527 break
2528 continue
2529 # trace event processing
2530 if(t.fevent):
2531 # general trace events have two types, begin and end
2532 if(re.match('(?P<name>.*) begin$', t.name)):
2533 isbegin = True
2534 elif(re.match('(?P<name>.*) end$', t.name)):
2535 isbegin = False
2536 else:
2537 continue
2538 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2539 if(m):
2540 val = m.group('val')
2541 if val == '0':
2542 name = m.group('name')
2543 else:
2544 name = m.group('name')+'['+val+']'
2545 else:
2546 m = re.match('(?P<name>.*) .*', t.name)
2547 name = m.group('name')
2548 # special processing for trace events
2549 if re.match('dpm_prepare\[.*', name):
2550 continue
2551 elif re.match('machine_suspend.*', name):
2552 continue
2553 elif re.match('suspend_enter\[.*', name):
2554 if(not isbegin):
2555 data.dmesg['suspend_prepare']['end'] = t.time
2556 continue
2557 elif re.match('dpm_suspend\[.*', name):
2558 if(not isbegin):
2559 data.dmesg['suspend']['end'] = t.time
2560 continue
2561 elif re.match('dpm_suspend_late\[.*', name):
2562 if(isbegin):
2563 data.dmesg['suspend_late']['start'] = t.time
2564 else:
2565 data.dmesg['suspend_late']['end'] = t.time
2566 continue
2567 elif re.match('dpm_suspend_noirq\[.*', name):
2568 if(isbegin):
2569 data.dmesg['suspend_noirq']['start'] = t.time
2570 else:
2571 data.dmesg['suspend_noirq']['end'] = t.time
2572 continue
2573 elif re.match('dpm_resume_noirq\[.*', name):
2574 if(isbegin):
2575 data.dmesg['resume_machine']['end'] = t.time
2576 data.dmesg['resume_noirq']['start'] = t.time
2577 else:
2578 data.dmesg['resume_noirq']['end'] = t.time
2579 continue
2580 elif re.match('dpm_resume_early\[.*', name):
2581 if(isbegin):
2582 data.dmesg['resume_early']['start'] = t.time
2583 else:
2584 data.dmesg['resume_early']['end'] = t.time
2585 continue
2586 elif re.match('dpm_resume\[.*', name):
2587 if(isbegin):
2588 data.dmesg['resume']['start'] = t.time
2589 else:
2590 data.dmesg['resume']['end'] = t.time
2591 continue
2592 elif re.match('dpm_complete\[.*', name):
2593 if(isbegin):
2594 data.dmesg['resume_complete']['start'] = t.time
2595 else:
2596 data.dmesg['resume_complete']['end'] = t.time
2597 continue
2598 # skip trace events inside devices calls
2599 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2600 continue
2601 # global events (outside device calls) are simply graphed
2602 if(isbegin):
2603 # store each trace event in ttemp
2604 if(name not in testrun[testidx].ttemp):
2605 testrun[testidx].ttemp[name] = []
2606 testrun[testidx].ttemp[name].append(\
2607 {'begin': t.time, 'end': t.time})
2608 else:
2609 # finish off matching trace event in ttemp
2610 if(name in testrun[testidx].ttemp):
2611 testrun[testidx].ttemp[name][-1]['end'] = t.time
2612 # call/return processing
2613 elif sysvals.usecallgraph:
2614 # create a callgraph object for the data
2615 if(pid not in testrun[testidx].ftemp):
2616 testrun[testidx].ftemp[pid] = []
2617 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2618 # when the call is finished, see which device matches it
2619 cg = testrun[testidx].ftemp[pid][-1]
2620 res = cg.addLine(t)
2621 if(res != 0):
2622 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
2623 if(res == -1):
2624 testrun[testidx].ftemp[pid][-1].addLine(t)
2625 tf.close()
2626
2627 for test in testrun:
2628 # add the traceevent data to the device hierarchy
2629 if(sysvals.usetraceevents):
2630 for name in test.ttemp:
2631 for event in test.ttemp[name]:
2632 test.data.newActionGlobal(name, event['begin'], event['end'])
2633
2634 # add the callgraph data to the device hierarchy
2635 for pid in test.ftemp:
2636 for cg in test.ftemp[pid]:
2637 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
2638 continue
2639 if(not cg.postProcess()):
2640 id = 'task %s cpu %s' % (pid, m.group('cpu'))
2641 sysvals.vprint('Sanity check failed for '+\
2642 id+', ignoring this callback')
2643 continue
2644 callstart = cg.start
2645 callend = cg.end
2646 for p in test.data.phases:
2647 if(test.data.dmesg[p]['start'] <= callstart and
2648 callstart <= test.data.dmesg[p]['end']):
2649 list = test.data.dmesg[p]['list']
2650 for devname in list:
2651 dev = list[devname]
2652 if(pid == dev['pid'] and
2653 callstart <= dev['start'] and
2654 callend >= dev['end']):
2655 dev['ftrace'] = cg
2656 break
2657
2658# Function: parseTraceLog
2659# Description:
2660# Analyze an ftrace log output file generated from this app during
2661# the execution phase. Used when the ftrace log is the primary data source
2662# and includes the suspend_resume and device_pm_callback trace events
2663# The ftrace filename is taken from sysvals
2664# Output:
2665# An array of Data objects
2666def parseTraceLog(live=False):
2667 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
2668 os.path.basename(sysvals.ftracefile))
2669 if(os.path.exists(sysvals.ftracefile) == False):
2670 doError('%s does not exist' % sysvals.ftracefile)
2671 if not live:
2672 sysvals.setupAllKprobes()
2673 tracewatch = []
2674 if sysvals.usekprobes:
2675 tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
2676 'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON', 'CPU_OFF']
2677
2678 # extract the callgraph and traceevent data
2679 tp = TestProps()
2680 testruns = []
2681 testdata = []
2682 testrun = 0
2683 data = 0
2684 tf = sysvals.openlog(sysvals.ftracefile, 'r')
2685 phase = 'suspend_prepare'
2686 for line in tf:
2687 # remove any latent carriage returns
2688 line = line.replace('\r\n', '')
2689 # stamp and sysinfo lines
2690 if re.match(tp.stampfmt, line):
2691 tp.stamp = line
2692 continue
2693 elif re.match(tp.sysinfofmt, line):
2694 tp.sysinfo = line
2695 continue
2696 elif re.match(tp.cmdlinefmt, line):
2697 tp.cmdline = line
2698 continue
2699 # firmware line: pull out any firmware data
2700 m = re.match(sysvals.firmwarefmt, line)
2701 if(m):
2702 tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
2703 continue
2704 # tracer type line: determine the trace data type
2705 m = re.match(sysvals.tracertypefmt, line)
2706 if(m):
2707 tp.setTracerType(m.group('t'))
2708 continue
2709 # device properties line
2710 if(re.match(sysvals.devpropfmt, line)):
2711 devProps(line)
2712 continue
2713 # ignore all other commented lines
2714 if line[0] == '#':
2715 continue
2716 # ftrace line: parse only valid lines
2717 m = re.match(tp.ftrace_line_fmt, line)
2718 if(not m):
2719 continue
2720 # gather the basic message data from the line
2721 m_time = m.group('time')
2722 m_proc = m.group('proc')
2723 m_pid = m.group('pid')
2724 m_msg = m.group('msg')
2725 if(tp.cgformat):
2726 m_param3 = m.group('dur')
2727 else:
2728 m_param3 = 'traceevent'
2729 if(m_time and m_pid and m_msg):
2730 t = FTraceLine(m_time, m_msg, m_param3)
2731 pid = int(m_pid)
2732 else:
2733 continue
2734 # the line should be a call, return, or event
2735 if(not t.fcall and not t.freturn and not t.fevent):
2736 continue
2737 # find the start of suspend
2738 if(t.startMarker()):
2739 phase = 'suspend_prepare'
2740 data = Data(len(testdata))
2741 testdata.append(data)
2742 testrun = TestRun(data)
2743 testruns.append(testrun)
2744 tp.parseStamp(data, sysvals)
2745 data.setStart(t.time)
2746 data.tKernSus = t.time
2747 continue
2748 if(not data):
2749 continue
2750 # process cpu exec line
2751 if t.type == 'tracing_mark_write':
2752 m = re.match(sysvals.procexecfmt, t.name)
2753 if(m):
2754 proclist = dict()
2755 for ps in m.group('ps').split(','):
2756 val = ps.split()
2757 if not val:
2758 continue
2759 name = val[0].replace('--', '-')
2760 proclist[name] = int(val[1])
2761 data.pstl[t.time] = proclist
2762 continue
2763 # find the end of resume
2764 if(t.endMarker()):
2765 data.setEnd(t.time)
2766 if data.tKernRes == 0.0:
2767 data.tKernRes = t.time
2768 if data.dmesg['resume_complete']['end'] < 0:
2769 data.dmesg['resume_complete']['end'] = t.time
2770 if sysvals.suspendmode == 'mem' and len(tp.fwdata) > data.testnumber:
2771 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
2772 if(data.tSuspended != 0 and data.tResumed != 0 and \
2773 (data.fwSuspend > 0 or data.fwResume > 0)):
2774 data.fwValid = True
2775 if(not sysvals.usetracemarkers):
2776 # no trace markers? then quit and be sure to finish recording
2777 # the event we used to trigger resume end
2778 if(len(testrun.ttemp['thaw_processes']) > 0):
2779 # if an entry exists, assume this is its end
2780 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
2781 break
2782 continue
2783 # trace event processing
2784 if(t.fevent):
2785 if(phase == 'post_resume'):
2786 data.setEnd(t.time)
2787 if(t.type == 'suspend_resume'):
2788 # suspend_resume trace events have two types, begin and end
2789 if(re.match('(?P<name>.*) begin$', t.name)):
2790 isbegin = True
2791 elif(re.match('(?P<name>.*) end$', t.name)):
2792 isbegin = False
2793 else:
2794 continue
2795 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
2796 if(m):
2797 val = m.group('val')
2798 if val == '0':
2799 name = m.group('name')
2800 else:
2801 name = m.group('name')+'['+val+']'
2802 else:
2803 m = re.match('(?P<name>.*) .*', t.name)
2804 name = m.group('name')
2805 # ignore these events
2806 if(name.split('[')[0] in tracewatch):
2807 continue
2808 # -- phase changes --
2809 # start of kernel suspend
2810 if(re.match('suspend_enter\[.*', t.name)):
2811 if(isbegin):
2812 data.dmesg[phase]['start'] = t.time
2813 data.tKernSus = t.time
2814 continue
2815 # suspend_prepare start
2816 elif(re.match('dpm_prepare\[.*', t.name)):
2817 phase = 'suspend_prepare'
2818 if(not isbegin):
2819 data.dmesg[phase]['end'] = t.time
2820 if data.dmesg[phase]['start'] < 0:
2821 data.dmesg[phase]['start'] = data.start
2822 continue
2823 # suspend start
2824 elif(re.match('dpm_suspend\[.*', t.name)):
2825 phase = 'suspend'
2826 data.setPhase(phase, t.time, isbegin)
2827 continue
2828 # suspend_late start
2829 elif(re.match('dpm_suspend_late\[.*', t.name)):
2830 phase = 'suspend_late'
2831 data.setPhase(phase, t.time, isbegin)
2832 continue
2833 # suspend_noirq start
2834 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
2835 if data.phaseCollision('suspend_noirq', isbegin, line):
2836 continue
2837 phase = 'suspend_noirq'
2838 data.setPhase(phase, t.time, isbegin)
2839 if(not isbegin):
2840 phase = 'suspend_machine'
2841 data.dmesg[phase]['start'] = t.time
2842 continue
2843 # suspend_machine/resume_machine
2844 elif(re.match('machine_suspend\[.*', t.name)):
2845 if(isbegin):
2846 phase = 'suspend_machine'
2847 data.dmesg[phase]['end'] = t.time
2848 data.tSuspended = t.time
2849 else:
2850 if(sysvals.suspendmode in ['mem', 'disk'] and not tp.S0i3):
2851 data.dmesg['suspend_machine']['end'] = t.time
2852 data.tSuspended = t.time
2853 phase = 'resume_machine'
2854 data.dmesg[phase]['start'] = t.time
2855 data.tResumed = t.time
2856 data.tLow = data.tResumed - data.tSuspended
2857 continue
2858 # acpi_suspend
2859 elif(re.match('acpi_suspend\[.*', t.name)):
2860 # acpi_suspend[0] S0i3
2861 if(re.match('acpi_suspend\[0\] begin', t.name)):
2862 if(sysvals.suspendmode == 'mem'):
2863 tp.S0i3 = True
2864 data.dmesg['suspend_machine']['end'] = t.time
2865 data.tSuspended = t.time
2866 continue
2867 # resume_noirq start
2868 elif(re.match('dpm_resume_noirq\[.*', t.name)):
2869 if data.phaseCollision('resume_noirq', isbegin, line):
2870 continue
2871 phase = 'resume_noirq'
2872 data.setPhase(phase, t.time, isbegin)
2873 if(isbegin):
2874 data.dmesg['resume_machine']['end'] = t.time
2875 continue
2876 # resume_early start
2877 elif(re.match('dpm_resume_early\[.*', t.name)):
2878 phase = 'resume_early'
2879 data.setPhase(phase, t.time, isbegin)
2880 continue
2881 # resume start
2882 elif(re.match('dpm_resume\[.*', t.name)):
2883 phase = 'resume'
2884 data.setPhase(phase, t.time, isbegin)
2885 continue
2886 # resume complete start
2887 elif(re.match('dpm_complete\[.*', t.name)):
2888 phase = 'resume_complete'
2889 if(isbegin):
2890 data.dmesg[phase]['start'] = t.time
2891 continue
2892 # skip trace events inside devices calls
2893 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
2894 continue
2895 # global events (outside device calls) are graphed
2896 if(name not in testrun.ttemp):
2897 testrun.ttemp[name] = []
2898 if(isbegin):
2899 # create a new list entry
2900 testrun.ttemp[name].append(\
2901 {'begin': t.time, 'end': t.time, 'pid': pid})
2902 else:
2903 if(len(testrun.ttemp[name]) > 0):
2904 # if an entry exists, assume this is its end
2905 testrun.ttemp[name][-1]['end'] = t.time
2906 elif(phase == 'post_resume'):
2907 # post resume events can just have ends
2908 testrun.ttemp[name].append({
2909 'begin': data.dmesg[phase]['start'],
2910 'end': t.time})
2911 # device callback start
2912 elif(t.type == 'device_pm_callback_start'):
2913 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
2914 t.name);
2915 if(not m):
2916 continue
2917 drv = m.group('drv')
2918 n = m.group('d')
2919 p = m.group('p')
2920 if(n and p):
2921 data.newAction(phase, n, pid, p, t.time, -1, drv)
2922 if pid not in data.devpids:
2923 data.devpids.append(pid)
2924 # device callback finish
2925 elif(t.type == 'device_pm_callback_end'):
2926 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
2927 if(not m):
2928 continue
2929 n = m.group('d')
2930 list = data.dmesg[phase]['list']
2931 if(n in list):
2932 dev = list[n]
2933 dev['length'] = t.time - dev['start']
2934 dev['end'] = t.time
2935 # kprobe event processing
2936 elif(t.fkprobe):
2937 kprobename = t.type
2938 kprobedata = t.name
2939 key = (kprobename, pid)
2940 # displayname is generated from kprobe data
2941 displayname = ''
2942 if(t.fcall):
2943 displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
2944 if not displayname:
2945 continue
2946 if(key not in tp.ktemp):
2947 tp.ktemp[key] = []
2948 tp.ktemp[key].append({
2949 'pid': pid,
2950 'begin': t.time,
2951 'end': t.time,
2952 'name': displayname,
2953 'cdata': kprobedata,
2954 'proc': m_proc,
2955 })
2956 elif(t.freturn):
2957 if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
2958 continue
2959 e = tp.ktemp[key][-1]
2960 if e['begin'] < 0.0 or t.time - e['begin'] < 0.000001:
2961 tp.ktemp[key].pop()
2962 else:
2963 e['end'] = t.time
2964 e['rdata'] = kprobedata
2965 # end of kernel resume
2966 if(kprobename == 'pm_notifier_call_chain' or \
2967 kprobename == 'pm_restore_console'):
2968 data.dmesg[phase]['end'] = t.time
2969 data.tKernRes = t.time
2970
2971 # callgraph processing
2972 elif sysvals.usecallgraph:
2973 # create a callgraph object for the data
2974 key = (m_proc, pid)
2975 if(key not in testrun.ftemp):
2976 testrun.ftemp[key] = []
2977 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2978 # when the call is finished, see which device matches it
2979 cg = testrun.ftemp[key][-1]
2980 res = cg.addLine(t)
2981 if(res != 0):
2982 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
2983 if(res == -1):
2984 testrun.ftemp[key][-1].addLine(t)
2985 tf.close()
2986
2987 if sysvals.suspendmode == 'command':
2988 for test in testruns:
2989 for p in test.data.phases:
2990 if p == 'suspend_prepare':
2991 test.data.dmesg[p]['start'] = test.data.start
2992 test.data.dmesg[p]['end'] = test.data.end
2993 else:
2994 test.data.dmesg[p]['start'] = test.data.end
2995 test.data.dmesg[p]['end'] = test.data.end
2996 test.data.tSuspended = test.data.end
2997 test.data.tResumed = test.data.end
2998 test.data.tLow = 0
2999 test.data.fwValid = False
3000
3001 # dev source and procmon events can be unreadable with mixed phase height
3002 if sysvals.usedevsrc or sysvals.useprocmon:
3003 sysvals.mixedphaseheight = False
3004
3005 for i in range(len(testruns)):
3006 test = testruns[i]
3007 data = test.data
3008 # find the total time range for this test (begin, end)
3009 tlb, tle = data.start, data.end
3010 if i < len(testruns) - 1:
3011 tle = testruns[i+1].data.start
3012 # add the process usage data to the timeline
3013 if sysvals.useprocmon:
3014 data.createProcessUsageEvents()
3015 # add the traceevent data to the device hierarchy
3016 if(sysvals.usetraceevents):
3017 # add actual trace funcs
3018 for name in test.ttemp:
3019 for event in test.ttemp[name]:
3020 data.newActionGlobal(name, event['begin'], event['end'], event['pid'])
3021 # add the kprobe based virtual tracefuncs as actual devices
3022 for key in tp.ktemp:
3023 name, pid = key
3024 if name not in sysvals.tracefuncs:
3025 continue
3026 for e in tp.ktemp[key]:
3027 kb, ke = e['begin'], e['end']
3028 if kb == ke or tlb > kb or tle <= kb:
3029 continue
3030 color = sysvals.kprobeColor(name)
3031 data.newActionGlobal(e['name'], kb, ke, pid, color)
3032 # add config base kprobes and dev kprobes
3033 if sysvals.usedevsrc:
3034 for key in tp.ktemp:
3035 name, pid = key
3036 if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3037 continue
3038 for e in tp.ktemp[key]:
3039 kb, ke = e['begin'], e['end']
3040 if kb == ke or tlb > kb or tle <= kb:
3041 continue
3042 data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3043 ke, e['cdata'], e['rdata'])
3044 if sysvals.usecallgraph:
3045 # add the callgraph data to the device hierarchy
3046 sortlist = dict()
3047 for key in test.ftemp:
3048 proc, pid = key
3049 for cg in test.ftemp[key]:
3050 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3051 continue
3052 if(not cg.postProcess()):
3053 id = 'task %s' % (pid)
3054 sysvals.vprint('Sanity check failed for '+\
3055 id+', ignoring this callback')
3056 continue
3057 # match cg data to devices
3058 devname = ''
3059 if sysvals.suspendmode != 'command':
3060 devname = cg.deviceMatch(pid, data)
3061 if not devname:
3062 sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3063 sortlist[sortkey] = cg
3064 elif len(cg.list) > 1000000:
3065 print 'WARNING: the callgraph for %s is massive (%d lines)' %\
3066 (devname, len(cg.list))
3067 # create blocks for orphan cg data
3068 for sortkey in sorted(sortlist):
3069 cg = sortlist[sortkey]
3070 name = cg.name
3071 if sysvals.isCallgraphFunc(name):
3072 sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3073 cg.newActionFromFunction(data)
3074 if sysvals.suspendmode == 'command':
3075 return testdata
3076
3077 # fill in any missing phases
3078 for data in testdata:
3079 lp = data.phases[0]
3080 for p in data.phases:
3081 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3082 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3083 if(data.dmesg[p]['start'] < 0):
3084 data.dmesg[p]['start'] = data.dmesg[lp]['end']
3085 if(p == 'resume_machine'):
3086 data.tSuspended = data.dmesg[lp]['end']
3087 data.tResumed = data.dmesg[lp]['end']
3088 data.tLow = 0
3089 if(data.dmesg[p]['end'] < 0):
3090 data.dmesg[p]['end'] = data.dmesg[p]['start']
3091 if(p != lp and not ('machine' in p and 'machine' in lp)):
3092 data.dmesg[lp]['end'] = data.dmesg[p]['start']
3093 lp = p
3094
3095 if(len(sysvals.devicefilter) > 0):
3096 data.deviceFilter(sysvals.devicefilter)
3097 data.fixupInitcallsThatDidntReturn()
3098 if sysvals.usedevsrc:
3099 data.optimizeDevSrc()
3100
3101 # x2: merge any overlapping devices between test runs
3102 if sysvals.usedevsrc and len(testdata) > 1:
3103 tc = len(testdata)
3104 for i in range(tc - 1):
3105 devlist = testdata[i].overflowDevices()
3106 for j in range(i + 1, tc):
3107 testdata[j].mergeOverlapDevices(devlist)
3108 testdata[0].stitchTouchingThreads(testdata[1:])
3109 return testdata
3110
3111# Function: loadKernelLog
3112# Description:
3113# [deprecated for kernel 3.15.0 or newer]
3114# load the dmesg file into memory and fix up any ordering issues
3115# The dmesg filename is taken from sysvals
3116# Output:
3117# An array of empty Data objects with only their dmesgtext attributes set
3118def loadKernelLog():
3119 sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3120 os.path.basename(sysvals.dmesgfile))
3121 if(os.path.exists(sysvals.dmesgfile) == False):
3122 doError('%s does not exist' % sysvals.dmesgfile)
3123
3124 # there can be multiple test runs in a single file
3125 tp = TestProps()
3126 tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3127 testruns = []
3128 data = 0
3129 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3130 for line in lf:
3131 line = line.replace('\r\n', '')
3132 idx = line.find('[')
3133 if idx > 1:
3134 line = line[idx:]
3135 # grab the stamp and sysinfo
3136 if re.match(tp.stampfmt, line):
3137 tp.stamp = line
3138 continue
3139 elif re.match(tp.sysinfofmt, line):
3140 tp.sysinfo = line
3141 continue
3142 elif re.match(tp.cmdlinefmt, line):
3143 tp.cmdline = line
3144 continue
3145 m = re.match(sysvals.firmwarefmt, line)
3146 if(m):
3147 tp.fwdata.append((int(m.group('s')), int(m.group('r'))))
3148 continue
3149 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3150 if(not m):
3151 continue
3152 msg = m.group("msg")
3153 if(re.match('PM: Syncing filesystems.*', msg)):
3154 if(data):
3155 testruns.append(data)
3156 data = Data(len(testruns))
3157 tp.parseStamp(data, sysvals)
3158 if len(tp.fwdata) > data.testnumber:
3159 data.fwSuspend, data.fwResume = tp.fwdata[data.testnumber]
3160 if(data.fwSuspend > 0 or data.fwResume > 0):
3161 data.fwValid = True
3162 if(not data):
3163 continue
3164 m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3165 if(m):
3166 sysvals.stamp['kernel'] = m.group('k')
3167 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3168 if(m):
3169 sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3170 data.dmesgtext.append(line)
3171 lf.close()
3172
3173 if data:
3174 testruns.append(data)
3175 if len(testruns) < 1:
3176 doError(' dmesg log has no suspend/resume data: %s' \
3177 % sysvals.dmesgfile)
3178
3179 # fix lines with same timestamp/function with the call and return swapped
3180 for data in testruns:
3181 last = ''
3182 for line in data.dmesgtext:
3183 mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
3184 '(?P<f>.*)\+ @ .*, parent: .*', line)
3185 mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3186 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3187 if(mc and mr and (mc.group('t') == mr.group('t')) and
3188 (mc.group('f') == mr.group('f'))):
3189 i = data.dmesgtext.index(last)
3190 j = data.dmesgtext.index(line)
3191 data.dmesgtext[i] = line
3192 data.dmesgtext[j] = last
3193 last = line
3194 return testruns
3195
3196# Function: parseKernelLog
3197# Description:
3198# [deprecated for kernel 3.15.0 or newer]
3199# Analyse a dmesg log output file generated from this app during
3200# the execution phase. Create a set of device structures in memory
3201# for subsequent formatting in the html output file
3202# This call is only for legacy support on kernels where the ftrace
3203# data lacks the suspend_resume or device_pm_callbacks trace events.
3204# Arguments:
3205# data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3206# Output:
3207# The filled Data object
3208def parseKernelLog(data):
3209 phase = 'suspend_runtime'
3210
3211 if(data.fwValid):
3212 sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3213 (data.fwSuspend, data.fwResume))
3214
3215 # dmesg phase match table
3216 dm = {
3217 'suspend_prepare': 'PM: Syncing filesystems.*',
3218 'suspend': 'PM: Entering [a-z]* sleep.*',
3219 'suspend_late': 'PM: suspend of devices complete after.*',
3220 'suspend_noirq': 'PM: late suspend of devices complete after.*',
3221 'suspend_machine': 'PM: noirq suspend of devices complete after.*',
3222 'resume_machine': 'ACPI: Low-level resume complete.*',
3223 'resume_noirq': 'ACPI: Waking up from system sleep state.*',
3224 'resume_early': 'PM: noirq resume of devices complete after.*',
3225 'resume': 'PM: early resume of devices complete after.*',
3226 'resume_complete': 'PM: resume of devices complete after.*',
3227 'post_resume': '.*Restarting tasks \.\.\..*',
3228 }
3229 if(sysvals.suspendmode == 'standby'):
3230 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3231 elif(sysvals.suspendmode == 'disk'):
3232 dm['suspend_late'] = 'PM: freeze of devices complete after.*'
3233 dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
3234 dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
3235 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
3236 dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
3237 dm['resume'] = 'PM: early restore of devices complete after.*'
3238 dm['resume_complete'] = 'PM: restore of devices complete after.*'
3239 elif(sysvals.suspendmode == 'freeze'):
3240 dm['resume_machine'] = 'ACPI: resume from mwait'
3241
3242 # action table (expected events that occur and show up in dmesg)
3243 at = {
3244 'sync_filesystems': {
3245 'smsg': 'PM: Syncing filesystems.*',
3246 'emsg': 'PM: Preparing system for mem sleep.*' },
3247 'freeze_user_processes': {
3248 'smsg': 'Freezing user space processes .*',
3249 'emsg': 'Freezing remaining freezable tasks.*' },
3250 'freeze_tasks': {
3251 'smsg': 'Freezing remaining freezable tasks.*',
3252 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3253 'ACPI prepare': {
3254 'smsg': 'ACPI: Preparing to enter system sleep state.*',
3255 'emsg': 'PM: Saving platform NVS memory.*' },
3256 'PM vns': {
3257 'smsg': 'PM: Saving platform NVS memory.*',
3258 'emsg': 'Disabling non-boot CPUs .*' },
3259 }
3260
3261 t0 = -1.0
3262 cpu_start = -1.0
3263 prevktime = -1.0
3264 actions = dict()
3265 for line in data.dmesgtext:
3266 # parse each dmesg line into the time and message
3267 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3268 if(m):
3269 val = m.group('ktime')
3270 try:
3271 ktime = float(val)
3272 except:
3273 continue
3274 msg = m.group('msg')
3275 # initialize data start to first line time
3276 if t0 < 0:
3277 data.setStart(ktime)
3278 t0 = ktime
3279 else:
3280 continue
3281
3282 # hack for determining resume_machine end for freeze
3283 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3284 and phase == 'resume_machine' and \
3285 re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3286 data.dmesg['resume_machine']['end'] = ktime
3287 phase = 'resume_noirq'
3288 data.dmesg[phase]['start'] = ktime
3289
3290 # suspend start
3291 if(re.match(dm['suspend_prepare'], msg)):
3292 phase = 'suspend_prepare'
3293 data.dmesg[phase]['start'] = ktime
3294 data.setStart(ktime)
3295 data.tKernSus = ktime
3296 # suspend start
3297 elif(re.match(dm['suspend'], msg)):
3298 data.dmesg['suspend_prepare']['end'] = ktime
3299 phase = 'suspend'
3300 data.dmesg[phase]['start'] = ktime
3301 # suspend_late start
3302 elif(re.match(dm['suspend_late'], msg)):
3303 data.dmesg['suspend']['end'] = ktime
3304 phase = 'suspend_late'
3305 data.dmesg[phase]['start'] = ktime
3306 # suspend_noirq start
3307 elif(re.match(dm['suspend_noirq'], msg)):
3308 data.dmesg['suspend_late']['end'] = ktime
3309 phase = 'suspend_noirq'
3310 data.dmesg[phase]['start'] = ktime
3311 # suspend_machine start
3312 elif(re.match(dm['suspend_machine'], msg)):
3313 data.dmesg['suspend_noirq']['end'] = ktime
3314 phase = 'suspend_machine'
3315 data.dmesg[phase]['start'] = ktime
3316 # resume_machine start
3317 elif(re.match(dm['resume_machine'], msg)):
3318 if(sysvals.suspendmode in ['freeze', 'standby']):
3319 data.tSuspended = prevktime
3320 data.dmesg['suspend_machine']['end'] = prevktime
3321 else:
3322 data.tSuspended = ktime
3323 data.dmesg['suspend_machine']['end'] = ktime
3324 phase = 'resume_machine'
3325 data.tResumed = ktime
3326 data.tLow = data.tResumed - data.tSuspended
3327 data.dmesg[phase]['start'] = ktime
3328 # resume_noirq start
3329 elif(re.match(dm['resume_noirq'], msg)):
3330 data.dmesg['resume_machine']['end'] = ktime
3331 phase = 'resume_noirq'
3332 data.dmesg[phase]['start'] = ktime
3333 # resume_early start
3334 elif(re.match(dm['resume_early'], msg)):
3335 data.dmesg['resume_noirq']['end'] = ktime
3336 phase = 'resume_early'
3337 data.dmesg[phase]['start'] = ktime
3338 # resume start
3339 elif(re.match(dm['resume'], msg)):
3340 data.dmesg['resume_early']['end'] = ktime
3341 phase = 'resume'
3342 data.dmesg[phase]['start'] = ktime
3343 # resume complete start
3344 elif(re.match(dm['resume_complete'], msg)):
3345 data.dmesg['resume']['end'] = ktime
3346 phase = 'resume_complete'
3347 data.dmesg[phase]['start'] = ktime
3348 # post resume start
3349 elif(re.match(dm['post_resume'], msg)):
3350 data.dmesg['resume_complete']['end'] = ktime
3351 data.setEnd(ktime)
3352 data.tKernRes = ktime
3353 break
3354
3355 # -- device callbacks --
3356 if(phase in data.phases):
3357 # device init call
3358 if(re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3359 sm = re.match('calling (?P<f>.*)\+ @ '+\
3360 '(?P<n>.*), parent: (?P<p>.*)', msg);
3361 f = sm.group('f')
3362 n = sm.group('n')
3363 p = sm.group('p')
3364 if(f and n and p):
3365 data.newAction(phase, f, int(n), p, ktime, -1, '')
3366 # device init return
3367 elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3368 '(?P<t>.*) usecs', msg)):
3369 sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3370 '(?P<t>.*) usecs(?P<a>.*)', msg);
3371 f = sm.group('f')
3372 t = sm.group('t')
3373 list = data.dmesg[phase]['list']
3374 if(f in list):
3375 dev = list[f]
3376 dev['length'] = int(t)
3377 dev['end'] = ktime
3378
3379 # if trace events are not available, these are better than nothing
3380 if(not sysvals.usetraceevents):
3381 # look for known actions
3382 for a in at:
3383 if(re.match(at[a]['smsg'], msg)):
3384 if(a not in actions):
3385 actions[a] = []
3386 actions[a].append({'begin': ktime, 'end': ktime})
3387 if(re.match(at[a]['emsg'], msg)):
3388 if(a in actions):
3389 actions[a][-1]['end'] = ktime
3390 # now look for CPU on/off events
3391 if(re.match('Disabling non-boot CPUs .*', msg)):
3392 # start of first cpu suspend
3393 cpu_start = ktime
3394 elif(re.match('Enabling non-boot CPUs .*', msg)):
3395 # start of first cpu resume
3396 cpu_start = ktime
3397 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3398 # end of a cpu suspend, start of the next
3399 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3400 cpu = 'CPU'+m.group('cpu')
3401 if(cpu not in actions):
3402 actions[cpu] = []
3403 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3404 cpu_start = ktime
3405 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3406 # end of a cpu resume, start of the next
3407 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3408 cpu = 'CPU'+m.group('cpu')
3409 if(cpu not in actions):
3410 actions[cpu] = []
3411 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3412 cpu_start = ktime
3413 prevktime = ktime
3414
3415 # fill in any missing phases
3416 lp = data.phases[0]
3417 for p in data.phases:
3418 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
3419 print('WARNING: phase "%s" is missing, something went wrong!' % p)
3420 print(' In %s, this dmesg line denotes the start of %s:' % \
3421 (sysvals.suspendmode, p))
3422 print(' "%s"' % dm[p])
3423 if(data.dmesg[p]['start'] < 0):
3424 data.dmesg[p]['start'] = data.dmesg[lp]['end']
3425 if(p == 'resume_machine'):
3426 data.tSuspended = data.dmesg[lp]['end']
3427 data.tResumed = data.dmesg[lp]['end']
3428 data.tLow = 0
3429 if(data.dmesg[p]['end'] < 0):
3430 data.dmesg[p]['end'] = data.dmesg[p]['start']
3431 lp = p
3432
3433 # fill in any actions we've found
3434 for name in actions:
3435 for event in actions[name]:
3436 data.newActionGlobal(name, event['begin'], event['end'])
3437
3438 if(len(sysvals.devicefilter) > 0):
3439 data.deviceFilter(sysvals.devicefilter)
3440 data.fixupInitcallsThatDidntReturn()
3441 return True
3442
3443def callgraphHTML(sv, hf, num, cg, title, color, devid):
3444 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'
3445 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
3446 html_func_end = '</article>\n'
3447 html_func_leaf = '<article>{0} {1}</article>\n'
3448
3449 cgid = devid
3450 if cg.id:
3451 cgid += cg.id
3452 cglen = (cg.end - cg.start) * 1000
3453 if cglen < sv.mincglen:
3454 return num
3455
3456 fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
3457 flen = fmt % (cglen, cg.start, cg.end)
3458 hf.write(html_func_top.format(cgid, color, num, title, flen))
3459 num += 1
3460 for line in cg.list:
3461 if(line.length < 0.000000001):
3462 flen = ''
3463 else:
3464 fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
3465 flen = fmt % (line.length*1000, line.time)
3466 if line.isLeaf():
3467 hf.write(html_func_leaf.format(line.name, flen))
3468 elif line.freturn:
3469 hf.write(html_func_end)
3470 else:
3471 hf.write(html_func_start.format(num, line.name, flen))
3472 num += 1
3473 hf.write(html_func_end)
3474 return num
3475
3476def addCallgraphs(sv, hf, data):
3477 hf.write('<section id="callgraphs" class="callgraph">\n')
3478 # write out the ftrace data converted to html
3479 num = 0
3480 for p in data.phases:
3481 if sv.cgphase and p != sv.cgphase:
3482 continue
3483 list = data.dmesg[p]['list']
3484 for devname in data.sortedDevices(p):
3485 if len(sv.cgfilter) > 0 and devname not in sv.cgfilter:
3486 continue
3487 dev = list[devname]
3488 color = 'white'
3489 if 'color' in data.dmesg[p]:
3490 color = data.dmesg[p]['color']
3491 if 'color' in dev:
3492 color = dev['color']
3493 name = devname
3494 if(devname in sv.devprops):
3495 name = sv.devprops[devname].altName(devname)
3496 if sv.suspendmode in suspendmodename:
3497 name += ' '+p
3498 if('ftrace' in dev):
3499 cg = dev['ftrace']
3500 num = callgraphHTML(sv, hf, num, cg,
3501 name, color, dev['id'])
3502 if('ftraces' in dev):
3503 for cg in dev['ftraces']:
3504 num = callgraphHTML(sv, hf, num, cg,
3505 name+' → '+cg.name, color, dev['id'])
3506 hf.write('\n\n </section>\n')
3507
3508# Function: createHTMLSummarySimple
3509# Description:
3510# Create summary html file for a series of tests
3511# Arguments:
3512# testruns: array of Data objects from parseTraceLog
3513def createHTMLSummarySimple(testruns, htmlfile, folder):
3514 # write the html header first (html head, css code, up to body start)
3515 html = '<!DOCTYPE html>\n<html>\n<head>\n\
3516 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3517 <title>SleepGraph Summary</title>\n\
3518 <style type=\'text/css\'>\n\
3519 .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
3520 table {width:100%;border-collapse: collapse;}\n\
3521 .summary {border:1px solid;}\n\
3522 th {border: 1px solid black;background:#222;color:white;}\n\
3523 td {font: 16px "Times New Roman";text-align: center;}\n\
3524 tr.alt td {background:#ddd;}\n\
3525 tr.avg td {background:#aaa;}\n\
3526 </style>\n</head>\n<body>\n'
3527
3528 # group test header
3529 html += '<div class="stamp">%s (%d tests)</div>\n' % (folder, len(testruns))
3530 th = '\t<th>{0}</th>\n'
3531 td = '\t<td>{0}</td>\n'
3532 tdlink = '\t<td><a href="{0}">html</a></td>\n'
3533
3534 # table header
3535 html += '<table class="summary">\n<tr>\n' + th.format('#') +\
3536 th.format('Mode') + th.format('Host') + th.format('Kernel') +\
3537 th.format('Test Time') + th.format('Suspend') + th.format('Resume') +\
3538 th.format('Detail') + '</tr>\n'
3539
3540 # test data, 1 row per test
3541 avg = '<tr class="avg"><td></td><td></td><td></td><td></td>'+\
3542 '<td>Average of {0} {1} tests</td><td>{2}</td><td>{3}</td><td></td></tr>\n'
3543 sTimeAvg = rTimeAvg = 0.0
3544 mode = ''
3545 num = 0
3546 for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
3547 if mode != data['mode']:
3548 # test average line
3549 if(num > 0):
3550 sTimeAvg /= (num - 1)
3551 rTimeAvg /= (num - 1)
3552 html += avg.format('%d' % (num - 1), mode,
3553 '%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg)
3554 sTimeAvg = rTimeAvg = 0.0
3555 mode = data['mode']
3556 num = 1
3557 # alternate row color
3558 if num % 2 == 1:
3559 html += '<tr class="alt">\n'
3560 else:
3561 html += '<tr>\n'
3562 html += td.format("%d" % num)
3563 num += 1
3564 # basic info
3565 for item in ['mode', 'host', 'kernel', 'time']:
3566 val = "unknown"
3567 if(item in data):
3568 val = data[item]
3569 html += td.format(val)
3570 # suspend time
3571 sTime = float(data['suspend'])
3572 sTimeAvg += sTime
3573 html += td.format('%.3f ms' % sTime)
3574 # resume time
3575 rTime = float(data['resume'])
3576 rTimeAvg += rTime
3577 html += td.format('%.3f ms' % rTime)
3578 # link to the output html
3579 html += tdlink.format(data['url']) + '</tr>\n'
3580 # last test average line
3581 if(num > 0):
3582 sTimeAvg /= (num - 1)
3583 rTimeAvg /= (num - 1)
3584 html += avg.format('%d' % (num - 1), mode,
3585 '%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg)
3586
3587 # flush the data to file
3588 hf = open(htmlfile, 'w')
3589 hf.write(html+'</table>\n</body>\n</html>\n')
3590 hf.close()
3591
3592def ordinal(value):
3593 suffix = 'th'
3594 if value < 10 or value > 19:
3595 if value % 10 == 1:
3596 suffix = 'st'
3597 elif value % 10 == 2:
3598 suffix = 'nd'
3599 elif value % 10 == 3:
3600 suffix = 'rd'
3601 return '%d%s' % (value, suffix)
3602
3603# Function: createHTML
3604# Description:
3605# Create the output html file from the resident test data
3606# Arguments:
3607# testruns: array of Data objects from parseKernelLog or parseTraceLog
3608# Output:
3609# True if the html file was created, false if it failed
3610def createHTML(testruns):
3611 if len(testruns) < 1:
3612 print('ERROR: Not enough test data to build a timeline')
3613 return
3614
3615 kerror = False
3616 for data in testruns:
3617 if data.kerror:
3618 kerror = True
3619 data.normalizeTime(testruns[-1].tSuspended)
3620
3621 # html function templates
3622 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</div>\n'
3623 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'
3624 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
3625 html_timetotal = '<table class="time1">\n<tr>'\
3626 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
3627 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
3628 '</tr>\n</table>\n'
3629 html_timetotal2 = '<table class="time1">\n<tr>'\
3630 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
3631 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
3632 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
3633 '</tr>\n</table>\n'
3634 html_timetotal3 = '<table class="time1">\n<tr>'\
3635 '<td class="green">Execution Time: <b>{0} ms</b></td>'\
3636 '<td class="yellow">Command: <b>{1}</b></td>'\
3637 '</tr>\n</table>\n'
3638 html_timegroups = '<table class="time2">\n<tr>'\
3639 '<td class="green" title="time from kernel enter_state({5}) to firmware mode [kernel time only]">{4}Kernel Suspend: {0} ms</td>'\
3640 '<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
3641 '<td class="purple">{4}Firmware Resume: {2} ms</td>'\
3642 '<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
3643 '</tr>\n</table>\n'
3644
3645 # html format variables
3646 scaleH = 20
3647 if kerror:
3648 scaleH = 40
3649
3650 # device timeline
3651 devtl = Timeline(30, scaleH)
3652
3653 # write the test title and general info header
3654 devtl.createHeader(sysvals, testruns[0].stamp)
3655
3656 # Generate the header for this timeline
3657 for data in testruns:
3658 tTotal = data.end - data.start
3659 sktime, rktime = data.getTimeValues()
3660 if(tTotal == 0):
3661 doError('No timeline data')
3662 if(data.tLow > 0):
3663 low_time = '%.0f'%(data.tLow*1000)
3664 if sysvals.suspendmode == 'command':
3665 run_time = '%.0f'%((data.end-data.start)*1000)
3666 if sysvals.testcommand:
3667 testdesc = sysvals.testcommand
3668 else:
3669 testdesc = 'unknown'
3670 if(len(testruns) > 1):
3671 testdesc = ordinal(data.testnumber+1)+' '+testdesc
3672 thtml = html_timetotal3.format(run_time, testdesc)
3673 devtl.html += thtml
3674 elif data.fwValid:
3675 suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0))
3676 resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0))
3677 testdesc1 = 'Total'
3678 testdesc2 = ''
3679 stitle = 'time from kernel enter_state(%s) to low-power mode [kernel & firmware time]' % sysvals.suspendmode
3680 rtitle = 'time from low-power mode to return from kernel enter_state(%s) [firmware & kernel time]' % sysvals.suspendmode
3681 if(len(testruns) > 1):
3682 testdesc1 = testdesc2 = ordinal(data.testnumber+1)
3683 testdesc2 += ' '
3684 if(data.tLow == 0):
3685 thtml = html_timetotal.format(suspend_time, \
3686 resume_time, testdesc1, stitle, rtitle)
3687 else:
3688 thtml = html_timetotal2.format(suspend_time, low_time, \
3689 resume_time, testdesc1, stitle, rtitle)
3690 devtl.html += thtml
3691 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
3692 rftime = '%.3f'%(data.fwResume / 1000000.0)
3693 devtl.html += html_timegroups.format('%.3f'%sktime, \
3694 sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode)
3695 else:
3696 suspend_time = '%.3f' % sktime
3697 resume_time = '%.3f' % rktime
3698 testdesc = 'Kernel'
3699 stitle = 'time from kernel enter_state(%s) to firmware mode [kernel time only]' % sysvals.suspendmode
3700 rtitle = 'time from firmware mode to return from kernel enter_state(%s) [kernel time only]' % sysvals.suspendmode
3701 if(len(testruns) > 1):
3702 testdesc = ordinal(data.testnumber+1)+' '+testdesc
3703 if(data.tLow == 0):
3704 thtml = html_timetotal.format(suspend_time, \
3705 resume_time, testdesc, stitle, rtitle)
3706 else:
3707 thtml = html_timetotal2.format(suspend_time, low_time, \
3708 resume_time, testdesc, stitle, rtitle)
3709 devtl.html += thtml
3710
3711 # time scale for potentially multiple datasets
3712 t0 = testruns[0].start
3713 tMax = testruns[-1].end
3714 tTotal = tMax - t0
3715
3716 # determine the maximum number of rows we need to draw
3717 fulllist = []
3718 threadlist = []
3719 pscnt = 0
3720 devcnt = 0
3721 for data in testruns:
3722 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
3723 for group in data.devicegroups:
3724 devlist = []
3725 for phase in group:
3726 for devname in data.tdevlist[phase]:
3727 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
3728 devlist.append(d)
3729 if d.isa('kth'):
3730 threadlist.append(d)
3731 else:
3732 if d.isa('ps'):
3733 pscnt += 1
3734 else:
3735 devcnt += 1
3736 fulllist.append(d)
3737 if sysvals.mixedphaseheight:
3738 devtl.getPhaseRows(devlist)
3739 if not sysvals.mixedphaseheight:
3740 if len(threadlist) > 0 and len(fulllist) > 0:
3741 if pscnt > 0 and devcnt > 0:
3742 msg = 'user processes & device pm callbacks'
3743 elif pscnt > 0:
3744 msg = 'user processes'
3745 else:
3746 msg = 'device pm callbacks'
3747 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
3748 fulllist.insert(0, d)
3749 devtl.getPhaseRows(fulllist)
3750 if len(threadlist) > 0:
3751 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
3752 threadlist.insert(0, d)
3753 devtl.getPhaseRows(threadlist, devtl.rows)
3754 devtl.calcTotalRows()
3755
3756 # draw the full timeline
3757 devtl.createZoomBox(sysvals.suspendmode, len(testruns))
3758 phases = {'suspend':[],'resume':[]}
3759 for phase in data.dmesg:
3760 if 'resume' in phase:
3761 phases['resume'].append(phase)
3762 else:
3763 phases['suspend'].append(phase)
3764
3765 # draw each test run chronologically
3766 for data in testruns:
3767 # now draw the actual timeline blocks
3768 for dir in phases:
3769 # draw suspend and resume blocks separately
3770 bname = '%s%d' % (dir[0], data.testnumber)
3771 if dir == 'suspend':
3772 m0 = data.start
3773 mMax = data.tSuspended
3774 left = '%f' % (((m0-t0)*100.0)/tTotal)
3775 else:
3776 m0 = data.tSuspended
3777 mMax = data.end
3778 # in an x2 run, remove any gap between blocks
3779 if len(testruns) > 1 and data.testnumber == 0:
3780 mMax = testruns[1].start
3781 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
3782 mTotal = mMax - m0
3783 # if a timeline block is 0 length, skip altogether
3784 if mTotal == 0:
3785 continue
3786 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
3787 devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
3788 for b in sorted(phases[dir]):
3789 # draw the phase color background
3790 phase = data.dmesg[b]
3791 length = phase['end']-phase['start']
3792 left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
3793 width = '%f' % ((length*100.0)/mTotal)
3794 devtl.html += devtl.html_phase.format(left, width, \
3795 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
3796 data.dmesg[b]['color'], '')
3797 for e in data.errorinfo[dir]:
3798 # draw red lines for any kernel errors found
3799 type, t, idx1, idx2 = e
3800 id = '%d_%d' % (idx1, idx2)
3801 right = '%f' % (((mMax-t)*100.0)/mTotal)
3802 devtl.html += html_error.format(right, id, type)
3803 for b in sorted(phases[dir]):
3804 # draw the devices for this phase
3805 phaselist = data.dmesg[b]['list']
3806 for d in data.tdevlist[b]:
3807 name = d
3808 drv = ''
3809 dev = phaselist[d]
3810 xtraclass = ''
3811 xtrainfo = ''
3812 xtrastyle = ''
3813 if 'htmlclass' in dev:
3814 xtraclass = dev['htmlclass']
3815 if 'color' in dev:
3816 xtrastyle = 'background:%s;' % dev['color']
3817 if(d in sysvals.devprops):
3818 name = sysvals.devprops[d].altName(d)
3819 xtraclass = sysvals.devprops[d].xtraClass()
3820 xtrainfo = sysvals.devprops[d].xtraInfo()
3821 elif xtraclass == ' kth':
3822 xtrainfo = ' kernel_thread'
3823 if('drv' in dev and dev['drv']):
3824 drv = ' {%s}' % dev['drv']
3825 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
3826 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
3827 top = '%.3f' % (rowtop + devtl.scaleH)
3828 left = '%f' % (((dev['start']-m0)*100)/mTotal)
3829 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
3830 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
3831 title = name+drv+xtrainfo+length
3832 if sysvals.suspendmode == 'command':
3833 title += sysvals.testcommand
3834 elif xtraclass == ' ps':
3835 if 'suspend' in b:
3836 title += 'pre_suspend_process'
3837 else:
3838 title += 'post_resume_process'
3839 else:
3840 title += b
3841 devtl.html += devtl.html_device.format(dev['id'], \
3842 title, left, top, '%.3f'%rowheight, width, \
3843 d+drv, xtraclass, xtrastyle)
3844 if('cpuexec' in dev):
3845 for t in sorted(dev['cpuexec']):
3846 start, end = t
3847 j = float(dev['cpuexec'][t]) / 5
3848 if j > 1.0:
3849 j = 1.0
3850 height = '%.3f' % (rowheight/3)
3851 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
3852 left = '%f' % (((start-m0)*100)/mTotal)
3853 width = '%f' % ((end-start)*100/mTotal)
3854 color = 'rgba(255, 0, 0, %f)' % j
3855 devtl.html += \
3856 html_cpuexec.format(left, top, height, width, color)
3857 if('src' not in dev):
3858 continue
3859 # draw any trace events for this device
3860 for e in dev['src']:
3861 height = '%.3f' % devtl.rowH
3862 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
3863 left = '%f' % (((e.time-m0)*100)/mTotal)
3864 width = '%f' % (e.length*100/mTotal)
3865 xtrastyle = ''
3866 if e.color:
3867 xtrastyle = 'background:%s;' % e.color
3868 devtl.html += \
3869 html_traceevent.format(e.title(), \
3870 left, top, height, width, e.text(), '', xtrastyle)
3871 # draw the time scale, try to make the number of labels readable
3872 devtl.createTimeScale(m0, mMax, tTotal, dir)
3873 devtl.html += '</div>\n'
3874
3875 # timeline is finished
3876 devtl.html += '</div>\n</div>\n'
3877
3878 # draw a legend which describes the phases by color
3879 if sysvals.suspendmode != 'command':
3880 data = testruns[-1]
3881 devtl.html += '<div class="legend">\n'
3882 pdelta = 100.0/len(data.phases)
3883 pmargin = pdelta / 4.0
3884 for phase in data.phases:
3885 tmp = phase.split('_')
3886 id = tmp[0][0]
3887 if(len(tmp) > 1):
3888 id += tmp[1][0]
3889 order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
3890 name = string.replace(phase, '_', ' ')
3891 devtl.html += devtl.html_legend.format(order, \
3892 data.dmesg[phase]['color'], name, id)
3893 devtl.html += '</div>\n'
3894
3895 hf = open(sysvals.htmlfile, 'w')
3896 addCSS(hf, sysvals, len(testruns), kerror)
3897
3898 # write the device timeline
3899 hf.write(devtl.html)
3900 hf.write('<div id="devicedetailtitle"></div>\n')
3901 hf.write('<div id="devicedetail" style="display:none;">\n')
3902 # draw the colored boxes for the device detail section
3903 for data in testruns:
3904 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
3905 pscolor = 'linear-gradient(to top left, #ccc, #eee)'
3906 hf.write(devtl.html_phaselet.format('pre_suspend_process', \
3907 '0', '0', pscolor))
3908 for b in data.phases:
3909 phase = data.dmesg[b]
3910 length = phase['end']-phase['start']
3911 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
3912 width = '%.3f' % ((length*100.0)/tTotal)
3913 hf.write(devtl.html_phaselet.format(b, left, width, \
3914 data.dmesg[b]['color']))
3915 hf.write(devtl.html_phaselet.format('post_resume_process', \
3916 '0', '0', pscolor))
3917 if sysvals.suspendmode == 'command':
3918 hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
3919 hf.write('</div>\n')
3920 hf.write('</div>\n')
3921
3922 # write the ftrace data (callgraph)
3923 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
3924 data = testruns[sysvals.cgtest]
3925 else:
3926 data = testruns[-1]
3927 if sysvals.usecallgraph:
3928 addCallgraphs(sysvals, hf, data)
3929
3930 # add the test log as a hidden div
3931 if sysvals.testlog and sysvals.logmsg:
3932 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
3933 # add the dmesg log as a hidden div
3934 if sysvals.dmesglog and sysvals.dmesgfile:
3935 hf.write('<div id="dmesglog" style="display:none;">\n')
3936 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3937 for line in lf:
3938 line = line.replace('<', '<').replace('>', '>')
3939 hf.write(line)
3940 lf.close()
3941 hf.write('</div>\n')
3942 # add the ftrace log as a hidden div
3943 if sysvals.ftracelog and sysvals.ftracefile:
3944 hf.write('<div id="ftracelog" style="display:none;">\n')
3945 lf = sysvals.openlog(sysvals.ftracefile, 'r')
3946 for line in lf:
3947 hf.write(line)
3948 lf.close()
3949 hf.write('</div>\n')
3950
3951 # write the footer and close
3952 addScriptCode(hf, testruns)
3953 hf.write('</body>\n</html>\n')
3954 hf.close()
3955 return True
3956
3957def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
3958 kernel = sv.stamp['kernel']
3959 host = sv.hostname[0].upper()+sv.hostname[1:]
3960 mode = sv.suspendmode
3961 if sv.suspendmode in suspendmodename:
3962 mode = suspendmodename[sv.suspendmode]
3963 title = host+' '+mode+' '+kernel
3964
3965 # various format changes by flags
3966 cgchk = 'checked'
3967 cgnchk = 'not(:checked)'
3968 if sv.cgexp:
3969 cgchk = 'not(:checked)'
3970 cgnchk = 'checked'
3971
3972 hoverZ = 'z-index:8;'
3973 if sv.usedevsrc:
3974 hoverZ = ''
3975
3976 devlistpos = 'absolute'
3977 if testcount > 1:
3978 devlistpos = 'relative'
3979
3980 scaleTH = 20
3981 if kerror:
3982 scaleTH = 60
3983
3984 # write the html header first (html head, css code, up to body start)
3985 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
3986 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
3987 <title>'+title+'</title>\n\
3988 <style type=\'text/css\'>\n\
3989 body {overflow-y:scroll;}\n\
3990 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
3991 .stamp.sysinfo {font:10px Arial;}\n\
3992 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
3993 .callgraph article * {padding-left:28px;}\n\
3994 h1 {color:black;font:bold 30px Times;}\n\
3995 t0 {color:black;font:bold 30px Times;}\n\
3996 t1 {color:black;font:30px Times;}\n\
3997 t2 {color:black;font:25px Times;}\n\
3998 t3 {color:black;font:20px Times;white-space:nowrap;}\n\
3999 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4000 cS {font:bold 13px Times;}\n\
4001 table {width:100%;}\n\
4002 .gray {background:rgba(80,80,80,0.1);}\n\
4003 .green {background:rgba(204,255,204,0.4);}\n\
4004 .purple {background:rgba(128,0,128,0.2);}\n\
4005 .yellow {background:rgba(255,255,204,0.4);}\n\
4006 .blue {background:rgba(169,208,245,0.4);}\n\
4007 .time1 {font:22px Arial;border:1px solid;}\n\
4008 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4009 td {text-align:center;}\n\
4010 r {color:#500000;font:15px Tahoma;}\n\
4011 n {color:#505050;font:15px Tahoma;}\n\
4012 .tdhl {color:red;}\n\
4013 .hide {display:none;}\n\
4014 .pf {display:none;}\n\
4015 .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\
4016 .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\
4017 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4018 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4019 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4020 .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\
4021 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4022 .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4023 .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4024 .hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4025 .hover.sync {background:white;}\n\
4026 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4027 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4028 .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\
4029 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4030 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4031 .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4032 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4033 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4034 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4035 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4036 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4037 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4038 .devlist {position:'+devlistpos+';width:190px;}\n\
4039 a:link {color:white;text-decoration:none;}\n\
4040 a:visited {color:white;}\n\
4041 a:hover {color:white;}\n\
4042 a:active {color:white;}\n\
4043 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4044 #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4045 .tblock {position:absolute;height:100%;background:#ddd;}\n\
4046 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4047 .bg {z-index:1;}\n\
4048'+extra+'\
4049 </style>\n</head>\n<body>\n'
4050 hf.write(html_header)
4051
4052# Function: addScriptCode
4053# Description:
4054# Adds the javascript code to the output html
4055# Arguments:
4056# hf: the open html file pointer
4057# testruns: array of Data objects from parseKernelLog or parseTraceLog
4058def addScriptCode(hf, testruns):
4059 t0 = testruns[0].start * 1000
4060 tMax = testruns[-1].end * 1000
4061 # create an array in javascript memory with the device details
4062 detail = ' var devtable = [];\n'
4063 for data in testruns:
4064 topo = data.deviceTopology()
4065 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
4066 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
4067 # add the code which will manipulate the data in the browser
4068 script_code = \
4069 '<script type="text/javascript">\n'+detail+\
4070 ' var resolution = -1;\n'\
4071 ' var dragval = [0, 0];\n'\
4072 ' function redrawTimescale(t0, tMax, tS) {\n'\
4073 ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4074 ' var tTotal = tMax - t0;\n'\
4075 ' var list = document.getElementsByClassName("tblock");\n'\
4076 ' for (var i = 0; i < list.length; i++) {\n'\
4077 ' var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4078 ' var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4079 ' var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4080 ' var mMax = m0 + mTotal;\n'\
4081 ' var html = "";\n'\
4082 ' var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4083 ' if(divTotal > 1000) continue;\n'\
4084 ' var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4085 ' var pos = 0.0, val = 0.0;\n'\
4086 ' for (var j = 0; j < divTotal; j++) {\n'\
4087 ' var htmlline = "";\n'\
4088 ' var mode = list[i].id[5];\n'\
4089 ' if(mode == "s") {\n'\
4090 ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4091 ' val = (j-divTotal+1)*tS;\n'\
4092 ' if(j == divTotal - 1)\n'\
4093 ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\
4094 ' else\n'\
4095 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4096 ' } else {\n'\
4097 ' pos = 100 - (((j)*tS*100)/mTotal);\n'\
4098 ' val = (j)*tS;\n'\
4099 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4100 ' if(j == 0)\n'\
4101 ' if(mode == "r")\n'\
4102 ' htmlline = rline+"<cS>←R</cS></div>";\n'\
4103 ' else\n'\
4104 ' htmlline = rline+"<cS>0ms</div>";\n'\
4105 ' }\n'\
4106 ' html += htmlline;\n'\
4107 ' }\n'\
4108 ' timescale.innerHTML = html;\n'\
4109 ' }\n'\
4110 ' }\n'\
4111 ' function zoomTimeline() {\n'\
4112 ' var dmesg = document.getElementById("dmesg");\n'\
4113 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
4114 ' var left = zoombox.scrollLeft;\n'\
4115 ' var val = parseFloat(dmesg.style.width);\n'\
4116 ' var newval = 100;\n'\
4117 ' var sh = window.outerWidth / 2;\n'\
4118 ' if(this.id == "zoomin") {\n'\
4119 ' newval = val * 1.2;\n'\
4120 ' if(newval > 910034) newval = 910034;\n'\
4121 ' dmesg.style.width = newval+"%";\n'\
4122 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4123 ' } else if (this.id == "zoomout") {\n'\
4124 ' newval = val / 1.2;\n'\
4125 ' if(newval < 100) newval = 100;\n'\
4126 ' dmesg.style.width = newval+"%";\n'\
4127 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4128 ' } else {\n'\
4129 ' zoombox.scrollLeft = 0;\n'\
4130 ' dmesg.style.width = "100%";\n'\
4131 ' }\n'\
4132 ' var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4133 ' var t0 = bounds[0];\n'\
4134 ' var tMax = bounds[1];\n'\
4135 ' var tTotal = tMax - t0;\n'\
4136 ' var wTotal = tTotal * 100.0 / newval;\n'\
4137 ' var idx = 7*window.innerWidth/1100;\n'\
4138 ' for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4139 ' if(i >= tS.length) i = tS.length - 1;\n'\
4140 ' if(tS[i] == resolution) return;\n'\
4141 ' resolution = tS[i];\n'\
4142 ' redrawTimescale(t0, tMax, tS[i]);\n'\
4143 ' }\n'\
4144 ' function deviceName(title) {\n'\
4145 ' var name = title.slice(0, title.indexOf(" ("));\n'\
4146 ' return name;\n'\
4147 ' }\n'\
4148 ' function deviceHover() {\n'\
4149 ' var name = deviceName(this.title);\n'\
4150 ' var dmesg = document.getElementById("dmesg");\n'\
4151 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4152 ' var cpu = -1;\n'\
4153 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4154 ' cpu = parseInt(name.slice(7));\n'\
4155 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4156 ' cpu = parseInt(name.slice(8));\n'\
4157 ' for (var i = 0; i < dev.length; i++) {\n'\
4158 ' dname = deviceName(dev[i].title);\n'\
4159 ' var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4160 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4161 ' (name == dname))\n'\
4162 ' {\n'\
4163 ' dev[i].className = "hover "+cname;\n'\
4164 ' } else {\n'\
4165 ' dev[i].className = cname;\n'\
4166 ' }\n'\
4167 ' }\n'\
4168 ' }\n'\
4169 ' function deviceUnhover() {\n'\
4170 ' var dmesg = document.getElementById("dmesg");\n'\
4171 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4172 ' for (var i = 0; i < dev.length; i++) {\n'\
4173 ' dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4174 ' }\n'\
4175 ' }\n'\
4176 ' function deviceTitle(title, total, cpu) {\n'\
4177 ' var prefix = "Total";\n'\
4178 ' if(total.length > 3) {\n'\
4179 ' prefix = "Average";\n'\
4180 ' total[1] = (total[1]+total[3])/2;\n'\
4181 ' total[2] = (total[2]+total[4])/2;\n'\
4182 ' }\n'\
4183 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
4184 ' var name = deviceName(title);\n'\
4185 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
4186 ' var driver = "";\n'\
4187 ' var tS = "<t2>(</t2>";\n'\
4188 ' var tR = "<t2>)</t2>";\n'\
4189 ' if(total[1] > 0)\n'\
4190 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
4191 ' if(total[2] > 0)\n'\
4192 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
4193 ' var s = title.indexOf("{");\n'\
4194 ' var e = title.indexOf("}");\n'\
4195 ' if((s >= 0) && (e >= 0))\n'\
4196 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
4197 ' if(total[1] > 0 && total[2] > 0)\n'\
4198 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
4199 ' else\n'\
4200 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
4201 ' return name;\n'\
4202 ' }\n'\
4203 ' function deviceDetail() {\n'\
4204 ' var devinfo = document.getElementById("devicedetail");\n'\
4205 ' devinfo.style.display = "block";\n'\
4206 ' var name = deviceName(this.title);\n'\
4207 ' var cpu = -1;\n'\
4208 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4209 ' cpu = parseInt(name.slice(7));\n'\
4210 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4211 ' cpu = parseInt(name.slice(8));\n'\
4212 ' var dmesg = document.getElementById("dmesg");\n'\
4213 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4214 ' var idlist = [];\n'\
4215 ' var pdata = [[]];\n'\
4216 ' if(document.getElementById("devicedetail1"))\n'\
4217 ' pdata = [[], []];\n'\
4218 ' var pd = pdata[0];\n'\
4219 ' var total = [0.0, 0.0, 0.0];\n'\
4220 ' for (var i = 0; i < dev.length; i++) {\n'\
4221 ' dname = deviceName(dev[i].title);\n'\
4222 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4223 ' (name == dname))\n'\
4224 ' {\n'\
4225 ' idlist[idlist.length] = dev[i].id;\n'\
4226 ' var tidx = 1;\n'\
4227 ' if(dev[i].id[0] == "a") {\n'\
4228 ' pd = pdata[0];\n'\
4229 ' } else {\n'\
4230 ' if(pdata.length == 1) pdata[1] = [];\n'\
4231 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
4232 ' pd = pdata[1];\n'\
4233 ' tidx = 3;\n'\
4234 ' }\n'\
4235 ' var info = dev[i].title.split(" ");\n'\
4236 ' var pname = info[info.length-1];\n'\
4237 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
4238 ' total[0] += pd[pname];\n'\
4239 ' if(pname.indexOf("suspend") >= 0)\n'\
4240 ' total[tidx] += pd[pname];\n'\
4241 ' else\n'\
4242 ' total[tidx+1] += pd[pname];\n'\
4243 ' }\n'\
4244 ' }\n'\
4245 ' var devname = deviceTitle(this.title, total, cpu);\n'\
4246 ' var left = 0.0;\n'\
4247 ' for (var t = 0; t < pdata.length; t++) {\n'\
4248 ' pd = pdata[t];\n'\
4249 ' devinfo = document.getElementById("devicedetail"+t);\n'\
4250 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
4251 ' for (var i = 0; i < phases.length; i++) {\n'\
4252 ' if(phases[i].id in pd) {\n'\
4253 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
4254 ' var fs = 32;\n'\
4255 ' if(w < 8) fs = 4*w | 0;\n'\
4256 ' var fs2 = fs*3/4;\n'\
4257 ' phases[i].style.width = w+"%";\n'\
4258 ' phases[i].style.left = left+"%";\n'\
4259 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
4260 ' left += w;\n'\
4261 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
4262 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
4263 ' phases[i].innerHTML = time+pname;\n'\
4264 ' } else {\n'\
4265 ' phases[i].style.width = "0%";\n'\
4266 ' phases[i].style.left = left+"%";\n'\
4267 ' }\n'\
4268 ' }\n'\
4269 ' }\n'\
4270 ' if(typeof devstats !== \'undefined\')\n'\
4271 ' callDetail(this.id, this.title);\n'\
4272 ' var cglist = document.getElementById("callgraphs");\n'\
4273 ' if(!cglist) return;\n'\
4274 ' var cg = cglist.getElementsByClassName("atop");\n'\
4275 ' if(cg.length < 10) return;\n'\
4276 ' for (var i = 0; i < cg.length; i++) {\n'\
4277 ' cgid = cg[i].id.split("x")[0]\n'\
4278 ' if(idlist.indexOf(cgid) >= 0) {\n'\
4279 ' cg[i].style.display = "block";\n'\
4280 ' } else {\n'\
4281 ' cg[i].style.display = "none";\n'\
4282 ' }\n'\
4283 ' }\n'\
4284 ' }\n'\
4285 ' function callDetail(devid, devtitle) {\n'\
4286 ' if(!(devid in devstats) || devstats[devid].length < 1)\n'\
4287 ' return;\n'\
4288 ' var list = devstats[devid];\n'\
4289 ' var tmp = devtitle.split(" ");\n'\
4290 ' var name = tmp[0], phase = tmp[tmp.length-1];\n'\
4291 ' var dd = document.getElementById(phase);\n'\
4292 ' var total = parseFloat(tmp[1].slice(1));\n'\
4293 ' var mlist = [];\n'\
4294 ' var maxlen = 0;\n'\
4295 ' var info = []\n'\
4296 ' for(var i in list) {\n'\
4297 ' if(list[i][0] == "@") {\n'\
4298 ' info = list[i].split("|");\n'\
4299 ' continue;\n'\
4300 ' }\n'\
4301 ' var tmp = list[i].split("|");\n'\
4302 ' var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
4303 ' var p = (t*100.0/total).toFixed(2);\n'\
4304 ' mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
4305 ' if(f.length > maxlen)\n'\
4306 ' maxlen = f.length;\n'\
4307 ' }\n'\
4308 ' var pad = 5;\n'\
4309 ' if(mlist.length == 0) pad = 30;\n'\
4310 ' var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
4311 ' if(info.length > 2)\n'\
4312 ' html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
4313 ' if(info.length > 3)\n'\
4314 ' html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
4315 ' if(info.length > 4)\n'\
4316 ' html += ", return=<b>"+info[4]+"</b>";\n'\
4317 ' html += "</t3></div>";\n'\
4318 ' if(mlist.length > 0) {\n'\
4319 ' html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
4320 ' for(var i in mlist)\n'\
4321 ' html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
4322 ' html += "</tr><tr><th>Calls</th>";\n'\
4323 ' for(var i in mlist)\n'\
4324 ' html += "<td>"+mlist[i][1]+"</td>";\n'\
4325 ' html += "</tr><tr><th>Time(ms)</th>";\n'\
4326 ' for(var i in mlist)\n'\
4327 ' html += "<td>"+mlist[i][2]+"</td>";\n'\
4328 ' html += "</tr><tr><th>Percent</th>";\n'\
4329 ' for(var i in mlist)\n'\
4330 ' html += "<td>"+mlist[i][3]+"</td>";\n'\
4331 ' html += "</tr></table>";\n'\
4332 ' }\n'\
4333 ' dd.innerHTML = html;\n'\
4334 ' var height = (maxlen*5)+100;\n'\
4335 ' dd.style.height = height+"px";\n'\
4336 ' document.getElementById("devicedetail").style.height = height+"px";\n'\
4337 ' }\n'\
4338 ' function callSelect() {\n'\
4339 ' var cglist = document.getElementById("callgraphs");\n'\
4340 ' if(!cglist) return;\n'\
4341 ' var cg = cglist.getElementsByClassName("atop");\n'\
4342 ' for (var i = 0; i < cg.length; i++) {\n'\
4343 ' if(this.id == cg[i].id) {\n'\
4344 ' cg[i].style.display = "block";\n'\
4345 ' } else {\n'\
4346 ' cg[i].style.display = "none";\n'\
4347 ' }\n'\
4348 ' }\n'\
4349 ' }\n'\
4350 ' function devListWindow(e) {\n'\
4351 ' var win = window.open();\n'\
4352 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
4353 ' "<style type=\\"text/css\\">"+\n'\
4354 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
4355 ' "</style>"\n'\
4356 ' var dt = devtable[0];\n'\
4357 ' if(e.target.id != "devlist1")\n'\
4358 ' dt = devtable[1];\n'\
4359 ' win.document.write(html+dt);\n'\
4360 ' }\n'\
4361 ' function errWindow() {\n'\
4362 ' var range = this.id.split("_");\n'\
4363 ' var idx1 = parseInt(range[0]);\n'\
4364 ' var idx2 = parseInt(range[1]);\n'\
4365 ' var win = window.open();\n'\
4366 ' var log = document.getElementById("dmesglog");\n'\
4367 ' var title = "<title>dmesg log</title>";\n'\
4368 ' var text = log.innerHTML.split("\\n");\n'\
4369 ' var html = "";\n'\
4370 ' for(var i = 0; i < text.length; i++) {\n'\
4371 ' if(i == idx1) {\n'\
4372 ' html += "<e id=target>"+text[i]+"</e>\\n";\n'\
4373 ' } else if(i > idx1 && i <= idx2) {\n'\
4374 ' html += "<e>"+text[i]+"</e>\\n";\n'\
4375 ' } else {\n'\
4376 ' html += text[i]+"\\n";\n'\
4377 ' }\n'\
4378 ' }\n'\
4379 ' win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
4380 ' win.location.hash = "#target";\n'\
4381 ' win.document.close();\n'\
4382 ' }\n'\
4383 ' function logWindow(e) {\n'\
4384 ' var name = e.target.id.slice(4);\n'\
4385 ' var win = window.open();\n'\
4386 ' var log = document.getElementById(name+"log");\n'\
4387 ' var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
4388 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
4389 ' win.document.close();\n'\
4390 ' }\n'\
4391 ' function onMouseDown(e) {\n'\
4392 ' dragval[0] = e.clientX;\n'\
4393 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
4394 ' document.onmousemove = onMouseMove;\n'\
4395 ' }\n'\
4396 ' function onMouseMove(e) {\n'\
4397 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
4398 ' zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
4399 ' }\n'\
4400 ' function onMouseUp(e) {\n'\
4401 ' document.onmousemove = null;\n'\
4402 ' }\n'\
4403 ' function onKeyPress(e) {\n'\
4404 ' var c = e.charCode;\n'\
4405 ' if(c != 42 && c != 43 && c != 45) return;\n'\
4406 ' var click = document.createEvent("Events");\n'\
4407 ' click.initEvent("click", true, false);\n'\
4408 ' if(c == 43) \n'\
4409 ' document.getElementById("zoomin").dispatchEvent(click);\n'\
4410 ' else if(c == 45)\n'\
4411 ' document.getElementById("zoomout").dispatchEvent(click);\n'\
4412 ' else if(c == 42)\n'\
4413 ' document.getElementById("zoomdef").dispatchEvent(click);\n'\
4414 ' }\n'\
4415 ' window.addEventListener("resize", function () {zoomTimeline();});\n'\
4416 ' window.addEventListener("load", function () {\n'\
4417 ' var dmesg = document.getElementById("dmesg");\n'\
4418 ' dmesg.style.width = "100%"\n'\
4419 ' dmesg.onmousedown = onMouseDown;\n'\
4420 ' document.onmouseup = onMouseUp;\n'\
4421 ' document.onkeypress = onKeyPress;\n'\
4422 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
4423 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
4424 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
4425 ' var list = document.getElementsByClassName("err");\n'\
4426 ' for (var i = 0; i < list.length; i++)\n'\
4427 ' list[i].onclick = errWindow;\n'\
4428 ' var list = document.getElementsByClassName("logbtn");\n'\
4429 ' for (var i = 0; i < list.length; i++)\n'\
4430 ' list[i].onclick = logWindow;\n'\
4431 ' list = document.getElementsByClassName("devlist");\n'\
4432 ' for (var i = 0; i < list.length; i++)\n'\
4433 ' list[i].onclick = devListWindow;\n'\
4434 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4435 ' for (var i = 0; i < dev.length; i++) {\n'\
4436 ' dev[i].onclick = deviceDetail;\n'\
4437 ' dev[i].onmouseover = deviceHover;\n'\
4438 ' dev[i].onmouseout = deviceUnhover;\n'\
4439 ' }\n'\
4440 ' var dev = dmesg.getElementsByClassName("srccall");\n'\
4441 ' for (var i = 0; i < dev.length; i++)\n'\
4442 ' dev[i].onclick = callSelect;\n'\
4443 ' zoomTimeline();\n'\
4444 ' });\n'\
4445 '</script>\n'
4446 hf.write(script_code);
4447
4448def setRuntimeSuspend(before=True):
4449 global sysvals
4450 sv = sysvals
4451 if sv.rs == 0:
4452 return
4453 if before:
4454 # runtime suspend disable or enable
4455 if sv.rs > 0:
4456 sv.rstgt, sv.rsval, sv.rsdir = 'on', 'auto', 'enabled'
4457 else:
4458 sv.rstgt, sv.rsval, sv.rsdir = 'auto', 'on', 'disabled'
4459 print('CONFIGURING RUNTIME SUSPEND...')
4460 sv.rslist = deviceInfo(sv.rstgt)
4461 for i in sv.rslist:
4462 sv.setVal(sv.rsval, i)
4463 print('runtime suspend %s on all devices (%d changed)' % (sv.rsdir, len(sv.rslist)))
4464 print('waiting 5 seconds...')
4465 time.sleep(5)
4466 else:
4467 # runtime suspend re-enable or re-disable
4468 for i in sv.rslist:
4469 sv.setVal(sv.rstgt, i)
4470 print('runtime suspend settings restored on %d devices' % len(sv.rslist))
4471
4472# Function: executeSuspend
4473# Description:
4474# Execute system suspend through the sysfs interface, then copy the output
4475# dmesg and ftrace files to the test output directory.
4476def executeSuspend():
4477 pm = ProcessMonitor()
4478 tp = sysvals.tpath
4479 fwdata = []
4480 # run these commands to prepare the system for suspend
4481 if sysvals.display:
4482 if sysvals.display > 0:
4483 print('TURN DISPLAY ON')
4484 call('xset -d :0.0 dpms force suspend', shell=True)
4485 call('xset -d :0.0 dpms force on', shell=True)
4486 else:
4487 print('TURN DISPLAY OFF')
4488 call('xset -d :0.0 dpms force suspend', shell=True)
4489 time.sleep(1)
4490 if sysvals.sync:
4491 print('SYNCING FILESYSTEMS')
4492 call('sync', shell=True)
4493 # mark the start point in the kernel ring buffer just as we start
4494 sysvals.initdmesg()
4495 # start ftrace
4496 if(sysvals.usecallgraph or sysvals.usetraceevents):
4497 print('START TRACING')
4498 sysvals.fsetVal('1', 'tracing_on')
4499 if sysvals.useprocmon:
4500 pm.start()
4501 # execute however many s/r runs requested
4502 for count in range(1,sysvals.execcount+1):
4503 # x2delay in between test runs
4504 if(count > 1 and sysvals.x2delay > 0):
4505 sysvals.fsetVal('WAIT %d' % sysvals.x2delay, 'trace_marker')
4506 time.sleep(sysvals.x2delay/1000.0)
4507 sysvals.fsetVal('WAIT END', 'trace_marker')
4508 # start message
4509 if sysvals.testcommand != '':
4510 print('COMMAND START')
4511 else:
4512 if(sysvals.rtcwake):
4513 print('SUSPEND START')
4514 else:
4515 print('SUSPEND START (press a key to resume)')
4516 # set rtcwake
4517 if(sysvals.rtcwake):
4518 print('will issue an rtcwake in %d seconds' % sysvals.rtcwaketime)
4519 sysvals.rtcWakeAlarmOn()
4520 # start of suspend trace marker
4521 if(sysvals.usecallgraph or sysvals.usetraceevents):
4522 sysvals.fsetVal('SUSPEND START', 'trace_marker')
4523 # predelay delay
4524 if(count == 1 and sysvals.predelay > 0):
4525 sysvals.fsetVal('WAIT %d' % sysvals.predelay, 'trace_marker')
4526 time.sleep(sysvals.predelay/1000.0)
4527 sysvals.fsetVal('WAIT END', 'trace_marker')
4528 # initiate suspend or command
4529 if sysvals.testcommand != '':
4530 call(sysvals.testcommand+' 2>&1', shell=True);
4531 else:
4532 mode = sysvals.suspendmode
4533 if sysvals.memmode and os.path.exists(sysvals.mempowerfile):
4534 mode = 'mem'
4535 pf = open(sysvals.mempowerfile, 'w')
4536 pf.write(sysvals.memmode)
4537 pf.close()
4538 pf = open(sysvals.powerfile, 'w')
4539 pf.write(mode)
4540 # execution will pause here
4541 try:
4542 pf.close()
4543 except:
4544 pass
4545 if(sysvals.rtcwake):
4546 sysvals.rtcWakeAlarmOff()
4547 # postdelay delay
4548 if(count == sysvals.execcount and sysvals.postdelay > 0):
4549 sysvals.fsetVal('WAIT %d' % sysvals.postdelay, 'trace_marker')
4550 time.sleep(sysvals.postdelay/1000.0)
4551 sysvals.fsetVal('WAIT END', 'trace_marker')
4552 # return from suspend
4553 print('RESUME COMPLETE')
4554 if(sysvals.usecallgraph or sysvals.usetraceevents):
4555 sysvals.fsetVal('RESUME COMPLETE', 'trace_marker')
4556 if(sysvals.suspendmode == 'mem' or sysvals.suspendmode == 'command'):
4557 fwdata.append(getFPDT(False))
4558 # stop ftrace
4559 if(sysvals.usecallgraph or sysvals.usetraceevents):
4560 if sysvals.useprocmon:
4561 pm.stop()
4562 sysvals.fsetVal('0', 'tracing_on')
4563 print('CAPTURING TRACE')
4564 op = sysvals.writeDatafileHeader(sysvals.ftracefile, fwdata)
4565 fp = open(tp+'trace', 'r')
4566 for line in fp:
4567 op.write(line)
4568 op.close()
4569 sysvals.fsetVal('', 'trace')
4570 devProps()
4571 # grab a copy of the dmesg output
4572 print('CAPTURING DMESG')
4573 sysvals.getdmesg(fwdata)
4574
4575def readFile(file):
4576 if os.path.islink(file):
4577 return os.readlink(file).split('/')[-1]
4578 else:
4579 return sysvals.getVal(file).strip()
4580
4581# Function: ms2nice
4582# Description:
4583# Print out a very concise time string in minutes and seconds
4584# Output:
4585# The time string, e.g. "1901m16s"
4586def ms2nice(val):
4587 val = int(val)
4588 h = val / 3600000
4589 m = (val / 60000) % 60
4590 s = (val / 1000) % 60
4591 if h > 0:
4592 return '%d:%02d:%02d' % (h, m, s)
4593 if m > 0:
4594 return '%02d:%02d' % (m, s)
4595 return '%ds' % s
4596
4597def yesno(val):
4598 list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
4599 'active':'A', 'suspended':'S', 'suspending':'S'}
4600 if val not in list:
4601 return ' '
4602 return list[val]
4603
4604# Function: deviceInfo
4605# Description:
4606# Detect all the USB hosts and devices currently connected and add
4607# a list of USB device names to sysvals for better timeline readability
4608def deviceInfo(output=''):
4609 if not output:
4610 print('LEGEND')
4611 print('---------------------------------------------------------------------------------------------')
4612 print(' A = async/sync PM queue (A/S) C = runtime active children')
4613 print(' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)')
4614 print(' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)')
4615 print(' U = runtime usage count')
4616 print('---------------------------------------------------------------------------------------------')
4617 print('DEVICE NAME A R S U C rACTIVE rSUSPEND')
4618 print('---------------------------------------------------------------------------------------------')
4619
4620 res = []
4621 tgtval = 'runtime_status'
4622 lines = dict()
4623 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4624 if(not re.match('.*/power', dirname) or
4625 'control' not in filenames or
4626 tgtval not in filenames):
4627 continue
4628 name = ''
4629 dirname = dirname[:-6]
4630 device = dirname.split('/')[-1]
4631 power = dict()
4632 power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
4633 # only list devices which support runtime suspend
4634 if power[tgtval] not in ['active', 'suspended', 'suspending']:
4635 continue
4636 for i in ['product', 'driver', 'subsystem']:
4637 file = '%s/%s' % (dirname, i)
4638 if os.path.exists(file):
4639 name = readFile(file)
4640 break
4641 for i in ['async', 'control', 'runtime_status', 'runtime_usage',
4642 'runtime_active_kids', 'runtime_active_time',
4643 'runtime_suspended_time']:
4644 if i in filenames:
4645 power[i] = readFile('%s/power/%s' % (dirname, i))
4646 if output:
4647 if power['control'] == output:
4648 res.append('%s/power/control' % dirname)
4649 continue
4650 lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
4651 (device[:26], name[:26],
4652 yesno(power['async']), \
4653 yesno(power['control']), \
4654 yesno(power['runtime_status']), \
4655 power['runtime_usage'], \
4656 power['runtime_active_kids'], \
4657 ms2nice(power['runtime_active_time']), \
4658 ms2nice(power['runtime_suspended_time']))
4659 for i in sorted(lines):
4660 print lines[i]
4661 return res
4662
4663# Function: devProps
4664# Description:
4665# Retrieve a list of properties for all devices in the trace log
4666def devProps(data=0):
4667 props = dict()
4668
4669 if data:
4670 idx = data.index(': ') + 2
4671 if idx >= len(data):
4672 return
4673 devlist = data[idx:].split(';')
4674 for dev in devlist:
4675 f = dev.split(',')
4676 if len(f) < 3:
4677 continue
4678 dev = f[0]
4679 props[dev] = DevProps()
4680 props[dev].altname = f[1]
4681 if int(f[2]):
4682 props[dev].async = True
4683 else:
4684 props[dev].async = False
4685 sysvals.devprops = props
4686 if sysvals.suspendmode == 'command' and 'testcommandstring' in props:
4687 sysvals.testcommand = props['testcommandstring'].altname
4688 return
4689
4690 if(os.path.exists(sysvals.ftracefile) == False):
4691 doError('%s does not exist' % sysvals.ftracefile)
4692
4693 # first get the list of devices we need properties for
4694 msghead = 'Additional data added by AnalyzeSuspend'
4695 alreadystamped = False
4696 tp = TestProps()
4697 tf = sysvals.openlog(sysvals.ftracefile, 'r')
4698 for line in tf:
4699 if msghead in line:
4700 alreadystamped = True
4701 continue
4702 # determine the trace data type (required for further parsing)
4703 m = re.match(sysvals.tracertypefmt, line)
4704 if(m):
4705 tp.setTracerType(m.group('t'))
4706 continue
4707 # parse only valid lines, if this is not one move on
4708 m = re.match(tp.ftrace_line_fmt, line)
4709 if(not m or 'device_pm_callback_start' not in line):
4710 continue
4711 m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
4712 if(not m):
4713 continue
4714 dev = m.group('d')
4715 if dev not in props:
4716 props[dev] = DevProps()
4717 tf.close()
4718
4719 if not alreadystamped and sysvals.suspendmode == 'command':
4720 out = '#\n# '+msghead+'\n# Device Properties: '
4721 out += 'testcommandstring,%s,0;' % (sysvals.testcommand)
4722 with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4723 fp.write(out+'\n')
4724 sysvals.devprops = props
4725 return
4726
4727 # now get the syspath for each of our target devices
4728 for dirname, dirnames, filenames in os.walk('/sys/devices'):
4729 if(re.match('.*/power', dirname) and 'async' in filenames):
4730 dev = dirname.split('/')[-2]
4731 if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
4732 props[dev].syspath = dirname[:-6]
4733
4734 # now fill in the properties for our target devices
4735 for dev in props:
4736 dirname = props[dev].syspath
4737 if not dirname or not os.path.exists(dirname):
4738 continue
4739 with open(dirname+'/power/async') as fp:
4740 text = fp.read()
4741 props[dev].async = False
4742 if 'enabled' in text:
4743 props[dev].async = True
4744 fields = os.listdir(dirname)
4745 if 'product' in fields:
4746 with open(dirname+'/product') as fp:
4747 props[dev].altname = fp.read()
4748 elif 'name' in fields:
4749 with open(dirname+'/name') as fp:
4750 props[dev].altname = fp.read()
4751 elif 'model' in fields:
4752 with open(dirname+'/model') as fp:
4753 props[dev].altname = fp.read()
4754 elif 'description' in fields:
4755 with open(dirname+'/description') as fp:
4756 props[dev].altname = fp.read()
4757 elif 'id' in fields:
4758 with open(dirname+'/id') as fp:
4759 props[dev].altname = fp.read()
4760 elif 'idVendor' in fields and 'idProduct' in fields:
4761 idv, idp = '', ''
4762 with open(dirname+'/idVendor') as fp:
4763 idv = fp.read().strip()
4764 with open(dirname+'/idProduct') as fp:
4765 idp = fp.read().strip()
4766 props[dev].altname = '%s:%s' % (idv, idp)
4767
4768 if props[dev].altname:
4769 out = props[dev].altname.strip().replace('\n', ' ')
4770 out = out.replace(',', ' ')
4771 out = out.replace(';', ' ')
4772 props[dev].altname = out
4773
4774 # and now write the data to the ftrace file
4775 if not alreadystamped:
4776 out = '#\n# '+msghead+'\n# Device Properties: '
4777 for dev in sorted(props):
4778 out += props[dev].out(dev)
4779 with sysvals.openlog(sysvals.ftracefile, 'a') as fp:
4780 fp.write(out+'\n')
4781
4782 sysvals.devprops = props
4783
4784# Function: getModes
4785# Description:
4786# Determine the supported power modes on this system
4787# Output:
4788# A string list of the available modes
4789def getModes():
4790 modes = []
4791 if(os.path.exists(sysvals.powerfile)):
4792 fp = open(sysvals.powerfile, 'r')
4793 modes = string.split(fp.read())
4794 fp.close()
4795 if(os.path.exists(sysvals.mempowerfile)):
4796 deep = False
4797 fp = open(sysvals.mempowerfile, 'r')
4798 for m in string.split(fp.read()):
4799 memmode = m.strip('[]')
4800 if memmode == 'deep':
4801 deep = True
4802 else:
4803 modes.append('mem-%s' % memmode)
4804 fp.close()
4805 if 'mem' in modes and not deep:
4806 modes.remove('mem')
4807 return modes
4808
4809# Function: dmidecode
4810# Description:
4811# Read the bios tables and pull out system info
4812# Arguments:
4813# mempath: /dev/mem or custom mem path
4814# fatal: True to exit on error, False to return empty dict
4815# Output:
4816# A dict object with all available key/values
4817def dmidecode(mempath, fatal=False):
4818 out = dict()
4819
4820 # the list of values to retrieve, with hardcoded (type, idx)
4821 info = {
4822 'bios-vendor': (0, 4),
4823 'bios-version': (0, 5),
4824 'bios-release-date': (0, 8),
4825 'system-manufacturer': (1, 4),
4826 'system-product-name': (1, 5),
4827 'system-version': (1, 6),
4828 'system-serial-number': (1, 7),
4829 'baseboard-manufacturer': (2, 4),
4830 'baseboard-product-name': (2, 5),
4831 'baseboard-version': (2, 6),
4832 'baseboard-serial-number': (2, 7),
4833 'chassis-manufacturer': (3, 4),
4834 'chassis-type': (3, 5),
4835 'chassis-version': (3, 6),
4836 'chassis-serial-number': (3, 7),
4837 'processor-manufacturer': (4, 7),
4838 'processor-version': (4, 16),
4839 }
4840 if(not os.path.exists(mempath)):
4841 if(fatal):
4842 doError('file does not exist: %s' % mempath)
4843 return out
4844 if(not os.access(mempath, os.R_OK)):
4845 if(fatal):
4846 doError('file is not readable: %s' % mempath)
4847 return out
4848
4849 # by default use legacy scan, but try to use EFI first
4850 memaddr = 0xf0000
4851 memsize = 0x10000
4852 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
4853 if not os.path.exists(ep) or not os.access(ep, os.R_OK):
4854 continue
4855 fp = open(ep, 'r')
4856 buf = fp.read()
4857 fp.close()
4858 i = buf.find('SMBIOS=')
4859 if i >= 0:
4860 try:
4861 memaddr = int(buf[i+7:], 16)
4862 memsize = 0x20
4863 except:
4864 continue
4865
4866 # read in the memory for scanning
4867 fp = open(mempath, 'rb')
4868 try:
4869 fp.seek(memaddr)
4870 buf = fp.read(memsize)
4871 except:
4872 if(fatal):
4873 doError('DMI table is unreachable, sorry')
4874 else:
4875 return out
4876 fp.close()
4877
4878 # search for either an SM table or DMI table
4879 i = base = length = num = 0
4880 while(i < memsize):
4881 if buf[i:i+4] == '_SM_' and i < memsize - 16:
4882 length = struct.unpack('H', buf[i+22:i+24])[0]
4883 base, num = struct.unpack('IH', buf[i+24:i+30])
4884 break
4885 elif buf[i:i+5] == '_DMI_':
4886 length = struct.unpack('H', buf[i+6:i+8])[0]
4887 base, num = struct.unpack('IH', buf[i+8:i+14])
4888 break
4889 i += 16
4890 if base == 0 and length == 0 and num == 0:
4891 if(fatal):
4892 doError('Neither SMBIOS nor DMI were found')
4893 else:
4894 return out
4895
4896 # read in the SM or DMI table
4897 fp = open(mempath, 'rb')
4898 try:
4899 fp.seek(base)
4900 buf = fp.read(length)
4901 except:
4902 if(fatal):
4903 doError('DMI table is unreachable, sorry')
4904 else:
4905 return out
4906 fp.close()
4907
4908 # scan the table for the values we want
4909 count = i = 0
4910 while(count < num and i <= len(buf) - 4):
4911 type, size, handle = struct.unpack('BBH', buf[i:i+4])
4912 n = i + size
4913 while n < len(buf) - 1:
4914 if 0 == struct.unpack('H', buf[n:n+2])[0]:
4915 break
4916 n += 1
4917 data = buf[i+size:n+2].split('\0')
4918 for name in info:
4919 itype, idxadr = info[name]
4920 if itype == type:
4921 idx = struct.unpack('B', buf[i+idxadr])[0]
4922 if idx > 0 and idx < len(data) - 1:
4923 s = data[idx-1].strip()
4924 if s and s.lower() != 'to be filled by o.e.m.':
4925 out[name] = data[idx-1]
4926 i = n + 2
4927 count += 1
4928 return out
4929
4930# Function: getFPDT
4931# Description:
4932# Read the acpi bios tables and pull out FPDT, the firmware data
4933# Arguments:
4934# output: True to output the info to stdout, False otherwise
4935def getFPDT(output):
4936 rectype = {}
4937 rectype[0] = 'Firmware Basic Boot Performance Record'
4938 rectype[1] = 'S3 Performance Table Record'
4939 prectype = {}
4940 prectype[0] = 'Basic S3 Resume Performance Record'
4941 prectype[1] = 'Basic S3 Suspend Performance Record'
4942
4943 sysvals.rootCheck(True)
4944 if(not os.path.exists(sysvals.fpdtpath)):
4945 if(output):
4946 doError('file does not exist: %s' % sysvals.fpdtpath)
4947 return False
4948 if(not os.access(sysvals.fpdtpath, os.R_OK)):
4949 if(output):
4950 doError('file is not readable: %s' % sysvals.fpdtpath)
4951 return False
4952 if(not os.path.exists(sysvals.mempath)):
4953 if(output):
4954 doError('file does not exist: %s' % sysvals.mempath)
4955 return False
4956 if(not os.access(sysvals.mempath, os.R_OK)):
4957 if(output):
4958 doError('file is not readable: %s' % sysvals.mempath)
4959 return False
4960
4961 fp = open(sysvals.fpdtpath, 'rb')
4962 buf = fp.read()
4963 fp.close()
4964
4965 if(len(buf) < 36):
4966 if(output):
4967 doError('Invalid FPDT table data, should '+\
4968 'be at least 36 bytes')
4969 return False
4970
4971 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
4972 if(output):
4973 print('')
4974 print('Firmware Performance Data Table (%s)' % table[0])
4975 print(' Signature : %s' % table[0])
4976 print(' Table Length : %u' % table[1])
4977 print(' Revision : %u' % table[2])
4978 print(' Checksum : 0x%x' % table[3])
4979 print(' OEM ID : %s' % table[4])
4980 print(' OEM Table ID : %s' % table[5])
4981 print(' OEM Revision : %u' % table[6])
4982 print(' Creator ID : %s' % table[7])
4983 print(' Creator Revision : 0x%x' % table[8])
4984 print('')
4985
4986 if(table[0] != 'FPDT'):
4987 if(output):
4988 doError('Invalid FPDT table')
4989 return False
4990 if(len(buf) <= 36):
4991 return False
4992 i = 0
4993 fwData = [0, 0]
4994 records = buf[36:]
4995 fp = open(sysvals.mempath, 'rb')
4996 while(i < len(records)):
4997 header = struct.unpack('HBB', records[i:i+4])
4998 if(header[0] not in rectype):
4999 i += header[1]
5000 continue
5001 if(header[1] != 16):
5002 i += header[1]
5003 continue
5004 addr = struct.unpack('Q', records[i+8:i+16])[0]
5005 try:
5006 fp.seek(addr)
5007 first = fp.read(8)
5008 except:
5009 if(output):
5010 print('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5011 return [0, 0]
5012 rechead = struct.unpack('4sI', first)
5013 recdata = fp.read(rechead[1]-8)
5014 if(rechead[0] == 'FBPT'):
5015 record = struct.unpack('HBBIQQQQQ', recdata)
5016 if(output):
5017 print('%s (%s)' % (rectype[header[0]], rechead[0]))
5018 print(' Reset END : %u ns' % record[4])
5019 print(' OS Loader LoadImage Start : %u ns' % record[5])
5020 print(' OS Loader StartImage Start : %u ns' % record[6])
5021 print(' ExitBootServices Entry : %u ns' % record[7])
5022 print(' ExitBootServices Exit : %u ns' % record[8])
5023 elif(rechead[0] == 'S3PT'):
5024 if(output):
5025 print('%s (%s)' % (rectype[header[0]], rechead[0]))
5026 j = 0
5027 while(j < len(recdata)):
5028 prechead = struct.unpack('HBB', recdata[j:j+4])
5029 if(prechead[0] not in prectype):
5030 continue
5031 if(prechead[0] == 0):
5032 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5033 fwData[1] = record[2]
5034 if(output):
5035 print(' %s' % prectype[prechead[0]])
5036 print(' Resume Count : %u' % \
5037 record[1])
5038 print(' FullResume : %u ns' % \
5039 record[2])
5040 print(' AverageResume : %u ns' % \
5041 record[3])
5042 elif(prechead[0] == 1):
5043 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5044 fwData[0] = record[1] - record[0]
5045 if(output):
5046 print(' %s' % prectype[prechead[0]])
5047 print(' SuspendStart : %u ns' % \
5048 record[0])
5049 print(' SuspendEnd : %u ns' % \
5050 record[1])
5051 print(' SuspendTime : %u ns' % \
5052 fwData[0])
5053 j += prechead[1]
5054 if(output):
5055 print('')
5056 i += header[1]
5057 fp.close()
5058 return fwData
5059
5060# Function: statusCheck
5061# Description:
5062# Verify that the requested command and options will work, and
5063# print the results to the terminal
5064# Output:
5065# True if the test will work, False if not
5066def statusCheck(probecheck=False):
5067 status = True
5068
5069 print('Checking this system (%s)...' % platform.node())
5070
5071 # check we have root access
5072 res = sysvals.colorText('NO (No features of this tool will work!)')
5073 if(sysvals.rootCheck(False)):
5074 res = 'YES'
5075 print(' have root access: %s' % res)
5076 if(res != 'YES'):
5077 print(' Try running this script with sudo')
5078 return False
5079
5080 # check sysfs is mounted
5081 res = sysvals.colorText('NO (No features of this tool will work!)')
5082 if(os.path.exists(sysvals.powerfile)):
5083 res = 'YES'
5084 print(' is sysfs mounted: %s' % res)
5085 if(res != 'YES'):
5086 return False
5087
5088 # check target mode is a valid mode
5089 if sysvals.suspendmode != 'command':
5090 res = sysvals.colorText('NO')
5091 modes = getModes()
5092 if(sysvals.suspendmode in modes):
5093 res = 'YES'
5094 else:
5095 status = False
5096 print(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5097 if(res == 'NO'):
5098 print(' valid power modes are: %s' % modes)
5099 print(' please choose one with -m')
5100
5101 # check if ftrace is available
5102 res = sysvals.colorText('NO')
5103 ftgood = sysvals.verifyFtrace()
5104 if(ftgood):
5105 res = 'YES'
5106 elif(sysvals.usecallgraph):
5107 status = False
5108 print(' is ftrace supported: %s' % res)
5109
5110 # check if kprobes are available
5111 res = sysvals.colorText('NO')
5112 sysvals.usekprobes = sysvals.verifyKprobes()
5113 if(sysvals.usekprobes):
5114 res = 'YES'
5115 else:
5116 sysvals.usedevsrc = False
5117 print(' are kprobes supported: %s' % res)
5118
5119 # what data source are we using
5120 res = 'DMESG'
5121 if(ftgood):
5122 sysvals.usetraceevents = True
5123 for e in sysvals.traceevents:
5124 if not os.path.exists(sysvals.epath+e):
5125 sysvals.usetraceevents = False
5126 if(sysvals.usetraceevents):
5127 res = 'FTRACE (all trace events found)'
5128 print(' timeline data source: %s' % res)
5129
5130 # check if rtcwake
5131 res = sysvals.colorText('NO')
5132 if(sysvals.rtcpath != ''):
5133 res = 'YES'
5134 elif(sysvals.rtcwake):
5135 status = False
5136 print(' is rtcwake supported: %s' % res)
5137
5138 if not probecheck:
5139 return status
5140
5141 # verify kprobes
5142 if sysvals.usekprobes:
5143 for name in sysvals.tracefuncs:
5144 sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5145 if sysvals.usedevsrc:
5146 for name in sysvals.dev_tracefuncs:
5147 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5148 sysvals.addKprobes(True)
5149
5150 return status
5151
5152# Function: doError
5153# Description:
5154# generic error function for catastrphic failures
5155# Arguments:
5156# msg: the error message to print
5157# help: True if printHelp should be called after, False otherwise
5158def doError(msg, help=False):
5159 if(help == True):
5160 printHelp()
5161 print('ERROR: %s\n') % msg
5162 sysvals.outputResult({'error':msg})
5163 sys.exit()
5164
5165# Function: getArgInt
5166# Description:
5167# pull out an integer argument from the command line with checks
5168def getArgInt(name, args, min, max, main=True):
5169 if main:
5170 try:
5171 arg = args.next()
5172 except:
5173 doError(name+': no argument supplied', True)
5174 else:
5175 arg = args
5176 try:
5177 val = int(arg)
5178 except:
5179 doError(name+': non-integer value given', True)
5180 if(val < min or val > max):
5181 doError(name+': value should be between %d and %d' % (min, max), True)
5182 return val
5183
5184# Function: getArgFloat
5185# Description:
5186# pull out a float argument from the command line with checks
5187def getArgFloat(name, args, min, max, main=True):
5188 if main:
5189 try:
5190 arg = args.next()
5191 except:
5192 doError(name+': no argument supplied', True)
5193 else:
5194 arg = args
5195 try:
5196 val = float(arg)
5197 except:
5198 doError(name+': non-numerical value given', True)
5199 if(val < min or val > max):
5200 doError(name+': value should be between %f and %f' % (min, max), True)
5201 return val
5202
5203def processData(live=False):
5204 print('PROCESSING DATA')
5205 if(sysvals.usetraceevents):
5206 testruns = parseTraceLog(live)
5207 if sysvals.dmesgfile:
5208 for data in testruns:
5209 data.extractErrorInfo()
5210 else:
5211 testruns = loadKernelLog()
5212 for data in testruns:
5213 parseKernelLog(data)
5214 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5215 appendIncompleteTraceLog(testruns)
5216 sysvals.vprint('Command:\n %s' % sysvals.cmdline)
5217 for data in testruns:
5218 data.printDetails()
5219 if sysvals.cgdump:
5220 for data in testruns:
5221 data.debugPrint()
5222 sys.exit()
5223
5224 sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
5225 createHTML(testruns)
5226 print('DONE')
5227 data = testruns[0]
5228 stamp = data.stamp
5229 stamp['suspend'], stamp['resume'] = data.getTimeValues()
5230 if data.fwValid:
5231 stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
5232 return (testruns, stamp)
5233
5234# Function: rerunTest
5235# Description:
5236# generate an output from an existing set of ftrace/dmesg logs
5237def rerunTest():
5238 if sysvals.ftracefile:
5239 doesTraceLogHaveTraceEvents()
5240 if not sysvals.dmesgfile and not sysvals.usetraceevents:
5241 doError('recreating this html output requires a dmesg file')
5242 sysvals.setOutputFile()
5243 if os.path.exists(sysvals.htmlfile):
5244 if not os.path.isfile(sysvals.htmlfile):
5245 doError('a directory already exists with this name: %s' % sysvals.htmlfile)
5246 elif not os.access(sysvals.htmlfile, os.W_OK):
5247 doError('missing permission to write to %s' % sysvals.htmlfile)
5248 testruns, stamp = processData(False)
5249 return stamp
5250
5251# Function: runTest
5252# Description:
5253# execute a suspend/resume, gather the logs, and generate the output
5254def runTest(n=0):
5255 # prepare for the test
5256 sysvals.initFtrace()
5257 sysvals.initTestOutput('suspend')
5258
5259 # execute the test
5260 executeSuspend()
5261 sysvals.cleanupFtrace()
5262 if sysvals.skiphtml:
5263 sysvals.sudouser(sysvals.testdir)
5264 return
5265 testruns, stamp = processData(True)
5266 for data in testruns:
5267 del data
5268 sysvals.sudouser(sysvals.testdir)
5269 sysvals.outputResult(stamp, n)
5270
5271def find_in_html(html, strs, div=False):
5272 for str in strs:
5273 l = len(str)
5274 i = html.find(str)
5275 if i >= 0:
5276 break
5277 if i < 0:
5278 return ''
5279 if not div:
5280 return re.search(r'[-+]?\d*\.\d+|\d+', html[i+l:i+l+50]).group()
5281 n = html[i+l:].find('</div>')
5282 if n < 0:
5283 return ''
5284 return html[i+l:i+l+n]
5285
5286# Function: runSummary
5287# Description:
5288# create a summary of tests in a sub-directory
5289def runSummary(subdir, local=True):
5290 inpath = os.path.abspath(subdir)
5291 outpath = inpath
5292 if local:
5293 outpath = os.path.abspath('.')
5294 print('Generating a summary of folder "%s"' % inpath)
5295 testruns = []
5296 for dirname, dirnames, filenames in os.walk(subdir):
5297 for filename in filenames:
5298 if(not re.match('.*.html', filename)):
5299 continue
5300 file = os.path.join(dirname, filename)
5301 html = open(file, 'r').read(10000)
5302 suspend = find_in_html(html,
5303 ['Kernel Suspend: ', 'Kernel Suspend Time: '])
5304 resume = find_in_html(html,
5305 ['Kernel Resume: ', 'Kernel Resume Time: '])
5306 line = find_in_html(html, ['<div class="stamp">'], True)
5307 stmp = line.split()
5308 if not suspend or not resume or len(stmp) < 4:
5309 continue
5310 data = {
5311 'host': stmp[0],
5312 'kernel': stmp[1],
5313 'mode': stmp[2],
5314 'time': string.join(stmp[3:], ' '),
5315 'suspend': suspend,
5316 'resume': resume,
5317 'url': os.path.relpath(file, outpath),
5318 }
5319 if len(stmp) == 7:
5320 data['kernel'] = 'unknown'
5321 data['mode'] = stmp[1]
5322 data['time'] = string.join(stmp[2:], ' ')
5323 testruns.append(data)
5324 outfile = os.path.join(outpath, 'summary.html')
5325 print('Summary file: %s' % outfile)
5326 createHTMLSummarySimple(testruns, outfile, inpath)
5327
5328# Function: checkArgBool
5329# Description:
5330# check if a boolean string value is true or false
5331def checkArgBool(name, value):
5332 if value in switchvalues:
5333 if value in switchoff:
5334 return False
5335 return True
5336 doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
5337 return False
5338
5339# Function: configFromFile
5340# Description:
5341# Configure the script via the info in a config file
5342def configFromFile(file):
5343 Config = ConfigParser.ConfigParser()
5344
5345 Config.read(file)
5346 sections = Config.sections()
5347 overridekprobes = False
5348 overridedevkprobes = False
5349 if 'Settings' in sections:
5350 for opt in Config.options('Settings'):
5351 value = Config.get('Settings', opt).lower()
5352 option = opt.lower()
5353 if(option == 'verbose'):
5354 sysvals.verbose = checkArgBool(option, value)
5355 elif(option == 'addlogs'):
5356 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
5357 elif(option == 'dev'):
5358 sysvals.usedevsrc = checkArgBool(option, value)
5359 elif(option == 'proc'):
5360 sysvals.useprocmon = checkArgBool(option, value)
5361 elif(option == 'x2'):
5362 if checkArgBool(option, value):
5363 sysvals.execcount = 2
5364 elif(option == 'callgraph'):
5365 sysvals.usecallgraph = checkArgBool(option, value)
5366 elif(option == 'override-timeline-functions'):
5367 overridekprobes = checkArgBool(option, value)
5368 elif(option == 'override-dev-timeline-functions'):
5369 overridedevkprobes = checkArgBool(option, value)
5370 elif(option == 'skiphtml'):
5371 sysvals.skiphtml = checkArgBool(option, value)
5372 elif(option == 'sync'):
5373 sysvals.sync = checkArgBool(option, value)
5374 elif(option == 'rs' or option == 'runtimesuspend'):
5375 if value in switchvalues:
5376 if value in switchoff:
5377 sysvals.rs = -1
5378 else:
5379 sysvals.rs = 1
5380 else:
5381 doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
5382 elif(option == 'display'):
5383 if value in switchvalues:
5384 if value in switchoff:
5385 sysvals.display = -1
5386 else:
5387 sysvals.display = 1
5388 else:
5389 doError('invalid value --> (%s: %s), use "on/off"' % (option, value), True)
5390 elif(option == 'gzip'):
5391 sysvals.gzip = checkArgBool(option, value)
5392 elif(option == 'cgfilter'):
5393 sysvals.setCallgraphFilter(value)
5394 elif(option == 'cgskip'):
5395 if value in switchoff:
5396 sysvals.cgskip = ''
5397 else:
5398 sysvals.cgskip = sysvals.configFile(val)
5399 if(not sysvals.cgskip):
5400 doError('%s does not exist' % sysvals.cgskip)
5401 elif(option == 'cgtest'):
5402 sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
5403 elif(option == 'cgphase'):
5404 d = Data(0)
5405 if value not in d.phases:
5406 doError('invalid phase --> (%s: %s), valid phases are %s'\
5407 % (option, value, d.phases), True)
5408 sysvals.cgphase = value
5409 elif(option == 'fadd'):
5410 file = sysvals.configFile(value)
5411 if(not file):
5412 doError('%s does not exist' % value)
5413 sysvals.addFtraceFilterFunctions(file)
5414 elif(option == 'result'):
5415 sysvals.result = value
5416 elif(option == 'multi'):
5417 nums = value.split()
5418 if len(nums) != 2:
5419 doError('multi requires 2 integers (exec_count and delay)', True)
5420 sysvals.multitest['run'] = True
5421 sysvals.multitest['count'] = getArgInt('multi: n d (exec count)', nums[0], 2, 1000000, False)
5422 sysvals.multitest['delay'] = getArgInt('multi: n d (delay between tests)', nums[1], 0, 3600, False)
5423 elif(option == 'devicefilter'):
5424 sysvals.setDeviceFilter(value)
5425 elif(option == 'expandcg'):
5426 sysvals.cgexp = checkArgBool(option, value)
5427 elif(option == 'srgap'):
5428 if checkArgBool(option, value):
5429 sysvals.srgap = 5
5430 elif(option == 'mode'):
5431 sysvals.suspendmode = value
5432 elif(option == 'command' or option == 'cmd'):
5433 sysvals.testcommand = value
5434 elif(option == 'x2delay'):
5435 sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
5436 elif(option == 'predelay'):
5437 sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
5438 elif(option == 'postdelay'):
5439 sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
5440 elif(option == 'maxdepth'):
5441 sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
5442 elif(option == 'rtcwake'):
5443 if value in switchoff:
5444 sysvals.rtcwake = False
5445 else:
5446 sysvals.rtcwake = True
5447 sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
5448 elif(option == 'timeprec'):
5449 sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
5450 elif(option == 'mindev'):
5451 sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
5452 elif(option == 'callloop-maxgap'):
5453 sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
5454 elif(option == 'callloop-maxlen'):
5455 sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
5456 elif(option == 'mincg'):
5457 sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
5458 elif(option == 'bufsize'):
5459 sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
5460 elif(option == 'output-dir'):
5461 sysvals.outdir = sysvals.setOutputFolder(value)
5462
5463 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
5464 doError('No command supplied for mode "command"')
5465
5466 # compatibility errors
5467 if sysvals.usedevsrc and sysvals.usecallgraph:
5468 doError('-dev is not compatible with -f')
5469 if sysvals.usecallgraph and sysvals.useprocmon:
5470 doError('-proc is not compatible with -f')
5471
5472 if overridekprobes:
5473 sysvals.tracefuncs = dict()
5474 if overridedevkprobes:
5475 sysvals.dev_tracefuncs = dict()
5476
5477 kprobes = dict()
5478 kprobesec = 'dev_timeline_functions_'+platform.machine()
5479 if kprobesec in sections:
5480 for name in Config.options(kprobesec):
5481 text = Config.get(kprobesec, name)
5482 kprobes[name] = (text, True)
5483 kprobesec = 'timeline_functions_'+platform.machine()
5484 if kprobesec in sections:
5485 for name in Config.options(kprobesec):
5486 if name in kprobes:
5487 doError('Duplicate timeline function found "%s"' % (name))
5488 text = Config.get(kprobesec, name)
5489 kprobes[name] = (text, False)
5490
5491 for name in kprobes:
5492 function = name
5493 format = name
5494 color = ''
5495 args = dict()
5496 text, dev = kprobes[name]
5497 data = text.split()
5498 i = 0
5499 for val in data:
5500 # bracketted strings are special formatting, read them separately
5501 if val[0] == '[' and val[-1] == ']':
5502 for prop in val[1:-1].split(','):
5503 p = prop.split('=')
5504 if p[0] == 'color':
5505 try:
5506 color = int(p[1], 16)
5507 color = '#'+p[1]
5508 except:
5509 color = p[1]
5510 continue
5511 # first real arg should be the format string
5512 if i == 0:
5513 format = val
5514 # all other args are actual function args
5515 else:
5516 d = val.split('=')
5517 args[d[0]] = d[1]
5518 i += 1
5519 if not function or not format:
5520 doError('Invalid kprobe: %s' % name)
5521 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
5522 if arg not in args:
5523 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
5524 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
5525 doError('Duplicate timeline function found "%s"' % (name))
5526
5527 kp = {
5528 'name': name,
5529 'func': function,
5530 'format': format,
5531 sysvals.archargs: args
5532 }
5533 if color:
5534 kp['color'] = color
5535 if dev:
5536 sysvals.dev_tracefuncs[name] = kp
5537 else:
5538 sysvals.tracefuncs[name] = kp
5539
5540# Function: printHelp
5541# Description:
5542# print out the help text
5543def printHelp():
5544 print('')
5545 print('%s v%s' % (sysvals.title, sysvals.version))
5546 print('Usage: sudo sleepgraph <options> <commands>')
5547 print('')
5548 print('Description:')
5549 print(' This tool is designed to assist kernel and OS developers in optimizing')
5550 print(' their linux stack\'s suspend/resume time. Using a kernel image built')
5551 print(' with a few extra options enabled, the tool will execute a suspend and')
5552 print(' capture dmesg and ftrace data until resume is complete. This data is')
5553 print(' transformed into a device timeline and an optional callgraph to give')
5554 print(' a detailed view of which devices/subsystems are taking the most')
5555 print(' time in suspend/resume.')
5556 print('')
5557 print(' If no specific command is given, the default behavior is to initiate')
5558 print(' a suspend/resume and capture the dmesg/ftrace output as an html timeline.')
5559 print('')
5560 print(' Generates output files in subdirectory: suspend-yymmdd-HHMMSS')
5561 print(' HTML output: <hostname>_<mode>.html')
5562 print(' raw dmesg output: <hostname>_<mode>_dmesg.txt')
5563 print(' raw ftrace output: <hostname>_<mode>_ftrace.txt')
5564 print('')
5565 print('Options:')
5566 print(' -h Print this help text')
5567 print(' -v Print the current tool version')
5568 print(' -config fn Pull arguments and config options from file fn')
5569 print(' -verbose Print extra information during execution and analysis')
5570 print(' -m mode Mode to initiate for suspend (default: %s)') % (sysvals.suspendmode)
5571 print(' -o name Overrides the output subdirectory name when running a new test')
5572 print(' default: suspend-{date}-{time}')
5573 print(' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)')
5574 print(' -addlogs Add the dmesg and ftrace logs to the html output')
5575 print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)')
5576 print(' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)')
5577 print(' -result fn Export a results table to a text file for parsing.')
5578 print(' [testprep]')
5579 print(' -sync Sync the filesystems before starting the test')
5580 print(' -rs on/off Enable/disable runtime suspend for all devices, restore all after test')
5581 print(' -display on/off Turn the display on or off for the test')
5582 print(' [advanced]')
5583 print(' -gzip Gzip the trace and dmesg logs to save space')
5584 print(' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"')
5585 print(' -proc Add usermode process info into the timeline (default: disabled)')
5586 print(' -dev Add kernel function calls and threads to the timeline (default: disabled)')
5587 print(' -x2 Run two suspend/resumes back to back (default: disabled)')
5588 print(' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)')
5589 print(' -predelay t Include t ms delay before 1st suspend (default: 0 ms)')
5590 print(' -postdelay t Include t ms delay after last resume (default: 0 ms)')
5591 print(' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)')
5592 print(' -multi n d Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
5593 print(' be created in a new subdirectory with a summary page.')
5594 print(' [debug]')
5595 print(' -f Use ftrace to create device callgraphs (default: disabled)')
5596 print(' -maxdepth N limit the callgraph data to N call levels (default: 0=all)')
5597 print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)')
5598 print(' -fadd file Add functions to be graphed in the timeline from a list in a text file')
5599 print(' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names')
5600 print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)')
5601 print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)')
5602 print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)')
5603 print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)')
5604 print(' -cgfilter S Filter the callgraph output in the timeline')
5605 print(' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)')
5606 print(' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)')
5607 print('')
5608 print('Other commands:')
5609 print(' -modes List available suspend modes')
5610 print(' -status Test to see if the system is enabled to run this tool')
5611 print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table')
5612 print(' -sysinfo Print out system info extracted from BIOS')
5613 print(' -devinfo Print out the pm settings of all devices which support runtime suspend')
5614 print(' -flist Print the list of functions currently being captured in ftrace')
5615 print(' -flistall Print all functions capable of being captured in ftrace')
5616 print(' -summary directory Create a summary of all test in this dir')
5617 print(' [redo]')
5618 print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)')
5619 print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)')
5620 print('')
5621 return True
5622
5623# ----------------- MAIN --------------------
5624# exec start (skipped if script is loaded as library)
5625if __name__ == '__main__':
5626 cmd = ''
5627 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-devinfo', '-status']
5628 if '-f' in sys.argv:
5629 sysvals.cgskip = sysvals.configFile('cgskip.txt')
5630 # loop through the command line arguments
5631 args = iter(sys.argv[1:])
5632 for arg in args:
5633 if(arg == '-m'):
5634 try:
5635 val = args.next()
5636 except:
5637 doError('No mode supplied', True)
5638 if val == 'command' and not sysvals.testcommand:
5639 doError('No command supplied for mode "command"', True)
5640 sysvals.suspendmode = val
5641 elif(arg in simplecmds):
5642 cmd = arg[1:]
5643 elif(arg == '-h'):
5644 printHelp()
5645 sys.exit()
5646 elif(arg == '-v'):
5647 print("Version %s" % sysvals.version)
5648 sys.exit()
5649 elif(arg == '-x2'):
5650 sysvals.execcount = 2
5651 elif(arg == '-x2delay'):
5652 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
5653 elif(arg == '-predelay'):
5654 sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
5655 elif(arg == '-postdelay'):
5656 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
5657 elif(arg == '-f'):
5658 sysvals.usecallgraph = True
5659 elif(arg == '-skiphtml'):
5660 sysvals.skiphtml = True
5661 elif(arg == '-cgdump'):
5662 sysvals.cgdump = True
5663 elif(arg == '-addlogs'):
5664 sysvals.dmesglog = sysvals.ftracelog = True
5665 elif(arg == '-verbose'):
5666 sysvals.verbose = True
5667 elif(arg == '-proc'):
5668 sysvals.useprocmon = True
5669 elif(arg == '-dev'):
5670 sysvals.usedevsrc = True
5671 elif(arg == '-sync'):
5672 sysvals.sync = True
5673 elif(arg == '-gzip'):
5674 sysvals.gzip = True
5675 elif(arg == '-rs'):
5676 try:
5677 val = args.next()
5678 except:
5679 doError('-rs requires "enable" or "disable"', True)
5680 if val.lower() in switchvalues:
5681 if val.lower() in switchoff:
5682 sysvals.rs = -1
5683 else:
5684 sysvals.rs = 1
5685 else:
5686 doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
5687 elif(arg == '-display'):
5688 try:
5689 val = args.next()
5690 except:
5691 doError('-display requires "on" or "off"', True)
5692 if val.lower() in switchvalues:
5693 if val.lower() in switchoff:
5694 sysvals.display = -1
5695 else:
5696 sysvals.display = 1
5697 else:
5698 doError('invalid option: %s, use "on/off"' % val, True)
5699 elif(arg == '-maxdepth'):
5700 sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
5701 elif(arg == '-rtcwake'):
5702 try:
5703 val = args.next()
5704 except:
5705 doError('No rtcwake time supplied', True)
5706 if val.lower() in switchoff:
5707 sysvals.rtcwake = False
5708 else:
5709 sysvals.rtcwake = True
5710 sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
5711 elif(arg == '-timeprec'):
5712 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
5713 elif(arg == '-mindev'):
5714 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
5715 elif(arg == '-mincg'):
5716 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
5717 elif(arg == '-bufsize'):
5718 sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
5719 elif(arg == '-cgtest'):
5720 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
5721 elif(arg == '-cgphase'):
5722 try:
5723 val = args.next()
5724 except:
5725 doError('No phase name supplied', True)
5726 d = Data(0)
5727 if val not in d.phases:
5728 doError('invalid phase --> (%s: %s), valid phases are %s'\
5729 % (arg, val, d.phases), True)
5730 sysvals.cgphase = val
5731 elif(arg == '-cgfilter'):
5732 try:
5733 val = args.next()
5734 except:
5735 doError('No callgraph functions supplied', True)
5736 sysvals.setCallgraphFilter(val)
5737 elif(arg == '-cgskip'):
5738 try:
5739 val = args.next()
5740 except:
5741 doError('No file supplied', True)
5742 if val.lower() in switchoff:
5743 sysvals.cgskip = ''
5744 else:
5745 sysvals.cgskip = sysvals.configFile(val)
5746 if(not sysvals.cgskip):
5747 doError('%s does not exist' % sysvals.cgskip)
5748 elif(arg == '-callloop-maxgap'):
5749 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
5750 elif(arg == '-callloop-maxlen'):
5751 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
5752 elif(arg == '-cmd'):
5753 try:
5754 val = args.next()
5755 except:
5756 doError('No command string supplied', True)
5757 sysvals.testcommand = val
5758 sysvals.suspendmode = 'command'
5759 elif(arg == '-expandcg'):
5760 sysvals.cgexp = True
5761 elif(arg == '-srgap'):
5762 sysvals.srgap = 5
5763 elif(arg == '-multi'):
5764 sysvals.multitest['run'] = True
5765 sysvals.multitest['count'] = getArgInt('-multi n d (exec count)', args, 2, 1000000)
5766 sysvals.multitest['delay'] = getArgInt('-multi n d (delay between tests)', args, 0, 3600)
5767 elif(arg == '-o'):
5768 try:
5769 val = args.next()
5770 except:
5771 doError('No subdirectory name supplied', True)
5772 sysvals.outdir = sysvals.setOutputFolder(val)
5773 elif(arg == '-config'):
5774 try:
5775 val = args.next()
5776 except:
5777 doError('No text file supplied', True)
5778 file = sysvals.configFile(val)
5779 if(not file):
5780 doError('%s does not exist' % val)
5781 configFromFile(file)
5782 elif(arg == '-fadd'):
5783 try:
5784 val = args.next()
5785 except:
5786 doError('No text file supplied', True)
5787 file = sysvals.configFile(val)
5788 if(not file):
5789 doError('%s does not exist' % val)
5790 sysvals.addFtraceFilterFunctions(file)
5791 elif(arg == '-dmesg'):
5792 try:
5793 val = args.next()
5794 except:
5795 doError('No dmesg file supplied', True)
5796 sysvals.notestrun = True
5797 sysvals.dmesgfile = val
5798 if(os.path.exists(sysvals.dmesgfile) == False):
5799 doError('%s does not exist' % sysvals.dmesgfile)
5800 elif(arg == '-ftrace'):
5801 try:
5802 val = args.next()
5803 except:
5804 doError('No ftrace file supplied', True)
5805 sysvals.notestrun = True
5806 sysvals.ftracefile = val
5807 if(os.path.exists(sysvals.ftracefile) == False):
5808 doError('%s does not exist' % sysvals.ftracefile)
5809 elif(arg == '-summary'):
5810 try:
5811 val = args.next()
5812 except:
5813 doError('No directory supplied', True)
5814 cmd = 'summary'
5815 sysvals.outdir = val
5816 sysvals.notestrun = True
5817 if(os.path.isdir(val) == False):
5818 doError('%s is not accesible' % val)
5819 elif(arg == '-filter'):
5820 try:
5821 val = args.next()
5822 except:
5823 doError('No devnames supplied', True)
5824 sysvals.setDeviceFilter(val)
5825 elif(arg == '-result'):
5826 try:
5827 val = args.next()
5828 except:
5829 doError('No result file supplied', True)
5830 sysvals.result = val
5831 else:
5832 doError('Invalid argument: '+arg, True)
5833
5834 # compatibility errors
5835 if(sysvals.usecallgraph and sysvals.usedevsrc):
5836 doError('-dev is not compatible with -f')
5837 if(sysvals.usecallgraph and sysvals.useprocmon):
5838 doError('-proc is not compatible with -f')
5839
5840 if sysvals.usecallgraph and sysvals.cgskip:
5841 sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
5842 sysvals.setCallgraphBlacklist(sysvals.cgskip)
5843
5844 # callgraph size cannot exceed device size
5845 if sysvals.mincglen < sysvals.mindevlen:
5846 sysvals.mincglen = sysvals.mindevlen
5847
5848 # remove existing buffers before calculating memory
5849 if(sysvals.usecallgraph or sysvals.usedevsrc):
5850 sysvals.fsetVal('16', 'buffer_size_kb')
5851 sysvals.cpuInfo()
5852
5853 # just run a utility command and exit
5854 if(cmd != ''):
5855 if(cmd == 'status'):
5856 statusCheck(True)
5857 elif(cmd == 'fpdt'):
5858 getFPDT(True)
5859 elif(cmd == 'sysinfo'):
5860 sysvals.printSystemInfo(True)
5861 elif(cmd == 'devinfo'):
5862 deviceInfo()
5863 elif(cmd == 'modes'):
5864 print getModes()
5865 elif(cmd == 'flist'):
5866 sysvals.getFtraceFilterFunctions(True)
5867 elif(cmd == 'flistall'):
5868 sysvals.getFtraceFilterFunctions(False)
5869 elif(cmd == 'summary'):
5870 runSummary(sysvals.outdir, True)
5871 sys.exit()
5872
5873 # if instructed, re-analyze existing data files
5874 if(sysvals.notestrun):
5875 stamp = rerunTest()
5876 sysvals.outputResult(stamp)
5877 sys.exit()
5878
5879 # verify that we can run a test
5880 if(not statusCheck()):
5881 doError('Check FAILED, aborting the test run!')
5882
5883 # extract mem modes and convert
5884 mode = sysvals.suspendmode
5885 if 'mem' == mode[:3]:
5886 if '-' in mode:
5887 memmode = mode.split('-')[-1]
5888 else:
5889 memmode = 'deep'
5890 if memmode == 'shallow':
5891 mode = 'standby'
5892 elif memmode == 's2idle':
5893 mode = 'freeze'
5894 else:
5895 mode = 'mem'
5896 sysvals.memmode = memmode
5897 sysvals.suspendmode = mode
5898
5899 sysvals.systemInfo(dmidecode(sysvals.mempath))
5900
5901 setRuntimeSuspend(True)
5902 if sysvals.display:
5903 call('xset -d :0.0 dpms 0 0 0', shell=True)
5904 call('xset -d :0.0 s off', shell=True)
5905 if sysvals.multitest['run']:
5906 # run multiple tests in a separate subdirectory
5907 if not sysvals.outdir:
5908 s = 'suspend-x%d' % sysvals.multitest['count']
5909 sysvals.outdir = datetime.now().strftime(s+'-%y%m%d-%H%M%S')
5910 if not os.path.isdir(sysvals.outdir):
5911 os.mkdir(sysvals.outdir)
5912 for i in range(sysvals.multitest['count']):
5913 if(i != 0):
5914 print('Waiting %d seconds...' % (sysvals.multitest['delay']))
5915 time.sleep(sysvals.multitest['delay'])
5916 print('TEST (%d/%d) START' % (i+1, sysvals.multitest['count']))
5917 fmt = 'suspend-%y%m%d-%H%M%S'
5918 sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
5919 runTest(i+1)
5920 print('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count']))
5921 sysvals.logmsg = ''
5922 if not sysvals.skiphtml:
5923 runSummary(sysvals.outdir, False)
5924 sysvals.sudouser(sysvals.outdir)
5925 else:
5926 if sysvals.outdir:
5927 sysvals.testdir = sysvals.outdir
5928 # run the test in the current directory
5929 runTest()
5930 if sysvals.display:
5931 call('xset -d :0.0 s reset', shell=True)
5932 setRuntimeSuspend(False)