From a6fbdbb2b852c2208156f21747b270931fd0d427 Mon Sep 17 00:00:00 2001 From: Todd E Brandt Date: Tue, 30 Jan 2018 00:17:18 -0800 Subject: pm-graph: config files and installer - name change: analyze_boot.py to bootgraph.py - name change: analyze_suspend.py to sleepgraph.py - added config files for easier sleepgraph usage - added example.cfg which describes all config options - added cgskip.txt definition for slimmer callgraphs Signed-off-by: Todd Brandt Signed-off-by: Rafael J. Wysocki --- tools/power/pm-graph/Makefile | 29 +- tools/power/pm-graph/analyze_boot.py | 1012 ---- tools/power/pm-graph/analyze_suspend.py | 5533 -------------------- tools/power/pm-graph/bootgraph.py | 1012 ++++ tools/power/pm-graph/config/cgskip.txt | 65 + .../pm-graph/config/custom-timeline-functions.cfg | 205 + tools/power/pm-graph/config/example.cfg | 133 + tools/power/pm-graph/config/freeze-callgraph.cfg | 94 + tools/power/pm-graph/config/freeze-dev.cfg | 93 + tools/power/pm-graph/config/freeze.cfg | 93 + tools/power/pm-graph/config/standby-callgraph.cfg | 94 + tools/power/pm-graph/config/standby-dev.cfg | 93 + tools/power/pm-graph/config/standby.cfg | 93 + tools/power/pm-graph/config/suspend-callgraph.cfg | 98 + tools/power/pm-graph/config/suspend-dev.cfg | 93 + tools/power/pm-graph/config/suspend-x2-proc.cfg | 93 + tools/power/pm-graph/config/suspend.cfg | 93 + tools/power/pm-graph/sleepgraph.py | 5533 ++++++++++++++++++++ 18 files changed, 7907 insertions(+), 6552 deletions(-) delete mode 100755 tools/power/pm-graph/analyze_boot.py delete mode 100755 tools/power/pm-graph/analyze_suspend.py create mode 100755 tools/power/pm-graph/bootgraph.py create mode 100644 tools/power/pm-graph/config/cgskip.txt create mode 100644 tools/power/pm-graph/config/custom-timeline-functions.cfg create mode 100644 tools/power/pm-graph/config/example.cfg create mode 100644 tools/power/pm-graph/config/freeze-callgraph.cfg create mode 100644 tools/power/pm-graph/config/freeze-dev.cfg create mode 100644 tools/power/pm-graph/config/freeze.cfg create mode 100644 tools/power/pm-graph/config/standby-callgraph.cfg create mode 100644 tools/power/pm-graph/config/standby-dev.cfg create mode 100644 tools/power/pm-graph/config/standby.cfg create mode 100644 tools/power/pm-graph/config/suspend-callgraph.cfg create mode 100644 tools/power/pm-graph/config/suspend-dev.cfg create mode 100644 tools/power/pm-graph/config/suspend-x2-proc.cfg create mode 100644 tools/power/pm-graph/config/suspend.cfg create mode 100755 tools/power/pm-graph/sleepgraph.py (limited to 'tools') diff --git a/tools/power/pm-graph/Makefile b/tools/power/pm-graph/Makefile index 4e1e999e7b05..c1899cd72c80 100644 --- a/tools/power/pm-graph/Makefile +++ b/tools/power/pm-graph/Makefile @@ -7,11 +7,24 @@ all: install : uninstall install -d $(DESTDIR)$(PREFIX)/lib/pm-graph - install analyze_suspend.py $(DESTDIR)$(PREFIX)/lib/pm-graph - install analyze_boot.py $(DESTDIR)$(PREFIX)/lib/pm-graph + install sleepgraph.py $(DESTDIR)$(PREFIX)/lib/pm-graph + install bootgraph.py $(DESTDIR)$(PREFIX)/lib/pm-graph + install -d $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/cgskip.txt $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/freeze-callgraph.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/freeze.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/freeze-dev.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/standby-callgraph.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/standby.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/standby-dev.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/suspend-callgraph.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/suspend.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/suspend-dev.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config + install -m 644 config/suspend-x2-proc.cfg $(DESTDIR)$(PREFIX)/lib/pm-graph/config - ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_boot.py $(DESTDIR)$(PREFIX)/bin/bootgraph - ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_suspend.py $(DESTDIR)$(PREFIX)/bin/sleepgraph + 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 install -d $(DESTDIR)$(PREFIX)/share/man/man8 install bootgraph.8 $(DESTDIR)$(PREFIX)/share/man/man8 @@ -24,9 +37,11 @@ uninstall : rm -f $(DESTDIR)$(PREFIX)/bin/bootgraph rm -f $(DESTDIR)$(PREFIX)/bin/sleepgraph - rm -f $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_boot.py - rm -f $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_suspend.py - rm -f $(DESTDIR)$(PREFIX)/lib/pm-graph/*.pyc + rm -f $(DESTDIR)$(PREFIX)/lib/pm-graph/config/* + if [ -d $(DESTDIR)$(PREFIX)/lib/pm-graph/config ] ; then \ + rmdir $(DESTDIR)$(PREFIX)/lib/pm-graph/config; \ + fi; + rm -f $(DESTDIR)$(PREFIX)/lib/pm-graph/* if [ -d $(DESTDIR)$(PREFIX)/lib/pm-graph ] ; then \ rmdir $(DESTDIR)$(PREFIX)/lib/pm-graph; \ fi; diff --git a/tools/power/pm-graph/analyze_boot.py b/tools/power/pm-graph/analyze_boot.py deleted file mode 100755 index e83df141a597..000000000000 --- a/tools/power/pm-graph/analyze_boot.py +++ /dev/null @@ -1,1012 +0,0 @@ -#!/usr/bin/python -# -# Tool for analyzing boot timing -# Copyright (c) 2013, Intel Corporation. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms and conditions of the GNU General Public License, -# version 2, as published by the Free Software Foundation. -# -# This program is distributed in the hope it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# Authors: -# Todd Brandt -# -# Description: -# This tool is designed to assist kernel and OS developers in optimizing -# their linux stack's boot time. It creates an html representation of -# the kernel boot timeline up to the start of the init process. -# - -# ----------------- LIBRARIES -------------------- - -import sys -import time -import os -import string -import re -import platform -import shutil -from datetime import datetime, timedelta -from subprocess import call, Popen, PIPE -import analyze_suspend as aslib - -# ----------------- CLASSES -------------------- - -# Class: SystemValues -# Description: -# A global, single-instance container used to -# store system values and test parameters -class SystemValues(aslib.SystemValues): - title = 'BootGraph' - version = '2.1' - hostname = 'localhost' - testtime = '' - kernel = '' - dmesgfile = '' - ftracefile = '' - htmlfile = 'bootgraph.html' - outfile = '' - testdir = '' - testdirprefix = 'boot' - embedded = False - testlog = False - dmesglog = False - ftracelog = False - useftrace = False - usecallgraph = False - usedevsrc = True - suspendmode = 'boot' - max_graph_depth = 2 - graph_filter = 'do_one_initcall' - reboot = False - manual = False - iscronjob = False - timeformat = '%.6f' - bootloader = 'grub' - blexec = [] - def __init__(self): - if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): - self.embedded = True - self.dmesglog = True - self.outfile = os.environ['LOG_FILE'] - self.htmlfile = os.environ['LOG_FILE'] - self.hostname = platform.node() - self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') - if os.path.exists('/proc/version'): - fp = open('/proc/version', 'r') - val = fp.read().strip() - fp.close() - self.kernel = self.kernelVersion(val) - else: - self.kernel = 'unknown' - self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S') - def kernelVersion(self, msg): - return msg.split()[2] - def checkFtraceKernelVersion(self): - val = tuple(map(int, self.kernel.split('-')[0].split('.'))) - if val >= (4, 10, 0): - return True - return False - def kernelParams(self): - cmdline = 'initcall_debug log_buf_len=32M' - if self.useftrace: - if self.cpucount > 0: - bs = min(self.memtotal / 2, 2*1024*1024) / self.cpucount - else: - bs = 131072 - cmdline += ' trace_buf_size=%dK trace_clock=global '\ - 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ - 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ - 'nofuncgraph-overhead,context-info,graph-time '\ - 'ftrace=function_graph '\ - 'ftrace_graph_max_depth=%d '\ - 'ftrace_graph_filter=%s' % \ - (bs, self.max_graph_depth, self.graph_filter) - return cmdline - def setGraphFilter(self, val): - master = self.getBootFtraceFilterFunctions() - fs = '' - for i in val.split(','): - func = i.strip() - if func == '': - doError('badly formatted filter function string') - if '[' in func or ']' in func: - doError('loadable module functions not allowed - "%s"' % func) - if ' ' in func: - doError('spaces found in filter functions - "%s"' % func) - if func not in master: - doError('function "%s" not available for ftrace' % func) - if not fs: - fs = func - else: - fs += ','+func - if not fs: - doError('badly formatted filter function string') - self.graph_filter = fs - def getBootFtraceFilterFunctions(self): - self.rootCheck(True) - fp = open(self.tpath+'available_filter_functions') - fulllist = fp.read().split('\n') - fp.close() - list = [] - for i in fulllist: - if not i or ' ' in i or '[' in i or ']' in i: - continue - list.append(i) - return list - def myCronJob(self, line): - if '@reboot' not in line: - return False - if 'bootgraph' in line or 'analyze_boot.py' in line or '-cronjob' in line: - return True - return False - def cronjobCmdString(self): - cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) - args = iter(sys.argv[1:]) - for arg in args: - if arg in ['-h', '-v', '-cronjob', '-reboot']: - continue - elif arg in ['-o', '-dmesg', '-ftrace', '-func']: - args.next() - continue - cmdline += ' '+arg - if self.graph_filter != 'do_one_initcall': - cmdline += ' -func "%s"' % self.graph_filter - cmdline += ' -o "%s"' % os.path.abspath(self.testdir) - return cmdline - def manualRebootRequired(self): - cmdline = self.kernelParams() - print 'To generate a new timeline manually, follow these steps:\n' - print '1. Add the CMDLINE string to your kernel command line.' - print '2. Reboot the system.' - print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' - print 'CMDLINE="%s"' % cmdline - sys.exit() - def getExec(self, cmd): - dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', - '/usr/local/sbin', '/usr/local/bin'] - for path in dirlist: - cmdfull = os.path.join(path, cmd) - if os.path.exists(cmdfull): - return cmdfull - return '' - def blGrub(self): - blcmd = '' - for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: - if blcmd: - break - blcmd = self.getExec(cmd) - if not blcmd: - doError('[GRUB] missing update command') - if not os.path.exists('/etc/default/grub'): - doError('[GRUB] missing /etc/default/grub') - if 'grub2' in blcmd: - cfg = '/boot/grub2/grub.cfg' - else: - cfg = '/boot/grub/grub.cfg' - if not os.path.exists(cfg): - doError('[GRUB] missing %s' % cfg) - if 'update-grub' in blcmd: - self.blexec = [blcmd] - else: - self.blexec = [blcmd, '-o', cfg] - def getBootLoader(self): - if self.bootloader == 'grub': - self.blGrub() - else: - doError('unknown boot loader: %s' % self.bootloader) - -sysvals = SystemValues() - -# Class: Data -# Description: -# The primary container for test data. -class Data(aslib.Data): - dmesg = {} # root data structure - start = 0.0 # test start - end = 0.0 # test end - dmesgtext = [] # dmesg text file in memory - testnumber = 0 - idstr = '' - html_device_id = 0 - valid = False - tUserMode = 0.0 - boottime = '' - phases = ['kernel', 'user'] - do_one_initcall = False - def __init__(self, num): - self.testnumber = num - self.idstr = 'a' - self.dmesgtext = [] - self.dmesg = { - 'kernel': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, - 'order': 0, 'color': 'linear-gradient(to bottom, #fff, #bcf)'}, - 'user': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, - 'order': 1, 'color': '#fff'} - } - def deviceTopology(self): - return '' - def newAction(self, phase, name, pid, start, end, ret, ulen): - # new device callback for a specific phase - self.html_device_id += 1 - devid = '%s%d' % (self.idstr, self.html_device_id) - list = self.dmesg[phase]['list'] - length = -1.0 - if(start >= 0 and end >= 0): - length = end - start - i = 2 - origname = name - while(name in list): - name = '%s[%d]' % (origname, i) - i += 1 - list[name] = {'name': name, 'start': start, 'end': end, - 'pid': pid, 'length': length, 'row': 0, 'id': devid, - 'ret': ret, 'ulen': ulen } - return name - def deviceMatch(self, pid, cg): - if cg.end - cg.start == 0: - return True - for p in data.phases: - list = self.dmesg[p]['list'] - for devname in list: - dev = list[devname] - if pid != dev['pid']: - continue - if cg.name == 'do_one_initcall': - if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): - dev['ftrace'] = cg - self.do_one_initcall = True - return True - else: - if(cg.start > dev['start'] and cg.end < dev['end']): - if 'ftraces' not in dev: - dev['ftraces'] = [] - dev['ftraces'].append(cg) - return True - return False - -# ----------------- FUNCTIONS -------------------- - -# Function: parseKernelLog -# Description: -# parse a kernel log for boot data -def parseKernelLog(): - phase = 'kernel' - data = Data(0) - data.dmesg['kernel']['start'] = data.start = ktime = 0.0 - sysvals.stamp = { - 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), - 'host': sysvals.hostname, - 'mode': 'boot', 'kernel': ''} - - tp = aslib.TestProps() - devtemp = dict() - if(sysvals.dmesgfile): - lf = open(sysvals.dmesgfile, 'r') - else: - lf = Popen('dmesg', stdout=PIPE).stdout - for line in lf: - line = line.replace('\r\n', '') - # grab the stamp and sysinfo - if re.match(tp.stampfmt, line): - tp.stamp = line - continue - elif re.match(tp.sysinfofmt, line): - tp.sysinfo = line - continue - idx = line.find('[') - if idx > 1: - line = line[idx:] - m = re.match('[ \t]*(\[ *)(?P[0-9\.]*)(\]) (?P.*)', line) - if(not m): - continue - ktime = float(m.group('ktime')) - if(ktime > 120): - break - msg = m.group('msg') - data.dmesgtext.append(line) - if(ktime == 0.0 and re.match('^Linux version .*', msg)): - if(not sysvals.stamp['kernel']): - sysvals.stamp['kernel'] = sysvals.kernelVersion(msg) - continue - m = re.match('.* setting system clock to (?P.*) UTC.*', msg) - if(m): - bt = datetime.strptime(m.group('t'), '%Y-%m-%d %H:%M:%S') - bt = bt - timedelta(seconds=int(ktime)) - data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') - sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') - continue - m = re.match('^calling *(?P.*)\+.* @ (?P

[0-9]*)', msg) - if(m): - func = m.group('f') - pid = int(m.group('p')) - devtemp[func] = (ktime, pid) - continue - m = re.match('^initcall *(?P.*)\+.* returned (?P.*) after (?P.*) usecs', msg) - if(m): - data.valid = True - data.end = ktime - f, r, t = m.group('f', 'r', 't') - if(f in devtemp): - start, pid = devtemp[f] - data.newAction(phase, f, pid, start, ktime, int(r), int(t)) - del devtemp[f] - continue - if(re.match('^Freeing unused kernel memory.*', msg)): - data.tUserMode = ktime - data.dmesg['kernel']['end'] = ktime - data.dmesg['user']['start'] = ktime - phase = 'user' - - if tp.stamp: - sysvals.stamp = 0 - tp.parseStamp(data, sysvals) - data.dmesg['user']['end'] = data.end - lf.close() - return data - -# Function: parseTraceLog -# Description: -# Check if trace is available and copy to a temp file -def parseTraceLog(data): - # parse the trace log - ftemp = dict() - tp = aslib.TestProps() - tp.setTracerType('function_graph') - tf = open(sysvals.ftracefile, 'r') - for line in tf: - if line[0] == '#': - continue - m = re.match(tp.ftrace_line_fmt, line.strip()) - if(not m): - continue - m_time, m_proc, m_pid, m_msg, m_dur = \ - m.group('time', 'proc', 'pid', 'msg', 'dur') - if float(m_time) > data.end: - break - if(m_time and m_pid and m_msg): - t = aslib.FTraceLine(m_time, m_msg, m_dur) - pid = int(m_pid) - else: - continue - if t.fevent or t.fkprobe: - continue - key = (m_proc, pid) - if(key not in ftemp): - ftemp[key] = [] - ftemp[key].append(aslib.FTraceCallGraph(pid)) - cg = ftemp[key][-1] - if(cg.addLine(t)): - ftemp[key].append(aslib.FTraceCallGraph(pid)) - tf.close() - - # add the callgraph data to the device hierarchy - for key in ftemp: - proc, pid = key - for cg in ftemp[key]: - if len(cg.list) < 1 or cg.invalid: - continue - if(not cg.postProcess()): - print('Sanity check failed for %s-%d' % (proc, pid)) - continue - # match cg data to devices - if not data.deviceMatch(pid, cg): - print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) - -# Function: retrieveLogs -# Description: -# Create copies of dmesg and/or ftrace for later processing -def retrieveLogs(): - # check ftrace is configured first - if sysvals.useftrace: - tracer = sysvals.fgetVal('current_tracer').strip() - if tracer != 'function_graph': - doError('ftrace not configured for a boot callgraph') - # create the folder and get dmesg - sysvals.systemInfo(aslib.dmidecode(sysvals.mempath)) - sysvals.initTestOutput('boot') - sysvals.writeDatafileHeader(sysvals.dmesgfile) - call('dmesg >> '+sysvals.dmesgfile, shell=True) - if not sysvals.useftrace: - return - # get ftrace - sysvals.writeDatafileHeader(sysvals.ftracefile) - call('cat '+sysvals.tpath+'trace >> '+sysvals.ftracefile, shell=True) - -# Function: colorForName -# Description: -# Generate a repeatable color from a list for a given name -def colorForName(name): - list = [ - ('c1', '#ec9999'), - ('c2', '#ffc1a6'), - ('c3', '#fff0a6'), - ('c4', '#adf199'), - ('c5', '#9fadea'), - ('c6', '#a699c1'), - ('c7', '#ad99b4'), - ('c8', '#eaffea'), - ('c9', '#dcecfb'), - ('c10', '#ffffea') - ] - i = 0 - total = 0 - count = len(list) - while i < len(name): - total += ord(name[i]) - i += 1 - return list[total % count] - -def cgOverview(cg, minlen): - stats = dict() - large = [] - for l in cg.list: - if l.fcall and l.depth == 1: - if l.length >= minlen: - large.append(l) - if l.name not in stats: - stats[l.name] = [0, 0.0] - stats[l.name][0] += (l.length * 1000.0) - stats[l.name][1] += 1 - return (large, stats) - -# Function: createBootGraph -# Description: -# Create the output html file from the resident test data -# Arguments: -# testruns: array of Data objects from parseKernelLog or parseTraceLog -# Output: -# True if the html file was created, false if it failed -def createBootGraph(data): - # html function templates - html_srccall = '

{0}
\n' - html_timetotal = '\n'\ - ''\ - ''\ - '\n
Init process starts @ {0} msLast initcall ends @ {1} ms
\n' - - # device timeline - devtl = aslib.Timeline(100, 20) - - # write the test title and general info header - devtl.createHeader(sysvals) - - # Generate the header for this timeline - t0 = data.start - tMax = data.end - tTotal = tMax - t0 - if(tTotal == 0): - print('ERROR: No timeline data') - return False - user_mode = '%.0f'%(data.tUserMode*1000) - last_init = '%.0f'%(tTotal*1000) - devtl.html += html_timetotal.format(user_mode, last_init) - - # determine the maximum number of rows we need to draw - devlist = [] - for p in data.phases: - list = data.dmesg[p]['list'] - for devname in list: - d = aslib.DevItem(0, p, list[devname]) - devlist.append(d) - devtl.getPhaseRows(devlist, 0, 'start') - devtl.calcTotalRows() - - # draw the timeline background - devtl.createZoomBox() - devtl.html += devtl.html_tblock.format('boot', '0', '100', devtl.scaleH) - for p in data.phases: - phase = data.dmesg[p] - length = phase['end']-phase['start'] - left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) - width = '%.3f' % ((length*100.0)/tTotal) - devtl.html += devtl.html_phase.format(left, width, \ - '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ - phase['color'], '') - - # draw the device timeline - num = 0 - devstats = dict() - for phase in data.phases: - list = data.dmesg[phase]['list'] - for devname in sorted(list): - cls, color = colorForName(devname) - dev = list[devname] - info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, - dev['ulen']/1000.0, dev['ret']) - devstats[dev['id']] = {'info':info} - dev['color'] = color - height = devtl.phaseRowHeight(0, phase, dev['row']) - top = '%.6f' % ((dev['row']*height) + devtl.scaleH) - left = '%.6f' % (((dev['start']-t0)*100)/tTotal) - width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) - length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) - devtl.html += devtl.html_device.format(dev['id'], - devname+length+phase+'_mode', left, top, '%.3f'%height, - width, devname, ' '+cls, '') - rowtop = devtl.phaseRowTop(0, phase, dev['row']) - height = '%.6f' % (devtl.rowH / 2) - top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) - if data.do_one_initcall: - if('ftrace' not in dev): - continue - cg = dev['ftrace'] - large, stats = cgOverview(cg, 0.001) - devstats[dev['id']]['fstat'] = stats - for l in large: - left = '%f' % (((l.time-t0)*100)/tTotal) - width = '%f' % (l.length*100/tTotal) - title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) - devtl.html += html_srccall.format(l.name, left, - top, height, width, title, 'x%d'%num) - num += 1 - continue - if('ftraces' not in dev): - continue - for cg in dev['ftraces']: - left = '%f' % (((cg.start-t0)*100)/tTotal) - width = '%f' % ((cg.end-cg.start)*100/tTotal) - cglen = (cg.end - cg.start) * 1000.0 - title = '%s (%0.3fms)' % (cg.name, cglen) - cg.id = 'x%d' % num - devtl.html += html_srccall.format(cg.name, left, - top, height, width, title, dev['id']+cg.id) - num += 1 - - # draw the time scale, try to make the number of labels readable - devtl.createTimeScale(t0, tMax, tTotal, 'boot') - devtl.html += '\n' - - # timeline is finished - devtl.html += '\n\n' - - # draw a legend which describes the phases by color - devtl.html += '
\n' - pdelta = 20.0 - pmargin = 36.0 - for phase in data.phases: - order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) - devtl.html += devtl.html_legend.format(order, \ - data.dmesg[phase]['color'], phase+'_mode', phase[0]) - devtl.html += '
\n' - - if(sysvals.outfile == sysvals.htmlfile): - hf = open(sysvals.htmlfile, 'a') - else: - hf = open(sysvals.htmlfile, 'w') - - # add the css if this is not an embedded run - extra = '\ - .c1 {background:rgba(209,0,0,0.4);}\n\ - .c2 {background:rgba(255,102,34,0.4);}\n\ - .c3 {background:rgba(255,218,33,0.4);}\n\ - .c4 {background:rgba(51,221,0,0.4);}\n\ - .c5 {background:rgba(17,51,204,0.4);}\n\ - .c6 {background:rgba(34,0,102,0.4);}\n\ - .c7 {background:rgba(51,0,68,0.4);}\n\ - .c8 {background:rgba(204,255,204,0.4);}\n\ - .c9 {background:rgba(169,208,245,0.4);}\n\ - .c10 {background:rgba(255,255,204,0.4);}\n\ - .vt {transform:rotate(-60deg);transform-origin:0 0;}\n\ - table.fstat {table-layout:fixed;padding:150px 15px 0 0;font-size:10px;column-width:30px;}\n\ - .fstat th {width:55px;}\n\ - .fstat td {text-align:left;width:35px;}\n\ - .srccall {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\ - .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' - if(not sysvals.embedded): - aslib.addCSS(hf, sysvals, 1, False, extra) - - # write the device timeline - hf.write(devtl.html) - - # add boot specific html - statinfo = 'var devstats = {\n' - for n in sorted(devstats): - statinfo += '\t"%s": [\n\t\t"%s",\n' % (n, devstats[n]['info']) - if 'fstat' in devstats[n]: - funcs = devstats[n]['fstat'] - for f in sorted(funcs, key=funcs.get, reverse=True): - if funcs[f][0] < 0.01 and len(funcs) > 10: - break - statinfo += '\t\t"%f|%s|%d",\n' % (funcs[f][0], f, funcs[f][1]) - statinfo += '\t],\n' - statinfo += '};\n' - html = \ - '
\n'\ - '\n'\ - '\n' - hf.write(html) - - # add the callgraph html - if(sysvals.usecallgraph): - aslib.addCallgraphs(sysvals, hf, data) - - # add the dmesg log as a hidden div - if sysvals.dmesglog: - hf.write('\n') - - if(not sysvals.embedded): - # write the footer and close - aslib.addScriptCode(hf, [data]) - hf.write('\n\n') - else: - # embedded out will be loaded in a page, skip the js - hf.write('' % \ - (data.start*1000, data.end*1000)) - hf.close() - return True - -# Function: updateCron -# Description: -# (restore=False) Set the tool to run automatically on reboot -# (restore=True) Restore the original crontab -def updateCron(restore=False): - if not restore: - sysvals.rootUser(True) - crondir = '/var/spool/cron/crontabs/' - if not os.path.exists(crondir): - crondir = '/var/spool/cron/' - if not os.path.exists(crondir): - doError('%s not found' % crondir) - cronfile = crondir+'root' - backfile = crondir+'root-analyze_boot-backup' - cmd = sysvals.getExec('crontab') - if not cmd: - doError('crontab not found') - # on restore: move the backup cron back into place - if restore: - if os.path.exists(backfile): - shutil.move(backfile, cronfile) - call([cmd, cronfile]) - return - # backup current cron and install new one with reboot - if os.path.exists(cronfile): - shutil.move(cronfile, backfile) - else: - fp = open(backfile, 'w') - fp.close() - res = -1 - try: - fp = open(backfile, 'r') - op = open(cronfile, 'w') - for line in fp: - if not sysvals.myCronJob(line): - op.write(line) - continue - fp.close() - op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) - op.close() - res = call([cmd, cronfile]) - except Exception, e: - print 'Exception: %s' % str(e) - shutil.move(backfile, cronfile) - res = -1 - if res != 0: - doError('crontab failed') - -# Function: updateGrub -# Description: -# update grub.cfg for all kernels with our parameters -def updateGrub(restore=False): - # call update-grub on restore - if restore: - try: - call(sysvals.blexec, stderr=PIPE, stdout=PIPE, - env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) - except Exception, e: - print 'Exception: %s\n' % str(e) - return - # extract the option and create a grub config without it - sysvals.rootUser(True) - tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' - cmdline = '' - grubfile = '/etc/default/grub' - tempfile = '/etc/default/grub.analyze_boot' - shutil.move(grubfile, tempfile) - res = -1 - try: - fp = open(tempfile, 'r') - op = open(grubfile, 'w') - cont = False - for line in fp: - line = line.strip() - if len(line) == 0 or line[0] == '#': - continue - opt = line.split('=')[0].strip() - if opt == tgtopt: - cmdline = line.split('=', 1)[1].strip('\\') - if line[-1] == '\\': - cont = True - elif cont: - cmdline += line.strip('\\') - if line[-1] != '\\': - cont = False - else: - op.write('%s\n' % line) - fp.close() - # if the target option value is in quotes, strip them - sp = '"' - val = cmdline.strip() - if val and (val[0] == '\'' or val[0] == '"'): - sp = val[0] - val = val.strip(sp) - cmdline = val - # append our cmd line options - if len(cmdline) > 0: - cmdline += ' ' - cmdline += sysvals.kernelParams() - # write out the updated target option - op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) - op.close() - res = call(sysvals.blexec) - os.remove(grubfile) - except Exception, e: - print 'Exception: %s' % str(e) - res = -1 - # cleanup - shutil.move(tempfile, grubfile) - if res != 0: - doError('update grub failed') - -# Function: updateKernelParams -# Description: -# update boot conf for all kernels with our parameters -def updateKernelParams(restore=False): - # find the boot loader - sysvals.getBootLoader() - if sysvals.bootloader == 'grub': - updateGrub(restore) - -# Function: doError Description: -# generic error function for catastrphic failures -# Arguments: -# msg: the error message to print -# help: True if printHelp should be called after, False otherwise -def doError(msg, help=False): - if help == True: - printHelp() - print 'ERROR: %s\n' % msg - sys.exit() - -# Function: printHelp -# Description: -# print out the help text -def printHelp(): - print('') - print('%s v%s' % (sysvals.title, sysvals.version)) - print('Usage: bootgraph ') - print('') - print('Description:') - print(' This tool reads in a dmesg log of linux kernel boot and') - print(' creates an html representation of the boot timeline up to') - print(' the start of the init process.') - print('') - print(' If no specific command is given the tool reads the current dmesg') - print(' and/or ftrace log and creates a timeline') - print('') - print(' Generates output files in subdirectory: boot-yymmdd-HHMMSS') - print(' HTML output: _boot.html') - print(' raw dmesg output: _boot_dmesg.txt') - print(' raw ftrace output: _boot_ftrace.txt') - print('') - print('Options:') - print(' -h Print this help text') - print(' -v Print the current tool version') - print(' -addlogs Add the dmesg log to the html output') - print(' -o name Overrides the output subdirectory name when running a new test') - print(' default: boot-{date}-{time}') - print(' [advanced]') - print(' -f Use ftrace to add function detail (default: disabled)') - print(' -callgraph Add callgraph detail, can be very large (default: disabled)') - print(' -maxdepth N limit the callgraph data to N call levels (default: 2)') - print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') - print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') - print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') - print(' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') - print(' -cgfilter S Filter the callgraph output in the timeline') - print(' -bl name Use the following boot loader for kernel params (default: grub)') - print(' -reboot Reboot the machine automatically and generate a new timeline') - print(' -manual Show the steps to generate a new timeline manually (used with -reboot)') - print('') - print('Other commands:') - print(' -flistall Print all functions capable of being captured in ftrace') - print(' -sysinfo Print out system info extracted from BIOS') - print(' [redo]') - print(' -dmesg file Create HTML output using dmesg input (used with -ftrace)') - print(' -ftrace file Create HTML output using ftrace input (used with -dmesg)') - print('') - return True - -# ----------------- MAIN -------------------- -# exec start (skipped if script is loaded as library) -if __name__ == '__main__': - # loop through the command line arguments - cmd = '' - testrun = True - simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] - args = iter(sys.argv[1:]) - for arg in args: - if(arg == '-h'): - printHelp() - sys.exit() - elif(arg == '-v'): - print("Version %s" % sysvals.version) - sys.exit() - elif(arg in simplecmds): - cmd = arg[1:] - elif(arg == '-f'): - sysvals.useftrace = True - elif(arg == '-callgraph'): - sysvals.useftrace = True - sysvals.usecallgraph = True - elif(arg == '-mincg'): - sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) - elif(arg == '-cgfilter'): - try: - val = args.next() - except: - doError('No callgraph functions supplied', True) - sysvals.setDeviceFilter(val) - elif(arg == '-bl'): - try: - val = args.next() - except: - doError('No boot loader name supplied', True) - if val.lower() not in ['grub']: - doError('Unknown boot loader: %s' % val, True) - sysvals.bootloader = val.lower() - elif(arg == '-timeprec'): - sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) - elif(arg == '-maxdepth'): - sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) - elif(arg == '-func'): - try: - val = args.next() - except: - doError('No filter functions supplied', True) - sysvals.useftrace = True - sysvals.usecallgraph = True - sysvals.rootCheck(True) - sysvals.setGraphFilter(val) - elif(arg == '-ftrace'): - try: - val = args.next() - except: - doError('No ftrace file supplied', True) - if(os.path.exists(val) == False): - doError('%s does not exist' % val) - testrun = False - sysvals.ftracefile = val - elif(arg == '-addlogs'): - sysvals.dmesglog = True - elif(arg == '-expandcg'): - sysvals.cgexp = True - elif(arg == '-dmesg'): - try: - val = args.next() - except: - doError('No dmesg file supplied', True) - if(os.path.exists(val) == False): - doError('%s does not exist' % val) - if(sysvals.htmlfile == val or sysvals.outfile == val): - doError('Output filename collision') - testrun = False - sysvals.dmesgfile = val - elif(arg == '-o'): - try: - val = args.next() - except: - doError('No subdirectory name supplied', True) - sysvals.testdir = sysvals.setOutputFolder(val) - elif(arg == '-reboot'): - sysvals.reboot = True - elif(arg == '-manual'): - sysvals.reboot = True - sysvals.manual = True - # remaining options are only for cron job use - elif(arg == '-cronjob'): - sysvals.iscronjob = True - else: - doError('Invalid argument: '+arg, True) - - # compatibility errors and access checks - if(sysvals.iscronjob and (sysvals.reboot or \ - sysvals.dmesgfile or sysvals.ftracefile or cmd)): - doError('-cronjob is meant for batch purposes only') - if(sysvals.reboot and (sysvals.dmesgfile or sysvals.ftracefile)): - doError('-reboot and -dmesg/-ftrace are incompatible') - if cmd or sysvals.reboot or sysvals.iscronjob or testrun: - sysvals.rootCheck(True) - if (testrun and sysvals.useftrace) or cmd == 'flistall': - if not sysvals.verifyFtrace(): - doError('Ftrace is not properly enabled') - - # run utility commands - sysvals.cpuInfo() - if cmd != '': - if cmd == 'kpupdate': - updateKernelParams() - elif cmd == 'flistall': - for f in sysvals.getBootFtraceFilterFunctions(): - print f - elif cmd == 'checkbl': - sysvals.getBootLoader() - print 'Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec) - elif(cmd == 'sysinfo'): - sysvals.printSystemInfo() - sys.exit() - - # reboot: update grub, setup a cronjob, and reboot - if sysvals.reboot: - if (sysvals.useftrace or sysvals.usecallgraph) and \ - not sysvals.checkFtraceKernelVersion(): - doError('Ftrace functionality requires kernel v4.10 or newer') - if not sysvals.manual: - updateKernelParams() - updateCron() - call('reboot') - else: - sysvals.manualRebootRequired() - sys.exit() - - # cronjob: remove the cronjob, grub changes, and disable ftrace - if sysvals.iscronjob: - updateCron(True) - updateKernelParams(True) - try: - sysvals.fsetVal('0', 'tracing_on') - except: - pass - - # testrun: generate copies of the logs - if testrun: - retrieveLogs() - else: - sysvals.setOutputFile() - - # process the log data - if sysvals.dmesgfile: - data = parseKernelLog() - if(not data.valid): - doError('No initcall data found in %s' % sysvals.dmesgfile) - if sysvals.useftrace and sysvals.ftracefile: - parseTraceLog(data) - else: - doError('dmesg file required') - - print(' Host: %s' % sysvals.hostname) - print(' Test time: %s' % sysvals.testtime) - print(' Boot time: %s' % data.boottime) - print('Kernel Version: %s' % sysvals.kernel) - print(' Kernel start: %.3f' % (data.start * 1000)) - print('Usermode start: %.3f' % (data.tUserMode * 1000)) - print('Last Init Call: %.3f' % (data.end * 1000)) - - # handle embedded output logs - if(sysvals.outfile and sysvals.embedded): - fp = open(sysvals.outfile, 'w') - fp.write('pass %s initstart %.3f end %.3f boot %s\n' % - (data.valid, data.tUserMode*1000, data.end*1000, data.boottime)) - fp.close() - - createBootGraph(data) - - # if running as root, change output dir owner to sudo_user - if testrun and os.path.isdir(sysvals.testdir) and \ - os.getuid() == 0 and 'SUDO_USER' in os.environ: - cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' - call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) diff --git a/tools/power/pm-graph/analyze_suspend.py b/tools/power/pm-graph/analyze_suspend.py deleted file mode 100755 index 1b60fe203741..000000000000 --- a/tools/power/pm-graph/analyze_suspend.py +++ /dev/null @@ -1,5533 +0,0 @@ -#!/usr/bin/python -# -# Tool for analyzing suspend/resume timing -# Copyright (c) 2013, Intel Corporation. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms and conditions of the GNU General Public License, -# version 2, as published by the Free Software Foundation. -# -# This program is distributed in the hope it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# Authors: -# Todd Brandt -# -# Links: -# Home Page -# https://01.org/suspendresume -# Source repo -# https://github.com/01org/pm-graph -# -# Description: -# This tool is designed to assist kernel and OS developers in optimizing -# their linux stack's suspend/resume time. Using a kernel image built -# with a few extra options enabled, the tool will execute a suspend and -# will capture dmesg and ftrace data until resume is complete. This data -# is transformed into a device timeline and a callgraph to give a quick -# and detailed view of which devices and callbacks are taking the most -# time in suspend/resume. The output is a single html file which can be -# viewed in firefox or chrome. -# -# The following kernel build options are required: -# CONFIG_PM_DEBUG=y -# CONFIG_PM_SLEEP_DEBUG=y -# CONFIG_FTRACE=y -# CONFIG_FUNCTION_TRACER=y -# CONFIG_FUNCTION_GRAPH_TRACER=y -# CONFIG_KPROBES=y -# CONFIG_KPROBES_ON_FTRACE=y -# -# For kernel versions older than 3.15: -# The following additional kernel parameters are required: -# (e.g. in file /etc/default/grub) -# GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..." -# - -# ----------------- LIBRARIES -------------------- - -import sys -import time -import os -import string -import re -import platform -from datetime import datetime -import struct -import ConfigParser -from threading import Thread -from subprocess import call, Popen, PIPE - -# ----------------- CLASSES -------------------- - -# Class: SystemValues -# Description: -# A global, single-instance container used to -# store system values and test parameters -class SystemValues: - title = 'SleepGraph' - version = '4.7' - ansi = False - verbose = False - testlog = True - dmesglog = False - ftracelog = False - mindevlen = 0.0 - mincglen = 0.0 - cgphase = '' - cgtest = -1 - max_graph_depth = 0 - callloopmaxgap = 0.0001 - callloopmaxlen = 0.005 - cpucount = 0 - memtotal = 204800 - srgap = 0 - cgexp = False - testdir = '' - tpath = '/sys/kernel/debug/tracing/' - fpdtpath = '/sys/firmware/acpi/tables/FPDT' - epath = '/sys/kernel/debug/tracing/events/power/' - traceevents = [ - 'suspend_resume', - 'device_pm_callback_end', - 'device_pm_callback_start' - ] - logmsg = '' - testcommand = '' - mempath = '/dev/mem' - powerfile = '/sys/power/state' - mempowerfile = '/sys/power/mem_sleep' - suspendmode = 'mem' - memmode = '' - hostname = 'localhost' - prefix = 'test' - teststamp = '' - sysstamp = '' - dmesgstart = 0.0 - dmesgfile = '' - ftracefile = '' - htmlfile = 'output.html' - embedded = False - rtcwake = True - rtcwaketime = 15 - rtcpath = '' - devicefilter = [] - stamp = 0 - execcount = 1 - x2delay = 0 - usecallgraph = False - usetraceevents = False - usetraceeventsonly = False - usetracemarkers = True - usekprobes = True - usedevsrc = False - useprocmon = False - notestrun = False - mixedphaseheight = True - devprops = dict() - predelay = 0 - postdelay = 0 - procexecfmt = 'ps - (?P.*)$' - devpropfmt = '# Device Properties: .*' - tracertypefmt = '# tracer: (?P.*)' - firmwarefmt = '# fwsuspend (?P[0-9]*) fwresume (?P[0-9]*)$' - tracefuncs = { - 'sys_sync': dict(), - 'pm_prepare_console': dict(), - 'pm_notifier_call_chain': dict(), - 'freeze_processes': dict(), - 'freeze_kernel_threads': dict(), - 'pm_restrict_gfp_mask': dict(), - 'acpi_suspend_begin': dict(), - 'suspend_console': dict(), - 'acpi_pm_prepare': dict(), - 'syscore_suspend': dict(), - 'arch_enable_nonboot_cpus_end': dict(), - 'syscore_resume': dict(), - 'acpi_pm_finish': dict(), - 'resume_console': dict(), - 'acpi_pm_end': dict(), - 'pm_restore_gfp_mask': dict(), - 'thaw_processes': dict(), - 'pm_restore_console': dict(), - 'CPU_OFF': { - 'func':'_cpu_down', - 'args_x86_64': {'cpu':'%di:s32'}, - 'format': 'CPU_OFF[{cpu}]' - }, - 'CPU_ON': { - 'func':'_cpu_up', - 'args_x86_64': {'cpu':'%di:s32'}, - 'format': 'CPU_ON[{cpu}]' - }, - } - 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 }, - 'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 }, - 'acpi_os_stall': {'ub': 1}, - # ACPI - 'acpi_resume_power_resources': dict(), - 'acpi_ps_parse_aml': dict(), - # filesystem - 'ext4_sync_fs': dict(), - # 80211 - 'iwlagn_mac_start': dict(), - 'iwlagn_alloc_bcast_station': dict(), - 'iwl_trans_pcie_start_hw': dict(), - 'iwl_trans_pcie_start_fw': dict(), - 'iwl_run_init_ucode': dict(), - 'iwl_load_ucode_wait_alive': dict(), - 'iwl_alive_start': dict(), - 'iwlagn_mac_stop': dict(), - 'iwlagn_mac_suspend': dict(), - 'iwlagn_mac_resume': dict(), - 'iwlagn_mac_add_interface': dict(), - 'iwlagn_mac_remove_interface': dict(), - 'iwlagn_mac_change_interface': dict(), - 'iwlagn_mac_config': dict(), - 'iwlagn_configure_filter': dict(), - 'iwlagn_mac_hw_scan': dict(), - 'iwlagn_bss_info_changed': dict(), - 'iwlagn_mac_channel_switch': dict(), - 'iwlagn_mac_flush': dict(), - # ATA - 'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} }, - # i915 - 'i915_gem_resume': dict(), - 'i915_restore_state': dict(), - 'intel_opregion_setup': dict(), - 'g4x_pre_enable_dp': dict(), - 'vlv_pre_enable_dp': dict(), - 'chv_pre_enable_dp': dict(), - 'g4x_enable_dp': dict(), - 'vlv_enable_dp': dict(), - 'intel_hpd_init': dict(), - 'intel_opregion_register': dict(), - 'intel_dp_detect': dict(), - 'intel_hdmi_detect': dict(), - 'intel_opregion_init': dict(), - 'intel_fbdev_set_suspend': dict(), - } - kprobes = dict() - timeformat = '%.3f' - def __init__(self): - # if this is a phoronix test run, set some default options - if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): - self.embedded = True - self.dmesglog = self.ftracelog = True - self.htmlfile = os.environ['LOG_FILE'] - self.archargs = 'args_'+platform.machine() - self.hostname = platform.node() - if(self.hostname == ''): - self.hostname = 'localhost' - rtc = "rtc0" - if os.path.exists('/dev/rtc'): - rtc = os.readlink('/dev/rtc') - rtc = '/sys/class/rtc/'+rtc - if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \ - os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'): - self.rtcpath = rtc - if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()): - self.ansi = True - self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S') - def rootCheck(self, fatal=True): - if(os.access(self.powerfile, os.W_OK)): - return True - if fatal: - doError('This command requires sysfs mount and root access') - return False - def rootUser(self, fatal=False): - if 'USER' in os.environ and os.environ['USER'] == 'root': - return True - if fatal: - doError('This command must be run as root') - return False - def setPrecision(self, num): - if num < 0 or num > 6: - return - self.timeformat = '%.{0}f'.format(num) - def setOutputFolder(self, value): - args = dict() - n = datetime.now() - args['date'] = n.strftime('%y%m%d') - args['time'] = n.strftime('%H%M%S') - args['hostname'] = self.hostname - return value.format(**args) - def setOutputFile(self): - if self.dmesgfile != '': - m = re.match('(?P.*)_dmesg\.txt$', self.dmesgfile) - if(m): - self.htmlfile = m.group('name')+'.html' - if self.ftracefile != '': - m = re.match('(?P.*)_ftrace\.txt$', self.ftracefile) - if(m): - self.htmlfile = m.group('name')+'.html' - def systemInfo(self, info): - p = c = m = b = '' - if 'baseboard-manufacturer' in info: - m = info['baseboard-manufacturer'] - elif 'system-manufacturer' in info: - m = info['system-manufacturer'] - if 'baseboard-product-name' in info: - p = info['baseboard-product-name'] - elif 'system-product-name' in info: - p = info['system-product-name'] - if 'processor-version' in info: - c = info['processor-version'] - if 'bios-version' in info: - b = info['bios-version'] - self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | numcpu:%d | memsz:%d' % \ - (m, p, c, b, self.cpucount, self.memtotal) - def printSystemInfo(self): - self.rootCheck(True) - out = dmidecode(self.mempath, True) - fmt = '%-24s: %s' - for name in sorted(out): - print fmt % (name, out[name]) - print fmt % ('cpucount', ('%d' % self.cpucount)) - print fmt % ('memtotal', ('%d kB' % self.memtotal)) - def cpuInfo(self): - self.cpucount = 0 - fp = open('/proc/cpuinfo', 'r') - for line in fp: - if re.match('^processor[ \t]*:[ \t]*[0-9]*', line): - self.cpucount += 1 - fp.close() - fp = open('/proc/meminfo', 'r') - for line in fp: - m = re.match('^MemTotal:[ \t]*(?P[0-9]*) *kB', line) - if m: - self.memtotal = int(m.group('sz')) - break - fp.close() - def initTestOutput(self, name): - self.prefix = self.hostname - v = open('/proc/version', 'r').read().strip() - kver = string.split(v)[2] - fmt = name+'-%m%d%y-%H%M%S' - testtime = datetime.now().strftime(fmt) - self.teststamp = \ - '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver - if(self.embedded): - self.dmesgfile = \ - '/tmp/'+testtime+'_'+self.suspendmode+'_dmesg.txt' - self.ftracefile = \ - '/tmp/'+testtime+'_'+self.suspendmode+'_ftrace.txt' - return - self.dmesgfile = \ - self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt' - self.ftracefile = \ - self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt' - self.htmlfile = \ - self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html' - if not os.path.isdir(self.testdir): - os.mkdir(self.testdir) - def setDeviceFilter(self, value): - self.devicefilter = [] - if value: - value = value.split(',') - for i in value: - self.devicefilter.append(i.strip()) - def rtcWakeAlarmOn(self): - call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True) - outD = open(self.rtcpath+'/date', 'r').read().strip() - outT = open(self.rtcpath+'/time', 'r').read().strip() - mD = re.match('^(?P[0-9]*)-(?P[0-9]*)-(?P[0-9]*)', outD) - mT = re.match('^(?P[0-9]*):(?P[0-9]*):(?P[0-9]*)', outT) - if(mD and mT): - # get the current time from hardware - utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds()) - dt = datetime(\ - int(mD.group('y')), int(mD.group('m')), int(mD.group('d')), - int(mT.group('h')), int(mT.group('m')), int(mT.group('s'))) - nowtime = int(dt.strftime('%s')) + utcoffset - else: - # if hardware time fails, use the software time - nowtime = int(datetime.now().strftime('%s')) - alarm = nowtime + self.rtcwaketime - call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True) - def rtcWakeAlarmOff(self): - call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True) - def initdmesg(self): - # get the latest time stamp from the dmesg log - fp = Popen('dmesg', stdout=PIPE).stdout - ktime = '0' - for line in fp: - line = line.replace('\r\n', '') - idx = line.find('[') - if idx > 1: - line = line[idx:] - m = re.match('[ \t]*(\[ *)(?P[0-9\.]*)(\]) (?P.*)', line) - if(m): - ktime = m.group('ktime') - fp.close() - self.dmesgstart = float(ktime) - def getdmesg(self): - # store all new dmesg lines since initdmesg was called - fp = Popen('dmesg', stdout=PIPE).stdout - op = open(self.dmesgfile, 'a') - for line in fp: - line = line.replace('\r\n', '') - idx = line.find('[') - if idx > 1: - line = line[idx:] - m = re.match('[ \t]*(\[ *)(?P[0-9\.]*)(\]) (?P.*)', line) - if(not m): - continue - ktime = float(m.group('ktime')) - if ktime > self.dmesgstart: - op.write(line) - fp.close() - op.close() - def addFtraceFilterFunctions(self, file): - fp = open(file) - list = fp.read().split('\n') - fp.close() - for i in list: - if len(i) < 2: - continue - self.tracefuncs[i] = dict() - def getFtraceFilterFunctions(self, current): - self.rootCheck(True) - if not current: - call('cat '+self.tpath+'available_filter_functions', shell=True) - return - fp = open(self.tpath+'available_filter_functions') - master = fp.read().split('\n') - fp.close() - for i in self.tracefuncs: - if 'func' in self.tracefuncs[i]: - i = self.tracefuncs[i]['func'] - if i in master: - print i - else: - print self.colorText(i) - def setFtraceFilterFunctions(self, list): - fp = open(self.tpath+'available_filter_functions') - master = fp.read().split('\n') - fp.close() - flist = '' - for i in list: - if i not in master: - continue - if ' [' in i: - flist += i.split(' ')[0]+'\n' - else: - flist += i+'\n' - fp = open(self.tpath+'set_graph_function', 'w') - fp.write(flist) - fp.close() - def basicKprobe(self, name): - self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name} - def defaultKprobe(self, name, kdata): - k = kdata - for field in ['name', 'format', 'func']: - if field not in k: - k[field] = name - if self.archargs in k: - k['args'] = k[self.archargs] - else: - k['args'] = dict() - k['format'] = name - self.kprobes[name] = k - def kprobeColor(self, name): - if name not in self.kprobes or 'color' not in self.kprobes[name]: - return '' - return self.kprobes[name]['color'] - def kprobeDisplayName(self, name, dataraw): - if name not in self.kprobes: - self.basicKprobe(name) - data = '' - quote=0 - # first remvoe any spaces inside quotes, and the quotes - for c in dataraw: - if c == '"': - quote = (quote + 1) % 2 - if quote and c == ' ': - data += '_' - elif c != '"': - data += c - fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args'] - arglist = dict() - # now process the args - for arg in sorted(args): - arglist[arg] = '' - m = re.match('.* '+arg+'=(?P.*) ', data); - if m: - arglist[arg] = m.group('arg') - else: - m = re.match('.* '+arg+'=(?P.*)', data); - if m: - arglist[arg] = m.group('arg') - out = fmt.format(**arglist) - out = out.replace(' ', '_').replace('"', '') - return out - def kprobeText(self, kname, kprobe): - name = fmt = func = kname - args = dict() - if 'name' in kprobe: - name = kprobe['name'] - if 'format' in kprobe: - fmt = kprobe['format'] - if 'func' in kprobe: - func = kprobe['func'] - if self.archargs in kprobe: - args = kprobe[self.archargs] - if 'args' in kprobe: - args = kprobe['args'] - if re.findall('{(?P[a-z,A-Z,0-9]*)}', func): - doError('Kprobe "%s" has format info in the function name "%s"' % (name, func)) - for arg in re.findall('{(?P[a-z,A-Z,0-9]*)}', fmt): - if arg not in args: - doError('Kprobe "%s" is missing argument "%s"' % (name, arg)) - val = 'p:%s_cal %s' % (name, func) - for i in sorted(args): - val += ' %s=%s' % (i, args[i]) - val += '\nr:%s_ret %s $retval\n' % (name, func) - return val - def addKprobes(self, output=False): - if len(self.kprobes) < 1: - return - if output: - print(' kprobe functions in this kernel:') - # first test each kprobe - rejects = [] - # sort kprobes: trace, ub-dev, custom, dev - kpl = [[], [], [], []] - for name in sorted(self.kprobes): - res = self.colorText('YES', 32) - if not self.testKprobe(name, self.kprobes[name]): - res = self.colorText('NO') - rejects.append(name) - else: - if name in self.tracefuncs: - kpl[0].append(name) - elif name in self.dev_tracefuncs: - if 'ub' in self.dev_tracefuncs[name]: - kpl[1].append(name) - else: - kpl[3].append(name) - else: - kpl[2].append(name) - if output: - print(' %s: %s' % (name, res)) - kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3] - # remove all failed ones from the list - for name in rejects: - self.kprobes.pop(name) - # set the kprobes all at once - self.fsetVal('', 'kprobe_events') - kprobeevents = '' - for kp in kplist: - kprobeevents += self.kprobeText(kp, self.kprobes[kp]) - self.fsetVal(kprobeevents, 'kprobe_events') - # verify that the kprobes were set as ordered - check = self.fgetVal('kprobe_events') - linesout = len(kprobeevents.split('\n')) - 1 - linesack = len(check.split('\n')) - 1 - if output: - res = '%d/%d' % (linesack, linesout) - if linesack < linesout: - res = self.colorText(res, 31) - else: - res = self.colorText(res, 32) - print(' working kprobe functions enabled: %s' % res) - self.fsetVal('1', 'events/kprobes/enable') - def testKprobe(self, kname, kprobe): - self.fsetVal('0', 'events/kprobes/enable') - kprobeevents = self.kprobeText(kname, kprobe) - if not kprobeevents: - return False - try: - self.fsetVal(kprobeevents, 'kprobe_events') - check = self.fgetVal('kprobe_events') - except: - return False - linesout = len(kprobeevents.split('\n')) - linesack = len(check.split('\n')) - if linesack < linesout: - return False - return True - def fsetVal(self, val, path, mode='w'): - file = self.tpath+path - if not os.path.exists(file): - return False - try: - fp = open(file, mode, 0) - fp.write(val) - fp.flush() - fp.close() - except: - return False - return True - def fgetVal(self, path): - file = self.tpath+path - res = '' - if not os.path.exists(file): - return res - try: - fp = open(file, 'r') - res = fp.read() - fp.close() - except: - pass - return res - def cleanupFtrace(self): - if(self.usecallgraph or self.usetraceevents): - self.fsetVal('0', 'events/kprobes/enable') - self.fsetVal('', 'kprobe_events') - def setupAllKprobes(self): - for name in self.tracefuncs: - self.defaultKprobe(name, self.tracefuncs[name]) - for name in self.dev_tracefuncs: - self.defaultKprobe(name, self.dev_tracefuncs[name]) - def isCallgraphFunc(self, name): - if len(self.tracefuncs) < 1 and self.suspendmode == 'command': - return True - for i in self.tracefuncs: - if 'func' in self.tracefuncs[i]: - f = self.tracefuncs[i]['func'] - else: - f = i - if name == f: - return True - return False - def initFtrace(self, testing=False): - print('INITIALIZING FTRACE...') - # turn trace off - self.fsetVal('0', 'tracing_on') - self.cleanupFtrace() - # set the trace clock to global - self.fsetVal('global', 'trace_clock') - self.fsetVal('nop', 'current_tracer') - # set trace buffer to a huge value - if self.usecallgraph or self.usedevsrc: - tgtsize = min(self.memtotal / 2, 2*1024*1024) - maxbuf = '%d' % (tgtsize / max(1, self.cpucount)) - if self.cpucount < 1 or not self.fsetVal(maxbuf, 'buffer_size_kb'): - self.fsetVal('131072', 'buffer_size_kb') - else: - self.fsetVal('16384', 'buffer_size_kb') - # go no further if this is just a status check - if testing: - return - # initialize the callgraph trace - if(self.usecallgraph): - # set trace type - self.fsetVal('function_graph', 'current_tracer') - self.fsetVal('', 'set_ftrace_filter') - # set trace format options - self.fsetVal('print-parent', 'trace_options') - self.fsetVal('funcgraph-abstime', 'trace_options') - self.fsetVal('funcgraph-cpu', 'trace_options') - self.fsetVal('funcgraph-duration', 'trace_options') - self.fsetVal('funcgraph-proc', 'trace_options') - self.fsetVal('funcgraph-tail', 'trace_options') - self.fsetVal('nofuncgraph-overhead', 'trace_options') - self.fsetVal('context-info', 'trace_options') - self.fsetVal('graph-time', 'trace_options') - self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth') - cf = ['dpm_run_callback'] - if(self.usetraceeventsonly): - cf += ['dpm_prepare', 'dpm_complete'] - for fn in self.tracefuncs: - if 'func' in self.tracefuncs[fn]: - cf.append(self.tracefuncs[fn]['func']) - else: - cf.append(fn) - self.setFtraceFilterFunctions(cf) - # initialize the kprobe trace - elif self.usekprobes: - for name in self.tracefuncs: - self.defaultKprobe(name, self.tracefuncs[name]) - if self.usedevsrc: - for name in self.dev_tracefuncs: - self.defaultKprobe(name, self.dev_tracefuncs[name]) - print('INITIALIZING KPROBES...') - self.addKprobes(self.verbose) - if(self.usetraceevents): - # turn trace events on - events = iter(self.traceevents) - for e in events: - self.fsetVal('1', 'events/power/'+e+'/enable') - # clear the trace buffer - self.fsetVal('', 'trace') - def verifyFtrace(self): - # files needed for any trace data - files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock', - 'trace_marker', 'trace_options', 'tracing_on'] - # files needed for callgraph trace data - tp = self.tpath - if(self.usecallgraph): - files += [ - 'available_filter_functions', - 'set_ftrace_filter', - 'set_graph_function' - ] - for f in files: - if(os.path.exists(tp+f) == False): - return False - return True - def verifyKprobes(self): - # files needed for kprobes to work - files = ['kprobe_events', 'events'] - tp = self.tpath - for f in files: - if(os.path.exists(tp+f) == False): - return False - return True - def colorText(self, str, color=31): - if not self.ansi: - return str - return '\x1B[%d;40m%s\x1B[m' % (color, str) - def writeDatafileHeader(self, filename, fwdata=[]): - fp = open(filename, 'w') - fp.write(self.teststamp+'\n') - fp.write(self.sysstamp+'\n') - if(self.suspendmode == 'mem' or self.suspendmode == 'command'): - for fw in fwdata: - if(fw): - fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1])) - fp.close() - -sysvals = SystemValues() -suspendmodename = { - 'freeze': 'Freeze (S0)', - 'standby': 'Standby (S1)', - 'mem': 'Suspend (S3)', - 'disk': 'Hibernate (S4)' -} - -# Class: DevProps -# Description: -# Simple class which holds property values collected -# for all the devices used in the timeline. -class DevProps: - syspath = '' - altname = '' - async = True - xtraclass = '' - xtrainfo = '' - def out(self, dev): - return '%s,%s,%d;' % (dev, self.altname, self.async) - def debug(self, dev): - print '%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.async) - def altName(self, dev): - if not self.altname or self.altname == dev: - return dev - return '%s [%s]' % (self.altname, dev) - def xtraClass(self): - if self.xtraclass: - return ' '+self.xtraclass - if not self.async: - return ' sync' - return '' - def xtraInfo(self): - if self.xtraclass: - return ' '+self.xtraclass - if self.async: - return ' async_device' - return ' sync_device' - -# Class: DeviceNode -# Description: -# 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 = [] - self.depth = nodedepth - -# Class: Data -# Description: -# The primary container for suspend/resume test data. There is one for -# each test run. The data is organized into a cronological hierarchy: -# Data.dmesg { -# phases { -# 10 sequential, non-overlapping phases of S/R -# contents: times for phase start/end, order/color data for html -# devlist { -# device callback or action list for this phase -# device { -# a single device callback or generic action -# contents: start/stop times, pid/cpu/driver info -# parents/children, html id for timeline/callgraph -# optionally includes an ftrace callgraph -# optionally includes dev/ps data -# } -# } -# } -# } -# -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 - def __init__(self, num): - idchar = 'abcdefghij' - self.pstl = dict() - 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.devicegroups = [] - for phase in self.phases: - self.devicegroups.append([phase]) - self.errorinfo = {'suspend':[],'resume':[]} - def extractErrorInfo(self, dmesg): - error = '' - tm = 0.0 - for i in range(len(dmesg)): - if 'Call Trace:' in dmesg[i]: - m = re.match('[ \t]*(\[ *)(?P[0-9\.]*)(\]) .*', dmesg[i]) - if not m