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