Browse Source

[2252] added XfrinCounter class

 - added XfrinCounter class
 - Common methods between Xfrout and Xfrin are gotten rid of out of
   each class, which is incrementer, decrementer, etc.
 - added an abstract class for concrete XfroutCounter and  XfrinCounter
   classes, in which common methods were defined
 - added per-zone counters into XfrinCounter
 - added per-zone timer handlers into XfrinCounter, which can start or
   stop a timer
Naoki Kambe 12 years ago
parent
commit
62c7075662
1 changed files with 266 additions and 178 deletions
  1. 266 178
      src/lib/python/isc/statistics/counter.py

+ 266 - 178
src/lib/python/isc/statistics/counter.py

@@ -72,7 +72,9 @@ future. For adding or modifying such accessor, we need to implement in
 """
 import threading
 import isc.config
+from datetime import datetime
 
+# container of a counter object
 _COUNTER = None
 
 def init(spec_file_name):
@@ -100,16 +102,93 @@ def inc_notifyoutv6(self, arg):
     """An empty method to be disclosed"""
     pass
 
+# static internal functions
+def _add_counter(element, spec, identifier):
+    """Returns value of the identifier if the identifier is in the
+    element. Otherwise, sets a default value from the spec then
+    returns it. If the top-level type of the identifier is named_set
+    and the second-level type is map, it sets a set of default values
+    under the level and then returns the default value of the
+    identifier. Raises DataNotFoundError if the element is invalid for
+    spec."""
+    try:
+        return isc.cc.data.find(element, identifier)
+    except isc.cc.data.DataNotFoundError:
+        pass
+    try:
+        isc.config.find_spec_part(spec, identifier)
+    except isc.cc.data.DataNotFoundError:
+        # spec or identifier is wrong
+        raise
+    # examine spec of the top-level item first
+    spec_ = isc.config.find_spec_part(
+        spec, '%s' % identifier.split('/')[0])
+    if spec_['item_type'] == 'named_set' and \
+            spec_['named_set_item_spec']['item_type'] ==  'map':
+        map_spec = spec_['named_set_item_spec']['map_item_spec']
+        for name in isc.config.spec_name_list(map_spec):
+            spec_ = isc.config.find_spec_part(map_spec, name)
+            id_str = '%s/%s/%s' % \
+                tuple(identifier.split('/')[0:2] + [name])
+            isc.cc.data.set(element, id_str, spec_['item_default'])
+    else:
+        spec_ = isc.config.find_spec_part(spec, identifier)
+        isc.cc.data.set(element, identifier, spec_['item_default'])
+    return isc.cc.data.find(element, identifier)
+
+def _set_counter(element, spec, identifier, value):
+    """Invokes _add_counter() for checking whether the identifier is
+    in the element. If not, it creates a new identifier in the element
+    and set the default value from the spec. After that, it sets the
+    value specified in the arguments."""
+    _add_counter(element, spec, identifier)
+    isc.cc.data.set(element, identifier, value)
+
+def _get_counter(element, identifier):
+    """Returns the value of the identifier in the element"""
+    return isc.cc.data.find(element, identifier)
+
+def _inc_counter(element, spec, identifier, step=1):
+    """Increments the value of the identifier in the element to the
+    step from the current value. If the identifier isn't in the
+    element, it creates a new identifier in the element."""
+    isc.cc.data.set(element, identifier,
+                    _add_counter(element, spec, identifier) + step)
+
+def _start_timer():
+    """Returns the current datetime as a datetime object."""
+    return datetime.now()
+
+def _stop_timer(start_time, element, spec, identifier):
+    """Sets duration time in seconds as a value of the identifier in
+    the element, which is in seconds between start_time and the
+    current time and is float-type."""
+    delta = datetime.now() - start_time
+    sec = round(delta.days * 86400 + delta.seconds + \
+                    delta.microseconds * 1E-6, 6)
+    _set_counter(element, spec, identifier, sec)
+
 class Counter():
     """A basic counter class for concrete classes"""
-    _statistics_spec = {}
-    _statistics_data = {}
-    _disabled = False
-    _rlock = threading.RLock()
-    _to_global = {}
 
-    def __init__(self, module_spec):
-        self._statistics_spec = module_spec.get_statistics_spec()
+    # '_SERVER_' is a special zone name representing an entire
+    # count. It doesn't mean a specific zone, but it means an
+    # entire count in the server.
+    _entire_server = '_SERVER_'
+    # zone names are contained under this dirname in the spec file.
+    _perzone_prefix = 'zones'
+
+    def __init__(self):
+        # for exporting to the global scope
+        self._to_global = {}
+        self._statistics_spec = {}
+        self._statistics_data = {}
+        self._zones_item_list = []
+        self._xfrrunning_names = []
+        self._unixsocket_names = []
+        self._start_time = {}
+        self._disabled = False
+        self._rlock = threading.RLock()
         self._to_global['clear_counters'] = self.clear_counters
         self._to_global['disable'] = self.disable
         self._to_global['enable'] = self.enable
@@ -127,6 +206,105 @@ class Counter():
         """enables incrementing/decrementing counters"""
         self._disabled = False
 
+    def _incrementer(self, identifier, step=1):
+        """A per-zone incrementer for counter_name. Locks the
+        thread because it is considered to be invoked by a
+        multi-threading caller."""
+        if self._disabled: return
+        with self._rlock:
+            _inc_counter(self._statistics_data,
+                         self._statistics_spec,
+                         identifier, step)
+
+    def _decrementer(self, identifier, step=-1):
+        """A decrementer for axfr or ixfr running. Locks the
+        thread because it is considered to be invoked by a
+        multi-threading caller."""
+        self._incrementer(identifier, step)
+
+    def _getter(self, identifier):
+        """A getter method for perzone counters"""
+        return _get_counter(self._statistics_data, identifier)
+
+    def _starttimer(self, identifier):
+        """Sets the value returned from _start_timer() as a value of
+        the identifier in the self._start_time which is dict-type"""
+        isc.cc.data.set(self._start_time, identifier, _start_timer())
+
+    def _stoptimer(self, identifier):
+        """Sets duration time between corresponding time in
+        self._start_time and current time into the value of the
+        identifier. It deletes corresponding time in self._start_time
+        after setting is successfully done. If DataNotFoundError is
+        raised while invoking _stop_timer(), it stops setting and
+        ignores the exception."""
+        try:
+            _stop_timer(
+                isc.cc.data.find(self._start_time, identifier),
+                self._statistics_data,
+                self._statistics_spec,
+                identifier)
+            del isc.cc.data.find(
+                self._start_time,
+                '/'.join(identifier.split('/')[0:-1]))\
+                [identifier.split('/')[-1]]
+        except isc.cc.data.DataNotFoundError:
+            # do not set end time if it's not started
+            pass
+
+    def _create_perzone_functors(self):
+        """Creates increment method of each per-zone counter based on
+        the spec file. Incrementer can be accessed by name
+        "inc_${item_name}".Incrementers are passed to the
+        XfrinConnection class as counter handlers."""
+        for item in self._zones_item_list:
+            if item.find('time_to_') == 0: continue
+            def __incrementer(zone_name, counter_name=item, step=1):
+                """A per-zone incrementer for counter_name."""
+                self._incrementer(
+                    '%s/%s/%s' % \
+                        (self._perzone_prefix, zone_name, counter_name),
+                    step)
+            def __getter(zone_name, counter_name=item):
+                """A getter method for perzone counters"""
+                return self._getter(
+                    '%s/%s/%s' % \
+                        (self._perzone_prefix, zone_name, counter_name))
+            self._to_global['inc_%s' % item] = __incrementer
+            self._to_global['get_%s' % item] = __getter
+
+    def dump_statistics(self):
+        """Calculates an entire server counts, and returns statistics
+        data format to send out the stats module including each
+        counter. If there is no counts, then it returns an empty
+        dictionary."""
+        # entire copy
+        statistics_data = self._statistics_data.copy()
+        # If self.statistics_data contains nothing of zone name, it
+        # returns an empty dict.
+        if self._perzone_prefix not in statistics_data:
+            return statistics_data
+        zones = statistics_data[self._perzone_prefix]
+        # Start calculation for '_SERVER_' counts
+        zones_spec = isc.config.find_spec_part(self._statistics_spec,
+                                               self._perzone_prefix)
+        zones_attrs = zones_spec['item_default'][self._entire_server]
+        zones_data = {}
+        for attr in zones_attrs:
+            id_str = '%s/%s' % (self._entire_server, attr)
+            sum_ = 0
+            for name in zones:
+                if attr in zones[name]:
+                    sum_ += zones[name][attr]
+            if  sum_ > 0:
+                _set_counter(zones_data, zones_spec,
+                             id_str, sum_)
+        # insert entire-sever counts
+        statistics_data[self._perzone_prefix] = dict(
+            statistics_data[self._perzone_prefix],
+            **zones_data)
+        return statistics_data
+
 class XfroutCounter(Counter):
     """A module for holding all statistics counters of Xfrout. The
     counter numbers can be accessed by the accesseers defined
