PEL: Capture the journal in UserData sections
If a PEL message registry entry has a 'JournalCapture' section, capture
the listed portions of the journal in UserData sections for that error.
If the JSON looks like:
"JournalCapture": {
"NumLines": 30
}
Then the code will capture the previous 30 lines from the journal into a
single UserData section.
If the JSON looks like:
"JournalCapture":
{
"Sections": [
{
"SyslogID": "phosphor-bmc-state-manager",
"NumLines": 20
},
{
"SyslogID": "phosphor-log-manager",
"NumLines": 15
}
]
}
Then the code will create two UserData sections, the first with the most
recent 20 lines from phosphor-bmc-state-manager, and the second with 15
lines from phosphor-log-manager.
If a section would cause the PEL to exceed its maximum size of 16KB, it
will be dropped. While the UserData class does have a shrink() method,
it prunes data from the end, which would cause the most recent journal
entries to be removed, which could be misleading.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I2ecbd8002b0e7087eb166a1219c6ab9da14a122a
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index f74eaf3..9fa1747 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -56,7 +56,7 @@
PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
phosphor::logging::Entry::Level severity,
const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn,
- const DataInterfaceBase& dataIface)
+ const DataInterfaceBase& dataIface, const JournalBase& journal)
{
// No changes in input, for non SBE error related requests
PelFFDC ffdcFiles = ffdcFilesIn;
@@ -195,6 +195,8 @@
}
}
+ addJournalSections(regEntry, journal);
+
_ph->setSectionCount(2 + _optionalSections.size());
checkRulesAndFix();
@@ -596,6 +598,111 @@
}
}
+void PEL::addJournalSections(const message::Entry& regEntry,
+ const JournalBase& journal)
+{
+ if (!regEntry.journalCapture)
+ {
+ return;
+ }
+
+ // Write all unwritten journal data to disk.
+ journal.sync();
+
+ const auto& jc = regEntry.journalCapture.value();
+ std::vector<std::vector<std::string>> allMessages;
+
+ if (std::holds_alternative<size_t>(jc))
+ {
+ // Get the previous numLines journal entries
+ const auto& numLines = std::get<size_t>(jc);
+ try
+ {
+ auto messages = journal.getMessages("", numLines);
+ if (!messages.empty())
+ {
+ allMessages.push_back(std::move(messages));
+ }
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>(
+ fmt::format("Failed during journal collection: {}", e.what())
+ .c_str());
+ }
+ }
+ else if (std::holds_alternative<message::AppCaptureList>(jc))
+ {
+ // Get journal entries based on the syslog id field.
+ const auto& sections = std::get<message::AppCaptureList>(jc);
+ for (const auto& [syslogID, numLines] : sections)
+ {
+ try
+ {
+ auto messages = journal.getMessages(syslogID, numLines);
+ if (!messages.empty())
+ {
+ allMessages.push_back(std::move(messages));
+ }
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>(
+ fmt::format("Failed during journal collection: {}",
+ e.what())
+ .c_str());
+ }
+ }
+ }
+
+ // Create the UserData sections
+ for (const auto& messages : allMessages)
+ {
+ auto buffer = util::flattenLines(messages);
+
+ // If the buffer is way too big, it can overflow the uint16_t
+ // PEL section size field that is checked below so do a cursory
+ // check here.
+ if (buffer.size() > _maxPELSize)
+ {
+ log<level::WARNING>(
+ "Journal UserData section does not fit in PEL, dropping");
+ log<level::WARNING>(fmt::format("PEL size = {}, data size = {}",
+ size(), buffer.size())
+ .c_str());
+ continue;
+ }
+
+ // Sections must be 4 byte aligned.
+ while (buffer.size() % 4 != 0)
+ {
+ buffer.push_back(0);
+ }
+
+ auto ud = std::make_unique<UserData>(
+ static_cast<uint16_t>(ComponentID::phosphorLogging),
+ static_cast<uint8_t>(UserDataFormat::text),
+ static_cast<uint8_t>(UserDataFormatVersion::text), buffer);
+
+ if (size() + ud->header().size <= _maxPELSize)
+ {
+ _optionalSections.push_back(std::move(ud));
+ }
+ else
+ {
+ // Don't attempt to shrink here since we'd be dropping the
+ // most recent journal entries which would be confusing.
+ log<level::WARNING>(
+ "Journal UserData section does not fit in PEL, dropping");
+ log<level::WARNING>(fmt::format("PEL size = {}, UserData size = {}",
+ size(), ud->header().size)
+ .c_str());
+ ud.reset();
+ continue;
+ }
+ }
+}
+
namespace util
{
@@ -852,6 +959,23 @@
return std::make_unique<UserData>(compID, subType, version, data);
}
+std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines)
+{
+ std::vector<uint8_t> out;
+
+ for (const auto& line : lines)
+ {
+ out.insert(out.end(), line.begin(), line.end());
+
+ if (out.back() != '\n')
+ {
+ out.push_back('\n');
+ }
+ }
+
+ return out;
+}
+
} // namespace util
} // namespace pels