]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
add backport of collections.Counter for python2.6
authorAlfredo Deza <alfredo@deza.pe>
Wed, 28 May 2014 15:48:12 +0000 (11:48 -0400)
committerAlfredo Deza <alfredo.deza@inktank.com>
Fri, 30 May 2014 15:44:08 +0000 (11:44 -0400)
Using Raymond Hettinger's MIT backport

Signed-off-by: Alfredo Deza <alfredo.deza@inktank.com>
(cherry picked from commit 23b75b550507438c79b3aa75e06721e5f7b134a4)

src/brag/client/ceph-brag

index 91981e580da8eea0e1e49cb6b1f0089ba90e5f25..9ae8e5acf4f875930001d38c138c662ff81071b0 100755 (executable)
@@ -7,17 +7,204 @@ import json
 import sys
 import ast
 import requests
-from collections import Counter
+from operator import itemgetter
+from heapq import nlargest
+from itertools import repeat, ifilter
+
 
 CLUSTER_UUID_NAME='cluster-uuid'
 CLUSTER_OWNERSHIP_NAME='cluster-ownership'
 
+
+class Counter(dict):
+    '''Dict subclass for counting hashable objects.  Sometimes called a bag
+    or multiset.  Elements are stored as dictionary keys and their counts
+    are stored as dictionary values.
+
+    >>> Counter('zyzygy')
+    Counter({'y': 3, 'z': 2, 'g': 1})
+
+    '''
+
+    def __init__(self, iterable=None, **kwds):
+        '''Create a new, empty Counter object.  And if given, count elements
+        from an input iterable.  Or, initialize the count from another mapping
+        of elements to their counts.
+
+        >>> c = Counter()                           # a new, empty counter
+        >>> c = Counter('gallahad')                 # a new counter from an iterable
+        >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
+        >>> c = Counter(a=4, b=2)                   # a new counter from keyword args
+
+        '''
+        self.update(iterable, **kwds)
+
+    def __missing__(self, key):
+        return 0
+
+    def most_common(self, n=None):
+        '''List the n most common elements and their counts from the most
+        common to the least.  If n is None, then list all element counts.
+
+        >>> Counter('abracadabra').most_common(3)
+        [('a', 5), ('r', 2), ('b', 2)]
+
+        '''
+        if n is None:
+            return sorted(self.iteritems(), key=itemgetter(1), reverse=True)
+        return nlargest(n, self.iteritems(), key=itemgetter(1))
+
+    def elements(self):
+        '''Iterator over elements repeating each as many times as its count.
+
+        >>> c = Counter('ABCABC')
+        >>> sorted(c.elements())
+        ['A', 'A', 'B', 'B', 'C', 'C']
+
+        If an element's count has been set to zero or is a negative number,
+        elements() will ignore it.
+
+        '''
+        for elem, count in self.iteritems():
+            for _ in repeat(None, count):
+                yield elem
+
+    # Override dict methods where the meaning changes for Counter objects.
+
+    @classmethod
+    def fromkeys(cls, iterable, v=None):
+        raise NotImplementedError(
+            'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')
+
+    def update(self, iterable=None, **kwds):
+        '''Like dict.update() but add counts instead of replacing them.
+
+        Source can be an iterable, a dictionary, or another Counter instance.
+
+        >>> c = Counter('which')
+        >>> c.update('witch')           # add elements from another iterable
+        >>> d = Counter('watch')
+        >>> c.update(d)                 # add elements from another counter
+        >>> c['h']                      # four 'h' in which, witch, and watch
+        4
+
+        '''
+        if iterable is not None:
+            if hasattr(iterable, 'iteritems'):
+                if self:
+                    self_get = self.get
+                    for elem, count in iterable.iteritems():
+                        self[elem] = self_get(elem, 0) + count
+                else:
+                    dict.update(self, iterable) # fast path when counter is empty
+            else:
+                self_get = self.get
+                for elem in iterable:
+                    self[elem] = self_get(elem, 0) + 1
+        if kwds:
+            self.update(kwds)
+
+    def copy(self):
+        'Like dict.copy() but returns a Counter instance instead of a dict.'
+        return Counter(self)
+
+    def __delitem__(self, elem):
+        'Like dict.__delitem__() but does not raise KeyError for missing values.'
+        if elem in self:
+            dict.__delitem__(self, elem)
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % self.__class__.__name__
+        items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
+        return '%s({%s})' % (self.__class__.__name__, items)
+
+    # Multiset-style mathematical operations discussed in:
+    #       Knuth TAOCP Volume II section 4.6.3 exercise 19
+    #       and at http://en.wikipedia.org/wiki/Multiset
+    #
+    # Outputs guaranteed to only include positive counts.
+    #
+    # To strip negative and zero counts, add-in an empty counter:
+    #       c += Counter()
+
+    def __add__(self, other):
+        '''Add counts from two counters.
+
+        >>> Counter('abbb') + Counter('bcc')
+        Counter({'b': 4, 'c': 2, 'a': 1})
+
+
+        '''
+        if not isinstance(other, Counter):
+            return NotImplemented
+        result = Counter()
+        for elem in set(self) | set(other):
+            newcount = self[elem] + other[elem]
+            if newcount > 0:
+                result[elem] = newcount
+        return result
+
+    def __sub__(self, other):
+        ''' Subtract count, but keep only results with positive counts.
+
+        >>> Counter('abbbc') - Counter('bccd')
+        Counter({'b': 2, 'a': 1})
+
+        '''
+        if not isinstance(other, Counter):
+            return NotImplemented
+        result = Counter()
+        for elem in set(self) | set(other):
+            newcount = self[elem] - other[elem]
+            if newcount > 0:
+                result[elem] = newcount
+        return result
+
+    def __or__(self, other):
+        '''Union is the maximum of value in either of the input counters.
+
+        >>> Counter('abbb') | Counter('bcc')
+        Counter({'b': 3, 'c': 2, 'a': 1})
+
+        '''
+        if not isinstance(other, Counter):
+            return NotImplemented
+        _max = max
+        result = Counter()
+        for elem in set(self) | set(other):
+            newcount = _max(self[elem], other[elem])
+            if newcount > 0:
+                result[elem] = newcount
+        return result
+
+    def __and__(self, other):
+        ''' Intersection is the minimum of corresponding counts.
+
+        >>> Counter('abbb') & Counter('bcc')
+        Counter({'b': 1})
+
+        '''
+        if not isinstance(other, Counter):
+            return NotImplemented
+        _min = min
+        result = Counter()
+        if len(self) < len(other):
+            self, other = other, self
+        for elem in ifilter(self.__contains__, other):
+            newcount = _min(self[elem], other[elem])
+            if newcount > 0:
+                result[elem] = newcount
+        return result
+
+
 def run_command(cmd):
   child = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
   (o, e) = child.communicate()
   return (child.returncode, o, e)
 
