• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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._description = description
16        self._units = units
17        self._higher_is_better = higher_is_better
18        self._samples = []
19
20    @property
21    def description(self):
22        """Description of the metric."""
23        return self._description
24
25    @property
26    def units(self):
27        """Units of the metric."""
28        return self._units
29
30    @property
31    def higher_is_better(self):
32        """Whether a higher value is considered better or not."""
33        return self._higher_is_better
34
35    @property
36    def values(self):
37        """Measured values of the metric."""
38        if len(self._samples) == 0:
39            return self._samples
40        return self._aggregate(self._samples)
41
42    @values.setter
43    def values(self, samples):
44        self._samples = samples
45
46    def _aggregate(self, samples):
47        """
48        Subclasses can override this to aggregate the metric into a single
49        sample.
50        """
51        return samples
52
53    def _store_sample(self, sample):
54        self._samples.append(sample)
55
56    def pre_collect(self):
57        """
58        Hook called before metrics are being collected.
59        """
60        pass
61
62    def post_collect(self):
63        """
64        Hook called after metrics has been collected.
65        """
66        pass
67
68    def collect_metric(self):
69        """
70        Collects one sample.
71
72        Implementations should call self._store_sample() once if it's not an
73        aggregate, i.e., it overrides self._aggregate().
74        """
75        pass
76
77    @classmethod
78    def from_metric(cls, other):
79        """
80        Instantiate from an existing metric instance.
81        """
82        metric = cls(
83                description=other.description,
84                units=other.units,
85                higher_is_better=other.higher_is_better)
86        metric.values = other.values
87        return metric
88
89class PeakMetric(Metric):
90    """
91    Metric that collects the peak of another metric.
92    """
93
94    @property
95    def description(self):
96        return 'peak_' + super(PeakMetric, self).description
97
98    def _aggregate(self, samples):
99        return max(samples)
100
101class SumMetric(Metric):
102    """
103    Metric that sums another metric.
104    """
105
106    @property
107    def description(self):
108        return 'sum_' + super(SumMetric, self).description
109
110    def _aggregate(self, samples):
111        return sum(samples)
112
113class MemUsageMetric(Metric):
114    """
115    Metric that collects memory usage in percent.
116
117    Memory usage is collected in percent. Buffers and cached are calculated
118    as free memory.
119    """
120    def __init__(self, system_facade):
121        super(MemUsageMetric, self).__init__('memory_usage', units='percent')
122        self.system_facade = system_facade
123
124    def collect_metric(self):
125        total_memory = self.system_facade.get_mem_total()
126        free_memory = self.system_facade.get_mem_free_plus_buffers_and_cached()
127        used_memory = total_memory - free_memory
128        usage_percent = (used_memory * 100) / total_memory
129        self._store_sample(usage_percent)
130
131class CpuUsageMetric(Metric):
132    """
133    Metric that collects cpu usage in percent.
134    """
135    def __init__(self, system_facade):
136        super(CpuUsageMetric, self).__init__('cpu_usage', units='percent')
137        self.last_usage = None
138        self.system_facade = system_facade
139
140    def pre_collect(self):
141        self.last_usage = self.system_facade.get_cpu_usage()
142
143    def collect_metric(self):
144        """
145        Collects CPU usage in percent.
146        """
147        current_usage = self.system_facade.get_cpu_usage()
148        # Compute the percent of active time since the last update to
149        # current_usage.
150        usage_percent = 100 * self.system_facade.compute_active_cpu_time(
151                self.last_usage, current_usage)
152        self._store_sample(usage_percent)
153        self.last_usage = current_usage
154
155class AllocatedFileHandlesMetric(Metric):
156    """
157    Metric that collects the number of allocated file handles.
158    """
159    def __init__(self, system_facade):
160        super(AllocatedFileHandlesMetric, self).__init__(
161                'allocated_file_handles', units='handles')
162        self.system_facade = system_facade
163
164    def collect_metric(self):
165        self._store_sample(self.system_facade.get_num_allocated_file_handles())
166
167class StorageWrittenAmountMetric(Metric):
168    """
169    Metric that collects amount of kB written to persistent storage.
170    """
171    def __init__(self, system_facade):
172        super(StorageWrittenAmountMetric, self).__init__(
173                'storage_written_amount', units='kB')
174        self.last_written_kb = None
175        self.system_facade = system_facade
176
177    def pre_collect(self):
178        statistics = self.system_facade.get_storage_statistics()
179        self.last_written_kb = statistics['written_kb']
180
181    def collect_metric(self):
182        """
183        Collects total amount of data written to persistent storage in kB.
184        """
185        statistics = self.system_facade.get_storage_statistics()
186        written_kb = statistics['written_kb']
187        written_period = written_kb - self.last_written_kb
188        self._store_sample(written_period)
189        self.last_written_kb = written_kb
190
191class StorageWrittenCountMetric(Metric):
192    """
193    Metric that collects the number of writes to persistent storage.
194    """
195    def __init__(self, system_facade):
196        super(StorageWrittenCountMetric, self).__init__(
197                'storage_written_count', units='count')
198        self.system_facade = system_facade
199
200    def pre_collect(self):
201        command = ('/usr/sbin/fatrace', '--timestamp', '--filter=W')
202        self.system_facade.start_bg_worker(command)
203
204    def collect_metric(self):
205        output = self.system_facade.get_and_discard_bg_worker_output()
206        # fatrace outputs a line of text for each file it detects being written
207        # to.
208        written_count = output.count('\n')
209        self._store_sample(written_count)
210
211    def post_collect(self):
212        self.system_facade.stop_bg_worker()
213
214class TemperatureMetric(Metric):
215    """
216    Metric that collects the max of the temperatures measured on all sensors.
217    """
218    def __init__(self, system_facade):
219        super(TemperatureMetric, self).__init__('temperature', units='Celsius')
220        self.system_facade = system_facade
221
222    def collect_metric(self):
223        self._store_sample(self.system_facade.get_current_temperature_max())
224
225
226class EnergyUsageMetric(Metric):
227    """
228    Metric that collects the amount of energy used.
229    """
230
231    def __init__(self, system_facade):
232        super(EnergyUsageMetric, self).__init__('energy_usage',
233                                                units='microjoules')
234        self.system_facade = system_facade
235        self.initial_energy = int(self.system_facade.get_energy_usage())
236
237    def collect_metric(self):
238        self._store_sample(
239                int(self.system_facade.get_energy_usage()) -
240                self.initial_energy)
241
242    def _aggregate(self, samples):
243        return samples[-1]
244
245
246def create_default_metric_set(system_facade):
247    """
248    Creates the default set of metrics.
249
250    @param system_facade the system facade to initialize the metrics with.
251    @return a list with Metric instances.
252    """
253    cpu = CpuUsageMetric(system_facade)
254    mem = MemUsageMetric(system_facade)
255    file_handles = AllocatedFileHandlesMetric(system_facade)
256    storage_written_amount = StorageWrittenAmountMetric(system_facade)
257    temperature = TemperatureMetric(system_facade)
258    peak_cpu = PeakMetric.from_metric(cpu)
259    peak_mem = PeakMetric.from_metric(mem)
260    peak_temperature = PeakMetric.from_metric(temperature)
261    sum_storage_written_amount = SumMetric.from_metric(storage_written_amount)
262    energy = EnergyUsageMetric(system_facade)
263    return [
264            cpu, mem, file_handles, storage_written_amount, temperature,
265            peak_cpu, peak_mem, peak_temperature, sum_storage_written_amount,
266            energy
267    ]
268
269
270class SystemMetricsCollector(object):
271    """
272    Collects system metrics.
273    """
274    def __init__(self, system_facade, metrics = None):
275        """
276        Initialize with facade and metric classes.
277
278        @param system_facade The system facade to use for querying the system,
279                e.g. system_facade.SystemFacadeLocal for client tests.
280        @param metrics List of metric instances. If None, the default set will
281                be created.
282        """
283        self.metrics = (create_default_metric_set(system_facade)
284                        if metrics is None else metrics)
285
286    def pre_collect(self):
287        """
288        Calls pre hook of metrics.
289        """
290        for metric in self.metrics:
291            metric.pre_collect()
292
293    def post_collect(self):
294        """
295        Calls post hook of metrics.
296        """
297        for metric in self.metrics:
298            metric.post_collect()
299
300    def collect_snapshot(self):
301        """
302        Collects one snapshot of metrics.
303        """
304        for metric in self.metrics:
305            metric.collect_metric()
306
307    def write_metrics(self, writer_function):
308        """
309        Writes the collected metrics using the specified writer function.
310
311        @param writer_function: A function with the following signature:
312                 f(description, value, units, higher_is_better)
313        """
314        for metric in self.metrics:
315            writer_function(
316                    description=metric.description,
317                    value=metric.values,
318                    units=metric.units,
319                    higher_is_better=metric.higher_is_better)
320