xref: /openbmc/phosphor-fan-presence/sensor-monitor/alarm_timestamps.hpp (revision f724c16b42abe061752f0d78f0bdb5e2c85de4b6)
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          std::vector<std::tuple<std::string, int, int, uint64_t>> times;
219  
220          std::filesystem::path path =
221              std::filesystem::path{SENSOR_MONITOR_PERSIST_ROOT_PATH} /
222              timestampsFilename;
223  
224          if (!std::filesystem::exists(path))
225          {
226              return;
227          }
228  
229          try
230          {
231              std::ifstream stream{path.c_str()};
232              cereal::JSONInputArchive iarchive{stream};
233              iarchive(times);
234  
235              for (const auto& [path, shutdownType, alarmType, timestamp] : times)
236              {
237                  timestamps.emplace(
238                      AlarmKey{path, static_cast<ShutdownType>(shutdownType),
239                               static_cast<AlarmType>(alarmType)},
240                      timestamp);
241              }
242          }
243          catch (const std::exception& e)
244          {
245              // Include possible exception when removing file, otherwise ec = 0
246              using namespace phosphor::logging;
247              std::error_code ec;
248              std::filesystem::remove(path, ec);
249              log<level::ERR>(
250                  fmt::format("Unable to restore persisted times ({}, ec: {})",
251                              e.what(), ec.value())
252                      .c_str());
253          }
254      }
255  
256      /**
257       * @brief The map of AlarmKeys and time start times.
258       */
259      std::map<AlarmKey, uint64_t> timestamps;
260  };
261  
262  } // namespace sensor::monitor
263