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
This commit is contained in:
parent
5cf4a36eed
commit
429c98d848
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue