Redfish TelemetryService schema implementation

Now user is able to communicate with Telemetry service using Redfish.
Added TelemetryService, MetricReports, MetricReportCollection,
MetricReportDefinition and MetricReportDefinitionCollection nodes
with GET method support. Added TelemetryService URI to root service.
Implemented communication with backend - Telemetry:
https://github.com/openbmc/telemetry

Added schemes attributes that are supported by Telemetry service
design, ref.:
https://github.com/openbmc/docs/blob/master/designs/telemetry.md

Change introduces function that converts decimal value into
duration format that is described by ISO 8601 and Redfish
specification.

Tested:
 - Tested using romulus and s2600wf images on QEMU
 - Verified DBus method calls to Telemetry service from bmcweb
 - Verified bmcweb responses from new nodes in different cases:
   - Report collection is empty
   - Report collection is filled with artificial data
   - Telemetry service is disabled
 - Verified time_utils::toDurationString() output
 - Passed RedfishServiceValidator.py

Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com>
Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com>
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
Change-Id: Ie6b0b49f4ef5eeaef07d1209b6c349270c04d570
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 5d5eb7b..e94c0f3 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -25,6 +25,8 @@
 #include "../lib/managers.hpp"
 #include "../lib/memory.hpp"
 #include "../lib/message_registries.hpp"
+#include "../lib/metric_report.hpp"
+#include "../lib/metric_report_definition.hpp"
 #include "../lib/network_protocol.hpp"
 #include "../lib/pcie.hpp"
 #include "../lib/power.hpp"
@@ -36,6 +38,7 @@
 #include "../lib/storage.hpp"
 #include "../lib/systems.hpp"
 #include "../lib/task.hpp"
+#include "../lib/telemetry_service.hpp"
 #include "../lib/thermal.hpp"
 #include "../lib/update_service.hpp"
 #ifdef BMCWEB_ENABLE_VM_NBDPROXY
@@ -209,6 +212,13 @@
         nodes.emplace_back(std::make_unique<HypervisorInterface>(app));
         nodes.emplace_back(std::make_unique<HypervisorSystem>(app));
 
