metrics-ipmi-blobs: Convert to nanopb
Tested: Decoded a metric proto from a host and verified that the fields
all looked as expected and "normal" with decode successfully identifying
everything.
Change-Id: Iaa1b5500db6d1b93fae95d44a1045a858624cc51
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index 4691b58..c21db72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
!subprojects/nemora-postd/
!subprojects/bare-metal-host-monitor/
!subprojects/googletest.wrap
+!subprojects/nanopb.wrap
!subprojects/phosphor-dbus-interfaces.wrap
!subprojects/phosphor-logging.wrap
!subprojects/sdbusplus.wrap
diff --git a/meson.build b/meson.build
index 100398a..9a1f6e1 100644
--- a/meson.build
+++ b/meson.build
@@ -26,7 +26,9 @@
])
# Dummy file to make code coverage happy
-executable('hello', 'hello.c')
+if get_option('tests').allowed()
+ test('hello', executable('hello', 'hello.c'))
+endif
# Meson has no feature option to str...
tests_str = get_option('tests').allowed() ? 'enabled' : 'disabled'
diff --git a/subprojects/metrics-ipmi-blobs/meson.build b/subprojects/metrics-ipmi-blobs/meson.build
index 54d831c..15eead9 100644
--- a/subprojects/metrics-ipmi-blobs/meson.build
+++ b/subprojects/metrics-ipmi-blobs/meson.build
@@ -14,54 +14,88 @@
project(
'metrics-ipmi-blobs',
- 'cpp',
+ ['cpp', 'c'],
version: '0.1',
default_options: [
'warning_level=3',
'werror=true',
'cpp_std=c++23',
+ 'c_std=c18',
'tests=' + (meson.is_subproject() ? 'disabled' : 'auto'),
],
)
-headers = include_directories('.')
+nanopb = find_program('nanopb_generator.py', native: true, required: false)
+if not nanopb.found()
+ nanopb_opts = import('cmake').subproject_options()
+ nanopb_opts.add_cmake_defines({'BUILD_SHARED_LIBS': 'ON'})
+ nanopb_proj = import('cmake').subproject('nanopb', options: nanopb_opts)
+ nanopb = find_program(meson.global_source_root() + '/subprojects/nanopb/generator/nanopb_generator.py', native: true)
+ nanopb_dep = nanopb_proj.dependency('protobuf_nanopb')
+else
+ nanopb_dep = meson.get_compiler('cpp').find_library('protobuf-nanopb')
+endif
-deps = [
- dependency('phosphor-logging'),
- dependency('phosphor-ipmi-blobs'),
- dependency('protobuf'),
- dependency('sdbusplus'),
-]
+nanopb_kwargs = {
+ 'output': [
+ '@BASENAME@.pb.n.h',
+ '@BASENAME@.pb.n.c',
+ ],
+ 'command': [
+ nanopb,
+ '-q',
+ '-s', 'packed_struct:0',
+ '-H.n.h',
+ '-S.n.c',
+ '-I' + import('fs').relative_to(meson.current_source_dir(), meson.global_build_root()),
+ '-D' + import('fs').relative_to(meson.current_build_dir(), meson.global_build_root()),
+ '@INPUT@',
+ ],
+}
-proto = custom_target(
- 'metricblob_proto',
- command: [
- find_program('protoc', native: true),
- '--proto_path=@CURRENT_SOURCE_DIR@',
- '--cpp_out=@OUTDIR@',
- '@INPUT@'
- ],
- output: [
- 'metricblob.pb.cc',
- 'metricblob.pb.h',
- ],
- input: 'metricblob.proto')
-proto_h = proto[1]
+tgt = custom_target(
+ 'metricblob.pb.n.hc',
+ input: 'metricblob.proto',
+ kwargs: nanopb_kwargs)
+metrics_nanopb_hdr = tgt[0]
+metrics_nanopb_src = tgt[1]
+
+metrics_nanopb_pre = declare_dependency(
+ include_directories: include_directories('.'),
+ sources: metrics_nanopb_hdr,
+ dependencies: [
+ nanopb_dep,
+ ])
+
+metrics_nanopb_lib = static_library(
+ 'metrics_nanopb',
+ metrics_nanopb_src,
+ implicit_include_directories: false,
+ dependencies: metrics_nanopb_pre)
+
+metrics_nanopb_dep = declare_dependency(
+ dependencies: metrics_nanopb_pre,
+ link_with: metrics_nanopb_lib)
+
+pre = declare_dependency(
+ include_directories: include_directories('.'),
+ dependencies: [
+ metrics_nanopb_dep,
+ dependency('phosphor-logging'),
+ dependency('phosphor-ipmi-blobs'),
+ dependency('sdbusplus'),
+ ])
lib = static_library(
'metricsblob',
'util.cpp',
'handler.cpp',
'metric.cpp',
- proto,
- include_directories: headers,
implicit_include_directories: false,
- dependencies: deps)
+ dependencies: pre)
dep = declare_dependency(
- sources: proto_h,
- dependencies: deps,
- include_directories: headers,
+ dependencies: pre,
link_with: lib)
shared_module(
diff --git a/subprojects/metrics-ipmi-blobs/metric.cpp b/subprojects/metrics-ipmi-blobs/metric.cpp
index 954d988..4b21347 100644
--- a/subprojects/metrics-ipmi-blobs/metric.cpp
+++ b/subprojects/metrics-ipmi-blobs/metric.cpp
@@ -14,10 +14,11 @@
#include "metric.hpp"
-#include "metricblob.pb.h"
+#include "metricblob.pb.n.h"
#include "util.hpp"
+#include <pb_encode.h>
#include <sys/statvfs.h>
#include <phosphor-logging/log.hpp>
@@ -39,6 +40,45 @@
done(false), stringId(0), ticksPerSec(0)
{}
+template <typename T>
+static constexpr auto pbEncodeStr = [](pb_ostream_t* stream,
+ const pb_field_iter_t* field,
+ void* const* arg) noexcept {
+ static_assert(sizeof(*std::declval<T>().data()) == sizeof(pb_byte_t));
+ const auto& s = *reinterpret_cast<const T*>(*arg);
+ return pb_encode_tag_for_field(stream, field) &&
+ pb_encode_string(
+ stream, reinterpret_cast<const pb_byte_t*>(s.data()), s.size());
+};
+
+template <typename T>
+static pb_callback_t pbStrEncoder(const T& t) noexcept
+{
+ return {{.encode = pbEncodeStr<T>}, const_cast<T*>(&t)};
+}
+
+template <auto fields, typename T>
+static constexpr auto pbEncodeSubs = [](pb_ostream_t* stream,
+ const pb_field_iter_t* field,
+ void* const* arg) noexcept {
+ for (const auto& sub : *reinterpret_cast<const std::vector<T>*>(*arg))
+ {
+ if (!pb_encode_tag_for_field(stream, field) ||
+ !pb_encode_submessage(stream, fields, &sub))
+ {
+ return false;
+ }
+ }
+ return true;
+};
+
+template <auto fields, typename T>
+static pb_callback_t pbSubsEncoder(const std::vector<T>& t)
+{
+ return {{.encode = pbEncodeSubs<fields, T>},
+ const_cast<std::vector<T>*>(&t)};
+}
+
struct ProcStatEntry
{
std::string cmdline;
@@ -57,11 +97,17 @@
}
};
-bmcmetrics::metricproto::BmcProcStatMetric BmcHealthSnapshot::getProcStatList()
+static bmcmetrics_metricproto_BmcProcStatMetric getProcStatMetric(
+ BmcHealthSnapshot& obj, long ticksPerSec,
+ std::vector<bmcmetrics_metricproto_BmcProcStatMetric_BmcProcStat>& procs,
+ bool& use) noexcept
{
+ if (ticksPerSec == 0)
+ {
+ return {};
+ }
constexpr std::string_view procPath = "/proc/";
- bmcmetrics::metricproto::BmcProcStatMetric ret;
std::vector<ProcStatEntry> entries;
for (const auto& procEntry : std::filesystem::directory_iterator(procPath))
@@ -113,7 +159,7 @@
isOthers = true;
}
- ProcStatEntry& entry = entries[i];
+ const ProcStatEntry& entry = entries[i];
if (isOthers)
{
@@ -122,29 +168,36 @@
}
else
{
- bmcmetrics::metricproto::BmcProcStatMetric::BmcProcStat s;
std::string fullCmdline = entry.cmdline;
if (entry.tcomm.size() > 0)
{
- fullCmdline += " " + entry.tcomm;
+ fullCmdline += " ";
+ fullCmdline += entry.tcomm;
}
- s.set_sidx_cmdline(getStringID(fullCmdline));
- s.set_utime(entry.utime);
- s.set_stime(entry.stime);
- *(ret.add_stats()) = s;
+ procs.emplace_back(
+ bmcmetrics_metricproto_BmcProcStatMetric_BmcProcStat{
+ .sidx_cmdline = obj.getStringID(fullCmdline),
+ .utime = entry.utime,
+ .stime = entry.stime,
+ });
}
}
if (isOthers)
{
- bmcmetrics::metricproto::BmcProcStatMetric::BmcProcStat s;
- s.set_sidx_cmdline(getStringID(others.cmdline));
- s.set_utime(others.utime);
- s.set_stime(others.stime);
- *(ret.add_stats()) = s;
+ procs.emplace_back(bmcmetrics_metricproto_BmcProcStatMetric_BmcProcStat{
+ .sidx_cmdline = obj.getStringID(others.cmdline),
+ .utime = others.utime,
+ .stime = others.stime,
+
+ });
}
- return ret;
+ use = true;
+ return bmcmetrics_metricproto_BmcProcStatMetric{
+ .stats = pbSubsEncoder<
+ bmcmetrics_metricproto_BmcProcStatMetric_BmcProcStat_fields>(procs),
+ };
}
int getFdCount(int pid)
@@ -171,9 +224,15 @@
}
};
-bmcmetrics::metricproto::BmcFdStatMetric BmcHealthSnapshot::getFdStatList()
+static bmcmetrics_metricproto_BmcFdStatMetric getFdStatMetric(
+ BmcHealthSnapshot& obj, long ticksPerSec,
+ std::vector<bmcmetrics_metricproto_BmcFdStatMetric_BmcFdStat>& fds,
+ bool& use) noexcept
{
- bmcmetrics::metricproto::BmcFdStatMetric ret;
+ if (ticksPerSec == 0)
+ {
+ return {};
+ }
// Sort by fd count, no tie-breaking
std::vector<FdStatEntry> entries;
@@ -227,178 +286,199 @@
}
else
{
- bmcmetrics::metricproto::BmcFdStatMetric::BmcFdStat s;
std::string fullCmdline = entry.cmdline;
if (entry.tcomm.size() > 0)
{
- fullCmdline += " " + entry.tcomm;
+ fullCmdline += " ";
+ fullCmdline += entry.tcomm;
}
- s.set_sidx_cmdline(getStringID(fullCmdline));
- s.set_fd_count(entry.fdCount);
- *(ret.add_stats()) = s;
+ fds.emplace_back(bmcmetrics_metricproto_BmcFdStatMetric_BmcFdStat{
+ .sidx_cmdline = obj.getStringID(fullCmdline),
+ .fd_count = entry.fdCount,
+ });
}
}
if (isOthers)
{
- bmcmetrics::metricproto::BmcFdStatMetric::BmcFdStat s;
- s.set_sidx_cmdline(getStringID(others.cmdline));
- s.set_fd_count(others.fdCount);
- *(ret.add_stats()) = s;
+ fds.emplace_back(bmcmetrics_metricproto_BmcFdStatMetric_BmcFdStat{
+ .sidx_cmdline = obj.getStringID(others.cmdline),
+ .fd_count = others.fdCount,
+ });
}
+ use = true;
+ return bmcmetrics_metricproto_BmcFdStatMetric{
+ .stats = pbSubsEncoder<
+ bmcmetrics_metricproto_BmcFdStatMetric_BmcFdStat_fields>(fds),
+ };
+}
+
+static bmcmetrics_metricproto_BmcMemoryMetric getMemMetric() noexcept
+{
+ bmcmetrics_metricproto_BmcMemoryMetric ret = {};
+ auto data = readFileThenGrepIntoString("/proc/meminfo");
+ int value;
+ if (parseMeminfoValue(data, "MemAvailable:", value))
+ {
+ ret.mem_available = value;
+ }
+ if (parseMeminfoValue(data, "Slab:", value))
+ {
+ ret.slab = value;
+ }
+
+ if (parseMeminfoValue(data, "KernelStack:", value))
+ {
+ ret.kernel_stack = value;
+ }
return ret;
}
-void BmcHealthSnapshot::serializeSnapshotToArray(
- const bmcmetrics::metricproto::BmcMetricSnapshot& snapshot)
+static bmcmetrics_metricproto_BmcUptimeMetric
+ getUptimeMetric(bool& use) noexcept
{
- size_t size = snapshot.ByteSizeLong();
- if (size > 0)
- {
- pbDump.resize(size);
- if (!snapshot.SerializeToArray(pbDump.data(), size))
- {
- log<level::ERR>("Could not serialize protobuf to array");
- }
- }
-}
+ bmcmetrics_metricproto_BmcUptimeMetric ret = {};
-void BmcHealthSnapshot::doWork()
-{
- bmcmetrics::metricproto::BmcMetricSnapshot snapshot;
-
- // Memory info
- std::string meminfoBuffer = readFileThenGrepIntoString("/proc/meminfo");
-
- {
- bmcmetrics::metricproto::BmcMemoryMetric m;
-
- std::string_view sv(meminfoBuffer.data());
- // MemAvailable
- int value;
- bool ok = parseMeminfoValue(sv, "MemAvailable:", value);
- if (ok)
- {
- m.set_mem_available(value);
- }
-
- ok = parseMeminfoValue(sv, "Slab:", value);
- if (ok)
- {
- m.set_slab(value);
- }
-
- ok = parseMeminfoValue(sv, "KernelStack:", value);
- if (ok)
- {
- m.set_kernel_stack(value);
- }
-
- *(snapshot.mutable_memory_metric()) = m;
- }
-
- // Uptime
- 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");
+ auto data = readFileThenGrepIntoString("/proc/uptime");
+ double idleProcessTime = 0;
+ if (!parseProcUptime(data, uptime, idleProcessTime))
+ {
+ log<level::ERR>("Error parsing /proc/uptime");
+ return ret;
+ }
+ ret.uptime = uptime;
+ ret.idle_process_time = idleProcessTime;
}
- else if (!getBootTimesMonotonic(btm))
+
+ BootTimesMonotonic btm;
+ if (!getBootTimesMonotonic(btm))
{
log<level::ERR>("Could not get boot time");
+ return ret;
+ }
+ if (btm.firmwareTime == 0 && btm.powerOnSecCounterTime != 0)
+ {
+ ret.firmware_boot_time_sec =
+ static_cast<double>(btm.powerOnSecCounterTime) - uptime;
}
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;
+ ret.firmware_boot_time_sec =
+ static_cast<double>(btm.firmwareTime - btm.loaderTime) / 1e6;
+ }
+ ret.loader_boot_time_sec = static_cast<double>(btm.loaderTime) / 1e6;
+ if (btm.initrdTime != 0)
+ {
+ ret.kernel_boot_time_sec = static_cast<double>(btm.initrdTime) / 1e6;
+ ret.initrd_boot_time_sec =
+ static_cast<double>(btm.userspaceTime - btm.initrdTime) / 1e6;
+ ret.userspace_boot_time_sec =
+ static_cast<double>(btm.finishTime - btm.userspaceTime) / 1e6;
+ }
+ else
+ {
+ ret.kernel_boot_time_sec = static_cast<double>(btm.userspaceTime) / 1e6;
+ ret.initrd_boot_time_sec = 0;
+ ret.userspace_boot_time_sec =
+ static_cast<double>(btm.finishTime - btm.userspaceTime) / 1e6;
}
- // Storage space
+ use = true;
+ return ret;
+}
+
+static bmcmetrics_metricproto_BmcDiskSpaceMetric
+ getStorageMetric(bool& use) noexcept
+{
+ bmcmetrics_metricproto_BmcDiskSpaceMetric ret = {};
struct statvfs fiData;
- if ((statvfs("/", &fiData)) < 0)
+ if (statvfs("/", &fiData) < 0)
{
log<level::ERR>("Could not call statvfs");
}
else
{
- uint64_t kib = (fiData.f_bsize * fiData.f_bfree) / 1024;
- bmcmetrics::metricproto::BmcDiskSpaceMetric m2;
- m2.set_rwfs_kib_available(static_cast<int>(kib));
- *(snapshot.mutable_storage_space_metric()) = m2;
+ ret.rwfs_kib_available = (fiData.f_bsize * fiData.f_bfree) / 1024;
+ use = true;
}
+ return ret;
+}
+void BmcHealthSnapshot::doWork()
+{
// The next metrics require a sane ticks_per_sec value, typically 100 on
// the BMC. In the very rare circumstance when it's 0, exit early and return
// a partially complete snapshot (no process).
ticksPerSec = getTicksPerSec();
- // FD stat
- *(snapshot.mutable_fdstat_metric()) = getFdStatList();
-
- if (ticksPerSec == 0)
+ static constexpr auto stcb = [](pb_ostream_t* stream,
+ const pb_field_t* field,
+ void* const* arg) noexcept {
+ auto& self = *reinterpret_cast<BmcHealthSnapshot*>(*arg);
+ std::vector<std::string_view> strs(self.stringTable.size());
+ for (const auto& [str, i] : self.stringTable)
+ {
+ strs[i] = str;
+ }
+ for (auto& str : strs)
+ {
+ bmcmetrics_metricproto_BmcStringTable_StringEntry msg = {
+ .value = pbStrEncoder(str),
+ };
+ if (!pb_encode_tag_for_field(stream, field) ||
+ !pb_encode_submessage(
+ stream,
+ bmcmetrics_metricproto_BmcStringTable_StringEntry_fields,
+ &msg))
+ {
+ return false;
+ }
+ }
+ return true;
+ };
+ std::vector<bmcmetrics_metricproto_BmcProcStatMetric_BmcProcStat> procs;
+ std::vector<bmcmetrics_metricproto_BmcFdStatMetric_BmcFdStat> fds;
+ bmcmetrics_metricproto_BmcMetricSnapshot snapshot = {
+ .has_string_table = true,
+ .string_table =
+ {
+ .entries = {{.encode = stcb}, this},
+ },
+ .has_memory_metric = true,
+ .memory_metric = getMemMetric(),
+ .has_uptime_metric = false,
+ .uptime_metric = getUptimeMetric(snapshot.has_uptime_metric),
+ .has_storage_space_metric = false,
+ .storage_space_metric =
+ getStorageMetric(snapshot.has_storage_space_metric),
+ .has_procstat_metric = false,
+ .procstat_metric = getProcStatMetric(*this, ticksPerSec, procs,
+ snapshot.has_procstat_metric),
+ .has_fdstat_metric = false,
+ .fdstat_metric = getFdStatMetric(*this, ticksPerSec, fds,
+ snapshot.has_fdstat_metric),
+ };
+ pb_ostream_t nost = {};
+ if (!pb_encode(&nost, bmcmetrics_metricproto_BmcMetricSnapshot_fields,
+ &snapshot))
{
- log<level::ERR>("ticksPerSec is 0, skipping the process list metric");
- serializeSnapshotToArray(snapshot);
- done = true;
+ auto msg = std::format("Getting pb size: {}", PB_GET_ERROR(&nost));
+ log<level::ERR>(msg.c_str());
return;
}
-
- // Proc stat
- *(snapshot.mutable_procstat_metric()) = getProcStatList();
-
- // String table
- std::vector<std::string_view> strings(stringTable.size());
- for (const auto& [s, i] : stringTable)
+ pbDump.resize(nost.bytes_written);
+ auto ost = pb_ostream_from_buffer(
+ reinterpret_cast<pb_byte_t*>(pbDump.data()), pbDump.size());
+ if (!pb_encode(&ost, bmcmetrics_metricproto_BmcMetricSnapshot_fields,
+ &snapshot))
{
- strings[i] = s;
+ auto msg = std::format("Writing pb msg: {}", PB_GET_ERROR(&ost));
+ log<level::ERR>(msg.c_str());
+ return;
}
-
- bmcmetrics::metricproto::BmcStringTable st;
- for (size_t i = 0; i < strings.size(); ++i)
- {
- bmcmetrics::metricproto::BmcStringTable::StringEntry entry;
- entry.set_value(strings[i].data());
- *(st.add_entries()) = entry;
- }
- *(snapshot.mutable_string_table()) = st;
-
- // Save to buffer
- serializeSnapshotToArray(snapshot);
done = true;
}
diff --git a/subprojects/metrics-ipmi-blobs/metric.hpp b/subprojects/metrics-ipmi-blobs/metric.hpp
index 7af7914..0f94d5f 100644
--- a/subprojects/metrics-ipmi-blobs/metric.hpp
+++ b/subprojects/metrics-ipmi-blobs/metric.hpp
@@ -13,11 +13,6 @@
// limitations under the License.
#pragma once
-
-#include "metricblob.pb.h"
-
-#include <unistd.h>
-
#include <blobs-ipmid/blobs.hpp>
#include <atomic>
@@ -60,19 +55,12 @@
*/
uint32_t size();
- private:
/**
- * Serialize to the pb_dump_ array.
+ * Returns the ID of the provided string
*/
- void serializeSnapshotToArray(
- const bmcmetrics::metricproto::BmcMetricSnapshot& snapshot);
-
- // The two following functions access the snapshot's string table so they
- // have to be member functions.
- bmcmetrics::metricproto::BmcProcStatMetric getProcStatList();
- bmcmetrics::metricproto::BmcFdStatMetric getFdStatList();
-
int getStringID(const std::string_view s);
+
+ private:
std::atomic<bool> done;
std::vector<char> pbDump;
std::unordered_map<std::string, int> stringTable;
diff --git a/subprojects/nanopb.wrap b/subprojects/nanopb.wrap
new file mode 100644
index 0000000..cb50ef5
--- /dev/null
+++ b/subprojects/nanopb.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/nanopb/nanopb
+revision = HEAD