PEL: Add BMC uptime to PELs in UserData section

A UserData section has been added to each PEL with additional debug
information, now there is a need to add the output of the uptime
command to UserData and display it, but for Hostboot doesn't care
about this property, so skip adding it here it.

Tested: unit test passed
"User Data 0": {
    "Section Version": "1",
    "Sub-section type": "1",
    "Created by": "0x2000",
    ...
    "Uptime": "3y 332d 21h 33m 9s",
    "Load": "1.47 0.94 0.61",
},

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I3d4c78bb1650da9a91804fc83de60597992ffc8a
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 4095cce..c0e8c45 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -4,6 +4,7 @@
 #include "dbus_watcher.hpp"
 
 #include <filesystem>
+#include <fstream>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/bus/match.hpp>
@@ -144,6 +145,91 @@
     }
 
     /**
+     * @brief Returns the time the system was running.
+     *
+     * @return std::optional<uint64_t> - The System uptime or std::nullopt
+     */
+    std::optional<uint64_t> getUptimeInSeconds() const
+    {
+        std::ifstream versionFile{"/proc/uptime"};
+        std::string line{};
+
+        std::getline(versionFile, line);
+        auto pos = line.find(" ");
+        if (pos == std::string::npos)
+        {
+            return std::nullopt;
+        }
+
+        uint64_t seconds = atol(line.substr(0, pos).c_str());
+        if (seconds == 0)
+        {
+            return std::nullopt;
+        }
+
+        return seconds;
+    }
+
+    /**
+     * @brief Returns the time the system was running.
+     *
+     * @param[in] seconds - The number of seconds the system has been running
+     *
+     * @return std::string - days/hours/minutes/seconds
+     */
+    std::string getBMCUptime(uint64_t seconds) const
+    {
+        time_t t(seconds);
+        tm* p = gmtime(&t);
+
+        std::string uptime = std::to_string(p->tm_year - 70) + "y " +
+                             std::to_string(p->tm_yday) + "d " +
+                             std::to_string(p->tm_hour) + "h " +
+                             std::to_string(p->tm_min) + "m " +
+                             std::to_string(p->tm_sec) + "s";
+
+        return uptime;
+    }
+
+    /**
+     * @brief Returns the system load average over the past 1 minute, 5 minutes
+     *        and 15 minutes.
+     *
+     * @return std::string - The system load average
+     */
+    std::string getBMCLoadAvg() const
+    {
+        std::string loadavg{};
+
+        std::ifstream loadavgFile{"/proc/loadavg"};
+        std::string line;
+        std::getline(loadavgFile, line);
+
+        size_t count = 3;
+        for (size_t i = 0; i < count; i++)
+        {
+            auto pos = line.find(" ");
+            if (pos == std::string::npos)
+            {
+                return {};
+            }
+
+            if (i != count - 1)
+            {
+                loadavg.append(line.substr(0, pos + 1));
+            }
+            else
+            {
+                loadavg.append(line.substr(0, pos));
+            }
+
+            line = line.substr(pos + 1);
+        }
+
+        return loadavg;
+    }
+
+    /**
      * @brief Returns the 'send event logs to host' setting.
      *
      * @return bool - If sending PELs to the host is enabled.
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 898dce5..a84b7f8 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -574,8 +574,8 @@
                     static_cast<uint16_t>(ComponentID::phosphorLogging);
 
                 // Update system data to ED section
-                auto ud =
-                    util::makeSysInfoUserDataSection(additionalData, dataIface);
+                auto ud = util::makeSysInfoUserDataSection(additionalData,
+                                                           dataIface, false);
                 extUserData->updateDataSection(subType, componentId,
                                                ud->data());
             }
@@ -709,9 +709,24 @@
     json["BootState"] = lastSegment('.', dataIface.getBootState());
 }
 
+void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface)
+{
+    auto seconds = dataIface.getUptimeInSeconds();
+    if (seconds)
+    {
+        json["BMCUptime"] = dataIface.getBMCUptime(*seconds);
+    }
+    else
+    {
+        json["BMCUptime"] = "";
+    }
+    json["BMCLoad"] = dataIface.getBMCLoadAvg();
+}
+
 std::unique_ptr<UserData>
     makeSysInfoUserDataSection(const AdditionalData& ad,
-                               const DataInterfaceBase& dataIface)
+                               const DataInterfaceBase& dataIface,
+                               bool addUptime)
 {
     nlohmann::json json;
 
@@ -720,6 +735,11 @@
     addIMKeyword(json, dataIface);
     addStatesToJSON(json, dataIface);
 
+    if (addUptime)
+    {
+        addBMCUptime(json, dataIface);
+    }
+
     return makeJSONUserDataSection(json);
 }
 
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index 5edcf10..631cbdb 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -429,12 +429,15 @@
  *
  * @param[in] ad - The AdditionalData contents
  * @param[in] dataIface - The data interface object
+ * @param[in] addUptime - Whether to add the uptime attribute the default is
+ *                        true
  *
  * @return std::unique_ptr<UserData> - The section
  */
 std::unique_ptr<UserData>
     makeSysInfoUserDataSection(const AdditionalData& ad,
-                               const DataInterfaceBase& dataIface);
+                               const DataInterfaceBase& dataIface,
+                               bool addUptime = true);
 
 /**
  * @brief Reads data from an opened file descriptor.
diff --git a/test/openpower-pels/data_interface_test.cpp b/test/openpower-pels/data_interface_test.cpp
index 28e455f..3041bc2 100644
--- a/test/openpower-pels/data_interface_test.cpp
+++ b/test/openpower-pels/data_interface_test.cpp
@@ -22,3 +22,15 @@
         EXPECT_TRUE(connector.empty());
     }
 }
+
+TEST(DataInterfaceTest, ExtractUptime)
+{
+    uint64_t seconds = 123456789;
+    std::string retUptime = "3y 332d 21h 33m 9s";
+
+    auto bus = sdbusplus::bus::new_default();
+    DataInterface dataIntf(bus);
+    std::string uptime = dataIntf.getBMCUptime(seconds);
+
+    EXPECT_EQ(uptime, retUptime);
+}
\ No newline at end of file