+
 def get_uuid():
   (rc,uid,e) = run_command(['ceph', 'config-key', 'get', CLUSTER_UUID_NAME])
   if rc is not 0:
@@ -43,7 +230,7 @@ def bytes_pretty_to_raw(byte_count, byte_scale):
     return byte_count >> 50
   if byte_scale == 'EB':
     return byte_count >> 60
-  
+
   return byte_count
 
 def get_nums():
@@ -64,7 +251,7 @@ def get_nums():
   (rc, o, e) = run_command(['ceph', 'pg', 'dump', 'pools', '-f', 'json-pretty'])
   if rc is not 0:
     raise RuntimeError("\'ceph pg dump pools\' failed - " + e)
+
   pools = json.loads(o)
   num_pools = len(pools)
   num_objs = 0
@@ -126,7 +313,7 @@ def get_osd_dump_info():
 def get_sysinfo(max_osds):
   count = 0
   osd_metadata_available = False
-  
+
   os = {}
   kern_version = {}
   kern_description = {}
@@ -165,10 +352,10 @@ def get_sysinfo(max_osds):
         distro[dstr] = incr(distro, dstr)
       except KeyError as ke:
         pass
-  
+
       cpu[jmeta['cpu']] = incr(cpu, jmeta['cpu'])
       arch[jmeta['arch']] = incr(arch, jmeta['arch'])
-  
+
     count = count + 1
 
   sysinfo = {}
@@ -202,7 +389,7 @@ def get_ownership_info():
 def output_json():
   out = {}
   url = None
-  
+
   out['uuid'] = get_uuid()
   nums = get_nums()
   num_osds = int(nums['num_osds'])
@@ -305,12 +492,12 @@ def unpublish():
     return 1
 
   uuid = get_uuid()
-  
+
   params = {'uuid':uuid}
   req = requests.delete(url, params=params)
   if req.status_code is not 200:
     print >> sys.stderr, "Failed to unpublish, server responsed with code " + str(req.status_code)
-    return 1 
+    return 1
 
   return 0