00001
00002
00003
00004
00005
00006
00007
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
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
00230
00231
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
00267 def start_element(name, attrs):
00268
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
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
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
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
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
00379 timeStampLocal = self.obtainLocalTime(t)
00380 secsTimeStamp = calendar.timegm(t)
00381 timeStamp = timeStampLocal.split('T')
00382 date = timeStamp[0]
00383
00384 startDate = calendar.timegm(t)
00385 endQDate = startDate + 3600 * 24
00386 endDate = self.convertTimeTuple(time.localtime(endQDate)).split('T')[0]
00387
00388 dateList = date.split('-')
00389 dateTag = dateList[0] + dateList[1] + dateList[2]
00390
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
00401
00402
00403
00404 secsToStartTime = secsTimeStamp - secsStartTime
00405 secsToEndTime = secsTimeStamp - secsEndTime
00406
00407
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]