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/views.cpp b/views.cpp
index bb0ab84..f5d9684 100644
--- a/views.cpp
+++ b/views.cpp
@@ -29,6 +29,8 @@
 extern DBusTopWindow* g_current_active_view;
 extern const std::string FieldNames[];
 extern const int FieldPreferredWidths[];
+extern bool g_sensor_update_thread_active;
+extern std::string g_snapshot_update_bus_cxn;
 
 namespace dbus_top_analyzer
 {
@@ -494,6 +496,78 @@
                          sensor->ObjectPath().c_str());
                 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
                 y++;
+
+                // TODO: can cache in and out & only update when there are user
+                // UI actions
+                std::map<std::string, std::set<std::string>> in, out;
+                g_sensor_snapshot->FindAssociationEndpoints(
+                    sensor->ObjectPath(), &out, &in);
+
+                y++;
+                mvwprintw(win, y, x, "Association status:");
+
+                if (out.size() > 0)
+                {
+                    y++;
+                    int nforward = 0;
+                    for (const auto& [k, v] : out)
+                    {
+                        nforward += int(v.size());
+                    }
+                    mvwprintw(win, y, x,
+                              "Used as Forward vertex %d times:", nforward);
+                    y++;
+                    int idx = 0;
+                    for (const auto& [k, v] : out)
+                    {
+                        idx++;
+                        snprintf(buf, sizeof(buf), "%d. %s (%zu)", idx,
+                                 k.c_str(), v.size());
+                        y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
+                        for (const std::string& entry : v)
+                        {
+                            y += DrawTextWithWidthLimit(win, entry, y, x + 2,
+                                                        w - 2, "/");
+                        }
+                    }
+                }
+                else
+                {
+                    y++;
+                    mvwprintw(win, y, x, "Not used as forward vertex");
+                    y++;
+                }
+
+                if (in.size() > 0)
+                {
+                    y++;
+                    int nbackward = 0;
+                    for (const auto& [k, v] : in)
+                    {
+                        nbackward += int(v.size());
+                    }
+                    mvwprintw(win, y, x,
+                              "Used as reverse vertex %d times:", nbackward);
+                    y++;
+                    int idx = 0;
+                    for (const auto& [k, v] : in)
+                    {
+                        idx++;
+                        snprintf(buf, sizeof(buf), "%d. %s (%zu)", idx,
+                                 k.c_str(), v.size());
+                        y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
+                        for (const std::string& entry : v)
+                        {
+                            y += DrawTextWithWidthLimit(win, entry, y, x + 2,
+                                                        w - 2, "/");
+                        }
+                    }
+                }
+                else
+                {
+                    y++;
+                    mvwprintw(win, y, x, "Not used as reverse vertex");
+                }
             }
         }
         else
@@ -1237,6 +1311,8 @@
         help_info = g_current_active_view->GetStatusString();
     }
     mvwaddstr(win, 0, 1, date_time);
+    mvwaddstr(win, 0, 27, "                                           ");
+    mvwaddstr(win, 0, 27, status_string_.c_str());
     mvwaddstr(win, 0, rect.w - int(help_info.size()) - 1, help_info.c_str());
     wrefresh(win);
 }