Statistiques
| Révision :

root / ase / utils / memory.py @ 16

Historique | Voir | Annoter | Télécharger (16,32 ko)

1
import os
2
import numpy as np
3

    
4
from UserDict import DictMixin
5

    
6
# -------------------------------------------------------------------
7

    
8
class MemoryBase(object, DictMixin):
9
    """Virtual memory (VM) statistics of the current process
10
    obtained from the relevant entries in /proc/<pid>/status:
11
    VmPeak       Peak virtual memory size in bytes.
12
    VmLck        ???
13
    VmHWM        Peak resident set size ("high water mark") in bytes.
14
    VmRSS        Resident memory usage in bytes.
15
    VmSize       VM usage of the entire process in bytes.
16
    VmData       VM usage of heap in bytes.
17
    VmStk        VM usage of stack in bytes.
18
    VmExe        VM usage of exe's and statically linked libraries in bytes.
19
    VmLib        VM usage of dynamically linked libraries in bytes.
20
    VmPTE        ???
21

22
    Note that VmSize > VmData + VmStk + VmExe + VmLib due to overhead.
23
    """
24

    
25
    _scale = {'KB':1024.0, 'MB':1024.0**2}
26
    _keys = ('VmPeak', 'VmLck', 'VmHWM', 'VmRSS', 'VmSize', 'VmData', \
27
            'VmStk', 'VmExe', 'VmLib', 'VmPTE')
28

    
29
    def __init__(self, verbose=0):
30
        self.verbose = verbose
31
        if self.verbose>=2: print 'MemoryBase.__init__'
32
        object.__init__(self)
33
        self._values = np.empty(len(self._keys), dtype=np.float)
34

    
35
    def __repr__(self):
36
        """Return a representation of recorded VM statistics.
37
        x.__repr__() <==> repr(x)"""
38
        if self.verbose>=2: print 'MemoryBase.__repr__'
39
        s = object.__repr__(self)
40
        w = max(map(len, self._keys))
41
        unit = 'MB'
42
        for k,v in self.iteritems():
43
            res = '<N/A>'
44
            if not np.isnan(v):
45
                res = '%8.3f %s' % (v/self._scale[unit], unit)
46
            s += '\n\t' + k.ljust(w) + ': ' + res.rjust(8)
47
        return s
48

    
49
    def __len__(self):
50
        """Number of VM keys which have not been outdated.
51
        x.__len__() <==> len(x)"""
52
        if self.verbose>=3: print 'MemoryBase.__len__'
53
        return np.sum(~np.isnan(self._values))
54

    
55
    def __getitem__(self, key):
56
        """Return floating point number associated with a VM key.
57
        x.__getitem__(y) <==> x[y]"""
58
        if self.verbose>=2: print 'MemoryBase.__getitem__'
59
        if key not in self:
60
            raise KeyError(key)
61
        i = self.keys().index(key)
62
        return self._values[i]
63

    
64
    def __setitem__(self, key, value):
65
        """x.__setitem__(i, y) <==> x[i]=y"""
66
        if self.verbose>=2: print 'MemoryBase.__setitem__'
67
        raise Exception('Virtual member function.')
68

    
69
    def __delitem__(self, key):
70
        """x.__delitem__(y) <==> del x[y]"""
71
        if self.verbose>=2: print 'MemoryBase.__delitem__'
72
        raise Exception('Virtual member function.')
73

    
74
    def clear(self):
75
        """D.clear() -> None.  Remove all items from D."""
76
        if self.verbose>=1: print 'MemoryBase.clear'
77
        raise Exception('Virtual member function.')
78

    
79
    def update(self, other=None):
80
        """D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
81
        if self.verbose>=1: print 'MemoryBase.update'
82
        DictMixin.update(self, other)
83

    
84
    def copy(self):
85
        """Return a shallow copy of a VM statistics instance.
86
        D.copy() -> a shallow copy of D"""
87
        if self.verbose>=1: print 'MemoryBase.copy'
88
        res = object.__new__(self.__class__)
89
        MemoryBase.__init__(res, self.verbose)
90
        DictMixin.update(res, self)
91
        return res
92

    
93
    def has_key(self, key): #necessary to avoid infinite recursion
94
        """Return boolean to indicate whether key is a supported VM key.
95
        D.has_key(k) -> True if D has a key k, else False"""
96
        if self.verbose>=3: print 'MemoryBase.has_key'
97
        return key in self._keys
98

    
99
    def keys(self):
100
        """Return list of supported VM keys.
101
        D.keys() -> list of D's keys"""
102
        if self.verbose>=3: print 'MemoryBase.keys'