+        nodes.emplace_back(std::make_unique<TelemetryService>(app));
+        nodes.emplace_back(
+            std::make_unique<MetricReportDefinitionCollection>(app));
+        nodes.emplace_back(std::make_unique<MetricReportDefinition>(app));
+        nodes.emplace_back(std::make_unique<MetricReportCollection>(app));
+        nodes.emplace_back(std::make_unique<MetricReport>(app));
+
         for (const auto& node : nodes)
         {
             node->initPrivileges();
diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp
new file mode 100644
index 0000000..a3a8156
--- /dev/null
+++ b/redfish-core/include/utils/telemetry_utils.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+namespace redfish
+{
+
+namespace telemetry
+{
+
+constexpr const char* service = "xyz.openbmc_project.Telemetry";
+constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report";
+constexpr const char* metricReportDefinitionUri =
+    "/redfish/v1/TelemetryService/MetricReportDefinitions/";
+constexpr const char* metricReportUri =
+    "/redfish/v1/TelemetryService/MetricReports/";
+
+inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp,
+                                const std::string& uri)
+{
+    const std::array<const char*, 1> interfaces = {reportInterface};
+
+    crow::connections::systemBus->async_method_call(
+        [asyncResp, uri](const boost::system::error_code ec,
+                         const std::vector<std::string>& reports) {
+            if (ec == boost::system::errc::io_error)
+            {
+                asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
+                asyncResp->res.jsonValue["Members@odata.count"] = 0;
+                return;
+            }
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR << "Dbus method call failed: " << ec;
+                messages::internalError(asyncResp->res);
+                return;
+            }
+
+            nlohmann::json& members = asyncResp->res.jsonValue["Members"];
+            members = nlohmann::json::array();
+
+            for (const std::string& report : reports)
+            {
+                sdbusplus::message::object_path path(report);
+                std::string name = path.filename();
+                if (name.empty())
+                {
+                    BMCWEB_LOG_ERROR << "Received invalid path: " << report;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                members.push_back({{"@odata.id", uri + name}});
+            }
+
+            asyncResp->res.jsonValue["Members@odata.count"] = members.size();
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
+        "/xyz/openbmc_project/Telemetry/Reports/TelemetryService", 1,
+        interfaces);
+}
+
+inline std::string getDbusReportPath(const std::string& id)
+{
+    std::string path =
+        "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id;
+    dbus::utility::escapePathForDbus(path);
+    return path;
+}
+
+} // namespace telemetry
+} // namespace redfish
diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp
new file mode 100644
index 0000000..dd4ea75
--- /dev/null
+++ b/redfish-core/include/utils/time_utils.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <chrono>
+#include <string>
+
+namespace redfish
+{
+
+namespace time_utils
+{
+
+namespace details
+{
+
+inline void leftZeroPadding(std::string& str, const std::size_t padding)
+{
+    if (str.size() < padding)
+    {
+        str.insert(0, padding - str.size(), '0');
+    }
+}
+} // namespace details
+
+/**
+ * @brief Convert time value into duration format that is based on ISO 8601.
+ *        Example output: "P12DT1M5.5S"
+ *        Ref: Redfish Specification, Section 9.4.4. Duration values
+ */
+std::string toDurationString(std::chrono::milliseconds ms)
+{
+    if (ms < std::chrono::milliseconds::zero())
+    {
+        return "";
+    }
+
+    std::string fmt;
+    fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS"));
+
+    using Days = std::chrono::duration<long, std::ratio<24 * 60 * 60>>;
+    Days days = std::chrono::floor<Days>(ms);
+    ms -= days;
+
+    std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
+    ms -= hours;
+
+    std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
+    ms -= minutes;
+
+    std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
+    ms -= seconds;
+
+    fmt = "P";
+    if (days.count() > 0)
+    {
+        fmt += std::to_string(days.count()) + "D";
+    }
+    fmt += "T";
+    if (hours.count() > 0)
+    {
+        fmt += std::to_string(hours.count()) + "H";
+    }
+    if (minutes.count() > 0)
+    {
+        fmt += std::to_string(minutes.count()) + "M";
+    }
+    if (seconds.count() != 0 || ms.count() != 0)
+    {
+        fmt += std::to_string(seconds.count()) + ".";
+        std::string msStr = std::to_string(ms.count());
+        details::leftZeroPadding(msStr, 3);
+        fmt += msStr + "S";
+    }
+
+    return fmt;
+}
+
+} // namespace time_utils
+} // namespace redfish
diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp
new file mode 100644
index 0000000..9caf4a3
--- /dev/null
+++ b/redfish-core/lib/metric_report.hpp
@@ -0,0 +1,159 @@
+#pragma once
+
+#include "node.hpp"
+#include "utils/telemetry_utils.hpp"
+
+namespace redfish
+{
+
+namespace telemetry
+{
+
+using Readings =
+    std::vector<std::tuple<std::string, std::string, double, uint64_t>>;
+using TimestampReadings = std::tuple<uint64_t, Readings>;
+
+inline nlohmann::json toMetricValues(const Readings& readings)
+{
+    nlohmann::json metricValues = nlohmann::json::array_t();
+
+    for (auto& [id, metadata, sensorValue, timestamp] : readings)
+    {
+        metricValues.push_back({
+            {"MetricId", id},
+            {"MetricProperty", metadata},
+            {"MetricValue", std::to_string(sensorValue)},
+            {"Timestamp",
+             crow::utility::getDateTime(static_cast<time_t>(timestamp))},
+        });
+    }
+
+    return metricValues;
+}
+
+inline void fillReport(const std::shared_ptr<AsyncResp>& asyncResp,
+                       const std::string& id,
+                       const std::variant<TimestampReadings>& var)
+{
+    asyncResp->res.jsonValue["@odata.type"] =
+        "#MetricReport.v1_3_0.MetricReport";
+    asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id;
+    asyncResp->res.jsonValue["Id"] = id;
+    asyncResp->res.jsonValue["Name"] = id;
+    asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] =
+        telemetry::metricReportDefinitionUri + id;
+
+    const TimestampReadings* timestampReadings =
+        std::get_if<TimestampReadings>(&var);
+    if (!timestampReadings)
+    {
+        BMCWEB_LOG_ERROR << "Property type mismatch or property is missing";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    const auto& [timestamp, readings] = *timestampReadings;
+    asyncResp->res.jsonValue["Timestamp"] =
+        crow::utility::getDateTime(static_cast<time_t>(timestamp));
+    asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings);
+}
+} // namespace telemetry
+
+class MetricReportCollection : public Node
+{
+  public:
+    MetricReportCollection(App& app) :
+        Node(app, "/redfish/v1/TelemetryService/MetricReports/")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request&,
+               const std::vector<std::string>&) override
+    {
+        res.jsonValue["@odata.type"] =
+            "#MetricReportCollection.MetricReportCollection";
+        res.jsonValue["@odata.id"] =
+            "/redfish/v1/TelemetryService/MetricReports";
+        res.jsonValue["Name"] = "Metric Report Collection";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        telemetry::getReportCollection(asyncResp, telemetry::metricReportUri);
+    }
+};
+
+class MetricReport : public Node
+{
+  public:
+    MetricReport(App& app) :
+        Node(app, "/redfish/v1/TelemetryService/MetricReports/<str>/",
+             std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request&,
+               const std::vector<std::string>& params) override
+    {
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        if (params.size() != 1)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
+
+        const std::string& id = params[0];
+        const std::string reportPath = telemetry::getDbusReportPath(id);
+        crow::connections::systemBus->async_method_call(
+            [asyncResp, id, reportPath](const boost::system::error_code& ec) {
+                if (ec.value() == EBADR ||
+                    ec == boost::system::errc::host_unreachable)
+                {
+                    messages::resourceNotFound(asyncResp->res, "MetricReport",
+                                               id);
+                    return;
+                }
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                crow::connections::systemBus->async_method_call(
+                    [asyncResp, id](
+                        const boost::system::error_code ec,
+                        const std::variant<telemetry::TimestampReadings>& ret) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
+                            messages::internalError(asyncResp->res);
+                            return;
+                        }
+
+                        telemetry::fillReport(asyncResp, id, ret);
+                    },
+                    telemetry::service, reportPath,
+                    "org.freedesktop.DBus.Properties", "Get",
+                    telemetry::reportInterface, "Readings");
+            },
+            telemetry::service, reportPath, telemetry::reportInterface,
+            "Update");
+    }
+};
+} // namespace redfish
diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp
new file mode 100644
index 0000000..59025d9
--- /dev/null
+++ b/redfish-core/lib/metric_report_definition.hpp
@@ -0,0 +1,188 @@
+#pragma once
+
+#include "node.hpp"
+#include "utils/telemetry_utils.hpp"
+#include "utils/time_utils.hpp"
+
+#include <tuple>
+#include <variant>
+
+namespace redfish
+{
+
+namespace telemetry
+{
+
+using ReadingParameters =
+    std::vector<std::tuple<sdbusplus::message::object_path, std::string,
+                           std::string, std::string>>;
+
+inline void fillReportDefinition(
+    const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id,
+    const std::vector<
+        std::pair<std::string, std::variant<std::string, bool, uint64_t,
+                                            ReadingParameters>>>& ret)
+{
+    asyncResp->res.jsonValue["@odata.type"] =
+        "#MetricReportDefinition.v1_3_0.MetricReportDefinition";
+    asyncResp->res.jsonValue["@odata.id"] =
+        telemetry::metricReportDefinitionUri + id;
+    asyncResp->res.jsonValue["Id"] = id;
+    asyncResp->res.jsonValue["Name"] = id;
+    asyncResp->res.jsonValue["MetricReport"]["@odata.id"] =
+        telemetry::metricReportUri + id;
+    asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
+    asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite";
+
+    const bool* emitsReadingsUpdate = nullptr;
+    const bool* logToMetricReportsCollection = nullptr;
+    const ReadingParameters* readingParams = nullptr;
+    const std::string* reportingType = nullptr;
+    const uint64_t* interval = nullptr;
+    for (const auto& [key, var] : ret)
+    {
+        if (key == "EmitsReadingsUpdate")
+        {
+            emitsReadingsUpdate = std::get_if<bool>(&var);
+        }
+        else if (key == "LogToMetricReportsCollection")
+        {
+            logToMetricReportsCollection = std::get_if<bool>(&var);
+        }
+        else if (key == "ReadingParameters")
+        {
+            readingParams = std::get_if<ReadingParameters>(&var);
+        }
+        else if (key == "ReportingType")
+        {
+            reportingType = std::get_if<std::string>(&var);
+        }
+        else if (key == "Interval")
+        {
+            interval = std::get_if<uint64_t>(&var);
+        }
+    }
+    if (!emitsReadingsUpdate || !logToMetricReportsCollection ||
+        !readingParams || !reportingType || !interval)
+    {
+        BMCWEB_LOG_ERROR << "Property type mismatch or property is missing";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    std::vector<std::string> redfishReportActions;
+    redfishReportActions.reserve(2);
+    if (*emitsReadingsUpdate)
+    {
+        redfishReportActions.emplace_back("RedfishEvent");
+    }
+    if (*logToMetricReportsCollection)
+    {
+        redfishReportActions.emplace_back("LogToMetricReportsCollection");
+    }
+
+    nlohmann::json metrics = nlohmann::json::array();
+    for (auto& [sensorPath, operationType, id, metadata] : *readingParams)
+    {
+        metrics.push_back({
+            {"MetricId", id},
+            {"MetricProperties", {metadata}},
+        });
+    }
+    asyncResp->res.jsonValue["Metrics"] = metrics;
+    asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType;
+    asyncResp->res.jsonValue["ReportActions"] = redfishReportActions;
+    asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
+        time_utils::toDurationString(std::chrono::milliseconds(*interval));
+}
+} // namespace telemetry
+
+class MetricReportDefinitionCollection : public Node
+{
+  public:
+    MetricReportDefinitionCollection(App& app) :
+        Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request&,
+               const std::vector<std::string>&) override
+    {
+        res.jsonValue["@odata.type"] = "#MetricReportDefinitionCollection."
+                                       "MetricReportDefinitionCollection";
+        res.jsonValue["@odata.id"] =
+            "/redfish/v1/TelemetryService/MetricReportDefinitions";
+        res.jsonValue["Name"] = "Metric Definition Collection";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        telemetry::getReportCollection(asyncResp,
+                                       telemetry::metricReportDefinitionUri);
+    }
+};
+
+class MetricReportDefinition : public Node
+{
+  public:
+    MetricReportDefinition(App& app) :
+        Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/",
+             std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request&,
+               const std::vector<std::string>& params) override
+    {
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        if (params.size() != 1)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
+
+        const std::string& id = params[0];
+        crow::connections::systemBus->async_method_call(
+            [asyncResp,
+             id](const boost::system::error_code ec,
+                 const std::vector<std::pair<
+                     std::string, std::variant<std::string, bool, uint64_t,
+                                               telemetry::ReadingParameters>>>&
+                     ret) {
+                if (ec.value() == EBADR ||
+                    ec == boost::system::errc::host_unreachable)
+                {
+                    messages::resourceNotFound(asyncResp->res,
+                                               "MetricReportDefinition", id);
+                    return;
+                }
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                telemetry::fillReportDefinition(asyncResp, id, ret);
+            },
+            telemetry::service, telemetry::getDbusReportPath(id),
+            "org.freedesktop.DBus.Properties", "GetAll",
+            telemetry::reportInterface);
+    }
+};
+} // namespace redfish
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 629280c..3df5ec5 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -68,6 +68,8 @@
         res.jsonValue["Tasks"] = {{"@odata.id", "/redfish/v1/TaskService"}};
         res.jsonValue["EventService"] = {
             {"@odata.id", "/redfish/v1/EventService"}};
