source: trunk/anuga_core/source/anuga/utilities/system_tools.py @ 8758

Last change on this file since 8758 was 8299, checked in by pittj, 13 years ago

Adding memory functions

File size: 17.8 KB
Line 
1"""Implementation of tools to do with system administration made as platform independent as possible.
2
3
4"""
5
6import sys
7import os
8import string
9import urllib
10import urllib2
11import getpass
12import tarfile
13import warnings
14import pdb
15
16try:
17    import hashlib
18except ImportError:
19    import md5 as hashlib
20
21import anuga.utilities.log as log
22
23
24def log_to_file(filename, s, verbose=False, mode='a'):
25    """Log string to file name
26    """
27
28    fid = open(filename, mode)
29    if verbose: print s
30    fid.write(s + '\n')
31    fid.close()
32
33
34def get_user_name():
35    """Get user name provide by operating system
36    """
37
38    if sys.platform == 'win32':
39        #user = os.getenv('USERPROFILE')
40        user = os.getenv('USERNAME')
41    else:
42        user = os.getenv('LOGNAME')
43
44
45    return user   
46
47def get_host_name():
48    """Get host name provide by operating system
49    """
50
51    if sys.platform == 'win32':
52        host = os.getenv('COMPUTERNAME')
53    else:
54        host = os.uname()[1]
55
56
57    return host   
58
59   
60   
61   
62   
63def __get_revision_from_svn_entries__():
64    """Get a subversion revision number from the .svn/entries file."""
65
66   
67    msg = '''
68No version info stored and command 'svn' is not recognised on the system PATH.
69
70If ANUGA has been installed from a distribution e.g. as obtained from SourceForge,
71the version info should be available in the automatically generated file
72'stored_version_info.py' in the anuga root directory.
73
74If run from a Subversion sandpit, ANUGA will try to obtain the version info by
75using the command 'svn info'.  In this case, make sure the command line client
76'svn' is accessible on the system path.  Simply aliasing 'svn' to the binary will
77not work.
78
79If you are using Windows, you have to install the file svn.exe which can be
80obtained from http://www.collab.net/downloads/subversion.
81
82Good luck!
83'''
84
85    try:
86        fd = open(os.path.join('.svn', 'entries'))
87    except:
88        raise Exception, msg
89
90    line = fd.readlines()[3]
91    fd.close()
92    try:
93        revision_number = int(line)
94    except:
95        msg = ".svn/entries, line 4 was '%s'?" % line.strip()
96        raise Exception, msg
97
98    return revision_number
99
100def __get_revision_from_svn_client__():
101    """Get a subversion revision number from an svn client."""
102
103    if sys.platform[0:3] == 'win':
104        try:
105            fid = os.popen(r'C:\Program Files\TortoiseSVN\bin\SubWCRev.exe')
106        except:
107            return __get_revision_from_svn_entries__()
108        else:
109            version_info = fid.read()
110            if version_info == '':
111                return __get_revision_from_svn_entries__()
112
113        # split revision number from data
114        for line in version_info.split('\n'):
115            if line.startswith('Updated to revision '):
116                break
117
118        fields = line.split(' ')
119        msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
120        assert fields[0].startswith('Updated'), msg
121
122        try:
123            revision_number = int(fields[3])
124        except:
125            msg = ('Revision number must be an integer. I got "%s" from '
126                   '"SubWCRev.exe".' % fields[3])
127            raise Exception, msg
128    else:                   # assume Linux
129        try:
130            fid = os.popen('svn info . 2>/dev/null')
131        except:
132            return __get_revision_from_svn_entries__()
133        else:
134            version_info = fid.read()
135            if version_info == '':
136                return __get_revision_from_svn_entries__()
137
138        # Split revision number from data
139        for line in version_info.split('\n'):
140            if line.startswith('Revision:'):
141                break
142       
143        fields = line.split(':')
144        msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
145        assert fields[0].startswith('Revision'), msg
146       
147       
148        #if ':' in version_info:
149        #    revision_number, _ = version_info.split(':', 1)
150        #    msg = ('Some modules have not been checked in. '
151        #           'Using last version from repository: %s' % revision_number)
152        #    warnings.warn(msg)
153        #else:
154        #    revision_number = version_info
155
156        try:
157            revision_number = int(fields[1])
158        except:
159            msg = ("Revision number must be an integer. I got '%s' from "
160                   "'svn'." % fields[1])
161            raise Exception, msg
162
163    return revision_number
164
165   
166   
167   
168   
169def get_revision_number():
170    """Get the version number of this repository copy.
171
172    Try getting data from stored_version_info.py first, otherwise
173    try using SubWCRev.exe (Windows) or svnversion (linux), otherwise
174    try reading file .svn/entries for version information, otherwise
175    throw an exception.
176
177    NOTE: This requires that the command svn is on the system PATH
178    (simply aliasing svn to the binary will not work)
179    """
180
181    # try to get revision information from stored_version_info.py
182    try:
183        from anuga.stored_version_info import version_info
184    except:
185        return __get_revision_from_svn_client__()
186
187    # split revision number from data
188    for line in version_info.split('\n'):
189        if line.startswith('Revision:'):
190            break
191
192    fields = line.split(':')
193    msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
194    assert fields[0].startswith('Revision'), msg
195
196    try:
197        revision_number = int(fields[1])
198    except:
199        msg = ("Revision number must be an integer. I got '%s'.\n"
200               'Check that the command svn is on the system path.'
201               % fields[1])
202        raise Exception, msg
203
204    return revision_number
205
206
207def store_version_info(destination_path='.', verbose=False):
208    """Obtain current version from Subversion and store it.
209   
210    Title: store_version_info()
211
212    Author: Ole Nielsen (Ole.Nielsen@ga.gov.au)
213
214    CreationDate: January 2006
215
216    Description:
217        This function obtains current version from Subversion and stores it
218        is a Python file named 'stored_version_info.py' for use with
219        get_version_info()
220
221        If svn is not available on the system PATH, an Exception is thrown
222    """
223
224    # Note (Ole): This function should not be unit tested as it will only
225    # work when running out of the sandpit. End users downloading the
226    # ANUGA distribution would see a failure.
227    #
228    # FIXME: This function should really only be used by developers (
229    # (e.g. for creating new ANUGA releases), so maybe it should move
230    # to somewhere else.
231   
232    import config
233
234    try:
235        fid = os.popen('svn info')
236    except:
237        msg = 'Command "svn" is not recognised on the system PATH'
238        raise Exception(msg)
239    else:   
240        txt = fid.read()
241        fid.close()
242
243
244        # Determine absolute filename
245        if destination_path[-1] != os.sep:
246            destination_path += os.sep
247           
248        filename = destination_path + config.version_filename
249
250        fid = open(filename, 'w')
251
252        docstring = 'Stored version info.\n\n'
253        docstring += 'This file provides the version for distributions '
254        docstring += 'that are not accessing Subversion directly.\n'
255        docstring += 'The file is automatically generated and should not '
256        docstring += 'be modified manually.\n'
257        fid.write('"""%s"""\n\n' %docstring)
258       
259        fid.write('version_info = """\n%s"""' %txt)
260        fid.close()
261
262
263        if verbose is True:
264            log.critical('Version info stored to %s' % filename)
265
266
267def safe_crc(string):
268    """64 bit safe crc computation.
269
270    See http://docs.python.org/library/zlib.html#zlib.crc32:
271
272        To generate the same numeric value across all Python versions
273        and platforms use crc32(data) & 0xffffffff.
274    """
275
276    from zlib import crc32
277
278    return crc32(string) & 0xffffffff
279
280
281def compute_checksum(filename, max_length=2**20):
282    """Compute the CRC32 checksum for specified file
283
284    Optional parameter max_length sets the maximum number
285    of bytes used to limit time used with large files.
286    Default = 2**20 (1MB)
287    """
288
289    fid = open(filename, 'rb') # Use binary for portability
290    crcval = safe_crc(fid.read(max_length))
291    fid.close()
292
293    return crcval
294
295def get_pathname_from_package(package):
296    """Get pathname of given package (provided as string)
297
298    This is useful for reading files residing in the same directory as
299    a particular module. Typically, this is required in unit tests depending
300    on external files.
301
302    The given module must start from a directory on the pythonpath
303    and be importable using the import statement.
304
305    Example
306    path = get_pathname_from_package('anuga.utilities')
307
308    """
309
310    exec('import %s as x' %package)
311
312    path = x.__path__[0]
313   
314    return path
315
316    # Alternative approach that has been used at times
317    #try:
318    #    # When unit test is run from current dir
319    #    p1 = read_polygon('mainland_only.csv')
320    #except:
321    #    # When unit test is run from ANUGA root dir
322    #    from os.path import join, split
323    #    dir, tail = split(__file__)
324    #    path = join(dir, 'mainland_only.csv')
325    #    p1 = read_polygon(path)
326       
327   
328def clean_line(str, delimiter):
329    """Split a string into 'clean' fields.
330
331    str        the string to process
332    delimiter  the delimiter string to split 'line' with
333
334    Returns a list of 'cleaned' field strings.
335
336    Any fields that were initially zero length will be removed.
337    If a field contains '\n' it isn't zero length.
338    """
339
340    return [x.strip() for x in str.strip().split(delimiter) if x != '']
341
342
343################################################################################
344# The following two functions are used to get around a problem with numpy and
345# NetCDF files.  Previously, using Numeric, we could take a list of strings and
346# convert to a Numeric array resulting in this:
347#     Numeric.array(['abc', 'xy']) -> [['a', 'b', 'c'],
348#                                      ['x', 'y', ' ']]
349#
350# However, under numpy we get:
351#     numpy.array(['abc', 'xy']) -> ['abc',
352#                                    'xy']
353#
354# And writing *strings* to a NetCDF file is problematic.
355#
356# The solution is to use these two routines to convert a 1-D list of strings
357# to the 2-D list of chars form and back.  The 2-D form can be written to a
358# NetCDF file as before.
359#
360# The other option, of inverting a list of tag strings into a dictionary with
361# keys being the unique tag strings and the key value a list of indices of where
362# the tag string was in the original list was rejected because:
363#    1. It's a lot of work
364#    2. We'd have to rewite the I/O code a bit (extra variables instead of one)
365#    3. The code below is fast enough in an I/O scenario
366################################################################################
367
368def string_to_char(l):
369    '''Convert 1-D list of strings to 2-D list of chars.'''
370
371    if not l:
372        return []
373
374    if l == ['']:
375        l = [' ']
376
377    maxlen = reduce(max, map(len, l))
378    ll = [x.ljust(maxlen) for x in l]
379    result = []
380    for s in ll:
381        result.append([x for x in s])
382    return result
383
384
385def char_to_string(ll):
386    '''Convert 2-D list of chars to 1-D list of strings.'''
387
388    return map(string.rstrip, [''.join(x) for x in ll])
389
390################################################################################
391
392def get_vars_in_expression(source):
393    '''Get list of variable names in a python expression.'''
394
395    import compiler
396    from compiler.ast import Node
397
398    def get_vars_body(node, var_list=[]):
399        if isinstance(node, Node):
400            if node.__class__.__name__ == 'Name':
401                for child in node.getChildren():
402                    if child not in var_list:
403                        var_list.append(child)
404            for child in node.getChildren():
405                if isinstance(child, Node):
406                    for child in node.getChildren():
407                        var_list = get_vars_body(child, var_list)
408                    break
409
410        return var_list
411
412    return get_vars_body(compiler.parse(source))
413
414
415def get_web_file(file_url, file_name, auth=None, blocksize=1024*1024):
416    '''Get a file from the web (HTTP).
417
418    file_url:  The URL of the file to get
419    file_name: Local path to save loaded file in
420    auth:      A tuple (httpproxy, proxyuser, proxypass)
421    blocksize: Block size of file reads
422   
423    Will try simple load through urllib first.  Drop down to urllib2
424    if there is a proxy and it requires authentication.
425
426    Environment variable HTTP_PROXY can be used to supply proxy information.
427    PROXY_USERNAME is used to supply the authentication username.
428    PROXY_PASSWORD supplies the password, if you dare!
429    '''
430
431    # Simple fetch, if fails, check for proxy error
432    try:
433        urllib.urlretrieve(file_url, file_name)
434        return (True, auth)     # no proxy, no auth required
435    except IOError, e:
436        if e[1] == 407:     # proxy error
437            pass
438        elif e[1][0] == 113:  # no route to host
439            print 'No route to host for %s' % file_url
440            return (False, auth)    # return False
441        else:
442            print 'Unknown connection error to %s' % file_url
443            return (False, auth)
444
445    # We get here if there was a proxy error, get file through the proxy
446    # unpack auth info
447    try:
448        (httpproxy, proxyuser, proxypass) = auth
449    except:
450        (httpproxy, proxyuser, proxypass) = (None, None, None)
451
452    # fill in any gaps from the environment
453    if httpproxy is None:
454        httpproxy = os.getenv('HTTP_PROXY')
455    if proxyuser is None:
456        proxyuser = os.getenv('PROXY_USERNAME')
457    if proxypass is None:
458        proxypass = os.getenv('PROXY_PASSWORD')
459
460    # Get auth info from user if still not supplied
461    if httpproxy is None or proxyuser is None or proxypass is None:
462        print '-'*72
463        print ('You need to supply proxy authentication information.')
464        if httpproxy is None:
465            httpproxy = raw_input('                    proxy server: ')
466        else:
467            print '         HTTP proxy was supplied: %s' % httpproxy
468        if proxyuser is None:
469            proxyuser = raw_input('                  proxy username: ') 
470        else:
471            print 'HTTP proxy username was supplied: %s' % proxyuser
472        if proxypass is None:
473            proxypass = getpass.getpass('                  proxy password: ')
474        else:
475            print 'HTTP proxy password was supplied: %s' % '*'*len(proxyuser)
476        print '-'*72
477
478    # the proxy URL cannot start with 'http://', we add that later
479    httpproxy = httpproxy.lower()
480    if httpproxy.startswith('http://'):
481        httpproxy = httpproxy.replace('http://', '', 1)
482
483    # open remote file
484    proxy = urllib2.ProxyHandler({'http': 'http://' + proxyuser
485                                              + ':' + proxypass
486                                              + '@' + httpproxy})
487    authinfo = urllib2.HTTPBasicAuthHandler()
488    opener = urllib2.build_opener(proxy, authinfo, urllib2.HTTPHandler)
489    urllib2.install_opener(opener)
490    try:
491        webget = urllib2.urlopen(file_url)
492    except urllib2.HTTPError, e:
493        print 'Error received from proxy:\n%s' % str(e)
494        print 'Possibly the user/password is wrong.'
495        return (False, (httpproxy, proxyuser, proxypass))
496
497    # transfer file to local filesystem
498    fd = open(file_name, 'wb')
499    while True:
500        data = webget.read(blocksize)
501        if len(data) == 0:
502            break
503        fd.write(data)
504    fd.close
505    webget.close()
506
507    # return successful auth info
508    return (True, (httpproxy, proxyuser, proxypass))
509
510
511def tar_file(files, tarname):
512    '''Compress a file or directory into a tar file.'''
513
514    if isinstance(files, basestring):
515        files = [files]
516
517    o = tarfile.open(tarname, 'w:gz')
518    for file in files:
519        o.add(file)
520    o.close()
521
522
523def untar_file(tarname, target_dir='.'):
524    '''Uncompress a tar file.'''
525
526    o = tarfile.open(tarname, 'r:gz')
527    members = o.getmembers()
528    for member in members:
529        o.extract(member, target_dir)
530    o.close()
531
532
533def get_file_hexdigest(filename, blocksize=1024*1024*10):
534    '''Get a hex digest of a file.'''
535
536    if hashlib.__name__ == 'hashlib':
537        m = hashlib.md5()       # new - 'hashlib' module
538    else:
539        m = hashlib.new()       # old - 'md5' module - remove once py2.4 gone
540    fd = open(filename, 'r')
541           
542    while True:
543        data = fd.read(blocksize)
544        if len(data) == 0:
545            break
546        m.update(data)
547                                                               
548    fd.close()
549    return m.hexdigest()
550
551
552def make_digest_file(data_file, digest_file):
553    '''Create a file containing the hex digest string of a data file.'''
554   
555    hexdigest = get_file_hexdigest(data_file)
556    fd = open(digest_file, 'w')
557    fd.write(hexdigest)
558    fd.close()
559
560
561def file_length(in_file):
562    '''Function to return the length of a file.'''
563
564    fid = open(in_file)
565    data = fid.readlines()
566    fid.close()
567    return len(data)
568
569
570#### Memory functions
571_proc_status = '/proc/%d/status' % os.getpid()
572_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,
573          'KB': 1024.0, 'MB': 1024.0*1024.0}
574_total_memory = 0.0
575_last_memory = 0.0
576
577def _VmB(VmKey):
578    '''private method'''
579    global _proc_status, _scale
580     # get pseudo file  /proc/<pid>/status
581    try:
582        t = open(_proc_status)
583        v = t.read()
584        t.close()
585    except:
586        return 0.0  # non-Linux?
587     # get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
588    i = v.index(VmKey)
589    v = v[i:].split(None, 3)  # whitespace
590    if len(v) < 3:
591        return 0.0  # invalid format?
592     # convert Vm value to MB
593    return (float(v[1]) * _scale[v[2]]) / (1024.0*1024.0)
594
595
596def MemoryUpdate(print_msg=None,str_return=False):
597    '''print memory usage stats in MB.
598    '''
599    global _total_memory, _last_memory
600
601    _last_memory = _total_memory
602    _total_memory = _VmB('VmSize:')
603
604    #if print_msg is not None:
605    mem_diff = _total_memory - _last_memory
606    return (mem_diff,_total_memory)
607   
608
Note: See TracBrowser for help on using the repository browser.