Convert percent metrics back into the [0, 1] range

With the recent revamp of the Nova metrics framework in the Liberty
cycle, there was a problem introduced in terms of how the percentage-
based metrics (e.g., cpu.percent) are represented. That is, prior to
Liberty, we stored the percentages in compute_nodes.metrics as a
floating point value in the range [0, 1]. For example, 17.5% was
represented as 0.175.

The revamped framework expects the MonitorMetric.value properties to
be integers, so when the percentages such as 0.175 are stored in this
object, they're erroneously converted to 0.

This patch multiplies the 'problematic' fields by 100 in preparation
for storage within the MonitorMetric object and then divides the
same problematic fields by 100.0 when converting back to their JSON
format, which will preserve RPC notifier and database compatibility.

Added some unit tests to test the conversions as well as making some
of the unit tests more explicit so this doesn't happen again.

Change-Id: I17a9412c63eeeb78ba2b2600f9007e9b58fcbf68
Closes-Bug: #1488696
This commit is contained in:
Joe Cropper 2015-08-25 20:53:55 -05:00
parent 764c4d467e
commit 87e1d8fab2
4 changed files with 42 additions and 16 deletions

View File

@ -77,17 +77,20 @@ class Monitor(base.CPUMonitorBase):
+ stats["idle"] + stats["iowait"])
cputime = float(stats["total"] - self._cpu_stats.get("total", 0))
# NOTE(jwcroppe): Convert all the `perc` values to their integer forms
# since pre-conversion their values are within the range [0, 1] and the
# objects.MonitorMetric.value field requires an integer.
perc = (stats["user"] - self._cpu_stats.get("user", 0)) / cputime
self._data["cpu.user.percent"] = perc
self._data["cpu.user.percent"] = int(perc * 100)
perc = (stats["kernel"] - self._cpu_stats.get("kernel", 0)) / cputime
self._data["cpu.kernel.percent"] = perc
self._data["cpu.kernel.percent"] = int(perc * 100)
perc = (stats["idle"] - self._cpu_stats.get("idle", 0)) / cputime
self._data["cpu.idle.percent"] = perc
self._data["cpu.idle.percent"] = int(perc * 100)
perc = (stats["iowait"] - self._cpu_stats.get("iowait", 0)) / cputime
self._data["cpu.iowait.percent"] = perc
self._data["cpu.iowait.percent"] = int(perc * 100)
# Compute the current system-wide CPU utilization as a percentage.
used = stats["user"] + stats["kernel"] + stats["iowait"]
@ -95,6 +98,6 @@ class Monitor(base.CPUMonitorBase):
+ self._cpu_stats.get("kernel", 0)
+ self._cpu_stats.get("iowait", 0))
perc = (used - prev_used) / cputime
self._data["cpu.percent"] = perc
self._data["cpu.percent"] = int(perc * 100)
self._cpu_stats = stats.copy()

View File

@ -17,6 +17,16 @@ from nova.objects import fields
from nova import utils
# NOTE(jwcroppe): Used to determine which fields whose value we need to adjust
# (read: divide by 100.0) before sending information to the RPC notifier since
# these values were expected to be within the range [0, 1].
FIELDS_REQUIRING_CONVERSION = [fields.MonitorMetricType.CPU_USER_PERCENT,
fields.MonitorMetricType.CPU_KERNEL_PERCENT,
fields.MonitorMetricType.CPU_IDLE_PERCENT,
fields.MonitorMetricType.CPU_IOWAIT_PERCENT,
fields.MonitorMetricType.CPU_PERCENT]
@base.NovaObjectRegistry.register
class MonitorMetric(base.NovaObject):
# Version 1.0: Initial version
@ -54,7 +64,10 @@ class MonitorMetric(base.NovaObject):
}
if self.obj_attr_is_set('value'):
dict_to_return['value'] = self.value
if self.name in FIELDS_REQUIRING_CONVERSION:
dict_to_return['value'] = self.value / 100.0
else:
dict_to_return['value'] = self.value
elif self.obj_attr_is_set('numa_membw_values'):
dict_to_return['numa_membw_values'] = self.numa_membw_values

View File

@ -65,13 +65,8 @@ class VirtDriverCPUMonitorTestCase(test.NoDBTestCase):
self.assertEqual(metrics["cpu.kernel.time"], 5664160000000)
self.assertEqual(metrics["cpu.idle.time"], 1592705190000000)
self.assertEqual(metrics["cpu.iowait.time"], 6121490000000)
self.assertTrue(metrics["cpu.user.percent"] <= 1
and metrics["cpu.user.percent"] >= 0)
self.assertTrue(metrics["cpu.kernel.percent"] <= 1
and metrics["cpu.kernel.percent"] >= 0)
self.assertTrue(metrics["cpu.idle.percent"] <= 1
and metrics["cpu.idle.percent"] >= 0)
self.assertTrue(metrics["cpu.iowait.percent"] <= 1
and metrics["cpu.iowait.percent"] >= 0)
self.assertTrue(metrics["cpu.percent"] <= 1
and metrics["cpu.percent"] >= 0)
self.assertEqual(metrics["cpu.user.percent"], 1)
self.assertEqual(metrics["cpu.kernel.percent"], 0)
self.assertEqual(metrics["cpu.idle.percent"], 97)
self.assertEqual(metrics["cpu.iowait.percent"], 0)
self.assertEqual(metrics["cpu.percent"], 2)

View File

@ -25,6 +25,13 @@ _monitor_metric_spec = {
'source': 'nova.virt.libvirt.driver'
}
_monitor_metric_perc_spec = {
'name': fields.MonitorMetricType.CPU_PERCENT,
'value': 0.17,
'timestamp': timeutils.strtime(_ts_now),
'source': 'nova.virt.libvirt.driver'
}
_monitor_numa_metric_spec = {
'name': fields.MonitorMetricType.NUMA_MEM_BW_CURRENT,
'numa_membw_values': {"0": 10, "1": 43},
@ -43,6 +50,14 @@ class _TestMonitorMetricObject(object):
source='nova.virt.libvirt.driver')
self.assertEqual(_monitor_metric_spec, obj.to_dict())
def test_monitor_metric_perc_to_dict(self):
"""Test to ensure division by 100.0 occurs on percentage value."""
obj = objects.MonitorMetric(name='cpu.percent',
value=17,
timestamp=_ts_now,
source='nova.virt.libvirt.driver')
self.assertEqual(_monitor_metric_perc_spec, obj.to_dict())
def test_monitor_metric_list_to_list(self):
obj = objects.MonitorMetric(name='cpu.frequency',
value=1000,