Add optional sensor reading logging instrumentation

Noteworthy sensor readings,
such as the first reading,
a new minimum or maximum value,
or ending a good or bad streak of readings,
will now have some useful logging output.

This feature defaults to disabled, to enable it,
set "enableInstrumentation" constant to "true".

Tested:
It runs, and produces useful output, when enabled.
When disabled, it does nothing.

Signed-off-by: Josh Lehan <krellan@google.com>
Change-Id: I4c1b5105ad6dbb92ae6a23f2b99e2a8b68b56dca
diff --git a/include/sensor.hpp b/include/sensor.hpp
index 6fd7e43..0ef87d5 100644
--- a/include/sensor.hpp
+++ b/include/sensor.hpp
@@ -12,6 +12,10 @@
 
 constexpr size_t sensorFailedPollTimeMs = 5000;
 
+// Enable useful logging with sensor instrumentation
+// This is intentionally not DEBUG, avoid clash with usage in .cpp files
+constexpr bool enableInstrumentation = false;
+
 constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
 constexpr const char* availableInterfaceName =
     "xyz.openbmc_project.State.Decorator.Availability";
@@ -19,6 +23,17 @@
     "xyz.openbmc_project.State.Decorator.OperationalStatus";
 constexpr const size_t errorThreshold = 5;
 
+struct SensorInstrumentation
+{
+    // These are for instrumentation for debugging
+    int numCollectsGood = 0;
+    int numCollectsMiss = 0;
+    int numStreakGreats = 0;
+    int numStreakMisses = 0;
+    double minCollected = 0.0;
+    double maxCollected = 0.0;
+};
+
 struct Sensor
 {
     Sensor(const std::string& name,
@@ -32,7 +47,10 @@
         maxValue(max), minValue(min), thresholds(std::move(thresholdData)),
         hysteresisTrigger((max - min) * 0.01),
         hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
-        readState(readState), errCount(0)
+        readState(readState), errCount(0),
+        instrumentation(enableInstrumentation
+                            ? std::make_unique<SensorInstrumentation>()
+                            : nullptr)
     {}
     virtual ~Sensor() = default;
     virtual void checkThresholds(void) = 0;
@@ -57,6 +75,93 @@
     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
     PowerState readState;
     size_t errCount;
+    std::unique_ptr<SensorInstrumentation> instrumentation;
+
+    void updateInstrumentation(double readValue)
+    {
+        // Do nothing if this feature is not enabled
+        if constexpr (!enableInstrumentation)
+        {
+            return;
+        }
+        if (!instrumentation)
+        {
+            return;
+        }
+
+        // Save some typing
+        auto& inst = *instrumentation;
+
+        // Show constants if first reading (even if unsuccessful)
+        if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
+        {
+            std::cerr << "Sensor " << name << ": Configuration min=" << minValue
+                      << ", max=" << maxValue << ", type=" << objectType
+                      << ", path=" << configurationPath << "\n";
+        }
+
+        // Sensors can use "nan" to indicate unavailable reading
+        if (!std::isfinite(readValue))
+        {
+            // Only show this if beginning a new streak
+            if (inst.numStreakMisses == 0)
+            {
+                std::cerr << "Sensor " << name
+                          << ": Missing reading, Reading counts good="
+                          << inst.numCollectsGood
+                          << ", miss=" << inst.numCollectsMiss
+                          << ", Prior good streak=" << inst.numStreakGreats
+                          << "\n";
+            }
+
+            inst.numStreakGreats = 0;
+            ++(inst.numCollectsMiss);
+            ++(inst.numStreakMisses);
+
+            return;
+        }
+
+        // Only show this if beginning a new streak and not the first time
+        if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
+        {
+            std::cerr << "Sensor " << name
+                      << ": Recovered reading, Reading counts good="
+                      << inst.numCollectsGood
+                      << ", miss=" << inst.numCollectsMiss
+                      << ", Prior miss streak=" << inst.numStreakMisses << "\n";
+        }
+
+        // Initialize min/max if the first successful reading
+        if (inst.numCollectsGood == 0)
+        {
+            std::cerr << "Sensor " << name << ": First reading=" << readValue
+                      << "\n";
+
+            inst.minCollected = readValue;
+            inst.maxCollected = readValue;
+        }
+
+        inst.numStreakMisses = 0;
+        ++(inst.numCollectsGood);
+        ++(inst.numStreakGreats);
+
+        // Only provide subsequent output if new min/max established
+        if (readValue < inst.minCollected)
+        {
+            std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
+                      << "\n";
+
+            inst.minCollected = readValue;
+        }
+
+        if (readValue > inst.maxCollected)
+        {
+            std::cerr << "Sensor " << name << ": Highest reading=" << readValue
+                      << "\n";
+
+            inst.maxCollected = readValue;
+        }
+    }
 
     int setSensorValue(const double& newValue, double& oldValue)
     {
@@ -286,6 +391,7 @@
         }
 
         updateValueProperty(newValue);
+        updateInstrumentation(newValue);
 
         // Always check thresholds after changing the value,
         // as the test against hysteresisTrigger now takes place in