control: Dump flight recorder to a JSON file

A future commit will dump more debug data as opposed to just the flight
recorder.  To better support the data all being in the same file, it
will be a JSON file.  The first step of that is to write the flight
recorder output to a JSON file.

This also reorganizes the Manager code that does it to prepare for
different data also being in the same file.

An example is:
{
    "flight_recorder": [
        "Oct 06 05:59:01.183998: main: Startup"
    ]
}

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iaeb55ffde3a30c2345968e1b3fad313b50aff331
diff --git a/control/json/manager.cpp b/control/json/manager.cpp
index 92ef0d0..10bb84d 100644
--- a/control/json/manager.cpp
+++ b/control/json/manager.cpp
@@ -58,6 +58,8 @@
     Manager::_objects;
 std::unordered_map<std::string, PropertyVariantType> Manager::_parameters;
 
+const std::string Manager::dumpFile = "/tmp/fan_control_dump.json";
+
 Manager::Manager(const sdeventplus::Event& event) :
     _bus(util::SDBusPlus::getBus()), _event(event),
     _mgr(util::SDBusPlus::getBus(), CONTROL_OBJPATH), _loadAllowed(true),
@@ -95,15 +97,26 @@
 void Manager::sigUsr1Handler(sdeventplus::source::Signal&,
                              const struct signalfd_siginfo*)
 {
-    _flightRecEventSource = std::make_unique<sdeventplus::source::Defer>(
-        _event, std::bind(std::mem_fn(&Manager::dumpFlightRecorder), this,
+    debugDumpEventSource = std::make_unique<sdeventplus::source::Defer>(
+        _event, std::bind(std::mem_fn(&Manager::dumpDebugData), this,
                           std::placeholders::_1));
 }
 
-void Manager::dumpFlightRecorder(sdeventplus::source::EventBase& /*source*/)
+void Manager::dumpDebugData(sdeventplus::source::EventBase& /*source*/)
 {
-    FlightRecorder::instance().dump();
-    _flightRecEventSource.reset();
+    json data;
+    FlightRecorder::instance().dump(data);
+
+    std::ofstream file{Manager::dumpFile};
+    if (!file)
+    {
+        log<level::ERR>("Could not open file for fan dump");
+        return;
+    }
+
+    file << std::setw(4) << data;
+
+    debugDumpEventSource.reset();
 }
 
 void Manager::load()
diff --git a/control/json/manager.hpp b/control/json/manager.hpp
index 86e47dd..92f837e 100644
--- a/control/json/manager.hpp
+++ b/control/json/manager.hpp
@@ -479,6 +479,9 @@
         _parameters.erase(name);
     }
 
+    /* The name of the dump file */
+    static const std::string dumpFile;
+
   private:
     /* The sdbusplus bus object to use */
     sdbusplus::bus::bus& _bus;
@@ -525,9 +528,9 @@
     /* List of events configured */
     std::map<configKey, std::unique_ptr<Event>> _events;
 
-    /* The sdeventplus wrapper around sd_event_add_defer to dump the
-     * flight recorder from the event loop after the USR1 signal.  */
-    std::unique_ptr<sdeventplus::source::Defer> _flightRecEventSource;
+    /* The sdeventplus wrapper around sd_event_add_defer to dump debug
+     * data from the event loop after the USR1 signal.  */
+    std::unique_ptr<sdeventplus::source::Defer> debugDumpEventSource;
 
     /**
      * @brief A map of parameter names and values that are something
@@ -581,9 +584,9 @@
     void setProfiles();
 
     /**
-     * @brief Callback from _flightRecEventSource to dump the flight recorder
+     * @brief Callback from debugDumpEventSource to dump debug data
      */
-    void dumpFlightRecorder(sdeventplus::source::EventBase&);
+    void dumpDebugData(sdeventplus::source::EventBase&);
 };
 
 } // namespace phosphor::fan::control::json
diff --git a/control/json/utils/flight_recorder.cpp b/control/json/utils/flight_recorder.cpp
index 4cc274f..af04834 100644
--- a/control/json/utils/flight_recorder.cpp
+++ b/control/json/utils/flight_recorder.cpp
@@ -21,15 +21,15 @@
 
 #include <algorithm>
 #include <ctime>
-#include <fstream>
 #include <iomanip>
+#include <sstream>
 #include <vector>
 
 constexpr auto maxEntriesPerID = 20;
-constexpr auto outputFile = "/tmp/fan_control.txt";
 
 namespace phosphor::fan::control::json
 {
+using json = nlohmann::json;
 
 FlightRecorder& FlightRecorder::instance()
 {
@@ -51,7 +51,7 @@
     }
 }
 
-void FlightRecorder::dump()
+void FlightRecorder::dump(json& data)
 {
     using namespace std::chrono;
     using Timepoint = time_point<system_clock, microseconds>;
@@ -74,15 +74,6 @@
                   return std::get<Timepoint>(left) < std::get<Timepoint>(right);
               });
 
-    std::ofstream file{outputFile};
-
-    if (!file)
-    {
-        phosphor::logging::log<phosphor::logging::level::ERR>(
-            fmt::format("Could not open {} for writing", outputFile).c_str());
-        return;
-    }
-
     auto formatTime = [](const Timepoint& tp) {
         std::stringstream ss;
         std::time_t tt = system_clock::to_time_t(tp);
@@ -95,10 +86,14 @@
         return ss.str();
     };
 
+    auto& fr = data["flight_recorder"];
+    std::stringstream ss;
+
     for (const auto& [ts, id, msg] : output)
     {
-        file << formatTime(ts) << ": " << std::setw(idSize) << id << ": " << msg
-             << "\n";
+        ss << formatTime(ts) << ": " << std::setw(idSize) << id << ": " << msg;
+        fr.push_back(ss.str());
+        ss.str("");
     }
 }
 
diff --git a/control/json/utils/flight_recorder.hpp b/control/json/utils/flight_recorder.hpp
index 154d5ab..fcc3908 100644
--- a/control/json/utils/flight_recorder.hpp
+++ b/control/json/utils/flight_recorder.hpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 #pragma once
+#include <nlohmann/json.hpp>
+
 #include <chrono>
 #include <cstdint>
 #include <deque>
@@ -23,6 +25,7 @@
 
 namespace phosphor::fan::control::json
 {
+using json = nlohmann::json;
 
 /**
  * @class FlightRecorder
@@ -61,11 +64,13 @@
     void log(const std::string& id, const std::string& message);
 
     /**
-     * @brief Writes the flight recorder contents to file.
+     * @brief Writes the flight recorder contents to JSON.
      *
      * Sorts all messages by timestamp when doing so.
+     *
+     * @param[out] data - Filled in with the flight recorder data
      */
-    void dump();
+    void dump(json& data);
 
   private:
     FlightRecorder() = default;