blob: 0c7d1489da56ace7833c6fb201b70905daf8398c [file] [log] [blame]
Matt Spinler08a66ef2021-01-21 13:46:45 -06001/**
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 <sdeventplus/clock.hpp>
26#include <sdeventplus/utility/timer.hpp>
27
28#include <filesystem>
29#include <fstream>
30#include <map>
31#include <tuple>
32
33namespace sensor::monitor
34{
35
36/**
37 * @class AlarmTimestamps
38 *
39 * This class keeps track of the timestamps at which the shutdown
40 * timers are started in case the process or whole BMC restarts
41 * while a timer is running. In the case where the process starts
42 * when a timer was previously running and an alarm is still active,
43 * a new timer can be started with just the remaining time.
44 */
45class AlarmTimestamps
46{
47 public:
48 ~AlarmTimestamps() = default;
49 AlarmTimestamps(const AlarmTimestamps&) = delete;
50 AlarmTimestamps& operator=(const AlarmTimestamps&) = delete;
51 AlarmTimestamps(AlarmTimestamps&&) = delete;
52 AlarmTimestamps& operator=(AlarmTimestamps&&) = delete;
53
54 /**
55 * @brief Constructor
56 *
57 * Loads any saved timestamps
58 */
59 AlarmTimestamps()
60 {
61 load();
62 }
63
64 /**
65 * @brief Adds an entry to the timestamps map and persists it.
66 *
67 * @param[in] key - The AlarmKey value
68 * @param[in] timestamp - The start timestamp to save
69 */
70 void add(const AlarmKey& key, uint64_t timestamp)
71 {
72 // Emplace won't do anything if an entry with that
73 // key was already present, so only save if an actual
74 // entry was added.
75 auto result = timestamps.emplace(key, timestamp);
76 if (result.second)
77 {
78 save();
79 }
80 }
81
82 /**
83 * @brief Erase an entry using the passed in alarm key.
84 *
85 * @param[in] key - The AlarmKey value
86 */
87 void erase(const AlarmKey& key)
88 {
89 size_t removed = timestamps.erase(key);
90 if (removed)
91 {
92 save();
93 }
94 }
95
96 /**
97 * @brief Erase an entry using an iterator.
98 */
99 void erase(std::map<AlarmKey, uint64_t>::const_iterator& entry)
100 {
101 timestamps.erase(entry);
102 save();
103 }
104
105 /**
106 * @brief Clear all entries.
107 */
108 void clear()
109 {
110 if (!timestamps.empty())
111 {
112 timestamps.clear();
113 save();
114 }
115 }
116
117 /**
118 * @brief Remove any entries for which there is not a running timer
119 * for. This is used on startup when an alarm could have cleared
120 * during a restart to get rid of the old entries.
121 *
122 * @param[in] alarms - The current alarms map.
123 */
124 void prune(
125 const std::map<AlarmKey, std::unique_ptr<sdeventplus::utility::Timer<
126 sdeventplus::ClockId::Monotonic>>>& alarms)
127 {
128 auto size = timestamps.size();
129
130 auto isTimerStopped = [&alarms](const AlarmKey& key) {
131 auto alarm = alarms.find(key);
132 if (alarm != alarms.end())
133 {
134 auto& timer = alarm->second;
135 if (timer && timer->isEnabled())
136 {
137 return false;
138 }
139 }
140 return true;
141 };
142
143 auto it = timestamps.begin();
144
145 while (it != timestamps.end())
146 {
147 if (isTimerStopped(it->first))
148 {
149 it = timestamps.erase(it);
150 }
151 else
152 {
153 ++it;
154 }
155 }
156
157 if (size != timestamps.size())
158 {
159 save();
160 }
161 }
162
163 /**
164 * @brief Returns the timestamps map
165 */
166 const std::map<AlarmKey, uint64_t>& get() const
167 {
168 return timestamps;
169 }
170
171 /**
172 * @brief Saves the timestamps map in the filesystem using cereal.
173 *
174 * Since cereal doesn't understand the AlarmType or ShutdownType
175 * enums, they are converted to ints before being written.
176 */
177 void save()
178 {
179 std::filesystem::path path =
180 std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
181 timestampsFilename;
182
183 if (!std::filesystem::exists(path.parent_path()))
184 {
185 std::filesystem::create_directory(path.parent_path());
186 }
187
188 std::vector<std::tuple<std::string, int, int, uint64_t>> times;
189
190 for (const auto& [key, time] : timestamps)
191 {
192 times.emplace_back(std::get<std::string>(key),
193 static_cast<int>(std::get<ShutdownType>(key)),
194 static_cast<int>(std::get<AlarmType>(key)),
195 time);
196 }
197
198 std::ofstream stream{path.c_str()};
199 cereal::JSONOutputArchive oarchive{stream};
200
201 oarchive(times);
202 }
203
204 private:
205 static constexpr auto timestampsFilename = "shutdownAlarmStartTimes";
206
207 /**
208 * @brief Loads the saved timestamps from the filesystem.
209 *
210 * As with save(), cereal doesn't understand the ShutdownType or AlarmType
211 * enums so they have to have been saved as ints and converted.
212 */
213 void load()
214 {
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 std::ifstream stream{path.c_str()};
228 cereal::JSONInputArchive iarchive{stream};
229 iarchive(times);
230
231 for (const auto& [path, shutdownType, alarmType, timestamp] : times)
232 {
233 timestamps.emplace(AlarmKey{path,
234 static_cast<ShutdownType>(shutdownType),
235 static_cast<AlarmType>(alarmType)},
236 timestamp);
237 }
238 }
239
240 /**
241 * @brief The map of AlarmKeys and time start times.
242 */
243 std::map<AlarmKey, uint64_t> timestamps;
244};
245
246} // namespace sensor::monitor