blob: 66af29733ef5db20d9af1848433b03f63f4eeaf4 [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 "main.hpp"
16
17#include "analyzer.hpp"
18#include "bargraph.hpp"
19#include "dbus_capture.hpp"
20#include "histogram.hpp"
21#include "menu.hpp"
22#include "sensorhelper.hpp"
23#include "views.hpp"
24
Adedeji Adebisi684ec912021-07-22 18:07:52 +000025#include <ncurses.h>
26#include <stdio.h>
27#include <unistd.h>
28
Sui Chen8c5208f2023-04-21 14:10:05 -070029#include <cassert>
Patrick Williams471703e2023-07-13 17:56:59 -050030#include <format>
Adedeji Adebisi684ec912021-07-22 18:07:52 +000031#include <iomanip>
Sui Chen8c5208f2023-04-21 14:10:05 -070032#include <mutex>
Adedeji Adebisi684ec912021-07-22 18:07:52 +000033#include <sstream>
34#include <thread>
35
36DBusTopWindow* g_current_active_view;
37SummaryView* g_summary_window;
38SensorDetailView* g_sensor_detail_view;
39DBusStatListView* g_dbus_stat_list_view;
40FooterView* g_footer_view;
41BarGraph<float>* g_bargraph = nullptr;
42Histogram<float>* g_histogram;
43std::vector<DBusTopWindow*> g_views;
44int g_highlighted_view_index = INVALID;
45sd_bus* g_bus = nullptr;
Sui Chen8c5208f2023-04-21 14:10:05 -070046SensorSnapshot *g_sensor_snapshot, *g_sensor_snapshot_staging = nullptr;
47DBusConnectionSnapshot *g_connection_snapshot,
48 *g_connection_snapshot_staging = nullptr;
Adedeji Adebisi684ec912021-07-22 18:07:52 +000049DBusTopStatistics* g_dbus_statistics; // At every update interval,
50 // dbus_top_analyzer::g_dbus_statistics's
51 // value is copied to this one for display
Sui Chen8c5208f2023-04-21 14:10:05 -070052
53// Whenever an update of SensorSnapshot and DBusConnectionSnapshot is needed,
54// they are populated into the "staging" copies and a pointer swap is done
55// by the main thread (the thread that constructs the snapshots shall not touch
56// the copy used for UI rendering)
57bool g_sensor_update_thread_active = false;
58std::string g_snapshot_update_bus_cxn =
59 ""; // The DBus connection used by the updater thread.
60int g_snapshot_update_bus_cxn_id = -999;
61std::mutex g_mtx_snapshot_update;
62
63int GetConnectionNumericID(const std::string& unique_name)
64{
65 size_t idx = unique_name.find('.');
66 if (idx == std::string::npos)
67 {
68 return -999;
69 }
70 try
71 {
72 int ret = std::atoi(unique_name.substr(idx + 1).c_str());
73 return ret;
74 }
75 catch (const std::exception& e)
76 {
77 return -999;
78 }
79}
80
Adedeji Adebisi684ec912021-07-22 18:07:52 +000081void ReinitializeUI();
82int maxx, maxy, halfx, halfy;
83
84void ActivateWindowA()
85{
nitroglycerine49dcde12023-03-14 07:24:39 -070086 g_views[0]->visible_ = true;
87 g_views[0]->maximize_ = true;
88 g_views[1]->visible_ = false;
89 g_views[2]->visible_ = false;
Adedeji Adebisi684ec912021-07-22 18:07:52 +000090}
91
92void ActivateWindowB()
93{
nitroglycerine49dcde12023-03-14 07:24:39 -070094 g_views[1]->visible_ = true;
95 g_views[1]->maximize_ = true;
96 g_views[0]->visible_ = false;
97 g_views[2]->visible_ = false;
Adedeji Adebisi684ec912021-07-22 18:07:52 +000098}
99void ActivateWindowC()
100{
nitroglycerine49dcde12023-03-14 07:24:39 -0700101 g_views[2]->visible_ = true;
102 g_views[2]->maximize_ = true;
103 g_views[0]->visible_ = false;
104 g_views[1]->visible_ = false;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000105}
106void ActivateAllWindows()
107{
108 g_views[0]->maximize_ = false;
nitroglycerine49dcde12023-03-14 07:24:39 -0700109 g_views[1]->maximize_ = false;
110 g_views[2]->maximize_ = false;
111 g_views[0]->visible_ = true;
112 g_views[1]->visible_ = true;
113 g_views[2]->visible_ = true;
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000114}
115
116void InitColorPairs()
117{
118 init_pair(1, COLOR_WHITE, COLOR_BLACK); // Does not work on actual machine
119 init_pair(2, COLOR_BLACK, COLOR_WHITE);
120}
121
122// Returns number of lines drawn
123int DrawTextWithWidthLimit(WINDOW* win, std::string txt, int y, int x,
nitroglycerine49dcde12023-03-14 07:24:39 -0700124 int width,
125 [[maybe_unused]] const std::string& delimiters)
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000126{
127 int ret = 0;
128 std::string curr_word, curr_line;
129 while (txt.empty() == false)
130 {
131 ret++;
132 if (static_cast<int>(txt.size()) > width)
133 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700134 mvwaddstr(win, y, x, txt.substr(0, width).c_str());
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000135 txt = txt.substr(width);
136 }
137 else
138 {
Sui Chen8643b5d2022-08-14 11:56:30 -0700139 mvwaddstr(win, y, x, txt.c_str());
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000140 break;
141 }
142 y++;
143 }
144 return ret;
145}
146
147void UpdateWindowSizes()
148{
149 /* calculate window sizes and locations */
150 if (getenv("FIXED_TERMINAL_SIZE"))
151 {
152 maxx = 100;
153 maxy = 30;
154 }
155 else
156 {
157 getmaxyx(stdscr, maxy, maxx);
158 halfx = maxx >> 1;
159 halfy = maxy >> 1;
160 }
161 for (DBusTopWindow* v : g_views)
162 {
163 v->OnResize(maxx, maxy);
nitroglycerine49dcde12023-03-14 07:24:39 -0700164 if (v->maximize_)
165 {
166 v->rect = {0, 0, maxx, maxy - MARGIN_BOTTOM};
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000167 v->UpdateWindowSizeAndPosition();
168 }
169 }
170}
171
172std::string FloatToString(float value)
173{
Patrick Williams471703e2023-07-13 17:56:59 -0500174 return std::format("{:.2f}", value);
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000175}
176
177void DBusTopRefresh()
178{
Sui Chen8c5208f2023-04-21 14:10:05 -0700179 g_mtx_snapshot_update.lock();
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000180 UpdateWindowSizes();
181 for (DBusTopWindow* v : g_views)
182 {
183 v->Render();
184 }
185 DBusTopWindow* focused_view = g_current_active_view;
186 if (focused_view)
187 {
188 focused_view->DrawBorderIfNeeded(); // focused view border: on top
189 }
190 refresh();
Sui Chen8c5208f2023-04-21 14:10:05 -0700191 g_mtx_snapshot_update.unlock();
192}
193
194void DBusTopUpdateFooterView()
195{
196 g_mtx_snapshot_update.lock();
197 g_footer_view->Render();
198 g_mtx_snapshot_update.unlock();
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000199}
200
201// This function is called by the Capture thread
202void DBusTopStatisticsCallback(DBusTopStatistics* stat, Histogram<float>* hist)
203{
204 if (stat == nullptr)
205 return;
206 // Makes a copy for display
207 // TODO: Add a mutex here for safety
208 stat->Assign(g_dbus_statistics);
209 hist->Assign(g_histogram);
210 float interval_secs = stat->seconds_since_last_sample_;
211 if (interval_secs == 0)
212 {
213 interval_secs = GetSummaryIntervalInMillises() / 1000.0f;
214 }
215 g_summary_window->UpdateDBusTopStatistics(stat);
Sui Chen8c5208f2023-04-21 14:10:05 -0700216
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000217 stat->SetSortFieldsAndReset(g_dbus_stat_list_view->GetSortFields());
Sui Chen8c5208f2023-04-21 14:10:05 -0700218
219 g_mtx_snapshot_update.lock();
220 if (g_sensor_snapshot_staging != nullptr &&
221 g_connection_snapshot_staging != nullptr)
222 {
223 std::swap(g_sensor_snapshot_staging, g_sensor_snapshot);
224 std::swap(g_connection_snapshot_staging, g_connection_snapshot);
225
226 delete g_connection_snapshot_staging;
227 delete g_sensor_snapshot_staging;
228
229 g_sensor_snapshot_staging = nullptr;
230 g_connection_snapshot_staging = nullptr;
231 }
232 g_mtx_snapshot_update.unlock();
233 g_sensor_detail_view->UpdateSensorSnapshot(g_sensor_snapshot);
234
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000235 // ReinitializeUI(); // Don't do it here, only when user presses [R]
236 DBusTopRefresh();
237}
238
239void CycleHighlightedView()
240{
241 int new_index = 0;
242 if (g_highlighted_view_index == INVALID)
243 {
244 new_index = 0;
245 }
246 else
247 {
248 new_index = g_highlighted_view_index + 1;
249 }
250 while (new_index < static_cast<int>(g_views.size()) &&
251 g_views[new_index]->selectable_ == false)
252 {
253 new_index++;
254 }
255 if (new_index >= static_cast<int>(g_views.size()))
256 {
257 new_index = INVALID;
258 }
259 // Un-highlight all
260 for (DBusTopWindow* v : g_views)
261 {
262 v->focused_ = false;
263 }
264 if (new_index != INVALID)
265 {
266 g_views[new_index]->focused_ = true;
267 g_current_active_view = g_views[new_index];
268 }
269 else
270 {
271 g_current_active_view = nullptr;
272 }
273 g_highlighted_view_index = new_index;
274 DBusTopRefresh();
275}
276
277int UserInputThread()
278{
279 while (true)
280 {
281 int c = getch();
282 DBusTopWindow* curr_view = g_current_active_view;
283 // If a view is currently focused on
284 if (curr_view)
285 {
286 switch (c)
287 {
nitroglycerine49dcde12023-03-14 07:24:39 -0700288 case 0x1B: // 27 in dec, 0x1B in hex, escape key
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000289 {
290 getch();
291 c = getch();
292 switch (c)
293 {
294 case 'A':
295 curr_view->OnKeyDown("up");
296 break;
297 case 'B':
298 curr_view->OnKeyDown("down");
299 break;
300 case 'C':
301 curr_view->OnKeyDown("right");
302 break;
303 case 'D':
304 curr_view->OnKeyDown("left");
305 break;
nitroglycerine49dcde12023-03-14 07:24:39 -0700306 case 0x1B:
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000307 curr_view->OnKeyDown("escape");
308 break;
309 }
310 break;
311 }
312 case '\n': // 10 in dec, 0x0A in hex, line feed
313 {
314 curr_view->OnKeyDown("enter");
315 break;
316 }
317 case 'q':
318 case 'Q': // Q key
319 {
320 curr_view->OnKeyDown("escape");
321 break;
322 }
323 case 'a':
324 case 'A': // A key
325 {
326 curr_view->OnKeyDown("a");
327 break;
328 }
329 case 'd':
330 case 'D': // D key
331 {
332 curr_view->OnKeyDown("d");
333 break;
334 }
335 case 33: // Page up
336 {
337 curr_view->OnKeyDown("pageup");
338 break;
339 }
340 case 34: // Page down
341 {
342 curr_view->OnKeyDown("pagedown");
343 break;
344 }
345 case ' ': // Spacebar
346 {
347 curr_view->OnKeyDown("space");
348 break;
349 }
350 }
351 }
352 // The following keys are registered both when a view is selected and
353 // when it is not
354 switch (c)
355 {
356 case '\t': // 9 in dec, 0x09 in hex, tab
357 {
358 CycleHighlightedView();
359 break;
360 }
361 case 'r':
362 case 'R':
363 {
364 ReinitializeUI();
365 DBusTopRefresh();
366 break;
367 }
368 case 'x':
369 case 'X':
370 {
371 clear();
372 ActivateWindowA();
373 break;
374 }
375 case 'y':
376 case 'Y':
377 {
378 clear();
379 ActivateWindowB();
380 break;
381 }
382 case 'z':
383 case 'Z':
384 {
385 clear();
386 ActivateWindowC();
387 break;
388 }
389 case 'h':
390 case 'H':
391 {
392 ActivateAllWindows();
393 DBusTopRefresh();
394 }
395 default:
396 break;
397 }
398 }
399 exit(0);
400}
401
402void ReinitializeUI()
403{
404 endwin();
405 initscr();
406 use_default_colors();
407 noecho();
408 for (int i = 0; i < static_cast<int>(g_views.size()); i++)
409 {
410 g_views[i]->RecreateWindow();
411 }
412}
413
Sui Chen8c5208f2023-04-21 14:10:05 -0700414void ListAllSensorsThread()
415{
416 // Create a temporary connection
417 assert(g_sensor_update_thread_active == false);
418 sd_bus* bus;
419 AcquireBus(&bus);
420
421 const char* bus_name;
422 sd_bus_get_unique_name(bus, &bus_name);
423 g_snapshot_update_bus_cxn = std::string(bus_name);
424 g_snapshot_update_bus_cxn_id =
425 GetConnectionNumericID(g_snapshot_update_bus_cxn);
426
427 g_sensor_update_thread_active = true;
428 DBusConnectionSnapshot* cxn_snapshot;
429 SensorSnapshot* sensor_snapshot;
430 dbus_top_analyzer::ListAllSensors(bus, &cxn_snapshot, &sensor_snapshot);
431
432 g_mtx_snapshot_update.lock();
433 g_connection_snapshot = cxn_snapshot;
434 g_sensor_snapshot = sensor_snapshot;
435 g_mtx_snapshot_update.unlock();
436 g_sensor_update_thread_active = false;
437 g_snapshot_update_bus_cxn = "";
438 g_snapshot_update_bus_cxn_id = -999;
439
440 sd_bus_close(bus);
441}
442
nitroglycerine49dcde12023-03-14 07:24:39 -0700443int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000444{
445 int r = AcquireBus(&g_bus);
446 if (r <= 0)
447 {
448 printf("Error acquiring bus for monitoring\n");
449 exit(0);
450 }
451
452 printf("Listing all sensors for display\n");
453 // ListAllSensors creates connection snapshot and sensor snapshot
Sui Chen8c5208f2023-04-21 14:10:05 -0700454 g_connection_snapshot = new DBusConnectionSnapshot();
455 g_sensor_snapshot = new SensorSnapshot(g_connection_snapshot);
456
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000457 g_bargraph = new BarGraph<float>(300);
458 g_histogram = new Histogram<float>();
459
460 initscr();
461 use_default_colors();
462 start_color();
463 noecho();
464
465 clear();
466 g_dbus_statistics = new DBusTopStatistics();
467 g_summary_window = new SummaryView();
468 g_sensor_detail_view = new SensorDetailView();
469 g_dbus_stat_list_view = new DBusStatListView();
470 g_footer_view = new FooterView();
471 g_views.push_back(g_summary_window);
472 g_views.push_back(g_sensor_detail_view);
473 g_views.push_back(g_dbus_stat_list_view);
474 g_views.push_back(g_footer_view);
475
Sui Chen8c5208f2023-04-21 14:10:05 -0700476 // Do the scan in a separate thread.
477 std::thread list_all_sensors_thread(ListAllSensorsThread);
478
Adedeji Adebisi684ec912021-07-22 18:07:52 +0000479 g_sensor_detail_view->UpdateSensorSnapshot(g_sensor_snapshot);
480 UpdateWindowSizes();
481 dbus_top_analyzer::SetDBusTopStatisticsCallback(&DBusTopStatisticsCallback);
482 std::thread capture_thread(DbusCaptureThread);
483 std::thread user_input_thread(UserInputThread);
484 capture_thread.join();
485
486 return 0;
487}