PEL: Keep size statistics of PEL repo

Keep a running total of several size statistics of the PEL repository.

The statistics are:
* Total size of all PELs
* Total size of BMC created PELs
* Total size of BMC created informational PELs
* Total size of BMC created non-informational PELs
* Total size of non-BMC created PELs
* Total size of non-BMC created informational PELs
* Total size of non-BMC created non-informational PELs

Here, size refers to the disk size the PEL uses, which is different than
the file size.  The disk size is retrieved from the stat() function and
is the number of 512B blocks used.

The term 'informational' above doesn't strictly refer to the
informational severity value (0x0), but also includes other cases, such
as hidden recovered PELs.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I07a3ccd7cfe802a344a2db47daba5fb05d56489f
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
index 4e40678..8458fcc 100644
--- a/extensions/openpower-pels/repository.cpp
+++ b/extensions/openpower-pels/repository.cpp
@@ -118,6 +118,8 @@
                 _pelAttributes.emplace(
                     LogID(pelID(pel.id()), obmcID(pel.obmcLogID())),
                     attributes);
+
+                updateRepoStats(attributes, true);
             }
             else
             {
@@ -167,6 +169,8 @@
     _pelAttributes.emplace(LogID(pelID(pel->id()), obmcID(pel->obmcLogID())),
                            attributes);
 
+    updateRepoStats(attributes, true);
+
     processAddCallbacks(*pel);
 }
 
@@ -206,6 +210,8 @@
     auto pel = findPEL(id);
     if (pel != _pelAttributes.end())
     {
+        updateRepoStats(pel->second, false);
+
         log<level::DEBUG>("Removing PEL from repository",
                           entry("PEL_ID=0x%X", pel->first.pelID.id),
                           entry("OBMC_LOG_ID=%d", pel->first.obmcID.id));
@@ -425,5 +431,73 @@
     }
 }
 
+bool Repository::isServiceableSev(const PELAttributes& pel)
+{
+    auto sevType = static_cast<SeverityType>(pel.severity & 0xF0);
+    auto sevPVEntry =
+        pel_values::findByValue(pel.severity, pel_values::severityValues);
+    std::string sevName = std::get<pel_values::registryNamePos>(*sevPVEntry);
+
+    bool check1 = (sevType == SeverityType::predictive) ||
+                  (sevType == SeverityType::unrecoverable) ||
+                  (sevType == SeverityType::critical);
+
+    bool check2 = ((sevType == SeverityType::recovered) ||
+                   (sevName == "symptom_recovered")) &&
+                  !pel.actionFlags.test(hiddenFlagBit);
+
+    bool check3 = (sevName == "symptom_predictive") ||
+                  (sevName == "symptom_unrecoverable") ||
+                  (sevName == "symptom_critical");
+
+    return check1 || check2 || check3;
+}
+
+void Repository::updateRepoStats(const PELAttributes& pel, bool pelAdded)
+{
+    auto isServiceable = Repository::isServiceableSev(pel);
+    auto bmcPEL = CreatorID::openBMC == static_cast<CreatorID>(pel.creator);
+
+    auto adjustSize = [pelAdded, &pel](auto& runningSize) {
+        if (pelAdded)
+        {
+            runningSize += pel.sizeOnDisk;
+        }
+        else
+        {
+            runningSize = std::max(static_cast<int64_t>(runningSize) -
+                                       static_cast<int64_t>(pel.sizeOnDisk),
+                                   static_cast<int64_t>(0));
+        }
+    };
+
+    adjustSize(_sizes.total);
+
+    if (bmcPEL)
+    {
+        adjustSize(_sizes.bmc);
+        if (isServiceable)
+        {
+            adjustSize(_sizes.bmcServiceable);
+        }
+        else
+        {
+            adjustSize(_sizes.bmcInfo);
+        }
+    }
+    else
+    {
+        adjustSize(_sizes.nonBMC);
+        if (isServiceable)
+        {
+            adjustSize(_sizes.nonBMCServiceable);
+        }
+        else
+        {
+            adjustSize(_sizes.nonBMCInfo);
+        }
+    }
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/repository.hpp b/extensions/openpower-pels/repository.hpp
index ecfcf7e..e9b6a27 100644
--- a/extensions/openpower-pels/repository.hpp
+++ b/extensions/openpower-pels/repository.hpp
@@ -112,6 +112,27 @@
         }
     };
 
