summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorTodd Brandt <todd.e.brandt@linux.intel.com>2018-10-08 15:56:31 -0700
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2018-10-09 09:27:33 +0200
commit5484f0334439701900121a107709c461215cadb6 (patch)
tree8fea56173f8252d0eb801771c567ace532a776b9 /tools
parent0238df646e6224016a45505d2c111a24669ebe21 (diff)
PM / tools: sleepgraph: first batch of v5.2 changes
general: - add battery charge data before and after test - remove special s0i3 handling - remove melding of dmesg & ftrace data in old kernels, use one only - updates to various kprobes in trace (ksys_sync, etc) - enable pm_debug_messages during the test - instrument more subsystems with dev functions (phy0) error handling: - return codes for tool show the status of the test run - 0: success, 1: general error (no timeline), 2: fail (suspend aborted) - monitor output of /sys/power/state, mark as failure if exception occurs - add signal handler when using -result to catch tool exceptions display control - add -x commands for testing xset with mode settings and status - allow display setting to on, off, suspend, standby - add display mode change info to the log, along with a warning on fail s2idle (freeze) - remove fixed 10-phase dependency, allow any phase order & any count - multiple phase occurences show as phase_nameN e.g. suspend_noirq3 - if multiple freezes occur, print multiple time values in header summary: - add new columns to summary output: issues, worst suspend/resume devices - worst device: includes summation of all phases of suspend or resume - issues: includes WARNING/ERROR/BUG from dmesg log, and other issues - s2idle: multiple freezes show as FREEZExN in the issues column Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/power/pm-graph/Makefile4
-rw-r--r--tools/power/pm-graph/sleepgraph.813
-rwxr-xr-xtools/power/pm-graph/sleepgraph.py1378
3 files changed, 760 insertions, 635 deletions
diff --git a/tools/power/pm-graph/Makefile b/tools/power/pm-graph/Makefile
index c1899cd72c80..845541544570 100644
--- a/tools/power/pm-graph/Makefile
+++ b/tools/power/pm-graph/Makefile
@@ -23,8 +23,8 @@ install : uninstall
install -m 644 config/suspend-x2-proc.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config
install -d $(DESTDIR)$(PREFIX)/bin
- ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/bootgraph.py $(DESTDIR)$(PREFIX)/bin/bootgraph
- ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/sleepgraph.py $(DESTDIR)$(PREFIX)/bin/sleepgraph
+ ln -s ../lib/pm-graph/bootgraph.py $(DESTDIR)$(PREFIX)/bin/bootgraph
+ ln -s ../lib/pm-graph/sleepgraph.py $(DESTDIR)$(PREFIX)/bin/sleepgraph
install -d $(DESTDIR)$(PREFIX)/share/man/man8
install bootgraph.8 $(DESTDIR)$(PREFIX)/share/man/man8
diff --git a/tools/power/pm-graph/sleepgraph.8 b/tools/power/pm-graph/sleepgraph.8
index 070be2cf7f74..24a2e7d0ae63 100644
--- a/tools/power/pm-graph/sleepgraph.8
+++ b/tools/power/pm-graph/sleepgraph.8
@@ -65,9 +65,9 @@ During test, enable/disable runtime suspend for all devices. The test is delayed
by 5 seconds to allow runtime suspend changes to occur. The settings are restored
after the test is complete.
.TP
-\fB-display \fIon/off\fR
-Turn the display on or off for the test using the xset command. This helps
-maintain the consistecy of test data for better comparison.
+\fB-display \fIon/off/standby/suspend\fR
+Switch the display to the requested mode for the test using the xset command.
+This helps maintain the consistency of test data for better comparison.
.TP
\fB-skiphtml\fR
Run the test and capture the trace logs, but skip the timeline generation.
@@ -183,6 +183,13 @@ Print out the contents of the ACPI Firmware Performance Data Table.
\fB-battery\fR
Print out battery status and current charge.
.TP
+\fB-xon/-xoff/-xstandby/-xsuspend\fR
+Test xset by attempting to switch the display to the given mode. This
+is the same command which will be issued by \fB-display \fImode\fR.
+.TP
+\fB-xstat\fR
+Get the current DPMS display mode.
+.TP
\fB-sysinfo\fR
Print out system info extracted from BIOS. Reads /dev/mem directly instead of going through dmidecode.
.TP
diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py
index 0c760478f7d7..343e8000d9ca 100755
--- a/tools/power/pm-graph/sleepgraph.py
+++ b/tools/power/pm-graph/sleepgraph.py
@@ -54,6 +54,7 @@ import os
import string
import re
import platform
+import signal
from datetime import datetime
import struct
import ConfigParser
@@ -72,7 +73,7 @@ class SystemValues:
version = '5.1'
ansi = False
rs = 0
- display = 0
+ display = ''
gzip = False
sync = False
verbose = False
@@ -99,6 +100,7 @@ class SystemValues:
tpath = '/sys/kernel/debug/tracing/'
fpdtpath = '/sys/firmware/acpi/tables/FPDT'
epath = '/sys/kernel/debug/tracing/events/power/'
+ pmdpath = '/sys/power/pm_debug_messages'
traceevents = [
'suspend_resume',
'device_pm_callback_end',
@@ -141,12 +143,10 @@ class SystemValues:
devprops = dict()
predelay = 0
postdelay = 0
- procexecfmt = 'ps - (?P<ps>.*)$'
- devpropfmt = '# Device Properties: .*'
- tracertypefmt = '# tracer: (?P<t>.*)'
- firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
+ pmdebug = ''
tracefuncs = {
'sys_sync': {},
+ 'ksys_sync': {},
'__pm_notifier_call_chain': {},
'pm_prepare_console': {},
'pm_notifier_call_chain': {},
@@ -187,7 +187,6 @@ class SystemValues:
dev_tracefuncs = {
# general wait/delay/sleep
'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
- 'schedule_timeout_uninterruptible': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
'usleep_range': { 'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'}, 'ub': 1 },
@@ -199,6 +198,9 @@ class SystemValues:
# filesystem
'ext4_sync_fs': {},
# 80211
+ 'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
+ 'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
+ 'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
'iwlagn_mac_start': {},
'iwlagn_alloc_bcast_station': {},
'iwl_trans_pcie_start_hw': {},
@@ -241,6 +243,7 @@ class SystemValues:
timeformat = '%.3f'
cmdline = '%s %s' % \
(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
+ sudouser = ''
def __init__(self):
self.archargs = 'args_'+platform.machine()
self.hostname = platform.node()
@@ -256,10 +259,32 @@ class SystemValues:
if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
self.ansi = True
self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
+ if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
+ os.environ['SUDO_USER']:
+ self.sudouser = os.environ['SUDO_USER']
def vprint(self, msg):
self.logmsg += msg+'\n'
- if(self.verbose):
+ if self.verbose or msg.startswith('WARNING:'):
print(msg)
+ def signalHandler(self, signum, frame):
+ if not self.result:
+ return
+ signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
+ msg = 'Signal %s caused a tool exit, line %d' % (signame, frame.f_lineno)
+ sysvals.outputResult({'error':msg})
+ sys.exit(3)
+ def signalHandlerInit(self):
+ capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
+ 'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM', 'TSTP']
+ self.signames = dict()
+ for i in capture:
+ s = 'SIG'+i
+ try:
+ signum = getattr(signal, s)
+ signal.signal(signum, self.signalHandler)
+ except:
+ continue
+ self.signames[signum] = s
def rootCheck(self, fatal=True):
if(os.access(self.powerfile, os.W_OK)):
return True
@@ -267,7 +292,7 @@ class SystemValues:
msg = 'This command requires sysfs mount and root access'
print('ERROR: %s\n') % msg
self.outputResult({'error':msg})
- sys.exit()
+ sys.exit(1)
return False
def rootUser(self, fatal=False):
if 'USER' in os.environ and os.environ['USER'] == 'root':
@@ -276,7 +301,7 @@ class SystemValues:
msg = 'This command must be run as root'
print('ERROR: %s\n') % msg
self.outputResult({'error':msg})
- sys.exit()
+ sys.exit(1)
return False
def getExec(self, cmd):
dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
@@ -406,8 +431,8 @@ class SystemValues:
ktime = m.group('ktime')
fp.close()
self.dmesgstart = float(ktime)
- def getdmesg(self, fwdata=[]):
- op = self.writeDatafileHeader(sysvals.dmesgfile, fwdata)
+ def getdmesg(self, testdata):
+ op = self.writeDatafileHeader(sysvals.dmesgfile, testdata)
# store all new dmesg lines since initdmesg was called
fp = Popen('dmesg', stdout=PIPE).stdout
for line in fp:
@@ -619,6 +644,8 @@ class SystemValues:
self.fsetVal('0', 'events/kprobes/enable')
self.fsetVal('', 'kprobe_events')
self.fsetVal('1024', 'buffer_size_kb')
+ if self.pmdebug:
+ self.setVal(self.pmdebug, self.pmdpath)
def setupAllKprobes(self):
for name in self.tracefuncs:
self.defaultKprobe(name, self.tracefuncs[name])
@@ -641,6 +668,11 @@ class SystemValues:
# turn trace off
self.fsetVal('0', 'tracing_on')
self.cleanupFtrace()
+ # pm debug messages
+ pv = self.getVal(self.pmdpath)
+ if pv != '1':
+ self.setVal('1', self.pmdpath)
+ self.pmdebug = pv
# set the trace clock to global
self.fsetVal('global', 'trace_clock')
self.fsetVal('nop', 'current_tracer')
@@ -728,19 +760,24 @@ class SystemValues:
if not self.ansi:
return str
return '\x1B[%d;40m%s\x1B[m' % (color, str)
- def writeDatafileHeader(self, filename, fwdata=[]):
+ def writeDatafileHeader(self, filename, testdata):
fp = self.openlog(filename, 'w')
fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
- if(self.suspendmode == 'mem' or self.suspendmode == 'command'):
- for fw in fwdata:
+ for test in testdata:
+ if 'fw' in test:
+ fw = test['fw']
if(fw):
fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
+ if 'bat' in test:
+ (a1, c1), (a2, c2) = test['bat']
+ fp.write('# battery %s %d %s %d\n' % (a1, c1, a2, c2))
+ if test['error'] or len(testdata) > 1:
+ fp.write('# enter_sleep_error %s\n' % test['error'])
return fp
- def sudouser(self, dir):
- if os.path.exists(dir) and os.getuid() == 0 and \
- 'SUDO_USER' in os.environ:
+ def sudoUserchown(self, dir):
+ if os.path.exists(dir) and self.sudouser:
cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
- call(cmd.format(os.environ['SUDO_USER'], dir), shell=True)
+ call(cmd.format(self.sudouser, dir), shell=True)
def outputResult(self, testdata, num=0):
if not self.result:
return
@@ -762,7 +799,7 @@ class SystemValues:
if 'bugurl' in testdata:
fp.write('url%s: %s\n' % (n, testdata['bugurl']))
fp.close()
- self.sudouser(self.result)
+ self.sudoUserchown(self.result)
def configFile(self, file):
dir = os.path.dirname(os.path.realpath(__file__))
if os.path.exists(file):
@@ -800,11 +837,12 @@ suspendmodename = {
# Simple class which holds property values collected
# for all the devices used in the timeline.
class DevProps:
- syspath = ''
- altname = ''
- async = True
- xtraclass = ''
- xtrainfo = ''
+ def __init__(self):
+ self.syspath = ''
+ self.altname = ''
+ self.async = True
+ self.xtraclass = ''
+ self.xtrainfo = ''
def out(self, dev):
return '%s,%s,%d;' % (dev, self.altname, self.async)
def debug(self, dev):
@@ -831,9 +869,6 @@ class DevProps:
# A container used to create a device hierachy, with a single root node
# and a tree of child nodes. Used by Data.deviceTopology()
class DeviceNode:
- name = ''
- children = 0
- depth = 0
def __init__(self, nodename, nodedepth):
self.name = nodename
self.children = []
@@ -861,71 +896,78 @@ class DeviceNode:
# }
#
class Data:
- dmesg = {} # root data structure
- phases = [] # ordered list of phases
- start = 0.0 # test start
- end = 0.0 # test end
- tSuspended = 0.0 # low-level suspend start
- tResumed = 0.0 # low-level resume start
- tKernSus = 0.0 # kernel level suspend start
- tKernRes = 0.0 # kernel level resume end
- tLow = 0.0 # time spent in low-level suspend (standby/freeze)
- fwValid = False # is firmware data available
- fwSuspend = 0 # time spent in firmware suspend
- fwResume = 0 # time spent in firmware resume
- dmesgtext = [] # dmesg text file in memory
- pstl = 0 # process timeline
- testnumber = 0
- idstr = ''
- html_device_id = 0
- stamp = 0
- outfile = ''
- devpids = []
- kerror = False
+ phasedef = {
+ 'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
+ 'suspend': {'order': 1, 'color': '#88FF88'},
+ 'suspend_late': {'order': 2, 'color': '#00AA00'},
+ 'suspend_noirq': {'order': 3, 'color': '#008888'},
+ 'suspend_machine': {'order': 4, 'color': '#0000FF'},
+ 'resume_machine': {'order': 5, 'color': '#FF0000'},
+ 'resume_noirq': {'order': 6, 'color': '#FF9900'},
+ 'resume_early': {'order': 7, 'color': '#FFCC00'},
+ 'resume': {'order': 8, 'color': '#FFFF88'},
+ 'resume_complete': {'order': 9, 'color': '#FFFFCC'},
+ }
+ errlist = {
+ 'HWERROR' : '.*\[ *Hardware Error *\].*',
+ 'FWBUG' : '.*\[ *Firmware Bug *\].*',
+ 'BUG' : '.*BUG.*',
+ 'ERROR' : '.*ERROR.*',
+ 'WARNING' : '.*WARNING.*',
+ 'IRQ' : '.*genirq: .*',
+ 'TASKFAIL': '.*Freezing of tasks failed.*',
+ }
def __init__(self, num):
idchar = 'abcdefghij'
- self.pstl = dict()
+ self.start = 0.0 # test start
+ self.end = 0.0 # test end
+ self.tSuspended = 0.0 # low-level suspend start
+ self.tResumed = 0.0 # low-level resume start
+ self.tKernSus = 0.0 # kernel level suspend start
+ self.tKernRes = 0.0 # kernel level resume end
+ self.fwValid = False # is firmware data available
+ self.fwSuspend = 0 # time spent in firmware suspend
+ self.fwResume = 0 # time spent in firmware resume
+ self.html_device_id = 0
+ self.stamp = 0
+ self.outfile = ''
+ self.kerror = False
+ self.battery = 0
+ self.enterfail = ''
+ self.currphase = ''
+ self.pstl = dict() # process timeline
self.testnumber = num
self.idstr = idchar[num]
- self.dmesgtext = []
- self.phases = []
- self.dmesg = { # fixed list of 10 phases
- 'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#CCFFCC', 'order': 0},
- 'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#88FF88', 'order': 1},
- 'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#00AA00', 'order': 2},
- 'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#008888', 'order': 3},
- 'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#0000FF', 'order': 4},
- 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#FF0000', 'order': 5},
- 'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#FF9900', 'order': 6},
- 'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#FFCC00', 'order': 7},
- 'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#FFFF88', 'order': 8},
- 'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
- 'row': 0, 'color': '#FFFFCC', 'order': 9}
- }
- self.phases = self.sortedPhases()
+ self.dmesgtext = [] # dmesg text file in memory
+ self.dmesg = dict() # root data structure
+ self.errorinfo = {'suspend':[],'resume':[]}
+ self.tLow = [] # time spent in low-level suspends (standby/freeze)
+ self.devpids = []
+ self.devicegroups = 0
+ def sortedPhases(self):
+ return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
+ def initDevicegroups(self):
+ # called when phases are all finished being added
+ for phase in self.dmesg.keys():
+ if '*' in phase:
+ p = phase.split('*')
+ pnew = '%s%d' % (p[0], len(p))
+ self.dmesg[pnew] = self.dmesg.pop(phase)
self.devicegroups = []
- for phase in self.phases:
+ for phase in self.sortedPhases():
self.devicegroups.append([phase])
- self.errorinfo = {'suspend':[],'resume':[]}
+ def nextPhase(self, phase, offset):
+ order = self.dmesg[phase]['order'] + offset
+ for p in self.dmesg:
+ if self.dmesg[p]['order'] == order:
+ return p
+ return ''
+ def lastPhase(self):
+ plist = self.sortedPhases()
+ if len(plist) < 1:
+ return ''
+ return plist[-1]
def extractErrorInfo(self):
- elist = {
- 'HWERROR' : '.*\[ *Hardware Error *\].*',
- 'FWBUG' : '.*\[ *Firmware Bug *\].*',
- 'BUG' : '.*BUG.*',
- 'ERROR' : '.*ERROR.*',
- 'WARNING' : '.*WARNING.*',
- 'IRQ' : '.*genirq: .*',
- 'TASKFAIL': '.*Freezing of tasks failed.*',
- }
lf = sysvals.openlog(sysvals.dmesgfile, 'r')
i = 0
list = []
@@ -939,8 +981,8 @@ class Data:
continue
dir = 'suspend' if t < self.tSuspended else 'resume'
msg = m.group('msg')
- for err in elist:
- if re.match(elist[err], msg):
+ for err in self.errlist:
+ if re.match(self.errlist[err], msg):
list.append((err, dir, t, i, i))
self.kerror = True
break
@@ -956,7 +998,7 @@ class Data:
def setEnd(self, time):
self.end = time
def isTraceEventOutsideDeviceCalls(self, pid, time):
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
for dev in list:
d = list[dev]
@@ -964,16 +1006,10 @@ class Data:
time < d['end']):
return False
return True
- def phaseCollision(self, phase, isbegin, line):
- key = 'end'
- if isbegin:
- key = 'start'
- if self.dmesg[phase][key] >= 0:
- sysvals.vprint('IGNORE: %s' % line.strip())
- return True
- return False
def sourcePhase(self, start):
- for phase in self.phases:
+ for phase in self.sortedPhases():
+ if 'machine' in phase:
+ continue
pend = self.dmesg[phase]['end']
if start <= pend:
return phase
@@ -1004,14 +1040,15 @@ class Data:
return tgtdev
def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
# try to place the call in a device
- tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device')
+ phases = self.sortedPhases()
+ tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
# calls with device pids that occur outside device bounds are dropped
# TODO: include these somehow
if not tgtdev and pid in self.devpids:
return False
# try to place the call in a thread
if not tgtdev:
- tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread')
+ tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
# create new thread blocks, expand as new calls are found
if not tgtdev:
if proc == '<...>':
@@ -1053,7 +1090,7 @@ class Data:
def overflowDevices(self):
# get a list of devices that extend beyond the end of this test run
devlist = []
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
for devname in list:
dev = list[devname]
@@ -1064,7 +1101,7 @@ class Data:
# merge any devices that overlap devlist
for dev in devlist:
devname = dev['name']
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
if devname not in list:
continue
@@ -1079,7 +1116,7 @@ class Data:
del list[devname]
def usurpTouchingThread(self, name, dev):
# the caller test has priority of this thread, give it to him
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
if name in list:
tdev = list[name]
@@ -1093,7 +1130,7 @@ class Data:
break
def stitchTouchingThreads(self, testlist):
# merge any threads between tests that touch
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
for devname in list:
dev = list[devname]
@@ -1103,7 +1140,7 @@ class Data:
data.usurpTouchingThread(devname, dev)
def optimizeDevSrc(self):
# merge any src call loops to reduce timeline size
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
for dev in list:
if 'src' not in list[dev]:
@@ -1141,7 +1178,7 @@ class Data:
self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
self.end = self.trimTimeVal(self.end, t0, dT, left)
- for phase in self.phases:
+ for phase in self.sortedPhases():
p = self.dmesg[phase]
p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
@@ -1150,6 +1187,7 @@ class Data:
d = list[name]
d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
+ d['length'] = d['end'] - d['start']
if('ftrace' in d):
cg = d['ftrace']
cg.start = self.trimTimeVal(cg.start, t0, dT, left)
@@ -1166,30 +1204,59 @@ class Data:
tm = self.trimTimeVal(tm, t0, dT, left)
list.append((type, tm, idx1, idx2))
self.errorinfo[dir] = list
- def normalizeTime(self, tZero):
+ def trimFreezeTime(self, tZero):
# trim out any standby or freeze clock time
- if(self.tSuspended != self.tResumed):
- if(self.tResumed > tZero):
- self.trimTime(self.tSuspended, \
- self.tResumed-self.tSuspended, True)
- else:
- self.trimTime(self.tSuspended, \
- self.tResumed-self.tSuspended, False)
+ lp = ''
+ for phase in self.sortedPhases():
+ if 'resume_machine' in phase and 'suspend_machine' in lp:
+ tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
+ tL = tR - tS
+ if tL > 0:
+ left = True if tR > tZero else False
+ self.trimTime(tS, tL, left)
+ self.tLow.append('%.0f'%(tL*1000))
+ lp = phase
def getTimeValues(self):
- sktime = (self.dmesg['suspend_machine']['end'] - \
- self.tKernSus) * 1000
- rktime = (self.dmesg['resume_complete']['end'] - \
- self.dmesg['resume_machine']['start']) * 1000
+ if 'suspend_machine' in self.dmesg:
+ sktime = (self.dmesg['suspend_machine']['end'] - \
+ self.tKernSus) * 1000
+ else:
+ sktime = (self.tSuspended - self.tKernSus) * 1000
+ if 'resume_machine' in self.dmesg:
+ rktime = (self.tKernRes - \
+ self.dmesg['resume_machine']['start']) * 1000
+ else:
+ rktime = (self.tKernRes - self.tResumed) * 1000
return (sktime, rktime)
- def setPhase(self, phase, ktime, isbegin):
+ def setPhase(self, phase, ktime, isbegin, order=-1):
if(isbegin):
+ # phase start over current phase
+ if self.currphase:
+ if 'resume_machine' not in self.currphase:
+ sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
+ self.dmesg[self.currphase]['end'] = ktime
+ phases = self.dmesg.keys()
+ color = self.phasedef[phase]['color']
+ count = len(phases) if order < 0 else order
+ # create unique name for every new phase
+ while phase in phases:
+ phase += '*'
+ self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
+ 'row': 0, 'color': color, 'order': count}
self.dmesg[phase]['start'] = ktime
+ self.currphase = phase
else:
+ # phase end without a start
+ if phase not in self.currphase:
+ if self.currphase:
+ sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
+ else:
+ sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
+ return phase
+ phase = self.currphase
self.dmesg[phase]['end'] = ktime
- def dmesgSortVal(self, phase):
- return self.dmesg[phase]['order']
- def sortedPhases(self):
- return sorted(self.dmesg, key=self.dmesgSortVal)
+ self.currphase = ''
+ return phase
def sortedDevices(self, phase):
list = self.dmesg[phase]['list']
slist = []
@@ -1208,13 +1275,13 @@ class Data:
for devname in phaselist:
dev = phaselist[devname]
if(dev['end'] < 0):
- for p in self.phases:
+ for p in self.sortedPhases():
if self.dmesg[p]['end'] > dev['start']:
dev['end'] = self.dmesg[p]['end']
break
sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
def deviceFilter(self, devicefilter):
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
rmlist = []
for name in list:
@@ -1229,7 +1296,7 @@ class Data:
del list[name]
def fixupInitcallsThatDidntReturn(self):
# if any calls never returned, clip them at system resume end
- for phase in self.phases:
+ for phase in self.sortedPhases():
self.fixupInitcalls(phase)
def phaseOverlap(self, phases):
rmgroups = []
@@ -1248,17 +1315,18 @@ class Data:
self.devicegroups.append(newgroup)
def newActionGlobal(self, name, start, end, pid=-1, color=''):
# which phase is this device callback or action in
+ phases = self.sortedPhases()
targetphase = 'none'
htmlclass = ''
overlap = 0.0
- phases = []
- for phase in self.phases:
+ myphases = []
+ for phase in phases:
pstart = self.dmesg[phase]['start']
pend = self.dmesg[phase]['end']
# see if the action overlaps this phase
o = max(0, min(end, pend) - max(start, pstart))
if o > 0:
- phases.append(phase)
+ myphases.append(phase)
# set the target phase to the one that overlaps most
if o > overlap:
if overlap > 0 and phase == 'post_resume':
@@ -1267,19 +1335,19 @@ class Data:
overlap = o
# if no target phase was found, pin it to the edge
if targetphase == 'none':
- p0start = self.dmesg[self.phases[0]]['start']
+ p0start = self.dmesg[phases[0]]['start']
if start <= p0start:
- targetphase = self.phases[0]
+ targetphase = phases[0]
else:
- targetphase = self.phases[-1]
+ targetphase = phases[-1]
if pid == -2:
htmlclass = ' bg'
elif pid == -3:
htmlclass = ' ps'
- if len(phases) > 1:
+ if len(myphases) > 1:
htmlclass = ' bg'
- self.phaseOverlap(phases)
- if targetphase in self.phases:
+ self.phaseOverlap(myphases)
+ if targetphase in phases:
newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
return (targetphase, newname)
return False
@@ -1315,7 +1383,7 @@ class Data:
sysvals.vprint('Timeline Details:')
sysvals.vprint(' test start: %f' % self.start)
sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
- for phase in self.phases:
+ for phase in self.sortedPhases():
dc = len(self.dmesg[phase]['list'])
sysvals.vprint(' %16s: %f - %f (%d devices)' % (phase, \
self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
@@ -1323,7 +1391,7 @@ class Data:
sysvals.vprint(' test end: %f' % self.end)
def deviceChildrenAllPhases(self, devname):
devlist = []
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.deviceChildren(devname, phase)
for dev in list:
if dev not in devlist:
@@ -1344,7 +1412,7 @@ class Data:
if node.name:
info = ''
drv = ''
- for phase in self.phases:
+ for phase in self.sortedPhases():
list = self.dmesg[phase]['list']
if node.name in list:
s = list[node.name]['start']
@@ -1478,8 +1546,29 @@ class Data:
c = self.addProcessUsageEvent(ps, tres)
if c > 0:
sysvals.vprint('%25s (res): %d' % (ps, c))
+ def handleEndMarker(self, time):
+ dm = self.dmesg
+ self.setEnd(time)
+ self.initDevicegroups()
+ # give suspend_prepare an end if needed
+ if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
+ dm['suspend_prepare']['end'] = time
+ # assume resume machine ends at next phase start
+ if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
+ np = self.nextPhase('resume_machine', 1)
+ if np:
+ dm['resume_machine']['end'] = dm[np]['start']
+ # if kernel resume end not found, assume its the end marker
+ if self.tKernRes == 0.0:
+ self.tKernRes = time
+ # if kernel suspend start not found, assume its the end marker
+ if self.tKernSus == 0.0:
+ self.tKernSus = time
+ # set resume complete to end at end marker
+ if 'resume_complete' in dm:
+ dm['resume_complete']['end'] = time
def debugPrint(self):
- for p in self.phases:
+ for p in self.sortedPhases():
list = self.dmesg[p]['list']
for devname in list:
dev = list[devname]
@@ -1490,9 +1579,9 @@ class Data:
# Description:
# A container for kprobe function data we want in the dev timeline
class DevFunction:
- row = 0
- count = 1
def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
+ self.row = 0
+ self.count = 1
self.name = name
self.args = args
self.caller = caller
@@ -1546,16 +1635,15 @@ class DevFunction:
# suspend_resume: phase or custom exec block data
# device_pm_callback: device callback info
class FTraceLine:
- time = 0.0
- length = 0.0
- fcall = False
- freturn = False
- fevent = False
- fkprobe = False
- depth = 0
- name = ''
- type = ''
def __init__(self, t, m='', d=''):
+ self.length = 0.0
+ self.fcall = False
+ self.freturn = False
+ self.fevent = False
+ self.fkprobe = False
+ self.depth = 0
+ self.name = ''
+ self.type = ''
self.time = float(t)
if not m and not d:
return
@@ -1675,19 +1763,13 @@ class FTraceLine:
# Each instance is tied to a single device in a single phase, and is
# comprised of an ordered list of FTraceLine objects
class FTraceCallGraph:
- id = ''
- start = -1.0
- end = -1.0
- list = []
- invalid = False
- depth = 0
- pid = 0
- name = ''
- partial = False
vfname = 'missing_function_name'
- ignore = False
- sv = 0
def __init__(self, pid, sv):
+ self.id = ''
+ self.invalid = False
+ self.name = ''
+ self.partial = False
+ self.ignore = False
self.start = -1.0
self.end = -1.0
self.list = []
@@ -1943,7 +2025,7 @@ class FTraceCallGraph:
dev['ftrace'] = cg
found = devname
return found
- for p in data.phases:
+ for p in data.sortedPhases():
if(data.dmesg[p]['start'] <= self.start and
self.start <= data.dmesg[p]['end']):
list = data.dmesg[p]['list']
@@ -1966,7 +2048,7 @@ class FTraceCallGraph:
if fs < data.start or fe > data.end:
return
phase = ''
- for p in data.phases:
+ for p in data.sortedPhases():
if(data.dmesg[p]['start'] <= self.start and
self.start < data.dmesg[p]['end']):
phase = p
@@ -2008,23 +2090,20 @@ class DevItem:
# A container for a device timeline which calculates
# all the html properties to display it correctly
class Timeline:
- html = ''
- height = 0 # total timeline height
- scaleH = 20 # timescale (top) row height
- rowH = 30 # device row height
- bodyH = 0 # body height
- rows = 0 # total timeline rows
- rowlines = dict()
- rowheight = dict()
html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
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'
html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
def __init__(self, rowheight, scaleheight):
- self.rowH = rowheight
- self.scaleH = scaleheight
self.html = ''
+ self.height = 0 # total timeline height
+ self.scaleH = scaleheight # timescale (top) row height
+ self.rowH = rowheight # device row height
+ self.bodyH = 0 # body height
+ self.rows = 0 # total timeline rows
+ self.rowlines = dict()
+ self.rowheight = dict()
def createHeader(self, sv, stamp):
if(not stamp['time']):
return
@@ -2251,18 +2330,18 @@ class Timeline:
# Description:
# A list of values describing the properties of these test runs
class TestProps:
- stamp = ''
- sysinfo = ''
- cmdline = ''
- kparams = ''
- S0i3 = False
- fwdata = []
stampfmt = '# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
+ batteryfmt = '^# battery (?P<a1>\w*) (?P<c1>\d*) (?P<a2>\w*) (?P<c2>\d*)'
+ testerrfmt = '^# enter_sleep_error (?P<e>.*)'
sysinfofmt = '^# sysinfo .*'
cmdlinefmt = '^# command \| (?P<cmd>.*)'
kparamsfmt = '^# kparams \| (?P<kp>.*)'
+ devpropfmt = '# Device Properties: .*'
+ tracertypefmt = '# tracer: (?P<t>.*)'
+ firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
+ procexecfmt = 'ps - (?P<ps>.*)$'
ftrace_line_fmt_fg = \
'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
@@ -2271,11 +2350,17 @@ class TestProps:
' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
'(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
'(?P<msg>.*)'
- ftrace_line_fmt = ftrace_line_fmt_nop
- cgformat = False
- data = 0
- ktemp = dict()
def __init__(self):
+ self.stamp = ''
+ self.sysinfo = ''
+ self.cmdline = ''
+ self.kparams = ''
+ self.testerror = []
+ self.battery = []
+ self.fwdata = []
+ self.ftrace_line_fmt = self.ftrace_line_fmt_nop
+ self.cgformat = False
+ self.data = 0
self.ktemp = dict()
def setTracerType(self, tracer):
if(tracer == 'function_graph'):
@@ -2286,6 +2371,7 @@ class TestProps:
else:
doError('Invalid tracer format: [%s]' % tracer)
def parseStamp(self, data, sv):
+ # global test data
m = re.match(self.stampfmt, self.stamp)
data.stamp = {'time': '', 'host': '', 'mode': ''}
dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
@@ -2324,23 +2410,36 @@ class TestProps:
sv.kparams = m.group('kp')
if not sv.stamp:
sv.stamp = data.stamp
+ # firmware data
+ if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
+ data.fwSuspend, data.fwResume = self.fwdata[data.testnumber]
+ if(data.fwSuspend > 0 or data.fwResume > 0):
+ data.fwValid = True
+ # battery data
+ if len(self.battery) > data.testnumber:
+ m = re.match(self.batteryfmt, self.battery[data.testnumber])
+ if m:
+ data.battery = m.groups()
+ # sleep mode enter errors
+ if len(self.testerror) > data.testnumber:
+ m = re.match(self.testerrfmt, self.testerror[data.testnumber])
+ if m:
+ data.enterfail = m.group('e')
# Class: TestRun
# Description:
# A container for a suspend/resume test run. This is necessary as
# there could be more than one, and they need to be separate.
class TestRun:
- ftemp = dict()
- ttemp = dict()
- data = 0
def __init__(self, dataobj):
self.data = dataobj
self.ftemp = dict()
self.ttemp = dict()
class ProcessMonitor:
- proclist = dict()
- running = False
+ def __init__(self):
+ self.proclist = dict()
+ self.running = False
def procstat(self):
c = ['cat /proc/[1-9]*/stat 2>/dev/null']
process = Popen(c, shell=True, stdout=PIPE)
@@ -2391,8 +2490,8 @@ class ProcessMonitor:
# markers, and/or kprobes required for primary parsing.
def doesTraceLogHaveTraceEvents():
kpcheck = ['_cal: (', '_cpu_down()']
- techeck = ['suspend_resume']
- tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
+ techeck = ['suspend_resume', 'device_pm_callback']
+ tmcheck = ['tracing_mark_write']
sysvals.usekprobes = False
fp = sysvals.openlog(sysvals.ftracefile, 'r')
for line in fp:
@@ -2414,23 +2513,14 @@ def doesTraceLogHaveTraceEvents():
check.remove(i)
tmcheck = check
fp.close()
- if len(techeck) == 0:
- sysvals.usetraceevents = True
- else:
- sysvals.usetraceevents = False
- if len(tmcheck) == 0:
- sysvals.usetracemarkers = True
- else:
- sysvals.usetracemarkers = False
+ sysvals.usetraceevents = True if len(techeck) < 2 else False
+ sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
# Function: appendIncompleteTraceLog
# Description:
# [deprecated for kernel 3.15 or newer]
-# Legacy support of ftrace outputs that lack the device_pm_callback
-# and/or suspend_resume trace events. The primary data should be
-# taken from dmesg, and this ftrace is used only for callgraph data
-# or custom actions in the timeline. The data is appended to the Data
-# objects provided.
+# Adds callgraph data which lacks trace event data. This is only
+# for timelines generated from 3.15 or older
# Arguments:
# testruns: the array of Da