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:
esberglu 2017-10-17 16:19:00 -05:00 committed by Eric Fried
parent 5cf4a36eed
commit 429c98d848
5 changed files with 32 additions and 582 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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