103
        return list(self._keys)
104

    
105
    def values(self):
106
        """Return list of recorded VM statistics.
107
        D.values() -> list of D's values"""
108
        if self.verbose>=3: print 'MemoryBase.values'
109
        return list(self._values)
110

    
111
    def get(self, key, default=None):
112
        """Return floating point number associated with a VM key.
113
        D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
114
        if self.verbose>=1: print 'MemoryBase.get'
115
        v = self[key]
116

    
117
        if type(default) in [int,float]:
118
            default = np.float_(default)
119
        if default is not None and not isinstance(default, np.floating):
120
            raise ValueError('Default value must be a floating point number.')
121

    
122
        if default is not None and np.isnan(v):
123
            return default
124
        else:
125
            return v
126

    
127
    def setdefault(self, key, default=None):
128
        """Return floating point number associated with a VM key.
129
        D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D"""
130
        if self.verbose>=1: print 'MemoryBase.setdefault'
131
        v = self[key]
132

    
133
        if type(default) in [int,float]:
134
            default = np.float_(default)
135
        if default is not None and not isinstance(default, np.floating):
136
            raise ValueError('Default value must be a floating point number.')
137

    
138
        if default is not None and np.isnan(v):
139
            self[key] = default
140
            return default
141
        else:
142
            return v
143

    
144
    def pop(self, key, default=None):
145
        """Return floating point number for a VM key and mark it as outdated.
146
        D.pop(k[,d]) -> v, remove specified key and return the corresponding value
147
        If key is not found, d is returned if given, otherwise KeyError is raised"""
148

    
149
        if self.verbose>=1: print 'MemoryBase.pop'
150
        v = self[key]
151

    
152
        if type(default) in [int,float]:
153
            default = np.float_(default)
154
        if default is not None and not isinstance(default, np.floating):
155
            raise ValueError('Default value must be a floating point number.')
156

    
157
        if default is not None and np.isnan(v):
158
            return default
159
        else:
160
            del self[key]
161
            return v
162

    
163
    def popitem(self):
164
        """Return floating point number for some not-yet outdated VM key.
165
        D.popitem() -> (k, v), remove and return some (key, value) pair as a
166
        2-tuple; but raise KeyError if D is empty"""
167
        if self.verbose>=1: print 'MemoryBase.popitem'
168

    
169
        for k,v in self.iteritems():
170
            if not np.isnan(v):
171
                del self[k]
172
                return (k,v)
173
        raise KeyError
174

    
175
    def __add__(self, other):
176
        """x.__add__(y) <==> x+y"""
177
        if self.verbose>=1: print 'MemoryBase.__add__(%s,%s)' \
178
            % (object.__repr__(self), object.__repr__(other))
179
        res = self.copy()
180
        if isinstance(other, MemoryBase):
181
            res._values.__iadd__(other._values)
182
        elif type(other) in [int,float]:
183
            res._values.__iadd__(other)
184
        else:
185
            raise TypeError('Unsupported operand type')
186
        return res
187

    
188
    def __sub__(self, other):
189
        """x.__sub__(y) <==> x-y"""
190
        if self.verbose>=1: print 'MemoryBase.__sub__(%s,%s)' \
191
            % (object.__repr__(self), object.__repr__(other))
192
        res = self.copy()
193
        if isinstance(other, MemoryBase):
194
            res._values.__isub__(other._values)
195
        elif type(other) in [int,float]:
196
            res._values.__isub__(other)
197
        else:
198
            raise TypeError('Unsupported operand type')
199
        return res
200

    
201
    def __radd__(self, other):
202
        """x.__radd__(y) <==> y+x"""
203
        if self.verbose>=1: print 'MemoryBase.__radd__(%s,%s)' \
204
            % (object.__repr__(self), object.__repr__(other))
205
        res = self.copy()
206
        if isinstance(other, MemoryBase):
207
            res._values.__iadd__(other._values)
208
        elif type(other) in [int,float]:
209
            res._values.__iadd__(other)
210
        else:
211
            raise TypeError('Unsupported operand type')
212
        return res
213

    
214
    def __rsub__(self, other):
215
        """x.__rsub__(y) <==> y-x"""
216
        if self.verbose>=1: print 'MemoryBase.__rsub__(%s,%s)' \
217
            % (object.__repr__(self), object.__repr__(other))
218
        res = self.copy()
219
        res._values.__imul__(-1.0)
220
        if isinstance(other, MemoryBase):
221
            res._values.__iadd__(other._values)
222
        elif type(other) in [int,float]:
223
            res._values.__iadd__(other)
224
        else:
225
            raise TypeError('Unsupported operand type')
226
        return res
227

    
228
# -------------------------------------------------------------------
229

    
230
class MemoryStatistics(MemoryBase):
231

    
232
    def __init__(self, verbose=0):
233
        MemoryBase.__init__(self, verbose)
234
        self.update()
235

    
236
    def __setitem__(self, key, value):
237
        """Set VM key to a floating point number.
