metrics-ipmi-blobs: Add bootup time to metrics

Change-Id: I1da66deca4a0cc2d75abe7c40c258c34593ea6a0
Signed-off-by: Michael Shen <gpgpgp@google.com>
diff --git a/subprojects/metrics-ipmi-blobs/meson.build b/subprojects/metrics-ipmi-blobs/meson.build
index 1fc0b0c..005d066 100644
--- a/subprojects/metrics-ipmi-blobs/meson.build
+++ b/subprojects/metrics-ipmi-blobs/meson.build
@@ -30,6 +30,7 @@
   dependency('phosphor-logging'),
   dependency('phosphor-ipmi-blobs'),
   dependency('protobuf'),
+  dependency('sdbusplus'),
 ]
 
 proto = custom_target(
diff --git a/subprojects/metrics-ipmi-blobs/metric.cpp b/subprojects/metrics-ipmi-blobs/metric.cpp
index 078b3e1..bc8ac94 100644
--- a/subprojects/metrics-ipmi-blobs/metric.cpp
+++ b/subprojects/metrics-ipmi-blobs/metric.cpp
@@ -269,7 +269,7 @@
     bmcmetrics::metricproto::BmcMetricSnapshot snapshot;
 
     // Memory info
-    std::string meminfoBuffer = readFileIntoString("/proc/meminfo");
+    std::string meminfoBuffer = readFileThenGrepIntoString("/proc/meminfo");
 
     {
         bmcmetrics::metricproto::BmcMemoryMetric m;
@@ -299,19 +299,54 @@
     }
 
     // Uptime
-    std::string uptimeBuffer = readFileIntoString("/proc/uptime");
-    double uptime = 0, idleProcessTime = 0;
-    if (parseProcUptime(uptimeBuffer, uptime, idleProcessTime))
+    std::string uptimeBuffer = readFileThenGrepIntoString("/proc/uptime");
+    double uptime = 0;
+    double idleProcessTime = 0;
+    BootTimesMonotonic btm;
+    if (!parseProcUptime(uptimeBuffer, uptime, idleProcessTime))
+    {
+        log<level::ERR>("Error parsing /proc/uptime");
+    }
+    else if (!getBootTimesMonotonic(btm))
+    {
+        log<level::ERR>("Could not get boot time");
+    }
+    else
     {
         bmcmetrics::metricproto::BmcUptimeMetric m1;
         m1.set_uptime(uptime);
         m1.set_idle_process_time(idleProcessTime);
+        if (btm.firmwareTime == 0 && btm.powerOnSecCounterTime != 0)
+        {
+            m1.set_firmware_boot_time_sec(
+                static_cast<double>(btm.powerOnSecCounterTime) - uptime);
+        }
+        else
+        {
+            m1.set_firmware_boot_time_sec(
+                static_cast<double>(btm.firmwareTime - btm.loaderTime) / 1e6);
+        }
+        m1.set_loader_boot_time_sec(static_cast<double>(btm.loaderTime) / 1e6);
+        // initrf presents
+        if (btm.initrdTime != 0)
+        {
+            m1.set_kernel_boot_time_sec(static_cast<double>(btm.initrdTime) /
+                                        1e6);
+            m1.set_initrd_boot_time_sec(
+                static_cast<double>(btm.userspaceTime - btm.initrdTime) / 1e6);
+            m1.set_userspace_boot_time_sec(
+                static_cast<double>(btm.finishTime - btm.userspaceTime) / 1e6);
+        }
+        else
+        {
+            m1.set_kernel_boot_time_sec(static_cast<double>(btm.userspaceTime) /
+                                        1e6);
+            m1.set_initrd_boot_time_sec(0);
+            m1.set_userspace_boot_time_sec(
+                static_cast<double>(btm.finishTime - btm.userspaceTime) / 1e6);
+        }
         *(snapshot.mutable_uptime_metric()) = m1;
     }
-    else
-    {
-        log<level::ERR>("Error parsing /proc/uptime");
-    }
 
     // Storage space
     struct statvfs fiData;
diff --git a/subprojects/metrics-ipmi-blobs/metricblob.proto b/subprojects/metrics-ipmi-blobs/metricblob.proto
index 848a54e..9842692 100644
--- a/subprojects/metrics-ipmi-blobs/metricblob.proto
+++ b/subprojects/metrics-ipmi-blobs/metricblob.proto
@@ -23,8 +23,13 @@
 }
 
 message BmcUptimeMetric {
-  float uptime = 1;             // Uptime (wall clock time)
-  float idle_process_time = 2;  // Idle process time across all cores
+  float uptime = 1;                   // Uptime (wall clock time)
+  float idle_process_time = 2;        // Idle process time across all cores
+  float firmware_boot_time_sec = 3;   // Time (seconds) elapsed in firmware process
+  float loader_boot_time_sec = 4;     // Time (seconds) elapsed in loader process
+  float kernel_boot_time_sec = 5;     // Time (seconds) elapsed in kernel process
+  float initrd_boot_time_sec = 6;     // Time (seconds) elapsed in initrd process
+  float userspace_boot_time_sec = 7;  // Time (seconds) elapsed in userspace process
 }
 
 message BmcDiskSpaceMetric {
diff --git a/subprojects/metrics-ipmi-blobs/test/util_test.cpp b/subprojects/metrics-ipmi-blobs/test/util_test.cpp
index 7b38d23..00c5ed8 100644
--- a/subprojects/metrics-ipmi-blobs/test/util_test.cpp
+++ b/subprojects/metrics-ipmi-blobs/test/util_test.cpp
@@ -35,22 +35,22 @@
     EXPECT_EQ(id, 10000);
 }
 
