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