238
        x.__setitem__(i, y) <==> x[i]=y"""
239
        if self.verbose>=2: print 'MemoryStatistics.__setitem__'
240
        if key not in self:
241
            raise KeyError(key)
242
        if type(value) in [int,float]:
243
            value = np.float_(value)
244
        if not isinstance(value, np.floating):
245
            raise ValueError('Value must be a floating point number.')
246
        i = self.keys().index(key)
247
        self._values[i] = value
248

    
249
    def __delitem__(self, key):
250
        """Mark a VK key as outdated.
251
        x.__delitem__(y) <==> del x[y]"""
252
        if self.verbose>=2: print 'MemoryStatistics.__delitem__'
253
        if key not in self:
254
            raise KeyError(key)
255
        self[key] = np.nan
256

    
257
    def clear(self):
258
        """Mark all supported VM keys as outdated.
259
        D.clear() -> None.  Remove all items from D."""
260
        if self.verbose>=1: print 'MemoryStatistics.clear'
261
        self._values[:] = np.nan
262

    
263
    def refresh(self):
264
        """Refresh all outdated VM keys by reading /proc/<pid>/status."""
265
        if self.verbose>=1: print 'MemoryBase.refresh'
266

    
267
        # NB: Linux /proc is for humans; Solaris /proc is for programs!
268
        # TODO: Use pipe from 'prstat -p <pid>' or 'pmap -x <pid> 1 1'
269

    
270
        # Skip refresh if none are outdated (i.e. nan)
271
        if not np.isnan(self._values).any():
272
            if self.verbose>=2: print 'refresh: skipping...'
273
            return
274

    
275
        try:
276
            f = open('/proc/%d/status' % os.getpid(), 'r')
277
            for line in f:
278
                k, v = line.decode('ascii').split(':')
279

    
280
                # Only refresh supported keys that are outdated (i.e. nan)
281
                if k in self and np.isnan(self[k]):
282
                    t, s = v.strip().split(None, 1)
283
                    if self.verbose >= 2:
284
                        print 'refresh: k=%s, t=%s, s=%s' % (k, t, s)
285
                    self[k] = float(t) * self._scale[s.upper()]
286

    
287
            f.close()
288
        except (IOError, UnicodeError, ValueError):
289
            # Reset on error
290
            self.clear()
291

    
292
    def update(self, other=None):
293
        """Update VM statistics from a supplied dict, else clear and refresh.
294
        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
295
        if self.verbose>=1: print 'MemoryStatistics.update'
296

    
297
        # Call to update without arguments has special meaning
298
        if other is None:
299
            self.clear()
300
            self.refresh()
301
        else:
302
            MemoryBase.update(self, other)
303

    
304
    def __iadd__(self, other):
305
        """x.__iadd__(y) <==> x+=y"""
306
        if self.verbose>=1: print 'MemoryStatistics.__iadd__(%s,%s)' \
307
            % (object.__repr__(self), object.__repr__(other))
308
        if isinstance(other, MemoryBase):
309
            self._values.__iadd__(other._values)
310
        elif type(other) in [int,float]:
311
            self._values.__iadd__(other)
312
        else:
313
            raise TypeError('Unsupported operand type')
314
        return self
315

    
316
    def __isub__(self, other):
317
        """x.__isub__(y) <==> x-=y"""
318
        if self.verbose>=1: print 'MemoryStatistics.__isub__(%s,%s)' \
319
            % (object.__repr__(self), object.__repr__(other))
320
        if isinstance(other, MemoryBase):
321
            self._values.__isub__(other._values)
322
        elif type(other) in [int,float]:
323
            self._values.__isub__(other)
324
        else:
325
            raise TypeError('Unsupported operand type')
326
        return self
327

    
328
# -------------------------------------------------------------------
329

    
330
#http://www.eecho.info/Echo/python/singleton/
331
#http://mail.python.org/pipermail/python-list/2007-July/622333.html
332

    
333
class Singleton(object):
334
    """A Pythonic Singleton object."""
335
    def __new__(cls, *args, **kwargs):
336
        if '_inst' not in vars(cls):
337
            cls._inst = object.__new__(cls, *args, **kwargs)