@@ -149,92 +327,45 @@ class XfroutCounter(Counter):
         socket/unixdomain/recverr
     """
 
-    # '_SERVER_' is a special zone name representing an entire
-    # count. It doesn't mean a specific zone, but it means an
-    # entire count in the server.
-    _entire_server = '_SERVER_'
-    # zone names are contained under this dirname in the spec file.
-    _perzone_prefix = 'zones'
-    _xfrrunning_names = []
-    _unixsocket_names = []
-
     def __init__(self, module_spec):
-        Counter.__init__(self, module_spec)
-        self._xfrrunning_names = [ \
-            n for n in \
-                isc.config.spec_name_list(self._statistics_spec) \
-                if 'xfr_running' in n ]
+        """Creates an instance. A module_spec object for the target
+        module Xfrout is required in the argument."""
+        Counter.__init__(self)
+        self._statistics_spec = module_spec.get_statistics_spec()
+        self._zones_item_list = isc.config.spec_name_list(
+            isc.config.find_spec_part(
+                self._statistics_spec, self._perzone_prefix)\
+                ['named_set_item_spec']['map_item_spec'])
+        self._xfrrunning_names = [
+            n for n in isc.config.spec_name_list\
+                (self._statistics_spec) \
+                if n.find('xfr_running') == 1 ]
         self._unixsocket_names = [ \
             n.split('/')[-1] for n in \
-                isc.config.spec_name_list(\
+                isc.config.spec_name_list(
                 self._statistics_spec, "", True) \
                 if n.find('socket/unixdomain/') == 0 ]
         self._create_perzone_functors()
         self._create_xfrrunning_functors()
         self._create_unixsocket_functors()
-        self._to_global['dump_default_statistics'] = \
-            self.dump_default_statistics
         self._to_global['dump_statistics'] = self.dump_statistics
 
-    def _create_perzone_functors(self):
-        """Creates increment method of each per-zone counter based on
-        the spec file. Incrementer can be accessed by name
-        "inc_${item_name}".Incrementers are passed to the
-        XfroutSession and NotifyOut class as counter handlers."""
-        # add a new element under the named_set item for the zone
-        zones_spec = isc.config.find_spec_part(
-            self._statistics_spec, self._perzone_prefix)
-        item_list =  isc.config.spec_name_list(\
-            zones_spec['named_set_item_spec']['map_item_spec'])
-        # can be accessed by the name 'inc_xxx'
-        for item in item_list:
-            def __incrementer(zone_name, counter_name=item, step=1):
-                """A per-zone incrementer for counter_name. Locks the
-                thread because it is considered to be invoked by a
-                multi-threading caller."""
-                if self._disabled: return
-                with self._rlock:
-                    self._add_perzone_counter(zone_name)
-                    self._statistics_data[self._perzone_prefix]\
-                        [zone_name][counter_name] += step
-            def __getter(zone_name, counter_name=item):
-                """A getter method for perzone counters"""
-                return isc.cc.data.find(
-                    self._statistics_data,
-                    '%s/%s/%s' % ( self._perzone_prefix,
-                                   zone_name,
-                                   counter_name )
-                    )
-            self._to_global['inc_%s' % item] = __incrementer
-            self._to_global['get_%s' % item] = __getter
-
     def _create_xfrrunning_functors(self):
         """Creates increment/decrement method of (a|i)xfr_running
         based on the spec file. Incrementer can be accessed by name
         "inc_${item_name}". Decrementer can be accessed by name
         "dec_${item_name}". Both of them are passed to the
         XfroutSession as counter handlers."""
-        # can be accessed by the name 'inc_xxx' or 'dec_xxx'
         for item in self._xfrrunning_names:
             def __incrementer(counter_name=item, step=1):
-                """A incrementer for axfr or ixfr running. Locks the
-                thread because it is considered to be invoked by a
-                multi-threading caller."""
-                if self._disabled: return
-                with self._rlock:
-                    self._add_xfrrunning_counter(counter_name)
-                    self._statistics_data[counter_name] += step
+                """A incrementer for axfr or ixfr running."""
+                self._incrementer(counter_name, step)
             def __decrementer(counter_name=item, step=-1):
-                """A decrementer for axfr or ixfr running. Locks the
-                thread because it is considered to be invoked by a
-                multi-threading caller."""
-                if self._disabled: return
-                with self._rlock:
-                    self._statistics_data[counter_name] += step
+                """A decrementer for axfr or ixfr running."""
+                self._decrementer(counter_name, step)
             def __getter(counter_name=item):
                 """A getter method for xfr_running counters"""
-                return isc.cc.data.find(
-                        self._statistics_data, counter_name )
+                return self._getter(counter_name)
             self._to_global['inc_%s' % item] = __incrementer
             self._to_global['dec_%s' % item] = __decrementer
             self._to_global['get_%s' % item] = __getter
@@ -245,119 +376,76 @@ class XfroutCounter(Counter):
         "inc_${item_name}". Decrementer can be accessed by name
         "dec_${item_name}". Both of them are passed to the
         XfroutSession as counter handlers."""
