Merge back earlier PM tools material for v4.18.

This commit is contained in:
Rafael J. Wysocki 2018-06-03 10:12:30 +02:00
commit 9b34ffa09d
4 changed files with 301 additions and 156 deletions

View file

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2
# #
# Tool for analyzing boot timing # Tool for analyzing boot timing
# Copyright (c) 2013, Intel Corporation. # Copyright (c) 2013, Intel Corporation.

View file

@ -168,6 +168,7 @@ Create a summary page of all tests in \fIindir\fR. Creates summary.html
in the current folder. The output page is a table of tests with in the current folder. The output page is a table of tests with
suspend and resume values sorted by suspend mode, host, and kernel. suspend and resume values sorted by suspend mode, host, and kernel.
Includes test averages by mode and links to the test html files. Includes test averages by mode and links to the test html files.
Use -genhtml to include tests with missing html.
.TP .TP
\fB-modes\fR \fB-modes\fR
List available suspend modes. List available suspend modes.
@ -179,6 +180,9 @@ with any options you intend to use to see if they will work.
\fB-fpdt\fR \fB-fpdt\fR
Print out the contents of the ACPI Firmware Performance Data Table. Print out the contents of the ACPI Firmware Performance Data Table.
.TP .TP
\fB-battery\fR
Print out battery status and current charge.
.TP
\fB-sysinfo\fR \fB-sysinfo\fR
Print out system info extracted from BIOS. Reads /dev/mem directly instead of going through dmidecode. Print out system info extracted from BIOS. Reads /dev/mem directly instead of going through dmidecode.
.TP .TP

