dbus-top: initial commits
This commit covers the basic functionalities of the dbus-top tool.
The UI is divided into 3 windows as follows:
+--------------------------+ Window list
| Window A | A: Summary statistics
+------------+-------------+ B: Sensor list or detail
| Window B | Window C | C: Detailed statistics
+------------+-------------+
To navigate the UI:
* Use tab to navigate each window
When a window is highlighted:
In Window B:
* Press esc key 3 times to leave the current sensor selection
In Window C:
* Press [Enter] to show/hide pop-up menu for column selectio
* Press [Left] to move highlight cursor to the left
* Press [Right] to move highlight cursor to the right
* Press [A] to sort by the highlighted column in ascending order
* Press [D] to sort by the highlighted column in descending order
To add recipe to Yocto and build the recipe:
1) Copy and paste the content of the .bb file into a folder that can be
detected by bitbake, such as meta-phosphor/recipes-phosphor/ipmi.
2) run "devtool modify -n dbus-top (path_to_openbmc_tools)/dbus-top/".
Signed-off-by: Adedeji Adebisi <adedejiadebisi01@gmail.com>
Change-Id: Id58ba30b815cfd9d18f54cf477d749dbdbc4545b
diff --git a/dbus-top/analyzer.cpp b/dbus-top/analyzer.cpp
new file mode 100644
index 0000000..f31868c
--- /dev/null
+++ b/dbus-top/analyzer.cpp
@@ -0,0 +1,624 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "analyzer.hpp"
+#include "histogram.hpp"
+#include "main.hpp"
+#include "sensorhelper.hpp"
+#include "views.hpp"
+#include "xmlparse.hpp"
+
+#include <unistd.h>
+#include <atomic>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+extern SensorSnapshot* g_sensor_snapshot;
+extern DBusConnectionSnapshot* g_connection_snapshot;
+extern sd_bus* g_bus;
+extern SensorDetailView* g_sensor_detail_view;
+
+static std::unordered_map<uint64_t, uint64_t>
+ in_flight_methodcalls; // serial => microseconds
+uint64_t Microseconds()
+{
+ long us; // usec
+ time_t s; // Seconds
+ struct timespec spec;
+ clock_gettime(CLOCK_REALTIME, &spec);
+ s = spec.tv_sec;
+ us = round(spec.tv_nsec / 1000); // Convert nanoseconds to milliseconds
+ if (us > 999999)
+ {
+ s++;
+ us = 0;
+ }
+ return s * 1000000 + us;
+}
+
+int g_update_interval_millises = 2000;
+int GetSummaryIntervalInMillises()
+{
+ return g_update_interval_millises;
+}
+
+bool DBusTopSortFieldIsNumeric(DBusTopSortField field)
+{
+ switch (field)
+ {
+ case kSender:
+ case kDestination:
+ case kInterface:
+ case kPath:
+ case kMember:
+ case kSenderCMD:
+ return false;
+ case kSenderPID:
+ case kMsgPerSec:
+ case kAverageLatency:
+ return true;
+ }
+ return false;
+}
+
+namespace dbus_top_analyzer
+{
+ DBusTopStatistics g_dbus_statistics;
+ Histogram<float> g_mc_time_histogram;
+ std::unordered_map<uint32_t, uint64_t> in_flight_methodcalls;
+ std::atomic<bool> g_program_done = false;
+ std::chrono::time_point<std::chrono::steady_clock> g_last_update;
+ DBusTopStatisticsCallback g_callback;
+ void SetDBusTopStatisticsCallback(DBusTopStatisticsCallback cb)
+ {
+ g_callback = cb;
+ }
+
+ int UserInputThread()
+ {
+ return 0;
+ }
+
+ std::string g_dbus_top_conn = " ";
+ void SetDBusTopConnectionForMonitoring(const std::string& conn)
+ {
+ g_dbus_top_conn = conn;
+ }
+
+ // Performs one step of analysis
+ void Process()
+ {
+ std::chrono::time_point<std::chrono::steady_clock> t =
+ std::chrono::steady_clock::now();
+ std::chrono::time_point<std::chrono::steady_clock> next_update =
+ g_last_update + std::chrono::milliseconds(g_update_interval_millises);
+ if (t >= next_update)
+ {
+ float seconds_since_last_sample =
+ std::chrono::duration_cast<std::chrono::microseconds>(t -
+ g_last_update)
+ .count() /
+ 1000000.0f;
+ g_dbus_statistics.seconds_since_last_sample_ =
+ seconds_since_last_sample;
+ // Update snapshot
+ if (g_callback)
+ {
+ g_callback(&g_dbus_statistics, &g_mc_time_histogram);
+ }
+ g_dbus_statistics.Reset();
+ g_last_update = t;
+ }
+ }
+
+ void Finish()
+ {
+ g_program_done = true;
+ }
+
+ std::vector<std::string> FindAllObjectPathsForService(
+ const std::string& service,
+ std::function<void(const std::string&, const std::vector<std::string>&)>
+ on_interface_cb)
+ {
+ sd_bus_error err = SD_BUS_ERROR_NULL;
+ sd_bus_message *m, *reply;
+ std::vector<std::string> paths; // Current iteration
+ std::vector<std::string>
+ all_obj_paths; // All object paths under the supervision of ObjectMapper
+ paths.push_back("/");
+ // busctl call xyz.openbmc_project.ObjectMapper /
+ // org.freedesktop.DBus.Introspectable Introspect
+ while (!paths.empty())
+ {
+ // printf("%d paths to explore, total %d paths so far\n",
+ // int(paths.size()), int(all_obj_paths.size()));
+ std::vector<std::string> new_paths;
+ for (const std::string& obj_path : paths)
+ {
+ 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(),
+ "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);
+ if (r < 0)
+ {
+ printf("Could not execute method call, r=%d, strerror=%s\n", r,
+ strerror(-r));
+ }
+ 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));
+ }
+ else
+ {
+ XMLNode* t = ParseXML(s1);
+ std::vector<std::string> ch = t->GetChildNodeNames();
+ if (on_interface_cb != nullptr)
+ {
+ on_interface_cb(obj_path, t->GetInterfaceNames());
+ }
+ DeleteTree(t);
+ for (const std::string& cn : ch)
+ {
+ std::string ch_path = obj_path;
+ if (obj_path.back() == '/')
+ {}
+ else
+ ch_path.push_back('/');
+ ch_path += cn;
+ new_paths.push_back(ch_path);
+ }
+ }
+ }
+ }
+ paths = new_paths;
+ }
+ return all_obj_paths;
+ }
+
+ void ListAllSensors()
+ {
+ g_connection_snapshot = new DBusConnectionSnapshot();
+ printf("1. Getting names\n");
+ char** names;
+ int r = sd_bus_list_names(g_bus, &names, nullptr);
+ std::vector<std::string> services;
+ std::vector<int> pids;
+ std::vector<std::string> comms;
+ for (char** ptr = names; ptr && *ptr; ++ptr)
+ {
+ services.push_back(*ptr);
+ free(*ptr);
+ }
+ free(names);
+ printf("2. Getting creds of each name\n");
+ for (int i = 0; i < static_cast<int>(services.size()); i++)
+ {
+ const std::string& service = services[i];
+ sd_bus_creds* creds = nullptr;
+ r = sd_bus_get_name_creds(g_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 |
+ SD_BUS_CREDS_UNIT | SD_BUS_CREDS_SESSION |
+ SD_BUS_CREDS_DESCRIPTION,
+ &creds);
+ // 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);
+ }
+ pids.push_back(pid);
+ // comm
+ std::string comm;
+ if (pid != INVALID)
+ {
+ std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline");
+ std::string line;
+ std::getline(ifs, line);
+ for (char c : line)
+ {
+ if (c < 32 || c >= 127)
+ c = ' ';
+ comm.push_back(c);
+ }
+ }
+ comms.push_back(comm);
+ // unique name, also known as "Connection"
+ std::string connection;
+ const char* u;
+ r = sd_bus_creds_get_unique_name(creds, &u);
+ if (r >= 0)
+ {
+ 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);
+ }
+ 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");
+ std::vector<std::string> all_obj_paths = FindAllObjectPathsForService(
+ "xyz.openbmc_project.ObjectMapper", nullptr);
+ 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)
+ {
+ if (IsSensorObjectPath(p))
+ {
+ err = SD_BUS_ERROR_NULL;
+ r = sd_bus_message_new_method_call(
+ g_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);
+ 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)
+ {
+ r = sd_bus_message_enter_container(
+ 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)
+ {}
+ else
+ {
+ // The following 2 correspond to `interface_map` in
+ // phosphor-mapper
+ const char* interface_map_first;
+ r = sd_bus_message_read_basic(reply, 's',
+ &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;
+ while (true)
+ {
+ const char* interface_map_second;
+ 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"))
+ {
+ has_value_interface = true;
+ }
+ }
+ }
+ if (has_value_interface)
+ {
+ g_sensor_snapshot->SerSensorVisibleFromObjectMapper(
+ std::string(interface_map_first), p);
+ }
+ r = sd_bus_message_exit_container(reply);
+ }
+ r = sd_bus_message_exit_container(reply);
+ }
+ r = sd_bus_message_exit_container(reply);
+ }
+ DONE:
+ {}
+ }
+ }
+ printf("4. Check Hwmon's DBus objects\n");
+ for (int i = 0; i < int(comms.size()); i++)
+ {
+ const std::string& comm = comms[i];
+ const std::string& service = services[i];
+ if (comm.find("phosphor-hwmon-readd") != std::string::npos &&
+ !IsUniqueName(service))
+ {
+ // printf("Should introspect %s\n", service.c_str());
+ std::vector<std::string> objpaths =
+ FindAllObjectPathsForService(service, nullptr);
+ for (const std::string& op : objpaths)
+ {
+ if (IsSensorObjectPath(op))
+ {
+ g_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();
+ }
+} // namespace dbus_top_analyzer
+
+void DBusTopStatistics::OnNewDBusMessage(const char* sender,
+ const char* destination,
+ const char* interface,
+ const char* path, const char* member,
+ const char type, sd_bus_message* m)
+{
+ num_messages_++;
+ std::vector<std::string> keys;
+
+ std::string sender_orig = CheckAndFixNullString(sender);
+ std::string dest_orig = CheckAndFixNullString(destination);
+ // For method return messages, we actually want to show the sender
+ // and destination of the original method call, so we swap the
+ // sender and destination
+ if (type == 2)
+ { // DBUS_MESSAGE_TYPE_METHOD_METHOD_RETURN
+ std::swap(sender_orig, dest_orig);
+ }
+
+ // Special case: when PID == 1 (init), the DBus unit would be systemd.
+ // It seems it was not possible to obtain the connection name of systemd
+ // so we manually set it here.
+ const int sender_orig_pid =
+ g_connection_snapshot->GetConnectionPIDFromNameOrUniqueName(
+ sender_orig);
+
+ if (sender_orig_pid == 1)
+ {
+ sender_orig = "systemd";
+ }
+ const int dest_orig_pid =
+ g_connection_snapshot->GetConnectionPIDFromNameOrUniqueName(dest_orig);
+ if (dest_orig_pid == 1)
+ {
+ dest_orig = "systemd";
+ }
+
+ for (DBusTopSortField field : fields_)
+ {
+ switch (field)
+ {
+ case kSender:
+ keys.push_back(sender_orig);
+ break;
+ case kDestination:
+ keys.push_back(dest_orig);
+ break;
+ case kInterface:
+ keys.push_back(CheckAndFixNullString(interface));
+ break;
+ case kPath:
+ keys.push_back(CheckAndFixNullString(path));
+ break;
+ case kMember:
+ keys.push_back(CheckAndFixNullString(member));
+ break;
+ case kSenderPID:
+ {
+ if (sender_orig_pid != INVALID)
+ {
+ keys.push_back(std::to_string(sender_orig_pid));
+ }
+ else
+ {
+ keys.push_back("(unknown)");
+ }
+ break;
+ }
+ case kSenderCMD:
+ {
+ keys.push_back(
+ g_connection_snapshot->GetConnectionCMDFromNameOrUniqueName(
+ sender_orig));
+ break;
+ }
+ case kMsgPerSec:
+ case kAverageLatency:
+ break; // Don't populate "keys" using these 2 fields
+ }
+ }
+ // keys = combination of fields of user's choice
+
+ if (stats_.count(keys) == 0)
+ {
+ stats_[keys] = DBusTopComputedMetrics();
+ }
+ // Need to update msg/s regardless
+ switch (type)
+ {
+ case 1: // DBUS_MESSAGE_TYPE_METHOD_CALL
+ stats_[keys].num_method_calls++;
+ break;
+ case 2: // DBUS_MESSAGE_TYPE_METHOD_METHOD_RETURN
+ stats_[keys].num_method_returns++;
+ break;
+ case 3: // DBUS_MESSAGE_TYPE_ERROR
+ stats_[keys].num_errors++;
+ break;
+ case 4: // DBUS_MESSAGE_TYPE_SIGNAL
+ stats_[keys].num_signals++;
+ break;
+ }
+ // Update global latency histogram
+ // For method call latency
+ if (type == 1) // DBUS_MESSAGE_TYPE_METHOD_CALL
+ {
+ uint64_t serial; // serial == cookie
+ sd_bus_message_get_cookie(m, &serial);
+ in_flight_methodcalls[serial] = Microseconds();
+ }
+ else if (type == 2) // DBUS_MESSAGE_TYPE_MEHOTD_RETURN
+ {
+ uint64_t reply_serial = 0; // serial == cookie
+ sd_bus_message_get_reply_cookie(m, &reply_serial);
+ if (in_flight_methodcalls.count(reply_serial) > 0)
+ {
+ float dt_usec =
+ Microseconds() - in_flight_methodcalls[reply_serial];
+ in_flight_methodcalls.erase(reply_serial);
+ dbus_top_analyzer::g_mc_time_histogram.AddSample(dt_usec);
+
+ // Add method call count and total latency to the corresponding key
+ stats_[keys].total_latency_usec += dt_usec;
+ }
+ }
+ // For meaning of type see here
+ // https://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html#ga4a9012edd7f22342f845e98150aeb858
+ switch (type)
+ {
+ case 1:
+ num_mc_++;
+ break;
+ case 2:
+ num_mr_++;
+ break;
+ case 3:
+ num_error_++;
+ break;
+ case 4:
+ num_sig_++;
+ break;
+ }
+}