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