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 <phosphor-logging/log.hpp> 26 #include <sdeventplus/clock.hpp> 27 #include <sdeventplus/utility/timer.hpp> 28 29 #include <filesystem> 30 #include <format> 31 #include <fstream> 32 #include <map> 33 #include <tuple> 34 35 namespace 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 */ 47 class 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 */ AlarmTimestamps()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 */ add(const AlarmKey & key,uint64_t timestamp)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 */ erase(const AlarmKey & key)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 */ erase(std::map<AlarmKey,uint64_t>::const_iterator & entry)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 */ clear()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 */ prune(const std::map<AlarmKey,std::unique_ptr<sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>> & alarms)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 */ get() const168 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 */ save()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 */ load()215 void load() 216 { 217 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 228 try 229 { 230 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 245 using namespace phosphor::logging; 246 std::error_code ec; 247 std::filesystem::remove(path, ec); 248 log<level::ERR>( 249 std::format("Unable to restore persisted times ({}, ec: {})", 250 e.what(), ec.value()) 251 .c_str()); 252 } 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 262