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
8 import os
9 import subprocess
10 import sys
11
12
13 from tkFileDialog import askopenfilename
14 from Tkinter import *
15
16
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
24 """Sets up a dialog used to set up the Pyro server.
25 """
26
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
119
120
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
140 self.parent.focus_set()
141 self.destroy()
142
143 - def cancel(self, event=None):
144
145 self.pyroServerString = 'monoprocessor'
146
147
148 self.parent.focus_set()
149 self.destroy()
150
151
153 return True
154
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
178 """Returns the pyro server specification string.
179 """
180
181 return self.pyroServerString
182
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
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
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
274 if sys.platform[0:5] == 'linux':
275
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
311 """Loads another pyro server configuration file.
312 """
313
314
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
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
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