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 225def create_default_metric_set(system_facade): 226 """ 227 Creates the default set of metrics. 228 229 @param system_facade the system facade to initialize the metrics with. 230 @return a list with Metric instances. 231 """ 232 cpu = CpuUsageMetric(system_facade) 233 mem = MemUsageMetric(system_facade) 234 file_handles = AllocatedFileHandlesMetric(system_facade) 235 storage_written_amount = StorageWrittenAmountMetric(system_facade) 236 temperature = TemperatureMetric(system_facade) 237 peak_cpu = PeakMetric.from_metric(cpu) 238 peak_mem = PeakMetric.from_metric(mem) 239 peak_temperature = PeakMetric.from_metric(temperature) 240 sum_storage_written_amount = SumMetric.from_metric(storage_written_amount) 241 return [cpu, 242 mem, 243 file_handles, 244 storage_written_amount, 245 temperature, 246 peak_cpu, 247 peak_mem, 248 peak_temperature, 249 sum_storage_written_amount] 250 251class SystemMetricsCollector(object): 252 """ 253 Collects system metrics. 254 """ 255 def __init__(self, system_facade, metrics = None): 256 """ 257 Initialize with facade and metric classes. 258 259 @param system_facade The system facade to use for querying the system, 260 e.g. system_facade_native.SystemFacadeNative for client tests. 261 @param metrics List of metric instances. If None, the default set will 262 be created. 263 """ 264 self.metrics = (create_default_metric_set(system_facade) 265 if metrics is None else metrics) 266 267 def pre_collect(self): 268 """ 269 Calls pre hook of metrics. 270 """ 271 for metric in self.metrics: 272 metric.pre_collect() 273 274 def post_collect(self): 275 """ 276 Calls post hook of metrics. 277 """ 278 for metric in self.metrics: 279 metric.post_collect() 280 281 def collect_snapshot(self): 282 """ 283 Collects one snapshot of metrics. 284 """ 285 for metric in self.metrics: 286 metric.collect_metric() 287 288 def write_metrics(self, writer_function): 289 """ 290 Writes the collected metrics using the specified writer function. 291 292 @param writer_function: A function with the following signature: 293 f(description, value, units, higher_is_better) 294 """ 295 for metric in self.metrics: 296 writer_function( 297 description=metric.description, 298 value=metric.values, 299 units=metric.units, 300 higher_is_better=metric.higher_is_better) 301