| // 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; |
| } |
| } |