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