02098f8e7b
information that can be used for other aspects of sending jobs. New graphing output stuff with matplotlib. util/pbs/job.py: Shuffle code around and create the JobDir class which encapsulates all of the functionality needed for making, organizing, and cleaning a job directory. Better status output util/pbs/jobfile.py: Majory re-working of the jobfile code. A job file now consists of several objects that describe how jobs should be run, it includes information about checkpoints, and graphing. util/pbs/send.py: use the new jobfile code. deal with the 15 character limit of pbs by truncating the name and using the raj hack. util/stats/db.py: fix the __str__ function for nodes provide __getitem__ for the Database class util/stats/stats.py: use the jobfile stuff to figure out what the proper naming and organziation of the graphs should be. move all output code to output.py, get rid of ploticus and use matplotlib --HG-- rename : util/categories.py => util/stats/categories.py extra : convert_revision : 0d793cbf6ad9492290e8ec875ce001c84095e1f7
471 lines
15 KiB
Python
471 lines
15 KiB
Python
# Copyright (c) 2003-2004 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.
|
|
|
|
import MySQLdb, re, string
|
|
|
|
def statcmp(a, b):
|
|
v1 = a.split('.')
|
|
v2 = b.split('.')
|
|
|
|
last = min(len(v1), len(v2)) - 1
|
|
for i,j in zip(v1[0:last], v2[0:last]):
|
|
if i != j:
|
|
return cmp(i, j)
|
|
|
|
# Special compare for last element.
|
|
if len(v1) == len(v2):
|
|
return cmp(v1[last], v2[last])
|
|
else:
|
|
return cmp(len(v1), len(v2))
|
|
|
|
class RunData:
|
|
def __init__(self, row):
|
|
self.run = int(row[0])
|
|
self.name = row[1]
|
|
self.user = row[2]
|
|
self.project = row[3]
|
|
|
|
class SubData:
|
|
def __init__(self, row):
|
|
self.stat = int(row[0])
|
|
self.x = int(row[1])
|
|
self.y = int(row[2])
|
|
self.name = row[3]
|
|
self.descr = row[4]
|
|
|
|
class Data:
|
|
def __init__(self, row):
|
|
if len(row) != 5:
|
|
raise 'stat db error'
|
|
self.stat = int(row[0])
|
|
self.run = int(row[1])
|
|
self.x = int(row[2])
|
|
self.y = int(row[3])
|
|
self.data = float(row[4])
|
|
|
|
def __repr__(self):
|
|
return '''Data(['%d', '%d', '%d', '%d', '%f'])''' % ( self.stat,
|
|
self.run, self.x, self.y, self.data)
|
|
|
|
class StatData(object):
|
|
def __init__(self, row):
|
|
self.stat = int(row[0])
|
|
self.name = row[1]
|
|
self.desc = row[2]
|
|
self.type = row[3]
|
|
self.prereq = int(row[5])
|
|
self.precision = int(row[6])
|
|
|
|
import flags
|
|
self.flags = 0
|
|
if int(row[4]): self.flags |= flags.printable
|
|
if int(row[7]): self.flags |= flags.nozero
|
|
if int(row[8]): self.flags |= flags.nonan
|
|
if int(row[9]): self.flags |= flags.total
|
|
if int(row[10]): self.flags |= flags.pdf
|
|
if int(row[11]): self.flags |= flags.cdf
|
|
|
|
if self.type == 'DIST' or self.type == 'VECTORDIST':
|
|
self.min = float(row[12])
|
|
self.max = float(row[13])
|
|
self.bktsize = float(row[14])
|
|
self.size = int(row[15])
|
|
|
|
if self.type == 'FORMULA':
|
|
self.formula = self.db.allFormulas[self.stat]
|
|
|
|
class Node(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Database(object):
|
|
def __init__(self):
|
|
self.host = 'zizzer.pool'
|
|
self.user = ''
|
|
self.passwd = ''
|
|
self.db = 'm5stats'
|
|
self.cursor = None
|
|
|
|
self.allStats = []
|
|
self.allStatIds = {}
|
|
self.allStatNames = {}
|
|
|
|
self.allSubData = {}
|
|
|
|
self.allRuns = []
|
|
self.allRunIds = {}
|
|
self.allRunNames = {}
|
|
|
|
self.allBins = []
|
|
self.allBinIds = {}
|
|
self.allBinNames = {}
|
|
|
|
self.allFormulas = {}
|
|
|
|
self.stattop = {}
|
|
self.statdict = {}
|
|
self.statlist = []
|
|
|
|
self.mode = 'sum';
|
|
self.runs = None
|
|
self.bins = None
|
|
self.ticks = None
|
|
self.__dict__['get'] = type(self).sum
|
|
|
|
def query(self, sql):
|
|
self.cursor.execute(sql)
|
|
|
|
def update_dict(self, dict):
|
|
dict.update(self.stattop)
|
|
|
|
def append(self, stat):
|
|
statname = re.sub(':', '__', stat.name)
|
|
path = string.split(statname, '.')
|
|
pathtop = path[0]
|
|
fullname = ''
|
|
|
|
x = self
|
|
while len(path) > 1:
|
|
name = path.pop(0)
|
|
if not x.__dict__.has_key(name):
|
|
x.__dict__[name] = Node(fullname + name)
|
|
x = x.__dict__[name]
|
|
fullname = '%s%s.' % (fullname, name)
|
|
|
|
name = path.pop(0)
|
|
x.__dict__[name] = stat
|
|
|
|
self.stattop[pathtop] = self.__dict__[pathtop]
|
|
self.statdict[statname] = stat
|
|
self.statlist.append(statname)
|
|
|
|
def connect(self):
|
|
# connect
|
|
self.thedb = MySQLdb.connect(db=self.db,
|
|
host=self.host,
|
|
user=self.user,
|
|
passwd=self.passwd)
|
|
|
|
# create a cursor
|
|
self.cursor = self.thedb.cursor()
|
|
|
|
self.query('''select rn_id,rn_name,rn_sample,rn_user,rn_project
|
|
from runs''')
|
|
for result in self.cursor.fetchall():
|
|
run = RunData(result);
|
|
self.allRuns.append(run)
|
|
self.allRunIds[run.run] = run
|
|
self.allRunNames[run.name] = run
|
|
|
|
self.query('select * from bins')
|
|
for id,name in self.cursor.fetchall():
|
|
self.allBinIds[int(id)] = name
|
|
self.allBinNames[name] = int(id)
|
|
|
|
self.query('select sd_stat,sd_x,sd_y,sd_name,sd_descr from subdata')
|
|
for result in self.cursor.fetchall():
|
|
subdata = SubData(result)
|
|
if self.allSubData.has_key(subdata.stat):
|
|
self.allSubData[subdata.stat].append(subdata)
|
|
else:
|
|
self.allSubData[subdata.stat] = [ subdata ]
|
|
|
|
self.query('select * from formulas')
|
|
for id,formula in self.cursor.fetchall():
|
|
self.allFormulas[int(id)] = formula.tostring()
|
|
|
|
StatData.db = self
|
|
self.query('select * from stats')
|
|
import info
|
|
for result in self.cursor.fetchall():
|
|
stat = info.NewStat(StatData(result))
|
|
self.append(stat)
|
|
self.allStats.append(stat)
|
|
self.allStatIds[stat.stat] = stat
|
|
self.allStatNames[stat.name] = stat
|
|
|
|
# Name: listbins
|
|
# Desc: Prints all bins matching regex argument, if no argument
|
|
# is given all bins are returned
|
|
def listBins(self, regex='.*'):
|
|
print '%-50s %-10s' % ('bin name', 'id')
|
|
print '-' * 61
|
|
names = self.allBinNames.keys()
|
|
names.sort()
|
|
for name in names:
|
|
id = self.allBinNames[name]
|
|
print '%-50s %-10d' % (name, id)
|
|
|
|
# Name: listruns
|
|
# Desc: Prints all runs matching a given user, if no argument
|
|
# is given all runs are returned
|
|
def listRuns(self, user=None):
|
|
print '%-40s %-10s %-5s' % ('run name', 'user', 'id')
|
|
print '-' * 62
|
|
for run in self.allRuns:
|
|
if user == None or user == run.user:
|
|
print '%-40s %-10s %-10d' % (run.name, run.user, run.run)
|
|
|
|
# Name: listTicks
|
|
# Desc: Prints all samples for a given run
|
|
def listTicks(self, runs=None):
|
|
print "tick"
|
|
print "----------------------------------------"
|
|
sql = 'select distinct dt_tick from data where dt_stat=1180 and ('
|
|
if runs != None:
|
|
first = True
|
|
for run in runs:
|
|
if first:
|
|
# sql += ' where'
|
|
first = False
|
|
else:
|
|
sql += ' or'
|
|
sql += ' dt_run=%s' % run.run
|
|
sql += ')'
|
|
self.query(sql)
|
|
for r in self.cursor.fetchall():
|
|
print r[0]
|
|
|
|
# Name: retTicks
|
|
# Desc: Prints all samples for a given run
|
|
def retTicks(self, runs=None):
|
|
sql = 'select distinct dt_tick from data where dt_stat=1180 and ('
|
|
if runs != None:
|
|
first = True
|
|
for run in runs:
|
|
if first:
|
|
first = False
|
|
else:
|
|
sql += ' or'
|
|
sql += ' dt_run=%s' % run.run
|
|
sql += ')'
|
|
self.query(sql)
|
|
ret = []
|
|
for r in self.cursor.fetchall():
|
|
ret.append(r[0])
|
|
return ret
|
|
|
|
# Name: liststats
|
|
# Desc: Prints all statistics that appear in the database,
|
|
# the optional argument is a regular expression that can
|
|
# be used to prune the result set
|
|
def listStats(self, regex=None):
|
|
print '%-60s %-8s %-10s' % ('stat name', 'id', 'type')
|
|
print '-' * 80
|
|
|
|
rx = None
|
|
if regex != None:
|
|
rx = re.compile(regex)
|
|
|
|
stats = [ stat.name for stat in self.allStats ]
|
|
stats.sort(statcmp)
|
|
for stat in stats:
|
|
stat = self.allStatNames[stat]
|
|
if rx == None or rx.match(stat.name):
|
|
print '%-60s %-8s %-10s' % (stat.name, stat.stat, stat.type)
|
|
|
|
# Name: liststats
|
|
# Desc: Prints all statistics that appear in the database,
|
|
# the optional argument is a regular expression that can
|
|
# be used to prune the result set
|
|
def listFormulas(self, regex=None):
|
|
print '%-60s %s' % ('formula name', 'formula')
|
|
print '-' * 80
|
|
|
|
rx = None
|
|
if regex != None:
|
|
rx = re.compile(regex)
|
|
|
|
stats = [ stat.name for stat in self.allStats ]
|
|
stats.sort(statcmp)
|
|
for stat in stats:
|
|
stat = self.allStatNames[stat]
|
|
if stat.type == 'FORMULA' and (rx == None or rx.match(stat.name)):
|
|
print '%-60s %s' % (stat.name, self.allFormulas[stat.stat])
|
|
|
|
def getStat(self, stats):
|
|
if type(stats) is not list:
|
|
stats = [ stats ]
|
|
|
|
ret = []
|
|
for stat in stats:
|
|
if type(stat) is int:
|
|
ret.append(self.allStatIds[stat])
|
|
|
|
if type(stat) is str:
|
|
rx = re.compile(stat)
|
|
for stat in self.allStats:
|
|
if rx.match(stat.name):
|
|
ret.append(stat)
|
|
return ret
|
|
|
|
def getBin(self, bins):
|
|
if type(bins) is not list:
|
|
bins = [ bins ]
|
|
|
|
ret = []
|
|
for bin in bins:
|
|
if type(bin) is int:
|
|
ret.append(bin)
|
|
elif type(bin) is str:
|
|
ret.append(self.allBinNames[bin])
|
|
else:
|
|
for name,id in self.allBinNames.items():
|
|
if bin.match(name):
|
|
ret.append(id)
|
|
|
|
return ret
|
|
|
|
def getNotBin(self, bin):
|
|
map = {}
|
|
for bin in getBin(bin):
|
|
map[bin] = 1
|
|
|
|
ret = []
|
|
for bin in self.allBinIds.keys():
|
|
if not map.has_key(bin):
|
|
ret.append(bin)
|
|
|
|
return ret
|
|
|
|
#########################################
|
|
# get the data
|
|
#
|
|
def inner(self, op, stat, bins, ticks, group=False):
|
|
sql = 'select '
|
|
sql += 'dt_stat as stat, '
|
|
sql += 'dt_run as run, '
|
|
sql += 'dt_x as x, '
|
|
sql += 'dt_y as y, '
|
|
if group:
|
|
sql += 'dt_tick as tick, '
|
|
sql += '%s(dt_data) as data ' % op
|
|
sql += 'from data '
|
|
sql += 'where '
|
|
|
|
if isinstance(stat, list):
|
|
val = ' or '.join([ 'dt_stat=%d' % s.stat for s in stat ])
|
|
sql += ' (%s)' % val
|
|
else:
|
|
sql += ' dt_stat=%d' % stat.stat
|
|
|
|
if self.runs != None and len(self.runs):
|
|
val = ' or '.join([ 'dt_run=%d' % r for r in self.runs ])
|
|
sql += ' and (%s)' % val
|
|
|
|
if bins != None and len(bins):
|
|
val = ' or '.join([ 'dt_bin=%d' % b for b in bins ])
|
|
sql += ' and (%s)' % val
|
|
|
|
if ticks != None and len(ticks):
|
|
val = ' or '.join([ 'dt_tick=%d' % s for s in ticks ])
|
|
sql += ' and (%s)' % val
|
|
|
|
sql += ' group by dt_stat,dt_run,dt_x,dt_y'
|
|
if group:
|
|
sql += ',dt_tick'
|
|
return sql
|
|
|
|
def outer(self, op_out, op_in, stat, bins, ticks):
|
|
sql = self.inner(op_in, stat, bins, ticks, True)
|
|
sql = 'select stat,run,x,y,%s(data) from (%s) as tb ' % (op_out, sql)
|
|
sql += 'group by stat,run,x,y'
|
|
return sql
|
|
|
|
# Name: sum
|
|
# Desc: given a run, a stat and an array of samples and bins,
|
|
# sum all the bins and then get the standard deviation of the
|
|
# samples for non-binned runs. This will just return the average
|
|
# of samples, however a bin array still must be passed
|
|
def sum(self, stat, bins, ticks):
|
|
return self.inner('sum', stat, bins, ticks)
|
|
|
|
# Name: avg
|
|
# Desc: given a run, a stat and an array of samples and bins,
|
|
# sum all the bins and then average the samples for non-binned
|
|
# runs this will just return the average of samples, however
|
|
# a bin array still must be passed
|
|
def avg(self, stat, bins, ticks):
|
|
return self.outer('avg', 'sum', stat, bins, ticks)
|
|
|
|
# Name: stdev
|
|
# Desc: given a run, a stat and an array of samples and bins,
|
|
# sum all the bins and then get the standard deviation of the
|
|
# samples for non-binned runs. This will just return the average
|
|
# of samples, however a bin array still must be passed
|
|
def stdev(self, stat, bins, ticks):
|
|
return self.outer('stddev', 'sum', stat, bins, ticks)
|
|
|
|
def __getattribute__(self, attr):
|
|
if attr != 'get':
|
|
return super(Database, self).__getattribute__(attr)
|
|
|
|
if self.__dict__['get'] == type(self).sum:
|
|
return 'sum'
|
|
elif self.__dict__['get'] == type(self).avg:
|
|
return 'avg'
|
|
elif self.__dict__['get'] == type(self).stdev:
|
|
return 'stdev'
|
|
else:
|
|
return ''
|
|
|
|
def __setattr__(self, attr, value):
|
|
if attr != 'get':
|
|
super(Database, self).__setattr__(attr, value)
|
|
return
|
|
|
|
if value == 'sum':
|
|
self.__dict__['get'] = type(self).sum
|
|
elif value == 'avg':
|
|
self.__dict__['get'] = type(self).avg
|
|
elif value == 'stdev':
|
|
self.__dict__['get'] = type(self).stdev
|
|
else:
|
|
raise AttributeError, "can only set get to: sum | avg | stdev"
|
|
|
|
def data(self, stat, bins=None, ticks=None):
|
|
if bins is None:
|
|
bins = self.bins
|
|
if ticks is None:
|
|
ticks = self.ticks
|
|
sql = self.__dict__['get'](self, stat, bins, ticks)
|
|
self.query(sql)
|
|
|
|
runs = {}
|
|
for x in self.cursor.fetchall():
|
|
data = Data(x)
|
|
if not runs.has_key(data.run):
|
|
runs[data.run] = {}
|
|
if not runs[data.run].has_key(data.x):
|
|
runs[data.run][data.x] = {}
|
|
|
|
runs[data.run][data.x][data.y] = data.data
|
|
return runs
|
|
|
|
def __getitem__(self, key):
|
|
return self.stattop[key]
|