import os,re,string, struct, tkMessageBox, zipfile from time import * from Tkinter import * """ DSpython monitoring program for the D2OL program by Sengent, Inc. Copyright (C) 2003 S. Michael Last updated 17 Nov 2003 The author can be reached at http://forum.sengent.com/cgi-bin/ultimatebb.cgi under the nickname of malör. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. To received a copy of the GNU General Public License write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ #Global Settings timedif = mktime((1970,1,1,0,0,0,0,0,-1)) - timezone #check to make sure time starts at 1970 #replace the previous line with timedif = 0 if it gives you trouble if os.name == "mac": #Macintosh settings #macs must use : for path seperator, needs to end in the res folder D2OLDIR = ":" #run the script from inside the res folder #doesn't seem to work if not run from inside the res folder #D2OLDIR = "Applications:CommunityTSC.app:Contents:MacOS:res:" #D2OLDIR = "Applications:SengentD2OL.app:Contents:MacOS:res:" txtFont = ("courier", 10, "bold") elif os.name == "posix": #Linux settings #D2OLDir = "CommunityTSC/TSC/res/" #must end in /res/ D2OLDir = "SengentD2OL/D2OL/res/" #must end in /res/ txtFont = ("Terminal [DEC]", 12, "normal") else: #default to Windows settings #D2OLDir = "/Program Files/CommunityTSC/TSC/res/" #must end in /res/ D2OLDir = "/Program Files/SengentD2OL/D2OL/res/" #must end in /res/ txtFont = ("courier", 8, "bold") logName = "dspython.log" milTime = False #24-hour clock refreshRate = 5000 #in ms if re.search("TSC",D2OLDir): #first two colors are for the average lines, the rest are the target colors. colors = ("#C800F0","#41DCFF","#E6E6D2","#B4B478","#91A5D2","#F0BE6E","#FF6600","#996600","#FFD700") #TSC graph colors else: colors = colors = ("#C800F0","#41DCFF","#5D2909","#FF6600","#996600","#FFD700","#F0BE6E","#A52A2A","#FFFF99","#FFA500","#DEB887","#D2691E","#A0522D") #D2OL graph colors days = 14 #default number of days shown on the graph graphWidth = 450 graphHeight = 350 #global variables outputPath = D2OLDir + "data" + os.sep + "gui" + os.sep + "output" + os.sep + "20.pdb" taskdataPath = D2OLDir + "data" + os.sep + "taskdata" + os.sep statePath = D2OLDir + "taskState.xml" statsPath = D2OLDir + "nodeStatistics.xml" currentPath = D2OLDir + "data" + os.sep + "current" + os.sep ligandPath = D2OLDir + "data" + os.sep + "gui" + os.sep + "ligand"+ os.sep + "ligand.pdbq" sharedPath = D2OLDir + "data" + os.sep + "shared" + os.sep + "structs" months = {"Jan" : 1, "Feb" : 2, "Mar" : 3, "Apr" : 4, "May" : 5, "Jun" : 6, "Jul" : 7, "Aug" : 8, "Sep" : 9, "Oct" : 10, "Nov" : 11, "Dec" : 12} def GetNum (pat,string): """Search a string for the pattern and return converted to a long.""" mat = re.search(pat,string) if (mat): return long(mat.group(1)) else: return 0 def StrTime(stime, seconds=False): """Convert the time to formated string""" global milTime ptm = localtime(stime) if (seconds): if (milTime): return strftime("%d %b %Y %H:%M:%S",ptm) else: return strftime("%d %b %Y %I:%M:%S %p",ptm) else: if (milTime): return strftime("%d %b %Y %H:%M",ptm) else: return strftime("%d %b %Y %I:%M %p",ptm) def ElapsedTime(time1,time2,blank=False): """Return the time elapsed in hours,minutes, and second""" eTime = time2-time1 if eTime < 1: if blank: return " " else: return "00:00:00" h = int(eTime/3600) m = int((eTime%3600)/60) s = eTime%60 return "%02i:%02i:%02i" % (h,m,s) class Results (Frame): lastResultUploadedTime = lastTaskDownlodedTime = startTime = 0 tasksCompleted = completedQueued = tasksQueued = numTasks = 0 target = candidate = resultPath = gridPath = targetType = "" numAtoms = numTorsions = 0 lastLogged = "" statsTime = 0 grids = elements = 0 docking = False sTime = lTime = rTime = gTime = dTime = meanTime = 0 conformers = 0 nodeID = 0 tlist = {} plist = {} tasklist = "" def __init__(self, master=None): Frame.__init__(self, master) self.master.title("DSpython") self.pack(side=LEFT,fill=BOTH, expand=YES) if (not os.path.exists(statePath)): tkMessageBox.showerror("Error","Please set the D2OLDir variable in the script correctly.") self.master.destroy() return self.CreateWidgets() self.FullUpdate() self.GetUpdate() #needed to start timer def CreateWidgets(self): self.text = Text(self,font = txtFont,height=20, width = 76) self.text.insert(END,"\n\n\n\n\n\n\n\n\n\n\n") self.text.pack(side=LEFT,fill=BOTH, expand=YES) #self.text.bind("", self.FullUpdate) #do a full update on right mouse click self.ListTypes() #add menus menu = Menu(self) self.master.config(menu=menu) filemenu = Menu(menu) menu.add_cascade(label="File", menu=filemenu) filemenu.add_command(label="Task List", command=self.ShowTasklist) filemenu.add_command(label="Graph", command=self.ShowGraph) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.master.destroy) helpmenu = Menu(menu) menu.add_cascade(label="Help", menu=helpmenu) helpmenu.add_command(label="About...", command=self.About) #make tasklist window self.tasklistWindow = Toplevel() self.tasklistWindow.title("Tasklist") scrollbar = Scrollbar(self.tasklistWindow) scrollbar.pack(side=RIGHT, fill=Y) self.taskText = Text(self.tasklistWindow,font = txtFont,width=36,yscrollcommand=scrollbar.set) self.taskText.pack(side=LEFT, fill=BOTH, expand=YES) scrollbar.config(command=self.taskText.yview) self.tasklistWindow.protocol("WM_DELETE_WINDOW", self.CloseTasklist) self.tasklistWindow.withdraw() self.graphWindow = Toplevel() self.graphWindow.title("Graph") self.graph = Canvas(self.graphWindow,width=graphWidth,height=graphHeight) self.graph.pack(expand=YES,fill=BOTH) self.graph.bind("",self.ResizeGraph) self.graphWindow.protocol("WM_DELETE_WINDOW", self.CloseGraph) self.graphWindow.withdraw() self.graphWindow.bind("", self.DecreaseDays) self.graphWindow.bind("", self.IncreaseDays) def About(self): tkMessageBox.showinfo("About","DSpython\nby malör\nLast updated: 17 Nov 2003") def ResizeGraph(self,event): global graphWidth, graphHeight graphWidth = event.width graphHeight = event.height self.DrawGraph() def DecreaseDays(self,event): """Decrease the number of days shown on the graph""" global days days = max(1,days-1) self.DrawGraph() def IncreaseDays(self,event): """Increase the number of days shown on the graph""" global days days += 1 self.DrawGraph() def ListTypes(self): """Find the english name for different 4 letter type codes""" self.types = {} for filename in os.listdir(sharedPath): mat = re.search("(\w+).zip",filename) if mat: #print filename zfile = zipfile.ZipFile(sharedPath + os.sep + filename, "r") txt = zfile.read(mat.group(1)+".pdbqs") zfile.close() mat =re.search("^(.+)\n",txt) target = mat.group(1) #print target target = re.sub("REMARK |\r|TSC Target.*- | Lethal Factor| Target( I$)?","",target) #shorten the target names #print target self.types[filename[:4]] = target #print self.types def ShowGraph(self): self.DrawGraph() self.graphWindow.deiconify() def ShowTasklist(self): self.taskCount = 0 self.ListTaskDir(taskdataPath) self.taskText.delete(1.0,END) self.taskText.insert(END,self.tasklist) self.tasklistWindow.deiconify() def CloseTasklist(self): self.tasklistWindow.withdraw() def CloseGraph(self): self.graphWindow.withdraw() def FullUpdate(self,args=None): self.docklen = 0 self.meanTime = 0 self.GetInfo(True) self.GetUpdate(False) def GetUpdate(self, timed=True): """Timed loop that is responsible for updating the display information.""" if timed: self.timmer = self.after(refreshRate, self.GetUpdate) self.text.config(state=NORMAL) if (self.docking): self.ReadOutputFile() else: self.ReadGridFiles() if (os.path.exists(statePath)): if (os.stat(statePath)[8] > self.statsTime): #print "%s\tStats file changed" % (StrTime(time(),True)) #check to see if a new candidate needs to be found because the D2OL program was restarted if (time() - os.stat(ligandPath)[8]) < 20 and (self.conformers < 19 or (os.path.exists(self.resultPath) and (time() - os.stat(self.resultPath)[8]) > 20)): #if new candidate and weren't about the finish the old one print "Forced Update" self.elements = 0 self.rTime = 0 self.GetInfo(True) else: self.GetInfo(False) if ((self.sTime+20) < self.startTime < (time()-20)): #check if the task was suspended and restarted print "start time mismatch" self.docking = False self.GetInfo(True) te = ElapsedTime(self.lTime,time()) tt = ElapsedTime(self.sTime,time()) tr = ElapsedTime(0,self.rTime,True) self.text.delete(8.0,9.0) self.text.insert(7.0, "\nTime Elapsed: %s Total: %s Remaining: %s" % (te,tt,tr)) #self.text.config(state=DISABLED) def FindCandidate(self): """Finds the candidate D2OL is currently working on.""" self.startTime = os.stat(ligandPath)[8] ligand = open(ligandPath) self.numTorsions = GetNum("(\d+) active torsions",ligand.readline()) list = re.findall("ATOM\s+(\d+)",ligand.read()) ligand.close() if (list): self.numAtoms = list.pop() else: self.numAtoms = 0 for filename in os.listdir(currentPath): mat = re.search("([^\.]+)\.(\w{4})\.dpf",filename) if mat: self.resultPath = currentPath + filename[:-4] + ".dlg" self.gridPath = currentPath + "grid.out" type = mat.group(2) self.elementPath = currentPath + type + ".e.map" self.candidate = mat.group(1) self.targetType = type self.target = self.types[type] self.startTime = os.stat(currentPath + type + ".pdbqs")[8] #self.taskText.delete(1.0,END) #self.taskText.insert(END,self.tasklist) #if find: #print "%s\tGetInfo\t%s\t%s\t%s\t%s" % (StrTime(time(),True), self.candidate, self.target, self.numAtoms, self.numTorsions) def GetInfo(self,find): """Read basic information from stats file.""" self.statsTime = time() stats = open(statePath) statsData = stats.read() stats.close() self.lastResultUploadedTime = os.stat(statsPath)[8] self.numTasks = GetNum("(\d+)",statsData) self.tasksQueued = GetNum("(\d+)",statsData) self.completedQueued = GetNum("(\d+)",statsData) stats = open(statsPath) statsData = stats.read() stats.close() self.tasksCompleted = GetNum("(\d+)",statsData) self.FindCandidate() #updates the number of tasks queued self.DrawInfo() def DrawInfo(self): """Draw the basic heading info.""" self.text.delete(1.0, 7.0) self.text.insert(1.0, "\n\n\n\n\n") # self.text.insert(1.0, "Node ID: %d\n" % self.nodeID) self.text.insert(2.0, "%-16s %-23s %-16s %d (%d)" % ("Last Uploaded:",StrTime(self.lastResultUploadedTime),"Completed:", self.tasksCompleted, self.completedQueued)) self.text.insert(3.0, "%-16s %-23s %-16s %d of %d" % ("Start Time:",StrTime(self.startTime),"Tasks Queued:", self.tasksQueued, self.numTasks)) self.text.insert(4.0, "%-16s %-23s %-16s %s" % ("Candidate:", self.candidate,"Target:", self.target)) self.text.insert(5.0, "%-16s %-23s %-16s %s" % ("Number of Atoms:",self.numAtoms,"Rotatable Bonds:", self.numTorsions)) def ReadGridFiles(self): """Find progress of computing the docking grid.""" self.text.delete(8.0,END) self.text.insert(END,"\n\n\n") if (self.elements == 0): #check to see if number of elements has already been read self.sTime = self.lTime = self.startTime if (os.path.exists(self.gridPath)): gridFile = open(self.gridPath) gridFile.seek(-1024,2) gridData = gridFile.read() mat = re.search("Calculating (\d+) grids over (\d+) elements", gridData) if (mat): self.grids = long(mat.group(1)) self.elements = long(mat.group(2)) else: return else: self.sTime = self.lTime = time() #reset elapsed times self.GetInfo(True) return if (os.path.exists(self.resultPath) and os.stat(self.resultPath)[8] > self.startTime): #check for result file self.gTime = self.sTime = self.lTime = os.stat(self.elementPath)[8] self.meanTime = 0 self.docklen = 0 self.docking = True #print StrTime(time(),True),"\tStart reading docking results" self.ReadOutputFile() return if (not os.path.exists(self.elementPath)): self.GetInfo(True) else: info = os.stat(self.elementPath) size = info[6] if (size > 10000): #wait until the file is large enough to make a decent estimate on the time remaining e = long(time() - self.startTime) self.rTime = long(e*self.elements*8/size) - e else: self.rTime = 0 self.text.insert(10.0,"Calculating %d grids over %d elements" % (self.grids, self.elements)) def ReadOutputFile(self): """Read the outputfile and calculate the times and energy values.""" if (not os.path.exists(self.resultPath)): #File not found if (os.path.exists(outputPath)): #get the completion time from the 20.pdb file self.fTime = os.stat(outputPath)[8] self.LogResults() self.docking = False self.conformers = 0 self.elements = 0 self.rTime = 0 #print StrTime(time(),True),"\tStart reading grid files" # self.GetInfo(True) self.ReadGridFiles() self.DrawGraph() return resultfile = open(self.resultPath,"rb") resultData = resultfile.read() resultfile.close() length = len(resultData) if (length > self.docklen): self.docklen = length lTime = GetNum("current time, value = (\d+)",resultData) self.energies = re.findall("Final Docked Energy\s+=\s+(.*?) kcal/mol",resultData) self.text.delete(8.0,END) self.text.insert(END,"\n\n\n") cnt = 0 if self.energies: #all least 1 conformer done dates = re.findall("Date:\s+\w{3} +([^\r\n]+)",resultData) datepat = re.compile("(\w{3}) +(\d+) +(\d+):(\d+):(\d+) +(\d+)") mat = re.match(datepat,dates[cnt]) sTime =lTime = mktime((long(mat.group(6)),months[mat.group(1)],long(mat.group(2)),long(mat.group(3)),long(mat.group(4)),long(mat.group(5)),0,0,-1)) self.best = self.worst = float(self.energies[0]) tlist = [] for energy in self.energies: e = float(energy) if e < self.best: self.best = e elif e > self.worst: self.worst = e if (cnt < 19): mat = re.match(datepat,dates[cnt+1]) cTime = mktime((long(mat.group(6)),months[mat.group(1)],long(mat.group(2)),long(mat.group(3)),long(mat.group(4)),long(mat.group(5)),0,0,-1)) else: cTime = time() eTime = cTime-lTime tlist.append(eTime) m = int((eTime%3600)/60) s = eTime%60 #self.text.insert(END, "%2i: %10s %02i:%02i\n" % (cnt+1, energy,m,s)) if cnt < 10: self.text.insert("%d.%d" % (10+cnt, 0), "%2i: %10s %02i:%02i\n" % (cnt+1, energy,m,s)) else: self.text.insert("%d.%d" % (cnt, 40), " %2i: %10s %02i:%02i" % (cnt+1, energy,m,s)) lTime = cTime cnt +=1 self.sTime = sTime self.lTime = lTime tlist.sort() i = int(cnt/2) if cnt%2: #odd number of times self.meanTime = tlist[i] else: #even number of times self.meanTime = (tlist[i-1]+tlist[i])/2 self.conformers = cnt if cnt < 10: self.text.insert("%d.%d" % (10+cnt, 0), "%2i: Docking...\n" % (cnt+1)) elif cnt < 20: self.text.insert("%d.%d" % (cnt, 40), " %2i: Docking..." % (cnt+1)) self.rTime = (20-self.conformers)*self.meanTime - min(time() - self.lTime, self.meanTime) def LogResults(self): #print StrTime(time(),True),"\tLogResults:", self.conformers """Write the results to a log file""" logFile = open(logName, "a") tt = ElapsedTime(self.startTime,self.fTime) # total time td = ElapsedTime(self.sTime,self.fTime) # docking time tg = ElapsedTime(self.startTime,self.gTime) # grid time if (self.best < -100000 or self.best > 100000 or self.worst < -100000 or self.worst > 100000): #use scientific notation if very large or small number logFile.write("%s\t%i\t%s\t%s\t%s\t%s\t%+.2e\t%+.2e\t%s\t%d\t%d\t%s\t%s\n" % (StrTime(self.fTime,True),self.nodeID,self.candidate,self.target,self.numAtoms,self.numTorsions,self.best,self.worst,td,self.grids,self.elements,tg,tt)) else: logFile.write("%s\t%i\t%s\t%s\t%s\t%s\t%+.2f\t%+.2f\t%s\t%d\t%d\t%s\t%s\n" % (StrTime(self.fTime,True),self.nodeID,self.candidate,self.target,self.numAtoms,self.numTorsions,self.best,self.worst,td,self.grids,self.elements,tg,tt)) logFile.close() self.lastLogged = self.candidate def DrawGraph(self): try: logFile = open(logName, "r") except: tkMessageBox.showwarning("Open file","Cannot open this file\n(%s)" % logName) return line = logFile.readline() tcount = {} #total count pcount = {} #time period count dcount = [None] * days #daily count color = {} targets = dict(zip(self.types.values(), self.types.keys())).keys() #eliminate multiple targets with the same name targets.sort() i = 0 for type in targets: tcount[type] = 0 pcount[type] = 0 color[type] = colors[i+2] i+=1 for i in range(days): dcount[i] = {} for type in targets: dcount[i][type] = 0 ctime = long(time()) date = localtime(time()) stime = ctime - (days-1)*86400 - date[3]*3600 - date[4]*60 - date[5] oldest = time() latest = 0 gcount = ht = mt = st = 0 logpat = re.compile("(\d+) (\w+) (\d+) +(\d+):(\d+):(\d+) ?(AM|PM)?\t(\d+)\t\S+\t([^\t]+)\t.*?(\d+):(\d+):(\d+)[\r\n]") while (line): #read the log file a line at a time mat = re.match(logpat,line) if mat and (self.nodeID == 0 or long(mat.group(8))== self.nodeID): hour = long(mat.group(4)) if mat.group(7) == "PM": hour = hour%12 + 12 elif hour == 12 and mat.group(7) == "AM": hour = 0 ttime = long(mktime((long(mat.group(3)),months[mat.group(2)],long(mat.group(1)),hour,long(mat.group(5)),long(mat.group(6)),0,0,-1))) if ttime < oldest: oldest = ttime-long(mat.group(10))*3600-long(mat.group(11))*60-long(mat.group(12)) elif ttime > latest: latest = ttime if tcount.has_key(mat.group(9)): gcount += 1 ht += long(mat.group(10)) mt += long(mat.group(11)) st += long(mat.group(12)) tcount[mat.group(9)] +=1 if (ttime > stime): day = (ttime - stime) / 86400 dcount[day][mat.group(9)] +=1 line = logFile.readline() logFile.close() if gcount == 0: return #no logged results if latest == 0: latest = ttime # only 1 logged result self.graph.delete(ALL) # clear graph most = 0 for i in range(days): # count most results in a day and totals dcount[i]["total"] = 0 for type in targets: pcount[type] += dcount[i][type] dcount[i]["total"] += dcount[i][type] if dcount[i]["total"] > most: most = dcount[i]["total"] pcount["total"] = 0 tcount["total"] = 0 for type in targets: pcount["total"] += pcount[type] tcount["total"] += tcount[type] divisions = 4 # number of dividing lines on the graph most = max(divisions,(most+divisions-1)/divisions*divisions) #setup graph dimensions h = graphHeight - (len(targets)+4)*16 w = graphWidth - 50 x = 30 y = h+10 bottom = y inc = most/divisions total = 0 #draw the graph frame for i in range(divisions+1): self.graph.create_text(x-5,bottom,anchor=E,text="%2d" % (i*inc)) self.graph.create_line(x-3,bottom,x-3+w,bottom) total+=inc bottom = y-total*h/most bottom = y self.graph.create_line(x,y-h,x,bottom) space = 3 wd = (w/days-space)/2 left = x ttime = stime x2 = w/2 +60 if tcount["total"] > 0: avg = tcount["total"]*86400.0/(latest-oldest) bottom = y-(int)(avg*h/most) self.graph.create_line(x+2,bottom,x+w-3,bottom,width=2,fill=colors[0]) self.graph.create_line(x2,y+55,x2+20,y+55,width=2,fill=colors[0]) self.graph.create_text(x2+30,y+55,anchor=W,text ="%.1f Daily Average" % avg) if gcount > 0: eavg = gcount*86400.0/(ht*3600+mt*60+st) bottom = y-(int)(eavg*h/most) self.graph.create_line(x+2,bottom,x+w-3,bottom,width=2,fill=colors[1]) self.graph.create_line(x2,y+36,x2+20,y+36,width=2,fill=colors[1]) self.graph.create_text(x2+30,y+36,anchor=W,text ="%.1f 24-Hour Average" % eavg) #draw the bars on the graph for i in range(days) : mid= int(x+w*(i+.5)/days) bottom = y day = localtime(ttime)[2] ttime+=86400 if (i % int(days/(w/20)+1) == 0): self.graph.create_text(mid,bottom+4,text="%d" % day,anchor=N) total = 0 for type in targets: if dcount[i][type] > 0: total += dcount[i][type] top = y-(total*h/most) self.graph.create_rectangle(left,top,mid+wd,bottom,fill=color[type]) bottom = top left = mid+wd+space y += 30 w = 42 for type in (targets + ["total"]): x = 15 if (type != "total"): self.graph.create_rectangle(x,y,x+12,y+12,fill=color[type]) x+=20 self.graph.create_text(x,y,anchor=NW,text=type) else: x+=20 x+=w+w self.graph.create_text(x,y,anchor=NE,text="%d" % dcount[days-1][type]) x+=w self.graph.create_text(x,y,anchor=NE,text="%d" % pcount[type]) x+=w self.graph.create_text(x,y,anchor=NE,text="%d" % tcount[type]) y+=16 def ListTaskDir(self, dir): for path in os.listdir(dir): fpath = dir + os.sep + path if (os.path.isdir(fpath)): self.ListTaskDir(fpath) else: mat = re.search("([^\.]+)\.(\w{4})\.zip",path) if mat: #print path self.taskCount+=1 self.tasklist += "%3i. %-16s %s\n" %(self.taskCount,mat.group(1),self.types[mat.group(2)]) #Start of Program results = Results() results.mainloop()