| // 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 <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; |
| 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() |
| { |
| connection_snapshot_ = nullptr; |
| } |
| |
| 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 SetSensorVisibleFromObjectMapper(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; |
| } |
| |
| 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); |
| bool IsUniqueName(const std::string& x); |
| std::string Trim(const std::string& s); |