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