Major improvements in the graph output code. Mostly adding more
authorNathan Binkert <binkertn@umich.edu>
Wed, 23 Nov 2005 02:50:34 +0000 (21:50 -0500)
committerNathan Binkert <binkertn@umich.edu>
Wed, 23 Nov 2005 02:50:34 +0000 (21:50 -0500)
options, making existing options more visible and dealing with
holes in data better.

util/stats/barchart.py:
    - move the options for BarChart to a base class ChartOptions so
    they can be more easily set and copied.
    - add an option to set the chart size (so you can adjust the aspect ratio)
    - don't do the add_subplot thing, use add_axes directly so we can
    affect the size of the figure itself to make room for the legend
    - make the initial array bottom floating point so we don't lose precision
    - add an option to set the limits on the y axis
    - use a figure legend instead of an axes legend so we can put the legend
    outside of the actual chart.  Also add an option to set the fontsize of
    the legend.
    - initial hack at outputting csv files
util/stats/db.py:
    don't print out an error when the run is missing from the database
    just return None, the error will be print elsewhere.
util/stats/output.py:
    - make StatOutput derive from ChartOptions so that it's easier to
    set default chart options.
    - make the various output functions (graph, display, etc.) take the
    name of the data as a parameter instead of making it a parameter to
    __init__.  This allows me to create the StatOutput object with
    generic parameters while still being able to specialize the name
    after the fact
    - add support for graph_group and graph_bars to be applied to multiple
    configuration groups.  This results in a cross product of the groups
    to be generated and used.
    - flush the html file output as we go so that we can load the file
    while graphs are still being generated.
    - make the proxy a parameter to the graph function so the proper system's
    data can be graphed
    - for any groups or bars that are completely missing, remove them from
    the graph.  This way, if we decide not to do a set of runs, there won't
    be holes in the data.
    - output eps and ps by default in addition to the png.
util/stats/profile.py:
    - clean up the data structures that are used to store the function
    profile information and try our best to avoid keeping extra data
    around that isn't used.
    - make get() return None if a job is missing so we know it was
    missing rather than the all zeroes thing.
    - make the function profile categorization stuff total up to 100%
    - Fixup the x-axis and y-axis labels.
    - fix the dot file output stuff.
util/stats/stats.py:
    support the new options stuff for StatOutput

--HG--
extra : convert_revision : fae35df8c57a36257ea93bc3e0a0e617edc46bb7

util/stats/barchart.py
util/stats/chart.py [new file with mode: 0644]
util/stats/db.py
util/stats/output.py
util/stats/profile.py
util/stats/stats.py

index a2cbea816bb1222d30868398299616713ab19390..19cccb58a78966a055774d01506336500c16e492 100644 (file)
 #          Lisa Hsu
 
 import matplotlib, pylab
+from matplotlib.font_manager import FontProperties
 from matplotlib.numerix import array, arange, reshape, shape, transpose, zeros
 from matplotlib.numerix import Float
 
 matplotlib.interactive(False)
 
-class BarChart(object):
-    def __init__(self, **kwargs):
-        self.init(**kwargs)
+from chart import ChartOptions
 
-    def init(self, **kwargs):
-        self.colormap = 'jet'
+class BarChart(ChartOptions):
+    def __init__(self, default=None, **kwargs):
+        super(BarChart, self).__init__(default, **kwargs)
         self.inputdata = None
         self.chartdata = None
-        self.xlabel = None
-        self.ylabel = None
-        self.legend = None
-        self.xticks = None
-        self.yticks = None
-        self.title = None
-
-        for key,value in kwargs.iteritems():
-            self.__setattr__(key, value)
 
     def gen_colors(self, count):
         cmap = matplotlib.cm.get_cmap(self.colormap)
@@ -129,8 +120,8 @@ class BarChart(object):
         if self.chartdata is None:
             raise AttributeError, "Data not set for bar chart!"
 
-        self.figure = pylab.figure()
-        self.axes = self.figure.add_subplot(111)
+        self.figure = pylab.figure(figsize=self.chart_size)
+        self.axes = self.figure.add_axes(self.figure_size)
 
         dim = len(shape(self.inputdata))
         cshape = shape(self.chartdata)
@@ -158,7 +149,7 @@ class BarChart(object):
 
         bars = []
         for i,stackdata in enumerate(self.chartdata):
-            bottom = array([0] * len(stackdata[0]))
+            bottom = array([0.0] * len(stackdata[0]), Float)
             stack = []
             for j,bardata in enumerate(stackdata):
                 bardata = array(bardata)
