xref: /openbmc/phosphor-fan-presence/sensor-monitor/alarm_timestamps.hpp (revision 32c4feff9b1cd1fd4d75bb530761f70f0e5922e0)
108a66efaSMatt Spinler /**
208a66efaSMatt Spinler  * Copyright © 2021 IBM Corporation
308a66efaSMatt Spinler  *
408a66efaSMatt Spinler  * Licensed under the Apache License, Version 2.0 (the "License");
508a66efaSMatt Spinler  * you may not use this file except in compliance with the License.
608a66efaSMatt Spinler  * You may obtain a copy of the License at
708a66efaSMatt Spinler  *
808a66efaSMatt Spinler  *     http://www.apache.org/licenses/LICENSE-2.0
908a66efaSMatt Spinler  *
1008a66efaSMatt Spinler  * Unless required by applicable law or agreed to in writing, software
1108a66efaSMatt Spinler  * distributed under the License is distributed on an "AS IS" BASIS,
1208a66efaSMatt Spinler  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1308a66efaSMatt Spinler  * See the License for the specific language governing permissions and
1408a66efaSMatt Spinler  * limitations under the License.
1508a66efaSMatt Spinler  */
1608a66efaSMatt Spinler #pragma once
1708a66efaSMatt Spinler #include "config.h"
1808a66efaSMatt Spinler 
1908a66efaSMatt Spinler #include "types.hpp"
2008a66efaSMatt Spinler 
2108a66efaSMatt Spinler #include <cereal/archives/json.hpp>
2208a66efaSMatt Spinler #include <cereal/types/string.hpp>
2308a66efaSMatt Spinler #include <cereal/types/tuple.hpp>
2408a66efaSMatt Spinler #include <cereal/types/vector.hpp>
25*32c4feffSAnwaar Hadi #include <phosphor-logging/lg2.hpp>
2608a66efaSMatt Spinler #include <sdeventplus/clock.hpp>
2708a66efaSMatt Spinler #include <sdeventplus/utility/timer.hpp>
2808a66efaSMatt Spinler 
2908a66efaSMatt Spinler #include <filesystem>
3008a66efaSMatt Spinler #include <fstream>
3108a66efaSMatt Spinler #include <map>
3208a66efaSMatt Spinler #include <tuple>
3308a66efaSMatt Spinler 
3408a66efaSMatt Spinler namespace sensor::monitor
3508a66efaSMatt Spinler {
3608a66efaSMatt Spinler 
3708a66efaSMatt Spinler /**
3808a66efaSMatt Spinler  * @class AlarmTimestamps
3908a66efaSMatt Spinler  *
4008a66efaSMatt Spinler  * This class keeps track of the timestamps at which the shutdown
4108a66efaSMatt Spinler  * timers are started in case the process or whole BMC restarts
4208a66efaSMatt Spinler  * while a timer is running.  In the case where the process starts
4308a66efaSMatt Spinler  * when a timer was previously running and an alarm is still active,
4408a66efaSMatt Spinler  * a new timer can be started with just the remaining time.
4508a66efaSMatt Spinler  */
4608a66efaSMatt Spinler class AlarmTimestamps
4708a66efaSMatt Spinler {
4808a66efaSMatt Spinler   public:
4908a66efaSMatt Spinler     ~AlarmTimestamps() = default;
5008a66efaSMatt Spinler     AlarmTimestamps(const AlarmTimestamps&) = delete;
5108a66efaSMatt Spinler     AlarmTimestamps& operator=(const AlarmTimestamps&) = delete;
5208a66efaSMatt Spinler     AlarmTimestamps(AlarmTimestamps&&) = delete;
5308a66efaSMatt Spinler     AlarmTimestamps& operator=(AlarmTimestamps&&) = delete;
5408a66efaSMatt Spinler 
5508a66efaSMatt Spinler     /**
5608a66efaSMatt Spinler      * @brief Constructor
5708a66efaSMatt Spinler      *
5808a66efaSMatt Spinler      * Loads any saved timestamps
5908a66efaSMatt Spinler      */
AlarmTimestamps()6008a66efaSMatt Spinler     AlarmTimestamps()
6108a66efaSMatt Spinler     {
6208a66efaSMatt Spinler         load();
6308a66efaSMatt Spinler     }
6408a66efaSMatt Spinler 
6508a66efaSMatt Spinler     /**
6608a66efaSMatt Spinler      * @brief Adds an entry to the timestamps map and persists it.
6708a66efaSMatt Spinler      *
6808a66efaSMatt Spinler      * @param[in] key - The AlarmKey value
6908a66efaSMatt Spinler      * @param[in] timestamp - The start timestamp to save
7008a66efaSMatt Spinler      */
add(const AlarmKey & key,uint64_t timestamp)7108a66efaSMatt Spinler     void add(const AlarmKey& key, uint64_t timestamp)
7208a66efaSMatt Spinler     {
7308a66efaSMatt Spinler         // Emplace won't do anything if an entry with that
7408a66efaSMatt Spinler         // key was already present, so only save if an actual
7508a66efaSMatt Spinler         // entry was added.
7608a66efaSMatt Spinler         auto result = timestamps.emplace(key, timestamp);
7708a66efaSMatt Spinler         if (result.second)
7808a66efaSMatt Spinler         {
7908a66efaSMatt Spinler             save();
8008a66efaSMatt Spinler         }
8108a66efaSMatt Spinler     }
8208a66efaSMatt Spinler 
8308a66efaSMatt Spinler     /**
8408a66efaSMatt Spinler      * @brief Erase an entry using the passed in alarm key.
8508a66efaSMatt Spinler      *
8608a66efaSMatt Spinler      * @param[in] key - The AlarmKey value
8708a66efaSMatt Spinler      */
erase(const AlarmKey & key)8808a66efaSMatt Spinler     void erase(const AlarmKey& key)
8908a66efaSMatt Spinler     {
9008a66efaSMatt Spinler         size_t removed = timestamps.erase(key);
9108a66efaSMatt Spinler         if (removed)
9208a66efaSMatt Spinler         {
9308a66efaSMatt Spinler             save();
9408a66efaSMatt Spinler         }
9508a66efaSMatt Spinler     }
9608a66efaSMatt Spinler 
9708a66efaSMatt Spinler     /**
9808a66efaSMatt Spinler      * @brief Erase an entry using an iterator.
9908a66efaSMatt Spinler      */
erase(std::map<AlarmKey,uint64_t>::const_iterator & entry)10008a66efaSMatt Spinler     void erase(std::map<AlarmKey, uint64_t>::const_iterator& entry)
10108a66efaSMatt Spinler     {
10208a66efaSMatt Spinler         timestamps.erase(entry);
10308a66efaSMatt Spinler         save();
10408a66efaSMatt Spinler     }
10508a66efaSMatt Spinler 
10608a66efaSMatt Spinler     /**
10708a66efaSMatt Spinler      * @brief Clear all entries.
10808a66efaSMatt Spinler      */
clear()10908a66efaSMatt Spinler     void clear()
11008a66efaSMatt Spinler     {
11108a66efaSMatt Spinler         if (!timestamps.empty())
11208a66efaSMatt Spinler         {
11308a66efaSMatt Spinler             timestamps.clear();
11408a66efaSMatt Spinler             save();
11508a66efaSMatt Spinler         }
11608a66efaSMatt Spinler     }
11708a66efaSMatt Spinler 
11808a66efaSMatt Spinler     /**
11908a66efaSMatt Spinler      * @brief Remove any entries for which there is not a running timer
12008a66efaSMatt Spinler      *        for.  This is used on startup when an alarm could have cleared
12108a66efaSMatt Spinler      *        during a restart to get rid of the old entries.
12208a66efaSMatt Spinler      *
12308a66efaSMatt Spinler      * @param[in] alarms - The current alarms map.
12408a66efaSMatt Spinler      */
prune(const std::map<AlarmKey,std::unique_ptr<sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>> & alarms)12508a66efaSMatt Spinler     void prune(
12608a66efaSMatt Spinler         const std::map<AlarmKey, std::unique_ptr<sdeventplus::utility::Timer<
12708a66efaSMatt Spinler                                      sdeventplus::ClockId::Monotonic>>>& alarms)
12808a66efaSMatt Spinler     {
12908a66efaSMatt Spinler         auto size = timestamps.size();
13008a66efaSMatt Spinler 
13108a66efaSMatt Spinler         auto isTimerStopped = [&alarms](const AlarmKey& key) {
13208a66efaSMatt Spinler             auto alarm = alarms.find(key);
13308a66efaSMatt Spinler             if (alarm != alarms.end())
13408a66efaSMatt Spinler             {
13508a66efaSMatt Spinler                 auto& timer = alarm->second;
13608a66efaSMatt Spinler                 if (timer && timer->isEnabled())
13708a66efaSMatt Spinler                 {
13808a66efaSMatt Spinler                     return false;
13908a66efaSMatt Spinler                 }
14008a66efaSMatt Spinler             }
14108a66efaSMatt Spinler             return true;
14208a66efaSMatt Spinler         };
14308a66efaSMatt Spinler 
14408a66efaSMatt Spinler         auto it = timestamps.begin();
14508a66efaSMatt Spinler 
14608a66efaSMatt Spinler         while (it != timestamps.end())
14708a66efaSMatt Spinler         {
14808a66efaSMatt Spinler             if (isTimerStopped(it->first))
14908a66efaSMatt Spinler             {
15008a66efaSMatt Spinler                 it = timestamps.erase(it);
15108a66efaSMatt Spinler             }
15208a66efaSMatt Spinler             else
15308a66efaSMatt Spinler             {
15408a66efaSMatt Spinler                 ++it;
15508a66efaSMatt Spinler             }
15608a66efaSMatt Spinler         }
15708a66efaSMatt Spinler 
15808a66efaSMatt Spinler         if (size != timestamps.size())
15908a66efaSMatt Spinler         {
16008a66efaSMatt Spinler             save();
16108a66efaSMatt Spinler         }
16208a66efaSMatt Spinler     }
16308a66efaSMatt Spinler 
16408a66efaSMatt Spinler     /**
16508a66efaSMatt Spinler      * @brief Returns the timestamps map
16608a66efaSMatt Spinler      */
get() const16708a66efaSMatt Spinler     const std::map<AlarmKey, uint64_t>& get() const
16808a66efaSMatt Spinler     {
16908a66efaSMatt Spinler         return timestamps;
17008a66efaSMatt Spinler     }
17108a66efaSMatt Spinler 
17208a66efaSMatt Spinler     /**
17308a66efaSMatt Spinler      * @brief Saves the timestamps map in the filesystem using cereal.
17408a66efaSMatt Spinler      *
17508a66efaSMatt Spinler      * Since cereal doesn't understand the AlarmType or ShutdownType
17608a66efaSMatt Spinler      * enums, they are converted to ints before being written.
17708a66efaSMatt Spinler      */
save()17808a66efaSMatt Spinler     void save()
17908a66efaSMatt Spinler     {
18008a66efaSMatt Spinler         std::filesystem::path path =
18108a66efaSMatt Spinler             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
18208a66efaSMatt Spinler             timestampsFilename;
18308a66efaSMatt Spinler 
18408a66efaSMatt Spinler         if (!std::filesystem::exists(path.parent_path()))
18508a66efaSMatt Spinler         {
18608a66efaSMatt Spinler             std::filesystem::create_directory(path.parent_path());
18708a66efaSMatt Spinler         }
18808a66efaSMatt Spinler 
18908a66efaSMatt Spinler         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
19008a66efaSMatt Spinler 
19108a66efaSMatt Spinler         for (const auto& [key, time] : timestamps)
19208a66efaSMatt Spinler         {
19308a66efaSMatt Spinler             times.emplace_back(std::get<std::string>(key),
19408a66efaSMatt Spinler                                static_cast<int>(std::get<ShutdownType>(key)),
19508a66efaSMatt Spinler                                static_cast<int>(std::get<AlarmType>(key)),
19608a66efaSMatt Spinler                                time);
19708a66efaSMatt Spinler         }
19808a66efaSMatt Spinler 
19908a66efaSMatt Spinler         std::ofstream stream{path.c_str()};
20008a66efaSMatt Spinler         cereal::JSONOutputArchive oarchive{stream};
20108a66efaSMatt Spinler 
20208a66efaSMatt Spinler         oarchive(times);
20308a66efaSMatt Spinler     }
20408a66efaSMatt Spinler 
20508a66efaSMatt Spinler   private:
20608a66efaSMatt Spinler     static constexpr auto timestampsFilename = "shutdownAlarmStartTimes";
20708a66efaSMatt Spinler 
20808a66efaSMatt Spinler     /**
20908a66efaSMatt Spinler      * @brief Loads the saved timestamps from the filesystem.
21008a66efaSMatt Spinler      *
21108a66efaSMatt Spinler      * As with save(), cereal doesn't understand the ShutdownType or AlarmType
21208a66efaSMatt Spinler      * enums so they have to have been saved as ints and converted.
21308a66efaSMatt Spinler      */
load()21408a66efaSMatt Spinler     void load()
21508a66efaSMatt Spinler     {
21608a66efaSMatt Spinler         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
21708a66efaSMatt Spinler 
21808a66efaSMatt Spinler         std::filesystem::path path =
21908a66efaSMatt Spinler             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
22008a66efaSMatt Spinler             timestampsFilename;
22108a66efaSMatt Spinler 
22208a66efaSMatt Spinler         if (!std::filesystem::exists(path))
22308a66efaSMatt Spinler         {
22408a66efaSMatt Spinler             return;
22508a66efaSMatt Spinler         }
22608a66efaSMatt Spinler 
2270a56d459SGeorge Liu         try
2280a56d459SGeorge Liu         {
22908a66efaSMatt Spinler             std::ifstream stream{path.c_str()};
23008a66efaSMatt Spinler             cereal::JSONInputArchive iarchive{stream};
23108a66efaSMatt Spinler             iarchive(times);
23208a66efaSMatt Spinler 
23308a66efaSMatt Spinler             for (const auto& [path, shutdownType, alarmType, timestamp] : times)
23408a66efaSMatt Spinler             {
2350a56d459SGeorge Liu                 timestamps.emplace(
2360a56d459SGeorge Liu                     AlarmKey{path, static_cast<ShutdownType>(shutdownType),
23708a66efaSMatt Spinler                              static_cast<AlarmType>(alarmType)},
23808a66efaSMatt Spinler                     timestamp);
23908a66efaSMatt Spinler             }
24008a66efaSMatt Spinler         }
2410a56d459SGeorge Liu         catch (const std::exception& e)
2420a56d459SGeorge Liu         {
2430a56d459SGeorge Liu             // Include possible exception when removing file, otherwise ec = 0
2440a56d459SGeorge Liu             std::error_code ec;
245a899aa0cSMatt Spinler             std::filesystem::remove(path, ec);
246*32c4feffSAnwaar Hadi             lg2::error("Unable to restore persisted times ({ERROR}, ec: {EC})",
247*32c4feffSAnwaar Hadi                        "ERROR", e, "EC", ec.value());
2480a56d459SGeorge Liu         }
2490a56d459SGeorge Liu     }
25008a66efaSMatt Spinler 
25108a66efaSMatt Spinler     /**
25208a66efaSMatt Spinler      * @brief The map of AlarmKeys and time start times.
25308a66efaSMatt Spinler      */
25408a66efaSMatt Spinler     std::map<AlarmKey, uint64_t> timestamps;
25508a66efaSMatt Spinler };
25608a66efaSMatt Spinler 
25708a66efaSMatt Spinler } // namespace sensor::monitor
258