diff --git a/ceilometer/compute/pollsters/disk.py b/ceilometer/compute/pollsters/disk.py index 54937c0f4..fdb587916 100644 --- a/ceilometer/compute/pollsters/disk.py +++ b/ceilometer/compute/pollsters/disk.py @@ -50,6 +50,12 @@ DiskIOPSData = collections.namedtuple('DiskIOPSData', ['iops_count', 'per_disk_iops']) +DiskInfoData = collections.namedtuple('DiskInfoData', + ['capacity', + 'allocation', + 'physical', + 'per_disk_info']) + @six.add_metaclass(abc.ABCMeta) class _Base(pollsters.BaseComputePollster): @@ -635,3 +641,173 @@ class PerDeviceDiskIOPSPollster(_DiskIOPSPollsterBase): resource_id="%s-%s" % (instance.id, disk) )) return samples + + +@six.add_metaclass(abc.ABCMeta) +class _DiskInfoPollsterBase(pollsters.BaseComputePollster): + + CACHE_KEY_DISK_INFO = 'diskinfo' + + def _populate_cache(self, inspector, cache, instance): + i_cache = cache.setdefault(self.CACHE_KEY_DISK_INFO, {}) + if instance.id not in i_cache: + all_capacity = 0 + all_allocation = 0 + all_physical = 0 + per_disk_capacity = {} + per_disk_allocation = {} + per_disk_physical = {} + disk_info = inspector.inspect_disk_info( + instance) + for disk, info in disk_info: + all_capacity += info.capacity + all_allocation += info.allocation + all_physical += info.physical + + per_disk_capacity[disk.device] = info.capacity + per_disk_allocation[disk.device] = info.allocation + per_disk_physical[disk.device] = info.physical + per_disk_info = { + 'capacity': per_disk_capacity, + 'allocation': per_disk_allocation, + 'physical': per_disk_physical, + } + i_cache[instance.id] = DiskInfoData( + all_capacity, + all_allocation, + all_physical, + per_disk_info + ) + return i_cache[instance.id] + + @abc.abstractmethod + def _get_samples(self, instance, disk_info): + """Return one or more Sample.""" + + def get_samples(self, manager, cache, resources): + for instance in resources: + try: + disk_size_info = self._populate_cache( + self.inspector, + cache, + instance, + ) + for disk_info in self._get_samples(instance, disk_size_info): + yield disk_info + except virt_inspector.InstanceNotFoundException as err: + # Instance was deleted while getting samples. Ignore it. + LOG.debug(_('Exception while getting samples %s'), err) + except virt_inspector.InstanceShutOffException as e: + LOG.warn(_LW('Instance %(instance_id)s was shut off while ' + 'getting samples of %(pollster)s: %(exc)s'), + {'instance_id': instance.id, + 'pollster': self.__class__.__name__, 'exc': e}) + except ceilometer.NotImplementedError: + # Selected inspector does not implement this pollster. + LOG.debug(_('%(inspector)s does not provide data for ' + ' %(pollster)s'), ( + {'inspector': manager.inspector.__class__.__name__, + 'pollster': self.__class__.__name__})) + except Exception as err: + instance_name = util.instance_name(instance) + LOG.exception(_('Ignoring instance %(name)s ' + '(%(instance_id)s) : %(error)s') % ( + {'name': instance_name, + 'instance_id': instance.id, + 'error': err})) + + +class CapacityPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + return [util.make_sample_from_instance( + instance, + name='disk.capacity', + type=sample.TYPE_GAUGE, + unit='B', + volume=disk_info.capacity, + additional_metadata={ + 'device': disk_info.per_disk_info[ + 'capacity'].keys()}, + )] + + +class PerDeviceCapacityPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + samples = [] + for disk, value in six.iteritems(disk_info.per_disk_info[ + 'capacity']): + samples.append(util.make_sample_from_instance( + instance, + name='disk.device.capacity', + type=sample.TYPE_GAUGE, + unit='B', + volume=value, + resource_id="%s-%s" % (instance.id, disk), + )) + return samples + + +class AllocationPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + return [util.make_sample_from_instance( + instance, + name='disk.allocation', + type=sample.TYPE_GAUGE, + unit='B', + volume=disk_info.allocation, + additional_metadata={ + 'device': disk_info.per_disk_info[ + 'allocation'].keys()}, + )] + + +class PerDeviceAllocationPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + samples = [] + for disk, value in six.iteritems(disk_info.per_disk_info[ + 'allocation']): + samples.append(util.make_sample_from_instance( + instance, + name='disk.device.allocation', + type=sample.TYPE_GAUGE, + unit='B', + volume=value, + resource_id="%s-%s" % (instance.id, disk), + )) + return samples + + +class PhysicalPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + return [util.make_sample_from_instance( + instance, + name='disk.usage', + type=sample.TYPE_GAUGE, + unit='B', + volume=disk_info.physical, + additional_metadata={ + 'device': disk_info.per_disk_info[ + 'physical'].keys()}, + )] + + +class PerDevicePhysicalPollster(_DiskInfoPollsterBase): + + def _get_samples(self, instance, disk_info): + samples = [] + for disk, value in six.iteritems(disk_info.per_disk_info[ + 'physical']): + samples.append(util.make_sample_from_instance( + instance, + name='disk.device.usage', + type=sample.TYPE_GAUGE, + unit='B', + volume=value, + resource_id="%s-%s" % (instance.id, disk), + )) + return samples diff --git a/ceilometer/compute/virt/inspector.py b/ceilometer/compute/virt/inspector.py index 55f060c96..f1b9bc227 100644 --- a/ceilometer/compute/virt/inspector.py +++ b/ceilometer/compute/virt/inspector.py @@ -143,6 +143,18 @@ DiskIOPSStats = collections.namedtuple('DiskIOPSStats', ['iops_count']) +# Named tuple representing disk Information. +# +# capacity: capacity of the disk +# allocation: allocation of the disk +# physical: usage of the disk + +DiskInfo = collections.namedtuple('DiskInfo', + ['capacity', + 'allocation', + 'physical']) + + # Exception types # class InspectorException(Exception): @@ -250,6 +262,14 @@ class Inspector(object): """ raise ceilometer.NotImplementedError + def inspect_disk_info(self, instance): + """Inspect the disk information for an instance. + + :param instance: the target instance + :return: for each disk , capacity , alloaction and usage + """ + raise ceilometer.NotImplementedError + def get_hypervisor_inspector(): try: diff --git a/ceilometer/compute/virt/libvirt/inspector.py b/ceilometer/compute/virt/libvirt/inspector.py index 3b04e5300..ec68bc35c 100644 --- a/ceilometer/compute/virt/libvirt/inspector.py +++ b/ceilometer/compute/virt/libvirt/inspector.py @@ -196,3 +196,19 @@ class LibvirtInspector(virt_inspector.Inspector): 'can not get info from libvirt: %(error)s') % { 'instance_uuid': instance.id, 'error': e} raise virt_inspector.NoDataException(msg) + + def inspect_disk_info(self, instance): + domain = self._get_domain_not_shut_off_or_raise(instance) + + tree = etree.fromstring(domain.XMLDesc(0)) + for device in filter( + bool, + [target.get("dev") + for target in tree.findall('devices/disk/target')]): + disk = virt_inspector.Disk(device=device) + block_info = domain.blockInfo(device) + info = virt_inspector.DiskInfo(capacity=block_info[0], + allocation=block_info[1], + physical=block_info[2]) + + yield (disk, info) diff --git a/ceilometer/tests/agent/test_manager.py b/ceilometer/tests/agent/test_manager.py index f4fe5cf57..b9cb6666e 100644 --- a/ceilometer/tests/agent/test_manager.py +++ b/ceilometer/tests/agent/test_manager.py @@ -39,8 +39,8 @@ class TestManager(base.BaseTestCase): def test_load_plugins_pollster_list(self): mgr = manager.AgentManager(pollster_list=['disk.*']) - # currently we do have 20 disk-related pollsters - self.assertEqual(20, len(list(mgr.extensions))) + # currently we do have 26 disk-related pollsters + self.assertEqual(26, len(list(mgr.extensions))) def test_load_plugins_no_intersection(self): # Let's test nothing will be polled if namespace and pollsters diff --git a/ceilometer/tests/compute/pollsters/test_diskio.py b/ceilometer/tests/compute/pollsters/test_diskio.py index ba26daf4a..8852cc5db 100644 --- a/ceilometer/tests/compute/pollsters/test_diskio.py +++ b/ceilometer/tests/compute/pollsters/test_diskio.py @@ -304,3 +304,58 @@ class TestDiskIOPSPollsters(TestBaseDiskIO): self._check_per_device_samples(disk.PerDeviceDiskIOPSPollster, 'disk.device.iops', 20L, 'disk2') + + +class TestDiskInfoPollsters(TestBaseDiskIO): + + DISKS = [ + (virt_inspector.Disk(device='vda1'), + virt_inspector.DiskInfo(capacity=3L, allocation=2L, physical=1L)), + (virt_inspector.Disk(device='vda2'), + virt_inspector.DiskInfo(capacity=4L, allocation=3L, physical=2L)), + ] + TYPE = 'gauge' + CACHE_KEY = "CACHE_KEY_DISK_INFO" + + def setUp(self): + super(TestDiskInfoPollsters, self).setUp() + self.inspector.inspect_disk_info = mock.Mock(return_value=self.DISKS) + + def test_disk_capacity(self): + self._check_aggregate_samples(disk.CapacityPollster, + 'disk.capacity', 7L, + expected_device=['vda1', 'vda2']) + + def test_disk_allocation(self): + self._check_aggregate_samples(disk.AllocationPollster, + 'disk.allocation', 5L, + expected_device=['vda1', 'vda2']) + + def test_disk_physical(self): + self._check_aggregate_samples(disk.PhysicalPollster, + 'disk.usage', 3L, + expected_device=['vda1', 'vda2']) + + def test_per_disk_capacity(self): + self._check_per_device_samples(disk.PerDeviceCapacityPollster, + 'disk.device.capacity', 3L, + 'vda1') + self._check_per_device_samples(disk.PerDeviceCapacityPollster, + 'disk.device.capacity', 4L, + 'vda2') + + def test_per_disk_allocation(self): + self._check_per_device_samples(disk.PerDeviceAllocationPollster, + 'disk.device.allocation', 2L, + 'vda1') + self._check_per_device_samples(disk.PerDeviceAllocationPollster, + 'disk.device.allocation', 3L, + 'vda2') + + def test_per_disk_physical(self): + self._check_per_device_samples(disk.PerDevicePhysicalPollster, + 'disk.device.usage', 1L, + 'vda1') + self._check_per_device_samples(disk.PerDevicePhysicalPollster, + 'disk.device.usage', 2L, + 'vda2') diff --git a/ceilometer/tests/compute/virt/libvirt/test_inspector.py b/ceilometer/tests/compute/virt/libvirt/test_inspector.py index e493957ef..fb8861f39 100644 --- a/ceilometer/tests/compute/virt/libvirt/test_inspector.py +++ b/ceilometer/tests/compute/virt/libvirt/test_inspector.py @@ -245,6 +245,42 @@ class TestLibvirtInspection(base.BaseTestCase): self.instance) self.assertEqual(25600L / units.Ki, memory.usage) + def test_inspect_disk_info(self): + dom_xml = """ + + + + + + + +
+ + + + """ + + with contextlib.nested(mock.patch.object(self.inspector.connection, + 'lookupByUUIDString', + return_value=self.domain), + mock.patch.object(self.domain, 'XMLDesc', + return_value=dom_xml), + mock.patch.object(self.domain, 'blockInfo', + return_value=(1L, 2L, 3L, + -1)), + mock.patch.object(self.domain, 'info', + return_value=(0L, 0L, 0L, + 2L, 999999L))): + disks = list(self.inspector.inspect_disk_info(self.instance)) + + self.assertEqual(1, len(disks)) + disk0, info0 = disks[0] + self.assertEqual('vda', disk0.device) + self.assertEqual(1L, info0.capacity) + self.assertEqual(2L, info0.allocation) + self.assertEqual(3L, info0.physical) + def test_inspect_memory_usage_with_domain_shutoff(self): connection = self.inspector.connection with mock.patch.object(connection, 'lookupByUUIDString', diff --git a/doc/source/measurements.rst b/doc/source/measurements.rst index 466d6997d..6bdcefd96 100644 --- a/doc/source/measurements.rst +++ b/doc/source/measurements.rst @@ -97,6 +97,12 @@ disk.device.latency g ms disk ID p 2 disk.device.iops g count/s disk ID p 2 Average disk iops per device disk.root.size g GB inst ID n 1, 2 Size of root disk disk.ephemeral.size g GB inst ID n 1, 2 Size of ephemeral disk +disk.capacity g B inst ID p 1 Capacity of disk +disk.allocation g B inst ID p 1 Allocation of disk +disk.usage g B inst ID p 1 Usage of virtual disk +disk.device.capacity g B disk ID p 1 Capacity per device of disk +disk.device.allocation g B disk ID p 1 Allocation per device of disk +disk.device.usage g B disk ID p 1 Usage per device of virtual disk network.incoming.bytes c B iface ID p 1, 2 Number of incoming bytes network.incoming.bytes.rate g B/s iface ID p 1, 2, 3, 4 Average rate of incoming bytes network.outgoing.bytes c B iface ID p 1, 2 Number of outgoing bytes diff --git a/setup.cfg b/setup.cfg index 3a0a8cef1..195a4e237 100644 --- a/setup.cfg +++ b/setup.cfg @@ -140,6 +140,12 @@ ceilometer.poll.compute = instance = ceilometer.compute.pollsters.instance:InstancePollster instance_flavor = ceilometer.compute.pollsters.instance:InstanceFlavorPollster memory.usage = ceilometer.compute.pollsters.memory:MemoryUsagePollster + disk.capacity = ceilometer.compute.pollsters.disk:CapacityPollster + disk.allocation = ceilometer.compute.pollsters.disk:AllocationPollster + disk.usage = ceilometer.compute.pollsters.disk:PhysicalPollster + disk.device.capacity = ceilometer.compute.pollsters.disk:PerDeviceCapacityPollster + disk.device.allocation = ceilometer.compute.pollsters.disk:PerDeviceAllocationPollster + disk.device.usage = ceilometer.compute.pollsters.disk:PerDevicePhysicalPollster ceilometer.poll.ipmi = hardware.ipmi.node.power = ceilometer.ipmi.pollsters.node:PowerPollster