@@ -181,6 +172,8 @@ class BarChart(object):
             ticks = arange(nticks) / (nticks - 1) * (ymax - ymin)  + ymin
             self.axes.set_yticks(ticks)
             self.axes.set_yticklabels(self.yticks)
+        elif self.ylim is not None:
+            self.axes.set_ylim(self.ylim)
 
         if self.xticks is not None:
             self.axes.set_xticks(arange(cshape[2]) + .5)
@@ -195,7 +188,8 @@ class BarChart(object):
                 number = len(bars[0])
                 lbars = [ bars[0][number - j - 1][0] for j in xrange(number)]
 
-            self.axes.legend(lbars, self.legend, loc='best')
+            self.figure.legend(lbars, self.legend, self.legend_loc,
+                               prop=FontProperties(size=self.legend_size))
 
         if self.title is not None:
             self.axes.set_title(self.title)
@@ -203,7 +197,32 @@ class BarChart(object):
     def savefig(self, name):
         self.figure.savefig(name)
 
+    def savecsv(self, name):
+        f = file(name, 'w')
+        data = array(self.inputdata)
+        dim = len(data.shape)
+
+        if dim == 1:
+            #if self.xlabel:
+            #    f.write(', '.join(list(self.xlabel)) + '\n')
+            f.write(', '.join([ '%f' % val for val in data]) + '\n')
+        if dim == 2:
+            #if self.xlabel:
+            #    f.write(', '.join([''] + list(self.xlabel)) + '\n')
+            for i,row in enumerate(data):
+                ylabel = []
+                #if self.ylabel:
+                #    ylabel = [ self.ylabel[i] ]
+                f.write(', '.join(ylabel + [ '%f' % val for val in row]) + '\n')
+        if dim == 3:
+            f.write("don't do 3D csv files\n")
+            pass
+
+        f.close()
+
+
 if __name__ == '__main__':
+    from random import randrange
     import random, sys
 
     dim = 3
@@ -234,13 +253,17 @@ if __name__ == '__main__':
         chart1.xticks = [ 'xtick%d' % x for x in xrange(myshape[0]) ]
         chart1.title = 'this is the title'
         chart1.graph()
-        #chart1.savefig('/tmp/test1.png')
+        chart1.savefig('/tmp/test1.png')
+        chart1.savefig('/tmp/test1.ps')
+        chart1.savefig('/tmp/test1.eps')
+        chart1.savecsv('/tmp/test1.csv')
 
     if False:
         chart2 = BarChart()
         chart2.data = data
         chart2.colormap = 'gray'
         chart2.graph()
-        #chart2.savefig('/tmp/test2.png')
+        chart2.savefig('/tmp/test2.png')
+        chart2.savefig('/tmp/test2.ps')
 
-    pylab.show()
+    #pylab.show()
diff --git a/util/stats/chart.py b/util/stats/chart.py
new file mode 100644 (file)
index 0000000..1e301cb
--- /dev/null
@@ -0,0 +1,84 @@
+# 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
+#          Lisa Hsu
+
+class ChartOptions(object):
+    defaults = { 'chart_size' : (8, 4),
+                 'figure_size' : [0.1, 0.1, 0.6, 0.85],
+                 'title' : None,
+                 'legend' : None,
+                 'legend_loc' : 'upper right',
+                 'legend_size' : 6,
+                 'colormap' : 'jet',
+                 'xlabel' : None,
+                 'ylabel' : None,
+                 'xticks' : None,
+                 'yticks' : None,
+                 'ylim' : None,
+                 }
+
+    def __init__(self, options=None, **kwargs):
+        self.init(options, **kwargs)
+
+    def clear(self):
+        self.options = {}
+
+    def init(self, options=None, **kwargs):
+        self.clear()
+        self.update(options, **kwargs)
+
+    def update(self, options=None, **kwargs):
+        if options is not None:
+            if not isinstance(options, ChartOptions):
+                raise AttributeError, \
+                      'attribute options of type %s should be %s' % \
+                      (type(options), ChartOptions)
+            self.options.update(options.options)
+
+        for key,value in kwargs.iteritems():
+            if key not in ChartOptions.defaults:
+                raise AttributeError, \
+                      "%s instance has no attribute '%s'" % (type(self), key)
+            self.options[key] = value
+
+    def __getattr__(self, attr):
+        if attr in self.options:
+            return self.options[attr]
+
+        if attr in ChartOptions.defaults:
+            return ChartOptions.defaults[attr]
+
+        raise AttributeError, \
+              "%s instance has no attribute '%s'" % (type(self), attr)
+
+    def __setattr__(self, attr, value):
+        if attr in ChartOptions.defaults:
+            self.options[attr] = value
+        else:
+            super(ChartOptions, self).__setattr__(attr, value)
+
index 1ece6df88ef94cb13c78697f27ea60283a7aa22c..8e57f904362accc9866270e0160e3531d43496ac 100644 (file)
@@ -155,7 +155,6 @@ class Database(object):
     def get(self, job, stat):
         run = self.allRunNames.get(job.name, None)
         if run is None:
