Add ConnectX-9 PortMetrics and TransceiverTelemetry support to D-Bus network ports

Expose PortMetrics and TransceiverTelemetry for ConnectX-9 network ports in gBMCWeb:
- Add @odata.id link to Metrics in buildNetworkPortDBus.
- Implement buildNetworkPortMetricDBus to query PortECC, PortPacketCounters, and PCIeECC from NSM D-Bus interfaces.
- Route handleNetworkPortMetricGetDBus to buildNetworkPortMetricDBus.

Tested:
Verified PortMetrics and TransceiverTelemetry endpoints return expected metric values on ConnectX-9 network ports.
Google-Bug-Id:540603557
PiperOrigin-RevId: 970680678
Change-Id: I953cf2ff8eb51e43254fa7f9efb0321d517b0dc9
diff --git a/redfish-core/lib/network_adapter.hpp b/redfish-core/lib/network_adapter.hpp
index b022448..f7c890b 100644
--- a/redfish-core/lib/network_adapter.hpp
+++ b/redfish-core/lib/network_adapter.hpp
@@ -28,9 +28,12 @@
 #include <memory>
 #include <string>
 #include <string_view>
+#include <type_traits>
 #include <utility>
+#include <variant>
 #include <vector>
 
+#include "absl/strings/str_cat.h"
 #include "boost/system/error_code.hpp"  // NOLINT
 #include "bmcweb_config.h"
 #include "app.hpp"
@@ -739,8 +742,9 @@
   } else {
     // usb sideband using ifalias
     std::string ifalias;
-    if (readOneLineFile(std::filesystem::path(netSysfsPath) / portId / "ifalias",
-                        ifalias)) {
+    if (readOneLineFile(
+            std::filesystem::path(netSysfsPath) / portId / "ifalias",
+            ifalias)) {
       if (ifalias.size() >= 5 &&
           ifalias.compare(ifalias.size() - 5, 5, "-side") == 0) {
         managedStore::ManagedObjectStoreContext requestContext5(asyncResp);
@@ -752,31 +756,32 @@
             [asyncResp](
                 const boost::system::error_code& ec5,
                 const dbus::utility::DBusPropertiesMap& propertiesList) {
-          if (ec5) {
-            BMCWEB_LOG_ERROR << "GetAllProperties failed: " << ec5.what();
-            return;
-          }
-          const std::string* locationCode = nullptr;
-          const std::string* partLocationContext = nullptr;
-          const bool success = sdbusplus::unpackPropertiesNoThrow(
-              dbus_utils::UnpackErrorPrinter(), propertiesList, "LocationCode",
-              locationCode, "PartLocationContext", partLocationContext);
-          if (!success) {
-            BMCWEB_LOG_ERROR << "Failed to unpack BmcNet_Side properties";
-            return;
-          }
-          if (locationCode != nullptr) {
-            asyncResp->res
-                .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
-                *locationCode;
-          }
-          if (partLocationContext != nullptr) {
-            asyncResp->res.jsonValue["Location"]["PartLocationContext"] =
-                *partLocationContext;
-          }
-          asyncResp->res.jsonValue["Oem"]["Google"]["ConnectionPath"] =
-              "SIDEBAND";
-        });
+              if (ec5) {
+                BMCWEB_LOG_ERROR << "GetAllProperties failed: " << ec5.what();
+                return;
+              }
+              const std::string* locationCode = nullptr;
+              const std::string* partLocationContext = nullptr;
+              const bool success = sdbusplus::unpackPropertiesNoThrow(
+                  dbus_utils::UnpackErrorPrinter(), propertiesList,
+                  "LocationCode", locationCode, "PartLocationContext",
+                  partLocationContext);
+              if (!success) {
+                BMCWEB_LOG_ERROR << "Failed to unpack BmcNet_Side properties";
+                return;
+              }
+              if (locationCode != nullptr) {
+                asyncResp->res
+                    .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
+                    *locationCode;
+              }
+              if (partLocationContext != nullptr) {
+                asyncResp->res.jsonValue["Location"]["PartLocationContext"] =
+                    *partLocationContext;
+              }
+              asyncResp->res.jsonValue["Oem"]["Google"]["ConnectionPath"] =
+                  "SIDEBAND";
+            });
       }
     }
   }
@@ -805,6 +810,10 @@
       networkAdapterId, "Ports", portId);
   asyncResp->res.jsonValue["Name"] = portId;
   asyncResp->res.jsonValue["Id"] = portId;