View file

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2
# #
# Tool for analyzing suspend/resume timing # Tool for analyzing suspend/resume timing
# Copyright (c) 2013, Intel Corporation. # Copyright (c) 2013, Intel Corporation.
@ -69,7 +69,7 @@ from subprocess import call, Popen, PIPE
# store system values and test parameters # store system values and test parameters
class SystemValues: class SystemValues:
title = 'SleepGraph' title = 'SleepGraph'
version = '5.0' version = '5.1'
ansi = False ansi = False
rs = 0 rs = 0
display = 0 display = 0
@ -240,7 +240,7 @@ class SystemValues:
kprobes = dict() kprobes = dict()
timeformat = '%.3f' timeformat = '%.3f'
cmdline = '%s %s' % \ cmdline = '%s %s' % \
(os.path.basename(sys.argv[0]), string.join(sys.argv[1:], ' ')) (os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
def __init__(self): def __init__(self):
self.archargs = 'args_'+platform.machine() self.archargs = 'args_'+platform.machine()
self.hostname = platform.node() self.hostname = platform.node()
@ -917,12 +917,18 @@ class Data:
self.devicegroups.append([phase]) self.devicegroups.append([phase])
self.errorinfo = {'suspend':[],'resume':[]} self.errorinfo = {'suspend':[],'resume':[]}
def extractErrorInfo(self): 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') lf = sysvals.openlog(sysvals.dmesgfile, 'r')
i = 0 i = 0
list = [] list = []
# sl = start line, et = error time, el = error line
type = 'ERROR'
sl = et = el = -1
for line in lf: for line in lf:
i += 1 i += 1
m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line) m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
@ -931,43 +937,13 @@ class Data:
t = float(m.group('ktime')) t = float(m.group('ktime'))
if t < self.start or t > self.end: if t < self.start or t > self.end:
continue continue
if t < self.tSuspended: dir = 'suspend' if t < self.tSuspended else 'resume'
dir = 'suspend'
else:
dir = 'resume'
msg = m.group('msg') msg = m.group('msg')
if re.match('-*\[ *cut here *\]-*', msg): for err in elist:
type = 'WARNING' if re.match(elist[err], msg):
sl = i list.append((err, dir, t, i, i))
elif re.match('genirq: .*', msg):
type = 'IRQ'
sl = i
elif re.match('BUG: .*', msg) or re.match('kernel BUG .*', msg):
type = 'BUG'
sl = i
elif re.match('-*\[ *end trace .*\]-*', msg) or \
re.match('R13: .*', msg):
if et >= 0 and sl >= 0:
list.append((type, dir, et, sl, i))
self.kerror = True self.kerror = True
sl = et = el = -1 break
type = 'ERROR'
elif 'Call Trace:' in msg:
if el >= 0 and et >= 0:
list.append((type, dir, et, el, el))
self.kerror = True
et, el = t, i
if sl < 0 or type == 'BUG':
slval = i
if sl >= 0:
slval = sl
list.append((type, dir, et, slval, i))
self.kerror = True
sl = et = el = -1
type = 'ERROR'
if el >= 0 and et >= 0:
list.append((type, dir, et, el, el))
self.kerror = True
for e in list: for e in list:
type, dir, t, idx1, idx2 = e type, dir, t, idx1, idx2 = e
sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t)) sysvals.vprint('kernel %s found in %s at %f' % (type, dir, t))
@ -2331,12 +2307,14 @@ class TestProps:
sv.suspendmode = data.stamp['mode'] sv.suspendmode = data.stamp['mode']
if sv.suspendmode == 'command' and sv.ftracefile != '': if sv.suspendmode == 'command' and sv.ftracefile != '':
modes = ['on', 'freeze', 'standby', 'mem', 'disk'] modes = ['on', 'freeze', 'standby', 'mem', 'disk']
out = Popen(['grep', 'machine_suspend', sv.ftracefile], fp = sysvals.openlog(sv.ftracefile, 'r')
stderr=PIPE, stdout=PIPE).stdout.read() for line in fp:
m = re.match('.* machine_suspend\[(?P<mode>.*)\]', out) m = re.match('.* machine_suspend\[(?P<mode>.*)\]', line)
if m and m.group('mode') in ['1', '2', '3', '4']: if m and m.group('mode') in ['1', '2', '3', '4']:
sv.suspendmode = modes[int(m.group('mode'))] sv.suspendmode = modes[int(m.group('mode'))]
data.stamp['mode'] = sv.suspendmode data.stamp['mode'] = sv.suspendmode
break
fp.close()
m = re.match(self.cmdlinefmt, self.cmdline) m = re.match(self.cmdlinefmt, self.cmdline)
if m: if m:
sv.cmdline = m.group('cmd') sv.cmdline = m.group('cmd')
@ -2413,7 +2391,7 @@ class ProcessMonitor:
# markers, and/or kprobes required for primary parsing. # markers, and/or kprobes required for primary parsing.
def doesTraceLogHaveTraceEvents(): def doesTraceLogHaveTraceEvents():
kpcheck = ['_cal: (', '_cpu_down()'] kpcheck = ['_cal: (', '_cpu_down()']
techeck = sysvals.traceevents[:] techeck = ['suspend_resume']
tmcheck = ['SUSPEND START', 'RESUME COMPLETE'] tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
sysvals.usekprobes = False sysvals.usekprobes = False
fp = sysvals.openlog(sysvals.ftracefile, 'r') fp = sysvals.openlog(sysvals.ftracefile, 'r')
@ -2808,7 +2786,7 @@ def parseTraceLog(live=False):
# -- phase changes -- # -- phase changes --
# start of kernel suspend # start of kernel suspend
if(re.match('suspend_enter\[.*', t.name)): if(re.match('suspend_enter\[.*', t.name)):
if(isbegin): if(isbegin and data.start == data.tKernSus):
data.dmesg[phase]['start'] = t.time data.dmesg[phase]['start'] = t.time
data.tKernSus = t.time data.tKernSus = t.time
continue continue
@ -3072,13 +3050,20 @@ def parseTraceLog(live=False):
sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name)) sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
cg.newActionFromFunction(data) cg.newActionFromFunction(data)
if sysvals.suspendmode == 'command': if sysvals.suspendmode == 'command':
return testdata return (testdata, '')
# fill in any missing phases # fill in any missing phases
error = []
for data in testdata: for data in testdata:
tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
terr = ''
lp = data.phases[0] lp = data.phases[0]
for p in data.phases: for p in data.phases:
if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0): if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
if not terr:
print 'TEST%s FAILED: %s failed in %s phase' % (tn, sysvals.suspendmode, lp)
terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, lp)
error.append(terr)
sysvals.vprint('WARNING: phase "%s" is missing!' % p) sysvals.vprint('WARNING: phase "%s" is missing!' % p)
if(data.dmesg[p]['start'] < 0): if(data.dmesg[p]['start'] < 0):
data.dmesg[p]['start'] = data.dmesg[lp]['end'] data.dmesg[p]['start'] = data.dmesg[lp]['end']
@ -3106,7 +3091,7 @@ def parseTraceLog(live=False):
for j in range(i + 1, tc): for j in range(i + 1, tc):
testdata[j].mergeOverlapDevices(devlist) testdata[j].mergeOverlapDevices(devlist)
testdata[0].stitchTouchingThreads(testdata[1:]) testdata[0].stitchTouchingThreads(testdata[1:])
return testdata return (testdata, ', '.join(error))
# Function: loadKernelLog # Function: loadKernelLog
# Description: # Description:
@ -3173,7 +3158,7 @@ def loadKernelLog():
if data: if data:
testruns.append(data) testruns.append(data)
if len(testruns) < 1: if len(testruns) < 1:
doError(' dmesg log has no suspend/resume data: %s' \ print('ERROR: dmesg log has no suspend/resume data: %s' \
% sysvals.dmesgfile) % sysvals.dmesgfile)
# fix lines with same timestamp/function with the call and return swapped # fix lines with same timestamp/function with the call and return swapped
@ -3521,68 +3506,144 @@ def createHTMLSummarySimple(testruns, htmlfile, folder):
.summary {border:1px solid;}\n\ .summary {border:1px solid;}\n\
th {border: 1px solid black;background:#222;color:white;}\n\ th {border: 1px solid black;background:#222;color:white;}\n\
td {font: 16px "Times New Roman";text-align: center;}\n\ td {font: 16px "Times New Roman";text-align: center;}\n\
tr.alt td {background:#ddd;}\n\ tr.head td {border: 1px solid black;background:#aaa;}\n\
tr.avg td {background:#aaa;}\n\ tr.alt {background-color:#ddd;}\n\
tr.notice {color:red;}\n\
.minval {background-color:#BBFFBB;}\n\
.medval {background-color:#BBBBFF;}\n\
.maxval {background-color:#FFBBBB;}\n\
.head a {color:#000;text-decoration: none;}\n\
</style>\n</head>\n<body>\n' </style>\n</head>\n<body>\n'
# extract the test data into list
list = dict()
tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [[], []]
iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
num = 0
lastmode = ''
cnt = {'pass':0, 'fail':0, 'hang':0}
for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
mode = data['mode']
if mode not in list:
list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
if lastmode and lastmode != mode and num > 0:
for i in range(2):
s = sorted(tMed[i])
list[lastmode]['med'][i] = s[int(len(s)/2)]
iMed[i] = tMed[i].index(list[lastmode]['med'][i])
list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
list[lastmode]['min'] = tMin
list[lastmode]['max'] = tMax
list[lastmode]['idx'] = (iMin, iMed, iMax)
tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [[], []]
iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
num = 0
tVal = [float(data['suspend']), float(data['resume'])]
list[mode]['data'].append([data['host'], data['kernel'],
data['time'], tVal[0], tVal[1], data['url'], data['result'],
data['issues']])
idx = len(list[mode]['data']) - 1
if data['result'] == 'pass':
cnt['pass'] += 1
for i in range(2):
tMed[i].append(tVal[i])
tAvg[i] += tVal[i]
if tMin[i] == 0 or tVal[i] < tMin[i]:
iMin[i] = idx
tMin[i] = tVal[i]
if tMax[i] == 0 or tVal[i] > tMax[i]:
iMax[i] = idx
tMax[i] = tVal[i]
num += 1
elif data['result'] == 'hang':
cnt['hang'] += 1
elif data['result'] == 'fail':
cnt['fail'] += 1
lastmode = mode
if lastmode and num > 0:
for i in range(2):
s = sorted(tMed[i])
list[lastmode]['med'][i] = s[int(len(s)/2)]
iMed[i] = tMed[i].index(list[lastmode]['med'][i])
list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
list[lastmode]['min'] = tMin
list[lastmode]['max'] = tMax
list[lastmode]['idx'] = (iMin, iMed, iMax)
# group test header # group test header
html += '<div class="stamp">%s (%d tests)</div>\n' % (folder, len(testruns)) desc = []
for ilk in sorted(cnt, reverse=True):
if cnt[ilk] > 0:
desc.append('%d %s' % (cnt[ilk], ilk))
html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (folder, len(testruns), ', '.join(desc))
th = '\t<th>{0}</th>\n' th = '\t<th>{0}</th>\n'
td = '\t<td>{0}</td>\n' td = '\t<td>{0}</td>\n'
tdh = '\t<td{1}>{0}</td>\n'
tdlink = '\t<td><a href="{0}">html</a></td>\n' tdlink = '\t<td><a href="{0}">html</a></td>\n'
# table header # table header
html += '<table class="summary">\n<tr>\n' + th.format('#') +\ html += '<table class="summary">\n<tr>\n' + th.format('#') +\
th.format('Mode') + th.format('Host') + th.format('Kernel') +\ th.format('Mode') + th.format('Host') + th.format('Kernel') +\
th.format('Test Time') + th.format('Suspend') + th.format('Resume') +\ th.format('Test Time') + th.format('Result') + th.format('Issues') +\
th.format('Detail') + '</tr>\n' th.format('Suspend') + th.format('Resume') + th.format('Detail') + '</tr>\n'
# test data, 1 row per test # export list into html
avg = '<tr class="avg"><td></td><td></td><td></td><td></td>'+\ head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
'<td>Average of {0} {1} tests</td><td>{2}</td><td>{3}</td><td></td></tr>\n' '<td colspan=8 class="sus">Suspend Avg={2} '+\
sTimeAvg = rTimeAvg = 0.0 '<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
mode = '' '<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
num = 0 '<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])): 'Resume Avg={6} '+\
if mode != data['mode']: '<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
# test average line '<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
if(num > 0): '<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
sTimeAvg /= (num - 1) '</tr>\n'
rTimeAvg /= (num - 1) headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan=8></td></tr>\n'
html += avg.format('%d' % (num - 1), mode, for mode in list:
'%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg) # header line for each suspend mode
sTimeAvg = rTimeAvg = 0.0 num = 0
mode = data['mode'] tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
num = 1 list[mode]['max'], list[mode]['med']
# alternate row color count = len(list[mode]['data'])
if num % 2 == 1: if 'idx' in list[mode]:
html += '<tr class="alt">\n' iMin, iMed, iMax = list[mode]['idx']
html += head.format('%d' % count, mode.upper(),
'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
mode.lower()
)
else: else:
html += '<tr>\n' iMin = iMed = iMax = [-1, -1, -1]
html += td.format("%d" % num) html += headnone.format('%d' % count, mode.upper())
num += 1 for d in list[mode]['data']:
# basic info # row classes - alternate row color
for item in ['mode', 'host', 'kernel', 'time']: rcls = ['alt'] if num % 2 == 1 else []
val = "unknown" if d[6] != 'pass':
if(item in data): rcls.append('notice')
val = data[item] html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
html += td.format(val) # figure out if the line has sus or res highlighted
# suspend time idx = list[mode]['data'].index(d)
sTime = float(data['suspend']) tHigh = ['', '']
sTimeAvg += sTime for i in range(2):
html += td.format('%.3f ms' % sTime) tag = 's%s' % mode if i == 0 else 'r%s' % mode
# resume time if idx == iMin[i]:
rTime = float(data['resume']) tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
rTimeAvg += rTime elif idx == iMax[i]:
html += td.format('%.3f ms' % rTime) tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
# link to the output html elif idx == iMed[i]:
html += tdlink.format(data['url']) + '</tr>\n' tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
# last test average line html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
if(num > 0): html += td.format(mode) # mode
sTimeAvg /= (num - 1) html += td.format(d[0]) # host
rTimeAvg /= (num - 1) html += td.format(d[1]) # kernel
html += avg.format('%d' % (num - 1), mode, html += td.format(d[2]) # time
'%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg) html += td.format(d[6]) # result
html += td.format(d[7]) # issues
html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend
html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume
html += tdlink.format(d[5]) if d[5] else td.format('') # url
html += '</tr>\n'
num += 1
# flush the data to file # flush the data to file
hf = open(htmlfile, 'w') hf = open(htmlfile, 'w')
@ -3607,7 +3668,7 @@ def ordinal(value):
# testruns: array of Data objects from parseKernelLog or parseTraceLog # testruns: array of Data objects from parseKernelLog or parseTraceLog
# Output: # Output:
# True if the html file was created, false if it failed # True if the html file was created, false if it failed
def createHTML(testruns): def createHTML(testruns, testfail):
if len(testruns) < 1: if len(testruns) < 1:
print('ERROR: Not enough test data to build a timeline') print('ERROR: Not enough test data to build a timeline')
return return
@ -3641,6 +3702,7 @@ def createHTML(testruns):
'<td class="purple">{4}Firmware Resume: {2} ms</td>'\ '<td class="purple">{4}Firmware Resume: {2} ms</td>'\
'<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\ '<td class="yellow" title="time from firmware mode to return from kernel enter_state({5}) [kernel time only]">{4}Kernel Resume: {3} ms</td>'\
'</tr>\n</table>\n' '</tr>\n</table>\n'
html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
# html format variables # html format variables
scaleH = 20 scaleH = 20
@ -3708,6 +3770,9 @@ def createHTML(testruns):
resume_time, testdesc, stitle, rtitle) resume_time, testdesc, stitle, rtitle)
devtl.html += thtml devtl.html += thtml
if testfail:
devtl.html += html_fail.format(testfail)
# time scale for potentially multiple datasets # time scale for potentially multiple datasets
t0 = testruns[0].start t0 = testruns[0].start
tMax = testruns[-1].end tMax = testruns[-1].end
@ -4006,6 +4071,7 @@ def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
.blue {background:rgba(169,208,245,0.4);}\n\ .blue {background:rgba(169,208,245,0.4);}\n\
.time1 {font:22px Arial;border:1px solid;}\n\ .time1 {font:22px Arial;border:1px solid;}\n\
.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\ .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
td {text-align:center;}\n\ td {text-align:center;}\n\
r {color:#500000;font:15px Tahoma;}\n\ r {color:#500000;font:15px Tahoma;}\n\
n {color:#505050;font:15px Tahoma;}\n\ n {color:#505050;font:15px Tahoma;}\n\
@ -4927,6 +4993,25 @@ def dmidecode(mempath, fatal=False):
count += 1 count += 1
return out return out
def getBattery():
p = '/sys/class/power_supply'
bat = dict()
for d in os.listdir(p):
type = sysvals.getVal(os.path.join(p, d, 'type')).strip().lower()
if type != 'battery':
continue
for v in ['status', 'energy_now', 'capacity_now']:
bat[v] = sysvals.getVal(os.path.join(p, d, v)).strip().lower()
break
ac = True
if 'status' in bat and 'discharging' in bat['status']:
ac = False
charge = 0
for v in ['energy_now', 'capacity_now']:
if v in bat and bat[v]:
charge = int(bat[v])
return (ac, charge)
# Function: getFPDT # Function: getFPDT
# Description: # Description:
# Read the acpi bios tables and pull out FPDT, the firmware data # Read the acpi bios tables and pull out FPDT, the firmware data
@ -5202,8 +5287,9 @@ def getArgFloat(name, args, min, max, main=True):
def processData(live=False): def processData(live=False):
print('PROCESSING DATA') print('PROCESSING DATA')
error = ''
if(sysvals.usetraceevents): if(sysvals.usetraceevents):
testruns = parseTraceLog(live) testruns, error = parseTraceLog(live)
if sysvals.dmesgfile: if sysvals.dmesgfile:
for data in testruns: for data in testruns:
data.extractErrorInfo() data.extractErrorInfo()
@ -5220,15 +5306,18 @@ def processData(live=False):
for data in testruns: for data in testruns:
data.debugPrint() data.debugPrint()
sys.exit() sys.exit()
if len(testruns) < 1:
return (testruns, {'error': 'timeline generation failed'})
sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile) sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
createHTML(testruns) createHTML(testruns, error)
print('DONE') print('DONE')
data = testruns[0] data = testruns[0]
stamp = data.stamp stamp = data.stamp
stamp['suspend'], stamp['resume'] = data.getTimeValues() stamp['suspend'], stamp['resume'] = data.getTimeValues()
if data.fwValid: if data.fwValid:
stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
if error:
stamp['error'] = error
return (testruns, stamp) return (testruns, stamp)
# Function: rerunTest # Function: rerunTest
@ -5268,58 +5357,88 @@ def runTest(n=0):
sysvals.sudouser(sysvals.testdir) sysvals.sudouser(sysvals.testdir)
sysvals.outputResult(stamp, n) sysvals.outputResult(stamp, n)
def find_in_html(html, strs, div=False): def find_in_html(html, start, end, firstonly=True):
for str in strs: n, out = 0, []
l = len(str) while n < len(html):
i = html.find(str) m = re.search(start, html[n:])
if i >= 0: if not m:
break break
if i < 0: i = m.end()
m = re.search(end, html[n+i:])
if not m:
break
j = m.start()
str = html[n+i:n+i+j]
if end == 'ms':
num = re.search(r'[-+]?\d*\.\d+|\d+', str)
str = num.group() if num else 'NaN'
if firstonly:
return str
out.append(str)
n += i+j
if firstonly:
return '' return ''
if not div: return out
return re.search(r'[-+]?\d*\.\d+|\d+', html[i+l:i+l+50]).group()
n = html[i+l:].find('</div>')
if n < 0:
return ''
return html[i+l:i+l+n]
# Function: runSummary # Function: runSummary
# Description: # Description:
# create a summary of tests in a sub-directory # create a summary of tests in a sub-directory
def runSummary(subdir, local=True): def runSummary(subdir, local=True, genhtml=False):
inpath = os.path.abspath(subdir) inpath = os.path.abspath(subdir)
outpath = inpath outpath = inpath
if local: if local:
outpath = os.path.abspath('.') outpath = os.path.abspath('.')
print('Generating a summary of folder "%s"' % inpath) print('Generating a summary of folder "%s"' % inpath)
if genhtml:
for dirname, dirnames, filenames in os.walk(subdir):
sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
for filename in filenames:
if(re.match('.*_dmesg.txt', filename)):
sysvals.dmesgfile = os.path.join(dirname, filename)
elif(re.match('.*_ftrace.txt', filename)):
sysvals.ftracefile = os.path.join(dirname, filename)
sysvals.setOutputFile()
if sysvals.ftracefile and sysvals.htmlfile and \
not os.path.exists(sysvals.htmlfile):
print('FTRACE: %s' % sysvals.ftracefile)
if sysvals.dmesgfile:
print('DMESG : %s' % sysvals.dmesgfile)
rerunTest()
testruns = [] testruns = []
for dirname, dirnames, filenames in os.walk(subdir): for dirname, dirnames, filenames in os.walk(subdir):
for filename in filenames: for filename in filenames:
if(not re.match('.*.html', filename)): if(not re.match('.*.html', filename)):
continue continue
file = os.path.join(dirname, filename) file = os.path.join(dirname, filename)
html = open(file, 'r').read(10000) html = open(file, 'r').read()
suspend = find_in_html(html, suspend = find_in_html(html, 'Kernel Suspend', 'ms')
['Kernel Suspend: ', 'Kernel Suspend Time: ']) resume = find_in_html(html, 'Kernel Resume', 'ms')
resume = find_in_html(html, line = find_in_html(html, '<div class="stamp">', '</div>')
['Kernel Resume: ', 'Kernel Resume Time: '])
line = find_in_html(html, ['<div class="stamp">'], True)
stmp = line.split() stmp = line.split()
if not suspend or not resume or len(stmp) < 4: if not suspend or not resume or len(stmp) != 8:
continue continue
try:
dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
except:
continue
tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
result = 'fail' if error else 'pass'
ilist = []
e = find_in_html(html, 'class="err"[\w=":;\.%\- ]*>', '&rarr;</div>', False)
for i in list(set(e)):
ilist.append('%sx%d' % (i, e.count(i)) if e.count(i) > 1 else i)
data = { data = {
'mode': stmp[2],
'host': stmp[0], 'host': stmp[0],
'kernel': stmp[1], 'kernel': stmp[1],
'mode': stmp[2], 'time': tstr,
'time': string.join(stmp[3:], ' '), 'result': result,
'issues': ','.join(ilist),
'suspend': suspend, 'suspend': suspend,
'resume': resume, 'resume': resume,
'url': os.path.relpath(file, outpath), 'url': os.path.relpath(file, outpath),
} }
if len(stmp) == 7:
data['kernel'] = 'unknown'
data['mode'] = stmp[1]
data['time'] = string.join(stmp[2:], ' ')
testruns.append(data) testruns.append(data)
outfile = os.path.join(outpath, 'summary.html') outfile = os.path.join(outpath, 'summary.html')
print('Summary file: %s' % outfile) print('Summary file: %s' % outfile)
@ -5609,11 +5728,12 @@ def printHelp():
print(' -modes List available suspend modes') print(' -modes List available suspend modes')
print(' -status Test to see if the system is enabled to run this tool') print(' -status Test to see if the system is enabled to run this tool')
print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table') print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table')
print(' -battery Print out battery info (if available)')
print(' -sysinfo Print out system info extracted from BIOS') print(' -sysinfo Print out system info extracted from BIOS')
print(' -devinfo Print out the pm settings of all devices which support runtime suspend') print(' -devinfo Print out the pm settings of all devices which support runtime suspend')
print(' -flist Print the list of functions currently being captured in ftrace') print(' -flist Print the list of functions currently being captured in ftrace')
print(' -flistall Print all functions capable of being captured in ftrace') print(' -flistall Print all functions capable of being captured in ftrace')
print(' -summary directory Create a summary of all test in this dir') print(' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]')
print(' [redo]') print(' [redo]')
print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)') print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)')
print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)') print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)')
@ -5623,8 +5743,9 @@ def printHelp():
# ----------------- MAIN -------------------- # ----------------- MAIN --------------------
# exec start (skipped if script is loaded as library) # exec start (skipped if script is loaded as library)
if __name__ == '__main__': if __name__ == '__main__':
genhtml = False
cmd = '' cmd = ''
simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-devinfo', '-status'] simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall', '-devinfo', '-status', '-battery']
if '-f' in sys.argv: if '-f' in sys.argv:
sysvals.cgskip = sysvals.configFile('cgskip.txt') sysvals.cgskip = sysvals.configFile('cgskip.txt')
# loop through the command line arguments # loop through the command line arguments
@ -5660,6 +5781,8 @@ if __name__ == '__main__':
sysvals.skiphtml = True sysvals.skiphtml = True
elif(arg == '-cgdump'): elif(arg == '-cgdump'):
sysvals.cgdump = True sysvals.cgdump = True
elif(arg == '-genhtml'):
genhtml = True
elif(arg == '-addlogs'): elif(arg == '-addlogs'):
sysvals.dmesglog = sysvals.ftracelog = True sysvals.dmesglog = sysvals.ftracelog = True
elif(arg == '-verbose'): elif(arg == '-verbose'):
@ -5856,6 +5979,8 @@ if __name__ == '__main__':
statusCheck(True) statusCheck(True)
elif(cmd == 'fpdt'): elif(cmd == 'fpdt'):
getFPDT(True) getFPDT(True)
elif(cmd == 'battery'):
print 'AC Connect: %s\nCharge: %d' % getBattery()
elif(cmd == 'sysinfo'): elif(cmd == 'sysinfo'):
sysvals.printSystemInfo(True) sysvals.printSystemInfo(True)
elif(cmd == 'devinfo'): elif(cmd == 'devinfo'):
@ -5867,7 +5992,7 @@ if __name__ == '__main__':
elif(cmd == 'flistall'): elif(cmd == 'flistall'):
sysvals.getFtraceFilterFunctions(False) sysvals.getFtraceFilterFunctions(False)
elif(cmd == 'summary'): elif(cmd == 'summary'):
runSummary(sysvals.outdir, True) runSummary(sysvals.outdir, True, genhtml)
sys.exit() sys.exit()
# if instructed, re-analyze existing data files # if instructed, re-analyze existing data files
@ -5920,7 +6045,7 @@ if __name__ == '__main__':
print('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count'])) print('TEST (%d/%d) COMPLETE' % (i+1, sysvals.multitest['count']))
sysvals.logmsg = '' sysvals.logmsg = ''
if not sysvals.skiphtml: if not sysvals.skiphtml:
runSummary(sysvals.outdir, False) runSummary(sysvals.outdir, False, False)
sysvals.sudouser(sysvals.outdir) sysvals.sudouser(sysvals.outdir)
else: else:
if sysvals.outdir: if sysvals.outdir:

