From 429c98d84811722f1fb7e9913ba7d304499d47d1 Mon Sep 17 00:00:00 2001 From: esberglu Date: Tue, 17 Oct 2017 16:19:00 -0500 Subject: [PATCH] Get host-level cpu metrics from pypowervm metric cache This removes the host-level cpu utilization metrics from nova-powervm. Host-level cpu metrics are being cached at the pypowervm level where running totals are kept for total cycles, firmware cycles, and user cycles. The get_host_cpu_stats driver method converts these metrics into the format expected by nova. Change-Id: I85798a8baa81e6737674ec819e9f147a4374050d --- nova_powervm/tests/virt/powervm/fixtures.py | 11 +- .../tests/virt/powervm/test_driver.py | 13 + nova_powervm/tests/virt/powervm/test_host.py | 319 ------------------ nova_powervm/virt/powervm/driver.py | 16 +- nova_powervm/virt/powervm/host.py | 255 -------------- 5 files changed, 32 insertions(+), 582 deletions(-) diff --git a/nova_powervm/tests/virt/powervm/fixtures.py b/nova_powervm/tests/virt/powervm/fixtures.py index f18d7a76..0e690c98 100644 --- a/nova_powervm/tests/virt/powervm/fixtures.py +++ b/nova_powervm/tests/virt/powervm/fixtures.py @@ -48,13 +48,14 @@ class DiskAdapter(fixtures.Fixture): self.std_disk_adpt = self.std_disk_adpt_fx.mock -class HostCPUStats(fixtures.Fixture): - """Mock out the HostCPUStats.""" +class HostCPUMetricCache(fixtures.Fixture): + """Mock out the HostCPUMetricCache.""" def setUp(self): - super(HostCPUStats, self).setUp() + super(HostCPUMetricCache, self).setUp() self.host_cpu_stats = self.useFixture( - fixtures.MockPatch('nova_powervm.virt.powervm.host.HostCPUStats')) + fixtures.MockPatch('pypowervm.tasks.monitor.host_cpu.' + 'HostCPUMetricCache')) class ComprehensiveScrub(fixtures.Fixture): @@ -120,7 +121,7 @@ class PowerVMComputeDriver(fixtures.Fixture): super(PowerVMComputeDriver, self).setUp() # Set up the mock CPU stats (init_host uses it) - self.useFixture(HostCPUStats()) + self.useFixture(HostCPUMetricCache()) self.scrubber = ComprehensiveScrub() self.useFixture(self.scrubber) diff --git a/nova_powervm/tests/virt/powervm/test_driver.py b/nova_powervm/tests/virt/powervm/test_driver.py index 1715b40e..a3a82800 100644 --- a/nova_powervm/tests/virt/powervm/test_driver.py +++ b/nova_powervm/tests/virt/powervm/test_driver.py @@ -1970,3 +1970,16 @@ class TestPowerVMDriver(test.TestCase): mock_find_orphans.return_value = [mock_orphan] self.drv._cleanup_orphan_adapters('my_vswitch') mock_orphan.delete.assert_called_once_with() + + def test_get_host_cpu_stats(self): + hcpu_stats = self.drv.get_host_cpu_stats() + expected_stats = { + 'kernel': self.drv.host_cpu_cache.total_fw_cycles, + 'user': self.drv.host_cpu_cache.total_user_cycles, + 'idle': (self.drv.host_cpu_cache.total_cycles - + self.drv.host_cpu_cache.total_user_cycles - + self.drv.host_cpu_cache.total_fw_cycles), + 'iowait': 0, + 'frequency': self.drv.host_cpu_cache.cpu_freq} + self.assertEqual(expected_stats, hcpu_stats) + self.drv.host_cpu_cache.refresh.assert_called_once() diff --git a/nova_powervm/tests/virt/powervm/test_host.py b/nova_powervm/tests/virt/powervm/test_host.py index 0e4fe01b..79a5d40c 100644 --- a/nova_powervm/tests/virt/powervm/test_host.py +++ b/nova_powervm/tests/virt/powervm/test_host.py @@ -20,7 +20,6 @@ import mock import logging from nova import test from oslo_serialization import jsonutils -import pypowervm.tests.test_fixtures as pvm_fx from pypowervm.wrappers import iocard as pvm_card from pypowervm.wrappers import managed_system as pvm_ms @@ -102,321 +101,3 @@ class TestPowerVMHost(test.NoDBTestCase): self.assertEqual('*', ppd['vendor_id']) self.assertEqual('*', ppd['product_id']) self.assertEqual(1, ppd['numa_node']) - - -class TestHostCPUStats(test.TestCase): - - def setUp(self): - super(TestHostCPUStats, self).setUp() - - # Fixture for the adapter - self.adpt = self.useFixture(pvm_fx.AdapterFx()).adpt - - def _get_sample(self, lpar_id, sample): - for lpar in sample.lpars: - if lpar.id == lpar_id: - return lpar - return None - - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_get_fw_cycles_delta') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats._get_cpu_freq') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_get_total_cycles_delta') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_gather_user_cycles_delta') - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_update_internal_metric( - self, mock_ensure_ltm, mock_refresh, mock_user_cycles, - mock_total_cycles, mock_cpu_freq, mock_fw_cycles): - - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_cpu_freq.return_value = 4116 - - # Make sure None is returned if there is no data. - host_stats.cur_phyp = None - host_stats._update_internal_metric() - expect = {'iowait': 0, 'idle': 0, 'kernel': 0, 'user': 0, - 'frequency': 0} - self.assertEqual(expect, host_stats.tot_data) - - # Create mock phyp objects to test with - mock_phyp = mock.MagicMock() - mock_fw_cycles.return_value = 58599310268 - mock_prev_phyp = mock.MagicMock() - - # Mock methods not currently under test - mock_user_cycles.return_value = 50 - mock_total_cycles.return_value = 1.6125945178663e+16 - - # Make the 'prev' the current...for the first pass - host_stats.cur_phyp = mock_prev_phyp - host_stats.prev_phyp = None - host_stats._update_internal_metric() - - # Validate the dictionary... No user cycles because all of the - # previous data is empty. - expect = {'iowait': 0, 'idle': 1.6125886579352682e+16, - 'kernel': 58599310268, 'user': 50, - 'frequency': 4116} - self.assertEqual(expect, host_stats.tot_data) - - # Mock methods not currently under test - mock_user_cycles.return_value = 30010090000 - mock_total_cycles.return_value = 1.6125945178663e+16 - - # Now 'increment' it with a new current/previous - host_stats.cur_phyp = mock_phyp - host_stats.prev_phyp = mock_prev_phyp - mock_user_cycles.return_value = 100000 - host_stats._update_internal_metric() - - # Validate this dictionary. Note that these values are 'higher' - # because this is a running total. - new_kern = 58599310268 * 2 - expect = {'iowait': 0, 'idle': 3.2251773158605416e+16, - 'kernel': new_kern, 'user': 100050, - 'frequency': 4116} - self.assertEqual(expect, host_stats.tot_data) - - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_get_fw_cycles_delta') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_get_total_cycles_delta') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_gather_user_cycles_delta') - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats._get_cpu_freq') - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_update_internal_metric_bad_total( - self, mock_ensure_ltm, mock_refresh, mock_cpu_freq, - mock_user_cycles, mock_tot_cycles, mock_fw_cycles): - """Validates that if the total cycles are off, we handle.""" - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_cpu_freq.return_value = 4116 - mock_user_cycles.return_value = 30010090000 - mock_fw_cycles.return_value = 58599310268 - - # Mock the total cycles to some really low number. - mock_tot_cycles.return_value = 5 - - # Create mock phyp objects to test with - mock_phyp = mock.MagicMock() - mock_prev_phyp = mock.MagicMock() - mock_phyp.sample.system_firmware.utilized_proc_cycles = 58599310268 - - # Run the actual test - 'increment' it with a new current/previous - host_stats.cur_phyp = mock_phyp - host_stats.prev_phyp = mock_prev_phyp - host_stats._update_internal_metric() - - # Validate this dictionary. Note that the idle is now 0...not a - # negative number. - expect = {'iowait': 0, 'idle': 0, 'kernel': 58599310268, - 'user': 30010090000, 'frequency': 4116} - self.assertEqual(expect, host_stats.tot_data) - - @mock.patch('subprocess.check_output') - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_get_cpu_freq(self, mock_ensure_ltm, mock_refresh, mock_cmd): - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_cmd.return_value = '4116.000000MHz\n' - self.assertEqual(4116, host_stats._get_cpu_freq()) - self.assertEqual(int, type(host_stats._get_cpu_freq())) - - @mock.patch('nova_powervm.virt.powervm.host.HostCPUStats.' - '_delta_proc_cycles') - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_gather_user_cycles_delta(self, mock_ensure_ltm, mock_refresh, - mock_cycles): - # Crete objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_phyp = mock.MagicMock() - mock_prev_phyp = mock.MagicMock() - - # Mock methods not currently under test - mock_cycles.return_value = 15005045000 - - # Test that we can run with previous samples and then without. - host_stats.cur_phyp = mock_phyp - host_stats.prev_phyp = mock_prev_phyp - resp = host_stats._gather_user_cycles_delta() - self.assertEqual(30010090000, resp) - - # Now test if there is no previous sample. Since there are no previous - # samples, it will be 0. - host_stats.prev_phyp = None - mock_cycles.return_value = 0 - resp = host_stats._gather_user_cycles_delta() - self.assertEqual(0, resp) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_delta_proc_cycles(self, mock_ensure_ltm, mock_refresh): - # Create objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_phyp, mock_prev_phyp = self._get_mock_phyps() - - # Test that a previous sample allows us to gather the delta across all - # of the VMs. This should take into account the scenario where a LPAR - # is deleted and a new one takes its place (LPAR ID 6) - delta = host_stats._delta_proc_cycles(mock_phyp.sample.lpars, - mock_prev_phyp.sample.lpars) - self.assertEqual(10010000000, delta) - - # Now test as if there is no previous data. This results in 0 as they - # could have all been LPMs with months of cycles (rather than 30 - # seconds delta). - delta2 = host_stats._delta_proc_cycles(mock_phyp.sample.lpars, None) - self.assertEqual(0, delta2) - self.assertNotEqual(delta2, delta) - - # Test that if previous sample had 0 values, the sample is not - # considered for evaluation, and resultant delta cycles is 0. - prev_lpar_sample = mock_prev_phyp.sample.lpars[0].processor - prev_lpar_sample.util_cap_proc_cycles = 0 - prev_lpar_sample.util_uncap_proc_cycles = 0 - prev_lpar_sample.idle_proc_cycles = 0 - delta3 = host_stats._delta_proc_cycles(mock_phyp.sample.lpars, - mock_prev_phyp.sample.lpars) - self.assertEqual(0, delta3) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_delta_user_cycles(self, mock_ensure_ltm, mock_refresh): - # Create objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_phyp, mock_prev_phyp = self._get_mock_phyps() - mock_phyp.sample.lpars[0].processor.util_cap_proc_cycles = 250000 - mock_phyp.sample.lpars[0].processor.util_uncap_proc_cycles = 250000 - mock_phyp.sample.lpars[0].processor.idle_proc_cycles = 500 - mock_prev_phyp.sample.lpars[0].processor.util_cap_proc_cycles = 0 - num = 455000 - mock_prev_phyp.sample.lpars[0].processor.util_uncap_proc_cycles = num - mock_prev_phyp.sample.lpars[0].processor.idle_proc_cycles = 1000 - - # Test that a previous sample allows us to gather just the delta. - new_elem = self._get_sample(4, mock_phyp.sample) - old_elem = self._get_sample(4, mock_prev_phyp.sample) - delta = host_stats._delta_user_cycles(new_elem, old_elem) - self.assertEqual(45500, delta) - - # Validate the scenario where we don't have a previous. Should default - # to 0, given no context of why the previous sample did not have the - # data. - delta = host_stats._delta_user_cycles(new_elem, None) - self.assertEqual(0, delta) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_find_prev_sample(self, mock_ensure_ltm, mock_refresh): - # Create objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_lpar_4A = mock.Mock() - mock_lpar_4A.configure_mock(id=4, name='A') - mock_lpar_4A.processor = mock.MagicMock( - entitled_proc_cycles=500000) - mock_lpar_6A = mock.Mock() - mock_lpar_6A.configure_mock(id=6, name='A') - mock_lpar_6B = mock.Mock() - mock_lpar_6A.configure_mock(id=6, name='B') - mock_phyp = mock.MagicMock(sample=mock.MagicMock(lpars=[mock_lpar_4A, - mock_lpar_6A])) - mock_prev_phyp = mock.MagicMock(sample=mock.MagicMock( - lpars=[mock_lpar_4A, mock_lpar_6B])) - - # Sample 6 in the current shouldn't match the previous. It has the - # same LPAR ID, but a different name. This is considered different - new_elem = self._get_sample(6, mock_phyp.sample) - prev = host_stats._find_prev_sample(new_elem, - mock_prev_phyp.sample.lpars) - self.assertIsNone(prev) - - # Lpar 4 should be in the old one. Match that up. - new_elem = self._get_sample(4, mock_phyp.sample) - prev = host_stats._find_prev_sample(new_elem, - mock_prev_phyp.sample.lpars) - self.assertIsNotNone(prev) - self.assertEqual(500000, prev.processor.entitled_proc_cycles) - - # Test that we get None back if there are no previous samples - prev = host_stats._find_prev_sample(new_elem, None) - self.assertIsNone(prev) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_get_total_cycles(self, mock_ensure_ltm, mock_refresh): - # Mock objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - mock_phyp = mock.MagicMock() - mock_phyp.sample = mock.MagicMock() - mock_phyp.sample.processor.configurable_proc_units = 5 - mock_phyp.sample.time_based_cycles = 500 - host_stats.cur_phyp = mock_phyp - - # Make sure we get the full system cycles. - max_cycles = host_stats._get_total_cycles_delta() - self.assertEqual(2500, max_cycles) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_get_total_cycles_diff_cores(self, mock_ensure_ltm, mock_refresh): - # Mock objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - - # Latest Sample - mock_phyp = mock.MagicMock(sample=mock.MagicMock()) - mock_phyp.sample.processor.configurable_proc_units = 48 - mock_phyp.sample.time_based_cycles = 1000 - host_stats.cur_phyp = mock_phyp - - # Earlier sample. Use a higher proc unit sample - mock_phyp = mock.MagicMock(sample=mock.MagicMock()) - mock_phyp.sample.processor.configurable_proc_units = 1 - mock_phyp.sample.time_based_cycles = 500 - host_stats.prev_phyp = mock_phyp - - # Make sure we get the full system cycles. - max_cycles = host_stats._get_total_cycles_delta() - self.assertEqual(24000, max_cycles) - - @mock.patch('pypowervm.tasks.monitor.util.MetricCache._refresh_if_needed') - @mock.patch('pypowervm.tasks.monitor.util.ensure_ltm_monitors') - def test_get_firmware_cycles(self, mock_ensure_ltm, mock_refresh): - # Mock objects to test with - host_stats = pvm_host.HostCPUStats(self.adpt, 'host_uuid') - - # Latest Sample - mock_phyp = mock.MagicMock(sample=mock.MagicMock()) - mock_phyp.sample.system_firmware.utilized_proc_cycles = 2000 - # Previous Sample - prev_phyp = mock.MagicMock(sample=mock.MagicMock()) - prev_phyp.sample.system_firmware.utilized_proc_cycles = 1000 - - host_stats.cur_phyp = mock_phyp - host_stats.prev_phyp = prev_phyp - # Get delta - delta_firmware_cycles = host_stats._get_fw_cycles_delta() - self.assertEqual(1000, delta_firmware_cycles) - - def _get_mock_phyps(self): - """Helper method to return cur_phyp and prev_phyp.""" - mock_lpar_4A = mock.Mock() - mock_lpar_4A.configure_mock(id=4, name='A') - mock_lpar_4A.processor = mock.MagicMock( - util_cap_proc_cycles=5005045000, - util_uncap_proc_cycles=5005045000, - idle_proc_cycles=10000) - mock_lpar_4A_prev = mock.Mock() - mock_lpar_4A_prev.configure_mock(id=4, name='A') - mock_lpar_4A_prev.processor = mock.MagicMock( - util_cap_proc_cycles=40000, - util_uncap_proc_cycles=40000, - idle_proc_cycles=0) - mock_phyp = mock.MagicMock(sample=mock.MagicMock(lpars=[mock_lpar_4A])) - mock_prev_phyp = mock.MagicMock( - sample=mock.MagicMock(lpars=[mock_lpar_4A_prev])) - return mock_phyp, mock_prev_phyp diff --git a/nova_powervm/virt/powervm/driver.py b/nova_powervm/virt/powervm/driver.py index e0ef7758..e61ed33b 100644 --- a/nova_powervm/virt/powervm/driver.py +++ b/nova_powervm/virt/powervm/driver.py @@ -39,6 +39,7 @@ from pypowervm.helpers import log_helper as log_hlp from pypowervm.helpers import vios_busy as vio_hlp from pypowervm.tasks import cna as pvm_cna from pypowervm.tasks import memory as pvm_mem +from pypowervm.tasks.monitor import host_cpu as pvm_hcpu from pypowervm.tasks import partition as pvm_par from pypowervm.tasks import power_opts as pvm_popts from pypowervm.tasks import scsi_mapper as pvm_smap @@ -130,8 +131,8 @@ class PowerVMDriver(driver.ComputeDriver): self._setup_rebuild_store() # Init Host CPU Statistics - self.host_cpu_stats = pvm_host.HostCPUStats(self.adapter, - self.host_uuid) + self.host_cpu_cache = pvm_hcpu.HostCPUMetricCache(self.adapter, + self.host_uuid) # Cache for instance overhead. # Key: max_mem (int MB) @@ -298,7 +299,16 @@ class PowerVMDriver(driver.ComputeDriver): def get_host_cpu_stats(self): """Return the current CPU state of the host.""" - return self.host_cpu_stats.get_host_cpu_stats() + self.host_cpu_cache.refresh() + return { + 'kernel': self.host_cpu_cache.total_fw_cycles, + 'user': self.host_cpu_cache.total_user_cycles, + 'idle': (self.host_cpu_cache.total_cycles - + self.host_cpu_cache.total_user_cycles - + self.host_cpu_cache.total_fw_cycles), + # Not reported by PowerVM + 'iowait': 0, + 'frequency': self.host_cpu_cache.cpu_freq} def instance_on_disk(self, instance): """Checks access of instance files on the host. diff --git a/nova_powervm/virt/powervm/host.py b/nova_powervm/virt/powervm/host.py index 6fe95e76..13ee617a 100644 --- a/nova_powervm/virt/powervm/host.py +++ b/nova_powervm/virt/powervm/host.py @@ -16,11 +16,8 @@ import math from nova.objects import fields -from oslo_concurrency import lockutils from oslo_log import log as logging from oslo_serialization import jsonutils -from pypowervm.tasks.monitor import util as pcm_util -import subprocess from nova import conf as cfg @@ -116,255 +113,3 @@ def _build_pci_json(sys_w): for vfn in range(pport.supp_max_lps)] return jsonutils.dumps(pci_devs) - - -class HostCPUStats(pcm_util.MetricCache): - """Transforms the PowerVM CPU metrics into the Nova format. - - PowerVM only gathers the CPU statistics once every 30 seconds. It does - this to reduce overhead. There is a function to gather statistics quicker, - but that can be very expensive. Therefore, to ensure that the client's - workload is not impacted, these 'longer term' metrics will be used. - - This class builds off of a base pypowervm function where it can obtain - the samples through a PCM 'cache'. If a new sample is available, the cache - pulls the sample. If it is not, the existing sample is used. - - This can result in multiple, quickly successive calls to the host stats - returning the same data (because a new sample may not be available yet). - - The class analyzes the data and collapses it down to the format needed by - the Nova manager. - """ - - def __init__(self, adapter, host_uuid): - """Creates an instance of the HostCPUStats. - - :param adapter: The pypowervm Adapter. - :param host_uuid: The UUID of the host CEC to maintain a metrics - cache for. - """ - # This represents the current state of cycles spent on the system. - # These are used to figure out usage statistics. As such, they are - # tied to the start of the nova compute process. - # - # - idle: Total idle cycles on the compute host. - # - kernel: How many cycles the hypervisor has consumed. Not a direct - # analogy to KVM - # - user: The amount of time spent by the VM's themselves. - # - iowait: Not used in PowerVM, but needed for nova. - # - frequency: The CPU frequency - self.tot_data = {'idle': 0, 'kernel': 0, 'user': 0, 'iowait': 0, - 'frequency': 0} - - # Invoke the parent to seed the metrics. Don't include VIO - will - # result in quicker calls. - super(HostCPUStats, self).__init__(adapter, host_uuid, - include_vio=False) - - @lockutils.synchronized('pvm_host_metrics_get') - def get_host_cpu_stats(self): - """Returns the currently known host CPU stats. - - :return: The dictionary (as defined by the compute driver's - get_host_cpu_stats). If insufficient data is available, - then 'None' will be returned. - """ - # Refresh if needed. Will no-op if no refresh is required. - self._refresh_if_needed() - - # The invoking code needs the total cycles for this to work properly. - # Return the dictionary format of the cycles as derived by the - # _update_internal_metric method. If there is no data yet, None would - # be the result. - return self.tot_data - - def _update_internal_metric(self): - """Uses the latest stats from the cache, and parses to Nova format. - - This method is invoked by the parent class after the raw metrics are - updated. - """ - # If there is no 'new' data (perhaps sampling is not turned on) then - # return no data. - if self.cur_phyp is None: - return - - # Compute the cycles spent in FW since last collection. - fw_cycles_delta = self._get_fw_cycles_delta() - - # Compute the cycles the system spent since last run. - tot_cycles_delta = self._get_total_cycles_delta() - - # Get the user cycles since last run - user_cycles_delta = self._gather_user_cycles_delta() - - # Make sure that the total cycles is higher than the user/fw cycles. - # Should not happen, but just in case there is any precision loss from - # CPU data back to system. - if user_cycles_delta + fw_cycles_delta > tot_cycles_delta: - LOG.warning( - "Host CPU Metrics determined that the total cycles reported " - "was less than the used cycles. This indicates an issue with " - "the PCM data. Please investigate the results.\n" - "Total Delta Cycles: %(tot_cycles)d\n" - "User Delta Cycles: %(user_cycles)d\n" - "Firmware Delta Cycles: %(fw_cycles)d", - {'tot_cycles': tot_cycles_delta, 'fw_cycles': fw_cycles_delta, - 'user_cycles': user_cycles_delta}) - tot_cycles_delta = user_cycles_delta + fw_cycles_delta - - # Idle is the subtraction of all. - idle_delta_cycles = (tot_cycles_delta - user_cycles_delta - - fw_cycles_delta) - - # The only moving cycles are idle, kernel and user. - self.tot_data['idle'] += idle_delta_cycles - self.tot_data['kernel'] += fw_cycles_delta - self.tot_data['user'] += user_cycles_delta - - # Frequency doesn't accumulate like the others. So this stays static. - self.tot_data['frequency'] = self._get_cpu_freq() - - def _gather_user_cycles_delta(self): - """The estimated user cycles of all VMs/VIOSes since last run. - - The sample data includes information about how much CPU has been used - by workloads and the Virtual I/O Servers. There is not one global - counter that can be used to obtain the CPU spent cycles. - - This method will calculate the delta of workload (and I/O Server) - cycles between the previous sample and the current sample. - - There are edge cases for this however. If a VM is deleted or migrated - its cycles will no longer be taken into account. The algorithm takes - this into account by building on top of the previous sample's user - cycles. - - :return: Estimated cycles spent on workload (including VMs and Virtual - I/O Server). This represents the entire server's current - 'user' load. - """ - # Current samples should be guaranteed to be there. - vm_cur_samples = self.cur_phyp.sample.lpars - vios_cur_samples = self.cur_phyp.sample.vioses - - # The previous samples may not have been there. - vm_prev_samples, vios_prev_samples = None, None - if self.prev_phyp is not None: - vm_prev_samples = self.prev_phyp.sample.lpars - vios_prev_samples = self.prev_phyp.sample.vioses - - # Gather the delta cycles between the previous and current data sets - vm_delta_cycles = self._delta_proc_cycles(vm_cur_samples, - vm_prev_samples) - vios_delta_cycles = self._delta_proc_cycles(vios_cur_samples, - vios_prev_samples) - - return vm_delta_cycles + vios_delta_cycles - - @staticmethod - def _get_cpu_freq(): - # The output will be similar to '4116.000000MHz' on a POWER system. - cmd = ['/usr/bin/awk', '/clock/ {print $3; exit}', '/proc/cpuinfo'] - return int(float(subprocess.check_output(cmd).rstrip("MHz\n"))) - - def _delta_proc_cycles(self, samples, prev_samples): - """Sums all the processor delta cycles for a set of VM/VIOS samples. - - This sum is the difference from the last sample to the current sample. - - :param samples: A set of PhypVMSample or PhypViosSample samples. - :param prev_samples: The set of the previous samples. May be None. - :return: The cycles spent on workload across all of the samples. - """ - # Determine the user cycles spent between the last sample and the - # current. - user_cycles = 0 - for lpar_sample in samples: - prev_sample = self._find_prev_sample(lpar_sample, prev_samples) - user_cycles += self._delta_user_cycles(lpar_sample, prev_sample) - return user_cycles - - @staticmethod - def _delta_user_cycles(cur_sample, prev_sample): - """Determines the delta of user cycles from the cur and prev sample. - - :param cur_sample: The current sample. - :param prev_sample: The previous sample. May be None. - :return: The difference in cycles between the two samples. If the data - only exists in the current sample (indicates a new workload), - then all of the cycles from the current sample will be - considered the delta. - """ - # If the previous sample for this VM is None it could be one of two - # conditions. It could be a new spawn or a live migration. The cycles - # from a live migrate are brought over from the previous host. That - # can disorient the calculation because all of a sudden you could get - # months of cycles. Since we can not discern between the two - # scenarios, we return 0 (effectively throwing the sample out). - # The next pass through will have the previous sample and will be - # included. - if prev_sample is None: - return 0 - # If the previous sample values are all 0 (happens when VM is just - # migrated, phyp creates entry for VM with 0 values), then ignore the - # sample. - if (prev_sample.processor.util_cap_proc_cycles == - prev_sample.processor.util_uncap_proc_cycles == - prev_sample.processor.idle_proc_cycles == 0): - return 0 - # The VM utilization on host is its capped + uncapped - idle cycles. - # Donated proc cycles should not be considered as these are - # not guaranteed to be getting utilized by any other lpar on the host. - prev_amount = (prev_sample.processor.util_cap_proc_cycles + - prev_sample.processor.util_uncap_proc_cycles - - prev_sample.processor.idle_proc_cycles) - cur_amount = (cur_sample.processor.util_cap_proc_cycles + - cur_sample.processor.util_uncap_proc_cycles - - cur_sample.processor.idle_proc_cycles) - return cur_amount - prev_amount - - @staticmethod - def _find_prev_sample(sample, prev_samples): - """Finds the previous VM Sample for a given current sample. - - :param sample: The current sample. - :param prev_samples: The previous samples to search through. - :return: The previous sample, if it exists. None otherwise. - """ - # Will occur if there are no previous samples. - if prev_samples is None: - return None - for prev_sample in prev_samples: - if prev_sample.id == sample.id and prev_sample.name == sample.name: - return prev_sample - return None - - def _get_total_cycles_delta(self): - """Returns the 'total cycles' on the system since last sample. - - :return: The total delta cycles since the last run. - """ - sample = self.cur_phyp.sample - cur_cores = sample.processor.configurable_proc_units - cur_cycles_per_core = sample.time_based_cycles - - if self.prev_phyp: - prev_cycles_per_core = self.prev_phyp.sample.time_based_cycles - else: - prev_cycles_per_core = 0 - - # Get the delta cycles between the cores. - delta_cycles_per_core = cur_cycles_per_core - prev_cycles_per_core - - # Total cycles since last sample is the 'per cpu' cycles spent - # times the number of active cores. - return delta_cycles_per_core * cur_cores - - def _get_fw_cycles_delta(self): - """Returns the number of cycles spent on firmware since last sample.""" - cur_fw = self.cur_phyp.sample.system_firmware.utilized_proc_cycles - prev_fw = (self.prev_phyp.sample.system_firmware.utilized_proc_cycles - if self.prev_phyp else 0) - return cur_fw - prev_fw