-        # can be accessed by the name 'inc_xxx' or 'dec_xxx'
         for item in self._unixsocket_names:
             def __incrementer(counter_name=item, step=1):
-                """A incrementer for axfr or ixfr running. Locks the
-                thread because it is considered to be invoked by a
-                multi-threading caller."""
-                if self._disabled: return
-                with self._rlock:
-                    self._add_unixsocket_counter(counter_name)
-                    self._statistics_data['socket']['unixdomain']\
-                       [counter_name] += step
+                """A incrementer for axfr or ixfr running."""
+                self._incrementer(
+                    'socket/unixdomain/%s' % counter_name,
+                    step)
             def __getter(counter_name=item):
                 """A getter method for unixsockets counters"""
-                return isc.cc.data.find(
-                    self._statistics_data,
-                    'socket/unixdomain/%s' % counter_name )
+                return self._getter(
+                    'socket/unixdomain/%s' % counter_name)
             self._to_global['inc_unixsocket_%s' % item] = __incrementer
             self._to_global['get_unixsocket_%s' % item] = __getter
 
-    def _add_perzone_counter(self, zone):
-        """Adds a named_set-type counter for each zone name."""
-        try:
-            self._statistics_data[self._perzone_prefix][zone]
-        except KeyError:
-            # add a new element under the named_set item for the zone
-            map_spec = isc.config.find_spec_part(
-                self._statistics_spec, '%s/%s' % \
-                    (self._perzone_prefix, zone))['map_item_spec']
-            id_list =  isc.config.spec_name_list(map_spec)
-            for id_ in id_list:
-                spec = isc.config.find_spec_part(map_spec, id_)
-                isc.cc.data.set(self._statistics_data,
-                                '%s/%s/%s' % \
-                                    (self._perzone_prefix, zone, id_),
-                                spec['item_default'])
-
-    def _add_xfrrunning_counter(self, counter_name):
-        """Adds a counter for counting (a|i)xfr_running"""
-        try:
-            self._statistics_data[counter_name]
-        except KeyError:
-            # examines the names of xfer running
-            for n in self._xfrrunning_names:
-                spec = isc.config.find_spec_part\
-                    (self._statistics_spec, n)
-                isc.cc.data.set(self._statistics_data, n, \
-                                spec['item_default'])
-
-    def _add_unixsocket_counter(self, counter_name):
-        """Adds a counter for counting unix sockets"""
-        try:
-            self._statistics_data['socket']['unixdomain'][counter_name]
-        except KeyError:
-            # examines the name of unixsocket
-            name = 'socket/unixdomain/%s' % counter_name
-            spec = isc.config.find_spec_part\
-                (self._statistics_spec, name)
-            isc.cc.data.set(self._statistics_data, name, \
-                                spec['item_default'])
-
-    def dump_default_statistics(self):
-        """Returns default statistics data from the spec file"""
-        statistics_data = {}
-        for id_ in isc.config.spec_name_list(self._statistics_spec):
-            spec = isc.config.find_spec_part(\
-                self._statistics_spec, id_)
-            if 'item_default' in spec:
-                statistics_data.update({id_: spec['item_default']})
-        return statistics_data
+class XfrinCounter(Counter):
+    """A module for holding all statistics counters of Xfrin. The
+    counter numbers can be accessed by the accesseers defined
+    according to a spec file. In this class, the structure of per-zone
+    counters is assumed to be like this:
 
-    def dump_statistics(self):
-        """Calculates an entire server counts, and returns statistics
-        data format to send out the stats module including each
-        counter. If there is no counts, then it returns an empty
-        dictionary."""
-        # If self.statistics_data contains nothing of zone name, it
-        # returns an empty dict.
-        if len(self._statistics_data) == 0: return {}
-        # for per-zone counter
-        zones = self._statistics_data[self._perzone_prefix]
-        # Start calculation for '_SERVER_' counts
-        attrs = self.dump_default_statistics()\
-            [self._perzone_prefix][self._entire_server]
-        statistics_data = {self._perzone_prefix: {}}
-        for attr in attrs:
-            sum_ = 0
-            for name in zones:
-                if name == self._entire_server: continue
-                if attr in zones[name]:
-                    if  name not in \
-                            statistics_data[self._perzone_prefix]:
-                        statistics_data[self._perzone_prefix][name]\
-                            = {}
-                    statistics_data[self._perzone_prefix][name].\
-                        update({attr: zones[name][attr]})
-                    sum_ += zones[name][attr]
-            if  sum_ > 0:
-                if self._entire_server not in \
-                        statistics_data[self._perzone_prefix]:
-                    statistics_data[self._perzone_prefix][self._entire_server]\
-                        = {}
-                statistics_data[self._perzone_prefix][self._entire_server]\
-                    .update({attr:sum_})
-
-        # for xfrrunning incrementer/decrementer
-        for name in self._xfrrunning_names:
-            if name in self._statistics_data:
-                statistics_data[name] = self._statistics_data[name]
-
-        # for unixsocket incrementer/decrementer
-        if 'socket' in self._statistics_data:
-            statistics_data['socket'] = \
-                self._statistics_data['socket']
+	zones/example.com./soaoutv4
+	zones/example.com./soaoutv6
+	zones/example.com./axfrreqv4
+	zones/example.com./axfrreqv6
+	zones/example.com./ixfrreqv4
+	zones/example.com./ixfrreqv6
+	zones/example.com./xfrsuccess
+	zones/example.com./xfrfail
+	zones/example.com./time_to_ixfr
+	zones/example.com./time_to_axfr
+    """
 
