Panda3D
gtkStatsMonitor.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file gtkStatsMonitor.cxx
10  * @author drose
11  * @date 2006-01-16
12  */
13 
14 #include "gtkStatsMonitor.h"
15 #include "gtkStats.h"
16 #include "gtkStatsServer.h"
17 #include "gtkStatsStripChart.h"
18 #include "gtkStatsChartMenu.h"
19 #include "gtkStatsPianoRoll.h"
20 #include "gtkStatsMenuId.h"
21 #include "pStatGraph.h"
22 #include "pStatCollectorDef.h"
23 #include "indent.h"
24 
25 typedef void vc();
26 
27 GtkItemFactoryEntry GtkStatsMonitor::menu_entries[] = {
28  { (gchar *)"/Options", nullptr, nullptr, 0, (gchar *)"<Branch>" },
29  { (gchar *)"/Options/Units", nullptr, nullptr, 0, (gchar *)"<Branch>" },
30  { (gchar *)"/Options/Units/ms", nullptr, (vc *)&handle_menu_command, MI_time_ms, (gchar *)"<RadioItem>" },
31  { (gchar *)"/Options/Units/Hz", nullptr, (vc *)&handle_menu_command, MI_time_hz, (gchar *)"/Options/Units/ms" },
32  { (gchar *)"/Speed", nullptr, nullptr, 0, (gchar *)"<Branch>" },
33  { (gchar *)"/Speed/1", nullptr, (vc *)&handle_menu_command, MI_speed_1, (gchar *)"<RadioItem>" },
34  { (gchar *)"/Speed/2", nullptr, (vc *)&handle_menu_command, MI_speed_2, (gchar *)"/Speed/1" },
35  { (gchar *)"/Speed/3", nullptr, (vc *)&handle_menu_command, MI_speed_3, (gchar *)"/Speed/1" },
36  { (gchar *)"/Speed/6", nullptr, (vc *)&handle_menu_command, MI_speed_6, (gchar *)"/Speed/1" },
37  { (gchar *)"/Speed/12", nullptr, (vc *)&handle_menu_command, MI_speed_12, (gchar *)"/Speed/1" },
38  { (gchar *)"/Speed/sep", nullptr, nullptr, 0, (gchar *)"<Separator>" },
39  { (gchar *)"/Speed/pause", nullptr, (vc *)&handle_menu_command, MI_pause, (gchar *)"<CheckItem>" },
40 };
41 
42 int GtkStatsMonitor::num_menu_entries = sizeof(menu_entries) / sizeof(GtkItemFactoryEntry);
43 
44 /**
45  *
46  */
47 GtkStatsMonitor::
48 GtkStatsMonitor(GtkStatsServer *server) : PStatMonitor(server) {
49  _window = nullptr;
50  _item_factory = nullptr;
51 
52  // These will be filled in later when the menu is created.
53  _time_units = 0;
54  _scroll_speed = 0.0;
55  _pause = false;
56 }
57 
58 /**
59  *
60  */
61 GtkStatsMonitor::
62 ~GtkStatsMonitor() {
63  shutdown();
64 }
65 
66 /**
67  * Should be redefined to return a descriptive name for the type of
68  * PStatsMonitor this is.
69  */
70 std::string GtkStatsMonitor::
72  return "GtkStats";
73 }
74 
75 /**
76  * Called after the monitor has been fully set up. At this time, it will have
77  * a valid _client_data pointer, and things like is_alive() and close() will
78  * be meaningful. However, we may not yet know who we're connected to
79  * (is_client_known() may return false), and we may not know anything about
80  * the threads or collectors we're about to get data on.
81  */
84 }
85 
86 /**
87  * Called when the "hello" message has been received from the client. At this
88  * time, the client's hostname and program name will be known.
89  */
92  create_window();
93  open_strip_chart(0, 0, false);
94 }
95 
96 /**
97  * Like got_hello(), this is called when the "hello" message has been received
98  * from the client. At this time, the client's hostname and program name will
99  * be known. However, the client appears to be an incompatible version and
100  * the connection will be terminated; the monitor should issue a message to
101  * that effect.
102  */
104 got_bad_version(int client_major, int client_minor,
105  int server_major, int server_minor) {
106  std::ostringstream str;
107  str << "Unable to honor connection attempt from "
108  << get_client_progname() << " on " << get_client_hostname()
109  << ": unsupported PStats version "
110  << client_major << "." << client_minor;
111 
112  if (server_minor == 0) {
113  str << " (server understands version " << server_major
114  << "." << server_minor << " only).";
115  } else {
116  str << " (server understands versions " << server_major
117  << ".0 through " << server_major << "." << server_minor << ").";
118  }
119 
120  std::string message = str.str();
121  GtkWidget *dialog =
122  gtk_message_dialog_new(GTK_WINDOW(main_window),
123  GTK_DIALOG_DESTROY_WITH_PARENT,
124  GTK_MESSAGE_ERROR,
125  GTK_BUTTONS_CLOSE,
126  "%s", message.c_str());
127  gtk_dialog_run(GTK_DIALOG(dialog));
128  gtk_widget_destroy(dialog);
129 }
130 
131 /**
132  * Called whenever a new Collector definition is received from the client.
133  * Generally, the client will send all of its collectors over shortly after
134  * connecting, but there's no guarantee that they will all be received before
135  * the first frames are received. The monitor should be prepared to accept
136  * new Collector definitions midstream.
137  */
139 new_collector(int collector_index) {
140  Graphs::iterator gi;
141  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
142  GtkStatsGraph *graph = (*gi);
143  graph->new_collector(collector_index);
144  }
145 
146  // We might need to update our menus.
147  ChartMenus::iterator mi;
148  for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) {
149  (*mi)->do_update();
150  }
151 }
152 
153 /**
154  * Called whenever a new Thread definition is received from the client.
155  * Generally, the client will send all of its threads over shortly after
156  * connecting, but there's no guarantee that they will all be received before
157  * the first frames are received. The monitor should be prepared to accept
158  * new Thread definitions midstream.
159  */
161 new_thread(int thread_index) {
162  GtkStatsChartMenu *chart_menu = new GtkStatsChartMenu(this, thread_index);
163  GtkWidget *menu_bar = gtk_item_factory_get_widget(_item_factory, "<PStats>");
164  chart_menu->add_to_menu_bar(menu_bar, _next_chart_index);
165  ++_next_chart_index;
166  _chart_menus.push_back(chart_menu);
167 }
168 
169 /**
170  * Called as each frame's data is made available. There is no guarantee the
171  * frames will arrive in order, or that all of them will arrive at all. The
172  * monitor should be prepared to accept frames received out-of-order or
173  * missing.
174  */
176 new_data(int thread_index, int frame_number) {
177  Graphs::iterator gi;
178  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
179  GtkStatsGraph *graph = (*gi);
180  graph->new_data(thread_index, frame_number);
181  }
182 }
183 
184 
185 /**
186  * Called whenever the connection to the client has been lost. This is a
187  * permanent state change. The monitor should update its display to represent
188  * this, and may choose to close down automatically.
189  */
192  nout << "Lost connection to " << get_client_hostname() << "\n";
193 
194  shutdown();
195 }
196 
197 /**
198  * If has_idle() returns true, this will be called periodically to allow the
199  * monitor to update its display or whatever it needs to do.
200  */
202 idle() {
203  // Check if any of our chart menus need updating.
204  ChartMenus::iterator mi;
205  for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) {
206  (*mi)->check_update();
207  }
208 
209  // Update the frame rate label from the main thread (thread 0).
210  const PStatThreadData *thread_data = get_client_data()->get_thread_data(0);
211  double frame_rate = thread_data->get_frame_rate();
212  if (frame_rate != 0.0f) {
213  char buffer[128];
214  sprintf(buffer, "%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate);
215 
216  gtk_label_set_text(GTK_LABEL(_frame_rate_label), buffer);
217  }
218 }
219 
220 /**
221  * Should be redefined to return true if you want to redefine idle() and
222  * expect it to be called.
223  */
226  return true;
227 }
228 
229 /**
230  * Called when the user guide bars have been changed.
231  */
234  Graphs::iterator gi;
235  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
236  GtkStatsGraph *graph = (*gi);
237  graph->user_guide_bars_changed();
238  }
239 }
240 
241 /**
242  * Returns the window handle to the monitor's window.
243  */
244 GtkWidget *GtkStatsMonitor::
245 get_window() const {
246  return _window;
247 }
248 
249 /**
250  * Opens a new strip chart showing the indicated data.
251  */
253 open_strip_chart(int thread_index, int collector_index, bool show_level) {
254  GtkStatsStripChart *graph =
255  new GtkStatsStripChart(this, thread_index, collector_index, show_level);
256  add_graph(graph);
257 
258  graph->set_time_units(_time_units);
259  graph->set_scroll_speed(_scroll_speed);
260  graph->set_pause(_pause);
261 }
262 
263 /**
264  * Opens a new piano roll showing the indicated data.
265  */
267 open_piano_roll(int thread_index) {
268  GtkStatsPianoRoll *graph = new GtkStatsPianoRoll(this, thread_index);
269  add_graph(graph);
270 
271  graph->set_time_units(_time_units);
272  graph->set_scroll_speed(_scroll_speed);
273  graph->set_pause(_pause);
274 }
275 
276 /**
277  * Adds a new MenuDef to the monitor, or returns an existing one if there is
278  * already one just like it.
279  */
281 add_menu(const MenuDef &menu_def) {
282  std::pair<Menus::iterator, bool> result = _menus.insert(menu_def);
283  Menus::iterator mi = result.first;
284  const GtkStatsMonitor::MenuDef &new_menu_def = (*mi);
285  if (result.second) {
286  // A new MenuDef was inserted.
287  ((GtkStatsMonitor::MenuDef &)new_menu_def)._monitor = this;
288  }
289  return &new_menu_def;
290 }
291 
292 /**
293  * Called when the user selects a new time units from the monitor pulldown
294  * menu, this should adjust the units for all graphs to the indicated mask if
295  * it is a time-based graph.
296  */
298 set_time_units(int unit_mask) {
299  _time_units = unit_mask;
300 
301  // First, change all of the open graphs appropriately.
302  Graphs::iterator gi;
303  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
304  GtkStatsGraph *graph = (*gi);
305  graph->set_time_units(_time_units);
306  }
307 }
308 
309 /**
310  * Called when the user selects a new scroll speed from the monitor pulldown
311  * menu, this should adjust the speeds for all graphs to the indicated value.
312  */
314 set_scroll_speed(double scroll_speed) {
315  _scroll_speed = scroll_speed;
316 
317  // First, change all of the open graphs appropriately.
318  Graphs::iterator gi;
319  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
320  GtkStatsGraph *graph = (*gi);
321  graph->set_scroll_speed(_scroll_speed);
322  }
323 }
324 
325 /**
326  * Called when the user selects a pause on or pause off option from the menu.
327  */
329 set_pause(bool pause) {
330  _pause = pause;
331 
332  // First, change all of the open graphs appropriately.
333  Graphs::iterator gi;
334  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
335  GtkStatsGraph *graph = (*gi);
336  graph->set_pause(_pause);
337  }
338 }
339 
340 /**
341  * Adds the newly-created graph to the list of managed graphs.
342  */
343 void GtkStatsMonitor::
344 add_graph(GtkStatsGraph *graph) {
345  _graphs.insert(graph);
346 }
347 
348 /**
349  * Deletes the indicated graph.
350  */
351 void GtkStatsMonitor::
352 remove_graph(GtkStatsGraph *graph) {
353  Graphs::iterator gi = _graphs.find(graph);
354  if (gi != _graphs.end()) {
355  _graphs.erase(gi);
356  delete graph;
357  }
358 }
359 
360 /**
361  * Creates the window for this monitor.
362  */
363 void GtkStatsMonitor::
364 create_window() {
365  if (_window != nullptr) {
366  return;
367  }
368 
369  _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
370 
371  g_signal_connect(G_OBJECT(_window), "delete_event",
372  G_CALLBACK(window_delete_event), this);
373  g_signal_connect(G_OBJECT(_window), "destroy",
374  G_CALLBACK(window_destroy), this);
375 
376  _window_title = get_client_progname() + " on " + get_client_hostname();
377  gtk_window_set_title(GTK_WINDOW(_window), _window_title.c_str());
378 
379  gtk_window_set_default_size(GTK_WINDOW(_window), 500, 360);
380 
381  // Set up the menu.
382  GtkAccelGroup *accel_group = gtk_accel_group_new();
383  _item_factory =
384  gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PStats>", accel_group);
385  gtk_item_factory_create_items(_item_factory, num_menu_entries, menu_entries,
386  this);
387  gtk_window_add_accel_group(GTK_WINDOW(_window), accel_group);
388  GtkWidget *menu_bar = gtk_item_factory_get_widget(_item_factory, "<PStats>");
389  _next_chart_index = 2;
390 
391  setup_frame_rate_label();
392 
393  ChartMenus::iterator mi;
394  for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) {
395  (*mi)->add_to_menu_bar(menu_bar, _next_chart_index);
396  ++_next_chart_index;
397  }
398 
399  // Pack the menu into the window.
400  GtkWidget *main_vbox = gtk_vbox_new(FALSE, 1);
401  gtk_container_add(GTK_CONTAINER(_window), main_vbox);
402  gtk_box_pack_start(GTK_BOX(main_vbox), menu_bar, FALSE, TRUE, 0);
403 
404  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(_item_factory, "/Speed/3")),
405  TRUE);
406  set_scroll_speed(3);
407 
408  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(_item_factory, "/Options/Units/ms")),
409  TRUE);
410  set_time_units(PStatGraph::GBU_ms);
411 
412  gtk_widget_show_all(_window);
413  gtk_widget_show(_window);
414 
415  set_pause(false);
416 }
417 
418 /**
419  * Closes all the graphs associated with this monitor.
420  */
421 void GtkStatsMonitor::
422 shutdown() {
423  Graphs::iterator gi;
424  for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) {
425  delete (*gi);
426  }
427  _graphs.clear();
428 
429  ChartMenus::iterator mi;
430  for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) {
431  delete (*mi);
432  }
433  _chart_menus.clear();
434 
435  if (_window != nullptr) {
436  gtk_widget_destroy(_window);
437  _window = nullptr;
438  }
439 
440 #ifdef DEVELOP_GTKSTATS
441  // For GtkStats developers, exit when the first monitor closes.
442  gtk_main_quit();
443 #endif
444 }
445 
446 /**
447  * Callback when the window is closed by the user.
448  */
449 gboolean GtkStatsMonitor::
450 window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
451  // Returning FALSE to indicate we should destroy the window when the user
452  // selects "close".
453  return FALSE;
454 }
455 
456 /**
457  * Callback when the window is destroyed by the system (or by delete_event).
458  */
459 void GtkStatsMonitor::
460 window_destroy(GtkWidget *widget, gpointer data) {
461  GtkStatsMonitor *self = (GtkStatsMonitor *)data;
462  self->close();
463 }
464 
465 /**
466  * Creates the frame rate label on the right end of the menu bar. This is
467  * used as a text label to display the main thread's frame rate to the user,
468  * although it is implemented as a right-justified toplevel menu item that
469  * doesn't open to anything.
470  */
471 void GtkStatsMonitor::
472 setup_frame_rate_label() {
473  GtkWidget *menu_bar = gtk_item_factory_get_widget(_item_factory, "<PStats>");
474 
475  _frame_rate_menu_item = gtk_menu_item_new();
476  _frame_rate_label = gtk_label_new("");
477  gtk_container_add(GTK_CONTAINER(_frame_rate_menu_item), _frame_rate_label);
478 
479  gtk_widget_show(_frame_rate_menu_item);
480  gtk_widget_show(_frame_rate_label);
481  gtk_menu_item_right_justify(GTK_MENU_ITEM(_frame_rate_menu_item));
482 
483  gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), _frame_rate_menu_item);
484 }
485 
486 /**
487  *
488  */
489 void GtkStatsMonitor::
490 handle_menu_command(gpointer callback_data, guint menu_id, GtkWidget *widget) {
491  GtkStatsMonitor *self = (GtkStatsMonitor *)callback_data;
492  switch (menu_id) {
493  case MI_none:
494  break;
495 
496  case MI_time_ms:
497  self->set_time_units(PStatGraph::GBU_ms);
498  break;
499 
500  case MI_time_hz:
501  self->set_time_units(PStatGraph::GBU_hz);
502  break;
503 
504  case MI_speed_1:
505  self->set_scroll_speed(1);
506  break;
507 
508  case MI_speed_2:
509  self->set_scroll_speed(2);
510  break;
511 
512  case MI_speed_3:
513  self->set_scroll_speed(3);
514  break;
515 
516  case MI_speed_6:
517  self->set_scroll_speed(6);
518  break;
519 
520  case MI_speed_12:
521  self->set_scroll_speed(12);
522  break;
523 
524  case MI_pause:
525  self->set_pause(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)));
526  break;
527  }
528 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A window that draws a strip chart, given a view.
virtual void new_thread(int thread_index)
Called whenever a new Thread definition is received from the client.
virtual void set_time_units(int unit_mask)
Called when the user selects a new time units from the monitor pulldown menu, this should adjust the ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void got_hello()
Called when the "hello" message has been received from the client.
void open_piano_roll(int thread_index)
Opens a new piano roll showing the indicated data.
virtual void new_data(int thread_index, int frame_number)
Called whenever new data arrives.
void user_guide_bars_changed()
Called when the user guide bars have been changed.
void set_pause(bool pause)
Changes the pause flag for the graph.
The class that owns the main loop, waiting for client connections.
void close()
Closes the client connection if it is active.
virtual void got_bad_version(int client_major, int client_minor, int server_major, int server_minor)
Like got_hello(), this is called when the "hello" message has been received from the client.
std::string get_client_hostname() const
Returns the hostname of the client we're connected to, if known.
Definition: pStatMonitor.I:58
virtual void new_collector(int collector_index)
Called whenever a new Collector definition is received from the client.
A window that draws a piano-roll style chart, which shows the collectors explicitly stopping and star...
virtual void user_guide_bars_changed()
Called when the user guide bars have been changed.
This is an abstract class that presents the interface to any number of different front-ends for the s...
Definition: pStatMonitor.h:39
virtual std::string get_monitor_name()
Should be redefined to return a descriptive name for the type of PStatsMonitor this is.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_frame_rate() const
Computes the average frame rate over the past pstats_average_time seconds, by counting up the number ...
A pulldown menu of charts available for a particular thread.
virtual void set_time_units(int unit_mask)
Called when the user selects a new time units from the monitor pulldown menu, this should adjust the ...
std::string get_client_progname() const
Returns the program name of the client we're connected to, if known.
Definition: pStatMonitor.I:69
void add_to_menu_bar(GtkWidget *menu_bar, int position)
Adds the menu to the end of the indicated menu bar.
virtual void set_scroll_speed(double scroll_speed)
Called when the user selects a new scroll speed from the monitor pulldown menu, this should adjust th...
This is just an abstract base class to provide a common pointer type for the various kinds of graphs ...
Definition: gtkStatsGraph.h:29
A collection of FrameData structures for recently-received frames within a particular thread.
const MenuDef * add_menu(const MenuDef &menu_def)
Adds a new MenuDef to the monitor, or returns an existing one if there is already one just like it.
This class represents a connection to a PStatsClient and manages the data exchange with the client.
void open_strip_chart(int thread_index, int collector_index, bool show_level)
Opens a new strip chart showing the indicated data.
const PStatThreadData * get_thread_data(int index) const
Returns the data associated with the indicated thread.
void set_time_units(int unit_mask)
Called when the user selects a new time units from the monitor pulldown menu, this should adjust the ...
virtual void set_time_units(int unit_mask)
Called when the user selects a new time units from the monitor pulldown menu, this should adjust the ...
virtual void initialized()
Called after the monitor has been fully set up.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void new_data(int thread_index, int frame_number)
Called as each frame's data is made available.
virtual void set_scroll_speed(double scroll_speed)
Called when the user selects a new scroll speed from the monitor pulldown menu, this should adjust th...
GtkWidget * get_window() const
Returns the window handle to the monitor's window.
virtual void lost_connection()
Called whenever the connection to the client has been lost.
virtual bool has_idle()
Should be redefined to return true if you want to redefine idle() and expect it to be called.
virtual void idle()
If has_idle() returns true, this will be called periodically to allow the monitor to update its displ...
const PStatClientData * get_client_data() const
Returns the client data associated with this monitor.
Definition: pStatMonitor.I:26
void set_scroll_speed(double scroll_speed)
Called when the user selects a new scroll speed from the monitor pulldown menu, this should adjust th...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void new_collector(int collector_index)
Called whenever a new Collector definition is received from the client.
void set_pause(bool pause)
Called when the user selects a pause on or pause off option from the menu.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.