blob: 81988a106a5499b336f2809a86405cfb6635ac4b [file] [log] [blame]
// 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 <cassert>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#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
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(
sd_bus* bus, 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(
bus, &m, service.c_str(), obj_path.c_str(),
"org.freedesktop.DBus.Introspectable", "Introspect");
if (r < 0)
{
continue;
}
r = sd_bus_call(bus, m, 0, &err, &reply);
if (r < 0)
{
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);
if (r < 0)
{
continue;
}
else
{
XMLNode* t = ParseXML(std::string(s));
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(sd_bus* bus, DBusConnectionSnapshot** cxn_snapshot,
SensorSnapshot** sensor_snapshot)
{
// 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(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);
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(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)
{}
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;
}
std::string unit;
r = sd_bus_creds_get_unit(creds, &u);
if (r >= 0)
{
unit = u;
}
(*cxn_snapshot)->AddConnection(service, connection, comm, unit, pid);
}
// busctl call xyz.openbmc_project.ObjectMapper /
// org.freedesktop.DBus.Introspectable Introspect
g_footer_view->SetStatusString("3. Examine objects in ObjectMapper");
std::vector<std::string> all_obj_paths = FindAllObjectPathsForService(
bus, "xyz.openbmc_project.ObjectMapper", nullptr);
N = all_obj_paths.size();
sd_bus_error err = SD_BUS_ERROR_NULL;
sd_bus_message *m, *reply;
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(
bus, &m, "xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetObject");
if (r < 0)
{
continue;
}
r = sd_bus_message_append_basic(m, 's', p.c_str());
if (r < 0)
{
continue;
}
// empty array
r = sd_bus_message_open_container(m, 'a', "s");
if (r < 0)
{
continue;
}
r = sd_bus_message_close_container(m);
if (r < 0)
{
continue;
}
r = sd_bus_call(bus, m, 0, &err, &reply);
if (r < 0)
{}
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)
{
continue;
}
while (true)
{
r = sd_bus_message_enter_container(
reply, SD_BUS_TYPE_DICT_ENTRY, "sas");
if (r < 0)
{
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)
{
goto DONE;
}
r = sd_bus_message_enter_container(reply, 'a', "s");
if (r < 0)
{
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)
{}
else if (r == 0)
break;
else
{
if (!strcmp(interface_map_second,
"xyz.openbmc_project.Sensor.Value"))
{
has_value_interface = true;
}
}
}
if (has_value_interface)
{
(*sensor_snapshot)
->SetSensorVisibleFromObjectMapper(
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:
{}
}
}
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 &&
!IsUniqueName(service))
{
// printf("Should introspect %s\n", service.c_str());
std::vector<std::string> objpaths =
FindAllObjectPathsForService(bus, service, nullptr);
for (const std::string& op : objpaths)
{
if (IsSensorObjectPath(op))
{
(*sensor_snapshot)->SetSensorVisibleFromHwmon(service, op);
}
}
}
}
g_footer_view->SetStatusString("DBus object scan complete");
DBusTopUpdateFooterView();
}
} // 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;
}
}