Commit 7cca2257 authored by eric pellegrini's avatar eric pellegrini
Browse files

Bug fix when running multiproc run from the GUI on Windows

Added a specific logfile for each launched job
Improved the JobControllerPanel
Removed the global logfile toolbar button
Removed the 'info' JobStatus entry
Removed unused Plugin.has_parent method
Bug fixes
parent 925b4b4b
......@@ -36,6 +36,7 @@ import wx.aui as wxaui
from MDANSE.Externals.pubsub import pub as Publisher
from MDANSE.App.GUI.Icons import ICONS
from MDANSE.App.GUI.Framework import has_parent
from MDANSE.App.GUI.Framework.Plugins.ComponentPlugin import ComponentPlugin
class AnimationPlugin(ComponentPlugin):
......@@ -154,7 +155,7 @@ class AnimationPlugin(ComponentPlugin):
mv = message
if not self.has_parent(mv):
if not has_parent(self,mv):
return
frame = mv.current_frame
......@@ -177,8 +178,10 @@ class AnimationPlugin(ComponentPlugin):
def on_update_animation_icon(self, message):
mv = message
if not self.has_parent(mv):
if not has_parent(self,mv):
return
if mv.animation_loop:
......@@ -187,7 +190,9 @@ class AnimationPlugin(ComponentPlugin):
self.playPause.SetBitmapLabel(ICONS["play",32,32])
def on_set_up_frame_slider(self, message):
mv = message
if not self.has_parent(mv):
if not has_parent(self,mv):
return
self.frameSlider.SetRange(0,self._parent.n_frames-1)
\ No newline at end of file
......@@ -99,9 +99,6 @@ class DataPlugin(IPlugin):
plugin.plug()
plugin.SetFocus()
def has_parent(self, window):
return False
def on_changing_pane(self, event):
......
......@@ -106,12 +106,8 @@ class JobPlugin(ComponentPlugin):
t = tempfile.mkstemp(prefix = "MDANSE_%s_" % self._job.type, text = True)
os.close(t[0])
self._job.save_job(t[1], parameters)
self._job.configuration.set_parameters(parameters)
self._job.configuration.configure()
self._job.save(t[1], parameters)
if PLATFORM.name == "windows":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
......@@ -119,7 +115,7 @@ class JobPlugin(ComponentPlugin):
else:
startupinfo = None
subprocess.Popen([sys.executable, t[1]], startupinfo=startupinfo)
subprocess.Popen([sys.executable, t[1]], startupinfo=startupinfo, stdin=subprocess.PIPE,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(1)
......@@ -145,7 +141,7 @@ class JobPlugin(ComponentPlugin):
if os.path.splitext(path)[1] != ".py":
path += ".py"
self._job.save_job(path, parameters)
self._job.save(path, parameters)
def plug(self):
......
......@@ -35,7 +35,6 @@ import abc
import wx
from MDANSE import REGISTRY
from MDANSE.App.GUI.Framework.Plugins.IPlugin import IPlugin, plugin_parent
class IWidget(wx.Panel):
......@@ -89,16 +88,6 @@ class IWidget(wx.Panel):
def add_widgets(self):
pass
def has_parent(self, target):
if self == target:
return True
if self.TopLevelParent == self:
return False
return self.has_parent(self.Parent, target)
def build_panel(self):
self._staticBox = wx.StaticBox(self, wx.ID_ANY, label=self.label)
......
......@@ -38,6 +38,7 @@ from MDANSE.Externals.pubsub import pub
from MDANSE.Framework.Configurable import ConfigurationError
from MDANSE.App.GUI import DATA_CONTROLLER
from MDANSE.App.GUI.Framework import has_parent
from MDANSE.App.GUI.Framework.Widgets.IWidget import IWidget
class MMTKTrajectoryWidget(IWidget):
......@@ -71,7 +72,7 @@ class MMTKTrajectoryWidget(IWidget):
window, filename = message
if not self.has_parent(window):
if not has_parent(self,window):
return
data = DATA_CONTROLLER[filename].data
......
......@@ -38,6 +38,7 @@ from MDANSE.Externals.pubsub import pub
from MDANSE.Framework.Configurable import ConfigurationError
from MDANSE.App.GUI import DATA_CONTROLLER
from MDANSE.App.GUI.Framework import has_parent
from MDANSE.App.GUI.Framework.Widgets.IWidget import IWidget
class NetCDFInputWidget(IWidget):
......@@ -77,7 +78,7 @@ class NetCDFInputWidget(IWidget):
window, filename = message
if not self.has_parent(window):
if not has_parent(self,window):
return
self._netcdf = DATA_CONTROLLER[filename].netcdf
......
def has_parent(window, target):
if window == target:
return True
else:
return has_parent(window.Parent, target)
......@@ -27,7 +27,7 @@
'''
Created on Apr 10, 2015
@author: pellegrini
@author: Gael Goret and Eric C. Pellegrini
'''
import collections
......@@ -46,10 +46,10 @@ from MDANSE.Core.Singleton import Singleton
from MDANSE.Framework.Status import convert_duration, total_seconds
from MDANSE.Framework.Jobs.JobStatus import JobState
from MDANSE.Externals.pubsub import pub
from MDANSE.Logging.Logger import LOGGER
from MDANSE.App.GUI.Icons import ICONS
from MDANSE.App.GUI.Events.JobControllerEvent import EVT_JOB_CONTROLLER, JobControllerEvent
from MDANSE.App.GUI.LogfileFrame import LogfileFrame
class JobController(threading.Thread):
......@@ -65,6 +65,8 @@ class JobController(threading.Thread):
self._registry = collections.OrderedDict()
self._firstCheck = True
if start:
self.start()
......@@ -73,12 +75,29 @@ class JobController(threading.Thread):
return self._registry
def remove_job(self, job):
def kill_job(self, job):
if not self._registry.has_key(job):
return
if self._registry[job]['state'] == 'running':
try:
PLATFORM.kill_process(self._registry[job]['pid'])
except:
return
if os.path.exists(self._registry[job]['temporary_file']):
try:
os.unlink(self._registry[job]['temporary_file'])
except:
return
try:
del self._registry[job]
except KeyError:
pass
return
self.update()
def run(self):
......@@ -96,11 +115,11 @@ class JobController(threading.Thread):
# The list of the registered jobs.
jobs = [f for f in glob.glob(os.path.join(PLATFORM.temporary_files_directory(),'*'))]
# Loop over the job registered at the previous controller check point
for j in self._registry.keys():
for j in self._registry.keys():
# Case where a job has finished during two controller check points (i.e. its temporary file has been deleted)
if self._registry[j]['state'] == 'finished':
continue
......@@ -130,6 +149,8 @@ class JobController(threading.Thread):
if not isinstance(info,JobState):
continue
name = info['name']
# Check that the pid of the running job corresponds to an active pid.
running = (info['pid'] in pids)
......@@ -139,15 +160,14 @@ class JobController(threading.Thread):
jobStartingTime = datetime.datetime.strptime(info["start"],"%d-%m-%Y %H:%M:%S")
procStartingTime = datetime.datetime.strptime(pids[info['pid']],"%d-%m-%Y %H:%M:%S")
running = (jobStartingTime >= procStartingTime)
# Case where the job is running, update the registry with the new status
if running:
self._registry[j] = info
self._registry[name] = info
# Case where the job is not running
else:
info['state'] = 'aborted'
self._registry[j] = info
self._registry[name] = info
wx.PostEvent(self._window, JobControllerEvent(self._registry))
......@@ -165,14 +185,16 @@ class JobControllerPanel(wx.ScrolledWindow):
self._jobs = collections.OrderedDict()
self.build_panel()
self._gbSizer = wx.GridBagSizer(0,0)
self.SetSizer(self._gbSizer)
self._jobsController = JobController(self,True)
EVT_JOB_CONTROLLER(self,self.on_update)
pub.subscribe(self.on_start_job,"on_start_job")
self._jobsController = JobController(self,True)
def __del__(self):
self._jobsController.stop()
......@@ -182,22 +204,21 @@ class JobControllerPanel(wx.ScrolledWindow):
def on_start_job(self,message):
self._jobsController.update()
def add_job(self, name, jobStatus):
r = self._gbSizer.Rows
self._jobs[name] = r
name = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
pid = intctrl.IntCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
start = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
elapsed = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
state = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
name = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['name'])
pid = intctrl.IntCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['pid'])
start = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['start'])
elapsed = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['elapsed'])
state = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['state'])
progress = wx.Gauge(self, wx.ID_ANY,range=100)
eta = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL)
progress.SetValue(jobStatus['progress'])
eta = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL,value=jobStatus['eta'])
kill = wx.BitmapButton(self, wx.ID_ANY, ICONS["stop",24,24])
r = self._gbSizer.GetRows()
self._gbSizer.Add(name ,pos=(r,0),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.Add(pid ,pos=(r,1),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.Add(start ,pos=(r,2),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
......@@ -206,73 +227,47 @@ class JobControllerPanel(wx.ScrolledWindow):
self._gbSizer.Add(progress,pos=(r,5),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.Add(eta ,pos=(r,6),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.Add(kill ,pos=(r,7),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
name.Bind(wx.EVT_LEFT_DCLICK,self.on_display_logfile)
self._gbSizer.Layout()
kill.Bind(wx.EVT_BUTTON, self.on_kill_job)
def build_panel(self):
self._gbSizer = wx.GridBagSizer(0,0)
for i,(col,s) in enumerate(self.columns):
self._gbSizer.Add(wx.TextCtrl(self,wx.ID_ANY,value=col.upper(),size=s,style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL),pos=(0,i),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.AddGrowableCol(5)
self.SetSizer(self._gbSizer)
def on_display_logfile(self,event):
row = self._gbSizer.GetItemPosition(event.GetEventObject())[0]
name = self._gbSizer.FindItemAtPosition((row,0)).Window.GetValue()
f = LogfileFrame(self,name)
f.Show()
def on_kill_job(self, event):
row = self._gbSizer.GetItemPosition(event.GetEventObject())[0]
state = self._gbSizer.FindItemAtPosition((row,4)).Window.GetValue()
if state == "finished":
return
for row in range(self._gbSizer.GetCols()):
name = self._gbSizer.FindItemAtPosition((row,0)).Window.GetValue()
name = self._gbSizer.FindItemAtPosition((row,0)).Window.GetValue()
d = wx.MessageDialog(None, 'Do you really want to kill job %r ?' % name, 'Question', wx.YES_NO|wx.YES_DEFAULT|wx.ICON_EXCLAMATION)
if d.ShowModal() == wx.ID_YES:
try:
pid = self._gbSizer.FindItemAtPosition((row,1)).Window.GetValue()
PLATFORM.kill_process(pid)
except Exception:
LOGGER("The job %r could not be killed." % name,"error")
else:
event.GetEventObject().Disable()
def update_job(self,name,jobStatus):
r = self._jobs[name]
for i,(name,_) in enumerate(self.columns):
try:
self._gbSizer.FindItemAtPosition((r,i)).Window.SetValue(jobStatus[name])
if jobStatus['state'] == 'finished':
self._gbSizer.FindItemAtPosition((r,7)).Window.Disable()
except AttributeError:
pass
info = "\n".join(["%s = %s" % (k,v) for k,v in jobStatus.items()])
self._gbSizer.FindItemAtPosition((r,0)).Window.SetToolTipString(info)
self._jobsController.kill_job(name)
def on_update(self, event):
registry = event.registry
for name,jobStatus in registry.items():
if not self._jobs.has_key(name):
if jobStatus["state"] == "aborted":
continue
self.add_job(name,jobStatus)
self.update_job(name,jobStatus)
self._gbSizer.Clear(True)
for i,(col,s) in enumerate(self.columns):
self._gbSizer.Add(wx.TextCtrl(self,wx.ID_ANY,value=col.upper(),size=s,style=wx.TE_READONLY|wx.ALIGN_CENTER_HORIZONTAL),pos=(0,i),flag=wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
self._gbSizer.AddGrowableCol(5)
self._gbSizer.Layout()
for name,jobStatus in registry.items():
self.add_job(name,jobStatus)
if __name__ == "__main__":
app = wx.App(0)
......
......@@ -38,10 +38,12 @@ from MDANSE import PLATFORM
class LogfileFrame(wx.Frame):
def __init__(self,parent,*args,**kwargs):
def __init__(self,parent,jobName,*args,**kwargs):
wx.Frame.__init__(self,parent,size=(800,500),*args,**kwargs)
self._logfile = os.path.join(PLATFORM.logfiles_directory(),jobName)+'.txt'
panel = wx.Panel(self,wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
......@@ -61,12 +63,12 @@ class LogfileFrame(wx.Frame):
def update(self):
logfile = os.path.join(PLATFORM.application_directory(),"mdanse.log")
if os.path.exists(logfile):
with open(logfile,"r") as f:
self._logFileContents.SetValue(f.read())
try:
f = open(self._logfile,"r")
except IOError:
self._logFileContents.SetValue("Error opening %r file." % self._logfile)
else:
self._logFileContents.SetValue(f.read())
def on_update(self,event):
......
......@@ -145,7 +145,6 @@ class MainFrame(wx.Frame):
plotButton = self._toolbar.AddSimpleTool(wx.ID_ANY,ICONS["plot",32,32], 'Open MDANSE plotter')
udButton = self._toolbar.AddSimpleTool(wx.ID_ANY,ICONS["user",32,32], 'Edit the user definitions')
preferencesButton = self._toolbar.AddSimpleTool(wx.ID_ANY, ICONS["preferences",32,32], 'Edit the preferences')
logfileButton = self._toolbar.AddSimpleTool(wx.ID_ANY, ICONS["logfile",32,32], 'Display MDANSE log file')
registryButton = self._toolbar.AddSimpleTool(wx.ID_ANY, ICONS["registry",32,32], 'Inspect MDANSE classes registry')
helpButton = self._toolbar.AddSimpleTool(wx.ID_ANY, ICONS["help",32,32], 'Help')
websiteButton = self._toolbar.AddSimpleTool(wx.ID_ANY, ICONS["web",32,32], 'Open MDANSE website')
......@@ -162,7 +161,6 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.on_open_mdanse_elements_database, databaseButton)
self.Bind(wx.EVT_MENU, self.on_start_plotter, plotButton)
self.Bind(wx.EVT_MENU, self.on_set_preferences, preferencesButton)
self.Bind(wx.EVT_MENU, self.on_display_logfile, logfileButton)
self.Bind(wx.EVT_MENU, self.on_open_user_definitions, udButton)
self.Bind(wx.EVT_MENU, self.on_open_classes_registry, registryButton)
self.Bind(wx.EVT_MENU, self.on_about, aboutButton)
......@@ -220,14 +218,6 @@ or directly to the MDANSE mailing list:
d.ShowModal()
d.Destroy()
def on_display_logfile(self,event):
from MDANSE.App.GUI.LogfileFrame import LogfileFrame
f = LogfileFrame(self)
f.Show()
def on_open_classes_registry(self,event):
from MDANSE.App.GUI.Framework.Plugins.RegistryViewerPlugin import RegistryViewerFrame
......
......@@ -288,6 +288,20 @@ class Platform(object):
'''
pass
def logfiles_directory(self):
'''
Returns the path of the directory where the MDANSE job logfiles are stored.
:return: the path of the directory where the MDANSE job logfiles are stored..
:rtype: str
'''
path = os.path.join(self.application_directory(), 'logfiles')
self.create_directory(path)
return path
def temporary_files_directory(self):
'''
Returns the path of the directory where the temporary MDANSE job status files are stored.
......
......@@ -81,6 +81,8 @@ import Pyro.naming
from Scientific.DistributedComputing.TaskManager import TaskManager, TaskManagerTermination
from MDANSE import PLATFORM
debug = False
class MasterProcessError(Exception):
......@@ -559,7 +561,7 @@ from %s import *
MDANSE.DistributedComputing.MasterSlave.startSlaveProcess()
"""
process.task_manager.storeData(slave_code = source,
cwd = os.getcwd())
cwd = PLATFORM.home_directory())
if debug:
print "Slave source code:"
print 50*'-'
......
......@@ -36,9 +36,9 @@ from MDANSE import PLATFORM
import Pyro
Pyro.config.PYRO_STORAGE = PLATFORM.home_directory()
Pyro.config.PYRO_NS_URIFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_USER_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYROSSL_CERTDIR = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_log')
Pyro.config.PYRO_USER_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_userlog')
Pyro.config.PYROSSL_CERTDIR = os.path.join(Pyro.config.PYRO_STORAGE,'certs')
# Define (or import) all the task handlers.
def do_run_step(job, step):
......
......@@ -75,12 +75,11 @@ class RunningModeConfigurator(IConfigurator):
import Pyro
Pyro.config.PYRO_STORAGE = PLATFORM.home_directory()
Pyro.config.PYRO_STORAGE = PLATFORM.home_directory()
Pyro.config.PYRO_NS_URIFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_USER_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYROSSL_CERTDIR = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_NS_URI')
Pyro.config.PYRO_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_log')
Pyro.config.PYRO_USER_LOGFILE = os.path.join(Pyro.config.PYRO_STORAGE,'Pyro_userlog')
Pyro.config.PYROSSL_CERTDIR = os.path.join(Pyro.config.PYRO_STORAGE,'certs')
slots = int(value[1])
......
......@@ -27,7 +27,7 @@
'''
Created on Mar 30, 2015
@author: pellegrini
@author: Eric C. Pellegrini
'''
import abc
......@@ -38,6 +38,7 @@ import stat
import string
import subprocess
import sys
import traceback
from MDANSE import LOGGER, PLATFORM, REGISTRY
from MDANSE.Core.Error import Error
......@@ -58,14 +59,26 @@ class JobError(Error):
:type job: IJob derived object
'''
trace = []
tback = traceback.extract_stack()
for tb in tback:
trace.append(' -- '.join([str(t) for t in tb]))
if message is None:
message = sys.exc_info()[1]
self._message = str(message)
trace.append("\n%s" % self._message)
trace = '\n'.join(trace)
LOGGER(trace,'error',[job._name])
if job._status is not None:
job._status._state["state"] = "crashed"
job._status._state["info"] += "ERROR: %s" % self._message
job._status._state["state"] = "aborted"
job._status.update(force=True)
def __str__(self):
......@@ -121,10 +134,12 @@ class IJob(Configurable):
"""
Configurable.__init__(self)