+  asyncResp->res.jsonValue["Metrics"]["@odata.id"] =
+      crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId,
+                                   "NetworkAdapters", networkAdapterId, "Ports",
+                                   portId, "Metrics");
 
   managedStore::ManagedObjectStoreContext requestContext(asyncResp);
   managedStore::GetManagedObjectStore()->getAllProperties(
@@ -939,13 +948,349 @@
       sysfsGetNetworkPortMetrics(portId);
 }
 
+inline void buildNetworkPortMetricDBus(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& chassisId, const std::string& networkAdapterId,
+    const std::string& portId, const std::string& serviceName,
+    const std::string& networkPortPath) {
+  asyncResp->res.jsonValue["@odata.type"] = "#PortMetrics.v1_3_0.PortMetrics";
+  asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+      "redfish", "v1", "Chassis", chassisId, "NetworkAdapters",
+      networkAdapterId, "Ports", portId, "Metrics");
+  asyncResp->res.jsonValue["Name"] = portId + " Metrics";
+  asyncResp->res.jsonValue["Id"] = "Metrics";
+  asyncResp->res.jsonValue["CorrectableBitErrors"] = 0;
+  asyncResp->res.jsonValue["SymbolErrors"] = 0;
+  asyncResp->res.jsonValue["RXBroadcastPackets"] = 0;
+  asyncResp->res.jsonValue["RXMulticastPackets"] = 0;
+  asyncResp->res.jsonValue["RXUnicastPackets"] = 0;
+  asyncResp->res.jsonValue["TXBroadcastPackets"] = 0;
+  asyncResp->res.jsonValue["TXMulticastPackets"] = 0;
+  asyncResp->res.jsonValue["TXUnicastPackets"] = 0;
+  asyncResp->res
+      .jsonValue["Oem"]["Google"]["TransceiverTelemetry"]["FECCorrectedCount"] =
+      0;
+  asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                          ["FECUncorrectableCount"] = 0;
+  asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                          ["PerLaneFECCounters"] = nlohmann::json::array();
+  asyncResp->res
+      .jsonValue["Oem"]["Google"]["TransceiverTelemetry"]["FECHistogramBins"] =
+      nlohmann::json::array();
+
+  managedStore::ManagedObjectStoreContext requestContext(asyncResp);
+
+  // Query PortECC metrics
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath, "xyz.openbmc_project.Metrics.PortECC",
+      requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        for (const auto& [propName, propValue] : propertiesList) {
+          uint64_t numVal = 0;
+          bool hasNum = false;
+          std::visit(
+              [&numVal, &hasNum](const auto& v) {
+                using T = std::decay_t<decltype(v)>;
+                if constexpr (std::is_arithmetic_v<T>) {
+                  numVal = static_cast<uint64_t>(v);
+                  hasNum = true;
+                }
+              },
+              propValue);
+
+          if (hasNum) {
+            if (propName == "CorrectedBits") {
+              asyncResp->res.jsonValue["CorrectableBitErrors"] = numVal;
+              asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                                      ["FECCorrectedCount"] = numVal;
+            } else if (propName == "SymbolErrorRXBytes") {
+              asyncResp->res.jsonValue["SymbolErrors"] = numVal;
+              asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                                      ["FECUncorrectableCount"] = numVal;
+            }
+          }
+          if (propName == "RawErrorsPerLane") {
+            std::visit(
+                [asyncResp](const auto& v) {
+                  using T = std::decay_t<decltype(v)>;
+                  if constexpr (std::is_same_v<T, std::vector<uint32_t>> ||
+                                std::is_same_v<T, std::vector<uint64_t>> ||
+                                std::is_same_v<T, std::vector<int32_t>> ||
+                                std::is_same_v<T, std::vector<int64_t>>) {
+                    std::vector<uint64_t> uCounters;
+                    uCounters.reserve(v.size());
+                    for (auto elem : v) {
+                      uCounters.push_back(static_cast<uint64_t>(elem));
+                    }
+                    asyncResp->res
+                        .jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                                  ["PerLaneFECCounters"] = uCounters;
+                  }
+                },
+                propValue);
+          }
+        }
+      });
+
+  // Query PortPacketCounters
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath,
+      "xyz.openbmc_project.Metrics.PortPacketCounters", requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        for (const auto& [propName, propValue] : propertiesList) {
+          uint64_t numVal = 0;
+          bool hasNum = false;
+          std::visit(
+              [&numVal, &hasNum](const auto& v) {
+                using T = std::decay_t<decltype(v)>;
+                if constexpr (std::is_arithmetic_v<T>) {
+                  numVal = static_cast<uint64_t>(v);
+                  hasNum = true;
+                }
+              },
+              propValue);
+
+          if (hasNum) {
+            if (propName == "RXBroadcastPkts") {
+              asyncResp->res.jsonValue["RXBroadcastPackets"] = numVal;
+            } else if (propName == "RXMulticastPkts") {
+              asyncResp->res.jsonValue["RXMulticastPackets"] = numVal;
+            } else if (propName == "RXUnicastPkts") {
+              asyncResp->res.jsonValue["RXUnicastPackets"] = numVal;
+            } else if (propName == "TXBroadcastPkts") {
+              asyncResp->res.jsonValue["TXBroadcastPackets"] = numVal;
+            } else if (propName == "TXMulticastPkts") {
+              asyncResp->res.jsonValue["TXMulticastPackets"] = numVal;
+            } else if (propName == "TXUnicastPkts") {
+              asyncResp->res.jsonValue["TXUnicastPackets"] = numVal;
+            }
+          }
+        }
+      });
+
+  // Query EthPort metrics
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath, "xyz.openbmc_project.Metrics.EthPort",
+      requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        for (const auto& [propName, propValue] : propertiesList) {
+          uint64_t numVal = 0;
+          bool hasNum = false;
+          std::visit(
+              [&numVal, &hasNum](const auto& v) {
+                using T = std::decay_t<decltype(v)>;
+                if constexpr (std::is_arithmetic_v<T>) {
+                  numVal = static_cast<uint64_t>(v);
+                  hasNum = true;
+                }
+              },
+              propValue);
+
+          if (hasNum) {
+            if (propName == "RXFCSErrors") {
+              asyncResp->res.jsonValue["RXFCSErrors"] = numVal;
+            } else if (propName == "RXAlignmentErrors") {
+              asyncResp->res.jsonValue["RXAlignmentErrors"] = numVal;
+            } else if (propName == "RXFalseCarrierDetections") {
+              asyncResp->res.jsonValue["RXFalseCarrierDetections"] = numVal;
+            } else if (propName == "RXRuntPkts") {
+              asyncResp->res.jsonValue["RXRuntPackets"] = numVal;
+            } else if (propName == "RXJabberPkts") {
+              asyncResp->res.jsonValue["RXJabberPackets"] = numVal;
+            } else if (propName == "RXXOFFFrames") {
+              asyncResp->res.jsonValue["PauseXOFFFramesReceived"] = numVal;
+            } else if (propName == "RXXONFrames") {
+              asyncResp->res.jsonValue["PauseXONFramesReceived"] = numVal;
+            } else if (propName == "TXXOFFFrames") {
+              asyncResp->res.jsonValue["PauseXOFFFramesTransmitted"] = numVal;
+            } else if (propName == "TXXONFrames") {
+              asyncResp->res.jsonValue["PauseXONFramesTransmitted"] = numVal;
+            } else if (propName == "TXSingleCollisionFrames") {
+              asyncResp->res.jsonValue["SingleCollisionTransmitFrames"] =
+                  numVal;
+            } else if (propName == "TXMultipleCollisionFrames") {
+              asyncResp->res.jsonValue["MultipleCollisionTransmitFrames"] =
+                  numVal;
+            } else if (propName == "TXLateCollisionFrames") {
+              asyncResp->res.jsonValue["LateCollisionFrames"] = numVal;
+            } else if (propName == "TXExcessCollisionFrames") {
+              asyncResp->res.jsonValue["ExcessiveCollisionFrames"] = numVal;
+            }
+          }
+        }
+      });
+
+  // Query IBPort metrics
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath, "xyz.openbmc_project.Metrics.IBPort",
+      requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        for (const auto& [propName, propValue] : propertiesList) {
+          if (propName == "BitErrorRate" || propName == "EffectiveBER" ||
+              propName == "TotalRawBER") {
+            double dVal = 0.0;
+            bool hasD = false;
+            std::visit(
+                [&dVal, &hasD](const auto& v) {
+                  using T = std::decay_t<decltype(v)>;
+                  if constexpr (std::is_floating_point_v<T> ||
+                                std::is_arithmetic_v<T>) {
+                    dVal = static_cast<double>(v);
+                    hasD = true;
+                  }
+                },
+                propValue);
+            if (hasD) {
+              asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"]
+                                      [propName] = dVal;
+            }
+          } else if (propName == "LinkDownCount") {
+            uint64_t numVal = 0;
+            bool hasNum = false;
+            std::visit(
+                [&numVal, &hasNum](const auto& v) {
+                  using T = std::decay_t<decltype(v)>;
+                  if constexpr (std::is_arithmetic_v<T>) {
+                    numVal = static_cast<uint64_t>(v);
+                    hasNum = true;
+                  }
+                },
+                propValue);
+            if (hasNum) {
+              asyncResp->res.jsonValue["LinkDownCount"] = numVal;
+            }
+          }
+        }
+      });
+
+  // Query PortMetricsOem2 (RXBytes, TXBytes)
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath,
+      "xyz.openbmc_project.Metrics.PortMetricsOem2", requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        for (const auto& [propName, propValue] : propertiesList) {
+          uint64_t numVal = 0;
+          bool hasNum = false;
+          std::visit(
+              [&numVal, &hasNum](const auto& v) {
+                using T = std::decay_t<decltype(v)>;
+                if constexpr (std::is_arithmetic_v<T>) {
+                  numVal = static_cast<uint64_t>(v);
+                  hasNum = true;
+                }
+              },
+              propValue);
+
+          if (hasNum) {
+            if (propName == "RXBytes") {
+              asyncResp->res.jsonValue["RXBytes"] = numVal;
+            } else if (propName == "TXBytes") {
+              asyncResp->res.jsonValue["TXBytes"] = numVal;
+            }
+          }
+        }
+      });
+
+  // Query PCIeECC
+  managedStore::GetManagedObjectStore()->getAllProperties(
+      serviceName, networkPortPath, "xyz.openbmc_project.PCIe.PCIeECC",
+      requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::DBusPropertiesMap& propertiesList) {
+        if (ec) {
+          return;
+        }
+        nlohmann::json& pcieObj =
+            asyncResp->res.jsonValue["Oem"]["Google"]["PCIeErrors"];
+        for (const auto& [propName, propValue] : propertiesList) {
+          uint64_t val = 0;
+          bool hasVal = false;
+          std::visit(
+              [&val, &hasVal](const auto& v) {
+                using T = std::decay_t<decltype(v)>;
+                if constexpr (std::is_arithmetic_v<T>) {
+                  val = static_cast<uint64_t>(v);
+                  hasVal = true;
+                }
+              },
+              propValue);
+          if (hasVal) {
+            pcieObj[propName] = val;
+          }
+        }
+      });
+}
+
 inline void handleNetworkPortMetricGetDBus(
-    [[maybe_unused]] const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
     const std::string& chassisId, const std::string& networkAdapterId,
     const std::string& portId) {
-  BMCWEB_LOG_ERROR << "Metric not supported: chassis " << chassisId
-                   << " network adapter " << networkAdapterId << " port "
-                   << portId;
+  constexpr std::array<std::string_view, 1> interfaces = {portInventoryIntf};
+  managedStore::ManagedObjectStoreContext requestContext(asyncResp);
+  managedStore::GetManagedObjectStore()->getSubTree(
+      inventoryPrefix, 0, interfaces, requestContext,
+      [asyncResp, chassisId, networkAdapterId, portId, requestContext](
+          const boost::system::error_code& ec,
+          const dbus::utility::MapperGetSubTreeResponse& subtree) {
+        if (ec) {
+          if (ec == boost::system::errc::host_unreachable) {
+            messages::resourceNotFound(
+                asyncResp->res, "#PortMetrics.v1_3_0.PortMetrics", portId);
+            return;
+          }
+          BMCWEB_LOG_ERROR << "Failed to get " << portInventoryIntf
+                           << " due to:" << ec.what();
+          messages::internalError(asyncResp->res);
+          return;
+        }
+
+        std::string matchingPath;
+        std::string matchingService;
+        std::string portIdSuffix1 =
+            absl::StrCat("/", chassisId, "/", networkAdapterId, "/", portId);
+        std::string portIdSuffix2 =
+            absl::StrCat("/", chassisId, "/NetworkAdapters/", networkAdapterId,
+                         "/Ports/", portId);
+
+        for (const auto& [path, connectionNames] : subtree) {
+          if (path.ends_with(portIdSuffix1) || path.ends_with(portIdSuffix2)) {
+            if (!connectionNames.empty()) {
+              matchingPath = path;
+              matchingService = connectionNames[0].first;
+              break;
+            }
+          }
+        }
+
+        if (!matchingPath.empty()) {
+          buildNetworkPortMetricDBus(asyncResp, chassisId, networkAdapterId,
+                                     portId, matchingService, matchingPath);
+          return;
+        }
+
+        messages::resourceNotFound(asyncResp->res,
+                                   "#PortMetrics.v1_3_0.PortMetrics", portId);
+      });
 }
 
 inline void handleNetworkPortMetricGet(
diff --git a/test/redfish-core/lib/network_adapter_test.cpp b/test/redfish-core/lib/network_adapter_test.cpp
index e930d05..7eb455d 100644
--- a/test/redfish-core/lib/network_adapter_test.cpp
+++ b/test/redfish-core/lib/network_adapter_test.cpp
@@ -1,5 +1,6 @@
 #include "network_adapter.hpp"
 
+#include <cstdint>
 #include <memory>
 #include <vector>
 
@@ -269,5 +270,192 @@
   EXPECT_EQ(json["Oem"]["Google"]["Networking"]["ReceivedPacketsDropped"], 0);
 }
 
