Create new meter to poll power usage
IPMI sensor 'Current' captures current & power consumption metrics. With the help of new pollster "hardware.ipmi.power", ceilometer ipmi agent can differentiate between current and power metrics as both are generated from the same sensor(Current). Power metrics are captured using a slightly different command than other sensors which is "ipmitool get sensor 'Pwr Consumption'". Closes-Bug: #2038425 Change-Id: I0a8af40626cd44dca9743fba63c8dbda8729d054
This commit is contained in:
parent
f1e6594e52
commit
8f54f95134
|
@ -115,6 +115,13 @@ class SensorNotification(endpoint.SampleEndpoint):
|
|||
except KeyError as exc:
|
||||
raise InvalidSensorData('missing key in payload: %s' % exc)
|
||||
|
||||
# Do not pick up power consumption metrics from Current sensor
|
||||
if (
|
||||
self.metric == 'Current' and
|
||||
'Pwr Consumption' in payload['Sensor ID']
|
||||
):
|
||||
continue
|
||||
|
||||
info = self._package_payload(message, payload)
|
||||
|
||||
try:
|
||||
|
@ -159,3 +166,7 @@ class FanSensorNotification(SensorNotification):
|
|||
|
||||
class VoltageSensorNotification(SensorNotification):
|
||||
metric = 'Voltage'
|
||||
|
||||
|
||||
class PowerSensorNotification(SensorNotification):
|
||||
metric = 'Power'
|
||||
|
|
|
@ -24,7 +24,8 @@ IPMICMD = {"sdr_dump": "sdr dump",
|
|||
"sensor_dump_temperature": "sdr -v type Temperature",
|
||||
"sensor_dump_current": "sdr -v type Current",
|
||||
"sensor_dump_fan": "sdr -v type Fan",
|
||||
"sensor_dump_voltage": "sdr -v type Voltage"}
|
||||
"sensor_dump_voltage": "sdr -v type Voltage",
|
||||
"sensor_dump_power": "sensor get 'Pwr Consumption'"}
|
||||
|
||||
# Requires translation of output into dict
|
||||
DICT_TRANSLATE_TEMPLATE = {"translate": 1}
|
||||
|
@ -74,6 +75,11 @@ class IPMISensor(object):
|
|||
"""Get the sensor data for Voltage."""
|
||||
return IPMICMD['sensor_dump_voltage']
|
||||
|
||||
@ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE)
|
||||
def _read_sensor_power(self):
|
||||
"""Get the sensor data for Power."""
|
||||
return IPMICMD['sensor_dump_power']
|
||||
|
||||
@ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE)
|
||||
def _read_sensor_current(self):
|
||||
"""Get the sensor data for Current."""
|
||||
|
@ -93,7 +99,8 @@ class IPMISensor(object):
|
|||
'Temperature': self._read_sensor_temperature,
|
||||
'Fan': self._read_sensor_fan,
|
||||
'Voltage': self._read_sensor_voltage,
|
||||
'Current': self._read_sensor_current}
|
||||
'Current': self._read_sensor_current,
|
||||
'Power': self._read_sensor_power}
|
||||
|
||||
try:
|
||||
return mapping[sensor_type]()
|
||||
|
|
|
@ -19,6 +19,7 @@ from ceilometer.i18n import _
|
|||
from ceilometer.ipmi.platform import exception as ipmiexcept
|
||||
|
||||
import ceilometer.privsep.ipmitool
|
||||
import shlex
|
||||
|
||||
|
||||
# Following 2 functions are copied from ironic project to handle ipmitool's
|
||||
|
@ -122,7 +123,7 @@ def execute_ipmi_cmd(template=None):
|
|||
def _execute(self, **kwargs):
|
||||
args = ['ipmitool']
|
||||
command = f(self, **kwargs)
|
||||
args.extend(command.split(" "))
|
||||
args.extend(shlex.split(command))
|
||||
try:
|
||||
(out, __) = ceilometer.privsep.ipmitool.ipmi(*args)
|
||||
except processutils.ProcessExecutionError:
|
||||
|
|
|
@ -47,6 +47,11 @@ class SensorPollster(plugin_base.PollsterBase):
|
|||
|
||||
@staticmethod
|
||||
def _get_sensor_types(data, sensor_type):
|
||||
# Ipmitool reports 'Pwr Consumption' as sensor type 'Current'.
|
||||
# Set sensor_type to 'Current' when polling 'Power' metrics.
|
||||
if sensor_type == 'Power':
|
||||
sensor_type = 'Current'
|
||||
|
||||
try:
|
||||
return (sensor_type_data for _, sensor_type_data
|
||||
in data[sensor_type].items())
|
||||
|
@ -81,6 +86,10 @@ class SensorPollster(plugin_base.PollsterBase):
|
|||
except KeyError:
|
||||
continue
|
||||
|
||||
# Do not pick up power consumption metrics from 'Current' sensor
|
||||
if self.METRIC == 'Current' and 'Pwr Consumption' in sensor_id:
|
||||
continue
|
||||
|
||||
if not parser.validate_reading(sensor_reading):
|
||||
continue
|
||||
|
||||
|
@ -123,3 +132,7 @@ class FanSensorPollster(SensorPollster):
|
|||
|
||||
class VoltageSensorPollster(SensorPollster):
|
||||
METRIC = 'Voltage'
|
||||
|
||||
|
||||
class PowerSensorPollster(SensorPollster):
|
||||
METRIC = 'Power'
|
||||
|
|
|
@ -273,9 +273,9 @@ TEMPERATURE_DATA = {
|
|||
|
||||
|
||||
CURRENT_DATA = {
|
||||
'Avg Power (0x2e)': {
|
||||
'Current 1 (0x6b)': {
|
||||
'Status': 'ok',
|
||||
'Sensor Reading': '130 (+/- 0) Watts',
|
||||
'Sensor Reading': '0.800 (+/- 0) Amps',
|
||||
'Entity ID': '21.0 (Power Management)',
|
||||
'Assertions Enabled': '',
|
||||
'Event Message Control': 'Per-threshold',
|
||||
|
@ -284,10 +284,56 @@ CURRENT_DATA = {
|
|||
'Sensor Type (Analog)': 'Current',
|
||||
'Negative Hysteresis': 'Unspecified',
|
||||
'Maximum sensor range': 'Unspecified',
|
||||
'Sensor ID': 'Avg Power (0x2e)',
|
||||
'Sensor ID': 'Current 1 (0x6b)',
|
||||
'Assertion Events': '',
|
||||
'Minimum sensor range': '2550.000',
|
||||
'Settable Thresholds': 'No Thresholds'
|
||||
},
|
||||
'Pwr Consumption (0x76)': {
|
||||
'Entity ID': '7.1 (System Board)',
|
||||
'Sensor Type (Threshold)': 'Current (0x03)',
|
||||
'Sensor Reading': '160 (+/- 0) Watts',
|
||||
'Status': 'ok',
|
||||
'Nominal Reading': '1034.000',
|
||||
'Normal Maximum': '1056.000',
|
||||
'Upper critical': '1914.000',
|
||||
'Upper non-critical': '1738.000',
|
||||
'Positive Hysteresis': 'Unspecified',
|
||||
'Negative Hysteresis': 'Unspecified',
|
||||
'Minimum sensor range': 'Unspecified',
|
||||
'Maximum sensor range': '5588.000',
|
||||
'Sensor ID': 'Pwr Consumption (0x76)',
|
||||
'Event Message Control': 'Per-threshold',
|
||||
'Readable Thresholds': 'unc ucr',
|
||||
'Settable Thresholds': 'unc',
|
||||
'Assertion Events': '',
|
||||
'Assertions Enabled': 'unc+ ucr+',
|
||||
'Deassertions Enabled': 'unc+ ucr+'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
POWER_DATA = {
|
||||
'Pwr Consumption (0x76)': {
|
||||
'Entity ID': '7.1 (System Board)',
|
||||
'Sensor Type (Threshold)': 'Current (0x03)',
|
||||
'Sensor Reading': '154 (+/- 0) Watts',
|
||||
'Status': 'ok',
|
||||
'Nominal Reading': '1034.000',
|
||||
'Normal Maximum': '1056.000',
|
||||
'Upper critical': '1914.000',
|
||||
'Upper non-critical': '1738.000',
|
||||
'Positive Hysteresis': 'Unspecified',
|
||||
'Negative Hysteresis': 'Unspecified',
|
||||
'Minimum sensor range': 'Unspecified',
|
||||
'Maximum sensor range': '5588.000',
|
||||
'Sensor ID': 'Pwr Consumption (0x76)',
|
||||
'Event Message Control': 'Per-threshold',
|
||||
'Readable Thresholds': 'unc ucr',
|
||||
'Settable Thresholds': 'unc',
|
||||
'Assertion Events': '',
|
||||
'Assertions Enabled': 'unc+ ucr+',
|
||||
'Deassertions Enabled': 'unc+ ucr+'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -661,7 +707,8 @@ SENSOR_DATA = {
|
|||
'Temperature': TEMPERATURE_DATA,
|
||||
'Current': CURRENT_DATA,
|
||||
'Fan': FAN_DATA,
|
||||
'Voltage': VOLTAGE_DATA
|
||||
'Voltage': VOLTAGE_DATA,
|
||||
'Power': POWER_DATA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,14 +71,35 @@ class TestNotifications(base.BaseTestCase):
|
|||
|
||||
self.assertEqual(1, len(counters), 'expected 1 current reading')
|
||||
resource_id = (
|
||||
'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-avg_power_(0x2e)'
|
||||
'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-current_1_(0x6b)'
|
||||
)
|
||||
test_counter = counters[resource_id]
|
||||
self.assertEqual(130.0, test_counter.volume)
|
||||
self.assertEqual('W', test_counter.unit)
|
||||
self.assertEqual(0.800, test_counter.volume)
|
||||
self.assertEqual('Amps', test_counter.unit)
|
||||
self.assertEqual(sample.TYPE_GAUGE, test_counter.type)
|
||||
self.assertEqual('hardware.ipmi.current', test_counter.name)
|
||||
|
||||
def test_ipmi_power_notification(self):
|
||||
"""Test IPMI Power sample from Current sensor.
|
||||
|
||||
A single power reading is effectively the same as temperature,
|
||||
modulo "power".
|
||||
"""
|
||||
processor = ipmi.PowerSensorNotification(None, None)
|
||||
counters = dict([(counter.resource_id, counter) for counter in
|
||||
processor.build_sample(
|
||||
ipmi_test_data.SENSOR_DATA)])
|
||||
|
||||
self.assertEqual(1, len(counters), 'expected 1 current reading')
|
||||
resource_id = (
|
||||
'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-pwr_consumption_(0x76)'
|
||||
)
|
||||
test_counter = counters[resource_id]
|
||||
self.assertEqual(154, test_counter.volume)
|
||||
self.assertEqual('W', test_counter.unit)
|
||||
self.assertEqual(sample.TYPE_GAUGE, test_counter.type)
|
||||
self.assertEqual('hardware.ipmi.power', test_counter.name)
|
||||
|
||||
def test_ipmi_fan_notification(self):
|
||||
"""Test IPMI Fan sensor data.
|
||||
|
||||
|
|
|
@ -226,6 +226,26 @@ Sensor ID : PS2 Curr Out % (0x59)
|
|||
Assertions Enabled : unc+ ucr+
|
||||
Deassertions Enabled : unc+ ucr+
|
||||
|
||||
Sensor ID : Pwr Consumption (0x76)
|
||||
Entity ID : 7.1 (System Board)
|
||||
Sensor Type (Threshold) : Current (0x03)
|
||||
Sensor Reading : 154 (+/- 0) Watts
|
||||
Status : ok
|
||||
Nominal Reading : 1034.000
|
||||
Normal Maximum : 1056.000
|
||||
Upper critical : 1914.000
|
||||
Upper non-critical : 1738.000
|
||||
Positive Hysteresis : Unspecified
|
||||
Negative Hysteresis : Unspecified
|
||||
Minimum sensor range : Unspecified
|
||||
Maximum sensor range : 5588.000
|
||||
Event Message Control : Per-threshold
|
||||
Readable Thresholds : unc ucr
|
||||
Settable Thresholds : unc
|
||||
Assertion Events :
|
||||
Assertions Enabled : unc+ ucr+
|
||||
Deassertions Enabled : unc+ ucr+
|
||||
|
||||
"""
|
||||
|
||||
sensor_fan_data = """Sensor ID : System Fan 1 (0x30)
|
||||
|
|
|
@ -69,12 +69,25 @@ class TestIPMISensor(base.BaseTestCase):
|
|||
self.assertIn('Current', sensors)
|
||||
self.assertEqual(1, len(sensors))
|
||||
|
||||
# 2 sensor data in total.
|
||||
# 3 sensor data in total.
|
||||
# Check ceilometer/tests/ipmi/platform/ipmi_test_data.py
|
||||
self.assertEqual(2, len(sensors['Current']))
|
||||
self.assertEqual(3, len(sensors['Current']))
|
||||
sensor = sensors['Current']['PS1 Curr Out % (0x58)']
|
||||
self.assertEqual('11 (+/- 0) unspecified', sensor['Sensor Reading'])
|
||||
|
||||
def test_read_sensor_power(self):
|
||||
sensors = self.ipmi.read_sensor_any('Current')
|
||||
|
||||
# only Current data returned.
|
||||
self.assertIn('Current', sensors)
|
||||
self.assertEqual(1, len(sensors))
|
||||
|
||||
# 3 sensor data in total.
|
||||
# Check ceilometer/tests/ipmi/platform/ipmi_test_data.py
|
||||
self.assertEqual(3, len(sensors['Current']))
|
||||
sensor = sensors['Current']['Pwr Consumption (0x76)']
|
||||
self.assertEqual('154 (+/- 0) Watts', sensor['Sensor Reading'])
|
||||
|
||||
def test_read_sensor_fan(self):
|
||||
sensors = self.ipmi.read_sensor_any('Fan')
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ VOLTAGE_SENSOR_DATA = {
|
|||
'Voltage': ipmi_test_data.VOLTAGE_DATA
|
||||
}
|
||||
|
||||
POWER_SENSOR_DATA = {
|
||||
'Current': ipmi_test_data.POWER_DATA
|
||||
}
|
||||
|
||||
MISSING_SENSOR_DATA = ipmi_test_data.MISSING_SENSOR['payload']['payload']
|
||||
MALFORMED_SENSOR_DATA = ipmi_test_data.BAD_SENSOR['payload']['payload']
|
||||
MISSING_ID_SENSOR_DATA = ipmi_test_data.NO_SENSOR_ID['payload']['payload']
|
||||
|
@ -115,7 +119,7 @@ class TestCurrentSensorPollster(base.TestPollsterBase):
|
|||
def test_get_samples(self):
|
||||
self._test_get_samples()
|
||||
|
||||
self._verify_metering(1, float(130), self.CONF.host)
|
||||
self._verify_metering(1, float(0.800), self.CONF.host)
|
||||
|
||||
|
||||
class TestVoltageSensorPollster(base.TestPollsterBase):
|
||||
|
@ -130,3 +134,17 @@ class TestVoltageSensorPollster(base.TestPollsterBase):
|
|||
self._test_get_samples()
|
||||
|
||||
self._verify_metering(4, float(3.309), self.CONF.host)
|
||||
|
||||
|
||||
class TestPowerSensorPollster(base.TestPollsterBase):
|
||||
|
||||
def fake_sensor_data(self, sensor_type):
|
||||
return POWER_SENSOR_DATA
|
||||
|
||||
def make_pollster(self):
|
||||
return sensor.PowerSensorPollster(self.CONF)
|
||||
|
||||
def test_get_samples(self):
|
||||
self._test_get_samples()
|
||||
|
||||
self._verify_metering(1, int(154), self.CONF.host)
|
||||
|
|
|
@ -102,7 +102,7 @@ class TestManager(base.BaseTestCase):
|
|||
mgr = manager.AgentManager(0, self.conf,
|
||||
namespaces=['ipmi'])
|
||||
# 8 pollsters for Node Manager
|
||||
self.assertEqual(12, len(mgr.extensions))
|
||||
self.assertEqual(13, len(mgr.extensions))
|
||||
|
||||
# Skip loading pollster upon ExtensionLoadError
|
||||
@mock.patch('ceilometer.ipmi.pollsters.node._Base.__init__',
|
||||
|
|
|
@ -115,6 +115,7 @@ ceilometer.poll.ipmi =
|
|||
hardware.ipmi.voltage = ceilometer.ipmi.pollsters.sensor:VoltageSensorPollster
|
||||
hardware.ipmi.current = ceilometer.ipmi.pollsters.sensor:CurrentSensorPollster
|
||||
hardware.ipmi.fan = ceilometer.ipmi.pollsters.sensor:FanSensorPollster
|
||||
hardware.ipmi.power = ceilometer.ipmi.pollsters.sensor:PowerSensorPollster
|
||||
|
||||
ceilometer.poll.central =
|
||||
ip.floating = ceilometer.network.floatingip:FloatingIPPollster
|
||||
|
|
Loading…
Reference in New Issue