-        return statistics_data
+    def __init__(self, module_spec):
+        """Creates an instance. A module_spec object for the target
+        module Xfrin is required in the argument."""
+        Counter.__init__(self)
+        self._statistics_spec = module_spec.get_statistics_spec()
+        self._zones_item_list = isc.config.spec_name_list(\
+            isc.config.find_spec_part(
+            self._statistics_spec, self._perzone_prefix)\
+                ['named_set_item_spec']['map_item_spec'])
+        self._create_perzone_functors()
+        self._create_perzone_timer_functors()
+        self._to_global['dump_statistics'] = self.dump_statistics
 
+    def _create_perzone_timer_functors(self):
+        """Creates timer method of each per-zone counter based on the
+        spec file. Starter of the timer can be accessed by the name
+        "start_${item_name}".  Stopper of the timer can be accessed by
+        the name "stop_${item_name}".  These starter and stopper are
+        passed to the XfrinConnection class as timer handlers."""
+        for item in self._zones_item_list:
+            if item.find('time_to_') == -1: continue
+            def __getter(zone_name, counter_name=item):
+                """A getter method for perzone timer. A zone name in
+                string is required in argument."""
+                return self._getter(
+                    '%s/%s/%s' % \
+                        (self._perzone_prefix, zone_name, counter_name))
+            def __starttimer(zone_name, counter_name=item):
+                """A starter method for perzone timer. A zone name in
+                string is required in argument."""
+                self._starttimer(
+                    '%s/%s/%s' % \
+                        (self._perzone_prefix, zone_name, counter_name))
+            def __stoptimer(zone_name, counter_name=item):
+                """A stopper method for perzone timer. A zone name in
+                string is required in argument."""
+                self._stoptimer(
+                    '%s/%s/%s' % \
+                        (self._perzone_prefix, zone_name, counter_name))
+            self._to_global['start_%s' % item] = __starttimer
+            self._to_global['stop_%s' % item] = __stoptimer
+            self._to_global['get_%s' % item] = __getter