enable health_monitor new implementation
Add health_monitor interface and implementation. Enable the
health_monitor from existing healthMonitor flow.
This change is in relation to following design and D-Bus interface
update -
https://gerrit.openbmc.org/c/openbmc/docs/+/64917
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/64914
Tested:
```
busctl tree xyz.openbmc_project.HealthMon
`- /xyz
`- /xyz/openbmc_project
`- /xyz/openbmc_project/metric
`- /xyz/openbmc_project/metric/bmc
|- /xyz/openbmc_project/metric/bmc/cpu
| |- /xyz/openbmc_project/metric/bmc/cpu/kernel
| |- /xyz/openbmc_project/metric/bmc/cpu/total
| `- /xyz/openbmc_project/metric/bmc/cpu/user
|- /xyz/openbmc_project/metric/bmc/memory
| |- /xyz/openbmc_project/metric/bmc/memory/available
| |- /xyz/openbmc_project/metric/bmc/memory/buffered_and_cached
| |- /xyz/openbmc_project/metric/bmc/memory/free
| |- /xyz/openbmc_project/metric/bmc/memory/shared
| `- /xyz/openbmc_project/metric/bmc/memory/total
`- /xyz/openbmc_project/metric/bmc/storage
|- /xyz/openbmc_project/metric/bmc/storage/rw
`- /xyz/openbmc_project/metric/bmc/storage/tmp
curl -k https://localhost:8443/redfish/v1/Managers/bmc/ManagerDiagnosticData
--user "root:0penBmc"
{
"@odata.id": "/redfish/v1/Managers/bmc/ManagerDiagnosticData",
"@odata.type": "#ManagerDiagnosticData.v1_2_0.ManagerDiagnosticData",
"FreeStorageSpaceKiB": 3,
"Id": "ManagerDiagnosticData",
"MemoryStatistics": {
"AvailableBytes": 354029533,
"BuffersAndCacheBytes": 79077333,
"FreeBytes": 291730033,
"SharedBytes": 11876000,
"TotalBytes": 425516000
},
"Name": "Manager Diagnostic Data",
"ProcessorStatistics": {
"KernelPercent": 16.1534,
"UserPercent": 10.6638
},
"ServiceRootUptimeSeconds": 834.753
}
```
Change-Id: I1e9fac226b438f0556cb20321f1f2b6d51af7dbc
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/healthMonitor.cpp b/healthMonitor.cpp
deleted file mode 100644
index 2b4e0c2..0000000
--- a/healthMonitor.cpp
+++ /dev/null
@@ -1,824 +0,0 @@
-#include "config.h"
-
-#include "healthMonitor.hpp"
-
-#include <unistd.h>
-
-#include <boost/asio/steady_timer.hpp>
-#include <sdbusplus/asio/connection.hpp>
-#include <sdbusplus/asio/object_server.hpp>
-#include <sdbusplus/asio/sd_event.hpp>
-#include <sdbusplus/bus/match.hpp>
-#include <sdbusplus/server/manager.hpp>
-#include <sdeventplus/event.hpp>
-
-#include <fstream>
-#include <iostream>
-#include <memory>
-#include <numeric>
-#include <sstream>
-
-extern "C"
-{
-#include <sys/statvfs.h>
-#include <sys/sysinfo.h>
-}
-
-PHOSPHOR_LOG2_USING;
-
-static constexpr bool DEBUG = false;
-static constexpr uint8_t defaultHighThreshold = 100;
-
-// Limit sensor recreation interval to 10s
-bool needUpdate;
-static constexpr int TIMER_INTERVAL = 10;
-std::shared_ptr<boost::asio::steady_timer> sensorRecreateTimer;
-std::shared_ptr<phosphor::health::HealthMon> healthMon;
-
-namespace phosphor
-{
-namespace health
-{
-
-// Example values for iface:
-// BMC_CONFIGURATION
-// BMC_INVENTORY_ITEM
-std::vector<std::string> findPathsWithType(sdbusplus::bus_t& bus,
- const std::string& iface)
-{
- PHOSPHOR_LOG2_USING;
- std::vector<std::string> ret;
-
- // Find all BMCs (DBus objects implementing the
- // Inventory.Item.Bmc interface that may be created by
- // configuring the Inventory Manager)
- sdbusplus::message_t msg = bus.new_method_call(
- "xyz.openbmc_project.ObjectMapper",
- "/xyz/openbmc_project/object_mapper",
- "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
-
- // "/": No limit for paths for all the paths that may be touched
- // in this daemon
-
- // 0: Limit the depth to 0 to match both objects created by
- // EntityManager and by InventoryManager
-
- // {iface}: The endpoint of the Association Definition must have
- // the Inventory.Item.Bmc interface
- msg.append("/", 0, std::vector<std::string>{iface});
-
- try
- {
- bus.call(msg, 0).read(ret);
-
- if (!ret.empty())
- {
- debug("{IFACE} found", "IFACE", iface);
- }
- else
- {
- debug("{IFACE} not found", "IFACE", iface);
- }
- }
- catch (std::exception& e)
- {
- error("Exception occurred while calling {PATH}: {ERROR}", "PATH",
- InventoryPath, "ERROR", e);
- }
- return ret;
-}
-
-enum CPUStatesTime
-{
- USER_IDX = 0,
- NICE_IDX,
- SYSTEM_IDX,
- IDLE_IDX,
- IOWAIT_IDX,
- IRQ_IDX,
- SOFTIRQ_IDX,
- STEAL_IDX,
- GUEST_USER_IDX,
- GUEST_NICE_IDX,
- NUM_CPU_STATES_TIME
-};
-
-// # cat /proc/stat|grep 'cpu '
-// cpu 5750423 14827 1572788 9259794 1317 0 28879 0 0 0
-static_assert(NUM_CPU_STATES_TIME == 10);
-
-enum CPUUtilizationType
-{
- USER = 0,
- KERNEL,
- TOTAL
-};
-
-double readCPUUtilization(enum CPUUtilizationType type)
-{
- auto proc_stat = "/proc/stat";
- std::ifstream fileStat(proc_stat);
- if (!fileStat.is_open())
- {
- error("cpu file not available: {PATH}", "PATH", proc_stat);
- return -1;
- }
-
- std::string firstLine, labelName;
- std::size_t timeData[NUM_CPU_STATES_TIME];
-
- std::getline(fileStat, firstLine);
- std::stringstream ss(firstLine);
- ss >> labelName;
-
- if (DEBUG)
- debug("CPU stats first Line is: {LINE}", "LINE", firstLine);
-
- if (labelName.compare("cpu"))
- {
- error("CPU data not available");
- return -1;
- }
-
- int i;
- for (i = 0; i < NUM_CPU_STATES_TIME; i++)
- {
- if (!(ss >> timeData[i]))
- break;
- }
-
- if (i != NUM_CPU_STATES_TIME)
- {
- error("CPU data not correct");
- return -1;
- }
-
- static std::unordered_map<enum CPUUtilizationType, uint64_t> preActiveTime,
- preTotalTime;
-
- // These are actually Jiffies. On the BMC, 1 jiffy usually corresponds to
- // 0.01 second.
- uint64_t activeTime = 0, activeTimeDiff = 0, totalTime = 0,
- totalTimeDiff = 0;
- double activePercValue = 0;
-
- if (type == TOTAL)
- {
- activeTime = timeData[USER_IDX] + timeData[NICE_IDX] +
- timeData[SYSTEM_IDX] + timeData[IRQ_IDX] +
- timeData[SOFTIRQ_IDX] + timeData[STEAL_IDX] +
- timeData[GUEST_USER_IDX] + timeData[GUEST_NICE_IDX];
- }
- else if (type == KERNEL)
- {
- activeTime = timeData[SYSTEM_IDX];
- }
- else if (type == USER)
- {
- activeTime = timeData[USER_IDX];
- }
-
- totalTime = std::accumulate(std::begin(timeData), std::end(timeData), 0);
-
- activeTimeDiff = activeTime - preActiveTime[type];
- totalTimeDiff = totalTime - preTotalTime[type];
-
- /* Store current idle and active time for next calculation */
- preActiveTime[type] = activeTime;
- preTotalTime[type] = totalTime;
-
- activePercValue = (100.0 * activeTimeDiff) / totalTimeDiff;
-
- if (DEBUG)
- debug("CPU Utilization is {VALUE}", "VALUE", activePercValue);
-
- return activePercValue;
-}
-
-auto readCPUUtilizationTotal([[maybe_unused]] const std::string& path)
-{
- return readCPUUtilization(CPUUtilizationType::TOTAL);
-}
-
-auto readCPUUtilizationKernel([[maybe_unused]] const std::string& path)
-{
- return readCPUUtilization(CPUUtilizationType::KERNEL);
-}
-
-auto readCPUUtilizationUser([[maybe_unused]] const std::string& path)
-{
- return readCPUUtilization(CPUUtilizationType::USER);
-}
-
-double readMemoryUtilization([[maybe_unused]] const std::string& path)
-{
- /* Unused var: path */
- std::ignore = path;
- std::ifstream meminfo("/proc/meminfo");
- std::string line;
- double memTotal = -1;
- double memAvail = -1;
-
- while (std::getline(meminfo, line))
- {
- std::string name;
- double value;
- std::istringstream iss(line);
-
- if (!(iss >> name >> value))
- {
- continue;
- }
-
- if (name.starts_with("MemTotal"))
- {
- memTotal = value;
- }
- else if (name.starts_with("MemAvailable"))
- {
- memAvail = value;
- }
- }
-
- if (memTotal <= 0 || memAvail <= 0)
- {
- return std::numeric_limits<double>::quiet_NaN();
- }
-
- if (DEBUG)
- {
- debug("MemTotal: {VALUE}", "VALUE", memTotal);
- debug("MemAvailable: {VALUE}", "VALUE", memAvail);
- }
-
- return (memTotal - memAvail) / memTotal * 100;
-}
-
-double readStorageUtilization([[maybe_unused]] const std::string& path)
-{
- struct statvfs buffer
- {};
- int ret = statvfs(path.c_str(), &buffer);
- double total = 0;
- double available = 0;
- double used = 0;
- double usedPercentage = 0;
-
- if (ret != 0)
- {
- auto e = errno;
- error("Error from statvfs: {ERROR}; {PATH}", "ERROR", strerror(e),
- "PATH", path);
- return 0;
- }
-
- total = buffer.f_blocks * (buffer.f_frsize / 1024);
- available = buffer.f_bfree * (buffer.f_frsize / 1024);
- used = total - available;
- usedPercentage = (used / total) * 100;
-
- if (DEBUG)
- {
- debug("Storage Total: {VALUE}", "VALUE", total);
- debug("Available: {VALUE}", "VALUE", available);
- debug("Used: {VALUE}", "VALUE", used);
- debug("Storage Utilization: {VALUE}", "VALUE", usedPercentage);
- }
-
- return usedPercentage;
-}
-
-double readInodeUtilization([[maybe_unused]] const std::string& path)
-{
- struct statvfs buffer
- {};
- int ret = statvfs(path.c_str(), &buffer);
- double totalInodes = 0;
- double availableInodes = 0;
- double used = 0;
- double usedPercentage = 0;
-
- if (ret != 0)
- {
- auto e = errno;
- error("Error from statvfs on {PATH}: {ERROR}", "PATH", path, "ERROR",
- strerror(e));
- return 0;
- }
-
- totalInodes = buffer.f_files;
- availableInodes = buffer.f_ffree;
- used = totalInodes - availableInodes;
- usedPercentage = (used / totalInodes) * 100;
-
- if (DEBUG)
- {
- debug("Total Inodes: {VALUE}", "VALUE", totalInodes);
- debug("Available Inodes: {VALUE}", "VALUE", availableInodes);
- debug("Used: {VALUE}", "VALUE", used);
- debug("Inodes utilization is: {VALUE}", "VALUE", usedPercentage);
- }
-
- return usedPercentage;
-}
-
-constexpr auto storage = "Storage";
-constexpr auto inode = "Inode";
-
-/** Map of read function for each health sensors supported
- *
- * The following health sensors are read in the ManagerDiagnosticData
- * Redfish resource:
- * - CPU_Kernel populates ProcessorStatistics.KernelPercent
- * - CPU_User populates ProcessorStatistics.UserPercent
- */
-const std::map<std::string, std::function<double(const std::string& path)>>
- readSensors = {{"CPU", readCPUUtilizationTotal},
- {"CPU_Kernel", readCPUUtilizationKernel},
- {"CPU_User", readCPUUtilizationUser},
- {"Memory", readMemoryUtilization},
- {storage, readStorageUtilization},
- {inode, readInodeUtilization}};
-
-void HealthSensor::setSensorThreshold(double criticalHigh, double warningHigh)
-{
- CriticalInterface::criticalHigh(criticalHigh);
- CriticalInterface::criticalLow(std::numeric_limits<double>::quiet_NaN());
-
- WarningInterface::warningHigh(warningHigh);
- WarningInterface::warningLow(std::numeric_limits<double>::quiet_NaN());
-}
-
-void HealthSensor::setSensorValueToDbus(const double value)
-{
- ValueIface::value(value);
-}
-
-void HealthSensor::initHealthSensor(
- const std::vector<std::string>& bmcInventoryPaths)
-{
- info("{SENSOR} Health Sensor initialized", "SENSOR", sensorConfig.name);
-
- /* Look for sensor read functions and Read Sensor values */
- auto it = readSensors.find(sensorConfig.name);
-
- if (sensorConfig.name.rfind(storage, 0) == 0)
- {
- it = readSensors.find(storage);
- }
- else if (sensorConfig.name.rfind(inode, 0) == 0)
- {
- it = readSensors.find(inode);
- }
- else if (it == readSensors.end())
- {
- error("Sensor read function not available");
- return;
- }
-
- double value = it->second(sensorConfig.path);
-
- if (value < 0)
- {
- error("Reading Sensor Utilization failed: {SENSOR}", "SENSOR",
- sensorConfig.name);
- return;
- }
-
- /* Initialize unit value (Percent) for utilization sensor */
- ValueIface::unit(ValueIface::Unit::Percent);
-
- ValueIface::maxValue(100);
- ValueIface::minValue(0);
- ValueIface::value(std::numeric_limits<double>::quiet_NaN());
-
- // Associate the sensor to chassis
- // This connects the DBus object to a Chassis.
-
- std::vector<AssociationTuple> associationTuples;
- for (const auto& chassisId : bmcInventoryPaths)
- {
- // This utilization sensor "is monitoring" the BMC with path chassisId.
- // The chassisId is "monitored_by" this utilization sensor.
- associationTuples.push_back({"monitors", "monitored_by", chassisId});
- }
- AssociationDefinitionInterface::associations(associationTuples);
-
- /* Start the timer for reading sensor data at regular interval */
- readTimer.restart(std::chrono::milliseconds(sensorConfig.freq * 1000));
-}
-
-void HealthSensor::checkSensorThreshold(const double value)
-{
- if (std::isfinite(sensorConfig.criticalHigh) &&
- (value > sensorConfig.criticalHigh))
- {
- if (!CriticalInterface::criticalAlarmHigh())
- {
- CriticalInterface::criticalAlarmHigh(true);
- if (sensorConfig.criticalLog)
- {
- error(
- "ASSERT: sensor {SENSOR} is above the upper threshold critical high",
- "SENSOR", sensorConfig.name);
- startUnit(sensorConfig.criticalTgt);
- }
- }
- return;
- }
-
- if (CriticalInterface::criticalAlarmHigh())
- {
- CriticalInterface::criticalAlarmHigh(false);
- if (sensorConfig.criticalLog)
- info(
- "DEASSERT: sensor {SENSOR} is under the upper threshold critical high",
- "SENSOR", sensorConfig.name);
- }
-
- if (std::isfinite(sensorConfig.warningHigh) &&
- (value > sensorConfig.warningHigh))
- {
- if (!WarningInterface::warningAlarmHigh())
- {
- WarningInterface::warningAlarmHigh(true);
- if (sensorConfig.warningLog)
- {
- error(
- "ASSERT: sensor {SENSOR} is above the upper threshold warning high",
- "SENSOR", sensorConfig.name);
- startUnit(sensorConfig.warningTgt);
- }
- }
- return;
- }
-
- if (WarningInterface::warningAlarmHigh())
- {
- WarningInterface::warningAlarmHigh(false);
- if (sensorConfig.warningLog)
- info(
- "DEASSERT: sensor {SENSOR} is under the upper threshold warning high",
- "SENSOR", sensorConfig.name);
- }
-}
-
-void HealthSensor::readHealthSensor()
-{
- /* Read current sensor value */
- double value;
-
- if (sensorConfig.name.rfind(storage, 0) == 0)
- {
- value = readSensors.find(storage)->second(sensorConfig.path);
- }
- else if (sensorConfig.name.rfind(inode, 0) == 0)
- {
- value = readSensors.find(inode)->second(sensorConfig.path);
- }
- else
- {
- value = readSensors.find(sensorConfig.name)->second(sensorConfig.path);
- }
-
- if (value < 0)
- {
- error("Reading Sensor Utilization failed: {SENSOR}", "SENSOR",
- sensorConfig.name);
- return;
- }
-
- /* Remove first item from the queue */
- if (valQueue.size() >= sensorConfig.windowSize)
- {
- valQueue.pop_front();
- }
- /* Add new item at the back */
- valQueue.push_back(value);
- /* Wait until the queue is filled with enough reference*/
- if (valQueue.size() < sensorConfig.windowSize)
- {
- return;
- }
-
- /* Calculate average values for the given window size */
- double avgValue = 0;
- avgValue = accumulate(valQueue.begin(), valQueue.end(), avgValue);
- avgValue = avgValue / sensorConfig.windowSize;
-
- /* Set this new value to dbus */
- setSensorValueToDbus(avgValue);
-
- /* Check the sensor threshold and log required message */
- checkSensorThreshold(avgValue);
-}
-
-void HealthSensor::startUnit(const std::string& sysdUnit)
-{
- if (sysdUnit.empty())
- {
- return;
- }
-
- sdbusplus::message_t msg = bus.new_method_call(
- "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager", "StartUnit");
- msg.append(sysdUnit, "replace");
- bus.call_noreply(msg);
-}
-
-void HealthMon::recreateSensors()
-{
- PHOSPHOR_LOG2_USING;
- healthSensors.clear();
-
- // Find BMC inventory paths and create health sensors
- std::vector<std::string> bmcInventoryPaths =
- findPathsWithType(bus, BMC_INVENTORY_ITEM);
- createHealthSensors(bmcInventoryPaths);
-}
-
-void printConfig(HealthConfig& cfg)
-{
- debug("Name: {VALUE}", "VALUE", cfg.name);
- debug("Freq: {VALUE}", "VALUE", cfg.freq);
- debug("Window Size: {VALUE}", "VALUE", cfg.windowSize);
- debug("Critical value: {VALUE}", "VALUE", cfg.criticalHigh);
- debug("warning value: {VALUE}", "VALUE", cfg.warningHigh);
- debug("Critical log: {VALUE}", "VALUE", cfg.criticalLog);
- debug("Warning log: {VALUE}", "VALUE", cfg.warningLog);
- debug("Critical Target: {VALUE}", "VALUE", cfg.criticalTgt);
- debug("Warning Target: {VALUE}", "VALUE", cfg.warningTgt);
- debug("Path: {VALUE}", "VALUE", cfg.path);
-}
-
-/* Create dbus utilization sensor object for each configured sensors */
-void HealthMon::createHealthSensors(
- const std::vector<std::string>& bmcInventoryPaths)
-{
- for (auto& cfg : sensorConfigs)
- {
- std::string objPath = std::string(HEALTH_SENSOR_PATH) + cfg.name;
- auto healthSensor = std::make_shared<HealthSensor>(
- bus, objPath.c_str(), cfg, bmcInventoryPaths);
- healthSensors.emplace(cfg.name, healthSensor);
-
- info("{SENSOR} Health Sensor created", "SENSOR", cfg.name);
-
- /* Set configured values of crtical and warning high to dbus */
- healthSensor->setSensorThreshold(cfg.criticalHigh, cfg.warningHigh);
- }
-}
-
-/** @brief Parsing Health config JSON file */
-Json HealthMon::parseConfigFile(std::string configFile)
-{
- std::ifstream jsonFile(configFile);
- if (!jsonFile.is_open())
- {
- error("config JSON file not found: {PATH}", "PATH", configFile);
- }
-
- auto data = Json::parse(jsonFile, nullptr, false);
- if (data.is_discarded())
- {
- error("config readings JSON parser failure: {PATH}", "PATH",
- configFile);
- }
-
- return data;
-}
-
-void HealthMon::getConfigData(Json& data, HealthConfig& cfg)
-{
- static const Json empty{};
-
- /* Default frerquency of sensor polling is 1 second */
- cfg.freq = data.value("Frequency", 1);
-
- /* Default window size sensor queue is 1 */
- cfg.windowSize = data.value("Window_size", 1);
-
- auto threshold = data.value("Threshold", empty);
- if (!threshold.empty())
- {
- auto criticalData = threshold.value("Critical", empty);
- if (!criticalData.empty())
- {
- cfg.criticalHigh = criticalData.value("Value",
- defaultHighThreshold);
- cfg.criticalLog = criticalData.value("Log", true);
- cfg.criticalTgt = criticalData.value("Target", "");
- }
- auto warningData = threshold.value("Warning", empty);
- if (!warningData.empty())
- {
- cfg.warningHigh = warningData.value("Value", defaultHighThreshold);
- cfg.warningLog = warningData.value("Log", false);
- cfg.warningTgt = warningData.value("Target", "");
- }
- }
- cfg.path = data.value("Path", "");
-}
-
-std::vector<HealthConfig> HealthMon::getHealthConfig()
-{
- std::vector<HealthConfig> cfgs;
- auto data = parseConfigFile(HEALTH_CONFIG_FILE);
-
- // print values
- if (DEBUG)
- debug("Config json data: {VALUE}", "VALUE", data.dump(2));
-
- /* Get data items from config json data*/
- for (auto& j : data.items())
- {
- auto key = j.key();
- /* key need match default value in map readSensors or match the key
- * start with "Storage" or "Inode" */
- bool isStorageOrInode = (key.rfind(storage, 0) == 0 ||
- key.rfind(inode, 0) == 0);
- if (readSensors.find(key) != readSensors.end() || isStorageOrInode)
- {
- HealthConfig cfg = HealthConfig();
- cfg.name = j.key();
- getConfigData(j.value(), cfg);
- if (isStorageOrInode)
- {
- struct statvfs buffer
- {};
- int ret = statvfs(cfg.path.c_str(), &buffer);
- if (ret != 0)
- {
- auto e = errno;
- error("Error from statvfs: {ERROR}; ({NAME}, {PATH})",
- "ERROR", strerror(e), "NAME", cfg.name, "PATH",
- cfg.path);
- continue;
- }
- }
- cfgs.push_back(cfg);
- if (DEBUG)
- printConfig(cfg);
- }
- else
- {
- error("{SENSOR} Health Sensor not supported", "SENSOR", key);
- }
- }
- return cfgs;
-}
-
-// Two caveats here.
-// 1. The BMC Inventory will only show up by the nearest ObjectMapper polling
-// interval.
-// 2. InterfacesAdded events will are not emitted like they are with E-M.
-void HealthMon::createBmcInventoryIfNotCreated()
-{
- if (bmcInventory == nullptr)
- {
- info("createBmcInventory");
- bmcInventory = std::make_shared<phosphor::health::BmcInventory>(
- bus, "/xyz/openbmc_project/inventory/bmc");
- }
-}
-
-bool HealthMon::bmcInventoryCreated()
-{
- return bmcInventory != nullptr;
-}
-
-} // namespace health
-} // namespace phosphor
-
-void sensorRecreateTimerCallback(
- std::shared_ptr<boost::asio::steady_timer> timer, sdbusplus::bus_t& bus)
-{
- timer->expires_after(std::chrono::seconds(TIMER_INTERVAL));
- timer->async_wait([timer, &bus](const boost::system::error_code& ec) {
- if (ec == boost::asio::error::operation_aborted)
- {
- info("sensorRecreateTimer aborted");
- return;
- }
-
- // When Entity-manager is already running
- if (!needUpdate)
- {
- if ((!healthMon->bmcInventoryCreated()) &&
- (!phosphor::health::findPathsWithType(bus, BMC_CONFIGURATION)
- .empty()))
- {
- healthMon->createBmcInventoryIfNotCreated();
- needUpdate = true;
- }
- }
- else
- {
- // If this daemon maintains its own DBus object, we must make sure
- // the object is registered to ObjectMapper
- if (phosphor::health::findPathsWithType(bus, BMC_INVENTORY_ITEM)
- .empty())
- {
- info(
- "BMC inventory item not registered to Object Mapper yet, waiting for next iteration");
- }
- else
- {
- info(
- "BMC inventory item registered to Object Mapper, creating sensors now");
- healthMon->recreateSensors();
- needUpdate = false;
- }
- }
- sensorRecreateTimerCallback(timer, bus);
- });
-}
-
-/**
- * @brief Main
- */
-int main()
-{
- // The io_context is needed for the timer
- boost::asio::io_context io;
-
- // DBus connection
- auto conn = std::make_shared<sdbusplus::asio::connection>(io);
-
- conn->request_name(HEALTH_BUS_NAME);
-
- // Get a default event loop
- auto event = sdeventplus::Event::get_default();
-
- // Create an health monitor object
- healthMon = std::make_shared<phosphor::health::HealthMon>(*conn);
-
- // Add object manager through object_server
- sdbusplus::asio::object_server objectServer(conn);
-
- sdbusplus::asio::sd_event_wrapper sdEvents(io);
-
- sensorRecreateTimer = std::make_shared<boost::asio::steady_timer>(io);
-
- // If the SystemInventory does not exist: wait for the InterfaceAdded signal
- auto interfacesAddedSignalHandler =
- std::make_unique<sdbusplus::bus::match_t>(
- static_cast<sdbusplus::bus_t&>(*conn),
- sdbusplus::bus::match::rules::interfacesAdded(),
- [conn](sdbusplus::message_t& msg) {
- using Association = std::tuple<std::string, std::string, std::string>;
- using InterfacesAdded = std::vector<std::pair<
- std::string,
- std::vector<std::pair<std::string,
- std::variant<std::vector<Association>>>>>>;
-
- sdbusplus::message::object_path o;
- InterfacesAdded interfacesAdded;
-
- try
- {
- msg.read(o);
- msg.read(interfacesAdded);
- }
- catch (const std::exception& e)
- {
- error(
- "Exception occurred while processing interfacesAdded: {ERROR}",
- "ERROR", e);
- return;
- }
-
- // Ignore any signal coming from health-monitor itself.
- if (msg.get_sender() == conn->get_unique_name())
- {
- return;
- }
-
- // Check if the BMC Inventory is in the interfaces created.
- bool hasBmcConfiguration = false;
- for (const auto& x : interfacesAdded)
- {
- if (x.first == BMC_CONFIGURATION)
- {
- hasBmcConfiguration = true;
- }
- }
-
- if (hasBmcConfiguration)
- {
- info(
- "BMC configuration detected, will create a corresponding Inventory item");
- healthMon->createBmcInventoryIfNotCreated();
- needUpdate = true;
- }
- });
-
- // Start the timer
- boost::asio::post(io, [conn]() {
- sensorRecreateTimerCallback(sensorRecreateTimer, *conn);
- });
- io.run();
-
- return 0;
-}
diff --git a/healthMonitor.hpp b/healthMonitor.hpp
deleted file mode 100644
index c6e77dd..0000000
--- a/healthMonitor.hpp
+++ /dev/null
@@ -1,202 +0,0 @@
-#include <nlohmann/json.hpp>
-#include <phosphor-logging/lg2.hpp>
-#include <sdbusplus/bus.hpp>
-#include <sdbusplus/message.hpp>
-#include <sdbusplus/server/manager.hpp>
-#include <sdeventplus/clock.hpp>
-#include <sdeventplus/event.hpp>
-#include <sdeventplus/utility/timer.hpp>
-#include <xyz/openbmc_project/Association/Definitions/server.hpp>
-#include <xyz/openbmc_project/Inventory/Item/Bmc/server.hpp>
-#include <xyz/openbmc_project/Sensor/Threshold/Critical/server.hpp>
-#include <xyz/openbmc_project/Sensor/Threshold/Warning/server.hpp>
-#include <xyz/openbmc_project/Sensor/Value/server.hpp>
-
-constexpr char BMC_INVENTORY_ITEM[] = "xyz.openbmc_project.Inventory.Item.Bmc";
-constexpr char BMC_CONFIGURATION[] = "xyz.openbmc_project.Configuration.Bmc";
-
-#include <deque>
-#include <limits>
-#include <map>
-#include <string>
-
-namespace phosphor
-{
-namespace health
-{
-
-const char* InventoryPath = "/xyz/openbmc_project/inventory";
-
-// Used for identifying the BMC inventory creation signal
-const char* BMCActivationPath = "/xyz/openbmc_project/inventory/bmc/activation";
-
-bool FindSystemInventoryInObjectMapper(sdbusplus::bus_t& bus)
-{
- sdbusplus::message_t msg =
- bus.new_method_call("xyz.openbmc_project.ObjectMapper",
- "/xyz/openbmc_project/object_mapper",
- "xyz.openbmc_project.ObjectMapper", "GetObject");
- msg.append(InventoryPath);
- msg.append(std::vector<std::string>{});
-
- try
- {
- sdbusplus::message_t reply = bus.call(msg, 0);
- return true;
- }
- catch (const std::exception& e)
- {}
- return false;
-}
-
-using Json = nlohmann::json;
-using ValueIface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
-
-using CriticalInterface =
- sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Critical;
-
-using WarningInterface =
- sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Warning;
-
-using AssociationDefinitionInterface =
- sdbusplus::xyz::openbmc_project::Association::server::Definitions;
-
-using healthIfaces =
- sdbusplus::server::object_t<ValueIface, CriticalInterface, WarningInterface,
- AssociationDefinitionInterface>;
-
-using BmcInterface = sdbusplus::server::object_t<
- sdbusplus::xyz::openbmc_project::Inventory::Item::server::Bmc>;
-
-using AssociationTuple = std::tuple<std::string, std::string, std::string>;
-
-struct HealthConfig
-{
- std::string name;
- uint16_t freq;
- uint16_t windowSize;
- double criticalHigh = std::numeric_limits<double>::quiet_NaN();
- double warningHigh = std::numeric_limits<double>::quiet_NaN();
- bool criticalLog;
- bool warningLog;
- std::string criticalTgt;
- std::string warningTgt;
- std::string path;
-};
-
-class HealthSensor : public healthIfaces
-{
- public:
- HealthSensor() = delete;
- HealthSensor(const HealthSensor&) = delete;
- HealthSensor& operator=(const HealthSensor&) = delete;
- HealthSensor(HealthSensor&&) = delete;
- HealthSensor& operator=(HealthSensor&&) = delete;
- virtual ~HealthSensor() = default;
-
- /** @brief Constructs HealthSensor
- *
- * @param[in] bus - Handle to system dbus
- * @param[in] objPath - The Dbus path of health sensor
- */
- HealthSensor(sdbusplus::bus_t& bus, const char* objPath,
- HealthConfig& sensorConfig,
- const std::vector<std::string>& bmcIds) :
- healthIfaces(bus, objPath),
- bus(bus), sensorConfig(sensorConfig),
- timerEvent(sdeventplus::Event::get_default()),
- readTimer(timerEvent, std::bind(&HealthSensor::readHealthSensor, this))
- {
- initHealthSensor(bmcIds);
- }
-
- /** @brief list of sensor data values */
- std::deque<double> valQueue;
- /** @brief Initialize sensor, set default value and association */
- void initHealthSensor(const std::vector<std::string>& bmcIds);
- /** @brief Set sensor value utilization to health sensor D-bus */
- void setSensorValueToDbus(const double value);
- /** @brief Set Sensor Threshold to D-bus at beginning */
- void setSensorThreshold(double criticalHigh, double warningHigh);
- /** @brief Check Sensor threshold and update alarm and log */
- void checkSensorThreshold(const double value);
-
- private:
- /** @brief sdbusplus bus client connection. */
- sdbusplus::bus_t& bus;
- /** @brief Sensor config from config file */
- HealthConfig& sensorConfig;
- /** @brief the Event Loop structure */
- sdeventplus::Event timerEvent;
- /** @brief Sensor Read Timer */
- sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> readTimer;
- /** @brief Read sensor at regular intrval */
- void readHealthSensor();
- /** @brief Start configured threshold systemd unit */
- void startUnit(const std::string& sysdUnit);
-};
-
-class BmcInventory : public BmcInterface
-{
- public:
- BmcInventory() = delete;
- BmcInventory(sdbusplus::bus_t& bus, const char* objPath) :
- BmcInterface(bus, objPath)
- {}
-};
-
-class HealthMon
-{
- public:
- HealthMon() = delete;
- HealthMon(const HealthMon&) = delete;
- HealthMon& operator=(const HealthMon&) = delete;
- HealthMon(HealthMon&&) = delete;
- HealthMon& operator=(HealthMon&&) = delete;
- virtual ~HealthMon() = default;
-
- /** @brief Recreates sensor objects and their association if possible
- */
- void recreateSensors();
-
- /** @brief Constructs HealthMon
- *
- * @param[in] bus - Handle to system dbus
- */
- HealthMon(sdbusplus::bus_t& bus) :
- bus(bus), sensorsObjectManager(bus, "/xyz/openbmc_project/sensors")
- {
- // Read JSON file
- sensorConfigs = getHealthConfig();
- recreateSensors();
- }
-
- /** @brief Parse Health config JSON file */
- Json parseConfigFile(std::string configFile);
-
- /** @brief Read config for each health sensor component */
- void getConfigData(Json& data, HealthConfig& cfg);
-
- /** @brief Map of the object HealthSensor */
- std::unordered_map<std::string, std::shared_ptr<HealthSensor>>
- healthSensors;
-
- /** @brief Create sensors for health monitoring */
- void createHealthSensors(const std::vector<std::string>& bmcIds);
-
- /** @brief Create the BMC Inventory object */
- void createBmcInventoryIfNotCreated();
-
- std::shared_ptr<BmcInventory> bmcInventory;
-
- bool bmcInventoryCreated();
-
- private:
- sdbusplus::bus_t& bus;
- std::vector<HealthConfig> sensorConfigs;
- std::vector<HealthConfig> getHealthConfig();
- sdbusplus::server::manager_t sensorsObjectManager;
-};
-
-} // namespace health
-} // namespace phosphor
diff --git a/health_monitor.cpp b/health_monitor.cpp
new file mode 100644
index 0000000..b839ae7
--- /dev/null
+++ b/health_monitor.cpp
@@ -0,0 +1,66 @@
+#include "health_monitor.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::health::monitor
+{
+
+using namespace phosphor::health::utils;
+
+void HealthMonitor::create()
+{
+ info("Creating Health Monitor with config size {SIZE}", "SIZE",
+ configs.size());
+ constexpr auto BMCInventoryItem = "xyz.openbmc_project.Inventory.Item.Bmc";
+ auto bmcPaths = findPaths(bus, BMCInventoryItem);
+
+ for (auto& [type, collectionConfig] : configs)
+ {
+ info("Creating Health Metric Collection for {TYPE}", "TYPE",
+ std::to_underlying(type));
+ collections[type] =
+ std::make_unique<CollectionIntf::HealthMetricCollection>(
+ bus, type, collectionConfig, bmcPaths);
+ }
+}
+
+void HealthMonitor::run()
+{
+ info("Running Health Monitor");
+ for (auto& [type, collection] : collections)
+ {
+ debug("Reading Health Metric Collection for {TYPE}", "TYPE",
+ std::to_underlying(type));
+ collection->read();
+ }
+}
+
+} // namespace phosphor::health::monitor
+
+using namespace phosphor::health::monitor;
+
+int main()
+{
+ constexpr auto path = MetricIntf::ValueIntf::Value::namespace_path::value;
+ sdbusplus::async::context ctx;
+ sdbusplus::server::manager_t manager{ctx, path};
+ constexpr auto healthMonitorServiceName = "xyz.openbmc_project.HealthMon";
+
+ info("Creating health monitor");
+ HealthMonitor healthMonitor{ctx.get_bus()};
+ ctx.request_name(healthMonitorServiceName);
+
+ ctx.spawn([&]() -> sdbusplus::async::task<> {
+ while (!ctx.stop_requested())
+ {
+ healthMonitor.run();
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(5));
+ }
+ }());
+
+ ctx.run();
+ return 0;
+}
diff --git a/health_monitor.hpp b/health_monitor.hpp
new file mode 100644
index 0000000..31d16ba
--- /dev/null
+++ b/health_monitor.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "health_metric_collection.hpp"
+
+#include <unordered_map>
+
+namespace phosphor::health::monitor
+{
+namespace ConfigIntf = phosphor::health::metric::config;
+namespace MetricIntf = phosphor::health::metric;
+namespace CollectionIntf = phosphor::health::metric::collection;
+class HealthMonitor
+{
+ public:
+ HealthMonitor() = delete;
+
+ HealthMonitor(sdbusplus::bus_t& bus) :
+ bus(bus), configs(ConfigIntf::getHealthMetricConfigs())
+ {
+ create();
+ }
+
+ /** @brief Run the health monitor */
+ void run();
+
+ private:
+ using map_t = std::unordered_map<
+ MetricIntf::Type,
+ std::unique_ptr<CollectionIntf::HealthMetricCollection>>;
+ /** @brief Create a new health monitor object */
+ void create();
+ /** @brief D-Bus bus connection */
+ sdbusplus::bus_t& bus;
+ /** @brief Health metric configs */
+ ConfigIntf::HealthMetric::map_t configs;
+ map_t collections;
+};
+
+} // namespace phosphor::health::monitor
diff --git a/health_utils.cpp b/health_utils.cpp
index 163fc8c..a60db8b 100644
--- a/health_utils.cpp
+++ b/health_utils.cpp
@@ -20,4 +20,29 @@
bus.call_noreply(msg);
}
+auto findPaths(sdbusplus::bus_t& bus, const std::string& iface) -> paths_t
+{
+ sdbusplus::message_t msg = bus.new_method_call(
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
+ const char* inventoryPath = "/xyz/openbmc_project/inventory";
+
+ msg.append("/", 0, std::vector<std::string>{iface});
+
+ try
+ {
+ auto paths = bus.call(msg, int32_t(0)).unpack<paths_t>();
+ debug("Found {COUNT} paths for {IFACE}", "COUNT", paths.size(), "IFACE",
+ iface);
+ return paths;
+ }
+ catch (std::exception& e)
+ {
+ error("Exception occurred for GetSubTreePaths for {PATH}: {ERROR}",
+ "PATH", inventoryPath, "ERROR", e);
+ }
+ return {};
+}
+
} // namespace phosphor::health::utils
diff --git a/health_utils.hpp b/health_utils.hpp
index b89d8e5..ded0159 100644
--- a/health_utils.hpp
+++ b/health_utils.hpp
@@ -12,5 +12,7 @@
/** @brief Start a systemd unit */
void startUnit(sdbusplus::bus_t& bus, const std::string& sysdUnit);
+/** @brief Find D-Bus paths for given interface */
+auto findPaths(sdbusplus::bus_t& bus, const std::string& iface) -> paths_t;
} // namespace phosphor::health::utils
diff --git a/meson.build b/meson.build
index 09ef147..f56e3f5 100644
--- a/meson.build
+++ b/meson.build
@@ -24,11 +24,11 @@
executable(
'health-monitor',
[
- 'healthMonitor.cpp',
'health_metric_config.cpp',
'health_metric.cpp',
'health_utils.cpp',
'health_metric_collection.cpp',
+ 'health_monitor.cpp',
],
dependencies: [
base_deps