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

Last change on this file since 9072 was 9009, checked in by steve, 11 years ago

Commit latest version

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