xref: /openbmc/phosphor-fan-presence/sensor-monitor/alarm_timestamps.hpp (revision 32c4feff9b1cd1fd4d75bb530761f70f0e5922e0)
1 /**
2  * Copyright © 2021 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 #include "config.h"
18 
19 #include "types.hpp"
20 
21 #include <cereal/archives/json.hpp>
22 #include <cereal/types/string.hpp>
23 #include <cereal/types/tuple.hpp>
24 #include <cereal/types/vector.hpp>
25 #include <phosphor-logging/lg2.hpp>
26 #include <sdeventplus/clock.hpp>
27 #include <sdeventplus/utility/timer.hpp>
28 
29 #include <filesystem>
30 #include <fstream>
31 #include <map>
32 #include <tuple>
33 
34 namespace sensor::monitor
35 {
36 
37 /**
38  * @class AlarmTimestamps
39  *
40  * This class keeps track of the timestamps at which the shutdown
41  * timers are started in case the process or whole BMC restarts
42  * while a timer is running.  In the case where the process starts
43  * when a timer was previously running and an alarm is still active,
44  * a new timer can be started with just the remaining time.
45  */
46 class AlarmTimestamps
47 {
48   public:
49     ~AlarmTimestamps() = default;
50     AlarmTimestamps(const AlarmTimestamps&) = delete;
51     AlarmTimestamps& operator=(const AlarmTimestamps&) = delete;
52     AlarmTimestamps(AlarmTimestamps&&) = delete;
53     AlarmTimestamps& operator=(AlarmTimestamps&&) = delete;
54 
55     /**
56      * @brief Constructor
57      *
58      * Loads any saved timestamps
59      */
AlarmTimestamps()60     AlarmTimestamps()
61     {
62         load();
63     }
64 
65     /**
66      * @brief Adds an entry to the timestamps map and persists it.
67      *
68      * @param[in] key - The AlarmKey value
69      * @param[in] timestamp - The start timestamp to save
70      */
add(const AlarmKey & key,uint64_t timestamp)71     void add(const AlarmKey& key, uint64_t timestamp)
72     {
73         // Emplace won't do anything if an entry with that
74         // key was already present, so only save if an actual
75         // entry was added.
76         auto result = timestamps.emplace(key, timestamp);
77         if (result.second)
78         {
79             save();
80         }
81     }
82 
83     /**
84      * @brief Erase an entry using the passed in alarm key.
85      *
86      * @param[in] key - The AlarmKey value
87      */
erase(const AlarmKey & key)88     void erase(const AlarmKey& key)
89     {
90         size_t removed = timestamps.erase(key);
91         if (removed)
92         {
93             save();
94         }
95     }
96 
97     /**
98      * @brief Erase an entry using an iterator.
99      */
erase(std::map<AlarmKey,uint64_t>::const_iterator & entry)100     void erase(std::map<AlarmKey, uint64_t>::const_iterator& entry)
101     {
102         timestamps.erase(entry);
103         save();
104     }
105 
106     /**
107      * @brief Clear all entries.
108      */
clear()109     void clear()
110     {
111         if (!timestamps.empty())
112         {
113             timestamps.clear();
114             save();
115         }
116     }
117 
118     /**
119      * @brief Remove any entries for which there is not a running timer
120      *        for.  This is used on startup when an alarm could have cleared
121      *        during a restart to get rid of the old entries.
122      *
123      * @param[in] alarms - The current alarms map.
124      */
prune(const std::map<AlarmKey,std::unique_ptr<sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>> & alarms)125     void prune(
126         const std::map<AlarmKey, std::unique_ptr<sdeventplus::utility::Timer<
127                                      sdeventplus::ClockId::Monotonic>>>& alarms)
128     {
129         auto size = timestamps.size();
130 
131         auto isTimerStopped = [&alarms](const AlarmKey& key) {
132             auto alarm = alarms.find(key);
133             if (alarm != alarms.end())
134             {
135                 auto& timer = alarm->second;
136                 if (timer && timer->isEnabled())
137                 {
138                     return false;
139                 }
140             }
141             return true;
142         };
143 
144         auto it = timestamps.begin();
145 
146         while (it != timestamps.end())
147         {
148             if (isTimerStopped(it->first))
149             {
150                 it = timestamps.erase(it);
151             }
152             else
153             {
154                 ++it;
155             }
156         }
157 
158         if (size != timestamps.size())
159         {
160             save();
161         }
162     }
163 
164     /**
165      * @brief Returns the timestamps map
166      */
get() const167     const std::map<AlarmKey, uint64_t>& get() const
168     {
169         return timestamps;
170     }
171 
172     /**
173      * @brief Saves the timestamps map in the filesystem using cereal.
174      *
175      * Since cereal doesn't understand the AlarmType or ShutdownType
176      * enums, they are converted to ints before being written.
177      */
save()178     void save()
179     {
180         std::filesystem::path path =
181             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
182             timestampsFilename;
183 
184         if (!std::filesystem::exists(path.parent_path()))
185         {
186             std::filesystem::create_directory(path.parent_path());
187         }
188 
189         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
190 
191         for (const auto& [key, time] : timestamps)
192         {
193             times.emplace_back(std::get<std::string>(key),
194                                static_cast<int>(std::get<ShutdownType>(key)),
195                                static_cast<int>(std::get<AlarmType>(key)),
196                                time);
197         }
198 
199         std::ofstream stream{path.c_str()};
200         cereal::JSONOutputArchive oarchive{stream};
201 
202         oarchive(times);
203     }
204 
205   private:
206     static constexpr auto timestampsFilename = "shutdownAlarmStartTimes";
207 
208     /**
209      * @brief Loads the saved timestamps from the filesystem.
210      *
211      * As with save(), cereal doesn't understand the ShutdownType or AlarmType
212      * enums so they have to have been saved as ints and converted.
213      */
load()214     void load()
215     {
216         std::vector<std::tuple<std::string, int, int, uint64_t>> times;
217 
218         std::filesystem::path path =
219             std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
220             timestampsFilename;
221 
222         if (!std::filesystem::exists(path))
223         {
224             return;
225         }
226 
227         try
228         {
229             std::ifstream stream{path.c_str()};
230             cereal::JSONInputArchive iarchive{stream};
231             iarchive(times);
232 
233             for (const auto& [path, shutdownType, alarmType, timestamp] : times)
234             {
235                 timestamps.emplace(
236                     AlarmKey{path, static_cast<ShutdownType>(shutdownType),
237                              static_cast<AlarmType>(alarmType)},
238                     timestamp);
239             }
240         }
241         catch (const std::exception& e)
242         {
243             // Include possible exception when removing file, otherwise ec = 0
244             std::error_code ec;
245             std::filesystem::remove(path, ec);
246             lg2::error("Unable to restore persisted times ({ERROR}, ec: {EC})",
247                        "ERROR", e, "EC", ec.value());
248         }
249     }
250 
251     /**
252      * @brief The map of AlarmKeys and time start times.
253      */
254     std::map<AlarmKey, uint64_t> timestamps;
255 };
256 
257 } // namespace sensor::monitor
258