Add LastStateChangeTime to chassis manager

This property is set to the timestamp of the last time
the chassis power state changed.  It is persisted
so it survives reboots.

Resolves openbmc/openbmc#3300

Tested:  Various incantations of power cycling, reboots,
         and AC pulls.

Change-Id: I19f244e0490bc9b921454e393989a9cbd283e2dd
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/chassis_state_manager.cpp b/chassis_state_manager.cpp
index a723d8f..a99aec9 100644
--- a/chassis_state_manager.cpp
+++ b/chassis_state_manager.cpp
@@ -95,6 +95,22 @@
             server::Chassis::requestedPowerTransition(Transition::On);
             return;
         }
+        else
+        {
+            // The system is off.  If we think it should be on then
+            // we probably lost AC while up, so set a new state
+            // change time.
+            uint64_t lastTime;
+            PowerState lastState;
+
+            if (deserializeStateChangeTime(lastTime, lastState))
+            {
+                if (lastState == PowerState::On)
+                {
+                    setStateChangeTime();
+                }
+            }
+        }
     }
     catch (const SdBusError& e)
     {
@@ -220,6 +236,7 @@
     {
         log<level::INFO>("Received signal that power OFF is complete");
         this->currentPowerState(server::Chassis::PowerState::Off);
+        this->setStateChangeTime();
     }
     else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) &&
              (newStateResult == "done") &&
@@ -227,6 +244,7 @@
     {
         log<level::INFO>("Received signal that power ON is complete");
         this->currentPowerState(server::Chassis::PowerState::On);
+        this->setStateChangeTime();
     }
 
     return 0;
@@ -367,6 +385,80 @@
     }
 }
 
+void Chassis::serializeStateChangeTime()
+{
+    fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
+    std::ofstream os(path.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+
+    oarchive(ChassisInherit::lastStateChangeTime(),
+             ChassisInherit::currentPowerState());
+}
+
+bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
+{
+    fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
+
+    try
+    {
+        if (fs::exists(path))
+        {
+            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+            cereal::JSONInputArchive iarchive(is);
+            iarchive(time, state);
+            return true;
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+        fs::remove(path);
+    }
+
+    return false;
+}
+
+void Chassis::restoreChassisStateChangeTime()
+{
+    uint64_t time;
+    PowerState state;
+
+    if (!deserializeStateChangeTime(time, state))
+    {
+        ChassisInherit::lastStateChangeTime(0);
+    }
+    else
+    {
+        ChassisInherit::lastStateChangeTime(time);
+    }
+}
+
+void Chassis::setStateChangeTime()
+{
+    using namespace std::chrono;
+    uint64_t lastTime;
+    PowerState lastState;
+
+    auto now =
+        duration_cast<milliseconds>(system_clock::now().time_since_epoch())
+            .count();
+
+    // If power is on when the BMC is rebooted, this function will get called
+    // because sysStateChange() runs.  Since the power state didn't change
+    // in this case, neither should the state change time, so check that
+    // the power state actually did change here.
+    if (deserializeStateChangeTime(lastTime, lastState))
+    {
+        if (lastState == ChassisInherit::currentPowerState())
+        {
+            return;
+        }
+    }
+
+    ChassisInherit::lastStateChangeTime(now);
+    serializeStateChangeTime();
+}
+
 } // namespace manager
 } // namespace state
 } // namepsace phosphor