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);