Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 1 | // 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 | |
| 22 | extern SensorSnapshot* g_sensor_snapshot; |
| 23 | extern BarGraph<float>* g_bargraph; |
| 24 | extern DBusTopStatistics* g_dbus_statistics; |
| 25 | extern Histogram<float>* g_histogram; |
| 26 | extern DBusTopWindow* g_current_active_view; |
| 27 | extern const std::string FieldNames[]; |
| 28 | extern const int FieldPreferredWidths[]; |
| 29 | |
| 30 | namespace dbus_top_analyzer |
| 31 | { |
| 32 | extern DBusTopStatistics g_dbus_statistics; |
| 33 | } |
| 34 | |
| 35 | // Linear interpolation |
| 36 | float Lerp(float a, float b, float t) |
| 37 | { |
| 38 | return a + t * (b - a); |
| 39 | } |
| 40 | |
| 41 | // Linear map |
| 42 | float 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 | |
| 57 | template <typename T> |
| 58 | void 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 | |
| 163 | template <typename T> |
| 164 | void 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 | |
| 286 | void 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 | |
| 304 | std::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 | |
| 318 | void 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 | |
| 368 | void 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 Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 423 | mvwaddstr(win, y, x, s.c_str()); |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 424 | 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 Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 495 | mvwaddstr(win, y, x, "Sensor details not found"); |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 496 | } |
| 497 | } |
| 498 | DrawBorderIfNeeded(); |
| 499 | wrefresh(win); |
| 500 | } |
| 501 | |
| 502 | std::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 | |
| 515 | DBusStatListView::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 | |
| 566 | std::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 |
| 589 | bool 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 | |
| 643 | bool 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 | } |
| 649 | std::vector<std::string> DBusStatListView::ColumnHeaders() |
| 650 | { |
| 651 | return visible_columns_; |
| 652 | } |
| 653 | |
| 654 | std::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] ] |
| 670 | void 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 | |
| 711 | void 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 |
| 831 | void 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 Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 856 | mvwaddstr(win, y1 - 3, arrow_x - 8, "Press [Enter] to show/hide"); |
| 857 | mvwaddstr(win, y1 - 2, caption_x - 5, |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 858 | "DBus fields for aggregating and sorting results:"); |
| 859 | if (curr_menu_state_ == LeftSide) |
| 860 | { |
Sui Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 861 | mvwaddstr(win, y1 - 1, x1 - 4, "--[ Available Fields ]--"); |
| 862 | mvwaddstr(win, y1 - 1, x2 - 4, "--- Active Fields ---"); |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 863 | } |
| 864 | else |
| 865 | { |
Sui Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 866 | mvwaddstr(win, y1 - 1, x1 - 4, "--- Available Fields ---"); |
| 867 | mvwaddstr(win, y1 - 1, x2 - 4, "--[ Active Fields ]--"); |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 868 | } |
| 869 | if (curr_menu_state_ == LeftSide) |
| 870 | { |
Sui Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 871 | mvwaddstr(win, arrow_y, arrow_x, "-->"); |
| 872 | mvwaddstr(win, caption_y, caption_x, |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 873 | "Press [Space] to move to the right"); |
| 874 | } |
| 875 | else |
| 876 | { |
Sui Chen | a296b77 | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 877 | mvwaddstr(win, arrow_y, arrow_x, "<--"); |
| 878 | mvwaddstr(win, caption_y, caption_x, |
Adedeji Adebisi | 12c5f11 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 879 | "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 | |
| 1171 | void 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 | |
| 1187 | std::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 | |
| 1205 | std::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 | |
| 1218 | void 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 | } |