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