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