-            print 'run "%s" not found' % job
             return None
 
         from info import scalar, vector, value, values, total, len
index cf76f291e22810bd58960ab96a48f9b27db6a1ac..e67751bbcff3be0283c67c30c46c15c23fc85151 100644 (file)
 #
 # Authors: Nathan Binkert
 
-class StatOutput(object):
-    def __init__(self, name, jobfile, info, stat=None, binstats=None):
-        self.name = name
+from chart import ChartOptions
+
+class StatOutput(ChartOptions):
+    def __init__(self, jobfile, info, stat=None, binstats=None):
+        super(StatOutput, self).__init__()
         self.jobfile = jobfile
         self.stat = stat
         self.binstats = None
-        self.label = self.name
         self.invert = False
         self.info = info
 
-    def printdata(self, bin = None, printmode = 'G'):
+    def printdata(self, name, bin = None, printmode = 'G'):
         import info
 
         if bin:
-            print '%s %s stats' % (self.name, bin)
+            print '%s %s stats' % (name, bin)
 
         if self.binstats:
             for stat in self.binstats:
@@ -69,70 +70,78 @@ class StatOutput(object):
             valstring = ', '.join([ valformat % val for val in value ])
             print '%-50s    %s' % (job.name + ':', valstring)
 
-    def display(self, binned = False, printmode = 'G'):
+    def display(self, name, binned = False, printmode = 'G'):
         if binned and self.binstats:
-            self.printdata('kernel', printmode)
-            self.printdata('idle', printmode)
-            self.printdata('user', printmode)
-            self.printdata('interrupt', printmode)
+            self.printdata(name, 'kernel', printmode)
+            self.printdata(name, 'idle', printmode)
+            self.printdata(name, 'user', printmode)
+            self.printdata(name, 'interrupt', printmode)
 
-            print '%s total stats' % self.name
-        self.printdata(printmode=printmode)
+            print '%s total stats' % name
+        self.printdata(name, printmode=printmode)
 
-    def graph(self, graphdir):
+    def graph(self, name, graphdir, proxy=None):
         from os.path import expanduser, isdir, join as joinpath
         from barchart import BarChart
         from matplotlib.numerix import Float, array, zeros
-        import os, re
+        import os, re, urllib
+        from jobfile import crossproduct
 
         confgroups = self.jobfile.groups()
         ngroups = len(confgroups)
         skiplist = [ False ] * ngroups
-        groupopts = None
-        baropts = None
+        groupopts = []
+        baropts = []
         groups = []
         for i,group in enumerate(confgroups):
             if group.flags.graph_group:
-                if groupopts is not None:
-                    raise AttributeError, \
-                          'Two groups selected for graph group'
-                groupopts = group.subopts()
+                groupopts.append(group.subopts())
                 skiplist[i] = True
             elif group.flags.graph_bars:
-                if baropts is not None:
-                    raise AttributeError, \
-                          'Two groups selected for graph bars'
-                baropts = group.subopts()
+                baropts.append(group.subopts())
                 skiplist[i] = True
             else:
                 groups.append(group)
 
-        if groupopts is None:
+        if not groupopts:
             raise AttributeError, 'No group selected for graph group'
 
-        if baropts is None:
+        if not baropts:
             raise AttributeError, 'No group selected for graph bars'
 
+        groupopts = [ group for group in crossproduct(groupopts) ]
+        baropts = [ bar for bar in crossproduct(baropts) ]
+
         directory = expanduser(graphdir)
         if not isdir(directory):
             os.mkdir(directory)
-        html = file(joinpath(directory, '%s.html' % self.name), 'w')
+        html = file(joinpath(directory, '%s.html' % name), 'w')
         print >>html, '<html>'
-        print >>html, '<title>Graphs for %s</title>' % self.name
+        print >>html, '<title>Graphs for %s</title>' % name
         print >>html, '<body>'
+        html.flush()
 
         for options in self.jobfile.options(groups):
