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