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/sensorhelper.hpp b/dbus-top/sensorhelper.hpp
new file mode 100644
index 0000000..a619c4a
--- /dev/null
+++ b/dbus-top/sensorhelper.hpp
@@ -0,0 +1,342 @@
+// 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.
+
+#pragma once
+
+#include "main.hpp"
+// This is the form a sensor assumes on DBus.
+// Aggregates their view from all other daemons.
+#include <bitset>
+#include <optional>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+// Where is this sensor seen?
+constexpr int VISIBILITY_OBJECT_MAPPER = 0;
+constexpr int VISIBILITY_HWMON = 1;
+constexpr int VISIBILITY_IPMITOOL_SDR = 2;
+class DBusConnection
+{
+ public:
+ std::string service; // example: "systemd-resolved"
+ std::string connection; // example: ":1.1"
+ std::string cmd; // the comm line
+ std::string unit; // example: "systemd-resolved.service"
+ int pid;
+ // For actual DBus capture: service name, connection name, command line,
+ // systmed unit and PID are all known
+ DBusConnection(const std::string& _s, const std::string& _c,
+ const std::string& _cmd, const std::string& _u, int _pid)
+ {
+ service = _s;
+ connection = _c;
+ cmd = _cmd;
+ unit = _u;
+ pid = _pid;
+ }
+
+ // During PCap replay: only service name, connection name, and PID are known
+ // cmd and unit are not known since they are not
+ // stored in the PCap file
+ DBusConnection(const std::string& _s, const std::string& _c, int _pid)
+ {
+ service = _s;
+ connection = _c;
+ pid = _pid;
+ }
+};
+
+class DBusConnectionSnapshot
+{
+ public:
+ std::vector<DBusConnection*> connections_;
+ std::unordered_map<std::string, DBusConnection*> unique_name_to_cxn;
+ DBusConnection* FindDBusConnectionByService(const std::string& service)
+ {
+ for (DBusConnection* cxn : connections_)
+ {
+ if (cxn->service == service)
+ return cxn;
+ }
+ return nullptr;
+ }
+
+ void SetConnectionPID(const std::string& connection, int pid)
+ {
+ DBusConnection* cxn = FindDBusConnectionByService(connection);
+ if (cxn != nullptr)
+ {
+ cxn->pid = pid;
+ unique_name_to_cxn[connection] = cxn; // Just to make sure
+ }
+ }
+
+ void SetConnectionUniqueName(const std::string& service,
+ const std::string& unique_name)
+ {
+ DBusConnection* cxn = FindDBusConnectionByService(service);
+ if (cxn != nullptr)
+ {
+ cxn->connection = unique_name;
+ unique_name_to_cxn[unique_name] = cxn;
+ }
+ }
+
+ DBusConnection* FindDBusConnectionByConnection(const std::string& conn)
+ {
+ for (DBusConnection* cxn : connections_)
+ {
+ if (cxn->connection == conn)
+ return cxn;
+ }
+ return nullptr;
+ }
+
+
+ // Only when service is known (during playback)
+ void AddConnection(const std::string& _s)
+ {
+ connections_.push_back(new DBusConnection(_s, "", "", "", INVALID));
+ }
+
+ // When all 5 pieces of details are known (during actual capture)
+ void AddConnection(const std::string& _s, const std::string& _connection,
+ const std::string& _cmd, const std::string& _unit,
+ int _pid)
+ {
+ DBusConnection* cxn =
+ new DBusConnection(_s, _connection, _cmd, _unit, _pid);
+ connections_.push_back(cxn);
+ unique_name_to_cxn[_connection] = cxn;
+ }
+
+ int GetConnectionPIDFromNameOrUniqueName(const std::string& key)
+ {
+ if (unique_name_to_cxn.find(key) == unique_name_to_cxn.end())
+ {
+ return INVALID;
+ }
+ else
+ {
+ return unique_name_to_cxn[key]->pid;
+ }
+ }
+
+ std::string GetConnectionCMDFromNameOrUniqueName(const std::string& key)
+ {
+ if (unique_name_to_cxn.find(key) == unique_name_to_cxn.end())
+ {
+ return "(unknown)";
+ }
+ else
+ {
+ return unique_name_to_cxn[key]->cmd;
+ }
+ }
+
+ std::string GetUniqueNameIfExists(const std::string service)
+ {
+ for (DBusConnection* cxn : connections_)
+ {
+ if (cxn->service == service)
+ return cxn->connection;
+ }
+ return service;
+ }
+
+};
+
+// Each sensor might have different units, for example current and voltage
+class Sensor
+{
+ public:
+ DBusConnection* connection_;
+ // Example: "/xyz/openbmc_project/sensors/temperature/powerseq_temp"
+ std::string object_path_;
+ std::string SensorID()
+ {
+ const size_t idx = object_path_.rfind('/');
+ if (idx != std::string::npos)
+ {
+ return object_path_.substr(idx + 1);
+ }
+ else
+ return ("unknown sensor");
+ }
+
+ std::string ServiceName()
+ {
+ if (connection_ == nullptr)
+ return "";
+ else
+ return connection_->service;
+ }
+
+ std::string ConnectionName()
+ {
+ if (connection_ == nullptr)
+ return "";
+ else
+ return connection_->connection;
+ }
+
+ std::string ObjectPath()
+ {
+ return object_path_;
+ }
+
+ // Should contain the following:
+ // 1. "org.freedesktop.DBus.Introspectable"
+ // 2. "org.freedesktop.DBus.Peer"
+ // 3. "org.freedesktop.DBus.Properties"
+ // 4. "xyz.openbmc_project.Sensor.Value"
+ // 5. "xyz.openbmc_project.State.Decorator.OperationalStatus"
+ std::set<std::string> interfaces_;
+ std::bitset<4> visibility_flags_;
+ std::set<std::string> associations_;
+};
+
+class SensorSnapshot
+{
+ public:
+ std::vector<std::string> GetDistinctSensorNames()
+ {
+ std::unordered_set<std::string> seen;
+ std::vector<std::string> ret;
+ for (Sensor* s : sensors_)
+ {
+ std::string sn = s->SensorID();
+ if (seen.find(sn) == seen.end())
+ {
+ ret.push_back(sn);
+ seen.insert(sn);
+ }
+ }
+ return ret;
+ }
+
+ explicit SensorSnapshot(DBusConnectionSnapshot* cs)
+ {
+ connection_snapshot_ = cs;
+ }
+
+ ~SensorSnapshot()
+ {
+ for (Sensor* s : sensors_)
+ {
+ delete s;
+ }
+ }
+
+ int SensorCount()
+ {
+ return int(sensors_.size());
+ }
+
+ Sensor* FindOrCreateSensorByServiceAndObject(const std::string& service,
+ const std::string& object)
+ {
+ Sensor* ret = nullptr;
+ for (Sensor* s : sensors_)
+ {
+ if (s->ServiceName() == service && s->object_path_ == object)
+ {
+ ret = s;
+ break;
+ }
+ }
+ if (ret == nullptr)
+ {
+ DBusConnection* cxn =
+ connection_snapshot_->FindDBusConnectionByService(service);
+ ret = new Sensor();
+ ret->connection_ = cxn;
+ ret->object_path_ = object;
+ sensors_.push_back(ret);
+ }
+ return ret;
+ }
+
+ // Note: one sensor_id might correspond to multiple sensors.
+ // Example: "VDD" can have all 3 of power, current and voltage.
+ std::vector<Sensor*> FindSensorsBySensorID(const std::string& sensor_id)
+ {
+ std::vector<Sensor*> ret;
+ for (Sensor* s : sensors_)
+ {
+ const std::string& p = s->object_path_;
+ if (p.find(sensor_id) == p.size() - sensor_id.size())
+ {
+ ret.push_back(s);
+ }
+ }
+ return ret;
+ }
+
+ // This sensor is visible from Object Mapper
+ void SerSensorVisibleFromObjectMapper(const std::string& service,
+ const std::string& object)
+ {
+ Sensor* s = FindOrCreateSensorByServiceAndObject(service, object);
+ s->visibility_flags_.set(VISIBILITY_OBJECT_MAPPER);
+ }
+
+ // This sensor is visible from Hwmon
+ void SetSensorVisibleFromHwmon(const std::string& service,
+ const std::string& object)
+ {
+ Sensor* s = FindOrCreateSensorByServiceAndObject(service, object);
+ s->visibility_flags_.set(VISIBILITY_HWMON);
+ }
+
+ // This sensor is visible from `ipmitool sdr`
+ // The first column is referred to as "sensorid".
+ void SetSensorVisibleFromIpmitoolSdr(const std::string& sensor_id)
+ {
+ std::vector<Sensor*> sensors = FindSensorsBySensorID(sensor_id);
+ for (Sensor* s : sensors)
+ s->visibility_flags_.set(VISIBILITY_IPMITOOL_SDR);
+ }
+
+ void PrintSummary()
+ {
+ for (Sensor* s : sensors_)
+ {
+ printf("%50s %50s %9s\n", s->ServiceName().c_str(),
+ s->object_path_.c_str(),
+ s->visibility_flags_.to_string().c_str());
+ }
+ }
+
+ Sensor* FindSensorByDBusUniqueNameOrServiceName(const std::string& key)
+ {
+ for (Sensor* s : sensors_)
+ {
+ if (s->ConnectionName() == key || s->ServiceName() == key)
+ return s;
+ }
+ return nullptr;
+ }
+
+ private:
+ std::vector<Sensor*> sensors_;
+ std::unordered_map<std::string, int> conn2pid_;
+ DBusConnectionSnapshot* connection_snapshot_;
+};
+
+bool IsSensorObjectPath(const std::string& s);
+bool IsUniqueName(const std::string& x);
+std::string Trim(const std::string& s);