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 pre_collect(self): 54 """ 55 Hook called before metrics are being collected. 56 """ 57 pass 58 59 def _store_sample(self, sample): 60 self._samples.append(sample) 61 62 def collect_metric(self): 63 """ 64 Collects one sample. 65 66 Implementations should call self._store_sample() once if it's not an 67 aggregate, i.e., it overrides self._aggregate(). 68 """ 69 pass 70 71 @classmethod 72 def from_metric(cls, other): 73 """ 74 Instantiate from an existing metric instance. 75 """ 76 metric = cls( 77 description=other.description, 78 units=other.units, 79 higher_is_better=other.higher_is_better) 80 metric.values = other.values 81 return metric 82 83class PeakMetric(Metric): 84 """ 85 Metric that collects the peak of another metric. 86 """ 87 88 @property 89 def description(self): 90 return 'peak_' + super(PeakMetric, self).description 91 92 def _aggregate(self, samples): 93 return max(samples) 94 95class SumMetric(Metric): 96 """ 97 Metric that sums another metric. 98 """ 99 100 @property 101 def description(self): 102 return 'sum_' + super(SumMetric, self).description 103 104 def _aggregate(self, samples): 105 return sum(samples) 106 107class MemUsageMetric(Metric): 108 """ 109 Metric that collects memory usage in percent. 110 111 Memory usage is collected in percent. Buffers and cached are calculated 112 as free memory. 113 """ 114 def __init__(self, system_facade): 115 super(MemUsageMetric, self).__init__('memory_usage', units='percent') 116 self.system_facade = system_facade 117 118 def collect_metric(self): 119 total_memory = self.system_facade.get_mem_total() 120 free_memory = self.system_facade.get_mem_free_plus_buffers_and_cached() 121 used_memory = total_memory - free_memory 122 usage_percent = (used_memory * 100) / total_memory 123 self._store_sample(usage_percent) 124 125class CpuUsageMetric(Metric): 126 """ 127 Metric that collects cpu usage in percent. 128 """ 129 def __init__(self, system_facade): 130 super(CpuUsageMetric, self).__init__('cpu_usage', units='percent') 131 self.last_usage = None 132 self.system_facade = system_facade 133 134 def pre_collect(self): 135 self.last_usage = self.system_facade.get_cpu_usage() 136 137 def collect_metric(self): 138 """ 139 Collects CPU usage in percent. 140 """ 141 current_usage = self.system_facade.get_cpu_usage() 142 # Compute the percent of active time since the last update to 143 # current_usage. 144 usage_percent = 100 * self.system_facade.compute_active_cpu_time( 145 self.last_usage, current_usage) 146 self._store_sample(usage_percent) 147 self.last_usage = current_usage 148 149class AllocatedFileHandlesMetric(Metric): 150 """ 151 Metric that collects the number of allocated file handles. 152 """ 153 def __init__(self, system_facade): 154 super(AllocatedFileHandlesMetric, self).__init__( 155 'allocated_file_handles', units='handles') 156 self.system_facade = system_facade 157 158 def collect_metric(self): 159 self._store_sample(self.system_facade.get_num_allocated_file_handles()) 160 161class StorageWrittenMetric(Metric): 162 """ 163 Metric that collects amount of data written to persistent storage. 164 """ 165 def __init__(self, system_facade): 166 super(StorageWrittenMetric, self).__init__( 167 'storage_written', units='kB') 168 self.last_written_kb = None 169 self.system_facade = system_facade 170 171 def pre_collect(self): 172 statistics = self.system_facade.get_storage_statistics() 173 self.last_written_kb = statistics['written_kb'] 174 175 def collect_metric(self): 176 """ 177 Collects total amount of data written to persistent storage in kB. 178 """ 179 statistics = self.system_facade.get_storage_statistics() 180 written_kb = statistics['written_kb'] 181 written_period = written_kb - self.last_written_kb 182 self._store_sample(written_period) 183 self.last_written_kb = written_kb 184 185class TemperatureMetric(Metric): 186 """ 187 Metric that collects the max of the temperatures measured on all sensors. 188 """ 189 def __init__(self, system_facade): 190 super(TemperatureMetric, self).__init__('temperature', units='Celsius') 191 self.system_facade = system_facade 192 193 def collect_metric(self): 194 self._store_sample(self.system_facade.get_current_temperature_max()) 195 196def create_default_metric_set(system_facade): 197 """ 198 Creates the default set of metrics. 199 200 @param system_facade the system facade to initialize the metrics with. 201 @return a list with Metric instances. 202 """ 203 cpu = CpuUsageMetric(system_facade) 204 mem = MemUsageMetric(system_facade) 205 file_handles = AllocatedFileHandlesMetric(system_facade) 206 storage_written = StorageWrittenMetric(system_facade) 207 temperature = TemperatureMetric(system_facade) 208 peak_cpu = PeakMetric.from_metric(cpu) 209 peak_mem = PeakMetric.from_metric(mem) 210 peak_temperature = PeakMetric.from_metric(temperature) 211 sum_storage_written = SumMetric.from_metric(storage_written) 212 return [cpu, 213 mem, 214 file_handles, 215 storage_written, 216 temperature, 217 peak_cpu, 218 peak_mem, 219 peak_temperature, 220 sum_storage_written] 221 222class SystemMetricsCollector(object): 223 """ 224 Collects system metrics. 225 """ 226 def __init__(self, system_facade, metrics = None): 227 """ 228 Initialize with facade and metric classes. 229 230 @param system_facade The system facade to use for querying the system, 231 e.g. system_facade_native.SystemFacadeNative for client tests. 232 @param metrics List of metric instances. If None, the default set will 233 be created. 234 """ 235 self.metrics = (create_default_metric_set(system_facade) 236 if metrics is None else metrics) 237 238 def pre_collect(self): 239 """ 240 Calls pre hook of metrics. 241 """ 242 for metric in self.metrics: 243 metric.pre_collect() 244 245 def collect_snapshot(self): 246 """ 247 Collects one snapshot of metrics. 248 """ 249 for metric in self.metrics: 250 metric.collect_metric() 251 252 def write_metrics(self, writer_function): 253 """ 254 Writes the collected metrics using the specified writer function. 255 256 @param writer_function: A function with the following signature: 257 f(description, value, units, higher_is_better) 258 """ 259 for metric in self.metrics: 260 writer_function( 261 description=metric.description, 262 value=metric.values, 263 units=metric.units, 264 higher_is_better=metric.higher_is_better) 265