338
            #cls._inst = super(type, cls).__new__(cls, *args, **kwargs)
339
        return cls._inst
340

    
341
class MemorySingleton(MemoryBase, Singleton):
342
    __doc__ = MemoryBase.__doc__ + """
343
    The singleton variant is immutable once it has been instantiated, which
344
    makes it suitable for recording the initial overhead of starting Python."""
345

    
346
    def __init__(self, verbose=0):
347
        if verbose>=1: print 'MemorySingleton.__init__'
348
        if '_values' not in vars(self):
349
            if verbose>=1: print 'MemorySingleton.__init__ FIRST!'
350
            # Hack to circumvent singleton immutability
351
            self.__class__ = MemoryStatistics
352
            self.__init__(verbose)
353
            self.__class__ = MemorySingleton
354

    
355
    def __setitem__(self, key, value):
356
        """Disabled for the singleton.
357
        x.__setitem__(i, y) <==> x[i]=y"""
358
        if self.verbose>=2: print 'MemorySingleton.__setitem__'
359
        raise ReferenceError('Singleton is immutable.')
360

    
361
    def __delitem__(self, key):
362
        """Disabled for the singleton.
363
        x.__delitem__(y) <==> del x[y]"""
364
        if self.verbose>=2: print 'MemorySingleton.__delitem__'
365
        raise ReferenceError('Singleton is immutable.')
366

    
367
    def clear(self):
368
        """Disabled for the singleton.
369
        D.clear() -> None.  Remove all items from D."""
370
        if self.verbose>=1: print 'MemorySingleton.clear'
371
        raise ReferenceError('Singleton is immutable.')
372

    
373
    def update(self):
374
        """Disabled for the singleton.
375
        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
376
        if self.verbose>=1: print 'MemorySingleton.update'
377
        raise ReferenceError('Singleton is immutable.')
378

    
379
    def copy(self):
380
        """Return a shallow non-singleton copy of a VM statistics instance.
381
        D.copy() -> a shallow copy of D"""
382
        if self.verbose>=1: print 'MemorySingleton.copy'
383
        # Hack to circumvent singleton self-copy
384
        self.__class__ = MemoryStatistics
385
        res = self.copy()
386
        self.__class__ = MemorySingleton
387
        return res
388

    
389
# Make sure singleton is instantiated
390
MemorySingleton()
391

    
392
# -------------------------------------------------------------------
393

    
394
# Helper functions for leak testing with NumPy arrays
395

    
396
def shapegen(size, ndims, ecc=0.5):
397
    """Return a generator of an N-dimensional array shape
398
    which approximately contains a given number of elements.
399

400
        size:       int or long in [1,inf[
401
                    The total number of elements
402
        ndims=3:    int in [1,inf[
403
                    The number of dimensions
404
        ecc=0.5:    float in ]0,1[
405
                    The eccentricity of the distribution
406
    """
407
    assert type(size) in [int,float] and size>=1
408
    assert type(ndims) is int and ndims>=1
409
    assert type(ecc) in [int,float] and ecc>0 and ecc<1
410

    
411
    for i in range(ndims-1):
412
        scale = size**(1.0/(ndims-i))
413
        c = round(np.random.uniform((1-ecc)*scale, 1.0/(1-ecc)*scale))
414
        size/=c
415
        yield c
416
    yield round(size)
417

    
418
def shapeopt(maxseed, size, ndims, ecc=0.5):
419
    """Return optimal estimate of an N-dimensional array shape
420
    which is closest to containing a given number of elements.
421

422
        maxseed:    int in [1,inf[
423
                    The maximal number of seeds to try
424
        size:       int or long in [1,inf[
425
                    The total number of elements
426
        ndims=3:    int in [1,inf[
427
                    The number of dimensions
428
        ecc=0.5:    float in ]0,1[
429
                    The eccentricity of the distribution
430
    """
431
    assert type(maxseed) is int and maxseed>=1
432
    assert type(size) in [int,float] and size>=1
433
    assert type(ndims) is int and ndims>=1
434
    assert type(ecc) in [int,float] and ecc>0 and ecc<1
435

    
436
    digits_best = np.inf
437
    shape_best = None
438
    for seed in range(maxseed):
439
        np.random.seed(seed)
440
        shape = tuple(shapegen(size, ndims, ecc))
441
        if np.prod(shape) == size:
442
            return -np.inf, shape
443
        digits = np.log10(abs(np.prod(shape)-size))
444
        if digits < digits_best:
445
            (digits_best, shape_best) = (digits, shape)
446
    return digits_best, shape_best
447