blob: 1c36fdb0189b7404c6db490ff2bcce7300cd089c [file] [log] [blame]
Adedeji Adebisi684ec912021-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 "views.hpp"
kuiyingdfb0cd92023-03-14 11:43:23 +080016
Adedeji Adebisi684ec912021-07-22 18:07:52 +000017#include "bargraph.hpp"
18#include "histogram.hpp"
19#include "menu.hpp"
kuiyingdfb0cd92023-03-14 11:43:23 +080020
Adedeji Adebisi684ec912021-07-22 18:07:52 +000021#include <string.h>
kuiyingdfb0cd92023-03-14 11:43:23 +080022
Adedeji Adebisi684ec912021-07-22 18:07:52 +000023#include <algorithm>
24
25extern SensorSnapshot* g_sensor_snapshot;
26extern BarGraph<float>* g_bargraph;
27extern DBusTopStatistics* g_dbus_statistics;
28extern Histogram<float>* g_histogram;
29extern DBusTopWindow* g_current_active_view;
30extern const std::string FieldNames[];
31extern const int FieldPreferredWidths[];
Sui Chen8c5208f2023-04-21 14:10:05 -070032extern bool g_sensor_update_thread_active;
33extern std::string g_snapshot_update_bus_cxn;
Adedeji Adebisi684ec912021-07-22 18:07:52 +000034
35namespace dbus_top_analyzer
36{
kuiyingdfb0cd92023-03-14 11:43:23 +080037extern DBusTopStatistics g_dbus_statistics;
Adedeji Adebisi684ec912021-07-22 18:07:52 +000038}
39
40// Linear interpolation
41float Lerp(float a, float b, float t)
42{
43 return a + t * (b - a);
44}
45
46// Linear map
47float Map(float value, float start1, float stop1, float start2, float stop2,
48 bool within_bounds)
49{
50 float t = (value - start1) / (stop1 - start1);
51 float ret = Lerp(start2, stop2, t);
52 if (within_bounds)
53 {
54 if (ret < start2)
55 ret = start2;
56 if (ret > stop2)
57 ret = stop2;
58 }
59 return ret;
60}
61
62template <typename T>
63void HistoryBarGraph(WINDOW* win, const Rect& rect, BarGraph<T>* bargraph)
64{
65 const int RIGHT_MARGIN = 5;
66 const int x0 = rect.x, y0 = 2;
67 const int w = rect.w - 2 - RIGHT_MARGIN;
68 const int h = rect.h - 3; // height of content
69 wattrset(win, 0);
70 wattron(win, A_BOLD | A_UNDERLINE);
71 mvwaddstr(win, 1, x0, "History (Total msg/s)");
72 wattrset(win, 0);
73 // 1. Obtain data, determine Y range
74 std::vector<float> data = bargraph->GetLastNValues(w - RIGHT_MARGIN - 1);
75 float ymax = -1e20, ymin = 1e20;
76 if (data.empty())
77 {
78 data.push_back(0);
79 ymin = 0;
80 ymax = 10;
81 }
82 else
83 {
84 for (const float x : data)
85 {
86 ymax = std::max(ymax, x);
87 ymin = std::min(ymin, x);
88 }
89 }
90 // Fix edge case for both == 0
91 float diff = ymax - ymin;
92 if (diff < 0)
93 {
94 diff = -diff;
95 }
96 const float EPS = 1e-4;
97 if (diff < EPS)
98 {
99 ymax += 10;
100 ymin -= 10;
101 }
102 // Choose a suitable round-up unit to snap the grid labels to
103 int snap = 1;
104 if (ymax < 100)
105 {
106 snap = 10;
107 }
108 else if (ymax < 10000)
109 {
110 snap = 100;
111 }
112 else
113 {
114 snap = 1000;
115 }
116 const float eps = snap / 100.0f;
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -0500117 int label_ymax = (static_cast<int>((ymax - eps) / snap) + 1) *
118 snap; // round up
119 int label_ymin = static_cast<int>(ymin / snap) * snap; // round down
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000120 float y_per_row = (label_ymax - label_ymin) * 1.0f / (h - 1);
121 int actual_ymax = label_ymax + static_cast<int>(y_per_row / 2);
122 int actual_ymin = label_ymin - static_cast<int>(y_per_row / 2);
123 // 2. Print Y axis ticks
124 for (int i = 0; i < h; i++)
125 {
126 char buf[10];
127 snprintf(
128 buf, sizeof(buf), "%-6d",
129 static_cast<int>(Lerp(label_ymax, label_ymin, i * 1.0f / (h - 1))));
130 mvwaddstr(win, i + y0, x0 + w - RIGHT_MARGIN + 1, buf);
131 mvwaddch(win, i + y0, x0, '-');
132 mvwaddch(win, i + y0, x0 + w - RIGHT_MARGIN, '-');
133 }
134 // 3. Go through the historical data and draw on the canvas
135 for (int i = 0;
136 i < std::min(static_cast<int>(data.size()), w - RIGHT_MARGIN - 1); i++)
137 {
138 float value = data[i];
139 // antialiasing: todo for now
140 // float value1 = value; // value1 is 1 column to the right
141 // if (i > 0) value1 = data[i-1];
142 int x = x0 + w - i - RIGHT_MARGIN - 1;
143 float t = Map(value, actual_ymin, actual_ymax, 0, h, true);
144 int row = static_cast<int>(t);
145 float remaining = t - row;
146 char ch; // Last filling character
147 if (remaining >= 0.66f)
148 {
149 ch = ':';
150 }
151 else if (remaining >= 0.33f)
152 {
153 ch = '.';
154 }
155 else
156 {
157 ch = ' ';
158 }
159 int y = y0 + h - row - 1;
160 mvwaddch(win, y, x, ch);
161 for (int j = 0; j < row; j++)
162 {
163 mvwaddch(win, y + j + 1, x, ':');
164 }
165 }
166}
167
168template <typename T>
169void DrawHistogram(WINDOW* win, const Rect& rect, Histogram<T>* histogram)
170{
171 // const int MARGIN = 7; // 5 digits margin
172 const int LEFT_MARGIN = 7;
173 // const int max_bucket_h = histogram->MaxBucketHeight();
174 const int H_PAD = 0, V_PAD = 1;
175 // x0, x1, y0 and y1 are the bounding box of the contents to be printed
176 const int x0 = rect.x + H_PAD;
177 const int x1 = rect.x + rect.w - H_PAD;
178 const int y0 = rect.y + V_PAD;
179 const int y1 = rect.y + rect.h - 1 - V_PAD;
180 // Title
181 wattron(win, A_BOLD | A_UNDERLINE);
182 mvwaddstr(win, y0, x0, "Method Call Time (us) Histogram");
183 wattrset(win, 0);
184 // x2 is the beginning X of the histogram itself (not containing the margin)
185 const int x2 = x0 + LEFT_MARGIN;
186 if (histogram->Empty())
187 {
188 mvwaddstr(win, (y1 + y0) / 2, (x0 + x1) / 2, "(Empty)");
189 return;
190 }
191 histogram->SetBucketCount(x1 - x2 + 1);
192 histogram->ComputeHistogram();
193 // Draw X axis labels
194 char buf[22];
195 snprintf(buf, sizeof(buf), "%.2f",
196 static_cast<float>(histogram->LowPercentile()));
197 mvwaddstr(win, y1, x0 + LEFT_MARGIN, buf);
198 snprintf(buf, sizeof(buf), "%.2f",
199 static_cast<float>(histogram->HighPercentile()));
200 mvwaddstr(win, y1, x1 + 1 - strlen(buf), buf);
201 snprintf(buf, sizeof(buf), "%d%%-%d%%",
202 static_cast<int>(histogram->LowCumDensity() * 100),
203 static_cast<int>(histogram->HighCumDensity() * 100));
204 mvwaddstr(win, y1, x0, buf);
205 // Draw Y axis labels
206 const float hist_ymax = y1 - 1;
207 const float hist_ymin = y0 + 1;
208 const int max_histogram_h = histogram->MaxBucketHeight();
209 if (hist_ymax <= hist_ymin)
210 return; // Not enough space for rendering
211 if (max_histogram_h <= 0)
212 return;
213 bool LOG_TRANSFORM = true;
214 float lg_maxh = 0;
215 if (LOG_TRANSFORM)
216 {
217 lg_maxh = log(max_histogram_h);
218 }
219 for (int y = hist_ymin; y <= hist_ymax; y++)
220 {
221 // There are (hist_ymax - hist_ymin + 1) divisions
222 float fullness;
223 fullness = (hist_ymax - y + 1) * 1.0f / (hist_ymax - hist_ymin + 1);
224 int h;
225 if (!LOG_TRANSFORM)
226 {
227 h = static_cast<int>(max_histogram_h * fullness);
228 }
229 else
230 {
231 h = static_cast<int>(exp(fullness * lg_maxh));
232 }
233 snprintf(buf, sizeof(buf), "%6d-", h);
234 mvwaddstr(win, y, x0 + LEFT_MARGIN - strlen(buf), buf);
235 }
236 const int bar_height = hist_ymax - hist_ymin + 1; // Height of a full bar
237 for (int x = x2, bidx = 0; x <= x1; x++, bidx++)
238 {
239 int h = histogram->BucketHeight(bidx);
240 float lines_visible;
241 if (!LOG_TRANSFORM)
242 {
243 lines_visible = h * 1.0f / max_histogram_h * bar_height;
244 }
245 else
246 {
247 if (h <= 0)
248 lines_visible = 0;
249 else
250 lines_visible = log(h) * 1.0f / lg_maxh * bar_height;
251 }
252 // The histogram's top shall start from this line
253 int y = hist_ymax - static_cast<int>(lines_visible);
254 float y_frac = lines_visible - static_cast<int>(lines_visible);
255 char ch; // Last filling character
256 if (y >= hist_ymin)
Patrick Williamsc3aa2a82023-05-10 07:51:38 -0500257 { // At the maximum bucket the Y overflows, so skip
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000258 if (y_frac >= 0.66f)
259 {
260 ch = ':';
261 }
262 else if (y_frac >= 0.33f)
263 {
264 ch = '.';
265 }
266 else
267 {
268 if (y < hist_ymax)
269 {
270 ch = ' ';
271 }
272 else
273 {
274 if (y_frac > 0)
275 {
276 ch =
277 '.'; // Makes long-tailed distribution easier to see
278 }
279 }
280 }
281 mvwaddch(win, y, x, ch);
282 }
283 y++;
284 for (; y <= hist_ymax; y++)
285 {
286 mvwaddch(win, y, x, ':');
287 }
288 }
289}
290
291void SummaryView::UpdateDBusTopStatistics(DBusTopStatistics* stat)
292{
293 if (!stat)
294 return;
295 float interval_secs = stat->seconds_since_last_sample_;
296 if (interval_secs == 0)
297 {
298 interval_secs = GetSummaryIntervalInMillises() / 1000.0f;
299 }
300 // Per-second
301 method_call_ = stat->num_mc_ / interval_secs;
302 method_return_ = stat->num_mr_ / interval_secs;
303 signal_ = stat->num_sig_ / interval_secs;
304 error_ = stat->num_error_ / interval_secs;
305 total_ = stat->num_messages_ / interval_secs;
306 g_bargraph->AddValue(total_);
307}
308
309std::string Ellipsize(const std::string& s, int len_limit)
310{
311 if (len_limit <= 3)
312 return s.substr(0, len_limit);
313 if (static_cast<int>(s.size()) < len_limit)
314 {
315 return s;
316 }
317 else
318 {
319 return s.substr(0, len_limit - 3) + "...";
320 }
321}
322
323void SummaryView::Render()
324{
325 // Draw text
326 werase(win);
327 if (!visible_)
328 return;
329 wattron(win, A_BOLD | A_UNDERLINE);
330 mvwaddstr(win, 1, 1, "Message Type | msg/s");
331 wattrset(win, 0);
332 const int xend = 30;
333 std::string s;
334 s = FloatToString(method_call_);
335 mvwaddstr(win, 2, 1, "Method Call");
336 mvwaddstr(win, 2, xend - s.size(), s.c_str());
337 s = FloatToString(method_return_);
338 mvwaddstr(win, 3, 1, "Method Return ");
339 mvwaddstr(win, 3, xend - s.size(), s.c_str());
340 s = FloatToString(signal_);
341 mvwaddstr(win, 4, 1, "Signal");
342 mvwaddstr(win, 4, xend - s.size(), s.c_str());
343 s = FloatToString(error_);
344 mvwaddstr(win, 5, 1, "Error ");
345 mvwaddstr(win, 5, xend - s.size(), s.c_str());
346 wattron(win, A_UNDERLINE);
347 s = FloatToString(total_);
348 mvwaddstr(win, 6, 1, "Total");
349 mvwaddstr(win, 6, xend - s.size(), s.c_str());
350 wattroff(win, A_UNDERLINE);
351 wattrset(win, 0);
352 // Draw history bar graph
353 Rect bargraph_rect = rect;
354 const int bargraph_x = 64;
355 bargraph_rect.x += bargraph_x;
356 bargraph_rect.w -= bargraph_x;
357 HistoryBarGraph(win, bargraph_rect, g_bargraph);
358 // Draw histogram
359 Rect histogram_rect = rect;
360 histogram_rect.x += 32;
361 histogram_rect.w = bargraph_rect.x - histogram_rect.x - 3;
362 DrawHistogram(win, histogram_rect, g_histogram);
363 // Draw border between summary and histogram
364 for (int y = bargraph_rect.y; y <= bargraph_rect.y + bargraph_rect.h; y++)
365 {
366 mvwaddch(win, y, histogram_rect.x - 1, '|');
367 mvwaddch(win, y, bargraph_rect.x - 1, '|');
368 }
369 DrawBorderIfNeeded();
370 wrefresh(win);
371}
372
373void SensorDetailView::Render()
374{
375 werase(win);
376 if (!visible_)
377 return;
378 // If some sensor is focused, show details regarding that sensor
379 if (state == SensorList)
380 { // Otherwise show the complete list
381 const int ncols = DispSensorsPerRow(); // Number of columns in viewport
382 const int nrows = DispSensorsPerColumn(); // # rows in viewport
383 int sensors_per_page = nrows * ncols;
384 // Just in case the window gets invisibly small
385 if (sensors_per_page < 1)
386 return;
387 int num_sensors = sensor_ids_.size();
388 int total_num_columns = (num_sensors - 1) / nrows + 1;
389 bool is_cursor_out_of_view = false;
390 if (idx0 > choice_ || idx1 <= choice_)
391 {
392 is_cursor_out_of_view = true;
393 }
394 if (idx0 == INVALID || idx1 == INVALID)
395 {
396 is_cursor_out_of_view = true;
397 }
398 if (is_cursor_out_of_view)
399 {
400 idx0 = 0, idx1 = sensors_per_page;
401 }
402 while (idx1 <= choice_)
403 {
404 idx0 += nrows;
405 idx1 += nrows;
406 }
407 const int y0 = 2; // to account for the border and info line
408 const int x0 = 4; // to account for the left overflow marks
409 int y = y0, x = x0;
410 for (int i = 0; i < sensors_per_page; i++)
411 {
412 int idx = idx0 + i;
413 if (idx < static_cast<int>(sensor_ids_.size()))
414 {
415 if (idx == choice_)
416 {
417 wattrset(win, A_REVERSE);
418 }
419 std::string s = sensor_ids_[idx];
kuiyingdfb0cd92023-03-14 11:43:23 +0800420 if (static_cast<int>(s.size()) > col_width)
421 {
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000422 s = s.substr(0, col_width - 2) + "..";
kuiyingdfb0cd92023-03-14 11:43:23 +0800423 }
424 else
425 {
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000426 while (static_cast<int>(s.size()) < col_width)
427 {
428 s.push_back(' ');
429 }
430 }
Sui Chen8643b5d2022-08-14 11:56:30 -0700431 mvwaddstr(win, y, x, s.c_str());
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000432 wattrset(win, 0);
433 }
434 else
435 break;
436 y++;
437 if (i % nrows == nrows - 1)
438 {
439 y = y0;
440 x += col_width + h_spacing;
441 }
442 }
443 // Print overflow marks to the right of the screen
444 for (int i = 0; i < nrows; i++)
445 {
446 int idx = idx0 + sensors_per_page + i;
447 if (idx < num_sensors)
448 {
449 mvwaddch(win, y0 + i, x, '>');
450 }
451 }
452 // Print overflow marks to the left of the screen
453 for (int i = 0; i < nrows; i++)
454 {
455 int idx = idx0 - nrows + i;
456 if (idx >= 0)
457 {
458 mvwaddch(win, y0 + i, 2, '<');
459 }
460 }
461 // idx1 is one past the visible range, so no need to +1
462 const int col0 = idx0 / nrows + 1, col1 = idx1 / nrows;
463 mvwprintw(win, 1, 2, "Columns %d-%d of %d", col0, col1,
464 total_num_columns);
nitroglycerine49dcde12023-03-14 07:24:39 -0700465 mvwprintw(win, 1, rect.w - 15, "%zu sensors", sensor_ids_.size());
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000466 }
467 else if (state == SensorDetail)
468 {
469 // sensor_ids_ is the cached list of sensors, it should be the same size
470 // as the actual number of sensors in the snapshot
471 mvwprintw(win, 1, 2, "Details of sensor %s", curr_sensor_id_.c_str());
nitroglycerine49dcde12023-03-14 07:24:39 -0700472 mvwprintw(win, 1, rect.w - 15, "Sensor %d/%zu", choice_ + 1,
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000473 sensor_ids_.size()); // 1-based
474 std::vector<Sensor*> sensors =
475 g_sensor_snapshot->FindSensorsBySensorID(curr_sensor_id_);
476 const int N = static_cast<int>(sensors.size());
477 const int w = rect.w - 5;
478 mvwprintw(win, 3, 2, "There are %d sensors with the name %s", N,
479 curr_sensor_id_.c_str());
480 int y = 5;
481 int x = 2;
482 if (N > 0)
483 {
484 for (int j = 0; j < N; j++)
485 {
486 Sensor* sensor = sensors[j];
487 mvwprintw(win, y, x, "%d/%d", j + 1, N);
488 char buf[200];
489 snprintf(buf, sizeof(buf), "DBus Service : %s",
490 sensor->ServiceName().c_str());
491 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
492 snprintf(buf, sizeof(buf), "DBus Connection : %s",
493 sensor->ConnectionName().c_str());
494 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
495 snprintf(buf, sizeof(buf), "DBus Object Path: %s",
496 sensor->ObjectPath().c_str());
497 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
498 y++;
Sui Chen8c5208f2023-04-21 14:10:05 -0700499
500 // TODO: can cache in and out & only update when there are user
501 // UI actions
502 std::map<std::string, std::set<std::string>> in, out;
503 g_sensor_snapshot->FindAssociationEndpoints(
504 sensor->ObjectPath(), &out, &in);
505
506 y++;
507 mvwprintw(win, y, x, "Association status:");
508
509 if (out.size() > 0)
510 {
511 y++;
512 int nforward = 0;
513 for (const auto& [k, v] : out)
514 {
515 nforward += int(v.size());
516 }
517 mvwprintw(win, y, x,
518 "Used as Forward vertex %d times:", nforward);
519 y++;
520 int idx = 0;
521 for (const auto& [k, v] : out)
522 {
523 idx++;
524 snprintf(buf, sizeof(buf), "%d. %s (%zu)", idx,
525 k.c_str(), v.size());
526 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
527 for (const std::string& entry : v)
528 {
529 y += DrawTextWithWidthLimit(win, entry, y, x + 2,
530 w - 2, "/");
531 }
532 }
533 }
534 else
535 {
536 y++;
537 mvwprintw(win, y, x, "Not used as forward vertex");
538 y++;
539 }
540
541 if (in.size() > 0)
542 {
543 y++;
544 int nbackward = 0;
545 for (const auto& [k, v] : in)
546 {
547 nbackward += int(v.size());
548 }
549 mvwprintw(win, y, x,
550 "Used as reverse vertex %d times:", nbackward);
551 y++;
552 int idx = 0;
553 for (const auto& [k, v] : in)
554 {
555 idx++;
556 snprintf(buf, sizeof(buf), "%d. %s (%zu)", idx,
557 k.c_str(), v.size());
558 y += DrawTextWithWidthLimit(win, buf, y, x, w, "/");
559 for (const std::string& entry : v)
560 {
561 y += DrawTextWithWidthLimit(win, entry, y, x + 2,
562 w - 2, "/");
563 }
564 }
565 }
566 else
567 {
568 y++;
569 mvwprintw(win, y, x, "Not used as reverse vertex");
570 }
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000571 }
572 }
573 else
574 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700575 mvwaddstr(win, y, x, "Sensor details not found");
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000576 }
577 }
578 DrawBorderIfNeeded();
579 wrefresh(win);
580}
581
582std::string SensorDetailView::GetStatusString()
583{
584 if (state == SensorList)
585 {
586 return "[Arrow Keys]=Move Cursor [Q]=Deselect [Enter]=Show Sensor "
587 "Detail";
588 }
589 else
590 {
591 return "[Arrow Keys]=Cycle Through Sensors [Esc/Q]=Exit";
592 }
593}
594
595DBusStatListView::DBusStatListView() : DBusTopWindow()
596{
597 highlight_col_idx_ = 0;
598 sort_col_idx_ = 0;
599 sort_order_ = SortOrder::Ascending;
600 horizontal_pan_ = 0;
601 row_idx_ = INVALID;
602 disp_row_idx_ = 0;
603 horizontal_pan_ = 0;
604 menu1_ = new ArrowKeyNavigationMenu(this);
605 menu2_ = new ArrowKeyNavigationMenu(this);
606 // Load all available field names
607 std::set<std::string> inactive_fields;
608 std::set<std::string> active_fields;
609
610 // Default choice of field names
611 const int N = static_cast<int>(sizeof(FieldNames) / sizeof(FieldNames[0]));
612 for (int i = 0; i < N; i++)
613 {
614 inactive_fields.insert(FieldNames[i]);
615 }
616 for (const std::string& s :
617 dbus_top_analyzer::g_dbus_statistics.GetFieldNames())
618 {
619 inactive_fields.erase(s);
620 active_fields.insert(s);
621 }
622 for (int i = 0; i < N; i++)
623 {
kuiyingdfb0cd92023-03-14 11:43:23 +0800624 const std::string s = FieldNames[i];
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000625 if (inactive_fields.count(s) > 0)
626 {
627 menu1_->AddItem(s);
628 }
629 else
630 {
631 menu2_->AddItem(s);
632 }
633 }
kuiyingdfb0cd92023-03-14 11:43:23 +0800634
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000635 curr_menu_state_ = LeftSide;
636 menu_h_ = 5;
637 menu_w_ = 24; // Need at least 2*padding + 15 for enough space, see menu.hpp
638 menu_margin_ = 6;
639 // Populate preferred column widths
640 for (int i = 0; i < N; i++)
641 {
642 column_widths_[FieldNames[i]] = FieldPreferredWidths[i];
643 }
644}
645
646std::pair<int, int> DBusStatListView::GetXSpanForColumn(const int col_idx)
647{
648 std::vector<int> cw = ColumnWidths();
649 if (col_idx < 0 || col_idx >= static_cast<int>(cw.size()))
650 {
651 return std::make_pair(INVALID, INVALID);
652 }
653 int x0 = 0, x1 = 0;
654 for (int i = 0; i < col_idx; i++)
655 {
656 if (i > 0)
657 {
658 x0 += cw[i];
659 }
660 }
661 x1 = x0 + cw[col_idx] - 1;
662 return std::make_pair(x0, x1);
663}
664
665// If tolerance > 0, consider overlap before 2 intervals intersect
666// If tolerance ==0, consider overlap if 2 intervals exactly intersect
667// If tolerance < 0, consider overlap if Minimal Translate Distance is >=
668// -threshold
669bool IsSpansOverlap(const std::pair<int, int>& s0,
670 const std::pair<int, int>& s1, int tolerance)
671{
672 if (tolerance >= 0)
673 {
674 if (s0.second < s1.first - tolerance)
675 return false;
676 else if (s1.second < s0.first - tolerance)
677 return false;
678 else
679 return true;
680 }
681 else
682 {
683 // Compute overlapping distance
684 std::vector<std::pair<int, int>> tmp(
685 4); // [x, 1] means the start of interval
686 // [x,-1] means the end of interval
687 tmp[0] = std::make_pair(s0.first, 1);
688 tmp[1] = std::make_pair(s0.second, -1);
689 tmp[2] = std::make_pair(s1.first, 1);
690 tmp[3] = std::make_pair(s1.second, -1);
691 std::sort(tmp.begin(), tmp.end());
692 int overlap_x0 = -INVALID, overlap_x1 = -INVALID;
693 int idx = 0;
694 const int N = static_cast<int>(tmp.size());
695 int level = 0;
696 while (idx < N)
697 {
698 const int x = tmp[idx].first;
699 while (idx < N && x == tmp[idx].first)
700 {
701 level += tmp[idx].second;
702 idx++;
703 }
704 // The starting position of the overlap
705 if (level == 2)
706 {
707 overlap_x0 = idx - 1;
708 }
709 // The ending position of the overlap
710 if (overlap_x0 != -INVALID && level < 2 && overlap_x1 == -INVALID)
711 {
712 overlap_x1 = idx - 1;
713 }
714 }
715 const int overlap_length = overlap_x1 - overlap_x0 + 1;
716 if (overlap_length >= -tolerance)
717 return true;
718 else
719 return false;
720 }
721}
722
723bool DBusStatListView::IsXSpanVisible(const std::pair<int, int>& xs,
724 int tolerance)
725{
726 const std::pair<int, int> vxs = {horizontal_pan_, horizontal_pan_ + rect.w};
727 return IsSpansOverlap(xs, vxs, tolerance);
728}
729std::vector<std::string> DBusStatListView::ColumnHeaders()
730{
731 return visible_columns_;
732}
733
734std::vector<int> DBusStatListView::ColumnWidths()
735{
736 std::vector<int> widths = {8}; // for "Msg/s"
737 std::vector<std::string> agg_headers = visible_columns_;
738 std::vector<int> agg_widths(agg_headers.size(), 0);
739 for (int i = 0; i < static_cast<int>(agg_headers.size()); i++)
740 {
741 agg_widths[i] = column_widths_[agg_headers[i]];
742 }
743 widths.insert(widths.end(), agg_widths.begin(), agg_widths.end());
744 return widths;
745}
746
747// Coordinate systems are in world space, +x faces to the right
748// Viewport: [horizontal_pan_, horizontal_pan_ + rect.w]
749// Contents: [ column_width[0] ][ column_width[1] ][ column_width[2] ]
750void DBusStatListView::PanViewportOrMoveHighlightedColumn(const int delta_x)
751{
752 // If the column to the left is visible, highlight it
753 const int N = static_cast<int>(ColumnHeaders().size());
754 bool col_idx_changed = false;
755 if (delta_x < 0)
756 { // Pan left
757 if (highlight_col_idx_ > 0)
758 {
759 std::pair<int, int> xs_left =
760 GetXSpanForColumn(highlight_col_idx_ - 1);
761 if (IsXSpanVisible(xs_left, -1))
762 {
763 highlight_col_idx_--;
764 col_idx_changed = true;
765 }
766 }
767 if (!col_idx_changed)
768 {
769 horizontal_pan_ += delta_x;
770 }
771 }
772 else if (delta_x > 0)
773 { // Pan right
774 if (highlight_col_idx_ < N - 1)
775 {
776 std::pair<int, int> xs_right =
777 GetXSpanForColumn(highlight_col_idx_ + 1);
778 if (IsXSpanVisible(xs_right, -1))
779 {
780 highlight_col_idx_++;
781 col_idx_changed = true;
782 }
783 }
784 if (!col_idx_changed)
785 {
786 horizontal_pan_ += delta_x;
787 }
788 }
789}
790
791void DBusStatListView::OnKeyDown(const std::string& key)
792{
793 {
794 switch (curr_menu_state_)
795 {
796 case LeftSide:
797 {
798 if (key == "up")
799 {
800 menu1_->OnKeyDown("up");
801 }
802 else if (key == "down")
803 {
804 menu1_->OnKeyDown("down");
805 }
806 else if (key == "right")
807 {
808 SetMenuState(RightSide);
809 }
810 else if (key == "enter")
811 {
812 SetMenuState(Hidden);
813 }
814 else if (key == "space")
815 {
816 std::string ch;
817 if (menu1_->RemoveHighlightedItem(&ch))
818 {
819 menu2_->AddItem(ch);
820 }
821 }
822 break;
823 }
824 case RightSide:
825 {
826 if (key == "up")
827 {
828 menu2_->OnKeyDown("up");
829 }
830 else if (key == "down")
831 {
832 menu2_->OnKeyDown("down");
833 }
834 else if (key == "left")
835 {
836 SetMenuState(LeftSide);
837 }
838 else if (key == "enter")
839 {
840 SetMenuState(Hidden);
841 }
842 else if (key == "space")
843 {
844 std::string ch;
845 if (menu2_->RemoveHighlightedItem(&ch))
846 {
847 menu1_->AddItem(ch);
848 }
849 }
850 break;
851 }
852 case Hidden:
853 {
854 if (key == "enter")
855 {
856 switch (last_menu_state_)
857 {
858 case LeftSide:
859 case RightSide:
860 SetMenuState(last_menu_state_);
861 break;
862 default:
863 SetMenuState(LeftSide);
864 }
865 }
866 else if (key == "left")
867 {
868 PanViewportOrMoveHighlightedColumn(-2);
869 }
870 else if (key == "right")
871 {
872 PanViewportOrMoveHighlightedColumn(2);
873 }
874 else if (key == "up")
875 {
876 disp_row_idx_--;
877 if (disp_row_idx_ < 0)
878 {
879 disp_row_idx_ = 0;
880 }
881 }
882 else if (key == "down")
883 {
884 disp_row_idx_++;
885 const int N = static_cast<int>(stats_snapshot_.size());
886 if (disp_row_idx_ >= N)
887 {
888 disp_row_idx_ = N - 1;
889 }
890 }
891 else if (key == "a")
892 {
893 sort_order_ = SortOrder::Ascending;
894 sort_col_idx_ = highlight_col_idx_;
895 break;
896 }
897 else if (key == "d")
898 {
899 sort_order_ = SortOrder::Descending;
900 sort_col_idx_ = highlight_col_idx_;
901 break;
902 }
903 break;
904 }
905 }
906 }
907 Render();
908}
909
910// Window C
911void DBusStatListView::Render()
912{
913 werase(win);
914 if (!visible_)
915 return;
916 int num_lines_shown = rect.h - 3;
917 if (curr_menu_state_ == LeftSide || curr_menu_state_ == RightSide)
918 {
919 menu1_->Render();
920 menu2_->Render();
921 num_lines_shown -= (menu_h_ + 3);
922 // Draw the arrow
923 const int x1 = menu1_->rect_.x;
924 const int h1 = menu1_->rect_.h;
925 const int x2 = menu2_->rect_.x;
926 const int w2 = menu2_->rect_.w;
927 const int y1 = menu1_->rect_.y;
928 const int arrow_x = (x1 + x2 + w2) / 2 - 2;
929 const int arrow_y = y1 + 2;
930 const int caption_x = x1;
931 const int caption_y = y1 + h1;
932 for (int x = 1; x < rect.w - 1; x++)
933 {
934 mvwaddch(win, y1 - 3, x, '-');
935 }
Sui Chen8643b5d2022-08-14 11:56:30 -0700936 mvwaddstr(win, y1 - 3, arrow_x - 8, "Press [Enter] to show/hide");
937 mvwaddstr(win, y1 - 2, caption_x - 5,
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000938 "DBus fields for aggregating and sorting results:");
939 if (curr_menu_state_ == LeftSide)
940 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700941 mvwaddstr(win, y1 - 1, x1 - 4, "--[ Available Fields ]--");
942 mvwaddstr(win, y1 - 1, x2 - 4, "--- Active Fields ---");
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000943 }
944 else
945 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700946 mvwaddstr(win, y1 - 1, x1 - 4, "--- Available Fields ---");
947 mvwaddstr(win, y1 - 1, x2 - 4, "--[ Active Fields ]--");
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000948 }
949 if (curr_menu_state_ == LeftSide)
950 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700951 mvwaddstr(win, arrow_y, arrow_x, "-->");
952 mvwaddstr(win, caption_y, caption_x,
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000953 "Press [Space] to move to the right");
954 }
955 else
956 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700957 mvwaddstr(win, arrow_y, arrow_x, "<--");
958 mvwaddstr(win, caption_y, caption_x,
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000959 "Press [Space] to move to the left");
960 }
961 }
962 std::vector<std::string> headers;
963 std::vector<int> widths;
964 visible_columns_ = g_dbus_statistics->GetFieldNames();
965 std::vector<std::string> agg_headers = visible_columns_;
966 std::vector<int> agg_widths(agg_headers.size(), 0);
967 for (int i = 0; i < static_cast<int>(agg_headers.size()); i++)
968 {
969 agg_widths[i] = column_widths_[agg_headers[i]];
970 }
971 headers.insert(headers.end(), agg_headers.begin(), agg_headers.end());
972 widths.insert(widths.end(), agg_widths.begin(), agg_widths.end());
973 std::vector<int> xs;
974 int curr_x = 2 - horizontal_pan_;
975 for (const int w : widths)
976 {
977 xs.push_back(curr_x);
978 curr_x += w;
979 }
980 const int N = headers.size();
981 // Bound col_idx_
982 if (highlight_col_idx_ >= N)
983 {
984 highlight_col_idx_ = N - 1;
985 }
986 // Render column headers
987 for (int i = 0; i < N; i++)
988 {
989 std::string s = headers[i];
990 // 1 char outside boundary = start printing from the second character,
991 // etc
992
993 // Print "<" for Ascending order (meaning: row 0 < row 1 < row 2 ... )
994 // Print ">" for Descending order (meaning: row 0 > row 1 > row 2 ... )
995 if (sort_col_idx_ == i)
996 {
997 if (sort_order_ == SortOrder::Ascending)
998 {
999 s.push_back('<');
1000 }
1001 else
1002 {
1003 s.push_back('>');
1004 }
1005 }
1006
1007 // Highlight the "currently-selected column"
1008 if (highlight_col_idx_ == i)
1009 {
1010 wattrset(win, 0);
1011 wattron(win, A_REVERSE);
1012 }
1013 else
1014 {
1015 wattrset(win, 0);
1016 wattron(win, A_UNDERLINE);
1017 }
1018 int x = xs[i];
1019 if (x < 0)
1020 {
1021 if (-x < static_cast<int>(s.size()))
1022 {
1023 s = s.substr(-x);
1024 }
1025 else
1026 s = "";
1027 x = 0;
1028 }
1029 mvwaddstr(win, 1, x, s.c_str());
1030 }
1031 wattrset(win, 0);
1032 // Time since the last update of Window C
1033 float interval_secs = g_dbus_statistics->seconds_since_last_sample_;
1034 if (interval_secs == 0)
1035 {
1036 interval_secs = GetSummaryIntervalInMillises() / 1000.0f;
1037 }
1038
1039 stats_snapshot_ = g_dbus_statistics->StatsSnapshot();
1040 const int nrows = static_cast<int>(stats_snapshot_.size());
1041 const std::vector<DBusTopSortField> fields = g_dbus_statistics->GetFields();
1042 const int ncols = static_cast<int>(fields.size());
1043 // Merge the list of DBus Message properties & computed metrics together
1044 std::map<std::vector<std::string>, DBusTopComputedMetrics>::iterator itr =
1045 stats_snapshot_.begin();
1046 struct StringOrFloat
1047 { // Cannot use union so using struct
1048 std::string s;
1049 float f;
1050 };
1051
1052 // "Stage" the snapshot for displaying in the form of a spreadsheet
1053 std::vector<std::pair<StringOrFloat, std::vector<std::string>>>
1054 stats_snapshot_staged;
1055 const DBusTopSortField sort_field = fields[sort_col_idx_];
1056 const bool is_sort_key_numeric = DBusTopSortFieldIsNumeric(sort_field);
1057
1058 for (int i = 0; i < nrows; i++) // One row of cells
1059 {
1060 int idx0 = 0; // indexing into the std::vector<string> of each row
1061 std::vector<std::string> row;
1062
kuiyingdfb0cd92023-03-14 11:43:23 +08001063 StringOrFloat sort_key; // The key used for sorting
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001064 for (int j = 0; j < ncols; j++) // one column in the row
1065 {
1066 DBusTopSortField field = fields[j];
1067 // Populate the content of stats_snapshot_staged
1068
1069 StringOrFloat sof; // Represents this column
kuiyingdfb0cd92023-03-14 11:43:23 +08001070
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001071 // When we haven't used up all
1072 if (idx0 < static_cast<int>(itr->first.size()))
1073 {
1074 sof.s = itr->first[idx0];
1075 }
1076 switch (field)
1077 {
1078 case kSender: // string
1079 case kDestination: // string
1080 case kInterface: // string
1081 case kPath: // string
1082 case kMember: // string
1083 case kSenderPID: // numeric
1084 case kSenderCMD: // string
1085 row.push_back(itr->first[idx0]);
1086 idx0++;
1087 if (field == kSenderPID)
1088 {
1089 // Note: attempting to std::atof("(unknown)") on the BMC
1090 // will cause hang. And GDB won't show backtrace.
1091 if (sof.s == "(unknown)")
1092 {
1093 if (sort_order_ == Ascending)
1094 {
1095 sof.f = -1;
1096 }
1097 else
1098 {
1099 sof.f = 1e20;
1100 }
1101 }
1102 else
1103 {
1104 sof.f = std::atof(sof.s.c_str());
1105 }
1106 }
1107 break;
1108 case kMsgPerSec: // Compute "messages per second"
1109 {
1110 int numbers[] = {
1111 itr->second.num_method_calls,
1112 itr->second.num_method_returns,
1113 itr->second.num_signals,
1114 itr->second.num_errors,
1115 };
1116 int the_sum = 0; // For sorting
1117
Patrick Williamsc3aa2a82023-05-10 07:51:38 -05001118 std::string s; // String representation in the form or
1119 // "1.00/2.00/3.00/4.00"
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001120 for (int i = 0; i < 4; i++)
1121 {
1122 the_sum += numbers[i];
1123 if (i > 0)
1124 s += "/";
1125 float per_sec = numbers[i] / interval_secs;
1126 s += FloatToString(per_sec);
1127 }
1128
1129 row.push_back(s);
1130 sof.f = the_sum;
1131 break;
1132 }
1133 case kAverageLatency: // Compute "average Method Call latency"
1134 const DBusTopComputedMetrics& m = itr->second;
1135 if (m.num_method_calls == 0)
1136 {
1137 row.push_back("n/a");
1138 if (sort_order_ == Ascending)
1139 {
1140 sof.f = -1; // Put to the top
1141 }
1142 else
1143 {
1144 sof.f = 1e20; // Put to the top
1145 }
1146 }
1147 else
1148 {
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -05001149 float avg_latency_usec = m.total_latency_usec /
1150 m.num_method_calls;
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001151 row.push_back(FloatToString(avg_latency_usec));
1152 sof.f = avg_latency_usec;
1153 }
1154 break;
1155 }
1156 if (j == sort_col_idx_)
1157 {
1158 sort_key = sof;
1159 }
1160 }
1161 stats_snapshot_staged.push_back(std::make_pair(sort_key, row));
1162 itr++;
1163 }
kuiyingdfb0cd92023-03-14 11:43:23 +08001164
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001165 // Sort the "staged snapshot" using the sort_key, using different functions
1166 // depending on whether sort key is numeric or string
1167 if (is_sort_key_numeric)
1168 {
1169 std::sort(
1170 stats_snapshot_staged.begin(), stats_snapshot_staged.end(),
1171 [](const std::pair<StringOrFloat, std::vector<std::string>>& a,
1172 const std::pair<StringOrFloat, std::vector<std::string>>& b) {
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -05001173 return a.first.f < b.first.f;
Patrick Williams97749092023-10-20 11:20:24 -05001174 });
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001175 }
1176 else
1177 {
1178 std::sort(
1179 stats_snapshot_staged.begin(), stats_snapshot_staged.end(),
1180 [](const std::pair<StringOrFloat, std::vector<std::string>>& a,
1181 const std::pair<StringOrFloat, std::vector<std::string>>& b) {
Patrick Williamsf5c0b9d2023-03-14 09:31:01 -05001182 return a.first.s < b.first.s;
Patrick Williams97749092023-10-20 11:20:24 -05001183 });
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001184 }
kuiyingdfb0cd92023-03-14 11:43:23 +08001185
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001186 if (sort_order_ == Descending)
1187 {
1188 std::reverse(stats_snapshot_staged.begin(),
1189 stats_snapshot_staged.end());
1190 }
1191 // Minus 2 because of "msgs/s" and "+"
1192 const int num_fields = N;
1193 // The Y span of the area for rendering the "spreadsheet"
1194 const int y0 = 2, y1 = y0 + num_lines_shown - 1;
1195 // Key is sender, destination, interface, path, etc
1196 for (int i = 0, shown = 0;
kuiyingdfb0cd92023-03-14 11:43:23 +08001197 i + disp_row_idx_ < static_cast<int>(stats_snapshot_staged.size()) &&
1198 shown < num_lines_shown;
1199 i++, shown++)
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001200 {
1201 std::string s;
1202 int x = 0;
1203 const std::vector<std::string> key =
1204 stats_snapshot_staged[i + disp_row_idx_].second;
1205 for (int j = 0; j < num_fields; j++)
1206 {
1207 x = xs[j];
1208 s = key[j];
1209 // Determine column width limit for this particular column
1210 int col_width = 100;
1211 if (j < num_fields - 1)
1212 {
1213 col_width = xs[j + 1] - xs[j] - 1;
1214 }
1215 s = Ellipsize(s, col_width);
1216 if (x < 0)
1217 {
1218 if (-x < static_cast<int>(s.size()))
1219 s = s.substr(-x);
1220 else
1221 s = "";
1222 x = 0;
1223 }
1224 // Trim if string overflows to the right
1225 if (x + static_cast<int>(s.size()) > rect.w)
1226 {
1227 s = s.substr(0, rect.w - x);
1228 }
1229 mvwaddstr(win, 2 + i, x, s.c_str());
1230 }
1231 }
1232 // Overflows past the top ...
1233 if (disp_row_idx_ > 0)
1234 {
1235 std::string x = " [+" + std::to_string(disp_row_idx_) + " rows above]";
1236 mvwaddstr(win, y0, rect.w - static_cast<int>(x.size()) - 1, x.c_str());
1237 }
1238 // Overflows past the bottom ...
1239 const int last_disp_row_idx = disp_row_idx_ + num_lines_shown - 1;
1240 if (last_disp_row_idx < nrows - 1)
1241 {
1242 std::string x = " [+" +
1243 std::to_string((nrows - 1) - last_disp_row_idx) +
1244 " rows below]";
1245 mvwaddstr(win, y1, rect.w - static_cast<int>(x.size()) - 1, x.c_str());
1246 }
1247 DrawBorderIfNeeded();
1248 wrefresh(win);
1249}
1250
1251void DBusStatListView::OnResize(int win_w, int win_h)
1252{
1253 rect.y = 8 - MARGIN_BOTTOM;
1254 rect.w = win_w - (win_w / 2) + 1; // Perfectly overlap on the vertical edge
1255 rect.x = win_w - rect.w;
1256 rect.h = win_h - rect.y - MARGIN_BOTTOM;
1257 const int x0 = rect.w / 2 - menu_w_ - menu_margin_ / 2;
1258 const int x1 = x0 + menu_margin_ + menu_w_;
1259 const int menu_y = rect.h - menu_h_;
1260 menu1_->SetRect(Rect(x0, menu_y, menu_w_, menu_h_)); // Local coordinates
1261 menu1_->SetOrder(ArrowKeyNavigationMenu::Order::ColumnMajor);
1262 menu2_->SetRect(Rect(x1, menu_y, menu_w_, menu_h_));
1263 menu2_->SetOrder(ArrowKeyNavigationMenu::Order::ColumnMajor);
1264 UpdateWindowSizeAndPosition();
1265}
1266
1267std::vector<DBusTopSortField> DBusStatListView::GetSortFields()
1268{
1269 std::vector<DBusTopSortField> ret;
1270 const int N = sizeof(FieldNames) / sizeof(FieldNames[0]);
1271 for (const std::string& s : menu2_->Items())
1272 {
1273 for (int i = 0; i < N; i++)
1274 {
1275 if (FieldNames[i] == s)
1276 {
1277 ret.push_back(static_cast<DBusTopSortField>(i));
1278 break;
1279 }
1280 }
1281 }
1282 return ret;
1283}
1284
1285std::string DBusStatListView::GetStatusString()
1286{
1287 if (curr_menu_state_ == LeftSide || curr_menu_state_ == RightSide)
1288 {
1289 return "[Enter]=Hide Panel [Space]=Choose Entry [Arrow Keys]=Move "
1290 "Cursor";
1291 }
1292 else
1293 {
1294 return "[Enter]=Show Column Select Panel [Arrow Keys]=Pan View";
1295 }
1296}
1297
1298void FooterView::Render()
1299{
1300 werase(win);
1301 const time_t now = time(nullptr);
1302 const char* date_time = ctime(&now);
1303 wattrset(win, 0);
1304 std::string help_info;
1305 if (g_current_active_view == nullptr)
1306 {
1307 help_info = "Press [Tab] to cycle through views";
1308 }
1309 else
1310 {
1311 help_info = g_current_active_view->GetStatusString();
1312 }
1313 mvwaddstr(win, 0, 1, date_time);
Sui Chen8c5208f2023-04-21 14:10:05 -07001314 mvwaddstr(win, 0, 27, " ");
1315 mvwaddstr(win, 0, 27, status_string_.c_str());
Adedeji Adebisi684ec912021-07-22 18:07:52 +00001316 mvwaddstr(win, 0, rect.w - int(help_info.size()) - 1, help_info.c_str());
1317 wrefresh(win);
1318}