blob: f31868c67c848c5a21ef795ba26ba2cb324bb0ee [file] [log] [blame]
Adedeji Adebisi12c5f112021-07-22 18:07:52 +00001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "analyzer.hpp"
16#include "histogram.hpp"
17#include "main.hpp"
18#include "sensorhelper.hpp"
19#include "views.hpp"
20#include "xmlparse.hpp"
21
22#include <unistd.h>
23#include <atomic>
24#include <filesystem>
25#include <fstream>
26#include <functional>
27#include <iostream>
28#include <sstream>
29#include <string>
30
31extern SensorSnapshot* g_sensor_snapshot;
32extern DBusConnectionSnapshot* g_connection_snapshot;
33extern sd_bus* g_bus;
34extern SensorDetailView* g_sensor_detail_view;
35
36static std::unordered_map<uint64_t, uint64_t>
37 in_flight_methodcalls; // serial => microseconds
38uint64_t Microseconds()
39{
40 long us; // usec
41 time_t s; // Seconds
42 struct timespec spec;
43 clock_gettime(CLOCK_REALTIME, &spec);
44 s = spec.tv_sec;
45 us = round(spec.tv_nsec / 1000); // Convert nanoseconds to milliseconds
46 if (us > 999999)
47 {
48 s++;
49 us = 0;
50 }
51 return s * 1000000 + us;
52}
53
54int g_update_interval_millises = 2000;
55int GetSummaryIntervalInMillises()
56{
57 return g_update_interval_millises;
58}
59
60bool DBusTopSortFieldIsNumeric(DBusTopSortField field)
61{
62 switch (field)
63 {
64 case kSender:
65 case kDestination:
66 case kInterface:
67 case kPath:
68 case kMember:
69 case kSenderCMD:
70 return false;
71 case kSenderPID:
72 case kMsgPerSec:
73 case kAverageLatency:
74 return true;
75 }
76 return false;
77}
78
79namespace dbus_top_analyzer
80{
81 DBusTopStatistics g_dbus_statistics;
82 Histogram<float> g_mc_time_histogram;
83 std::unordered_map<uint32_t, uint64_t> in_flight_methodcalls;
84 std::atomic<bool> g_program_done = false;
85 std::chrono::time_point<std::chrono::steady_clock> g_last_update;
86 DBusTopStatisticsCallback g_callback;
87 void SetDBusTopStatisticsCallback(DBusTopStatisticsCallback cb)
88 {
89 g_callback = cb;
90 }
91
92 int UserInputThread()
93 {
94 return 0;
95 }
96
97 std::string g_dbus_top_conn = " ";
98 void SetDBusTopConnectionForMonitoring(const std::string& conn)
99 {
100 g_dbus_top_conn = conn;
101 }
102
103 // Performs one step of analysis
104 void Process()
105 {
106 std::chrono::time_point<std::chrono::steady_clock> t =
107 std::chrono::steady_clock::now();
108 std::chrono::time_point<std::chrono::steady_clock> next_update =
109 g_last_update + std::chrono::milliseconds(g_update_interval_millises);
110 if (t >= next_update)
111 {
112 float seconds_since_last_sample =
113 std::chrono::duration_cast<std::chrono::microseconds>(t -
114 g_last_update)
115 .count() /
116 1000000.0f;
117 g_dbus_statistics.seconds_since_last_sample_ =
118 seconds_since_last_sample;
119 // Update snapshot
120 if (g_callback)
121 {
122 g_callback(&g_dbus_statistics, &g_mc_time_histogram);
123 }
124 g_dbus_statistics.Reset();
125 g_last_update = t;
126 }
127 }
128
129 void Finish()
130 {
131 g_program_done = true;
132 }
133
134 std::vector<std::string> FindAllObjectPathsForService(
135 const std::string& service,
136 std::function<void(const std::string&, const std::vector<std::string>&)>
137 on_interface_cb)
138 {
139 sd_bus_error err = SD_BUS_ERROR_NULL;
140 sd_bus_message *m, *reply;
141 std::vector<std::string> paths; // Current iteration
142 std::vector<std::string>
143 all_obj_paths; // All object paths under the supervision of ObjectMapper
144 paths.push_back("/");
145 // busctl call xyz.openbmc_project.ObjectMapper /
146 // org.freedesktop.DBus.Introspectable Introspect
147 while (!paths.empty())
148 {
149 // printf("%d paths to explore, total %d paths so far\n",
150 // int(paths.size()), int(all_obj_paths.size()));
151 std::vector<std::string> new_paths;
152 for (const std::string& obj_path : paths)
153 {
154 all_obj_paths.push_back(obj_path);
155 int r = sd_bus_message_new_method_call(
156 g_bus, &m, service.c_str(), obj_path.c_str(),
157 "org.freedesktop.DBus.Introspectable", "Introspect");
158 if (r < 0)
159 {
160 printf("Oh! Cannot create new method call. r=%d, strerror=%s\n",
161 r, strerror(-r));
162 continue;
163 }
164 r = sd_bus_call(g_bus, m, 0, &err, &reply);
165 if (r < 0)
166 {
167 printf("Could not execute method call, r=%d, strerror=%s\n", r,
168 strerror(-r));
169 }
170 const char* sig = sd_bus_message_get_signature(reply, 0);
171 if (!strcmp(sig, "s"))
172 {
173 const char* s;
174 int r = sd_bus_message_read(reply, "s", &s);
175 std::string s1(s);
176 if (r < 0)
177 {
178 printf("Could not read string payload, r=%d, strerror=%s\n",
179 r, strerror(-r));
180 }
181 else
182 {
183 XMLNode* t = ParseXML(s1);
184 std::vector<std::string> ch = t->GetChildNodeNames();
185 if (on_interface_cb != nullptr)
186 {
187 on_interface_cb(obj_path, t->GetInterfaceNames());
188 }
189 DeleteTree(t);
190 for (const std::string& cn : ch)
191 {
192 std::string ch_path = obj_path;
193 if (obj_path.back() == '/')
194 {}
195 else
196 ch_path.push_back('/');
197 ch_path += cn;
198 new_paths.push_back(ch_path);
199 }
200 }
201 }
202 }
203 paths = new_paths;
204 }
205 return all_obj_paths;
206 }
207
208 void ListAllSensors()
209 {
210 g_connection_snapshot = new DBusConnectionSnapshot();
211 printf("1. Getting names\n");
212 char** names;
213 int r = sd_bus_list_names(g_bus, &names, nullptr);
214 std::vector<std::string> services;
215 std::vector<int> pids;
216 std::vector<std::string> comms;
217 for (char** ptr = names; ptr && *ptr; ++ptr)
218 {
219 services.push_back(*ptr);
220 free(*ptr);
221 }
222 free(names);
223 printf("2. Getting creds of each name\n");
224 for (int i = 0; i < static_cast<int>(services.size()); i++)
225 {
226 const std::string& service = services[i];
227 sd_bus_creds* creds = nullptr;
228 r = sd_bus_get_name_creds(g_bus, services[i].c_str(),
229 SD_BUS_CREDS_AUGMENT | SD_BUS_CREDS_EUID |
230 SD_BUS_CREDS_PID | SD_BUS_CREDS_COMM |
231 SD_BUS_CREDS_UNIQUE_NAME |
232 SD_BUS_CREDS_UNIT | SD_BUS_CREDS_SESSION |
233 SD_BUS_CREDS_DESCRIPTION,
234 &creds);
235 // PID
236 int pid = INVALID;
237 if (r < 0)
238 {
239 printf("Oh! Cannot get creds for %s\n", services[i].c_str());
240 }
241 else
242 {
243 r = sd_bus_creds_get_pid(creds, &pid);
244 }
245 pids.push_back(pid);
246 // comm
247 std::string comm;
248 if (pid != INVALID)
249 {
250 std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline");
251 std::string line;
252 std::getline(ifs, line);
253 for (char c : line)
254 {
255 if (c < 32 || c >= 127)
256 c = ' ';
257 comm.push_back(c);
258 }
259 }
260 comms.push_back(comm);
261 // unique name, also known as "Connection"
262 std::string connection;
263 const char* u;
264 r = sd_bus_creds_get_unique_name(creds, &u);
265 if (r >= 0)
266 {
267 connection = u;
268 }
269 else
270 {
271 printf("Oh! Could not get unique name for %s\n", service.c_str());
272 }
273 std::string unit;
274 r = sd_bus_creds_get_unit(creds, &u);
275 if (r >= 0)
276 {
277 unit = u;
278 }
279 else
280 {
281 printf("Oh! Could not get unit name for %s\n", unit.c_str());
282 }
283 printf("AddConnection %s %s %s %s %d\n", service.c_str(),
284 connection.c_str(), comm.c_str(), unit.c_str(), pid);
285 g_connection_snapshot->AddConnection(service, connection, comm, unit,
286 pid);
287 }
288 printf("There are %d DBus names.\n", int(services.size()));
289 for (int i = 0; i < int(services.size()); i++)
290 {
291 printf(" %d: %s [%s]\n", i, services[i].c_str(), comms[i].c_str());
292 }
293 g_sensor_snapshot = new SensorSnapshot(g_connection_snapshot);
294 // busctl call xyz.openbmc_project.ObjectMapper /
295 // org.freedesktop.DBus.Introspectable Introspect
296 printf("3. See which sensors are visible from Object Mapper\n");
297 printf("3.1. Introspect Object Mapper for object paths\n");
298 std::vector<std::string> all_obj_paths = FindAllObjectPathsForService(
299 "xyz.openbmc_project.ObjectMapper", nullptr);
300 sd_bus_error err = SD_BUS_ERROR_NULL;
301 sd_bus_message *m, *reply;
302 printf("%d paths found while introspecting ObjectMapper.\n",
303 int(all_obj_paths.size()));
304 printf("3.2. Call ObjectMapper's GetObject method against the sensor "
305 "object paths that represent sensors\n");
306 for (const std::string& p : all_obj_paths)
307 {
308 if (IsSensorObjectPath(p))
309 {
310 err = SD_BUS_ERROR_NULL;
311 r = sd_bus_message_new_method_call(
312 g_bus, &m, "xyz.openbmc_project.ObjectMapper",
313 "/xyz/openbmc_project/object_mapper",
314 "xyz.openbmc_project.ObjectMapper", "GetObject");
315 if (r < 0)
316 {
317 printf("Cannot create new method call. r=%d, strerror=%s\n", r,
318 strerror(-r));
319 continue;
320 }
321 r = sd_bus_message_append_basic(m, 's', p.c_str());
322 if (r < 0)
323 {
324 printf("Could not append a string parameter to m\n");
325 continue;
326 }
327 // empty array
328 r = sd_bus_message_open_container(m, 'a', "s");
329 if (r < 0)
330 {
331 printf("Could not open a container for m\n");
332 continue;
333 }
334 r = sd_bus_message_close_container(m);
335 if (r < 0)
336 {
337 printf("Could not close container for m\n");
338 continue;
339 }
340 r = sd_bus_call(g_bus, m, 0, &err, &reply);
341 if (r < 0)
342 {
343 printf("Error performing dbus method call\n");
344 }
345 const char* sig = sd_bus_message_get_signature(reply, 0);
346 if (!strcmp(sig, "a{sas}"))
347 {
348 r = sd_bus_message_enter_container(reply, 'a', "{sas}");
349 if (r < 0)
350 {
351 printf("Could not enter the level 0 array container\n");
352 continue;
353 }
354 while (true)
355 {
356 r = sd_bus_message_enter_container(
357 reply, SD_BUS_TYPE_DICT_ENTRY, "sas");
358 if (r < 0)
359 {
360 // printf("Could not enter the level 1 dict
361 // container\n");
362 goto DONE;
363 }
364 else if (r == 0)
365 {}
366 else
367 {
368 // The following 2 correspond to `interface_map` in
369 // phosphor-mapper
370 const char* interface_map_first;
371 r = sd_bus_message_read_basic(reply, 's',
372 &interface_map_first);
373 if (r < 0)
374 {
375 printf("Could not read interface_map_first\n");
376 goto DONE;
377 }
378 r = sd_bus_message_enter_container(reply, 'a', "s");
379 if (r < 0)
380 {
381 printf("Could not enter the level 2 array "
382 "container\n");
383 goto DONE;
384 }
385 bool has_value_interface = false;
386 while (true)
387 {
388 const char* interface_map_second;
389 r = sd_bus_message_read_basic(
390 reply, 's', &interface_map_second);
391 if (r < 0)
392 {
393 printf("Could not read interface_map_second\n");
394 }
395 else if (r == 0)
396 break;
397 else
398 {
399 // printf(" %s\n", interface_map_second);
400 if (!strcmp(interface_map_second,
401 "xyz.openbmc_project.Sensor.Value"))
402 {
403 has_value_interface = true;
404 }
405 }
406 }
407 if (has_value_interface)
408 {
409 g_sensor_snapshot->SerSensorVisibleFromObjectMapper(
410 std::string(interface_map_first), p);
411 }
412 r = sd_bus_message_exit_container(reply);
413 }
414 r = sd_bus_message_exit_container(reply);
415 }
416 r = sd_bus_message_exit_container(reply);
417 }
418 DONE:
419 {}
420 }
421 }
422 printf("4. Check Hwmon's DBus objects\n");
423 for (int i = 0; i < int(comms.size()); i++)
424 {
425 const std::string& comm = comms[i];
426 const std::string& service = services[i];
427 if (comm.find("phosphor-hwmon-readd") != std::string::npos &&
428 !IsUniqueName(service))
429 {
430 // printf("Should introspect %s\n", service.c_str());
431 std::vector<std::string> objpaths =
432 FindAllObjectPathsForService(service, nullptr);
433 for (const std::string& op : objpaths)
434 {
435 if (IsSensorObjectPath(op))
436 {
437 g_sensor_snapshot->SetSensorVisibleFromHwmon(service, op);
438 }
439 }
440 }
441 }
442 // Call `ipmitool sdr list` and see which sensors exist.
443 printf("5. Checking ipmitool SDR List\n");
444 std::string out;
445 bool skip_sdr_list = false;
446 if (getenv("SKIP"))
447 {
448 skip_sdr_list = true;
449 }
450 if (!skip_sdr_list)
451 {
452 constexpr int MAX_BUFFER = 255;
453 char buffer[MAX_BUFFER];
454 FILE* stream = popen("ipmitool sdr list", "r");
455 while (fgets(buffer, MAX_BUFFER, stream) != NULL)
456 {
457 out.append(buffer);
458 }
459 pclose(stream);
460 }
461 std::stringstream ss(out);
462 while (true)
463 {
464 std::string sensor_id, reading, status;
465 std::getline(ss, sensor_id, '|');
466 std::getline(ss, reading, '|');
467 std::getline(ss, status);
468 // printf("%s %s %s\n", sensor_id.c_str(), reading.c_str(),
469 // status.c_str());
470 if (sensor_id.size() > 0 && reading.size() > 0 && status.size() > 0)
471 {
472 g_sensor_snapshot->SetSensorVisibleFromIpmitoolSdr(Trim(sensor_id));
473 }
474 else
475 break;
476 }
477 printf("=== Sensors snapshot summary: ===\n");
478 g_sensor_snapshot->PrintSummary();
479 }
480} // namespace dbus_top_analyzer
481
482void DBusTopStatistics::OnNewDBusMessage(const char* sender,
483 const char* destination,
484 const char* interface,
485 const char* path, const char* member,
486 const char type, sd_bus_message* m)
487{
488 num_messages_++;
489 std::vector<std::string> keys;
490
491 std::string sender_orig = CheckAndFixNullString(sender);
492 std::string dest_orig = CheckAndFixNullString(destination);
493 // For method return messages, we actually want to show the sender
494 // and destination of the original method call, so we swap the
495 // sender and destination
496 if (type == 2)
497 { // DBUS_MESSAGE_TYPE_METHOD_METHOD_RETURN
498 std::swap(sender_orig, dest_orig);
499 }
500
501 // Special case: when PID == 1 (init), the DBus unit would be systemd.
502 // It seems it was not possible to obtain the connection name of systemd
503 // so we manually set it here.
504 const int sender_orig_pid =
505 g_connection_snapshot->GetConnectionPIDFromNameOrUniqueName(
506 sender_orig);
507
508 if (sender_orig_pid == 1)
509 {
510 sender_orig = "systemd";
511 }
512 const int dest_orig_pid =
513 g_connection_snapshot->GetConnectionPIDFromNameOrUniqueName(dest_orig);
514 if (dest_orig_pid == 1)
515 {
516 dest_orig = "systemd";
517 }
518
519 for (DBusTopSortField field : fields_)
520 {
521 switch (field)
522 {
523 case kSender:
524 keys.push_back(sender_orig);
525 break;
526 case kDestination:
527 keys.push_back(dest_orig);
528 break;
529 case kInterface:
530 keys.push_back(CheckAndFixNullString(interface));
531 break;
532 case kPath:
533 keys.push_back(CheckAndFixNullString(path));
534 break;
535 case kMember:
536 keys.push_back(CheckAndFixNullString(member));
537 break;
538 case kSenderPID:
539 {
540 if (sender_orig_pid != INVALID)
541 {
542 keys.push_back(std::to_string(sender_orig_pid));
543 }
544 else
545 {
546 keys.push_back("(unknown)");
547 }
548 break;
549 }
550 case kSenderCMD:
551 {
552 keys.push_back(
553 g_connection_snapshot->GetConnectionCMDFromNameOrUniqueName(
554 sender_orig));
555 break;
556 }
557 case kMsgPerSec:
558 case kAverageLatency:
559 break; // Don't populate "keys" using these 2 fields
560 }
561 }
562 // keys = combination of fields of user's choice
563
564 if (stats_.count(keys) == 0)
565 {
566 stats_[keys] = DBusTopComputedMetrics();
567 }
568 // Need to update msg/s regardless
569 switch (type)
570 {
571 case 1: // DBUS_MESSAGE_TYPE_METHOD_CALL
572 stats_[keys].num_method_calls++;
573 break;
574 case 2: // DBUS_MESSAGE_TYPE_METHOD_METHOD_RETURN
575 stats_[keys].num_method_returns++;
576 break;
577 case 3: // DBUS_MESSAGE_TYPE_ERROR
578 stats_[keys].num_errors++;
579 break;
580 case 4: // DBUS_MESSAGE_TYPE_SIGNAL
581 stats_[keys].num_signals++;
582 break;
583 }
584 // Update global latency histogram
585 // For method call latency
586 if (type == 1) // DBUS_MESSAGE_TYPE_METHOD_CALL
587 {
588 uint64_t serial; // serial == cookie
589 sd_bus_message_get_cookie(m, &serial);
590 in_flight_methodcalls[serial] = Microseconds();
591 }
592 else if (type == 2) // DBUS_MESSAGE_TYPE_MEHOTD_RETURN
593 {
594 uint64_t reply_serial = 0; // serial == cookie
595 sd_bus_message_get_reply_cookie(m, &reply_serial);
596 if (in_flight_methodcalls.count(reply_serial) > 0)
597 {
598 float dt_usec =
599 Microseconds() - in_flight_methodcalls[reply_serial];
600 in_flight_methodcalls.erase(reply_serial);
601 dbus_top_analyzer::g_mc_time_histogram.AddSample(dt_usec);
602
603 // Add method call count and total latency to the corresponding key
604 stats_[keys].total_latency_usec += dt_usec;
605 }
606 }
607 // For meaning of type see here
608 // https://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html#ga4a9012edd7f22342f845e98150aeb858
609 switch (type)
610 {
611 case 1:
612 num_mc_++;
613 break;
614 case 2:
615 num_mr_++;
616 break;
617 case 3:
618 num_error_++;
619 break;
620 case 4:
621 num_sig_++;
622 break;
623 }
624}