1class Metric(object):
2    """Abstract base class for metrics."""
3    def __init__(self,
4                 description,
5                 units=None,
6                 higher_is_better=False):
7        """
8        Initializes a Metric.
9        @param description: Description of the metric, e.g., used as label on a
10                dashboard chart
11        @param units: Units of the metric, e.g. percent, seconds, MB.
12        @param higher_is_better: Whether a higher value is considered better or
13                not.
14        """
15        self.values = []
16        self.description = description
17        self.units = units
18        self.higher_is_better = higher_is_better
19
20    def collect_metric(self):
21        """
22        Collects one metric.
23
24        Implementations should add a metric value to the self.values list.
25        """
26        raise NotImplementedError('Subclasses should override')
27
28class PeakMetric(Metric):
29    """
30    Metric that collects the peak of another metric.
31    """
32    def __init__(self, metric):
33        """
34        Initializes with a Metric.
35
36        @param metric The Metric to get the peak from.
37        """
38        super(PeakMetric, self).__init__(
39                'peak_' + metric.description,
40                units = metric.units,
41                higher_is_better = metric.higher_is_better)
42        self.metric = metric
43
44    def collect_metric(self):
45        self.values = [max(self.metric.values)] if self.metric.values else []
46
47class MemUsageMetric(Metric):
48    """
49    Metric that collects memory usage in percent.
50
51    Memory usage is collected in percent. Buffers and cached are calculated
52    as free memory.
53    """
54    def __init__(self, system_facade):
55        super(MemUsageMetric, self).__init__('memory_usage', units='percent')
56        self.system_facade = system_facade
57
58    def collect_metric(self):
59        total_memory = self.system_facade.get_mem_total()
60        free_memory = self.system_facade.get_mem_free_plus_buffers_and_cached()
61        used_memory = total_memory - free_memory
62        usage_percent = (used_memory * 100) / total_memory
63        self.values.append(usage_percent)
64
65class CpuUsageMetric(Metric):
66    """
67    Metric that collects cpu usage in percent.
68    """
69    def __init__(self, system_facade):
70        super(CpuUsageMetric, self).__init__('cpu_usage', units='percent')
71        self.last_usage = None
72        self.system_facade = system_facade
73
74    def collect_metric(self):
75        """
76        Collects CPU usage in percent.
77
78        Since the CPU active time we query is a cumulative metric, the first
79        collection does not actually save a value. It saves the first value to
80        be used for subsequent deltas.
81        """
82        current_usage = self.system_facade.get_cpu_usage()
83        if self.last_usage is not None:
84            # Compute the percent of active time since the last update to
85            # current_usage.
86            usage_percent = 100 * self.system_facade.compute_active_cpu_time(
87                    self.last_usage, current_usage)
88            self.values.append(usage_percent)
89        self.last_usage = current_usage
90
91class AllocatedFileHandlesMetric(Metric):
92    """
93    Metric that collects the number of allocated file handles.
94    """
95    def __init__(self, system_facade):
96        super(AllocatedFileHandlesMetric, self).__init__(
97                'allocated_file_handles', units='handles')
98        self.system_facade = system_facade
99
100    def collect_metric(self):
101        self.values.append(self.system_facade.get_num_allocated_file_handles())
102
103class TemperatureMetric(Metric):
104    """
105    Metric that collects the max of the temperatures measured on all sensors.
106    """
107    def __init__(self, system_facade):
108        super(TemperatureMetric, self).__init__('temperature', units='Celsius')
109        self.system_facade = system_facade
110
111    def collect_metric(self):
112        self.values.append(self.system_facade.get_current_temperature_max())
113
114def create_default_metric_set(system_facade):
115    """
116    Creates the default set of metrics.
117
118    @param system_facade the system facade to initialize the metrics with.
119    @return a list with Metric instances.
120    """
121    cpu = CpuUsageMetric(system_facade)
122    mem = MemUsageMetric(system_facade)
123    file_handles = AllocatedFileHandlesMetric(system_facade)
124    temperature = TemperatureMetric(system_facade)
125    peak_cpu = PeakMetric(cpu)
126    peak_mem = PeakMetric(mem)
127    peak_temperature = PeakMetric(temperature)
128    return [cpu,
129            mem,
130            file_handles,
131            temperature,
132            peak_cpu,
133            peak_mem,
134            peak_temperature]
135
136class SystemMetricsCollector(object):
137    """
138    Collects system metrics.
139    """
140    def __init__(self, system_facade, metrics = None):
141        """
142        Initialize with facade and metric classes.
143
144        @param system_facade The system facade to use for querying the system,
145                e.g. system_facade_native.SystemFacadeNative for client tests.
146        @param metrics List of metric instances. If None, the default set will
147                be created.
148        """
149        self.metrics = (create_default_metric_set(system_facade)
150                        if metrics is None else metrics)
151
152    def collect_snapshot(self):
153        """
154        Collects one snapshot of metrics.
155        """
156        for metric in self.metrics:
157            metric.collect_metric()
158
159    def write_metrics(self, writer_function):
160        """
161        Writes the collected metrics using the specified writer function.
162
163        @param writer_function: A function with the following signature:
164                 f(description, value, units, higher_is_better)
165        """
166        for metric in self.metrics:
167            writer_function(
168                    description=metric.description,
169                    value=metric.values,
170                    units=metric.units,
171                    higher_is_better=metric.higher_is_better)
172