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
69def pprint(msg):
70 print(msg)
71 sys.stdout.flush()
72
73def ascii(text):
74 return text.decode('ascii', 'ignore')
75
76# ----------------- CLASSES --------------------
77
78# Class: SystemValues
79# Description:
80# A global, single-instance container used to
81# store system values and test parameters
82class SystemValues:
83 title = 'SleepGraph'
84 version = '5.8'
85 ansi = False
86 rs = 0
87 display = ''
88 gzip = False
89 sync = False
90 wifi = False
91 verbose = False
92 testlog = True
93 dmesglog = True
94 ftracelog = False
95 acpidebug = True
96 tstat = True
97 mindevlen = 0.0001
98 mincglen = 0.0
99 cgphase = ''
100 cgtest = -1
101 cgskip = ''
102 maxfail = 0
103 multitest = {'run': False, 'count': 1000000, 'delay': 0}
104 max_graph_depth = 0
105 callloopmaxgap = 0.0001
106 callloopmaxlen = 0.005
107 bufsize = 0
108 cpucount = 0
109 memtotal = 204800
110 memfree = 204800
111 srgap = 0
112 cgexp = False
113 testdir = ''
114 outdir = ''
115 tpath = '/sys/kernel/debug/tracing/'
116 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
117 epath = '/sys/kernel/debug/tracing/events/power/'
118 pmdpath = '/sys/power/pm_debug_messages'
119 acpipath='/sys/module/acpi/parameters/debug_level'
120 traceevents = [
121 'suspend_resume',
122 'wakeup_source_activate',
123 'wakeup_source_deactivate',
124 'device_pm_callback_end',
125 'device_pm_callback_start'
126 ]
127 logmsg = ''
128 testcommand = ''
129 mempath = '/dev/mem'
130 powerfile = '/sys/power/state'
131 mempowerfile = '/sys/power/mem_sleep'
132 diskpowerfile = '/sys/power/disk'
133 suspendmode = 'mem'
134 memmode = ''
135 diskmode = ''
136 hostname = 'localhost'
137 prefix = 'test'
138 teststamp = ''
139 sysstamp = ''
140 dmesgstart = 0.0
141 dmesgfile = ''
142 ftracefile = ''
143 htmlfile = 'output.html'
144 result = ''
145 rtcwake = True
146 rtcwaketime = 15
147 rtcpath = ''
148 devicefilter = []
149 cgfilter = []
150 stamp = 0
151 execcount = 1
152 x2delay = 0
153 skiphtml = False
154 usecallgraph = False
155 ftopfunc = 'pm_suspend'
156 ftop = False
157 usetraceevents = False
158 usetracemarkers = True
159 usekprobes = True
160 usedevsrc = False
161 useprocmon = False
162 notestrun = False
163 cgdump = False
164 devdump = False
165 mixedphaseheight = True
166 devprops = dict()
167 cfgdef = dict()
168 platinfo = []
169 predelay = 0
170 postdelay = 0
171 tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
172 tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
173 tracefuncs = {
174 'sys_sync': {},
175 'ksys_sync': {},
176 '__pm_notifier_call_chain': {},
177 'pm_prepare_console': {},
178 'pm_notifier_call_chain': {},
179 'freeze_processes': {},
180 'freeze_kernel_threads': {},
181 'pm_restrict_gfp_mask': {},
182 'acpi_suspend_begin': {},
183 'acpi_hibernation_begin': {},
184 'acpi_hibernation_enter': {},
185 'acpi_hibernation_leave': {},
186 'acpi_pm_freeze': {},
187 'acpi_pm_thaw': {},
188 'acpi_s2idle_end': {},
189 'acpi_s2idle_sync': {},
190 'acpi_s2idle_begin': {},
191 'acpi_s2idle_prepare': {},
192 'acpi_s2idle_prepare_late': {},
193 'acpi_s2idle_wake': {},
194 'acpi_s2idle_wakeup': {},
195 'acpi_s2idle_restore': {},
196 'acpi_s2idle_restore_early': {},
197 'hibernate_preallocate_memory': {},
198 'create_basic_memory_bitmaps': {},
199 'swsusp_write': {},
200 'suspend_console': {},
201 'acpi_pm_prepare': {},
202 'syscore_suspend': {},
203 'arch_enable_nonboot_cpus_end': {},
204 'syscore_resume': {},
205 'acpi_pm_finish': {},
206 'resume_console': {},
207 'acpi_pm_end': {},
208 'pm_restore_gfp_mask': {},
209 'thaw_processes': {},
210 'pm_restore_console': {},
211 'CPU_OFF': {
212 'func':'_cpu_down',
213 'args_x86_64': {'cpu':'%di:s32'},
214 'format': 'CPU_OFF[{cpu}]'
215 },
216 'CPU_ON': {
217 'func':'_cpu_up',
218 'args_x86_64': {'cpu':'%di:s32'},
219 'format': 'CPU_ON[{cpu}]'
220 },
221 }
222 dev_tracefuncs = {
223 # general wait/delay/sleep
224 'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
225 'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
226 'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
227 'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
228 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
229 'acpi_os_stall': {'ub': 1},
230 'rt_mutex_slowlock': {'ub': 1},
231 # ACPI
232 'acpi_resume_power_resources': {},
233 'acpi_ps_execute_method': { 'args_x86_64': {
234 'fullpath':'+0(+40(%di)):string',
235 }},
236 # mei_me
237 'mei_reset': {},
238 # filesystem
239 'ext4_sync_fs': {},
240 # 80211
241 'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
242 'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
243 'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
244 'iwlagn_mac_start': {},
245 'iwlagn_alloc_bcast_station': {},
246 'iwl_trans_pcie_start_hw': {},
247 'iwl_trans_pcie_start_fw': {},
248 'iwl_run_init_ucode': {},
249 'iwl_load_ucode_wait_alive': {},
250 'iwl_alive_start': {},
251 'iwlagn_mac_stop': {},
252 'iwlagn_mac_suspend': {},
253 'iwlagn_mac_resume': {},
254 'iwlagn_mac_add_interface': {},
255 'iwlagn_mac_remove_interface': {},
256 'iwlagn_mac_change_interface': {},
257 'iwlagn_mac_config': {},
258 'iwlagn_configure_filter': {},
259 'iwlagn_mac_hw_scan': {},
260 'iwlagn_bss_info_changed': {},
261 'iwlagn_mac_channel_switch': {},
262 'iwlagn_mac_flush': {},
263 # ATA
264 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
265 # i915
266 'i915_gem_resume': {},
267 'i915_restore_state': {},
268 'intel_opregion_setup': {},
269 'g4x_pre_enable_dp': {},
270 'vlv_pre_enable_dp': {},
271 'chv_pre_enable_dp': {},
272 'g4x_enable_dp': {},
273 'vlv_enable_dp': {},
274 'intel_hpd_init': {},
275 'intel_opregion_register': {},
276 'intel_dp_detect': {},
277 'intel_hdmi_detect': {},
278 'intel_opregion_init': {},
279 'intel_fbdev_set_suspend': {},
280 }
281 infocmds = [
282 [0, 'kparams', 'cat', '/proc/cmdline'],
283 [0, 'mcelog', 'mcelog'],
284 [0, 'pcidevices', 'lspci', '-tv'],
285 [0, 'usbdevices', 'lsusb', '-t'],
286 [1, 'interrupts', 'cat', '/proc/interrupts'],
287 [1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
288 [2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
289 [2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
290 [2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
291 [2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
292 ]
293 cgblacklist = []
294 kprobes = dict()
295 timeformat = '%.3f'
296 cmdline = '%s %s' % \
297 (os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
298 sudouser = ''
299 def __init__(self):
300 self.archargs = 'args_'+platform.machine()
301 self.hostname = platform.node()
302 if(self.hostname == ''):
303 self.hostname = 'localhost'
304 rtc = "rtc0"
305 if os.path.exists('/dev/rtc'):
306 rtc = os.readlink('/dev/rtc')
307 rtc = '/sys/class/rtc/'+rtc
308 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
309 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
310 self.rtcpath = rtc
311 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
312 self.ansi = True
313 self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
314 if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
315 os.environ['SUDO_USER']:
316 self.sudouser = os.environ['SUDO_USER']
317 def resetlog(self):
318 self.logmsg = ''
319 self.platinfo = []
320 def vprint(self, msg):
321 self.logmsg += msg+'\n'
322 if self.verbose or msg.startswith('WARNING:'):
323 pprint(msg)
324 def signalHandler(self, signum, frame):
325 if not self.result:
326 return
327 signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
328 msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
329 self.outputResult({'error':msg})
330 sys.exit(3)
331 def signalHandlerInit(self):
332 capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
333 'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM']
334 self.signames = dict()
335 for i in capture:
336 s = 'SIG'+i
337 try:
338 signum = getattr(signal, s)
339 signal.signal(signum, self.signalHandler)
340 except:
341 continue
342 self.signames[signum] = s
343 def rootCheck(self, fatal=True):
344 if(os.access(self.powerfile, os.W_OK)):
345 return True
346 if fatal:
347 msg = 'This command requires sysfs mount and root access'
348 pprint('ERROR: %s\n' % msg)
349 self.outputResult({'error':msg})
350 sys.exit(1)
351 return False
352 def rootUser(self, fatal=False):
353 if 'USER' in os.environ and os.environ['USER'] == 'root':
354 return True
355 if fatal:
356 msg = 'This command must be run as root'
357 pprint('ERROR: %s\n' % msg)
358 self.outputResult({'error':msg})
359 sys.exit(1)
360 return False
361 def usable(self, file):
362 return (os.path.exists(file) and os.path.getsize(file) > 0)
363 def getExec(self, cmd):
364 try:
365 fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
366 out = ascii(fp.read()).strip()
367 fp.close()
368 except:
369 out = ''
370 if out:
371 return out
372 for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
373 '/usr/local/sbin', '/usr/local/bin']:
374 cmdfull = os.path.join(path, cmd)
375 if os.path.exists(cmdfull):
376 return cmdfull
377 return out
378 def setPrecision(self, num):
379 if num < 0 or num > 6:
380 return
381 self.timeformat = '%.{0}f'.format(num)
382 def setOutputFolder(self, value):
383 args = dict()
384 n = datetime.now()
385 args['date'] = n.strftime('%y%m%d')
386 args['time'] = n.strftime('%H%M%S')
387 args['hostname'] = args['host'] = self.hostname
388 args['mode'] = self.suspendmode
389 return value.format(**args)
390 def setOutputFile(self):
391 if self.dmesgfile != '':
392 m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
393 if(m):
394 self.htmlfile = m.group('name')+'.html'
395 if self.ftracefile != '':
396 m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
397 if(m):
398 self.htmlfile = m.group('name')+'.html'
399 def systemInfo(self, info):
400 p = m = ''
401 if 'baseboard-manufacturer' in info:
402 m = info['baseboard-manufacturer']
403 elif 'system-manufacturer' in info:
404 m = info['system-manufacturer']
405 if 'system-product-name' in info:
406 p = info['system-product-name']
407 elif 'baseboard-product-name' in info:
408 p = info['baseboard-product-name']
409 if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
410 p = info['baseboard-product-name']
411 c = info['processor-version'] if 'processor-version' in info else ''
412 b = info['bios-version'] if 'bios-version' in info else ''
413 r = info['bios-release-date'] if 'bios-release-date' in info else ''
414 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
415 (m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
416 def printSystemInfo(self, fatal=False):
417 self.rootCheck(True)
418 out = dmidecode(self.mempath, fatal)
419 if len(out) < 1:
420 return
421 fmt = '%-24s: %s'
422 for name in sorted(out):
423 print(fmt % (name, out[name]))
424 print(fmt % ('cpucount', ('%d' % self.cpucount)))
425 print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
426 print(fmt % ('memfree', ('%d kB' % self.memfree)))
427 def cpuInfo(self):
428 self.cpucount = 0
429 fp = open('/proc/cpuinfo', 'r')
430 for line in fp:
431 if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
432 self.cpucount += 1
433 fp.close()
434 fp = open('/proc/meminfo', 'r')
435 for line in fp:
436 m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
437 if m:
438 self.memtotal = int(m.group('sz'))
439 m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
440 if m:
441 self.memfree = int(m.group('sz'))
442 fp.close()
443 def initTestOutput(self, name):
444 self.prefix = self.hostname
445 v = open('/proc/version', 'r').read().strip()
446 kver = v.split()[2]
447 fmt = name+'-%m%d%y-%H%M%S'
448 testtime = datetime.now().strftime(fmt)
449 self.teststamp = \
450 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
451 ext = ''
452 if self.gzip:
453 ext = '.gz'
454 self.dmesgfile = \
455 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
456 self.ftracefile = \
457 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
458 self.htmlfile = \
459 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
460 if not os.path.isdir(self.testdir):
461 os.makedirs(self.testdir)
462 self.sudoUserchown(self.testdir)
463 def getValueList(self, value):
464 out = []
465 for i in value.split(','):
466 if i.strip():
467 out.append(i.strip())
468 return out
469 def setDeviceFilter(self, value):
470 self.devicefilter = self.getValueList(value)
471 def setCallgraphFilter(self, value):
472 self.cgfilter = self.getValueList(value)
473 def skipKprobes(self, value):
474 for k in self.getValueList(value):
475 if k in self.tracefuncs:
476 del self.tracefuncs[k]
477 if k in self.dev_tracefuncs:
478 del self.dev_tracefuncs[k]
479 def setCallgraphBlacklist(self, file):
480 self.cgblacklist = self.listFromFile(file)
481 def rtcWakeAlarmOn(self):
482 call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
483 nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
484 if nowtime:
485 nowtime = int(nowtime)
486 else:
487 # if hardware time fails, use the software time
488 nowtime = int(datetime.now().strftime('%s'))
489 alarm = nowtime + self.rtcwaketime
490 call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
491 def rtcWakeAlarmOff(self):
492 call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
493 def initdmesg(self):
494 # get the latest time stamp from the dmesg log
495 lines = Popen('dmesg', stdout=PIPE).stdout.readlines()
496 ktime = '0'
497 for line in reversed(lines):
498 line = ascii(line).replace('\r\n', '')
499 idx = line.find('[')
500 if idx > 1:
501 line = line[idx:]
502 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
503 if(m):
504 ktime = m.group('ktime')
505 break
506 self.dmesgstart = float(ktime)
507 def getdmesg(self, testdata):
508 op = self.writeDatafileHeader(self.dmesgfile, testdata)
509 # store all new dmesg lines since initdmesg was called
510 fp = Popen('dmesg', stdout=PIPE).stdout
511 for line in fp:
512 line = ascii(line).replace('\r\n', '')
513 idx = line.find('[')
514 if idx > 1:
515 line = line[idx:]
516 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
517 if(not m):
518 continue
519 ktime = float(m.group('ktime'))
520 if ktime > self.dmesgstart:
521 op.write(line)
522 fp.close()
523 op.close()
524 def listFromFile(self, file):
525 list = []
526 fp = open(file)
527 for i in fp.read().split('\n'):
528 i = i.strip()
529 if i and i[0] != '#':
530 list.append(i)
531 fp.close()
532 return list
533 def addFtraceFilterFunctions(self, file):
534 for i in self.listFromFile(file):
535 if len(i) < 2:
536 continue
537 self.tracefuncs[i] = dict()
538 def getFtraceFilterFunctions(self, current):
539 self.rootCheck(True)
540 if not current:
541 call('cat '+self.tpath+'available_filter_functions', shell=True)
542 return
543 master = self.listFromFile(self.tpath+'available_filter_functions')
544 for i in sorted(self.tracefuncs):
545 if 'func' in self.tracefuncs[i]:
546 i = self.tracefuncs[i]['func']
547 if i in master:
548 print(i)
549 else:
550 print(self.colorText(i))
551 def setFtraceFilterFunctions(self, list):
552 master = self.listFromFile(self.tpath+'available_filter_functions')
553 flist = ''
554 for i in list:
555 if i not in master:
556 continue
557 if ' [' in i:
558 flist += i.split(' ')[0]+'\n'
559 else:
560 flist += i+'\n'
561 fp = open(self.tpath+'set_graph_function', 'w')
562 fp.write(flist)
563 fp.close()
564 def basicKprobe(self, name):
565 self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
566 def defaultKprobe(self, name, kdata):
567 k = kdata
568 for field in ['name', 'format', 'func']:
569 if field not in k:
570 k[field] = name
571 if self.archargs in k:
572 k['args'] = k[self.archargs]
573 else:
574 k['args'] = dict()
575 k['format'] = name
576 self.kprobes[name] = k
577 def kprobeColor(self, name):
578 if name not in self.kprobes or 'color' not in self.kprobes[name]:
579 return ''
580 return self.kprobes[name]['color']
581 def kprobeDisplayName(self, name, dataraw):
582 if name not in self.kprobes:
583 self.basicKprobe(name)
584 data = ''
585 quote=0
586 # first remvoe any spaces inside quotes, and the quotes
587 for c in dataraw:
588 if c == '"':
589 quote = (quote + 1) % 2
590 if quote and c == ' ':
591 data += '_'
592 elif c != '"':
593 data += c
594 fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
595 arglist = dict()
596 # now process the args
597 for arg in sorted(args):
598 arglist[arg] = ''
599 m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
600 if m:
601 arglist[arg] = m.group('arg')
602 else:
603 m = re.match('.* '+arg+'=(?P<arg>.*)', data);
604 if m:
605 arglist[arg] = m.group('arg')
606 out = fmt.format(**arglist)
607 out = out.replace(' ', '_').replace('"', '')
608 return out
609 def kprobeText(self, kname, kprobe):
610 name = fmt = func = kname
611 args = dict()
612 if 'name' in kprobe:
613 name = kprobe['name']
614 if 'format' in kprobe:
615 fmt = kprobe['format']
616 if 'func' in kprobe:
617 func = kprobe['func']
618 if self.archargs in kprobe:
619 args = kprobe[self.archargs]
620 if 'args' in kprobe:
621 args = kprobe['args']
622 if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
623 doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
624 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
625 if arg not in args:
626 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
627 val = 'p:%s_cal %s' % (name, func)
628 for i in sorted(args):
629 val += ' %s=%s' % (i, args[i])
630 val += '\nr:%s_ret %s $retval\n' % (name, func)
631 return val
632 def addKprobes(self, output=False):
633 if len(self.kprobes) < 1:
634 return
635 if output:
636 pprint(' kprobe functions in this kernel:')
637 # first test each kprobe
638 rejects = []
639 # sort kprobes: trace, ub-dev, custom, dev
640 kpl = [[], [], [], []]
641 linesout = len(self.kprobes)
642 for name in sorted(self.kprobes):
643 res = self.colorText('YES', 32)
644 if not self.testKprobe(name, self.kprobes[name]):
645 res = self.colorText('NO')
646 rejects.append(name)
647 else:
648 if name in self.tracefuncs:
649 kpl[0].append(name)
650 elif name in self.dev_tracefuncs:
651 if 'ub' in self.dev_tracefuncs[name]:
652 kpl[1].append(name)
653 else:
654 kpl[3].append(name)
655 else:
656 kpl[2].append(name)
657 if output:
658 pprint(' %s: %s' % (name, res))
659 kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
660 # remove all failed ones from the list
661 for name in rejects:
662 self.kprobes.pop(name)
663 # set the kprobes all at once
664 self.fsetVal('', 'kprobe_events')
665 kprobeevents = ''
666 for kp in kplist:
667 kprobeevents += self.kprobeText(kp, self.kprobes[kp])
668 self.fsetVal(kprobeevents, 'kprobe_events')
669 if output:
670 check = self.fgetVal('kprobe_events')
671 linesack = (len(check.split('\n')) - 1) // 2
672 pprint(' kprobe functions enabled: %d/%d' % (linesack, linesout))
673 self.fsetVal('1', 'events/kprobes/enable')
674 def testKprobe(self, kname, kprobe):
675 self.fsetVal('0', 'events/kprobes/enable')
676 kprobeevents = self.kprobeText(kname, kprobe)
677 if not kprobeevents:
678 return False
679 try:
680 self.fsetVal(kprobeevents, 'kprobe_events')
681 check = self.fgetVal('kprobe_events')
682 except:
683 return False
684 linesout = len(kprobeevents.split('\n'))
685 linesack = len(check.split('\n'))
686 if linesack < linesout:
687 return False
688 return True
689 def setVal(self, val, file):
690 if not os.path.exists(file):
691 return False
692 try:
693 fp = open(file, 'wb', 0)
694 fp.write(val.encode())
695 fp.flush()
696 fp.close()
697 except:
698 return False
699 return True
700 def fsetVal(self, val, path):
701 return self.setVal(val, self.tpath+path)
702 def getVal(self, file):
703 res = ''
704 if not os.path.exists(file):
705 return res
706 try:
707 fp = open(file, 'r')
708 res = fp.read()
709 fp.close()
710 except:
711 pass
712 return res
713 def fgetVal(self, path):
714 return self.getVal(self.tpath+path)
715 def cleanupFtrace(self):
716 if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
717 self.fsetVal('0', 'events/kprobes/enable')
718 self.fsetVal('', 'kprobe_events')
719 self.fsetVal('1024', 'buffer_size_kb')
720 def setupAllKprobes(self):
721 for name in self.tracefuncs:
722 self.defaultKprobe(name, self.tracefuncs[name])
723 for name in self.dev_tracefuncs:
724 self.defaultKprobe(name, self.dev_tracefuncs[name])
725 def isCallgraphFunc(self, name):
726 if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
727 return True
728 for i in self.tracefuncs:
729 if 'func' in self.tracefuncs[i]:
730 f = self.tracefuncs[i]['func']
731 else:
732 f = i
733 if name == f:
734 return True
735 return False
736 def initFtrace(self, quiet=False):
737 if not quiet:
738 sysvals.printSystemInfo(False)
739 pprint('INITIALIZING FTRACE...')
740 # turn trace off
741 self.fsetVal('0', 'tracing_on')
742 self.cleanupFtrace()
743 self.testVal(self.pmdpath, 'basic', '1')
744 # set the trace clock to global
745 self.fsetVal('global', 'trace_clock')
746 self.fsetVal('nop', 'current_tracer')
747 # set trace buffer to an appropriate value
748 cpus = max(1, self.cpucount)
749 if self.bufsize > 0:
750 tgtsize = self.bufsize
751 elif self.usecallgraph or self.usedevsrc:
752 bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
753 else (3*1024*1024)
754 tgtsize = min(self.memfree, bmax)
755 else:
756 tgtsize = 65536
757 while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
758 # if the size failed to set, lower it and keep trying
759 tgtsize -= 65536
760 if tgtsize < 65536:
761 tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
762 break
763 self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
764 # initialize the callgraph trace
765 if(self.usecallgraph):
766 # set trace type
767 self.fsetVal('function_graph', 'current_tracer')
768 self.fsetVal('', 'set_ftrace_filter')
769 # set trace format options
770 self.fsetVal('print-parent', 'trace_options')
771 self.fsetVal('funcgraph-abstime', 'trace_options')
772 self.fsetVal('funcgraph-cpu', 'trace_options')
773 self.fsetVal('funcgraph-duration', 'trace_options')
774 self.fsetVal('funcgraph-proc', 'trace_options')
775 self.fsetVal('funcgraph-tail', 'trace_options')
776 self.fsetVal('nofuncgraph-overhead', 'trace_options')
777 self.fsetVal('context-info', 'trace_options')
778 self.fsetVal('graph-time', 'trace_options')
779 self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
780 cf = ['dpm_run_callback']
781 if(self.usetraceevents):
782 cf += ['dpm_prepare', 'dpm_complete']
783 for fn in self.tracefuncs:
784 if 'func' in self.tracefuncs[fn]:
785 cf.append(self.tracefuncs[fn]['func'])
786 else:
787 cf.append(fn)
788 if self.ftop:
789 self.setFtraceFilterFunctions([self.ftopfunc])
790 else:
791 self.setFtraceFilterFunctions(cf)
792 # initialize the kprobe trace
793 elif self.usekprobes:
794 for name in self.tracefuncs:
795 self.defaultKprobe(name, self.tracefuncs[name])
796 if self.usedevsrc:
797 for name in self.dev_tracefuncs:
798 self.defaultKprobe(name, self.dev_tracefuncs[name])
799 if not quiet:
800 pprint('INITIALIZING KPROBES...')
801 self.addKprobes(self.verbose)
802 if(self.usetraceevents):
803 # turn trace events on
804 events = iter(self.traceevents)
805 for e in events:
806 self.fsetVal('1', 'events/power/'+e+'/enable')
807 # clear the trace buffer
808 self.fsetVal('', 'trace')
809 def verifyFtrace(self):
810 # files needed for any trace data
811 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
812 'trace_marker', 'trace_options', 'tracing_on']
813 # files needed for callgraph trace data
814 tp = self.tpath
815 if(self.usecallgraph):
816 files += [
817 'available_filter_functions',
818 'set_ftrace_filter',
819 'set_graph_function'
820 ]
821 for f in files:
822 if(os.path.exists(tp+f) == False):
823 return False
824 return True
825 def verifyKprobes(self):
826 # files needed for kprobes to work
827 files = ['kprobe_events', 'events']
828 tp = self.tpath
829 for f in files:
830 if(os.path.exists(tp+f) == False):
831 return False
832 return True
833 def colorText(self, str, color=31):
834 if not self.ansi:
835 return str
836 return '\x1B[%d;40m%s\x1B[m' % (color, str)
837 def writeDatafileHeader(self, filename, testdata):
838 fp = self.openlog(filename, 'w')
839 fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
840 for test in testdata:
841 if 'fw' in test:
842 fw = test['fw']
843 if(fw):
844 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
845 if 'turbo' in test:
846 fp.write('# turbostat %s\n' % test['turbo'])
847 if 'wifi' in test:
848 fp.write('# wifi %s\n' % test['wifi'])
849 if test['error'] or len(testdata) > 1:
850 fp.write('# enter_sleep_error %s\n' % test['error'])
851 return fp
852 def sudoUserchown(self, dir):
853 if os.path.exists(dir) and self.sudouser:
854 cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
855 call(cmd.format(self.sudouser, dir), shell=True)
856 def outputResult(self, testdata, num=0):
857 if not self.result:
858 return
859 n = ''
860 if num > 0:
861 n = '%d' % num
862 fp = open(self.result, 'a')
863 if 'error' in testdata:
864 fp.write('result%s: fail\n' % n)
865 fp.write('error%s: %s\n' % (n, testdata['error']))
866 else:
867 fp.write('result%s: pass\n' % n)
868 for v in ['suspend', 'resume', 'boot', 'lastinit']:
869 if v in testdata:
870 fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
871 for v in ['fwsuspend', 'fwresume']:
872 if v in testdata:
873 fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
874 if 'bugurl' in testdata:
875 fp.write('url%s: %s\n' % (n, testdata['bugurl']))
876 fp.close()
877 self.sudoUserchown(self.result)
878 def configFile(self, file):
879 dir = os.path.dirname(os.path.realpath(__file__))
880 if os.path.exists(file):
881 return file
882 elif os.path.exists(dir+'/'+file):
883 return dir+'/'+file
884 elif os.path.exists(dir+'/config/'+file):
885 return dir+'/config/'+file
886 return ''
887 def openlog(self, filename, mode):
888 isgz = self.gzip
889 if mode == 'r':
890 try:
891 with gzip.open(filename, mode+'t') as fp:
892 test = fp.read(64)
893 isgz = True
894 except:
895 isgz = False
896 if isgz:
897 return gzip.open(filename, mode+'t')
898 return open(filename, mode)
899 def putlog(self, filename, text):
900 with self.openlog(filename, 'a') as fp:
901 fp.write(text)
902 fp.close()
903 def dlog(self, text):
904 self.putlog(self.dmesgfile, '# %s\n' % text)
905 def flog(self, text):
906 self.putlog(self.ftracefile, text)
907 def b64unzip(self, data):
908 try:
909 out = codecs.decode(base64.b64decode(data), 'zlib').decode()
910 except:
911 out = data
912 return out
913 def b64zip(self, data):
914 out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
915 return out
916 def platforminfo(self, cmdafter):
917 # add platform info on to a completed ftrace file
918 if not os.path.exists(self.ftracefile):
919 return False
920 footer = '#\n'
921
922 # add test command string line if need be
923 if self.suspendmode == 'command' and self.testcommand:
924 footer += '# platform-testcmd: %s\n' % (self.testcommand)
925
926 # get a list of target devices from the ftrace file
927 props = dict()
928 tp = TestProps()
929 tf = self.openlog(self.ftracefile, 'r')
930 for line in tf:
931 if tp.stampInfo(line, self):
932 continue
933 # parse only valid lines, if this is not one move on
934 m = re.match(tp.ftrace_line_fmt, line)
935 if(not m or 'device_pm_callback_start' not in line):
936 continue
937 m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
938 if(not m):
939 continue
940 dev = m.group('d')
941 if dev not in props:
942 props[dev] = DevProps()
943 tf.close()
944
945 # now get the syspath for each target device
946 for dirname, dirnames, filenames in os.walk('/sys/devices'):
947 if(re.match('.*/power', dirname) and 'async' in filenames):
948 dev = dirname.split('/')[-2]
949 if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
950 props[dev].syspath = dirname[:-6]
951
952 # now fill in the properties for our target devices
953 for dev in sorted(props):
954 dirname = props[dev].syspath
955 if not dirname or not os.path.exists(dirname):
956 continue
957 with open(dirname+'/power/async') as fp:
958 text = fp.read()
959 props[dev].isasync = False
960 if 'enabled' in text:
961 props[dev].isasync = True
962 fields = os.listdir(dirname)
963 if 'product' in fields:
964 with open(dirname+'/product', 'rb') as fp:
965 props[dev].altname = ascii(fp.read())
966 elif 'name' in fields:
967 with open(dirname+'/name', 'rb') as fp:
968 props[dev].altname = ascii(fp.read())
969 elif 'model' in fields:
970 with open(dirname+'/model', 'rb') as fp:
971 props[dev].altname = ascii(fp.read())
972 elif 'description' in fields:
973 with open(dirname+'/description', 'rb') as fp:
974 props[dev].altname = ascii(fp.read())
975 elif 'id' in fields:
976 with open(dirname+'/id', 'rb') as fp:
977 props[dev].altname = ascii(fp.read())
978 elif 'idVendor' in fields and 'idProduct' in fields:
979 idv, idp = '', ''
980 with open(dirname+'/idVendor', 'rb') as fp:
981 idv = ascii(fp.read()).strip()
982 with open(dirname+'/idProduct', 'rb') as fp:
983 idp = ascii(fp.read()).strip()
984 props[dev].altname = '%s:%s' % (idv, idp)
985 if props[dev].altname:
986 out = props[dev].altname.strip().replace('\n', ' ')\
987 .replace(',', ' ').replace(';', ' ')
988 props[dev].altname = out
989
990 # add a devinfo line to the bottom of ftrace
991 out = ''
992 for dev in sorted(props):
993 out += props[dev].out(dev)
994 footer += '# platform-devinfo: %s\n' % self.b64zip(out)
995
996 # add a line for each of these commands with their outputs
997 for name, cmdline, info in cmdafter:
998 footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
999 self.flog(footer)
1000 return True
1001 def commonPrefix(self, list):
1002 if len(list) < 2:
1003 return ''
1004 prefix = list[0]
1005 for s in list[1:]:
1006 while s[:len(prefix)] != prefix and prefix:
1007 prefix = prefix[:len(prefix)-1]
1008 if not prefix:
1009 break
1010 if '/' in prefix and prefix[-1] != '/':
1011 prefix = prefix[0:prefix.rfind('/')+1]
1012 return prefix
1013 def dictify(self, text, format):
1014 out = dict()
1015 header = True if format == 1 else False
1016 delim = ' ' if format == 1 else ':'
1017 for line in text.split('\n'):
1018 if header:
1019 header, out['@'] = False, line
1020 continue
1021 line = line.strip()
1022 if delim in line:
1023 data = line.split(delim, 1)
1024 num = re.search(r'[\d]+', data[1])
1025 if format == 2 and num:
1026 out[data[0].strip()] = num.group()
1027 else:
1028 out[data[0].strip()] = data[1]
1029 return out
1030 def cmdinfo(self, begin, debug=False):
1031 out = []
1032 if begin:
1033 self.cmd1 = dict()
1034 for cargs in self.infocmds:
1035 delta, name = cargs[0], cargs[1]
1036 cmdline, cmdpath = ' '.join(cargs[2:]), self.getExec(cargs[2])
1037 if not cmdpath or (begin and not delta):
1038 continue
1039 self.dlog('[%s]' % cmdline)
1040 try:
1041 fp = Popen([cmdpath]+cargs[3:], stdout=PIPE, stderr=PIPE).stdout
1042 info = ascii(fp.read()).strip()
1043 fp.close()
1044 except:
1045 continue
1046 if not debug and begin:
1047 self.cmd1[name] = self.dictify(info, delta)
1048 elif not debug and delta and name in self.cmd1:
1049 before, after = self.cmd1[name], self.dictify(info, delta)
1050 dinfo = ('\t%s\n' % before['@']) if '@' in before else ''
1051 prefix = self.commonPrefix(list(before.keys()))
1052 for key in sorted(before):
1053 if key in after and before[key] != after[key]:
1054 title = key.replace(prefix, '')
1055 if delta == 2:
1056 dinfo += '\t%s : %s -> %s\n' % \
1057 (title, before[key].strip(), after[key].strip())
1058 else:
1059 dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1060 (title, before[key], title, after[key])
1061 dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1062 out.append((name, cmdline, dinfo))
1063 else:
1064 out.append((name, cmdline, '\tnothing' if not info else info))
1065 return out
1066 def testVal(self, file, fmt='basic', value=''):
1067 if file == 'restoreall':
1068 for f in self.cfgdef:
1069 if os.path.exists(f):
1070 fp = open(f, 'w')
1071 fp.write(self.cfgdef[f])
1072 fp.close()
1073 self.cfgdef = dict()
1074 elif value and os.path.exists(file):
1075 fp = open(file, 'r+')
1076 if fmt == 'radio':
1077 m = re.match('.*\[(?P<v>.*)\].*', fp.read())
1078 if m:
1079 self.cfgdef[file] = m.group('v')
1080 elif fmt == 'acpi':
1081 line = fp.read().strip().split('\n')[-1]
1082 m = re.match('.* (?P<v>[0-9A-Fx]*) .*', line)
1083 if m:
1084 self.cfgdef[file] = m.group('v')
1085 else:
1086 self.cfgdef[file] = fp.read().strip()
1087 fp.write(value)
1088 fp.close()
1089 def haveTurbostat(self):
1090 if not self.tstat:
1091 return False
1092 cmd = self.getExec('turbostat')
1093 if not cmd:
1094 return False
1095 fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1096 out = ascii(fp.read()).strip()
1097 fp.close()
1098 if re.match('turbostat version .*', out):
1099 self.vprint(out)
1100 return True
1101 return False
1102 def turbostat(self):
1103 cmd = self.getExec('turbostat')
1104 rawout = keyline = valline = ''
1105 fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1106 fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr
1107 for line in fp:
1108 line = ascii(line)
1109 rawout += line
1110 if keyline and valline:
1111 continue
1112 if re.match('(?i)Avg_MHz.*', line):
1113 keyline = line.strip().split()
1114 elif keyline:
1115 valline = line.strip().split()
1116 fp.close()
1117 if not keyline or not valline or len(keyline) != len(valline):
1118 errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1119 self.vprint(errmsg)
1120 if not self.verbose:
1121 pprint(errmsg)
1122 return ''
1123 if self.verbose:
1124 pprint(rawout.strip())
1125 out = []
1126 for key in keyline:
1127 idx = keyline.index(key)
1128 val = valline[idx]
1129 out.append('%s=%s' % (key, val))
1130 return '|'.join(out)
1131 def wifiDetails(self, dev):
1132 try:
1133 info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1134 except:
1135 return dev
1136 vals = [dev]
1137 for prop in info.split('\n'):
1138 if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1139 vals.append(prop.split('=')[-1])
1140 return ':'.join(vals)
1141 def checkWifi(self, dev=''):
1142 try:
1143 w = open('/proc/net/wireless', 'r').read().strip()
1144 except:
1145 return ''
1146 for line in reversed(w.split('\n')):
1147 m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', w.split('\n')[-1])
1148 if not m or (dev and dev != m.group('dev')):
1149 continue
1150 return m.group('dev')
1151 return ''
1152 def pollWifi(self, dev, timeout=60):
1153 start = time.time()
1154 while (time.time() - start) < timeout:
1155 w = self.checkWifi(dev)
1156 if w:
1157 return '%s reconnected %.2f' % \
1158 (self.wifiDetails(dev), max(0, time.time() - start))
1159 time.sleep(0.01)
1160 return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1161 def errorSummary(self, errinfo, msg):
1162 found = False
1163 for entry in errinfo:
1164 if re.match(entry['match'], msg):
1165 entry['count'] += 1
1166 if self.hostname not in entry['urls']:
1167 entry['urls'][self.hostname] = [self.htmlfile]
1168 elif self.htmlfile not in entry['urls'][self.hostname]:
1169 entry['urls'][self.hostname].append(self.htmlfile)
1170 found = True
1171 break
1172 if found:
1173 return
1174 arr = msg.split()
1175 for j in range(len(arr)):
1176 if re.match('^[0-9,\-\.]*$', arr[j]):
1177 arr[j] = '[0-9,\-\.]*'
1178 else:
1179 arr[j] = arr[j]\
1180 .replace('\\', '\\\\').replace(']', '\]').replace('[', '\[')\
1181 .replace('.', '\.').replace('+', '\+').replace('*', '\*')\
1182 .replace('(', '\(').replace(')', '\)').replace('}', '\}')\
1183 .replace('{', '\{')
1184 mstr = ' *'.join(arr)
1185 entry = {
1186 'line': msg,
1187 'match': mstr,
1188 'count': 1,
1189 'urls': {self.hostname: [self.htmlfile]}
1190 }
1191 errinfo.append(entry)
1192 def multistat(self, start, idx, finish):
1193 if 'time' in self.multitest:
1194 id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1195 else:
1196 id = '%d/%d' % (idx+1, self.multitest['count'])
1197 t = time.time()
1198 if 'start' not in self.multitest:
1199 self.multitest['start'] = self.multitest['last'] = t
1200 self.multitest['total'] = 0.0
1201 pprint('TEST (%s) START' % id)
1202 return
1203 dt = t - self.multitest['last']
1204 if not start:
1205 if idx == 0 and self.multitest['delay'] > 0:
1206 self.multitest['total'] += self.multitest['delay']
1207 pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1208 return
1209 self.multitest['total'] += dt
1210 self.multitest['last'] = t
1211 avg = self.multitest['total'] / idx
1212 if 'time' in self.multitest:
1213 left = finish - datetime.now()
1214 left -= timedelta(microseconds=left.microseconds)
1215 else:
1216 left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1217 pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1218 (id, avg, str(left)))
1219 def multiinit(self, c, d):
1220 sz, unit = 'count', 'm'
1221 if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1222 sz, unit, c = 'time', c[-1], c[:-1]
1223 self.multitest['run'] = True
1224 self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1225 self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1226 if unit == 'd':
1227 self.multitest[sz] *= 1440
1228 elif unit == 'h':
1229 self.multitest[sz] *= 60
1230 def displayControl(self, cmd):
1231 xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
1232 if self.sudouser:
1233 xset = 'sudo -u %s %s' % (self.sudouser, xset)
1234 if cmd == 'init':
1235 ret = call(xset.format('dpms 0 0 0'), shell=True)
1236 if not ret:
1237 ret = call(xset.format('s off'), shell=True)
1238 elif cmd == 'reset':
1239 ret = call(xset.format('s reset'), shell=True)
1240 elif cmd in ['on', 'off', 'standby', 'suspend']:
1241 b4 = self.displayControl('stat')
1242 ret = call(xset.format('dpms force %s' % cmd), shell=True)
1243 if not ret:
1244 curr = self.displayControl('stat')
1245 self.vprint('Display Switched: %s -> %s' % (b4, curr))
1246 if curr != cmd:
1247 self.vprint('WARNING: Display failed to change to %s' % cmd)
1248 if ret:
1249 self.vprint('WARNING: Display failed to change to %s with xset' % cmd)
1250 return ret
1251 elif cmd == 'stat':
1252 fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
1253 ret = 'unknown'
1254 for line in fp:
1255 m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))
1256 if(m and len(m.group('m')) >= 2):
1257 out = m.group('m').lower()
1258 ret = out[3:] if out[0:2] == 'in' else out
1259 break
1260 fp.close()
1261 return ret
1262 def setRuntimeSuspend(self, before=True):
1263 if before:
1264 # runtime suspend disable or enable
1265 if self.rs > 0:
1266 self.rstgt, self.rsval, self.rsdir = 'on', 'auto', 'enabled'
1267 else:
1268 self.rstgt, self.rsval, self.rsdir = 'auto', 'on', 'disabled'
1269 pprint('CONFIGURING RUNTIME SUSPEND...')
1270 self.rslist = deviceInfo(self.rstgt)
1271 for i in self.rslist:
1272 self.setVal(self.rsval, i)
1273 pprint('runtime suspend %s on all devices (%d changed)' % (self.rsdir, len(self.rslist)))
1274 pprint('waiting 5 seconds...')
1275 time.sleep(5)
1276 else:
1277 # runtime suspend re-enable or re-disable
1278 for i in self.rslist:
1279 self.setVal(self.rstgt, i)
1280 pprint('runtime suspend settings restored on %d devices' % len(self.rslist))
1281
1282sysvals = SystemValues()
1283switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1284switchoff = ['disable', 'off', 'false', '0']
1285suspendmodename = {
1286 'freeze': 'Freeze (S0)',
1287 'standby': 'Standby (S1)',
1288 'mem': 'Suspend (S3)',
1289 'disk': 'Hibernate (S4)'
1290}
1291
1292# Class: DevProps
1293# Description:
1294# Simple class which holds property values collected
1295# for all the devices used in the timeline.
1296class DevProps:
1297 def __init__(self):
1298 self.syspath = ''
1299 self.altname = ''
1300 self.isasync = True
1301 self.xtraclass = ''
1302 self.xtrainfo = ''
1303 def out(self, dev):
1304 return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1305 def debug(self, dev):
1306 pprint('%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.isasync))
1307 def altName(self, dev):
1308 if not self.altname or self.altname == dev:
1309 return dev
1310 return '%s [%s]' % (self.altname, dev)
1311 def xtraClass(self):
1312 if self.xtraclass:
1313 return ' '+self.xtraclass
1314 if not self.isasync:
1315 return ' sync'
1316 return ''
1317 def xtraInfo(self):
1318 if self.xtraclass:
1319 return ' '+self.xtraclass
1320 if self.isasync:
1321 return ' (async)'
1322 return ' (sync)'
1323
1324# Class: DeviceNode
1325# Description:
1326# A container used to create a device hierachy, with a single root node
1327# and a tree of child nodes. Used by Data.deviceTopology()
1328class DeviceNode:
1329 def __init__(self, nodename, nodedepth):
1330 self.name = nodename
1331 self.children = []
1332 self.depth = nodedepth
1333
1334# Class: Data
1335# Description:
1336# The primary container for suspend/resume test data. There is one for
1337# each test run. The data is organized into a cronological hierarchy:
1338# Data.dmesg {
1339# phases {
1340# 10 sequential, non-overlapping phases of S/R
1341# contents: times for phase start/end, order/color data for html
1342# devlist {
1343# device callback or action list for this phase
1344# device {
1345# a single device callback or generic action
1346# contents: start/stop times, pid/cpu/driver info
1347# parents/children, html id for timeline/callgraph
1348# optionally includes an ftrace callgraph
1349# optionally includes dev/ps data
1350# }
1351# }
1352# }
1353# }
1354#
1355class Data:
1356 phasedef = {
1357 'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1358 'suspend': {'order': 1, 'color': '#88FF88'},
1359 'suspend_late': {'order': 2, 'color': '#00AA00'},
1360 'suspend_noirq': {'order': 3, 'color': '#008888'},
1361 'suspend_machine': {'order': 4, 'color': '#0000FF'},
1362 'resume_machine': {'order': 5, 'color': '#FF0000'},
1363 'resume_noirq': {'order': 6, 'color': '#FF9900'},
1364 'resume_early': {'order': 7, 'color': '#FFCC00'},
1365 'resume': {'order': 8, 'color': '#FFFF88'},
1366 'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1367 }
1368 errlist = {
1369 'HWERROR' : r'.*\[ *Hardware Error *\].*',
1370 'FWBUG' : r'.*\[ *Firmware Bug *\].*',
1371 'BUG' : r'(?i).*\bBUG\b.*',
1372 'ERROR' : r'(?i).*\bERROR\b.*',
1373 'WARNING' : r'(?i).*\bWARNING\b.*',
1374 'FAULT' : r'(?i).*\bFAULT\b.*',
1375 'FAIL' : r'(?i).*\bFAILED\b.*',
1376 'INVALID' : r'(?i).*\bINVALID\b.*',
1377 'CRASH' : r'(?i).*\bCRASHED\b.*',
1378 'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
1379 'IRQ' : r'.*\bgenirq: .*',
1380 'TASKFAIL': r'.*Freezing of tasks *.*',
1381 'ACPI' : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1382 'DISKFULL': r'.*\bNo space left on device.*',
1383 'USBERR' : r'.*usb .*device .*, error [0-9-]*',
1384 'ATAERR' : r' *ata[0-9\.]*: .*failed.*',
1385 'MEIERR' : r' *mei.*: .*failed.*',
1386 'TPMERR' : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1387 }
1388 def __init__(self, num):
1389 idchar = 'abcdefghij'
1390 self.start = 0.0 # test start
1391 self.end = 0.0 # test end
1392 self.hwstart = 0 # rtc test start
1393 self.hwend = 0 # rtc test end
1394 self.tSuspended = 0.0 # low-level suspend start
1395 self.tResumed = 0.0 # low-level resume start
1396 self.tKernSus = 0.0 # kernel level suspend start
1397 self.tKernRes = 0.0 # kernel level resume end
1398 self.fwValid = False # is firmware data available
1399 self.fwSuspend = 0 # time spent in firmware suspend
1400 self.fwResume = 0 # time spent in firmware resume
1401 self.html_device_id = 0
1402 self.stamp = 0
1403 self.outfile = ''
1404 self.kerror = False
1405 self.wifi = dict()
1406 self.turbostat = 0
1407 self.enterfail = ''
1408 self.currphase = ''
1409 self.pstl = dict() # process timeline
1410 self.testnumber = num
1411 self.idstr = idchar[num]
1412 self.dmesgtext = [] # dmesg text file in memory
1413 self.dmesg = dict() # root data structure
1414 self.errorinfo = {'suspend':[],'resume':[]}
1415 self.tLow = [] # time spent in low-level suspends (standby/freeze)
1416 self.devpids = []
1417 self.devicegroups = 0
1418 def sortedPhases(self):
1419 return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1420 def initDevicegroups(self):
1421 # called when phases are all finished being added
1422 for phase in sorted(self.dmesg.keys()):
1423 if '*' in phase:
1424 p = phase.split('*')
1425 pnew = '%s%d' % (p[0], len(p))
1426 self.dmesg[pnew] = self.dmesg.pop(phase)
1427 self.devicegroups = []
1428 for phase in self.sortedPhases():
1429 self.devicegroups.append([phase])
1430 def nextPhase(self, phase, offset):
1431 order = self.dmesg[phase]['order'] + offset
1432 for p in self.dmesg:
1433 if self.dmesg[p]['order'] == order:
1434 return p
1435 return ''
1436 def lastPhase(self, depth=1):
1437 plist = self.sortedPhases()
1438 if len(plist) < depth:
1439 return ''
1440 return plist[-1*depth]
1441 def turbostatInfo(self):
1442 tp = TestProps()
1443 out = {'syslpi':'N/A','pkgpc10':'N/A'}
1444 for line in self.dmesgtext:
1445 m = re.match(tp.tstatfmt, line)
1446 if not m:
1447 continue
1448 for i in m.group('t').split('|'):
1449 if 'SYS%LPI' in i:
1450 out['syslpi'] = i.split('=')[-1]+'%'
1451 elif 'pc10' in i:
1452 out['pkgpc10'] = i.split('=')[-1]+'%'
1453 break
1454 return out
1455 def extractErrorInfo(self):
1456 lf = self.dmesgtext
1457 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1458 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1459 i = 0
1460 tp = TestProps()
1461 list = []
1462 for line in lf:
1463 i += 1
1464 if tp.stampInfo(line, sysvals):
1465 continue
1466 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1467 if not m:
1468 continue
1469 t = float(m.group('ktime'))
1470 if t < self.start or t > self.end:
1471 continue
1472 dir = 'suspend' if t < self.tSuspended else 'resume'
1473 msg = m.group('msg')
1474 if re.match('capability: warning: .*', msg):
1475 continue
1476 for err in self.errlist:
1477 if re.match(self.errlist[err], msg):
1478 list.append((msg, err, dir, t, i, i))
1479 self.kerror = True
1480 break
1481 tp.msglist = []
1482 for msg, type, dir, t, idx1, idx2 in list:
1483 tp.msglist.append(msg)
1484 self.errorinfo[dir].append((type, t, idx1, idx2))
1485 if self.kerror:
1486 sysvals.dmesglog = True
1487 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1488 lf.close()
1489 return tp
1490 def setStart(self, time, msg=''):
1491 self.start = time
1492 if msg:
1493 try:
1494 self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1495 except:
1496 self.hwstart = 0
1497 def setEnd(self, time, msg=''):
1498 self.end = time
1499 if msg:
1500 try:
1501 self.hwend = datetime.strptime(msg, sysvals.tmend)
1502 except:
1503 self.hwend = 0
1504 def isTraceEventOutsideDeviceCalls(self, pid, time):
1505 for phase in self.sortedPhases():
1506 list = self.dmesg[phase]['list']
1507 for dev in list:
1508 d = list[dev]
1509 if(d['pid'] == pid and time >= d['start'] and
1510 time < d['end']):
1511 return False
1512 return True
1513 def sourcePhase(self, start):
1514 for phase in self.sortedPhases():
1515 if 'machine' in phase:
1516 continue
1517 pend = self.dmesg[phase]['end']
1518 if start <= pend:
1519 return phase
1520 return 'resume_complete'
1521 def sourceDevice(self, phaselist, start, end, pid, type):
1522 tgtdev = ''
1523 for phase in phaselist:
1524 list = self.dmesg[phase]['list']
1525 for devname in list:
1526 dev = list[devname]
1527 # pid must match
1528 if dev['pid'] != pid:
1529 continue
1530 devS = dev['start']
1531 devE = dev['end']
1532 if type == 'device':
1533 # device target event is entirely inside the source boundary
1534 if(start < devS or start >= devE or end <= devS or end > devE):
1535 continue
1536 elif type == 'thread':
1537 # thread target event will expand the source boundary
1538 if start < devS:
1539 dev['start'] = start
1540 if end > devE:
1541 dev['end'] = end
1542 tgtdev = dev
1543 break
1544 return tgtdev
1545 def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1546 # try to place the call in a device
1547 phases = self.sortedPhases()
1548 tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1549 # calls with device pids that occur outside device bounds are dropped
1550 # TODO: include these somehow
1551 if not tgtdev and pid in self.devpids:
1552 return False
1553 # try to place the call in a thread
1554 if not tgtdev:
1555 tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1556 # create new thread blocks, expand as new calls are found
1557 if not tgtdev:
1558 if proc == '<...>':
1559 threadname = 'kthread-%d' % (pid)
1560 else:
1561 threadname = '%s-%d' % (proc, pid)
1562 tgtphase = self.sourcePhase(start)
1563 self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1564 return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1565 # this should not happen
1566 if not tgtdev:
1567 sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1568 (start, end, proc, pid, kprobename, cdata, rdata))
1569 return False
1570 # place the call data inside the src element of the tgtdev
1571 if('src' not in tgtdev):
1572 tgtdev['src'] = []
1573 dtf = sysvals.dev_tracefuncs
1574 ubiquitous = False
1575 if kprobename in dtf and 'ub' in dtf[kprobename]:
1576 ubiquitous = True
1577 title = cdata+' '+rdata
1578 mstr = '\(.*\) *(?P<args>.*) *\((?P<caller>.*)\+.* arg1=(?P<ret>.*)'
1579 m = re.match(mstr, title)
1580 if m:
1581 c = m.group('caller')
1582 a = m.group('args').strip()
1583 r = m.group('ret')
1584 if len(r) > 6:
1585 r = ''
1586 else:
1587 r = 'ret=%s ' % r
1588 if ubiquitous and c in dtf and 'ub' in dtf[c]:
1589 return False
1590 color = sysvals.kprobeColor(kprobename)
1591 e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1592 tgtdev['src'].append(e)
1593 return True
1594 def overflowDevices(self):
1595 # get a list of devices that extend beyond the end of this test run
1596 devlist = []
1597 for phase in self.sortedPhases():
1598 list = self.dmesg[phase]['list']
1599 for devname in list:
1600 dev = list[devname]
1601 if dev['end'] > self.end:
1602 devlist.append(dev)
1603 return devlist
1604 def mergeOverlapDevices(self, devlist):
1605 # merge any devices that overlap devlist
1606 for dev in devlist:
1607 devname = dev['name']
1608 for phase in self.sortedPhases():
1609 list = self.dmesg[phase]['list']
1610 if devname not in list:
1611 continue
1612 tdev = list[devname]
1613 o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1614 if o <= 0:
1615 continue
1616 dev['end'] = tdev['end']
1617 if 'src' not in dev or 'src' not in tdev:
1618 continue
1619 dev['src'] += tdev['src']
1620 del list[devname]
1621 def usurpTouchingThread(self, name, dev):
1622 # the caller test has priority of this thread, give it to him
1623 for phase in self.sortedPhases():
1624 list = self.dmesg[phase]['list']
1625 if name in list:
1626 tdev = list[name]
1627 if tdev['start'] - dev['end'] < 0.1:
1628 dev['end'] = tdev['end']
1629 if 'src' not in dev:
1630 dev['src'] = []
1631 if 'src' in tdev:
1632 dev['src'] += tdev['src']
1633 del list[name]
1634 break
1635 def stitchTouchingThreads(self, testlist):
1636 # merge any threads between tests that touch
1637 for phase in self.sortedPhases():
1638 list = self.dmesg[phase]['list']
1639 for devname in list:
1640 dev = list[devname]
1641 if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1642 continue
1643 for data in testlist:
1644 data.usurpTouchingThread(devname, dev)
1645 def optimizeDevSrc(self):
1646 # merge any src call loops to reduce timeline size
1647 for phase in self.sortedPhases():
1648 list = self.dmesg[phase]['list']
1649 for dev in list:
1650 if 'src' not in list[dev]:
1651 continue
1652 src = list[dev]['src']
1653 p = 0
1654 for e in sorted(src, key=lambda event: event.time):
1655 if not p or not e.repeat(p):
1656 p = e
1657 continue
1658 # e is another iteration of p, move it into p
1659 p.end = e.end
1660 p.length = p.end - p.time
1661 p.count += 1
1662 src.remove(e)
1663 def trimTimeVal(self, t, t0, dT, left):
1664 if left:
1665 if(t > t0):
1666 if(t - dT < t0):
1667 return t0
1668 return t - dT
1669 else:
1670 return t
1671 else:
1672 if(t < t0 + dT):
1673 if(t > t0):
1674 return t0 + dT
1675 return t + dT
1676 else:
1677 return t
1678 def trimTime(self, t0, dT, left):
1679 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1680 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1681 self.start = self.trimTimeVal(self.start, t0, dT, left)
1682 self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1683 self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1684 self.end = self.trimTimeVal(self.end, t0, dT, left)
1685 for phase in self.sortedPhases():
1686 p = self.dmesg[phase]
1687 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1688 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1689 list = p['list']
1690 for name in list:
1691 d = list[name]
1692 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1693 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1694 d['length'] = d['end'] - d['start']
1695 if('ftrace' in d):
1696 cg = d['ftrace']
1697 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1698 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1699 for line in cg.list:
1700 line.time = self.trimTimeVal(line.time, t0, dT, left)
1701 if('src' in d):
1702 for e in d['src']:
1703 e.time = self.trimTimeVal(e.time, t0, dT, left)
1704 e.end = self.trimTimeVal(e.end, t0, dT, left)
1705 e.length = e.end - e.time
1706 for dir in ['suspend', 'resume']:
1707 list = []
1708 for e in self.errorinfo[dir]:
1709 type, tm, idx1, idx2 = e
1710 tm = self.trimTimeVal(tm, t0, dT, left)
1711 list.append((type, tm, idx1, idx2))
1712 self.errorinfo[dir] = list
1713 def trimFreezeTime(self, tZero):
1714 # trim out any standby or freeze clock time
1715 lp = ''
1716 for phase in self.sortedPhases():
1717 if 'resume_machine' in phase and 'suspend_machine' in lp:
1718 tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1719 tL = tR - tS
1720 if tL <= 0:
1721 continue
1722 left = True if tR > tZero else False
1723 self.trimTime(tS, tL, left)
1724 if 'waking' in self.dmesg[lp]:
1725 tCnt = self.dmesg[lp]['waking'][0]
1726 if self.dmesg[lp]['waking'][1] >= 0.001:
1727 tTry = '-%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
1728 else:
1729 tTry = '-%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
1730 text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
1731 else:
1732 text = '%.0f' % (tL * 1000)
1733 self.tLow.append(text)
1734 lp = phase
1735 def getMemTime(self):
1736 if not self.hwstart or not self.hwend:
1737 return
1738 stime = (self.tSuspended - self.start) * 1000000
1739 rtime = (self.end - self.tResumed) * 1000000
1740 hws = self.hwstart + timedelta(microseconds=stime)
1741 hwr = self.hwend - timedelta(microseconds=rtime)
1742 self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1743 def getTimeValues(self):
1744 sktime = (self.tSuspended - self.tKernSus) * 1000
1745 rktime = (self.tKernRes - self.tResumed) * 1000
1746 return (sktime, rktime)
1747 def setPhase(self, phase, ktime, isbegin, order=-1):
1748 if(isbegin):
1749 # phase start over current phase
1750 if self.currphase:
1751 if 'resume_machine' not in self.currphase:
1752 sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1753 self.dmesg[self.currphase]['end'] = ktime
1754 phases = self.dmesg.keys()
1755 color = self.phasedef[phase]['color']
1756 count = len(phases) if order < 0 else order
1757 # create unique name for every new phase
1758 while phase in phases:
1759 phase += '*'
1760 self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1761 'row': 0, 'color': color, 'order': count}
1762 self.dmesg[phase]['start'] = ktime
1763 self.currphase = phase
1764 else:
1765 # phase end without a start
1766 if phase not in self.currphase:
1767 if self.currphase:
1768 sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1769 else:
1770 sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1771 return phase
1772 phase = self.currphase
1773 self.dmesg[phase]['end'] = ktime
1774 self.currphase = ''
1775 return phase
1776 def sortedDevices(self, phase):
1777 list = self.dmesg[phase]['list']
1778 return sorted(list, key=lambda k:list[k]['start'])
1779 def fixupInitcalls(self, phase):
1780 # if any calls never returned, clip them at system resume end
1781 phaselist = self.dmesg[phase]['list']
1782 for devname in phaselist:
1783 dev = phaselist[devname]
1784 if(dev['end'] < 0):
1785 for p in self.sortedPhases():
1786 if self.dmesg[p]['end'] > dev['start']:
1787 dev['end'] = self.dmesg[p]['end']
1788 break
1789 sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1790 def deviceFilter(self, devicefilter):
1791 for phase in self.sortedPhases():
1792 list = self.dmesg[phase]['list']
1793 rmlist = []
1794 for name in list:
1795 keep = False
1796 for filter in devicefilter:
1797 if filter in name or \
1798 ('drv' in list[name] and filter in list[name]['drv']):
1799 keep = True
1800 if not keep:
1801 rmlist.append(name)
1802 for name in rmlist:
1803 del list[name]
1804 def fixupInitcallsThatDidntReturn(self):
1805 # if any calls never returned, clip them at system resume end
1806 for phase in self.sortedPhases():
1807 self.fixupInitcalls(phase)
1808 def phaseOverlap(self, phases):
1809 rmgroups = []
1810 newgroup = []
1811 for group in self.devicegroups:
1812 for phase in phases:
1813 if phase not in group:
1814 continue
1815 for p in group:
1816 if p not in newgroup:
1817 newgroup.append(p)
1818 if group not in rmgroups:
1819 rmgroups.append(group)
1820 for group in rmgroups:
1821 self.devicegroups.remove(group)
1822 self.devicegroups.append(newgroup)
1823 def newActionGlobal(self, name, start, end, pid=-1, color=''):
1824 # which phase is this device callback or action in
1825 phases = self.sortedPhases()
1826 targetphase = 'none'
1827 htmlclass = ''
1828 overlap = 0.0
1829 myphases = []
1830 for phase in phases:
1831 pstart = self.dmesg[phase]['start']
1832 pend = self.dmesg[phase]['end']
1833 # see if the action overlaps this phase
1834 o = max(0, min(end, pend) - max(start, pstart))
1835 if o > 0:
1836 myphases.append(phase)
1837 # set the target phase to the one that overlaps most
1838 if o > overlap:
1839 if overlap > 0 and phase == 'post_resume':
1840 continue
1841 targetphase = phase
1842 overlap = o
1843 # if no target phase was found, pin it to the edge
1844 if targetphase == 'none':
1845 p0start = self.dmesg[phases[0]]['start']
1846 if start <= p0start:
1847 targetphase = phases[0]
1848 else:
1849 targetphase = phases[-1]
1850 if pid == -2:
1851 htmlclass = ' bg'
1852 elif pid == -3:
1853 htmlclass = ' ps'
1854 if len(myphases) > 1:
1855 htmlclass = ' bg'
1856 self.phaseOverlap(myphases)
1857 if targetphase in phases:
1858 newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1859 return (targetphase, newname)
1860 return False
1861 def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1862 # new device callback for a specific phase
1863 self.html_device_id += 1
1864 devid = '%s%d' % (self.idstr, self.html_device_id)
1865 list = self.dmesg[phase]['list']
1866 length = -1.0
1867 if(start >= 0 and end >= 0):
1868 length = end - start
1869 if pid == -2 or name not in sysvals.tracefuncs.keys():
1870 i = 2
1871 origname = name
1872 while(name in list):
1873 name = '%s[%d]' % (origname, i)
1874 i += 1
1875 list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1876 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1877 if htmlclass:
1878 list[name]['htmlclass'] = htmlclass
1879 if color:
1880 list[name]['color'] = color
1881 return name
1882 def findDevice(self, phase, name):
1883 list = self.dmesg[phase]['list']
1884 mydev = ''
1885 for devname in sorted(list):
1886 if name == devname or re.match('^%s\[(?P<num>[0-9]*)\]$' % name, devname):
1887 mydev = devname
1888 if mydev:
1889 return list[mydev]
1890 return False
1891 def deviceChildren(self, devname, phase):
1892 devlist = []
1893 list = self.dmesg[phase]['list']
1894 for child in list:
1895 if(list[child]['par'] == devname):
1896 devlist.append(child)
1897 return devlist
1898 def maxDeviceNameSize(self, phase):
1899 size = 0
1900 for name in self.dmesg[phase]['list']:
1901 if len(name) > size:
1902 size = len(name)
1903 return size
1904 def printDetails(self):
1905 sysvals.vprint('Timeline Details:')
1906 sysvals.vprint(' test start: %f' % self.start)
1907 sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
1908 tS = tR = False
1909 for phase in self.sortedPhases():
1910 devlist = self.dmesg[phase]['list']
1911 dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
1912 if not tS and ps >= self.tSuspended:
1913 sysvals.vprint(' machine suspended: %f' % self.tSuspended)
1914 tS = True
1915 if not tR and ps >= self.tResumed:
1916 sysvals.vprint(' machine resumed: %f' % self.tResumed)
1917 tR = True
1918 sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
1919 if sysvals.devdump:
1920 sysvals.vprint(''.join('-' for i in range(80)))
1921 maxname = '%d' % self.maxDeviceNameSize(phase)
1922 fmt = '%3d) %'+maxname+'s - %f - %f'
1923 c = 1
1924 for name in sorted(devlist):
1925 s = devlist[name]['start']
1926 e = devlist[name]['end']
1927 sysvals.vprint(fmt % (c, name, s, e))
1928 c += 1
1929 sysvals.vprint(''.join('-' for i in range(80)))
1930 sysvals.vprint(' kernel resume end: %f' % self.tKernRes)
1931 sysvals.vprint(' test end: %f' % self.end)
1932 def deviceChildrenAllPhases(self, devname):
1933 devlist = []
1934 for phase in self.sortedPhases():
1935 list = self.deviceChildren(devname, phase)
1936 for dev in sorted(list):
1937 if dev not in devlist:
1938 devlist.append(dev)
1939 return devlist
1940 def masterTopology(self, name, list, depth):
1941 node = DeviceNode(name, depth)
1942 for cname in list:
1943 # avoid recursions
1944 if name == cname:
1945 continue
1946 clist = self.deviceChildrenAllPhases(cname)
1947 cnode = self.masterTopology(cname, clist, depth+1)
1948 node.children.append(cnode)
1949 return node
1950 def printTopology(self, node):
1951 html = ''
1952 if node.name:
1953 info = ''
1954 drv = ''
1955 for phase in self.sortedPhases():
1956 list = self.dmesg[phase]['list']
1957 if node.name in list:
1958 s = list[node.name]['start']
1959 e = list[node.name]['end']
1960 if list[node.name]['drv']:
1961 drv = ' {'+list[node.name]['drv']+'}'
1962 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
1963 html += '<li><b>'+node.name+drv+'</b>'
1964 if info:
1965 html += '<ul>'+info+'</ul>'
1966 html += '</li>'
1967 if len(node.children) > 0:
1968 html += '<ul>'
1969 for cnode in node.children:
1970 html += self.printTopology(cnode)
1971 html += '</ul>'
1972 return html
1973 def rootDeviceList(self):
1974 # list of devices graphed
1975 real = []
1976 for phase in self.sortedPhases():
1977 list = self.dmesg[phase]['list']
1978 for dev in sorted(list):
1979 if list[dev]['pid'] >= 0 and dev not in real:
1980 real.append(dev)
1981 # list of top-most root devices
1982 rootlist = []
1983 for phase in self.sortedPhases():
1984 list = self.dmesg[phase]['list']
1985 for dev in sorted(list):
1986 pdev = list[dev]['par']
1987 pid = list[dev]['pid']
1988 if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
1989 continue
1990 if pdev and pdev not in real and pdev not in rootlist:
1991 rootlist.append(pdev)
1992 return rootlist
1993 def deviceTopology(self):
1994 rootlist = self.rootDeviceList()
1995 master = self.masterTopology('', rootlist, 0)
1996 return self.printTopology(master)
1997 def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
1998 # only select devices that will actually show up in html
1999 self.tdevlist = dict()
2000 for phase in self.dmesg:
2001 devlist = []
2002 list = self.dmesg[phase]['list']
2003 for dev in list:
2004 length = (list[dev]['end'] - list[dev]['start']) * 1000
2005 width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
2006 if length >= mindevlen:
2007 devlist.append(dev)
2008 self.tdevlist[phase] = devlist
2009 def addHorizontalDivider(self, devname, devend):
2010 phase = 'suspend_prepare'
2011 self.newAction(phase, devname, -2, '', \
2012 self.start, devend, '', ' sec', '')
2013 if phase not in self.tdevlist:
2014 self.tdevlist[phase] = []
2015 self.tdevlist[phase].append(devname)
2016 d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
2017 return d
2018 def addProcessUsageEvent(self, name, times):
2019 # get the start and end times for this process
2020 maxC = 0
2021 tlast = 0
2022 start = -1
2023 end = -1
2024 for t in sorted(times):
2025 if tlast == 0:
2026 tlast = t
2027 continue
2028 if name in self.pstl[t]:
2029 if start == -1 or tlast < start:
2030 start = tlast
2031 if end == -1 or t > end:
2032 end = t
2033 tlast = t
2034 if start == -1 or end == -1:
2035 return 0
2036 # add a new action for this process and get the object
2037 out = self.newActionGlobal(name, start, end, -3)
2038 if not out:
2039 return 0
2040 phase, devname = out
2041 dev = self.dmesg[phase]['list'][devname]
2042 # get the cpu exec data
2043 tlast = 0
2044 clast = 0
2045 cpuexec = dict()
2046 for t in sorted(times):
2047 if tlast == 0 or t <= start or t > end:
2048 tlast = t
2049 continue
2050 list = self.pstl[t]
2051 c = 0
2052 if name in list:
2053 c = list[name]
2054 if c > maxC:
2055 maxC = c
2056 if c != clast:
2057 key = (tlast, t)
2058 cpuexec[key] = c
2059 tlast = t
2060 clast = c
2061 dev['cpuexec'] = cpuexec
2062 return maxC
2063 def createProcessUsageEvents(self):
2064 # get an array of process names
2065 proclist = []
2066 for t in sorted(self.pstl):
2067 pslist = self.pstl[t]
2068 for ps in sorted(pslist):
2069 if ps not in proclist:
2070 proclist.append(ps)
2071 # get a list of data points for suspend and resume
2072 tsus = []
2073 tres = []
2074 for t in sorted(self.pstl):
2075 if t < self.tSuspended:
2076 tsus.append(t)
2077 else:
2078 tres.append(t)
2079 # process the events for suspend and resume
2080 if len(proclist) > 0:
2081 sysvals.vprint('Process Execution:')
2082 for ps in proclist:
2083 c = self.addProcessUsageEvent(ps, tsus)
2084 if c > 0:
2085 sysvals.vprint('%25s (sus): %d' % (ps, c))
2086 c = self.addProcessUsageEvent(ps, tres)
2087 if c > 0:
2088 sysvals.vprint('%25s (res): %d' % (ps, c))
2089 def handleEndMarker(self, time, msg=''):
2090 dm = self.dmesg
2091 self.setEnd(time, msg)
2092 self.initDevicegroups()
2093 # give suspend_prepare an end if needed
2094 if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2095 dm['suspend_prepare']['end'] = time
2096 # assume resume machine ends at next phase start
2097 if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2098 np = self.nextPhase('resume_machine', 1)
2099 if np:
2100 dm['resume_machine']['end'] = dm[np]['start']
2101 # if kernel resume end not found, assume its the end marker
2102 if self.tKernRes == 0.0:
2103 self.tKernRes = time
2104 # if kernel suspend start not found, assume its the end marker
2105 if self.tKernSus == 0.0:
2106 self.tKernSus = time
2107 # set resume complete to end at end marker
2108 if 'resume_complete' in dm:
2109 dm['resume_complete']['end'] = time
2110 def debugPrint(self):
2111 for p in self.sortedPhases():
2112 list = self.dmesg[p]['list']
2113 for devname in sorted(list):
2114 dev = list[devname]
2115 if 'ftrace' in dev:
2116 dev['ftrace'].debugPrint(' [%s]' % devname)
2117
2118# Class: DevFunction
2119# Description:
2120# A container for kprobe function data we want in the dev timeline
2121class DevFunction:
2122 def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2123 self.row = 0
2124 self.count = 1
2125 self.name = name
2126 self.args = args
2127 self.caller = caller
2128 self.ret = ret
2129 self.time = start
2130 self.length = end - start
2131 self.end = end
2132 self.ubiquitous = u
2133 self.proc = proc
2134 self.pid = pid
2135 self.color = color
2136 def title(self):
2137 cnt = ''
2138 if self.count > 1:
2139 cnt = '(x%d)' % self.count
2140 l = '%0.3fms' % (self.length * 1000)
2141 if self.ubiquitous:
2142 title = '%s(%s)%s <- %s, %s(%s)' % \
2143 (self.name, self.args, cnt, self.caller, self.ret, l)
2144 else:
2145 title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2146 return title.replace('"', '')
2147 def text(self):
2148 if self.count > 1:
2149 text = '%s(x%d)' % (self.name, self.count)
2150 else:
2151 text = self.name
2152 return text
2153 def repeat(self, tgt):
2154 # is the tgt call just a repeat of this call (e.g. are we in a loop)
2155 dt = self.time - tgt.end
2156 # only combine calls if -all- attributes are identical
2157 if tgt.caller == self.caller and \
2158 tgt.name == self.name and tgt.args == self.args and \
2159 tgt.proc == self.proc and tgt.pid == self.pid and \
2160 tgt.ret == self.ret and dt >= 0 and \
2161 dt <= sysvals.callloopmaxgap and \
2162 self.length < sysvals.callloopmaxlen:
2163 return True
2164 return False
2165
2166# Class: FTraceLine
2167# Description:
2168# A container for a single line of ftrace data. There are six basic types:
2169# callgraph line:
2170# call: " dpm_run_callback() {"
2171# return: " }"
2172# leaf: " dpm_run_callback();"
2173# trace event:
2174# tracing_mark_write: SUSPEND START or RESUME COMPLETE
2175# suspend_resume: phase or custom exec block data
2176# device_pm_callback: device callback info
2177class FTraceLine:
2178 def __init__(self, t, m='', d=''):
2179 self.length = 0.0
2180 self.fcall = False
2181 self.freturn = False
2182 self.fevent = False
2183 self.fkprobe = False
2184 self.depth = 0
2185 self.name = ''
2186 self.type = ''
2187 self.time = float(t)
2188 if not m and not d:
2189 return
2190 # is this a trace event
2191 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2192 if(d == 'traceevent'):
2193 # nop format trace event
2194 msg = m
2195 else:
2196 # function_graph format trace event
2197 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2198 msg = em.group('msg')
2199
2200 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
2201 if(emm):
2202 self.name = emm.group('msg')
2203 self.type = emm.group('call')
2204 else:
2205 self.name = msg
2206 km = re.match('^(?P<n>.*)_cal$', self.type)
2207 if km:
2208 self.fcall = True
2209 self.fkprobe = True
2210 self.type = km.group('n')
2211 return
2212 km = re.match('^(?P<n>.*)_ret$', self.type)
2213 if km:
2214 self.freturn = True
2215 self.fkprobe = True
2216 self.type = km.group('n')
2217 return
2218 self.fevent = True
2219 return
2220 # convert the duration to seconds
2221 if(d):
2222 self.length = float(d)/1000000
2223 # the indentation determines the depth
2224 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
2225 if(not match):
2226 return
2227 self.depth = self.getDepth(match.group('d'))
2228 m = match.group('o')
2229 # function return
2230 if(m[0] == '}'):
2231 self.freturn = True
2232 if(len(m) > 1):
2233 # includes comment with function name
2234 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2235 if(match):
2236 self.name = match.group('n').strip()
2237 # function call
2238 else:
2239 self.fcall = True
2240 # function call with children
2241 if(m[-1] == '{'):
2242 match = re.match('^(?P<n>.*) *\(.*', m)
2243 if(match):
2244 self.name = match.group('n').strip()
2245 # function call with no children (leaf)
2246 elif(m[-1] == ';'):
2247 self.freturn = True
2248 match = re.match('^(?P<n>.*) *\(.*', m)
2249 if(match):
2250 self.name = match.group('n').strip()
2251 # something else (possibly a trace marker)
2252 else:
2253 self.name = m
2254 def isCall(self):
2255 return self.fcall and not self.freturn
2256 def isReturn(self):
2257 return self.freturn and not self.fcall
2258 def isLeaf(self):
2259 return self.fcall and self.freturn
2260 def getDepth(self, str):
2261 return len(str)/2
2262 def debugPrint(self, info=''):
2263 if self.isLeaf():
2264 pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2265 self.depth, self.name, self.length*1000000, info))
2266 elif self.freturn:
2267 pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2268 self.depth, self.name, self.length*1000000, info))
2269 else:
2270 pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2271 self.depth, self.name, self.length*1000000, info))
2272 def startMarker(self):
2273 # Is this the starting line of a suspend?
2274 if not self.fevent:
2275 return False
2276 if sysvals.usetracemarkers:
2277 if(self.name.startswith('SUSPEND START')):
2278 return True
2279 return False
2280 else:
2281 if(self.type == 'suspend_resume' and
2282 re.match('suspend_enter\[.*\] begin', self.name)):
2283 return True
2284 return False
2285 def endMarker(self):
2286 # Is this the ending line of a resume?
2287 if not self.fevent:
2288 return False
2289 if sysvals.usetracemarkers:
2290 if(self.name.startswith('RESUME COMPLETE')):
2291 return True
2292 return False
2293 else:
2294 if(self.type == 'suspend_resume' and
2295 re.match('thaw_processes\[.*\] end', self.name)):
2296 return True
2297 return False
2298
2299# Class: FTraceCallGraph
2300# Description:
2301# A container for the ftrace callgraph of a single recursive function.
2302# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2303# Each instance is tied to a single device in a single phase, and is
2304# comprised of an ordered list of FTraceLine objects
2305class FTraceCallGraph:
2306 vfname = 'missing_function_name'
2307 def __init__(self, pid, sv):
2308 self.id = ''
2309 self.invalid = False
2310 self.name = ''
2311 self.partial = False
2312 self.ignore = False
2313 self.start = -1.0
2314 self.end = -1.0
2315 self.list = []
2316 self.depth = 0
2317 self.pid = pid
2318 self.sv = sv
2319 def addLine(self, line):
2320 # if this is already invalid, just leave
2321 if(self.invalid):
2322 if(line.depth == 0 and line.freturn):
2323 return 1
2324 return 0
2325 # invalidate on bad depth
2326 if(self.depth < 0):
2327 self.invalidate(line)
2328 return 0
2329 # ignore data til we return to the current depth
2330 if self.ignore:
2331 if line.depth > self.depth:
2332 return 0
2333 else:
2334 self.list[-1].freturn = True
2335 self.list[-1].length = line.time - self.list[-1].time
2336 self.ignore = False
2337 # if this is a return at self.depth, no more work is needed
2338 if line.depth == self.depth and line.isReturn():
2339 if line.depth == 0:
2340 self.end = line.time
2341 return 1
2342 return 0
2343 # compare current depth with this lines pre-call depth
2344 prelinedep = line.depth
2345 if line.isReturn():
2346 prelinedep += 1
2347 last = 0
2348 lasttime = line.time
2349 if len(self.list) > 0:
2350 last = self.list[-1]
2351 lasttime = last.time
2352 if last.isLeaf():
2353 lasttime += last.length
2354 # handle low misalignments by inserting returns
2355 mismatch = prelinedep - self.depth
2356 warning = self.sv.verbose and abs(mismatch) > 1
2357 info = []
2358 if mismatch < 0:
2359 idx = 0
2360 # add return calls to get the depth down
2361 while prelinedep < self.depth:
2362 self.depth -= 1
2363 if idx == 0 and last and last.isCall():
2364 # special case, turn last call into a leaf
2365 last.depth = self.depth
2366 last.freturn = True
2367 last.length = line.time - last.time
2368 if warning:
2369 info.append(('[make leaf]', last))
2370 else:
2371 vline = FTraceLine(lasttime)
2372 vline.depth = self.depth
2373 vline.name = self.vfname
2374 vline.freturn = True
2375 self.list.append(vline)
2376 if warning:
2377 if idx == 0:
2378 info.append(('', last))
2379 info.append(('[add return]', vline))
2380 idx += 1
2381 if warning:
2382 info.append(('', line))
2383 # handle high misalignments by inserting calls
2384 elif mismatch > 0:
2385 idx = 0
2386 if warning:
2387 info.append(('', last))
2388 # add calls to get the depth up
2389 while prelinedep > self.depth:
2390 if idx == 0 and line.isReturn():
2391 # special case, turn this return into a leaf
2392 line.fcall = True
2393 prelinedep -= 1
2394 if warning:
2395 info.append(('[make leaf]', line))
2396 else:
2397 vline = FTraceLine(lasttime)
2398 vline.depth = self.depth
2399 vline.name = self.vfname
2400 vline.fcall = True
2401 self.list.append(vline)
2402 self.depth += 1
2403 if not last:
2404 self.start = vline.time
2405 if warning:
2406 info.append(('[add call]', vline))
2407 idx += 1
2408 if warning and ('[make leaf]', line) not in info:
2409 info.append(('', line))
2410 if warning:
2411 pprint('WARNING: ftrace data missing, corrections made:')
2412 for i in info:
2413 t, obj = i
2414 if obj:
2415 obj.debugPrint(t)
2416 # process the call and set the new depth
2417 skipadd = False
2418 md = self.sv.max_graph_depth
2419 if line.isCall():
2420 # ignore blacklisted/overdepth funcs
2421 if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2422 self.ignore = True
2423 else:
2424 self.depth += 1
2425 elif line.isReturn():
2426 self.depth -= 1
2427 # remove blacklisted/overdepth/empty funcs that slipped through
2428 if (last and last.isCall() and last.depth == line.depth) or \
2429 (md and last and last.depth >= md) or \
2430 (line.name in self.sv.cgblacklist):
2431 while len(self.list) > 0 and self.list[-1].depth > line.depth:
2432 self.list.pop(-1)
2433 if len(self.list) == 0:
2434 self.invalid = True
2435 return 1
2436 self.list[-1].freturn = True
2437 self.list[-1].length = line.time - self.list[-1].time
2438 self.list[-1].name = line.name
2439 skipadd = True
2440 if len(self.list) < 1:
2441 self.start = line.time
2442 # check for a mismatch that returned all the way to callgraph end
2443 res = 1
2444 if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2445 line = self.list[-1]
2446 skipadd = True
2447 res = -1
2448 if not skipadd:
2449 self.list.append(line)
2450 if(line.depth == 0 and line.freturn):
2451 if(self.start < 0):
2452 self.start = line.time
2453 self.end = line.time
2454 if line.fcall:
2455 self.end += line.length
2456 if self.list[0].name == self.vfname:
2457 self.invalid = True
2458 if res == -1:
2459 self.partial = True
2460 return res
2461 return 0
2462 def invalidate(self, line):
2463 if(len(self.list) > 0):
2464 first = self.list[0]
2465 self.list = []
2466 self.list.append(first)
2467 self.invalid = True
2468 id = 'task %s' % (self.pid)
2469 window = '(%f - %f)' % (self.start, line.time)
2470 if(self.depth < 0):
2471 pprint('Data misalignment for '+id+\
2472 ' (buffer overflow), ignoring this callback')
2473 else:
2474 pprint('Too much data for '+id+\
2475 ' '+window+', ignoring this callback')
2476 def slice(self, dev):
2477 minicg = FTraceCallGraph(dev['pid'], self.sv)
2478 minicg.name = self.name
2479 mydepth = -1
2480 good = False
2481 for l in self.list:
2482 if(l.time < dev['start'] or l.time > dev['end']):
2483 continue
2484 if mydepth < 0:
2485 if l.name == 'mutex_lock' and l.freturn:
2486 mydepth = l.depth
2487 continue
2488 elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2489 good = True
2490 break
2491 l.depth -= mydepth
2492 minicg.addLine(l)
2493 if not good or len(minicg.list) < 1:
2494 return 0
2495 return minicg
2496 def repair(self, enddepth):
2497 # bring the depth back to 0 with additional returns
2498 fixed = False
2499 last = self.list[-1]
2500 for i in reversed(range(enddepth)):
2501 t = FTraceLine(last.time)
2502 t.depth = i
2503 t.freturn = True
2504 fixed = self.addLine(t)
2505 if fixed != 0:
2506 self.end = last.time
2507 return True
2508 return False
2509 def postProcess(self):
2510 if len(self.list) > 0:
2511 self.name = self.list[0].name
2512 stack = dict()
2513 cnt = 0
2514 last = 0
2515 for l in self.list:
2516 # ftrace bug: reported duration is not reliable
2517 # check each leaf and clip it at max possible length
2518 if last and last.isLeaf():
2519 if last.length > l.time - last.time:
2520 last.length = l.time - last.time
2521 if l.isCall():
2522 stack[l.depth] = l
2523 cnt += 1
2524 elif l.isReturn():
2525 if(l.depth not in stack):
2526 if self.sv.verbose:
2527 pprint('Post Process Error: Depth missing')
2528 l.debugPrint()
2529 return False
2530 # calculate call length from call/return lines
2531 cl = stack[l.depth]
2532 cl.length = l.time - cl.time
2533 if cl.name == self.vfname:
2534 cl.name = l.name
2535 stack.pop(l.depth)
2536 l.length = 0
2537 cnt -= 1
2538 last = l
2539 if(cnt == 0):
2540 # trace caught the whole call tree
2541 return True
2542 elif(cnt < 0):
2543 if self.sv.verbose:
2544 pprint('Post Process Error: Depth is less than 0')
2545 return False
2546 # trace ended before call tree finished
2547 return self.repair(cnt)
2548 def deviceMatch(self, pid, data):
2549 found = ''
2550 # add the callgraph data to the device hierarchy
2551 borderphase = {
2552 'dpm_prepare': 'suspend_prepare',
2553 'dpm_complete': 'resume_complete'
2554 }
2555 if(self.name in borderphase):
2556 p = borderphase[self.name]
2557 list = data.dmesg[p]['list']
2558 for devname in list:
2559 dev = list[devname]
2560 if(pid == dev['pid'] and
2561 self.start <= dev['start'] and
2562 self.end >= dev['end']):
2563 cg = self.slice(dev)
2564 if cg:
2565 dev['ftrace'] = cg
2566 found = devname
2567 return found
2568 for p in data.sortedPhases():
2569 if(data.dmesg[p]['start'] <= self.start and
2570 self.start <= data.dmesg[p]['end']):
2571 list = data.dmesg[p]['list']
2572 for devname in sorted(list, key=lambda k:list[k]['start']):
2573 dev = list[devname]
2574 if(pid == dev['pid'] and
2575 self.start <= dev['start'] and
2576 self.end >= dev['end']):
2577 dev['ftrace'] = self
2578 found = devname
2579 break
2580 break
2581 return found
2582 def newActionFromFunction(self, data):
2583 name = self.name
2584 if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2585 return
2586 fs = self.start
2587 fe = self.end
2588 if fs < data.start or fe > data.end:
2589 return
2590 phase = ''
2591 for p in data.sortedPhases():
2592 if(data.dmesg[p]['start'] <= self.start and
2593 self.start < data.dmesg[p]['end']):
2594 phase = p
2595 break
2596 if not phase:
2597 return
2598 out = data.newActionGlobal(name, fs, fe, -2)
2599 if out:
2600 phase, myname = out
2601 data.dmesg[phase]['list'][myname]['ftrace'] = self
2602 def debugPrint(self, info=''):
2603 pprint('%s pid=%d [%f - %f] %.3f us' % \
2604 (self.name, self.pid, self.start, self.end,
2605 (self.end - self.start)*1000000))
2606 for l in self.list:
2607 if l.isLeaf():
2608 pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2609 l.depth, l.name, l.length*1000000, info))
2610 elif l.freturn:
2611 pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2612 l.depth, l.name, l.length*1000000, info))
2613 else:
2614 pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2615 l.depth, l.name, l.length*1000000, info))
2616 pprint(' ')
2617
2618class DevItem:
2619 def __init__(self, test, phase, dev):
2620 self.test = test
2621 self.phase = phase
2622 self.dev = dev
2623 def isa(self, cls):
2624 if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2625 return True
2626 return False
2627
2628# Class: Timeline
2629# Description:
2630# A container for a device timeline which calculates
2631# all the html properties to display it correctly
2632class Timeline:
2633 html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2634 html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2635 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2636 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2637 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}"> {2}</div>\n'
2638 def __init__(self, rowheight, scaleheight):
2639 self.html = ''
2640 self.height = 0 # total timeline height
2641 self.scaleH = scaleheight # timescale (top) row height
2642 self.rowH = rowheight # device row height
2643 self.bodyH = 0 # body height
2644 self.rows = 0 # total timeline rows
2645 self.rowlines = dict()
2646 self.rowheight = dict()
2647 def createHeader(self, sv, stamp):
2648 if(not stamp['time']):
2649 return
2650 self.html += '<div class="version"><a href="https://01.org/pm-graph">%s v%s</a></div>' \
2651 % (sv.title, sv.version)
2652 if sv.logmsg and sv.testlog:
2653 self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2654 if sv.dmesglog:
2655 self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2656 if sv.ftracelog:
2657 self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2658 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2659 self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2660 stamp['mode'], stamp['time'])
2661 if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2662 stamp['man'] and stamp['plat'] and stamp['cpu']:
2663 headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2664 self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2665
2666 # Function: getDeviceRows
2667 # Description:
2668 # determine how may rows the device funcs will take
2669 # Arguments:
2670 # rawlist: the list of devices/actions for a single phase
2671 # Output:
2672 # The total number of rows needed to display this phase of the timeline
2673 def getDeviceRows(self, rawlist):
2674 # clear all rows and set them to undefined
2675 sortdict = dict()
2676 for item in rawlist:
2677 item.row = -1
2678 sortdict[item] = item.length
2679 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2680 remaining = len(sortlist)
2681 rowdata = dict()
2682 row = 1
2683 # try to pack each row with as many ranges as possible
2684 while(remaining > 0):
2685 if(row not in rowdata):
2686 rowdata[row] = []
2687 for i in sortlist:
2688 if(i.row >= 0):
2689 continue
2690 s = i.time
2691 e = i.time + i.length
2692 valid = True
2693 for ritem in rowdata[row]:
2694 rs = ritem.time
2695 re = ritem.time + ritem.length
2696 if(not (((s <= rs) and (e <= rs)) or
2697 ((s >= re) and (e >= re)))):
2698 valid = False
2699 break
2700 if(valid):
2701 rowdata[row].append(i)
2702 i.row = row
2703 remaining -= 1
2704 row += 1
2705 return row
2706 # Function: getPhaseRows
2707 # Description:
2708 # Organize the timeline entries into the smallest
2709 # number of rows possible, with no entry overlapping
2710 # Arguments:
2711 # devlist: the list of devices/actions in a group of contiguous phases
2712 # Output:
2713 # The total number of rows needed to display this phase of the timeline
2714 def getPhaseRows(self, devlist, row=0, sortby='length'):
2715 # clear all rows and set them to undefined
2716 remaining = len(devlist)
2717 rowdata = dict()
2718 sortdict = dict()
2719 myphases = []
2720 # initialize all device rows to -1 and calculate devrows
2721 for item in devlist:
2722 dev = item.dev
2723 tp = (item.test, item.phase)
2724 if tp not in myphases:
2725 myphases.append(tp)
2726 dev['row'] = -1
2727 if sortby == 'start':
2728 # sort by start 1st, then length 2nd
2729 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2730 else:
2731 # sort by length 1st, then name 2nd
2732 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2733 if 'src' in dev:
2734 dev['devrows'] = self.getDeviceRows(dev['src'])
2735 # sort the devlist by length so that large items graph on top
2736 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2737 orderedlist = []
2738 for item in sortlist:
2739 if item.dev['pid'] == -2:
2740 orderedlist.append(item)
2741 for item in sortlist:
2742 if item not in orderedlist:
2743 orderedlist.append(item)
2744 # try to pack each row with as many devices as possible
2745 while(remaining > 0):
2746 rowheight = 1
2747 if(row not in rowdata):
2748 rowdata[row] = []
2749 for item in orderedlist:
2750 dev = item.dev
2751 if(dev['row'] < 0):
2752 s = dev['start']
2753 e = dev['end']
2754 valid = True
2755 for ritem in rowdata[row]:
2756 rs = ritem.dev['start']
2757 re = ritem.dev['end']
2758 if(not (((s <= rs) and (e <= rs)) or
2759 ((s >= re) and (e >= re)))):
2760 valid = False
2761 break
2762 if(valid):
2763 rowdata[row].append(item)
2764 dev['row'] = row
2765 remaining -= 1
2766 if 'devrows' in dev and dev['devrows'] > rowheight:
2767 rowheight = dev['devrows']
2768 for t, p in myphases:
2769 if t not in self.rowlines or t not in self.rowheight:
2770 self.rowlines[t] = dict()
2771 self.rowheight[t] = dict()
2772 if p not in self.rowlines[t] or p not in self.rowheight[t]:
2773 self.rowlines[t][p] = dict()
2774 self.rowheight[t][p] = dict()
2775 rh = self.rowH
2776 # section headers should use a different row height
2777 if len(rowdata[row]) == 1 and \
2778 'htmlclass' in rowdata[row][0].dev and \
2779 'sec' in rowdata[row][0].dev['htmlclass']:
2780 rh = 15
2781 self.rowlines[t][p][row] = rowheight
2782 self.rowheight[t][p][row] = rowheight * rh
2783 row += 1
2784 if(row > self.rows):
2785 self.rows = int(row)
2786 return row
2787 def phaseRowHeight(self, test, phase, row):
2788 return self.rowheight[test][phase][row]
2789 def phaseRowTop(self, test, phase, row):
2790 top = 0
2791 for i in sorted(self.rowheight[test][phase]):
2792 if i >= row:
2793 break
2794 top += self.rowheight[test][phase][i]
2795 return top
2796 def calcTotalRows(self):
2797 # Calculate the heights and offsets for the header and rows
2798 maxrows = 0
2799 standardphases = []
2800 for t in self.rowlines:
2801 for p in self.rowlines[t]:
2802 total = 0
2803 for i in sorted(self.rowlines[t][p]):
2804 total += self.rowlines[t][p][i]
2805 if total > maxrows:
2806 maxrows = total
2807 if total == len(self.rowlines[t][p]):
2808 standardphases.append((t, p))
2809 self.height = self.scaleH + (maxrows*self.rowH)
2810 self.bodyH = self.height - self.scaleH
2811 # if there is 1 line per row, draw them the standard way
2812 for t, p in standardphases:
2813 for i in sorted(self.rowheight[t][p]):
2814 self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2815 def createZoomBox(self, mode='command', testcount=1):
2816 # Create bounding box, add buttons
2817 html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2818 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2819 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2820 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2821 if mode != 'command':
2822 if testcount > 1:
2823 self.html += html_devlist2
2824 self.html += html_devlist1.format('1')
2825 else:
2826 self.html += html_devlist1.format('')
2827 self.html += html_zoombox
2828 self.html += html_timeline.format('dmesg', self.height)
2829 # Function: createTimeScale
2830 # Description:
2831 # Create the timescale for a timeline block
2832 # Arguments:
2833 # m0: start time (mode begin)
2834 # mMax: end time (mode end)
2835 # tTotal: total timeline time
2836 # mode: suspend or resume
2837 # Output:
2838 # The html code needed to display the time scale
2839 def createTimeScale(self, m0, mMax, tTotal, mode):
2840 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2841 rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2842 output = '<div class="timescale">\n'
2843 # set scale for timeline
2844 mTotal = mMax - m0
2845 tS = 0.1
2846 if(tTotal <= 0):
2847 return output+'</div>\n'
2848 if(tTotal > 4):
2849 tS = 1
2850 divTotal = int(mTotal/tS) + 1
2851 divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2852 for i in range(divTotal):
2853 htmlline = ''
2854 if(mode == 'suspend'):
2855 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2856 val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2857 if(i == divTotal - 1):
2858 val = mode
2859 htmlline = timescale.format(pos, val)
2860 else:
2861 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2862 val = '%0.fms' % (float(i)*tS*1000)
2863 htmlline = timescale.format(pos, val)
2864 if(i == 0):
2865 htmlline = rline.format(mode)
2866 output += htmlline
2867 self.html += output+'</div>\n'
2868
2869# Class: TestProps
2870# Description:
2871# A list of values describing the properties of these test runs
2872class TestProps:
2873 stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2874 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2875 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2876 wififmt = '^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
2877 tstatfmt = '^# turbostat (?P<t>\S*)'
2878 testerrfmt = '^# enter_sleep_error (?P<e>.*)'
2879 sysinfofmt = '^# sysinfo .*'
2880 cmdlinefmt = '^# command \| (?P<cmd>.*)'
2881 kparamsfmt = '^# kparams \| (?P<kp>.*)'
2882 devpropfmt = '# Device Properties: .*'
2883 pinfofmt = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
2884 tracertypefmt = '# tracer: (?P<t>.*)'
2885 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
2886 procexecfmt = 'ps - (?P<ps>.*)$'
2887 ftrace_line_fmt_fg = \
2888 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2889 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2890 '[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
2891 ftrace_line_fmt_nop = \
2892 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
2893 '(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
2894 '(?P<msg>.*)'
2895 machinesuspend = 'machine_suspend\[.*'
2896 def __init__(self):
2897 self.stamp = ''
2898 self.sysinfo = ''
2899 self.cmdline = ''
2900 self.testerror = []
2901 self.turbostat = []
2902 self.wifi = []
2903 self.fwdata = []
2904 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2905 self.cgformat = False
2906 self.data = 0
2907 self.ktemp = dict()
2908 def setTracerType(self, tracer):
2909 if(tracer == 'function_graph'):
2910 self.cgformat = True
2911 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
2912 elif(tracer == 'nop'):
2913 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
2914 else:
2915 doError('Invalid tracer format: [%s]' % tracer)
2916 def stampInfo(self, line, sv):
2917 if re.match(self.stampfmt, line):
2918 self.stamp = line
2919 return True
2920 elif re.match(self.sysinfofmt, line):
2921 self.sysinfo = line
2922 return True
2923 elif re.match(self.tstatfmt, line):
2924 self.turbostat.append(line)
2925 return True
2926 elif re.match(self.wififmt, line):
2927 self.wifi.append(line)
2928 return True
2929 elif re.match(self.testerrfmt, line):
2930 self.testerror.append(line)
2931 return True
2932 elif re.match(self.firmwarefmt, line):
2933 self.fwdata.append(line)
2934 return True
2935 elif(re.match(self.devpropfmt, line)):
2936 self.parseDevprops(line, sv)
2937 return True
2938 elif(re.match(self.pinfofmt, line)):
2939 self.parsePlatformInfo(line, sv)
2940 return True
2941 m = re.match(self.cmdlinefmt, line)
2942 if m:
2943 self.cmdline = m.group('cmd')
2944 return True
2945 m = re.match(self.tracertypefmt, line)
2946 if(m):
2947 self.setTracerType(m.group('t'))
2948 return True
2949 return False
2950 def parseStamp(self, data, sv):
2951 # global test data
2952 m = re.match(self.stampfmt, self.stamp)
2953 if not self.stamp or not m:
2954 doError('data does not include the expected stamp')
2955 data.stamp = {'time': '', 'host': '', 'mode': ''}
2956 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
2957 int(m.group('d')), int(m.group('H')), int(m.group('M')),
2958 int(m.group('S')))
2959 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
2960 data.stamp['host'] = m.group('host')
2961 data.stamp['mode'] = m.group('mode')
2962 data.stamp['kernel'] = m.group('kernel')
2963 if re.match(self.sysinfofmt, self.sysinfo):
2964 for f in self.sysinfo.split('|'):
2965 if '#' in f:
2966 continue
2967 tmp = f.strip().split(':', 1)
2968 key = tmp[0]
2969 val = tmp[1]
2970 data.stamp[key] = val
2971 sv.hostname = data.stamp['host']
2972 sv.suspendmode = data.stamp['mode']
2973 if sv.suspendmode == 'freeze':
2974 self.machinesuspend = 'timekeeping_freeze\[.*'
2975 else:
2976 self.machinesuspend = 'machine_suspend\[.*'
2977 if sv.suspendmode == 'command' and sv.ftracefile != '':
2978 modes = ['on', 'freeze', 'standby', 'mem', 'disk']
2979 fp = sv.openlog(sv.ftracefile, 'r')
2980 for line in fp:
2981 m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
2982 if m and m.group('mode') in ['1', '2', '3', '4']:
2983 sv.suspendmode = modes[int(m.group('mode'))]
2984 data.stamp['mode'] = sv.suspendmode
2985 break
2986 fp.close()
2987 sv.cmdline = self.cmdline
2988 if not sv.stamp:
2989 sv.stamp = data.stamp
2990 # firmware data
2991 if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
2992 m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
2993 if m:
2994 data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
2995 if(data.fwSuspend > 0 or data.fwResume > 0):
2996 data.fwValid = True
2997 # turbostat data
2998 if len(self.turbostat) > data.testnumber:
2999 m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
3000 if m:
3001 data.turbostat = m.group('t')
3002 # wifi data
3003 if len(self.wifi) > data.testnumber:
3004 m = re.match(self.wififmt, self.wifi[data.testnumber])
3005 if m:
3006 data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
3007 'time': float(m.group('t'))}
3008 data.stamp['wifi'] = m.group('d')
3009 # sleep mode enter errors
3010 if len(self.testerror) > data.testnumber:
3011 m = re.match(self.testerrfmt, self.testerror[data.testnumber])
3012 if m:
3013 data.enterfail = m.group('e')
3014 def devprops(self, data):
3015 props = dict()
3016 devlist = data.split(';')
3017 for dev in devlist:
3018 f = dev.split(',')
3019 if len(f) < 3:
3020 continue
3021 dev = f[0]
3022 props[dev] = DevProps()
3023 props[dev].altname = f[1]
3024 if int(f[2]):
3025 props[dev].isasync = True
3026 else:
3027 props[dev].isasync = False
3028 return props
3029 def parseDevprops(self, line, sv):
3030 idx = line.index(': ') + 2
3031 if idx >= len(line):
3032 return
3033 props = self.devprops(line[idx:])
3034 if sv.suspendmode == 'command' and 'testcommandstring' in props:
3035 sv.testcommand = props['testcommandstring'].altname
3036 sv.devprops = props
3037 def parsePlatformInfo(self, line, sv):
3038 m = re.match(self.pinfofmt, line)
3039 if not m:
3040 return
3041 name, info = m.group('val'), m.group('info')
3042 if name == 'devinfo':
3043 sv.devprops = self.devprops(sv.b64unzip(info))
3044 return
3045 elif name == 'testcmd':
3046 sv.testcommand = info
3047 return
3048 field = info.split('|')
3049 if len(field) < 2:
3050 return
3051 cmdline = field[0].strip()
3052 output = sv.b64unzip(field[1].strip())
3053 sv.platinfo.append([name, cmdline, output])
3054
3055# Class: TestRun
3056# Description:
3057# A container for a suspend/resume test run. This is necessary as
3058# there could be more than one, and they need to be separate.
3059class TestRun:
3060 def __init__(self, dataobj):
3061 self.data = dataobj
3062 self.ftemp = dict()
3063 self.ttemp = dict()
3064
3065class ProcessMonitor:
3066 def __init__(self):
3067 self.proclist = dict()
3068 self.running = False
3069 def procstat(self):
3070 c = ['cat /proc/[1-9]*/stat 2>/dev/null']
3071 process = Popen(c, shell=True, stdout=PIPE)
3072 running = dict()
3073 for line in process.stdout:
3074 data = ascii(line).split()
3075 pid = data[0]
3076 name = re.sub('[()]', '', data[1])
3077 user = int(data[13])
3078 kern = int(data[14])
3079 kjiff = ujiff = 0
3080 if pid not in self.proclist:
3081 self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3082 else:
3083 val = self.proclist[pid]
3084 ujiff = user - val['user']
3085 kjiff = kern - val['kern']
3086 val['user'] = user
3087 val['kern'] = kern
3088 if ujiff > 0 or kjiff > 0:
3089 running[pid] = ujiff + kjiff
3090 process.wait()
3091 out = ''
3092 for pid in running:
3093 jiffies = running[pid]
3094 val = self.proclist[pid]
3095 if out:
3096 out += ','
3097 out += '%s-%s %d' % (val['name'], pid, jiffies)
3098 return 'ps - '+out
3099 def processMonitor(self, tid):
3100 while self.running:
3101 out = self.procstat()
3102 if out:
3103 sysvals.fsetVal(out, 'trace_marker')
3104 def start(self):
3105 self.thread = Thread(target=self.processMonitor, args=(0,))
3106 self.running = True
3107 self.thread.start()
3108 def stop(self):
3109 self.running = False
3110
3111# ----------------- FUNCTIONS --------------------
3112
3113# Function: doesTraceLogHaveTraceEvents
3114# Description:
3115# Quickly determine if the ftrace log has all of the trace events,
3116# markers, and/or kprobes required for primary parsing.
3117def doesTraceLogHaveTraceEvents():
3118 kpcheck = ['_cal: (', '_ret: (']
3119 techeck = ['suspend_resume', 'device_pm_callback']
3120 tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3121 sysvals.usekprobes = False
3122 fp = sysvals.openlog(sysvals.ftracefile, 'r')
3123 for line in fp:
3124 # check for kprobes
3125 if not sysvals.usekprobes:
3126 for i in kpcheck:
3127 if i in line:
3128 sysvals.usekprobes = True
3129 # check for all necessary trace events
3130 check = techeck[:]
3131 for i in techeck:
3132 if i in line:
3133 check.remove(i)
3134 techeck = check
3135 # check for all necessary trace markers
3136 check = tmcheck[:]
3137 for i in tmcheck:
3138 if i in line:
3139 check.remove(i)
3140 tmcheck = check
3141 fp.close()
3142 sysvals.usetraceevents = True if len(techeck) < 2 else False
3143 sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
3144
3145# Function: appendIncompleteTraceLog
3146# Description:
3147# [deprecated for kernel 3.15 or newer]
3148# Adds callgraph data which lacks trace event data. This is only
3149# for timelines generated from 3.15 or older
3150# Arguments:
3151# testruns: the array of Data objects obtained from parseKernelLog
3152def appendIncompleteTraceLog(testruns):
3153 # create TestRun vessels for ftrace parsing
3154 testcnt = len(testruns)
3155 testidx = 0
3156 testrun = []
3157 for data in testruns:
3158 testrun.append(TestRun(data))
3159
3160 # extract the callgraph and traceevent data
3161 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3162 os.path.basename(sysvals.ftracefile))
3163 tp = TestProps()
3164 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3165 data = 0
3166 for line in tf:
3167 # remove any latent carriage returns
3168 line = line.replace('\r\n', '')
3169 if tp.stampInfo(line, sysvals):
3170 continue
3171 # parse only valid lines, if this is not one move on
3172 m = re.match(tp.ftrace_line_fmt, line)
3173 if(not m):
3174 continue
3175 # gather the basic message data from the line
3176 m_time = m.group('time')
3177 m_pid = m.group('pid')
3178 m_msg = m.group('msg')
3179 if(tp.cgformat):
3180 m_param3 = m.group('dur')
3181 else:
3182 m_param3 = 'traceevent'
3183 if(m_time and m_pid and m_msg):
3184 t = FTraceLine(m_time, m_msg, m_param3)
3185 pid = int(m_pid)
3186 else:
3187 continue
3188 # the line should be a call, return, or event
3189 if(not t.fcall and not t.freturn and not t.fevent):
3190 continue
3191 # look for the suspend start marker
3192 if(t.startMarker()):
3193 data = testrun[testidx].data
3194 tp.parseStamp(data, sysvals)
3195 data.setStart(t.time, t.name)
3196 continue
3197 if(not data):
3198 continue
3199 # find the end of resume
3200 if(t.endMarker()):
3201 data.setEnd(t.time, t.name)
3202 testidx += 1
3203 if(testidx >= testcnt):
3204 break
3205 continue
3206 # trace event processing
3207 if(t.fevent):
3208 continue
3209 # call/return processing
3210 elif sysvals.usecallgraph:
3211 # create a callgraph object for the data
3212 if(pid not in testrun[testidx].ftemp):
3213 testrun[testidx].ftemp[pid] = []
3214 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3215 # when the call is finished, see which device matches it
3216 cg = testrun[testidx].ftemp[pid][-1]
3217 res = cg.addLine(t)
3218 if(res != 0):
3219 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3220 if(res == -1):
3221 testrun[testidx].ftemp[pid][-1].addLine(t)
3222 tf.close()
3223
3224 for test in testrun:
3225 # add the callgraph data to the device hierarchy
3226 for pid in test.ftemp:
3227 for cg in test.ftemp[pid]:
3228 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3229 continue
3230 if(not cg.postProcess()):
3231 id = 'task %s cpu %s' % (pid, m.group('cpu'))
3232 sysvals.vprint('Sanity check failed for '+\
3233 id+', ignoring this callback')
3234 continue
3235 callstart = cg.start
3236 callend = cg.end
3237 for p in test.data.sortedPhases():
3238 if(test.data.dmesg[p]['start'] <= callstart and
3239 callstart <= test.data.dmesg[p]['end']):
3240 list = test.data.dmesg[p]['list']
3241 for devname in list:
3242 dev = list[devname]
3243 if(pid == dev['pid'] and
3244 callstart <= dev['start'] and
3245 callend >= dev['end']):
3246 dev['ftrace'] = cg
3247 break
3248
3249# Function: parseTraceLog
3250# Description:
3251# Analyze an ftrace log output file generated from this app during
3252# the execution phase. Used when the ftrace log is the primary data source
3253# and includes the suspend_resume and device_pm_callback trace events
3254# The ftrace filename is taken from sysvals
3255# Output:
3256# An array of Data objects
3257def parseTraceLog(live=False):
3258 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3259 os.path.basename(sysvals.ftracefile))
3260 if(os.path.exists(sysvals.ftracefile) == False):
3261 doError('%s does not exist' % sysvals.ftracefile)
3262 if not live:
3263 sysvals.setupAllKprobes()
3264 ksuscalls = ['ksys_sync', 'pm_prepare_console']
3265 krescalls = ['pm_restore_console']
3266 tracewatch = ['irq_wakeup']
3267 if sysvals.usekprobes:
3268 tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3269 'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3270 'CPU_OFF', 'acpi_suspend']
3271
3272 # extract the callgraph and traceevent data
3273 s2idle_enter = hwsus = False
3274 tp = TestProps()
3275 testruns, testdata = [], []
3276 testrun, data, limbo = 0, 0, True
3277 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3278 phase = 'suspend_prepare'
3279 for line in tf:
3280 # remove any latent carriage returns
3281 line = line.replace('\r\n', '')
3282 if tp.stampInfo(line, sysvals):
3283 continue
3284 # ignore all other commented lines
3285 if line[0] == '#':
3286 continue
3287 # ftrace line: parse only valid lines
3288 m = re.match(tp.ftrace_line_fmt, line)
3289 if(not m):
3290 continue
3291 # gather the basic message data from the line
3292 m_time = m.group('time')
3293 m_proc = m.group('proc')
3294 m_pid = m.group('pid')
3295 m_msg = m.group('msg')
3296 if(tp.cgformat):
3297 m_param3 = m.group('dur')
3298 else:
3299 m_param3 = 'traceevent'
3300 if(m_time and m_pid and m_msg):
3301 t = FTraceLine(m_time, m_msg, m_param3)
3302 pid = int(m_pid)
3303 else:
3304 continue
3305 # the line should be a call, return, or event
3306 if(not t.fcall and not t.freturn and not t.fevent):
3307 continue
3308 # find the start of suspend
3309 if(t.startMarker()):
3310 data, limbo = Data(len(testdata)), False
3311 testdata.append(data)
3312 testrun = TestRun(data)
3313 testruns.append(testrun)
3314 tp.parseStamp(data, sysvals)
3315 data.setStart(t.time, t.name)
3316 data.first_suspend_prepare = True
3317 phase = data.setPhase('suspend_prepare', t.time, True)
3318 continue
3319 if(not data or limbo):
3320 continue
3321 # process cpu exec line
3322 if t.type == 'tracing_mark_write':
3323 m = re.match(tp.procexecfmt, t.name)
3324 if(m):
3325 proclist = dict()
3326 for ps in m.group('ps').split(','):
3327 val = ps.split()
3328 if not val:
3329 continue
3330 name = val[0].replace('--', '-')
3331 proclist[name] = int(val[1])
3332 data.pstl[t.time] = proclist
3333 continue
3334 # find the end of resume
3335 if(t.endMarker()):
3336 if data.tKernRes == 0:
3337 data.tKernRes = t.time
3338 data.handleEndMarker(t.time, t.name)
3339 if(not sysvals.usetracemarkers):
3340 # no trace markers? then quit and be sure to finish recording
3341 # the event we used to trigger resume end
3342 if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3343 # if an entry exists, assume this is its end
3344 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3345 limbo = True
3346 continue
3347 # trace event processing
3348 if(t.fevent):
3349 if(t.type == 'suspend_resume'):
3350 # suspend_resume trace events have two types, begin and end
3351 if(re.match('(?P<name>.*) begin$', t.name)):
3352 isbegin = True
3353 elif(re.match('(?P<name>.*) end$', t.name)):
3354 isbegin = False
3355 else:
3356 continue
3357 if '[' in t.name:
3358 m = re.match('(?P<name>.*)\[.*', t.name)
3359 else:
3360 m = re.match('(?P<name>.*) .*', t.name)
3361 name = m.group('name')
3362 # ignore these events
3363 if(name.split('[')[0] in tracewatch):
3364 continue
3365 # -- phase changes --
3366 # start of kernel suspend
3367 if(re.match('suspend_enter\[.*', t.name)):
3368 if(isbegin and data.tKernSus == 0):
3369 data.tKernSus = t.time
3370 continue
3371 # suspend_prepare start
3372 elif(re.match('dpm_prepare\[.*', t.name)):
3373 if isbegin and data.first_suspend_prepare:
3374 data.first_suspend_prepare = False
3375 if data.tKernSus == 0:
3376 data.tKernSus = t.time
3377 continue
3378 phase = data.setPhase('suspend_prepare', t.time, isbegin)
3379 continue
3380 # suspend start
3381 elif(re.match('dpm_suspend\[.*', t.name)):
3382 phase = data.setPhase('suspend', t.time, isbegin)
3383 continue
3384 # suspend_late start
3385 elif(re.match('dpm_suspend_late\[.*', t.name)):
3386 phase = data.setPhase('suspend_late', t.time, isbegin)
3387 continue
3388 # suspend_noirq start
3389 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3390 phase = data.setPhase('suspend_noirq', t.time, isbegin)
3391 continue
3392 # suspend_machine/resume_machine
3393 elif(re.match(tp.machinesuspend, t.name)):
3394 lp = data.lastPhase()
3395 if(isbegin):
3396 hwsus = True
3397 if lp.startswith('resume_machine'):
3398 # trim out s2idle loops, track time trying to freeze
3399 llp = data.lastPhase(2)
3400 if llp.startswith('suspend_machine'):
3401 if 'waking' not in data.dmesg[llp]:
3402 data.dmesg[llp]['waking'] = [0, 0.0]
3403 data.dmesg[llp]['waking'][0] += 1
3404 data.dmesg[llp]['waking'][1] += \
3405 t.time - data.dmesg[lp]['start']
3406 data.currphase = ''
3407 del data.dmesg[lp]
3408 continue
3409 phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3410 data.setPhase(phase, t.time, False)
3411 if data.tSuspended == 0:
3412 data.tSuspended = t.time
3413 else:
3414 if lp.startswith('resume_machine'):
3415 data.dmesg[lp]['end'] = t.time
3416 continue
3417 phase = data.setPhase('resume_machine', t.time, True)
3418 if(sysvals.suspendmode in ['mem', 'disk']):
3419 susp = phase.replace('resume', 'suspend')
3420 if susp in data.dmesg:
3421 data.dmesg[susp]['end'] = t.time
3422 data.tSuspended = t.time
3423 data.tResumed = t.time
3424 continue
3425 # resume_noirq start
3426 elif(re.match('dpm_resume_noirq\[.*', t.name)):
3427 phase = data.setPhase('resume_noirq', t.time, isbegin)
3428 continue
3429 # resume_early start
3430 elif(re.match('dpm_resume_early\[.*', t.name)):
3431 phase = data.setPhase('resume_early', t.time, isbegin)
3432 continue
3433 # resume start
3434 elif(re.match('dpm_resume\[.*', t.name)):
3435 phase = data.setPhase('resume', t.time, isbegin)
3436 continue
3437 # resume complete start
3438 elif(re.match('dpm_complete\[.*', t.name)):
3439 phase = data.setPhase('resume_complete', t.time, isbegin)
3440 continue
3441 # skip trace events inside devices calls
3442 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3443 continue
3444 # global events (outside device calls) are graphed
3445 if(name not in testrun.ttemp):
3446 testrun.ttemp[name] = []
3447 # special handling for s2idle_enter
3448 if name == 'machine_suspend':
3449 if hwsus:
3450 s2idle_enter = hwsus = False
3451 elif s2idle_enter and not isbegin:
3452 if(len(testrun.ttemp[name]) > 0):
3453 testrun.ttemp[name][-1]['end'] = t.time
3454 testrun.ttemp[name][-1]['loop'] += 1
3455 elif not s2idle_enter and isbegin:
3456 s2idle_enter = True
3457 testrun.ttemp[name].append({'begin': t.time,
3458 'end': t.time, 'pid': pid, 'loop': 0})
3459 continue
3460 if(isbegin):
3461 # create a new list entry
3462 testrun.ttemp[name].append(\
3463 {'begin': t.time, 'end': t.time, 'pid': pid})
3464 else:
3465 if(len(testrun.ttemp[name]) > 0):
3466 # if an entry exists, assume this is its end
3467 testrun.ttemp[name][-1]['end'] = t.time
3468 # device callback start
3469 elif(t.type == 'device_pm_callback_start'):
3470 if phase not in data.dmesg:
3471 continue
3472 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3473 t.name);
3474 if(not m):
3475 continue
3476 drv = m.group('drv')
3477 n = m.group('d')
3478 p = m.group('p')
3479 if(n and p):
3480 data.newAction(phase, n, pid, p, t.time, -1, drv)
3481 if pid not in data.devpids:
3482 data.devpids.append(pid)
3483 # device callback finish
3484 elif(t.type == 'device_pm_callback_end'):
3485 if phase not in data.dmesg:
3486 continue
3487 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
3488 if(not m):
3489 continue
3490 n = m.group('d')
3491 dev = data.findDevice(phase, n)
3492 if dev:
3493 dev['length'] = t.time - dev['start']
3494 dev['end'] = t.time
3495 # kprobe event processing
3496 elif(t.fkprobe):
3497 kprobename = t.type
3498 kprobedata = t.name
3499 key = (kprobename, pid)
3500 # displayname is generated from kprobe data
3501 displayname = ''
3502 if(t.fcall):
3503 displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3504 if not displayname:
3505 continue
3506 if(key not in tp.ktemp):
3507 tp.ktemp[key] = []
3508 tp.ktemp[key].append({
3509 'pid': pid,
3510 'begin': t.time,
3511 'end': -1,
3512 'name': displayname,
3513 'cdata': kprobedata,
3514 'proc': m_proc,
3515 })
3516 # start of kernel resume
3517 if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3518 and kprobename in ksuscalls):
3519 data.tKernSus = t.time
3520 elif(t.freturn):
3521 if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3522 continue
3523 e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3524 if not e:
3525 continue
3526 e['end'] = t.time
3527 e['rdata'] = kprobedata
3528 # end of kernel resume
3529 if(phase != 'suspend_prepare' and kprobename in krescalls):
3530 if phase in data.dmesg:
3531 data.dmesg[phase]['end'] = t.time
3532 data.tKernRes = t.time
3533
3534 # callgraph processing
3535 elif sysvals.usecallgraph:
3536 # create a callgraph object for the data
3537 key = (m_proc, pid)
3538 if(key not in testrun.ftemp):
3539 testrun.ftemp[key] = []
3540 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3541 # when the call is finished, see which device matches it
3542 cg = testrun.ftemp[key][-1]
3543 res = cg.addLine(t)
3544 if(res != 0):
3545 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3546 if(res == -1):
3547 testrun.ftemp[key][-1].addLine(t)
3548 tf.close()
3549 if len(testdata) < 1:
3550 sysvals.vprint('WARNING: ftrace start marker is missing')
3551 if data and not data.devicegroups:
3552 sysvals.vprint('WARNING: ftrace end marker is missing')
3553 data.handleEndMarker(t.time, t.name)
3554
3555 if sysvals.suspendmode == 'command':
3556 for test in testruns:
3557 for p in test.data.sortedPhases():
3558 if p == 'suspend_prepare':
3559 test.data.dmesg[p]['start'] = test.data.start
3560 test.data.dmesg[p]['end'] = test.data.end
3561 else:
3562 test.data.dmesg[p]['start'] = test.data.end
3563 test.data.dmesg[p]['end'] = test.data.end
3564 test.data.tSuspended = test.data.end
3565 test.data.tResumed = test.data.end
3566 test.data.fwValid = False
3567
3568 # dev source and procmon events can be unreadable with mixed phase height
3569 if sysvals.usedevsrc or sysvals.useprocmon:
3570 sysvals.mixedphaseheight = False
3571
3572 # expand phase boundaries so there are no gaps
3573 for data in testdata:
3574 lp = data.sortedPhases()[0]
3575 for p in data.sortedPhases():
3576 if(p != lp and not ('machine' in p and 'machine' in lp)):
3577 data.dmesg[lp]['end'] = data.dmesg[p]['start']
3578 lp = p
3579
3580 for i in range(len(testruns)):
3581 test = testruns[i]
3582 data = test.data
3583 # find the total time range for this test (begin, end)
3584 tlb, tle = data.start, data.end
3585 if i < len(testruns) - 1:
3586 tle = testruns[i+1].data.start
3587 # add the process usage data to the timeline
3588 if sysvals.useprocmon:
3589 data.createProcessUsageEvents()
3590 # add the traceevent data to the device hierarchy
3591 if(sysvals.usetraceevents):
3592 # add actual trace funcs
3593 for name in sorted(test.ttemp):
3594 for event in test.ttemp[name]:
3595 if event['end'] - event['begin'] <= 0:
3596 continue
3597 title = name
3598 if name == 'machine_suspend' and 'loop' in event:
3599 title = 's2idle_enter_%dx' % event['loop']
3600 data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3601 # add the kprobe based virtual tracefuncs as actual devices
3602 for key in sorted(tp.ktemp):
3603 name, pid = key
3604 if name not in sysvals.tracefuncs:
3605 continue
3606 if pid not in data.devpids:
3607 data.devpids.append(pid)
3608 for e in tp.ktemp[key]:
3609 kb, ke = e['begin'], e['end']
3610 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3611 continue
3612 color = sysvals.kprobeColor(name)
3613 data.newActionGlobal(e['name'], kb, ke, pid, color)
3614 # add config base kprobes and dev kprobes
3615 if sysvals.usedevsrc:
3616 for key in sorted(tp.ktemp):
3617 name, pid = key
3618 if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3619 continue
3620 for e in tp.ktemp[key]:
3621 kb, ke = e['begin'], e['end']
3622 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3623 continue
3624 data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3625 ke, e['cdata'], e['rdata'])
3626 if sysvals.usecallgraph:
3627 # add the callgraph data to the device hierarchy
3628 sortlist = dict()
3629 for key in sorted(test.ftemp):
3630 proc, pid = key
3631 for cg in test.ftemp[key]:
3632 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3633 continue
3634 if(not cg.postProcess()):
3635 id = 'task %s' % (pid)
3636 sysvals.vprint('Sanity check failed for '+\
3637 id+', ignoring this callback')
3638 continue
3639 # match cg data to devices
3640 devname = ''
3641 if sysvals.suspendmode != 'command':
3642 devname = cg.deviceMatch(pid, data)
3643 if not devname:
3644 sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3645 sortlist[sortkey] = cg
3646 elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3647 sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3648 (devname, len(cg.list)))
3649 # create blocks for orphan cg data
3650 for sortkey in sorted(sortlist):
3651 cg = sortlist[sortkey]
3652 name = cg.name
3653 if sysvals.isCallgraphFunc(name):
3654 sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3655 cg.newActionFromFunction(data)
3656 if sysvals.suspendmode == 'command':
3657 return (testdata, '')
3658
3659 # fill in any missing phases
3660 error = []
3661 for data in testdata:
3662 tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3663 terr = ''
3664 phasedef = data.phasedef
3665 lp = 'suspend_prepare'
3666 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3667 if p not in data.dmesg:
3668 if not terr:
3669 ph = p if 'machine' in p else lp
3670 terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
3671 pprint('TEST%s FAILED: %s' % (tn, terr))
3672 error.append(terr)
3673 if data.tSuspended == 0:
3674 data.tSuspended = data.dmesg[lp]['end']
3675 if data.tResumed == 0:
3676 data.tResumed = data.dmesg[lp]['end']
3677 data.fwValid = False
3678 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3679 lp = p
3680 if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3681 terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3682 (sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3683 error.append(terr)
3684 if not terr and data.enterfail:
3685 pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3686 terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3687 error.append(terr)
3688 if data.tSuspended == 0:
3689 data.tSuspended = data.tKernRes
3690 if data.tResumed == 0:
3691 data.tResumed = data.tSuspended
3692
3693 if(len(sysvals.devicefilter) > 0):
3694 data.deviceFilter(sysvals.devicefilter)
3695 data.fixupInitcallsThatDidntReturn()
3696 if sysvals.usedevsrc:
3697 data.optimizeDevSrc()
3698
3699 # x2: merge any overlapping devices between test runs
3700 if sysvals.usedevsrc and len(testdata) > 1:
3701 tc = len(testdata)
3702 for i in range(tc - 1):
3703 devlist = testdata[i].overflowDevices()
3704 for j in range(i + 1, tc):
3705 testdata[j].mergeOverlapDevices(devlist)
3706 testdata[0].stitchTouchingThreads(testdata[1:])
3707 return (testdata, ', '.join(error))
3708
3709# Function: loadKernelLog
3710# Description:
3711# [deprecated for kernel 3.15.0 or newer]
3712# load the dmesg file into memory and fix up any ordering issues
3713# The dmesg filename is taken from sysvals
3714# Output:
3715# An array of empty Data objects with only their dmesgtext attributes set
3716def loadKernelLog():
3717 sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3718 os.path.basename(sysvals.dmesgfile))
3719 if(os.path.exists(sysvals.dmesgfile) == False):
3720 doError('%s does not exist' % sysvals.dmesgfile)
3721
3722 # there can be multiple test runs in a single file
3723 tp = TestProps()
3724 tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3725 testruns = []
3726 data = 0
3727 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3728 for line in lf:
3729 line = line.replace('\r\n', '')
3730 idx = line.find('[')
3731 if idx > 1:
3732 line = line[idx:]
3733 if tp.stampInfo(line, sysvals):
3734 continue
3735 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3736 if(not m):
3737 continue
3738 msg = m.group("msg")
3739 if(re.match('PM: Syncing filesystems.*', msg)):
3740 if(data):
3741 testruns.append(data)
3742 data = Data(len(testruns))
3743 tp.parseStamp(data, sysvals)
3744 if(not data):
3745 continue
3746 m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3747 if(m):
3748 sysvals.stamp['kernel'] = m.group('k')
3749 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3750 if(m):
3751 sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3752 data.dmesgtext.append(line)
3753 lf.close()
3754
3755 if data:
3756 testruns.append(data)
3757 if len(testruns) < 1:
3758 doError('dmesg log has no suspend/resume data: %s' \
3759 % sysvals.dmesgfile)
3760
3761 # fix lines with same timestamp/function with the call and return swapped
3762 for data in testruns:
3763 last = ''
3764 for line in data.dmesgtext:
3765 mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
3766 '(?P<f>.*)\+ @ .*, parent: .*', line)
3767 mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
3768 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
3769 if(mc and mr and (mc.group('t') == mr.group('t')) and
3770 (mc.group('f') == mr.group('f'))):
3771 i = data.dmesgtext.index(last)
3772 j = data.dmesgtext.index(line)
3773 data.dmesgtext[i] = line
3774 data.dmesgtext[j] = last
3775 last = line
3776 return testruns
3777
3778# Function: parseKernelLog
3779# Description:
3780# [deprecated for kernel 3.15.0 or newer]
3781# Analyse a dmesg log output file generated from this app during
3782# the execution phase. Create a set of device structures in memory
3783# for subsequent formatting in the html output file
3784# This call is only for legacy support on kernels where the ftrace
3785# data lacks the suspend_resume or device_pm_callbacks trace events.
3786# Arguments:
3787# data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3788# Output:
3789# The filled Data object
3790def parseKernelLog(data):
3791 phase = 'suspend_runtime'
3792
3793 if(data.fwValid):
3794 sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3795 (data.fwSuspend, data.fwResume))
3796
3797 # dmesg phase match table
3798 dm = {
3799 'suspend_prepare': ['PM: Syncing filesystems.*'],
3800 'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
3801 'suspend_late': ['PM: suspend of devices complete after.*'],
3802 'suspend_noirq': ['PM: late suspend of devices complete after.*'],
3803 'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
3804 'resume_machine': ['ACPI: Low-level resume complete.*'],
3805 'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
3806 'resume_early': ['PM: noirq resume of devices complete after.*'],
3807 'resume': ['PM: early resume of devices complete after.*'],
3808 'resume_complete': ['PM: resume of devices complete after.*'],
3809 'post_resume': ['.*Restarting tasks \.\.\..*'],
3810 }
3811 if(sysvals.suspendmode == 'standby'):
3812 dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3813 elif(sysvals.suspendmode == 'disk'):
3814 dm['suspend_late'] = ['PM: freeze of devices complete after.*']
3815 dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
3816 dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
3817 dm['resume_machine'] = ['PM: Restoring platform NVS memory']
3818 dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
3819 dm['resume'] = ['PM: early restore of devices complete after.*']
3820 dm['resume_complete'] = ['PM: restore of devices complete after.*']
3821 elif(sysvals.suspendmode == 'freeze'):
3822 dm['resume_machine'] = ['ACPI: resume from mwait']
3823
3824 # action table (expected events that occur and show up in dmesg)
3825 at = {
3826 'sync_filesystems': {
3827 'smsg': 'PM: Syncing filesystems.*',
3828 'emsg': 'PM: Preparing system for mem sleep.*' },
3829 'freeze_user_processes': {
3830 'smsg': 'Freezing user space processes .*',
3831 'emsg': 'Freezing remaining freezable tasks.*' },
3832 'freeze_tasks': {
3833 'smsg': 'Freezing remaining freezable tasks.*',
3834 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
3835 'ACPI prepare': {
3836 'smsg': 'ACPI: Preparing to enter system sleep state.*',
3837 'emsg': 'PM: Saving platform NVS memory.*' },
3838 'PM vns': {
3839 'smsg': 'PM: Saving platform NVS memory.*',
3840 'emsg': 'Disabling non-boot CPUs .*' },
3841 }
3842
3843 t0 = -1.0
3844 cpu_start = -1.0
3845 prevktime = -1.0
3846 actions = dict()
3847 for line in data.dmesgtext:
3848 # parse each dmesg line into the time and message
3849 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3850 if(m):
3851 val = m.group('ktime')
3852 try:
3853 ktime = float(val)
3854 except:
3855 continue
3856 msg = m.group('msg')
3857 # initialize data start to first line time
3858 if t0 < 0:
3859 data.setStart(ktime)
3860 t0 = ktime
3861 else:
3862 continue
3863
3864 # check for a phase change line
3865 phasechange = False
3866 for p in dm:
3867 for s in dm[p]:
3868 if(re.match(s, msg)):
3869 phasechange, phase = True, p
3870 break
3871
3872 # hack for determining resume_machine end for freeze
3873 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
3874 and phase == 'resume_machine' and \
3875 re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3876 data.setPhase(phase, ktime, False)
3877 phase = 'resume_noirq'
3878 data.setPhase(phase, ktime, True)
3879
3880 if phasechange:
3881 if phase == 'suspend_prepare':
3882 data.setPhase(phase, ktime, True)
3883 data.setStart(ktime)
3884 data.tKernSus = ktime
3885 elif phase == 'suspend':
3886 lp = data.lastPhase()
3887 if lp:
3888 data.setPhase(lp, ktime, False)
3889 data.setPhase(phase, ktime, True)
3890 elif phase == 'suspend_late':
3891 lp = data.lastPhase()
3892 if lp:
3893 data.setPhase(lp, ktime, False)
3894 data.setPhase(phase, ktime, True)
3895 elif phase == 'suspend_noirq':
3896 lp = data.lastPhase()
3897 if lp:
3898 data.setPhase(lp, ktime, False)
3899 data.setPhase(phase, ktime, True)
3900 elif phase == 'suspend_machine':
3901 lp = data.lastPhase()
3902 if lp:
3903 data.setPhase(lp, ktime, False)
3904 data.setPhase(phase, ktime, True)
3905 elif phase == 'resume_machine':
3906 lp = data.lastPhase()
3907 if(sysvals.suspendmode in ['freeze', 'standby']):
3908 data.tSuspended = prevktime
3909 if lp:
3910 data.setPhase(lp, prevktime, False)
3911 else:
3912 data.tSuspended = ktime
3913 if lp:
3914 data.setPhase(lp, prevktime, False)
3915 data.tResumed = ktime
3916 data.setPhase(phase, ktime, True)
3917 elif phase == 'resume_noirq':
3918 lp = data.lastPhase()
3919 if lp:
3920 data.setPhase(lp, ktime, False)
3921 data.setPhase(phase, ktime, True)
3922 elif phase == 'resume_early':
3923 lp = data.lastPhase()
3924 if lp:
3925 data.setPhase(lp, ktime, False)
3926 data.setPhase(phase, ktime, True)
3927 elif phase == 'resume':
3928 lp = data.lastPhase()
3929 if lp:
3930 data.setPhase(lp, ktime, False)
3931 data.setPhase(phase, ktime, True)
3932 elif phase == 'resume_complete':
3933 lp = data.lastPhase()
3934 if lp:
3935 data.setPhase(lp, ktime, False)
3936 data.setPhase(phase, ktime, True)
3937 elif phase == 'post_resume':
3938 lp = data.lastPhase()
3939 if lp:
3940 data.setPhase(lp, ktime, False)
3941 data.setEnd(ktime)
3942 data.tKernRes = ktime
3943 break
3944
3945 # -- device callbacks --
3946 if(phase in data.sortedPhases()):
3947 # device init call
3948 if(re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
3949 sm = re.match('calling (?P<f>.*)\+ @ '+\
3950 '(?P<n>.*), parent: (?P<p>.*)', msg);
3951 f = sm.group('f')
3952 n = sm.group('n')
3953 p = sm.group('p')
3954 if(f and n and p):
3955 data.newAction(phase, f, int(n), p, ktime, -1, '')
3956 # device init return
3957 elif(re.match('call (?P<f>.*)\+ returned .* after '+\
3958 '(?P<t>.*) usecs', msg)):
3959 sm = re.match('call (?P<f>.*)\+ returned .* after '+\
3960 '(?P<t>.*) usecs(?P<a>.*)', msg);
3961 f = sm.group('f')
3962 t = sm.group('t')
3963 list = data.dmesg[phase]['list']
3964 if(f in list):
3965 dev = list[f]
3966 dev['length'] = int(t)
3967 dev['end'] = ktime
3968
3969 # if trace events are not available, these are better than nothing
3970 if(not sysvals.usetraceevents):
3971 # look for known actions
3972 for a in sorted(at):
3973 if(re.match(at[a]['smsg'], msg)):
3974 if(a not in actions):
3975 actions[a] = []
3976 actions[a].append({'begin': ktime, 'end': ktime})
3977 if(re.match(at[a]['emsg'], msg)):
3978 if(a in actions):
3979 actions[a][-1]['end'] = ktime
3980 # now look for CPU on/off events
3981 if(re.match('Disabling non-boot CPUs .*', msg)):
3982 # start of first cpu suspend
3983 cpu_start = ktime
3984 elif(re.match('Enabling non-boot CPUs .*', msg)):
3985 # start of first cpu resume
3986 cpu_start = ktime
3987 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
3988 # end of a cpu suspend, start of the next
3989 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
3990 cpu = 'CPU'+m.group('cpu')
3991 if(cpu not in actions):
3992 actions[cpu] = []
3993 actions[cpu].append({'begin': cpu_start, 'end': ktime})
3994 cpu_start = ktime
3995 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
3996 # end of a cpu resume, start of the next
3997 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
3998 cpu = 'CPU'+m.group('cpu')
3999 if(cpu not in actions):
4000 actions[cpu] = []
4001 actions[cpu].append({'begin': cpu_start, 'end': ktime})
4002 cpu_start = ktime
4003 prevktime = ktime
4004 data.initDevicegroups()
4005
4006 # fill in any missing phases
4007 phasedef = data.phasedef
4008 terr, lp = '', 'suspend_prepare'
4009 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4010 if p not in data.dmesg:
4011 if not terr:
4012 pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
4013 terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
4014 if data.tSuspended == 0:
4015 data.tSuspended = data.dmesg[lp]['end']
4016 if data.tResumed == 0:
4017 data.tResumed = data.dmesg[lp]['end']
4018 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
4019 lp = p
4020 lp = data.sortedPhases()[0]
4021 for p in data.sortedPhases():
4022 if(p != lp and not ('machine' in p and 'machine' in lp)):
4023 data.dmesg[lp]['end'] = data.dmesg[p]['start']
4024 lp = p
4025 if data.tSuspended == 0:
4026 data.tSuspended = data.tKernRes
4027 if data.tResumed == 0:
4028 data.tResumed = data.tSuspended
4029
4030 # fill in any actions we've found
4031 for name in sorted(actions):
4032 for event in actions[name]:
4033 data.newActionGlobal(name, event['begin'], event['end'])
4034
4035 if(len(sysvals.devicefilter) > 0):
4036 data.deviceFilter(sysvals.devicefilter)
4037 data.fixupInitcallsThatDidntReturn()
4038 return True
4039
4040def callgraphHTML(sv, hf, num, cg, title, color, devid):
4041 html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
4042 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
4043 html_func_end = '</article>\n'
4044 html_func_leaf = '<article>{0} {1}</article>\n'
4045
4046 cgid = devid
4047 if cg.id:
4048 cgid += cg.id
4049 cglen = (cg.end - cg.start) * 1000
4050 if cglen < sv.mincglen:
4051 return num
4052
4053 fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
4054 flen = fmt % (cglen, cg.start, cg.end)
4055 hf.write(html_func_top.format(cgid, color, num, title, flen))
4056 num += 1
4057 for line in cg.list:
4058 if(line.length < 0.000000001):
4059 flen = ''
4060 else:
4061 fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
4062 flen = fmt % (line.length*1000, line.time)
4063 if line.isLeaf():
4064 hf.write(html_func_leaf.format(line.name, flen))
4065 elif line.freturn:
4066 hf.write(html_func_end)
4067 else:
4068 hf.write(html_func_start.format(num, line.name, flen))
4069 num += 1
4070 hf.write(html_func_end)
4071 return num
4072
4073def addCallgraphs(sv, hf, data):
4074 hf.write('<section id="callgraphs" class="callgraph">\n')
4075 # write out the ftrace data converted to html
4076 num = 0
4077 for p in data.sortedPhases():
4078 if sv.cgphase and p != sv.cgphase:
4079 continue
4080 list = data.dmesg[p]['list']
4081 for d in data.sortedDevices(p):
4082 if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4083 continue
4084 dev = list[d]
4085 color = 'white'
4086 if 'color' in data.dmesg[p]:
4087 color = data.dmesg[p]['color']
4088 if 'color' in dev:
4089 color = dev['color']
4090 name = d if '[' not in d else d.split('[')[0]
4091 if(d in sv.devprops):
4092 name = sv.devprops[d].altName(d)
4093 if 'drv' in dev and dev['drv']:
4094 name += ' {%s}' % dev['drv']
4095 if sv.suspendmode in suspendmodename:
4096 name += ' '+p
4097 if('ftrace' in dev):
4098 cg = dev['ftrace']
4099 if cg.name == sv.ftopfunc:
4100 name = 'top level suspend/resume call'
4101 num = callgraphHTML(sv, hf, num, cg,
4102 name, color, dev['id'])
4103 if('ftraces' in dev):
4104 for cg in dev['ftraces']:
4105 num = callgraphHTML(sv, hf, num, cg,
4106 name+' → '+cg.name, color, dev['id'])
4107 hf.write('\n\n </section>\n')
4108
4109def summaryCSS(title, center=True):
4110 tdcenter = 'text-align:center;' if center else ''
4111 out = '<!DOCTYPE html>\n<html>\n<head>\n\
4112 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4113 <title>'+title+'</title>\n\
4114 <style type=\'text/css\'>\n\
4115 .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4116 table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4117 th {border: 1px solid black;background:#222;color:white;}\n\
4118 td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4119 tr.head td {border: 1px solid black;background:#aaa;}\n\
4120 tr.alt {background-color:#ddd;}\n\
4121 tr.notice {color:red;}\n\
4122 .minval {background-color:#BBFFBB;}\n\
4123 .medval {background-color:#BBBBFF;}\n\
4124 .maxval {background-color:#FFBBBB;}\n\
4125 .head a {color:#000;text-decoration: none;}\n\
4126 </style>\n</head>\n<body>\n'
4127 return out
4128
4129# Function: createHTMLSummarySimple
4130# Description:
4131# Create summary html file for a series of tests
4132# Arguments:
4133# testruns: array of Data objects from parseTraceLog
4134def createHTMLSummarySimple(testruns, htmlfile, title):
4135 # write the html header first (html head, css code, up to body start)
4136 html = summaryCSS('Summary - SleepGraph')
4137
4138 # extract the test data into list
4139 list = dict()
4140 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4141 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4142 num = 0
4143 useturbo = usewifi = False
4144 lastmode = ''
4145 cnt = dict()
4146 for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4147 mode = data['mode']
4148 if mode not in list:
4149 list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4150 if lastmode and lastmode != mode and num > 0:
4151 for i in range(2):
4152 s = sorted(tMed[i])
4153 list[lastmode]['med'][i] = s[int(len(s)//2)]
4154 iMed[i] = tMed[i][list[lastmode]['med'][i]]
4155 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4156 list[lastmode]['min'] = tMin
4157 list[lastmode]['max'] = tMax
4158 list[lastmode]['idx'] = (iMin, iMed, iMax)
4159 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4160 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4161 num = 0
4162 pkgpc10 = syslpi = wifi = ''
4163 if 'pkgpc10' in data and 'syslpi' in data:
4164 pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4165 if 'wifi' in data:
4166 wifi, usewifi = data['wifi'], True
4167 res = data['result']
4168 tVal = [float(data['suspend']), float(data['resume'])]
4169 list[mode]['data'].append([data['host'], data['kernel'],
4170 data['time'], tVal[0], tVal[1], data['url'], res,
4171 data['issues'], data['sus_worst'], data['sus_worsttime'],
4172 data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi])
4173 idx = len(list[mode]['data']) - 1
4174 if res.startswith('fail in'):
4175 res = 'fail'
4176 if res not in cnt:
4177 cnt[res] = 1
4178 else:
4179 cnt[res] += 1
4180 if res == 'pass':
4181 for i in range(2):
4182 tMed[i][tVal[i]] = idx
4183 tAvg[i] += tVal[i]
4184 if tMin[i] == 0 or tVal[i] < tMin[i]:
4185 iMin[i] = idx
4186 tMin[i] = tVal[i]
4187 if tMax[i] == 0 or tVal[i] > tMax[i]:
4188 iMax[i] = idx
4189 tMax[i] = tVal[i]
4190 num += 1
4191 lastmode = mode
4192 if lastmode and num > 0:
4193 for i in range(2):
4194 s = sorted(tMed[i])
4195 list[lastmode]['med'][i] = s[int(len(s)//2)]
4196 iMed[i] = tMed[i][list[lastmode]['med'][i]]
4197 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4198 list[lastmode]['min'] = tMin
4199 list[lastmode]['max'] = tMax
4200 list[lastmode]['idx'] = (iMin, iMed, iMax)
4201
4202 # group test header
4203 desc = []
4204 for ilk in sorted(cnt, reverse=True):
4205 if cnt[ilk] > 0:
4206 desc.append('%d %s' % (cnt[ilk], ilk))
4207 html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4208 th = '\t<th>{0}</th>\n'
4209 td = '\t<td>{0}</td>\n'
4210 tdh = '\t<td{1}>{0}</td>\n'
4211 tdlink = '\t<td><a href="{0}">html</a></td>\n'
4212 cols = 12
4213 if useturbo:
4214 cols += 2
4215 if usewifi:
4216 cols += 1
4217 colspan = '%d' % cols
4218
4219 # table header
4220 html += '<table>\n<tr>\n' + th.format('#') +\
4221 th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4222 th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4223 th.format('Suspend') + th.format('Resume') +\
4224 th.format('Worst Suspend Device') + th.format('SD Time') +\
4225 th.format('Worst Resume Device') + th.format('RD Time')
4226 if useturbo:
4227 html += th.format('PkgPC10') + th.format('SysLPI')
4228 if usewifi:
4229 html += th.format('Wifi')
4230 html += th.format('Detail')+'</tr>\n'
4231 # export list into html
4232 head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4233 '<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4234 '<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4235 '<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4236 '<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4237 'Resume Avg={6} '+\
4238 '<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4239 '<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4240 '<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4241 '</tr>\n'
4242 headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4243 colspan+'></td></tr>\n'
4244 for mode in sorted(list):
4245 # header line for each suspend mode
4246 num = 0
4247 tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4248 list[mode]['max'], list[mode]['med']
4249 count = len(list[mode]['data'])
4250 if 'idx' in list[mode]:
4251 iMin, iMed, iMax = list[mode]['idx']
4252 html += head.format('%d' % count, mode.upper(),
4253 '%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4254 '%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4255 mode.lower()
4256 )
4257 else:
4258 iMin = iMed = iMax = [-1, -1, -1]
4259 html += headnone.format('%d' % count, mode.upper())
4260 for d in list[mode]['data']:
4261 # row classes - alternate row color
4262 rcls = ['alt'] if num % 2 == 1 else []
4263 if d[6] != 'pass':
4264 rcls.append('notice')
4265 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4266 # figure out if the line has sus or res highlighted
4267 idx = list[mode]['data'].index(d)
4268 tHigh = ['', '']
4269 for i in range(2):
4270 tag = 's%s' % mode if i == 0 else 'r%s' % mode
4271 if idx == iMin[i]:
4272 tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4273 elif idx == iMax[i]:
4274 tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4275 elif idx == iMed[i]:
4276 tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4277 html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4278 html += td.format(mode) # mode
4279 html += td.format(d[0]) # host
4280 html += td.format(d[1]) # kernel
4281 html += td.format(d[2]) # time
4282 html += td.format(d[6]) # result
4283 html += td.format(d[7]) # issues
4284 html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend
4285 html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume
4286 html += td.format(d[8]) # sus_worst
4287 html += td.format('%.3f ms' % d[9]) if d[9] else td.format('') # sus_worst time
4288 html += td.format(d[10]) # res_worst
4289 html += td.format('%.3f ms' % d[11]) if d[11] else td.format('') # res_worst time
4290 if useturbo:
4291 html += td.format(d[12]) # pkg_pc10
4292 html += td.format(d[13]) # syslpi
4293 if usewifi:
4294 html += td.format(d[14]) # wifi
4295 html += tdlink.format(d[5]) if d[5] else td.format('') # url
4296 html += '</tr>\n'
4297 num += 1
4298
4299 # flush the data to file
4300 hf = open(htmlfile, 'w')
4301 hf.write(html+'</table>\n</body>\n</html>\n')
4302 hf.close()
4303
4304def createHTMLDeviceSummary(testruns, htmlfile, title):
4305 html = summaryCSS('Device Summary - SleepGraph', False)
4306
4307 # create global device list from all tests
4308 devall = dict()
4309 for data in testruns:
4310 host, url, devlist = data['host'], data['url'], data['devlist']
4311 for type in devlist:
4312 if type not in devall:
4313 devall[type] = dict()
4314 mdevlist, devlist = devall[type], data['devlist'][type]
4315 for name in devlist:
4316 length = devlist[name]
4317 if name not in mdevlist:
4318 mdevlist[name] = {'name': name, 'host': host,
4319 'worst': length, 'total': length, 'count': 1,
4320 'url': url}
4321 else:
4322 if length > mdevlist[name]['worst']:
4323 mdevlist[name]['worst'] = length
4324 mdevlist[name]['url'] = url
4325 mdevlist[name]['host'] = host
4326 mdevlist[name]['total'] += length
4327 mdevlist[name]['count'] += 1
4328
4329 # generate the html
4330 th = '\t<th>{0}</th>\n'
4331 td = '\t<td align=center>{0}</td>\n'
4332 tdr = '\t<td align=right>{0}</td>\n'
4333 tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4334 limit = 1
4335 for type in sorted(devall, reverse=True):
4336 num = 0
4337 devlist = devall[type]
4338 # table header
4339 html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4340 (title, type.upper(), limit)
4341 html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4342 th.format('Average Time') + th.format('Count') +\
4343 th.format('Worst Time') + th.format('Host (worst time)') +\
4344 th.format('Link (worst time)') + '</tr>\n'
4345 for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4346 devlist[k]['total'], devlist[k]['name']), reverse=True):
4347 data = devall[type][name]
4348 data['average'] = data['total'] / data['count']
4349 if data['average'] < limit:
4350 continue
4351 # row classes - alternate row color
4352 rcls = ['alt'] if num % 2 == 1 else []
4353 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4354 html += tdr.format(data['name']) # name
4355 html += td.format('%.3f ms' % data['average']) # average
4356 html += td.format(data['count']) # count
4357 html += td.format('%.3f ms' % data['worst']) # worst
4358 html += td.format(data['host']) # host
4359 html += tdlink.format(data['url']) # url
4360 html += '</tr>\n'
4361 num += 1
4362 html += '</table>\n'
4363
4364 # flush the data to file
4365 hf = open(htmlfile, 'w')
4366 hf.write(html+'</body>\n</html>\n')
4367 hf.close()
4368 return devall
4369
4370def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4371 multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4372 html = summaryCSS('Issues Summary - SleepGraph', False)
4373 total = len(testruns)
4374
4375 # generate the html
4376 th = '\t<th>{0}</th>\n'
4377 td = '\t<td align={0}>{1}</td>\n'
4378 tdlink = '<a href="{1}">{0}</a>'
4379 subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4380 html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4381 html += '<tr>\n' + th.format('Issue') + th.format('Count')
4382 if multihost:
4383 html += th.format('Hosts')
4384 html += th.format('Tests') + th.format('Fail Rate') +\
4385 th.format('First Instance') + '</tr>\n'
4386
4387 num = 0
4388 for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4389 testtotal = 0
4390 links = []
4391 for host in sorted(e['urls']):
4392 links.append(tdlink.format(host, e['urls'][host][0]))
4393 testtotal += len(e['urls'][host])
4394 rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4395 # row classes - alternate row color
4396 rcls = ['alt'] if num % 2 == 1 else []
4397 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4398 html += td.format('left', e['line']) # issue
4399 html += td.format('center', e['count']) # count
4400 if multihost:
4401 html += td.format('center', len(e['urls'])) # hosts
4402 html += td.format('center', testtotal) # test count
4403 html += td.format('center', rate) # test rate
4404 html += td.format('center nowrap', '<br>'.join(links)) # links
4405 html += '</tr>\n'
4406 num += 1
4407
4408 # flush the data to file
4409 hf = open(htmlfile, 'w')
4410 hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4411 hf.close()
4412 return issues
4413
4414def ordinal(value):
4415 suffix = 'th'
4416 if value < 10 or value > 19:
4417 if value % 10 == 1:
4418 suffix = 'st'
4419 elif value % 10 == 2:
4420 suffix = 'nd'
4421 elif value % 10 == 3:
4422 suffix = 'rd'
4423 return '%d%s' % (value, suffix)
4424
4425# Function: createHTML
4426# Description:
4427# Create the output html file from the resident test data
4428# Arguments:
4429# testruns: array of Data objects from parseKernelLog or parseTraceLog
4430# Output:
4431# True if the html file was created, false if it failed
4432def createHTML(testruns, testfail):
4433 if len(testruns) < 1:
4434 pprint('ERROR: Not enough test data to build a timeline')
4435 return
4436
4437 kerror = False
4438 for data in testruns:
4439 if data.kerror:
4440 kerror = True
4441 if(sysvals.suspendmode in ['freeze', 'standby']):
4442 data.trimFreezeTime(testruns[-1].tSuspended)
4443 else:
4444 data.getMemTime()
4445
4446 # html function templates
4447 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</div>\n'
4448 html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n'
4449 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4450 html_timetotal = '<table class="time1">\n<tr>'\
4451 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4452 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4453 '</tr>\n</table>\n'
4454 html_timetotal2 = '<table class="time1">\n<tr>'\
4455 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4456 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4457 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4458 '</tr>\n</table>\n'
4459 html_timetotal3 = '<table class="time1">\n<tr>'\
4460 '<td class="green">Execution Time: <b>{0} ms</b></td>'\
4461 '<td class="yellow">Command: <b>{1}</b></td>'\
4462 '</tr>\n</table>\n'
4463 html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4464 html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4465 html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4466 html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
4467
4468 # html format variables
4469 scaleH = 20
4470 if kerror:
4471 scaleH = 40
4472
4473 # device timeline
4474 devtl = Timeline(30, scaleH)
4475
4476 # write the test title and general info header
4477 devtl.createHeader(sysvals, testruns[0].stamp)
4478
4479 # Generate the header for this timeline
4480 for data in testruns:
4481 tTotal = data.end - data.start
4482 if(tTotal == 0):
4483 doError('No timeline data')
4484 if sysvals.suspendmode == 'command':
4485 run_time = '%.0f' % (tTotal * 1000)
4486 if sysvals.testcommand:
4487 testdesc = sysvals.testcommand
4488 else:
4489 testdesc = 'unknown'
4490 if(len(testruns) > 1):
4491 testdesc = ordinal(data.testnumber+1)+' '+testdesc
4492 thtml = html_timetotal3.format(run_time, testdesc)
4493 devtl.html += thtml
4494 continue
4495 # typical full suspend/resume header
4496 stot, rtot = sktime, rktime = data.getTimeValues()
4497 ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4498 if data.fwValid:
4499 stot += (data.fwSuspend/1000000.0)
4500 rtot += (data.fwResume/1000000.0)
4501 ssrc.append('firmware')
4502 rsrc.append('firmware')
4503 testdesc = 'Total'
4504 if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4505 rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4506 rsrc.append('wifi')
4507 testdesc = 'Total'
4508 suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4509 stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4510 (sysvals.suspendmode, ' & '.join(ssrc))
4511 rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4512 (sysvals.suspendmode, ' & '.join(rsrc))
4513 if(len(testruns) > 1):
4514 testdesc = testdesc2 = ordinal(data.testnumber+1)
4515 testdesc2 += ' '
4516 if(len(data.tLow) == 0):
4517 thtml = html_timetotal.format(suspend_time, \
4518 resume_time, testdesc, stitle, rtitle)
4519 else:
4520 low_time = '+'.join(data.tLow)
4521 thtml = html_timetotal2.format(suspend_time, low_time, \
4522 resume_time, testdesc, stitle, rtitle)
4523 devtl.html += thtml
4524 if not data.fwValid and 'dev' not in data.wifi:
4525 continue
4526 # extra detail when the times come from multiple sources
4527 thtml = '<table class="time2">\n<tr>'
4528 thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4529 if data.fwValid:
4530 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4531 rftime = '%.3f'%(data.fwResume / 1000000.0)
4532 thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4533 thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4534 thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4535 if 'time' in data.wifi:
4536 if data.wifi['stat'] != 'timeout':
4537 wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
4538 else:
4539 wtime = 'TIMEOUT'
4540 thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4541 thtml += '</tr>\n</table>\n'
4542 devtl.html += thtml
4543 if testfail:
4544 devtl.html += html_fail.format(testfail)
4545
4546 # time scale for potentially multiple datasets
4547 t0 = testruns[0].start
4548 tMax = testruns[-1].end
4549 tTotal = tMax - t0
4550
4551 # determine the maximum number of rows we need to draw
4552 fulllist = []
4553 threadlist = []
4554 pscnt = 0
4555 devcnt = 0
4556 for data in testruns:
4557 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4558 for group in data.devicegroups:
4559 devlist = []
4560 for phase in group:
4561 for devname in sorted(data.tdevlist[phase]):
4562 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4563 devlist.append(d)
4564 if d.isa('kth'):
4565 threadlist.append(d)
4566 else:
4567 if d.isa('ps'):
4568 pscnt += 1
4569 else:
4570 devcnt += 1
4571 fulllist.append(d)
4572 if sysvals.mixedphaseheight:
4573 devtl.getPhaseRows(devlist)
4574 if not sysvals.mixedphaseheight:
4575 if len(threadlist) > 0 and len(fulllist) > 0:
4576 if pscnt > 0 and devcnt > 0:
4577 msg = 'user processes & device pm callbacks'
4578 elif pscnt > 0:
4579 msg = 'user processes'
4580 else:
4581 msg = 'device pm callbacks'
4582 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4583 fulllist.insert(0, d)
4584 devtl.getPhaseRows(fulllist)
4585 if len(threadlist) > 0:
4586 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4587 threadlist.insert(0, d)
4588 devtl.getPhaseRows(threadlist, devtl.rows)
4589 devtl.calcTotalRows()
4590
4591 # draw the full timeline
4592 devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4593 for data in testruns:
4594 # draw each test run and block chronologically
4595 phases = {'suspend':[],'resume':[]}
4596 for phase in data.sortedPhases():
4597 if data.dmesg[phase]['start'] >= data.tSuspended:
4598 phases['resume'].append(phase)
4599 else:
4600 phases['suspend'].append(phase)
4601 # now draw the actual timeline blocks
4602 for dir in phases:
4603 # draw suspend and resume blocks separately
4604 bname = '%s%d' % (dir[0], data.testnumber)
4605 if dir == 'suspend':
4606 m0 = data.start
4607 mMax = data.tSuspended
4608 left = '%f' % (((m0-t0)*100.0)/tTotal)
4609 else:
4610 m0 = data.tSuspended
4611 mMax = data.end
4612 # in an x2 run, remove any gap between blocks
4613 if len(testruns) > 1 and data.testnumber == 0:
4614 mMax = testruns[1].start
4615 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4616 mTotal = mMax - m0
4617 # if a timeline block is 0 length, skip altogether
4618 if mTotal == 0:
4619 continue
4620 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4621 devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4622 for b in phases[dir]:
4623 # draw the phase color background
4624 phase = data.dmesg[b]
4625 length = phase['end']-phase['start']
4626 left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4627 width = '%f' % ((length*100.0)/mTotal)
4628 devtl.html += devtl.html_phase.format(left, width, \
4629 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4630 data.dmesg[b]['color'], '')
4631 for e in data.errorinfo[dir]:
4632 # draw red lines for any kernel errors found
4633 type, t, idx1, idx2 = e
4634 id = '%d_%d' % (idx1, idx2)
4635 right = '%f' % (((mMax-t)*100.0)/mTotal)
4636 devtl.html += html_error.format(right, id, type)
4637 for b in phases[dir]:
4638 # draw the devices for this phase
4639 phaselist = data.dmesg[b]['list']
4640 for d in sorted(data.tdevlist[b]):
4641 dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0]
4642 name, dev = dname, phaselist[d]
4643 drv = xtraclass = xtrainfo = xtrastyle = ''
4644 if 'htmlclass' in dev:
4645 xtraclass = dev['htmlclass']
4646 if 'color' in dev:
4647 xtrastyle = 'background:%s;' % dev['color']
4648 if(d in sysvals.devprops):
4649 name = sysvals.devprops[d].altName(d)
4650 xtraclass = sysvals.devprops[d].xtraClass()
4651 xtrainfo = sysvals.devprops[d].xtraInfo()
4652 elif xtraclass == ' kth':
4653 xtrainfo = ' kernel_thread'
4654 if('drv' in dev and dev['drv']):
4655 drv = ' {%s}' % dev['drv']
4656 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4657 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4658 top = '%.3f' % (rowtop + devtl.scaleH)
4659 left = '%f' % (((dev['start']-m0)*100)/mTotal)
4660 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4661 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4662 title = name+drv+xtrainfo+length
4663 if sysvals.suspendmode == 'command':
4664 title += sysvals.testcommand
4665 elif xtraclass == ' ps':
4666 if 'suspend' in b:
4667 title += 'pre_suspend_process'
4668 else:
4669 title += 'post_resume_process'
4670 else:
4671 title += b
4672 devtl.html += devtl.html_device.format(dev['id'], \
4673 title, left, top, '%.3f'%rowheight, width, \
4674 dname+drv, xtraclass, xtrastyle)
4675 if('cpuexec' in dev):
4676 for t in sorted(dev['cpuexec']):
4677 start, end = t
4678 j = float(dev['cpuexec'][t]) / 5
4679 if j > 1.0:
4680 j = 1.0
4681 height = '%.3f' % (rowheight/3)
4682 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4683 left = '%f' % (((start-m0)*100)/mTotal)
4684 width = '%f' % ((end-start)*100/mTotal)
4685 color = 'rgba(255, 0, 0, %f)' % j
4686 devtl.html += \
4687 html_cpuexec.format(left, top, height, width, color)
4688 if('src' not in dev):
4689 continue
4690 # draw any trace events for this device
4691 for e in dev['src']:
4692 if e.length == 0:
4693 continue
4694 height = '%.3f' % devtl.rowH
4695 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4696 left = '%f' % (((e.time-m0)*100)/mTotal)
4697 width = '%f' % (e.length*100/mTotal)
4698 xtrastyle = ''
4699 if e.color:
4700 xtrastyle = 'background:%s;' % e.color
4701 devtl.html += \
4702 html_traceevent.format(e.title(), \
4703 left, top, height, width, e.text(), '', xtrastyle)
4704 # draw the time scale, try to make the number of labels readable
4705 devtl.createTimeScale(m0, mMax, tTotal, dir)
4706 devtl.html += '</div>\n'
4707
4708 # timeline is finished
4709 devtl.html += '</div>\n</div>\n'
4710
4711 # draw a legend which describes the phases by color
4712 if sysvals.suspendmode != 'command':
4713 phasedef = testruns[-1].phasedef
4714 devtl.html += '<div class="legend">\n'
4715 pdelta = 100.0/len(phasedef.keys())
4716 pmargin = pdelta / 4.0
4717 for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4718 id, p = '', phasedef[phase]
4719 for word in phase.split('_'):
4720 id += word[0]
4721 order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4722 name = phase.replace('_', ' ')
4723 devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4724 devtl.html += '</div>\n'
4725
4726 hf = open(sysvals.htmlfile, 'w')
4727 addCSS(hf, sysvals, len(testruns), kerror)
4728
4729 # write the device timeline
4730 hf.write(devtl.html)
4731 hf.write('<div id="devicedetailtitle"></div>\n')
4732 hf.write('<div id="devicedetail" style="display:none;">\n')
4733 # draw the colored boxes for the device detail section
4734 for data in testruns:
4735 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4736 pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4737 hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4738 '0', '0', pscolor))
4739 for b in data.sortedPhases():
4740 phase = data.dmesg[b]
4741 length = phase['end']-phase['start']
4742 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4743 width = '%.3f' % ((length*100.0)/tTotal)
4744 hf.write(devtl.html_phaselet.format(b, left, width, \
4745 data.dmesg[b]['color']))
4746 hf.write(devtl.html_phaselet.format('post_resume_process', \
4747 '0', '0', pscolor))
4748 if sysvals.suspendmode == 'command':
4749 hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4750 hf.write('</div>\n')
4751 hf.write('</div>\n')
4752
4753 # write the ftrace data (callgraph)
4754 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4755 data = testruns[sysvals.cgtest]
4756 else:
4757 data = testruns[-1]
4758 if sysvals.usecallgraph:
4759 addCallgraphs(sysvals, hf, data)
4760
4761 # add the test log as a hidden div
4762 if sysvals.testlog and sysvals.logmsg:
4763 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4764 # add the dmesg log as a hidden div
4765 if sysvals.dmesglog and sysvals.dmesgfile:
4766 hf.write('<div id="dmesglog" style="display:none;">\n')
4767 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4768 for line in lf:
4769 line = line.replace('<', '<').replace('>', '>')
4770 hf.write(line)
4771 lf.close()
4772 hf.write('</div>\n')
4773 # add the ftrace log as a hidden div
4774 if sysvals.ftracelog and sysvals.ftracefile:
4775 hf.write('<div id="ftracelog" style="display:none;">\n')
4776 lf = sysvals.openlog(sysvals.ftracefile, 'r')
4777 for line in lf:
4778 hf.write(line)
4779 lf.close()
4780 hf.write('</div>\n')
4781
4782 # write the footer and close
4783 addScriptCode(hf, testruns)
4784 hf.write('</body>\n</html>\n')
4785 hf.close()
4786 return True
4787
4788def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4789 kernel = sv.stamp['kernel']
4790 host = sv.hostname[0].upper()+sv.hostname[1:]
4791 mode = sv.suspendmode
4792 if sv.suspendmode in suspendmodename:
4793 mode = suspendmodename[sv.suspendmode]
4794 title = host+' '+mode+' '+kernel
4795
4796 # various format changes by flags
4797 cgchk = 'checked'
4798 cgnchk = 'not(:checked)'
4799 if sv.cgexp:
4800 cgchk = 'not(:checked)'
4801 cgnchk = 'checked'
4802
4803 hoverZ = 'z-index:8;'
4804 if sv.usedevsrc:
4805 hoverZ = ''
4806
4807 devlistpos = 'absolute'
4808 if testcount > 1:
4809 devlistpos = 'relative'
4810
4811 scaleTH = 20
4812 if kerror:
4813 scaleTH = 60
4814
4815 # write the html header first (html head, css code, up to body start)
4816 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4817 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4818 <title>'+title+'</title>\n\
4819 <style type=\'text/css\'>\n\
4820 body {overflow-y:scroll;}\n\
4821 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4822 .stamp.sysinfo {font:10px Arial;}\n\
4823 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4824 .callgraph article * {padding-left:28px;}\n\
4825 h1 {color:black;font:bold 30px Times;}\n\
4826 t0 {color:black;font:bold 30px Times;}\n\
4827 t1 {color:black;font:30px Times;}\n\
4828 t2 {color:black;font:25px Times;}\n\
4829 t3 {color:black;font:20px Times;white-space:nowrap;}\n\
4830 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
4831 cS {font:bold 13px Times;}\n\
4832 table {width:100%;}\n\
4833 .gray {background:rgba(80,80,80,0.1);}\n\
4834 .green {background:rgba(204,255,204,0.4);}\n\
4835 .purple {background:rgba(128,0,128,0.2);}\n\
4836 .yellow {background:rgba(255,255,204,0.4);}\n\
4837 .blue {background:rgba(169,208,245,0.4);}\n\
4838 .time1 {font:22px Arial;border:1px solid;}\n\
4839 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
4840 .testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
4841 td {text-align:center;}\n\
4842 r {color:#500000;font:15px Tahoma;}\n\
4843 n {color:#505050;font:15px Tahoma;}\n\
4844 .tdhl {color:red;}\n\
4845 .hide {display:none;}\n\
4846 .pf {display:none;}\n\
4847 .pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4848 .pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
4849 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
4850 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
4851 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
4852 .thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\
4853 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
4854 .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4855 .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
4856 .hover {background:white;border:1px solid red;'+hoverZ+'}\n\
4857 .hover.sync {background:white;}\n\
4858 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
4859 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
4860 .traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\
4861 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
4862 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
4863 .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
4864 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
4865 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
4866 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
4867 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
4868 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
4869 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
4870 .devlist {position:'+devlistpos+';width:190px;}\n\
4871 a:link {color:white;text-decoration:none;}\n\
4872 a:visited {color:white;}\n\
4873 a:hover {color:white;}\n\
4874 a:active {color:white;}\n\
4875 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
4876 #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
4877 .tblock {position:absolute;height:100%;background:#ddd;}\n\
4878 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
4879 .bg {z-index:1;}\n\
4880'+extra+'\
4881 </style>\n</head>\n<body>\n'
4882 hf.write(html_header)
4883
4884# Function: addScriptCode
4885# Description:
4886# Adds the javascript code to the output html
4887# Arguments:
4888# hf: the open html file pointer
4889# testruns: array of Data objects from parseKernelLog or parseTraceLog
4890def addScriptCode(hf, testruns):
4891 t0 = testruns[0].start * 1000
4892 tMax = testruns[-1].end * 1000
4893 # create an array in javascript memory with the device details
4894 detail = ' var devtable = [];\n'
4895 for data in testruns:
4896 topo = data.deviceTopology()
4897 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
4898 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
4899 # add the code which will manipulate the data in the browser
4900 script_code = \
4901 '<script type="text/javascript">\n'+detail+\
4902 ' var resolution = -1;\n'\
4903 ' var dragval = [0, 0];\n'\
4904 ' function redrawTimescale(t0, tMax, tS) {\n'\
4905 ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
4906 ' var tTotal = tMax - t0;\n'\
4907 ' var list = document.getElementsByClassName("tblock");\n'\
4908 ' for (var i = 0; i < list.length; i++) {\n'\
4909 ' var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
4910 ' var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
4911 ' var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
4912 ' var mMax = m0 + mTotal;\n'\
4913 ' var html = "";\n'\
4914 ' var divTotal = Math.floor(mTotal/tS) + 1;\n'\
4915 ' if(divTotal > 1000) continue;\n'\
4916 ' var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
4917 ' var pos = 0.0, val = 0.0;\n'\
4918 ' for (var j = 0; j < divTotal; j++) {\n'\
4919 ' var htmlline = "";\n'\
4920 ' var mode = list[i].id[5];\n'\
4921 ' if(mode == "s") {\n'\
4922 ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
4923 ' val = (j-divTotal+1)*tS;\n'\
4924 ' if(j == divTotal - 1)\n'\
4925 ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\
4926 ' else\n'\
4927 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4928 ' } else {\n'\
4929 ' pos = 100 - (((j)*tS*100)/mTotal);\n'\
4930 ' val = (j)*tS;\n'\
4931 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
4932 ' if(j == 0)\n'\
4933 ' if(mode == "r")\n'\
4934 ' htmlline = rline+"<cS>←R</cS></div>";\n'\
4935 ' else\n'\
4936 ' htmlline = rline+"<cS>0ms</div>";\n'\
4937 ' }\n'\
4938 ' html += htmlline;\n'\
4939 ' }\n'\
4940 ' timescale.innerHTML = html;\n'\
4941 ' }\n'\
4942 ' }\n'\
4943 ' function zoomTimeline() {\n'\
4944 ' var dmesg = document.getElementById("dmesg");\n'\
4945 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
4946 ' var left = zoombox.scrollLeft;\n'\
4947 ' var val = parseFloat(dmesg.style.width);\n'\
4948 ' var newval = 100;\n'\
4949 ' var sh = window.outerWidth / 2;\n'\
4950 ' if(this.id == "zoomin") {\n'\
4951 ' newval = val * 1.2;\n'\
4952 ' if(newval > 910034) newval = 910034;\n'\
4953 ' dmesg.style.width = newval+"%";\n'\
4954 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4955 ' } else if (this.id == "zoomout") {\n'\
4956 ' newval = val / 1.2;\n'\
4957 ' if(newval < 100) newval = 100;\n'\
4958 ' dmesg.style.width = newval+"%";\n'\
4959 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
4960 ' } else {\n'\
4961 ' zoombox.scrollLeft = 0;\n'\
4962 ' dmesg.style.width = "100%";\n'\
4963 ' }\n'\
4964 ' var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
4965 ' var t0 = bounds[0];\n'\
4966 ' var tMax = bounds[1];\n'\
4967 ' var tTotal = tMax - t0;\n'\
4968 ' var wTotal = tTotal * 100.0 / newval;\n'\
4969 ' var idx = 7*window.innerWidth/1100;\n'\
4970 ' for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
4971 ' if(i >= tS.length) i = tS.length - 1;\n'\
4972 ' if(tS[i] == resolution) return;\n'\
4973 ' resolution = tS[i];\n'\
4974 ' redrawTimescale(t0, tMax, tS[i]);\n'\
4975 ' }\n'\
4976 ' function deviceName(title) {\n'\
4977 ' var name = title.slice(0, title.indexOf(" ("));\n'\
4978 ' return name;\n'\
4979 ' }\n'\
4980 ' function deviceHover() {\n'\
4981 ' var name = deviceName(this.title);\n'\
4982 ' var dmesg = document.getElementById("dmesg");\n'\
4983 ' var dev = dmesg.getElementsByClassName("thread");\n'\
4984 ' var cpu = -1;\n'\
4985 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
4986 ' cpu = parseInt(name.slice(7));\n'\
4987 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
4988 ' cpu = parseInt(name.slice(8));\n'\
4989 ' for (var i = 0; i < dev.length; i++) {\n'\
4990 ' dname = deviceName(dev[i].title);\n'\
4991 ' var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
4992 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
4993 ' (name == dname))\n'\
4994 ' {\n'\
4995 ' dev[i].className = "hover "+cname;\n'\
4996 ' } else {\n'\
4997 ' dev[i].className = cname;\n'\
4998 ' }\n'\
4999 ' }\n'\
5000 ' }\n'\
5001 ' function deviceUnhover() {\n'\
5002 ' var dmesg = document.getElementById("dmesg");\n'\
5003 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5004 ' for (var i = 0; i < dev.length; i++) {\n'\
5005 ' dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
5006 ' }\n'\
5007 ' }\n'\
5008 ' function deviceTitle(title, total, cpu) {\n'\
5009 ' var prefix = "Total";\n'\
5010 ' if(total.length > 3) {\n'\
5011 ' prefix = "Average";\n'\
5012 ' total[1] = (total[1]+total[3])/2;\n'\
5013 ' total[2] = (total[2]+total[4])/2;\n'\
5014 ' }\n'\
5015 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
5016 ' var name = deviceName(title);\n'\
5017 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
5018 ' var driver = "";\n'\
5019 ' var tS = "<t2>(</t2>";\n'\
5020 ' var tR = "<t2>)</t2>";\n'\
5021 ' if(total[1] > 0)\n'\
5022 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
5023 ' if(total[2] > 0)\n'\
5024 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
5025 ' var s = title.indexOf("{");\n'\
5026 ' var e = title.indexOf("}");\n'\
5027 ' if((s >= 0) && (e >= 0))\n'\
5028 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
5029 ' if(total[1] > 0 && total[2] > 0)\n'\
5030 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
5031 ' else\n'\
5032 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
5033 ' return name;\n'\
5034 ' }\n'\
5035 ' function deviceDetail() {\n'\
5036 ' var devinfo = document.getElementById("devicedetail");\n'\
5037 ' devinfo.style.display = "block";\n'\
5038 ' var name = deviceName(this.title);\n'\
5039 ' var cpu = -1;\n'\
5040 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
5041 ' cpu = parseInt(name.slice(7));\n'\
5042 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
5043 ' cpu = parseInt(name.slice(8));\n'\
5044 ' var dmesg = document.getElementById("dmesg");\n'\
5045 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5046 ' var idlist = [];\n'\
5047 ' var pdata = [[]];\n'\
5048 ' if(document.getElementById("devicedetail1"))\n'\
5049 ' pdata = [[], []];\n'\
5050 ' var pd = pdata[0];\n'\
5051 ' var total = [0.0, 0.0, 0.0];\n'\
5052 ' for (var i = 0; i < dev.length; i++) {\n'\
5053 ' dname = deviceName(dev[i].title);\n'\
5054 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
5055 ' (name == dname))\n'\
5056 ' {\n'\
5057 ' idlist[idlist.length] = dev[i].id;\n'\
5058 ' var tidx = 1;\n'\
5059 ' if(dev[i].id[0] == "a") {\n'\
5060 ' pd = pdata[0];\n'\
5061 ' } else {\n'\
5062 ' if(pdata.length == 1) pdata[1] = [];\n'\
5063 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
5064 ' pd = pdata[1];\n'\
5065 ' tidx = 3;\n'\
5066 ' }\n'\
5067 ' var info = dev[i].title.split(" ");\n'\
5068 ' var pname = info[info.length-1];\n'\
5069 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
5070 ' total[0] += pd[pname];\n'\
5071 ' if(pname.indexOf("suspend") >= 0)\n'\
5072 ' total[tidx] += pd[pname];\n'\
5073 ' else\n'\
5074 ' total[tidx+1] += pd[pname];\n'\
5075 ' }\n'\
5076 ' }\n'\
5077 ' var devname = deviceTitle(this.title, total, cpu);\n'\
5078 ' var left = 0.0;\n'\
5079 ' for (var t = 0; t < pdata.length; t++) {\n'\
5080 ' pd = pdata[t];\n'\
5081 ' devinfo = document.getElementById("devicedetail"+t);\n'\
5082 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
5083 ' for (var i = 0; i < phases.length; i++) {\n'\
5084 ' if(phases[i].id in pd) {\n'\
5085 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
5086 ' var fs = 32;\n'\
5087 ' if(w < 8) fs = 4*w | 0;\n'\
5088 ' var fs2 = fs*3/4;\n'\
5089 ' phases[i].style.width = w+"%";\n'\
5090 ' phases[i].style.left = left+"%";\n'\
5091 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
5092 ' left += w;\n'\
5093 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
5094 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
5095 ' phases[i].innerHTML = time+pname;\n'\
5096 ' } else {\n'\
5097 ' phases[i].style.width = "0%";\n'\
5098 ' phases[i].style.left = left+"%";\n'\
5099 ' }\n'\
5100 ' }\n'\
5101 ' }\n'\
5102 ' if(typeof devstats !== \'undefined\')\n'\
5103 ' callDetail(this.id, this.title);\n'\
5104 ' var cglist = document.getElementById("callgraphs");\n'\
5105 ' if(!cglist) return;\n'\
5106 ' var cg = cglist.getElementsByClassName("atop");\n'\
5107 ' if(cg.length < 10) return;\n'\
5108 ' for (var i = 0; i < cg.length; i++) {\n'\
5109 ' cgid = cg[i].id.split("x")[0]\n'\
5110 ' if(idlist.indexOf(cgid) >= 0) {\n'\
5111 ' cg[i].style.display = "block";\n'\
5112 ' } else {\n'\
5113 ' cg[i].style.display = "none";\n'\
5114 ' }\n'\
5115 ' }\n'\
5116 ' }\n'\
5117 ' function callDetail(devid, devtitle) {\n'\
5118 ' if(!(devid in devstats) || devstats[devid].length < 1)\n'\
5119 ' return;\n'\
5120 ' var list = devstats[devid];\n'\
5121 ' var tmp = devtitle.split(" ");\n'\
5122 ' var name = tmp[0], phase = tmp[tmp.length-1];\n'\
5123 ' var dd = document.getElementById(phase);\n'\
5124 ' var total = parseFloat(tmp[1].slice(1));\n'\
5125 ' var mlist = [];\n'\
5126 ' var maxlen = 0;\n'\
5127 ' var info = []\n'\
5128 ' for(var i in list) {\n'\
5129 ' if(list[i][0] == "@") {\n'\
5130 ' info = list[i].split("|");\n'\
5131 ' continue;\n'\
5132 ' }\n'\
5133 ' var tmp = list[i].split("|");\n'\
5134 ' var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
5135 ' var p = (t*100.0/total).toFixed(2);\n'\
5136 ' mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
5137 ' if(f.length > maxlen)\n'\
5138 ' maxlen = f.length;\n'\
5139 ' }\n'\
5140 ' var pad = 5;\n'\
5141 ' if(mlist.length == 0) pad = 30;\n'\
5142 ' var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
5143 ' if(info.length > 2)\n'\
5144 ' html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
5145 ' if(info.length > 3)\n'\
5146 ' html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
5147 ' if(info.length > 4)\n'\
5148 ' html += ", return=<b>"+info[4]+"</b>";\n'\
5149 ' html += "</t3></div>";\n'\
5150 ' if(mlist.length > 0) {\n'\
5151 ' html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
5152 ' for(var i in mlist)\n'\
5153 ' html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
5154 ' html += "</tr><tr><th>Calls</th>";\n'\
5155 ' for(var i in mlist)\n'\
5156 ' html += "<td>"+mlist[i][1]+"</td>";\n'\
5157 ' html += "</tr><tr><th>Time(ms)</th>";\n'\
5158 ' for(var i in mlist)\n'\
5159 ' html += "<td>"+mlist[i][2]+"</td>";\n'\
5160 ' html += "</tr><tr><th>Percent</th>";\n'\
5161 ' for(var i in mlist)\n'\
5162 ' html += "<td>"+mlist[i][3]+"</td>";\n'\
5163 ' html += "</tr></table>";\n'\
5164 ' }\n'\
5165 ' dd.innerHTML = html;\n'\
5166 ' var height = (maxlen*5)+100;\n'\
5167 ' dd.style.height = height+"px";\n'\
5168 ' document.getElementById("devicedetail").style.height = height+"px";\n'\
5169 ' }\n'\
5170 ' function callSelect() {\n'\
5171 ' var cglist = document.getElementById("callgraphs");\n'\
5172 ' if(!cglist) return;\n'\
5173 ' var cg = cglist.getElementsByClassName("atop");\n'\
5174 ' for (var i = 0; i < cg.length; i++) {\n'\
5175 ' if(this.id == cg[i].id) {\n'\
5176 ' cg[i].style.display = "block";\n'\
5177 ' } else {\n'\
5178 ' cg[i].style.display = "none";\n'\
5179 ' }\n'\
5180 ' }\n'\
5181 ' }\n'\
5182 ' function devListWindow(e) {\n'\
5183 ' var win = window.open();\n'\
5184 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
5185 ' "<style type=\\"text/css\\">"+\n'\
5186 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
5187 ' "</style>"\n'\
5188 ' var dt = devtable[0];\n'\
5189 ' if(e.target.id != "devlist1")\n'\
5190 ' dt = devtable[1];\n'\
5191 ' win.document.write(html+dt);\n'\
5192 ' }\n'\
5193 ' function errWindow() {\n'\
5194 ' var range = this.id.split("_");\n'\
5195 ' var idx1 = parseInt(range[0]);\n'\
5196 ' var idx2 = parseInt(range[1]);\n'\
5197 ' var win = window.open();\n'\
5198 ' var log = document.getElementById("dmesglog");\n'\
5199 ' var title = "<title>dmesg log</title>";\n'\
5200 ' var text = log.innerHTML.split("\\n");\n'\
5201 ' var html = "";\n'\
5202 ' for(var i = 0; i < text.length; i++) {\n'\
5203 ' if(i == idx1) {\n'\
5204 ' html += "<e id=target>"+text[i]+"</e>\\n";\n'\
5205 ' } else if(i > idx1 && i <= idx2) {\n'\
5206 ' html += "<e>"+text[i]+"</e>\\n";\n'\
5207 ' } else {\n'\
5208 ' html += text[i]+"\\n";\n'\
5209 ' }\n'\
5210 ' }\n'\
5211 ' win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
5212 ' win.location.hash = "#target";\n'\
5213 ' win.document.close();\n'\
5214 ' }\n'\
5215 ' function logWindow(e) {\n'\
5216 ' var name = e.target.id.slice(4);\n'\
5217 ' var win = window.open();\n'\
5218 ' var log = document.getElementById(name+"log");\n'\
5219 ' var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
5220 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
5221 ' win.document.close();\n'\
5222 ' }\n'\
5223 ' function onMouseDown(e) {\n'\
5224 ' dragval[0] = e.clientX;\n'\
5225 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
5226 ' document.onmousemove = onMouseMove;\n'\
5227 ' }\n'\
5228 ' function onMouseMove(e) {\n'\
5229 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
5230 ' zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
5231 ' }\n'\
5232 ' function onMouseUp(e) {\n'\
5233 ' document.onmousemove = null;\n'\
5234 ' }\n'\
5235 ' function onKeyPress(e) {\n'\
5236 ' var c = e.charCode;\n'\
5237 ' if(c != 42 && c != 43 && c != 45) return;\n'\
5238 ' var click = document.createEvent("Events");\n'\
5239 ' click.initEvent("click", true, false);\n'\
5240 ' if(c == 43) \n'\
5241 ' document.getElementById("zoomin").dispatchEvent(click);\n'\
5242 ' else if(c == 45)\n'\
5243 ' document.getElementById("zoomout").dispatchEvent(click);\n'\
5244 ' else if(c == 42)\n'\
5245 ' document.getElementById("zoomdef").dispatchEvent(click);\n'\
5246 ' }\n'\
5247 ' window.addEventListener("resize", function () {zoomTimeline();});\n'\
5248 ' window.addEventListener("load", function () {\n'\
5249 ' var dmesg = document.getElementById("dmesg");\n'\
5250 ' dmesg.style.width = "100%"\n'\
5251 ' dmesg.onmousedown = onMouseDown;\n'\
5252 ' document.onmouseup = onMouseUp;\n'\
5253 ' document.onkeypress = onKeyPress;\n'\
5254 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
5255 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
5256 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
5257 ' var list = document.getElementsByClassName("err");\n'\
5258 ' for (var i = 0; i < list.length; i++)\n'\
5259 ' list[i].onclick = errWindow;\n'\
5260 ' var list = document.getElementsByClassName("logbtn");\n'\
5261 ' for (var i = 0; i < list.length; i++)\n'\
5262 ' list[i].onclick = logWindow;\n'\
5263 ' list = document.getElementsByClassName("devlist");\n'\
5264 ' for (var i = 0; i < list.length; i++)\n'\
5265 ' list[i].onclick = devListWindow;\n'\
5266 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5267 ' for (var i = 0; i < dev.length; i++) {\n'\
5268 ' dev[i].onclick = deviceDetail;\n'\
5269 ' dev[i].onmouseover = deviceHover;\n'\
5270 ' dev[i].onmouseout = deviceUnhover;\n'\
5271 ' }\n'\
5272 ' var dev = dmesg.getElementsByClassName("srccall");\n'\
5273 ' for (var i = 0; i < dev.length; i++)\n'\
5274 ' dev[i].onclick = callSelect;\n'\
5275 ' zoomTimeline();\n'\
5276 ' });\n'\
5277 '</script>\n'
5278 hf.write(script_code);
5279
5280# Function: executeSuspend
5281# Description:
5282# Execute system suspend through the sysfs interface, then copy the output
5283# dmesg and ftrace files to the test output directory.
5284def executeSuspend(quiet=False):
5285 sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor()
5286 if sv.wifi:
5287 wifi = sv.checkWifi()
5288 sv.dlog('wifi check, connected device is "%s"' % wifi)
5289 testdata = []
5290 # run these commands to prepare the system for suspend
5291 if sv.display:
5292 if not quiet:
5293 pprint('SET DISPLAY TO %s' % sv.display.upper())
5294 ret = sv.displayControl(sv.display)
5295 sv.dlog('xset display %s, ret = %d' % (sv.display, ret))
5296 time.sleep(1)
5297 if sv.sync:
5298 if not quiet:
5299 pprint('SYNCING FILESYSTEMS')
5300 sv.dlog('syncing filesystems')
5301 call('sync', shell=True)
5302 sv.dlog('read dmesg')
5303 sv.initdmesg()
5304 # start ftrace
5305 if(sv.usecallgraph or sv.usetraceevents):
5306 if not quiet:
5307 pprint('START TRACING')
5308 sv.dlog('start ftrace tracing')
5309 sv.fsetVal('1', 'tracing_on')
5310 if sv.useprocmon:
5311 sv.dlog('start the process monitor')
5312 pm.start()
5313 sv.dlog('run the cmdinfo list before')
5314 sv.cmdinfo(True)
5315 # execute however many s/r runs requested
5316 for count in range(1,sv.execcount+1):
5317 # x2delay in between test runs
5318 if(count > 1 and sv.x2delay > 0):
5319 sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker')
5320 time.sleep(sv.x2delay/1000.0)
5321 sv.fsetVal('WAIT END', 'trace_marker')
5322 # start message
5323 if sv.testcommand != '':
5324 pprint('COMMAND START')
5325 else:
5326 if(sv.rtcwake):
5327 pprint('SUSPEND START')
5328 else:
5329 pprint('SUSPEND START (press a key to resume)')
5330 # set rtcwake
5331 if(sv.rtcwake):
5332 if not quiet:
5333 pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime)
5334 sv.dlog('enable RTC wake alarm')
5335 sv.rtcWakeAlarmOn()
5336 # start of suspend trace marker
5337 if(sv.usecallgraph or sv.usetraceevents):
5338 sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
5339 # predelay delay
5340 if(count == 1 and sv.predelay > 0):
5341 sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
5342 time.sleep(sv.predelay/1000.0)
5343 sv.fsetVal('WAIT END', 'trace_marker')
5344 # initiate suspend or command
5345 sv.dlog('system executing a suspend')
5346 tdata = {'error': ''}
5347 if sv.testcommand != '':
5348 res = call(sv.testcommand+' 2>&1', shell=True);
5349 if res != 0:
5350 tdata['error'] = 'cmd returned %d' % res
5351 else:
5352 mode = sv.suspendmode
5353 if sv.memmode and os.path.exists(sv.mempowerfile):
5354 mode = 'mem'
5355 sv.testVal(sv.mempowerfile, 'radio', sv.memmode)
5356 if sv.diskmode and os.path.exists(sv.diskpowerfile):
5357 mode = 'disk'
5358 sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode)
5359 if sv.acpidebug:
5360 sv.testVal(sv.acpipath, 'acpi', '0xe')
5361 if mode == 'freeze' and sv.haveTurbostat():
5362 # execution will pause here
5363 turbo = sv.turbostat()
5364 if turbo:
5365 tdata['turbo'] = turbo
5366 else:
5367 pf = open(sv.powerfile, 'w')
5368 pf.write(mode)
5369 # execution will pause here
5370 try:
5371 pf.close()
5372 except Exception as e:
5373 tdata['error'] = str(e)
5374 sv.dlog('system returned from resume')
5375 # reset everything
5376 sv.testVal('restoreall')
5377 if(sv.rtcwake):
5378 sv.dlog('disable RTC wake alarm')
5379 sv.rtcWakeAlarmOff()
5380 # postdelay delay
5381 if(count == sv.execcount and sv.postdelay > 0):
5382 sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker')
5383 time.sleep(sv.postdelay/1000.0)
5384 sv.fsetVal('WAIT END', 'trace_marker')
5385 # return from suspend
5386 pprint('RESUME COMPLETE')
5387 if(sv.usecallgraph or sv.usetraceevents):
5388 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5389 if sv.wifi and wifi:
5390 tdata['wifi'] = sv.pollWifi(wifi)
5391 sv.dlog('wifi check, %s' % tdata['wifi'])
5392 if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
5393 sv.dlog('read the ACPI FPDT')
5394 tdata['fw'] = getFPDT(False)
5395 testdata.append(tdata)
5396 sv.dlog('run the cmdinfo list after')
5397 cmdafter = sv.cmdinfo(False)
5398 # stop ftrace
5399 if(sv.usecallgraph or sv.usetraceevents):
5400 if sv.useprocmon:
5401 sv.dlog('stop the process monitor')
5402 pm.stop()
5403 sv.fsetVal('0', 'tracing_on')
5404 # grab a copy of the dmesg output
5405 if not quiet:
5406 pprint('CAPTURING DMESG')
5407 sysvals.dlog('EXECUTION TRACE END')
5408 sv.getdmesg(testdata)
5409 # grab a copy of the ftrace output
5410 if(sv.usecallgraph or sv.usetraceevents):
5411 if not quiet:
5412 pprint('CAPTURING TRACE')
5413 op = sv.writeDatafileHeader(sv.ftracefile, testdata)
5414 fp = open(tp+'trace', 'r')
5415 for line in fp:
5416 op.write(line)
5417 op.close()
5418 sv.fsetVal('', 'trace')
5419 sv.platforminfo(cmdafter)
5420
5421def readFile(file):
5422 if os.path.islink(file):
5423 return os.readlink(file).split('/')[-1]
5424 else:
5425 return sysvals.getVal(file).strip()
5426
5427# Function: ms2nice
5428# Description:
5429# Print out a very concise time string in minutes and seconds
5430# Output:
5431# The time string, e.g. "1901m16s"
5432def ms2nice(val):
5433 val = int(val)
5434 h = val // 3600000
5435 m = (val // 60000) % 60
5436 s = (val // 1000) % 60
5437 if h > 0:
5438 return '%d:%02d:%02d' % (h, m, s)
5439 if m > 0:
5440 return '%02d:%02d' % (m, s)
5441 return '%ds' % s
5442
5443def yesno(val):
5444 list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5445 'active':'A', 'suspended':'S', 'suspending':'S'}
5446 if val not in list:
5447 return ' '
5448 return list[val]
5449
5450# Function: deviceInfo
5451# Description:
5452# Detect all the USB hosts and devices currently connected and add
5453# a list of USB device names to sysvals for better timeline readability
5454def deviceInfo(output=''):
5455 if not output:
5456 pprint('LEGEND\n'\
5457 '---------------------------------------------------------------------------------------------\n'\
5458 ' A = async/sync PM queue (A/S) C = runtime active children\n'\
5459 ' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)\n'\
5460 ' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)\n'\
5461 ' U = runtime usage count\n'\
5462 '---------------------------------------------------------------------------------------------\n'\
5463 'DEVICE NAME A R S U C rACTIVE rSUSPEND\n'\
5464 '---------------------------------------------------------------------------------------------')
5465
5466 res = []
5467 tgtval = 'runtime_status'
5468 lines = dict()
5469 for dirname, dirnames, filenames in os.walk('/sys/devices'):
5470 if(not re.match('.*/power', dirname) or
5471 'control' not in filenames or
5472 tgtval not in filenames):
5473 continue
5474 name = ''
5475 dirname = dirname[:-6]
5476 device = dirname.split('/')[-1]
5477 power = dict()
5478 power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5479 # only list devices which support runtime suspend
5480 if power[tgtval] not in ['active', 'suspended', 'suspending']:
5481 continue
5482 for i in ['product', 'driver', 'subsystem']:
5483 file = '%s/%s' % (dirname, i)
5484 if os.path.exists(file):
5485 name = readFile(file)
5486 break
5487 for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5488 'runtime_active_kids', 'runtime_active_time',
5489 'runtime_suspended_time']:
5490 if i in filenames:
5491 power[i] = readFile('%s/power/%s' % (dirname, i))
5492 if output:
5493 if power['control'] == output:
5494 res.append('%s/power/control' % dirname)
5495 continue
5496 lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5497 (device[:26], name[:26],
5498 yesno(power['async']), \
5499 yesno(power['control']), \
5500 yesno(power['runtime_status']), \
5501 power['runtime_usage'], \
5502 power['runtime_active_kids'], \
5503 ms2nice(power['runtime_active_time']), \
5504 ms2nice(power['runtime_suspended_time']))
5505 for i in sorted(lines):
5506 print(lines[i])
5507 return res
5508
5509# Function: getModes
5510# Description:
5511# Determine the supported power modes on this system
5512# Output:
5513# A string list of the available modes
5514def getModes():
5515 modes = []
5516 if(os.path.exists(sysvals.powerfile)):
5517 fp = open(sysvals.powerfile, 'r')
5518 modes = fp.read().split()
5519 fp.close()
5520 if(os.path.exists(sysvals.mempowerfile)):
5521 deep = False
5522 fp = open(sysvals.mempowerfile, 'r')
5523 for m in fp.read().split():
5524 memmode = m.strip('[]')
5525 if memmode == 'deep':
5526 deep = True
5527 else:
5528 modes.append('mem-%s' % memmode)
5529 fp.close()
5530 if 'mem' in modes and not deep:
5531 modes.remove('mem')
5532 if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5533 fp = open(sysvals.diskpowerfile, 'r')
5534 for m in fp.read().split():
5535 modes.append('disk-%s' % m.strip('[]'))
5536 fp.close()
5537 return modes
5538
5539# Function: dmidecode
5540# Description:
5541# Read the bios tables and pull out system info
5542# Arguments:
5543# mempath: /dev/mem or custom mem path
5544# fatal: True to exit on error, False to return empty dict
5545# Output:
5546# A dict object with all available key/values
5547def dmidecode(mempath, fatal=False):
5548 out = dict()
5549
5550 # the list of values to retrieve, with hardcoded (type, idx)
5551 info = {
5552 'bios-vendor': (0, 4),
5553 'bios-version': (0, 5),
5554 'bios-release-date': (0, 8),
5555 'system-manufacturer': (1, 4),
5556 'system-product-name': (1, 5),
5557 'system-version': (1, 6),
5558 'system-serial-number': (1, 7),
5559 'baseboard-manufacturer': (2, 4),
5560 'baseboard-product-name': (2, 5),
5561 'baseboard-version': (2, 6),
5562 'baseboard-serial-number': (2, 7),
5563 'chassis-manufacturer': (3, 4),
5564 'chassis-type': (3, 5),
5565 'chassis-version': (3, 6),
5566 'chassis-serial-number': (3, 7),
5567 'processor-manufacturer': (4, 7),
5568 'processor-version': (4, 16),
5569 }
5570 if(not os.path.exists(mempath)):
5571 if(fatal):
5572 doError('file does not exist: %s' % mempath)
5573 return out
5574 if(not os.access(mempath, os.R_OK)):
5575 if(fatal):
5576 doError('file is not readable: %s' % mempath)
5577 return out
5578
5579 # by default use legacy scan, but try to use EFI first
5580 memaddr = 0xf0000
5581 memsize = 0x10000
5582 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5583 if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5584 continue
5585 fp = open(ep, 'r')
5586 buf = fp.read()
5587 fp.close()
5588 i = buf.find('SMBIOS=')
5589 if i >= 0:
5590 try:
5591 memaddr = int(buf[i+7:], 16)
5592 memsize = 0x20
5593 except:
5594 continue
5595
5596 # read in the memory for scanning
5597 try:
5598 fp = open(mempath, 'rb')
5599 fp.seek(memaddr)
5600 buf = fp.read(memsize)
5601 except:
5602 if(fatal):
5603 doError('DMI table is unreachable, sorry')
5604 else:
5605 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5606 return out
5607 fp.close()
5608
5609 # search for either an SM table or DMI table
5610 i = base = length = num = 0
5611 while(i < memsize):
5612 if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5613 length = struct.unpack('H', buf[i+22:i+24])[0]
5614 base, num = struct.unpack('IH', buf[i+24:i+30])
5615 break
5616 elif buf[i:i+5] == b'_DMI_':
5617 length = struct.unpack('H', buf[i+6:i+8])[0]
5618 base, num = struct.unpack('IH', buf[i+8:i+14])
5619 break
5620 i += 16
5621 if base == 0 and length == 0 and num == 0:
5622 if(fatal):
5623 doError('Neither SMBIOS nor DMI were found')
5624 else:
5625 return out
5626
5627 # read in the SM or DMI table
5628 try:
5629 fp = open(mempath, 'rb')
5630 fp.seek(base)
5631 buf = fp.read(length)
5632 except:
5633 if(fatal):
5634 doError('DMI table is unreachable, sorry')
5635 else:
5636 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5637 return out
5638 fp.close()
5639
5640 # scan the table for the values we want
5641 count = i = 0
5642 while(count < num and i <= len(buf) - 4):
5643 type, size, handle = struct.unpack('BBH', buf[i:i+4])
5644 n = i + size
5645 while n < len(buf) - 1:
5646 if 0 == struct.unpack('H', buf[n:n+2])[0]:
5647 break
5648 n += 1
5649 data = buf[i+size:n+2].split(b'\0')
5650 for name in info:
5651 itype, idxadr = info[name]
5652 if itype == type:
5653 idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5654 if idx > 0 and idx < len(data) - 1:
5655 s = data[idx-1].decode('utf-8')
5656 if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5657 out[name] = s
5658 i = n + 2
5659 count += 1
5660 return out
5661
5662# Function: getFPDT
5663# Description:
5664# Read the acpi bios tables and pull out FPDT, the firmware data
5665# Arguments:
5666# output: True to output the info to stdout, False otherwise
5667def getFPDT(output):
5668 rectype = {}
5669 rectype[0] = 'Firmware Basic Boot Performance Record'
5670 rectype[1] = 'S3 Performance Table Record'
5671 prectype = {}
5672 prectype[0] = 'Basic S3 Resume Performance Record'
5673 prectype[1] = 'Basic S3 Suspend Performance Record'
5674
5675 sysvals.rootCheck(True)
5676 if(not os.path.exists(sysvals.fpdtpath)):
5677 if(output):
5678 doError('file does not exist: %s' % sysvals.fpdtpath)
5679 return False
5680 if(not os.access(sysvals.fpdtpath, os.R_OK)):
5681 if(output):
5682 doError('file is not readable: %s' % sysvals.fpdtpath)
5683 return False
5684 if(not os.path.exists(sysvals.mempath)):
5685 if(output):
5686 doError('file does not exist: %s' % sysvals.mempath)
5687 return False
5688 if(not os.access(sysvals.mempath, os.R_OK)):
5689 if(output):
5690 doError('file is not readable: %s' % sysvals.mempath)
5691 return False
5692
5693 fp = open(sysvals.fpdtpath, 'rb')
5694 buf = fp.read()
5695 fp.close()
5696
5697 if(len(buf) < 36):
5698 if(output):
5699 doError('Invalid FPDT table data, should '+\
5700 'be at least 36 bytes')
5701 return False
5702
5703 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5704 if(output):
5705 pprint('\n'\
5706 'Firmware Performance Data Table (%s)\n'\
5707 ' Signature : %s\n'\
5708 ' Table Length : %u\n'\
5709 ' Revision : %u\n'\
5710 ' Checksum : 0x%x\n'\
5711 ' OEM ID : %s\n'\
5712 ' OEM Table ID : %s\n'\
5713 ' OEM Revision : %u\n'\
5714 ' Creator ID : %s\n'\
5715 ' Creator Revision : 0x%x\n'\
5716 '' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5717 table[3], ascii(table[4]), ascii(table[5]), table[6],
5718 ascii(table[7]), table[8]))
5719
5720 if(table[0] != b'FPDT'):
5721 if(output):
5722 doError('Invalid FPDT table')
5723 return False
5724 if(len(buf) <= 36):
5725 return False
5726 i = 0
5727 fwData = [0, 0]
5728 records = buf[36:]
5729 try:
5730 fp = open(sysvals.mempath, 'rb')
5731 except:
5732 pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5733 return False
5734 while(i < len(records)):
5735 header = struct.unpack('HBB', records[i:i+4])
5736 if(header[0] not in rectype):
5737 i += header[1]
5738 continue
5739 if(header[1] != 16):
5740 i += header[1]
5741 continue
5742 addr = struct.unpack('Q', records[i+8:i+16])[0]
5743 try:
5744 fp.seek(addr)
5745 first = fp.read(8)
5746 except:
5747 if(output):
5748 pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5749 return [0, 0]
5750 rechead = struct.unpack('4sI', first)
5751 recdata = fp.read(rechead[1]-8)
5752 if(rechead[0] == b'FBPT'):
5753 record = struct.unpack('HBBIQQQQQ', recdata[:48])
5754 if(output):
5755 pprint('%s (%s)\n'\
5756 ' Reset END : %u ns\n'\
5757 ' OS Loader LoadImage Start : %u ns\n'\
5758 ' OS Loader StartImage Start : %u ns\n'\
5759 ' ExitBootServices Entry : %u ns\n'\
5760 ' ExitBootServices Exit : %u ns'\
5761 '' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5762 record[6], record[7], record[8]))
5763 elif(rechead[0] == b'S3PT'):
5764 if(output):
5765 pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5766 j = 0
5767 while(j < len(recdata)):
5768 prechead = struct.unpack('HBB', recdata[j:j+4])
5769 if(prechead[0] not in prectype):
5770 continue
5771 if(prechead[0] == 0):
5772 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5773 fwData[1] = record[2]
5774 if(output):
5775 pprint(' %s\n'\
5776 ' Resume Count : %u\n'\
5777 ' FullResume : %u ns\n'\
5778 ' AverageResume : %u ns'\
5779 '' % (prectype[prechead[0]], record[1],
5780 record[2], record[3]))
5781 elif(prechead[0] == 1):
5782 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5783 fwData[0] = record[1] - record[0]
5784 if(output):
5785 pprint(' %s\n'\
5786 ' SuspendStart : %u ns\n'\
5787 ' SuspendEnd : %u ns\n'\
5788 ' SuspendTime : %u ns'\
5789 '' % (prectype[prechead[0]], record[0],
5790 record[1], fwData[0]))
5791
5792 j += prechead[1]
5793 if(output):
5794 pprint('')
5795 i += header[1]
5796 fp.close()
5797 return fwData
5798
5799# Function: statusCheck
5800# Description:
5801# Verify that the requested command and options will work, and
5802# print the results to the terminal
5803# Output:
5804# True if the test will work, False if not
5805def statusCheck(probecheck=False):
5806 status = ''
5807
5808 pprint('Checking this system (%s)...' % platform.node())
5809
5810 # check we have root access
5811 res = sysvals.colorText('NO (No features of this tool will work!)')
5812 if(sysvals.rootCheck(False)):
5813 res = 'YES'
5814 pprint(' have root access: %s' % res)
5815 if(res != 'YES'):
5816 pprint(' Try running this script with sudo')
5817 return 'missing root access'
5818
5819 # check sysfs is mounted
5820 res = sysvals.colorText('NO (No features of this tool will work!)')
5821 if(os.path.exists(sysvals.powerfile)):
5822 res = 'YES'
5823 pprint(' is sysfs mounted: %s' % res)
5824 if(res != 'YES'):
5825 return 'sysfs is missing'
5826
5827 # check target mode is a valid mode
5828 if sysvals.suspendmode != 'command':
5829 res = sysvals.colorText('NO')
5830 modes = getModes()
5831 if(sysvals.suspendmode in modes):
5832 res = 'YES'
5833 else:
5834 status = '%s mode is not supported' % sysvals.suspendmode
5835 pprint(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
5836 if(res == 'NO'):
5837 pprint(' valid power modes are: %s' % modes)
5838 pprint(' please choose one with -m')
5839
5840 # check if ftrace is available
5841 res = sysvals.colorText('NO')
5842 ftgood = sysvals.verifyFtrace()
5843 if(ftgood):
5844 res = 'YES'
5845 elif(sysvals.usecallgraph):
5846 status = 'ftrace is not properly supported'
5847 pprint(' is ftrace supported: %s' % res)
5848
5849 # check if kprobes are available
5850 if sysvals.usekprobes:
5851 res = sysvals.colorText('NO')
5852 sysvals.usekprobes = sysvals.verifyKprobes()
5853 if(sysvals.usekprobes):
5854 res = 'YES'
5855 else:
5856 sysvals.usedevsrc = False
5857 pprint(' are kprobes supported: %s' % res)
5858
5859 # what data source are we using
5860 res = 'DMESG'
5861 if(ftgood):
5862 sysvals.usetraceevents = True
5863 for e in sysvals.traceevents:
5864 if not os.path.exists(sysvals.epath+e):
5865 sysvals.usetraceevents = False
5866 if(sysvals.usetraceevents):
5867 res = 'FTRACE (all trace events found)'
5868 pprint(' timeline data source: %s' % res)
5869
5870 # check if rtcwake
5871 res = sysvals.colorText('NO')
5872 if(sysvals.rtcpath != ''):
5873 res = 'YES'
5874 elif(sysvals.rtcwake):
5875 status = 'rtcwake is not properly supported'
5876 pprint(' is rtcwake supported: %s' % res)
5877
5878 # check info commands
5879 pprint(' optional commands this tool may use for info:')
5880 no = sysvals.colorText('MISSING')
5881 yes = sysvals.colorText('FOUND', 32)
5882 for c in ['turbostat', 'mcelog', 'lspci', 'lsusb']:
5883 if c == 'turbostat':
5884 res = yes if sysvals.haveTurbostat() else no
5885 else:
5886 res = yes if sysvals.getExec(c) else no
5887 pprint(' %s: %s' % (c, res))
5888
5889 if not probecheck:
5890 return status
5891
5892 # verify kprobes
5893 if sysvals.usekprobes:
5894 for name in sysvals.tracefuncs:
5895 sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
5896 if sysvals.usedevsrc:
5897 for name in sysvals.dev_tracefuncs:
5898 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
5899 sysvals.addKprobes(True)
5900
5901 return status
5902
5903# Function: doError
5904# Description:
5905# generic error function for catastrphic failures
5906# Arguments:
5907# msg: the error message to print
5908# help: True if printHelp should be called after, False otherwise
5909def doError(msg, help=False):
5910 if(help == True):
5911 printHelp()
5912 pprint('ERROR: %s\n' % msg)
5913 sysvals.outputResult({'error':msg})
5914 sys.exit(1)
5915
5916# Function: getArgInt
5917# Description:
5918# pull out an integer argument from the command line with checks
5919def getArgInt(name, args, min, max, main=True):
5920 if main:
5921 try:
5922 arg = next(args)
5923 except:
5924 doError(name+': no argument supplied', True)
5925 else:
5926 arg = args
5927 try:
5928 val = int(arg)
5929 except:
5930 doError(name+': non-integer value given', True)
5931 if(val < min or val > max):
5932 doError(name+': value should be between %d and %d' % (min, max), True)
5933 return val
5934
5935# Function: getArgFloat
5936# Description:
5937# pull out a float argument from the command line with checks
5938def getArgFloat(name, args, min, max, main=True):
5939 if main:
5940 try:
5941 arg = next(args)
5942 except:
5943 doError(name+': no argument supplied', True)
5944 else:
5945 arg = args
5946 try:
5947 val = float(arg)
5948 except:
5949 doError(name+': non-numerical value given', True)
5950 if(val < min or val > max):
5951 doError(name+': value should be between %f and %f' % (min, max), True)
5952 return val
5953
5954def processData(live=False, quiet=False):
5955 if not quiet:
5956 pprint('PROCESSING: %s' % sysvals.htmlfile)
5957 sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
5958 (sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
5959 error = ''
5960 if(sysvals.usetraceevents):
5961 testruns, error = parseTraceLog(live)
5962 if sysvals.dmesgfile:
5963 for data in testruns:
5964 data.extractErrorInfo()
5965 else:
5966 testruns = loadKernelLog()
5967 for data in testruns:
5968 parseKernelLog(data)
5969 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
5970 appendIncompleteTraceLog(testruns)
5971 if not sysvals.stamp:
5972 pprint('ERROR: data does not include the expected stamp')
5973 return (testruns, {'error': 'timeline generation failed'})
5974 shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
5975 'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
5976 sysvals.vprint('System Info:')
5977 for key in sorted(sysvals.stamp):
5978 if key in shown:
5979 sysvals.vprint(' %-8s : %s' % (key.upper(), sysvals.stamp[key]))
5980 sysvals.vprint('Command:\n %s' % sysvals.cmdline)
5981 for data in testruns:
5982 if data.turbostat:
5983 idx, s = 0, 'Turbostat:\n '
5984 for val in data.turbostat.split('|'):
5985 idx += len(val) + 1
5986 if idx >= 80:
5987 idx = 0
5988 s += '\n '
5989 s += val + ' '
5990 sysvals.vprint(s)
5991 data.printDetails()
5992 if len(sysvals.platinfo) > 0:
5993 sysvals.vprint('\nPlatform Info:')
5994 for info in sysvals.platinfo:
5995 sysvals.vprint('[%s - %s]' % (info[0], info[1]))
5996 sysvals.vprint(info[2])
5997 sysvals.vprint('')
5998 if sysvals.cgdump:
5999 for data in testruns:
6000 data.debugPrint()
6001 sys.exit(0)
6002 if len(testruns) < 1:
6003 pprint('ERROR: Not enough test data to build a timeline')
6004 return (testruns, {'error': 'timeline generation failed'})
6005 sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6006 createHTML(testruns, error)
6007 if not quiet:
6008 pprint('DONE: %s' % sysvals.htmlfile)
6009 data = testruns[0]
6010 stamp = data.stamp
6011 stamp['suspend'], stamp['resume'] = data.getTimeValues()
6012 if data.fwValid:
6013 stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6014 if error:
6015 stamp['error'] = error
6016 return (testruns, stamp)
6017
6018# Function: rerunTest
6019# Description:
6020# generate an output from an existing set of ftrace/dmesg logs
6021def rerunTest(htmlfile=''):
6022 if sysvals.ftracefile:
6023 doesTraceLogHaveTraceEvents()
6024 if not sysvals.dmesgfile and not sysvals.usetraceevents:
6025 doError('recreating this html output requires a dmesg file')
6026 if htmlfile:
6027 sysvals.htmlfile = htmlfile
6028 else:
6029 sysvals.setOutputFile()
6030 if os.path.exists(sysvals.htmlfile):
6031 if not os.path.isfile(sysvals.htmlfile):
6032 doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6033 elif not os.access(sysvals.htmlfile, os.W_OK):
6034 doError('missing permission to write to %s' % sysvals.htmlfile)
6035 testruns, stamp = processData()
6036 sysvals.resetlog()
6037 return stamp
6038
6039# Function: runTest
6040# Description:
6041# execute a suspend/resume, gather the logs, and generate the output
6042def runTest(n=0, quiet=False):
6043 # prepare for the test
6044 sysvals.initTestOutput('suspend')
6045 op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6046 op.write('# EXECUTION TRACE START\n')
6047 op.close()
6048 if n <= 1:
6049 if sysvals.rs != 0:
6050 sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6051 sysvals.setRuntimeSuspend(True)
6052 if sysvals.display:
6053 ret = sysvals.displayControl('init')
6054 sysvals.dlog('xset display init, ret = %d' % ret)
6055 sysvals.dlog('initialize ftrace')
6056 sysvals.initFtrace(quiet)
6057
6058 # execute the test
6059 executeSuspend(quiet)
6060 sysvals.cleanupFtrace()
6061 if sysvals.skiphtml:
6062 sysvals.outputResult({}, n)
6063 sysvals.sudoUserchown(sysvals.testdir)
6064 return
6065 testruns, stamp = processData(True, quiet)
6066 for data in testruns:
6067 del data
6068 sysvals.sudoUserchown(sysvals.testdir)
6069 sysvals.outputResult(stamp, n)
6070 if 'error' in stamp:
6071 return 2
6072 return 0
6073
6074def find_in_html(html, start, end, firstonly=True):
6075 cnt, out, list = len(html), [], []
6076 if firstonly:
6077 m = re.search(start, html)
6078 if m:
6079 list.append(m)
6080 else:
6081 list = re.finditer(start, html)
6082 for match in list:
6083 s = match.end()
6084 e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6085 m = re.search(end, html[s:e])
6086 if not m:
6087 break
6088 e = s + m.start()
6089 str = html[s:e]
6090 if end == 'ms':
6091 num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6092 str = num.group() if num else 'NaN'
6093 if firstonly:
6094 return str
6095 out.append(str)
6096 if firstonly:
6097 return ''
6098 return out
6099
6100def data_from_html(file, outpath, issues, fulldetail=False):
6101 html = open(file, 'r').read()
6102 sysvals.htmlfile = os.path.relpath(file, outpath)
6103 # extract general info
6104 suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6105 resume = find_in_html(html, 'Kernel Resume', 'ms')
6106 sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6107 line = find_in_html(html, '<div class="stamp">', '</div>')
6108 stmp = line.split()
6109 if not suspend or not resume or len(stmp) != 8:
6110 return False
6111 try:
6112 dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6113 except:
6114 return False
6115 sysvals.hostname = stmp[0]
6116 tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6117 error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6118 if error:
6119 m = re.match('[a-z0-9]* failed in (?P<p>\S*).*', error)
6120 if m:
6121 result = 'fail in %s' % m.group('p')
6122 else:
6123 result = 'fail'
6124 else:
6125 result = 'pass'
6126 # extract error info
6127 tp, ilist = False, []
6128 extra = dict()
6129 log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6130 '</div>').strip()
6131 if log:
6132 d = Data(0)
6133 d.end = 999999999
6134 d.dmesgtext = log.split('\n')
6135 tp = d.extractErrorInfo()
6136 for msg in tp.msglist:
6137 sysvals.errorSummary(issues, msg)
6138 if stmp[2] == 'freeze':
6139 extra = d.turbostatInfo()
6140 elist = dict()
6141 for dir in d.errorinfo:
6142 for err in d.errorinfo[dir]:
6143 if err[0] not in elist:
6144 elist[err[0]] = 0
6145 elist[err[0]] += 1
6146 for i in elist:
6147 ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6148 wifi = find_in_html(html, 'Wifi Resume: ', '</td>')
6149 if wifi:
6150 extra['wifi'] = wifi
6151 low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6152 for lowstr in ['waking', '+']:
6153 if not low:
6154 break
6155 if lowstr not in low:
6156 continue
6157 if lowstr == '+':
6158 issue = 'S2LOOPx%d' % len(low.split('+'))
6159 else:
6160 m = re.match('.*waking *(?P<n>[0-9]*) *times.*', low)
6161 issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6162 match = [i for i in issues if i['match'] == issue]
6163 if len(match) > 0:
6164 match[0]['count'] += 1
6165 if sysvals.hostname not in match[0]['urls']:
6166 match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6167 elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6168 match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6169 else:
6170 issues.append({
6171 'match': issue, 'count': 1, 'line': issue,
6172 'urls': {sysvals.hostname: [sysvals.htmlfile]},
6173 })
6174 ilist.append(issue)
6175 # extract device info
6176 devices = dict()
6177 for line in html.split('\n'):
6178 m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6179 if not m or 'thread kth' in line or 'thread sec' in line:
6180 continue
6181 m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6182 if not m:
6183 continue
6184 name, time, phase = m.group('n'), m.group('t'), m.group('p')
6185 if ' async' in name or ' sync' in name:
6186 name = ' '.join(name.split(' ')[:-1])
6187 if phase.startswith('suspend'):
6188 d = 'suspend'
6189 elif phase.startswith('resume'):
6190 d = 'resume'
6191 else:
6192 continue
6193 if d not in devices:
6194 devices[d] = dict()
6195 if name not in devices[d]:
6196 devices[d][name] = 0.0
6197 devices[d][name] += float(time)
6198 # create worst device info
6199 worst = dict()
6200 for d in ['suspend', 'resume']:
6201 worst[d] = {'name':'', 'time': 0.0}
6202 dev = devices[d] if d in devices else 0
6203 if dev and len(dev.keys()) > 0:
6204 n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6205 worst[d]['name'], worst[d]['time'] = n, dev[n]
6206 data = {
6207 'mode': stmp[2],
6208 'host': stmp[0],
6209 'kernel': stmp[1],
6210 'sysinfo': sysinfo,
6211 'time': tstr,
6212 'result': result,
6213 'issues': ' '.join(ilist),
6214 'suspend': suspend,
6215 'resume': resume,
6216 'devlist': devices,
6217 'sus_worst': worst['suspend']['name'],
6218 'sus_worsttime': worst['suspend']['time'],
6219 'res_worst': worst['resume']['name'],
6220 'res_worsttime': worst['resume']['time'],
6221 'url': sysvals.htmlfile,
6222 }
6223 for key in extra:
6224 data[key] = extra[key]
6225 if fulldetail:
6226 data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6227 if tp:
6228 for arg in ['-multi ', '-info ']:
6229 if arg in tp.cmdline:
6230 data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6231 break
6232 return data
6233
6234def genHtml(subdir, force=False):
6235 for dirname, dirnames, filenames in os.walk(subdir):
6236 sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6237 for filename in filenames:
6238 file = os.path.join(dirname, filename)
6239 if sysvals.usable(file):
6240 if(re.match('.*_dmesg.txt', filename)):
6241 sysvals.dmesgfile = file
6242 elif(re.match('.*_ftrace.txt', filename)):
6243 sysvals.ftracefile = file
6244 sysvals.setOutputFile()
6245 if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6246 (force or not sysvals.usable(sysvals.htmlfile)):
6247 pprint('FTRACE: %s' % sysvals.ftracefile)
6248 if sysvals.dmesgfile:
6249 pprint('DMESG : %s' % sysvals.dmesgfile)
6250 rerunTest()
6251
6252# Function: runSummary
6253# Description:
6254# create a summary of tests in a sub-directory
6255def runSummary(subdir, local=True, genhtml=False):
6256 inpath = os.path.abspath(subdir)
6257 outpath = os.path.abspath('.') if local else inpath
6258 pprint('Generating a summary of folder:\n %s' % inpath)
6259 if genhtml:
6260 genHtml(subdir)
6261 target, issues, testruns = '', [], []
6262 desc = {'host':[],'mode':[],'kernel':[]}
6263 for dirname, dirnames, filenames in os.walk(subdir):
6264 for filename in filenames:
6265 if(not re.match('.*.html', filename)):
6266 continue
6267 data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6268 if(not data):
6269 continue
6270 if 'target' in data:
6271 target = data['target']
6272 testruns.append(data)
6273 for key in desc:
6274 if data[key] not in desc[key]:
6275 desc[key].append(data[key])
6276 pprint('Summary files:')
6277 if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6278 title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6279 if target:
6280 title += ' %s' % target
6281 else:
6282 title = inpath
6283 createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6284 pprint(' summary.html - tabular list of test data found')
6285 createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6286 pprint(' summary-devices.html - kernel device list sorted by total execution time')
6287 createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6288 pprint(' summary-issues.html - kernel issues found sorted by frequency')
6289
6290# Function: checkArgBool
6291# Description:
6292# check if a boolean string value is true or false
6293def checkArgBool(name, value):
6294 if value in switchvalues:
6295 if value in switchoff:
6296 return False
6297 return True
6298 doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6299 return False
6300
6301# Function: configFromFile
6302# Description:
6303# Configure the script via the info in a config file
6304def configFromFile(file):
6305 Config = configparser.ConfigParser()
6306
6307 Config.read(file)
6308 sections = Config.sections()
6309 overridekprobes = False
6310 overridedevkprobes = False
6311 if 'Settings' in sections:
6312 for opt in Config.options('Settings'):
6313 value = Config.get('Settings', opt).lower()
6314 option = opt.lower()
6315 if(option == 'verbose'):
6316 sysvals.verbose = checkArgBool(option, value)
6317 elif(option == 'addlogs'):
6318 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6319 elif(option == 'dev'):
6320 sysvals.usedevsrc = checkArgBool(option, value)
6321 elif(option == 'proc'):
6322 sysvals.useprocmon = checkArgBool(option, value)
6323 elif(option == 'x2'):
6324 if checkArgBool(option, value):
6325 sysvals.execcount = 2
6326 elif(option == 'callgraph'):
6327 sysvals.usecallgraph = checkArgBool(option, value)
6328 elif(option == 'override-timeline-functions'):
6329 overridekprobes = checkArgBool(option, value)
6330 elif(option == 'override-dev-timeline-functions'):
6331 overridedevkprobes = checkArgBool(option, value)
6332 elif(option == 'skiphtml'):
6333 sysvals.skiphtml = checkArgBool(option, value)
6334 elif(option == 'sync'):
6335 sysvals.sync = checkArgBool(option, value)
6336 elif(option == 'rs' or option == 'runtimesuspend'):
6337 if value in switchvalues:
6338 if value in switchoff:
6339 sysvals.rs = -1
6340 else:
6341 sysvals.rs = 1
6342 else:
6343 doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6344 elif(option == 'display'):
6345 disopt = ['on', 'off', 'standby', 'suspend']
6346 if value not in disopt:
6347 doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6348 sysvals.display = value
6349 elif(option == 'gzip'):
6350 sysvals.gzip = checkArgBool(option, value)
6351 elif(option == 'cgfilter'):
6352 sysvals.setCallgraphFilter(value)
6353 elif(option == 'cgskip'):
6354 if value in switchoff:
6355 sysvals.cgskip = ''
6356 else:
6357 sysvals.cgskip = sysvals.configFile(val)
6358 if(not sysvals.cgskip):
6359 doError('%s does not exist' % sysvals.cgskip)
6360 elif(option == 'cgtest'):
6361 sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6362 elif(option == 'cgphase'):
6363 d = Data(0)
6364 if value not in d.phasedef:
6365 doError('invalid phase --> (%s: %s), valid phases are %s'\
6366 % (option, value, d.phasedef.keys()), True)
6367 sysvals.cgphase = value
6368 elif(option == 'fadd'):
6369 file = sysvals.configFile(value)
6370 if(not file):
6371 doError('%s does not exist' % value)
6372 sysvals.addFtraceFilterFunctions(file)
6373 elif(option == 'result'):
6374 sysvals.result = value
6375 elif(option == 'multi'):
6376 nums = value.split()
6377 if len(nums) != 2:
6378 doError('multi requires 2 integers (exec_count and delay)', True)
6379 sysvals.multiinit(nums[0], nums[1])
6380 elif(option == 'devicefilter'):
6381 sysvals.setDeviceFilter(value)
6382 elif(option == 'expandcg'):
6383 sysvals.cgexp = checkArgBool(option, value)
6384 elif(option == 'srgap'):
6385 if checkArgBool(option, value):
6386 sysvals.srgap = 5
6387 elif(option == 'mode'):
6388 sysvals.suspendmode = value
6389 elif(option == 'command' or option == 'cmd'):
6390 sysvals.testcommand = value
6391 elif(option == 'x2delay'):
6392 sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6393 elif(option == 'predelay'):
6394 sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6395 elif(option == 'postdelay'):
6396 sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6397 elif(option == 'maxdepth'):
6398 sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6399 elif(option == 'rtcwake'):
6400 if value in switchoff:
6401 sysvals.rtcwake = False
6402 else:
6403 sysvals.rtcwake = True
6404 sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6405 elif(option == 'timeprec'):
6406 sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6407 elif(option == 'mindev'):
6408 sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6409 elif(option == 'callloop-maxgap'):
6410 sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6411 elif(option == 'callloop-maxlen'):
6412 sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6413 elif(option == 'mincg'):
6414 sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6415 elif(option == 'bufsize'):
6416 sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6417 elif(option == 'output-dir'):
6418 sysvals.outdir = sysvals.setOutputFolder(value)
6419
6420 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6421 doError('No command supplied for mode "command"')
6422
6423 # compatibility errors
6424 if sysvals.usedevsrc and sysvals.usecallgraph:
6425 doError('-dev is not compatible with -f')
6426 if sysvals.usecallgraph and sysvals.useprocmon:
6427 doError('-proc is not compatible with -f')
6428
6429 if overridekprobes:
6430 sysvals.tracefuncs = dict()
6431 if overridedevkprobes:
6432 sysvals.dev_tracefuncs = dict()
6433
6434 kprobes = dict()
6435 kprobesec = 'dev_timeline_functions_'+platform.machine()
6436 if kprobesec in sections:
6437 for name in Config.options(kprobesec):
6438 text = Config.get(kprobesec, name)
6439 kprobes[name] = (text, True)
6440 kprobesec = 'timeline_functions_'+platform.machine()
6441 if kprobesec in sections:
6442 for name in Config.options(kprobesec):
6443 if name in kprobes:
6444 doError('Duplicate timeline function found "%s"' % (name))
6445 text = Config.get(kprobesec, name)
6446 kprobes[name] = (text, False)
6447
6448 for name in kprobes:
6449 function = name
6450 format = name
6451 color = ''
6452 args = dict()
6453 text, dev = kprobes[name]
6454 data = text.split()
6455 i = 0
6456 for val in data:
6457 # bracketted strings are special formatting, read them separately
6458 if val[0] == '[' and val[-1] == ']':
6459 for prop in val[1:-1].split(','):
6460 p = prop.split('=')
6461 if p[0] == 'color':
6462 try:
6463 color = int(p[1], 16)
6464 color = '#'+p[1]
6465 except:
6466 color = p[1]
6467 continue
6468 # first real arg should be the format string
6469 if i == 0:
6470 format = val
6471 # all other args are actual function args
6472 else:
6473 d = val.split('=')
6474 args[d[0]] = d[1]
6475 i += 1
6476 if not function or not format:
6477 doError('Invalid kprobe: %s' % name)
6478 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6479 if arg not in args:
6480 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6481 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6482 doError('Duplicate timeline function found "%s"' % (name))
6483
6484 kp = {
6485 'name': name,
6486 'func': function,
6487 'format': format,
6488 sysvals.archargs: args
6489 }
6490 if color:
6491 kp['color'] = color
6492 if dev:
6493 sysvals.dev_tracefuncs[name] = kp
6494 else:
6495 sysvals.tracefuncs[name] = kp
6496
6497# Function: printHelp
6498# Description:
6499# print out the help text
6500def printHelp():
6501 pprint('\n%s v%s\n'\
6502 'Usage: sudo sleepgraph <options> <commands>\n'\
6503 '\n'\
6504 'Description:\n'\
6505 ' This tool is designed to assist kernel and OS developers in optimizing\n'\
6506 ' their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6507 ' with a few extra options enabled, the tool will execute a suspend and\n'\
6508 ' capture dmesg and ftrace data until resume is complete. This data is\n'\
6509 ' transformed into a device timeline and an optional callgraph to give\n'\
6510 ' a detailed view of which devices/subsystems are taking the most\n'\
6511 ' time in suspend/resume.\n'\
6512 '\n'\
6513 ' If no specific command is given, the default behavior is to initiate\n'\
6514 ' a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6515 '\n'\
6516 ' Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6517 ' HTML output: <hostname>_<mode>.html\n'\
6518 ' raw dmesg output: <hostname>_<mode>_dmesg.txt\n'\
6519 ' raw ftrace output: <hostname>_<mode>_ftrace.txt\n'\
6520 '\n'\
6521 'Options:\n'\
6522 ' -h Print this help text\n'\
6523 ' -v Print the current tool version\n'\
6524 ' -config fn Pull arguments and config options from file fn\n'\
6525 ' -verbose Print extra information during execution and analysis\n'\
6526 ' -m mode Mode to initiate for suspend (default: %s)\n'\
6527 ' -o name Overrides the output subdirectory name when running a new test\n'\
6528 ' default: suspend-{date}-{time}\n'\
6529 ' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6530 ' -addlogs Add the dmesg and ftrace logs to the html output\n'\
6531 ' -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6532 ' -srgap Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6533 ' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6534 ' -result fn Export a results table to a text file for parsing.\n'\
6535 ' -wifi If a wifi connection is available, check that it reconnects after resume.\n'\
6536 ' [testprep]\n'\
6537 ' -sync Sync the filesystems before starting the test\n'\
6538 ' -rs on/off Enable/disable runtime suspend for all devices, restore all after test\n'\
6539 ' -display m Change the display mode to m for the test (on/off/standby/suspend)\n'\
6540 ' [advanced]\n'\
6541 ' -gzip Gzip the trace and dmesg logs to save space\n'\
6542 ' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"\n'\
6543 ' -proc Add usermode process info into the timeline (default: disabled)\n'\
6544 ' -dev Add kernel function calls and threads to the timeline (default: disabled)\n'\
6545 ' -x2 Run two suspend/resumes back to back (default: disabled)\n'\
6546 ' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)\n'\
6547 ' -predelay t Include t ms delay before 1st suspend (default: 0 ms)\n'\
6548 ' -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6549 ' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6550 ' -multi n d Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6551 ' by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6552 ' The outputs will be created in a new subdirectory with a summary page.\n'\
6553 ' -maxfail n Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6554 ' [debug]\n'\
6555 ' -f Use ftrace to create device callgraphs (default: disabled)\n'\
6556 ' -ftop Use ftrace on the top level call: "%s" (default: disabled)\n'\
6557 ' -maxdepth N limit the callgraph data to N call levels (default: 0=all)\n'\
6558 ' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\
6559 ' -fadd file Add functions to be graphed in the timeline from a list in a text file\n'\
6560 ' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6561 ' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6562 ' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)\n'\
6563 ' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6564 ' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6565 ' -cgfilter S Filter the callgraph output in the timeline\n'\
6566 ' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6567 ' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6568 ' -devdump Print out all the raw device data for each phase\n'\
6569 ' -cgdump Print out all the raw callgraph data\n'\
6570 '\n'\
6571 'Other commands:\n'\
6572 ' -modes List available suspend modes\n'\
6573 ' -status Test to see if the system is enabled to run this tool\n'\
6574 ' -fpdt Print out the contents of the ACPI Firmware Performance Data Table\n'\
6575 ' -wificheck Print out wifi connection info\n'\
6576 ' -x<mode> Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6577 ' -sysinfo Print out system info extracted from BIOS\n'\
6578 ' -devinfo Print out the pm settings of all devices which support runtime suspend\n'\
6579 ' -cmdinfo Print out all the platform info collected before and after suspend/resume\n'\
6580 ' -flist Print the list of functions currently being captured in ftrace\n'\
6581 ' -flistall Print all functions capable of being captured in ftrace\n'\
6582 ' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6583 ' [redo]\n'\
6584 ' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)\n'\
6585 ' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)\n'\
6586 '' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6587 return True
6588
6589# ----------------- MAIN --------------------
6590# exec start (skipped if script is loaded as library)
6591if __name__ == '__main__':
6592 genhtml = False
6593 cmd = ''
6594 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6595 '-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6596 '-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6597 if '-f' in sys.argv:
6598 sysvals.cgskip = sysvals.configFile('cgskip.txt')
6599 # loop through the command line arguments
6600 args = iter(sys.argv[1:])
6601 for arg in args:
6602 if(arg == '-m'):
6603 try:
6604 val = next(args)
6605 except:
6606 doError('No mode supplied', True)
6607 if val == 'command' and not sysvals.testcommand:
6608 doError('No command supplied for mode "command"', True)
6609 sysvals.suspendmode = val
6610 elif(arg in simplecmds):
6611 cmd = arg[1:]
6612 elif(arg == '-h'):
6613 printHelp()
6614 sys.exit(0)
6615 elif(arg == '-v'):
6616 pprint("Version %s" % sysvals.version)
6617 sys.exit(0)
6618 elif(arg == '-x2'):
6619 sysvals.execcount = 2
6620 elif(arg == '-x2delay'):
6621 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6622 elif(arg == '-predelay'):
6623 sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6624 elif(arg == '-postdelay'):
6625 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6626 elif(arg == '-f'):
6627 sysvals.usecallgraph = True
6628 elif(arg == '-ftop'):
6629 sysvals.usecallgraph = True
6630 sysvals.ftop = True
6631 sysvals.usekprobes = False
6632 elif(arg == '-skiphtml'):
6633 sysvals.skiphtml = True
6634 elif(arg == '-cgdump'):
6635 sysvals.cgdump = True
6636 elif(arg == '-devdump'):
6637 sysvals.devdump = True
6638 elif(arg == '-genhtml'):
6639 genhtml = True
6640 elif(arg == '-addlogs'):
6641 sysvals.dmesglog = sysvals.ftracelog = True
6642 elif(arg == '-nologs'):
6643 sysvals.dmesglog = sysvals.ftracelog = False
6644 elif(arg == '-addlogdmesg'):
6645 sysvals.dmesglog = True
6646 elif(arg == '-addlogftrace'):
6647 sysvals.ftracelog = True
6648 elif(arg == '-noturbostat'):
6649 sysvals.tstat = False
6650 elif(arg == '-verbose'):
6651 sysvals.verbose = True
6652 elif(arg == '-proc'):
6653 sysvals.useprocmon = True
6654 elif(arg == '-dev'):
6655 sysvals.usedevsrc = True
6656 elif(arg == '-sync'):
6657 sysvals.sync = True
6658 elif(arg == '-wifi'):
6659 sysvals.wifi = True
6660 elif(arg == '-gzip'):
6661 sysvals.gzip = True
6662 elif(arg == '-info'):
6663 try:
6664 val = next(args)
6665 except:
6666 doError('-info requires one string argument', True)
6667 elif(arg == '-desc'):
6668 try:
6669 val = next(args)
6670 except:
6671 doError('-desc requires one string argument', True)
6672 elif(arg == '-rs'):
6673 try:
6674 val = next(args)
6675 except:
6676 doError('-rs requires "enable" or "disable"', True)
6677 if val.lower() in switchvalues:
6678 if val.lower() in switchoff:
6679 sysvals.rs = -1
6680 else:
6681 sysvals.rs = 1
6682 else:
6683 doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6684 elif(arg == '-display'):
6685 try:
6686 val = next(args)
6687 except:
6688 doError('-display requires an mode value', True)
6689 disopt = ['on', 'off', 'standby', 'suspend']
6690 if val.lower() not in disopt:
6691 doError('valid display mode values are %s' % disopt, True)
6692 sysvals.display = val.lower()
6693 elif(arg == '-maxdepth'):
6694 sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6695 elif(arg == '-rtcwake'):
6696 try:
6697 val = next(args)
6698 except:
6699 doError('No rtcwake time supplied', True)
6700 if val.lower() in switchoff:
6701 sysvals.rtcwake = False
6702 else:
6703 sysvals.rtcwake = True
6704 sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6705 elif(arg == '-timeprec'):
6706 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6707 elif(arg == '-mindev'):
6708 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6709 elif(arg == '-mincg'):
6710 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6711 elif(arg == '-bufsize'):
6712 sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6713 elif(arg == '-cgtest'):
6714 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6715 elif(arg == '-cgphase'):
6716 try:
6717 val = next(args)
6718 except:
6719 doError('No phase name supplied', True)
6720 d = Data(0)
6721 if val not in d.phasedef:
6722 doError('invalid phase --> (%s: %s), valid phases are %s'\
6723 % (arg, val, d.phasedef.keys()), True)
6724 sysvals.cgphase = val
6725 elif(arg == '-cgfilter'):
6726 try:
6727 val = next(args)
6728 except:
6729 doError('No callgraph functions supplied', True)
6730 sysvals.setCallgraphFilter(val)
6731 elif(arg == '-skipkprobe'):
6732 try:
6733 val = next(args)
6734 except:
6735 doError('No kprobe functions supplied', True)
6736 sysvals.skipKprobes(val)
6737 elif(arg == '-cgskip'):
6738 try:
6739 val = next(args)
6740 except:
6741 doError('No file supplied', True)
6742 if val.lower() in switchoff:
6743 sysvals.cgskip = ''
6744 else:
6745 sysvals.cgskip = sysvals.configFile(val)
6746 if(not sysvals.cgskip):
6747 doError('%s does not exist' % sysvals.cgskip)
6748 elif(arg == '-callloop-maxgap'):
6749 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6750 elif(arg == '-callloop-maxlen'):
6751 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6752 elif(arg == '-cmd'):
6753 try:
6754 val = next(args)
6755 except:
6756 doError('No command string supplied', True)
6757 sysvals.testcommand = val
6758 sysvals.suspendmode = 'command'
6759 elif(arg == '-expandcg'):
6760 sysvals.cgexp = True
6761 elif(arg == '-srgap'):
6762 sysvals.srgap = 5
6763 elif(arg == '-maxfail'):
6764 sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
6765 elif(arg == '-multi'):
6766 try:
6767 c, d = next(args), next(args)
6768 except:
6769 doError('-multi requires two values', True)
6770 sysvals.multiinit(c, d)
6771 elif(arg == '-o'):
6772 try:
6773 val = next(args)
6774 except:
6775 doError('No subdirectory name supplied', True)
6776 sysvals.outdir = sysvals.setOutputFolder(val)
6777 elif(arg == '-config'):
6778 try:
6779 val = next(args)
6780 except:
6781 doError('No text file supplied', True)
6782 file = sysvals.configFile(val)
6783 if(not file):
6784 doError('%s does not exist' % val)
6785 configFromFile(file)
6786 elif(arg == '-fadd'):
6787 try:
6788 val = next(args)
6789 except:
6790 doError('No text file supplied', True)
6791 file = sysvals.configFile(val)
6792 if(not file):
6793 doError('%s does not exist' % val)
6794 sysvals.addFtraceFilterFunctions(file)
6795 elif(arg == '-dmesg'):
6796 try:
6797 val = next(args)
6798 except:
6799 doError('No dmesg file supplied', True)
6800 sysvals.notestrun = True
6801 sysvals.dmesgfile = val
6802 if(os.path.exists(sysvals.dmesgfile) == False):
6803 doError('%s does not exist' % sysvals.dmesgfile)
6804 elif(arg == '-ftrace'):
6805 try:
6806 val = next(args)
6807 except:
6808 doError('No ftrace file supplied', True)
6809 sysvals.notestrun = True
6810 sysvals.ftracefile = val
6811 if(os.path.exists(sysvals.ftracefile) == False):
6812 doError('%s does not exist' % sysvals.ftracefile)
6813 elif(arg == '-summary'):
6814 try:
6815 val = next(args)
6816 except:
6817 doError('No directory supplied', True)
6818 cmd = 'summary'
6819 sysvals.outdir = val
6820 sysvals.notestrun = True
6821 if(os.path.isdir(val) == False):
6822 doError('%s is not accessible' % val)
6823 elif(arg == '-filter'):
6824 try:
6825 val = next(args)
6826 except:
6827 doError('No devnames supplied', True)
6828 sysvals.setDeviceFilter(val)
6829 elif(arg == '-result'):
6830 try:
6831 val = next(args)
6832 except:
6833 doError('No result file supplied', True)
6834 sysvals.result = val
6835 sysvals.signalHandlerInit()
6836 else:
6837 doError('Invalid argument: '+arg, True)
6838
6839 # compatibility errors
6840 if(sysvals.usecallgraph and sysvals.usedevsrc):
6841 doError('-dev is not compatible with -f')
6842 if(sysvals.usecallgraph and sysvals.useprocmon):
6843 doError('-proc is not compatible with -f')
6844
6845 if sysvals.usecallgraph and sysvals.cgskip:
6846 sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
6847 sysvals.setCallgraphBlacklist(sysvals.cgskip)
6848
6849 # callgraph size cannot exceed device size
6850 if sysvals.mincglen < sysvals.mindevlen:
6851 sysvals.mincglen = sysvals.mindevlen
6852
6853 # remove existing buffers before calculating memory
6854 if(sysvals.usecallgraph or sysvals.usedevsrc):
6855 sysvals.fsetVal('16', 'buffer_size_kb')
6856 sysvals.cpuInfo()
6857
6858 # just run a utility command and exit
6859 if(cmd != ''):
6860 ret = 0
6861 if(cmd == 'status'):
6862 if not statusCheck(True):
6863 ret = 1
6864 elif(cmd == 'fpdt'):
6865 if not getFPDT(True):
6866 ret = 1
6867 elif(cmd == 'sysinfo'):
6868 sysvals.printSystemInfo(True)
6869 elif(cmd == 'devinfo'):
6870 deviceInfo()
6871 elif(cmd == 'modes'):
6872 pprint(getModes())
6873 elif(cmd == 'flist'):
6874 sysvals.getFtraceFilterFunctions(True)
6875 elif(cmd == 'flistall'):
6876 sysvals.getFtraceFilterFunctions(False)
6877 elif(cmd == 'summary'):
6878 runSummary(sysvals.outdir, True, genhtml)
6879 elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
6880 sysvals.verbose = True
6881 ret = sysvals.displayControl(cmd[1:])
6882 elif(cmd == 'xstat'):
6883 pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
6884 elif(cmd == 'wificheck'):
6885 dev = sysvals.checkWifi()
6886 if dev:
6887 print('%s is connected' % sysvals.wifiDetails(dev))
6888 else:
6889 print('No wifi connection found')
6890 elif(cmd == 'cmdinfo'):
6891 for out in sysvals.cmdinfo(False, True):
6892 print('[%s - %s]\n%s\n' % out)
6893 sys.exit(ret)
6894
6895 # if instructed, re-analyze existing data files
6896 if(sysvals.notestrun):
6897 stamp = rerunTest(sysvals.outdir)
6898 sysvals.outputResult(stamp)
6899 sys.exit(0)
6900
6901 # verify that we can run a test
6902 error = statusCheck()
6903 if(error):
6904 doError(error)
6905
6906 # extract mem/disk extra modes and convert
6907 mode = sysvals.suspendmode
6908 if mode.startswith('mem'):
6909 memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
6910 if memmode == 'shallow':
6911 mode = 'standby'
6912 elif memmode == 's2idle':
6913 mode = 'freeze'
6914 else:
6915 mode = 'mem'
6916 sysvals.memmode = memmode
6917 sysvals.suspendmode = mode
6918 if mode.startswith('disk-'):
6919 sysvals.diskmode = mode.split('-', 1)[-1]
6920 sysvals.suspendmode = 'disk'
6921 sysvals.systemInfo(dmidecode(sysvals.mempath))
6922
6923 failcnt, ret = 0, 0
6924 if sysvals.multitest['run']:
6925 # run multiple tests in a separate subdirectory
6926 if not sysvals.outdir:
6927 if 'time' in sysvals.multitest:
6928 s = '-%dm' % sysvals.multitest['time']
6929 else:
6930 s = '-x%d' % sysvals.multitest['count']
6931 sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
6932 if not os.path.isdir(sysvals.outdir):
6933 os.makedirs(sysvals.outdir)
6934 sysvals.sudoUserchown(sysvals.outdir)
6935 finish = datetime.now()
6936 if 'time' in sysvals.multitest:
6937 finish += timedelta(minutes=sysvals.multitest['time'])
6938 for i in range(sysvals.multitest['count']):
6939 sysvals.multistat(True, i, finish)
6940 if i != 0 and sysvals.multitest['delay'] > 0:
6941 pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
6942 time.sleep(sysvals.multitest['delay'])
6943 fmt = 'suspend-%y%m%d-%H%M%S'
6944 sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
6945 ret = runTest(i+1, True)
6946 failcnt = 0 if not ret else failcnt + 1
6947 if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
6948 pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
6949 break
6950 time.sleep(5)
6951 sysvals.resetlog()
6952 sysvals.multistat(False, i, finish)
6953 if 'time' in sysvals.multitest and datetime.now() >= finish:
6954 break
6955 if not sysvals.skiphtml:
6956 runSummary(sysvals.outdir, False, False)
6957 sysvals.sudoUserchown(sysvals.outdir)
6958 else:
6959 if sysvals.outdir:
6960 sysvals.testdir = sysvals.outdir
6961 # run the test in the current directory
6962 ret = runTest()
6963
6964 # reset to default values after testing
6965 if sysvals.display:
6966 sysvals.displayControl('reset')
6967 if sysvals.rs != 0:
6968 sysvals.setRuntimeSuspend(False)
6969 sys.exit(ret)
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.11'
90 ansi = False
91 rs = 0
92 display = ''
93 gzip = False
94 sync = False
95 wifi = False
96 netfix = False
97 verbose = False
98 testlog = True
99 dmesglog = True
100 ftracelog = False
101 acpidebug = True
102 tstat = True
103 wifitrace = False
104 mindevlen = 0.0001
105 mincglen = 0.0
106 cgphase = ''
107 cgtest = -1
108 cgskip = ''
109 maxfail = 0
110 multitest = {'run': False, 'count': 1000000, 'delay': 0}
111 max_graph_depth = 0
112 callloopmaxgap = 0.0001
113 callloopmaxlen = 0.005
114 bufsize = 0
115 cpucount = 0
116 memtotal = 204800
117 memfree = 204800
118 osversion = ''
119 srgap = 0
120 cgexp = False
121 testdir = ''
122 outdir = ''
123 tpath = '/sys/kernel/tracing/'
124 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
125 epath = '/sys/kernel/tracing/events/power/'
126 pmdpath = '/sys/power/pm_debug_messages'
127 s0ixpath = '/sys/module/intel_pmc_core/parameters/warn_on_s0ix_failures'
128 s0ixres = '/sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us'
129 acpipath='/sys/module/acpi/parameters/debug_level'
130 traceevents = [
131 'suspend_resume',
132 'wakeup_source_activate',
133 'wakeup_source_deactivate',
134 'device_pm_callback_end',
135 'device_pm_callback_start'
136 ]
137 logmsg = ''
138 testcommand = ''
139 mempath = '/dev/mem'
140 powerfile = '/sys/power/state'
141 mempowerfile = '/sys/power/mem_sleep'
142 diskpowerfile = '/sys/power/disk'
143 suspendmode = 'mem'
144 memmode = ''
145 diskmode = ''
146 hostname = 'localhost'
147 prefix = 'test'
148 teststamp = ''
149 sysstamp = ''
150 dmesgstart = 0.0
151 dmesgfile = ''
152 ftracefile = ''
153 htmlfile = 'output.html'
154 result = ''
155 rtcwake = True
156 rtcwaketime = 15
157 rtcpath = ''
158 devicefilter = []
159 cgfilter = []
160 stamp = 0
161 execcount = 1
162 x2delay = 0
163 skiphtml = False
164 usecallgraph = False
165 ftopfunc = 'pm_suspend'
166 ftop = False
167 usetraceevents = False
168 usetracemarkers = True
169 useftrace = True
170 usekprobes = True
171 usedevsrc = False
172 useprocmon = False
173 notestrun = False
174 cgdump = False
175 devdump = False
176 mixedphaseheight = True
177 devprops = dict()
178 cfgdef = dict()
179 platinfo = []
180 predelay = 0
181 postdelay = 0
182 tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
183 tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
184 tracefuncs = {
185 'async_synchronize_full': {},
186 'sys_sync': {},
187 'ksys_sync': {},
188 '__pm_notifier_call_chain': {},
189 'pm_prepare_console': {},
190 'pm_notifier_call_chain': {},
191 'freeze_processes': {},
192 'freeze_kernel_threads': {},
193 'pm_restrict_gfp_mask': {},
194 'acpi_suspend_begin': {},
195 'acpi_hibernation_begin': {},
196 'acpi_hibernation_enter': {},
197 'acpi_hibernation_leave': {},
198 'acpi_pm_freeze': {},
199 'acpi_pm_thaw': {},
200 'acpi_s2idle_end': {},
201 'acpi_s2idle_sync': {},
202 'acpi_s2idle_begin': {},
203 'acpi_s2idle_prepare': {},
204 'acpi_s2idle_prepare_late': {},
205 'acpi_s2idle_wake': {},
206 'acpi_s2idle_wakeup': {},
207 'acpi_s2idle_restore': {},
208 'acpi_s2idle_restore_early': {},
209 'hibernate_preallocate_memory': {},
210 'create_basic_memory_bitmaps': {},
211 'swsusp_write': {},
212 'suspend_console': {},
213 'acpi_pm_prepare': {},
214 'syscore_suspend': {},
215 'arch_enable_nonboot_cpus_end': {},
216 'syscore_resume': {},
217 'acpi_pm_finish': {},
218 'resume_console': {},
219 'acpi_pm_end': {},
220 'pm_restore_gfp_mask': {},
221 'thaw_processes': {},
222 'pm_restore_console': {},
223 'CPU_OFF': {
224 'func':'_cpu_down',
225 'args_x86_64': {'cpu':'%di:s32'},
226 'format': 'CPU_OFF[{cpu}]'
227 },
228 'CPU_ON': {
229 'func':'_cpu_up',
230 'args_x86_64': {'cpu':'%di:s32'},
231 'format': 'CPU_ON[{cpu}]'
232 },
233 }
234 dev_tracefuncs = {
235 # general wait/delay/sleep
236 'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
237 'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
238 'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
239 'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
240 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
241 'acpi_os_stall': {'ub': 1},
242 'rt_mutex_slowlock': {'ub': 1},
243 # ACPI
244 'acpi_resume_power_resources': {},
245 'acpi_ps_execute_method': { 'args_x86_64': {
246 'fullpath':'+0(+40(%di)):string',
247 }},
248 # mei_me
249 'mei_reset': {},
250 # filesystem
251 'ext4_sync_fs': {},
252 # 80211
253 'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
254 'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
255 'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
256 'iwlagn_mac_start': {},
257 'iwlagn_alloc_bcast_station': {},
258 'iwl_trans_pcie_start_hw': {},
259 'iwl_trans_pcie_start_fw': {},
260 'iwl_run_init_ucode': {},
261 'iwl_load_ucode_wait_alive': {},
262 'iwl_alive_start': {},
263 'iwlagn_mac_stop': {},
264 'iwlagn_mac_suspend': {},
265 'iwlagn_mac_resume': {},
266 'iwlagn_mac_add_interface': {},
267 'iwlagn_mac_remove_interface': {},
268 'iwlagn_mac_change_interface': {},
269 'iwlagn_mac_config': {},
270 'iwlagn_configure_filter': {},
271 'iwlagn_mac_hw_scan': {},
272 'iwlagn_bss_info_changed': {},
273 'iwlagn_mac_channel_switch': {},
274 'iwlagn_mac_flush': {},
275 # ATA
276 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
277 # i915
278 'i915_gem_resume': {},
279 'i915_restore_state': {},
280 'intel_opregion_setup': {},
281 'g4x_pre_enable_dp': {},
282 'vlv_pre_enable_dp': {},
283 'chv_pre_enable_dp': {},
284 'g4x_enable_dp': {},
285 'vlv_enable_dp': {},
286 'intel_hpd_init': {},
287 'intel_opregion_register': {},
288 'intel_dp_detect': {},
289 'intel_hdmi_detect': {},
290 'intel_opregion_init': {},
291 'intel_fbdev_set_suspend': {},
292 }
293 infocmds = [
294 [0, 'sysinfo', 'uname', '-a'],
295 [0, 'cpuinfo', 'head', '-7', '/proc/cpuinfo'],
296 [0, 'kparams', 'cat', '/proc/cmdline'],
297 [0, 'mcelog', 'mcelog'],
298 [0, 'pcidevices', 'lspci', '-tv'],
299 [0, 'usbdevices', 'lsusb', '-tv'],
300 [0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'],
301 [0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'],
302 [0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'],
303 [0, 'ethtool', 'ethtool', '{ethdev}'],
304 [1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'],
305 [1, 'interrupts', 'cat', '/proc/interrupts'],
306 [1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
307 [2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
308 [2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
309 [2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
310 [2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
311 [2, 'thermal', 'sh', '-c', 'grep . /sys/class/thermal/thermal_zone*/temp'],
312 ]
313 cgblacklist = []
314 kprobes = dict()
315 timeformat = '%.3f'
316 cmdline = '%s %s' % \
317 (os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
318 sudouser = ''
319 def __init__(self):
320 self.archargs = 'args_'+platform.machine()
321 self.hostname = platform.node()
322 if(self.hostname == ''):
323 self.hostname = 'localhost'
324 rtc = "rtc0"
325 if os.path.exists('/dev/rtc'):
326 rtc = os.readlink('/dev/rtc')
327 rtc = '/sys/class/rtc/'+rtc
328 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
329 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
330 self.rtcpath = rtc
331 if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
332 self.ansi = True
333 self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
334 if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
335 os.environ['SUDO_USER']:
336 self.sudouser = os.environ['SUDO_USER']
337 def resetlog(self):
338 self.logmsg = ''
339 self.platinfo = []
340 def vprint(self, msg):
341 self.logmsg += msg+'\n'
342 if self.verbose or msg.startswith('WARNING:'):
343 pprint(msg)
344 def signalHandler(self, signum, frame):
345 if not self.result:
346 return
347 signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
348 msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
349 self.outputResult({'error':msg})
350 sys.exit(3)
351 def signalHandlerInit(self):
352 capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
353 'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM']
354 self.signames = dict()
355 for i in capture:
356 s = 'SIG'+i
357 try:
358 signum = getattr(signal, s)
359 signal.signal(signum, self.signalHandler)
360 except:
361 continue
362 self.signames[signum] = s
363 def rootCheck(self, fatal=True):
364 if(os.access(self.powerfile, os.W_OK)):
365 return True
366 if fatal:
367 msg = 'This command requires sysfs mount and root access'
368 pprint('ERROR: %s\n' % msg)
369 self.outputResult({'error':msg})
370 sys.exit(1)
371 return False
372 def rootUser(self, fatal=False):
373 if 'USER' in os.environ and os.environ['USER'] == 'root':
374 return True
375 if fatal:
376 msg = 'This command must be run as root'
377 pprint('ERROR: %s\n' % msg)
378 self.outputResult({'error':msg})
379 sys.exit(1)
380 return False
381 def usable(self, file, ishtml=False):
382 if not os.path.exists(file) or os.path.getsize(file) < 1:
383 return False
384 if ishtml:
385 try:
386 fp = open(file, 'r')
387 res = fp.read(1000)
388 fp.close()
389 except:
390 return False
391 if '<html>' not in res:
392 return False
393 return True
394 def getExec(self, cmd):
395 try:
396 fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
397 out = ascii(fp.read()).strip()
398 fp.close()
399 except:
400 out = ''
401 if out:
402 return out
403 for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
404 '/usr/local/sbin', '/usr/local/bin']:
405 cmdfull = os.path.join(path, cmd)
406 if os.path.exists(cmdfull):
407 return cmdfull
408 return out
409 def setPrecision(self, num):
410 if num < 0 or num > 6:
411 return
412 self.timeformat = '%.{0}f'.format(num)
413 def setOutputFolder(self, value):
414 args = dict()
415 n = datetime.now()
416 args['date'] = n.strftime('%y%m%d')
417 args['time'] = n.strftime('%H%M%S')
418 args['hostname'] = args['host'] = self.hostname
419 args['mode'] = self.suspendmode
420 return value.format(**args)
421 def setOutputFile(self):
422 if self.dmesgfile != '':
423 m = re.match('(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
424 if(m):
425 self.htmlfile = m.group('name')+'.html'
426 if self.ftracefile != '':
427 m = re.match('(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
428 if(m):
429 self.htmlfile = m.group('name')+'.html'
430 def systemInfo(self, info):
431 p = m = ''
432 if 'baseboard-manufacturer' in info:
433 m = info['baseboard-manufacturer']
434 elif 'system-manufacturer' in info:
435 m = info['system-manufacturer']
436 if 'system-product-name' in info:
437 p = info['system-product-name']
438 elif 'baseboard-product-name' in info:
439 p = info['baseboard-product-name']
440 if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
441 p = info['baseboard-product-name']
442 c = info['processor-version'] if 'processor-version' in info else ''
443 b = info['bios-version'] if 'bios-version' in info else ''
444 r = info['bios-release-date'] if 'bios-release-date' in info else ''
445 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
446 (m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
447 if self.osversion:
448 self.sysstamp += ' | os:%s' % self.osversion
449 def printSystemInfo(self, fatal=False):
450 self.rootCheck(True)
451 out = dmidecode(self.mempath, fatal)
452 if len(out) < 1:
453 return
454 fmt = '%-24s: %s'
455 if self.osversion:
456 print(fmt % ('os-version', self.osversion))
457 for name in sorted(out):
458 print(fmt % (name, out[name]))
459 print(fmt % ('cpucount', ('%d' % self.cpucount)))
460 print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
461 print(fmt % ('memfree', ('%d kB' % self.memfree)))
462 def cpuInfo(self):
463 self.cpucount = 0
464 if os.path.exists('/proc/cpuinfo'):
465 with open('/proc/cpuinfo', 'r') as fp:
466 for line in fp:
467 if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
468 self.cpucount += 1
469 if os.path.exists('/proc/meminfo'):
470 with open('/proc/meminfo', 'r') as fp:
471 for line in fp:
472 m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
473 if m:
474 self.memtotal = int(m.group('sz'))
475 m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
476 if m:
477 self.memfree = int(m.group('sz'))
478 if os.path.exists('/etc/os-release'):
479 with open('/etc/os-release', 'r') as fp:
480 for line in fp:
481 if line.startswith('PRETTY_NAME='):
482 self.osversion = line[12:].strip().replace('"', '')
483 def initTestOutput(self, name):
484 self.prefix = self.hostname
485 v = open('/proc/version', 'r').read().strip()
486 kver = v.split()[2]
487 fmt = name+'-%m%d%y-%H%M%S'
488 testtime = datetime.now().strftime(fmt)
489 self.teststamp = \
490 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
491 ext = ''
492 if self.gzip:
493 ext = '.gz'
494 self.dmesgfile = \
495 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
496 self.ftracefile = \
497 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
498 self.htmlfile = \
499 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
500 if not os.path.isdir(self.testdir):
501 os.makedirs(self.testdir)
502 self.sudoUserchown(self.testdir)
503 def getValueList(self, value):
504 out = []
505 for i in value.split(','):
506 if i.strip():
507 out.append(i.strip())
508 return out
509 def setDeviceFilter(self, value):
510 self.devicefilter = self.getValueList(value)
511 def setCallgraphFilter(self, value):
512 self.cgfilter = self.getValueList(value)
513 def skipKprobes(self, value):
514 for k in self.getValueList(value):
515 if k in self.tracefuncs:
516 del self.tracefuncs[k]
517 if k in self.dev_tracefuncs:
518 del self.dev_tracefuncs[k]
519 def setCallgraphBlacklist(self, file):
520 self.cgblacklist = self.listFromFile(file)
521 def rtcWakeAlarmOn(self):
522 call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
523 nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
524 if nowtime:
525 nowtime = int(nowtime)
526 else:
527 # if hardware time fails, use the software time
528 nowtime = int(datetime.now().strftime('%s'))
529 alarm = nowtime + self.rtcwaketime
530 call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
531 def rtcWakeAlarmOff(self):
532 call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
533 def initdmesg(self):
534 # get the latest time stamp from the dmesg log
535 lines = Popen('dmesg', stdout=PIPE).stdout.readlines()
536 ktime = '0'
537 for line in reversed(lines):
538 line = ascii(line).replace('\r\n', '')
539 idx = line.find('[')
540 if idx > 1:
541 line = line[idx:]
542 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
543 if(m):
544 ktime = m.group('ktime')
545 break
546 self.dmesgstart = float(ktime)
547 def getdmesg(self, testdata):
548 op = self.writeDatafileHeader(self.dmesgfile, testdata)
549 # store all new dmesg lines since initdmesg was called
550 fp = Popen('dmesg', stdout=PIPE).stdout
551 for line in fp:
552 line = ascii(line).replace('\r\n', '')
553 idx = line.find('[')
554 if idx > 1:
555 line = line[idx:]
556 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
557 if(not m):
558 continue
559 ktime = float(m.group('ktime'))
560 if ktime > self.dmesgstart:
561 op.write(line)
562 fp.close()
563 op.close()
564 def listFromFile(self, file):
565 list = []
566 fp = open(file)
567 for i in fp.read().split('\n'):
568 i = i.strip()
569 if i and i[0] != '#':
570 list.append(i)
571 fp.close()
572 return list
573 def addFtraceFilterFunctions(self, file):
574 for i in self.listFromFile(file):
575 if len(i) < 2:
576 continue
577 self.tracefuncs[i] = dict()
578 def getFtraceFilterFunctions(self, current):
579 self.rootCheck(True)
580 if not current:
581 call('cat '+self.tpath+'available_filter_functions', shell=True)
582 return
583 master = self.listFromFile(self.tpath+'available_filter_functions')
584 for i in sorted(self.tracefuncs):
585 if 'func' in self.tracefuncs[i]:
586 i = self.tracefuncs[i]['func']
587 if i in master:
588 print(i)
589 else:
590 print(self.colorText(i))
591 def setFtraceFilterFunctions(self, list):
592 master = self.listFromFile(self.tpath+'available_filter_functions')
593 flist = ''
594 for i in list:
595 if i not in master:
596 continue
597 if ' [' in i:
598 flist += i.split(' ')[0]+'\n'
599 else:
600 flist += i+'\n'
601 fp = open(self.tpath+'set_graph_function', 'w')
602 fp.write(flist)
603 fp.close()
604 def basicKprobe(self, name):
605 self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
606 def defaultKprobe(self, name, kdata):
607 k = kdata
608 for field in ['name', 'format', 'func']:
609 if field not in k:
610 k[field] = name
611 if self.archargs in k:
612 k['args'] = k[self.archargs]
613 else:
614 k['args'] = dict()
615 k['format'] = name
616 self.kprobes[name] = k
617 def kprobeColor(self, name):
618 if name not in self.kprobes or 'color' not in self.kprobes[name]:
619 return ''
620 return self.kprobes[name]['color']
621 def kprobeDisplayName(self, name, dataraw):
622 if name not in self.kprobes:
623 self.basicKprobe(name)
624 data = ''
625 quote=0
626 # first remvoe any spaces inside quotes, and the quotes
627 for c in dataraw:
628 if c == '"':
629 quote = (quote + 1) % 2
630 if quote and c == ' ':
631 data += '_'
632 elif c != '"':
633 data += c
634 fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
635 arglist = dict()
636 # now process the args
637 for arg in sorted(args):
638 arglist[arg] = ''
639 m = re.match('.* '+arg+'=(?P<arg>.*) ', data);
640 if m:
641 arglist[arg] = m.group('arg')
642 else:
643 m = re.match('.* '+arg+'=(?P<arg>.*)', data);
644 if m:
645 arglist[arg] = m.group('arg')
646 out = fmt.format(**arglist)
647 out = out.replace(' ', '_').replace('"', '')
648 return out
649 def kprobeText(self, kname, kprobe):
650 name = fmt = func = kname
651 args = dict()
652 if 'name' in kprobe:
653 name = kprobe['name']
654 if 'format' in kprobe:
655 fmt = kprobe['format']
656 if 'func' in kprobe:
657 func = kprobe['func']
658 if self.archargs in kprobe:
659 args = kprobe[self.archargs]
660 if 'args' in kprobe:
661 args = kprobe['args']
662 if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
663 doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
664 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
665 if arg not in args:
666 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
667 val = 'p:%s_cal %s' % (name, func)
668 for i in sorted(args):
669 val += ' %s=%s' % (i, args[i])
670 val += '\nr:%s_ret %s $retval\n' % (name, func)
671 return val
672 def addKprobes(self, output=False):
673 if len(self.kprobes) < 1:
674 return
675 if output:
676 pprint(' kprobe functions in this kernel:')
677 # first test each kprobe
678 rejects = []
679 # sort kprobes: trace, ub-dev, custom, dev
680 kpl = [[], [], [], []]
681 linesout = len(self.kprobes)
682 for name in sorted(self.kprobes):
683 res = self.colorText('YES', 32)
684 if not self.testKprobe(name, self.kprobes[name]):
685 res = self.colorText('NO')
686 rejects.append(name)
687 else:
688 if name in self.tracefuncs:
689 kpl[0].append(name)
690 elif name in self.dev_tracefuncs:
691 if 'ub' in self.dev_tracefuncs[name]:
692 kpl[1].append(name)
693 else:
694 kpl[3].append(name)
695 else:
696 kpl[2].append(name)
697 if output:
698 pprint(' %s: %s' % (name, res))
699 kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
700 # remove all failed ones from the list
701 for name in rejects:
702 self.kprobes.pop(name)
703 # set the kprobes all at once
704 self.fsetVal('', 'kprobe_events')
705 kprobeevents = ''
706 for kp in kplist:
707 kprobeevents += self.kprobeText(kp, self.kprobes[kp])
708 self.fsetVal(kprobeevents, 'kprobe_events')
709 if output:
710 check = self.fgetVal('kprobe_events')
711 linesack = (len(check.split('\n')) - 1) // 2
712 pprint(' kprobe functions enabled: %d/%d' % (linesack, linesout))
713 self.fsetVal('1', 'events/kprobes/enable')
714 def testKprobe(self, kname, kprobe):
715 self.fsetVal('0', 'events/kprobes/enable')
716 kprobeevents = self.kprobeText(kname, kprobe)
717 if not kprobeevents:
718 return False
719 try:
720 self.fsetVal(kprobeevents, 'kprobe_events')
721 check = self.fgetVal('kprobe_events')
722 except:
723 return False
724 linesout = len(kprobeevents.split('\n'))
725 linesack = len(check.split('\n'))
726 if linesack < linesout:
727 return False
728 return True
729 def setVal(self, val, file):
730 if not os.path.exists(file):
731 return False
732 try:
733 fp = open(file, 'wb', 0)
734 fp.write(val.encode())
735 fp.flush()
736 fp.close()
737 except:
738 return False
739 return True
740 def fsetVal(self, val, path):
741 if not self.useftrace:
742 return False
743 return self.setVal(val, self.tpath+path)
744 def getVal(self, file):
745 res = ''
746 if not os.path.exists(file):
747 return res
748 try:
749 fp = open(file, 'r')
750 res = fp.read()
751 fp.close()
752 except:
753 pass
754 return res
755 def fgetVal(self, path):
756 if not self.useftrace:
757 return ''
758 return self.getVal(self.tpath+path)
759 def cleanupFtrace(self):
760 if self.useftrace:
761 self.fsetVal('0', 'events/kprobes/enable')
762 self.fsetVal('', 'kprobe_events')
763 self.fsetVal('1024', 'buffer_size_kb')
764 def setupAllKprobes(self):
765 for name in self.tracefuncs:
766 self.defaultKprobe(name, self.tracefuncs[name])
767 for name in self.dev_tracefuncs:
768 self.defaultKprobe(name, self.dev_tracefuncs[name])
769 def isCallgraphFunc(self, name):
770 if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
771 return True
772 for i in self.tracefuncs:
773 if 'func' in self.tracefuncs[i]:
774 f = self.tracefuncs[i]['func']
775 else:
776 f = i
777 if name == f:
778 return True
779 return False
780 def initFtrace(self, quiet=False):
781 if not self.useftrace:
782 return
783 if not quiet:
784 sysvals.printSystemInfo(False)
785 pprint('INITIALIZING FTRACE')
786 # turn trace off
787 self.fsetVal('0', 'tracing_on')
788 self.cleanupFtrace()
789 # set the trace clock to global
790 self.fsetVal('global', 'trace_clock')
791 self.fsetVal('nop', 'current_tracer')
792 # set trace buffer to an appropriate value
793 cpus = max(1, self.cpucount)
794 if self.bufsize > 0:
795 tgtsize = self.bufsize
796 elif self.usecallgraph or self.usedevsrc:
797 bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
798 else (3*1024*1024)
799 tgtsize = min(self.memfree, bmax)
800 else:
801 tgtsize = 65536
802 while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
803 # if the size failed to set, lower it and keep trying
804 tgtsize -= 65536
805 if tgtsize < 65536:
806 tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
807 break
808 self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
809 # initialize the callgraph trace
810 if(self.usecallgraph):
811 # set trace type
812 self.fsetVal('function_graph', 'current_tracer')
813 self.fsetVal('', 'set_ftrace_filter')
814 # temporary hack to fix https://bugzilla.kernel.org/show_bug.cgi?id=212761
815 fp = open(self.tpath+'set_ftrace_notrace', 'w')
816 fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
817 fp.close()
818 # set trace format options
819 self.fsetVal('print-parent', 'trace_options')
820 self.fsetVal('funcgraph-abstime', 'trace_options')
821 self.fsetVal('funcgraph-cpu', 'trace_options')
822 self.fsetVal('funcgraph-duration', 'trace_options')
823 self.fsetVal('funcgraph-proc', 'trace_options')
824 self.fsetVal('funcgraph-tail', 'trace_options')
825 self.fsetVal('nofuncgraph-overhead', 'trace_options')
826 self.fsetVal('context-info', 'trace_options')
827 self.fsetVal('graph-time', 'trace_options')
828 self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
829 cf = ['dpm_run_callback']
830 if(self.usetraceevents):
831 cf += ['dpm_prepare', 'dpm_complete']
832 for fn in self.tracefuncs:
833 if 'func' in self.tracefuncs[fn]:
834 cf.append(self.tracefuncs[fn]['func'])
835 else:
836 cf.append(fn)
837 if self.ftop:
838 self.setFtraceFilterFunctions([self.ftopfunc])
839 else:
840 self.setFtraceFilterFunctions(cf)
841 # initialize the kprobe trace
842 elif self.usekprobes:
843 for name in self.tracefuncs:
844 self.defaultKprobe(name, self.tracefuncs[name])
845 if self.usedevsrc:
846 for name in self.dev_tracefuncs:
847 self.defaultKprobe(name, self.dev_tracefuncs[name])
848 if not quiet:
849 pprint('INITIALIZING KPROBES')
850 self.addKprobes(self.verbose)
851 if(self.usetraceevents):
852 # turn trace events on
853 events = iter(self.traceevents)
854 for e in events:
855 self.fsetVal('1', 'events/power/'+e+'/enable')
856 # clear the trace buffer
857 self.fsetVal('', 'trace')
858 def verifyFtrace(self):
859 # files needed for any trace data
860 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
861 'trace_marker', 'trace_options', 'tracing_on']
862 # files needed for callgraph trace data
863 tp = self.tpath
864 if(self.usecallgraph):
865 files += [
866 'available_filter_functions',
867 'set_ftrace_filter',
868 'set_graph_function'
869 ]
870 for f in files:
871 if(os.path.exists(tp+f) == False):
872 return False
873 return True
874 def verifyKprobes(self):
875 # files needed for kprobes to work
876 files = ['kprobe_events', 'events']
877 tp = self.tpath
878 for f in files:
879 if(os.path.exists(tp+f) == False):
880 return False
881 return True
882 def colorText(self, str, color=31):
883 if not self.ansi:
884 return str
885 return '\x1B[%d;40m%s\x1B[m' % (color, str)
886 def writeDatafileHeader(self, filename, testdata):
887 fp = self.openlog(filename, 'w')
888 fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
889 for test in testdata:
890 if 'fw' in test:
891 fw = test['fw']
892 if(fw):
893 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
894 if 'turbo' in test:
895 fp.write('# turbostat %s\n' % test['turbo'])
896 if 'wifi' in test:
897 fp.write('# wifi %s\n' % test['wifi'])
898 if 'netfix' in test:
899 fp.write('# netfix %s\n' % test['netfix'])
900 if test['error'] or len(testdata) > 1:
901 fp.write('# enter_sleep_error %s\n' % test['error'])
902 return fp
903 def sudoUserchown(self, dir):
904 if os.path.exists(dir) and self.sudouser:
905 cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
906 call(cmd.format(self.sudouser, dir), shell=True)
907 def outputResult(self, testdata, num=0):
908 if not self.result:
909 return
910 n = ''
911 if num > 0:
912 n = '%d' % num
913 fp = open(self.result, 'a')
914 if 'error' in testdata:
915 fp.write('result%s: fail\n' % n)
916 fp.write('error%s: %s\n' % (n, testdata['error']))
917 else:
918 fp.write('result%s: pass\n' % n)
919 if 'mode' in testdata:
920 fp.write('mode%s: %s\n' % (n, testdata['mode']))
921 for v in ['suspend', 'resume', 'boot', 'lastinit']:
922 if v in testdata:
923 fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
924 for v in ['fwsuspend', 'fwresume']:
925 if v in testdata:
926 fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
927 if 'bugurl' in testdata:
928 fp.write('url%s: %s\n' % (n, testdata['bugurl']))
929 fp.close()
930 self.sudoUserchown(self.result)
931 def configFile(self, file):
932 dir = os.path.dirname(os.path.realpath(__file__))
933 if os.path.exists(file):
934 return file
935 elif os.path.exists(dir+'/'+file):
936 return dir+'/'+file
937 elif os.path.exists(dir+'/config/'+file):
938 return dir+'/config/'+file
939 return ''
940 def openlog(self, filename, mode):
941 isgz = self.gzip
942 if mode == 'r':
943 try:
944 with gzip.open(filename, mode+'t') as fp:
945 test = fp.read(64)
946 isgz = True
947 except:
948 isgz = False
949 if isgz:
950 return gzip.open(filename, mode+'t')
951 return open(filename, mode)
952 def putlog(self, filename, text):
953 with self.openlog(filename, 'a') as fp:
954 fp.write(text)
955 fp.close()
956 def dlog(self, text):
957 if not self.dmesgfile:
958 return
959 self.putlog(self.dmesgfile, '# %s\n' % text)
960 def flog(self, text):
961 self.putlog(self.ftracefile, text)
962 def b64unzip(self, data):
963 try:
964 out = codecs.decode(base64.b64decode(data), 'zlib').decode()
965 except:
966 out = data
967 return out
968 def b64zip(self, data):
969 out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
970 return out
971 def platforminfo(self, cmdafter):
972 # add platform info on to a completed ftrace file
973 if not os.path.exists(self.ftracefile):
974 return False
975 footer = '#\n'
976
977 # add test command string line if need be
978 if self.suspendmode == 'command' and self.testcommand:
979 footer += '# platform-testcmd: %s\n' % (self.testcommand)
980
981 # get a list of target devices from the ftrace file
982 props = dict()
983 tp = TestProps()
984 tf = self.openlog(self.ftracefile, 'r')
985 for line in tf:
986 if tp.stampInfo(line, self):
987 continue
988 # parse only valid lines, if this is not one move on
989 m = re.match(tp.ftrace_line_fmt, line)
990 if(not m or 'device_pm_callback_start' not in line):
991 continue
992 m = re.match('.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
993 if(not m):
994 continue
995 dev = m.group('d')
996 if dev not in props:
997 props[dev] = DevProps()
998 tf.close()
999
1000 # now get the syspath for each target device
1001 for dirname, dirnames, filenames in os.walk('/sys/devices'):
1002 if(re.match('.*/power', dirname) and 'async' in filenames):
1003 dev = dirname.split('/')[-2]
1004 if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
1005 props[dev].syspath = dirname[:-6]
1006
1007 # now fill in the properties for our target devices
1008 for dev in sorted(props):
1009 dirname = props[dev].syspath
1010 if not dirname or not os.path.exists(dirname):
1011 continue
1012 props[dev].isasync = False
1013 if os.path.exists(dirname+'/power/async'):
1014 fp = open(dirname+'/power/async')
1015 if 'enabled' in fp.read():
1016 props[dev].isasync = True
1017 fp.close()
1018 fields = os.listdir(dirname)
1019 for file in ['product', 'name', 'model', 'description', 'id', 'idVendor']:
1020 if file not in fields:
1021 continue
1022 try:
1023 with open(os.path.join(dirname, file), 'rb') as fp:
1024 props[dev].altname = ascii(fp.read())
1025 except:
1026 continue
1027 if file == 'idVendor':
1028 idv, idp = props[dev].altname.strip(), ''
1029 try:
1030 with open(os.path.join(dirname, 'idProduct'), 'rb') as fp:
1031 idp = ascii(fp.read()).strip()
1032 except:
1033 props[dev].altname = ''
1034 break
1035 props[dev].altname = '%s:%s' % (idv, idp)
1036 break
1037 if props[dev].altname:
1038 out = props[dev].altname.strip().replace('\n', ' ')\
1039 .replace(',', ' ').replace(';', ' ')
1040 props[dev].altname = out
1041
1042 # add a devinfo line to the bottom of ftrace
1043 out = ''
1044 for dev in sorted(props):
1045 out += props[dev].out(dev)
1046 footer += '# platform-devinfo: %s\n' % self.b64zip(out)
1047
1048 # add a line for each of these commands with their outputs
1049 for name, cmdline, info in cmdafter:
1050 footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
1051 self.flog(footer)
1052 return True
1053 def commonPrefix(self, list):
1054 if len(list) < 2:
1055 return ''
1056 prefix = list[0]
1057 for s in list[1:]:
1058 while s[:len(prefix)] != prefix and prefix:
1059 prefix = prefix[:len(prefix)-1]
1060 if not prefix:
1061 break
1062 if '/' in prefix and prefix[-1] != '/':
1063 prefix = prefix[0:prefix.rfind('/')+1]
1064 return prefix
1065 def dictify(self, text, format):
1066 out = dict()
1067 header = True if format == 1 else False
1068 delim = ' ' if format == 1 else ':'
1069 for line in text.split('\n'):
1070 if header:
1071 header, out['@'] = False, line
1072 continue
1073 line = line.strip()
1074 if delim in line:
1075 data = line.split(delim, 1)
1076 num = re.search(r'[\d]+', data[1])
1077 if format == 2 and num:
1078 out[data[0].strip()] = num.group()
1079 else:
1080 out[data[0].strip()] = data[1]
1081 return out
1082 def cmdinfovar(self, arg):
1083 if arg == 'ethdev':
1084 try:
1085 cmd = [self.getExec('ip'), '-4', '-o', '-br', 'addr']
1086 fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout
1087 info = ascii(fp.read()).strip()
1088 fp.close()
1089 except:
1090 return 'iptoolcrash'
1091 for line in info.split('\n'):
1092 if line[0] == 'e' and 'UP' in line:
1093 return line.split()[0]
1094 return 'nodevicefound'
1095 return 'unknown'
1096 def cmdinfo(self, begin, debug=False):
1097 out = []
1098 if begin:
1099 self.cmd1 = dict()
1100 for cargs in self.infocmds:
1101 delta, name, args = cargs[0], cargs[1], cargs[2:]
1102 for i in range(len(args)):
1103 if args[i][0] == '{' and args[i][-1] == '}':
1104 args[i] = self.cmdinfovar(args[i][1:-1])
1105 cmdline, cmdpath = ' '.join(args[0:]), self.getExec(args[0])
1106 if not cmdpath or (begin and not delta):
1107 continue
1108 self.dlog('[%s]' % cmdline)
1109 try:
1110 fp = Popen([cmdpath]+args[1:], stdout=PIPE, stderr=PIPE).stdout
1111 info = ascii(fp.read()).strip()
1112 fp.close()
1113 except:
1114 continue
1115 if not debug and begin:
1116 self.cmd1[name] = self.dictify(info, delta)
1117 elif not debug and delta and name in self.cmd1:
1118 before, after = self.cmd1[name], self.dictify(info, delta)
1119 dinfo = ('\t%s\n' % before['@']) if '@' in before and len(before) > 1 else ''
1120 prefix = self.commonPrefix(list(before.keys()))
1121 for key in sorted(before):
1122 if key in after and before[key] != after[key]:
1123 title = key.replace(prefix, '')
1124 if delta == 2:
1125 dinfo += '\t%s : %s -> %s\n' % \
1126 (title, before[key].strip(), after[key].strip())
1127 else:
1128 dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1129 (title, before[key], title, after[key])
1130 dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1131 out.append((name, cmdline, dinfo))
1132 else:
1133 out.append((name, cmdline, '\tnothing' if not info else info))
1134 return out
1135 def testVal(self, file, fmt='basic', value=''):
1136 if file == 'restoreall':
1137 for f in self.cfgdef:
1138 if os.path.exists(f):
1139 fp = open(f, 'w')
1140 fp.write(self.cfgdef[f])
1141 fp.close()
1142 self.cfgdef = dict()
1143 elif value and os.path.exists(file):
1144 fp = open(file, 'r+')
1145 if fmt == 'radio':
1146 m = re.match('.*\[(?P<v>.*)\].*', fp.read())
1147 if m:
1148 self.cfgdef[file] = m.group('v')
1149 elif fmt == 'acpi':
1150 line = fp.read().strip().split('\n')[-1]
1151 m = re.match('.* (?P<v>[0-9A-Fx]*) .*', line)
1152 if m:
1153 self.cfgdef[file] = m.group('v')
1154 else:
1155 self.cfgdef[file] = fp.read().strip()
1156 fp.write(value)
1157 fp.close()
1158 def s0ixSupport(self):
1159 if not os.path.exists(self.s0ixres) or not os.path.exists(self.mempowerfile):
1160 return False
1161 fp = open(sysvals.mempowerfile, 'r')
1162 data = fp.read().strip()
1163 fp.close()
1164 if '[s2idle]' in data:
1165 return True
1166 return False
1167 def haveTurbostat(self):
1168 if not self.tstat:
1169 return False
1170 cmd = self.getExec('turbostat')
1171 if not cmd:
1172 return False
1173 fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1174 out = ascii(fp.read()).strip()
1175 fp.close()
1176 if re.match('turbostat version .*', out):
1177 self.vprint(out)
1178 return True
1179 return False
1180 def turbostat(self, s0ixready):
1181 cmd = self.getExec('turbostat')
1182 rawout = keyline = valline = ''
1183 fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1184 fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE).stderr
1185 for line in fp:
1186 line = ascii(line)
1187 rawout += line
1188 if keyline and valline:
1189 continue
1190 if re.match('(?i)Avg_MHz.*', line):
1191 keyline = line.strip().split()
1192 elif keyline:
1193 valline = line.strip().split()
1194 fp.close()
1195 if not keyline or not valline or len(keyline) != len(valline):
1196 errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1197 self.vprint(errmsg)
1198 if not self.verbose:
1199 pprint(errmsg)
1200 return ''
1201 if self.verbose:
1202 pprint(rawout.strip())
1203 out = []
1204 for key in keyline:
1205 idx = keyline.index(key)
1206 val = valline[idx]
1207 if key == 'SYS%LPI' and not s0ixready and re.match('^[0\.]*$', val):
1208 continue
1209 out.append('%s=%s' % (key, val))
1210 return '|'.join(out)
1211 def netfixon(self, net='both'):
1212 cmd = self.getExec('netfix')
1213 if not cmd:
1214 return ''
1215 fp = Popen([cmd, '-s', net, 'on'], stdout=PIPE, stderr=PIPE).stdout
1216 out = ascii(fp.read()).strip()
1217 fp.close()
1218 return out
1219 def wifiDetails(self, dev):
1220 try:
1221 info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1222 except:
1223 return dev
1224 vals = [dev]
1225 for prop in info.split('\n'):
1226 if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1227 vals.append(prop.split('=')[-1])
1228 return ':'.join(vals)
1229 def checkWifi(self, dev=''):
1230 try:
1231 w = open('/proc/net/wireless', 'r').read().strip()
1232 except:
1233 return ''
1234 for line in reversed(w.split('\n')):
1235 m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', line)
1236 if not m or (dev and dev != m.group('dev')):
1237 continue
1238 return m.group('dev')
1239 return ''
1240 def pollWifi(self, dev, timeout=10):
1241 start = time.time()
1242 while (time.time() - start) < timeout:
1243 w = self.checkWifi(dev)
1244 if w:
1245 return '%s reconnected %.2f' % \
1246 (self.wifiDetails(dev), max(0, time.time() - start))
1247 time.sleep(0.01)
1248 return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1249 def errorSummary(self, errinfo, msg):
1250 found = False
1251 for entry in errinfo:
1252 if re.match(entry['match'], msg):
1253 entry['count'] += 1
1254 if self.hostname not in entry['urls']:
1255 entry['urls'][self.hostname] = [self.htmlfile]
1256 elif self.htmlfile not in entry['urls'][self.hostname]:
1257 entry['urls'][self.hostname].append(self.htmlfile)
1258 found = True
1259 break
1260 if found:
1261 return
1262 arr = msg.split()
1263 for j in range(len(arr)):
1264 if re.match('^[0-9,\-\.]*$', arr[j]):
1265 arr[j] = '[0-9,\-\.]*'
1266 else:
1267 arr[j] = arr[j]\
1268 .replace('\\', '\\\\').replace(']', '\]').replace('[', '\[')\
1269 .replace('.', '\.').replace('+', '\+').replace('*', '\*')\
1270 .replace('(', '\(').replace(')', '\)').replace('}', '\}')\
1271 .replace('{', '\{')
1272 mstr = ' *'.join(arr)
1273 entry = {
1274 'line': msg,
1275 'match': mstr,
1276 'count': 1,
1277 'urls': {self.hostname: [self.htmlfile]}
1278 }
1279 errinfo.append(entry)
1280 def multistat(self, start, idx, finish):
1281 if 'time' in self.multitest:
1282 id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1283 else:
1284 id = '%d/%d' % (idx+1, self.multitest['count'])
1285 t = time.time()
1286 if 'start' not in self.multitest:
1287 self.multitest['start'] = self.multitest['last'] = t
1288 self.multitest['total'] = 0.0
1289 pprint('TEST (%s) START' % id)
1290 return
1291 dt = t - self.multitest['last']
1292 if not start:
1293 if idx == 0 and self.multitest['delay'] > 0:
1294 self.multitest['total'] += self.multitest['delay']
1295 pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1296 return
1297 self.multitest['total'] += dt
1298 self.multitest['last'] = t
1299 avg = self.multitest['total'] / idx
1300 if 'time' in self.multitest:
1301 left = finish - datetime.now()
1302 left -= timedelta(microseconds=left.microseconds)
1303 else:
1304 left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1305 pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1306 (id, avg, str(left)))
1307 def multiinit(self, c, d):
1308 sz, unit = 'count', 'm'
1309 if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1310 sz, unit, c = 'time', c[-1], c[:-1]
1311 self.multitest['run'] = True
1312 self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1313 self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1314 if unit == 'd':
1315 self.multitest[sz] *= 1440
1316 elif unit == 'h':
1317 self.multitest[sz] *= 60
1318 def displayControl(self, cmd):
1319 xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
1320 if self.sudouser:
1321 xset = 'sudo -u %s %s' % (self.sudouser, xset)
1322 if cmd == 'init':
1323 ret = call(xset.format('dpms 0 0 0'), shell=True)
1324 if not ret:
1325 ret = call(xset.format('s off'), shell=True)
1326 elif cmd == 'reset':
1327 ret = call(xset.format('s reset'), shell=True)
1328 elif cmd in ['on', 'off', 'standby', 'suspend']:
1329 b4 = self.displayControl('stat')
1330 ret = call(xset.format('dpms force %s' % cmd), shell=True)
1331 if not ret:
1332 curr = self.displayControl('stat')
1333 self.vprint('Display Switched: %s -> %s' % (b4, curr))
1334 if curr != cmd:
1335 self.vprint('WARNING: Display failed to change to %s' % cmd)
1336 if ret:
1337 self.vprint('WARNING: Display failed to change to %s with xset' % cmd)
1338 return ret
1339 elif cmd == 'stat':
1340 fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
1341 ret = 'unknown'
1342 for line in fp:
1343 m = re.match('[\s]*Monitor is (?P<m>.*)', ascii(line))
1344 if(m and len(m.group('m')) >= 2):
1345 out = m.group('m').lower()
1346 ret = out[3:] if out[0:2] == 'in' else out
1347 break
1348 fp.close()
1349 return ret
1350 def setRuntimeSuspend(self, before=True):
1351 if before:
1352 # runtime suspend disable or enable
1353 if self.rs > 0:
1354 self.rstgt, self.rsval, self.rsdir = 'on', 'auto', 'enabled'
1355 else:
1356 self.rstgt, self.rsval, self.rsdir = 'auto', 'on', 'disabled'
1357 pprint('CONFIGURING RUNTIME SUSPEND...')
1358 self.rslist = deviceInfo(self.rstgt)
1359 for i in self.rslist:
1360 self.setVal(self.rsval, i)
1361 pprint('runtime suspend %s on all devices (%d changed)' % (self.rsdir, len(self.rslist)))
1362 pprint('waiting 5 seconds...')
1363 time.sleep(5)
1364 else:
1365 # runtime suspend re-enable or re-disable
1366 for i in self.rslist:
1367 self.setVal(self.rstgt, i)
1368 pprint('runtime suspend settings restored on %d devices' % len(self.rslist))
1369 def start(self, pm):
1370 if self.useftrace:
1371 self.dlog('start ftrace tracing')
1372 self.fsetVal('1', 'tracing_on')
1373 if self.useprocmon:
1374 self.dlog('start the process monitor')
1375 pm.start()
1376 def stop(self, pm):
1377 if self.useftrace:
1378 if self.useprocmon:
1379 self.dlog('stop the process monitor')
1380 pm.stop()
1381 self.dlog('stop ftrace tracing')
1382 self.fsetVal('0', 'tracing_on')
1383
1384sysvals = SystemValues()
1385switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1386switchoff = ['disable', 'off', 'false', '0']
1387suspendmodename = {
1388 'standby': 'standby (S1)',
1389 'freeze': 'freeze (S2idle)',
1390 'mem': 'suspend (S3)',
1391 'disk': 'hibernate (S4)'
1392}
1393
1394# Class: DevProps
1395# Description:
1396# Simple class which holds property values collected
1397# for all the devices used in the timeline.
1398class DevProps:
1399 def __init__(self):
1400 self.syspath = ''
1401 self.altname = ''
1402 self.isasync = True
1403 self.xtraclass = ''
1404 self.xtrainfo = ''
1405 def out(self, dev):
1406 return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1407 def debug(self, dev):
1408 pprint('%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.isasync))
1409 def altName(self, dev):
1410 if not self.altname or self.altname == dev:
1411 return dev
1412 return '%s [%s]' % (self.altname, dev)
1413 def xtraClass(self):
1414 if self.xtraclass:
1415 return ' '+self.xtraclass
1416 if not self.isasync:
1417 return ' sync'
1418 return ''
1419 def xtraInfo(self):
1420 if self.xtraclass:
1421 return ' '+self.xtraclass
1422 if self.isasync:
1423 return ' (async)'
1424 return ' (sync)'
1425
1426# Class: DeviceNode
1427# Description:
1428# A container used to create a device hierachy, with a single root node
1429# and a tree of child nodes. Used by Data.deviceTopology()
1430class DeviceNode:
1431 def __init__(self, nodename, nodedepth):
1432 self.name = nodename
1433 self.children = []
1434 self.depth = nodedepth
1435
1436# Class: Data
1437# Description:
1438# The primary container for suspend/resume test data. There is one for
1439# each test run. The data is organized into a cronological hierarchy:
1440# Data.dmesg {
1441# phases {
1442# 10 sequential, non-overlapping phases of S/R
1443# contents: times for phase start/end, order/color data for html
1444# devlist {
1445# device callback or action list for this phase
1446# device {
1447# a single device callback or generic action
1448# contents: start/stop times, pid/cpu/driver info
1449# parents/children, html id for timeline/callgraph
1450# optionally includes an ftrace callgraph
1451# optionally includes dev/ps data
1452# }
1453# }
1454# }
1455# }
1456#
1457class Data:
1458 phasedef = {
1459 'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1460 'suspend': {'order': 1, 'color': '#88FF88'},
1461 'suspend_late': {'order': 2, 'color': '#00AA00'},
1462 'suspend_noirq': {'order': 3, 'color': '#008888'},
1463 'suspend_machine': {'order': 4, 'color': '#0000FF'},
1464 'resume_machine': {'order': 5, 'color': '#FF0000'},
1465 'resume_noirq': {'order': 6, 'color': '#FF9900'},
1466 'resume_early': {'order': 7, 'color': '#FFCC00'},
1467 'resume': {'order': 8, 'color': '#FFFF88'},
1468 'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1469 }
1470 errlist = {
1471 'HWERROR' : r'.*\[ *Hardware Error *\].*',
1472 'FWBUG' : r'.*\[ *Firmware Bug *\].*',
1473 'TASKFAIL': r'.*Freezing .*after *.*',
1474 'BUG' : r'(?i).*\bBUG\b.*',
1475 'ERROR' : r'(?i).*\bERROR\b.*',
1476 'WARNING' : r'(?i).*\bWARNING\b.*',
1477 'FAULT' : r'(?i).*\bFAULT\b.*',
1478 'FAIL' : r'(?i).*\bFAILED\b.*',
1479 'INVALID' : r'(?i).*\bINVALID\b.*',
1480 'CRASH' : r'(?i).*\bCRASHED\b.*',
1481 'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
1482 'ABORT' : r'(?i).*\bABORT\b.*',
1483 'IRQ' : r'.*\bgenirq: .*',
1484 'ACPI' : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1485 'DISKFULL': r'.*\bNo space left on device.*',
1486 'USBERR' : r'.*usb .*device .*, error [0-9-]*',
1487 'ATAERR' : r' *ata[0-9\.]*: .*failed.*',
1488 'MEIERR' : r' *mei.*: .*failed.*',
1489 'TPMERR' : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1490 }
1491 def __init__(self, num):
1492 idchar = 'abcdefghij'
1493 self.start = 0.0 # test start
1494 self.end = 0.0 # test end
1495 self.hwstart = 0 # rtc test start
1496 self.hwend = 0 # rtc test end
1497 self.tSuspended = 0.0 # low-level suspend start
1498 self.tResumed = 0.0 # low-level resume start
1499 self.tKernSus = 0.0 # kernel level suspend start
1500 self.tKernRes = 0.0 # kernel level resume end
1501 self.fwValid = False # is firmware data available
1502 self.fwSuspend = 0 # time spent in firmware suspend
1503 self.fwResume = 0 # time spent in firmware resume
1504 self.html_device_id = 0
1505 self.stamp = 0
1506 self.outfile = ''
1507 self.kerror = False
1508 self.wifi = dict()
1509 self.turbostat = 0
1510 self.enterfail = ''
1511 self.currphase = ''
1512 self.pstl = dict() # process timeline
1513 self.testnumber = num
1514 self.idstr = idchar[num]
1515 self.dmesgtext = [] # dmesg text file in memory
1516 self.dmesg = dict() # root data structure
1517 self.errorinfo = {'suspend':[],'resume':[]}
1518 self.tLow = [] # time spent in low-level suspends (standby/freeze)
1519 self.devpids = []
1520 self.devicegroups = 0
1521 def sortedPhases(self):
1522 return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1523 def initDevicegroups(self):
1524 # called when phases are all finished being added
1525 for phase in sorted(self.dmesg.keys()):
1526 if '*' in phase:
1527 p = phase.split('*')
1528 pnew = '%s%d' % (p[0], len(p))
1529 self.dmesg[pnew] = self.dmesg.pop(phase)
1530 self.devicegroups = []
1531 for phase in self.sortedPhases():
1532 self.devicegroups.append([phase])
1533 def nextPhase(self, phase, offset):
1534 order = self.dmesg[phase]['order'] + offset
1535 for p in self.dmesg:
1536 if self.dmesg[p]['order'] == order:
1537 return p
1538 return ''
1539 def lastPhase(self, depth=1):
1540 plist = self.sortedPhases()
1541 if len(plist) < depth:
1542 return ''
1543 return plist[-1*depth]
1544 def turbostatInfo(self):
1545 tp = TestProps()
1546 out = {'syslpi':'N/A','pkgpc10':'N/A'}
1547 for line in self.dmesgtext:
1548 m = re.match(tp.tstatfmt, line)
1549 if not m:
1550 continue
1551 for i in m.group('t').split('|'):
1552 if 'SYS%LPI' in i:
1553 out['syslpi'] = i.split('=')[-1]+'%'
1554 elif 'pc10' in i:
1555 out['pkgpc10'] = i.split('=')[-1]+'%'
1556 break
1557 return out
1558 def extractErrorInfo(self):
1559 lf = self.dmesgtext
1560 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1561 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1562 i = 0
1563 tp = TestProps()
1564 list = []
1565 for line in lf:
1566 i += 1
1567 if tp.stampInfo(line, sysvals):
1568 continue
1569 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1570 if not m:
1571 continue
1572 t = float(m.group('ktime'))
1573 if t < self.start or t > self.end:
1574 continue
1575 dir = 'suspend' if t < self.tSuspended else 'resume'
1576 msg = m.group('msg')
1577 if re.match('capability: warning: .*', msg):
1578 continue
1579 for err in self.errlist:
1580 if re.match(self.errlist[err], msg):
1581 list.append((msg, err, dir, t, i, i))
1582 self.kerror = True
1583 break
1584 tp.msglist = []
1585 for msg, type, dir, t, idx1, idx2 in list:
1586 tp.msglist.append(msg)
1587 self.errorinfo[dir].append((type, t, idx1, idx2))
1588 if self.kerror:
1589 sysvals.dmesglog = True
1590 if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1591 lf.close()
1592 return tp
1593 def setStart(self, time, msg=''):
1594 self.start = time
1595 if msg:
1596 try:
1597 self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1598 except:
1599 self.hwstart = 0
1600 def setEnd(self, time, msg=''):
1601 self.end = time
1602 if msg:
1603 try:
1604 self.hwend = datetime.strptime(msg, sysvals.tmend)
1605 except:
1606 self.hwend = 0
1607 def isTraceEventOutsideDeviceCalls(self, pid, time):
1608 for phase in self.sortedPhases():
1609 list = self.dmesg[phase]['list']
1610 for dev in list:
1611 d = list[dev]
1612 if(d['pid'] == pid and time >= d['start'] and
1613 time < d['end']):
1614 return False
1615 return True
1616 def sourcePhase(self, start):
1617 for phase in self.sortedPhases():
1618 if 'machine' in phase:
1619 continue
1620 pend = self.dmesg[phase]['end']
1621 if start <= pend:
1622 return phase
1623 return 'resume_complete' if 'resume_complete' in self.dmesg else ''
1624 def sourceDevice(self, phaselist, start, end, pid, type):
1625 tgtdev = ''
1626 for phase in phaselist:
1627 list = self.dmesg[phase]['list']
1628 for devname in list:
1629 dev = list[devname]
1630 # pid must match
1631 if dev['pid'] != pid:
1632 continue
1633 devS = dev['start']
1634 devE = dev['end']
1635 if type == 'device':
1636 # device target event is entirely inside the source boundary
1637 if(start < devS or start >= devE or end <= devS or end > devE):
1638 continue
1639 elif type == 'thread':
1640 # thread target event will expand the source boundary
1641 if start < devS:
1642 dev['start'] = start
1643 if end > devE:
1644 dev['end'] = end
1645 tgtdev = dev
1646 break
1647 return tgtdev
1648 def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1649 # try to place the call in a device
1650 phases = self.sortedPhases()
1651 tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1652 # calls with device pids that occur outside device bounds are dropped
1653 # TODO: include these somehow
1654 if not tgtdev and pid in self.devpids:
1655 return False
1656 # try to place the call in a thread
1657 if not tgtdev:
1658 tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1659 # create new thread blocks, expand as new calls are found
1660 if not tgtdev:
1661 if proc == '<...>':
1662 threadname = 'kthread-%d' % (pid)
1663 else:
1664 threadname = '%s-%d' % (proc, pid)
1665 tgtphase = self.sourcePhase(start)
1666 if not tgtphase:
1667 return False
1668 self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1669 return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1670 # this should not happen
1671 if not tgtdev:
1672 sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1673 (start, end, proc, pid, kprobename, cdata, rdata))
1674 return False
1675 # place the call data inside the src element of the tgtdev
1676 if('src' not in tgtdev):
1677 tgtdev['src'] = []
1678 dtf = sysvals.dev_tracefuncs
1679 ubiquitous = False
1680 if kprobename in dtf and 'ub' in dtf[kprobename]:
1681 ubiquitous = True
1682 mc = re.match('\(.*\) *(?P<args>.*)', cdata)
1683 mr = re.match('\((?P<caller>\S*).* arg1=(?P<ret>.*)', rdata)
1684 if mc and mr:
1685 c = mr.group('caller').split('+')[0]
1686 a = mc.group('args').strip()
1687 r = mr.group('ret')
1688 if len(r) > 6:
1689 r = ''
1690 else:
1691 r = 'ret=%s ' % r
1692 if ubiquitous and c in dtf and 'ub' in dtf[c]:
1693 return False
1694 else:
1695 return False
1696 color = sysvals.kprobeColor(kprobename)
1697 e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1698 tgtdev['src'].append(e)
1699 return True
1700 def overflowDevices(self):
1701 # get a list of devices that extend beyond the end of this test run
1702 devlist = []
1703 for phase in self.sortedPhases():
1704 list = self.dmesg[phase]['list']
1705 for devname in list:
1706 dev = list[devname]
1707 if dev['end'] > self.end:
1708 devlist.append(dev)
1709 return devlist
1710 def mergeOverlapDevices(self, devlist):
1711 # merge any devices that overlap devlist
1712 for dev in devlist:
1713 devname = dev['name']
1714 for phase in self.sortedPhases():
1715 list = self.dmesg[phase]['list']
1716 if devname not in list:
1717 continue
1718 tdev = list[devname]
1719 o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1720 if o <= 0:
1721 continue
1722 dev['end'] = tdev['end']
1723 if 'src' not in dev or 'src' not in tdev:
1724 continue
1725 dev['src'] += tdev['src']
1726 del list[devname]
1727 def usurpTouchingThread(self, name, dev):
1728 # the caller test has priority of this thread, give it to him
1729 for phase in self.sortedPhases():
1730 list = self.dmesg[phase]['list']
1731 if name in list:
1732 tdev = list[name]
1733 if tdev['start'] - dev['end'] < 0.1:
1734 dev['end'] = tdev['end']
1735 if 'src' not in dev:
1736 dev['src'] = []
1737 if 'src' in tdev:
1738 dev['src'] += tdev['src']
1739 del list[name]
1740 break
1741 def stitchTouchingThreads(self, testlist):
1742 # merge any threads between tests that touch
1743 for phase in self.sortedPhases():
1744 list = self.dmesg[phase]['list']
1745 for devname in list:
1746 dev = list[devname]
1747 if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1748 continue
1749 for data in testlist:
1750 data.usurpTouchingThread(devname, dev)
1751 def optimizeDevSrc(self):
1752 # merge any src call loops to reduce timeline size
1753 for phase in self.sortedPhases():
1754 list = self.dmesg[phase]['list']
1755 for dev in list:
1756 if 'src' not in list[dev]:
1757 continue
1758 src = list[dev]['src']
1759 p = 0
1760 for e in sorted(src, key=lambda event: event.time):
1761 if not p or not e.repeat(p):
1762 p = e
1763 continue
1764 # e is another iteration of p, move it into p
1765 p.end = e.end
1766 p.length = p.end - p.time
1767 p.count += 1
1768 src.remove(e)
1769 def trimTimeVal(self, t, t0, dT, left):
1770 if left:
1771 if(t > t0):
1772 if(t - dT < t0):
1773 return t0
1774 return t - dT
1775 else:
1776 return t
1777 else:
1778 if(t < t0 + dT):
1779 if(t > t0):
1780 return t0 + dT
1781 return t + dT
1782 else:
1783 return t
1784 def trimTime(self, t0, dT, left):
1785 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1786 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1787 self.start = self.trimTimeVal(self.start, t0, dT, left)
1788 self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1789 self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1790 self.end = self.trimTimeVal(self.end, t0, dT, left)
1791 for phase in self.sortedPhases():
1792 p = self.dmesg[phase]
1793 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1794 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1795 list = p['list']
1796 for name in list:
1797 d = list[name]
1798 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1799 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1800 d['length'] = d['end'] - d['start']
1801 if('ftrace' in d):
1802 cg = d['ftrace']
1803 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1804 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1805 for line in cg.list:
1806 line.time = self.trimTimeVal(line.time, t0, dT, left)
1807 if('src' in d):
1808 for e in d['src']:
1809 e.time = self.trimTimeVal(e.time, t0, dT, left)
1810 e.end = self.trimTimeVal(e.end, t0, dT, left)
1811 e.length = e.end - e.time
1812 if('cpuexec' in d):
1813 cpuexec = dict()
1814 for e in d['cpuexec']:
1815 c0, cN = e
1816 c0 = self.trimTimeVal(c0, t0, dT, left)
1817 cN = self.trimTimeVal(cN, t0, dT, left)
1818 cpuexec[(c0, cN)] = d['cpuexec'][e]
1819 d['cpuexec'] = cpuexec
1820 for dir in ['suspend', 'resume']:
1821 list = []
1822 for e in self.errorinfo[dir]:
1823 type, tm, idx1, idx2 = e
1824 tm = self.trimTimeVal(tm, t0, dT, left)
1825 list.append((type, tm, idx1, idx2))
1826 self.errorinfo[dir] = list
1827 def trimFreezeTime(self, tZero):
1828 # trim out any standby or freeze clock time
1829 lp = ''
1830 for phase in self.sortedPhases():
1831 if 'resume_machine' in phase and 'suspend_machine' in lp:
1832 tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1833 tL = tR - tS
1834 if tL <= 0:
1835 continue
1836 left = True if tR > tZero else False
1837 self.trimTime(tS, tL, left)
1838 if 'waking' in self.dmesg[lp]:
1839 tCnt = self.dmesg[lp]['waking'][0]
1840 if self.dmesg[lp]['waking'][1] >= 0.001:
1841 tTry = '%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
1842 else:
1843 tTry = '%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
1844 text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
1845 else:
1846 text = '%.0f' % (tL * 1000)
1847 self.tLow.append(text)
1848 lp = phase
1849 def getMemTime(self):
1850 if not self.hwstart or not self.hwend:
1851 return
1852 stime = (self.tSuspended - self.start) * 1000000
1853 rtime = (self.end - self.tResumed) * 1000000
1854 hws = self.hwstart + timedelta(microseconds=stime)
1855 hwr = self.hwend - timedelta(microseconds=rtime)
1856 self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1857 def getTimeValues(self):
1858 s = (self.tSuspended - self.tKernSus) * 1000
1859 r = (self.tKernRes - self.tResumed) * 1000
1860 return (max(s, 0), max(r, 0))
1861 def setPhase(self, phase, ktime, isbegin, order=-1):
1862 if(isbegin):
1863 # phase start over current phase
1864 if self.currphase:
1865 if 'resume_machine' not in self.currphase:
1866 sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1867 self.dmesg[self.currphase]['end'] = ktime
1868 phases = self.dmesg.keys()
1869 color = self.phasedef[phase]['color']
1870 count = len(phases) if order < 0 else order
1871 # create unique name for every new phase
1872 while phase in phases:
1873 phase += '*'
1874 self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1875 'row': 0, 'color': color, 'order': count}
1876 self.dmesg[phase]['start'] = ktime
1877 self.currphase = phase
1878 else:
1879 # phase end without a start
1880 if phase not in self.currphase:
1881 if self.currphase:
1882 sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1883 else:
1884 sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1885 return phase
1886 phase = self.currphase
1887 self.dmesg[phase]['end'] = ktime
1888 self.currphase = ''
1889 return phase
1890 def sortedDevices(self, phase):
1891 list = self.dmesg[phase]['list']
1892 return sorted(list, key=lambda k:list[k]['start'])
1893 def fixupInitcalls(self, phase):
1894 # if any calls never returned, clip them at system resume end
1895 phaselist = self.dmesg[phase]['list']
1896 for devname in phaselist:
1897 dev = phaselist[devname]
1898 if(dev['end'] < 0):
1899 for p in self.sortedPhases():
1900 if self.dmesg[p]['end'] > dev['start']:
1901 dev['end'] = self.dmesg[p]['end']
1902 break
1903 sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1904 def deviceFilter(self, devicefilter):
1905 for phase in self.sortedPhases():
1906 list = self.dmesg[phase]['list']
1907 rmlist = []
1908 for name in list:
1909 keep = False
1910 for filter in devicefilter:
1911 if filter in name or \
1912 ('drv' in list[name] and filter in list[name]['drv']):
1913 keep = True
1914 if not keep:
1915 rmlist.append(name)
1916 for name in rmlist:
1917 del list[name]
1918 def fixupInitcallsThatDidntReturn(self):
1919 # if any calls never returned, clip them at system resume end
1920 for phase in self.sortedPhases():
1921 self.fixupInitcalls(phase)
1922 def phaseOverlap(self, phases):
1923 rmgroups = []
1924 newgroup = []
1925 for group in self.devicegroups:
1926 for phase in phases:
1927 if phase not in group:
1928 continue
1929 for p in group:
1930 if p not in newgroup:
1931 newgroup.append(p)
1932 if group not in rmgroups:
1933 rmgroups.append(group)
1934 for group in rmgroups:
1935 self.devicegroups.remove(group)
1936 self.devicegroups.append(newgroup)
1937 def newActionGlobal(self, name, start, end, pid=-1, color=''):
1938 # which phase is this device callback or action in
1939 phases = self.sortedPhases()
1940 targetphase = 'none'
1941 htmlclass = ''
1942 overlap = 0.0
1943 myphases = []
1944 for phase in phases:
1945 pstart = self.dmesg[phase]['start']
1946 pend = self.dmesg[phase]['end']
1947 # see if the action overlaps this phase
1948 o = max(0, min(end, pend) - max(start, pstart))
1949 if o > 0:
1950 myphases.append(phase)
1951 # set the target phase to the one that overlaps most
1952 if o > overlap:
1953 if overlap > 0 and phase == 'post_resume':
1954 continue
1955 targetphase = phase
1956 overlap = o
1957 # if no target phase was found, pin it to the edge
1958 if targetphase == 'none':
1959 p0start = self.dmesg[phases[0]]['start']
1960 if start <= p0start:
1961 targetphase = phases[0]
1962 else:
1963 targetphase = phases[-1]
1964 if pid == -2:
1965 htmlclass = ' bg'
1966 elif pid == -3:
1967 htmlclass = ' ps'
1968 if len(myphases) > 1:
1969 htmlclass = ' bg'
1970 self.phaseOverlap(myphases)
1971 if targetphase in phases:
1972 newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1973 return (targetphase, newname)
1974 return False
1975 def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1976 # new device callback for a specific phase
1977 self.html_device_id += 1
1978 devid = '%s%d' % (self.idstr, self.html_device_id)
1979 list = self.dmesg[phase]['list']
1980 length = -1.0
1981 if(start >= 0 and end >= 0):
1982 length = end - start
1983 if pid == -2 or name not in sysvals.tracefuncs.keys():
1984 i = 2
1985 origname = name
1986 while(name in list):
1987 name = '%s[%d]' % (origname, i)
1988 i += 1
1989 list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
1990 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
1991 if htmlclass:
1992 list[name]['htmlclass'] = htmlclass
1993 if color:
1994 list[name]['color'] = color
1995 return name
1996 def findDevice(self, phase, name):
1997 list = self.dmesg[phase]['list']
1998 mydev = ''
1999 for devname in sorted(list):
2000 if name == devname or re.match('^%s\[(?P<num>[0-9]*)\]$' % name, devname):
2001 mydev = devname
2002 if mydev:
2003 return list[mydev]
2004 return False
2005 def deviceChildren(self, devname, phase):
2006 devlist = []
2007 list = self.dmesg[phase]['list']
2008 for child in list:
2009 if(list[child]['par'] == devname):
2010 devlist.append(child)
2011 return devlist
2012 def maxDeviceNameSize(self, phase):
2013 size = 0
2014 for name in self.dmesg[phase]['list']:
2015 if len(name) > size:
2016 size = len(name)
2017 return size
2018 def printDetails(self):
2019 sysvals.vprint('Timeline Details:')
2020 sysvals.vprint(' test start: %f' % self.start)
2021 sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
2022 tS = tR = False
2023 for phase in self.sortedPhases():
2024 devlist = self.dmesg[phase]['list']
2025 dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
2026 if not tS and ps >= self.tSuspended:
2027 sysvals.vprint(' machine suspended: %f' % self.tSuspended)
2028 tS = True
2029 if not tR and ps >= self.tResumed:
2030 sysvals.vprint(' machine resumed: %f' % self.tResumed)
2031 tR = True
2032 sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
2033 if sysvals.devdump:
2034 sysvals.vprint(''.join('-' for i in range(80)))
2035 maxname = '%d' % self.maxDeviceNameSize(phase)
2036 fmt = '%3d) %'+maxname+'s - %f - %f'
2037 c = 1
2038 for name in sorted(devlist):
2039 s = devlist[name]['start']
2040 e = devlist[name]['end']
2041 sysvals.vprint(fmt % (c, name, s, e))
2042 c += 1
2043 sysvals.vprint(''.join('-' for i in range(80)))
2044 sysvals.vprint(' kernel resume end: %f' % self.tKernRes)
2045 sysvals.vprint(' test end: %f' % self.end)
2046 def deviceChildrenAllPhases(self, devname):
2047 devlist = []
2048 for phase in self.sortedPhases():
2049 list = self.deviceChildren(devname, phase)
2050 for dev in sorted(list):
2051 if dev not in devlist:
2052 devlist.append(dev)
2053 return devlist
2054 def masterTopology(self, name, list, depth):
2055 node = DeviceNode(name, depth)
2056 for cname in list:
2057 # avoid recursions
2058 if name == cname:
2059 continue
2060 clist = self.deviceChildrenAllPhases(cname)
2061 cnode = self.masterTopology(cname, clist, depth+1)
2062 node.children.append(cnode)
2063 return node
2064 def printTopology(self, node):
2065 html = ''
2066 if node.name:
2067 info = ''
2068 drv = ''
2069 for phase in self.sortedPhases():
2070 list = self.dmesg[phase]['list']
2071 if node.name in list:
2072 s = list[node.name]['start']
2073 e = list[node.name]['end']
2074 if list[node.name]['drv']:
2075 drv = ' {'+list[node.name]['drv']+'}'
2076 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
2077 html += '<li><b>'+node.name+drv+'</b>'
2078 if info:
2079 html += '<ul>'+info+'</ul>'
2080 html += '</li>'
2081 if len(node.children) > 0:
2082 html += '<ul>'
2083 for cnode in node.children:
2084 html += self.printTopology(cnode)
2085 html += '</ul>'
2086 return html
2087 def rootDeviceList(self):
2088 # list of devices graphed
2089 real = []
2090 for phase in self.sortedPhases():
2091 list = self.dmesg[phase]['list']
2092 for dev in sorted(list):
2093 if list[dev]['pid'] >= 0 and dev not in real:
2094 real.append(dev)
2095 # list of top-most root devices
2096 rootlist = []
2097 for phase in self.sortedPhases():
2098 list = self.dmesg[phase]['list']
2099 for dev in sorted(list):
2100 pdev = list[dev]['par']
2101 pid = list[dev]['pid']
2102 if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
2103 continue
2104 if pdev and pdev not in real and pdev not in rootlist:
2105 rootlist.append(pdev)
2106 return rootlist
2107 def deviceTopology(self):
2108 rootlist = self.rootDeviceList()
2109 master = self.masterTopology('', rootlist, 0)
2110 return self.printTopology(master)
2111 def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
2112 # only select devices that will actually show up in html
2113 self.tdevlist = dict()
2114 for phase in self.dmesg:
2115 devlist = []
2116 list = self.dmesg[phase]['list']
2117 for dev in list:
2118 length = (list[dev]['end'] - list[dev]['start']) * 1000
2119 width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
2120 if length >= mindevlen:
2121 devlist.append(dev)
2122 self.tdevlist[phase] = devlist
2123 def addHorizontalDivider(self, devname, devend):
2124 phase = 'suspend_prepare'
2125 self.newAction(phase, devname, -2, '', \
2126 self.start, devend, '', ' sec', '')
2127 if phase not in self.tdevlist:
2128 self.tdevlist[phase] = []
2129 self.tdevlist[phase].append(devname)
2130 d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
2131 return d
2132 def addProcessUsageEvent(self, name, times):
2133 # get the start and end times for this process
2134 cpuexec = dict()
2135 tlast = start = end = -1
2136 for t in sorted(times):
2137 if tlast < 0:
2138 tlast = t
2139 continue
2140 if name in self.pstl[t] and self.pstl[t][name] > 0:
2141 if start < 0:
2142 start = tlast
2143 end, key = t, (tlast, t)
2144 maxj = (t - tlast) * 1024.0
2145 cpuexec[key] = min(1.0, float(self.pstl[t][name]) / maxj)
2146 tlast = t
2147 if start < 0 or end < 0:
2148 return
2149 # add a new action for this process and get the object
2150 out = self.newActionGlobal(name, start, end, -3)
2151 if out:
2152 phase, devname = out
2153 dev = self.dmesg[phase]['list'][devname]
2154 dev['cpuexec'] = cpuexec
2155 def createProcessUsageEvents(self):
2156 # get an array of process names and times
2157 proclist = {'sus': dict(), 'res': dict()}
2158 tdata = {'sus': [], 'res': []}
2159 for t in sorted(self.pstl):
2160 dir = 'sus' if t < self.tSuspended else 'res'
2161 for ps in sorted(self.pstl[t]):
2162 if ps not in proclist[dir]:
2163 proclist[dir][ps] = 0
2164 tdata[dir].append(t)
2165 # process the events for suspend and resume
2166 if len(proclist['sus']) > 0 or len(proclist['res']) > 0:
2167 sysvals.vprint('Process Execution:')
2168 for dir in ['sus', 'res']:
2169 for ps in sorted(proclist[dir]):
2170 self.addProcessUsageEvent(ps, tdata[dir])
2171 def handleEndMarker(self, time, msg=''):
2172 dm = self.dmesg
2173 self.setEnd(time, msg)
2174 self.initDevicegroups()
2175 # give suspend_prepare an end if needed
2176 if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2177 dm['suspend_prepare']['end'] = time
2178 # assume resume machine ends at next phase start
2179 if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2180 np = self.nextPhase('resume_machine', 1)
2181 if np:
2182 dm['resume_machine']['end'] = dm[np]['start']
2183 # if kernel resume end not found, assume its the end marker
2184 if self.tKernRes == 0.0:
2185 self.tKernRes = time
2186 # if kernel suspend start not found, assume its the end marker
2187 if self.tKernSus == 0.0:
2188 self.tKernSus = time
2189 # set resume complete to end at end marker
2190 if 'resume_complete' in dm:
2191 dm['resume_complete']['end'] = time
2192 def initcall_debug_call(self, line, quick=False):
2193 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2194 'PM: *calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2195 if not m:
2196 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2197 'calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2198 if not m:
2199 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
2200 '(?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)', line)
2201 if m:
2202 return True if quick else m.group('t', 'f', 'n', 'p')
2203 return False if quick else ('', '', '', '')
2204 def initcall_debug_return(self, line, quick=False):
2205 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: PM: '+\
2206 '.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2207 if not m:
2208 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2209 '.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2210 if not m:
2211 m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
2212 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', line)
2213 if m:
2214 return True if quick else m.group('t', 'f', 'dt')
2215 return False if quick else ('', '', '')
2216 def debugPrint(self):
2217 for p in self.sortedPhases():
2218 list = self.dmesg[p]['list']
2219 for devname in sorted(list):
2220 dev = list[devname]
2221 if 'ftrace' in dev:
2222 dev['ftrace'].debugPrint(' [%s]' % devname)
2223
2224# Class: DevFunction
2225# Description:
2226# A container for kprobe function data we want in the dev timeline
2227class DevFunction:
2228 def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2229 self.row = 0
2230 self.count = 1
2231 self.name = name
2232 self.args = args
2233 self.caller = caller
2234 self.ret = ret
2235 self.time = start
2236 self.length = end - start
2237 self.end = end
2238 self.ubiquitous = u
2239 self.proc = proc
2240 self.pid = pid
2241 self.color = color
2242 def title(self):
2243 cnt = ''
2244 if self.count > 1:
2245 cnt = '(x%d)' % self.count
2246 l = '%0.3fms' % (self.length * 1000)
2247 if self.ubiquitous:
2248 title = '%s(%s)%s <- %s, %s(%s)' % \
2249 (self.name, self.args, cnt, self.caller, self.ret, l)
2250 else:
2251 title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2252 return title.replace('"', '')
2253 def text(self):
2254 if self.count > 1:
2255 text = '%s(x%d)' % (self.name, self.count)
2256 else:
2257 text = self.name
2258 return text
2259 def repeat(self, tgt):
2260 # is the tgt call just a repeat of this call (e.g. are we in a loop)
2261 dt = self.time - tgt.end
2262 # only combine calls if -all- attributes are identical
2263 if tgt.caller == self.caller and \
2264 tgt.name == self.name and tgt.args == self.args and \
2265 tgt.proc == self.proc and tgt.pid == self.pid and \
2266 tgt.ret == self.ret and dt >= 0 and \
2267 dt <= sysvals.callloopmaxgap and \
2268 self.length < sysvals.callloopmaxlen:
2269 return True
2270 return False
2271
2272# Class: FTraceLine
2273# Description:
2274# A container for a single line of ftrace data. There are six basic types:
2275# callgraph line:
2276# call: " dpm_run_callback() {"
2277# return: " }"
2278# leaf: " dpm_run_callback();"
2279# trace event:
2280# tracing_mark_write: SUSPEND START or RESUME COMPLETE
2281# suspend_resume: phase or custom exec block data
2282# device_pm_callback: device callback info
2283class FTraceLine:
2284 def __init__(self, t, m='', d=''):
2285 self.length = 0.0
2286 self.fcall = False
2287 self.freturn = False
2288 self.fevent = False
2289 self.fkprobe = False
2290 self.depth = 0
2291 self.name = ''
2292 self.type = ''
2293 self.time = float(t)
2294 if not m and not d:
2295 return
2296 # is this a trace event
2297 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2298 if(d == 'traceevent'):
2299 # nop format trace event
2300 msg = m
2301 else:
2302 # function_graph format trace event
2303 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2304 msg = em.group('msg')
2305
2306 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
2307 if(emm):
2308 self.name = emm.group('msg')
2309 self.type = emm.group('call')
2310 else:
2311 self.name = msg
2312 km = re.match('^(?P<n>.*)_cal$', self.type)
2313 if km:
2314 self.fcall = True
2315 self.fkprobe = True
2316 self.type = km.group('n')
2317 return
2318 km = re.match('^(?P<n>.*)_ret$', self.type)
2319 if km:
2320 self.freturn = True
2321 self.fkprobe = True
2322 self.type = km.group('n')
2323 return
2324 self.fevent = True
2325 return
2326 # convert the duration to seconds
2327 if(d):
2328 self.length = float(d)/1000000
2329 # the indentation determines the depth
2330 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
2331 if(not match):
2332 return
2333 self.depth = self.getDepth(match.group('d'))
2334 m = match.group('o')
2335 # function return
2336 if(m[0] == '}'):
2337 self.freturn = True
2338 if(len(m) > 1):
2339 # includes comment with function name
2340 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
2341 if(match):
2342 self.name = match.group('n').strip()
2343 # function call
2344 else:
2345 self.fcall = True
2346 # function call with children
2347 if(m[-1] == '{'):
2348 match = re.match('^(?P<n>.*) *\(.*', m)
2349 if(match):
2350 self.name = match.group('n').strip()
2351 # function call with no children (leaf)
2352 elif(m[-1] == ';'):
2353 self.freturn = True
2354 match = re.match('^(?P<n>.*) *\(.*', m)
2355 if(match):
2356 self.name = match.group('n').strip()
2357 # something else (possibly a trace marker)
2358 else:
2359 self.name = m
2360 def isCall(self):
2361 return self.fcall and not self.freturn
2362 def isReturn(self):
2363 return self.freturn and not self.fcall
2364 def isLeaf(self):
2365 return self.fcall and self.freturn
2366 def getDepth(self, str):
2367 return len(str)/2
2368 def debugPrint(self, info=''):
2369 if self.isLeaf():
2370 pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2371 self.depth, self.name, self.length*1000000, info))
2372 elif self.freturn:
2373 pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2374 self.depth, self.name, self.length*1000000, info))
2375 else:
2376 pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2377 self.depth, self.name, self.length*1000000, info))
2378 def startMarker(self):
2379 # Is this the starting line of a suspend?
2380 if not self.fevent:
2381 return False
2382 if sysvals.usetracemarkers:
2383 if(self.name.startswith('SUSPEND START')):
2384 return True
2385 return False
2386 else:
2387 if(self.type == 'suspend_resume' and
2388 re.match('suspend_enter\[.*\] begin', self.name)):
2389 return True
2390 return False
2391 def endMarker(self):
2392 # Is this the ending line of a resume?
2393 if not self.fevent:
2394 return False
2395 if sysvals.usetracemarkers:
2396 if(self.name.startswith('RESUME COMPLETE')):
2397 return True
2398 return False
2399 else:
2400 if(self.type == 'suspend_resume' and
2401 re.match('thaw_processes\[.*\] end', self.name)):
2402 return True
2403 return False
2404
2405# Class: FTraceCallGraph
2406# Description:
2407# A container for the ftrace callgraph of a single recursive function.
2408# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2409# Each instance is tied to a single device in a single phase, and is
2410# comprised of an ordered list of FTraceLine objects
2411class FTraceCallGraph:
2412 vfname = 'missing_function_name'
2413 def __init__(self, pid, sv):
2414 self.id = ''
2415 self.invalid = False
2416 self.name = ''
2417 self.partial = False
2418 self.ignore = False
2419 self.start = -1.0
2420 self.end = -1.0
2421 self.list = []
2422 self.depth = 0
2423 self.pid = pid
2424 self.sv = sv
2425 def addLine(self, line):
2426 # if this is already invalid, just leave
2427 if(self.invalid):
2428 if(line.depth == 0 and line.freturn):
2429 return 1
2430 return 0
2431 # invalidate on bad depth
2432 if(self.depth < 0):
2433 self.invalidate(line)
2434 return 0
2435 # ignore data til we return to the current depth
2436 if self.ignore:
2437 if line.depth > self.depth:
2438 return 0
2439 else:
2440 self.list[-1].freturn = True
2441 self.list[-1].length = line.time - self.list[-1].time
2442 self.ignore = False
2443 # if this is a return at self.depth, no more work is needed
2444 if line.depth == self.depth and line.isReturn():
2445 if line.depth == 0:
2446 self.end = line.time
2447 return 1
2448 return 0
2449 # compare current depth with this lines pre-call depth
2450 prelinedep = line.depth
2451 if line.isReturn():
2452 prelinedep += 1
2453 last = 0
2454 lasttime = line.time
2455 if len(self.list) > 0:
2456 last = self.list[-1]
2457 lasttime = last.time
2458 if last.isLeaf():
2459 lasttime += last.length
2460 # handle low misalignments by inserting returns
2461 mismatch = prelinedep - self.depth
2462 warning = self.sv.verbose and abs(mismatch) > 1
2463 info = []
2464 if mismatch < 0:
2465 idx = 0
2466 # add return calls to get the depth down
2467 while prelinedep < self.depth:
2468 self.depth -= 1
2469 if idx == 0 and last and last.isCall():
2470 # special case, turn last call into a leaf
2471 last.depth = self.depth
2472 last.freturn = True
2473 last.length = line.time - last.time
2474 if warning:
2475 info.append(('[make leaf]', last))
2476 else:
2477 vline = FTraceLine(lasttime)
2478 vline.depth = self.depth
2479 vline.name = self.vfname
2480 vline.freturn = True
2481 self.list.append(vline)
2482 if warning:
2483 if idx == 0:
2484 info.append(('', last))
2485 info.append(('[add return]', vline))
2486 idx += 1
2487 if warning:
2488 info.append(('', line))
2489 # handle high misalignments by inserting calls
2490 elif mismatch > 0:
2491 idx = 0
2492 if warning:
2493 info.append(('', last))
2494 # add calls to get the depth up
2495 while prelinedep > self.depth:
2496 if idx == 0 and line.isReturn():
2497 # special case, turn this return into a leaf
2498 line.fcall = True
2499 prelinedep -= 1
2500 if warning:
2501 info.append(('[make leaf]', line))
2502 else:
2503 vline = FTraceLine(lasttime)
2504 vline.depth = self.depth
2505 vline.name = self.vfname
2506 vline.fcall = True
2507 self.list.append(vline)
2508 self.depth += 1
2509 if not last:
2510 self.start = vline.time
2511 if warning:
2512 info.append(('[add call]', vline))
2513 idx += 1
2514 if warning and ('[make leaf]', line) not in info:
2515 info.append(('', line))
2516 if warning:
2517 pprint('WARNING: ftrace data missing, corrections made:')
2518 for i in info:
2519 t, obj = i
2520 if obj:
2521 obj.debugPrint(t)
2522 # process the call and set the new depth
2523 skipadd = False
2524 md = self.sv.max_graph_depth
2525 if line.isCall():
2526 # ignore blacklisted/overdepth funcs
2527 if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2528 self.ignore = True
2529 else:
2530 self.depth += 1
2531 elif line.isReturn():
2532 self.depth -= 1
2533 # remove blacklisted/overdepth/empty funcs that slipped through
2534 if (last and last.isCall() and last.depth == line.depth) or \
2535 (md and last and last.depth >= md) or \
2536 (line.name in self.sv.cgblacklist):
2537 while len(self.list) > 0 and self.list[-1].depth > line.depth:
2538 self.list.pop(-1)
2539 if len(self.list) == 0:
2540 self.invalid = True
2541 return 1
2542 self.list[-1].freturn = True
2543 self.list[-1].length = line.time - self.list[-1].time
2544 self.list[-1].name = line.name
2545 skipadd = True
2546 if len(self.list) < 1:
2547 self.start = line.time
2548 # check for a mismatch that returned all the way to callgraph end
2549 res = 1
2550 if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2551 line = self.list[-1]
2552 skipadd = True
2553 res = -1
2554 if not skipadd:
2555 self.list.append(line)
2556 if(line.depth == 0 and line.freturn):
2557 if(self.start < 0):
2558 self.start = line.time
2559 self.end = line.time
2560 if line.fcall:
2561 self.end += line.length
2562 if self.list[0].name == self.vfname:
2563 self.invalid = True
2564 if res == -1:
2565 self.partial = True
2566 return res
2567 return 0
2568 def invalidate(self, line):
2569 if(len(self.list) > 0):
2570 first = self.list[0]
2571 self.list = []
2572 self.list.append(first)
2573 self.invalid = True
2574 id = 'task %s' % (self.pid)
2575 window = '(%f - %f)' % (self.start, line.time)
2576 if(self.depth < 0):
2577 pprint('Data misalignment for '+id+\
2578 ' (buffer overflow), ignoring this callback')
2579 else:
2580 pprint('Too much data for '+id+\
2581 ' '+window+', ignoring this callback')
2582 def slice(self, dev):
2583 minicg = FTraceCallGraph(dev['pid'], self.sv)
2584 minicg.name = self.name
2585 mydepth = -1
2586 good = False
2587 for l in self.list:
2588 if(l.time < dev['start'] or l.time > dev['end']):
2589 continue
2590 if mydepth < 0:
2591 if l.name == 'mutex_lock' and l.freturn:
2592 mydepth = l.depth
2593 continue
2594 elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2595 good = True
2596 break
2597 l.depth -= mydepth
2598 minicg.addLine(l)
2599 if not good or len(minicg.list) < 1:
2600 return 0
2601 return minicg
2602 def repair(self, enddepth):
2603 # bring the depth back to 0 with additional returns
2604 fixed = False
2605 last = self.list[-1]
2606 for i in reversed(range(enddepth)):
2607 t = FTraceLine(last.time)
2608 t.depth = i
2609 t.freturn = True
2610 fixed = self.addLine(t)
2611 if fixed != 0:
2612 self.end = last.time
2613 return True
2614 return False
2615 def postProcess(self):
2616 if len(self.list) > 0:
2617 self.name = self.list[0].name
2618 stack = dict()
2619 cnt = 0
2620 last = 0
2621 for l in self.list:
2622 # ftrace bug: reported duration is not reliable
2623 # check each leaf and clip it at max possible length
2624 if last and last.isLeaf():
2625 if last.length > l.time - last.time:
2626 last.length = l.time - last.time
2627 if l.isCall():
2628 stack[l.depth] = l
2629 cnt += 1
2630 elif l.isReturn():
2631 if(l.depth not in stack):
2632 if self.sv.verbose:
2633 pprint('Post Process Error: Depth missing')
2634 l.debugPrint()
2635 return False
2636 # calculate call length from call/return lines
2637 cl = stack[l.depth]
2638 cl.length = l.time - cl.time
2639 if cl.name == self.vfname:
2640 cl.name = l.name
2641 stack.pop(l.depth)
2642 l.length = 0
2643 cnt -= 1
2644 last = l
2645 if(cnt == 0):
2646 # trace caught the whole call tree
2647 return True
2648 elif(cnt < 0):
2649 if self.sv.verbose:
2650 pprint('Post Process Error: Depth is less than 0')
2651 return False
2652 # trace ended before call tree finished
2653 return self.repair(cnt)
2654 def deviceMatch(self, pid, data):
2655 found = ''
2656 # add the callgraph data to the device hierarchy
2657 borderphase = {
2658 'dpm_prepare': 'suspend_prepare',
2659 'dpm_complete': 'resume_complete'
2660 }
2661 if(self.name in borderphase):
2662 p = borderphase[self.name]
2663 list = data.dmesg[p]['list']
2664 for devname in list:
2665 dev = list[devname]
2666 if(pid == dev['pid'] and
2667 self.start <= dev['start'] and
2668 self.end >= dev['end']):
2669 cg = self.slice(dev)
2670 if cg:
2671 dev['ftrace'] = cg
2672 found = devname
2673 return found
2674 for p in data.sortedPhases():
2675 if(data.dmesg[p]['start'] <= self.start and
2676 self.start <= data.dmesg[p]['end']):
2677 list = data.dmesg[p]['list']
2678 for devname in sorted(list, key=lambda k:list[k]['start']):
2679 dev = list[devname]
2680 if(pid == dev['pid'] and
2681 self.start <= dev['start'] and
2682 self.end >= dev['end']):
2683 dev['ftrace'] = self
2684 found = devname
2685 break
2686 break
2687 return found
2688 def newActionFromFunction(self, data):
2689 name = self.name
2690 if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2691 return
2692 fs = self.start
2693 fe = self.end
2694 if fs < data.start or fe > data.end:
2695 return
2696 phase = ''
2697 for p in data.sortedPhases():
2698 if(data.dmesg[p]['start'] <= self.start and
2699 self.start < data.dmesg[p]['end']):
2700 phase = p
2701 break
2702 if not phase:
2703 return
2704 out = data.newActionGlobal(name, fs, fe, -2)
2705 if out:
2706 phase, myname = out
2707 data.dmesg[phase]['list'][myname]['ftrace'] = self
2708 def debugPrint(self, info=''):
2709 pprint('%s pid=%d [%f - %f] %.3f us' % \
2710 (self.name, self.pid, self.start, self.end,
2711 (self.end - self.start)*1000000))
2712 for l in self.list:
2713 if l.isLeaf():
2714 pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2715 l.depth, l.name, l.length*1000000, info))
2716 elif l.freturn:
2717 pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2718 l.depth, l.name, l.length*1000000, info))
2719 else:
2720 pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2721 l.depth, l.name, l.length*1000000, info))
2722 pprint(' ')
2723
2724class DevItem:
2725 def __init__(self, test, phase, dev):
2726 self.test = test
2727 self.phase = phase
2728 self.dev = dev
2729 def isa(self, cls):
2730 if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2731 return True
2732 return False
2733
2734# Class: Timeline
2735# Description:
2736# A container for a device timeline which calculates
2737# all the html properties to display it correctly
2738class Timeline:
2739 html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2740 html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2741 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2742 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2743 html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}"> {2}</div>\n'
2744 def __init__(self, rowheight, scaleheight):
2745 self.html = ''
2746 self.height = 0 # total timeline height
2747 self.scaleH = scaleheight # timescale (top) row height
2748 self.rowH = rowheight # device row height
2749 self.bodyH = 0 # body height
2750 self.rows = 0 # total timeline rows
2751 self.rowlines = dict()
2752 self.rowheight = dict()
2753 def createHeader(self, sv, stamp):
2754 if(not stamp['time']):
2755 return
2756 self.html += '<div class="version"><a href="https://01.org/pm-graph">%s v%s</a></div>' \
2757 % (sv.title, sv.version)
2758 if sv.logmsg and sv.testlog:
2759 self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2760 if sv.dmesglog:
2761 self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2762 if sv.ftracelog:
2763 self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2764 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2765 self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2766 stamp['mode'], stamp['time'])
2767 if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2768 stamp['man'] and stamp['plat'] and stamp['cpu']:
2769 headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2770 self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2771
2772 # Function: getDeviceRows
2773 # Description:
2774 # determine how may rows the device funcs will take
2775 # Arguments:
2776 # rawlist: the list of devices/actions for a single phase
2777 # Output:
2778 # The total number of rows needed to display this phase of the timeline
2779 def getDeviceRows(self, rawlist):
2780 # clear all rows and set them to undefined
2781 sortdict = dict()
2782 for item in rawlist:
2783 item.row = -1
2784 sortdict[item] = item.length
2785 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2786 remaining = len(sortlist)
2787 rowdata = dict()
2788 row = 1
2789 # try to pack each row with as many ranges as possible
2790 while(remaining > 0):
2791 if(row not in rowdata):
2792 rowdata[row] = []
2793 for i in sortlist:
2794 if(i.row >= 0):
2795 continue
2796 s = i.time
2797 e = i.time + i.length
2798 valid = True
2799 for ritem in rowdata[row]:
2800 rs = ritem.time
2801 re = ritem.time + ritem.length
2802 if(not (((s <= rs) and (e <= rs)) or
2803 ((s >= re) and (e >= re)))):
2804 valid = False
2805 break
2806 if(valid):
2807 rowdata[row].append(i)
2808 i.row = row
2809 remaining -= 1
2810 row += 1
2811 return row
2812 # Function: getPhaseRows
2813 # Description:
2814 # Organize the timeline entries into the smallest
2815 # number of rows possible, with no entry overlapping
2816 # Arguments:
2817 # devlist: the list of devices/actions in a group of contiguous phases
2818 # Output:
2819 # The total number of rows needed to display this phase of the timeline
2820 def getPhaseRows(self, devlist, row=0, sortby='length'):
2821 # clear all rows and set them to undefined
2822 remaining = len(devlist)
2823 rowdata = dict()
2824 sortdict = dict()
2825 myphases = []
2826 # initialize all device rows to -1 and calculate devrows
2827 for item in devlist:
2828 dev = item.dev
2829 tp = (item.test, item.phase)
2830 if tp not in myphases:
2831 myphases.append(tp)
2832 dev['row'] = -1
2833 if sortby == 'start':
2834 # sort by start 1st, then length 2nd
2835 sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2836 else:
2837 # sort by length 1st, then name 2nd
2838 sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2839 if 'src' in dev:
2840 dev['devrows'] = self.getDeviceRows(dev['src'])
2841 # sort the devlist by length so that large items graph on top
2842 sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2843 orderedlist = []
2844 for item in sortlist:
2845 if item.dev['pid'] == -2:
2846 orderedlist.append(item)
2847 for item in sortlist:
2848 if item not in orderedlist:
2849 orderedlist.append(item)
2850 # try to pack each row with as many devices as possible
2851 while(remaining > 0):
2852 rowheight = 1
2853 if(row not in rowdata):
2854 rowdata[row] = []
2855 for item in orderedlist:
2856 dev = item.dev
2857 if(dev['row'] < 0):
2858 s = dev['start']
2859 e = dev['end']
2860 valid = True
2861 for ritem in rowdata[row]:
2862 rs = ritem.dev['start']
2863 re = ritem.dev['end']
2864 if(not (((s <= rs) and (e <= rs)) or
2865 ((s >= re) and (e >= re)))):
2866 valid = False
2867 break
2868 if(valid):
2869 rowdata[row].append(item)
2870 dev['row'] = row
2871 remaining -= 1
2872 if 'devrows' in dev and dev['devrows'] > rowheight:
2873 rowheight = dev['devrows']
2874 for t, p in myphases:
2875 if t not in self.rowlines or t not in self.rowheight:
2876 self.rowlines[t] = dict()
2877 self.rowheight[t] = dict()
2878 if p not in self.rowlines[t] or p not in self.rowheight[t]:
2879 self.rowlines[t][p] = dict()
2880 self.rowheight[t][p] = dict()
2881 rh = self.rowH
2882 # section headers should use a different row height
2883 if len(rowdata[row]) == 1 and \
2884 'htmlclass' in rowdata[row][0].dev and \
2885 'sec' in rowdata[row][0].dev['htmlclass']:
2886 rh = 15
2887 self.rowlines[t][p][row] = rowheight
2888 self.rowheight[t][p][row] = rowheight * rh
2889 row += 1
2890 if(row > self.rows):
2891 self.rows = int(row)
2892 return row
2893 def phaseRowHeight(self, test, phase, row):
2894 return self.rowheight[test][phase][row]
2895 def phaseRowTop(self, test, phase, row):
2896 top = 0
2897 for i in sorted(self.rowheight[test][phase]):
2898 if i >= row:
2899 break
2900 top += self.rowheight[test][phase][i]
2901 return top
2902 def calcTotalRows(self):
2903 # Calculate the heights and offsets for the header and rows
2904 maxrows = 0
2905 standardphases = []
2906 for t in self.rowlines:
2907 for p in self.rowlines[t]:
2908 total = 0
2909 for i in sorted(self.rowlines[t][p]):
2910 total += self.rowlines[t][p][i]
2911 if total > maxrows:
2912 maxrows = total
2913 if total == len(self.rowlines[t][p]):
2914 standardphases.append((t, p))
2915 self.height = self.scaleH + (maxrows*self.rowH)
2916 self.bodyH = self.height - self.scaleH
2917 # if there is 1 line per row, draw them the standard way
2918 for t, p in standardphases:
2919 for i in sorted(self.rowheight[t][p]):
2920 self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2921 def createZoomBox(self, mode='command', testcount=1):
2922 # Create bounding box, add buttons
2923 html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2924 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2925 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2926 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2927 if mode != 'command':
2928 if testcount > 1:
2929 self.html += html_devlist2
2930 self.html += html_devlist1.format('1')
2931 else:
2932 self.html += html_devlist1.format('')
2933 self.html += html_zoombox
2934 self.html += html_timeline.format('dmesg', self.height)
2935 # Function: createTimeScale
2936 # Description:
2937 # Create the timescale for a timeline block
2938 # Arguments:
2939 # m0: start time (mode begin)
2940 # mMax: end time (mode end)
2941 # tTotal: total timeline time
2942 # mode: suspend or resume
2943 # Output:
2944 # The html code needed to display the time scale
2945 def createTimeScale(self, m0, mMax, tTotal, mode):
2946 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2947 rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2948 output = '<div class="timescale">\n'
2949 # set scale for timeline
2950 mTotal = mMax - m0
2951 tS = 0.1
2952 if(tTotal <= 0):
2953 return output+'</div>\n'
2954 if(tTotal > 4):
2955 tS = 1
2956 divTotal = int(mTotal/tS) + 1
2957 divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2958 for i in range(divTotal):
2959 htmlline = ''
2960 if(mode == 'suspend'):
2961 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2962 val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2963 if(i == divTotal - 1):
2964 val = mode
2965 htmlline = timescale.format(pos, val)
2966 else:
2967 pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2968 val = '%0.fms' % (float(i)*tS*1000)
2969 htmlline = timescale.format(pos, val)
2970 if(i == 0):
2971 htmlline = rline.format(mode)
2972 output += htmlline
2973 self.html += output+'</div>\n'
2974
2975# Class: TestProps
2976# Description:
2977# A list of values describing the properties of these test runs
2978class TestProps:
2979 stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
2980 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
2981 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
2982 wififmt = '^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
2983 tstatfmt = '^# turbostat (?P<t>\S*)'
2984 testerrfmt = '^# enter_sleep_error (?P<e>.*)'
2985 sysinfofmt = '^# sysinfo .*'
2986 cmdlinefmt = '^# command \| (?P<cmd>.*)'
2987 kparamsfmt = '^# kparams \| (?P<kp>.*)'
2988 devpropfmt = '# Device Properties: .*'
2989 pinfofmt = '# platform-(?P<val>[a-z,A-Z,0-9,_]*): (?P<info>.*)'
2990 tracertypefmt = '# tracer: (?P<t>.*)'
2991 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
2992 procexecfmt = 'ps - (?P<ps>.*)$'
2993 procmultifmt = '@(?P<n>[0-9]*)\|(?P<ps>.*)$'
2994 ftrace_line_fmt_fg = \
2995 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
2996 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
2997 '[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
2998 ftrace_line_fmt_nop = \
2999 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
3000 '(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
3001 '(?P<msg>.*)'
3002 machinesuspend = 'machine_suspend\[.*'
3003 multiproclist = dict()
3004 multiproctime = 0.0
3005 multiproccnt = 0
3006 def __init__(self):
3007 self.stamp = ''
3008 self.sysinfo = ''
3009 self.cmdline = ''
3010 self.testerror = []
3011 self.turbostat = []
3012 self.wifi = []
3013 self.fwdata = []
3014 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3015 self.cgformat = False
3016 self.data = 0
3017 self.ktemp = dict()
3018 def setTracerType(self, tracer):
3019 if(tracer == 'function_graph'):
3020 self.cgformat = True
3021 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
3022 elif(tracer == 'nop'):
3023 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3024 else:
3025 doError('Invalid tracer format: [%s]' % tracer)
3026 def stampInfo(self, line, sv):
3027 if re.match(self.stampfmt, line):
3028 self.stamp = line
3029 return True
3030 elif re.match(self.sysinfofmt, line):
3031 self.sysinfo = line
3032 return True
3033 elif re.match(self.tstatfmt, line):
3034 self.turbostat.append(line)
3035 return True
3036 elif re.match(self.wififmt, line):
3037 self.wifi.append(line)
3038 return True
3039 elif re.match(self.testerrfmt, line):
3040 self.testerror.append(line)
3041 return True
3042 elif re.match(self.firmwarefmt, line):
3043 self.fwdata.append(line)
3044 return True
3045 elif(re.match(self.devpropfmt, line)):
3046 self.parseDevprops(line, sv)
3047 return True
3048 elif(re.match(self.pinfofmt, line)):
3049 self.parsePlatformInfo(line, sv)
3050 return True
3051 m = re.match(self.cmdlinefmt, line)
3052 if m:
3053 self.cmdline = m.group('cmd')
3054 return True
3055 m = re.match(self.tracertypefmt, line)
3056 if(m):
3057 self.setTracerType(m.group('t'))
3058 return True
3059 return False
3060 def parseStamp(self, data, sv):
3061 # global test data
3062 m = re.match(self.stampfmt, self.stamp)
3063 if not self.stamp or not m:
3064 doError('data does not include the expected stamp')
3065 data.stamp = {'time': '', 'host': '', 'mode': ''}
3066 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
3067 int(m.group('d')), int(m.group('H')), int(m.group('M')),
3068 int(m.group('S')))
3069 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
3070 data.stamp['host'] = m.group('host')
3071 data.stamp['mode'] = m.group('mode')
3072 data.stamp['kernel'] = m.group('kernel')
3073 if re.match(self.sysinfofmt, self.sysinfo):
3074 for f in self.sysinfo.split('|'):
3075 if '#' in f:
3076 continue
3077 tmp = f.strip().split(':', 1)
3078 key = tmp[0]
3079 val = tmp[1]
3080 data.stamp[key] = val
3081 sv.hostname = data.stamp['host']
3082 sv.suspendmode = data.stamp['mode']
3083 if sv.suspendmode == 'freeze':
3084 self.machinesuspend = 'timekeeping_freeze\[.*'
3085 else:
3086 self.machinesuspend = 'machine_suspend\[.*'
3087 if sv.suspendmode == 'command' and sv.ftracefile != '':
3088 modes = ['on', 'freeze', 'standby', 'mem', 'disk']
3089 fp = sv.openlog(sv.ftracefile, 'r')
3090 for line in fp:
3091 m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
3092 if m and m.group('mode') in ['1', '2', '3', '4']:
3093 sv.suspendmode = modes[int(m.group('mode'))]
3094 data.stamp['mode'] = sv.suspendmode
3095 break
3096 fp.close()
3097 sv.cmdline = self.cmdline
3098 if not sv.stamp:
3099 sv.stamp = data.stamp
3100 # firmware data
3101 if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
3102 m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
3103 if m:
3104 data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
3105 if(data.fwSuspend > 0 or data.fwResume > 0):
3106 data.fwValid = True
3107 # turbostat data
3108 if len(self.turbostat) > data.testnumber:
3109 m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
3110 if m:
3111 data.turbostat = m.group('t')
3112 # wifi data
3113 if len(self.wifi) > data.testnumber:
3114 m = re.match(self.wififmt, self.wifi[data.testnumber])
3115 if m:
3116 data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
3117 'time': float(m.group('t'))}
3118 data.stamp['wifi'] = m.group('d')
3119 # sleep mode enter errors
3120 if len(self.testerror) > data.testnumber:
3121 m = re.match(self.testerrfmt, self.testerror[data.testnumber])
3122 if m:
3123 data.enterfail = m.group('e')
3124 def devprops(self, data):
3125 props = dict()
3126 devlist = data.split(';')
3127 for dev in devlist:
3128 f = dev.split(',')
3129 if len(f) < 3:
3130 continue
3131 dev = f[0]
3132 props[dev] = DevProps()
3133 props[dev].altname = f[1]
3134 if int(f[2]):
3135 props[dev].isasync = True
3136 else:
3137 props[dev].isasync = False
3138 return props
3139 def parseDevprops(self, line, sv):
3140 idx = line.index(': ') + 2
3141 if idx >= len(line):
3142 return
3143 props = self.devprops(line[idx:])
3144 if sv.suspendmode == 'command' and 'testcommandstring' in props:
3145 sv.testcommand = props['testcommandstring'].altname
3146 sv.devprops = props
3147 def parsePlatformInfo(self, line, sv):
3148 m = re.match(self.pinfofmt, line)
3149 if not m:
3150 return
3151 name, info = m.group('val'), m.group('info')
3152 if name == 'devinfo':
3153 sv.devprops = self.devprops(sv.b64unzip(info))
3154 return
3155 elif name == 'testcmd':
3156 sv.testcommand = info
3157 return
3158 field = info.split('|')
3159 if len(field) < 2:
3160 return
3161 cmdline = field[0].strip()
3162 output = sv.b64unzip(field[1].strip())
3163 sv.platinfo.append([name, cmdline, output])
3164
3165# Class: TestRun
3166# Description:
3167# A container for a suspend/resume test run. This is necessary as
3168# there could be more than one, and they need to be separate.
3169class TestRun:
3170 def __init__(self, dataobj):
3171 self.data = dataobj
3172 self.ftemp = dict()
3173 self.ttemp = dict()
3174
3175class ProcessMonitor:
3176 maxchars = 512
3177 def __init__(self):
3178 self.proclist = dict()
3179 self.running = False
3180 def procstat(self):
3181 c = ['cat /proc/[1-9]*/stat 2>/dev/null']
3182 process = Popen(c, shell=True, stdout=PIPE)
3183 running = dict()
3184 for line in process.stdout:
3185 data = ascii(line).split()
3186 pid = data[0]
3187 name = re.sub('[()]', '', data[1])
3188 user = int(data[13])
3189 kern = int(data[14])
3190 kjiff = ujiff = 0
3191 if pid not in self.proclist:
3192 self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3193 else:
3194 val = self.proclist[pid]
3195 ujiff = user - val['user']
3196 kjiff = kern - val['kern']
3197 val['user'] = user
3198 val['kern'] = kern
3199 if ujiff > 0 or kjiff > 0:
3200 running[pid] = ujiff + kjiff
3201 process.wait()
3202 out = ['']
3203 for pid in running:
3204 jiffies = running[pid]
3205 val = self.proclist[pid]
3206 if len(out[-1]) > self.maxchars:
3207 out.append('')
3208 elif len(out[-1]) > 0:
3209 out[-1] += ','
3210 out[-1] += '%s-%s %d' % (val['name'], pid, jiffies)
3211 if len(out) > 1:
3212 for line in out:
3213 sysvals.fsetVal('ps - @%d|%s' % (len(out), line), 'trace_marker')
3214 else:
3215 sysvals.fsetVal('ps - %s' % out[0], 'trace_marker')
3216 def processMonitor(self, tid):
3217 while self.running:
3218 self.procstat()
3219 def start(self):
3220 self.thread = Thread(target=self.processMonitor, args=(0,))
3221 self.running = True
3222 self.thread.start()
3223 def stop(self):
3224 self.running = False
3225
3226# ----------------- FUNCTIONS --------------------
3227
3228# Function: doesTraceLogHaveTraceEvents
3229# Description:
3230# Quickly determine if the ftrace log has all of the trace events,
3231# markers, and/or kprobes required for primary parsing.
3232def doesTraceLogHaveTraceEvents():
3233 kpcheck = ['_cal: (', '_ret: (']
3234 techeck = ['suspend_resume', 'device_pm_callback', 'tracing_mark_write']
3235 tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3236 sysvals.usekprobes = False
3237 fp = sysvals.openlog(sysvals.ftracefile, 'r')
3238 for line in fp:
3239 # check for kprobes
3240 if not sysvals.usekprobes:
3241 for i in kpcheck:
3242 if i in line:
3243 sysvals.usekprobes = True
3244 # check for all necessary trace events
3245 check = techeck[:]
3246 for i in techeck:
3247 if i in line:
3248 check.remove(i)
3249 techeck = check
3250 # check for all necessary trace markers
3251 check = tmcheck[:]
3252 for i in tmcheck:
3253 if i in line:
3254 check.remove(i)
3255 tmcheck = check
3256 fp.close()
3257 sysvals.usetraceevents = True if len(techeck) < 3 else False
3258 sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
3259
3260# Function: appendIncompleteTraceLog
3261# Description:
3262# Adds callgraph data which lacks trace event data. This is only
3263# for timelines generated from 3.15 or older
3264# Arguments:
3265# testruns: the array of Data objects obtained from parseKernelLog
3266def appendIncompleteTraceLog(testruns):
3267 # create TestRun vessels for ftrace parsing
3268 testcnt = len(testruns)
3269 testidx = 0
3270 testrun = []
3271 for data in testruns:
3272 testrun.append(TestRun(data))
3273
3274 # extract the callgraph and traceevent data
3275 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3276 os.path.basename(sysvals.ftracefile))
3277 tp = TestProps()
3278 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3279 data = 0
3280 for line in tf:
3281 # remove any latent carriage returns
3282 line = line.replace('\r\n', '')
3283 if tp.stampInfo(line, sysvals):
3284 continue
3285 # parse only valid lines, if this is not one move on
3286 m = re.match(tp.ftrace_line_fmt, line)
3287 if(not m):
3288 continue
3289 # gather the basic message data from the line
3290 m_time = m.group('time')
3291 m_pid = m.group('pid')
3292 m_msg = m.group('msg')
3293 if(tp.cgformat):
3294 m_param3 = m.group('dur')
3295 else:
3296 m_param3 = 'traceevent'
3297 if(m_time and m_pid and m_msg):
3298 t = FTraceLine(m_time, m_msg, m_param3)
3299 pid = int(m_pid)
3300 else:
3301 continue
3302 # the line should be a call, return, or event
3303 if(not t.fcall and not t.freturn and not t.fevent):
3304 continue
3305 # look for the suspend start marker
3306 if(t.startMarker()):
3307 data = testrun[testidx].data
3308 tp.parseStamp(data, sysvals)
3309 data.setStart(t.time, t.name)
3310 continue
3311 if(not data):
3312 continue
3313 # find the end of resume
3314 if(t.endMarker()):
3315 data.setEnd(t.time, t.name)
3316 testidx += 1
3317 if(testidx >= testcnt):
3318 break
3319 continue
3320 # trace event processing
3321 if(t.fevent):
3322 continue
3323 # call/return processing
3324 elif sysvals.usecallgraph:
3325 # create a callgraph object for the data
3326 if(pid not in testrun[testidx].ftemp):
3327 testrun[testidx].ftemp[pid] = []
3328 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3329 # when the call is finished, see which device matches it
3330 cg = testrun[testidx].ftemp[pid][-1]
3331 res = cg.addLine(t)
3332 if(res != 0):
3333 testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3334 if(res == -1):
3335 testrun[testidx].ftemp[pid][-1].addLine(t)
3336 tf.close()
3337
3338 for test in testrun:
3339 # add the callgraph data to the device hierarchy
3340 for pid in test.ftemp:
3341 for cg in test.ftemp[pid]:
3342 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3343 continue
3344 if(not cg.postProcess()):
3345 id = 'task %s cpu %s' % (pid, m.group('cpu'))
3346 sysvals.vprint('Sanity check failed for '+\
3347 id+', ignoring this callback')
3348 continue
3349 callstart = cg.start
3350 callend = cg.end
3351 for p in test.data.sortedPhases():
3352 if(test.data.dmesg[p]['start'] <= callstart and
3353 callstart <= test.data.dmesg[p]['end']):
3354 list = test.data.dmesg[p]['list']
3355 for devname in list:
3356 dev = list[devname]
3357 if(pid == dev['pid'] and
3358 callstart <= dev['start'] and
3359 callend >= dev['end']):
3360 dev['ftrace'] = cg
3361 break
3362
3363# Function: loadTraceLog
3364# Description:
3365# load the ftrace file into memory and fix up any ordering issues
3366# Output:
3367# TestProps instance and an array of lines in proper order
3368def loadTraceLog():
3369 tp, data, lines, trace = TestProps(), dict(), [], []
3370 tf = sysvals.openlog(sysvals.ftracefile, 'r')
3371 for line in tf:
3372 # remove any latent carriage returns
3373 line = line.replace('\r\n', '')
3374 if tp.stampInfo(line, sysvals):
3375 continue
3376 # ignore all other commented lines
3377 if line[0] == '#':
3378 continue
3379 # ftrace line: parse only valid lines
3380 m = re.match(tp.ftrace_line_fmt, line)
3381 if(not m):
3382 continue
3383 dur = m.group('dur') if tp.cgformat else 'traceevent'
3384 info = (m.group('time'), m.group('proc'), m.group('pid'),
3385 m.group('msg'), dur)
3386 # group the data by timestamp
3387 t = float(info[0])
3388 if t in data:
3389 data[t].append(info)
3390 else:
3391 data[t] = [info]
3392 # we only care about trace event ordering
3393 if (info[3].startswith('suspend_resume:') or \
3394 info[3].startswith('tracing_mark_write:')) and t not in trace:
3395 trace.append(t)
3396 tf.close()
3397 for t in sorted(data):
3398 first, last, blk = [], [], data[t]
3399 if len(blk) > 1 and t in trace:
3400 # move certain lines to the start or end of a timestamp block
3401 for i in range(len(blk)):
3402 if 'SUSPEND START' in blk[i][3]:
3403 first.append(i)
3404 elif re.match('.* timekeeping_freeze.*begin', blk[i][3]):
3405 last.append(i)
3406 elif re.match('.* timekeeping_freeze.*end', blk[i][3]):
3407 first.append(i)
3408 elif 'RESUME COMPLETE' in blk[i][3]:
3409 last.append(i)
3410 if len(first) == 1 and len(last) == 0:
3411 blk.insert(0, blk.pop(first[0]))
3412 elif len(last) == 1 and len(first) == 0:
3413 blk.append(blk.pop(last[0]))
3414 for info in blk:
3415 lines.append(info)
3416 return (tp, lines)
3417
3418# Function: parseTraceLog
3419# Description:
3420# Analyze an ftrace log output file generated from this app during
3421# the execution phase. Used when the ftrace log is the primary data source
3422# and includes the suspend_resume and device_pm_callback trace events
3423# The ftrace filename is taken from sysvals
3424# Output:
3425# An array of Data objects
3426def parseTraceLog(live=False):
3427 sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3428 os.path.basename(sysvals.ftracefile))
3429 if(os.path.exists(sysvals.ftracefile) == False):
3430 doError('%s does not exist' % sysvals.ftracefile)
3431 if not live:
3432 sysvals.setupAllKprobes()
3433 ksuscalls = ['ksys_sync', 'pm_prepare_console']
3434 krescalls = ['pm_restore_console']
3435 tracewatch = ['irq_wakeup']
3436 if sysvals.usekprobes:
3437 tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3438 'syscore_resume', 'resume_console', 'thaw_processes', 'CPU_ON',
3439 'CPU_OFF', 'acpi_suspend']
3440
3441 # extract the callgraph and traceevent data
3442 s2idle_enter = hwsus = False
3443 testruns, testdata = [], []
3444 testrun, data, limbo = 0, 0, True
3445 phase = 'suspend_prepare'
3446 tp, tf = loadTraceLog()
3447 for m_time, m_proc, m_pid, m_msg, m_param3 in tf:
3448 # gather the basic message data from the line
3449 if(m_time and m_pid and m_msg):
3450 t = FTraceLine(m_time, m_msg, m_param3)
3451 pid = int(m_pid)
3452 else:
3453 continue
3454 # the line should be a call, return, or event
3455 if(not t.fcall and not t.freturn and not t.fevent):
3456 continue
3457 # find the start of suspend
3458 if(t.startMarker()):
3459 data, limbo = Data(len(testdata)), False
3460 testdata.append(data)
3461 testrun = TestRun(data)
3462 testruns.append(testrun)
3463 tp.parseStamp(data, sysvals)
3464 data.setStart(t.time, t.name)
3465 data.first_suspend_prepare = True
3466 phase = data.setPhase('suspend_prepare', t.time, True)
3467 continue
3468 if(not data or limbo):
3469 continue
3470 # process cpu exec line
3471 if t.type == 'tracing_mark_write':
3472 if t.name == 'CMD COMPLETE' and data.tKernRes == 0:
3473 data.tKernRes = t.time
3474 m = re.match(tp.procexecfmt, t.name)
3475 if(m):
3476 parts, msg = 1, m.group('ps')
3477 m = re.match(tp.procmultifmt, msg)
3478 if(m):
3479 parts, msg = int(m.group('n')), m.group('ps')
3480 if tp.multiproccnt == 0:
3481 tp.multiproctime = t.time
3482 tp.multiproclist = dict()
3483 proclist = tp.multiproclist
3484 tp.multiproccnt += 1
3485 else:
3486 proclist = dict()
3487 tp.multiproccnt = 0
3488 for ps in msg.split(','):
3489 val = ps.split()
3490 if not val or len(val) != 2:
3491 continue
3492 name = val[0].replace('--', '-')
3493 proclist[name] = int(val[1])
3494 if parts == 1:
3495 data.pstl[t.time] = proclist
3496 elif parts == tp.multiproccnt:
3497 data.pstl[tp.multiproctime] = proclist
3498 tp.multiproccnt = 0
3499 continue
3500 # find the end of resume
3501 if(t.endMarker()):
3502 if data.tKernRes == 0:
3503 data.tKernRes = t.time
3504 data.handleEndMarker(t.time, t.name)
3505 if(not sysvals.usetracemarkers):
3506 # no trace markers? then quit and be sure to finish recording
3507 # the event we used to trigger resume end
3508 if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3509 # if an entry exists, assume this is its end
3510 testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3511 limbo = True
3512 continue
3513 # trace event processing
3514 if(t.fevent):
3515 if(t.type == 'suspend_resume'):
3516 # suspend_resume trace events have two types, begin and end
3517 if(re.match('(?P<name>.*) begin$', t.name)):
3518 isbegin = True
3519 elif(re.match('(?P<name>.*) end$', t.name)):
3520 isbegin = False
3521 else:
3522 continue
3523 if '[' in t.name:
3524 m = re.match('(?P<name>.*)\[.*', t.name)
3525 else:
3526 m = re.match('(?P<name>.*) .*', t.name)
3527 name = m.group('name')
3528 # ignore these events
3529 if(name.split('[')[0] in tracewatch):
3530 continue
3531 # -- phase changes --
3532 # start of kernel suspend
3533 if(re.match('suspend_enter\[.*', t.name)):
3534 if(isbegin and data.tKernSus == 0):
3535 data.tKernSus = t.time
3536 continue
3537 # suspend_prepare start
3538 elif(re.match('dpm_prepare\[.*', t.name)):
3539 if isbegin and data.first_suspend_prepare:
3540 data.first_suspend_prepare = False
3541 if data.tKernSus == 0:
3542 data.tKernSus = t.time
3543 continue
3544 phase = data.setPhase('suspend_prepare', t.time, isbegin)
3545 continue
3546 # suspend start
3547 elif(re.match('dpm_suspend\[.*', t.name)):
3548 phase = data.setPhase('suspend', t.time, isbegin)
3549 continue
3550 # suspend_late start
3551 elif(re.match('dpm_suspend_late\[.*', t.name)):
3552 phase = data.setPhase('suspend_late', t.time, isbegin)
3553 continue
3554 # suspend_noirq start
3555 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
3556 phase = data.setPhase('suspend_noirq', t.time, isbegin)
3557 continue
3558 # suspend_machine/resume_machine
3559 elif(re.match(tp.machinesuspend, t.name)):
3560 lp = data.lastPhase()
3561 if(isbegin):
3562 hwsus = True
3563 if lp.startswith('resume_machine'):
3564 # trim out s2idle loops, track time trying to freeze
3565 llp = data.lastPhase(2)
3566 if llp.startswith('suspend_machine'):
3567 if 'waking' not in data.dmesg[llp]:
3568 data.dmesg[llp]['waking'] = [0, 0.0]
3569 data.dmesg[llp]['waking'][0] += 1
3570 data.dmesg[llp]['waking'][1] += \
3571 t.time - data.dmesg[lp]['start']
3572 data.currphase = ''
3573 del data.dmesg[lp]
3574 continue
3575 phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3576 data.setPhase(phase, t.time, False)
3577 if data.tSuspended == 0:
3578 data.tSuspended = t.time
3579 else:
3580 if lp.startswith('resume_machine'):
3581 data.dmesg[lp]['end'] = t.time
3582 continue
3583 phase = data.setPhase('resume_machine', t.time, True)
3584 if(sysvals.suspendmode in ['mem', 'disk']):
3585 susp = phase.replace('resume', 'suspend')
3586 if susp in data.dmesg:
3587 data.dmesg[susp]['end'] = t.time
3588 data.tSuspended = t.time
3589 data.tResumed = t.time
3590 continue
3591 # resume_noirq start
3592 elif(re.match('dpm_resume_noirq\[.*', t.name)):
3593 phase = data.setPhase('resume_noirq', t.time, isbegin)
3594 continue
3595 # resume_early start
3596 elif(re.match('dpm_resume_early\[.*', t.name)):
3597 phase = data.setPhase('resume_early', t.time, isbegin)
3598 continue
3599 # resume start
3600 elif(re.match('dpm_resume\[.*', t.name)):
3601 phase = data.setPhase('resume', t.time, isbegin)
3602 continue
3603 # resume complete start
3604 elif(re.match('dpm_complete\[.*', t.name)):
3605 phase = data.setPhase('resume_complete', t.time, isbegin)
3606 continue
3607 # skip trace events inside devices calls
3608 if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3609 continue
3610 # global events (outside device calls) are graphed
3611 if(name not in testrun.ttemp):
3612 testrun.ttemp[name] = []
3613 # special handling for s2idle_enter
3614 if name == 'machine_suspend':
3615 if hwsus:
3616 s2idle_enter = hwsus = False
3617 elif s2idle_enter and not isbegin:
3618 if(len(testrun.ttemp[name]) > 0):
3619 testrun.ttemp[name][-1]['end'] = t.time
3620 testrun.ttemp[name][-1]['loop'] += 1
3621 elif not s2idle_enter and isbegin:
3622 s2idle_enter = True
3623 testrun.ttemp[name].append({'begin': t.time,
3624 'end': t.time, 'pid': pid, 'loop': 0})
3625 continue
3626 if(isbegin):
3627 # create a new list entry
3628 testrun.ttemp[name].append(\
3629 {'begin': t.time, 'end': t.time, 'pid': pid})
3630 else:
3631 if(len(testrun.ttemp[name]) > 0):
3632 # if an entry exists, assume this is its end
3633 testrun.ttemp[name][-1]['end'] = t.time
3634 # device callback start
3635 elif(t.type == 'device_pm_callback_start'):
3636 if phase not in data.dmesg:
3637 continue
3638 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3639 t.name);
3640 if(not m):
3641 continue
3642 drv = m.group('drv')
3643 n = m.group('d')
3644 p = m.group('p')
3645 if(n and p):
3646 data.newAction(phase, n, pid, p, t.time, -1, drv)
3647 if pid not in data.devpids:
3648 data.devpids.append(pid)
3649 # device callback finish
3650 elif(t.type == 'device_pm_callback_end'):
3651 if phase not in data.dmesg:
3652 continue
3653 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
3654 if(not m):
3655 continue
3656 n = m.group('d')
3657 dev = data.findDevice(phase, n)
3658 if dev:
3659 dev['length'] = t.time - dev['start']
3660 dev['end'] = t.time
3661 # kprobe event processing
3662 elif(t.fkprobe):
3663 kprobename = t.type
3664 kprobedata = t.name
3665 key = (kprobename, pid)
3666 # displayname is generated from kprobe data
3667 displayname = ''
3668 if(t.fcall):
3669 displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3670 if not displayname:
3671 continue
3672 if(key not in tp.ktemp):
3673 tp.ktemp[key] = []
3674 tp.ktemp[key].append({
3675 'pid': pid,
3676 'begin': t.time,
3677 'end': -1,
3678 'name': displayname,
3679 'cdata': kprobedata,
3680 'proc': m_proc,
3681 })
3682 # start of kernel resume
3683 if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3684 and kprobename in ksuscalls):
3685 data.tKernSus = t.time
3686 elif(t.freturn):
3687 if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3688 continue
3689 e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3690 if not e:
3691 continue
3692 if (t.time - e['begin']) * 1000 < sysvals.mindevlen:
3693 tp.ktemp[key].pop()
3694 continue
3695 e['end'] = t.time
3696 e['rdata'] = kprobedata
3697 # end of kernel resume
3698 if(phase != 'suspend_prepare' and kprobename in krescalls):
3699 if phase in data.dmesg:
3700 data.dmesg[phase]['end'] = t.time
3701 data.tKernRes = t.time
3702
3703 # callgraph processing
3704 elif sysvals.usecallgraph:
3705 # create a callgraph object for the data
3706 key = (m_proc, pid)
3707 if(key not in testrun.ftemp):
3708 testrun.ftemp[key] = []
3709 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3710 # when the call is finished, see which device matches it
3711 cg = testrun.ftemp[key][-1]
3712 res = cg.addLine(t)
3713 if(res != 0):
3714 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3715 if(res == -1):
3716 testrun.ftemp[key][-1].addLine(t)
3717 if len(testdata) < 1:
3718 sysvals.vprint('WARNING: ftrace start marker is missing')
3719 if data and not data.devicegroups:
3720 sysvals.vprint('WARNING: ftrace end marker is missing')
3721 data.handleEndMarker(t.time, t.name)
3722
3723 if sysvals.suspendmode == 'command':
3724 for test in testruns:
3725 for p in test.data.sortedPhases():
3726 if p == 'suspend_prepare':
3727 test.data.dmesg[p]['start'] = test.data.start
3728 test.data.dmesg[p]['end'] = test.data.end
3729 else:
3730 test.data.dmesg[p]['start'] = test.data.end
3731 test.data.dmesg[p]['end'] = test.data.end
3732 test.data.tSuspended = test.data.end
3733 test.data.tResumed = test.data.end
3734 test.data.fwValid = False
3735
3736 # dev source and procmon events can be unreadable with mixed phase height
3737 if sysvals.usedevsrc or sysvals.useprocmon:
3738 sysvals.mixedphaseheight = False
3739
3740 # expand phase boundaries so there are no gaps
3741 for data in testdata:
3742 lp = data.sortedPhases()[0]
3743 for p in data.sortedPhases():
3744 if(p != lp and not ('machine' in p and 'machine' in lp)):
3745 data.dmesg[lp]['end'] = data.dmesg[p]['start']
3746 lp = p
3747
3748 for i in range(len(testruns)):
3749 test = testruns[i]
3750 data = test.data
3751 # find the total time range for this test (begin, end)
3752 tlb, tle = data.start, data.end
3753 if i < len(testruns) - 1:
3754 tle = testruns[i+1].data.start
3755 # add the process usage data to the timeline
3756 if sysvals.useprocmon:
3757 data.createProcessUsageEvents()
3758 # add the traceevent data to the device hierarchy
3759 if(sysvals.usetraceevents):
3760 # add actual trace funcs
3761 for name in sorted(test.ttemp):
3762 for event in test.ttemp[name]:
3763 if event['end'] - event['begin'] <= 0:
3764 continue
3765 title = name
3766 if name == 'machine_suspend' and 'loop' in event:
3767 title = 's2idle_enter_%dx' % event['loop']
3768 data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3769 # add the kprobe based virtual tracefuncs as actual devices
3770 for key in sorted(tp.ktemp):
3771 name, pid = key
3772 if name not in sysvals.tracefuncs:
3773 continue
3774 if pid not in data.devpids:
3775 data.devpids.append(pid)
3776 for e in tp.ktemp[key]:
3777 kb, ke = e['begin'], e['end']
3778 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3779 continue
3780 color = sysvals.kprobeColor(name)
3781 data.newActionGlobal(e['name'], kb, ke, pid, color)
3782 # add config base kprobes and dev kprobes
3783 if sysvals.usedevsrc:
3784 for key in sorted(tp.ktemp):
3785 name, pid = key
3786 if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3787 continue
3788 for e in tp.ktemp[key]:
3789 kb, ke = e['begin'], e['end']
3790 if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3791 continue
3792 data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3793 ke, e['cdata'], e['rdata'])
3794 if sysvals.usecallgraph:
3795 # add the callgraph data to the device hierarchy
3796 sortlist = dict()
3797 for key in sorted(test.ftemp):
3798 proc, pid = key
3799 for cg in test.ftemp[key]:
3800 if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3801 continue
3802 if(not cg.postProcess()):
3803 id = 'task %s' % (pid)
3804 sysvals.vprint('Sanity check failed for '+\
3805 id+', ignoring this callback')
3806 continue
3807 # match cg data to devices
3808 devname = ''
3809 if sysvals.suspendmode != 'command':
3810 devname = cg.deviceMatch(pid, data)
3811 if not devname:
3812 sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3813 sortlist[sortkey] = cg
3814 elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3815 sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3816 (devname, len(cg.list)))
3817 # create blocks for orphan cg data
3818 for sortkey in sorted(sortlist):
3819 cg = sortlist[sortkey]
3820 name = cg.name
3821 if sysvals.isCallgraphFunc(name):
3822 sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3823 cg.newActionFromFunction(data)
3824 if sysvals.suspendmode == 'command':
3825 return (testdata, '')
3826
3827 # fill in any missing phases
3828 error = []
3829 for data in testdata:
3830 tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3831 terr = ''
3832 phasedef = data.phasedef
3833 lp = 'suspend_prepare'
3834 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3835 if p not in data.dmesg:
3836 if not terr:
3837 ph = p if 'machine' in p else lp
3838 if p == 'suspend_machine':
3839 sm = sysvals.suspendmode
3840 if sm in suspendmodename:
3841 sm = suspendmodename[sm]
3842 terr = 'test%s did not enter %s power mode' % (tn, sm)
3843 else:
3844 terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
3845 pprint('TEST%s FAILED: %s' % (tn, terr))
3846 error.append(terr)
3847 if data.tSuspended == 0:
3848 data.tSuspended = data.dmesg[lp]['end']
3849 if data.tResumed == 0:
3850 data.tResumed = data.dmesg[lp]['end']
3851 data.fwValid = False
3852 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3853 lp = p
3854 if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3855 terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3856 (sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3857 error.append(terr)
3858 if not terr and data.enterfail:
3859 pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3860 terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3861 error.append(terr)
3862 if data.tSuspended == 0:
3863 data.tSuspended = data.tKernRes
3864 if data.tResumed == 0:
3865 data.tResumed = data.tSuspended
3866
3867 if(len(sysvals.devicefilter) > 0):
3868 data.deviceFilter(sysvals.devicefilter)
3869 data.fixupInitcallsThatDidntReturn()
3870 if sysvals.usedevsrc:
3871 data.optimizeDevSrc()
3872
3873 # x2: merge any overlapping devices between test runs
3874 if sysvals.usedevsrc and len(testdata) > 1:
3875 tc = len(testdata)
3876 for i in range(tc - 1):
3877 devlist = testdata[i].overflowDevices()
3878 for j in range(i + 1, tc):
3879 testdata[j].mergeOverlapDevices(devlist)
3880 testdata[0].stitchTouchingThreads(testdata[1:])
3881 return (testdata, ', '.join(error))
3882
3883# Function: loadKernelLog
3884# Description:
3885# load the dmesg file into memory and fix up any ordering issues
3886# Output:
3887# An array of empty Data objects with only their dmesgtext attributes set
3888def loadKernelLog():
3889 sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3890 os.path.basename(sysvals.dmesgfile))
3891 if(os.path.exists(sysvals.dmesgfile) == False):
3892 doError('%s does not exist' % sysvals.dmesgfile)
3893
3894 # there can be multiple test runs in a single file
3895 tp = TestProps()
3896 tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3897 testruns = []
3898 data = 0
3899 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3900 for line in lf:
3901 line = line.replace('\r\n', '')
3902 idx = line.find('[')
3903 if idx > 1:
3904 line = line[idx:]
3905 if tp.stampInfo(line, sysvals):
3906 continue
3907 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3908 if(not m):
3909 continue
3910 msg = m.group("msg")
3911 if re.match('PM: Syncing filesystems.*', msg) or \
3912 re.match('PM: suspend entry.*', msg):
3913 if(data):
3914 testruns.append(data)
3915 data = Data(len(testruns))
3916 tp.parseStamp(data, sysvals)
3917 if(not data):
3918 continue
3919 m = re.match('.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3920 if(m):
3921 sysvals.stamp['kernel'] = m.group('k')
3922 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
3923 if not m:
3924 m = re.match('PM: Preparing system for sleep \((?P<m>.*)\)', msg)
3925 if m:
3926 sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3927 data.dmesgtext.append(line)
3928 lf.close()
3929
3930 if sysvals.suspendmode == 's2idle':
3931 sysvals.suspendmode = 'freeze'
3932 elif sysvals.suspendmode == 'deep':
3933 sysvals.suspendmode = 'mem'
3934 if data:
3935 testruns.append(data)
3936 if len(testruns) < 1:
3937 doError('dmesg log has no suspend/resume data: %s' \
3938 % sysvals.dmesgfile)
3939
3940 # fix lines with same timestamp/function with the call and return swapped
3941 for data in testruns:
3942 last = ''
3943 for line in data.dmesgtext:
3944 ct, cf, n, p = data.initcall_debug_call(line)
3945 rt, rf, l = data.initcall_debug_return(last)
3946 if ct and rt and ct == rt and cf == rf:
3947 i = data.dmesgtext.index(last)
3948 j = data.dmesgtext.index(line)
3949 data.dmesgtext[i] = line
3950 data.dmesgtext[j] = last
3951 last = line
3952 return testruns
3953
3954# Function: parseKernelLog
3955# Description:
3956# Analyse a dmesg log output file generated from this app during
3957# the execution phase. Create a set of device structures in memory
3958# for subsequent formatting in the html output file
3959# This call is only for legacy support on kernels where the ftrace
3960# data lacks the suspend_resume or device_pm_callbacks trace events.
3961# Arguments:
3962# data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3963# Output:
3964# The filled Data object
3965def parseKernelLog(data):
3966 phase = 'suspend_runtime'
3967
3968 if(data.fwValid):
3969 sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3970 (data.fwSuspend, data.fwResume))
3971
3972 # dmesg phase match table
3973 dm = {
3974 'suspend_prepare': ['PM: Syncing filesystems.*', 'PM: suspend entry.*'],
3975 'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*',
3976 'PM: Suspending system .*'],
3977 'suspend_late': ['PM: suspend of devices complete after.*',
3978 'PM: freeze of devices complete after.*'],
3979 'suspend_noirq': ['PM: late suspend of devices complete after.*',
3980 'PM: late freeze of devices complete after.*'],
3981 'suspend_machine': ['PM: suspend-to-idle',
3982 'PM: noirq suspend of devices complete after.*',
3983 'PM: noirq freeze of devices complete after.*'],
3984 'resume_machine': ['[PM: ]*Timekeeping suspended for.*',
3985 'ACPI: Low-level resume complete.*',
3986 'ACPI: resume from mwait',
3987 'Suspended for [0-9\.]* seconds'],
3988 'resume_noirq': ['PM: resume from suspend-to-idle',
3989 'ACPI: Waking up from system sleep state.*'],
3990 'resume_early': ['PM: noirq resume of devices complete after.*',
3991 'PM: noirq restore of devices complete after.*'],
3992 'resume': ['PM: early resume of devices complete after.*',
3993 'PM: early restore of devices complete after.*'],
3994 'resume_complete': ['PM: resume of devices complete after.*',
3995 'PM: restore of devices complete after.*'],
3996 'post_resume': ['.*Restarting tasks \.\.\..*'],
3997 }
3998
3999 # action table (expected events that occur and show up in dmesg)
4000 at = {
4001 'sync_filesystems': {
4002 'smsg': '.*[Ff]+ilesystems.*',
4003 'emsg': 'PM: Preparing system for[a-z]* sleep.*' },
4004 'freeze_user_processes': {
4005 'smsg': 'Freezing user space processes.*',
4006 'emsg': 'Freezing remaining freezable tasks.*' },
4007 'freeze_tasks': {
4008 'smsg': 'Freezing remaining freezable tasks.*',
4009 'emsg': 'PM: Suspending system.*' },
4010 'ACPI prepare': {
4011 'smsg': 'ACPI: Preparing to enter system sleep state.*',
4012 'emsg': 'PM: Saving platform NVS memory.*' },
4013 'PM vns': {
4014 'smsg': 'PM: Saving platform NVS memory.*',
4015 'emsg': 'Disabling non-boot CPUs .*' },
4016 }
4017
4018 t0 = -1.0
4019 cpu_start = -1.0
4020 prevktime = -1.0
4021 actions = dict()
4022 for line in data.dmesgtext:
4023 # parse each dmesg line into the time and message
4024 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
4025 if(m):
4026 val = m.group('ktime')
4027 try:
4028 ktime = float(val)
4029 except:
4030 continue
4031 msg = m.group('msg')
4032 # initialize data start to first line time
4033 if t0 < 0:
4034 data.setStart(ktime)
4035 t0 = ktime
4036 else:
4037 continue
4038
4039 # check for a phase change line
4040 phasechange = False
4041 for p in dm:
4042 for s in dm[p]:
4043 if(re.match(s, msg)):
4044 phasechange, phase = True, p
4045 dm[p] = [s]
4046 break
4047
4048 # hack for determining resume_machine end for freeze
4049 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
4050 and phase == 'resume_machine' and \
4051 data.initcall_debug_call(line, True)):
4052 data.setPhase(phase, ktime, False)
4053 phase = 'resume_noirq'
4054 data.setPhase(phase, ktime, True)
4055
4056 if phasechange:
4057 if phase == 'suspend_prepare':
4058 data.setPhase(phase, ktime, True)
4059 data.setStart(ktime)
4060 data.tKernSus = ktime
4061 elif phase == 'suspend':
4062 lp = data.lastPhase()
4063 if lp:
4064 data.setPhase(lp, ktime, False)
4065 data.setPhase(phase, ktime, True)
4066 elif phase == 'suspend_late':
4067 lp = data.lastPhase()
4068 if lp:
4069 data.setPhase(lp, ktime, False)
4070 data.setPhase(phase, ktime, True)
4071 elif phase == 'suspend_noirq':
4072 lp = data.lastPhase()
4073 if lp:
4074 data.setPhase(lp, ktime, False)
4075 data.setPhase(phase, ktime, True)
4076 elif phase == 'suspend_machine':
4077 lp = data.lastPhase()
4078 if lp:
4079 data.setPhase(lp, ktime, False)
4080 data.setPhase(phase, ktime, True)
4081 elif phase == 'resume_machine':
4082 lp = data.lastPhase()
4083 if(sysvals.suspendmode in ['freeze', 'standby']):
4084 data.tSuspended = prevktime
4085 if lp:
4086 data.setPhase(lp, prevktime, False)
4087 else:
4088 data.tSuspended = ktime
4089 if lp:
4090 data.setPhase(lp, prevktime, False)
4091 data.tResumed = ktime
4092 data.setPhase(phase, ktime, True)
4093 elif phase == 'resume_noirq':
4094 lp = data.lastPhase()
4095 if lp:
4096 data.setPhase(lp, ktime, False)
4097 data.setPhase(phase, ktime, True)
4098 elif phase == 'resume_early':
4099 lp = data.lastPhase()
4100 if lp:
4101 data.setPhase(lp, ktime, False)
4102 data.setPhase(phase, ktime, True)
4103 elif phase == 'resume':
4104 lp = data.lastPhase()
4105 if lp:
4106 data.setPhase(lp, ktime, False)
4107 data.setPhase(phase, ktime, True)
4108 elif phase == 'resume_complete':
4109 lp = data.lastPhase()
4110 if lp:
4111 data.setPhase(lp, ktime, False)
4112 data.setPhase(phase, ktime, True)
4113 elif phase == 'post_resume':
4114 lp = data.lastPhase()
4115 if lp:
4116 data.setPhase(lp, ktime, False)
4117 data.setEnd(ktime)
4118 data.tKernRes = ktime
4119 break
4120
4121 # -- device callbacks --
4122 if(phase in data.sortedPhases()):
4123 # device init call
4124 t, f, n, p = data.initcall_debug_call(line)
4125 if t and f and n and p:
4126 data.newAction(phase, f, int(n), p, ktime, -1, '')
4127 else:
4128 # device init return
4129 t, f, l = data.initcall_debug_return(line)
4130 if t and f and l:
4131 list = data.dmesg[phase]['list']
4132 if(f in list):
4133 dev = list[f]
4134 dev['length'] = int(l)
4135 dev['end'] = ktime
4136
4137 # if trace events are not available, these are better than nothing
4138 if(not sysvals.usetraceevents):
4139 # look for known actions
4140 for a in sorted(at):
4141 if(re.match(at[a]['smsg'], msg)):
4142 if(a not in actions):
4143 actions[a] = [{'begin': ktime, 'end': ktime}]
4144 if(re.match(at[a]['emsg'], msg)):
4145 if(a in actions and actions[a][-1]['begin'] == actions[a][-1]['end']):
4146 actions[a][-1]['end'] = ktime
4147 # now look for CPU on/off events
4148 if(re.match('Disabling non-boot CPUs .*', msg)):
4149 # start of first cpu suspend
4150 cpu_start = ktime
4151 elif(re.match('Enabling non-boot CPUs .*', msg)):
4152 # start of first cpu resume
4153 cpu_start = ktime
4154 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) \
4155 or re.match('psci: CPU(?P<cpu>[0-9]*) killed.*', msg)):
4156 # end of a cpu suspend, start of the next
4157 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
4158 if(not m):
4159 m = re.match('psci: CPU(?P<cpu>[0-9]*) killed.*', msg)
4160 cpu = 'CPU'+m.group('cpu')
4161 if(cpu not in actions):
4162 actions[cpu] = []
4163 actions[cpu].append({'begin': cpu_start, 'end': ktime})
4164 cpu_start = ktime
4165 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
4166 # end of a cpu resume, start of the next
4167 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
4168 cpu = 'CPU'+m.group('cpu')
4169 if(cpu not in actions):
4170 actions[cpu] = []
4171 actions[cpu].append({'begin': cpu_start, 'end': ktime})
4172 cpu_start = ktime
4173 prevktime = ktime
4174 data.initDevicegroups()
4175
4176 # fill in any missing phases
4177 phasedef = data.phasedef
4178 terr, lp = '', 'suspend_prepare'
4179 if lp not in data.dmesg:
4180 doError('dmesg log format has changed, could not find start of suspend')
4181 for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4182 if p not in data.dmesg:
4183 if not terr:
4184 pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
4185 terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
4186 if data.tSuspended == 0:
4187 data.tSuspended = data.dmesg[lp]['end']
4188 if data.tResumed == 0:
4189 data.tResumed = data.dmesg[lp]['end']
4190 sysvals.vprint('WARNING: phase "%s" is missing!' % p)
4191 lp = p
4192 lp = data.sortedPhases()[0]
4193 for p in data.sortedPhases():
4194 if(p != lp and not ('machine' in p and 'machine' in lp)):
4195 data.dmesg[lp]['end'] = data.dmesg[p]['start']
4196 lp = p
4197 if data.tSuspended == 0:
4198 data.tSuspended = data.tKernRes
4199 if data.tResumed == 0:
4200 data.tResumed = data.tSuspended
4201
4202 # fill in any actions we've found
4203 for name in sorted(actions):
4204 for event in actions[name]:
4205 data.newActionGlobal(name, event['begin'], event['end'])
4206
4207 if(len(sysvals.devicefilter) > 0):
4208 data.deviceFilter(sysvals.devicefilter)
4209 data.fixupInitcallsThatDidntReturn()
4210 return True
4211
4212def callgraphHTML(sv, hf, num, cg, title, color, devid):
4213 html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
4214 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
4215 html_func_end = '</article>\n'
4216 html_func_leaf = '<article>{0} {1}</article>\n'
4217
4218 cgid = devid
4219 if cg.id:
4220 cgid += cg.id
4221 cglen = (cg.end - cg.start) * 1000
4222 if cglen < sv.mincglen:
4223 return num
4224
4225 fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
4226 flen = fmt % (cglen, cg.start, cg.end)
4227 hf.write(html_func_top.format(cgid, color, num, title, flen))
4228 num += 1
4229 for line in cg.list:
4230 if(line.length < 0.000000001):
4231 flen = ''
4232 else:
4233 fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
4234 flen = fmt % (line.length*1000, line.time)
4235 if line.isLeaf():
4236 if line.length * 1000 < sv.mincglen:
4237 continue
4238 hf.write(html_func_leaf.format(line.name, flen))
4239 elif line.freturn:
4240 hf.write(html_func_end)
4241 else:
4242 hf.write(html_func_start.format(num, line.name, flen))
4243 num += 1
4244 hf.write(html_func_end)
4245 return num
4246
4247def addCallgraphs(sv, hf, data):
4248 hf.write('<section id="callgraphs" class="callgraph">\n')
4249 # write out the ftrace data converted to html
4250 num = 0
4251 for p in data.sortedPhases():
4252 if sv.cgphase and p != sv.cgphase:
4253 continue
4254 list = data.dmesg[p]['list']
4255 for d in data.sortedDevices(p):
4256 if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4257 continue
4258 dev = list[d]
4259 color = 'white'
4260 if 'color' in data.dmesg[p]:
4261 color = data.dmesg[p]['color']
4262 if 'color' in dev:
4263 color = dev['color']
4264 name = d if '[' not in d else d.split('[')[0]
4265 if(d in sv.devprops):
4266 name = sv.devprops[d].altName(d)
4267 if 'drv' in dev and dev['drv']:
4268 name += ' {%s}' % dev['drv']
4269 if sv.suspendmode in suspendmodename:
4270 name += ' '+p
4271 if('ftrace' in dev):
4272 cg = dev['ftrace']
4273 if cg.name == sv.ftopfunc:
4274 name = 'top level suspend/resume call'
4275 num = callgraphHTML(sv, hf, num, cg,
4276 name, color, dev['id'])
4277 if('ftraces' in dev):
4278 for cg in dev['ftraces']:
4279 num = callgraphHTML(sv, hf, num, cg,
4280 name+' → '+cg.name, color, dev['id'])
4281 hf.write('\n\n </section>\n')
4282
4283def summaryCSS(title, center=True):
4284 tdcenter = 'text-align:center;' if center else ''
4285 out = '<!DOCTYPE html>\n<html>\n<head>\n\
4286 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4287 <title>'+title+'</title>\n\
4288 <style type=\'text/css\'>\n\
4289 .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4290 table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4291 th {border: 1px solid black;background:#222;color:white;}\n\
4292 td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4293 tr.head td {border: 1px solid black;background:#aaa;}\n\
4294 tr.alt {background-color:#ddd;}\n\
4295 tr.notice {color:red;}\n\
4296 .minval {background-color:#BBFFBB;}\n\
4297 .medval {background-color:#BBBBFF;}\n\
4298 .maxval {background-color:#FFBBBB;}\n\
4299 .head a {color:#000;text-decoration: none;}\n\
4300 </style>\n</head>\n<body>\n'
4301 return out
4302
4303# Function: createHTMLSummarySimple
4304# Description:
4305# Create summary html file for a series of tests
4306# Arguments:
4307# testruns: array of Data objects from parseTraceLog
4308def createHTMLSummarySimple(testruns, htmlfile, title):
4309 # write the html header first (html head, css code, up to body start)
4310 html = summaryCSS('Summary - SleepGraph')
4311
4312 # extract the test data into list
4313 list = dict()
4314 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4315 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4316 num = 0
4317 useturbo = usewifi = False
4318 lastmode = ''
4319 cnt = dict()
4320 for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4321 mode = data['mode']
4322 if mode not in list:
4323 list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4324 if lastmode and lastmode != mode and num > 0:
4325 for i in range(2):
4326 s = sorted(tMed[i])
4327 list[lastmode]['med'][i] = s[int(len(s)//2)]
4328 iMed[i] = tMed[i][list[lastmode]['med'][i]]
4329 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4330 list[lastmode]['min'] = tMin
4331 list[lastmode]['max'] = tMax
4332 list[lastmode]['idx'] = (iMin, iMed, iMax)
4333 tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4334 iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4335 num = 0
4336 pkgpc10 = syslpi = wifi = ''
4337 if 'pkgpc10' in data and 'syslpi' in data:
4338 pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4339 if 'wifi' in data:
4340 wifi, usewifi = data['wifi'], True
4341 res = data['result']
4342 tVal = [float(data['suspend']), float(data['resume'])]
4343 list[mode]['data'].append([data['host'], data['kernel'],
4344 data['time'], tVal[0], tVal[1], data['url'], res,
4345 data['issues'], data['sus_worst'], data['sus_worsttime'],
4346 data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi])
4347 idx = len(list[mode]['data']) - 1
4348 if res.startswith('fail in'):
4349 res = 'fail'
4350 if res not in cnt:
4351 cnt[res] = 1
4352 else:
4353 cnt[res] += 1
4354 if res == 'pass':
4355 for i in range(2):
4356 tMed[i][tVal[i]] = idx
4357 tAvg[i] += tVal[i]
4358 if tMin[i] == 0 or tVal[i] < tMin[i]:
4359 iMin[i] = idx
4360 tMin[i] = tVal[i]
4361 if tMax[i] == 0 or tVal[i] > tMax[i]:
4362 iMax[i] = idx
4363 tMax[i] = tVal[i]
4364 num += 1
4365 lastmode = mode
4366 if lastmode and num > 0:
4367 for i in range(2):
4368 s = sorted(tMed[i])
4369 list[lastmode]['med'][i] = s[int(len(s)//2)]
4370 iMed[i] = tMed[i][list[lastmode]['med'][i]]
4371 list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4372 list[lastmode]['min'] = tMin
4373 list[lastmode]['max'] = tMax
4374 list[lastmode]['idx'] = (iMin, iMed, iMax)
4375
4376 # group test header
4377 desc = []
4378 for ilk in sorted(cnt, reverse=True):
4379 if cnt[ilk] > 0:
4380 desc.append('%d %s' % (cnt[ilk], ilk))
4381 html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4382 th = '\t<th>{0}</th>\n'
4383 td = '\t<td>{0}</td>\n'
4384 tdh = '\t<td{1}>{0}</td>\n'
4385 tdlink = '\t<td><a href="{0}">html</a></td>\n'
4386 cols = 12
4387 if useturbo:
4388 cols += 2
4389 if usewifi:
4390 cols += 1
4391 colspan = '%d' % cols
4392
4393 # table header
4394 html += '<table>\n<tr>\n' + th.format('#') +\
4395 th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4396 th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4397 th.format('Suspend') + th.format('Resume') +\
4398 th.format('Worst Suspend Device') + th.format('SD Time') +\
4399 th.format('Worst Resume Device') + th.format('RD Time')
4400 if useturbo:
4401 html += th.format('PkgPC10') + th.format('SysLPI')
4402 if usewifi:
4403 html += th.format('Wifi')
4404 html += th.format('Detail')+'</tr>\n'
4405 # export list into html
4406 head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4407 '<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4408 '<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4409 '<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4410 '<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4411 'Resume Avg={6} '+\
4412 '<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4413 '<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4414 '<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4415 '</tr>\n'
4416 headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4417 colspan+'></td></tr>\n'
4418 for mode in sorted(list):
4419 # header line for each suspend mode
4420 num = 0
4421 tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4422 list[mode]['max'], list[mode]['med']
4423 count = len(list[mode]['data'])
4424 if 'idx' in list[mode]:
4425 iMin, iMed, iMax = list[mode]['idx']
4426 html += head.format('%d' % count, mode.upper(),
4427 '%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4428 '%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4429 mode.lower()
4430 )
4431 else:
4432 iMin = iMed = iMax = [-1, -1, -1]
4433 html += headnone.format('%d' % count, mode.upper())
4434 for d in list[mode]['data']:
4435 # row classes - alternate row color
4436 rcls = ['alt'] if num % 2 == 1 else []
4437 if d[6] != 'pass':
4438 rcls.append('notice')
4439 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4440 # figure out if the line has sus or res highlighted
4441 idx = list[mode]['data'].index(d)
4442 tHigh = ['', '']
4443 for i in range(2):
4444 tag = 's%s' % mode if i == 0 else 'r%s' % mode
4445 if idx == iMin[i]:
4446 tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4447 elif idx == iMax[i]:
4448 tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4449 elif idx == iMed[i]:
4450 tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4451 html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4452 html += td.format(mode) # mode
4453 html += td.format(d[0]) # host
4454 html += td.format(d[1]) # kernel
4455 html += td.format(d[2]) # time
4456 html += td.format(d[6]) # result
4457 html += td.format(d[7]) # issues
4458 html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend
4459 html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume
4460 html += td.format(d[8]) # sus_worst
4461 html += td.format('%.3f ms' % d[9]) if d[9] else td.format('') # sus_worst time
4462 html += td.format(d[10]) # res_worst
4463 html += td.format('%.3f ms' % d[11]) if d[11] else td.format('') # res_worst time
4464 if useturbo:
4465 html += td.format(d[12]) # pkg_pc10
4466 html += td.format(d[13]) # syslpi
4467 if usewifi:
4468 html += td.format(d[14]) # wifi
4469 html += tdlink.format(d[5]) if d[5] else td.format('') # url
4470 html += '</tr>\n'
4471 num += 1
4472
4473 # flush the data to file
4474 hf = open(htmlfile, 'w')
4475 hf.write(html+'</table>\n</body>\n</html>\n')
4476 hf.close()
4477
4478def createHTMLDeviceSummary(testruns, htmlfile, title):
4479 html = summaryCSS('Device Summary - SleepGraph', False)
4480
4481 # create global device list from all tests
4482 devall = dict()
4483 for data in testruns:
4484 host, url, devlist = data['host'], data['url'], data['devlist']
4485 for type in devlist:
4486 if type not in devall:
4487 devall[type] = dict()
4488 mdevlist, devlist = devall[type], data['devlist'][type]
4489 for name in devlist:
4490 length = devlist[name]
4491 if name not in mdevlist:
4492 mdevlist[name] = {'name': name, 'host': host,
4493 'worst': length, 'total': length, 'count': 1,
4494 'url': url}
4495 else:
4496 if length > mdevlist[name]['worst']:
4497 mdevlist[name]['worst'] = length
4498 mdevlist[name]['url'] = url
4499 mdevlist[name]['host'] = host
4500 mdevlist[name]['total'] += length
4501 mdevlist[name]['count'] += 1
4502
4503 # generate the html
4504 th = '\t<th>{0}</th>\n'
4505 td = '\t<td align=center>{0}</td>\n'
4506 tdr = '\t<td align=right>{0}</td>\n'
4507 tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4508 limit = 1
4509 for type in sorted(devall, reverse=True):
4510 num = 0
4511 devlist = devall[type]
4512 # table header
4513 html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4514 (title, type.upper(), limit)
4515 html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4516 th.format('Average Time') + th.format('Count') +\
4517 th.format('Worst Time') + th.format('Host (worst time)') +\
4518 th.format('Link (worst time)') + '</tr>\n'
4519 for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4520 devlist[k]['total'], devlist[k]['name']), reverse=True):
4521 data = devall[type][name]
4522 data['average'] = data['total'] / data['count']
4523 if data['average'] < limit:
4524 continue
4525 # row classes - alternate row color
4526 rcls = ['alt'] if num % 2 == 1 else []
4527 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4528 html += tdr.format(data['name']) # name
4529 html += td.format('%.3f ms' % data['average']) # average
4530 html += td.format(data['count']) # count
4531 html += td.format('%.3f ms' % data['worst']) # worst
4532 html += td.format(data['host']) # host
4533 html += tdlink.format(data['url']) # url
4534 html += '</tr>\n'
4535 num += 1
4536 html += '</table>\n'
4537
4538 # flush the data to file
4539 hf = open(htmlfile, 'w')
4540 hf.write(html+'</body>\n</html>\n')
4541 hf.close()
4542 return devall
4543
4544def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4545 multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4546 html = summaryCSS('Issues Summary - SleepGraph', False)
4547 total = len(testruns)
4548
4549 # generate the html
4550 th = '\t<th>{0}</th>\n'
4551 td = '\t<td align={0}>{1}</td>\n'
4552 tdlink = '<a href="{1}">{0}</a>'
4553 subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4554 html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4555 html += '<tr>\n' + th.format('Issue') + th.format('Count')
4556 if multihost:
4557 html += th.format('Hosts')
4558 html += th.format('Tests') + th.format('Fail Rate') +\
4559 th.format('First Instance') + '</tr>\n'
4560
4561 num = 0
4562 for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4563 testtotal = 0
4564 links = []
4565 for host in sorted(e['urls']):
4566 links.append(tdlink.format(host, e['urls'][host][0]))
4567 testtotal += len(e['urls'][host])
4568 rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4569 # row classes - alternate row color
4570 rcls = ['alt'] if num % 2 == 1 else []
4571 html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4572 html += td.format('left', e['line']) # issue
4573 html += td.format('center', e['count']) # count
4574 if multihost:
4575 html += td.format('center', len(e['urls'])) # hosts
4576 html += td.format('center', testtotal) # test count
4577 html += td.format('center', rate) # test rate
4578 html += td.format('center nowrap', '<br>'.join(links)) # links
4579 html += '</tr>\n'
4580 num += 1
4581
4582 # flush the data to file
4583 hf = open(htmlfile, 'w')
4584 hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4585 hf.close()
4586 return issues
4587
4588def ordinal(value):
4589 suffix = 'th'
4590 if value < 10 or value > 19:
4591 if value % 10 == 1:
4592 suffix = 'st'
4593 elif value % 10 == 2:
4594 suffix = 'nd'
4595 elif value % 10 == 3:
4596 suffix = 'rd'
4597 return '%d%s' % (value, suffix)
4598
4599# Function: createHTML
4600# Description:
4601# Create the output html file from the resident test data
4602# Arguments:
4603# testruns: array of Data objects from parseKernelLog or parseTraceLog
4604# Output:
4605# True if the html file was created, false if it failed
4606def createHTML(testruns, testfail):
4607 if len(testruns) < 1:
4608 pprint('ERROR: Not enough test data to build a timeline')
4609 return
4610
4611 kerror = False
4612 for data in testruns:
4613 if data.kerror:
4614 kerror = True
4615 if(sysvals.suspendmode in ['freeze', 'standby']):
4616 data.trimFreezeTime(testruns[-1].tSuspended)
4617 else:
4618 data.getMemTime()
4619
4620 # html function templates
4621 html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}→</div>\n'
4622 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'
4623 html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4624 html_timetotal = '<table class="time1">\n<tr>'\
4625 '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4626 '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4627 '</tr>\n</table>\n'
4628 html_timetotal2 = '<table class="time1">\n<tr>'\
4629 '<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4630 '<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4631 '<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4632 '</tr>\n</table>\n'
4633 html_timetotal3 = '<table class="time1">\n<tr>'\
4634 '<td class="green">Execution Time: <b>{0} ms</b></td>'\
4635 '<td class="yellow">Command: <b>{1}</b></td>'\
4636 '</tr>\n</table>\n'
4637 html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4638 html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4639 html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4640 html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
4641
4642 # html format variables
4643 scaleH = 20
4644 if kerror:
4645 scaleH = 40
4646
4647 # device timeline
4648 devtl = Timeline(30, scaleH)
4649
4650 # write the test title and general info header
4651 devtl.createHeader(sysvals, testruns[0].stamp)
4652
4653 # Generate the header for this timeline
4654 for data in testruns:
4655 tTotal = data.end - data.start
4656 if(tTotal == 0):
4657 doError('No timeline data')
4658 if sysvals.suspendmode == 'command':
4659 run_time = '%.0f' % (tTotal * 1000)
4660 if sysvals.testcommand:
4661 testdesc = sysvals.testcommand
4662 else:
4663 testdesc = 'unknown'
4664 if(len(testruns) > 1):
4665 testdesc = ordinal(data.testnumber+1)+' '+testdesc
4666 thtml = html_timetotal3.format(run_time, testdesc)
4667 devtl.html += thtml
4668 continue
4669 # typical full suspend/resume header
4670 stot, rtot = sktime, rktime = data.getTimeValues()
4671 ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4672 if data.fwValid:
4673 stot += (data.fwSuspend/1000000.0)
4674 rtot += (data.fwResume/1000000.0)
4675 ssrc.append('firmware')
4676 rsrc.append('firmware')
4677 testdesc = 'Total'
4678 if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4679 rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4680 rsrc.append('wifi')
4681 testdesc = 'Total'
4682 suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4683 stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4684 (sysvals.suspendmode, ' & '.join(ssrc))
4685 rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4686 (sysvals.suspendmode, ' & '.join(rsrc))
4687 if(len(testruns) > 1):
4688 testdesc = testdesc2 = ordinal(data.testnumber+1)
4689 testdesc2 += ' '
4690 if(len(data.tLow) == 0):
4691 thtml = html_timetotal.format(suspend_time, \
4692 resume_time, testdesc, stitle, rtitle)
4693 else:
4694 low_time = '+'.join(data.tLow)
4695 thtml = html_timetotal2.format(suspend_time, low_time, \
4696 resume_time, testdesc, stitle, rtitle)
4697 devtl.html += thtml
4698 if not data.fwValid and 'dev' not in data.wifi:
4699 continue
4700 # extra detail when the times come from multiple sources
4701 thtml = '<table class="time2">\n<tr>'
4702 thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4703 if data.fwValid:
4704 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4705 rftime = '%.3f'%(data.fwResume / 1000000.0)
4706 thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4707 thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4708 thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4709 if 'time' in data.wifi:
4710 if data.wifi['stat'] != 'timeout':
4711 wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
4712 else:
4713 wtime = 'TIMEOUT'
4714 thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4715 thtml += '</tr>\n</table>\n'
4716 devtl.html += thtml
4717 if testfail:
4718 devtl.html += html_fail.format(testfail)
4719
4720 # time scale for potentially multiple datasets
4721 t0 = testruns[0].start
4722 tMax = testruns[-1].end
4723 tTotal = tMax - t0
4724
4725 # determine the maximum number of rows we need to draw
4726 fulllist = []
4727 threadlist = []
4728 pscnt = 0
4729 devcnt = 0
4730 for data in testruns:
4731 data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4732 for group in data.devicegroups:
4733 devlist = []
4734 for phase in group:
4735 for devname in sorted(data.tdevlist[phase]):
4736 d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4737 devlist.append(d)
4738 if d.isa('kth'):
4739 threadlist.append(d)
4740 else:
4741 if d.isa('ps'):
4742 pscnt += 1
4743 else:
4744 devcnt += 1
4745 fulllist.append(d)
4746 if sysvals.mixedphaseheight:
4747 devtl.getPhaseRows(devlist)
4748 if not sysvals.mixedphaseheight:
4749 if len(threadlist) > 0 and len(fulllist) > 0:
4750 if pscnt > 0 and devcnt > 0:
4751 msg = 'user processes & device pm callbacks'
4752 elif pscnt > 0:
4753 msg = 'user processes'
4754 else:
4755 msg = 'device pm callbacks'
4756 d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4757 fulllist.insert(0, d)
4758 devtl.getPhaseRows(fulllist)
4759 if len(threadlist) > 0:
4760 d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4761 threadlist.insert(0, d)
4762 devtl.getPhaseRows(threadlist, devtl.rows)
4763 devtl.calcTotalRows()
4764
4765 # draw the full timeline
4766 devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4767 for data in testruns:
4768 # draw each test run and block chronologically
4769 phases = {'suspend':[],'resume':[]}
4770 for phase in data.sortedPhases():
4771 if data.dmesg[phase]['start'] >= data.tSuspended:
4772 phases['resume'].append(phase)
4773 else:
4774 phases['suspend'].append(phase)
4775 # now draw the actual timeline blocks
4776 for dir in phases:
4777 # draw suspend and resume blocks separately
4778 bname = '%s%d' % (dir[0], data.testnumber)
4779 if dir == 'suspend':
4780 m0 = data.start
4781 mMax = data.tSuspended
4782 left = '%f' % (((m0-t0)*100.0)/tTotal)
4783 else:
4784 m0 = data.tSuspended
4785 mMax = data.end
4786 # in an x2 run, remove any gap between blocks
4787 if len(testruns) > 1 and data.testnumber == 0:
4788 mMax = testruns[1].start
4789 left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4790 mTotal = mMax - m0
4791 # if a timeline block is 0 length, skip altogether
4792 if mTotal == 0:
4793 continue
4794 width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4795 devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4796 for b in phases[dir]:
4797 # draw the phase color background
4798 phase = data.dmesg[b]
4799 length = phase['end']-phase['start']
4800 left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4801 width = '%f' % ((length*100.0)/mTotal)
4802 devtl.html += devtl.html_phase.format(left, width, \
4803 '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4804 data.dmesg[b]['color'], '')
4805 for e in data.errorinfo[dir]:
4806 # draw red lines for any kernel errors found
4807 type, t, idx1, idx2 = e
4808 id = '%d_%d' % (idx1, idx2)
4809 right = '%f' % (((mMax-t)*100.0)/mTotal)
4810 devtl.html += html_error.format(right, id, type)
4811 for b in phases[dir]:
4812 # draw the devices for this phase
4813 phaselist = data.dmesg[b]['list']
4814 for d in sorted(data.tdevlist[b]):
4815 dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0]
4816 name, dev = dname, phaselist[d]
4817 drv = xtraclass = xtrainfo = xtrastyle = ''
4818 if 'htmlclass' in dev:
4819 xtraclass = dev['htmlclass']
4820 if 'color' in dev:
4821 xtrastyle = 'background:%s;' % dev['color']
4822 if(d in sysvals.devprops):
4823 name = sysvals.devprops[d].altName(d)
4824 xtraclass = sysvals.devprops[d].xtraClass()
4825 xtrainfo = sysvals.devprops[d].xtraInfo()
4826 elif xtraclass == ' kth':
4827 xtrainfo = ' kernel_thread'
4828 if('drv' in dev and dev['drv']):
4829 drv = ' {%s}' % dev['drv']
4830 rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4831 rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4832 top = '%.3f' % (rowtop + devtl.scaleH)
4833 left = '%f' % (((dev['start']-m0)*100)/mTotal)
4834 width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4835 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4836 title = name+drv+xtrainfo+length
4837 if sysvals.suspendmode == 'command':
4838 title += sysvals.testcommand
4839 elif xtraclass == ' ps':
4840 if 'suspend' in b:
4841 title += 'pre_suspend_process'
4842 else:
4843 title += 'post_resume_process'
4844 else:
4845 title += b
4846 devtl.html += devtl.html_device.format(dev['id'], \
4847 title, left, top, '%.3f'%rowheight, width, \
4848 dname+drv, xtraclass, xtrastyle)
4849 if('cpuexec' in dev):
4850 for t in sorted(dev['cpuexec']):
4851 start, end = t
4852 height = '%.3f' % (rowheight/3)
4853 top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4854 left = '%f' % (((start-m0)*100)/mTotal)
4855 width = '%f' % ((end-start)*100/mTotal)
4856 color = 'rgba(255, 0, 0, %f)' % dev['cpuexec'][t]
4857 devtl.html += \
4858 html_cpuexec.format(left, top, height, width, color)
4859 if('src' not in dev):
4860 continue
4861 # draw any trace events for this device
4862 for e in dev['src']:
4863 if e.length == 0:
4864 continue
4865 height = '%.3f' % devtl.rowH
4866 top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4867 left = '%f' % (((e.time-m0)*100)/mTotal)
4868 width = '%f' % (e.length*100/mTotal)
4869 xtrastyle = ''
4870 if e.color:
4871 xtrastyle = 'background:%s;' % e.color
4872 devtl.html += \
4873 html_traceevent.format(e.title(), \
4874 left, top, height, width, e.text(), '', xtrastyle)
4875 # draw the time scale, try to make the number of labels readable
4876 devtl.createTimeScale(m0, mMax, tTotal, dir)
4877 devtl.html += '</div>\n'
4878
4879 # timeline is finished
4880 devtl.html += '</div>\n</div>\n'
4881
4882 # draw a legend which describes the phases by color
4883 if sysvals.suspendmode != 'command':
4884 phasedef = testruns[-1].phasedef
4885 devtl.html += '<div class="legend">\n'
4886 pdelta = 100.0/len(phasedef.keys())
4887 pmargin = pdelta / 4.0
4888 for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4889 id, p = '', phasedef[phase]
4890 for word in phase.split('_'):
4891 id += word[0]
4892 order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4893 name = phase.replace('_', ' ')
4894 devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4895 devtl.html += '</div>\n'
4896
4897 hf = open(sysvals.htmlfile, 'w')
4898 addCSS(hf, sysvals, len(testruns), kerror)
4899
4900 # write the device timeline
4901 hf.write(devtl.html)
4902 hf.write('<div id="devicedetailtitle"></div>\n')
4903 hf.write('<div id="devicedetail" style="display:none;">\n')
4904 # draw the colored boxes for the device detail section
4905 for data in testruns:
4906 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4907 pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4908 hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4909 '0', '0', pscolor))
4910 for b in data.sortedPhases():
4911 phase = data.dmesg[b]
4912 length = phase['end']-phase['start']
4913 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4914 width = '%.3f' % ((length*100.0)/tTotal)
4915 hf.write(devtl.html_phaselet.format(b, left, width, \
4916 data.dmesg[b]['color']))
4917 hf.write(devtl.html_phaselet.format('post_resume_process', \
4918 '0', '0', pscolor))
4919 if sysvals.suspendmode == 'command':
4920 hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4921 hf.write('</div>\n')
4922 hf.write('</div>\n')
4923
4924 # write the ftrace data (callgraph)
4925 if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4926 data = testruns[sysvals.cgtest]
4927 else:
4928 data = testruns[-1]
4929 if sysvals.usecallgraph:
4930 addCallgraphs(sysvals, hf, data)
4931
4932 # add the test log as a hidden div
4933 if sysvals.testlog and sysvals.logmsg:
4934 hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4935 # add the dmesg log as a hidden div
4936 if sysvals.dmesglog and sysvals.dmesgfile:
4937 hf.write('<div id="dmesglog" style="display:none;">\n')
4938 lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4939 for line in lf:
4940 line = line.replace('<', '<').replace('>', '>')
4941 hf.write(line)
4942 lf.close()
4943 hf.write('</div>\n')
4944 # add the ftrace log as a hidden div
4945 if sysvals.ftracelog and sysvals.ftracefile:
4946 hf.write('<div id="ftracelog" style="display:none;">\n')
4947 lf = sysvals.openlog(sysvals.ftracefile, 'r')
4948 for line in lf:
4949 hf.write(line)
4950 lf.close()
4951 hf.write('</div>\n')
4952
4953 # write the footer and close
4954 addScriptCode(hf, testruns)
4955 hf.write('</body>\n</html>\n')
4956 hf.close()
4957 return True
4958
4959def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4960 kernel = sv.stamp['kernel']
4961 host = sv.hostname[0].upper()+sv.hostname[1:]
4962 mode = sv.suspendmode
4963 if sv.suspendmode in suspendmodename:
4964 mode = suspendmodename[sv.suspendmode]
4965 title = host+' '+mode+' '+kernel
4966
4967 # various format changes by flags
4968 cgchk = 'checked'
4969 cgnchk = 'not(:checked)'
4970 if sv.cgexp:
4971 cgchk = 'not(:checked)'
4972 cgnchk = 'checked'
4973
4974 hoverZ = 'z-index:8;'
4975 if sv.usedevsrc:
4976 hoverZ = ''
4977
4978 devlistpos = 'absolute'
4979 if testcount > 1:
4980 devlistpos = 'relative'
4981
4982 scaleTH = 20
4983 if kerror:
4984 scaleTH = 60
4985
4986 # write the html header first (html head, css code, up to body start)
4987 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
4988 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4989 <title>'+title+'</title>\n\
4990 <style type=\'text/css\'>\n\
4991 body {overflow-y:scroll;}\n\
4992 .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
4993 .stamp.sysinfo {font:10px Arial;}\n\
4994 .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
4995 .callgraph article * {padding-left:28px;}\n\
4996 h1 {color:black;font:bold 30px Times;}\n\
4997 t0 {color:black;font:bold 30px Times;}\n\
4998 t1 {color:black;font:30px Times;}\n\
4999 t2 {color:black;font:25px Times;}\n\
5000 t3 {color:black;font:20px Times;white-space:nowrap;}\n\
5001 t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
5002 cS {font:bold 13px Times;}\n\
5003 table {width:100%;}\n\
5004 .gray {background:rgba(80,80,80,0.1);}\n\
5005 .green {background:rgba(204,255,204,0.4);}\n\
5006 .purple {background:rgba(128,0,128,0.2);}\n\
5007 .yellow {background:rgba(255,255,204,0.4);}\n\
5008 .blue {background:rgba(169,208,245,0.4);}\n\
5009 .time1 {font:22px Arial;border:1px solid;}\n\
5010 .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
5011 .testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
5012 td {text-align:center;}\n\
5013 r {color:#500000;font:15px Tahoma;}\n\
5014 n {color:#505050;font:15px Tahoma;}\n\
5015 .tdhl {color:red;}\n\
5016 .hide {display:none;}\n\
5017 .pf {display:none;}\n\
5018 .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\
5019 .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\
5020 .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
5021 .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
5022 .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
5023 .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\
5024 .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
5025 .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5026 .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
5027 .hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5028 .hover.sync {background:white;}\n\
5029 .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
5030 .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
5031 .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\
5032 .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
5033 .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
5034 .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
5035 .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
5036 .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
5037 .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
5038 .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
5039 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
5040 .btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
5041 .devlist {position:'+devlistpos+';width:190px;}\n\
5042 a:link {color:white;text-decoration:none;}\n\
5043 a:visited {color:white;}\n\
5044 a:hover {color:white;}\n\
5045 a:active {color:white;}\n\
5046 .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
5047 #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
5048 .tblock {position:absolute;height:100%;background:#ddd;}\n\
5049 .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
5050 .bg {z-index:1;}\n\
5051'+extra+'\
5052 </style>\n</head>\n<body>\n'
5053 hf.write(html_header)
5054
5055# Function: addScriptCode
5056# Description:
5057# Adds the javascript code to the output html
5058# Arguments:
5059# hf: the open html file pointer
5060# testruns: array of Data objects from parseKernelLog or parseTraceLog
5061def addScriptCode(hf, testruns):
5062 t0 = testruns[0].start * 1000
5063 tMax = testruns[-1].end * 1000
5064 # create an array in javascript memory with the device details
5065 detail = ' var devtable = [];\n'
5066 for data in testruns:
5067 topo = data.deviceTopology()
5068 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
5069 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
5070 # add the code which will manipulate the data in the browser
5071 script_code = \
5072 '<script type="text/javascript">\n'+detail+\
5073 ' var resolution = -1;\n'\
5074 ' var dragval = [0, 0];\n'\
5075 ' function redrawTimescale(t0, tMax, tS) {\n'\
5076 ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\
5077 ' var tTotal = tMax - t0;\n'\
5078 ' var list = document.getElementsByClassName("tblock");\n'\
5079 ' for (var i = 0; i < list.length; i++) {\n'\
5080 ' var timescale = list[i].getElementsByClassName("timescale")[0];\n'\
5081 ' var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);\n'\
5082 ' var mTotal = tTotal*parseFloat(list[i].style.width)/100;\n'\
5083 ' var mMax = m0 + mTotal;\n'\
5084 ' var html = "";\n'\
5085 ' var divTotal = Math.floor(mTotal/tS) + 1;\n'\
5086 ' if(divTotal > 1000) continue;\n'\
5087 ' var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;\n'\
5088 ' var pos = 0.0, val = 0.0;\n'\
5089 ' for (var j = 0; j < divTotal; j++) {\n'\
5090 ' var htmlline = "";\n'\
5091 ' var mode = list[i].id[5];\n'\
5092 ' if(mode == "s") {\n'\
5093 ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\
5094 ' val = (j-divTotal+1)*tS;\n'\
5095 ' if(j == divTotal - 1)\n'\
5096 ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\
5097 ' else\n'\
5098 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
5099 ' } else {\n'\
5100 ' pos = 100 - (((j)*tS*100)/mTotal);\n'\
5101 ' val = (j)*tS;\n'\
5102 ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\
5103 ' if(j == 0)\n'\
5104 ' if(mode == "r")\n'\
5105 ' htmlline = rline+"<cS>←R</cS></div>";\n'\
5106 ' else\n'\
5107 ' htmlline = rline+"<cS>0ms</div>";\n'\
5108 ' }\n'\
5109 ' html += htmlline;\n'\
5110 ' }\n'\
5111 ' timescale.innerHTML = html;\n'\
5112 ' }\n'\
5113 ' }\n'\
5114 ' function zoomTimeline() {\n'\
5115 ' var dmesg = document.getElementById("dmesg");\n'\
5116 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
5117 ' var left = zoombox.scrollLeft;\n'\
5118 ' var val = parseFloat(dmesg.style.width);\n'\
5119 ' var newval = 100;\n'\
5120 ' var sh = window.outerWidth / 2;\n'\
5121 ' if(this.id == "zoomin") {\n'\
5122 ' newval = val * 1.2;\n'\
5123 ' if(newval > 910034) newval = 910034;\n'\
5124 ' dmesg.style.width = newval+"%";\n'\
5125 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
5126 ' } else if (this.id == "zoomout") {\n'\
5127 ' newval = val / 1.2;\n'\
5128 ' if(newval < 100) newval = 100;\n'\
5129 ' dmesg.style.width = newval+"%";\n'\
5130 ' zoombox.scrollLeft = ((left + sh) * newval / val) - sh;\n'\
5131 ' } else {\n'\
5132 ' zoombox.scrollLeft = 0;\n'\
5133 ' dmesg.style.width = "100%";\n'\
5134 ' }\n'\
5135 ' var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];\n'\
5136 ' var t0 = bounds[0];\n'\
5137 ' var tMax = bounds[1];\n'\
5138 ' var tTotal = tMax - t0;\n'\
5139 ' var wTotal = tTotal * 100.0 / newval;\n'\
5140 ' var idx = 7*window.innerWidth/1100;\n'\
5141 ' for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);\n'\
5142 ' if(i >= tS.length) i = tS.length - 1;\n'\
5143 ' if(tS[i] == resolution) return;\n'\
5144 ' resolution = tS[i];\n'\
5145 ' redrawTimescale(t0, tMax, tS[i]);\n'\
5146 ' }\n'\
5147 ' function deviceName(title) {\n'\
5148 ' var name = title.slice(0, title.indexOf(" ("));\n'\
5149 ' return name;\n'\
5150 ' }\n'\
5151 ' function deviceHover() {\n'\
5152 ' var name = deviceName(this.title);\n'\
5153 ' var dmesg = document.getElementById("dmesg");\n'\
5154 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5155 ' var cpu = -1;\n'\
5156 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
5157 ' cpu = parseInt(name.slice(7));\n'\
5158 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
5159 ' cpu = parseInt(name.slice(8));\n'\
5160 ' for (var i = 0; i < dev.length; i++) {\n'\
5161 ' dname = deviceName(dev[i].title);\n'\
5162 ' var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
5163 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
5164 ' (name == dname))\n'\
5165 ' {\n'\
5166 ' dev[i].className = "hover "+cname;\n'\
5167 ' } else {\n'\
5168 ' dev[i].className = cname;\n'\
5169 ' }\n'\
5170 ' }\n'\
5171 ' }\n'\
5172 ' function deviceUnhover() {\n'\
5173 ' var dmesg = document.getElementById("dmesg");\n'\
5174 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5175 ' for (var i = 0; i < dev.length; i++) {\n'\
5176 ' dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));\n'\
5177 ' }\n'\
5178 ' }\n'\
5179 ' function deviceTitle(title, total, cpu) {\n'\
5180 ' var prefix = "Total";\n'\
5181 ' if(total.length > 3) {\n'\
5182 ' prefix = "Average";\n'\
5183 ' total[1] = (total[1]+total[3])/2;\n'\
5184 ' total[2] = (total[2]+total[4])/2;\n'\
5185 ' }\n'\
5186 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
5187 ' var name = deviceName(title);\n'\
5188 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
5189 ' var driver = "";\n'\
5190 ' var tS = "<t2>(</t2>";\n'\
5191 ' var tR = "<t2>)</t2>";\n'\
5192 ' if(total[1] > 0)\n'\
5193 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
5194 ' if(total[2] > 0)\n'\
5195 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
5196 ' var s = title.indexOf("{");\n'\
5197 ' var e = title.indexOf("}");\n'\
5198 ' if((s >= 0) && (e >= 0))\n'\
5199 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
5200 ' if(total[1] > 0 && total[2] > 0)\n'\
5201 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
5202 ' else\n'\
5203 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
5204 ' return name;\n'\
5205 ' }\n'\
5206 ' function deviceDetail() {\n'\
5207 ' var devinfo = document.getElementById("devicedetail");\n'\
5208 ' devinfo.style.display = "block";\n'\
5209 ' var name = deviceName(this.title);\n'\
5210 ' var cpu = -1;\n'\
5211 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
5212 ' cpu = parseInt(name.slice(7));\n'\
5213 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
5214 ' cpu = parseInt(name.slice(8));\n'\
5215 ' var dmesg = document.getElementById("dmesg");\n'\
5216 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5217 ' var idlist = [];\n'\
5218 ' var pdata = [[]];\n'\
5219 ' if(document.getElementById("devicedetail1"))\n'\
5220 ' pdata = [[], []];\n'\
5221 ' var pd = pdata[0];\n'\
5222 ' var total = [0.0, 0.0, 0.0];\n'\
5223 ' for (var i = 0; i < dev.length; i++) {\n'\
5224 ' dname = deviceName(dev[i].title);\n'\
5225 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
5226 ' (name == dname))\n'\
5227 ' {\n'\
5228 ' idlist[idlist.length] = dev[i].id;\n'\
5229 ' var tidx = 1;\n'\
5230 ' if(dev[i].id[0] == "a") {\n'\
5231 ' pd = pdata[0];\n'\
5232 ' } else {\n'\
5233 ' if(pdata.length == 1) pdata[1] = [];\n'\
5234 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
5235 ' pd = pdata[1];\n'\
5236 ' tidx = 3;\n'\
5237 ' }\n'\
5238 ' var info = dev[i].title.split(" ");\n'\
5239 ' var pname = info[info.length-1];\n'\
5240 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
5241 ' total[0] += pd[pname];\n'\
5242 ' if(pname.indexOf("suspend") >= 0)\n'\
5243 ' total[tidx] += pd[pname];\n'\
5244 ' else\n'\
5245 ' total[tidx+1] += pd[pname];\n'\
5246 ' }\n'\
5247 ' }\n'\
5248 ' var devname = deviceTitle(this.title, total, cpu);\n'\
5249 ' var left = 0.0;\n'\
5250 ' for (var t = 0; t < pdata.length; t++) {\n'\
5251 ' pd = pdata[t];\n'\
5252 ' devinfo = document.getElementById("devicedetail"+t);\n'\
5253 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
5254 ' for (var i = 0; i < phases.length; i++) {\n'\
5255 ' if(phases[i].id in pd) {\n'\
5256 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
5257 ' var fs = 32;\n'\
5258 ' if(w < 8) fs = 4*w | 0;\n'\
5259 ' var fs2 = fs*3/4;\n'\
5260 ' phases[i].style.width = w+"%";\n'\
5261 ' phases[i].style.left = left+"%";\n'\
5262 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
5263 ' left += w;\n'\
5264 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
5265 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";\n'\
5266 ' phases[i].innerHTML = time+pname;\n'\
5267 ' } else {\n'\
5268 ' phases[i].style.width = "0%";\n'\
5269 ' phases[i].style.left = left+"%";\n'\
5270 ' }\n'\
5271 ' }\n'\
5272 ' }\n'\
5273 ' if(typeof devstats !== \'undefined\')\n'\
5274 ' callDetail(this.id, this.title);\n'\
5275 ' var cglist = document.getElementById("callgraphs");\n'\
5276 ' if(!cglist) return;\n'\
5277 ' var cg = cglist.getElementsByClassName("atop");\n'\
5278 ' if(cg.length < 10) return;\n'\
5279 ' for (var i = 0; i < cg.length; i++) {\n'\
5280 ' cgid = cg[i].id.split("x")[0]\n'\
5281 ' if(idlist.indexOf(cgid) >= 0) {\n'\
5282 ' cg[i].style.display = "block";\n'\
5283 ' } else {\n'\
5284 ' cg[i].style.display = "none";\n'\
5285 ' }\n'\
5286 ' }\n'\
5287 ' }\n'\
5288 ' function callDetail(devid, devtitle) {\n'\
5289 ' if(!(devid in devstats) || devstats[devid].length < 1)\n'\
5290 ' return;\n'\
5291 ' var list = devstats[devid];\n'\
5292 ' var tmp = devtitle.split(" ");\n'\
5293 ' var name = tmp[0], phase = tmp[tmp.length-1];\n'\
5294 ' var dd = document.getElementById(phase);\n'\
5295 ' var total = parseFloat(tmp[1].slice(1));\n'\
5296 ' var mlist = [];\n'\
5297 ' var maxlen = 0;\n'\
5298 ' var info = []\n'\
5299 ' for(var i in list) {\n'\
5300 ' if(list[i][0] == "@") {\n'\
5301 ' info = list[i].split("|");\n'\
5302 ' continue;\n'\
5303 ' }\n'\
5304 ' var tmp = list[i].split("|");\n'\
5305 ' var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\
5306 ' var p = (t*100.0/total).toFixed(2);\n'\
5307 ' mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\
5308 ' if(f.length > maxlen)\n'\
5309 ' maxlen = f.length;\n'\
5310 ' }\n'\
5311 ' var pad = 5;\n'\
5312 ' if(mlist.length == 0) pad = 30;\n'\
5313 ' var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\
5314 ' if(info.length > 2)\n'\
5315 ' html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\
5316 ' if(info.length > 3)\n'\
5317 ' html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\
5318 ' if(info.length > 4)\n'\
5319 ' html += ", return=<b>"+info[4]+"</b>";\n'\
5320 ' html += "</t3></div>";\n'\
5321 ' if(mlist.length > 0) {\n'\
5322 ' html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\
5323 ' for(var i in mlist)\n'\
5324 ' html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\
5325 ' html += "</tr><tr><th>Calls</th>";\n'\
5326 ' for(var i in mlist)\n'\
5327 ' html += "<td>"+mlist[i][1]+"</td>";\n'\
5328 ' html += "</tr><tr><th>Time(ms)</th>";\n'\
5329 ' for(var i in mlist)\n'\
5330 ' html += "<td>"+mlist[i][2]+"</td>";\n'\
5331 ' html += "</tr><tr><th>Percent</th>";\n'\
5332 ' for(var i in mlist)\n'\
5333 ' html += "<td>"+mlist[i][3]+"</td>";\n'\
5334 ' html += "</tr></table>";\n'\
5335 ' }\n'\
5336 ' dd.innerHTML = html;\n'\
5337 ' var height = (maxlen*5)+100;\n'\
5338 ' dd.style.height = height+"px";\n'\
5339 ' document.getElementById("devicedetail").style.height = height+"px";\n'\
5340 ' }\n'\
5341 ' function callSelect() {\n'\
5342 ' var cglist = document.getElementById("callgraphs");\n'\
5343 ' if(!cglist) return;\n'\
5344 ' var cg = cglist.getElementsByClassName("atop");\n'\
5345 ' for (var i = 0; i < cg.length; i++) {\n'\
5346 ' if(this.id == cg[i].id) {\n'\
5347 ' cg[i].style.display = "block";\n'\
5348 ' } else {\n'\
5349 ' cg[i].style.display = "none";\n'\
5350 ' }\n'\
5351 ' }\n'\
5352 ' }\n'\
5353 ' function devListWindow(e) {\n'\
5354 ' var win = window.open();\n'\
5355 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
5356 ' "<style type=\\"text/css\\">"+\n'\
5357 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
5358 ' "</style>"\n'\
5359 ' var dt = devtable[0];\n'\
5360 ' if(e.target.id != "devlist1")\n'\
5361 ' dt = devtable[1];\n'\
5362 ' win.document.write(html+dt);\n'\
5363 ' }\n'\
5364 ' function errWindow() {\n'\
5365 ' var range = this.id.split("_");\n'\
5366 ' var idx1 = parseInt(range[0]);\n'\
5367 ' var idx2 = parseInt(range[1]);\n'\
5368 ' var win = window.open();\n'\
5369 ' var log = document.getElementById("dmesglog");\n'\
5370 ' var title = "<title>dmesg log</title>";\n'\
5371 ' var text = log.innerHTML.split("\\n");\n'\
5372 ' var html = "";\n'\
5373 ' for(var i = 0; i < text.length; i++) {\n'\
5374 ' if(i == idx1) {\n'\
5375 ' html += "<e id=target>"+text[i]+"</e>\\n";\n'\
5376 ' } else if(i > idx1 && i <= idx2) {\n'\
5377 ' html += "<e>"+text[i]+"</e>\\n";\n'\
5378 ' } else {\n'\
5379 ' html += text[i]+"\\n";\n'\
5380 ' }\n'\
5381 ' }\n'\
5382 ' win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");\n'\
5383 ' win.location.hash = "#target";\n'\
5384 ' win.document.close();\n'\
5385 ' }\n'\
5386 ' function logWindow(e) {\n'\
5387 ' var name = e.target.id.slice(4);\n'\
5388 ' var win = window.open();\n'\
5389 ' var log = document.getElementById(name+"log");\n'\
5390 ' var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";\n'\
5391 ' win.document.write(title+"<pre>"+log.innerHTML+"</pre>");\n'\
5392 ' win.document.close();\n'\
5393 ' }\n'\
5394 ' function onMouseDown(e) {\n'\
5395 ' dragval[0] = e.clientX;\n'\
5396 ' dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;\n'\
5397 ' document.onmousemove = onMouseMove;\n'\
5398 ' }\n'\
5399 ' function onMouseMove(e) {\n'\
5400 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
5401 ' zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;\n'\
5402 ' }\n'\
5403 ' function onMouseUp(e) {\n'\
5404 ' document.onmousemove = null;\n'\
5405 ' }\n'\
5406 ' function onKeyPress(e) {\n'\
5407 ' var c = e.charCode;\n'\
5408 ' if(c != 42 && c != 43 && c != 45) return;\n'\
5409 ' var click = document.createEvent("Events");\n'\
5410 ' click.initEvent("click", true, false);\n'\
5411 ' if(c == 43) \n'\
5412 ' document.getElementById("zoomin").dispatchEvent(click);\n'\
5413 ' else if(c == 45)\n'\
5414 ' document.getElementById("zoomout").dispatchEvent(click);\n'\
5415 ' else if(c == 42)\n'\
5416 ' document.getElementById("zoomdef").dispatchEvent(click);\n'\
5417 ' }\n'\
5418 ' window.addEventListener("resize", function () {zoomTimeline();});\n'\
5419 ' window.addEventListener("load", function () {\n'\
5420 ' var dmesg = document.getElementById("dmesg");\n'\
5421 ' dmesg.style.width = "100%"\n'\
5422 ' dmesg.onmousedown = onMouseDown;\n'\
5423 ' document.onmouseup = onMouseUp;\n'\
5424 ' document.onkeypress = onKeyPress;\n'\
5425 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
5426 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
5427 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
5428 ' var list = document.getElementsByClassName("err");\n'\
5429 ' for (var i = 0; i < list.length; i++)\n'\
5430 ' list[i].onclick = errWindow;\n'\
5431 ' var list = document.getElementsByClassName("logbtn");\n'\
5432 ' for (var i = 0; i < list.length; i++)\n'\
5433 ' list[i].onclick = logWindow;\n'\
5434 ' list = document.getElementsByClassName("devlist");\n'\
5435 ' for (var i = 0; i < list.length; i++)\n'\
5436 ' list[i].onclick = devListWindow;\n'\
5437 ' var dev = dmesg.getElementsByClassName("thread");\n'\
5438 ' for (var i = 0; i < dev.length; i++) {\n'\
5439 ' dev[i].onclick = deviceDetail;\n'\
5440 ' dev[i].onmouseover = deviceHover;\n'\
5441 ' dev[i].onmouseout = deviceUnhover;\n'\
5442 ' }\n'\
5443 ' var dev = dmesg.getElementsByClassName("srccall");\n'\
5444 ' for (var i = 0; i < dev.length; i++)\n'\
5445 ' dev[i].onclick = callSelect;\n'\
5446 ' zoomTimeline();\n'\
5447 ' });\n'\
5448 '</script>\n'
5449 hf.write(script_code);
5450
5451# Function: executeSuspend
5452# Description:
5453# Execute system suspend through the sysfs interface, then copy the output
5454# dmesg and ftrace files to the test output directory.
5455def executeSuspend(quiet=False):
5456 sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor()
5457 if sv.wifi:
5458 wifi = sv.checkWifi()
5459 sv.dlog('wifi check, connected device is "%s"' % wifi)
5460 testdata = []
5461 # run these commands to prepare the system for suspend
5462 if sv.display:
5463 if not quiet:
5464 pprint('SET DISPLAY TO %s' % sv.display.upper())
5465 ret = sv.displayControl(sv.display)
5466 sv.dlog('xset display %s, ret = %d' % (sv.display, ret))
5467 time.sleep(1)
5468 if sv.sync:
5469 if not quiet:
5470 pprint('SYNCING FILESYSTEMS')
5471 sv.dlog('syncing filesystems')
5472 call('sync', shell=True)
5473 sv.dlog('read dmesg')
5474 sv.initdmesg()
5475 sv.dlog('cmdinfo before')
5476 sv.cmdinfo(True)
5477 sv.start(pm)
5478 # execute however many s/r runs requested
5479 for count in range(1,sv.execcount+1):
5480 # x2delay in between test runs
5481 if(count > 1 and sv.x2delay > 0):
5482 sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker')
5483 time.sleep(sv.x2delay/1000.0)
5484 sv.fsetVal('WAIT END', 'trace_marker')
5485 # start message
5486 if sv.testcommand != '':
5487 pprint('COMMAND START')
5488 else:
5489 if(sv.rtcwake):
5490 pprint('SUSPEND START')
5491 else:
5492 pprint('SUSPEND START (press a key to resume)')
5493 # set rtcwake
5494 if(sv.rtcwake):
5495 if not quiet:
5496 pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime)
5497 sv.dlog('enable RTC wake alarm')
5498 sv.rtcWakeAlarmOn()
5499 # start of suspend trace marker
5500 sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
5501 # predelay delay
5502 if(count == 1 and sv.predelay > 0):
5503 sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
5504 time.sleep(sv.predelay/1000.0)
5505 sv.fsetVal('WAIT END', 'trace_marker')
5506 # initiate suspend or command
5507 sv.dlog('system executing a suspend')
5508 tdata = {'error': ''}
5509 if sv.testcommand != '':
5510 res = call(sv.testcommand+' 2>&1', shell=True);
5511 if res != 0:
5512 tdata['error'] = 'cmd returned %d' % res
5513 else:
5514 s0ixready = sv.s0ixSupport()
5515 mode = sv.suspendmode
5516 if sv.memmode and os.path.exists(sv.mempowerfile):
5517 mode = 'mem'
5518 sv.testVal(sv.mempowerfile, 'radio', sv.memmode)
5519 if sv.diskmode and os.path.exists(sv.diskpowerfile):
5520 mode = 'disk'
5521 sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode)
5522 if sv.acpidebug:
5523 sv.testVal(sv.acpipath, 'acpi', '0xe')
5524 if ((mode == 'freeze') or (sv.memmode == 's2idle')) \
5525 and sv.haveTurbostat():
5526 # execution will pause here
5527 turbo = sv.turbostat(s0ixready)
5528 if turbo:
5529 tdata['turbo'] = turbo
5530 else:
5531 pf = open(sv.powerfile, 'w')
5532 pf.write(mode)
5533 # execution will pause here
5534 try:
5535 pf.close()
5536 except Exception as e:
5537 tdata['error'] = str(e)
5538 sv.fsetVal('CMD COMPLETE', 'trace_marker')
5539 sv.dlog('system returned')
5540 # reset everything
5541 sv.testVal('restoreall')
5542 if(sv.rtcwake):
5543 sv.dlog('disable RTC wake alarm')
5544 sv.rtcWakeAlarmOff()
5545 # postdelay delay
5546 if(count == sv.execcount and sv.postdelay > 0):
5547 sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker')
5548 time.sleep(sv.postdelay/1000.0)
5549 sv.fsetVal('WAIT END', 'trace_marker')
5550 # return from suspend
5551 pprint('RESUME COMPLETE')
5552 if(count < sv.execcount):
5553 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5554 elif(not sv.wifitrace):
5555 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5556 sv.stop(pm)
5557 if sv.wifi and wifi:
5558 tdata['wifi'] = sv.pollWifi(wifi)
5559 sv.dlog('wifi check, %s' % tdata['wifi'])
5560 if(count == sv.execcount and sv.wifitrace):
5561 sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5562 sv.stop(pm)
5563 if sv.netfix:
5564 tdata['netfix'] = sv.netfixon()
5565 sv.dlog('netfix, %s' % tdata['netfix'])
5566 if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
5567 sv.dlog('read the ACPI FPDT')
5568 tdata['fw'] = getFPDT(False)
5569 testdata.append(tdata)
5570 sv.dlog('cmdinfo after')
5571 cmdafter = sv.cmdinfo(False)
5572 # grab a copy of the dmesg output
5573 if not quiet:
5574 pprint('CAPTURING DMESG')
5575 sv.getdmesg(testdata)
5576 # grab a copy of the ftrace output
5577 if sv.useftrace:
5578 if not quiet:
5579 pprint('CAPTURING TRACE')
5580 op = sv.writeDatafileHeader(sv.ftracefile, testdata)
5581 fp = open(tp+'trace', 'rb')
5582 op.write(ascii(fp.read()))
5583 op.close()
5584 sv.fsetVal('', 'trace')
5585 sv.platforminfo(cmdafter)
5586
5587def readFile(file):
5588 if os.path.islink(file):
5589 return os.readlink(file).split('/')[-1]
5590 else:
5591 return sysvals.getVal(file).strip()
5592
5593# Function: ms2nice
5594# Description:
5595# Print out a very concise time string in minutes and seconds
5596# Output:
5597# The time string, e.g. "1901m16s"
5598def ms2nice(val):
5599 val = int(val)
5600 h = val // 3600000
5601 m = (val // 60000) % 60
5602 s = (val // 1000) % 60
5603 if h > 0:
5604 return '%d:%02d:%02d' % (h, m, s)
5605 if m > 0:
5606 return '%02d:%02d' % (m, s)
5607 return '%ds' % s
5608
5609def yesno(val):
5610 list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5611 'active':'A', 'suspended':'S', 'suspending':'S'}
5612 if val not in list:
5613 return ' '
5614 return list[val]
5615
5616# Function: deviceInfo
5617# Description:
5618# Detect all the USB hosts and devices currently connected and add
5619# a list of USB device names to sysvals for better timeline readability
5620def deviceInfo(output=''):
5621 if not output:
5622 pprint('LEGEND\n'\
5623 '---------------------------------------------------------------------------------------------\n'\
5624 ' A = async/sync PM queue (A/S) C = runtime active children\n'\
5625 ' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)\n'\
5626 ' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)\n'\
5627 ' U = runtime usage count\n'\
5628 '---------------------------------------------------------------------------------------------\n'\
5629 'DEVICE NAME A R S U C rACTIVE rSUSPEND\n'\
5630 '---------------------------------------------------------------------------------------------')
5631
5632 res = []
5633 tgtval = 'runtime_status'
5634 lines = dict()
5635 for dirname, dirnames, filenames in os.walk('/sys/devices'):
5636 if(not re.match('.*/power', dirname) or
5637 'control' not in filenames or
5638 tgtval not in filenames):
5639 continue
5640 name = ''
5641 dirname = dirname[:-6]
5642 device = dirname.split('/')[-1]
5643 power = dict()
5644 power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5645 # only list devices which support runtime suspend
5646 if power[tgtval] not in ['active', 'suspended', 'suspending']:
5647 continue
5648 for i in ['product', 'driver', 'subsystem']:
5649 file = '%s/%s' % (dirname, i)
5650 if os.path.exists(file):
5651 name = readFile(file)
5652 break
5653 for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5654 'runtime_active_kids', 'runtime_active_time',
5655 'runtime_suspended_time']:
5656 if i in filenames:
5657 power[i] = readFile('%s/power/%s' % (dirname, i))
5658 if output:
5659 if power['control'] == output:
5660 res.append('%s/power/control' % dirname)
5661 continue
5662 lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5663 (device[:26], name[:26],
5664 yesno(power['async']), \
5665 yesno(power['control']), \
5666 yesno(power['runtime_status']), \
5667 power['runtime_usage'], \
5668 power['runtime_active_kids'], \
5669 ms2nice(power['runtime_active_time']), \
5670 ms2nice(power['runtime_suspended_time']))
5671 for i in sorted(lines):
5672 print(lines[i])
5673 return res
5674
5675# Function: getModes
5676# Description:
5677# Determine the supported power modes on this system
5678# Output:
5679# A string list of the available modes
5680def getModes():
5681 modes = []
5682 if(os.path.exists(sysvals.powerfile)):
5683 fp = open(sysvals.powerfile, 'r')
5684 modes = fp.read().split()
5685 fp.close()
5686 if(os.path.exists(sysvals.mempowerfile)):
5687 deep = False
5688 fp = open(sysvals.mempowerfile, 'r')
5689 for m in fp.read().split():
5690 memmode = m.strip('[]')
5691 if memmode == 'deep':
5692 deep = True
5693 else:
5694 modes.append('mem-%s' % memmode)
5695 fp.close()
5696 if 'mem' in modes and not deep:
5697 modes.remove('mem')
5698 if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5699 fp = open(sysvals.diskpowerfile, 'r')
5700 for m in fp.read().split():
5701 modes.append('disk-%s' % m.strip('[]'))
5702 fp.close()
5703 return modes
5704
5705# Function: dmidecode
5706# Description:
5707# Read the bios tables and pull out system info
5708# Arguments:
5709# mempath: /dev/mem or custom mem path
5710# fatal: True to exit on error, False to return empty dict
5711# Output:
5712# A dict object with all available key/values
5713def dmidecode(mempath, fatal=False):
5714 out = dict()
5715
5716 # the list of values to retrieve, with hardcoded (type, idx)
5717 info = {
5718 'bios-vendor': (0, 4),
5719 'bios-version': (0, 5),
5720 'bios-release-date': (0, 8),
5721 'system-manufacturer': (1, 4),
5722 'system-product-name': (1, 5),
5723 'system-version': (1, 6),
5724 'system-serial-number': (1, 7),
5725 'baseboard-manufacturer': (2, 4),
5726 'baseboard-product-name': (2, 5),
5727 'baseboard-version': (2, 6),
5728 'baseboard-serial-number': (2, 7),
5729 'chassis-manufacturer': (3, 4),
5730 'chassis-type': (3, 5),
5731 'chassis-version': (3, 6),
5732 'chassis-serial-number': (3, 7),
5733 'processor-manufacturer': (4, 7),
5734 'processor-version': (4, 16),
5735 }
5736 if(not os.path.exists(mempath)):
5737 if(fatal):
5738 doError('file does not exist: %s' % mempath)
5739 return out
5740 if(not os.access(mempath, os.R_OK)):
5741 if(fatal):
5742 doError('file is not readable: %s' % mempath)
5743 return out
5744
5745 # by default use legacy scan, but try to use EFI first
5746 memaddr = 0xf0000
5747 memsize = 0x10000
5748 for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5749 if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5750 continue
5751 fp = open(ep, 'r')
5752 buf = fp.read()
5753 fp.close()
5754 i = buf.find('SMBIOS=')
5755 if i >= 0:
5756 try:
5757 memaddr = int(buf[i+7:], 16)
5758 memsize = 0x20
5759 except:
5760 continue
5761
5762 # read in the memory for scanning
5763 try:
5764 fp = open(mempath, 'rb')
5765 fp.seek(memaddr)
5766 buf = fp.read(memsize)
5767 except:
5768 if(fatal):
5769 doError('DMI table is unreachable, sorry')
5770 else:
5771 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5772 return out
5773 fp.close()
5774
5775 # search for either an SM table or DMI table
5776 i = base = length = num = 0
5777 while(i < memsize):
5778 if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5779 length = struct.unpack('H', buf[i+22:i+24])[0]
5780 base, num = struct.unpack('IH', buf[i+24:i+30])
5781 break
5782 elif buf[i:i+5] == b'_DMI_':
5783 length = struct.unpack('H', buf[i+6:i+8])[0]
5784 base, num = struct.unpack('IH', buf[i+8:i+14])
5785 break
5786 i += 16
5787 if base == 0 and length == 0 and num == 0:
5788 if(fatal):
5789 doError('Neither SMBIOS nor DMI were found')
5790 else:
5791 return out
5792
5793 # read in the SM or DMI table
5794 try:
5795 fp = open(mempath, 'rb')
5796 fp.seek(base)
5797 buf = fp.read(length)
5798 except:
5799 if(fatal):
5800 doError('DMI table is unreachable, sorry')
5801 else:
5802 pprint('WARNING: /dev/mem is not readable, ignoring DMI data')
5803 return out
5804 fp.close()
5805
5806 # scan the table for the values we want
5807 count = i = 0
5808 while(count < num and i <= len(buf) - 4):
5809 type, size, handle = struct.unpack('BBH', buf[i:i+4])
5810 n = i + size
5811 while n < len(buf) - 1:
5812 if 0 == struct.unpack('H', buf[n:n+2])[0]:
5813 break
5814 n += 1
5815 data = buf[i+size:n+2].split(b'\0')
5816 for name in info:
5817 itype, idxadr = info[name]
5818 if itype == type:
5819 idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5820 if idx > 0 and idx < len(data) - 1:
5821 s = data[idx-1].decode('utf-8')
5822 if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5823 out[name] = s
5824 i = n + 2
5825 count += 1
5826 return out
5827
5828# Function: getFPDT
5829# Description:
5830# Read the acpi bios tables and pull out FPDT, the firmware data
5831# Arguments:
5832# output: True to output the info to stdout, False otherwise
5833def getFPDT(output):
5834 rectype = {}
5835 rectype[0] = 'Firmware Basic Boot Performance Record'
5836 rectype[1] = 'S3 Performance Table Record'
5837 prectype = {}
5838 prectype[0] = 'Basic S3 Resume Performance Record'
5839 prectype[1] = 'Basic S3 Suspend Performance Record'
5840
5841 sysvals.rootCheck(True)
5842 if(not os.path.exists(sysvals.fpdtpath)):
5843 if(output):
5844 doError('file does not exist: %s' % sysvals.fpdtpath)
5845 return False
5846 if(not os.access(sysvals.fpdtpath, os.R_OK)):
5847 if(output):
5848 doError('file is not readable: %s' % sysvals.fpdtpath)
5849 return False
5850 if(not os.path.exists(sysvals.mempath)):
5851 if(output):
5852 doError('file does not exist: %s' % sysvals.mempath)
5853 return False
5854 if(not os.access(sysvals.mempath, os.R_OK)):
5855 if(output):
5856 doError('file is not readable: %s' % sysvals.mempath)
5857 return False
5858
5859 fp = open(sysvals.fpdtpath, 'rb')
5860 buf = fp.read()
5861 fp.close()
5862
5863 if(len(buf) < 36):
5864 if(output):
5865 doError('Invalid FPDT table data, should '+\
5866 'be at least 36 bytes')
5867 return False
5868
5869 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5870 if(output):
5871 pprint('\n'\
5872 'Firmware Performance Data Table (%s)\n'\
5873 ' Signature : %s\n'\
5874 ' Table Length : %u\n'\
5875 ' Revision : %u\n'\
5876 ' Checksum : 0x%x\n'\
5877 ' OEM ID : %s\n'\
5878 ' OEM Table ID : %s\n'\
5879 ' OEM Revision : %u\n'\
5880 ' Creator ID : %s\n'\
5881 ' Creator Revision : 0x%x\n'\
5882 '' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5883 table[3], ascii(table[4]), ascii(table[5]), table[6],
5884 ascii(table[7]), table[8]))
5885
5886 if(table[0] != b'FPDT'):
5887 if(output):
5888 doError('Invalid FPDT table')
5889 return False
5890 if(len(buf) <= 36):
5891 return False
5892 i = 0
5893 fwData = [0, 0]
5894 records = buf[36:]
5895 try:
5896 fp = open(sysvals.mempath, 'rb')
5897 except:
5898 pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5899 return False
5900 while(i < len(records)):
5901 header = struct.unpack('HBB', records[i:i+4])
5902 if(header[0] not in rectype):
5903 i += header[1]
5904 continue
5905 if(header[1] != 16):
5906 i += header[1]
5907 continue
5908 addr = struct.unpack('Q', records[i+8:i+16])[0]
5909 try:
5910 fp.seek(addr)
5911 first = fp.read(8)
5912 except:
5913 if(output):
5914 pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5915 return [0, 0]
5916 rechead = struct.unpack('4sI', first)
5917 recdata = fp.read(rechead[1]-8)
5918 if(rechead[0] == b'FBPT'):
5919 record = struct.unpack('HBBIQQQQQ', recdata[:48])
5920 if(output):
5921 pprint('%s (%s)\n'\
5922 ' Reset END : %u ns\n'\
5923 ' OS Loader LoadImage Start : %u ns\n'\
5924 ' OS Loader StartImage Start : %u ns\n'\
5925 ' ExitBootServices Entry : %u ns\n'\
5926 ' ExitBootServices Exit : %u ns'\
5927 '' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5928 record[6], record[7], record[8]))
5929 elif(rechead[0] == b'S3PT'):
5930 if(output):
5931 pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5932 j = 0
5933 while(j < len(recdata)):
5934 prechead = struct.unpack('HBB', recdata[j:j+4])
5935 if(prechead[0] not in prectype):
5936 continue
5937 if(prechead[0] == 0):
5938 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5939 fwData[1] = record[2]
5940 if(output):
5941 pprint(' %s\n'\
5942 ' Resume Count : %u\n'\
5943 ' FullResume : %u ns\n'\
5944 ' AverageResume : %u ns'\
5945 '' % (prectype[prechead[0]], record[1],
5946 record[2], record[3]))
5947 elif(prechead[0] == 1):
5948 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5949 fwData[0] = record[1] - record[0]
5950 if(output):
5951 pprint(' %s\n'\
5952 ' SuspendStart : %u ns\n'\
5953 ' SuspendEnd : %u ns\n'\
5954 ' SuspendTime : %u ns'\
5955 '' % (prectype[prechead[0]], record[0],
5956 record[1], fwData[0]))
5957
5958 j += prechead[1]
5959 if(output):
5960 pprint('')
5961 i += header[1]
5962 fp.close()
5963 return fwData
5964
5965# Function: statusCheck
5966# Description:
5967# Verify that the requested command and options will work, and
5968# print the results to the terminal
5969# Output:
5970# True if the test will work, False if not
5971def statusCheck(probecheck=False):
5972 status = ''
5973
5974 pprint('Checking this system (%s)...' % platform.node())
5975
5976 # check we have root access
5977 res = sysvals.colorText('NO (No features of this tool will work!)')
5978 if(sysvals.rootCheck(False)):
5979 res = 'YES'
5980 pprint(' have root access: %s' % res)
5981 if(res != 'YES'):
5982 pprint(' Try running this script with sudo')
5983 return 'missing root access'
5984
5985 # check sysfs is mounted
5986 res = sysvals.colorText('NO (No features of this tool will work!)')
5987 if(os.path.exists(sysvals.powerfile)):
5988 res = 'YES'
5989 pprint(' is sysfs mounted: %s' % res)
5990 if(res != 'YES'):
5991 return 'sysfs is missing'
5992
5993 # check target mode is a valid mode
5994 if sysvals.suspendmode != 'command':
5995 res = sysvals.colorText('NO')
5996 modes = getModes()
5997 if(sysvals.suspendmode in modes):
5998 res = 'YES'
5999 else:
6000 status = '%s mode is not supported' % sysvals.suspendmode
6001 pprint(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
6002 if(res == 'NO'):
6003 pprint(' valid power modes are: %s' % modes)
6004 pprint(' please choose one with -m')
6005
6006 # check if ftrace is available
6007 if sysvals.useftrace:
6008 res = sysvals.colorText('NO')
6009 sysvals.useftrace = sysvals.verifyFtrace()
6010 efmt = '"{0}" uses ftrace, and it is not properly supported'
6011 if sysvals.useftrace:
6012 res = 'YES'
6013 elif sysvals.usecallgraph:
6014 status = efmt.format('-f')
6015 elif sysvals.usedevsrc:
6016 status = efmt.format('-dev')
6017 elif sysvals.useprocmon:
6018 status = efmt.format('-proc')
6019 pprint(' is ftrace supported: %s' % res)
6020
6021 # check if kprobes are available
6022 if sysvals.usekprobes:
6023 res = sysvals.colorText('NO')
6024 sysvals.usekprobes = sysvals.verifyKprobes()
6025 if(sysvals.usekprobes):
6026 res = 'YES'
6027 else:
6028 sysvals.usedevsrc = False
6029 pprint(' are kprobes supported: %s' % res)
6030
6031 # what data source are we using
6032 res = 'DMESG (very limited, ftrace is preferred)'
6033 if sysvals.useftrace:
6034 sysvals.usetraceevents = True
6035 for e in sysvals.traceevents:
6036 if not os.path.exists(sysvals.epath+e):
6037 sysvals.usetraceevents = False
6038 if(sysvals.usetraceevents):
6039 res = 'FTRACE (all trace events found)'
6040 pprint(' timeline data source: %s' % res)
6041
6042 # check if rtcwake
6043 res = sysvals.colorText('NO')
6044 if(sysvals.rtcpath != ''):
6045 res = 'YES'
6046 elif(sysvals.rtcwake):
6047 status = 'rtcwake is not properly supported'
6048 pprint(' is rtcwake supported: %s' % res)
6049
6050 # check info commands
6051 pprint(' optional commands this tool may use for info:')
6052 no = sysvals.colorText('MISSING')
6053 yes = sysvals.colorText('FOUND', 32)
6054 for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
6055 if c == 'turbostat':
6056 res = yes if sysvals.haveTurbostat() else no
6057 else:
6058 res = yes if sysvals.getExec(c) else no
6059 pprint(' %s: %s' % (c, res))
6060
6061 if not probecheck:
6062 return status
6063
6064 # verify kprobes
6065 if sysvals.usekprobes:
6066 for name in sysvals.tracefuncs:
6067 sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
6068 if sysvals.usedevsrc:
6069 for name in sysvals.dev_tracefuncs:
6070 sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
6071 sysvals.addKprobes(True)
6072
6073 return status
6074
6075# Function: doError
6076# Description:
6077# generic error function for catastrphic failures
6078# Arguments:
6079# msg: the error message to print
6080# help: True if printHelp should be called after, False otherwise
6081def doError(msg, help=False):
6082 if(help == True):
6083 printHelp()
6084 pprint('ERROR: %s\n' % msg)
6085 sysvals.outputResult({'error':msg})
6086 sys.exit(1)
6087
6088# Function: getArgInt
6089# Description:
6090# pull out an integer argument from the command line with checks
6091def getArgInt(name, args, min, max, main=True):
6092 if main:
6093 try:
6094 arg = next(args)
6095 except:
6096 doError(name+': no argument supplied', True)
6097 else:
6098 arg = args
6099 try:
6100 val = int(arg)
6101 except:
6102 doError(name+': non-integer value given', True)
6103 if(val < min or val > max):
6104 doError(name+': value should be between %d and %d' % (min, max), True)
6105 return val
6106
6107# Function: getArgFloat
6108# Description:
6109# pull out a float argument from the command line with checks
6110def getArgFloat(name, args, min, max, main=True):
6111 if main:
6112 try:
6113 arg = next(args)
6114 except:
6115 doError(name+': no argument supplied', True)
6116 else:
6117 arg = args
6118 try:
6119 val = float(arg)
6120 except:
6121 doError(name+': non-numerical value given', True)
6122 if(val < min or val > max):
6123 doError(name+': value should be between %f and %f' % (min, max), True)
6124 return val
6125
6126def processData(live=False, quiet=False):
6127 if not quiet:
6128 pprint('PROCESSING: %s' % sysvals.htmlfile)
6129 sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
6130 (sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
6131 error = ''
6132 if(sysvals.usetraceevents):
6133 testruns, error = parseTraceLog(live)
6134 if sysvals.dmesgfile:
6135 for data in testruns:
6136 data.extractErrorInfo()
6137 else:
6138 testruns = loadKernelLog()
6139 for data in testruns:
6140 parseKernelLog(data)
6141 if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
6142 appendIncompleteTraceLog(testruns)
6143 if not sysvals.stamp:
6144 pprint('ERROR: data does not include the expected stamp')
6145 return (testruns, {'error': 'timeline generation failed'})
6146 shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
6147 'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
6148 sysvals.vprint('System Info:')
6149 for key in sorted(sysvals.stamp):
6150 if key in shown:
6151 sysvals.vprint(' %-8s : %s' % (key.upper(), sysvals.stamp[key]))
6152 sysvals.vprint('Command:\n %s' % sysvals.cmdline)
6153 for data in testruns:
6154 if data.turbostat:
6155 idx, s = 0, 'Turbostat:\n '
6156 for val in data.turbostat.split('|'):
6157 idx += len(val) + 1
6158 if idx >= 80:
6159 idx = 0
6160 s += '\n '
6161 s += val + ' '
6162 sysvals.vprint(s)
6163 data.printDetails()
6164 if len(sysvals.platinfo) > 0:
6165 sysvals.vprint('\nPlatform Info:')
6166 for info in sysvals.platinfo:
6167 sysvals.vprint('[%s - %s]' % (info[0], info[1]))
6168 sysvals.vprint(info[2])
6169 sysvals.vprint('')
6170 if sysvals.cgdump:
6171 for data in testruns:
6172 data.debugPrint()
6173 sys.exit(0)
6174 if len(testruns) < 1:
6175 pprint('ERROR: Not enough test data to build a timeline')
6176 return (testruns, {'error': 'timeline generation failed'})
6177 sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6178 createHTML(testruns, error)
6179 if not quiet:
6180 pprint('DONE: %s' % sysvals.htmlfile)
6181 data = testruns[0]
6182 stamp = data.stamp
6183 stamp['suspend'], stamp['resume'] = data.getTimeValues()
6184 if data.fwValid:
6185 stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6186 if error:
6187 stamp['error'] = error
6188 return (testruns, stamp)
6189
6190# Function: rerunTest
6191# Description:
6192# generate an output from an existing set of ftrace/dmesg logs
6193def rerunTest(htmlfile=''):
6194 if sysvals.ftracefile:
6195 doesTraceLogHaveTraceEvents()
6196 if not sysvals.dmesgfile and not sysvals.usetraceevents:
6197 doError('recreating this html output requires a dmesg file')
6198 if htmlfile:
6199 sysvals.htmlfile = htmlfile
6200 else:
6201 sysvals.setOutputFile()
6202 if os.path.exists(sysvals.htmlfile):
6203 if not os.path.isfile(sysvals.htmlfile):
6204 doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6205 elif not os.access(sysvals.htmlfile, os.W_OK):
6206 doError('missing permission to write to %s' % sysvals.htmlfile)
6207 testruns, stamp = processData()
6208 sysvals.resetlog()
6209 return stamp
6210
6211# Function: runTest
6212# Description:
6213# execute a suspend/resume, gather the logs, and generate the output
6214def runTest(n=0, quiet=False):
6215 # prepare for the test
6216 sysvals.initTestOutput('suspend')
6217 op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6218 op.write('# EXECUTION TRACE START\n')
6219 op.close()
6220 if n <= 1:
6221 if sysvals.rs != 0:
6222 sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6223 sysvals.setRuntimeSuspend(True)
6224 if sysvals.display:
6225 ret = sysvals.displayControl('init')
6226 sysvals.dlog('xset display init, ret = %d' % ret)
6227 sysvals.testVal(sysvals.pmdpath, 'basic', '1')
6228 sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
6229 sysvals.dlog('initialize ftrace')
6230 sysvals.initFtrace(quiet)
6231
6232 # execute the test
6233 executeSuspend(quiet)
6234 sysvals.cleanupFtrace()
6235 if sysvals.skiphtml:
6236 sysvals.outputResult({}, n)
6237 sysvals.sudoUserchown(sysvals.testdir)
6238 return
6239 testruns, stamp = processData(True, quiet)
6240 for data in testruns:
6241 del data
6242 sysvals.sudoUserchown(sysvals.testdir)
6243 sysvals.outputResult(stamp, n)
6244 if 'error' in stamp:
6245 return 2
6246 return 0
6247
6248def find_in_html(html, start, end, firstonly=True):
6249 cnt, out, list = len(html), [], []
6250 if firstonly:
6251 m = re.search(start, html)
6252 if m:
6253 list.append(m)
6254 else:
6255 list = re.finditer(start, html)
6256 for match in list:
6257 s = match.end()
6258 e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6259 m = re.search(end, html[s:e])
6260 if not m:
6261 break
6262 e = s + m.start()
6263 str = html[s:e]
6264 if end == 'ms':
6265 num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6266 str = num.group() if num else 'NaN'
6267 if firstonly:
6268 return str
6269 out.append(str)
6270 if firstonly:
6271 return ''
6272 return out
6273
6274def data_from_html(file, outpath, issues, fulldetail=False):
6275 html = open(file, 'r').read()
6276 sysvals.htmlfile = os.path.relpath(file, outpath)
6277 # extract general info
6278 suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6279 resume = find_in_html(html, 'Kernel Resume', 'ms')
6280 sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6281 line = find_in_html(html, '<div class="stamp">', '</div>')
6282 stmp = line.split()
6283 if not suspend or not resume or len(stmp) != 8:
6284 return False
6285 try:
6286 dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6287 except:
6288 return False
6289 sysvals.hostname = stmp[0]
6290 tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6291 error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6292 if error:
6293 m = re.match('[a-z0-9]* failed in (?P<p>\S*).*', error)
6294 if m:
6295 result = 'fail in %s' % m.group('p')
6296 else:
6297 result = 'fail'
6298 else:
6299 result = 'pass'
6300 # extract error info
6301 tp, ilist = False, []
6302 extra = dict()
6303 log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6304 '</div>').strip()
6305 if log:
6306 d = Data(0)
6307 d.end = 999999999
6308 d.dmesgtext = log.split('\n')
6309 tp = d.extractErrorInfo()
6310 for msg in tp.msglist:
6311 sysvals.errorSummary(issues, msg)
6312 if stmp[2] == 'freeze':
6313 extra = d.turbostatInfo()
6314 elist = dict()
6315 for dir in d.errorinfo:
6316 for err in d.errorinfo[dir]:
6317 if err[0] not in elist:
6318 elist[err[0]] = 0
6319 elist[err[0]] += 1
6320 for i in elist:
6321 ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6322 line = find_in_html(log, '# wifi ', '\n')
6323 if line:
6324 extra['wifi'] = line
6325 line = find_in_html(log, '# netfix ', '\n')
6326 if line:
6327 extra['netfix'] = line
6328 low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6329 for lowstr in ['waking', '+']:
6330 if not low:
6331 break
6332 if lowstr not in low:
6333 continue
6334 if lowstr == '+':
6335 issue = 'S2LOOPx%d' % len(low.split('+'))
6336 else:
6337 m = re.match('.*waking *(?P<n>[0-9]*) *times.*', low)
6338 issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6339 match = [i for i in issues if i['match'] == issue]
6340 if len(match) > 0:
6341 match[0]['count'] += 1
6342 if sysvals.hostname not in match[0]['urls']:
6343 match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6344 elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6345 match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6346 else:
6347 issues.append({
6348 'match': issue, 'count': 1, 'line': issue,
6349 'urls': {sysvals.hostname: [sysvals.htmlfile]},
6350 })
6351 ilist.append(issue)
6352 # extract device info
6353 devices = dict()
6354 for line in html.split('\n'):
6355 m = re.match(' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6356 if not m or 'thread kth' in line or 'thread sec' in line:
6357 continue
6358 m = re.match('(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6359 if not m:
6360 continue
6361 name, time, phase = m.group('n'), m.group('t'), m.group('p')
6362 if name == 'async_synchronize_full':
6363 continue
6364 if ' async' in name or ' sync' in name:
6365 name = ' '.join(name.split(' ')[:-1])
6366 if phase.startswith('suspend'):
6367 d = 'suspend'
6368 elif phase.startswith('resume'):
6369 d = 'resume'
6370 else:
6371 continue
6372 if d not in devices:
6373 devices[d] = dict()
6374 if name not in devices[d]:
6375 devices[d][name] = 0.0
6376 devices[d][name] += float(time)
6377 # create worst device info
6378 worst = dict()
6379 for d in ['suspend', 'resume']:
6380 worst[d] = {'name':'', 'time': 0.0}
6381 dev = devices[d] if d in devices else 0
6382 if dev and len(dev.keys()) > 0:
6383 n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6384 worst[d]['name'], worst[d]['time'] = n, dev[n]
6385 data = {
6386 'mode': stmp[2],
6387 'host': stmp[0],
6388 'kernel': stmp[1],
6389 'sysinfo': sysinfo,
6390 'time': tstr,
6391 'result': result,
6392 'issues': ' '.join(ilist),
6393 'suspend': suspend,
6394 'resume': resume,
6395 'devlist': devices,
6396 'sus_worst': worst['suspend']['name'],
6397 'sus_worsttime': worst['suspend']['time'],
6398 'res_worst': worst['resume']['name'],
6399 'res_worsttime': worst['resume']['time'],
6400 'url': sysvals.htmlfile,
6401 }
6402 for key in extra:
6403 data[key] = extra[key]
6404 if fulldetail:
6405 data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6406 if tp:
6407 for arg in ['-multi ', '-info ']:
6408 if arg in tp.cmdline:
6409 data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6410 break
6411 return data
6412
6413def genHtml(subdir, force=False):
6414 for dirname, dirnames, filenames in os.walk(subdir):
6415 sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6416 for filename in filenames:
6417 file = os.path.join(dirname, filename)
6418 if sysvals.usable(file):
6419 if(re.match('.*_dmesg.txt', filename)):
6420 sysvals.dmesgfile = file
6421 elif(re.match('.*_ftrace.txt', filename)):
6422 sysvals.ftracefile = file
6423 sysvals.setOutputFile()
6424 if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6425 (force or not sysvals.usable(sysvals.htmlfile, True)):
6426 pprint('FTRACE: %s' % sysvals.ftracefile)
6427 if sysvals.dmesgfile:
6428 pprint('DMESG : %s' % sysvals.dmesgfile)
6429 rerunTest()
6430
6431# Function: runSummary
6432# Description:
6433# create a summary of tests in a sub-directory
6434def runSummary(subdir, local=True, genhtml=False):
6435 inpath = os.path.abspath(subdir)
6436 outpath = os.path.abspath('.') if local else inpath
6437 pprint('Generating a summary of folder:\n %s' % inpath)
6438 if genhtml:
6439 genHtml(subdir)
6440 target, issues, testruns = '', [], []
6441 desc = {'host':[],'mode':[],'kernel':[]}
6442 for dirname, dirnames, filenames in os.walk(subdir):
6443 for filename in filenames:
6444 if(not re.match('.*.html', filename)):
6445 continue
6446 data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6447 if(not data):
6448 continue
6449 if 'target' in data:
6450 target = data['target']
6451 testruns.append(data)
6452 for key in desc:
6453 if data[key] not in desc[key]:
6454 desc[key].append(data[key])
6455 pprint('Summary files:')
6456 if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6457 title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6458 if target:
6459 title += ' %s' % target
6460 else:
6461 title = inpath
6462 createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6463 pprint(' summary.html - tabular list of test data found')
6464 createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6465 pprint(' summary-devices.html - kernel device list sorted by total execution time')
6466 createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6467 pprint(' summary-issues.html - kernel issues found sorted by frequency')
6468
6469# Function: checkArgBool
6470# Description:
6471# check if a boolean string value is true or false
6472def checkArgBool(name, value):
6473 if value in switchvalues:
6474 if value in switchoff:
6475 return False
6476 return True
6477 doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6478 return False
6479
6480# Function: configFromFile
6481# Description:
6482# Configure the script via the info in a config file
6483def configFromFile(file):
6484 Config = configparser.ConfigParser()
6485
6486 Config.read(file)
6487 sections = Config.sections()
6488 overridekprobes = False
6489 overridedevkprobes = False
6490 if 'Settings' in sections:
6491 for opt in Config.options('Settings'):
6492 value = Config.get('Settings', opt).lower()
6493 option = opt.lower()
6494 if(option == 'verbose'):
6495 sysvals.verbose = checkArgBool(option, value)
6496 elif(option == 'addlogs'):
6497 sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6498 elif(option == 'dev'):
6499 sysvals.usedevsrc = checkArgBool(option, value)
6500 elif(option == 'proc'):
6501 sysvals.useprocmon = checkArgBool(option, value)
6502 elif(option == 'x2'):
6503 if checkArgBool(option, value):
6504 sysvals.execcount = 2
6505 elif(option == 'callgraph'):
6506 sysvals.usecallgraph = checkArgBool(option, value)
6507 elif(option == 'override-timeline-functions'):
6508 overridekprobes = checkArgBool(option, value)
6509 elif(option == 'override-dev-timeline-functions'):
6510 overridedevkprobes = checkArgBool(option, value)
6511 elif(option == 'skiphtml'):
6512 sysvals.skiphtml = checkArgBool(option, value)
6513 elif(option == 'sync'):
6514 sysvals.sync = checkArgBool(option, value)
6515 elif(option == 'rs' or option == 'runtimesuspend'):
6516 if value in switchvalues:
6517 if value in switchoff:
6518 sysvals.rs = -1
6519 else:
6520 sysvals.rs = 1
6521 else:
6522 doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6523 elif(option == 'display'):
6524 disopt = ['on', 'off', 'standby', 'suspend']
6525 if value not in disopt:
6526 doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6527 sysvals.display = value
6528 elif(option == 'gzip'):
6529 sysvals.gzip = checkArgBool(option, value)
6530 elif(option == 'cgfilter'):
6531 sysvals.setCallgraphFilter(value)
6532 elif(option == 'cgskip'):
6533 if value in switchoff:
6534 sysvals.cgskip = ''
6535 else:
6536 sysvals.cgskip = sysvals.configFile(val)
6537 if(not sysvals.cgskip):
6538 doError('%s does not exist' % sysvals.cgskip)
6539 elif(option == 'cgtest'):
6540 sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6541 elif(option == 'cgphase'):
6542 d = Data(0)
6543 if value not in d.phasedef:
6544 doError('invalid phase --> (%s: %s), valid phases are %s'\
6545 % (option, value, d.phasedef.keys()), True)
6546 sysvals.cgphase = value
6547 elif(option == 'fadd'):
6548 file = sysvals.configFile(value)
6549 if(not file):
6550 doError('%s does not exist' % value)
6551 sysvals.addFtraceFilterFunctions(file)
6552 elif(option == 'result'):
6553 sysvals.result = value
6554 elif(option == 'multi'):
6555 nums = value.split()
6556 if len(nums) != 2:
6557 doError('multi requires 2 integers (exec_count and delay)', True)
6558 sysvals.multiinit(nums[0], nums[1])
6559 elif(option == 'devicefilter'):
6560 sysvals.setDeviceFilter(value)
6561 elif(option == 'expandcg'):
6562 sysvals.cgexp = checkArgBool(option, value)
6563 elif(option == 'srgap'):
6564 if checkArgBool(option, value):
6565 sysvals.srgap = 5
6566 elif(option == 'mode'):
6567 sysvals.suspendmode = value
6568 elif(option == 'command' or option == 'cmd'):
6569 sysvals.testcommand = value
6570 elif(option == 'x2delay'):
6571 sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6572 elif(option == 'predelay'):
6573 sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6574 elif(option == 'postdelay'):
6575 sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6576 elif(option == 'maxdepth'):
6577 sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6578 elif(option == 'rtcwake'):
6579 if value in switchoff:
6580 sysvals.rtcwake = False
6581 else:
6582 sysvals.rtcwake = True
6583 sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6584 elif(option == 'timeprec'):
6585 sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6586 elif(option == 'mindev'):
6587 sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6588 elif(option == 'callloop-maxgap'):
6589 sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6590 elif(option == 'callloop-maxlen'):
6591 sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6592 elif(option == 'mincg'):
6593 sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6594 elif(option == 'bufsize'):
6595 sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6596 elif(option == 'output-dir'):
6597 sysvals.outdir = sysvals.setOutputFolder(value)
6598
6599 if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6600 doError('No command supplied for mode "command"')
6601
6602 # compatibility errors
6603 if sysvals.usedevsrc and sysvals.usecallgraph:
6604 doError('-dev is not compatible with -f')
6605 if sysvals.usecallgraph and sysvals.useprocmon:
6606 doError('-proc is not compatible with -f')
6607
6608 if overridekprobes:
6609 sysvals.tracefuncs = dict()
6610 if overridedevkprobes:
6611 sysvals.dev_tracefuncs = dict()
6612
6613 kprobes = dict()
6614 kprobesec = 'dev_timeline_functions_'+platform.machine()
6615 if kprobesec in sections:
6616 for name in Config.options(kprobesec):
6617 text = Config.get(kprobesec, name)
6618 kprobes[name] = (text, True)
6619 kprobesec = 'timeline_functions_'+platform.machine()
6620 if kprobesec in sections:
6621 for name in Config.options(kprobesec):
6622 if name in kprobes:
6623 doError('Duplicate timeline function found "%s"' % (name))
6624 text = Config.get(kprobesec, name)
6625 kprobes[name] = (text, False)
6626
6627 for name in kprobes:
6628 function = name
6629 format = name
6630 color = ''
6631 args = dict()
6632 text, dev = kprobes[name]
6633 data = text.split()
6634 i = 0
6635 for val in data:
6636 # bracketted strings are special formatting, read them separately
6637 if val[0] == '[' and val[-1] == ']':
6638 for prop in val[1:-1].split(','):
6639 p = prop.split('=')
6640 if p[0] == 'color':
6641 try:
6642 color = int(p[1], 16)
6643 color = '#'+p[1]
6644 except:
6645 color = p[1]
6646 continue
6647 # first real arg should be the format string
6648 if i == 0:
6649 format = val
6650 # all other args are actual function args
6651 else:
6652 d = val.split('=')
6653 args[d[0]] = d[1]
6654 i += 1
6655 if not function or not format:
6656 doError('Invalid kprobe: %s' % name)
6657 for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6658 if arg not in args:
6659 doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6660 if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6661 doError('Duplicate timeline function found "%s"' % (name))
6662
6663 kp = {
6664 'name': name,
6665 'func': function,
6666 'format': format,
6667 sysvals.archargs: args
6668 }
6669 if color:
6670 kp['color'] = color
6671 if dev:
6672 sysvals.dev_tracefuncs[name] = kp
6673 else:
6674 sysvals.tracefuncs[name] = kp
6675
6676# Function: printHelp
6677# Description:
6678# print out the help text
6679def printHelp():
6680 pprint('\n%s v%s\n'\
6681 'Usage: sudo sleepgraph <options> <commands>\n'\
6682 '\n'\
6683 'Description:\n'\
6684 ' This tool is designed to assist kernel and OS developers in optimizing\n'\
6685 ' their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6686 ' with a few extra options enabled, the tool will execute a suspend and\n'\
6687 ' capture dmesg and ftrace data until resume is complete. This data is\n'\
6688 ' transformed into a device timeline and an optional callgraph to give\n'\
6689 ' a detailed view of which devices/subsystems are taking the most\n'\
6690 ' time in suspend/resume.\n'\
6691 '\n'\
6692 ' If no specific command is given, the default behavior is to initiate\n'\
6693 ' a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6694 '\n'\
6695 ' Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6696 ' HTML output: <hostname>_<mode>.html\n'\
6697 ' raw dmesg output: <hostname>_<mode>_dmesg.txt\n'\
6698 ' raw ftrace output: <hostname>_<mode>_ftrace.txt\n'\
6699 '\n'\
6700 'Options:\n'\
6701 ' -h Print this help text\n'\
6702 ' -v Print the current tool version\n'\
6703 ' -config fn Pull arguments and config options from file fn\n'\
6704 ' -verbose Print extra information during execution and analysis\n'\
6705 ' -m mode Mode to initiate for suspend (default: %s)\n'\
6706 ' -o name Overrides the output subdirectory name when running a new test\n'\
6707 ' default: suspend-{date}-{time}\n'\
6708 ' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6709 ' -addlogs Add the dmesg and ftrace logs to the html output\n'\
6710 ' -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6711 ' -srgap Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6712 ' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6713 ' -result fn Export a results table to a text file for parsing.\n'\
6714 ' -wifi If a wifi connection is available, check that it reconnects after resume.\n'\
6715 ' -wifitrace Trace kernel execution through wifi reconnect.\n'\
6716 ' -netfix Use netfix to reset the network in the event it fails to resume.\n'\
6717 ' [testprep]\n'\
6718 ' -sync Sync the filesystems before starting the test\n'\
6719 ' -rs on/off Enable/disable runtime suspend for all devices, restore all after test\n'\
6720 ' -display m Change the display mode to m for the test (on/off/standby/suspend)\n'\
6721 ' [advanced]\n'\
6722 ' -gzip Gzip the trace and dmesg logs to save space\n'\
6723 ' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"\n'\
6724 ' -proc Add usermode process info into the timeline (default: disabled)\n'\
6725 ' -dev Add kernel function calls and threads to the timeline (default: disabled)\n'\
6726 ' -x2 Run two suspend/resumes back to back (default: disabled)\n'\
6727 ' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)\n'\
6728 ' -predelay t Include t ms delay before 1st suspend (default: 0 ms)\n'\
6729 ' -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6730 ' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6731 ' -multi n d Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6732 ' by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6733 ' The outputs will be created in a new subdirectory with a summary page.\n'\
6734 ' -maxfail n Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6735 ' [debug]\n'\
6736 ' -f Use ftrace to create device callgraphs (default: disabled)\n'\
6737 ' -ftop Use ftrace on the top level call: "%s" (default: disabled)\n'\
6738 ' -maxdepth N limit the callgraph data to N call levels (default: 0=all)\n'\
6739 ' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\
6740 ' -fadd file Add functions to be graphed in the timeline from a list in a text file\n'\
6741 ' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6742 ' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6743 ' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)\n'\
6744 ' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6745 ' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6746 ' -cgfilter S Filter the callgraph output in the timeline\n'\
6747 ' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6748 ' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6749 ' -devdump Print out all the raw device data for each phase\n'\
6750 ' -cgdump Print out all the raw callgraph data\n'\
6751 '\n'\
6752 'Other commands:\n'\
6753 ' -modes List available suspend modes\n'\
6754 ' -status Test to see if the system is enabled to run this tool\n'\
6755 ' -fpdt Print out the contents of the ACPI Firmware Performance Data Table\n'\
6756 ' -wificheck Print out wifi connection info\n'\
6757 ' -x<mode> Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6758 ' -sysinfo Print out system info extracted from BIOS\n'\
6759 ' -devinfo Print out the pm settings of all devices which support runtime suspend\n'\
6760 ' -cmdinfo Print out all the platform info collected before and after suspend/resume\n'\
6761 ' -flist Print the list of functions currently being captured in ftrace\n'\
6762 ' -flistall Print all functions capable of being captured in ftrace\n'\
6763 ' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6764 ' [redo]\n'\
6765 ' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)\n'\
6766 ' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)\n'\
6767 '' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6768 return True
6769
6770# ----------------- MAIN --------------------
6771# exec start (skipped if script is loaded as library)
6772if __name__ == '__main__':
6773 genhtml = False
6774 cmd = ''
6775 simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6776 '-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6777 '-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6778 if '-f' in sys.argv:
6779 sysvals.cgskip = sysvals.configFile('cgskip.txt')
6780 # loop through the command line arguments
6781 args = iter(sys.argv[1:])
6782 for arg in args:
6783 if(arg == '-m'):
6784 try:
6785 val = next(args)
6786 except:
6787 doError('No mode supplied', True)
6788 if val == 'command' and not sysvals.testcommand:
6789 doError('No command supplied for mode "command"', True)
6790 sysvals.suspendmode = val
6791 elif(arg in simplecmds):
6792 cmd = arg[1:]
6793 elif(arg == '-h'):
6794 printHelp()
6795 sys.exit(0)
6796 elif(arg == '-v'):
6797 pprint("Version %s" % sysvals.version)
6798 sys.exit(0)
6799 elif(arg == '-debugtiming'):
6800 debugtiming = True
6801 elif(arg == '-x2'):
6802 sysvals.execcount = 2
6803 elif(arg == '-x2delay'):
6804 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6805 elif(arg == '-predelay'):
6806 sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6807 elif(arg == '-postdelay'):
6808 sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6809 elif(arg == '-f'):
6810 sysvals.usecallgraph = True
6811 elif(arg == '-ftop'):
6812 sysvals.usecallgraph = True
6813 sysvals.ftop = True
6814 sysvals.usekprobes = False
6815 elif(arg == '-skiphtml'):
6816 sysvals.skiphtml = True
6817 elif(arg == '-cgdump'):
6818 sysvals.cgdump = True
6819 elif(arg == '-devdump'):
6820 sysvals.devdump = True
6821 elif(arg == '-genhtml'):
6822 genhtml = True
6823 elif(arg == '-addlogs'):
6824 sysvals.dmesglog = sysvals.ftracelog = True
6825 elif(arg == '-nologs'):
6826 sysvals.dmesglog = sysvals.ftracelog = False
6827 elif(arg == '-addlogdmesg'):
6828 sysvals.dmesglog = True
6829 elif(arg == '-addlogftrace'):
6830 sysvals.ftracelog = True
6831 elif(arg == '-noturbostat'):
6832 sysvals.tstat = False
6833 elif(arg == '-verbose'):
6834 sysvals.verbose = True
6835 elif(arg == '-proc'):
6836 sysvals.useprocmon = True
6837 elif(arg == '-dev'):
6838 sysvals.usedevsrc = True
6839 elif(arg == '-sync'):
6840 sysvals.sync = True
6841 elif(arg == '-wifi'):
6842 sysvals.wifi = True
6843 elif(arg == '-wifitrace'):
6844 sysvals.wifitrace = True
6845 elif(arg == '-netfix'):
6846 sysvals.netfix = True
6847 elif(arg == '-gzip'):
6848 sysvals.gzip = True
6849 elif(arg == '-info'):
6850 try:
6851 val = next(args)
6852 except:
6853 doError('-info requires one string argument', True)
6854 elif(arg == '-desc'):
6855 try:
6856 val = next(args)
6857 except:
6858 doError('-desc requires one string argument', True)
6859 elif(arg == '-rs'):
6860 try:
6861 val = next(args)
6862 except:
6863 doError('-rs requires "enable" or "disable"', True)
6864 if val.lower() in switchvalues:
6865 if val.lower() in switchoff:
6866 sysvals.rs = -1
6867 else:
6868 sysvals.rs = 1
6869 else:
6870 doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6871 elif(arg == '-display'):
6872 try:
6873 val = next(args)
6874 except:
6875 doError('-display requires an mode value', True)
6876 disopt = ['on', 'off', 'standby', 'suspend']
6877 if val.lower() not in disopt:
6878 doError('valid display mode values are %s' % disopt, True)
6879 sysvals.display = val.lower()
6880 elif(arg == '-maxdepth'):
6881 sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6882 elif(arg == '-rtcwake'):
6883 try:
6884 val = next(args)
6885 except:
6886 doError('No rtcwake time supplied', True)
6887 if val.lower() in switchoff:
6888 sysvals.rtcwake = False
6889 else:
6890 sysvals.rtcwake = True
6891 sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6892 elif(arg == '-timeprec'):
6893 sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6894 elif(arg == '-mindev'):
6895 sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6896 elif(arg == '-mincg'):
6897 sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6898 elif(arg == '-bufsize'):
6899 sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6900 elif(arg == '-cgtest'):
6901 sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6902 elif(arg == '-cgphase'):
6903 try:
6904 val = next(args)
6905 except:
6906 doError('No phase name supplied', True)
6907 d = Data(0)
6908 if val not in d.phasedef:
6909 doError('invalid phase --> (%s: %s), valid phases are %s'\
6910 % (arg, val, d.phasedef.keys()), True)
6911 sysvals.cgphase = val
6912 elif(arg == '-cgfilter'):
6913 try:
6914 val = next(args)
6915 except:
6916 doError('No callgraph functions supplied', True)
6917 sysvals.setCallgraphFilter(val)
6918 elif(arg == '-skipkprobe'):
6919 try:
6920 val = next(args)
6921 except:
6922 doError('No kprobe functions supplied', True)
6923 sysvals.skipKprobes(val)
6924 elif(arg == '-cgskip'):
6925 try:
6926 val = next(args)
6927 except:
6928 doError('No file supplied', True)
6929 if val.lower() in switchoff:
6930 sysvals.cgskip = ''
6931 else:
6932 sysvals.cgskip = sysvals.configFile(val)
6933 if(not sysvals.cgskip):
6934 doError('%s does not exist' % sysvals.cgskip)
6935 elif(arg == '-callloop-maxgap'):
6936 sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6937 elif(arg == '-callloop-maxlen'):
6938 sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6939 elif(arg == '-cmd'):
6940 try:
6941 val = next(args)
6942 except:
6943 doError('No command string supplied', True)
6944 sysvals.testcommand = val
6945 sysvals.suspendmode = 'command'
6946 elif(arg == '-expandcg'):
6947 sysvals.cgexp = True
6948 elif(arg == '-srgap'):
6949 sysvals.srgap = 5
6950 elif(arg == '-maxfail'):
6951 sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
6952 elif(arg == '-multi'):
6953 try:
6954 c, d = next(args), next(args)
6955 except:
6956 doError('-multi requires two values', True)
6957 sysvals.multiinit(c, d)
6958 elif(arg == '-o'):
6959 try:
6960 val = next(args)
6961 except:
6962 doError('No subdirectory name supplied', True)
6963 sysvals.outdir = sysvals.setOutputFolder(val)
6964 elif(arg == '-config'):
6965 try:
6966 val = next(args)
6967 except:
6968 doError('No text file supplied', True)
6969 file = sysvals.configFile(val)
6970 if(not file):
6971 doError('%s does not exist' % val)
6972 configFromFile(file)
6973 elif(arg == '-fadd'):
6974 try:
6975 val = next(args)
6976 except:
6977 doError('No text file supplied', True)
6978 file = sysvals.configFile(val)
6979 if(not file):
6980 doError('%s does not exist' % val)
6981 sysvals.addFtraceFilterFunctions(file)
6982 elif(arg == '-dmesg'):
6983 try:
6984 val = next(args)
6985 except:
6986 doError('No dmesg file supplied', True)
6987 sysvals.notestrun = True
6988 sysvals.dmesgfile = val
6989 if(os.path.exists(sysvals.dmesgfile) == False):
6990 doError('%s does not exist' % sysvals.dmesgfile)
6991 elif(arg == '-ftrace'):
6992 try:
6993 val = next(args)
6994 except:
6995 doError('No ftrace file supplied', True)
6996 sysvals.notestrun = True
6997 sysvals.ftracefile = val
6998 if(os.path.exists(sysvals.ftracefile) == False):
6999 doError('%s does not exist' % sysvals.ftracefile)
7000 elif(arg == '-summary'):
7001 try:
7002 val = next(args)
7003 except:
7004 doError('No directory supplied', True)
7005 cmd = 'summary'
7006 sysvals.outdir = val
7007 sysvals.notestrun = True
7008 if(os.path.isdir(val) == False):
7009 doError('%s is not accesible' % val)
7010 elif(arg == '-filter'):
7011 try:
7012 val = next(args)
7013 except:
7014 doError('No devnames supplied', True)
7015 sysvals.setDeviceFilter(val)
7016 elif(arg == '-result'):
7017 try:
7018 val = next(args)
7019 except:
7020 doError('No result file supplied', True)
7021 sysvals.result = val
7022 sysvals.signalHandlerInit()
7023 else:
7024 doError('Invalid argument: '+arg, True)
7025
7026 # compatibility errors
7027 if(sysvals.usecallgraph and sysvals.usedevsrc):
7028 doError('-dev is not compatible with -f')
7029 if(sysvals.usecallgraph and sysvals.useprocmon):
7030 doError('-proc is not compatible with -f')
7031
7032 if sysvals.usecallgraph and sysvals.cgskip:
7033 sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
7034 sysvals.setCallgraphBlacklist(sysvals.cgskip)
7035
7036 # callgraph size cannot exceed device size
7037 if sysvals.mincglen < sysvals.mindevlen:
7038 sysvals.mincglen = sysvals.mindevlen
7039
7040 # remove existing buffers before calculating memory
7041 if(sysvals.usecallgraph or sysvals.usedevsrc):
7042 sysvals.fsetVal('16', 'buffer_size_kb')
7043 sysvals.cpuInfo()
7044
7045 # just run a utility command and exit
7046 if(cmd != ''):
7047 ret = 0
7048 if(cmd == 'status'):
7049 if not statusCheck(True):
7050 ret = 1
7051 elif(cmd == 'fpdt'):
7052 if not getFPDT(True):
7053 ret = 1
7054 elif(cmd == 'sysinfo'):
7055 sysvals.printSystemInfo(True)
7056 elif(cmd == 'devinfo'):
7057 deviceInfo()
7058 elif(cmd == 'modes'):
7059 pprint(getModes())
7060 elif(cmd == 'flist'):
7061 sysvals.getFtraceFilterFunctions(True)
7062 elif(cmd == 'flistall'):
7063 sysvals.getFtraceFilterFunctions(False)
7064 elif(cmd == 'summary'):
7065 runSummary(sysvals.outdir, True, genhtml)
7066 elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
7067 sysvals.verbose = True
7068 ret = sysvals.displayControl(cmd[1:])
7069 elif(cmd == 'xstat'):
7070 pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
7071 elif(cmd == 'wificheck'):
7072 dev = sysvals.checkWifi()
7073 if dev:
7074 print('%s is connected' % sysvals.wifiDetails(dev))
7075 else:
7076 print('No wifi connection found')
7077 elif(cmd == 'cmdinfo'):
7078 for out in sysvals.cmdinfo(False, True):
7079 print('[%s - %s]\n%s\n' % out)
7080 sys.exit(ret)
7081
7082 # if instructed, re-analyze existing data files
7083 if(sysvals.notestrun):
7084 stamp = rerunTest(sysvals.outdir)
7085 sysvals.outputResult(stamp)
7086 sys.exit(0)
7087
7088 # verify that we can run a test
7089 error = statusCheck()
7090 if(error):
7091 doError(error)
7092
7093 # extract mem/disk extra modes and convert
7094 mode = sysvals.suspendmode
7095 if mode.startswith('mem'):
7096 memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
7097 if memmode == 'shallow':
7098 mode = 'standby'
7099 elif memmode == 's2idle':
7100 mode = 'freeze'
7101 else:
7102 mode = 'mem'
7103 sysvals.memmode = memmode
7104 sysvals.suspendmode = mode
7105 if mode.startswith('disk-'):
7106 sysvals.diskmode = mode.split('-', 1)[-1]
7107 sysvals.suspendmode = 'disk'
7108 sysvals.systemInfo(dmidecode(sysvals.mempath))
7109
7110 failcnt, ret = 0, 0
7111 if sysvals.multitest['run']:
7112 # run multiple tests in a separate subdirectory
7113 if not sysvals.outdir:
7114 if 'time' in sysvals.multitest:
7115 s = '-%dm' % sysvals.multitest['time']
7116 else:
7117 s = '-x%d' % sysvals.multitest['count']
7118 sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
7119 if not os.path.isdir(sysvals.outdir):
7120 os.makedirs(sysvals.outdir)
7121 sysvals.sudoUserchown(sysvals.outdir)
7122 finish = datetime.now()
7123 if 'time' in sysvals.multitest:
7124 finish += timedelta(minutes=sysvals.multitest['time'])
7125 for i in range(sysvals.multitest['count']):
7126 sysvals.multistat(True, i, finish)
7127 if i != 0 and sysvals.multitest['delay'] > 0:
7128 pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
7129 time.sleep(sysvals.multitest['delay'])
7130 fmt = 'suspend-%y%m%d-%H%M%S'
7131 sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
7132 ret = runTest(i+1, not sysvals.verbose)
7133 failcnt = 0 if not ret else failcnt + 1
7134 if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
7135 pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
7136 break
7137 sysvals.resetlog()
7138 sysvals.multistat(False, i, finish)
7139 if 'time' in sysvals.multitest and datetime.now() >= finish:
7140 break
7141 if not sysvals.skiphtml:
7142 runSummary(sysvals.outdir, False, False)
7143 sysvals.sudoUserchown(sysvals.outdir)
7144 else:
7145 if sysvals.outdir:
7146 sysvals.testdir = sysvals.outdir
7147 # run the test in the current directory
7148 ret = runTest()
7149
7150 # reset to default values after testing
7151 if sysvals.display:
7152 sysvals.displayControl('reset')
7153 if sysvals.rs != 0:
7154 sysvals.setRuntimeSuspend(False)
7155 sys.exit(ret)