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