blob: bed8088e3ed80f0b7244017791c1c3c1341f95d2 [file] [log] [blame]
# Copyright (c) 2005 The Regents of The University of Michigan
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from . import output
class FileData(dict):
def __init__(self, filename):
self.filename = filename
fd = file(filename)
current = []
for line in fd:
line = line.strip()
if line.startswith('>>>'):
current = []
self[line[3:]] = current
else:
current.append(line)
fd.close()
class RunData(dict):
def __init__(self, filename):
self.filename = filename
def __getattribute__(self, attr):
if attr == 'total':
total = 0.0
for value in self.values():
total += value
return total
if attr == 'filedata':
return FileData(self.filename)
if attr == 'maxsymlen':
return max([ len(sym) for sym in self.keys() ])
return super(RunData, self).__getattribute__(attr)
def display(self, output=None, limit=None, maxsymlen=None):
if not output:
import sys
output = sys.stdout
elif isinstance(output, str):
output = file(output, 'w')
total = float(self.total)
# swap (string,count) order so we can sort on count
symbols = [ (count,name) for name,count in self.items() ]
symbols.sort(reverse=True)
if limit is not None:
symbols = symbols[:limit]
if not maxsymlen:
maxsymlen = self.maxsymlen
symbolf = "%-" + str(maxsymlen + 1) + "s %.2f%%"
for number,name in symbols:
print(symbolf % (name, 100.0 * (float(number) / total)),
file=output)
class PCData(RunData):
def __init__(self, filename=None, categorize=None, showidle=True):
super(PCData, self).__init__(self, filename)
filedata = self.filedata['PC data']
for line in filedata:
(symbol, count) = line.split()
if symbol == "0x0":
continue
count = int(count)
if categorize is not None:
category = categorize(symbol)
if category is None:
category = 'other'
elif category == 'idle' and not showidle:
continue
self[category] = count
class FuncNode(object):
def __new__(cls, filedata=None):
if filedata is None:
return super(FuncNode, cls).__new__(cls)
nodes = {}
for line in filedata['function data']:
data = line.split(' ')
node_id = int(data[0], 16)
node = FuncNode()
node.symbol = data[1]
if node.symbol == '':
node.symbol = 'unknown'
node.count = int(data[2])
node.children = [ int(child, 16) for child in data[3:] ]
nodes[node_id] = node
for node in nodes.values():
children = []
for cid in node.children:
child = nodes[cid]
children.append(child)
child.parent = node
node.children = tuple(children)
if not nodes:
print(filedata.filename)
print(nodes)
return nodes[0]
def total(self):
total = self.count
for child in self.children:
total += child.total()
return total
def aggregate(self, dict, categorize, incategory):
category = None
if categorize:
category = categorize(self.symbol)
total = self.count
for child in self.children:
total += child.aggregate(dict, categorize, category or incategory)
if category:
dict[category] = dict.get(category, 0) + total
return 0
elif not incategory:
dict[self.symbol] = dict.get(self.symbol, 0) + total
return total
def dump(self):
kids = [ child.symbol for child in self.children]
print('%s %d <%s>' % (self.symbol, self.count, ', '.join(kids)))
for child in self.children:
child.dump()
def _dot(self, dot, threshold, categorize, total):
from pydot import Dot, Edge, Node
self.dot_node = None
value = self.total() * 100.0 / total
if value < threshold:
return
if categorize:
category = categorize(self.symbol)
if category and category != 'other':
return
label = '%s %.2f%%' % (self.symbol, value)
self.dot_node = Node(self, label=label)
dot.add_node(self.dot_node)
for child in self.children:
child._dot(dot, threshold, categorize, total)
if child.dot_node is not None:
dot.add_edge(Edge(self, child))
def _cleandot(self):
for child in self.children:
child._cleandot()
self.dot_node = None
del self.__dict__['dot_node']
def dot(self, dot, threshold=0.1, categorize=None):
self._dot(dot, threshold, categorize, self.total())
self._cleandot()
class FuncData(RunData):
def __init__(self, filename, categorize=None):
super(FuncData, self).__init__(filename)
tree = self.tree
tree.aggregate(self, categorize, incategory=False)
self.total = tree.total()
def __getattribute__(self, attr):
if attr == 'tree':
return FuncNode(self.filedata)
return super(FuncData, self).__getattribute__(attr)
def displayx(self, output=None, maxcount=None):
if output is None:
import sys
output = sys.stdout
items = [ (val,key) for key,val in self.items() ]
items.sort(reverse=True)
for val,key in items:
if maxcount is not None:
if maxcount == 0:
return
maxcount -= 1
percent = val * 100.0 / self.total
print('%-30s %8s' % (key, '%3.2f%%' % percent), file=output)
class Profile(object):
# This list controls the order of values in stacked bar data output
default_categories = [ 'interrupt',
'driver',
'stack',
'buffer',
'copy',
'syscall',
'user',
'other',
'idle']
def __init__(self, datatype, categorize=None):
categories = Profile.default_categories
self.datatype = datatype
self.categorize = categorize
self.data = {}
self.categories = categories[:]
self.rcategories = categories[:]
self.rcategories.reverse()
self.cpu = 0
# Read in files
def inputdir(self, directory):
import os, os.path, re
from os.path import expanduser, join as joinpath
directory = expanduser(directory)
label_ex = re.compile(r'profile\.(.*).dat')
for root,dirs,files in os.walk(directory):
for name in files:
match = label_ex.match(name)
if not match:
continue
filename = joinpath(root, name)
prefix = os.path.commonprefix([root, directory])
dirname = root[len(prefix)+1:]
data = self.datatype(filename, self.categorize)
self.setdata(dirname, match.group(1), data)
def setdata(self, run, cpu, data):
if run not in self.data:
self.data[run] = {}
if cpu in self.data[run]:
raise AttributeError(
'data already stored for run %s and cpu %s' % (run, cpu))
self.data[run][cpu] = data
def getdata(self, run, cpu):
try:
return self.data[run][cpu]
except KeyError:
print(run, cpu)
return None
def alldata(self):
for run,cpus in self.data.items():
for cpu,data in cpus.items():
yield run,cpu,data
def get(self, job, stat, system=None):
if system is None and hasattr('system', job):
system = job.system
if system is None:
raise AttributeError('The job must have a system set')
cpu = '%s.run%d' % (system, self.cpu)
data = self.getdata(str(job), cpu)
if not data:
return None
values = []
for category in self.categories:
val = float(data.get(category, 0.0))
if val < 0.0:
raise ValueError('value is %f' % val)
values.append(val)
total = sum(values)
return [ v / total * 100.0 for v in values ]
def dump(self):
for run,cpu,data in self.alldata():
print('run %s, cpu %s' % (run, cpu))
data.dump()
print()
def write_dot(self, threshold, jobfile=None, jobs=None):
import pydot
if jobs is None:
jobs = [ job for job in jobfile.jobs() ]
for job in jobs:
cpu = '%s.run%d' % (job.system, self.cpu)
symbols = self.getdata(job.name, cpu)
if not symbols:
continue
dot = pydot.Dot()
symbols.tree.dot(dot, threshold=threshold)
dot.write(symbols.filename[:-3] + 'dot')
def write_txt(self, jobfile=None, jobs=None, limit=None):
if jobs is None:
jobs = [ job for job in jobfile.jobs() ]
for job in jobs:
cpu = '%s.run%d' % (job.system, self.cpu)
symbols = self.getdata(job.name, cpu)
if not symbols:
continue
output = file(symbols.filename[:-3] + 'txt', 'w')
symbols.display(output, limit)
def display(self, jobfile=None, jobs=None, limit=None):
if jobs is None:
jobs = [ job for job in jobfile.jobs() ]
maxsymlen = 0
thejobs = []
for job in jobs:
cpu = '%s.run%d' % (job.system, self.cpu)
symbols = self.getdata(job.name, cpu)
if symbols:
thejobs.append(job)
maxsymlen = max(maxsymlen, symbols.maxsymlen)
for job in thejobs:
cpu = '%s.run%d' % (job.system, self.cpu)
symbols = self.getdata(job.name, cpu)
print(job.name)
symbols.display(limit=limit, maxsymlen=maxsymlen)
print()
from .categories import func_categorize, pc_categorize
class PCProfile(Profile):
def __init__(self, categorize=pc_categorize):
super(PCProfile, self).__init__(PCData, categorize)
class FuncProfile(Profile):
def __init__(self, categorize=func_categorize):
super(FuncProfile, self).__init__(FuncData, categorize)
def usage(exitcode = None):
print('''\
Usage: %s [-bc] [-g <dir>] [-j <jobfile>] [-n <num>]
-c groups symbols into categories
-b dumps data for bar charts
-d generate dot output
-g <d> draw graphs and send output to <d>
-j <jobfile> specify a different jobfile (default is Test.py)
-n <n> selects number of top symbols to print (default 5)
''' % sys.argv[0])
if exitcode is not None:
sys.exit(exitcode)
if __name__ == '__main__':
import getopt, re, sys
from os.path import expanduser
from .output import StatOutput
# default option values
numsyms = 10
graph = None
cpus = [ 0 ]
categorize = False
showidle = True
funcdata = True
jobfilename = 'Test.py'
dodot = False
dotfile = None
textout = False
threshold = 0.01
inputfile = None
try:
opts, args = getopt.getopt(sys.argv[1:], 'C:cdD:f:g:ij:n:pT:t')
except getopt.GetoptError:
usage(2)
for o,a in opts:
if o == '-C':
cpus = [ int(x) for x in a.split(',') ]
elif o == '-c':
categorize = True
elif o == '-D':
dotfile = a
elif o == '-d':
dodot = True
elif o == '-f':
inputfile = expanduser(a)
elif o == '-g':
graph = a
elif o == '-i':
showidle = False
elif o == '-j':
jobfilename = a
elif o == '-n':
numsyms = int(a)
elif o == '-p':
funcdata = False
elif o == '-T':
threshold = float(a)
elif o == '-t':
textout = True
if args:
print("'%s'" % args, len(args))
usage(1)
if inputfile:
catfunc = None
if categorize:
catfunc = func_categorize
data = FuncData(inputfile, categorize=catfunc)
if dodot:
import pydot
dot = pydot.Dot()
data.tree.dot(dot, threshold=threshold)
#dot.orientation = 'landscape'
#dot.ranksep='equally'
#dot.rank='samerank'
dot.write(dotfile, format='png')
else:
data.display(limit=numsyms)
else:
from jobfile import JobFile
jobfile = JobFile(jobfilename)
if funcdata:
profile = FuncProfile()
else:
profile = PCProfile()
if not categorize:
profile.categorize = None
profile.inputdir(jobfile.rootdir)
if graph:
for cpu in cpus:
profile.cpu = cpu
if funcdata:
name = 'funcstacks%d' % cpu
else:
name = 'stacks%d' % cpu
output = StatOutput(jobfile, info=profile)
output.xlabel = 'System Configuration'
output.ylabel = '% CPU utilization'
output.stat = name
output.graph(name, graph)
if dodot:
for cpu in cpus:
profile.cpu = cpu
profile.write_dot(jobfile=jobfile, threshold=threshold)
if textout:
for cpu in cpus:
profile.cpu = cpu
profile.write_txt(jobfile=jobfile)
if not graph and not textout and not dodot:
for cpu in cpus:
if not categorize:
profile.categorize = None
profile.cpu = cpu
profile.display(jobfile=jobfile, limit=numsyms)