+    /**
+     * @brief A structure for keeping a breakdown of the sizes of PELs
+     *        of different types in the repository.
+     */
+    struct SizeStats
+    {
+        uint64_t total;
+        uint64_t bmc;
+        uint64_t nonBMC;
+        uint64_t bmcServiceable;
+        uint64_t bmcInfo;
+        uint64_t nonBMCServiceable;
+        uint64_t nonBMCInfo;
+
+        SizeStats() :
+            total(0), bmc(0), nonBMC(0), bmcServiceable(0), bmcInfo(0),
+            nonBMCServiceable(0), nonBMCInfo(0)
+        {
+        }
+    };
+
     Repository() = delete;
     ~Repository() = default;
     Repository(const Repository&) = default;
@@ -313,6 +334,25 @@
      */
     void setPELHMCTransState(uint32_t pelID, TransmissionState state);
 
+    /**
+     * @brief Returns the size stats structure
+     *
+     * @return const SizeStats& - The stats structure
+     */
+    const SizeStats& getSizeStats() const
+    {
+        return _sizes;
+    }
+
+    /**
+     * @brief Says if the PEL is considered serviceable (not just
+     *        informational) as determined by its severity.
+     *
+     * @param[in] pel - The PELAttributes entry for the PEL
+     * @return bool - If serviceable or not
+     */
+    static bool isServiceableSev(const PELAttributes& pel);
+
   private:
     using PELUpdateFunc = std::function<void(PEL&)>;
 
@@ -372,6 +412,15 @@
     void write(const PEL& pel, const std::filesystem::path& path);
 
     /**
+     * @brief Updates the repository statistics after a PEL is
+     *        added or removed.
+     *
+     * @param[in] pel - The PELAttributes entry for the PEL
+     * @param[in] pelAdded - true if the PEL was added, false if removed
+     */
+    void updateRepoStats(const PELAttributes& pel, bool pelAdded);
+
+    /**
      * @brief The filesystem path to the PEL logs.
      */
     const std::filesystem::path _logPath;
@@ -402,6 +451,11 @@
      *        before pruning.
      */
     const size_t _maxNumPELs;
+
+    /**
+     * @brief Statistics on the sizes of the stored PELs.
+     */
+    SizeStats _sizes;
 };
 
 } // namespace pels
diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp
index 446e10e..ae88551 100644
--- a/test/openpower-pels/repository_test.cpp
+++ b/test/openpower-pels/repository_test.cpp
@@ -434,3 +434,157 @@
 
     EXPECT_FALSE(fd);
 }