+TEST_F(SnapshotFixture, GetNetworkPortMetricDBusReturnsCorrectResponse) {
+  auto* mockStore =
+      dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+          managedStore::GetManagedObjectStore());
+
+  // 1. Mock port Inventory SubTree
+  managedStore::KeyType portSubTreeKey(
+      ManagedType::kManagedSubtree, "/xyz/openbmc_project/inventory", 0,
+      {"xyz.openbmc_project.Inventory.Item.Port"});
+  dbus::utility::MapperGetSubTreeResponse portSubTree = {
+      {"/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+       "CX9_0_NIC_1/Ports/Port_1",
+       {{"xyz.openbmc_project.NSM",
+         {"xyz.openbmc_project.Inventory.Item.Port"}}}}};
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              portSubTreeKey,
+              managedStore::MockManagedStoreTest::CreateValueType(portSubTree))
+          .ok());
+
+  // 2. Mock PortECC properties
+  managedStore::KeyType eccKey(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.Metrics.PortECC", "");
+  dbus::utility::DBusPropertiesMap eccProperties;
+  eccProperties.emplace_back("CorrectedBits",
+                             dbus::utility::DbusVariantType(uint64_t{42}));
+  eccProperties.emplace_back("SymbolErrorRXBytes",
+                             dbus::utility::DbusVariantType(uint64_t{7}));
+  std::vector<uint32_t> perLaneCounters = {10, 20, 30, 40};
+  eccProperties.emplace_back("RawErrorsPerLane",
+                             dbus::utility::DbusVariantType(perLaneCounters));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              eccKey, managedStore::MockManagedStoreTest::CreateValueType(
+                          eccProperties))
+          .ok());
+
+  // 3. Mock PortPacketCounters properties
+  managedStore::KeyType packetKey(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.Metrics.PortPacketCounters", "");
+  dbus::utility::DBusPropertiesMap packetProperties;
+  packetProperties.emplace_back("RXBroadcastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{100}));
+  packetProperties.emplace_back("RXMulticastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{200}));
+  packetProperties.emplace_back("RXUnicastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{300}));
+  packetProperties.emplace_back("TXBroadcastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{400}));
+  packetProperties.emplace_back("TXMulticastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{500}));
+  packetProperties.emplace_back("TXUnicastPkts",
+                                dbus::utility::DbusVariantType(uint64_t{600}));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              packetKey, managedStore::MockManagedStoreTest::CreateValueType(
+                             packetProperties))
+          .ok());
+
+  // 4. Mock EthPort properties
+  managedStore::KeyType ethKey(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.Metrics.EthPort", "");
+  dbus::utility::DBusPropertiesMap ethProperties;
+  ethProperties.emplace_back("RXFCSErrors",
+                             dbus::utility::DbusVariantType(uint64_t{3}));
+  ethProperties.emplace_back("RXAlignmentErrors",
+                             dbus::utility::DbusVariantType(uint64_t{1}));
+  ethProperties.emplace_back("PauseXOFFFramesReceived",
+                             dbus::utility::DbusVariantType(uint64_t{5}));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              ethKey, managedStore::MockManagedStoreTest::CreateValueType(
+                          ethProperties))
+          .ok());
+
+  // 5. Mock IBPort properties
+  managedStore::KeyType ibKey(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.Metrics.IBPort", "");
+  dbus::utility::DBusPropertiesMap ibProperties;
+  ibProperties.emplace_back("BitErrorRate",
+                            dbus::utility::DbusVariantType(double{1e-12}));
+  ibProperties.emplace_back("LinkDownCount",
+                            dbus::utility::DbusVariantType(uint64_t{2}));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              ibKey,
+              managedStore::MockManagedStoreTest::CreateValueType(ibProperties))
+          .ok());
+
+  // 6. Mock PortMetricsOem2 properties
+  managedStore::KeyType oem2Key(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.Metrics.PortMetricsOem2", "");
+  dbus::utility::DBusPropertiesMap oem2Properties;
+  oem2Properties.emplace_back(
+      "RXBytes", dbus::utility::DbusVariantType(uint64_t{1048576}));
+  oem2Properties.emplace_back(
+      "TXBytes", dbus::utility::DbusVariantType(uint64_t{2097152}));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              oem2Key, managedStore::MockManagedStoreTest::CreateValueType(
+                           oem2Properties))
+          .ok());
+
+  // 7. Mock PCIeECC properties
+  managedStore::KeyType pcieKey(
+      ManagedType::kManagedPropertyMap, "xyz.openbmc_project.NSM",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/chassis/CX9_0/NetworkAdapters/"
+          "CX9_0_NIC_1/Ports/Port_1"),
+      "xyz.openbmc_project.PCIe.PCIeECC", "");
+  dbus::utility::DBusPropertiesMap pcieProperties;
+  pcieProperties.emplace_back("ceCount",
+                              dbus::utility::DbusVariantType(uint64_t{9}));
+  pcieProperties.emplace_back("ueCount",
+                              dbus::utility::DbusVariantType(uint64_t{0}));
+  EXPECT_TRUE(
+      mockStore
+          ->upsertMockObjectIntoManagedStore(
+              pcieKey, managedStore::MockManagedStoreTest::CreateValueType(
+                           pcieProperties))
+          .ok());
+
+  handleNetworkPortMetricGetDBus(share_async_resp_, "CX9_0", "CX9_0_NIC_1",
+                                 "Port_1");
+
+  RunIoUntilDone();
+  nlohmann::json& json = share_async_resp_->res.jsonValue;
+  EXPECT_EQ(
+      json["@odata.id"],
+      "/redfish/v1/Chassis/CX9_0/NetworkAdapters/CX9_0_NIC_1/Ports/Port_1/"
+      "Metrics");
+  EXPECT_EQ(json["@odata.type"], "#PortMetrics.v1_3_0.PortMetrics");
+  EXPECT_EQ(json["Name"], "Port_1 Metrics");
+  EXPECT_EQ(json["Id"], "Metrics");
+  EXPECT_EQ(json["CorrectableBitErrors"], 42);
+  EXPECT_EQ(json["SymbolErrors"], 7);
+  EXPECT_EQ(json["RXBroadcastPackets"], 100);
+  EXPECT_EQ(json["RXMulticastPackets"], 200);
+  EXPECT_EQ(json["RXUnicastPackets"], 300);
+  EXPECT_EQ(json["TXBroadcastPackets"], 400);
+  EXPECT_EQ(json["TXMulticastPackets"], 500);
+  EXPECT_EQ(json["TXUnicastPackets"], 600);
+  EXPECT_EQ(json["RXFCSErrors"], 3);
+  EXPECT_EQ(json["RXAlignmentErrors"], 1);
+  EXPECT_EQ(json["RXBytes"], 1048576);
+  EXPECT_EQ(json["TXBytes"], 2097152);
+  EXPECT_EQ(json["Oem"]["Google"]["TransceiverTelemetry"]["FECCorrectedCount"],
+            42);
+  EXPECT_EQ(
+      json["Oem"]["Google"]["TransceiverTelemetry"]["FECUncorrectableCount"],
+      7);
+  EXPECT_EQ(json["Oem"]["Google"]["TransceiverTelemetry"]["PerLaneFECCounters"],
+            (std::vector<uint64_t>{10, 20, 30, 40}));
+  EXPECT_DOUBLE_EQ(json["Oem"]["Google"]["TransceiverTelemetry"]["BitErrorRate"]
+                       .get<double>(),
+                   1e-12);
+  EXPECT_EQ(json["LinkDownCount"], 2);
+  EXPECT_EQ(json["Oem"]["Google"]["PCIeErrors"]["ceCount"], 9);
+  EXPECT_EQ(json["Oem"]["Google"]["PCIeErrors"]["ueCount"], 0);
+}
+
 }  // namespace
 }  // namespace redfish