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