rcReportGen.py

Go to the documentation of this file.
00001 #!/usr/local/bin/python
00002 #
00003 #                               Copyright 2005
00004 #                                     by
00005 #                        The Board of Trustees of the
00006 #                     Leland Stanford Junior University.
00007 #                            All rights reserved.
00008 #
00009 
00010 __facility__ = "Online"
00011 __abstract__ = "Run report generator class for the ScriptEngine"
00012 __author__   = "S. Tuvi <stuvi@SLAC.Stanford.edu> SLAC - GLAST LAT I&T/Online"
00013 __date__     = "2005/07/23 00:08:27"
00014 __updated__  = "$Date: 2006/04/28 01:41:21 $"
00015 __version__  = "$Revision: 1.10 $"
00016 __release__  = "$Name: HEAD $"
00017 __credits__  = "SLAC"
00018 
00019 import LICOS.copyright_SLAC
00020 
00021 import os
00022 import logging as log
00023 import time
00024 import calendar
00025 import cStringIO
00026 import MySQLdb
00027 
00028 from xml.sax.saxutils  import XMLGenerator
00029 from xml.parsers.expat import ParserCreate
00030 
00031 UNITS_INT    = 0
00032 UNITS_LONG   = 1
00033 UNITS_FLOAT  = 2
00034 UNITS_STRING = 3
00035 UNITS_LIST   = 4
00036 UNITS_DICT   = 5
00037 
00038 UNIT_MAP = {
00039             'OperatorId'      : UNITS_LONG,
00040             'EventCount'      : UNITS_LONG,
00041             'BadEventCount'   : UNITS_LONG,
00042             'ErrorEventCount' : UNITS_LONG,
00043             'PauseCount'      : UNITS_INT,
00044             'ElapsedTime'     : UNITS_FLOAT,
00045             'CompletionStatus': UNITS_INT,
00046            }
00047 
00048 # Shift Dictionary
00049 SHIFT_DICT =  { 0: ("T00:00:00", "T08:00:00", "Owl"),
00050                 1: ("T08:00:00", "T16:00:00", "Day"),
00051                 2: ("T16:00:00", "T00:00:00", "Swing")
00052               }
00053 
00054 class rcReportGen(object):
00055   """!\brief Run report generator class for the ScriptEngine.
00056 
00057   """
00058   def __init__(self, fileName="rcReport.out", prefs=None):
00059     """!\brief rcReportGen constructor.
00060 
00061     \param fileName Run report file name
00062     \param prefs    Preferences instance
00063 
00064     """
00065     self.__fileName  = fileName
00066     self.__prefs     = prefs
00067     if prefs is not None:
00068       self.__elognbl   = int(prefs["elognbl"])
00069     else:
00070       self.__elognbl   = 0
00071 
00072 
00073   def initialize(self, tstamp=None):
00074     """!\brief Initialize the run report generator.
00075 
00076     \param tstamp Timestamp of the run
00077     """
00078     self.__tstamp = tstamp
00079     self.__repFields = []
00080     self.__repData = []
00081     self.__repUnits = []
00082     if self.__elognbl:
00083       if 'ELOGBOOK_DB' in os.environ:
00084         dbname = os.environ['ELOGBOOK_DB']
00085       else:
00086         dbname = 'elogbook'
00087       if not self.connectDB(dbname):
00088         log.error("Error opening database %s"
00089                   " disabling Elogbook integration" % dbname)
00090         log.error(str(self.__db.lastError().text()))
00091         self.__elognbl = 0
00092       else:
00093         log.debug("Database %s opened successfully" % dbname)
00094 
00095   def connectDB(self, dbname):
00096     """!\brief Connect to the database.
00097 
00098     \return True if connect was successful, False otherwise.
00099     """
00100     if 'ELOGBOOK_HOST' in os.environ:
00101       host = os.environ['ELOGBOOK_HOST']
00102     else:
00103       host = 'localhost'
00104 
00105     try:
00106       self.__db = MySQLdb.connect(host   = host,
00107                                   db     = dbname,
00108                                   user   = 'elogbook',
00109                                   passwd = 'elogbook',
00110                                   port   = 3306)
00111     except MySQLdb.OperationalError, e:
00112       msg  = "Error encountered when attempting to connect to MySQL server on host: '%s'\n" % host
00113       msg += "  Actual exception was: %s" % e
00114       log.exception(MySQLdb.OperationalError(msg))
00115       return False
00116     return True
00117 
00118   def disconnectDB(self):
00119     """!\brief Disconnect from the database
00120     """
00121     self.__db.commit()
00122     return self.__db.close()
00123 
00124   def addField(self, field, data, units=UNITS_STRING):
00125     """!\brief Add \a field to the run report.
00126 
00127     \param field Name of the run report item
00128     \param data  Value of the run report item
00129     \param units Run report item unit
00130     """
00131     if field == 'TimeStamp':
00132       self.__tstamp = data
00133     else:
00134       self.__repFields.append(field)
00135       self.__repData.append(data)
00136       self.__repUnits.append(units)
00137 
00138   def writeReportCSV(self):
00139     """!\brief Write the run report as a comma delimited file.
00140 
00141     """
00142     if not os.path.exists(self.__fileName):
00143       f = file(self.__fileName, 'w+')
00144       i = 0
00145       for header in self.__repFields:
00146         f.write(header)
00147         i = i + 1
00148         if i < len(self.__repFields):
00149           f.write(',')
00150       f.write('\n')
00151 
00152     f = file(self.__fileName, 'a+')
00153     i = 0
00154     for data in self.__repData:
00155       f.write('"'+data+'"')
00156       i = i + 1
00157       if i < len(self.__repData):
00158         f.write(',')
00159     f.write('\n')
00160     f.close()
00161 
00162   def writeReportXML(self):
00163     """!\brief Write the run report as an XML.
00164 
00165     """
00166     if not os.path.exists(self.__fileName):
00167       f = file(self.__fileName, 'w+')
00168       newReportFile = 1
00169     else:
00170       f = file(self.__fileName, 'a+')
00171       newReportFile = 0
00172 
00173     result = cStringIO.StringIO()
00174     gen = XMLGenerator(result)
00175     if newReportFile:
00176       gen.startDocument()
00177     if self.__tstamp is None:
00178       self.__tstamp = time.gmtime()
00179     self.__tstamp = str(self.__tstamp)
00180     gen.startElement("testReport", {'timestamp': self.__tstamp})
00181     i = 0
00182     for header in self.__repFields:
00183       gen.startElement(header,{})
00184       gen.characters(self.__repData[i])
00185       gen.endElement(header)
00186       i = i + 1
00187     gen.endElement("testReport")
00188     if newReportFile:
00189       gen.endDocument()
00190     f.write(result.getvalue()+'\n')
00191     f.close()
00192     if self.__elognbl:
00193       if self.writeReportSQL() == 0:
00194         self.addShift(self.__tstamp)
00195       self.disconnectDB()
00196 
00197   def writeReportSQL(self):
00198     """!\brief Write the run report to a relational database.
00199 
00200     """
00201     fieldMap = {'RunId': 'RunID',
00202                 'OperatorId': 'OperatorID'
00203                }
00204 
00205     dateFields = ['TimeStamp', 'StartTime', 'EndTime']
00206 
00207     self.__repFields.append('TimeStamp')
00208     self.__repData.append(self.__tstamp)
00209     table = "elogreport"
00210     recInfo = self.recordInfo(table)
00211     fields = [f.name for f in recInfo]
00212     cur = self.__db.cursor()
00213     additional = ""
00214     sqlFields = ()
00215     sqlValues = ()
00216     for i in range(len(self.__repFields)):
00217       field = self.__repFields[i]
00218       if field in fieldMap:
00219         field = fieldMap[field]
00220       if field in dateFields:
00221         t = eval(self.__repData[i])
00222         value = "%04d-%02d-%02d %02d:%02d:%02d" % (t[0], t[1], t[2], t[3], t[4], t[5])
00223       else:
00224         value = self.__repData[i]
00225       if field in fields:
00226         sqlFields += (field,)
00227         sqlValues += (value,)
00228       else:
00229         # Tue Mar 15 14:05:15 2005: RiC debug log commented out.
00230         #log.debug("Can not find field %s in table %s. Adding it to the 'Additional Fields' column."
00231         #         % (field, table))
00232         additional = additional + field + "???" + value + "!!!"
00233     if additional != "":
00234       sqlFields += ("AdditionFields",)
00235       sqlValues += (additional,)
00236 
00237     sqlFields = ", ".join([item for item in sqlFields])
00238     insertStr = "insert into %s(%s) values %s" % (table, sqlFields, str(sqlValues))
00239     if cur.execute(insertStr) != 1:
00240       log.error("Could not insert elogreport record, dumping field data:")
00241       for i in range(len(self.__repFields)):
00242         log.error("\t%s\t\t\t%s" % (self.__repFields[i], self.__repData[i]))
00243       log.error("SQL Error text: %s", str(cur.lastError().text()))
00244       result = -1
00245     else:
00246       result = 0
00247       self.__db.commit()
00248     cur.close()
00249     return result
00250 
00251   def recordInfo(self, table):
00252     recordInfo = ()
00253     c = self.__db.cursor()
00254     c.execute("show columns from " + table)
00255     while (True):
00256       row = c.fetchone()
00257       if row is None: break
00258       recordInfo += (FieldInfo(row),)
00259     c.close()
00260     return recordInfo
00261 
00262   def readReportXML(self):
00263     """!\brief Read and parse the XML run report.
00264 
00265     """
00266     # 3 handler functions
00267     def start_element(name, attrs):
00268       #print 'Start element:', str(name), attrs
00269       self.__data = ''
00270       if str(name) == 'testReport':
00271         self.__tstamp = str(attrs['timestamp'])
00272         self.__repFields.append('TimeStamp')
00273         self.__repData.append(self.__tstamp)
00274         self.__repUnits.append(UNITS_STRING)
00275       else:
00276         self.__repFields.append(str(name))
00277     def end_element(name):
00278       #print 'End element:', str(name)
00279       name = str(name)
00280       if name != 'testReport':
00281         self.__repData.append(self.__data)
00282         if name in UNIT_MAP:
00283           self.__repUnits.append(UNIT_MAP[name])
00284         else:
00285           self.__repUnits.append(UNITS_STRING)
00286     def char_data(data):
00287       #print 'Character data:', str(data)
00288       if self.__data is not None:
00289         self.__data += str(data)
00290     def external_entity_ref_handler(context,base,systemId,publicId):
00291       subParser = p.ExternalEntityParserCreate(context)
00292       subParser.StartElementHandler = start_element
00293       subParser.EndElementHandler = end_element
00294       subParser.CharacterDataHandler = char_data
00295       subParser.ParseFile(file(systemId))
00296       return 1
00297     self.__data = None
00298     p = ParserCreate()
00299     p.ExternalEntityRefHandler = external_entity_ref_handler
00300     p.Parse("""<!DOCTYPE report [<!ENTITY reportfile SYSTEM \""""+self.__fileName+"""\">
00301             ]><report>&reportfile;</report>""")
00302 
00303   def getReportFields(self):
00304     """!\brief Retrieve the run report item names.
00305 
00306     \return A list containing run report item names.
00307     """
00308     return self.__repFields
00309 
00310   def getReportData(self):
00311     """!\brief Retrieve the run report item values.
00312 
00313     \return A list containing run report item values.
00314     """
00315     return self.__repData
00316 
00317   def getReportUnits(self):
00318     """!\brief Retrieve the run report item units.
00319 
00320     \return A list containing run report item units.
00321     """
00322     return self.__repUnits
00323 
00324   def getTimestamp(self):
00325     """!\brief Retrieve the timestamp of the run.
00326 
00327     \return Timestamp of the run.
00328     """
00329     return self.__tstamp
00330 
00331   def addShift(self,tstamp):
00332     """!\brief Add a shift summary to the database if it doesn't already exist.
00333 
00334     This method finds the shift id that the run belongs to based on \a tstamp
00335     and creates a shift summary if it doesn't already exist. This was done
00336     because the web e-logbook does not display the shift if it doesn't contain
00337     a shift summary even if it contains runs that belong to that shift.
00338 
00339     \param tstamp Timestamp of the run.
00340 
00341     \return True if the shift is created, None otherwise.
00342     """
00343     (shiftID, startLocal, endLocal, type) = self.getShiftInfo(eval(tstamp))
00344     timeStamp = self.convertTimeTuple(time.gmtime())
00345     # If there are no records, create a new one
00346     cur = self.__db.cursor()
00347     table = "elogshiftsummary"
00348     selectStr = "select ShiftID from %s where ShiftID = '%s'" % (table, shiftID)
00349     cur.execute(selectStr)
00350     row = cur.fetchone()
00351     if row is None:
00352       sqlFields = ("ShiftID", "ShiftType", "StartTime", "EndTime", "LastUpdate")
00353       sqlValues = (shiftID, type, startLocal, endLocal, timeStamp)
00354       sqlFields = ", ".join([item for item in sqlFields])
00355       insertStr = "insert into %s(%s) values %s" % (table, sqlFields, str(sqlValues))
00356       if cur.execute(insertStr) == 1:
00357         log.debug("rcReportGen: Added new shift id: %s, type: %s, startLocal: %s, endLocal: %s, timestamp: %s for run timestamp: %s"
00358                   % (shiftID, type, startLocal, endLocal, timeStamp, tstamp))
00359         self.__db.commit()
00360         result = True
00361       else:
00362         log.warn("rcReportGen: Unable to insert elogshiftsummary record")
00363         result = False
00364       cur.close()
00365       return result
00366     else:
00367       #print "Shift for run: ", tstamp, " already exists in the database."
00368       pass
00369 
00370 
00371   def getShiftInfo(self,t):
00372     """!\brief Find the shift information based on the timestamp \a t.
00373 
00374     \param t GMT timestamp of the run
00375 
00376     \return A tuple containing the shift id, start time, end time and type.
00377     """
00378     #print "Timestamp=", t
00379     timeStampLocal = self.obtainLocalTime(t)
00380     secsTimeStamp = calendar.timegm(t)
00381     timeStamp = timeStampLocal.split('T')
00382     date = timeStamp[0]
00383     #Obtain End Date (Local)
00384     startDate = calendar.timegm(t)
00385     endQDate = startDate + 3600 * 24
00386     endDate = self.convertTimeTuple(time.localtime(endQDate)).split('T')[0]
00387     # Date tag for ShiftID
00388     dateList = date.split('-')
00389     dateTag = dateList[0] + dateList[1] + dateList[2]
00390     # If run timeStamp is within shift obtain ShiftID
00391     for shift in SHIFT_DICT:
00392       (startT, endT, type) = SHIFT_DICT[shift]
00393       startLocal = date + startT
00394       if shift == 2:
00395         endLocal = endDate + endT
00396       else:
00397         endLocal = date + endT
00398       secsStartTime = calendar.timegm(self.obtainGMTime(startLocal))
00399       secsEndTime   = calendar.timegm(self.obtainGMTime(endLocal))
00400       # The Core Code:
00401       # secsTo Returns the number of seconds from this datetime1 to (datetime2)
00402       # which is negative if dt is earlier than this datetime).
00403       # Example: datetime1.secsTo(datetime2) > 0 if datetime2 is later than datetime1
00404       secsToStartTime = secsTimeStamp - secsStartTime
00405       secsToEndTime   = secsTimeStamp - secsEndTime
00406       #print "Shift: ", shift, "Start: ", startLocal, " End: ", endLocal
00407       #print "Shift: ", shift, "Secs To Start: ", secsToStartTime, "Secs To End: ", secsToEndTime
00408       if secsToStartTime >= 0 and secsToEndTime <= 0:
00409         shiftID    = dateTag + str(shift)
00410         return (shiftID, startLocal, endLocal, type)
00411 
00412   def obtainLocalTime(self, dateTime):
00413     """!\brief Convert a GMT date/time to local date/time.
00414 
00415     \param dateTime GMT date/time tuple
00416 
00417     \return Local date/time in SQL format
00418     """
00419     localTimeTuple = time.localtime(calendar.timegm(dateTime))
00420     localTime = self.convertTimeTuple(localTimeTuple)
00421     return localTime
00422 
00423   def obtainGMTime(self, dateTime):
00424     """!\brief Convert a local date/time to a GMT date/time.
00425 
00426     \param dateTime Local date/time value in SQL format
00427 
00428     \return GMT date/time tuple
00429     """
00430     splitTime = dateTime.split('T')
00431     dateTime = splitTime[0] + splitTime[1]
00432     timeStamp = time.strptime(dateTime, "%Y-%m-%d%H:%M:%S")
00433     gmTimeTuple = time.gmtime(time.mktime(timeStamp))
00434     return gmTimeTuple
00435 
00436   def convertTimeTuple(self, timeTuple):
00437     """!\brief Convert a time tuple to an SQL format.
00438 
00439     \brief A date/time tuple.
00440     """
00441     timeStamp =  "%04d" % timeTuple[0] + "-" + \
00442                  "%02d" % timeTuple[1] + "-" + \
00443                  "%02d" % timeTuple[2] + "T" + \
00444                  "%02d" % timeTuple[3] + ":" + \
00445                  "%02d" % timeTuple[4] + ":" + \
00446                  "%02d" % timeTuple[5]
00447     return timeStamp
00448 
00449 class FieldInfo(object):
00450   def __init__(self, row):
00451     self.name    = row[0]
00452     self.type    = row[1]
00453     self.null    = row[2]
00454     self.key     = row[3]
00455     self.default = row[4]
00456     self.extra   = row[5]

Generated on Thu Apr 27 20:52:43 2006 for LICOS L02-01-00 by doxygen 1.4.6-NO