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
diff --git a/chassis_state_manager.hpp b/chassis_state_manager.hpp
index 5532949..9a6b647 100644
--- a/chassis_state_manager.hpp
+++ b/chassis_state_manager.hpp
@@ -61,6 +61,8 @@
{
subscribeToSystemdSignals();
+ restoreChassisStateChangeTime();
+
determineInitialState();
restorePOHCounter(); // restore POHCounter from persisted file
@@ -157,6 +159,35 @@
*/
bool deserializePOH(const fs::path& path, uint32_t& retCounter);
+ /** @brief Sets the LastStateChangeTime property and persists it. */
+ void setStateChangeTime();
+
+ /** @brief Serialize the last power state change time.
+ *
+ * Save the time the state changed and the state itself.
+ * The state needs to be saved as well so that during rediscovery
+ * on reboots there's a way to know not to update the time again.
+ */
+ void serializeStateChangeTime();
+
+ /** @brief Deserialize the last power state change time.
+ *
+ * @param[out] time - Deserialized time
+ * @param[out] state - Deserialized power state
+ *
+ * @return bool - true if successful, false otherwise.
+ */
+ bool deserializeStateChangeTime(uint64_t& time, PowerState& state);
+
+ /** @brief Restores the power state change time.
+ *
+ * The time is loaded into the LastStateChangeTime D-Bus property.
+ * On the very first start after this code has been applied but
+ * before the state has changed, the LastStateChangeTime value
+ * will be zero.
+ */
+ void restoreChassisStateChangeTime();
+
/** @brief Timer */
std::unique_ptr<phosphor::state::manager::Timer> timer;
};
diff --git a/configure.ac b/configure.ac
index 838aaf2..f1d3a2f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,12 @@
AC_DEFINE_UNQUOTED([POH_COUNTER_PERSIST_PATH], ["$POH_COUNTER_PERSIST_PATH"], \
[Path of file for storing POH counter.])
+AC_ARG_VAR(CHASSIS_STATE_CHANGE_PERSIST_PATH, [Path of file for storing the state change time.])
+AS_IF([test "x$CHASSIS_STATE_CHANGE_PERSIST_PATH" == "x"], \
+ [CHASSIS_STATE_CHANGE_PERSIST_PATH="/var/lib/phosphor-state-manager/chassisStateChangeTime"])
+AC_DEFINE_UNQUOTED([CHASSIS_STATE_CHANGE_PERSIST_PATH], ["$CHASSIS_STATE_CHANGE_PERSIST_PATH"], \
+ [Path of file for storing the state change time.])
+
AC_ARG_VAR(BOOT_COUNT_MAX_ALLOWED, [The maximum allowed reboot count])
AS_IF([test "x$BOOT_COUNT_MAX_ALLOWED" == "x"], [BOOT_COUNT_MAX_ALLOWED=3])
AC_DEFINE_UNQUOTED([BOOT_COUNT_MAX_ALLOWED], [$BOOT_COUNT_MAX_ALLOWED], [The maximum allowed reboot count])