Package nMOLDYN :: Package GUI :: Module PyroServerDialog |
[hide private]
[frames] | no frames]

# Source Code for Module nMOLDYN.GUI.PyroServerDialog

      1  """This modules implements I{View-->Animation} dialog.
      2
      3  Classes:
      4      * PyroServerDialog: creates the dialog used to set up the Pyro server.
      5  """
      6
      7  # The python distribution modules
      8  import os
      9  import subprocess
     10  import sys
     11
     12  # The Tcl/Tk modules
     13  from tkFileDialog import askopenfilename
     14  from Tkinter import *
     15
     16  # The nMOLDYN modules
     17  from nMOLDYN.Core.Error import Error
     18  from nMOLDYN.Core.Logger import LogMessage
     19  from nMOLDYN.Core.Misc import cpuInfo
     20  from nMOLDYN.Core.Preferences import PREFERENCES
     21  from nMOLDYN.GUI.Widgets import ComboFileBrowser, ComboLabel, ComboRadiobutton, ComboSpinbox, ComboText
     22
     23 -class PyroServerDialog(Toplevel):
     24      """Sets up a dialog used to set up the Pyro server.
     25      """
     26
     27 -    def __init__(self, parent):
     28          """The constructor.
     29
     30          @param parent: the parent widget.
     31          """
     32
     33          Toplevel.__init__(self, parent)
     34          self.transient(parent)
     35
     36          self.title('Pyro server')
     37
     38          self.parent = parent
     39
     40          self.pyroServerString = 'monoprocessor'
     41
     42          self.pyroServerConfigFile = PREFERENCES.pyro_server_configfile
     43
     44          self.spinboxes = {}
     45
     46          body = Frame(self)
     47          self.initial_focus = self.body(body)
     48          body.grid(row = 0, column = 0, sticky = EW)
     49
     50          self.buttonbox()
     51
     52          self.grab_set()
     53
     54          if not self.initial_focus:
     55              self.initial_focus = self
     56
     57          self.protocol("WM_DELETE_WINDOW", self.cancel)
     58
     59          self.resizable(width = NO, height = NO)
     60
     61          self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
     62
     63          self.initial_focus.focus_set()
     64
     65          self.wait_window(self)
     66
     67 -    def body(self, master):
     68          """
     69          Create dialog body. Return widget that should have initial focus.
     70          """
     71
     72          self.settingsFrame = LabelFrame(master, text = 'Settings', bd = 2, relief = GROOVE, width = 750, height = 550)
     73          self.settingsFrame.grid(row = 0, column = 0, sticky = EW, padx = 3, pady = 3)
     74          self.settingsFrame.grid_columnconfigure(0, weight = 1)
     75          self.settingsFrame.grid_propagate(0)
     76
     77          pagesFrame = Frame(self.settingsFrame)
     78          pagesFrame.grid(row = 0, column = 0, sticky = 'EW', padx = 3, pady = 3)
     79
     80          pagesFrame.grid_columnconfigure(0, weight = 1)
     81
     82          self.selectPyroServer = ComboRadiobutton(pagesFrame,\
     83                                                   frameLabel = "Pyro server",\
     84                                                   tagName = 'pyro_server',\
     85                                                   contents = ["monoprocessor", "multiprocessor", "cluster"],\
     86                                                   layout = (3,1))
     87
     88          for rb in self.selectPyroServer.radio:
     89              rb.config(command = self.refreshServerInfo)
     90
     91          self.selectPyroServer.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'NEW')
     92          self.selectPyroServer.grid_columnconfigure(0, weight = 1)
     93          self.selectPyroServer.grid_rowconfigure(0, weight = 1)
     94
     95          self.serverSettingsFrame = Frame(self.settingsFrame)
     96          self.serverSettingsFrame.grid(row = 0, column = 1, sticky = 'NEW', padx = 3, pady = 3)
     97
     98          self.refreshServerInfo()
     99
    100          return None
    101
    102 -    def buttonbox(self):
    103          """
    104          Add standard button box.
    105          """
    106
    107          # The frame that contains the 'Cancel' and 'OK' buttons.
    108          box = LabelFrame(self, text = 'Actions', bd = 2, relief = GROOVE)
    109          box.grid(row = 1, column = 0, sticky = EW, padx = 3, pady = 3)
    110          box.grid_columnconfigure(0, weight = 1)
    111
    112          w = Button(box, text = "Cancel", width=10, command = self.cancel)
    113          w.grid(row = 0, column = 0, sticky = E)
    114          w = Button(box, text = "OK", width=10, command = self.ok, default=ACTIVE)
    115          w.grid(row = 0, column = 1, sticky = E)
    116
    117          self.bind("<Return>", self.ok)
    118          self.bind("<Escape>", self.cancel)
    119
    120      # Standard button semantics.
    121 -    def ok(self, event = None):
    122
    123          if event is not None:
    124              try:
    125                  if event.widget == self.pyroServerConfigFileBrowser.entry:
    126                      return
    127              except AttributeError:
    128                  pass
    129
    130          if not self.validate():
    131              self.initial_focus.focus_set()
    132              return
    133
    134          self.withdraw()
    135          self.update_idletasks()
    136
    137          self.apply()
    138
    139          # Put focus back to the parent window
    140          self.parent.focus_set()
    141          self.destroy()
    142
    143 -    def cancel(self, event=None):
    144
    145          self.pyroServerString = 'monoprocessor'
    146
    147          # Put focus back to the parent window
    148          self.parent.focus_set()
    149          self.destroy()
    150
    151      # Command hooks
    152 -    def validate(self):
    153          return True
    154
    155 -    def apply(self):
    156          """Builds the pyro server configuration string. This can be:
    157              -'monoprocessor' for monoprocessor running mode
    158              -'multiprocessor::hostname:number of allocated cpus'
    159              -'cluster::node name 1:# allocated cpus for node 1,node name 2:# allocated cpus for node 2 ...'
    160          """
    161
    162          self.pyroServerString = self.selectPyroServer.getValue()
    163
    164          if self.selectPyroServer.getValue() != 'monoprocessor':
    165              self.pyroServerString += '::'
    166
    167              try:
    168                  resources = ','.join(['%s:%s' % (v1,self.spinboxes[v1].get()) for v1, v2, v3, v4 in self.nodesInfo if int(self.spinboxes[v1].get()) > 0])
    169                  if not resources:
    170                      raise
    171                  self.pyroServerString += resources
    172
    173              except:
    174                  LogMessage('warning', 'Bad evaluation of cpu resources.', ['gui'])
    175                  return
    176
    177 -    def getValue(self):
    178          """Returns the pyro server specification string.
    179          """
    180
    181          return self.pyroServerString
    182
    183 -    def readPyroServerConfigFile(self, filename):
    184          """Reads a pyro server configuration file that contains information about the nodes of the cluster that will
    185          be checked for availibility.
    186          A pyro server configuration file is an ASCII file that contains either:
    187              -a single line 'host node-name-1 node-name-2 node-name-3...' for an explicit declaration of
    188              the nodes that will be checked,
    189          either
    190              -two lines:
    191                  *'basename basename-for-the-node-list'
    192                  *'number number1-number2 number3-number4 ...' for a contracted declaration of the nodes that will
    193                  be checked. E.g. conveneient if the node names are node1 node2 node3 ...
    194          """
    195
    196          try:
    197              f = open(filename, 'r')
    198              datas = f.readlines()
    199              f.close()
    200
    201              firstLine = datas[0].strip().split()
    202              if firstLine[0].lower() == 'host':
    203                  nodes = firstLine[1:]
    204                  if not nodes:
    205                      raise
    206                  return nodes
    207
    208              elif firstLine[0].lower() == 'basename':
    209                  basename = firstLine[1]
    210                  numbers = []
    211                  secondLine = datas[1].strip().split()
    212                  if secondLine[0].lower() != 'number':
    213                      raise
    214                  for v1, v2 in [[vv for vv in v.split('-')] for v in secondLine[1:]]:
    215                      numbers.extend(range(int(v1), int(v2) + 1))
    216                  numbers = list(sorted(set(numbers)))
    217                  nodes = [basename+str(n) for n in numbers]
    218                  if not nodes:
    219                      raise
    220
    221                  return nodes
    222
    223              else:
    224                  raise
    225
    226          except:
    227              raise Error('Error occured when reading/parsing the pyro server configuration file.')
    228
    229 -    def getCPUInfo(self):
    230          """Sets the total numbers of processors, the number of loaded and free processors on the host
    231          machine or on the different nodes of a cluster.
    232          """
    233
    234          self.nodesInfo = []
    235
    236          if self.selectPyroServer.getValue() == 'multiprocessor':
    237              # Pyro server in cluster mode implemented only for linux platform.
    238              if sys.platform[0:5] == 'linux':
    239                  nProcs = file('/proc/cpuinfo','r').read().count('processor\t:')
    240                  nLoadedProcs = min(nProcs,int(os.getloadavg()[1] + 0.5))
    241                  nFreeProcs = max(0,nProcs - nLoadedProcs)
    242                  self.nodesInfo.append([os.environ['HOSTNAME'],nProcs,nLoadedProcs,nFreeProcs])
    243
    244              elif sys.platform[0:5] == 'darwin':
    245                  try:
    246                      nProcs = int(subprocess.Popen('sysctl -n hw.ncpu', stdout = subprocess.PIPE).stdout.read())
    247                  except:
    248                      nProcs = 1
    249                  nLoadedProcs = min(nProcs,int(os.getloadavg()[1] + 0.5))
    250                  nFreeProcs = max(0,nProcs - nLoadedProcs)
    251                  self.nodesInfo.append([os.environ['HOSTNAME'],nProcs,nLoadedProcs,nFreeProcs])
    252
    253              elif sys.platform[0:5] == 'win32':
    254                  from win32com.client import GetObject
    255
    256                  nProcs = 0
    257                  loadAvg = 0.0
    258
    259                  wmi = GetObject('winmgmts:')
    260                  cpu = wmi.InstancesOf('Win32_Processor')
    261                  for c in cpu:
    262                      nProcs += c.Properties_('NumberOfCores').Value
    263                      loadAvg += c.Properties_('LoadPercentage').Value
    264                  loadAvg /= nProcs
    265
    266                  nLoadedProcs = int(nProcs * loadAvg / 100.0)
    267                  nFreeProcs = max(0,nProcs - nLoadedProcs)
    268
    269                  self.nodesInfo.append([os.environ['COMPUTERNAME'],nProcs,nLoadedProcs,nFreeProcs])
    270
    271          elif self.selectPyroServer.getValue() == 'cluster':
    272
    273              # Pyro server in cluster mode implemented only for linux platform.
    274              if sys.platform[0:5] == 'linux':
    275                  # No pyro server configuration file was provided, uses only the localhost.
    276                  if os.path.exists(self.pyroServerConfigFile):
    277
    278                      machines = self.readPyroServerConfigFile(self.pyroServerConfigFile)
    279
    280                      p1 = subprocess.Popen(['netstat', '-an'], stdout = subprocess.PIPE)
    281                      p2 = subprocess.Popen(['grep', ':161'], stdin = p1.stdout, stdout = subprocess.PIPE)
    282                      output = p2.communicate()[0].strip()
    283                      if not output:
    284                          raise Error('Your cluster is not configured for SNMP protocol.')
    285
    286                      for m in machines:
    287                          try:
    288                              p1 = subprocess.Popen(['snmpwalk', '-v', '2c', '-c', 'public', '%s' % m, 'hrProcessorTable'], stdout = subprocess.PIPE)
    289                              p2 = subprocess.Popen(['wc', '-l'], stdin = p1.stdout, stdout = subprocess.PIPE)
    290                              nProcs = int(p2.communicate()[0].strip())
    291
    292                              p1 = subprocess.Popen(['snmpget', '-v', '2c', '-c', 'public', '%s' % m, 'laLoad.2'], stdout = subprocess.PIPE)
    293                              output2 = p1.communicate()[0].strip()
    294                              loadavg = float(output2.split('=')[-1].strip())
    295
    296                              nLoadedProcs = min(nProcs,int(loadavg + 0.5))
    297                              nFreeProcs = max(0,nProcs - nLoadedProcs)
    298
    299                              self.nodesInfo.append([m,nProcs,nLoadedProcs,nFreeProcs])
    300
    301                          except:
    302                              pass
    303
    304                  else:
    305                      raise Error('No/wrong configuration file for the pyro server provided.')
    306
    307              else:
    308                  raise Error('nMOLDYN only supports pyro server on linux platform.')
    309
    310 -    def changePyroServerConfigFile(self, event = None):
    311          """Loads another pyro server configuration file.
    312          """
    313
    314          # Case where the user enters a file name directly in the entry widget without using the browser.
    315          if event is not None:
    316              if event.widget == self.pyroServerConfigFileBrowser.entry:
    317                  filename = self.pyroServerConfigFileBrowser.getValue()
    318              else:
    319                  return
    320
    321          else:
    322
    323              # The name of the NetCDF file to load.
    324              filename = askopenfilename(parent = self)
    325
    326          if filename:
    327
    328              self.pyroServerConfigFile = filename
    329              self.refreshServerInfo()
    330              event.widget = self.pyroServerConfigFileBrowser.entry
    331
    332          return 'break'
    333
    334 -    def refreshServerInfo(self):
    335          """Updates the text widget that contains the informations about the pyro server.
    336          """
    337
    338          for w in self.serverSettingsFrame.winfo_children():
    339              w.grid_forget()
    340
    341          if self.selectPyroServer.getValue() == 'monoprocessor':
    342              rowComp = 0
    343
    344          elif self.selectPyroServer.getValue() == 'multiprocessor':
    345              rowComp = 0
    346
    347          elif self.selectPyroServer.getValue() == 'cluster':
    348              self.pyroServerConfigFileBrowser = ComboFileBrowser(self.serverSettingsFrame,\
    349                                                                  frameLabel = "Pyro server configuration file",\
    350                                                                  tagName = 'pyro_server_configuration_file',\
    351                                                                  contents = self.pyroServerConfigFile,\
    352                                                                  save = False,\
    353                                                                  command = self.changePyroServerConfigFile)
    354
    355              self.pyroServerConfigFileBrowser.entry.bind('<Return>', self.changePyroServerConfigFile)
    356              self.pyroServerConfigFileBrowser.grid(row = 0, column = 0, sticky = EW, padx = 2, pady = 2)
    357              self.pyroServerConfigFileBrowser.grid_columnconfigure(0, weight = 1)
    358              rowComp = 1
    359
    360          self.firstLine = LabelFrame(self.serverSettingsFrame,\
    361                                      text = 'Information about localhost/available nodes',\
    362                                      bd = 0)
    363          self.firstLine.grid(row = rowComp, column = 0, sticky = EW, padx = 2, pady = 2)
    364          self.firstLine.grid_columnconfigure(3, weight = 1)
    365
    366          Label(self.firstLine, text = 'Node', width = 15).grid(row = 0, column = 0, sticky = W, padx = 1)
    367          Label(self.firstLine, text = '# cpus', width = 15).grid(row = 0, column = 1, sticky = W, padx = 1)
    368          Label(self.firstLine, text = '# free cpus', width = 15).grid(row = 0, column = 2, sticky = W, padx = 1)
    369          Label(self.firstLine, text = '# allocated cpus', width = 15).grid(row = 0, column = 3, sticky = W, padx = 1)
    370
    371          self.clusterInfo = ComboText(self.serverSettingsFrame)
    372          self.clusterInfo.config(bd = 0)
    373          self.clusterInfo.grid(row = rowComp + 1, column = 0, sticky = EW, padx = 2, pady = 2)
    374          self.clusterInfo.grid_columnconfigure(0, weight = 1)
    375          self.clusterInfo.text.config({'height' : 30, 'width' : 68})
    376
    377          if self.selectPyroServer.getValue() == 'monoprocessor':
    378              self.clusterInfo.insert(END, 'The analysis will be run in monoprocessor mode.')
    379
    380          else:
    381              if self.selectPyroServer.getValue() == 'cluster':
    382                  self.clusterInfo.insert(END, 'Evaluating the resources available on the cluster using\n\n\t%s\n\nconfiguration file.\n\nPlease wait ...' % self.pyroServerConfigFile)
    383
    384              self.clusterInfo.update()
    385
    386              self.getCPUInfo()
    387
    388              self.clusterInfo.cleanup()
    389
    390              for name, nTotal, nLoaded, nFree in self.nodesInfo:
    391
    392                  self.clusterInfo.text.window_create(END,\
    393                                                      window = Label(self.clusterInfo.text,
    394                                                                     text = name,\
    395                                                                     width = 15,\
    396                                                                     anchor = W))
    397
    398                  self.clusterInfo.text.window_create(END,\
    399                                                      window = Label(self.clusterInfo.text,
    400                                                                     text = str(nTotal),\
    401                                                                     width = 15,\
    402                                                                     anchor = CENTER))
    403
    404                  self.clusterInfo.text.window_create(END,\
    405                                                      window = Label(self.clusterInfo.text,
    406                                                                     text = str(nFree),\
    407                                                                     width = 15,\
    408                                                                     anchor = CENTER))
    409
    410
    411                  selProcs = [str(v) for v in range(0, nTotal + 1)]
    412
    413                  self.spinboxes[name] = Spinbox(self.clusterInfo.text,\
    414                                                 values = tuple(selProcs),\
    415                                                 wrap = True,\
    416                                                 width = 15)
    417
    418                  self.clusterInfo.text.window_create(END, window = self.spinboxes[name])
    419
    420                  [self.spinboxes[name].invoke('buttonup') for v in range(0, selProcs.index(str(nFree)))]
    421
    422                  self.clusterInfo.insert(END, '\n')
    423