ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/CRAB/python/apmon.py
Revision: 1.1
Committed: Tue Aug 30 13:15:51 2005 UTC (19 years, 8 months ago) by corvo
Content type: text/x-python
Branch: MAIN
CVS Tags: CRAB_1_0_1, CRAB_1_0_0_rc1, CRAB_1_0_0_beta4, CRAB_1_0_0_pre1_boss_2, CRAB_1_0_0_pre1_boss
Log Message:
MonaLisa monitoring integration

File Contents

# Content
1
2 """
3 * ApMon - Application Monitoring Tool
4 * Version: 2.0.4
5 *
6 * Copyright (C) 2005 California Institute of Technology
7 *
8 * Permission is hereby granted, free of charge, to use, copy and modify
9 * this software and its documentation (the "Software") for any
10 * purpose, provided that existing copyright notices are retained in
11 * all copies and that this notice is included verbatim in any distributions
12 * or substantial portions of the Software.
13 * This software is a part of the MonALISA framework (http://monalisa.cacr.caltech.edu).
14 * Users of the Software are asked to feed back problems, benefits,
15 * and/or suggestions about the software to the MonALISA Development Team
16 * (developers@monalisa.cern.ch). Support for this software - fixing of bugs,
17 * incorporation of new features - is done on a best effort basis. All bug
18 * fixes and enhancements will be made available under the same terms and
19 * conditions as the original software,
20
21 * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
22 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
23 * OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
24 * EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
28 * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS
29 * PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO
30 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
31 * MODIFICATIONS.
32 """
33
34 """
35 apmon.py
36
37 This is a python implementation for the ApMon API for sending
38 data to the MonALISA service.
39
40 For further details about ApMon please see the C/C++ or Java documentation
41 You can find a sample usage of this module in apmTest.py.
42
43 Note that the parameters must be either integers(32 bits) or doubles(64 bits).
44 Sending strings is supported, but they will not be stored in the
45 farm's store nor shown in the farm's window in the MonALISA client.
46 """
47
48 import re
49 import xdrlib
50 import socket
51 import urllib2
52 import threading
53 import time
54 import Logger
55 import ProcInfo
56
57 #__all__ = ["ApMon"]
58
59 #__debug = False # set this to True to be verbose
60
61 class ApMon:
62 """
63 Main class for sending monitoring data to a MonaLisa module.
64 One or more destinations can be chosen for the data. See constructor.
65
66 The data is packed in UDP datagrams, using XDR. The following fields are sent:
67 - version & password (string)
68 - cluster name (string)
69 - node name (string)
70 - number of parameters (int)
71 - for each parameter:
72 - name (string)
73 - value type (int)
74 - value
75 - optionally a (int) with the given timestamp
76
77 Attributes (public):
78 - destinations - a list containing (ip, port, password) tuples
79 - configAddresses - list with files and urls from where the config is read
80 - configRecheckInterval - period, in seconds, to check for changes
81 in the configAddresses list
82 - configRecheck - boolean - whether to recheck periodically for changes
83 in the configAddresses list
84 """
85 destinations = {} # empty, by default; key = tuple (host, port, pass) ; val = hash {"param_mame" : True/False, ...}
86 configAddresses = [] # empty, by default; list of files/urls from where we read config
87 configRecheckInterval = 120 # 2 minutes
88 configRecheck = True # enabled by default
89 performBgMonitoring = True # by default, perform background monitoring
90 monitoredJobs = {} # Monitored jobs; key = pid; value = hash with
91
92 __defaultOptions = {
93 'job_monitoring': True, # perform (or not) job monitoring
94 'job_interval' : 10, # at this interval (in seconds)
95 'job_data_sent' : 0, # time from Epoch when job information was sent; don't touch!
96
97 'job_cpu_time' : True, # elapsed time from the start of this job in seconds
98 'job_run_time' : True, # processor time spent running this job in seconds
99 'job_cpu_usage' : True, # current percent of the processor used for this job, as reported by ps
100 'job_virtualmem': True, # size in JB of the virtual memory occupied by the job, as reported by ps
101 'job_rss' : True, # size in KB of the resident image size of the job, as reported by ps
102 'job_mem_usage' : True, # percent of the memory occupied by the job, as reported by ps
103 'job_workdir_size': True, # size in MB of the working directory of the job
104 'job_disk_total': True, # size in MB of the total size of the disk partition containing the working directory
105 'job_disk_used' : True, # size in MB of the used disk partition containing the working directory
106 'job_disk_free' : True, # size in MB of the free disk partition containing the working directory
107 'job_disk_usage': True, # percent of the used disk partition containing the working directory
108
109 'sys_monitoring': True, # perform (or not) system monitoring
110 'sys_interval' : 10, # at this interval (in seconds)
111 'sys_data_sent' : 0, # time from Epoch when system information was sent; don't touch!
112
113 'sys_cpu_usr' : False, # cpu-usage information
114 'sys_cpu_sys' : False, # all these will produce coresponding paramas without "sys_"
115 'sys_cpu_nice' : False,
116 'sys_cpu_idle' : False,
117 'sys_cpu_usage' : True,
118 'sys_load1' : True, # system load information
119 'sys_load5' : True,
120 'sys_load15' : True,
121 'sys_mem_used' : False, # memory usage information
122 'sys_mem_free' : False,
123 'sys_mem_usage' : True,
124 'sys_pages_in' : False,
125 'sys_pages_out' : False,
126 'sys_swap_used' : True, # swap usage information
127 'sys_swap_free' : False,
128 'sys_swap_usage': True,
129 'sys_swap_in' : False,
130 'sys_swap_out' : False,
131 'sys_net_in' : True, # network transfer in kBps
132 'sys_net_out' : True, # these will produce params called ethX_in, ethX_out, ethX_errs
133 'sys_net_errs' : False, # for each eth interface
134 'sys_processes' : True,
135 'sys_uptime' : True, # uptime of the machine, in days (float number)
136
137 'general_info' : True, # send (or not) general host information once every 2 $sys_interval seconds
138 'general_data_sent': 0, # time from Epoch when general information was sent; don't touch!
139
140 'hostname' : True,
141 'ip' : True, # will produce ethX_ip params for each interface
142 'cpu_MHz' : True,
143 'no_CPUs' : True, # number of CPUs
144 'total_mem' : True,
145 'total_swap' : True};
146
147 def __init__ (self, initValue):
148 """
149 Class constructor:
150 - if initValue is a string, put it in configAddresses and load destinations
151 from the file named like that. if it starts with "http://", the configuration
152 is loaded from that URL. For background monitoring, given parameters will overwrite defaults
153
154 - if initValue is a list, put its contents in configAddresses and create
155 the list of destinations from all those sources. For background monitoring,
156 given parameters will overwrite defaults (see __defaultOptions)
157
158 - if initValue is a tuple (of strings), initialize destinations with that values.
159 Strings in this tuple have this form: "{hostname|ip}[:port][ passwd]", the
160 default port being 8884 and the default password being "". Background monitoring will be
161 enabled sending the parameters active from __defaultOptions (see end of file)
162
163 - if initValue is a hash (key = string(hostname|ip[:port][ passwd]),
164 val = hash{'param_name': True/False, ...}) the given options for each destination
165 will overwrite the default parameters (see __defaultOptions)
166 """
167 self.logger = Logger.Logger(self.__defaultLogLevel)
168 if type(initValue) == type("string"):
169 self.configAddresses.append(initValue)
170 self.__reloadAddresses()
171 elif type(initValue) == type([]):
172 self.configAddresses = initValue
173 self.__reloadAddresses()
174 elif type(initValue) == type(()):
175 for dest in initValue:
176 self.__addDestination (dest, self.destinations)
177 elif type(initValue) == type({}):
178 for dest, opts in initValue.items():
179 self.__addDestination (dest, self.destinations, opts)
180
181 self.__initializedOK = (len (self.destinations) > 0)
182 if not self.__initializedOK:
183 self.logger.log(Logger.ERROR, "Failed to initialize. No destination defined.");
184 #self.__defaultClusterName = None
185 #self.__defaultNodeName = self.getMyHo
186 if self.__initializedOK:
187 self.__udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
188 if len(self.configAddresses) > 0:
189 # if there are addresses that need to be monitored,
190 # start config checking and reloading thread
191 th = threading.Thread(target=self.__configLoader)
192 th.setDaemon(True) # this is a daemon thread
193 th.start()
194 # create the ProcInfo instance
195 self.procInfo = ProcInfo.ProcInfo(self.logger);
196 self.procInfo.update();
197 # start the background monitoring thread
198 th = threading.Thread(target=self.__bgMonitor);
199 th.setDaemon(True);
200 th.start();
201
202
203 def sendParams (self, params):
204 """
205 Send multiple parameters to MonALISA, with default (last given) cluser and node names.
206 """
207 self.sendTimedParams (-1, params)
208
209 def sendTimedParams (self, timeStamp, params):
210 """
211 Send multiple parameters, specifying the time for them, with default (last given) cluster and node names.
212 (See sendTimedParameters for more details)
213 """
214 self.sendTimedParameters (None, None, timeStamp, params);
215
216 def sendParameter (self, clusterName, nodeName, paramName, paramValue):
217 """
218 Send a single parameter to MonALISA.
219 """
220 self.sendTimedParameter(clusterName, nodeName, -1, paramName, paramValue);
221
222 def sendTimedParameter (self, clusterName, nodeName, timeStamp, paramName, paramValue):
223 """
224 Send a single parameter, with a given time.
225 """
226 self.sendTimedParameters (clusterName, nodeName, timeStamp, {paramName:paramValue})
227
228 def sendParameters (self, clusterName, nodeName, params):
229 """
230 Send multiple parameters specifying cluster and node name for them
231 """
232 self.sendTimedParameters (clusterName, nodeName, -1, params);
233
234 def sendTimedParameters (self, clusterName, nodeName, timeStamp, params):
235 """
236 Send multiple monitored parameters to MonALISA.
237
238 - clusterName is the name of the cluster being monitored. The first
239 time this function is called, this paramenter must not be None. Then,
240 it can be None; last given clusterName will be used instead.
241 - nodeName is the name of the node for which are the parameters. If this
242 is None, the full hostname of this machine will be sent instead.
243 - timeStamp, if > 0, is given time for the parameters. This is in seconds from Epoch.
244 Note that this option should be used only if you are sure about the time for the result.
245 Otherwize, the parameters will be assigned a correct time (obtained from NTP servers)
246 in MonALISA service. This option can be usefull when parsing logs, for example.
247 - params is a dictionary containing pairs with:
248 - key: parameter name
249 - value: parameter value, either int or float.
250 or params is a vector of tuples (key, value). This version can be used
251 in case you want to send the parameters in a given order.
252
253 NOTE that python doesn't know about 32-bit floats (only 64-bit floats!)
254 """
255 if not self.__initializedOK:
256 self.logger.log(Logger.WARNING, "Not initialized correctly. Message NOT sent!");
257 return
258 if clusterName == None:
259 clusterName = self.__defaultUserCluster
260 else:
261 self.__defaultUserCluster = clusterName
262 if nodeName == None:
263 nodeName = self.__defaultUserNode
264 else:
265 self.__defaultUserNode = nodeName
266 self.__configUpdateLock.acquire()
267 for dest in self.destinations.keys():
268 self.__directSendParams(dest, clusterName, nodeName, timeStamp, params);
269 self.__configUpdateLock.release()
270
271 def addJobToMonitor (self, pid, workDir, clusterName, nodeName):
272 """
273 Add a new job to monitor.
274 """
275 self.__bgMonitorLock.acquire();
276 self.monitoredJobs[pid] = {};
277 self.monitoredJobs[pid]['CLUSTER_NAME'] = clusterName;
278 self.monitoredJobs[pid]['NODE_NAME'] = nodeName;
279 self.procInfo.addJobToMonitor(pid, workDir);
280 self.__bgMonitorLock.release();
281
282 def removeJobToMonitor (self, pid):
283 """
284 Remove a job from being monitored.
285 """
286 self.__bgMonitorLock.acquire();
287 self.procInfo.removeJobToMonitor(pid);
288 del self.monitoredJobs[pid];
289 self.__bgMonitorLock.release();
290
291 def setMonitorClusterNode (self, clusterName, nodeName):
292 """
293 Set the cluster and node names where to send system related information.
294 """
295 self.__bgMonitorLock.acquire();
296 self.__defaultSysMonCluster = clusterName;
297 self.__defaultSysMonNode = nodeName;
298 self.__bgMonitorLock.release();
299
300 def enableBgMonitoring (self, onOff):
301 """
302 Enable or disable background monitoring. Note that background monitoring information
303 can still be sent if user calls the sendBgMonitoring method.
304 """
305 self.performBgMonitoring = onOff;
306
307 def sendBgMonitoring (self):
308 """
309 Send now background monitoring about system and jobs to all interested destinations.
310 """
311 self.__bgMonitorLock.acquire();
312 self.procInfo.update();
313 now = int(time.time());
314 for destination, options in self.destinations.items():
315 sysParams = [];
316 jobParams = [];
317 # for each destination and its options, check if we have to report any background monitoring data
318 if(options['sys_monitoring'] and options['sys_data_sent'] + options['sys_interval'] < now):
319 for param, active in options.items():
320 m = re.match("sys_(.+)", param);
321 if(m != None and active):
322 param = m.group(1);
323 if not (param == 'monitoring' or param == 'interval' or param == 'data_sent'):
324 sysParams.append(param)
325 options['sys_data_sent'] = now;
326 if(options['job_monitoring'] and options['job_data_sent'] + options['job_interval'] < now):
327 for param, active in options.items():
328 m = re.match("job_(.+)", param);
329 if(m != None and active):
330 param = m.group(1);
331 if not (param == 'monitoring' or param == 'interval' or param == 'data_sent'):
332 jobParams.append(param);
333 options['job_data_sent'] = now;
334 if(options['general_info'] and options['general_data_sent'] + 2 * int(options['sys_interval']) < now):
335 for param, active in options.items():
336 if not (param.startswith("sys_") or param.startswith("job_")) and active:
337 if not (param == 'general_info' or param == 'general_data_sent'):
338 sysParams.append(param);
339 sysResults = {}
340 if(len(sysParams) > 0):
341 sysResults = self.procInfo.getSystemData(sysParams);
342 if(len(sysResults.keys()) > 0):
343 self.__directSendParams(destination, self.__defaultSysMonCluster, self.__defaultSysMonNode, -1, sysResults);
344 for pid, props in self.monitoredJobs.items():
345 jobResults = {};
346 if(len(jobParams) > 0):
347 jobResults = self.procInfo.getJobData(pid, jobParams);
348 if(len(jobResults) > 0):
349 self.__directSendParams(destination, props['CLUSTER_NAME'], props['NODE_NAME'], -1, jobResults);
350 self.__bgMonitorLock.release();
351
352 def setLogLevel (self, strLevel):
353 """
354 Change the log level. Given level is a string, one of 'FATAL', 'ERROR', 'WARNING',
355 'INFO', 'NOTICE', 'DEBUG'.
356 """
357 self.logger.setLogLevel(strLevel);
358
359 #########################################################################################
360 # Internal functions - Config reloader thread
361 #########################################################################################
362
363 def __configLoader(self):
364 """
365 Main loop of the thread that checks for changes and reloads the configuration
366 """
367 while True:
368 time.sleep(self.configRecheckInterval)
369 if self.configRecheck:
370 self.__reloadAddresses()
371 self.logger.log(Logger.DEBUG, "Config reloaded. Seleeping for "+`self.configRecheckInterval`+" sec.");
372
373 def __reloadAddresses(self):
374 """
375 Refresh destinations hash, by loading data from all sources in configAddresses
376 """
377 newDestinations = {}
378 for src in self.configAddresses:
379 self.__initializeFromFile(src, newDestinations)
380 # avoid changing config in the middle of sending packets to previous destinations
381 self.__configUpdateLock.acquire()
382 self.destinations = newDestinations
383 self.__configUpdateLock.release()
384
385 def __addDestination (self, aDestination, tempDestinations, options = __defaultOptions):
386 """
387 Add a destination to the list.
388
389 aDestination is a string of the form "{hostname|ip}[:port] [passwd]" without quotes.
390 If the port is not given, it will be used the default port (8884)
391 If the password is missing, it will be considered an empty string
392 """
393 aDestination = aDestination.strip().replace('\t', ' ')
394 while aDestination != aDestination.replace(' ', ' '):
395 aDestination = aDestination.replace(' ', ' ')
396 sepPort = aDestination.find (':')
397 sepPasswd = aDestination.rfind (' ')
398 if sepPort >= 0:
399 host = aDestination[0:sepPort].strip()
400 if sepPasswd > sepPort + 1:
401 port = aDestination[sepPort+1:sepPasswd].strip()
402 passwd = aDestination[sepPasswd:].strip()
403 else:
404 port = aDestination[sepPort+1:].strip()
405 passwd = ""
406 else:
407 port = str(self.__defaultPort)
408 if sepPasswd >= 0:
409 host = aDestination[0:sepPasswd].strip()
410 passwd = aDestination[sepPasswd:].strip()
411 else:
412 host = aDestination.strip()
413 passwd = ""
414 if (not port.isdigit()):
415 self.logger.log(Logger.WARNING, "Bad value for port number "+`port`+" in "+aDestination+" destination");
416 return
417 alreadyAdded = False
418 port = int(port)
419 host = socket.gethostbyname(host) # convert hostnames to IP addresses to avoid suffocating DNSs
420 for h, p, w in tempDestinations.keys():
421 if (h == host) and (p == port):
422 alreadyAdded = True
423 break
424 if not alreadyAdded:
425 self.logger.log(Logger.INFO, "Adding destination "+host+':'+`port`+' '+passwd);
426 tempDestinations[(host, port, passwd)] = self.__defaultOptions;
427 if options != self.__defaultOptions:
428 # we have to overwrite defaults with given options
429 for key, value in options.items():
430 self.logger.log(Logger.NOTICE, "Overwritting option: "+key+" = "+`value`);
431 tempDestinations[(host, port, passwd)][key] = value;
432 else:
433 self.logger.log(Logger.NOTICE, "Destination "+host+":"+port+" "+passwd+" already added. Skipping it");
434
435 def __initializeFromFile (self, confFileName, tempDestinations):
436 """
437 Load destinations from confFileName file. If it's an URL (starts with "http://")
438 load configuration from there. Put all destinations in tempDestinations hash.
439
440 Calls addDestination for each line that doesn't start with # and
441 has non-whitespace characters on it
442 """
443 try:
444 if confFileName.find ("http://") == 0:
445 confFile = urllib2.urlopen (confFileName)
446 else:
447 confFile = open (confFileName)
448 except urllib2.HTTPError, e:
449 self.logger.log(Logger.ERROR, "Cannot open "+confFileName);
450 if e.code == 401:
451 self.logger.log(Logger.ERROR, 'HTTPError: not authorized.');
452 elif e.code == 404:
453 self.logger.log(Logger.ERROR, 'HTTPError: not found.');
454 elif e.code == 503:
455 self.logger.log(Logger.ERROR, 'HTTPError: service unavailable.');
456 else:
457 self.logger.log(Logger.ERROR, 'HTTPError: unknown error.');
458 return
459 except urllib2.URLError, ex:
460 self.logger.log(Logger.ERROR, "Cannot open "+confFileName);
461 self.logger.log(Logger.ERROR, "URL Error: "+str(ex.reason[1]));
462 return
463 except IOError, ex:
464 self.logger.log(Logger.ERROR, "Cannot open "+confFileName);
465 self.logger.log(Logger.ERROR, "IOError: "+str(ex));
466 return
467 self.logger.log(Logger.INFO, "Adding destinations from "+confFileName);
468 dests = []
469 opts = {}
470 while(True):
471 line = confFile.readline();
472 if line == '':
473 break;
474 line = line.strip()
475 self.logger.log(Logger.DEBUG, "Reading line "+line);
476 if (len(line) == 0) or (line[0] == '#'):
477 continue
478 elif line.startswith("xApMon_"):
479 m = re.match("xApMon_(.*)", line);
480 if m != None:
481 m = re.match("(\S+)\s*=\s*(\S+)", m.group(1));
482 if m != None:
483 param = m.group(1); value = m.group(2);
484 if(value.upper() == "ON"):
485 value = True;
486 elif(value.upper() == "OFF"):
487 value = False;
488 elif(param.endswith("_interval")):
489 value = int(value);
490 if param == "loglevel":
491 self.logger.setLogLevel(value);
492 elif param == "conf_recheck":
493 self.configRecheck = value;
494 elif param == "recheck_interval":
495 self.configRecheckInterval = value;
496 elif param.endswith("_data_sent"):
497 pass; # don't reset time in sys/job/general/_data_sent
498 else:
499 opts[param] = value;
500 else:
501 dests.append(line);
502 confFile.close ()
503 for line in dests:
504 self.__addDestination(line, tempDestinations, opts)
505
506 ###############################################################################################
507 # Internal functions - Background monitor thread
508 ###############################################################################################
509
510 def __bgMonitor (self):
511 while True:
512 time.sleep(10);
513 if self.performBgMonitoring:
514 self.sendBgMonitoring();
515
516 ###############################################################################################
517 # Internal helper functions
518 ###############################################################################################
519
520 def __directSendParams (self, destination, clusterName, nodeName, timeStamp, params):
521 xdrPacker = xdrlib.Packer ()
522 host, port, passwd = destination
523 self.logger.log(Logger.DEBUG, "Building XDR packet for ["+str(clusterName)+"] <"+str(nodeName)+"> len:"+str(len(params)));
524 xdrPacker.pack_string ("v:"+self.__version+"p:"+passwd)
525 xdrPacker.pack_string (clusterName)
526 xdrPacker.pack_string (nodeName)
527 xdrPacker.pack_int (len(params))
528 if type(params) == type( {} ):
529 for name, value in params.iteritems():
530 self.__packParameter(xdrPacker, name, value)
531 elif type(params) == type( [] ):
532 for name, value in params:
533 self.logger.log(Logger.DEBUG, "Adding parameter "+name+" = "+str(value));
534 self.__packParameter(xdrPacker, name, value)
535 else:
536 self.logger.log(Logger.WARNING, "Unsupported params type in sendParameters: " + str(type(params)));
537 if(timeStamp > 0):
538 xdrPacker.pack_int(timeStamp);
539 buffer = xdrPacker.get_buffer();
540 # send this buffer to the destination, using udp datagrams
541 try:
542 self.__udpSocket.sendto(buffer, (host, port))
543 self.logger.log(Logger.DEBUG, "Packet sent");
544 except socket.error, msg:
545 self.logger.log(Logger.ERROR, "Cannot send packet to "+host+":"+str(port)+" "+passwd+": "+str(msg[1]));
546 xdrPacker.reset()
547
548 def __packParameter(self, xdrPacker, name, value):
549 try:
550 typeValue = self.__valueTypes[type(value)]
551 xdrPacker.pack_string (name)
552 xdrPacker.pack_int (typeValue)
553 self.__packFunctions[typeValue] (xdrPacker, value)
554 self.logger.log(Logger.DEBUG, "Adding parameter "+str(name)+" = "+str(value));
555 except Exception, ex:
556 print "ApMon: error packing %s = %s; got %s" % (name, str(value), ex)
557
558 # Destructor
559 def __del__(self):
560 self.__udpSocket.close();
561
562 ################################################################################################
563 # Private variables. Don't touch
564 ################################################################################################
565
566 __valueTypes = {
567 type("string"): 0, # XDR_STRING (see ApMon.h from C/C++ ApMon version)
568 type(1): 2, # XDR_INT32
569 type(1.0): 5}; # XDR_REAL64
570
571 __packFunctions = {
572 0: xdrlib.Packer.pack_string,
573 2: xdrlib.Packer.pack_int,
574 5: xdrlib.Packer.pack_double }
575
576 __defaultUserCluster = "ApMon_UserSend";
577 __defaultUserNode = socket.getfqdn();
578 __defaultSysMonCluster = "ApMon_SysMon";
579 __defaultSysMonNode = socket.getfqdn();
580
581 __initializedOK = True
582 __configUpdateLock = threading.Lock()
583 __bgMonitorLock = threading.Lock()
584
585 __defaultPort = 8884
586 __defaultLogLevel = Logger.INFO
587 __version = "2.0.2-py" # apMon version number
588