Basic initial implementation

This is a base implementation which does following
1. create a daemon,
2. parse config file and create config object
3. create utilization sensor for given config options.

It currently support CPU and memory config only but it can be extended
for other feature as well. Reading and updating sensor data will be in
follow up patch.

Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
Change-Id: If52cfd3ff879d0d121836bf37e17e2cc63fa2a38
diff --git a/bmc_health_config.json b/bmc_health_config.json
new file mode 100644
index 0000000..398e547
--- /dev/null
+++ b/bmc_health_config.json
@@ -0,0 +1,34 @@
+{
+  "CPU" : {
+    "Frequency" : 1,
+    "Window_size": 120,
+    "Threshold":
+    {
+        "Critical":
+        {
+            "Value": 90.0,
+            "Log": true,
+            "Target": "reboot.target"
+        },
+        "Warning":
+        {
+          "Value": 80.0,
+          "Log": false,
+          "Target": "systemd unit file"
+        }
+    }
+  },
+  "Memory" : {
+    "Frequency" : 1,
+    "Window_size": 120,
+    "Threshold":
+    {
+        "Critical":
+        {
+            "Value": 85.0,
+            "Log": true,
+            "Target": "reboot.target"
+        }
+    }
+  }
+}
diff --git a/healthMonitor.cpp b/healthMonitor.cpp
new file mode 100644
index 0000000..9796927
--- /dev/null
+++ b/healthMonitor.cpp
@@ -0,0 +1,168 @@
+#include "config.h"
+
+#include "healthMonitor.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <fstream>
+#include <iostream>
+
+static constexpr bool DEBUG = false;
+
+namespace phosphor
+{
+namespace health
+{
+
+using namespace phosphor::logging;
+
+void HealthSensor::setSensorThreshold(uint8_t criticalHigh, uint8_t warningHigh)
+{
+    CriticalInterface::criticalHigh(criticalHigh);
+    WarningInterface::warningHigh(warningHigh);
+}
+
+void HealthSensor::setSensorValueToDbus(const uint8_t value)
+{
+    ValueIface::value(value);
+}
+
+/* Create dbus utilization sensor object for each configured sensors */
+void HealthMon::createHealthSensors()
+{
+    for (auto& cfg : sensorConfigs)
+    {
+        std::string objPath = std::string(HEALTH_SENSOR_PATH) + cfg.name;
+        auto healthSensor =
+            std::make_shared<HealthSensor>(bus, objPath.c_str());
+        healthSensors.emplace(cfg.name, healthSensor);
+
+        std::string logMsg = cfg.name + " Health Sensor created";
+        log<level::INFO>(logMsg.c_str(), entry("NAME = %s", cfg.name.c_str()));
+
+        /* 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())
+    {
+        log<level::ERR>("config JSON file not found",
+                        entry("FILENAME = %s", configFile.c_str()));
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("config readings JSON parser failure",
+                        entry("FILENAME = %s", configFile.c_str()));
+    }
+
+    return data;
+}
+
+void printConfig(HealthMon::HealthConfig& cfg)
+{
+    std::cout << "Name: " << cfg.name << "\n";
+    std::cout << "Freq: " << cfg.freq << "\n";
+    std::cout << "Window Size: " << cfg.windowSize << "\n";
+    std::cout << "Critical value: " << cfg.criticalHigh << "\n";
+    std::cout << "warning value: " << cfg.warningHigh << "\n";
+    std::cout << "Critical log: " << cfg.criticalLog << "\n";
+    std::cout << "Warning log: " << cfg.warningLog << "\n";
+    std::cout << "Critical Target: " << cfg.criticalTgt << "\n";
+    std::cout << "Warning Target: " << cfg.warningTgt << "\n\n";
+}
+
+void HealthMon::getConfigData(Json& data, HealthConfig& cfg)
+{
+
+    static const Json empty{};
+
+    cfg.freq = data.value("Frequency", 0);
+    cfg.windowSize = data.value("Window_size", 0);
+    auto threshold = data.value("Threshold", empty);
+    if (!threshold.empty())
+    {
+        auto criticalData = threshold.value("Critical", empty);
+        if (!criticalData.empty())
+        {
+            cfg.criticalHigh = criticalData.value("Value", 0);
+            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", 0);
+            cfg.warningLog = warningData.value("Log", true);
+            cfg.warningTgt = warningData.value("Target", "");
+        }
+    }
+}
+
+std::vector<HealthMon::HealthConfig> HealthMon::getHealthConfig()
+{
+
+    std::vector<HealthConfig> cfgs;
+    HealthConfig cfg;
+    auto data = parseConfigFile(HEALTH_CONFIG_FILE);
+
+    // print values
+    if (DEBUG)
+        std::cout << "Config json data:\n" << data << "\n\n";
+
+    /* Get CPU config data */
+    for (auto& j : data.items())
+    {
+        auto key = j.key();
+        if (std::find(cfgNames.begin(), cfgNames.end(), key) != cfgNames.end())
+        {
+            HealthConfig cfg = HealthConfig();
+            cfg.name = j.key();
+            getConfigData(j.value(), cfg);
+            cfgs.push_back(cfg);
+            if (DEBUG)
+                printConfig(cfg);
+        }
+        else
+        {
+            std::string logMsg = key + " Health Sensor not supported";
+            log<level::ERR>(logMsg.c_str(), entry("NAME = %s", key.c_str()));
+        }
+    }
+    return cfgs;
+}
+
+} // namespace health
+} // namespace phosphor
+
+/**
+ * @brief Main
+ */
+int main()
+{
+
+    // Get a default event loop
+    auto event = sdeventplus::Event::get_default();
+
+    // Get a handle to system dbus
+    auto bus = sdbusplus::bus::new_default();
+
+    // Create an health monitor object
+    phosphor::health::HealthMon healthMon(bus);
+
+    // Request service bus name
+    bus.request_name(HEALTH_BUS_NAME);
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+    event.loop();
+
+    return 0;
+}
diff --git a/healthMonitor.hpp b/healthMonitor.hpp
new file mode 100644
index 0000000..eff0519
--- /dev/null
+++ b/healthMonitor.hpp
@@ -0,0 +1,114 @@
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.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>
+
+#include <map>
+#include <string>
+
+namespace phosphor
+{
+namespace health
+{
+
+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 healthIfaces =
+    sdbusplus::server::object::object<ValueIface, CriticalInterface,
+                                      WarningInterface>;
+
+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::bus& bus, const char* objPath) :
+        healthIfaces(bus, objPath), bus(bus)
+    {}
+
+    /** @brief Set sensor value utilization to health sensor D-bus  */
+    void setSensorValueToDbus(const uint8_t value);
+    /** @brief Set Sensor Threshold to D-bus at beginning */
+    void setSensorThreshold(uint8_t criticalHigh, uint8_t warningHigh);
+
+  private:
+    sdbusplus::bus::bus& bus;
+};
+
+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 Constructs HealthMon
+     *
+     * @param[in] bus     - Handle to system dbus
+     */
+    HealthMon(sdbusplus::bus::bus& bus) : bus(bus)
+    {
+        // read json file
+        sensorConfigs = getHealthConfig();
+        createHealthSensors();
+    }
+
+    struct HealthConfig
+    {
+        std::string name;
+        uint16_t freq;
+        uint16_t windowSize;
+        uint8_t criticalHigh;
+        uint8_t warningHigh;
+        bool criticalLog;
+        bool warningLog;
+        std::string criticalTgt;
+        std::string warningTgt;
+    };
+
+    /** @brief List of health sensors supported */
+    std::vector<std::string> cfgNames = {"CPU", "Memory"};
+
+    /** @brief Parsing Health config JSON file  */
+    Json parseConfigFile(std::string configFile);
+
+    /** @brief reading 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();
+
+  private:
+    sdbusplus::bus::bus& bus;
+    std::vector<HealthConfig> sensorConfigs;
+    std::vector<HealthConfig> getHealthConfig();
+};
+
+} // namespace health
+} // namespace phosphor
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..cec97e7
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,43 @@
+project(
+    'phosphor-health-monitor',
+    'cpp',
+    version: '1.0',
+    default_options: [
+        'cpp_std=c++17',
+    ],
+)
+
+executable(
+    'health-monitor',
+    [
+        'healthMonitor.cpp',
+    ],
+    dependencies: [
+        dependency('phosphor-logging'),
+        dependency('sdbusplus'),
+        dependency('phosphor-dbus-interfaces'),
+        dependency('sdeventplus'),
+    ],
+    install: true,
+    install_dir: get_option('bindir')
+)
+
+install_data(sources : 'bmc_health_config.json', install_dir : '/etc/healthMon')
+
+conf_data = configuration_data()
+conf_data.set('HEALTH_CONFIG_FILE', '"/etc/healthMon/bmc_health_config.json"')
+conf_data.set('HEALTH_BUS_NAME', '"xyz.openbmc_project.HealthMon"')
+conf_data.set('HEALTH_SENSOR_PATH', '"/xyz/openbmc_project/sensors/utilization/"')
+
+configure_file(output : 'config.h',
+               configuration : conf_data)
+
+systemd = dependency('systemd')
+conf_data = configuration_data()
+conf_data.set('bindir', get_option('prefix') / get_option('bindir'))
+configure_file(
+  input: 'phosphor-health-monitor.service.in',
+  output: 'phosphor-health-monitor.service',
+  configuration: conf_data,
+  install: true,
+  install_dir: systemd.get_pkgconfig_variable('systemdsystemunitdir'))
diff --git a/phosphor-health-monitor.service.in b/phosphor-health-monitor.service.in
new file mode 100644
index 0000000..47b1532
--- /dev/null
+++ b/phosphor-health-monitor.service.in
@@ -0,0 +1,11 @@
+[Unit]
+Description=BMC health monitoring
+
+[Service]
+ExecStart=@bindir@/health-monitor
+Type=dbus
+BusName=xyz.openbmc_project.HealthMon
+SyslogIdentifier=phosphor-health-monitor
+
+[Install]
+WantedBy=multi-user.target