PEL: Let SRC class create a debug section

Add functionality so that when the SRC PEL section is being created, it
can save debug data (a vector of strings) along the way, and the PEL
class will then add that data into a JSON UserData section with any
other debug data.  The PEL class will then also write the debug data to
the journal.

This will be used for things like failure messages when looking up
callouts, additional information for device callouts, etc.

The functionality to save debug data is in the Section base class, so in
the future other Section classes can also save debug data for display in
the PEL.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I2f7923303e953c100c94ac81ba7c85152080fb72
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 2e675cb..f94a1fc 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -48,11 +48,18 @@
          const AdditionalData& additionalData, const PelFFDC& ffdcFiles,
          const DataInterfaceBase& dataIface)
 {
+    std::map<std::string, std::vector<std::string>> debugData;
+
     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
                                           timestamp);
     _uh = std::make_unique<UserHeader>(regEntry, severity, dataIface);
 
     auto src = std::make_unique<SRC>(regEntry, additionalData, dataIface);
+    if (!src->getDebugData().empty())
+    {
+        // Something didn't go as planned
+        debugData.emplace("SRC", src->getDebugData());
+    }
 
     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
 
@@ -63,31 +70,13 @@
     _optionalSections.push_back(std::move(mtms));
 
     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
-    _optionalSections.push_back(std::move(ud));
+    addUserDataSection(std::move(ud));
 
     // Create a UserData section from AdditionalData.
     if (!additionalData.empty())
     {
         ud = util::makeADUserDataSection(additionalData);
-
-        // Shrink the section if necessary.
-        if (size() + ud->header().size > _maxPELSize)
-        {
-            if (ud->shrink(_maxPELSize - size()))
-            {
-                _optionalSections.push_back(std::move(ud));
-            }
-            else
-            {
-                log<level::WARNING>(
-                    "Dropping AdditionalData UserData section",
-                    entry("SECTION_SIZE=%d\n", ud->header().size));
-            }
-        }
-        else
-        {
-            _optionalSections.push_back(std::move(ud));
-        }
+        addUserDataSection(std::move(ud));
     }
 
     // Add any FFDC files into UserData sections
@@ -96,31 +85,46 @@
         ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
         if (!ud)
         {
-            log<level::WARNING>(
-                "Could not make PEL FFDC UserData section from file",
-                entry("COMPONENT_ID=0x%02X", regEntry.componentID),
-                entry("SUBTYPE=0x%X", file.subType),
-                entry("VERSION=0x%X", file.version));
+            // Add this error into the debug data UserData section
+            std::ostringstream msg;
+            msg << "Could not make PEL FFDC UserData section from file"
+                << std::hex << regEntry.componentID << " " << file.subType
+                << " " << file.version;
+            if (debugData.count("FFDC File"))
+            {
+                debugData.at("FFDC File").push_back(msg.str());
+            }
+            else
+            {
+                debugData.emplace("FFDC File",
+                                  std::vector<std::string>{msg.str()});
+            }
+
             continue;
         }
 
-        // Shrink it if necessary
-        if (size() + ud->header().size > _maxPELSize)
-        {
-            if (!ud->shrink(_maxPELSize - size()))
-            {
-                log<level::WARNING>(
-                    "Could not shrink FFDC UserData section",
-                    entry("COMPONENT_ID=0x%02X", regEntry.componentID),
-                    entry("SUBTYPE=0x%X", file.subType),
-                    entry("VERSION=0x%X", file.version));
+        addUserDataSection(std::move(ud));
+    }
 
-                // Give up adding FFDC
-                break;
+    // Store in the PEL any important debug data created while
+    // building the PEL sections.
+    if (!debugData.empty())
+    {
+        nlohmann::json data;
+        data["PEL Internal Debug Data"] = debugData;
+        ud = util::makeJSONUserDataSection(data);
+
+        addUserDataSection(std::move(ud));
+
+        // Also put in the journal for debug
+        for (const auto& [name, data] : debugData)
+        {
+            for (const auto& message : data)
+            {
+                std::string entry = name + ": " + message;
+                log<level::INFO>(entry.c_str());
             }
         }
-
-        _optionalSections.push_back(std::move(ud));
     }
 
     _ph->setSectionCount(2 + _optionalSections.size());
@@ -370,6 +374,32 @@
     std::cout << buf << std::endl;
 }
 
+bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
+{
+    if (size() + userData->header().size > _maxPELSize)
+    {
+        if (userData->shrink(_maxPELSize - size()))
+        {
+            _optionalSections.push_back(std::move(userData));
+        }
+        else
+        {
+            log<level::WARNING>(
+                "Could not shrink UserData section. Dropping",
+                entry("SECTION_SIZE=%d\n", userData->header().size),
+                entry("COMPONENT_ID=0x%02X", userData->header().componentID),
+                entry("SUBTYPE=0x%X", userData->header().subType),
+                entry("VERSION=0x%X", userData->header().version));
+            return false;
+        }
+    }
+    else
+    {
+        _optionalSections.push_back(std::move(userData));
+    }
+    return true;
+}
+
 namespace util
 {
 
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index abcb673..ac1bddc 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -317,6 +317,28 @@
     std::map<uint16_t, size_t> getPluralSections() const;
 
     /**
+     * @brief Adds the UserData section to this PEL object,
+     *        shrinking it if necessary
+     *
+     * @param[in] userData - The section to add
+     *
+     * @return bool - If the section was added or not.
+     */
+    bool addUserDataSection(std::unique_ptr<UserData> userData);
+
+    /**
+     * @brief helper function for printing PELs.
+     * @param[in] Section& - section object reference
+     * @param[in] std::string - PEL string
+     * @param[in|out] pluralSections - Map used to track sections counts for
+     *                                 when there is more than 1.
+     * @param[in] registry - Registry object reference
+     */
+    void printSectionInJSON(const Section& section, std::string& buf,
+                            std::map<uint16_t, size_t>& pluralSections,
+                            message::Registry& registry) const;
+
+    /**
      * @brief The PEL Private Header section
      */
     std::unique_ptr<PrivateHeader> _ph;
@@ -332,18 +354,6 @@
     std::vector<std::unique_ptr<Section>> _optionalSections;
 
     /**
-     * @brief helper function for printing PELs.
-     * @param[in] Section& - section object reference
-     * @param[in] std::string - PEL string
-     * @param[in|out] pluralSections - Map used to track sections counts for
-     *                                 when there is more than 1.
-     * @param[in] registry - Registry object reference
-     */
-    void printSectionInJSON(const Section& section, std::string& buf,
-                            std::map<uint16_t, size_t>& pluralSections,
-                            message::Registry& registry) const;
-
-    /**
      * @brief The maximum size a PEL can be in bytes.
      */
     static constexpr size_t _maxPELSize = 16384;
@@ -353,6 +363,15 @@
 {
 
 /**
+ * @brief Creates a UserData section object that contains JSON.
+ *
+ * @param[in] json - The JSON contents
+ *
+ * @return std::unique_ptr<UserData> - The UserData object
+ */
+std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json);
+
+/**
  * @brief Create a UserData section containing the AdditionalData
  *        contents as a JSON string.
  *
diff --git a/extensions/openpower-pels/section.hpp b/extensions/openpower-pels/section.hpp
index 183268c..bf08aa5 100644
--- a/extensions/openpower-pels/section.hpp
+++ b/extensions/openpower-pels/section.hpp
@@ -90,6 +90,16 @@
         return false;
     }
 
+    /**
+     * @brief Returns any debug data stored in the object
+     *
+     * @return std::vector<std::string>& - The debug data
+     */
+    const std::vector<std::string>& getDebugData() const
+    {
+        return _debugData;
+    }
+
   protected:
     /**
      * @brief Returns the flattened size of the section header
@@ -100,6 +110,17 @@
     }
 
     /**
+     * @brief Adds debug data to the object that may be displayed
+     *        in a UserData section in the PEL.
+     *
+     * @param[in] data - The new entry to add to the vector of data.
+     */
+    void addDebugData(const std::string& data)
+    {
+        _debugData.push_back(data);
+    }
+
+    /**
      * @brief Used to validate the section.
      *
      * Implemented by derived classes.
@@ -119,6 +140,14 @@
      * This is determined by the derived class.
      */
     bool _valid = false;
+
+    /**
+     * @brief Messages that derived classes can add during construction
+     *        of a PEL when something happens that would be useful to
+     *        store in the PEL.  This may get added into a UserData section
+     *        in the PEL.
+     */
+    std::vector<std::string> _debugData;
 };
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index 13a3ae5..d2e58d5 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -28,6 +28,7 @@
 namespace pv = openpower::pels::pel_values;
 namespace rg = openpower::pels::message;
 using namespace phosphor::logging;
+using namespace std::string_literals;
 
 constexpr size_t ccinSize = 4;
 
@@ -144,9 +145,9 @@
         // Can only set words 6 - 9
         if (!isUserDefinedWord(wordNum))
         {
-            log<level::WARNING>("SRC user data word out of range",
-                                entry("WORD_NUM=%d", wordNum),
-                                entry("ERROR_NAME=%s", regEntry.name.c_str()));
+            std::string msg =
+                "SRC user data word out of range: " + std::to_string(wordNum);
+            addDebugData(msg);
             continue;
         }
 
@@ -158,9 +159,9 @@
         }
         else
         {
-            log<level::WARNING>("Source for user data SRC word not found",
-                                entry("ADDITIONALDATA_KEY=%s", adName.c_str()),
-                                entry("ERROR_NAME=%s", regEntry.name.c_str()));
+            std::string msg =
+                "Source for user data SRC word not found: " + adName;
+            addDebugData(msg);
         }
     }
 }
@@ -542,8 +543,9 @@
         }
         catch (const SdBusError& e)
         {
-            std::string msg = "No VPD found for " + inventoryPath;
-            log<level::WARNING>(msg.c_str(), entry("ERROR=%s", e.what()));
+            std::string msg =
+                "No VPD found for " + inventoryPath + ": " + e.what();
+            addDebugData(msg);
 
             // Just create the callout with empty FRU fields
             callout = std::make_unique<src::Callout>(CalloutPriority::high,
@@ -552,8 +554,9 @@
     }
     catch (const SdBusError& e)
     {
-        std::string msg = "Could not get location code for " + inventoryPath;
-        log<level::WARNING>(msg.c_str(), entry("ERROR=%s", e.what()));
+        std::string msg = "Could not get location code for " + inventoryPath +
+                          ": " + e.what();
+        addDebugData(msg);
 
         // If this were to happen, people would have to look in the UserData
         // section that contains CALLOUT_INVENTORY_PATH to see what failed.
@@ -583,8 +586,9 @@
     }
     catch (std::exception& e)
     {
-        log<level::ERR>("Error parsing PEL message registry callout JSON",
-                        entry("ERROR=%s", e.what()));
+        std::string msg =
+            "Error parsing PEL message registry callout JSON: "s + e.what();
+        addDebugData(msg);
     }
 }