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