View file

@ -28,6 +28,7 @@ import subprocess
import os import os
import time import time
import re import re
import signal
import sys import sys
import getopt import getopt
import Gnuplot import Gnuplot
@ -78,11 +79,12 @@ def print_help():
print(' Or') print(' Or')
print(' ./intel_pstate_tracer.py [--cpu cpus] ---trace_file <trace_file> --name <test_name>') print(' ./intel_pstate_tracer.py [--cpu cpus] ---trace_file <trace_file> --name <test_name>')
print(' To generate trace file, parse and plot, use (sudo required):') print(' To generate trace file, parse and plot, use (sudo required):')
print(' sudo ./intel_pstate_tracer.py [-c cpus] -i <interval> -n <test_name>') print(' sudo ./intel_pstate_tracer.py [-c cpus] -i <interval> -n <test_name> -m <kbytes>')
print(' Or') print(' Or')
print(' sudo ./intel_pstate_tracer.py [--cpu cpus] --interval <interval> --name <test_name>') print(' sudo ./intel_pstate_tracer.py [--cpu cpus] --interval <interval> --name <test_name> --memory <kbytes>')
print(' Optional argument:') print(' Optional argument:')
print(' cpus: comma separated list of CPUs') print(' cpus: comma separated list of CPUs')
print(' kbytes: Kilo bytes of memory per CPU to allocate to the trace buffer. Default: 10240')
print(' Output:') print(' Output:')
print(' If not already present, creates a "results/test_name" folder in the current working directory with:') print(' If not already present, creates a "results/test_name" folder in the current working directory with:')
print(' cpu.csv - comma seperated values file with trace contents and some additional calculations.') print(' cpu.csv - comma seperated values file with trace contents and some additional calculations.')
@ -379,7 +381,7 @@ def clear_trace_file():
f_handle.close() f_handle.close()
except: except:
print('IO error clearing trace file ') print('IO error clearing trace file ')
quit() sys.exit(2)
def enable_trace(): def enable_trace():
""" Enable trace """ """ Enable trace """
@ -389,7 +391,7 @@ def enable_trace():
, 'w').write("1") , 'w').write("1")
except: except:
print('IO error enabling trace ') print('IO error enabling trace ')
quit() sys.exit(2)
def disable_trace(): def disable_trace():
""" Disable trace """ """ Disable trace """
@ -399,17 +401,17 @@ def disable_trace():
, 'w').write("0") , 'w').write("0")
except: except:
print('IO error disabling trace ') print('IO error disabling trace ')
quit() sys.exit(2)
def set_trace_buffer_size(): def set_trace_buffer_size():
""" Set trace buffer size """ """ Set trace buffer size """
try: try:
open('/sys/kernel/debug/tracing/buffer_size_kb' with open('/sys/kernel/debug/tracing/buffer_size_kb', 'w') as fp:
, 'w').write("10240") fp.write(memory)
except: except:
print('IO error setting trace buffer size ') print('IO error setting trace buffer size ')
quit() sys.exit(2)
def free_trace_buffer(): def free_trace_buffer():
""" Free the trace buffer memory """ """ Free the trace buffer memory """
@ -418,8 +420,8 @@ def free_trace_buffer():
open('/sys/kernel/debug/tracing/buffer_size_kb' open('/sys/kernel/debug/tracing/buffer_size_kb'
, 'w').write("1") , 'w').write("1")
except: except:
print('IO error setting trace buffer size ') print('IO error freeing trace buffer ')
quit() sys.exit(2)
def read_trace_data(filename): def read_trace_data(filename):
""" Read and parse trace data """ """ Read and parse trace data """
@ -431,7 +433,7 @@ def read_trace_data(filename):
data = open(filename, 'r').read() data = open(filename, 'r').read()
except: except:
print('Error opening ', filename) print('Error opening ', filename)
quit() sys.exit(2)
for line in data.splitlines(): for line in data.splitlines():
search_obj = \ search_obj = \
@ -489,10 +491,22 @@ def read_trace_data(filename):
# Now seperate the main overall csv file into per CPU csv files. # Now seperate the main overall csv file into per CPU csv files.
split_csv() split_csv()
def signal_handler(signal, frame):
print(' SIGINT: Forcing cleanup before exit.')
if interval:
disable_trace()
clear_trace_file()
# Free the memory
free_trace_buffer()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
interval = "" interval = ""
filename = "" filename = ""
cpu_list = "" cpu_list = ""
testname = "" testname = ""
memory = "10240"
graph_data_present = False; graph_data_present = False;
valid1 = False valid1 = False
@ -501,7 +515,7 @@ valid2 = False
cpu_mask = zeros((MAX_CPUS,), dtype=int) cpu_mask = zeros((MAX_CPUS,), dtype=int)
try: try:
opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:",["help","trace_file=","interval=","cpu=","name="]) opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="])
except getopt.GetoptError: except getopt.GetoptError:
print_help() print_help()
sys.exit(2) sys.exit(2)
@ -521,6 +535,8 @@ for opt, arg in opts:
elif opt in ("-n", "--name"): elif opt in ("-n", "--name"):
valid2 = True valid2 = True
testname = arg testname = arg
elif opt in ("-m", "--memory"):
memory = arg
if not (valid1 and valid2): if not (valid1 and valid2):
print_help() print_help()
@ -569,6 +585,11 @@ current_max_cpu = 0
read_trace_data(filename) read_trace_data(filename)
clear_trace_file()
# Free the memory
if interval:
free_trace_buffer()
if graph_data_present == False: if graph_data_present == False:
print('No valid data to plot') print('No valid data to plot')
sys.exit(2) sys.exit(2)
@ -593,9 +614,4 @@ for root, dirs, files in os.walk('.'):
for f in files: for f in files:
fix_ownership(f) fix_ownership(f)
clear_trace_file()
# Free the memory
if interval:
free_trace_buffer()
os.chdir('../../') os.chdir('../../')