PEL: Implement respository pruning

This adds a prune() public method on the Repository class to remove PELs
down to at most 90% of capacity and then down to 80% of the maximum
number of PELs if there were more than the maximum.

It does the first set of pruning by placing each PEL is one of 4
categories, and then reducing the total size of each category.  The
categories are:
* BMC informational PELs - reduced to 15% of max
* BMC non-informational PELs - reduced to 30% of max
* non-BMC informational PELs - reduced to 15% of max
* non-BMC non-informational PELs - reduced to 30% of max

Within each category, PELs are removed oldest first, and also 4 passes
are made through the PELs, only removing PELs that meet a specific
requirement each pass, stopping as soon as the category limit is
reached.

The pass requirements are:
* Pass 1: Only remove HMC acked PELs
* Pass 2: Only remove OS acked PELs
* Pass 3: Only remove host sent PELs
* Pass 4: Remove any PEL

After the 4 passes on the 4 categories are done then the number of PELs
remaining is checked against the maximum number.  If it is more than the
maximum, it will remove the PELs down to 80% of that limit using the
same 4 passes as above.  This is done to keep the number of PELs down to
a manageable number when there are a lot of small PELs that don't engage
the size based pruning.

The pruning code doesn't just bring the size or number of PELs to just
below their limit, but rather a percentage below, so that it won't get
into a situation where the algorithm has to run on the repository every
single time a PEL is added.

The OpenBMC event log corresponding to the PELs are not removed.  That
is left to other code.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I24da611c095fd3b22b6b1ffab52d919cac5f68b4
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
index 8458fcc..e608927 100644
--- a/extensions/openpower-pels/repository.cpp
+++ b/extensions/openpower-pels/repository.cpp
@@ -499,5 +499,180 @@
     }
 }
 
