1# Copyright 2016 Cloudbase Solutions Srl 2# 3# All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# not use this file except in compliance with the License. You may obtain 7# a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations 15# under the License. 16""" 17Utility class for metrics related operations. 18Based on the "root/virtualization/v2" namespace available starting with 19Hyper-V Server / Windows Server 2012. 20""" 21 22from oslo_log import log as logging 23 24from os_win._i18n import _ 25from os_win import exceptions 26from os_win.utils import _wqlutils 27from os_win.utils import baseutils 28 29LOG = logging.getLogger(__name__) 30 31 32class MetricsUtils(baseutils.BaseUtilsVirt): 33 34 _VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized' 35 _DVD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual CD/DVD Disk' 36 _STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData' 37 _PROCESSOR_SETTING_DATA_CLASS = 'Msvm_ProcessorSettingData' 38 _SYNTH_ETH_PORT_SET_DATA = 'Msvm_SyntheticEthernetPortSettingData' 39 _PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData' 40 _PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData' 41 _BASE_METRICS_VALUE = 'Msvm_BaseMetricValue' 42 43 _CPU_METRICS = 'Aggregated Average CPU Utilization' 44 _MEMORY_METRICS = 'Aggregated Average Memory Utilization' 45 _NET_IN_METRICS = 'Filtered Incoming Network Traffic' 46 _NET_OUT_METRICS = 'Filtered Outgoing Network Traffic' 47 # Disk metrics are supported from Hyper-V 2012 R2 48 _DISK_RD_METRICS = 'Disk Data Read' 49 _DISK_WR_METRICS = 'Disk Data Written' 50 _DISK_LATENCY_METRICS = 'Average Disk Latency' 51 _DISK_IOPS_METRICS = 'Average Normalized Disk Throughput' 52 53 _METRICS_ENABLED = 2 54 55 def __init__(self, host='.'): 56 super(MetricsUtils, self).__init__(host) 57 self._metrics_svc_obj = None 58 self._metrics_defs_obj = {} 59 60 # We need to avoid a circular dependency. 61 from os_win import utilsfactory 62 self._vmutils = utilsfactory.get_vmutils(host) 63 64 @property 65 def _metrics_svc(self): 66 if not self._metrics_svc_obj: 67 self._metrics_svc_obj = self._compat_conn.Msvm_MetricService()[0] 68 return self._metrics_svc_obj 69 70 @property 71 def _metrics_defs(self): 72 if not self._metrics_defs_obj: 73 self._cache_metrics_defs() 74 return self._metrics_defs_obj 75 76 def _cache_metrics_defs(self): 77 for metrics_def in self._conn.CIM_BaseMetricDefinition(): 78 self._metrics_defs_obj[metrics_def.ElementName] = metrics_def 79 80 def enable_vm_metrics_collection(self, vm_name): 81 vm = self._get_vm(vm_name) 82 disks = self._get_vm_resources(vm_name, 83 self._STORAGE_ALLOC_SETTING_DATA_CLASS) 84 filtered_disks = [d for d in disks if 85 d.ResourceSubType != self._DVD_DISK_RES_SUB_TYPE] 86 87 # enable metrics for disk. 88 for disk in filtered_disks: 89 self._enable_metrics(disk) 90 91 metrics_names = [self._CPU_METRICS, self._MEMORY_METRICS] 92 self._enable_metrics(vm, metrics_names) 93 94 def enable_disk_metrics_collection(self, attached_disk_path=None, 95 is_physical=False, 96 serial=None): 97 disk = self._vmutils._get_mounted_disk_resource_from_path( 98 attached_disk_path, is_physical=is_physical, serial=serial) 99 self._enable_metrics(disk) 100 101 def enable_port_metrics_collection(self, switch_port_name): 102 port = self._get_switch_port(switch_port_name) 103 metrics_names = [self._NET_IN_METRICS, self._NET_OUT_METRICS] 104 self._enable_metrics(port, metrics_names) 105 106 def _enable_metrics(self, element, metrics_names=None): 107 if not metrics_names: 108 definition_paths = [None] 109 else: 110 definition_paths = [] 111 for metrics_name in metrics_names: 112 metrics_def = self._metrics_defs.get(metrics_name) 113 if not metrics_def: 114 LOG.warning("Metric not found: %s", metrics_name) 115 continue 116 definition_paths.append(metrics_def.path_()) 117 118 element_path = element.path_() 119 for definition_path in definition_paths: 120 ret_val = self._metrics_svc.ControlMetrics( 121 Subject=element_path, 122 Definition=definition_path, 123 MetricCollectionEnabled=self._METRICS_ENABLED)[0] 124 if ret_val: 125 err_msg = _("Failed to enable metrics for resource " 126 "%(resource_name)s. " 127 "Return code: %(ret_val)s.") % dict( 128 resource_name=element.ElementName, 129 ret_val=ret_val) 130 raise exceptions.OSWinException(err_msg) 131 132 def get_cpu_metrics(self, vm_name): 133 vm = self._get_vm(vm_name) 134 cpu_sd = self._get_vm_resources(vm_name, 135 self._PROCESSOR_SETTING_DATA_CLASS)[0] 136 cpu_metrics_def = self._metrics_defs[self._CPU_METRICS] 137 cpu_metrics_aggr = self._get_metrics(vm, cpu_metrics_def) 138 139 cpu_used = 0 140 if cpu_metrics_aggr: 141 cpu_used = int(cpu_metrics_aggr[0].MetricValue) 142 143 return (cpu_used, 144 int(cpu_sd.VirtualQuantity), 145 int(vm.OnTimeInMilliseconds)) 146 147 def get_memory_metrics(self, vm_name): 148 vm = self._get_vm(vm_name) 149 memory_def = self._metrics_defs[self._MEMORY_METRICS] 150 metrics_memory = self._get_metrics(vm, memory_def) 151 memory_usage = 0 152 if metrics_memory: 153 memory_usage = int(metrics_memory[0].MetricValue) 154 return memory_usage 155 156 def get_vnic_metrics(self, vm_name): 157 ports = self._get_vm_resources(vm_name, self._PORT_ALLOC_SET_DATA) 158 vnics = self._get_vm_resources(vm_name, self._SYNTH_ETH_PORT_SET_DATA) 159 160 metrics_def_in = self._metrics_defs[self._NET_IN_METRICS] 161 metrics_def_out = self._metrics_defs[self._NET_OUT_METRICS] 162 163 for port in ports: 164 vnic = [v for v in vnics if port.Parent == v.path_()][0] 165 port_acls = _wqlutils.get_element_associated_class( 166 self._conn, self._PORT_ALLOC_ACL_SET_DATA, 167 element_instance_id=port.InstanceID) 168 169 metrics_value_instances = self._get_metrics_value_instances( 170 port_acls, self._BASE_METRICS_VALUE) 171 metrics_values = self._sum_metrics_values_by_defs( 172 metrics_value_instances, [metrics_def_in, metrics_def_out]) 173 174 yield { 175 'rx_mb': metrics_values[0], 176 'tx_mb': metrics_values[1], 177 'element_name': vnic.ElementName, 178 'address': vnic.Address 179 } 180 181 def get_disk_metrics(self, vm_name): 182 metrics_def_r = self._metrics_defs[self._DISK_RD_METRICS] 183 metrics_def_w = self._metrics_defs[self._DISK_WR_METRICS] 184 185 disks = self._get_vm_resources(vm_name, 186 self._STORAGE_ALLOC_SETTING_DATA_CLASS) 187 for disk in disks: 188 metrics_values = self._get_metrics_values( 189 disk, [metrics_def_r, metrics_def_w]) 190 191 yield { 192 # Values are in megabytes 193 'read_mb': metrics_values[0], 194 'write_mb': metrics_values[1], 195 'instance_id': disk.InstanceID, 196 'host_resource': disk.HostResource[0] 197 } 198 199 def get_disk_latency_metrics(self, vm_name): 200 metrics_latency_def = self._metrics_defs[self._DISK_LATENCY_METRICS] 201 202 disks = self._get_vm_resources(vm_name, 203 self._STORAGE_ALLOC_SETTING_DATA_CLASS) 204 for disk in disks: 205 metrics_values = self._get_metrics_values( 206 disk, [metrics_latency_def]) 207 208 yield { 209 'disk_latency': metrics_values[0], 210 'instance_id': disk.InstanceID, 211 } 212 213 def get_disk_iops_count(self, vm_name): 214 metrics_def_iops = self._metrics_defs[self._DISK_IOPS_METRICS] 215 216 disks = self._get_vm_resources(vm_name, 217 self._STORAGE_ALLOC_SETTING_DATA_CLASS) 218 for disk in disks: 219 metrics_values = self._get_metrics_values( 220 disk, [metrics_def_iops]) 221 222 yield { 223 'iops_count': metrics_values[0], 224 'instance_id': disk.InstanceID, 225 } 226 227 @staticmethod 228 def _sum_metrics_values(metrics): 229 return sum([int(metric.MetricValue) for metric in metrics]) 230 231 def _sum_metrics_values_by_defs(self, element_metrics, metrics_defs): 232 metrics_values = [] 233 for metrics_def in metrics_defs: 234 if metrics_def: 235 metrics = self._filter_metrics(element_metrics, metrics_def) 236 metrics_values.append(self._sum_metrics_values(metrics)) 237 else: 238 # In case the metric is not defined on this host 239 metrics_values.append(0) 240 return metrics_values 241 242 def _get_metrics_value_instances(self, elements, result_class): 243 instances = [] 244 for el in elements: 245 # NOTE(abalutoiu): Msvm_MetricForME is the association between 246 # an element and all the metric values maintained for it. 247 el_metric = [ 248 x.Dependent for x in self._conn.Msvm_MetricForME( 249 Antecedent=el.path_())] 250 el_metric = [ 251 x for x in el_metric if x.path().Class == result_class] 252 if el_metric: 253 instances.append(el_metric[0]) 254 255 return instances 256 257 def _get_metrics_values(self, element, metrics_defs): 258 element_metrics = [ 259 x.Dependent for x in self._conn.Msvm_MetricForME( 260 Antecedent=element.path_())] 261 return self._sum_metrics_values_by_defs(element_metrics, metrics_defs) 262 263 def _get_metrics(self, element, metrics_def): 264 metrics = [ 265 x.Dependent for x in self._conn.Msvm_MetricForME( 266 Antecedent=element.path_())] 267 return self._filter_metrics(metrics, metrics_def) 268 269 @staticmethod 270 def _filter_metrics(all_metrics, metrics_def): 271 return [v for v in all_metrics if 272 v.MetricDefinitionId == metrics_def.Id] 273 274 def _get_vm_resources(self, vm_name, resource_class): 275 setting_data = self._get_vm_setting_data(vm_name) 276 return _wqlutils.get_element_associated_class( 277 self._conn, resource_class, 278 element_instance_id=setting_data.InstanceID) 279 280 def _get_vm(self, vm_name): 281 vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) 282 return self._unique_result(vms, vm_name) 283 284 def _get_switch_port(self, port_name): 285 ports = self._conn.Msvm_EthernetPortAllocationSettingData( 286 ElementName=port_name) 287 return self._unique_result(ports, port_name) 288 289 def _get_vm_setting_data(self, vm_name): 290 vssds = self._conn.Msvm_VirtualSystemSettingData( 291 ElementName=vm_name) 292 return self._unique_result(vssds, vm_name) 293 294 @staticmethod 295 def _unique_result(objects, resource_name): 296 n = len(objects) 297 if n == 0: 298 raise exceptions.NotFound(resource=resource_name) 299 elif n > 1: 300 raise exceptions.OSWinException( 301 _('Duplicate resource name found: %s') % resource_name) 302 else: 303 return objects[0] 304