summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Statistics.py
blob: e34135d4bafb53cc80ac0c0b16cd4720d1df497f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
""" Module for tracking execution time statistics from the Bcfg2
server core.  This data is exposed by
:func:`Bcfg2.Server.Core.BaseCore.get_statistics`."""

import time
from Bcfg2.Compat import wraps


class Statistic(object):
    """ A single named statistic, tracking minimum, maximum, and
    average execution time, and number of invocations. """

    def __init__(self, name, initial_value):
        """
        :param name: The name of this statistic
        :type name: string
        :param initial_value: The initial value to be added to this
                              statistic
        :type initial_value: int or float
        """
        self.name = name
        self.min = float(initial_value)
        self.max = float(initial_value)
        self.ave = float(initial_value)
        self.count = 1

    def add_value(self, value):
        """ Add a value to the statistic, recalculating the various
        metrics.

        :param value: The value to add to this statistic
        :type value: int or float
        """
        self.min = min(self.min, float(value))
        self.max = max(self.max, float(value))
        self.count += 1
        self.ave = (((self.ave * (self.count - 1)) + value) / self.count)

    def get_value(self):
        """ Get a tuple of all the stats tracked on this named item.
        The tuple is in the format::

            (<name>, (min, max, average, number of values))

        This makes it very easy to cast to a dict in
        :func:`Statistics.display`.

        :returns: tuple
        """
        return (self.name, (self.min, self.max, self.ave, self.count))

    def __repr__(self):
        return "%s(%s, (min=%s, avg=%s, max=%s, count=%s))" % (
            self.__class__.__name__,
            self.name, self.min, self.ave, self.max, self.count)


class Statistics(object):
    """ A collection of named :class:`Statistic` objects. """

    def __init__(self):
        self.data = dict()

    def add_value(self, name, value):
        """ Add a value to the named :class:`Statistic`.  This just
        proxies to :func:`Statistic.add_value` or the
        :class:`Statistic` constructor as appropriate.

        :param name: The name of the :class:`Statistic` to add the
                     value to
        :type name: string
        :param value: The value to add to the Statistic
        :type value: int or float
        """
        if name not in self.data:
            self.data[name] = Statistic(name, value)
        else:
            self.data[name].add_value(value)

    def display(self):
        """ Return a dict of all :class:`Statistic` object values.
        Keys are the statistic names, and values are tuples of the
        statistic metrics as returned by
        :func:`Statistic.get_value`. """
        return dict([value.get_value() for value in list(self.data.values())])


#: A module-level :class:`Statistics` objects used to track all
#: execution time metrics for the server.
stats = Statistics()  # pylint: disable=C0103


class track_statistics(object):  # pylint: disable=C0103
    """ Decorator that tracks execution time for the given method with
    :mod:`Bcfg2.Server.Statistics` for reporting via ``bcfg2-admin
    perf`` """

    def __init__(self, name=None):
        """
        :param name: The name under which statistics for this function
                     will be tracked.  By default, the name will be
                     the name of the function concatenated with the
                     name of the class the function is a member of.
        :type name: string
        """
        # if this is None, it will be set later during __call_
        self.name = name

    def __call__(self, func):
        if self.name is None:
            self.name = func.__name__

        @wraps(func)
        def inner(obj, *args, **kwargs):
            """ The decorated function """
            name = "%s:%s" % (obj.__class__.__name__, self.name)

            start = time.time()
            try:
                return func(obj, *args, **kwargs)
            finally:
                stats.add_value(name, time.time() - start)

        return inner