+        res.jsonValue["TelemetryService"] = {
+            {"@odata.id", "/redfish/v1/TelemetryService"}};
         res.end();
     }
 
diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp
new file mode 100644
index 0000000..a6acc34
--- /dev/null
+++ b/redfish-core/lib/telemetry_service.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "node.hpp"
+#include "utils/telemetry_utils.hpp"
+
+#include <variant>
+
+namespace redfish
+{
+
+class TelemetryService : public Node
+{
+  public:
+    TelemetryService(App& app) : Node(app, "/redfish/v1/TelemetryService/")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request&,
+               const std::vector<std::string>&) override
+    {
+        res.jsonValue["@odata.type"] =
+            "#TelemetryService.v1_2_1.TelemetryService";
+        res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService";
+        res.jsonValue["Id"] = "TelemetryService";
+        res.jsonValue["Name"] = "Telemetry Service";
+
+        res.jsonValue["LogService"]["@odata.id"] =
+            "/redfish/v1/Managers/bmc/LogServices/Journal";
+        res.jsonValue["MetricReportDefinitions"]["@odata.id"] =
+            "/redfish/v1/TelemetryService/MetricReportDefinitions";
+        res.jsonValue["MetricReports"]["@odata.id"] =
+            "/redfish/v1/TelemetryService/MetricReports";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        crow::connections::systemBus->async_method_call(
+            [asyncResp](
+                const boost::system::error_code ec,
+                const std::vector<std::pair<
+                    std::string, std::variant<uint32_t, uint64_t>>>& ret) {
+                if (ec == boost::system::errc::host_unreachable)
+                {
+                    asyncResp->res.jsonValue["Status"]["State"] = "Absent";
+                    return;
+                }
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
+
+                const size_t* maxReports = nullptr;
+                const uint64_t* minInterval = nullptr;
+                for (const auto& [key, var] : ret)
+                {
+                    if (key == "MaxReports")
+                    {
+                        maxReports = std::get_if<size_t>(&var);
+                    }
+                    else if (key == "MinInterval")
+                    {
+                        minInterval = std::get_if<uint64_t>(&var);
+                    }
+                }
+                if (!maxReports || !minInterval)
+                {
+                    BMCWEB_LOG_ERROR
+                        << "Property type mismatch or property is missing";
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                asyncResp->res.jsonValue["MaxReports"] = *maxReports;
+                asyncResp->res.jsonValue["MinCollectionInterval"] =
+                    time_utils::toDurationString(std::chrono::milliseconds(
+                        static_cast<time_t>(*minInterval)));
+            },
+            telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
+            "org.freedesktop.DBus.Properties", "GetAll",
+            "xyz.openbmc_project.Telemetry.ReportManager");
+    }
+};
+} // namespace redfish