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 threading
 import isc.config
 import isc.config
+from datetime import datetime
 
 
+# container of a counter object
 _COUNTER = None
 _COUNTER = None
 
 
 def init(spec_file_name):
 def init(spec_file_name):
@@ -100,16 +102,93 @@ def inc_notifyoutv6(self, arg):
     """An empty method to be disclosed"""
     """An empty method to be disclosed"""
     pass
     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():
 class Counter():
     """A basic counter class for concrete classes"""
     """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['clear_counters'] = self.clear_counters
         self._to_global['disable'] = self.disable
         self._to_global['disable'] = self.disable
         self._to_global['enable'] = self.enable
         self._to_global['enable'] = self.enable
@@ -127,6 +206,105 @@ class Counter():
         """enables incrementing/decrementing counters"""
         """enables incrementing/decrementing counters"""
         self._disabled = False
         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):
 class XfroutCounter(Counter):
     """A module for holding all statistics counters of Xfrout. The
     """A module for holding all statistics counters of Xfrout. The
     counter numbers can be accessed by the accesseers defined
     counter numbers can be accessed by the accesseers defined
@@ -149,92 +327,45 @@ class XfroutCounter(Counter):
         socket/unixdomain/recverr
         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):
     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 = [ \
         self._unixsocket_names = [ \
             n.split('/')[-1] for n in \
             n.split('/')[-1] for n in \
-                isc.config.spec_name_list(\
+                isc.config.spec_name_list(
                 self._statistics_spec, "", True) \
                 self._statistics_spec, "", True) \
                 if n.find('socket/unixdomain/') == 0 ]
                 if n.find('socket/unixdomain/') == 0 ]
         self._create_perzone_functors()
         self._create_perzone_functors()
         self._create_xfrrunning_functors()
         self._create_xfrrunning_functors()
         self._create_unixsocket_functors()
         self._create_unixsocket_functors()
-        self._to_global['dump_default_statistics'] = \
-            self.dump_default_statistics
         self._to_global['dump_statistics'] = self.dump_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):
     def _create_xfrrunning_functors(self):
         """Creates increment/decrement method of (a|i)xfr_running
         """Creates increment/decrement method of (a|i)xfr_running
         based on the spec file. Incrementer can be accessed by name
         based on the spec file. Incrementer can be accessed by name
         "inc_${item_name}". Decrementer can be accessed by name
         "inc_${item_name}". Decrementer can be accessed by name
         "dec_${item_name}". Both of them are passed to the
         "dec_${item_name}". Both of them are passed to the
         XfroutSession as counter handlers."""
         XfroutSession as counter handlers."""
-        # can be accessed by the name 'inc_xxx' or 'dec_xxx'
         for item in self._xfrrunning_names:
         for item in self._xfrrunning_names:
             def __incrementer(counter_name=item, step=1):
             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):
             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):
             def __getter(counter_name=item):
                 """A getter method for xfr_running counters"""
                 """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['inc_%s' % item] = __incrementer
             self._to_global['dec_%s' % item] = __decrementer
             self._to_global['dec_%s' % item] = __decrementer
             self._to_global['get_%s' % item] = __getter
             self._to_global['get_%s' % item] = __getter
@@ -245,119 +376,76 @@ class XfroutCounter(Counter):
         "inc_${item_name}". Decrementer can be accessed by name
         "inc_${item_name}". Decrementer can be accessed by name
         "dec_${item_name}". Both of them are passed to the
         "dec_${item_name}". Both of them are passed to the
         XfroutSession as counter handlers."""
         XfroutSession as counter handlers."""
-        # can be accessed by the name 'inc_xxx' or 'dec_xxx'
         for item in self._unixsocket_names:
         for item in self._unixsocket_names:
             def __incrementer(counter_name=item, step=1):
             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):
             def __getter(counter_name=item):
                 """A getter method for unixsockets counters"""
                 """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['inc_unixsocket_%s' % item] = __incrementer
             self._to_global['get_unixsocket_%s' % item] = __getter
             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