|
# 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. |
|
# |
|
# Authors: Nathan Binkert |
|
|
|
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.itervalues(): |
|
total += value |
|
return total |
|
|
|
if attr == 'filedata': |
|
return FileData(self.filename) |
|
|
|
if attr == 'maxsymlen': |
|
return max([ len(sym) for sym in self.iterkeys() ]) |
|
|
|
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.iteritems() ] |
|
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 >>output, symbolf % (name, 100.0 * (float(number) / total)) |
|
|
|
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 = long(data[0], 16) |
|
node = FuncNode() |
|
node.symbol = data[1] |
|
if node.symbol == '': |
|
node.symbol = 'unknown' |
|
node.count = long(data[2]) |
|
node.children = [ long(child, 16) for child in data[3:] ] |
|
nodes[node_id] = node |
|
|
|
for node in nodes.itervalues(): |
|
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.iteritems() ] |
|
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 >>output, '%-30s %8s' % (key, '%3.2f%%' % percent) |
|
|
|
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.iteritems(): |
|
for cpu,data in cpus.iteritems(): |
|
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) |