From 010a522cf2d9a01612b8a95abd787e70ae123ea6 Mon Sep 17 00:00:00 2001 From: Doug Smythies Date: Mon, 17 Apr 2017 17:12:13 -0700 Subject: tools/power/x86/intel_pstate_tracer: Adjust directory ownership The intel_pstate_tracer.py script only needs to be run as root when it is also used to actually acquire the trace data that it will post process. Otherwise it is generally preferable that it be run as a regular user. If run the first time as root the results directory will be incorrect for any subsequent run as a regular user. For any run as root the specific testname subdirectory will not allow any subsequent file saves by a regular user. Typically, and for example, the regular user might be attempting to save a .csv file converted to a spreadsheet with added calculations or graphs. Set the directories and files owner and groups IDs to be the regular user, if required. Signed-off-by: Doug Smythies Acked-by: Srinivas Pandruvada Signed-off-by: Rafael J. Wysocki --- .../x86/intel_pstate_tracer/intel_pstate_tracer.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py index fd706ac0f347..0b24dd9d01ff 100755 --- a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py +++ b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py @@ -353,6 +353,14 @@ def split_csv(): os.system('grep -m 1 common_cpu cpu.csv > cpu{:0>3}.csv'.format(index)) os.system('grep CPU_{:0>3} cpu.csv >> cpu{:0>3}.csv'.format(index, index)) +def fix_ownership(path): + """Change the owner of the file to SUDO_UID, if required""" + + uid = os.environ.get('SUDO_UID') + gid = os.environ.get('SUDO_GID') + if uid is not None: + os.chown(path, int(uid), int(gid)) + def cleanup_data_files(): """ clean up existing data files """ @@ -518,12 +526,16 @@ else: if not os.path.exists('results'): os.mkdir('results') + # The regular user needs to own the directory, not root. + fix_ownership('results') os.chdir('results') if os.path.exists(testname): print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.') sys.exit() os.mkdir(testname) +# The regular user needs to own the directory, not root. +fix_ownership(testname) os.chdir(testname) # Temporary (or perhaps not) @@ -566,4 +578,9 @@ plot_scaled_cpu() plot_boost_cpu() plot_ghz_cpu() +# It is preferrable, but not necessary, that the regular user owns the files, not root. +for root, dirs, files in os.walk('.'): + for f in files: + fix_ownership(f) + os.chdir('../../') -- cgit v1.2.3 From bc167c7de8886f08b3d8266b176eefaa9f22cd80 Mon Sep 17 00:00:00 2001 From: Todd E Brandt Date: Fri, 7 Apr 2017 11:05:35 -0700 Subject: tools: power: pm-graph: AnalyzeSuspend v4.6 Moved from scripts into tools, and updated from 4.5 to 4.6 - Changed the tool title to SleepGraph - Reformatted the code so analyze_suspend can be used as a library - Reorganized all html/js/css handling code to be used by other tools - upgraded the -summary feature to work faster with better readability Signed-off-by: Todd Brandt Signed-off-by: Rafael J. Wysocki --- scripts/analyze_suspend.py | 5235 ------------------------------ tools/power/pm-graph/analyze_suspend.py | 5309 +++++++++++++++++++++++++++++++ 2 files changed, 5309 insertions(+), 5235 deletions(-) delete mode 100755 scripts/analyze_suspend.py create mode 100755 tools/power/pm-graph/analyze_suspend.py diff --git a/scripts/analyze_suspend.py b/scripts/analyze_suspend.py deleted file mode 100755 index 20cdb2bc1dae..000000000000 --- a/scripts/analyze_suspend.py +++ /dev/null @@ -1,5235 +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. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. -# -# Authors: -# Todd Brandt -# -# Links: -# Home Page -# https://01.org/suspendresume -# Source repo -# https://github.com/01org/suspendresume -# -# 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: - ansi = False - version = '4.5' - verbose = False - addlogs = False - mindevlen = 0.0 - mincglen = 0.0 - cgphase = '' - cgtest = -1 - callloopmaxgap = 0.0001 - callloopmaxlen = 0.005 - srgap = 0 - cgexp = False - outdir = '' - 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' - suspendmode = 'mem' - hostname = 'localhost' - prefix = 'test' - teststamp = '' - dmesgstart = 0.0 - dmesgfile = '' - ftracefile = '' - htmlfile = '' - embedded = False - rtcwake = False - rtcwaketime = 10 - 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]*)$' - stampfmt = '# suspend-(?P[0-9]{2})(?P[0-9]{2})(?P[0-9]{2})-'+\ - '(?P[0-9]{2})(?P[0-9]{2})(?P[0-9]{2})'+\ - ' (?P.*) (?P.*) (?P.*)$' - 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.addlogs = 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 - 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 - self.outdir = value.format(**args) - def setOutputFile(self): - if((self.htmlfile == '') and (self.dmesgfile != '')): - m = re.match('(?P.*)_dmesg\.txt$', self.dmesgfile) - if(m): - self.htmlfile = m.group('name')+'.html' - if((self.htmlfile == '') and (self.ftracefile != '')): - m = re.match('(?P.*)_ftrace\.txt$', self.ftracefile) - if(m): - self.htmlfile = m.group('name')+'.html' - if(self.htmlfile == ''): - self.htmlfile = 'output.html' - def initTestOutput(self, subdir, testpath=''): - self.prefix = self.hostname - v = open('/proc/version', 'r').read().strip() - kver = string.split(v)[2] - n = datetime.now() - testtime = n.strftime('suspend-%m%d%y-%H%M%S') - if not testpath: - testpath = n.strftime('suspend-%y%m%d-%H%M%S') - if(subdir != "."): - self.testdir = subdir+"/"+testpath - else: - self.testdir = testpath - 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): - 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(sysvals.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: - pass - 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') - # set trace buffer to a huge value - self.fsetVal('nop', 'current_tracer') - self.fsetVal('100000', '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('0', '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) - -sysvals = SystemValues() - -# 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: - continue - tm = float(m.group('ktime')) - if tm < self.start or tm > self.end: - continue - for j in range(i-10, i+1): - error += dmesg[j] - continue - if error: - m = re.match('[ \t]*\[ *[0-9\.]*\] \[\<[0-9a-fA-F]*\>\] .*', dmesg[i]) - if m: - error += dmesg[i] - else: - if tm < self.tSuspended: - dir = 'suspend' - else: - dir = 'resume' - error = error.replace('<', '<').replace('>', '>') - vprint('kernel error found in %s at %f' % (dir, tm)) - self.errorinfo[dir].append((tm, error)) - self.kerror = True - error = '' - def setStart(self, time): - self.start = time - def setEnd(self, time): - self.end = time - def isTraceEventOutsideDeviceCalls(self, pid, time): - for phase in self.phases: - list = self.dmesg[phase]['list'] - for dev in list: - d = list[dev] - if(d['pid'] == pid and time >= d['start'] and - time < d['end']): - return False - return True - def sourcePhase(self, start): - for phase in self.phases: - pend = self.dmesg[phase]['end'] - if start <= pend: - return phase - return 'resume_complete' - def sourceDevice(self, phaselist, start, end, pid, type): - tgtdev = '' - for phase in phaselist: - list = self.dmesg[phase]['list'] - for devname in list: - dev = list[devname] - # pid must match - if dev['pid'] != pid: - continue - devS = dev['start'] - devE = dev['end'] - if type == 'device': - # device target event is entirely inside the source boundary - if(start < devS or start >= devE or end <= devS or end > devE): - continue - elif type == 'thread': - # thread target event will expand the source boundary - if start < devS: - dev['start'] = start - if end > devE: - dev['end'] = end - tgtdev = dev - break - return tgtdev - def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata): - # try to place the call in a device - tgtdev = self.sourceDevice(self.phases, start, end, pid, 'device') - # calls with device pids that occur outside device bounds are dropped - # TODO: include these somehow - if not tgtdev and pid in self.devpids: - return False - # try to place the call in a thread - if not tgtdev: - tgtdev = self.sourceDevice(self.phases, start, end, pid, 'thread') - # create new thread blocks, expand as new calls are found - if not tgtdev: - if proc == '<...>': - threadname = 'kthread-%d' % (pid) - else: - threadname = '%s-%d' % (proc, pid) - tgtphase = self.sourcePhase(start) - self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '') - return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata) - # this should not happen - if not tgtdev: - vprint('[%f - %f] %s-%d %s %s %s' % \ - (start, end, proc, pid, kprobename, cdata, rdata)) - return False - # place the call data inside the src element of the tgtdev - if('src' not in tgtdev): - tgtdev['src'] = [] - dtf = sysvals.dev_tracefuncs - ubiquitous = False - if kprobename in dtf and 'ub' in dtf[kprobename]: - ubiquitous = True - title = cdata+' '+rdata - mstr = '\(.*\) *(?P.*) *\((?P.*)\+.* arg1=(?P.*)' - m = re.match(mstr, title) - if m: - c = m.group('caller') - a = m.group('args').strip() - r = m.group('ret') - if len(r) > 6: - r = '' - else: - r = 'ret=%s ' % r - if ubiquitous and c in dtf and 'ub' in dtf[c]: - return False - color = sysvals.kprobeColor(kprobename) - e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color) - tgtdev['src'].append(e) - return True - def overflowDevices(self): - # get a list of devices that extend beyond the end of this test run - devlist = [] - for phase in self.phases: - list = self.dmesg[phase]['list'] - for devname in list: - dev = list[devname] - if dev['end'] > self.end: - devlist.append(dev) - return devlist - def mergeOverlapDevices(self, devlist): - # merge any devices that overlap devlist - for dev in devlist: - devname = dev['name'] - for phase in self.phases: - list = self.dmesg[phase]['list'] - if devname not in list: - continue - tdev = list[devname] - o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start']) - if o <= 0: - continue - dev['end'] = tdev['end'] - if 'src' not in dev or 'src' not in tdev: - continue - dev['src'] += tdev['src'] - del list[devname] - def usurpTouchingThread(self, name, dev): - # the caller test has priority of this thread, give it to him - for phase in self.phases: - list = self.dmesg[phase]['list'] - if name in list: - tdev = list[name] - if tdev['start'] - dev['end'] < 0.1: - dev['end'] = tdev['end'] - if 'src' not in dev: - dev['src'] = [] - if 'src' in tdev: - dev['src'] += tdev['src'] - del list[name] - break - def stitchTouchingThreads(self, testlist): - # merge any threads between tests that touch - for phase in self.phases: - list = self.dmesg[phase]['list'] - for devname in list: - dev = list[devname] - if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']: - continue - for data in testlist: - data.usurpTouchingThread(devname, dev) - def optimizeDevSrc(self): - # merge any src call loops to reduce timeline size - for phase in self.phases: - list = self.dmesg[phase]['list'] - for dev in list: - if 'src' not in list[dev]: - continue - src = list[dev]['src'] - p = 0 - for e in sorted(src, key=lambda event: event.time): - if not p or not e.repeat(p): - p = e - continue - # e is another iteration of p, move it into p - p.end = e.end - p.length = p.end - p.time - p.count += 1 - src.remove(e) - def trimTimeVal(self, t, t0, dT, left): - if left: - if(t > t0): - if(t - dT < t0): - return t0 - return t - dT - else: - return t - else: - if(t < t0 + dT): - if(t > t0): - return t0 + dT - return t + dT - else: - return t - def trimTime(self, t0, dT, left): - self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left) - self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left) - self.start = self.trimTimeVal(self.start, t0, dT, left) - self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left) - self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left) - self.end = self.trimTimeVal(self.end, t0, dT, left) - for phase in self.phases: - p = self.dmesg[phase] - p['start'] = self.trimTimeVal(p['start'], t0, dT, left) - p['end'] = self.trimTimeVal(p['end'], t0, dT, left) - list = p['list'] - for name in list: - d = list[name] - d['start'] = self.trimTimeVal(d['start'], t0, dT, left) - d['end'] = self.trimTimeVal(d['end'], t0, dT, left) - if('ftrace' in d): - cg = d['ftrace'] - cg.start = self.trimTimeVal(cg.start, t0, dT, left) - cg.end = self.trimTimeVal(cg.end, t0, dT, left) - for line in cg.list: - line.time = self.trimTimeVal(line.time, t0, dT, left) - if('src' in d): - for e in d['src']: - e.time = self.trimTimeVal(e.time, t0, dT, left) - def normalizeTime(self, tZero): - # trim out any standby or freeze clock time - if(self.tSuspended != self.tResumed): - if(self.tResumed > tZero): - self.trimTime(self.tSuspended, \ - self.tResumed-self.tSuspended, True) - else: - self.trimTime(self.tSuspended, \ - self.tResumed-self.tSuspended, False) - def setPhase(self, phase, ktime, isbegin): - if(isbegin): - self.dmesg[phase]['start'] = ktime - else: - self.dmesg[phase]['end'] = ktime - def dmesgSortVal(self, phase): - return self.dmesg[phase]['order'] - def sortedPhases(self): - return sorted(self.dmesg, key=self.dmesgSortVal) - def sortedDevices(self, phase): - list = self.dmesg[phase]['list'] - slist = [] - tmp = dict() - for devname in list: - dev = list[devname] - tmp[dev['start']] = devname - for t in sorted(tmp): - slist.append(tmp[t]) - return slist - def fixupInitcalls(self, phase): - # if any calls never returned, clip them at system resume end - phaselist = self.dmesg[phase]['list'] - for devname in phaselist: - dev = phaselist[devname] - if(dev['end'] < 0): - for p in self.phases: - if self.dmesg[p]['end'] > dev['start']: - dev['end'] = self.dmesg[p]['end'] - break - vprint('%s (%s): callback didnt return' % (devname, phase)) - def deviceFilter(self, devicefilter): - for phase in self.phases: - list = self.dmesg[phase]['list'] - rmlist = [] - for name in list: - keep = False - for filter in devicefilter: - if filter in name or \ - ('drv' in list[name] and filter in list[name]['drv']): - keep = True - if not keep: - rmlist.append(name) - for name in rmlist: - del list[name] - def fixupInitcallsThatDidntReturn(self): - # if any calls never returned, clip them at system resume end - for phase in self.phases: - self.fixupInitcalls(phase) - def phaseOverlap(self, phases): - rmgroups = [] - newgroup = [] - for group in self.devicegroups: - for phase in phases: - if phase not in group: - continue - for p in group: - if p not in newgroup: - newgroup.append(p) - if group not in rmgroups: - rmgroups.append(group) - for group in rmgroups: - self.devicegroups.remove(group) - self.devicegroups.append(newgroup) - def newActionGlobal(self, name, start, end, pid=-1, color=''): - # which phase is this device callback or action in - targetphase = 'none' - htmlclass = '' - overlap = 0.0 - phases = [] - for phase in self.phases: - pstart = self.dmesg[phase]['start'] - pend = self.dmesg[phase]['end'] - # see if the action overlaps this phase - o = max(0, min(end, pend) - max(start, pstart)) - if o > 0: - phases.append(phase) - # set the target phase to the one that overlaps most - if o > overlap: - if overlap > 0 and phase == 'post_resume': - continue - targetphase = phase - overlap = o - # if no target phase was found, pin it to the edge - if targetphase == 'none': - p0start = self.dmesg[self.phases[0]]['start'] - if start <= p0start: - targetphase = self.phases[0] - else: - targetphase = self.phases[-1] - if pid == -2: - htmlclass = ' bg' - elif pid == -3: - htmlclass = ' ps' - if len(phases) > 1: - htmlclass = ' bg' - self.phaseOverlap(phases) - if targetphase in self.phases: - newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color) - return (targetphase, newname) - return False - def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''): - # 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 - if pid == -2: - 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, - 'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv } - if htmlclass: - list[name]['htmlclass'] = htmlclass - if color: - list[name]['color'] = color - return name - def deviceChildren(self, devname, phase): - devlist = [] - list = self.dmesg[phase]['list'] - for child in list: - if(list[child]['par'] == devname): - devlist.append(child) - return devlist - def printDetails(self): - vprint('Timeline Details:') - vprint(' test start: %f' % self.start) - vprint('kernel suspend start: %f' % self.tKernSus) - for phase in self.phases: - dc = len(self.dmesg[phase]['list']) - vprint(' %16s: %f - %f (%d devices)' % (phase, \ - self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc)) - vprint(' kernel resume end: %f' % self.tKernRes) - vprint(' test end: %f' % self.end) - def deviceChildrenAllPhases(self, devname): - devlist = [] - for phase in self.phases: - list = self.deviceChildren(devname, phase) - for dev in list: - if dev not in devlist: - devlist.append(dev) - return devlist - def masterTopology(self, name, list, depth): - node = DeviceNode(name, depth) - for cname in list: - # avoid recursions - if name == cname: - continue - clist = self.deviceChildrenAllPhases(cname) - cnode = self.masterTopology(cname, clist, depth+1) - node.children.append(cnode) - return node - def printTopology(self, node): - html = '' - if node.name: - info = '' - drv = '' - for phase in self.phases: - list = self.dmesg[phase]['list'] - if node.name in list: - s = list[node.name]['start'] - e = list[node.name]['end'] - if list[node.name]['drv']: - drv = ' {'+list[node.name]['drv']+'}' - info += ('
  • %s: %.3fms
  • ' % (phase, (e-s)*1000)) - html += '
  • '+node.name+drv+'' - if info: - html += '
      '+info+'
    ' - html += '
  • ' - if len(node.children) > 0: - html += '
      ' - for cnode in node.children: - html += self.printTopology(cnode) - html += '
    ' - return html - def rootDeviceList(self): - # list of devices graphed - real = [] - for phase in self.dmesg: - list = self.dmesg[phase]['list'] - for dev in list: - if list[dev]['pid'] >= 0 and dev not in real: - real.append(dev) - # list of top-most root devices - rootlist = [] - for phase in self.dmesg: - list = self.dmesg[phase]['list'] - for dev in list: - pdev = list[dev]['par'] - pid = list[dev]['pid'] - if(pid < 0 or re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)): - continue - if pdev and pdev not in real and pdev not in rootlist: - rootlist.append(pdev) - return rootlist - def deviceTopology(self): - rootlist = self.rootDeviceList() - master = self.masterTopology('', rootlist, 0) - return self.printTopology(master) - def selectTimelineDevices(self, widfmt, tTotal, mindevlen): - # only select devices that will actually show up in html - self.tdevlist = dict() - for phase in self.dmesg: - devlist = [] - list = self.dmesg[phase]['list'] - for dev in list: - length = (list[dev]['end'] - list[dev]['start']) * 1000 - width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal) - if width != '0.000000' and length >= mindevlen: - devlist.append(dev) - self.tdevlist[phase] = devlist - def addHorizontalDivider(self, devname, devend): - phase = 'suspend_prepare' - self.newAction(phase, devname, -2, '', \ - self.start, devend, '', ' sec', '') - if phase not in self.tdevlist: - self.tdevlist[phase] = [] - self.tdevlist[phase].append(devname) - d = DevItem(0, phase, self.dmesg[phase]['list'][devname]) - return d - def addProcessUsageEvent(self, name, times): - # get the start and end times for this process - maxC = 0 - tlast = 0 - start = -1 - end = -1 - for t in sorted(times): - if tlast == 0: - tlast = t - continue - if name in self.pstl[t]: - if start == -1 or tlast < start: - start = tlast - if end == -1 or t > end: - end = t - tlast = t - if start == -1 or end == -1: - return 0 - # add a new action for this process and get the object - out = self.newActionGlobal(name, start, end, -3) - if not out: - return 0 - phase, devname = out - dev = self.dmesg[phase]['list'][devname] - # get the cpu exec data - tlast = 0 - clast = 0 - cpuexec = dict() - for t in sorted(times): - if tlast == 0 or t <= start or t > end: - tlast = t - continue - list = self.pstl[t] - c = 0 - if name in list: - c = list[name] - if c > maxC: - maxC = c - if c != clast: - key = (tlast, t) - cpuexec[key] = c - tlast = t - clast = c - dev['cpuexec'] = cpuexec - return maxC - def createProcessUsageEvents(self): - # get an array of process names - proclist = [] - for t in self.pstl: - pslist = self.pstl[t] - for ps in pslist: - if ps not in proclist: - proclist.append(ps) - # get a list of data points for suspend and resume - tsus = [] - tres = [] - for t in sorted(self.pstl): - if t < self.tSuspended: - tsus.append(t) - else: - tres.append(t) - # process the events for suspend and resume - if len(proclist) > 0: - vprint('Process Execution:') - for ps in proclist: - c = self.addProcessUsageEvent(ps, tsus) - if c > 0: - vprint('%25s (sus): %d' % (ps, c)) - c = self.addProcessUsageEvent(ps, tres) - if c > 0: - vprint('%25s (res): %d' % (ps, c)) - -# Class: DevFunction -# Description: -# A container for kprobe function data we want in the dev timeline -class DevFunction: - row = 0 - count = 1 - def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color): - self.name = name - self.args = args - self.caller = caller - self.ret = ret - self.time = start - self.length = end - start - self.end = end - self.ubiquitous = u - self.proc = proc - self.pid = pid - self.color = color - def title(self): - cnt = '' - if self.count > 1: - cnt = '(x%d)' % self.count - l = '%0.3fms' % (self.length * 1000) - if self.ubiquitous: - title = '%s(%s)%s <- %s, %s(%s)' % \ - (self.name, self.args, cnt, self.caller, self.ret, l) - else: - title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l) - return title.replace('"', '') - def text(self): - if self.count > 1: - text = '%s(x%d)' % (self.name, self.count) - else: - text = self.name - return text - def repeat(self, tgt): - # is the tgt call just a repeat of this call (e.g. are we in a loop) - dt = self.time - tgt.end - # only combine calls if -all- attributes are identical - if tgt.caller == self.caller and \ - tgt.name == self.name and tgt.args == self.args and \ - tgt.proc == self.proc and tgt.pid == self.pid and \ - tgt.ret == self.ret and dt >= 0 and \ - dt <= sysvals.callloopmaxgap and \ - self.length < sysvals.callloopmaxlen: - return True - return False - -# Class: FTraceLine -# Description: -# A container for a single line of ftrace data. There are six basic types: -# callgraph line: -# call: " dpm_run_callback() {" -# return: " }" -# leaf: " dpm_run_callback();" -# trace event: -# tracing_mark_write: SUSPEND START or RESUME COMPLETE -# suspend_resume: phase or custom exec block data -# device_pm_callback: device callback info -class FTraceLine: - time = 0.0 - length = 0.0 - fcall = False - freturn = False - fevent = False - fkprobe = False - depth = 0 - name = '' - type = '' - def __init__(self, t, m='', d=''): - self.time = float(t) - if not m and not d: - return - # is this a trace event - if(d == 'traceevent' or re.match('^ *\/\* *(?P.*) \*\/ *$', m)): - if(d == 'traceevent'): - # nop format trace event - msg = m - else: - # function_graph format trace event - em = re.match('^ *\/\* *(?P.*) \*\/ *$', m) - msg = em.group('msg') - - emm = re.match('^(?P.*?): (?P.*)', msg) - if(emm): - self.name = emm.group('msg') - self.type = emm.group('call') - else: - self.name = msg - km = re.match('^(?P.*)_cal$', self.type) - if km: - self.fcall = True - self.fkprobe = True - self.type = km.group('n') - return - km = re.match('^(?P.*)_ret$', self.type) - if km: - self.freturn = True - self.fkprobe = True - self.type = km.group('n') - return - self.fevent = True - return - # convert the duration to seconds - if(d): - self.length = float(d)/1000000 - # the indentation determines the depth - match = re.match('^(?P *)(?P.*)$', m) - if(not match): - return - self.depth = self.getDepth(match.group('d')) - m = match.group('o') - # function return - if(m[0] == '}'): - self.freturn = True - if(len(m) > 1): - # includes comment with function name - match = re.match('^} *\/\* *(?P.*) *\*\/$', m) - if(match): - self.name = match.group('n').strip() - # function call - else: - self.fcall = True - # function call with children - if(m[-1] == '{'): - match = re.match('^(?P.*) *\(.*', m) - if(match): - self.name = match.group('n').strip() - # function call with no children (leaf) - elif(m[-1] == ';'): - self.freturn = True - match = re.match('^(?P.*) *\(.*', m) - if(match): - self.name = match.group('n').strip() - # something else (possibly a trace marker) - else: - self.name = m - def getDepth(self, str): - return len(str)/2 - def debugPrint(self, dev=''): - if(self.freturn and self.fcall): - print('%s -- %f (%02d): %s(); (%.3f us)' % (dev, self.time, \ - self.depth, self.name, self.length*1000000)) - elif(self.freturn): - print('%s -- %f (%02d): %s} (%.3f us)' % (dev, self.time, \ - self.depth, self.name, self.length*1000000)) - else: - print('%s -- %f (%02d): %s() { (%.3f us)' % (dev, self.time, \ - self.depth, self.name, self.length*1000000)) - def startMarker(self): - # Is this the starting line of a suspend? - if not self.fevent: - return False - if sysvals.usetracemarkers: - if(self.name == 'SUSPEND START'): - return True - return False - else: - if(self.type == 'suspend_resume' and - re.match('suspend_enter\[.*\] begin', self.name)): - return True - return False - def endMarker(self): - # Is this the ending line of a resume? - if not self.fevent: - return False - if sysvals.usetracemarkers: - if(self.name == 'RESUME COMPLETE'): - return True - return False - else: - if(self.type == 'suspend_resume' and - re.match('thaw_processes\[.*\] end', self.name)): - return True - return False - -# Class: FTraceCallGraph -# Description: -# A container for the ftrace callgraph of a single recursive function. -# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph -# Each instance is tied to a single device in a single phase, and is -# comprised of an ordered list of FTraceLine objects -class FTraceCallGraph: - start = -1.0 - end = -1.0 - list = [] - invalid = False - depth = 0 - pid = 0 - def __init__(self, pid): - self.start = -1.0 - self.end = -1.0 - self.list = [] - self.depth = 0 - self.pid = pid - def addLine(self, line, debug=False): - # if this is already invalid, just leave - if(self.invalid): - return False - # invalidate on too much data or bad depth - if(len(self.list) >= 1000000 or self.depth < 0): - self.invalidate(line) - return False - # compare current depth with this lines pre-call depth - prelinedep = line.depth - if(line.freturn and not line.fcall): - prelinedep += 1 - last = 0 - lasttime = line.time - virtualfname = 'execution_misalignment' - if len(self.list) > 0: - last = self.list[-1] - lasttime = last.time - # handle low misalignments by inserting returns - if prelinedep < self.depth: - if debug and last: - print '-------- task %d --------' % self.pid - last.debugPrint() - idx = 0 - # add return calls to get the depth down - while prelinedep < self.depth: - if debug: - print 'MISALIGN LOW (add returns): C%d - eC%d' % (self.depth, prelinedep) - self.depth -= 1 - if idx == 0 and last and last.fcall and not last.freturn: - # special case, turn last call into a leaf - last.depth = self.depth - last.freturn = True - last.length = line.time - last.time - if debug: - last.debugPrint() - else: - vline = FTraceLine(lasttime) - vline.depth = self.depth - vline.name = virtualfname - vline.freturn = True - self.list.append(vline) - if debug: - vline.debugPrint() - idx += 1 - if debug: - line.debugPrint() - print '' - # handle high misalignments by inserting calls - elif prelinedep > self.depth: - if debug and last: - print '-------- task %d --------' % self.pid - last.debugPrint() - idx = 0 - # add calls to get the depth up - while prelinedep > self.depth: - if debug: - print 'MISALIGN HIGH (add calls): C%d - eC%d' % (self.depth, prelinedep) - if idx == 0 and line.freturn and not line.fcall: - # special case, turn this return into a leaf - line.fcall = True - prelinedep -= 1 - else: - vline = FTraceLine(lasttime) - vline.depth = self.depth - vline.name = virtualfname - vline.fcall = True - if debug: - vline.debugPrint() - self.list.append(vline) - self.depth += 1 - if not last: - self.start = vline.time - idx += 1 - if debug: - line.debugPrint() - print '' - # process the call and set the new depth - if(line.fcall and not line.freturn): - self.depth += 1 - elif(line.freturn and not line.fcall): - self.depth -= 1 - if len(self.list) < 1: - self.start = line.time - self.list.append(line) - if(line.depth == 0 and line.freturn): - if(self.start < 0): - self.start = line.time - self.end = line.time - if line.fcall: - self.end += line.length - if self.list[0].name == virtualfname: - self.invalid = True - return True - return False - def invalidate(self, line): - if(len(self.list) > 0): - first = self.list[0] - self.list = [] - self.list.append(first) - self.invalid = True - id = 'task %s' % (self.pid) - window = '(%f - %f)' % (self.start, line.time) - if(self.depth < 0): - vprint('Too much data for '+id+\ - ' (buffer overflow), ignoring this callback') - else: - vprint('Too much data for '+id+\ - ' '+window+', ignoring this callback') - def slice(self, t0, tN): - minicg = FTraceCallGraph(0) - count = -1 - firstdepth = 0 - for l in self.list: - if(l.time < t0 or l.time > tN): - continue - if(count < 0): - if(not l.fcall or l.name == 'dev_driver_string'): - continue - firstdepth = l.depth - count = 0 - l.depth -= firstdepth - minicg.addLine(l) - if((count == 0 and l.freturn and l.fcall) or - (count > 0 and l.depth <= 0)): - break - count += 1 - return minicg - def repair(self, enddepth): - # bring the depth back to 0 with additional returns - fixed = False - last = self.list[-1] - for i in reversed(range(enddepth)): - t = FTraceLine(last.time) - t.depth = i - t.freturn = True - fixed = self.addLine(t) - if fixed: - self.end = last.time - return True - return False - def postProcess(self, debug=False): - stack = dict() - cnt = 0 - for l in self.list: - if(l.fcall and not l.freturn): - stack[l.depth] = l - cnt += 1 - elif(l.freturn and not l.fcall): - if(l.depth not in stack): - if debug: - print 'Post Process Error: Depth missing' - l.debugPrint() - return False - # transfer total time from return line to call line - stack[l.depth].length = l.length - stack.pop(l.depth) - l.length = 0 - cnt -= 1 - if(cnt == 0): - # trace caught the whole call tree - return True - elif(cnt < 0): - if debug: - print 'Post Process Error: Depth is less than 0' - return False - # trace ended before call tree finished - return self.repair(cnt) - def deviceMatch(self, pid, data): - found = False - # add the callgraph data to the device hierarchy - borderphase = { - 'dpm_prepare': 'suspend_prepare', - 'dpm_complete': 'resume_complete' - } - if(self.list[0].name in borderphase): - p = borderphase[self.list[0].name] - list = data.dmesg[p]['list'] - for devname in list: - dev = list[devname] - if(pid == dev['pid'] and - self.start <= dev['start'] and - self.end >= dev['end']): - dev['ftrace'] = self.slice(dev['start'], dev['end']) - found = True - return found - for p in data.phases: - if(data.dmesg[p]['start'] <= self.start and - self.start <= data.dmesg[p]['end']): - list = data.dmesg[p]['list'] - for devname in list: - dev = list[devname] - if(pid == dev['pid'] and - self.start <= dev['start'] and - self.end >= dev['end']): - dev['ftrace'] = self - found = True - break - break - return found - def newActionFromFunction(self, data): - name = self.list[0].name - if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']: - return - fs = self.start - fe = self.end - if fs < data.start or fe > data.end: - return - phase = '' - for p in data.phases: - if(data.dmesg[p]['start'] <= self.start and - self.start < data.dmesg[p]['end']): - phase = p - break - if not phase: - return - out = data.newActionGlobal(name, fs, fe, -2) - if out: - phase, myname = out - data.dmesg[phase]['list'][myname]['ftrace'] = self - def debugPrint(self): - print('[%f - %f] %s (%d)') % (self.start, self.end, self.list[0].name, self.pid) - for l in self.list: - if(l.freturn and l.fcall): - print('%f (%02d): %s(); (%.3f us)' % (l.time, \ - l.depth, l.name, l.length*1000000)) - elif(l.freturn): - print('%f (%02d): %s} (%.3f us)' % (l.time, \ - l.depth, l.name, l.length*1000000)) - else: - print('%f (%02d): %s() { (%.3f us)' % (l.time, \ - l.depth, l.name, l.length*1000000)) - print(' ') - -class DevItem: - def __init__(self, test, phase, dev): - self.test = test - self.phase = phase - self.dev = dev - def isa(self, cls): - if 'htmlclass' in self.dev and cls in self.dev['htmlclass']: - return True - return False - -# Class: Timeline -# Description: -# A container for a device timeline which calculates -# all the html properties to display it correctly -class Timeline: - html = {} - height = 0 # total timeline height - scaleH = 20 # timescale (top) row height - rowH = 30 # device row height - bodyH = 0 # body height - rows = 0 # total timeline rows - rowlines = dict() - rowheight = dict() - def __init__(self, rowheight, scaleheight): - self.rowH = rowheight - self.scaleH = scaleheight - self.html = { - 'header': '', - 'timeline': '', - 'legend': '', - } - # Function: getDeviceRows - # Description: - # determine how may rows the device funcs will take - # Arguments: - # rawlist: the list of devices/actions for a single phase - # Output: - # The total number of rows needed to display this phase of the timeline - def getDeviceRows(self, rawlist): - # clear all rows and set them to undefined - sortdict = dict() - for item in rawlist: - item.row = -1 - sortdict[item] = item.length - sortlist = sorted(sortdict, key=sortdict.get, reverse=True) - remaining = len(sortlist) - rowdata = dict() - row = 1 - # try to pack each row with as many ranges as possible - while(remaining > 0): - if(row not in rowdata): - rowdata[row] = [] - for i in sortlist: - if(i.row >= 0): - continue - s = i.time - e = i.time + i.length - valid = True - for ritem in rowdata[row]: - rs = ritem.time - re = ritem.time + ritem.length - if(not (((s <= rs) and (e <= rs)) or - ((s >= re) and (e >= re)))): - valid = False - break - if(valid): - rowdata[row].append(i) - i.row = row - remaining -= 1 - row += 1 - return row - # Function: getPhaseRows - # Description: - # Organize the timeline entries into the smallest - # number of rows possible, with no entry overlapping - # Arguments: - # devlist: the list of devices/actions in a group of contiguous phases - # Output: - # The total number of rows needed to display this phase of the timeline - def getPhaseRows(self, devlist, row=0): - # clear all rows and set them to undefined - remaining = len(devlist) - rowdata = dict() - sortdict = dict() - myphases = [] - # initialize all device rows to -1 and calculate devrows - for item in devlist: - dev = item.dev - tp = (item.test, item.phase) - if tp not in myphases: - myphases.append(tp) - dev['row'] = -1 - # sort by length 1st, then name 2nd - sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name']) - if 'src' in dev: - dev['devrows'] = self.getDeviceRows(dev['src']) - # sort the devlist by length so that large items graph on top - sortlist = sorted(sortdict, key=sortdict.get, reverse=True) - orderedlist = [] - for item in sortlist: - if item.dev['pid'] == -2: - orderedlist.append(item) - for item in sortlist: - if item not in orderedlist: - orderedlist.append(item) - # try to pack each row with as many devices as possible - while(remaining > 0): - rowheight = 1 - if(row not in rowdata): - rowdata[row] = [] - for item in orderedlist: - dev = item.dev - if(dev['row'] < 0): - s = dev['start'] - e = dev['end'] - valid = True - for ritem in rowdata[row]: - rs = ritem.dev['start'] - re = ritem.dev['end'] - if(not (((s <= rs) and (e <= rs)) or - ((s >= re) and (e >= re)))): - valid = False - break - if(valid): - rowdata[row].append(item) - dev['row'] = row - remaining -= 1 - if 'devrows' in dev and dev['devrows'] > rowheight: - rowheight = dev['devrows'] - for t, p in myphases: - if t not in self.rowlines or t not in self.rowheight: - self.rowlines[t] = dict() - self.rowheight[t] = dict() - if p not in self.rowlines[t] or p not in self.rowheight[t]: - self.rowlines[t][p] = dict() - self.rowheight[t][p] = dict() - rh = self.rowH - # section headers should use a different row height - if len(rowdata[row]) == 1 and \ - 'htmlclass' in rowdata[row][0].dev and \ - 'sec' in rowdata[row][0].dev['htmlclass']: - rh = 15 - self.rowlines[t][p][row] = rowheight - self.rowheight[t][p][row] = rowheight * rh - row += 1 - if(row > self.rows): - self.rows = int(row) - return row - def phaseRowHeight(self, test, phase, row): - return self.rowheight[test][phase][row] - def phaseRowTop(self, test, phase, row): - top = 0 - for i in sorted(self.rowheight[test][phase]): - if i >= row: - break - top += self.rowheight[test][phase][i] - return top - # Function: calcTotalRows - # Description: - # Calculate the heights and offsets for the header and rows - def calcTotalRows(self): - maxrows = 0 - standardphases = [] - for t in self.rowlines: - for p in self.rowlines[t]: - total = 0 - for i in sorted(self.rowlines[t][p]): - total += self.rowlines[t][p][i] - if total > maxrows: - maxrows = total - if total == len(self.rowlines[t][p]): - standardphases.append((t, p)) - self.height = self.scaleH + (maxrows*self.rowH) - self.bodyH = self.height - self.scaleH - # if there is 1 line per row, draw them the standard way - for t, p in standardphases: - for i in sorted(self.rowheight[t][p]): - self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p]) - # Function: createTimeScale - # Description: - # Create the timescale for a timeline block - # Arguments: - # m0: start time (mode begin) - # mMax: end time (mode end) - # tTotal: total timeline time - # mode: suspend or resume - # Output: - # The html code needed to display the time scale - def createTimeScale(self, m0, mMax, tTotal, mode): - timescale = '
    {1}
    \n' - rline = '
    Resume
    \n' - output = '
    \n' - # set scale for timeline - mTotal = mMax - m0 - tS = 0.1 - if(tTotal <= 0): - return output+'
    \n' - if(tTotal > 4): - tS = 1 - divTotal = int(mTotal/tS) + 1 - divEdge = (mTotal - tS*(divTotal-1))*100/mTotal - for i in range(divTotal): - htmlline = '' - if(mode == 'resume'): - pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal)) - val = '%0.fms' % (float(i)*tS*1000) - htmlline = timescale.format(pos, val) - if(i == 0): - htmlline = rline - else: - pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge) - val = '%0.fms' % (float(i-divTotal+1)*tS*1000) - if(i == divTotal - 1): - val = 'Suspend' - htmlline = timescale.format(pos, val) - output += htmlline - output += '\n' - return output - -# Class: TestProps -# Description: -# A list of values describing the properties of these test runs -class TestProps: - stamp = '' - S0i3 = False - fwdata = [] - ftrace_line_fmt_fg = \ - '^ *(?P