+            chart = BarChart(self)
+
             data = zeros((len(groupopts), len(baropts)), Float)
             data = [ [ None ] * len(baropts) for i in xrange(len(groupopts)) ]
             enabled = False
             stacked = 0
             for g,gopt in enumerate(groupopts):
                 for b,bopt in enumerate(baropts):
-                    job = self.jobfile.job(options + [ gopt, bopt ])
+                    job = self.jobfile.job(options + gopt + bopt)
                     if not job:
                         continue
 
+                    if proxy:
+                        import db
+                        proxy.dict['system'] = self.info[job.system]
                     val = self.info.get(job, self.stat)
+                    if val is None:
+                        print 'stat "%s" for job "%s" not found' % \
+                              (self.stat, job)
+
                     if isinstance(val, (list, tuple)):
                         if len(val) == 1:
                             val = val[0]
@@ -151,7 +160,7 @@ class StatOutput(object):
                     for j in xrange(len(baropts)):
                         val = data[i][j]
                         if val is None:
-                            data[i][j] = [] * stacked
+                            data[i][j] = [ 0.0 ] * stacked
                         elif len(val) != stacked:
                             raise ValueError, "some stats stacked, some not"
 
@@ -159,29 +168,52 @@ class StatOutput(object):
             if data.sum() == 0:
                 continue
 
-            bar_descs = [ opt.desc for opt in baropts ]
-            group_descs = [ opt.desc for opt in groupopts ]
-            if stacked:
-                try:
-                    legend = self.info.rcategories
-                except:
-                    legend = [ str(i) for i in xrange(stacked) ]
-            else:
-                legend = bar_descs
-
-            chart = BarChart(data=data, xlabel='Benchmark', ylabel=self.label,
-                             legend=legend, xticks=group_descs)
+            x = data.shape[0]
+            y = data.shape[1]
+            xkeep = [ i for i in xrange(x) if data[i].sum() != 0 ]
+            ykeep = [ i for i in xrange(y) if data[:,i].sum() != 0 ]
+            data = data.take(xkeep, axis=0)
+            data = data.take(ykeep, axis=1)
+            chart.data = data
+
+            gopts = [ groupopts[i] for i in xkeep ]
+            bopts = [ baropts[i] for i in ykeep ]
+
+            bdescs = [ ' '.join([o.desc for o in opt]) for opt in bopts]
+            gdescs = [ ' '.join([o.desc for o in opt]) for opt in gopts]
+
+            if chart.legend is None:
+                if stacked:
+                    try:
+                        chart.legend = self.info.rcategories
+                    except:
+                        chart.legend = [ str(i) for i in xrange(stacked) ]
+                else:
+                    chart.legend = bdescs
+
+            if chart.xticks is None:
+                chart.xticks = gdescs
             chart.graph()
 
             names = [ opt.name for opt in options ]
             descs = [ opt.desc for opt in options ]
 
-            filename =  '%s-%s.png' % (self.name, ':'.join(names))
+            if names[0] == 'run':
+                names = names[1:]
+                descs = descs[1:]
+
+            basename = '%s-%s' % (name, ':'.join(names))
             desc = ' '.join(descs)
-            filepath = joinpath(directory, filename)
-            chart.savefig(filepath)
-            filename = re.sub(':', '%3A', filename)
-            print >>html, '''%s<br><img src="%s"><br>''' % (desc, filename)
+
+            pngname = '%s.png' % basename
+            psname = '%s.eps' % re.sub(':', '-', basename)
+            epsname = '%s.ps' % re.sub(':', '-', basename)
+            chart.savefig(joinpath(directory, pngname))
+            chart.savefig(joinpath(directory, epsname))
+            chart.savefig(joinpath(directory, psname))
+            html_name = urllib.quote(pngname)
+            print >>html, '''%s<br><img src="%s"><br>''' % (desc, html_name)
+            html.flush()
 
         print >>html, '</body>'
         print >>html, '</html>'
index 0f51d643e4a5550f2c6b9f372611560697b76072..1511702803bf74d5360189c225aa92213d810057 100644 (file)
 from orderdict import orderdict
 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=None):
+    def __init__(self, filename):
         self.filename = filename
 
-    def __getattr__(self, attr):
+    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
@@ -62,24 +82,12 @@ class RunData(dict):
         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)
-        if filename is None:
-            return
-
-        fd = file(filename)
-
-        for line in fd:
-            if line.strip() == '>>>PC data':
-                break
-
-        for line in fd:
-            if line.startswith('>>>'):
-                break
 