-TEST(ReadFileIntoString, goodFile)
+TEST(ReadFileThenGrepIntoString, goodFile)
 {
     const std::string& fileName = "./test_file";
     std::ofstream ofs(fileName, std::ios::trunc);
     std::string_view content = "This is\ntest\tcontentt\n\n\n\n.\n\n##$#$";
     ofs << content;
     ofs.close();
-    std::string readContent = metric_blob::readFileIntoString(fileName);
+    std::string readContent = metric_blob::readFileThenGrepIntoString(fileName);
     std::filesystem::remove(fileName);
     EXPECT_EQ(readContent, content);
 }
 
-TEST(ReadFileIntoString, inexistentFile)
+TEST(ReadFileThenGrepIntoString, inexistentFile)
 {
     const std::string& fileName = "./inexistent_file";
-    std::string readContent = metric_blob::readFileIntoString(fileName);
+    std::string readContent = metric_blob::readFileThenGrepIntoString(fileName);
     EXPECT_EQ(readContent, "");
 }
 
diff --git a/subprojects/metrics-ipmi-blobs/util.cpp b/subprojects/metrics-ipmi-blobs/util.cpp
index ad2e8c8..92cb80c 100644
--- a/subprojects/metrics-ipmi-blobs/util.cpp
+++ b/subprojects/metrics-ipmi-blobs/util.cpp
@@ -14,9 +14,14 @@
 
 #include "util.hpp"
 
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
 
 #include <cmath>
 #include <cstdlib>
@@ -24,6 +29,10 @@
 #include <sstream>
 #include <string>
 #include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <variant>
+#include <vector>
 
 namespace metric_blob
 {
@@ -45,7 +54,8 @@
     return sysconf(_SC_CLK_TCK);
 }
 