+std::vector<Repository::AttributesReference>
+    Repository::getAllPELAttributes(SortOrder order) const
+{
+    std::vector<Repository::AttributesReference> attributes;
+
+    std::for_each(
+        _pelAttributes.begin(), _pelAttributes.end(),
+        [&attributes](auto& pelEntry) { attributes.push_back(pelEntry); });
+
+    std::sort(attributes.begin(), attributes.end(),
+              [order](const auto& left, const auto& right) {
+                  if (order == SortOrder::ascending)
+                  {
+                      return left.get().second.path < right.get().second.path;
+                  }
+                  return left.get().second.path > right.get().second.path;
+              });
+
+    return attributes;
+}
+
+std::vector<uint32_t> Repository::prune()
+{
+    std::vector<uint32_t> obmcLogIDs;
+    std::string msg = "Pruning PEL repository that takes up " +
+                      std::to_string(_sizes.total) + " bytes and has " +
+                      std::to_string(_pelAttributes.size()) + " PELs";
+    log<level::INFO>(msg.c_str());
+
+    // Set up the 5 functions to check if the PEL category
+    // is still over its limits.
+
+    // BMC informational PELs should only take up 15%
+    IsOverLimitFunc overBMCInfoLimit = [this]() {
+        return _sizes.bmcInfo > _maxRepoSize * 15 / 100;
+    };
+
+    // BMC non informational PELs should only take up 30%
+    IsOverLimitFunc overBMCNonInfoLimit = [this]() {
+        return _sizes.bmcServiceable > _maxRepoSize * 30 / 100;
+    };
+
+    // Non BMC informational PELs should only take up 15%
+    IsOverLimitFunc overNonBMCInfoLimit = [this]() {
+        return _sizes.nonBMCInfo > _maxRepoSize * 15 / 100;
+    };
+
+    // Non BMC non informational PELs should only take up 15%
+    IsOverLimitFunc overNonBMCNonInfoLimit = [this]() {
+        return _sizes.nonBMCServiceable > _maxRepoSize * 30 / 100;
+    };
+
+    // Bring the total number of PELs down to 80% of the max
+    IsOverLimitFunc tooManyPELsLimit = [this]() {
+        return _pelAttributes.size() > _maxNumPELs * 80 / 100;
+    };
+
+    // Set up the functions to determine which category a PEL is in.
+    // TODO: Return false in these functions if a PEL caused a guard record.
+
+    // A BMC informational PEL
+    IsPELTypeFunc isBMCInfo = [](const PELAttributes& pel) {
+        return (CreatorID::openBMC == static_cast<CreatorID>(pel.creator)) &&
+               !Repository::isServiceableSev(pel);
+    };
+
+    // A BMC non informational PEL
+    IsPELTypeFunc isBMCNonInfo = [](const PELAttributes& pel) {
+        return (CreatorID::openBMC == static_cast<CreatorID>(pel.creator)) &&
+               Repository::isServiceableSev(pel);
+    };
+
+    // A non BMC informational PEL
+    IsPELTypeFunc isNonBMCInfo = [](const PELAttributes& pel) {
+        return (CreatorID::openBMC != static_cast<CreatorID>(pel.creator)) &&
+               !Repository::isServiceableSev(pel);
+    };
+
+    // A non BMC non informational PEL
+    IsPELTypeFunc isNonBMCNonInfo = [](const PELAttributes& pel) {
+        return (CreatorID::openBMC != static_cast<CreatorID>(pel.creator)) &&
+               Repository::isServiceableSev(pel);
+    };
+
+    // When counting PELs, count every PEL
+    IsPELTypeFunc isAnyPEL = [](const PELAttributes& pel) { return true; };
+
+    // Check all 4 categories, which will result in at most 90%
+    // usage (15 + 30 + 15 + 30).
+    removePELs(overBMCInfoLimit, isBMCInfo, obmcLogIDs);
+    removePELs(overBMCNonInfoLimit, isBMCNonInfo, obmcLogIDs);
+    removePELs(overNonBMCInfoLimit, isNonBMCInfo, obmcLogIDs);
+    removePELs(overNonBMCNonInfoLimit, isNonBMCNonInfo, obmcLogIDs);
+
+    // After the above pruning check if there are still too many PELs,
+    // which can happen depending on PEL sizes.
+    if (_pelAttributes.size() > _maxNumPELs)
+    {
+        removePELs(tooManyPELsLimit, isAnyPEL, obmcLogIDs);
+    }
+
+    if (!obmcLogIDs.empty())
+    {
+        std::string msg = "Number of PELs removed to save space: " +
+                          std::to_string(obmcLogIDs.size());
+        log<level::INFO>(msg.c_str());
+    }
+
+    return obmcLogIDs;
+}
+
+void Repository::removePELs(IsOverLimitFunc& isOverLimit,
+                            IsPELTypeFunc& isPELType,
+                            std::vector<uint32_t>& removedBMCLogIDs)
+{
+    if (!isOverLimit())
+    {
+        return;
+    }
+
+    auto attributes = getAllPELAttributes(SortOrder::ascending);
+
+    // Make 4 passes on the PELs, stopping as soon as isOverLimit
+    // returns false.
+    //   Pass 1: only delete HMC acked PELs
+    //   Pass 2: only delete OS acked PELs
+    //   Pass 3: only delete PHYP sent PELs
+    //   Pass 4: delete all PELs
+    static const std::vector<std::function<bool(const PELAttributes& pel)>>
+        stateChecks{[](const auto& pel) {
+                        return pel.hmcState == TransmissionState::acked;
+                    },
+
+                    [](const auto& pel) {
+                        return pel.hostState == TransmissionState::acked;
+                    },
+
+                    [](const auto& pel) {
+                        return pel.hostState == TransmissionState::sent;
+                    },
+
+                    [](const auto& pel) { return true; }};
+
+    for (const auto& stateCheck : stateChecks)
+    {
+        for (auto it = attributes.begin(); it != attributes.end();)
+        {
+            const auto& pel = it->get();
+            if (isPELType(pel.second) && stateCheck(pel.second))
+            {
+                auto removedID = pel.first.obmcID.id;
+                remove(pel.first);
+
+                removedBMCLogIDs.push_back(removedID);
+
+                attributes.erase(it);
+
+                if (!isOverLimit())
+                {
+                    break;
+                }
+            }
+            else
+            {
+                ++it;
+            }
+        }
+
+        if (!isOverLimit())
+        {
+            break;
+        }
+    }
+}
+
 } // namespace pels
 } // namespace openpower