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/analyzer.cpp b/analyzer.cpp
index dbcb754..80e22fc 100644
--- a/analyzer.cpp
+++ b/analyzer.cpp
@@ -23,6 +23,7 @@
 #include <unistd.h>
 
 #include <atomic>
+#include <cassert>
 #include <filesystem>
 #include <fstream>
 #include <functional>
@@ -30,10 +31,13 @@
 #include <sstream>
 #include <string>
 
+int AcquireBus(sd_bus** ret);
+
 extern SensorSnapshot* g_sensor_snapshot;
 extern DBusConnectionSnapshot* g_connection_snapshot;
 extern sd_bus* g_bus;
 extern SensorDetailView* g_sensor_detail_view;
+extern FooterView* g_footer_view;
 
 static std::unordered_map<uint64_t, uint64_t>
     in_flight_methodcalls; // serial => microseconds
@@ -134,7 +138,7 @@
 }
 
 std::vector<std::string> FindAllObjectPathsForService(
-    const std::string& service,
+    sd_bus* bus, const std::string& service,
     std::function<void(const std::string&, const std::vector<std::string>&)>
         on_interface_cb)
 {
@@ -155,34 +159,29 @@
         {
             all_obj_paths.push_back(obj_path);
             int r = sd_bus_message_new_method_call(
-                g_bus, &m, service.c_str(), obj_path.c_str(),
+                bus, &m, service.c_str(), obj_path.c_str(),
                 "org.freedesktop.DBus.Introspectable", "Introspect");
             if (r < 0)
             {
-                printf("Oh! Cannot create new method call. r=%d, strerror=%s\n",
-                       r, strerror(-r));
                 continue;
             }
-            r = sd_bus_call(g_bus, m, 0, &err, &reply);
+            r = sd_bus_call(bus, m, 0, &err, &reply);
             if (r < 0)
             {
-                printf("Could not execute method call, r=%d, strerror=%s\n", r,
-                       strerror(-r));
+                continue;
             }
             const char* sig = sd_bus_message_get_signature(reply, 0);
             if (!strcmp(sig, "s"))
             {
                 const char* s;
                 int r = sd_bus_message_read(reply, "s", &s);
-                std::string s1(s);
                 if (r < 0)
                 {
-                    printf("Could not read string payload, r=%d, strerror=%s\n",
-                           r, strerror(-r));
+                    continue;
                 }
                 else
                 {
-                    XMLNode* t = ParseXML(s1);
+                    XMLNode* t = ParseXML(std::string(s));
                     std::vector<std::string> ch = t->GetChildNodeNames();
                     if (on_interface_cb != nullptr)
                     {
@@ -207,12 +206,17 @@
     return all_obj_paths;
 }
 
-void ListAllSensors()
+void ListAllSensors(sd_bus* bus, DBusConnectionSnapshot** cxn_snapshot,
+                    SensorSnapshot** sensor_snapshot)
 {
-    g_connection_snapshot = new DBusConnectionSnapshot();
-    printf("1. Getting names\n");
+    // Craete new snapshots
+    (*cxn_snapshot) = new DBusConnectionSnapshot();
+    (*sensor_snapshot) = new SensorSnapshot(*cxn_snapshot);
+
+    g_footer_view->SetStatusString("1. Listing DBus Names");
+    DBusTopUpdateFooterView();
     char** names;
-    int r = sd_bus_list_names(g_bus, &names, nullptr);
+    int r = sd_bus_list_names(bus, &names, nullptr);
     std::vector<std::string> services;
     std::vector<int> pids;
     std::vector<std::string> comms;
@@ -222,12 +226,22 @@
         free(*ptr);
     }
     free(names);
-    printf("2. Getting creds of each name\n");
-    for (int i = 0; i < static_cast<int>(services.size()); i++)
+
+    g_footer_view->SetStatusString("2. Getting Creds");
+    DBusTopUpdateFooterView();
+    size_t N = services.size();
+    for (size_t i = 0; i < N; i++)
     {
+        if (i == N - 1 || (i % 100) == 99)
+        {
+            g_footer_view->SetStatusString("2. Getting Creds " +
+                                           std::to_string(i + 1) + "/" +
+                                           std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
         const std::string& service = services[i];
         sd_bus_creds* creds = nullptr;
-        r = sd_bus_get_name_creds(g_bus, services[i].c_str(),
+        r = sd_bus_get_name_creds(bus, services[i].c_str(),
                                   SD_BUS_CREDS_AUGMENT | SD_BUS_CREDS_EUID |
                                       SD_BUS_CREDS_PID | SD_BUS_CREDS_COMM |
                                       SD_BUS_CREDS_UNIQUE_NAME |
@@ -237,9 +251,7 @@
         // PID
         int pid = INVALID;
         if (r < 0)
-        {
-            printf("Oh! Cannot get creds for %s\n", services[i].c_str());
-        }
+        {}
         else
         {
             r = sd_bus_creds_get_pid(creds, &pid);
@@ -268,89 +280,70 @@
         {
             connection = u;
         }
-        else
-        {
-            printf("Oh! Could not get unique name for %s\n", service.c_str());
-        }
         std::string unit;
         r = sd_bus_creds_get_unit(creds, &u);
         if (r >= 0)
         {
             unit = u;
         }
-        else
-        {
-            printf("Oh! Could not get unit name for %s\n", unit.c_str());
-        }
-        printf("AddConnection    %s    %s    %s    %s    %d\n", service.c_str(),
-               connection.c_str(), comm.c_str(), unit.c_str(), pid);
-        g_connection_snapshot->AddConnection(service, connection, comm, unit,
-                                             pid);
+        (*cxn_snapshot)->AddConnection(service, connection, comm, unit, pid);
     }
-    printf("There are %d DBus names.\n", int(services.size()));
-    for (int i = 0; i < int(services.size()); i++)
-    {
-        printf("    %d: %s [%s]\n", i, services[i].c_str(), comms[i].c_str());
-    }
-    g_sensor_snapshot = new SensorSnapshot(g_connection_snapshot);
     // busctl call xyz.openbmc_project.ObjectMapper /
     // org.freedesktop.DBus.Introspectable Introspect
-    printf("3. See which sensors are visible from Object Mapper\n");
-    printf("3.1. Introspect Object Mapper for object paths\n");
+
+    g_footer_view->SetStatusString("3. Examine objects in ObjectMapper");
     std::vector<std::string> all_obj_paths = FindAllObjectPathsForService(
-        "xyz.openbmc_project.ObjectMapper", nullptr);
+        bus, "xyz.openbmc_project.ObjectMapper", nullptr);
+    N = all_obj_paths.size();
     sd_bus_error err = SD_BUS_ERROR_NULL;
     sd_bus_message *m, *reply;
-    printf("%d paths found while introspecting ObjectMapper.\n",
-           int(all_obj_paths.size()));
-    printf("3.2. Call ObjectMapper's GetObject method against the sensor "
-           "object paths that represent sensors\n");
-    for (const std::string& p : all_obj_paths)
+    for (size_t i = 0; i < N; i++)
     {
+        if (i % 100 == 99 || i == N - 1)
+        {
+            g_footer_view->SetStatusString(
+                "3. Examine objects in ObjectMapper " + std::to_string(i + 1) +
+                "/" + std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
+
+        const std::string& p = all_obj_paths[i];
         if (IsSensorObjectPath(p))
         {
             err = SD_BUS_ERROR_NULL;
             r = sd_bus_message_new_method_call(
-                g_bus, &m, "xyz.openbmc_project.ObjectMapper",
+                bus, &m, "xyz.openbmc_project.ObjectMapper",
                 "/xyz/openbmc_project/object_mapper",
                 "xyz.openbmc_project.ObjectMapper", "GetObject");
             if (r < 0)
             {
-                printf("Cannot create new method call. r=%d, strerror=%s\n", r,
-                       strerror(-r));
                 continue;
             }
             r = sd_bus_message_append_basic(m, 's', p.c_str());
             if (r < 0)
             {
-                printf("Could not append a string parameter to m\n");
                 continue;
             }
             // empty array
             r = sd_bus_message_open_container(m, 'a', "s");
             if (r < 0)
             {
-                printf("Could not open a container for m\n");
                 continue;
             }
             r = sd_bus_message_close_container(m);
             if (r < 0)
             {
-                printf("Could not close container for m\n");
                 continue;
             }
-            r = sd_bus_call(g_bus, m, 0, &err, &reply);
+            r = sd_bus_call(bus, m, 0, &err, &reply);
             if (r < 0)
-            {
-                printf("Error performing dbus method call\n");
-            }
+            {}
             const char* sig = sd_bus_message_get_signature(reply, 0);
             if (!strcmp(sig, "a{sas}"))
             {
                 r = sd_bus_message_enter_container(reply, 'a', "{sas}");
                 if (r < 0)
                 {
-                    printf("Could not enter the level 0 array container\n");
                     continue;
                 }
                 while (true)
@@ -359,8 +352,6 @@
                         reply, SD_BUS_TYPE_DICT_ENTRY, "sas");
                     if (r < 0)
                     {
-                        // printf("Could not enter the level 1 dict
-                        // container\n");
                         goto DONE;
                     }
                     else if (r == 0)
@@ -374,14 +365,11 @@
                                                       &interface_map_first);
                         if (r < 0)
                         {
-                            printf("Could not read interface_map_first\n");
                             goto DONE;
                         }
                         r = sd_bus_message_enter_container(reply, 'a', "s");
                         if (r < 0)
                         {
-                            printf("Could not enter the level 2 array "
-                                   "container\n");
                             goto DONE;
                         }
                         bool has_value_interface = false;
@@ -391,14 +379,11 @@
                             r = sd_bus_message_read_basic(
                                 reply, 's', &interface_map_second);
                             if (r < 0)
-                            {
-                                printf("Could not read interface_map_second\n");
-                            }
+                            {}
                             else if (r == 0)
                                 break;
                             else
                             {
-                                // printf("    %s\n", interface_map_second);
                                 if (!strcmp(interface_map_second,
                                             "xyz.openbmc_project.Sensor.Value"))
                                 {
@@ -408,8 +393,9 @@
                         }
                         if (has_value_interface)
                         {
-                            g_sensor_snapshot->SerSensorVisibleFromObjectMapper(
-                                std::string(interface_map_first), p);
+                            (*sensor_snapshot)
+                                ->SetSensorVisibleFromObjectMapper(
+                                    std::string(interface_map_first), p);
                         }
                         r = sd_bus_message_exit_container(reply);
                     }
@@ -421,9 +407,389 @@
         {}
         }
     }
-    printf("4. Check Hwmon's DBus objects\n");
-    for (int i = 0; i < int(comms.size()); i++)
+
+    g_footer_view->SetStatusString("4. List Associations in ObjectMapper");
+    DBusTopUpdateFooterView();
+    err = SD_BUS_ERROR_NULL;
+    r = sd_bus_message_new_method_call(
+        bus, &m, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
+    if (r < 0)
     {
+        assert(0);
+    }
+    r = sd_bus_message_append_basic(m, 's', "/");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    const int zero = 0;
+    r = sd_bus_message_append_basic(m, 'i', &zero);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_open_container(m, 'a', "s");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_append_basic(m, 's', "xyz.openbmc_project.Association");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_close_container(m);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_call(bus, m, 0, &err, &reply);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    const char* sig = sd_bus_message_get_signature(reply, 0);
+    if (strcmp(sig, "as"))
+    {
+        assert(0);
+    }
+    r = sd_bus_message_enter_container(reply, 'a', "s");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    std::set<std::string> assoc_paths;
+    while (true)
+    {
+        const char* p;
+        r = sd_bus_message_read_basic(reply, 's', &p);
+        if (r <= 0)
+            break;
+        else
+        {
+            assoc_paths.insert(p);
+        }
+    }
+    r = sd_bus_message_exit_container(reply);
+    if (r < 0)
+    {
+        assert(0);
+    }
+
+    size_t idx = 0;
+    for (const std::string& assoc_path : assoc_paths)
+    {
+        if (idx % 100 == 99 || idx == N - 1)
+        {
+            g_footer_view->SetStatusString(
+                "4. List Associations in ObjectMapper " +
+                std::to_string(idx + 1) + "/" + std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
+        ++idx;
+        err = SD_BUS_ERROR_NULL;
+        r = sd_bus_message_new_method_call(
+            bus, &m, "xyz.openbmc_project.ObjectMapper", assoc_path.c_str(),
+            "org.freedesktop.DBus.Properties", "Get");
+        r = sd_bus_message_append_basic(m, 's',
+                                        "xyz.openbmc_project.Association");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_message_append_basic(m, 's', "endpoints");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_call(bus, m, 0, &err, &reply);
+        if (r < 0)
+        {
+            // The object may not have any endpoints
+            continue;
+        }
+        const char* sig = sd_bus_message_get_signature(reply, 0);
+        if (strcmp(sig, "v"))
+        {
+            assert(0);
+        }
+        r = sd_bus_message_enter_container(reply, 'v', "as");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_message_enter_container(reply, 'a', "s");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        std::set<std::string> entries;
+        while (true)
+        {
+            const char* p;
+            r = sd_bus_message_read_basic(reply, 's', &p);
+            if (r <= 0)
+                break;
+            entries.insert(p);
+        }
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        }
+
+        (*sensor_snapshot)->AddAssociationEndpoints(assoc_path, entries);
+    }
+
+    g_footer_view->SetStatusString("5. Find Association Definitions");
+    DBusTopUpdateFooterView();
+
+    err = SD_BUS_ERROR_NULL;
+    r = sd_bus_message_new_method_call(
+        bus, &m, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+    std::vector<std::pair<std::string, std::string>>
+        services_and_objects; // Record the Associations from those pairs
+    r = sd_bus_message_append_basic(m, 's', "/");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_append_basic(m, 'i', &zero);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_open_container(m, 'a', "s");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_append_basic(
+        m, 's', "xyz.openbmc_project.Association.Definitions");
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_message_close_container(m);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    r = sd_bus_call(bus, m, 0, &err, &reply);
+    if (r < 0)
+    {
+        assert(0);
+    }
+    sig = sd_bus_message_get_signature(reply, 0);
+    if (strcmp(sig, "a{sa{sas}}"))
+    {
+        assert(0);
+    }
+    r = sd_bus_message_enter_container(reply, 'a', "{sa{sas}}");
+    if (r <= 0)
+    {
+        assert(0);
+    }
+
+    idx = 0;
+    N = services_and_objects.size();
+    while (true)
+    {
+        if (idx % 100 == 99 || idx == N - 1)
+        {
+            g_footer_view->SetStatusString("5. Find Association Definitions " +
+                                           std::to_string(idx + 1) + "/" +
+                                           std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
+        r = sd_bus_message_enter_container(reply, 'e', "sa{sas}");
+        if (r <= 0)
+        {
+            break;
+        }              // e denotes 'dict entry'
+        const char* p; // path
+        r = sd_bus_message_read_basic(reply, 's', &p);
+        if (r <= 0)
+            break;
+        r = sd_bus_message_enter_container(reply, 'a', "{sas}");
+        if (r <= 0)
+        {
+            assert(0);
+        }
+        while (true)
+        {
+            const char* service; // service
+            r = sd_bus_message_enter_container(reply, 'e', "sas");
+            if (r <= 0)
+            {
+                break;
+            }
+            r = sd_bus_message_read_basic(reply, 's', &service);
+            if (r < 0)
+            {
+                assert(0);
+            }
+            services_and_objects.emplace_back(std::string(service),
+                                              std::string(p));
+            r = sd_bus_message_enter_container(reply, 'a', "s");
+            if (r <= 0)
+            {
+                assert(0);
+            }
+            while (true)
+            {
+                const char* iface;
+                r = sd_bus_message_read_basic(reply, 's', &iface);
+                if (r <= 0)
+                {
+                    break;
+                }
+            }
+            r = sd_bus_message_exit_container(reply);
+            if (r < 0)
+            {
+                assert(0);
+            } // exit a
+            r = sd_bus_message_exit_container(reply);
+            if (r < 0)
+            {
+                assert(0);
+            } // exit e
+        }
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        } // exit a
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        } // exit e
+    }
+    r = sd_bus_message_exit_container(reply);
+    if (r < 0)
+    {
+        assert(0);
+    } // exit a
+
+    idx = 0;
+    N = services_and_objects.size();
+    for (const std::pair<std::string, std::string>& serv_and_obj :
+         services_and_objects)
+    {
+        if (idx % 100 == 99 || idx == N - 1)
+        {
+            g_footer_view->SetStatusString(
+                "6. Find Associations of Endpoints " + std::to_string(idx + 1) +
+                "/" + std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
+        ++idx;
+        err = SD_BUS_ERROR_NULL;
+        r = sd_bus_message_new_method_call(
+            bus, &m, serv_and_obj.first.c_str(), serv_and_obj.second.c_str(),
+            "org.freedesktop.DBus.Properties", "Get");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_message_append_basic(
+            m, 's', "xyz.openbmc_project.Association.Definitions");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_message_append_basic(m, 's', "Associations");
+        if (r < 0)
+        {
+            assert(0);
+        }
+        r = sd_bus_call(bus, m, 0, &err, &reply);
+        if (r <= 0)
+        {
+            continue;
+        }
+        sig = sd_bus_message_get_signature(reply, 0);
+        if (strcmp(sig, "v"))
+        {
+            assert(0);
+        }
+        r = sd_bus_message_enter_container(reply, 'v', "a(sss)");
+        if (r <= 0)
+        {
+            continue;
+        }
+        r = sd_bus_message_enter_container(reply, 'a', "(sss)");
+        if (r <= 0)
+        {
+            continue;
+        }
+        while (true)
+        {
+            r = sd_bus_message_enter_container(reply, 'r', "sss");
+            if (r <= 0)
+            {
+                break;
+            } // struct
+            const char *forward, *reverse, *endpoint;
+            r = sd_bus_message_read_basic(reply, 's', &forward);
+            if (r < 0)
+            {
+                break;
+            }
+            r = sd_bus_message_read_basic(reply, 's', &reverse);
+            if (r < 0)
+            {
+                assert(0);
+            }
+            r = sd_bus_message_read_basic(reply, 's', &endpoint);
+            if (r < 0)
+            {
+                assert(0);
+            }
+            (*sensor_snapshot)
+                ->AddAssociationDefinition(
+                    serv_and_obj.second, std::string(forward),
+                    std::string(reverse), std::string(endpoint));
+            r = sd_bus_message_exit_container(reply);
+            if (r < 0)
+            {
+                assert(0);
+            } // exit struct
+        }
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        } // exit a
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+        {
+            assert(0);
+        } // exit v
+    }
+
+    g_footer_view->SetStatusString("7. Check HWMon DBus objects");
+    DBusTopUpdateFooterView();
+    for (size_t i = 0; i < comms.size(); i++)
+    {
+        if (i % 100 == 99 || i == N - 1)
+        {
+            g_footer_view->SetStatusString("7. Check HWMon DBus objects " +
+                                           std::to_string(i + 1) + "/" +
+                                           std::to_string(N));
+            DBusTopUpdateFooterView();
+        }
         const std::string& comm = comms[i];
         const std::string& service = services[i];
         if (comm.find("phosphor-hwmon-readd") != std::string::npos &&
@@ -431,53 +797,19 @@
         {
             // printf("Should introspect %s\n", service.c_str());
             std::vector<std::string> objpaths =
-                FindAllObjectPathsForService(service, nullptr);
+                FindAllObjectPathsForService(bus, service, nullptr);
             for (const std::string& op : objpaths)
             {
                 if (IsSensorObjectPath(op))
                 {
-                    g_sensor_snapshot->SetSensorVisibleFromHwmon(service, op);
+                    (*sensor_snapshot)->SetSensorVisibleFromHwmon(service, op);
                 }
             }
         }
     }
-    // Call `ipmitool sdr list` and see which sensors exist.
-    printf("5. Checking ipmitool SDR List\n");
-    std::string out;
-    bool skip_sdr_list = false;
-    if (getenv("SKIP"))
-    {
-        skip_sdr_list = true;
-    }
-    if (!skip_sdr_list)
-    {
-        constexpr int MAX_BUFFER = 255;
-        char buffer[MAX_BUFFER];
-        FILE* stream = popen("ipmitool sdr list", "r");
-        while (fgets(buffer, MAX_BUFFER, stream) != NULL)
-        {
-            out.append(buffer);
-        }
-        pclose(stream);
-    }
-    std::stringstream ss(out);
-    while (true)
-    {
-        std::string sensor_id, reading, status;
-        std::getline(ss, sensor_id, '|');
-        std::getline(ss, reading, '|');
-        std::getline(ss, status);
-        // printf("%s %s %s\n", sensor_id.c_str(), reading.c_str(),
-        // status.c_str());
-        if (sensor_id.size() > 0 && reading.size() > 0 && status.size() > 0)
-        {
-            g_sensor_snapshot->SetSensorVisibleFromIpmitoolSdr(Trim(sensor_id));
-        }
-        else
-            break;
-    }
-    printf("=== Sensors snapshot summary: ===\n");
-    g_sensor_snapshot->PrintSummary();
+
+    g_footer_view->SetStatusString("DBus object scan complete");
+    DBusTopUpdateFooterView();
 }
 } // namespace dbus_top_analyzer
 
diff --git a/analyzer.hpp b/analyzer.hpp
index 0649dc6..463bad7 100644
--- a/analyzer.hpp
+++ b/analyzer.hpp
@@ -24,6 +24,9 @@
 #include <string>
 #include <vector>
 
+class DBusConnectionSnapshot;
+class SensorSnapshot;
+
 enum DBusTopSortField
 {
     // DBus Message properties
@@ -173,5 +176,5 @@
 void SetDBusTopStatisticsCallback(DBusTopStatisticsCallback cb);
 void AnalyzerThread();
 // Methods for sending Object Mapper queries
-void ListAllSensors();
+void ListAllSensors(sd_bus*, DBusConnectionSnapshot**, SensorSnapshot**);
 } // namespace dbus_top_analyzer
diff --git a/dbus_capture.cpp b/dbus_capture.cpp
index 38acfc4..e464456 100644
--- a/dbus_capture.cpp
+++ b/dbus_capture.cpp
@@ -28,6 +28,11 @@
 extern DBusConnectionSnapshot* g_connection_snapshot;
 static std::unordered_map<uint64_t, uint64_t> in_flight_methodcalls;
 
+extern bool g_sensor_update_thread_active;
+extern std::string g_snapshot_update_bus_cxn;
+extern int g_snapshot_update_bus_cxn_id;
+extern int GetConnectionNumericID(const std::string& unique_name);
+
 namespace dbus_top_analyzer
 {
 extern DBusTopStatistics g_dbus_statistics;
@@ -170,25 +175,56 @@
             const char* destination = sd_bus_message_get_destination(m);
             const char* interface = sd_bus_message_get_interface(m);
             const char* member = sd_bus_message_get_member(m);
-            // TODO: This is for the bottom-left window
-            TrackMessage(m);
 
-            // Look up the unique connection name for sender and destination
+            // For looking up the unique connection name for sender and
+            // destination
             std::string sender_uniq, dest_uniq;
-            if (sender != nullptr)
+            bool should_ignore = false;
+
+            if (g_sensor_update_thread_active &&
+                g_snapshot_update_bus_cxn != "")
             {
-                sender_uniq =
-                    g_connection_snapshot->GetUniqueNameIfExists(sender);
+                int cxn_id = -999;
+                if (sender != nullptr)
+                {
+                    cxn_id = GetConnectionNumericID(std::string(sender));
+                }
+                if (destination != nullptr)
+                {
+                    cxn_id = GetConnectionNumericID(std::string(destination));
+                }
+
+                // Reason for this: The connection may be the connection from
+                // the update thread plus 1, example: :1.10000 vs :1.10001 Not
+                // sure where the new connection comes from. FIXME
+                if (cxn_id <= 4 + g_snapshot_update_bus_cxn_id &&
+                    cxn_id >= g_snapshot_update_bus_cxn_id)
+                {
+                    should_ignore = true;
+                }
             }
-            if (destination != nullptr)
+
+            if (!should_ignore)
             {
-                dest_uniq =
-                    g_connection_snapshot->GetUniqueNameIfExists(destination);
+                // TODO: This is for the bottom-left window
+                TrackMessage(m);
+
+                if (sender != nullptr)
+                {
+                    sender_uniq =
+                        g_connection_snapshot->GetUniqueNameIfExists(sender);
+                }
+                if (destination != nullptr)
+                {
+                    dest_uniq = g_connection_snapshot->GetUniqueNameIfExists(
+                        destination);
+                }
+                // This is for the bottom-right window
+                dbus_top_analyzer::g_dbus_statistics.OnNewDBusMessage(
+                    sender_uniq.c_str(), dest_uniq.c_str(), interface, path,
+                    member, type, m);
             }
-            // This is for the bottom-right window
-            dbus_top_analyzer::g_dbus_statistics.OnNewDBusMessage(
-                sender_uniq.c_str(), dest_uniq.c_str(), interface, path, member,
-                type, m);
+
             sd_bus_message_unref(m);
         }
         r = sd_bus_wait(g_bus,
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);
diff --git a/main.hpp b/main.hpp
index 7debec3..4c68dc9 100644
--- a/main.hpp
+++ b/main.hpp
@@ -33,3 +33,4 @@
 std::string FloatToString(float value);
 template <typename T>
 void HistoryBarGraph(WINDOW* win, const Rect& rect, BarGraph<T>* bargraph);
+void DBusTopUpdateFooterView();
diff --git a/sensorhelper.cpp b/sensorhelper.cpp
index ed482e7..7326d47 100644
--- a/sensorhelper.cpp
+++ b/sensorhelper.cpp
@@ -16,11 +16,13 @@
 
 #include "main.hpp"
 
+#include <systemd/sd-bus.h>
 #include <unistd.h>
 
 #include <cassert>
 #include <fstream>
 #include <functional>
+#include <map>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -101,7 +103,7 @@
 }
 
 std::vector<std::string> FindAllObjectPathsForService(
-    [[maybe_unused]] const std::string& service,
+    [[maybe_unused]] sd_bus* bus, [[maybe_unused]] const std::string& service,
     [[maybe_unused]] std::function<void(const std::string&,
                                         const std::vector<std::string>&)>
         on_interface_cb)
@@ -130,3 +132,35 @@
         return "";
     return s.substr(idx0, idx1 - idx0 + 1);
 }
+
+int GetOrInsertPathID(std::map<std::string, int>* lookup,
+                      const std::string& path)
+{
+    if (lookup->find(path) == lookup->end())
+    {
+        (*lookup)[path] = lookup->size();
+    }
+    return lookup->at(path);
+}
+
+std::string SimplifyPath(std::string s)
+{
+    const std::string& k = "/xyz/openbmc_project";
+    if (s != k && s.find(k) == 0)
+    {
+        s = s.substr(k.size());
+    }
+    return s;
+}
+
+std::pair<std::string, std::string> ExtractFileName(std::string x)
+{
+    size_t idx = x.rfind('/');
+    std::string d = "";
+    if (idx != std::string::npos)
+    {
+        d = x.substr(0, idx);
+        x = x.substr(idx);
+    }
+    return {d, x};
+}
\ No newline at end of file
diff --git a/sensorhelper.hpp b/sensorhelper.hpp
index 3aa7c0f..1228339 100644
--- a/sensorhelper.hpp
+++ b/sensorhelper.hpp
@@ -18,12 +18,15 @@
 // This is the form a sensor assumes on DBus.
 // Aggregates their view from all other daemons.
 #include <bitset>
+#include <map>
 #include <optional>
 #include <set>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
+
+std::pair<std::string, std::string> ExtractFileName(std::string x);
 // Where is this sensor seen?
 constexpr int VISIBILITY_OBJECT_MAPPER = 0;
 constexpr int VISIBILITY_HWMON = 1;
@@ -226,6 +229,11 @@
         return ret;
     }
 
+    explicit SensorSnapshot()
+    {
+        connection_snapshot_ = nullptr;
+    }
+
     explicit SensorSnapshot(DBusConnectionSnapshot* cs)
     {
         connection_snapshot_ = cs;
@@ -285,7 +293,7 @@
     }
 
     // This sensor is visible from Object Mapper
-    void SerSensorVisibleFromObjectMapper(const std::string& service,
+    void SetSensorVisibleFromObjectMapper(const std::string& service,
                                           const std::string& object)
     {
         Sensor* s = FindOrCreateSensorByServiceAndObject(service, object);
@@ -329,10 +337,63 @@
         return nullptr;
     }
 
+    void AddAssociationEndpoints(const std::string& path,
+                                 const std::set<std::string>& entries)
+    {
+        associations_[path] = entries;
+    }
+
+    // Input: object path (regardless of what service name)
+    // out_edges: what associations does `path` declare?
+    // in_edges: what associations have `path` as endpoints?
+    void FindAssociationEndpoints(
+        const std::string& path,
+        std::map<std::string, std::set<std::string>>* out_edges,
+        std::map<std::string, std::set<std::string>>* in_edges)
+    {
+        std::map<std::string, std::set<std::string>> out, in;
+        for (const auto& [k, v] : associations_)
+        {
+            if (k.find(path) == 0)
+            {
+                out[k.substr(path.size())] = v;
+            }
+            for (const std::string& entry : v)
+            {
+                if (entry.find(path) == 0)
+                {
+                    std::pair<std::string, std::string> p = ExtractFileName(k);
+                    in[p.second].insert(k);
+                }
+            }
+        }
+
+        *out_edges = out;
+        *in_edges = in;
+    }
+
+    void AddAssociationDefinition(const std::string& path,
+                                  const std::string& forward,
+                                  const std::string& reverse,
+                                  const std::string& endpoint)
+    {
+        std::vector<std::string> d = {path, forward, reverse, endpoint};
+        association_definitions_.push_back(d);
+    }
+
   private:
     std::vector<Sensor*> sensors_;
     std::unordered_map<std::string, int> conn2pid_;
     DBusConnectionSnapshot* connection_snapshot_;
+
+    // Associations seen from Inventory objects
+    // Key: the path of the object that has the Association interface
+    // Value: all the endpoints
+    std::unordered_map<std::string, std::set<std::string>> associations_;
+
+    // Association Definitions
+    // Ideally, this should match associations_ above
+    std::vector<std::vector<std::string>> association_definitions_;
 };
 
 bool IsSensorObjectPath(const std::string& s);
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);
 }
diff --git a/views.hpp b/views.hpp
index 149411a..b661389 100644
--- a/views.hpp
+++ b/views.hpp
@@ -349,9 +349,10 @@
         curr_sensor_id_ = sensor_ids_[choice_];
     }
 
-    // Cache the sensor list in the sensor snapshot
+    // Make a copy of the SensorSnapshot object for display usage
     void UpdateSensorSnapshot(SensorSnapshot* snapshot)
     {
+        sensor_snapshot_ = *snapshot;
         std::string old_sensor_id = "";
         if (choice_ != -999)
         {
@@ -404,6 +405,7 @@
 
     State state;
     std::string GetStatusString() override;
+    SensorSnapshot sensor_snapshot_;
 };
 
 class DBusStatListView : public DBusTopWindow
@@ -532,4 +534,10 @@
     {
         return "";
     }
+
+    void SetStatusString(const std::string& s)
+    {
+        status_string_ = s;
+    }
+    std::string status_string_;
 };