Populate info in background thread

This change incorporates
https://gerrit.openbmc.org/c/openbmc/openbmc-tools/+/50784 and
https://gerrit.openbmc.org/c/openbmc/openbmc-tools/+/50088.

The function `ListAllSensors` populates a list of DBus sensor objects.
Currently it runs in the main thread before the UI gets created.

To enhance user experience, this routine is moved to a separate thread,
so the information is populated by that thread, so the main thread does
not get blocked while the list is being filled.

Originally, change 50784's ListAllSensors function includes the
following steps:
1) List DBus connections currently on the Bus
2) Get Creds for each of the DBus connections
3) Examine objects in ObjectMapper to ..
  * See if they are Sensor objects
  * If so mark a "Sensor" as "visible in ObjectMapper"
4) List all Associations recorded in the ObjectMapper
  * So they can be shown in the sensor info panel
5) Find association definitions
6) Find association endpoints
7) Check all objects from HwMon daemons
  * Objects from the DBus-Sensors daemon will be added later
8) Check `ipmitool sdr`

Step 8) is currently removed in this change since running `ipmitool sdr`
can potentially take a long time.

Change-Id: If6f27f130060b52e22c405942a861ba40e45ced2
Signed-off-by: Sui Chen <suichen6@gmail.com>
diff --git a/main.cpp b/main.cpp
index a61b493..e526c3c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -27,7 +27,9 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include <cassert>
 #include <iomanip>
+#include <mutex>
 #include <sstream>
 #include <thread>
 
@@ -41,11 +43,41 @@
 std::vector<DBusTopWindow*> g_views;
 int g_highlighted_view_index = INVALID;
 sd_bus* g_bus = nullptr;
-SensorSnapshot* g_sensor_snapshot;
-DBusConnectionSnapshot* g_connection_snapshot;
+SensorSnapshot *g_sensor_snapshot, *g_sensor_snapshot_staging = nullptr;
+DBusConnectionSnapshot *g_connection_snapshot,
+    *g_connection_snapshot_staging = nullptr;
 DBusTopStatistics* g_dbus_statistics; // At every update interval,
                                       // dbus_top_analyzer::g_dbus_statistics's
                                       // value is copied to this one for display
+
+// Whenever an update of SensorSnapshot and DBusConnectionSnapshot is needed,
+// they are populated into the "staging" copies and a pointer swap is done
+// by the main thread (the thread that constructs the snapshots shall not touch
+// the copy used for UI rendering)
+bool g_sensor_update_thread_active = false;
+std::string g_snapshot_update_bus_cxn =
+    ""; // The DBus connection used by the updater thread.
+int g_snapshot_update_bus_cxn_id = -999;
+std::mutex g_mtx_snapshot_update;
+
+int GetConnectionNumericID(const std::string& unique_name)
+{
+    size_t idx = unique_name.find('.');
+    if (idx == std::string::npos)
+    {
+        return -999;
+    }
+    try
+    {
+        int ret = std::atoi(unique_name.substr(idx + 1).c_str());
+        return ret;
+    }
+    catch (const std::exception& e)
+    {
+        return -999;
+    }
+}
+
 void ReinitializeUI();
 int maxx, maxy, halfx, halfy;
 
@@ -144,6 +176,7 @@
 
 void DBusTopRefresh()
 {
+    g_mtx_snapshot_update.lock();
     UpdateWindowSizes();
     for (DBusTopWindow* v : g_views)
     {
@@ -155,6 +188,14 @@
         focused_view->DrawBorderIfNeeded(); // focused view border: on top
     }
     refresh();
+    g_mtx_snapshot_update.unlock();
+}
+
+void DBusTopUpdateFooterView()
+{
+    g_mtx_snapshot_update.lock();
+    g_footer_view->Render();
+    g_mtx_snapshot_update.unlock();
 }
 
 // This function is called by the Capture thread
@@ -172,7 +213,25 @@
         interval_secs = GetSummaryIntervalInMillises() / 1000.0f;
     }
     g_summary_window->UpdateDBusTopStatistics(stat);
+
     stat->SetSortFieldsAndReset(g_dbus_stat_list_view->GetSortFields());
+
+    g_mtx_snapshot_update.lock();
+    if (g_sensor_snapshot_staging != nullptr &&
+        g_connection_snapshot_staging != nullptr)
+    {
+        std::swap(g_sensor_snapshot_staging, g_sensor_snapshot);
+        std::swap(g_connection_snapshot_staging, g_connection_snapshot);
+
+        delete g_connection_snapshot_staging;
+        delete g_sensor_snapshot_staging;
+
+        g_sensor_snapshot_staging = nullptr;
+        g_connection_snapshot_staging = nullptr;
+    }
+    g_mtx_snapshot_update.unlock();
+    g_sensor_detail_view->UpdateSensorSnapshot(g_sensor_snapshot);
+
     // ReinitializeUI(); // Don't do it here, only when user presses [R]
     DBusTopRefresh();
 }
@@ -352,6 +411,35 @@
     }
 }
 
+void ListAllSensorsThread()
+{
+    // Create a temporary connection
+    assert(g_sensor_update_thread_active == false);
+    sd_bus* bus;
+    AcquireBus(&bus);
+
+    const char* bus_name;
+    sd_bus_get_unique_name(bus, &bus_name);
+    g_snapshot_update_bus_cxn = std::string(bus_name);
+    g_snapshot_update_bus_cxn_id =
+        GetConnectionNumericID(g_snapshot_update_bus_cxn);
+
+    g_sensor_update_thread_active = true;
+    DBusConnectionSnapshot* cxn_snapshot;
+    SensorSnapshot* sensor_snapshot;
+    dbus_top_analyzer::ListAllSensors(bus, &cxn_snapshot, &sensor_snapshot);
+
+    g_mtx_snapshot_update.lock();
+    g_connection_snapshot = cxn_snapshot;
+    g_sensor_snapshot = sensor_snapshot;
+    g_mtx_snapshot_update.unlock();
+    g_sensor_update_thread_active = false;
+    g_snapshot_update_bus_cxn = "";
+    g_snapshot_update_bus_cxn_id = -999;
+
+    sd_bus_close(bus);
+}
+
 int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
 {
     int r = AcquireBus(&g_bus);
@@ -363,7 +451,9 @@
 
     printf("Listing all sensors for display\n");
     // ListAllSensors creates connection snapshot and sensor snapshot
-    dbus_top_analyzer::ListAllSensors();
+    g_connection_snapshot = new DBusConnectionSnapshot();
+    g_sensor_snapshot = new SensorSnapshot(g_connection_snapshot);
+
     g_bargraph = new BarGraph<float>(300);
     g_histogram = new Histogram<float>();
 
@@ -383,6 +473,9 @@
     g_views.push_back(g_dbus_stat_list_view);
     g_views.push_back(g_footer_view);
 
+    // Do the scan in a separate thread.
+    std::thread list_all_sensors_thread(ListAllSensorsThread);
+
     g_sensor_detail_view->UpdateSensorSnapshot(g_sensor_snapshot);
     UpdateWindowSizes();
     dbus_top_analyzer::SetDBusTopStatisticsCallback(&DBusTopStatisticsCallback);