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

Last change on this file since 8764 was 8764, checked in by steve, 12 years ago

Changed get_user_name using getpass as suggested by Roberto Vidmar

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