-std::string readFileIntoString(const std::string_view fileName)
+std::string readFileThenGrepIntoString(const std::string_view fileName,
+                                       const std::string_view grepStr)
 {
     std::stringstream ss;
     std::ifstream ifs(fileName.data());
@@ -53,7 +63,10 @@
     {
         std::string line;
         std::getline(ifs, line);
-        ss << line;
+        if (line.find(grepStr) != std::string::npos)
+        {
+            ss << line;
+        }
         if (ifs.good())
             ss << std::endl;
     }
@@ -101,7 +114,7 @@
     const std::string& cmdlinePath =
         "/proc/" + std::to_string(pid) + "/cmdline";
 
-    std::string cmdline = readFileIntoString(cmdlinePath);
+    std::string cmdline = readFileThenGrepIntoString(cmdlinePath);
     for (size_t i = 0; i < cmdline.size(); ++i)
     {
         cmdline[i] = controlCharsToSpace(cmdline[i]);
@@ -180,7 +193,7 @@
 TcommUtimeStime getTcommUtimeStime(const int pid, const long ticksPerSec)
 {
     const std::string& statPath = "/proc/" + std::to_string(pid) + "/stat";
-    return parseTcommUtimeStimeString(readFileIntoString(statPath),
+    return parseTcommUtimeStimeString(readFileThenGrepIntoString(statPath),
                                       ticksPerSec);
 }
 
@@ -220,4 +233,118 @@
     return false;
 }
 
+bool readMem(const uint32_t target, uint32_t& memResult)
+{
+    int fd = open("/dev/mem", O_RDONLY | O_SYNC);
+    if (fd < 0)
+    {
+        return false;
+    }
+
+    int pageSize = getpagesize();
+    uint32_t pageOffset = target & ~static_cast<uint32_t>(pageSize - 1);
+    uint32_t offsetInPage = target & static_cast<uint32_t>(pageSize - 1);
+
+    void* mapBase =
+        mmap(NULL, pageSize * 2, PROT_READ, MAP_SHARED, fd, pageOffset);
+    if (mapBase == MAP_FAILED)
+    {
+        close(fd);
+        return false;
+    }
+
+    char* virtAddr = reinterpret_cast<char*>(mapBase) + offsetInPage;
+    memResult = *(reinterpret_cast<uint32_t*>(virtAddr));
+    close(fd);
+    return true;
+}
+
+// clang-format off
+/*
+ *  power-on
+ *  counter(start)                 uptime(start)
+ *  firmware(Neg)    loader(Neg)   kernel(always 0)    initrd                 userspace              finish
+ *  |----------------|-------------|-------------------|----------------------|----------------------|
+ *  |----------------| <--- firmwareTime=firmware-loader
+ *                   |-------------| <--- loaderTime=loader
+ *  |------------------------------| <--- firmwareTime(Actually is firmware+loader)=counter-uptime \
+ *                                        (in this case we can treat this as firmware time \
+ *                                         since firmware consumes most of the time)
+ *                                 |-------------------| <--- kernelTime=initrd (if initrd present)
+ *                                 |------------------------------------------| <--- kernelTime=userspace (if no initrd)
+ *                                                     |----------------------| <--- initrdTime=userspace-initrd (if initrd present)
+ *                                                                            |----------------------| <--- userspaceTime=finish-userspace
+ */
+// clang-format on
+bool getBootTimesMonotonic(BootTimesMonotonic& btm)
+{
+    // Timestamp name and its offset in the struct.
+    std::vector<std::pair<std::string_view, size_t>> timeMap = {
+        {"FirmwareTimestampMonotonic",
+         offsetof(BootTimesMonotonic, firmwareTime)}, // negative value
+        {"LoaderTimestampMonotonic",
+         offsetof(BootTimesMonotonic, loaderTime)}, // negative value
+        {"InitRDTimestampMonotonic", offsetof(BootTimesMonotonic, initrdTime)},
+        {"UserspaceTimestampMonotonic",
+         offsetof(BootTimesMonotonic, userspaceTime)},
+        {"FinishTimestampMonotonic", offsetof(BootTimesMonotonic, finishTime)}};
+
+    auto b = sdbusplus::bus::new_default_system();
+    auto m = b.new_method_call("org.freedesktop.systemd1",
+                               "/org/freedesktop/systemd1",
+                               "org.freedesktop.DBus.Properties", "GetAll");
+    m.append("");
+    auto reply = b.call(m);
+    std::vector<std::pair<std::string, std::variant<uint64_t>>> timestamps;
+    reply.read(timestamps);
+
+    // Parse timestamps from dbus result.
+    auto btmPtr = reinterpret_cast<char*>(&btm);
+    unsigned int recordCnt = 0;
+    for (auto& t : timestamps)
+    {
+        for (auto& tm : timeMap)
+        {
+            if (tm.first.compare(t.first) == 0)
+            {
+                auto temp = std::get<uint64_t>(t.second);
+                memcpy(btmPtr + tm.second, reinterpret_cast<char*>(&temp),
+                       sizeof(temp));
+                recordCnt++;
+                break;
+            }
+        }
+        if (recordCnt == timeMap.size())
+        {
+            break;
+        }
+    }
+    if (recordCnt != timeMap.size())
+    {
+        log<level::ERR>("Didn't get desired timestamps");
+        return false;
+    }
+
+    std::string cpuinfo =
+        readFileThenGrepIntoString("/proc/cpuinfo", "Hardware");
+    // Nuvoton NPCM7XX chip has a counter which starts from power-on.
+    if (cpuinfo.find("NPCM7XX") != std::string::npos)
+    {
+        // Get elapsed seconds from SEC_CNT register
+        const uint32_t SEC_CNT_ADDR = 0xf0801068;
+        uint32_t memResult = 0;
+        if (readMem(SEC_CNT_ADDR, memResult))
+        {
+            btm.powerOnSecCounterTime = static_cast<uint64_t>(memResult);
+        }
+        else
+        {
+            log<level::ERR>("Read memory SEC_CNT_ADDR(0xf0801068) failed");
+            return false;
+        }
+    }
+
+    return true;
+}
+
 } // namespace metric_blob
\ No newline at end of file
diff --git a/subprojects/metrics-ipmi-blobs/util.hpp b/subprojects/metrics-ipmi-blobs/util.hpp
index d3c6f24..ec55962 100644
--- a/subprojects/metrics-ipmi-blobs/util.hpp
+++ b/subprojects/metrics-ipmi-blobs/util.hpp
@@ -27,9 +27,20 @@
     float stime;
 };
 
+struct BootTimesMonotonic
+{
+    uint64_t firmwareTime = 0;
+    uint64_t loaderTime = 0;
+    uint64_t initrdTime = 0;
+    uint64_t userspaceTime = 0;
+    uint64_t finishTime = 0;
+    uint64_t powerOnSecCounterTime = 0;
+};
+
 TcommUtimeStime parseTcommUtimeStimeString(std::string_view content,
                                            long ticksPerSec);
-std::string readFileIntoString(std::string_view fileName);
+std::string readFileThenGrepIntoString(std::string_view fileName,
+                                       std::string_view grepStr = "");
 bool isNumericPath(std::string_view path, int& value);
 TcommUtimeStime getTcommUtimeStime(int pid, long ticksPerSec);
 std::string getCmdLine(int pid);
@@ -37,6 +48,8 @@
                        int& value);
 bool parseProcUptime(const std::string_view content, double& uptime,
                      double& idleProcessTime);
+bool readMem(const uint32_t target, uint32_t& memResult);
+bool getBootTimesMonotonic(BootTimesMonotonic& btm);
 long getTicksPerSec();
 char controlCharsToSpace(char c);
 std::string trimStringRight(std::string_view s);