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