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