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 <fmt/format.h> 22 23 #include <cereal/archives/json.hpp> 24 #include <cereal/types/string.hpp> 25 #include <cereal/types/tuple.hpp> 26 #include <cereal/types/vector.hpp> 27 #include <phosphor-logging/log.hpp> 28 #include <sdeventplus/clock.hpp> 29 #include <sdeventplus/utility/timer.hpp> 30 31 #include <filesystem> 32 #include <fstream> 33 #include <map> 34 #include <tuple> 35 36 namespace 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 */ 48 class 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 230 try 231 { 232 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 247 using namespace phosphor::logging; 248 std::error_code ec; 249 std::filesystem::remove(path, ec); 250 log<level::ERR>( 251 fmt::format("Unable to restore persisted times ({}, ec: {})", 252 e.what(), ec.value()) 253 .c_str()); 254 } 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 264