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