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 <sdeventplus/clock.hpp> 26 #include <sdeventplus/utility/timer.hpp> 27 28 #include <filesystem> 29 #include <fstream> 30 #include <map> 31 #include <tuple> 32 33 namespace 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 */ 45 class 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 247