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