+
+// Test the repo size statistics
+TEST_F(RepositoryTest, TestRepoSizes)
+{
+    uint32_t id = 1;
+
+    Repository repo{repoPath, 10000, 500};
+
+    // All of the size stats are the sizes on disk a PEL takes up,
+    // which is different than the file size.  Disk usage seems
+    // to have a granularity of 4096 bytes.  This probably shouldn't
+    // be hardcoded, but I don't know how to look it up dynamically.
+
+    // All sizes are zero
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 0);
+        EXPECT_EQ(stats.bmc, 0);
+        EXPECT_EQ(stats.nonBMC, 0);
+        EXPECT_EQ(stats.bmcServiceable, 0);
+        EXPECT_EQ(stats.bmcInfo, 0);
+        EXPECT_EQ(stats.nonBMCServiceable, 0);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Add a 2000B BMC predictive error
+    auto data = pelFactory(id++, 'O', 0x20, 0x8800, 2000);
+    auto pel = std::make_unique<PEL>(data);
+    auto pelID1 = pel->id();
+    repo.add(pel);
+
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 4096);
+        EXPECT_EQ(stats.bmc, 4096);
+        EXPECT_EQ(stats.nonBMC, 0);
+        EXPECT_EQ(stats.bmcServiceable, 4096);
+        EXPECT_EQ(stats.bmcInfo, 0);
+        EXPECT_EQ(stats.nonBMCServiceable, 0);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Add a 5000B BMC informational error
+    data = pelFactory(id++, 'O', 0x00, 0x8800, 5000);
+    pel = std::make_unique<PEL>(data);
+    auto pelID2 = pel->id();
+    repo.add(pel);
+
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 4096 + 8192);
+        EXPECT_EQ(stats.bmc, 4096 + 8192);
+        EXPECT_EQ(stats.nonBMC, 0);
+        EXPECT_EQ(stats.bmcServiceable, 4096);
+        EXPECT_EQ(stats.bmcInfo, 8192);
+        EXPECT_EQ(stats.nonBMCServiceable, 0);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Add a 4000B Hostboot unrecoverable error
+    data = pelFactory(id++, 'B', 0x40, 0x8800, 4000);
+    pel = std::make_unique<PEL>(data);
+    auto pelID3 = pel->id();
+    repo.add(pel);
+
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 4096 + 8192 + 4096);
+        EXPECT_EQ(stats.bmc, 4096 + 8192);
+        EXPECT_EQ(stats.nonBMC, 4096);
+        EXPECT_EQ(stats.bmcServiceable, 4096);
+        EXPECT_EQ(stats.bmcInfo, 8192);
+        EXPECT_EQ(stats.nonBMCServiceable, 4096);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Add a 5000B Hostboot informational error
+    data = pelFactory(id++, 'B', 0x00, 0x8800, 5000);
+    pel = std::make_unique<PEL>(data);
+    auto pelID4 = pel->id();
+    repo.add(pel);
+
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 4096 + 8192 + 4096 + 8192);
+        EXPECT_EQ(stats.bmc, 4096 + 8192);
+        EXPECT_EQ(stats.nonBMC, 4096 + 8192);
+        EXPECT_EQ(stats.bmcServiceable, 4096);
+        EXPECT_EQ(stats.bmcInfo, 8192);
+        EXPECT_EQ(stats.nonBMCServiceable, 4096);
+        EXPECT_EQ(stats.nonBMCInfo, 8192);
+    }
+
+    // Remove the BMC serviceable error
+    using ID = Repository::LogID;
+    ID id1{ID::Pel(pelID1)};
+
+    repo.remove(id1);
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 8192 + 4096 + 8192);
+        EXPECT_EQ(stats.bmc, 8192);
+        EXPECT_EQ(stats.nonBMC, 4096 + 8192);
+        EXPECT_EQ(stats.bmcServiceable, 0);
+        EXPECT_EQ(stats.bmcInfo, 8192);
+        EXPECT_EQ(stats.nonBMCServiceable, 4096);
+        EXPECT_EQ(stats.nonBMCInfo, 8192);
+    }
+
+    // Remove the Hostboot informational error
+    ID id4{ID::Pel(pelID4)};
+
+    repo.remove(id4);
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 8192 + 4096);
+        EXPECT_EQ(stats.bmc, 8192);
+        EXPECT_EQ(stats.nonBMC, 4096);
+        EXPECT_EQ(stats.bmcServiceable, 0);
+        EXPECT_EQ(stats.bmcInfo, 8192);
+        EXPECT_EQ(stats.nonBMCServiceable, 4096);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Remove the BMC informational error
+    ID id2{ID::Pel(pelID2)};
+
+    repo.remove(id2);
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 4096);
+        EXPECT_EQ(stats.bmc, 0);
+        EXPECT_EQ(stats.nonBMC, 4096);
+        EXPECT_EQ(stats.bmcServiceable, 0);
+        EXPECT_EQ(stats.bmcInfo, 0);
+        EXPECT_EQ(stats.nonBMCServiceable, 4096);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+
+    // Remove the hostboot unrecoverable error
+    ID id3{ID::Pel(pelID3)};
+
+    repo.remove(id3);
+    {
+        const auto& stats = repo.getSizeStats();
+        EXPECT_EQ(stats.total, 0);
+        EXPECT_EQ(stats.bmc, 0);
+        EXPECT_EQ(stats.nonBMC, 0);
+        EXPECT_EQ(stats.bmcServiceable, 0);
+        EXPECT_EQ(stats.bmcInfo, 0);
+        EXPECT_EQ(stats.nonBMCServiceable, 0);
+        EXPECT_EQ(stats.nonBMCInfo, 0);
+    }
+}