Support averaging power values
Support new env variables 'AVERAGE_power* = "true"' in hwmon config file.
When this env variable is set, power value is the calculated average value.
Otherwise, power value is from power*_input by default.
The new average of power is calculated since the last time the sensor's
values were changed and read.
average =
(cur_average*cur_average_interval - pre_average*pre_average_interval) /
(cur_average_interval - pre_average_interval)
hwmon config example:
AVERAGE_power2 = "true"
AVERAGE_power3 = "true"
AVERAGE_power4 = "true"
Tested: Set AVERAGE_power* in p0 OCC hwmon conf but not in p1 OCC hwmon conf,
then get power sensor info with restapi to check the values.
1. The values of p0*power are all average values.
2. The values of p1*power are all input values.
Note:
Delete $(CODE_COVERAGE_CPPFLAGS) in AM_CPPFLAGS in test/Makefile.am.
This option will define NDEBUG during configuration, then assert in
code doesn't work.
Resolves: openbmc/openbmc#3187
Signed-off-by: Carol Wang <wangkair@cn.ibm.com>
Change-Id: I8d97a7b2905c79cd4f2c276b32e7f5590ffc0483
diff --git a/Makefile.am b/Makefile.am
index 2efbe77..378c565 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -67,6 +67,7 @@
hwmon.cpp \
hwmonio.cpp \
sensor.cpp \
- gpio_handle.cpp
+ gpio_handle.cpp \
+ average.cpp
SUBDIRS = . msl test tools
diff --git a/average.cpp b/average.cpp
new file mode 100644
index 0000000..c5864bd
--- /dev/null
+++ b/average.cpp
@@ -0,0 +1,51 @@
+#include "average.hpp"
+
+#include <cassert>
+
+std::optional<Average::averageValue>
+ Average::getAverageValue(const Average::averageKey& sensorKey) const
+{
+ const auto it = _previousAverageMap.find(sensorKey);
+ if (it == _previousAverageMap.end())
+ {
+ return {};
+ }
+
+ return std::optional(it->second);
+}
+
+void Average::setAverageValue(const Average::averageKey& sensorKey,
+ const Average::averageValue& sensorValue)
+{
+ _previousAverageMap[sensorKey] = sensorValue;
+}
+
+std::optional<int64_t> Average::calcAverage(int64_t preAverage,
+ int64_t preInterval,
+ int64_t curAverage,
+ int64_t curInterval)
+{
+ int64_t value = 0;
+ // Estimate that the interval will overflow about 292471
+ // years after it starts counting, so consider it won't
+ // overflow
+ int64_t delta = curInterval - preInterval;
+
+ assert(delta >= 0);
+ // 0 means the delta interval is too short, the value of
+ // power*_average_interval is not changed yet
+ if (delta == 0)
+ {
+ return {};
+ }
+ // Change formula (a2*i2-a1*i1)/(i2-i1) to be the
+ // following formula, to avoid multiplication overflow.
+ // (a2*i2-a1*i1)/(i2-i1) =
+ // (a2*(i1+delta)-a1*i1)/delta =
+ // (a2-a1)(i1/delta)+a2
+ value =
+ (curAverage - preAverage) * (static_cast<double>(preInterval) / delta) +
+ curAverage;
+
+ return value;
+}
diff --git a/average.hpp b/average.hpp
new file mode 100644
index 0000000..5474065
--- /dev/null
+++ b/average.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "sensorset.hpp"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+/** @class AverageHandling
+ * @brief Handle avergae value when AVERAGE_* is set in env
+ */
+class Average
+{
+ public:
+ /** @brief The key type of average_set */
+ using averageKey = SensorSet::key_type;
+
+ /** @brief <average, average_interval>
+ * average is the value of power*_average.
+ * average_interval is the value of power*_average_interval.
+ */
+ using averageValue = std::pair<int64_t, int64_t>;
+
+ /** @brief Store sensors' <averageKey, averageValue> map */
+ using averageMap = std::map<averageKey, averageValue>;
+
+ /** @brief Get averageValue in averageMap based on averageKey.
+ * This function will be called only when the env AVERAGE_xxx is set to
+ * true.
+ *
+ * @param[in] sensorKey - Sensor details
+ *
+ * @return - Optional
+ * return {}, if sensorKey can not be found in averageMap
+ * return averageValue, if sensorKey can be found in averageMap
+ */
+ std::optional<averageValue>
+ getAverageValue(const averageKey& sensorKey) const;
+
+ /** @brief Set average value in averageMap based on sensor key.
+ * This function will be called only when the env AVERAGE_xxx is set to
+ * true.
+ *
+ * @param[in] sensorKey - Sensor details
+ * @param[in] sensorValue - The related average values of this sensor
+ */
+ void setAverageValue(const averageKey& sensorKey,
+ const averageValue& sensorValue);
+
+ /** @brief Calculate the average value.
+ *
+ * @param[in] preAverage - The previous average value from *_average file
+ * @param[in] preInterval - The previous interval value from
+ * *_average_interval file
+ * @param[in] curAverage - The current average value from *_average file
+ * @param[in] curInterval - The current interval value from
+ * *_average_interval file
+ *
+ * @return value - Optional
+ * return {}, if curInterval-preInterval=0
+ * return new calculated average value, if curInterval-preInterval>0
+ */
+ static std::optional<int64_t> calcAverage(int64_t preAverage,
+ int64_t preInterval,
+ int64_t curAverage,
+ int64_t curInterval);
+
+ private:
+ /** @brief Store the previous average sensor map */
+ averageMap _previousAverageMap;
+};
\ No newline at end of file
diff --git a/hwmon.hpp b/hwmon.hpp
index 4f4545c..c02ec52 100644
--- a/hwmon.hpp
+++ b/hwmon.hpp
@@ -14,12 +14,16 @@
static constexpr auto ctarget = "target";
static constexpr auto cenable = "enable";
static constexpr auto cfault = "fault";
+static constexpr auto caverage = "average";
+static constexpr auto caverage_interval = "average_interval";
static const std::string input = cinput;
static const std::string label = clabel;
static const std::string target = ctarget;
static const std::string enable = cenable;
static const std::string fault = cfault;
+static const std::string average = caverage;
+static const std::string average_interval = caverage_interval;
} // namespace entry
namespace type
diff --git a/mainloop.cpp b/mainloop.cpp
index 05ac623..4abfc02 100644
--- a/mainloop.cpp
+++ b/mainloop.cpp
@@ -27,6 +27,7 @@
#include "sysfs.hpp"
#include "targets.hpp"
#include "thresholds.hpp"
+#include "util.hpp"
#include <cassert>
#include <cstdlib>
@@ -351,6 +352,13 @@
_state[std::move(i.first)] = std::move(value);
}
+
+ // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>>
+ if ((i.first.first == hwmon::type::power) &&
+ (phosphor::utility::isAverageEnvSet(i.first)))
+ {
+ _average.setAverageValue(i.first, std::make_pair(0, 0));
+ }
}
/* If there are no sensors specified by labels, exit. */
@@ -394,11 +402,18 @@
}
// Read value from sensor.
- std::string input = hwmon::entry::cinput;
- if (sensorSysfsType == "pwm")
+ std::string input = hwmon::entry::input;
+ if (sensorSysfsType == hwmon::type::pwm)
{
input = "";
}
+ // If type is power and AVERAGE_power* is true in env, use average
+ // instead of input
+ else if ((sensorSysfsType == hwmon::type::power) &&
+ (phosphor::utility::isAverageEnvSet(sensorSetKey)))
+ {
+ input = hwmon::entry::average;
+ }
int64_t value;
auto& obj = std::get<InterfaceMap>(objInfo);
@@ -437,6 +452,41 @@
statusIface->functional(true);
value = sensor->adjustValue(value);
+
+ if (input == hwmon::entry::average)
+ {
+ // Calculate the values of averageMap based on current
+ // average value, current average_interval value, previous
+ // average value, previous average_interval value
+ int64_t interval =
+ _ioAccess->read(sensorSysfsType, sensorSysfsNum,
+ hwmon::entry::caverage_interval,
+ hwmonio::retries, hwmonio::delay);
+ auto ret = _average.getAverageValue(sensorSetKey);
+ assert(ret);
+
+ const auto& [preAverage, preInterval] = *ret;
+
+ auto calValue = Average::calcAverage(
+ preAverage, preInterval, value, interval);
+ if (calValue)
+ {
+ // Update previous values in averageMap before the
+ // variable value is changed next
+ _average.setAverageValue(
+ sensorSetKey, std::make_pair(value, interval));
+ // Update value to be calculated average
+ value = calValue.value();
+ }
+ else
+ {
+ // the value of
+ // power*_average_interval is not changed yet, use the
+ // previous calculated average instead. So skip dbus
+ // update.
+ continue;
+ }
+ }
}
updateSensorInterfaces(obj, value);
@@ -450,9 +500,8 @@
// as the code may exit before reaching it.
statusIface->functional(false);
#endif
- auto file =
- sysfs::make_sysfs_path(_ioAccess->path(), sensorSysfsType,
- sensorSysfsNum, hwmon::entry::cinput);
+ auto file = sysfs::make_sysfs_path(
+ _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input);
// Check sensorAdjusts for sensor removal RCs
auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts();
@@ -534,10 +583,18 @@
_state[std::move(ssValueType.first)] = std::move(value);
+ std::string input = hwmon::entry::input;
+ // If type is power and AVERAGE_power* is true in env, use
+ // average instead of input
+ if ((it->first.first == hwmon::type::power) &&
+ (phosphor::utility::isAverageEnvSet(it->first)))
+ {
+ input = hwmon::entry::average;
+ }
// Sensor object added, erase entry from removal list
- auto file = sysfs::make_sysfs_path(
- _ioAccess->path(), it->first.first, it->first.second,
- hwmon::entry::cinput);
+ auto file =
+ sysfs::make_sysfs_path(_ioAccess->path(), it->first.first,
+ it->first.second, input);
log<level::INFO>("Added sensor to dbus after successful read",
entry("FILE=%s", file.c_str()));
diff --git a/mainloop.hpp b/mainloop.hpp
index d99ed91..6c5b8e0 100644
--- a/mainloop.hpp
+++ b/mainloop.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "average.hpp"
#include "hwmonio.hpp"
#include "interface.hpp"
#include "sensor.hpp"
@@ -121,6 +122,10 @@
*/
std::map<SensorSet::key_type, SensorSet::mapped_type> _rmSensors;
+ /** @brief Object of class Average, to handle with average related process
+ */
+ Average _average;
+
/**
* @brief Get the ID of the sensor
*
diff --git a/test/Makefile.am b/test/Makefile.am
index a40391a..dce609a 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -5,8 +5,7 @@
$(GTEST_CFLAGS) \
$(GMOCK_CFLAGS) \
$(SDBUSPLUS_CFLAGS) \
- $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
- $(CODE_COVERAGE_CPPFLAGS)
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
AM_CFLAGS = \
$(CODE_COVERAGE_CFLAGS)
AM_CXXFLAGS = \
@@ -21,9 +20,15 @@
$(CODE_COVERAGE_LIBS)
# Run all 'check' test programs
-check_PROGRAMS = hwmon_unittest fanpwm_unittest sensor_unittest hwmonio_default_unittest
+check_PROGRAMS = hwmon_unittest fanpwm_unittest sensor_unittest hwmonio_default_unittest env_unittest average_unittest
TESTS = $(check_PROGRAMS)
+env_unittest_SOURCES = env_unittest.cpp
+env_unittest_LDADD = env.o
+
+average_unittest_SOURCES = average_unittest.cpp
+average_unittest_LDADD = $(top_builddir)/average.o
+
hwmon_unittest_SOURCES = hwmon_unittest.cpp
hwmon_unittest_LDADD = $(top_builddir)/hwmon.o
diff --git a/test/average_unittest.cpp b/test/average_unittest.cpp
new file mode 100644
index 0000000..9eef0f8
--- /dev/null
+++ b/test/average_unittest.cpp
@@ -0,0 +1,49 @@
+#include "average.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::Return;
+
+TEST(SensorKeyTest, InvalidSensorKey)
+{
+ Average av;
+
+ av.setAverageValue(std::make_pair("power", "0"), std::make_pair(0L, 0L));
+ av.setAverageValue(std::make_pair("power", "1"), std::make_pair(0L, 0L));
+
+ EXPECT_FALSE(av.getAverageValue(std::make_pair("power", "4")));
+}
+
+TEST(SensorKeyTest, ValidSensorKey)
+{
+ Average av;
+
+ av.setAverageValue(std::make_pair("power", "0"), std::make_pair(0L, 0L));
+ av.setAverageValue(std::make_pair("power", "1"), std::make_pair(2L, 2L));
+
+ auto value = av.getAverageValue(std::make_pair("power", "1"));
+ EXPECT_TRUE(value == std::make_pair(2L, 2L));
+}
+
+TEST(AverageTest, ZeroDelta)
+{
+ Average av;
+
+ EXPECT_FALSE(av.calcAverage(1L, 1L, 2L, 1L));
+}
+
+TEST(AverageTest, NegativeDelta)
+{
+ Average av;
+
+ ASSERT_DEATH(av.calcAverage(1L, 1L, 2L, 0L), "");
+}
+
+TEST(AverageTest, RightAverage)
+{
+ Average av;
+
+ EXPECT_TRUE(38837438L == av.calcAverage(27624108L, 132864155500L, 27626120L,
+ 132887999500L));
+}
diff --git a/test/env_unittest.cpp b/test/env_unittest.cpp
new file mode 100644
index 0000000..665f8a5
--- /dev/null
+++ b/test/env_unittest.cpp
@@ -0,0 +1,35 @@
+#include "env.hpp"
+#include "env_mock.hpp"
+#include "util.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+TEST(EnvTest, EmptyEnv)
+{
+ EXPECT_FALSE(
+ phosphor::utility::isAverageEnvSet(std::make_pair("power", "1")));
+}
+
+TEST(EnvTest, ValidAverageEnv)
+{
+ StrictMock<EnvMock> eMock;
+ envIntf = &eMock;
+
+ std::string power = "power";
+ std::string one = "1";
+ std::string two = "2";
+
+ EXPECT_CALL(eMock, getEnv(StrEq("AVERAGE"), power, one))
+ .WillOnce(Return("true"));
+ EXPECT_CALL(eMock, getEnv(StrEq("AVERAGE"), power, two))
+ .WillOnce(Return("bar"));
+
+ EXPECT_TRUE(phosphor::utility::isAverageEnvSet(std::make_pair(power, one)));
+ EXPECT_FALSE(
+ phosphor::utility::isAverageEnvSet(std::make_pair(power, two)));
+}
diff --git a/util.hpp b/util.hpp
index 39749ac..d91b1b5 100644
--- a/util.hpp
+++ b/util.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "sensorset.hpp"
+
#include <cstdlib>
namespace phosphor
@@ -17,6 +19,17 @@
free(ptr);
}
};
+
+/** @brief Check if AVERAGE_power* is set to be true in env
+ *
+ * @param[in] sensor - Sensor details
+ *
+ * @return bool - true or false
+ */
+inline bool isAverageEnvSet(const SensorSet::key_type& sensor)
+{
+ return env::getEnv("AVERAGE", sensor.first, sensor.second) == "true";
+}
} // namespace utility
} // namespace phosphor
// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4