+        filedata = self.filedata['PC data']
+        for line in filedata:
             (symbol, count) = line.split()
             if symbol == "0x0":
                 continue
@@ -94,30 +102,21 @@ class PCData(RunData):
 
                 self[category] = count
 
-        fd.close()
-
 class FuncNode(object):
-    def __new__(cls, filename = None):
-        if filename is None:
+    def __new__(cls, filedata=None):
+        if filedata is None:
             return super(FuncNode, cls).__new__(cls)
 
-        fd = file(filename, 'r')
-        fditer = iter(fd)
         nodes = {}
-        for line in fditer:
-            if line.strip() == '>>>function data':
-                break
-
-        for line in fditer:
-            if line.startswith('>>>'):
-                break
-
-            data = line.split()
-            node_id = int(data[0], 16)
+        for line in filedata['function data']:
+            data = line.split(' ')
+            node_id = long(data[0], 16)
             node = FuncNode()
             node.symbol = data[1]
-            node.count = int(data[2])
-            node.children = [ int(child, 16) for child in data[3:] ]
+            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():
@@ -128,13 +127,10 @@ class FuncNode(object):
                 child.parent = node
             node.children = tuple(children)
         if not nodes:
-            print filename
+            print filedata.filename
             print nodes
         return nodes[0]
 
-    def __init__(self, filename=None):
-        pass
-
     def total(self):
         total = self.count
         for child in self.children:
@@ -198,9 +194,14 @@ class FuncNode(object):
 class FuncData(RunData):
     def __init__(self, filename, categorize=None):
         super(FuncData, self).__init__(filename)
-        self.tree = FuncNode(filename)
-        self.tree.aggregate(self, categorize, incategory=False)
-        self.total = self.tree.total()
+        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:
@@ -274,6 +275,7 @@ class Profile(object):
         try:
             return self.data[run][cpu]
         except KeyError:
+            print run, cpu
             return None
 
     def alldata(self):
@@ -289,12 +291,16 @@ class Profile(object):
         cpu = '%s.run%d' % (job.system, self.cpu)
         data = self.getdata(run, cpu)
         if not data:
-            return [ 0.0 for c in self.categories ]
+            return None
 
         values = []
         for category in self.categories:
-            values.append(data.get(category, 0.0))
-        return values
+            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():
@@ -382,7 +388,6 @@ if __name__ == '__main__':
     import getopt, re, sys
     from os.path import expanduser
     from output import StatOutput
-    from jobfile import JobFile
 
     # default option values
     numsyms = 10
@@ -393,7 +398,7 @@ if __name__ == '__main__':
     funcdata = True
     jobfilename = 'Test.py'
     dodot = False
-    dotformat = 'raw'
+    dotfile = None
     textout = False
     threshold = 0.01
     inputfile = None
@@ -409,7 +414,7 @@ if __name__ == '__main__':
         elif o == '-c':
             categorize = True
         elif o == '-D':
-            dotformat = a
+            dotfile = a
         elif o == '-d':
             dodot = True
         elif o == '-f':
@@ -434,20 +439,24 @@ if __name__ == '__main__':
         usage(1)
 
     if inputfile:
-        data = FuncData(inputfile)
+        catfunc = None
+        if categorize:
+            catfunc = func_categorize
+        data = FuncData(inputfile, categorize=catfunc)
 
         if dodot:
             import pydot
             dot = pydot.Dot()
-            data.dot(dot, threshold=threshold)
+            data.tree.dot(dot, threshold=threshold)
             #dot.orientation = 'landscape'
             #dot.ranksep='equally'
             #dot.rank='samerank'
-            dot.write(dotfile, format=dotformat)
+            dot.write(dotfile, format='png')
         else:
             data.display(limit=numsyms)
 
     else:
+        from jobfile import JobFile
         jobfile = JobFile(jobfilename)
 
         if funcdata:
@@ -466,8 +475,11 @@ if __name__ == '__main__':
                     name = 'funcstacks%d' % cpu
                 else:
                     name = 'stacks%d' % cpu
-                output = StatOutput(name, jobfile, info=profile)
-                output.graph(graph)
+                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:
index 2a24bf3fd1438750d91444e7a7b2daebdd79eb21..b75d9fec01b4c0ba6b5e5e5b177cfbe0d5c19c6a 100755 (executable)
@@ -259,14 +259,9 @@ def commands(options, command, args):
             print 'only displaying sample %s' % options.ticks
         source.ticks = [ int(x) for x in options.ticks.split() ]
 
