Adedeji Adebisi | 684ec91 | 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" |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 16 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 17 | #include "bargraph.hpp" |
| 18 | #include "histogram.hpp" |
| 19 | #include "menu.hpp" |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 20 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 21 | #include <string.h> |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 22 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 23 | #include <algorithm> |
| 24 | |
| 25 | extern SensorSnapshot* g_sensor_snapshot; |
| 26 | extern BarGraph<float>* g_bargraph; |
| 27 | extern DBusTopStatistics* g_dbus_statistics; |
| 28 | extern Histogram<float>* g_histogram; |
| 29 | extern DBusTopWindow* g_current_active_view; |
| 30 | extern const std::string FieldNames[]; |
| 31 | extern const int FieldPreferredWidths[]; |
| 32 | |
| 33 | namespace dbus_top_analyzer |
| 34 | { |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 35 | extern DBusTopStatistics g_dbus_statistics; |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 36 | } |
| 37 | |
| 38 | // Linear interpolation |
| 39 | float Lerp(float a, float b, float t) |
| 40 | { |
| 41 | return a + t * (b - a); |
| 42 | } |
| 43 | |
| 44 | // Linear map |
| 45 | float 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 | |
| 60 | template <typename T> |
| 61 | void 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 | |
| 166 | template <typename T> |
| 167 | void 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 | |
| 289 | void 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 | |
| 307 | std::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 | |
| 321 | void 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 | |
| 371 | void 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]; |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 418 | if (static_cast<int>(s.size()) > col_width) |
| 419 | { |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 420 | s = s.substr(0, col_width - 2) + ".."; |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 421 | } |
| 422 | else |
| 423 | { |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 424 | while (static_cast<int>(s.size()) < col_width) |
| 425 | { |
| 426 | s.push_back(' '); |
| 427 | } |
| 428 | } |
Sui Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 429 | mvwaddstr(win, y, x, s.c_str()); |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 430 | 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); |
nitroglycerine | 49dcde1 | 2023-03-14 07:24:39 -0700 | [diff] [blame] | 463 | mvwprintw(win, 1, rect.w - 15, "%zu sensors", sensor_ids_.size()); |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 464 | } |
| 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()); |
nitroglycerine | 49dcde1 | 2023-03-14 07:24:39 -0700 | [diff] [blame] | 470 | mvwprintw(win, 1, rect.w - 15, "Sensor %d/%zu", choice_ + 1, |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 471 | 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 Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 501 | mvwaddstr(win, y, x, "Sensor details not found"); |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 502 | } |
| 503 | } |
| 504 | DrawBorderIfNeeded(); |
| 505 | wrefresh(win); |
| 506 | } |
| 507 | |
| 508 | std::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 | |
| 521 | DBusStatListView::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 | { |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 550 | const std::string s = FieldNames[i]; |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 551 | if (inactive_fields.count(s) > 0) |
| 552 | { |
| 553 | menu1_->AddItem(s); |
| 554 | } |
| 555 | else |
| 556 | { |
| 557 | menu2_->AddItem(s); |
| 558 | } |
| 559 | } |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 560 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 561 | 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 | |
| 572 | std::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 |
| 595 | bool 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 | |
| 649 | bool 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 | } |
| 655 | std::vector<std::string> DBusStatListView::ColumnHeaders() |
| 656 | { |
| 657 | return visible_columns_; |
| 658 | } |
| 659 | |
| 660 | std::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] ] |
| 676 | void 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 | |
| 717 | void 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 |
| 837 | void 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 Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 862 | mvwaddstr(win, y1 - 3, arrow_x - 8, "Press [Enter] to show/hide"); |
| 863 | mvwaddstr(win, y1 - 2, caption_x - 5, |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 864 | "DBus fields for aggregating and sorting results:"); |
| 865 | if (curr_menu_state_ == LeftSide) |
| 866 | { |
Sui Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 867 | mvwaddstr(win, y1 - 1, x1 - 4, "--[ Available Fields ]--"); |
| 868 | mvwaddstr(win, y1 - 1, x2 - 4, "--- Active Fields ---"); |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 869 | } |
| 870 | else |
| 871 | { |
Sui Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 872 | mvwaddstr(win, y1 - 1, x1 - 4, "--- Available Fields ---"); |
| 873 | mvwaddstr(win, y1 - 1, x2 - 4, "--[ Active Fields ]--"); |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 874 | } |
| 875 | if (curr_menu_state_ == LeftSide) |
| 876 | { |
Sui Chen | 8643b5d | 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 | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 879 | "Press [Space] to move to the right"); |
| 880 | } |
| 881 | else |
| 882 | { |
Sui Chen | 8643b5d | 2022-08-14 11:56:30 -0700 | [diff] [blame] | 883 | mvwaddstr(win, arrow_y, arrow_x, "<--"); |
| 884 | mvwaddstr(win, caption_y, caption_x, |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 885 | "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 | |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 989 | StringOrFloat sort_key; // The key used for sorting |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 990 | 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 |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 996 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 997 | // 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 | } |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 1090 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 1091 | // 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 | } |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 1111 | |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 1112 | 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; |
kuiying | dfb0cd9 | 2023-03-14 11:43:23 +0800 | [diff] [blame^] | 1123 | i + disp_row_idx_ < static_cast<int>(stats_snapshot_staged.size()) && |
| 1124 | shown < num_lines_shown; |
| 1125 | i++, shown++) |
Adedeji Adebisi | 684ec91 | 2021-07-22 18:07:52 +0000 | [diff] [blame] | 1126 | { |
| 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 | |
| 1177 | void 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 | |
| 1193 | std::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 | |
| 1211 | std::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 | |
| 1224 | void 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 | } |