| # 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) |