-    import output
-
-    def display():
-        if options.graph:
-            output.graph(options.graphdir)
-        else:
-            output.display(options.binned, options.printmode)
-
+    from output import StatOutput
+    output = StatOutput(options.jobfile, source)
+    output.xlabel = 'System Configuration'
 
     if command == 'stat' or command == 'formula':
         if len(args) != 1:
@@ -278,17 +273,18 @@ def commands(options, command, args):
             stats = eval(args[0])
 
         for stat in stats:
-            output = output.StatOutput(stat.name, options.jobfile, source)
             output.stat = stat
-            output.label = stat.name
-            display()
+            output.ylabel = stat.name
+            if options.graph:
+                output.graph(stat.name, options.graphdir)
+            else:
+                output.display(stat.name, options.binned, options.printmode)
 
         return
 
     if len(args):
         raise CommandException
 
-    system = source.__dict__[options.system]
     from info import ProxyGroup
     sim_seconds = source['sim_seconds']
     proxy = ProxyGroup(system = source[options.system])
@@ -300,7 +296,11 @@ def commands(options, command, args):
     packets = etherdev.rxPackets + etherdev.txPackets
     bps = etherdev.rxBandwidth + etherdev.txBandwidth
 
-    output = output.StatOutput(command, options.jobfile, source)
+    def display():
+        if options.graph:
+            output.graph(command, options.graphdir, proxy)
+        else:
+            output.display(command, options.binned, options.printmode)
 
     if command == 'usertime':
         import copy
@@ -308,7 +308,7 @@ def commands(options, command, args):
         user.bins = 'user'
 
         output.stat = user / system.run0.numCycles
-        output.label = 'User Fraction'
+        output.ylabel = 'User Fraction'
 
         display()
         return
@@ -338,13 +338,13 @@ def commands(options, command, args):
 
     if command == 'pps':
         output.stat = packets / sim_seconds
-        output.label = 'Packets/s'
+        output.ylabel = 'Packets/s'
         display()
         return
 
     if command == 'bpt' or command == 'tpb':
         output.stat = bytes / system.run0.numCycles * 8
-        output.label = 'bps / Hz'
+        output.ylabel = 'bps / Hz'
         output.invert = command == 'tpb'
         display()
         return
@@ -357,37 +357,38 @@ def commands(options, command, args):
         if command == 'bps':
             output.stat = bps / 1e9
 
-        output.label = 'Bandwidth (Gbps)'
+        output.ylabel = 'Bandwidth (Gbps)'
+        output.ylim = [ 0.0, 10.0 ]
         display()
         return
 
     if command == 'bpp':
         output.stat = bytes / packets
-        output.label = 'Bytes / Packet'
+        output.ylabel = 'Bytes / Packet'
         display()
         return
 
     if command == 'rxbpp':
         output.stat = etherdev.rxBytes / etherdev.rxPackets
-        output.label = 'Receive Bytes / Packet'
+        output.ylabel = 'Receive Bytes / Packet'
         display()
         return
 
     if command == 'txbpp':
         output.stat = etherdev.txBytes / etherdev.txPackets
-        output.label = 'Transmit Bytes / Packet'
+        output.ylabel = 'Transmit Bytes / Packet'
         display()
         return
 
     if command == 'rtp':
         output.stat = etherdev.rxPackets / etherdev.txPackets
-        output.label = 'rxPackets / txPackets'
+        output.ylabel = 'rxPackets / txPackets'
         display()
         return
 
     if command == 'rtb':
         output.stat = etherdev.rxBytes / etherdev.txBytes
-        output.label = 'rxBytes / txBytes'
+        output.ylabel = 'rxBytes / txBytes'
         display()
         return
 
@@ -395,14 +396,14 @@ def commands(options, command, args):
 
     if command == 'misses':
         output.stat = misses
-        output.label = 'Overall MSHR Misses'
+        output.ylabel = 'Overall MSHR Misses'
         display()
         return
 
     if command == 'mpkb':
         output.stat = misses / (bytes / 1024)
         output.binstats = [ misses ]
-        output.label = 'Misses / KB'
+        output.ylabel = 'Misses / KB'
         display()
         return
 
@@ -410,7 +411,7 @@ def commands(options, command, args):
         interrupts = system.run0.kern.faults[4]
         output.stat = interrupts / kbytes
         output.binstats = [ interrupts ]
-        output.label = 'Interrupts / KB'
+        output.ylabel = 'Interrupts / KB'
         display()
         return