From bb6976d5582ee00e564812eda400397a2c76afdd Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 6 Apr 2022 15:59:14 +0200 Subject: [PATCH] pstats: Third significant update to PStats server UI, including: * Windows stay open after client disconnects, for further inspection * Ability to save session results to a file, and reopening those files * Ability to save the current graph window layout for new sessions * Ability to change colors (by right-clicking on bar) * SI prefixes for Hz units (kHz, MHz, etc.) * Ability to export session to Chrome Tracing JSON format * "Close All Graphs" menu option * Graphs now properly show data when opened while Pause is on * Some fixes for weird graph window minimize behavior on Windows --- makepanda/makepanda.py | 3 +- panda/src/pstatclient/pStatCollectorDef.h | 3 +- panda/src/pstatclient/pStatFrameData.cxx | 2 +- panda/src/pstatclient/pStatFrameData.h | 4 +- pandatool/src/gtk-stats/CMakeLists.txt | 1 - pandatool/src/gtk-stats/gtkStats.cxx | 81 +- pandatool/src/gtk-stats/gtkStats.h | 4 - pandatool/src/gtk-stats/gtkStatsChartMenu.cxx | 121 ++- pandatool/src/gtk-stats/gtkStatsChartMenu.h | 5 + .../src/gtk-stats/gtkStatsFlameGraph.cxx | 50 + pandatool/src/gtk-stats/gtkStatsFlameGraph.h | 5 + pandatool/src/gtk-stats/gtkStatsGraph.cxx | 41 + pandatool/src/gtk-stats/gtkStatsGraph.h | 7 + pandatool/src/gtk-stats/gtkStatsLabel.cxx | 59 +- pandatool/src/gtk-stats/gtkStatsLabel.h | 1 + .../src/gtk-stats/gtkStatsLabelStack.cxx | 16 +- pandatool/src/gtk-stats/gtkStatsLabelStack.h | 1 + pandatool/src/gtk-stats/gtkStatsMenuId.h | 40 - pandatool/src/gtk-stats/gtkStatsMonitor.cxx | 470 +++++----- pandatool/src/gtk-stats/gtkStatsMonitor.h | 36 +- pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx | 51 +- pandatool/src/gtk-stats/gtkStatsPianoRoll.h | 5 + pandatool/src/gtk-stats/gtkStatsServer.cxx | 678 +++++++++++++- pandatool/src/gtk-stats/gtkStatsServer.h | 48 +- .../src/gtk-stats/gtkStatsStripChart.cxx | 54 +- pandatool/src/gtk-stats/gtkStatsStripChart.h | 5 + pandatool/src/gtk-stats/gtkStatsTimeline.cxx | 50 + pandatool/src/gtk-stats/gtkStatsTimeline.h | 5 + pandatool/src/pstatserver/pStatClientData.cxx | 175 +++- pandatool/src/pstatserver/pStatClientData.h | 16 +- pandatool/src/pstatserver/pStatFlameGraph.cxx | 54 +- pandatool/src/pstatserver/pStatFlameGraph.h | 3 + pandatool/src/pstatserver/pStatGraph.cxx | 95 +- pandatool/src/pstatserver/pStatGraph.h | 8 + pandatool/src/pstatserver/pStatListener.cxx | 2 +- pandatool/src/pstatserver/pStatMonitor.I | 16 + pandatool/src/pstatserver/pStatMonitor.cxx | 472 ++++++++++ pandatool/src/pstatserver/pStatMonitor.h | 29 +- pandatool/src/pstatserver/pStatPianoRoll.cxx | 43 +- pandatool/src/pstatserver/pStatPianoRoll.h | 3 + pandatool/src/pstatserver/pStatReader.cxx | 1 + pandatool/src/pstatserver/pStatServer.cxx | 21 +- pandatool/src/pstatserver/pStatServer.h | 6 +- pandatool/src/pstatserver/pStatStripChart.cxx | 70 +- pandatool/src/pstatserver/pStatStripChart.h | 8 +- pandatool/src/pstatserver/pStatThreadData.cxx | 49 +- pandatool/src/pstatserver/pStatThreadData.h | 18 +- pandatool/src/pstatserver/pStatTimeline.cxx | 49 +- pandatool/src/pstatserver/pStatTimeline.h | 3 + pandatool/src/text-stats/textMonitor.cxx | 8 +- pandatool/src/text-stats/textStats.cxx | 2 +- pandatool/src/text-stats/textStats.h | 2 +- pandatool/src/win-stats/CMakeLists.txt | 2 +- pandatool/src/win-stats/winStats.cxx | 84 +- pandatool/src/win-stats/winStatsChartMenu.cxx | 59 +- .../src/win-stats/winStatsFlameGraph.cxx | 58 +- pandatool/src/win-stats/winStatsFlameGraph.h | 7 + pandatool/src/win-stats/winStatsGraph.cxx | 88 ++ pandatool/src/win-stats/winStatsGraph.h | 8 + pandatool/src/win-stats/winStatsLabel.cxx | 77 +- pandatool/src/win-stats/winStatsLabel.h | 5 +- .../src/win-stats/winStatsLabelStack.cxx | 30 +- pandatool/src/win-stats/winStatsLabelStack.h | 1 + pandatool/src/win-stats/winStatsMenuId.h | 13 + pandatool/src/win-stats/winStatsMonitor.cxx | 646 ++++++------- pandatool/src/win-stats/winStatsMonitor.h | 37 +- pandatool/src/win-stats/winStatsPianoRoll.cxx | 33 +- pandatool/src/win-stats/winStatsPianoRoll.h | 5 + pandatool/src/win-stats/winStatsServer.cxx | 855 +++++++++++++++++- pandatool/src/win-stats/winStatsServer.h | 52 +- .../src/win-stats/winStatsStripChart.cxx | 46 +- pandatool/src/win-stats/winStatsStripChart.h | 5 + pandatool/src/win-stats/winStatsTimeline.cxx | 30 + pandatool/src/win-stats/winStatsTimeline.h | 5 + 74 files changed, 4097 insertions(+), 1048 deletions(-) delete mode 100644 pandatool/src/gtk-stats/gtkStatsMenuId.h diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index c1d921cf0c..ea512dd87f 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -613,6 +613,7 @@ if (COMPILER == "MSVC"): LibName("WINSOCK2", "ws2_32.lib") LibName("WINCOMCTL", "comctl32.lib") LibName("WINCOMDLG", "comdlg32.lib") + LibName("UXTHEME", "uxtheme.lib") LibName("WINUSER", "user32.lib") LibName("WINMM", "winmm.lib") LibName("WINIMM", "imm32.lib") @@ -5910,7 +5911,7 @@ if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK3") TargetAdd('pstats.exe', input='libp3progbase.lib') TargetAdd('pstats.exe', input='libp3pandatoolbase.lib') TargetAdd('pstats.exe', input=COMMON_PANDA_LIBS) - TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK3']) + TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINCOMDLG', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'UXTHEME', 'GTK3']) # # DIRECTORY: pandatool/src/xfileprogs/ diff --git a/panda/src/pstatclient/pStatCollectorDef.h b/panda/src/pstatclient/pStatCollectorDef.h index 443f72a088..87a99df834 100644 --- a/panda/src/pstatclient/pStatCollectorDef.h +++ b/panda/src/pstatclient/pStatCollectorDef.h @@ -33,7 +33,8 @@ public: void set_parent(const PStatCollectorDef &parent); void write_datagram(Datagram &destination) const; - void read_datagram(DatagramIterator &source, PStatClientVersion *version); + void read_datagram(DatagramIterator &source, + PStatClientVersion *version = nullptr); struct ColorDef { float r, g, b; diff --git a/panda/src/pstatclient/pStatFrameData.cxx b/panda/src/pstatclient/pStatFrameData.cxx index 9c58f8d29d..810b7d9cd4 100644 --- a/panda/src/pstatclient/pStatFrameData.cxx +++ b/panda/src/pstatclient/pStatFrameData.cxx @@ -81,5 +81,5 @@ read_datagram(DatagramIterator &source, PStatClientVersion *) { dp._value = source.get_float32(); _level_data.push_back(dp); } - nassertv(source.get_remaining_size() == 0); + //nassertv(source.get_remaining_size() == 0); } diff --git a/panda/src/pstatclient/pStatFrameData.h b/panda/src/pstatclient/pStatFrameData.h index 45d69cfb7d..3f506ea98e 100644 --- a/panda/src/pstatclient/pStatFrameData.h +++ b/panda/src/pstatclient/pStatFrameData.h @@ -57,8 +57,8 @@ public: INLINE int get_level_collector(size_t n) const; INLINE double get_level(size_t n) const; - bool write_datagram(Datagram &destination, PStatClient *client) const; - void read_datagram(DatagramIterator &source, PStatClientVersion *version); + bool write_datagram(Datagram &destination, PStatClient *client = nullptr) const; + void read_datagram(DatagramIterator &source, PStatClientVersion *version = nullptr); private: class DataPoint { diff --git a/pandatool/src/gtk-stats/CMakeLists.txt b/pandatool/src/gtk-stats/CMakeLists.txt index 68a10d392f..26b4dd0372 100644 --- a/pandatool/src/gtk-stats/CMakeLists.txt +++ b/pandatool/src/gtk-stats/CMakeLists.txt @@ -8,7 +8,6 @@ set(GTKSTATS_HEADERS gtkStatsGraph.h gtkStatsLabel.h gtkStatsLabelStack.h - gtkStatsMenuId.h gtkStatsMonitor.h gtkStatsMonitor.I gtkStatsPianoRoll.h gtkStatsServer.h diff --git a/pandatool/src/gtk-stats/gtkStats.cxx b/pandatool/src/gtk-stats/gtkStats.cxx index 959b83a6a4..6e8e8b1dc9 100644 --- a/pandatool/src/gtk-stats/gtkStats.cxx +++ b/pandatool/src/gtk-stats/gtkStats.cxx @@ -16,88 +16,13 @@ #include "gtkStatsServer.h" #include "config_pstatclient.h" -GtkWidget *main_window; -static GtkStatsServer *server = nullptr; - -static gboolean -delete_event(GtkWidget *widget, - GdkEvent *event, gpointer data) { - // Returning FALSE to indicate we should destroy the main window when the - // user selects "close". - return FALSE; -} - -static void -destroy(GtkWidget *widget, gpointer data) { - gtk_main_quit(); -} - -static gboolean -timer(gpointer data) { - static int count = 0; - server->poll(); - - if (++count == 5) { - count = 0; - // Every once in a while, say once a second, we call this function, which - // should force gdk to make all changes visible. We do this in case we - // are getting starved and falling behind, so that the user still gets a - // chance to see *something* happen onscreen, even if it's just - // increasingly old data. - //gdk_window_process_all_updates(); - } - - return TRUE; -} - int main(int argc, char *argv[]) { gtk_init(&argc, &argv); - main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - gtk_window_set_title(GTK_WINDOW(main_window), "PStats"); - - // Connect the delete and destroy events, so the user can exit the - // application by closing the main window. - g_signal_connect(G_OBJECT(main_window), "delete_event", - G_CALLBACK(delete_event), nullptr); - - g_signal_connect(G_OBJECT(main_window), "destroy", - G_CALLBACK(destroy), nullptr); - - std::ostringstream stream; - stream << "Listening on port " << pstats_port; - std::string str = stream.str(); - GtkWidget *label = gtk_label_new(str.c_str()); - gtk_container_add(GTK_CONTAINER(main_window), label); - gtk_widget_show(label); - - // Create the server object. - server = new GtkStatsServer; - if (!server->listen()) { - std::ostringstream stream; - stream - << "Unable to open port " << pstats_port - << ". Try specifying a different\n" - << "port number using pstats-port in your Config file."; - std::string str = stream.str(); - - GtkWidget *dialog = - gtk_message_dialog_new(GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s", str.c_str()); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - exit(1); - } - - gtk_widget_show(main_window); - - // Set up a timer to poll the pstats every so often. - g_timeout_add(200, timer, nullptr); + // Create the server window. + GtkStatsServer *server = new GtkStatsServer; + server->new_session(); // Now get lost in the message loop. gtk_main(); diff --git a/pandatool/src/gtk-stats/gtkStats.h b/pandatool/src/gtk-stats/gtkStats.h index a2dfbfd381..a8cd1cb061 100644 --- a/pandatool/src/gtk-stats/gtkStats.h +++ b/pandatool/src/gtk-stats/gtkStats.h @@ -16,8 +16,4 @@ #include "pStatServer.h" -#include - -extern GtkWidget *main_window; - #endif diff --git a/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx b/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx index e610eb710f..569177abb4 100644 --- a/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx +++ b/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx @@ -56,11 +56,19 @@ add_to_menu_bar(GtkWidget *menu_bar, int position) { thread_name = client_data->get_thread_name(_thread_index); } - GtkWidget *menu_item = gtk_menu_item_new_with_label(thread_name.c_str()); - gtk_widget_show(menu_item); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), _menu); + _menu_item = gtk_menu_item_new_with_label(thread_name.c_str()); + gtk_widget_show(_menu_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(_menu_item), _menu); - gtk_menu_shell_insert(GTK_MENU_SHELL(menu_bar), menu_item, position); + gtk_menu_shell_insert(GTK_MENU_SHELL(menu_bar), _menu_item, position); +} + +/** + * Removes the menu from the menu bar. + */ +void GtkStatsChartMenu:: +remove_from_menu_bar(GtkWidget *menu_bar) { + gtk_container_remove(GTK_CONTAINER(menu_bar), _menu_item); } /** @@ -90,16 +98,32 @@ do_update() { if (_thread_index == 0) { // Timeline goes first. - GtkStatsMonitor::MenuDef smd(_thread_index, -1, GtkStatsMonitor::CT_timeline, false); - const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); + { + GtkStatsMonitor::MenuDef smd(_thread_index, -1, GtkStatsMonitor::CT_timeline, false); + const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); - GtkWidget *menu_item = gtk_menu_item_new_with_label("Timeline"); - gtk_widget_show(menu_item); - gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + GtkWidget *menu_item = gtk_menu_item_new_with_label("Timeline"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); - g_signal_connect(G_OBJECT(menu_item), "activate", - G_CALLBACK(GtkStatsMonitor::menu_activate), - (void *)menu_def); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + // And the piano roll (even though it's not very useful nowadays) + { + GtkStatsMonitor::MenuDef smd(_thread_index, -1, GtkStatsMonitor::CT_piano_roll, false); + const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Piano Roll"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } GtkWidget *sep = gtk_separator_menu_item_new(); gtk_widget_show(sep); @@ -134,22 +158,41 @@ do_update() { } } - // Also menu item for piano roll (following a separator). - GtkWidget *sep = gtk_separator_menu_item_new(); - gtk_widget_show(sep); - gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep); + // For the main thread menu, also some options relating to all graph windows. + if (_thread_index == 0) { + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_widget_show(sep); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep); - { - GtkStatsMonitor::MenuDef smd(_thread_index, -1, GtkStatsMonitor::CT_piano_roll, false); - const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Close All Graphs"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); - GtkWidget *menu_item = gtk_menu_item_new_with_label("Piano Roll"); - gtk_widget_show(menu_item); - gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_close_all), + (void *)_monitor); + } - g_signal_connect(G_OBJECT(menu_item), "activate", - G_CALLBACK(GtkStatsMonitor::menu_activate), - (void *)menu_def); + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reopen Default Graphs"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_reopen_default), + (void *)_monitor); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Save Current Layout as Default"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_save_default), + (void *)_monitor); + } } } @@ -250,3 +293,31 @@ remove_menu_child(GtkWidget *widget, gpointer data) { GtkWidget *menu = (GtkWidget *)data; gtk_container_remove(GTK_CONTAINER(menu), widget); } + +/** + * Callback for Close All Graphs. + */ +void GtkStatsChartMenu:: +activate_close_all(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->remove_all_graphs(); +} + +/** + * Callback for Reopen Default Graphs. + */ +void GtkStatsChartMenu:: +activate_reopen_default(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->remove_all_graphs(); + monitor->open_default_graphs(); +} + +/** + * Callback for Save Current Layout as Default. + */ +void GtkStatsChartMenu:: +activate_save_default(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->save_default_graphs(); +} diff --git a/pandatool/src/gtk-stats/gtkStatsChartMenu.h b/pandatool/src/gtk-stats/gtkStatsChartMenu.h index 41e21aa3ee..b9570c74c5 100644 --- a/pandatool/src/gtk-stats/gtkStatsChartMenu.h +++ b/pandatool/src/gtk-stats/gtkStatsChartMenu.h @@ -32,6 +32,7 @@ public: GtkWidget *get_menu_widget(); void add_to_menu_bar(GtkWidget *menu_bar, int position); + void remove_from_menu_bar(GtkWidget *menu_bar); void check_update(); void do_update(); @@ -41,12 +42,16 @@ private: bool show_level); static void remove_menu_child(GtkWidget *widget, gpointer data); + static void activate_close_all(GtkWidget *widget, gpointer data); + static void activate_reopen_default(GtkWidget *widget, gpointer data); + static void activate_save_default(GtkWidget *widget, gpointer data); GtkStatsMonitor *_monitor; int _thread_index; int _last_level_index; GtkWidget *_menu; + GtkWidget *_menu_item = nullptr; }; #endif diff --git a/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx b/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx index d5ecdb0d29..2e9476262e 100644 --- a/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx +++ b/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx @@ -306,6 +306,25 @@ animate(double time, double dt) { return PStatFlameGraph::animate(time, dt); } +/** + * Returns the current window dimensions. + */ +bool GtkStatsFlameGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsFlameGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * This is called during the servicing of the draw event; it gives a derived * class opportunity to do some further painting into the graph window. @@ -417,6 +436,37 @@ handle_button_press(int graph_x, int graph_y, bool double_click, int button) { (void *)menu_def); } + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, + GtkStatsMonitor::CT_choose_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, + GtkStatsMonitor::CT_reset_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + gtk_widget_show_all(menu); gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); return TRUE; diff --git a/pandatool/src/gtk-stats/gtkStatsFlameGraph.h b/pandatool/src/gtk-stats/gtkStatsFlameGraph.h index ad550d16ac..9227e1ab45 100644 --- a/pandatool/src/gtk-stats/gtkStatsFlameGraph.h +++ b/pandatool/src/gtk-stats/gtkStatsFlameGraph.h @@ -52,6 +52,11 @@ protected: virtual bool animate(double time, double dt); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + virtual void additional_graph_window_paint(cairo_t *cr); virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; virtual DragMode consider_drag_start(int graph_x, int graph_y); diff --git a/pandatool/src/gtk-stats/gtkStatsGraph.cxx b/pandatool/src/gtk-stats/gtkStatsGraph.cxx index 269b8ec47f..f16fdd2287 100644 --- a/pandatool/src/gtk-stats/gtkStatsGraph.cxx +++ b/pandatool/src/gtk-stats/gtkStatsGraph.cxx @@ -309,6 +309,36 @@ animate(double time, double dt) { return false; } +/** + * Returns the current window dimensions. + */ +void GtkStatsGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkWindow *window = GTK_WINDOW(_window); + gtk_window_get_position(window, &x, &y); + gtk_window_get_size(window, &width, &height); + maximized = gtk_window_is_maximized(window); + minimized = false; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkWindow *window = GTK_WINDOW(_window); + gtk_window_move(window, x, y); + gtk_window_resize(window, width, height); + if (maximized) { + gtk_window_maximize(window); + } + if (minimized) { + gtk_window_iconify(window); + } +} + /** * Returns a pattern suitable for drawing in the indicated collector's color. */ @@ -358,6 +388,17 @@ get_collector_text_color(int collector_index, bool highlight) { return highlight ? hcolor : color; } +/** + * Called when the given collector has changed colors. + */ +void GtkStatsGraph:: +reset_collector_color(int collector_index) { + _brushes.erase(collector_index); + _text_colors.erase(collector_index); + force_redraw(); + _label_stack.update_label_color(collector_index); +} + /** * This is called during the servicing of the draw event; it gives a derived * class opportunity to do some further painting into the graph window. diff --git a/pandatool/src/gtk-stats/gtkStatsGraph.h b/pandatool/src/gtk-stats/gtkStatsGraph.h index 2ff48cecca..ca0a6b9954 100644 --- a/pandatool/src/gtk-stats/gtkStatsGraph.h +++ b/pandatool/src/gtk-stats/gtkStatsGraph.h @@ -60,12 +60,19 @@ public: virtual void on_leave_label(int collector_index); virtual std::string get_label_tooltip(int collector_index) const; + void reset_collector_color(int collector_index); + protected: void close(); void start_animation(); virtual bool animate(double time, double dt); + void get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + cairo_pattern_t *get_collector_pattern(int collector_index, bool highlight = false); LRGBColor get_collector_text_color(int collector_index, bool highlight = false); diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.cxx b/pandatool/src/gtk-stats/gtkStatsLabel.cxx index 7dae836b99..43a077f354 100644 --- a/pandatool/src/gtk-stats/gtkStatsLabel.cxx +++ b/pandatool/src/gtk-stats/gtkStatsLabel.cxx @@ -51,34 +51,10 @@ GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph, gtk_widget_set_has_tooltip(_widget, TRUE); - // Set the fg and bg colors on the label. - LRGBColor rgb = _monitor->get_collector_color(_collector_index); - _bg_color = LRGBColor( - encode_sRGB_float((float)rgb[0]), - encode_sRGB_float((float)rgb[1]), - encode_sRGB_float((float)rgb[2])); - - _highlight_bg_color = LRGBColor( - encode_sRGB_float((float)rgb[0] * 0.75f), - encode_sRGB_float((float)rgb[1] * 0.75f), - encode_sRGB_float((float)rgb[2] * 0.75f)); - - // Should our foreground be black or white? - PN_stdfloat bright = _bg_color.dot(LRGBColor(0.2126, 0.7152, 0.0722)); - if (bright >= 0.5) { - _fg_color = LRGBColor(0); - } else { - _fg_color = LRGBColor(1); - } - if (bright * 0.75 >= 0.5) { - _highlight_fg_color = LRGBColor(0); - } else { - _highlight_fg_color = LRGBColor(1); - } - _highlight = false; _mouse_within = false; + update_color(); update_text(use_fullname); gtk_widget_show_all(_widget); } @@ -145,6 +121,39 @@ get_highlight() const { return _highlight; } +/** + * Updates the colors. + */ +void GtkStatsLabel:: +update_color() { + // Set the fg and bg colors on the label. + LRGBColor rgb = _monitor->get_collector_color(_collector_index); + _bg_color = LRGBColor( + encode_sRGB_float((float)rgb[0]), + encode_sRGB_float((float)rgb[1]), + encode_sRGB_float((float)rgb[2])); + + _highlight_bg_color = LRGBColor( + encode_sRGB_float((float)rgb[0] * 0.75f), + encode_sRGB_float((float)rgb[1] * 0.75f), + encode_sRGB_float((float)rgb[2] * 0.75f)); + + // Should our foreground be black or white? + PN_stdfloat bright = _bg_color.dot(LRGBColor(0.2126, 0.7152, 0.0722)); + if (bright >= 0.5) { + _fg_color = LRGBColor(0); + } else { + _fg_color = LRGBColor(1); + } + if (bright * 0.75 >= 0.5) { + _highlight_fg_color = LRGBColor(0); + } else { + _highlight_fg_color = LRGBColor(1); + } + + gtk_widget_queue_draw(_widget); +} + /** * Set to true if the full name of the collector should be shown. */ diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.h b/pandatool/src/gtk-stats/gtkStatsLabel.h index 185b29a605..6a4768f627 100644 --- a/pandatool/src/gtk-stats/gtkStatsLabel.h +++ b/pandatool/src/gtk-stats/gtkStatsLabel.h @@ -43,6 +43,7 @@ public: void set_highlight(bool highlight); bool get_highlight() const; + void update_color(); void update_text(bool use_fullname); private: diff --git a/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx b/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx index 5a08b2fe38..5874050f06 100644 --- a/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx +++ b/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx @@ -128,10 +128,20 @@ void GtkStatsLabelStack:: highlight_label(int collector_index) { if (_highlight_label != collector_index) { _highlight_label = collector_index; - Labels::iterator li; - for (li = _labels.begin(); li != _labels.end(); ++li) { - GtkStatsLabel *label = (*li); + for (GtkStatsLabel *label : _labels) { label->set_highlight(label->get_collector_index() == _highlight_label); } } } + +/** + * Refreshes the color of the label with the given index. + */ +void GtkStatsLabelStack:: +update_label_color(int collector_index) { + for (GtkStatsLabel *label : _labels) { + if (label->get_collector_index() == collector_index) { + label->update_color(); + } + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsLabelStack.h b/pandatool/src/gtk-stats/gtkStatsLabelStack.h index 32eed06221..422472395e 100644 --- a/pandatool/src/gtk-stats/gtkStatsLabelStack.h +++ b/pandatool/src/gtk-stats/gtkStatsLabelStack.h @@ -43,6 +43,7 @@ public: int get_num_labels() const; void highlight_label(int collector_index); + void update_label_color(int collector_index); private: GtkWidget *_widget; diff --git a/pandatool/src/gtk-stats/gtkStatsMenuId.h b/pandatool/src/gtk-stats/gtkStatsMenuId.h deleted file mode 100644 index 71e2fc4223..0000000000 --- a/pandatool/src/gtk-stats/gtkStatsMenuId.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * PANDA 3D SOFTWARE - * Copyright (c) Carnegie Mellon University. All rights reserved. - * - * All use of this software is subject to the terms of the revised BSD - * license. You should have received a copy of this license along - * with this source code in a file named "LICENSE." - * - * @file gtkStatsMenuId.h - * @author drose - * @date 2006-01-16 - */ - -#ifndef GTKSTATSMENUID_H -#define GTKSTATSMENUID_H - -#include "pandatoolbase.h" - -/** - * The enumerated values here are used for menu ID's for the various pulldown - * menus in the application. - */ -enum GtkStatsMenuId { - MI_none, - MI_time_ms, - MI_time_hz, - MI_frame_rate_label, - MI_speed_1, - MI_speed_2, - MI_speed_3, - MI_speed_6, - MI_speed_12, - MI_pause, - - // This one is last and represents the beginning of the range for the - // various "new chart" menu options. - MI_new_chart -}; - -#endif diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.cxx b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx index 9a04cc523a..6563d69e1f 100644 --- a/pandatool/src/gtk-stats/gtkStatsMonitor.cxx +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx @@ -19,23 +19,28 @@ #include "gtkStatsPianoRoll.h" #include "gtkStatsFlameGraph.h" #include "gtkStatsTimeline.h" -#include "gtkStatsMenuId.h" #include "pStatGraph.h" #include "pStatCollectorDef.h" +#include "convert_srgb.h" + /** * */ GtkStatsMonitor:: GtkStatsMonitor(GtkStatsServer *server) : PStatMonitor(server) { - _window = nullptr; + _window = server->get_window(); + _menu_bar = server->get_menu_bar(); + _status_bar = server->get_status_bar(); // These will be filled in later when the menu is created. - _time_units = 0; _scroll_speed = 0.0; _pause = false; + _next_chart_index = 2; _resolution = gdk_screen_get_resolution(gdk_screen_get_default()); + setup_speed_menu(); + setup_frame_rate_label(); } /** @@ -43,7 +48,41 @@ GtkStatsMonitor(GtkStatsServer *server) : PStatMonitor(server) { */ GtkStatsMonitor:: ~GtkStatsMonitor() { - shutdown(); + close(); +} + +/** + * Closes all the graphs associated with this monitor. + */ +void GtkStatsMonitor:: +close() { + PStatMonitor::close(); + + remove_all_graphs(); + + for (GtkWidget *label : _status_bar_labels) { + gtk_container_remove(GTK_CONTAINER(_status_bar), label); + } + _status_bar_collectors.clear(); + _status_bar_labels.clear(); + + if (_speed_menu_item != nullptr) { + gtk_container_remove(GTK_CONTAINER(_menu_bar), _speed_menu_item); + _speed_menu_item = nullptr; + } + + for (GtkStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->remove_from_menu_bar(_menu_bar); + delete chart_menu; + } + _chart_menus.clear(); + + if (_frame_rate_menu_item != nullptr) { + gtk_container_remove(GTK_CONTAINER(_menu_bar), _frame_rate_menu_item); + _frame_rate_menu_item = nullptr; + } + + _next_chart_index = 2; } /** @@ -72,8 +111,6 @@ initialized() { */ void GtkStatsMonitor:: got_hello() { - create_window(); - open_strip_chart(0, 0, false); } /** @@ -102,7 +139,7 @@ got_bad_version(int client_major, int client_minor, std::string message = str.str(); GtkWidget *dialog = - gtk_message_dialog_new(GTK_WINDOW(main_window), + gtk_message_dialog_new(GTK_WINDOW(_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -120,16 +157,13 @@ got_bad_version(int client_major, int client_minor, */ void GtkStatsMonitor:: new_collector(int collector_index) { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - GtkStatsGraph *graph = (*gi); + for (GtkStatsGraph *graph : _graphs) { graph->new_collector(collector_index); } // We might need to update our menus. - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - (*mi)->do_update(); + for (GtkStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->do_update(); } } @@ -163,6 +197,14 @@ new_data(int thread_index, int frame_number) { if (thread_index == 0) { update_status_bar(); } + + if (!_have_data) { + open_default_graphs(); + _have_data = true; + + // Flash the window. + gtk_window_set_urgency_hint(GTK_WINDOW(_window), TRUE); + } } /** @@ -173,8 +215,6 @@ new_data(int thread_index, int frame_number) { void GtkStatsMonitor:: lost_connection() { nout << "Lost connection to " << get_client_hostname() << "\n"; - - shutdown(); } /** @@ -184,9 +224,8 @@ lost_connection() { void GtkStatsMonitor:: idle() { // Check if any of our chart menus need updating. - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - (*mi)->check_update(); + for (GtkStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->check_update(); } // Update the frame rate label from the main thread (thread 0). @@ -218,9 +257,7 @@ has_idle() { */ void GtkStatsMonitor:: user_guide_bars_changed() { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - GtkStatsGraph *graph = (*gi); + for (GtkStatsGraph *graph : _graphs) { graph->user_guide_bars_changed(); } } @@ -242,56 +279,90 @@ get_resolution() const { } /** - * Opens a new strip chart showing the indicated data. + * Opens a new timeline. */ -void GtkStatsMonitor:: -open_strip_chart(int thread_index, int collector_index, bool show_level) { - GtkStatsStripChart *graph = - new GtkStatsStripChart(this, thread_index, collector_index, show_level); +PStatGraph *GtkStatsMonitor:: +open_timeline() { + GtkStatsTimeline *graph = new GtkStatsTimeline(this); add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); -} - -/** - * Opens a new piano roll showing the indicated data. - */ -void GtkStatsMonitor:: -open_piano_roll(int thread_index) { - GtkStatsPianoRoll *graph = new GtkStatsPianoRoll(this, thread_index); - add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); + return graph; } /** * Opens a new flame graph showing the indicated data. */ -void GtkStatsMonitor:: +PStatGraph *GtkStatsMonitor:: open_flame_graph(int thread_index, int collector_index) { GtkStatsFlameGraph *graph = new GtkStatsFlameGraph(this, thread_index, collector_index); add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); + return graph; } /** - * Opens a new timeline. + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *GtkStatsMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + GtkStatsStripChart *graph = + new GtkStatsStripChart(this, thread_index, collector_index, show_level); + add_graph(graph); + return graph; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *GtkStatsMonitor:: +open_piano_roll(int thread_index) { + GtkStatsPianoRoll *graph = new GtkStatsPianoRoll(this, thread_index); + add_graph(graph); + return graph; +} + +/** + * Opens a dialog to change the given collector color. */ void GtkStatsMonitor:: -open_timeline() { - GtkStatsTimeline *graph = new GtkStatsTimeline(this); - add_graph(graph); +choose_collector_color(int collector_index) { + const LRGBColor ¤t = get_collector_color(collector_index); - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); + GtkWidget *chooser = gtk_color_chooser_dialog_new(nullptr, GTK_WINDOW(_window)); + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(chooser), FALSE); + + GdkRGBA rgba; + rgba.red = encode_sRGB_float((float)current[0]); + rgba.green = encode_sRGB_float((float)current[1]); + rgba.blue = encode_sRGB_float((float)current[2]); + rgba.alpha = 1.0; + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(chooser), &rgba); + + if (gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_OK) { + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(chooser), &rgba); + LRGBColor result( + decode_sRGB_float((float)rgba.red), + decode_sRGB_float((float)rgba.green), + decode_sRGB_float((float)rgba.blue)); + + set_collector_color(collector_index, result); + + for (GtkStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } + + gtk_widget_destroy(chooser); +} + +/** + * Resets the color of the given collector to the default. + */ +void GtkStatsMonitor:: +reset_collector_color(int collector_index) { + clear_collector_color(collector_index); + + for (GtkStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } } /** @@ -317,13 +388,8 @@ add_menu(const MenuDef &menu_def) { */ void GtkStatsMonitor:: set_time_units(int unit_mask) { - _time_units = unit_mask; - - // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - GtkStatsGraph *graph = (*gi); - graph->set_time_units(_time_units); + for (GtkStatsGraph *graph : _graphs) { + graph->set_time_units(unit_mask); } } @@ -336,9 +402,7 @@ set_scroll_speed(double scroll_speed) { _scroll_speed = scroll_speed; // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - GtkStatsGraph *graph = (*gi); + for (GtkStatsGraph *graph : _graphs) { graph->set_scroll_speed(_scroll_speed); } } @@ -351,9 +415,7 @@ set_pause(bool pause) { _pause = pause; // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - GtkStatsGraph *graph = (*gi); + for (GtkStatsGraph *graph : _graphs) { graph->set_pause(_pause); } } @@ -364,6 +426,10 @@ set_pause(bool pause) { void GtkStatsMonitor:: add_graph(GtkStatsGraph *graph) { _graphs.insert(graph); + + graph->set_time_units(((GtkStatsServer *)_server)->get_time_units()); + graph->set_scroll_speed(_scroll_speed); + graph->set_pause(_pause); } /** @@ -379,145 +445,14 @@ remove_graph(GtkStatsGraph *graph) { } /** - * Creates the window for this monitor. + * Deletes all open graphs. */ void GtkStatsMonitor:: -create_window() { - if (_window != nullptr) { - return; - } - - _window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - g_signal_connect(G_OBJECT(_window), "delete_event", - G_CALLBACK(window_delete_event), this); - g_signal_connect(G_OBJECT(_window), "destroy", - G_CALLBACK(window_destroy), this); - - _window_title = get_client_progname() + " on " + get_client_hostname(); - gtk_window_set_title(GTK_WINDOW(_window), _window_title.c_str()); - - gtk_window_set_default_size(GTK_WINDOW(_window), 500, 360); - - // Set up the menu. - GtkAccelGroup *accel_group = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(_window), accel_group); - _menu_bar = gtk_menu_bar_new(); - _next_chart_index = 2; - - setup_options_menu(); - setup_speed_menu(); - setup_frame_rate_label(); - - for (GtkStatsChartMenu *chart_menu : _chart_menus) { - chart_menu->add_to_menu_bar(_menu_bar, _next_chart_index); - ++_next_chart_index; - } - - // Pack the menu into the window. - GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); - gtk_container_add(GTK_CONTAINER(_window), main_vbox); - gtk_box_pack_start(GTK_BOX(main_vbox), _menu_bar, FALSE, TRUE, 0); - - // Create the status bar. - _status_bar = gtk_flow_box_new(); - gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(_status_bar), FALSE); - gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(_status_bar), GTK_SELECTION_NONE); - g_signal_connect(G_OBJECT(_status_bar), "button_press_event", - G_CALLBACK(status_bar_button_event), this); - gtk_box_pack_end(GTK_BOX(main_vbox), _status_bar, FALSE, FALSE, 0); - update_status_bar(); - - GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - gtk_box_pack_end(GTK_BOX(main_vbox), sep, FALSE, FALSE, 0); - - gtk_widget_show_all(_window); - gtk_widget_show(_window); - gtk_widget_realize(_window); -} - -/** - * Closes all the graphs associated with this monitor. - */ -void GtkStatsMonitor:: -shutdown() { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - delete (*gi); +remove_all_graphs() { + for (GtkStatsGraph *graph : _graphs) { + delete graph; } _graphs.clear(); - - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - delete (*mi); - } - _chart_menus.clear(); - - if (_window != nullptr) { - gtk_widget_destroy(_window); - _window = nullptr; - } - -#ifdef DEVELOP_GTKSTATS - // For GtkStats developers, exit when the first monitor closes. - gtk_main_quit(); -#endif -} - -/** - * Callback when the window is closed by the user. - */ -gboolean GtkStatsMonitor:: -window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { - // Returning FALSE to indicate we should destroy the window when the user - // selects "close". - return FALSE; -} - -/** - * Callback when the window is destroyed by the system (or by delete_event). - */ -void GtkStatsMonitor:: -window_destroy(GtkWidget *widget, gpointer data) { - GtkStatsMonitor *self = (GtkStatsMonitor *)data; - self->close(); -} - - -/** - * Creates the "Options" pulldown menu. - */ -void GtkStatsMonitor:: -setup_options_menu() { - _options_menu = gtk_menu_new(); - - GtkWidget *item = gtk_menu_item_new_with_label("Options"); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _options_menu); - gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); - - GtkWidget *units_menu = gtk_menu_new(); - item = gtk_menu_item_new_with_label("Units"); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), units_menu); - gtk_menu_shell_append(GTK_MENU_SHELL(_options_menu), item); - - item = gtk_radio_menu_item_new_with_label(nullptr, "ms"); - gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); - g_signal_connect(G_OBJECT(item), "activate", - G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { - GtkStatsMonitor *self = (GtkStatsMonitor *)data; - self->set_time_units(PStatGraph::GBU_ms); - }), this); - - item = gtk_radio_menu_item_new_with_label( - gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)), "Hz"); - gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); - g_signal_connect(G_OBJECT(item), "activate", - G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { - GtkStatsMonitor *self = (GtkStatsMonitor *)data; - self->set_time_units(PStatGraph::GBU_hz); - }), this); - - set_time_units(PStatGraph::GBU_ms); } /** @@ -525,15 +460,16 @@ setup_options_menu() { */ void GtkStatsMonitor:: setup_speed_menu() { - _speed_menu = gtk_menu_new(); + GtkWidget *menu = gtk_menu_new(); - GtkWidget *item = gtk_menu_item_new_with_label("Speed"); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _speed_menu); - gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); + _speed_menu_item = gtk_menu_item_new_with_label("Speed"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(_speed_menu_item), menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), _speed_menu_item); GSList *group = nullptr; + GtkWidget *item; item = gtk_radio_menu_item_new_with_label(group, "1"); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { @@ -544,7 +480,7 @@ setup_speed_menu() { group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); item = gtk_radio_menu_item_new_with_label(group, "2"); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { @@ -556,7 +492,7 @@ setup_speed_menu() { item = gtk_radio_menu_item_new_with_label(group, "3"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { @@ -567,7 +503,7 @@ setup_speed_menu() { group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); item = gtk_radio_menu_item_new_with_label(group, "6"); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { @@ -578,7 +514,7 @@ setup_speed_menu() { group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); item = gtk_radio_menu_item_new_with_label(group, "12"); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { @@ -589,10 +525,10 @@ setup_speed_menu() { group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); item = gtk_separator_menu_item_new(); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); item = gtk_check_menu_item_new_with_label("pause"); - gtk_menu_shell_append(GTK_MENU_SHELL(_speed_menu), item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { GtkStatsMonitor *self = (GtkStatsMonitor *)data; @@ -601,6 +537,9 @@ setup_speed_menu() { set_scroll_speed(3); set_pause(false); + + gtk_widget_show_all(_speed_menu_item); + ++_next_chart_index; } /** @@ -715,7 +654,7 @@ status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) GList *children = gtk_container_get_children(GTK_CONTAINER(monitor->_status_bar)); int index = g_list_index(children, child); g_list_free(children); - if (index < 0 || index >= monitor->_status_bar_labels.size()) { + if (index < 0 || (size_t)index >= monitor->_status_bar_labels.size()) { return FALSE; } @@ -790,31 +729,114 @@ status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) */ void GtkStatsMonitor:: menu_activate(GtkWidget *widget, gpointer data) { - const MenuDef *menu_def = (const MenuDef *)data; - GtkStatsMonitor *monitor = menu_def->_monitor; + const MenuDef &menu_def = *(const MenuDef *)data; + GtkStatsMonitor *monitor = menu_def._monitor; if (monitor == nullptr) { return; } - switch (menu_def->_chart_type) { + switch (menu_def._chart_type) { case CT_timeline: monitor->open_timeline(); break; case CT_strip_chart: - monitor->open_strip_chart(menu_def->_thread_index, - menu_def->_collector_index, - menu_def->_show_level); + monitor->open_strip_chart(menu_def._thread_index, + menu_def._collector_index, + menu_def._show_level); break; case CT_flame_graph: - monitor->open_flame_graph(menu_def->_thread_index, - menu_def->_collector_index); + monitor->open_flame_graph(menu_def._thread_index, + menu_def._collector_index); break; case CT_piano_roll: - monitor->open_piano_roll(menu_def->_thread_index); + monitor->open_piano_roll(menu_def._thread_index); + break; + + case CT_choose_color: + monitor->choose_collector_color(menu_def._collector_index); + break; + + case CT_reset_color: + monitor->reset_collector_color(menu_def._collector_index); break; } } + +/** + * Called when a status bar item is double-clicked. + */ +void GtkStatsMonitor:: +handle_status_bar_click(int item) { + if (item == 0) { + open_strip_chart(0, 0, false); + } + else if (item >= 1 && (size_t)item < _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item]; + open_strip_chart(0, collector, true); + + // Also open a strip chart for other threads with data for this + // collector. + const PStatClientData *client_data = get_client_data(); + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + if (view.get_net_value() > 0.0) { + open_strip_chart(thread_index, collector, true); + } + } + } +} + +/** + * Called when a status bar item is right-clicked. + */ +void GtkStatsMonitor:: +handle_status_bar_popup(int item) { + if (item >= 0 && (size_t)item < _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item]; + + PStatView &level_view = get_level_view(collector, 0); + const PStatViewLevel *view_level = level_view.get_top_level(); + int num_children = view_level->get_num_children(); + if (num_children == 0) { + return; + } + + GtkWidget *menu = gtk_menu_new(); + + // Reverse the order since the menus are listed from the top down; we want + // to be visually consistent with the graphs, which list these labels from + // the bottom up. + const PStatClientData *client_data = get_client_data(); + for (int c = num_children - 1; c >= 0; c--) { + const PStatViewLevel *child_level = view_level->get_child(c); + + int child_collector = child_level->get_collector(); + const MenuDef *menu_def = add_menu({0, child_collector, CT_strip_chart, true}); + + double value = child_level->get_net_value(); + + const PStatCollectorDef &def = client_data->get_collector_def(child_collector); + std::string text = def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + + GtkWidget *menu_item = gtk_menu_item_new_with_label(text.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + + GtkWidget *label = _status_bar_labels[item]; + gtk_menu_popup_at_widget(GTK_MENU(menu), label, + GDK_GRAVITY_NORTH_WEST, + GDK_GRAVITY_SOUTH_WEST, nullptr); + + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.h b/pandatool/src/gtk-stats/gtkStatsMonitor.h index 99f1baecee..e752e8c346 100644 --- a/pandatool/src/gtk-stats/gtkStatsMonitor.h +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.h @@ -39,6 +39,9 @@ public: CT_strip_chart, CT_flame_graph, CT_piano_roll, + + CT_choose_color, + CT_reset_color, }; class MenuDef { @@ -57,6 +60,8 @@ public: GtkStatsMonitor(GtkStatsServer *server); virtual ~GtkStatsMonitor(); + void close(); + virtual std::string get_monitor_name(); virtual void initialized(); @@ -75,10 +80,13 @@ public: GtkWidget *get_window() const; double get_resolution() const; - void open_strip_chart(int thread_index, int collector_index, bool show_level); - void open_piano_roll(int thread_index); - void open_flame_graph(int thread_index, int collector_index = -1); - void open_timeline(); + PStatGraph *open_timeline(); + PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + PStatGraph *open_flame_graph(int thread_index, int collector_index = -1); + PStatGraph *open_piano_roll(int thread_index); + + void choose_collector_color(int collector_index); + void reset_collector_color(int collector_index); const MenuDef *add_menu(const MenuDef &menu_def); @@ -86,26 +94,22 @@ public: void set_scroll_speed(double scroll_speed); void set_pause(bool pause); -private: void add_graph(GtkStatsGraph *graph); void remove_graph(GtkStatsGraph *graph); + void remove_all_graphs(); - void create_window(); - void shutdown(); - static gboolean window_delete_event(GtkWidget *widget, GdkEvent *event, - gpointer data); - static void window_destroy(GtkWidget *widget, gpointer data); - void setup_options_menu(); +private: void setup_speed_menu(); void setup_frame_rate_label(); void update_status_bar(); - bool show_popup_menu(int collector); static gboolean status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data); public: static void menu_activate(GtkWidget *widget, gpointer data); + void handle_status_bar_click(int item); + void handle_status_bar_popup(int item); private: typedef pset Graphs; @@ -119,21 +123,21 @@ private: GtkWidget *_window; GtkWidget *_menu_bar; - GtkWidget *_options_menu; - GtkWidget *_speed_menu; + GtkWidget *_speed_menu_item = nullptr; int _next_chart_index; - GtkWidget *_frame_rate_menu_item; + GtkWidget *_frame_rate_menu_item = nullptr; GtkWidget *_frame_rate_label; GtkWidget *_status_bar; pvector _status_bar_collectors; pvector _status_bar_labels; std::string _window_title; - int _time_units; double _scroll_speed; bool _pause; + bool _have_data = false; double _resolution; friend class GtkStatsGraph; + friend class GtkStatsServer; }; #include "gtkStatsMonitor.I" diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx index 4c86269f02..fbab6cb1ef 100644 --- a/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx @@ -64,7 +64,8 @@ GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index) : // window's initial size. gtk_widget_set_size_request(_window, 0, 0); - clear_region(); + force_redraw(); + idle(); } /** @@ -170,6 +171,35 @@ on_popup_label(int collector_index) { (void *)menu_def); } + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, GtkStatsMonitor::CT_choose_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, GtkStatsMonitor::CT_reset_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + gtk_widget_show_all(menu); gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); } @@ -263,6 +293,25 @@ idle() { } } +/** + * Returns the current window dimensions. + */ +bool GtkStatsPianoRoll:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsPianoRoll:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * This is called during the servicing of the draw event; it gives a derived * class opportunity to do some further painting into the graph window. diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.h b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h index 18a30ee633..39261aabfe 100644 --- a/pandatool/src/gtk-stats/gtkStatsPianoRoll.h +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h @@ -51,6 +51,11 @@ protected: virtual void end_draw(); virtual void idle(); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + virtual void additional_graph_window_paint(cairo_t *cr); virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; virtual DragMode consider_drag_start(int graph_x, int graph_y); diff --git a/pandatool/src/gtk-stats/gtkStatsServer.cxx b/pandatool/src/gtk-stats/gtkStatsServer.cxx index 3b78af2dde..53770f4de9 100644 --- a/pandatool/src/gtk-stats/gtkStatsServer.cxx +++ b/pandatool/src/gtk-stats/gtkStatsServer.cxx @@ -13,11 +13,685 @@ #include "gtkStatsServer.h" #include "gtkStatsMonitor.h" +#include "pandaVersion.h" +#include "pStatGraph.h" +#include "config_pstatclient.h" + +/** + * + */ +GtkStatsServer:: +GtkStatsServer() { +#ifdef __APPLE__ + _last_session = Filename::expand_from( + "$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats"); +#else + _last_session = Filename::expand_from("$XDG_CACHE_HOME/panda3d/last-session.pstats"); +#endif + _last_session.set_binary(); + + _window = nullptr; + _menu_bar = nullptr; + _options_menu = nullptr; + + _time_units = 0; + + create_window(); +} /** * */ PStatMonitor *GtkStatsServer:: -make_monitor() { - return new GtkStatsMonitor(this); +make_monitor(const NetAddress &address) { + // Enable the "New Session", "Save Session" and "Close Session" menu items. + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + std::ostringstream strm; + strm << "PStats Server (connected to " << address << ")"; + std::string title = strm.str(); + gtk_window_set_title(GTK_WINDOW(_window), title.c_str()); + + if (_status_bar_label != nullptr) { + gtk_container_remove(GTK_CONTAINER(_status_bar), _status_bar_label); + _status_bar_label = nullptr; + } + + _monitor = new GtkStatsMonitor(this); + return _monitor; +} + +/** + * Called when connection has been lost. + */ +void GtkStatsServer:: +lost_connection(PStatMonitor *monitor) { + // Store a backup now, in case PStats crashes or something. + _last_session.make_dir(); + if (monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + } else { + nout << "Failed to write to " << _last_session << "\n"; + } + + stop_listening(); + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server (disconnected)"); +} + +/** + * Starts a new session. + */ +bool GtkStatsServer:: +new_session() { + if (!close_session()) { + return false; + } + + if (listen()) { + { + std::ostringstream strm; + strm << "PStats Server (listening on port " << pstats_port << ")"; + std::string title = strm.str(); + gtk_window_set_title(GTK_WINDOW(_window), title.c_str()); + } + { + std::ostringstream strm; + strm << "Waiting for client to connect on port " << pstats_port << "..."; + std::string title = strm.str(); + _status_bar_label = gtk_label_new(title.c_str()); + gtk_container_add(GTK_CONTAINER(_status_bar), _status_bar_label); + gtk_widget_show(_status_bar_label); + } + + gtk_widget_set_sensitive(_new_session_menu_item, FALSE); + gtk_widget_set_sensitive(_save_session_menu_item, FALSE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, FALSE); + + return true; + } + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Unable to open port %d. Try specifying a different port number " + "using pstats-port in your Config file.", pstats_port.get_value()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return false; +} + +/** + * Offers to open an existing session. + */ +bool GtkStatsServer:: +open_session() { + if (!close_session()) { + return false; + } + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Open Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Open", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "PStats Session Files"); + gtk_file_filter_add_pattern(filter, "*.pstats"); + gtk_file_chooser_add_filter(chooser, filter); + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)); + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + g_free(buffer); + g_object_unref(native); + + GtkStatsMonitor *monitor = new GtkStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to load session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + _save_filename = fn; + + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + _monitor = monitor; + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Opens the last session, if any. + */ +bool GtkStatsServer:: +open_last_session() { + if (!close_session()) { + return false; + } + + Filename fn = _last_session; + GtkStatsMonitor *monitor = new GtkStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to load session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + // If the file contained no graphs, open the default graphs. + if (monitor->_graphs.empty()) { + monitor->open_default_graphs(); + } + + return true; +} + +/** + * Offers to save the current session. + */ +bool GtkStatsServer:: +save_session() { + nassertr_always(_monitor != nullptr, true); + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Save Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Save", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "PStats Session Files"); + gtk_file_filter_add_pattern(filter, "*.pstats"); + gtk_file_chooser_add_filter(chooser, filter); + + if (_save_filename.empty()) { + gtk_file_chooser_set_current_name(chooser, "session.pstats"); + } + else { + gtk_file_chooser_set_filename(chooser, _save_filename.c_str()); + } + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(chooser); + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + g_free(buffer); + g_object_unref(native); + + if (!_monitor->write(fn)) { + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to save session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + _save_filename = fn; + _monitor->get_client_data()->clear_dirty(); + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Offers to export the current session as a JSON file. + */ +bool GtkStatsServer:: +export_session() { + nassertr_always(_monitor != nullptr, true); + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Export Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Export", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "JSON files"); + gtk_file_filter_add_pattern(filter, "*.json"); + gtk_file_chooser_add_filter(chooser, filter); + + gtk_file_chooser_set_current_name(chooser, "session.json"); + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(chooser); + Filename fn = Filename::from_os_specific(buffer); + fn.set_text(); + g_free(buffer); + g_object_unref(native); + + std::ofstream stream; + if (!fn.open_write(stream)) { + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to open file for export: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + + int pid = _monitor->get_client_pid(); + _monitor->get_client_data()->write_json(stream, std::max(0, pid)); + stream.close(); + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Closes the current session. + */ +bool GtkStatsServer:: +close_session() { + bool wrote_last_session = false; + + if (_monitor != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->is_dirty()) { + if (!_monitor->has_read_filename()) { + _last_session.make_dir(); + if (_monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + wrote_last_session = true; + } + else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Would you like to save the currently open session?"); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response == GTK_RESPONSE_CANCEL || + (response == GTK_RESPONSE_YES && !save_session())) { + return false; + } + } + + _monitor->close(); + _monitor = nullptr; + } + + _save_filename = Filename(); + stop_listening(); + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + + if (_status_bar_label != nullptr) { + gtk_container_remove(GTK_CONTAINER(_status_bar), _status_bar_label); + _status_bar_label = nullptr; + } + + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + if (wrote_last_session) { + gtk_widget_set_sensitive(_open_last_session_menu_item, TRUE); + } + gtk_widget_set_sensitive(_save_session_menu_item, FALSE); + gtk_widget_set_sensitive(_close_session_menu_item, FALSE); + gtk_widget_set_sensitive(_export_session_menu_item, FALSE); + return true; +} + + +/** + * Returns the window handle to the server's window. + */ +GtkWidget *GtkStatsServer:: +get_window() const { + return _window; +} + +/** + * Returns the menu handle to the server's menu bar. + */ +GtkWidget *GtkStatsServer:: +get_menu_bar() const { + return _menu_bar; +} + +/** + * Returns the window handle to the server's status bar. + */ +GtkWidget *GtkStatsServer:: +get_status_bar() const { + return _status_bar; +} + +/** + * + */ +int GtkStatsServer:: +get_time_units() const { + return _time_units; +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsServer:: +set_time_units(int unit_mask) { + _time_units = unit_mask; + + if (_monitor != nullptr) { + _monitor->set_time_units(unit_mask); + } +} + +/** + * Creates the window for this monitor. + */ +void GtkStatsServer:: +create_window() { + _window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + gtk_window_set_default_size(GTK_WINDOW(_window), 500, 360); + + // Connect the delete and destroy events, so the user can exit the + // application by closing the main window. + g_signal_connect(G_OBJECT(_window), "delete_event", + G_CALLBACK(+[](GtkWidget *widget, GdkEvent *event, gpointer data) -> gboolean { + GtkStatsServer *self = (GtkStatsServer *)data; + return self->close_session() ? FALSE : TRUE; + }), this); + + g_signal_connect(G_OBJECT(_window), "destroy", + G_CALLBACK(+[](GtkWidget *widget, GdkEvent *event, gpointer data) -> gboolean { + gtk_main_quit(); + return FALSE; + }), this); + + // Set up the menu. + GtkAccelGroup *accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(_window), accel_group); + _menu_bar = gtk_menu_bar_new(); + + setup_session_menu(); + setup_options_menu(); + + // Pack the menu into the window. + GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); + gtk_container_add(GTK_CONTAINER(_window), main_vbox); + gtk_box_pack_start(GTK_BOX(main_vbox), _menu_bar, FALSE, TRUE, 0); + + // Create the status bar. + _status_bar = gtk_flow_box_new(); + gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(_status_bar), FALSE); + gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(_status_bar), GTK_SELECTION_NONE); + g_signal_connect(G_OBJECT(_status_bar), "button_press_event", + G_CALLBACK(status_bar_button_event), this); + gtk_box_pack_end(GTK_BOX(main_vbox), _status_bar, FALSE, FALSE, 0); + + GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_end(GTK_BOX(main_vbox), sep, FALSE, FALSE, 0); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + gtk_widget_realize(_window); + + // Set up a timer to poll the pstats every so often. + g_timeout_add(200, + G_SOURCE_FUNC(+[](gpointer data) -> gboolean { + GtkStatsServer *self = (GtkStatsServer *)data; + self->poll(); + return TRUE; + }), this); +} + +/** + * Creates the "Session" pulldown menu. + */ +void GtkStatsServer:: +setup_session_menu() { + _session_menu = gtk_menu_new(); + + GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Session"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _session_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); + + item = gtk_menu_item_new_with_mnemonic("_New Session"); + _new_session_menu_item = item; + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->new_session(); + }), this); + + item = gtk_menu_item_new_with_mnemonic("_Open Session..."); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->open_session(); + }), this); + + item = gtk_menu_item_new_with_mnemonic("Open _Last Session"); + _open_last_session_menu_item = item; + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->open_last_session(); + }), this); + + if (!_last_session.exists()) { + gtk_widget_set_sensitive(item, FALSE); + } + + item = gtk_menu_item_new_with_mnemonic("_Save Session..."); + _save_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->save_session(); + }), this); + + item = gtk_menu_item_new_with_mnemonic("_Close Session"); + _close_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->close_session(); + }), this); + + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), sep); + + item = gtk_menu_item_new_with_mnemonic("_Export as JSON..."); + _export_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->export_session(); + }), this); + + sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), sep); + + item = gtk_menu_item_new_with_mnemonic("E_xit"); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + if (self->close_session()) { + gtk_main_quit(); + } + }), this); + + gtk_widget_show_all(_session_menu); + + /* + _session_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _session_menu; + + mii.dwTypeData = "&Session"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + AppendMenu(_session_menu, MF_STRING, MI_session_new, "&New Session\tCtrl+N"); + AppendMenu(_session_menu, MF_STRING, MI_session_open, "&Open Session...\tCtrl+O"); + + if (_last_session.exists()) { + AppendMenu(_session_menu, MF_STRING, MI_session_open_last, "Open &Last Session"); + } else { + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_open_last, "Open &Last Session"); + } + + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_save, "&Save Session...\tCtrl+S"); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_close, "&Close Session\tCtrl+W"); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_export_json, "&Export as JSON..."); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING, MI_exit, "E&xit");*/ +} + +/** + * Creates the "Options" pulldown menu. + */ +void GtkStatsServer:: +setup_options_menu() { + _options_menu = gtk_menu_new(); + + GtkWidget *item = gtk_menu_item_new_with_label("Options"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _options_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); + + GtkWidget *units_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_label("Units"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), units_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_options_menu), item); + + item = gtk_radio_menu_item_new_with_label(nullptr, "ms"); + gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->set_time_units(PStatGraph::GBU_ms); + }), this); + + item = gtk_radio_menu_item_new_with_label( + gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)), "Hz"); + gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->set_time_units(PStatGraph::GBU_hz); + }), this); + + set_time_units(PStatGraph::GBU_ms); +} + +/** + * Handles clicks on a partion of the status bar. + */ +gboolean GtkStatsServer:: +status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { + GtkStatsServer *server = (GtkStatsServer *)data; + + GtkFlowBoxChild *child = gtk_flow_box_get_child_at_pos( + GTK_FLOW_BOX(server->_status_bar), event->x, event->y); + if (child == nullptr) { + return FALSE; + } + + // Which child is this? + GList *children = gtk_container_get_children(GTK_CONTAINER(server->_status_bar)); + int index = g_list_index(children, child); + g_list_free(children); + if (index < 0) { + return FALSE; + } + + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + server->_monitor->handle_status_bar_click(index); + return TRUE; + } + else if (event->type == GDK_BUTTON_PRESS && event->button == 3 && index > 0) { + server->_monitor->handle_status_bar_popup(index); + return TRUE; + } + return FALSE; } diff --git a/pandatool/src/gtk-stats/gtkStatsServer.h b/pandatool/src/gtk-stats/gtkStatsServer.h index 89bb025c4e..72fa61bbd6 100644 --- a/pandatool/src/gtk-stats/gtkStatsServer.h +++ b/pandatool/src/gtk-stats/gtkStatsServer.h @@ -16,13 +16,59 @@ #include "pandatoolbase.h" #include "pStatServer.h" +#include "gtkStatsMonitor.h" /** * The class that owns the main loop, waiting for client connections. */ class GtkStatsServer : public PStatServer { public: - virtual PStatMonitor *make_monitor(); + GtkStatsServer(); + + virtual PStatMonitor *make_monitor(const NetAddress &address); + virtual void lost_connection(PStatMonitor *monitor); + + bool new_session(); + bool open_session(); + bool open_last_session(); + bool save_session(); + bool export_session(); + bool close_session(); + + GtkWidget *get_window() const; + GtkWidget *get_menu_bar() const; + GtkWidget *get_status_bar() const; + + int get_time_units() const; + void set_time_units(int unit_mask); + +private: + void create_window(); + void setup_session_menu(); + void setup_options_menu(); + + static gboolean status_bar_button_event(GtkWidget *widget, + GdkEventButton *event, + gpointer data); + +private: + PT(GtkStatsMonitor) _monitor; + + Filename _last_session; + Filename _save_filename; + + GtkWidget *_window; + GtkWidget *_menu_bar; + GtkWidget *_session_menu; + GtkWidget *_options_menu; + GtkWidget *_status_bar; + GtkWidget *_status_bar_label = nullptr; + GtkWidget *_new_session_menu_item; + GtkWidget *_open_last_session_menu_item; + GtkWidget *_save_session_menu_item; + GtkWidget *_close_session_menu_item; + GtkWidget *_export_session_menu_item; + int _time_units; }; #endif diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx index 78c31d6e45..ba1df8a7b9 100644 --- a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx @@ -25,9 +25,7 @@ static const int default_strip_chart_height = 100; GtkStatsStripChart:: GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index, int collector_index, bool show_level) : - PStatStripChart(monitor, - show_level ? monitor->get_level_view(0, thread_index) : monitor->get_view(thread_index), - thread_index, collector_index, 0, 0), + PStatStripChart(monitor, thread_index, collector_index, show_level, 0, 0), GtkStatsGraph(monitor, true) { if (show_level) { @@ -84,6 +82,8 @@ GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index, gtk_widget_set_size_request(_graph_window, 0, 0); clear_region(); + + update(); } /** @@ -261,6 +261,35 @@ on_popup_label(int collector_index) { (void *)menu_def); } + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, GtkStatsMonitor::CT_choose_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, collector_index, GtkStatsMonitor::CT_reset_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + gtk_widget_show_all(menu); gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); } @@ -416,6 +445,25 @@ end_draw(int from_x, int to_x) { gdk_window_invalidate_rect(window, &rect, FALSE); } +/** + * Returns the current window dimensions. + */ +bool GtkStatsStripChart:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsStripChart:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * This is called during the servicing of the draw event; it gives a derived * class opportunity to do some further painting into the graph window. diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.h b/pandatool/src/gtk-stats/gtkStatsStripChart.h index 01284e9719..760930ef6f 100644 --- a/pandatool/src/gtk-stats/gtkStatsStripChart.h +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.h @@ -56,6 +56,11 @@ protected: virtual void draw_cursor(int x); virtual void end_draw(int from_x, int to_x); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + virtual void additional_graph_window_paint(cairo_t *cr); virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; virtual DragMode consider_drag_start(int graph_x, int graph_y); diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.cxx b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx index fbc45ccc1b..839ca387ab 100644 --- a/pandatool/src/gtk-stats/gtkStatsTimeline.cxx +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx @@ -312,6 +312,25 @@ animate(double time, double dt) { return PStatTimeline::animate(time, dt); } +/** + * Returns the current window dimensions. + */ +bool GtkStatsTimeline:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsTimeline:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * This is called during the servicing of the draw event; it gives a derived * class opportunity to do some further painting into the graph window. @@ -414,6 +433,37 @@ handle_button_press(int graph_x, int graph_y, bool double_click, int button) { (void *)menu_def); } + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, bar._collector_index, + GtkStatsMonitor::CT_choose_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + -1, bar._collector_index, + GtkStatsMonitor::CT_reset_color, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + gtk_widget_show_all(menu); gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); return TRUE; diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.h b/pandatool/src/gtk-stats/gtkStatsTimeline.h index 4ab8d46505..872046e1be 100644 --- a/pandatool/src/gtk-stats/gtkStatsTimeline.h +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.h @@ -47,6 +47,11 @@ protected: virtual bool animate(double time, double dt); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + virtual void additional_graph_window_paint(cairo_t *cr); virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; virtual DragMode consider_drag_start(int graph_x, int graph_y); diff --git a/pandatool/src/pstatserver/pStatClientData.cxx b/pandatool/src/pstatserver/pStatClientData.cxx index 26e06f069c..1f28caefcf 100644 --- a/pandatool/src/pstatserver/pStatClientData.cxx +++ b/pandatool/src/pstatserver/pStatClientData.cxx @@ -12,6 +12,7 @@ */ #include "pStatClientData.h" +#include "pStatFrameData.h" #include "pStatReader.h" #include "pStatCollectorDef.h" @@ -20,16 +21,15 @@ using std::string; PStatCollectorDef PStatClientData::_null_collector(-1, "Unknown"); - - /** * */ PStatClientData:: PStatClientData(PStatReader *reader) : + _is_alive(true), + _is_dirty(false), _reader(reader) { - _is_alive = true; } /** @@ -37,12 +37,28 @@ PStatClientData(PStatReader *reader) : */ PStatClientData:: ~PStatClientData() { - Collectors::const_iterator ci; - for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) { - delete (*ci)._def; + for (Collector &collector : _collectors) { + delete collector._def; } } +/** + * Clears the is_dirty() flag. + */ +void PStatClientData:: +clear_dirty() const { + _is_dirty = false; +} + +/** + * Returns true if the data was modified since the last time clear_dirty() was + * called. + */ +bool PStatClientData:: +is_dirty() const { + return _is_dirty; +} + /** * Returns true if the data is actively getting filled by a connected client, * or false if the client has terminated. @@ -83,6 +99,28 @@ has_collector(int index) const { _collectors[index]._def != nullptr); } +/** + * Returns the index of the collector with the given full name, or -1 if no + * such collector has been defined by the client. + */ +int PStatClientData:: +find_collector(const std::string &fullname) const { + // Take the last bit, we can compare it more cheaply, only check the full + // name if the basename matches. + const char *colon = strrchr(fullname.c_str(), ':'); + std::string name(colon != nullptr ? colon + 1 : fullname.c_str()); + + for (int index = 0; index < get_num_collectors(); ++index) { + const PStatCollectorDef *def = _collectors[index]._def; + if (def != nullptr && def->_name == name && + get_collector_fullname(index) == fullname) { + return index; + } + } + + return -1; +} + /** * Returns the nth collector definition. */ @@ -153,6 +191,10 @@ set_collector_has_level(int index, int thread_index, bool flag) { } } + if (any_changed) { + _is_dirty = true; + } + return any_changed; } @@ -206,6 +248,21 @@ has_thread(int index) const { !_threads[index]._name.empty()); } +/** + * Returns the index of the thread with the given name, or -1 if no such thread + * has yet been defined. + */ +int PStatClientData:: +find_thread(const std::string &name) const { + for (int index = 0; index < get_num_threads(); ++index) { + if (_threads[index]._name == name) { + return index; + } + } + + return -1; +} + /** * Returns the name of the indicated thread. */ @@ -280,6 +337,8 @@ add_collector(PStatCollectorDef *def) { set_collector_has_level(def->_parent_index, thread_index, true); } } + + _is_dirty = true; } /** @@ -303,8 +362,9 @@ define_thread(int thread_index, const string &name) { if (_threads[thread_index]._data.is_null()) { _threads[thread_index]._data = new PStatThreadData(this); } -} + _is_dirty = true; +} /** * Makes room for and stores a new frame's worth of data associated with some @@ -319,6 +379,102 @@ record_new_frame(int thread_index, int frame_number, define_thread(thread_index); nassertv(thread_index >= 0 && thread_index < (int)_threads.size()); _threads[thread_index]._data->record_new_frame(frame_number, frame_data); + _is_dirty = true; +} + +/** + * Writes the client data in the form of a JSON output that can be loaded into + * Chrome's event tracer. + */ +void PStatClientData:: +write_json(std::ostream &out, int pid) const { + out << "[\n"; + + for (int thread_index = 0; thread_index < get_num_threads(); ++thread_index) { + const Thread &thread = _threads[thread_index]; + + if (thread_index == 0) { + out << "{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":" << pid + << ",\"tid\":0,\"args\":{\"name\":\"Main\"}}"; + } + else if (!thread._name.empty()) { + out + << ",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":" << pid + << ",\"tid\":" << thread_index << ",\"args\":{\"name\":\"" + << thread._name << "\"}}"; + } + if (thread._data == nullptr || thread._data->is_empty()) { + continue; + } + + int first_frame = thread._data->get_oldest_frame_number(); + int last_frame = thread._data->get_latest_frame_number(); + for (int frame_number = first_frame; frame_number <= last_frame; ++frame_number) { + const PStatFrameData &frame_data = thread._data->get_frame(frame_number); + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { + int collector_index = frame_data.get_time_collector(i); + out + << ",\n{\"name\":\"" << get_collector_fullname(collector_index) + << "\",\"ts\":" << (uint64_t)(frame_data.get_time(i) * 1000000) + << ",\"ph\":\"" << (frame_data.is_start(i) ? 'B' : 'E') << "\"" + << ",\"pid\":" << pid << ",\"tid\":" << thread_index << "}"; + } + } + } + + out << "\n]\n"; + out.flush(); +} + +/** + * Writes the client data to a datagram. + */ +void PStatClientData:: +write_datagram(Datagram &dg) const { + for (const Collector &collector : _collectors) { + PStatCollectorDef *def = collector._def; + if (def != nullptr && def->_index != -1) { + def->write_datagram(dg); + collector._is_level.write_datagram(nullptr, dg); + } + } + dg.add_int16(-1); + + int thread_index = 0; + for (const Thread &thread : _threads) { + if (thread._data != nullptr) { + dg.add_int16(thread_index); + dg.add_string(thread._name); + thread._data->write_datagram(dg); + } + ++thread_index; + } + dg.add_int16(-1); +} + +/** + * Restores the client data from a datagram. + */ +void PStatClientData:: +read_datagram(DatagramIterator &scan) { + while (scan.peek_int16() != -1) { + PStatCollectorDef *def = new PStatCollectorDef; + def->read_datagram(scan); + add_collector(def); + _collectors[def->_index]._is_level.read_datagram(scan, nullptr); + } + scan.skip_bytes(2); + + int thread_index; + while ((thread_index = scan.get_int16()) != -1) { + std::string name = scan.get_string(); + define_thread(thread_index, name); + + _threads[thread_index]._data->read_datagram(scan); + } + + update_toplevel_collectors(); } /** @@ -344,9 +500,8 @@ void PStatClientData:: update_toplevel_collectors() { _toplevel_collectors.clear(); - Collectors::const_iterator ci; - for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) { - PStatCollectorDef *def = (*ci)._def; + for (Collector &collector : _collectors) { + PStatCollectorDef *def = collector._def; if (def != nullptr && def->_parent_index == 0) { _toplevel_collectors.push_back(def->_index); } diff --git a/pandatool/src/pstatserver/pStatClientData.h b/pandatool/src/pstatserver/pStatClientData.h index 051bb568ae..179e512bd4 100644 --- a/pandatool/src/pstatserver/pStatClientData.h +++ b/pandatool/src/pstatserver/pStatClientData.h @@ -35,14 +35,19 @@ class PStatReader; */ class PStatClientData : public PStatClientVersion { public: + PStatClientData() = default; PStatClientData(PStatReader *reader); ~PStatClientData(); + void clear_dirty() const; + bool is_dirty() const; + bool is_alive() const; void close(); int get_num_collectors() const; bool has_collector(int index) const; + int find_collector(const std::string &fullname) const; const PStatCollectorDef &get_collector_def(int index) const; std::string get_collector_name(int index) const; std::string get_collector_fullname(int index) const; @@ -54,6 +59,7 @@ public: int get_num_threads() const; bool has_thread(int index) const; + int find_thread(const std::string &name) const; std::string get_thread_name(int index) const; const PStatThreadData *get_thread_data(int index) const; @@ -65,13 +71,19 @@ public: void record_new_frame(int thread_index, int frame_number, PStatFrameData *frame_data); + + void write_json(std::ostream &out, int pid = 0) const; + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan); + private: void slot_collector(int collector_index); void update_toplevel_collectors(); private: - bool _is_alive; - PStatReader *_reader; + bool _is_alive = false; + mutable bool _is_dirty = false; + PStatReader *_reader = nullptr; class Collector { public: diff --git a/pandatool/src/pstatserver/pStatFlameGraph.cxx b/pandatool/src/pstatserver/pStatFlameGraph.cxx index c146203e63..e91c037a07 100644 --- a/pandatool/src/pstatserver/pStatFlameGraph.cxx +++ b/pandatool/src/pstatserver/pStatFlameGraph.cxx @@ -34,14 +34,22 @@ PStatFlameGraph(PStatMonitor *monitor, { _average_mode = true; _average_cursor = 0; - - _time_width = 1.0 / pstats_target_frame_rate; _current_frame = -1; _title_unknown = true; + // NB. This won't call force_redraw() (which we can't do yet) because average + // mode is true + update_data(); + _time_width = _stack.get_net_value(false); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + _guide_bar_units = GBU_ms | GBU_hz | GBU_show_units; normal_guide_bars(); + + monitor->_flame_graphs.insert(this); } /** @@ -49,6 +57,7 @@ PStatFlameGraph(PStatMonitor *monitor, */ PStatFlameGraph:: ~PStatFlameGraph() { + _monitor->_flame_graphs.erase(this); } /** @@ -97,6 +106,15 @@ set_collector_index(int collector_index) { _title_unknown = true; _stack.clear(); update_data(); + + if (_average_mode) { + _stack.update_averages(_average_cursor); + _time_width = _stack.get_net_value(true); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + } } } @@ -167,6 +185,34 @@ get_bar_collector(int depth, int x) const { return -1; } +/** + * Writes the graph state to a datagram. + */ +void PStatFlameGraph:: +write_datagram(Datagram &dg) const { + dg.add_int16(_orig_collector_index); + dg.add_float64(_time_width); + dg.add_bool(_average_mode); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatFlameGraph:: +read_datagram(DatagramIterator &scan) { + _orig_collector_index = scan.get_int16(); + _time_width = scan.get_float64(); + _average_mode = scan.get_bool(); + + PStatGraph::read_datagram(scan); + + _current_frame = -1; + normal_guide_bars(); + update(); +} + /** * */ @@ -336,9 +382,11 @@ animate(double time, double dt) { _time_width = 1.0 / pstats_target_frame_rate; } normal_guide_bars(); - force_redraw(); } + // Always use force_redraw, since the mouse position may have changed. + force_redraw(); + // Cycle through the ring buffers. _average_cursor = (_average_cursor + 1) % _num_average_frames; return true; diff --git a/pandatool/src/pstatserver/pStatFlameGraph.h b/pandatool/src/pstatserver/pStatFlameGraph.h index 61a61447e2..611f285ac5 100644 --- a/pandatool/src/pstatserver/pStatFlameGraph.h +++ b/pandatool/src/pstatserver/pStatFlameGraph.h @@ -60,6 +60,9 @@ public: std::string get_bar_tooltip(int depth, int x) const; int get_bar_collector(int depth, int x) const; + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + protected: void update_data(); void changed_size(int xsize, int ysize); diff --git a/pandatool/src/pstatserver/pStatGraph.cxx b/pandatool/src/pstatserver/pStatGraph.cxx index 38cb21a5c8..003ca2effb 100644 --- a/pandatool/src/pstatserver/pStatGraph.cxx +++ b/pandatool/src/pstatserver/pStatGraph.cxx @@ -189,8 +189,8 @@ format_number(double value, int guide_bar_units, const string &unit_name) { label += " "; label += unit_name; } - - } else { + } + else { // Units are either milliseconds or hz, or both. if ((guide_bar_units & GBU_ms) != 0) { if ((guide_bar_units & GBU_show_units) != 0 && @@ -225,15 +225,36 @@ format_number(double value, int guide_bar_units, const string &unit_name) { if ((guide_bar_units & GBU_hz) != 0) { double hz = 1.0 / value; - if ((guide_bar_units & GBU_ms) != 0) { - label += " ("; + if ((guide_bar_units & GBU_show_units) != 0 && + (guide_bar_units & GBU_ms) == 0) { + if (hz >= 1000000000) { + label += format_number(hz / 1000000000); + label += " GHz"; + } + else if (hz >= 1000000) { + label += format_number(hz / 1000000); + label += " MHz"; + } + else if (hz >= 1000) { + label += format_number(hz / 1000); + label += " kHz"; + } + else { + label += format_number(hz); + label += " Hz"; + } } - label += format_number(hz); - if ((guide_bar_units & GBU_show_units) != 0) { - label += " Hz"; - } - if ((guide_bar_units & GBU_ms) != 0) { - label += ")"; + else { + if ((guide_bar_units & GBU_ms) != 0) { + label += " ("; + } + label += format_number(hz); + if ((guide_bar_units & GBU_show_units) != 0) { + label += " Hz"; + } + if ((guide_bar_units & GBU_ms) != 0) { + label += ")"; + } } } } @@ -241,6 +262,43 @@ format_number(double value, int guide_bar_units, const string &unit_name) { return label; } +/** + * Writes the graph state to a datagram. + */ +void PStatGraph:: +write_datagram(Datagram &dg) const { + int x, y, width, height; + bool minimized, maximized; + if (get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_bool(true); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + else { + dg.add_bool(false); + } +} + +/** + * Restores the graph state from a datagram. + */ +void PStatGraph:: +read_datagram(DatagramIterator &scan) { + if (scan.get_bool()) { + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + set_window_state(x, y, width, height, minimized, maximized); + } +} + /** * Resets the list of guide bars. */ @@ -294,3 +352,20 @@ make_guide_bar(double value, PStatGraph::GuideBarStyle style) const { return GuideBar(value, label, style); } + +/** + * Returns the current window dimensions. + */ +bool PStatGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + return false; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void PStatGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { +} diff --git a/pandatool/src/pstatserver/pStatGraph.h b/pandatool/src/pstatserver/pStatGraph.h index 69541a86ec..6c9168a485 100644 --- a/pandatool/src/pstatserver/pStatGraph.h +++ b/pandatool/src/pstatserver/pStatGraph.h @@ -91,6 +91,14 @@ public: static std::string format_number(double value, int guide_bar_units, const std::string &unit_name = std::string()); + virtual void write_datagram(Datagram &dg) const; + virtual void read_datagram(DatagramIterator &scan); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + protected: virtual void normal_guide_bars()=0; void update_guide_bars(int num_bars, double scale); diff --git a/pandatool/src/pstatserver/pStatListener.cxx b/pandatool/src/pstatserver/pStatListener.cxx index 73fe3d3b04..c2f7bb35ef 100644 --- a/pandatool/src/pstatserver/pStatListener.cxx +++ b/pandatool/src/pstatserver/pStatListener.cxx @@ -33,7 +33,7 @@ void PStatListener:: connection_opened(const PT(Connection) &, const NetAddress &address, const PT(Connection) &new_connection) { - PStatMonitor *monitor = _manager->make_monitor(); + PStatMonitor *monitor = _manager->make_monitor(address); if (monitor == nullptr) { nout << "Couldn't create monitor!\n"; return; diff --git a/pandatool/src/pstatserver/pStatMonitor.I b/pandatool/src/pstatserver/pStatMonitor.I index ac9f2e46f9..482102eb9d 100644 --- a/pandatool/src/pstatserver/pStatMonitor.I +++ b/pandatool/src/pstatserver/pStatMonitor.I @@ -77,3 +77,19 @@ INLINE int PStatMonitor:: get_client_pid() const { return _client_pid; } + +/** + * Returns true if the session data was loaded from a file. + */ +INLINE bool PStatMonitor:: +has_read_filename() const { + return !_read_filename.empty(); +} + +/** + * Returns the filename this session data was loaded from. + */ +INLINE const Filename &PStatMonitor:: +get_read_filename() const { + return _read_filename; +} diff --git a/pandatool/src/pstatserver/pStatMonitor.cxx b/pandatool/src/pstatserver/pStatMonitor.cxx index 20444352c1..11286f5558 100644 --- a/pandatool/src/pstatserver/pStatMonitor.cxx +++ b/pandatool/src/pstatserver/pStatMonitor.cxx @@ -13,10 +13,29 @@ #include "pStatMonitor.h" +#include "datagramInputFile.h" +#include "datagramOutputFile.h" +#include "pandaVersion.h" #include "pStatCollectorDef.h" +#include "pStatTimeline.h" +#include "pStatStripChart.h" +#include "pStatFlameGraph.h" +#include "pStatPianoRoll.h" using std::string; +static const string session_file_header("pstat\0\n\r", 8); +static const string layout_file_header("pslyt\0\n\r", 8); + +static const Filename layout_filename = Filename::binary_filename( +#ifdef _WIN32 + Filename::expand_from("$USER_APPDATA/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout") +#elif defined(__APPLE__) + Filename::expand_from("$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout") +#else + Filename::expand_from("$XDG_CACHE_HOME/panda3d/pstats-layout") +#endif +); /** * @@ -31,6 +50,7 @@ PStatMonitor(PStatServer *server) : _server(server) { */ PStatMonitor:: ~PStatMonitor() { + close(); } /** @@ -74,6 +94,283 @@ set_client_data(PStatClientData *client_data) { initialized(); } +/** + * Writes the data and the UI state to the given file. + */ +bool PStatMonitor:: +write(const Filename &fn) const { + DatagramOutputFile dof; + if (!dof.open(fn)) { + return false; + } + if (!dof.write_header(session_file_header)) { + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + dg.add_uint16(1); + dg.add_uint16(1); + write_datagram(dg); + dof.put_datagram(dg); + dof.close(); + return true; +} + +/** + * Reads the data and the UI state from the given file. + */ +bool PStatMonitor:: +read(const Filename &fn) { + close(); + + DatagramInputFile dif; + if (!dif.open(fn)) { + nout << "Failed to open " << fn << " for reading.\n"; + return false; + } + string header; + if (!dif.read_header(header, 8) || header != session_file_header) { + nout << "Session file contains invalid header.\n"; + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + + if (!dif.get_datagram(dg)) { + nout << "Failed to read datagram from session file.\n"; + return false; + } + + DatagramIterator scan(dg); + int version = scan.get_uint16(); + if (version != 1) { + nout << "Unsupported session file version " << version << ".\n"; + return false; + } + // Room for a minor version number + scan.get_uint16(); + + read_datagram(scan); + dif.close(); + + idle(); + + _read_filename = fn; + _client_data->clear_dirty(); + + return true; +} + +/** + * Opens the default set of graphs. + */ +void PStatMonitor:: +open_default_graphs() { + DatagramInputFile dif; + if (!dif.open(layout_filename)) { + open_strip_chart(0, 0, false); + return; + } + string header; + if (!dif.read_header(header, 8) || header != layout_file_header) { + nout << "Layout file contains invalid header.\n"; + open_strip_chart(0, 0, false); + return; + } + Datagram dg; + dg.set_stdfloat_double(false); + + if (!dif.get_datagram(dg)) { + nout << "Failed to read datagram from layout file.\n"; + open_strip_chart(0, 0, false); + return; + } + + DatagramIterator scan(dg); + int version = scan.get_uint16(); + if (version != 1) { + nout << "Unsupported layout file version " << version << ".\n"; + open_strip_chart(0, 0, false); + return; + } + // Room for a minor version number + scan.get_uint16(); + + size_t num_colors = scan.get_uint32(); + for (size_t i = 0; i < num_colors; ++i) { + int key = scan.get_int32(); + _colors[key].read_datagram(scan); + } + + PStatGraph *graph; + + size_t num_timelines = scan.get_uint16(); + for (size_t i = 0; i < num_timelines; ++i) { + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + graph = open_timeline(); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + + size_t num_strip_charts = scan.get_uint16(); + for (size_t i = 0; i < num_strip_charts; ++i) { + std::string thread_name = scan.get_string(); + std::string collector_name = scan.get_string(); + bool show_level = scan.get_bool(); + int thread_index = _client_data->find_thread(thread_name); + int collector_index = _client_data->find_collector(collector_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_strip_chart(thread_index, collector_index, show_level); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + size_t num_flame_graphs = scan.get_uint16(); + for (size_t i = 0; i < num_flame_graphs; ++i) { + std::string thread_name = scan.get_string(); + std::string collector_name = scan.get_string(); + int thread_index = _client_data->find_thread(thread_name); + int collector_index = collector_name.empty() ? -1 : _client_data->find_collector(collector_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_flame_graph(thread_index, collector_index); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + size_t num_piano_rolls = scan.get_uint16(); + for (size_t i = 0; i < num_piano_rolls; ++i) { + std::string thread_name = scan.get_string(); + int thread_index = _client_data->find_thread(thread_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_piano_roll(thread_index); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + dif.close(); + if (num_timelines + num_strip_charts + num_flame_graphs + num_piano_rolls == 0) { + open_strip_chart(0, 0, false); + } +} + +/** + * Saves the current graph layout as the default graph layout. + */ +bool PStatMonitor:: +save_default_graphs() const { + layout_filename.make_dir(); + + DatagramOutputFile dof; + if (!dof.open(layout_filename)) { + return false; + } + if (!dof.write_header(layout_file_header)) { + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + dg.add_uint16(1); + dg.add_uint16(1); + + dg.add_uint32((uint32_t)_colors.size()); + for (const auto &item : _colors) { + dg.add_int32(item.first); + item.second.write_datagram(dg); + } + + dg.add_uint16(_timelines.size()); + for (PStatGraph *graph : _timelines) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_strip_charts.size()); + for (PStatGraph *graph : _strip_charts) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_string(_client_data->get_thread_name(((PStatStripChart *)graph)->get_thread_index())); + dg.add_string(_client_data->get_collector_fullname(((PStatStripChart *)graph)->get_collector_index())); + dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level()); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_flame_graphs.size()); + for (PStatGraph *graph : _flame_graphs) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + int collector_index = ((PStatFlameGraph *)graph)->get_collector_index(); + dg.add_string(_client_data->get_thread_name(((PStatFlameGraph *)graph)->get_thread_index())); + dg.add_string(collector_index >= 0 ? _client_data->get_collector_fullname(collector_index) : ""); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_piano_rolls.size()); + for (PStatGraph *graph : _piano_rolls) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_string(_client_data->get_thread_name(((PStatPianoRoll *)graph)->get_thread_index())); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + // Reserved for future graph type + dg.add_uint16(0); + + dof.put_datagram(dg); + dof.close(); + return true; +} + /** * Returns true if the client is alive and connected, false otherwise. */ @@ -145,6 +442,22 @@ get_collector_color(int collector_index) { return (*ci).second; } +/** + * Sets a custom color associated with the given collector. + */ +void PStatMonitor:: +set_collector_color(int collector_index, const LRGBColor &color) { + _colors[collector_index] = color; +} + +/** + * Clears any custom custom color associated with the given collector. + */ +void PStatMonitor:: +clear_collector_color(int collector_index) { + _colors.erase(collector_index); +} + /** * Returns a view on the given thread index. If there is no such view already * for the indicated thread, this will create one. This view can be used to @@ -293,3 +606,162 @@ is_thread_safe() { void PStatMonitor:: user_guide_bars_changed() { } + +/** + * Opens a new timeline. + */ +PStatGraph *PStatMonitor:: +open_timeline() { + return nullptr; +} + +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + return nullptr; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_flame_graph(int thread_index, int collector_index) { + return nullptr; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_piano_roll(int thread_index) { + return nullptr; +} + +/** + * Writes the client data and open graphs to a datagram. + */ +void PStatMonitor:: +write_datagram(Datagram &dg) const { + dg.add_bool(_client_known); + dg.add_string(_client_hostname); + dg.add_string(_client_progname); + dg.add_int32(_client_pid); + + get_client_data()->write_datagram(dg); + + dg.add_uint32((uint32_t)_colors.size()); + for (const auto &item : _colors) { + dg.add_int32(item.first); + item.second.write_datagram(dg); + } + + dg.add_uint16(_timelines.size()); + for (PStatGraph *graph : _timelines) { + graph->write_datagram(dg); + } + + dg.add_uint16(_strip_charts.size()); + for (PStatGraph *graph : _strip_charts) { + dg.add_int16(((PStatStripChart *)graph)->get_thread_index()); + dg.add_int16(((PStatStripChart *)graph)->get_collector_index()); + dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level()); + graph->write_datagram(dg); + } + + dg.add_uint16(_flame_graphs.size()); + for (PStatGraph *graph : _flame_graphs) { + dg.add_int16(((PStatFlameGraph *)graph)->get_thread_index()); + dg.add_int16(((PStatFlameGraph *)graph)->get_collector_index()); + graph->write_datagram(dg); + } + + dg.add_uint16(_piano_rolls.size()); + for (PStatGraph *graph : _piano_rolls) { + dg.add_int16(((PStatPianoRoll *)graph)->get_thread_index()); + graph->write_datagram(dg); + } + + // Reserved for future graph type + dg.add_uint16(0); +} + +/** + * Restores the client data and open graphs from a datagram. + */ +void PStatMonitor:: +read_datagram(DatagramIterator &scan) { + _client_known = scan.get_bool(); + _client_hostname = scan.get_string(); + _client_progname = scan.get_string(); + _client_pid = scan.get_int32(); + + PStatClientData *client_data = new PStatClientData; + client_data->read_datagram(scan); + set_client_data(client_data); + + size_t num_colors = scan.get_uint32(); + for (size_t i = 0; i < num_colors; ++i) { + int key = scan.get_int32(); + _colors[key].read_datagram(scan); + } + + int num_collectors = client_data->get_num_collectors(); + for (int collector_index = 0; collector_index < num_collectors; ++collector_index) { + if (client_data->has_collector(collector_index)) { + new_collector(collector_index); + } + } + + int num_threads = client_data->get_num_threads(); + for (int thread_index = 0; thread_index < num_threads; ++thread_index) { + if (client_data->has_thread(thread_index)) { + const PStatThreadData *thread_data = client_data->get_thread_data(thread_index); + if (!thread_data->is_empty()) { + const PStatFrameData &frame_data = thread_data->get_latest_frame(); + get_view(thread_index).set_to_frame(frame_data); + + int num_collectors = client_data->get_num_toplevel_collectors(); + for (int i = 0; i < num_collectors; ++i) { + int collector_index = client_data->get_toplevel_collector(i); + if (client_data->has_collector(collector_index)) { + get_level_view(collector_index, thread_index).set_to_frame(thread_data->get_latest_frame()); + } + } + } + new_thread(thread_index); + } + } + + PStatGraph *graph; + + size_t num_timelines = scan.get_uint16(); + for (size_t i = 0; i < num_timelines; ++i) { + graph = open_timeline(); + graph->read_datagram(scan); + } + + size_t num_strip_charts = scan.get_uint16(); + for (size_t i = 0; i < num_strip_charts; ++i) { + int thread_index = scan.get_int16(); + int collector_index = scan.get_int16(); + graph = open_strip_chart(thread_index, collector_index, scan.get_bool()); + graph->read_datagram(scan); + } + + size_t num_flame_graphs = scan.get_uint16(); + for (size_t i = 0; i < num_flame_graphs; ++i) { + int thread_index = scan.get_int16(); + int collector_index = scan.get_int16(); + graph = open_flame_graph(thread_index, collector_index); + graph->read_datagram(scan); + } + + size_t num_piano_rolls = scan.get_uint16(); + for (size_t i = 0; i < num_piano_rolls; ++i) { + int thread_index = scan.get_int16(); + graph = open_piano_roll(thread_index); + graph->read_datagram(scan); + } +} diff --git a/pandatool/src/pstatserver/pStatMonitor.h b/pandatool/src/pstatserver/pStatMonitor.h index 8c1259c9f0..5685b98b3f 100644 --- a/pandatool/src/pstatserver/pStatMonitor.h +++ b/pandatool/src/pstatserver/pStatMonitor.h @@ -26,6 +26,7 @@ #include "pmap.h" class PStatCollectorDef; +class PStatGraph; class PStatServer; /** @@ -40,7 +41,7 @@ class PStatMonitor : public ReferenceCount { public: // The following functions are primarily for use by internal classes to set // up the monitor. - PStatMonitor(PStatServer *server); + PStatMonitor(PStatServer *server = nullptr); virtual ~PStatMonitor(); void hello_from(const std::string &hostname, const std::string &progname, @@ -51,6 +52,11 @@ public: int server_major, int server_minor); void set_client_data(PStatClientData *client_data); + bool write(const Filename &fn) const; + bool read(const Filename &fn); + + void open_default_graphs(); + bool save_default_graphs() const; // The following functions are for use by user code to determine information // about the client data available. @@ -61,16 +67,19 @@ public: INLINE const PStatClientData *get_client_data() const; INLINE std::string get_collector_name(int collector_index); const LRGBColor &get_collector_color(int collector_index); + void set_collector_color(int collector_index, const LRGBColor &color); + void clear_collector_color(int collector_index); INLINE bool is_client_known() const; INLINE std::string get_client_hostname() const; INLINE std::string get_client_progname() const; INLINE int get_client_pid() const; + INLINE bool has_read_filename() const; + INLINE const Filename &get_read_filename() const; PStatView &get_view(int thread_index); PStatView &get_level_view(int collector_index, int thread_index); - // The following virtual methods may be overridden by a derived monitor // class to customize behavior. @@ -92,6 +101,14 @@ public: virtual void user_guide_bars_changed(); + virtual PStatGraph *open_timeline(); + virtual PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + virtual PStatGraph *open_flame_graph(int thread_index, int collector_index = -1); + virtual PStatGraph *open_piano_roll(int thread_index); + + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan); + protected: PStatServer *_server; @@ -102,6 +119,7 @@ private: std::string _client_hostname; std::string _client_progname; int _client_pid; + Filename _read_filename; typedef pmap Views; Views _views; @@ -110,6 +128,13 @@ private: typedef pmap Colors; Colors _colors; + +public: + typedef pset Graphs; + Graphs _timelines; + Graphs _strip_charts; + Graphs _flame_graphs; + Graphs _piano_rolls; }; #include "pStatMonitor.I" diff --git a/pandatool/src/pstatserver/pStatPianoRoll.cxx b/pandatool/src/pstatserver/pStatPianoRoll.cxx index 25c2b416cb..ba95cc3f9e 100644 --- a/pandatool/src/pstatserver/pStatPianoRoll.cxx +++ b/pandatool/src/pstatserver/pStatPianoRoll.cxx @@ -89,10 +89,24 @@ PStatPianoRoll(PStatMonitor *monitor, int thread_index, int xsize, int ysize) : { _time_width = 1.0 / pstats_target_frame_rate; _start_time = 0.0; - _current_frame = -1; + + // If we already have data, load it in now. + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = + client_data->get_thread_data(thread_index); + if (!thread_data->is_empty()) { + int frame_number = thread_data->get_latest_frame_number(); + compute_page(thread_data->get_frame(frame_number)); + } + } + _guide_bar_units = GBU_ms | GBU_hz | GBU_show_units; normal_guide_bars(); + + monitor->_piano_rolls.insert(this); } /** @@ -100,6 +114,7 @@ PStatPianoRoll(PStatMonitor *monitor, int thread_index, int xsize, int ysize) : */ PStatPianoRoll:: ~PStatPianoRoll() { + _monitor->_piano_rolls.erase(this); } /** @@ -142,6 +157,32 @@ get_label_tooltip(int collector_index) const { return client_data->get_collector_fullname(collector_index); } +/** + * Writes the graph state to a datagram. + */ +void PStatPianoRoll:: +write_datagram(Datagram &dg) const { + dg.add_float64(_time_width); + dg.add_float64(_start_time); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatPianoRoll:: +read_datagram(DatagramIterator &scan) { + _time_width = scan.get_float64(); + _start_time = scan.get_float64(); + + PStatGraph::read_datagram(scan); + + _current_frame = -1; + normal_guide_bars(); + update(); +} + /** * To be called by the user class when the widget size has changed. This * updates the chart's internal data and causes it to issue redraw commands to diff --git a/pandatool/src/pstatserver/pStatPianoRoll.h b/pandatool/src/pstatserver/pStatPianoRoll.h index 6c909dc36e..7ba4ac2ea8 100644 --- a/pandatool/src/pstatserver/pStatPianoRoll.h +++ b/pandatool/src/pstatserver/pStatPianoRoll.h @@ -55,6 +55,9 @@ public: std::string get_label_tooltip(int collector_index) const; + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + protected: void changed_size(int xsize, int ysize); void force_redraw(); diff --git a/pandatool/src/pstatserver/pStatReader.cxx b/pandatool/src/pstatserver/pStatReader.cxx index a2a1b4b7c9..3d94ffcd50 100644 --- a/pandatool/src/pstatserver/pStatReader.cxx +++ b/pandatool/src/pstatserver/pStatReader.cxx @@ -94,6 +94,7 @@ void PStatReader:: lost_connection() { _client_data->_is_alive = false; _monitor->lost_connection(); + _manager->lost_connection(_monitor); _client_data.clear(); _manager->close_connection(_tcp_connection); diff --git a/pandatool/src/pstatserver/pStatServer.cxx b/pandatool/src/pstatserver/pStatServer.cxx index 23a6a62fda..095a870622 100644 --- a/pandatool/src/pstatserver/pStatServer.cxx +++ b/pandatool/src/pstatserver/pStatServer.cxx @@ -30,10 +30,11 @@ PStatServer() { */ PStatServer:: ~PStatServer() { + stop_listening(); + delete _listener; } - /** * Establishes a port number that the manager will listen on for TCP * connections. This may be called more than once to listen simulataneously @@ -47,20 +48,22 @@ PStatServer:: */ bool PStatServer:: listen(int port) { + stop_listening(); + if (port < 0) { port = pstats_port; } // Now try to listen to the port. - PT(Connection) rendezvous = open_TCP_server_rendezvous(port, 5); + _rendezvous = open_TCP_server_rendezvous(port, 5); - if (rendezvous.is_null()) { + if (_rendezvous.is_null()) { // Couldn't get it. return false; } // Tell the listener about the new port. - _listener->add_connection(rendezvous); + _listener->add_connection(_rendezvous); if (_next_udp_port == 0) { _next_udp_port = port + 1; @@ -68,6 +71,16 @@ listen(int port) { return true; } +/** + * Stops listening. + */ +void PStatServer:: +stop_listening() { + if (_rendezvous != nullptr) { + close_connection(_rendezvous); + _rendezvous.clear(); + } +} /** * Checks for any network activity and handles it, if appropriate, and then diff --git a/pandatool/src/pstatserver/pStatServer.h b/pandatool/src/pstatserver/pStatServer.h index 50128e4069..b4d53ed12d 100644 --- a/pandatool/src/pstatserver/pStatServer.h +++ b/pandatool/src/pstatserver/pStatServer.h @@ -39,11 +39,14 @@ public: ~PStatServer(); bool listen(int port = -1); + void stop_listening(); void poll(); void main_loop(bool *interrupt_flag = nullptr); - virtual PStatMonitor *make_monitor()=0; + virtual PStatMonitor *make_monitor(const NetAddress &address)=0; + virtual void lost_connection(PStatMonitor *monitor) {} + void add_reader(Connection *connection, PStatReader *reader); void remove_reader(Connection *connection, PStatReader *reader); @@ -67,6 +70,7 @@ private: void user_guide_bars_changed(); PStatListener *_listener; + PT(Connection) _rendezvous; typedef pmap Readers; Readers _readers; diff --git a/pandatool/src/pstatserver/pStatStripChart.cxx b/pandatool/src/pstatserver/pStatStripChart.cxx index 2b454edcc5..4f278d996e 100644 --- a/pandatool/src/pstatserver/pStatStripChart.cxx +++ b/pandatool/src/pstatserver/pStatStripChart.cxx @@ -29,11 +29,12 @@ using std::min; * */ PStatStripChart:: -PStatStripChart(PStatMonitor *monitor, PStatView &view, - int thread_index, int collector_index, int xsize, int ysize) : +PStatStripChart(PStatMonitor *monitor, + int thread_index, int collector_index, bool show_level, + int xsize, int ysize) : PStatGraph(monitor, xsize, ysize), _thread_index(thread_index), - _view(view), + _view(show_level ? monitor->get_level_view(0, thread_index) : monitor->get_view(thread_index)), _collector_index(collector_index) { _scroll_mode = pstats_scroll_mode; @@ -57,6 +58,8 @@ PStatStripChart(PStatMonitor *monitor, PStatView &view, } set_auto_vertical_scale(); + + monitor->_strip_charts.insert(this); } /** @@ -64,6 +67,7 @@ PStatStripChart(PStatMonitor *monitor, PStatView &view, */ PStatStripChart:: ~PStatStripChart() { + _monitor->_strip_charts.erase(this); } /** @@ -370,6 +374,65 @@ get_label_tooltip(int collector_index) const { return text.str(); } +/** + * Writes the graph state to a datagram. + */ +void PStatStripChart:: +write_datagram(Datagram &dg) const { + dg.add_bool(_scroll_mode); + dg.add_bool(_average_mode); + dg.add_float64(_time_width); + dg.add_float64(_start_time); + dg.add_float64(_value_height); + + // Not really necessary, we reconstructed this from the client data. + //for (const auto &item : _data) { + // dg.add_int32(item.first); + // dg.add_uint32(item.second.size()); + // + // for (const ColorData &cd : item.second) { + // dg.add_uint16(cd._collector_index); + // dg.add_uint16(cd._i); + // dg.add_float64(cd._net_value); + // } + //} + //dg.add_int32(-1); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatStripChart:: +read_datagram(DatagramIterator &scan) { + _next_frame = 0; + force_reset(); + + _scroll_mode = scan.get_bool(); + _average_mode = scan.get_bool(); + _time_width = scan.get_float64(); + _start_time = scan.get_float64(); + _value_height = scan.get_float64(); + + //int key; + //while ((key = scan.get_int32()) != -1) { + // FrameData &fdata = _data[key]; + // fdata.resize(scan.get_uint32()); + // + // for (ColorData &cd : fdata) { + // cd._collector_index = scan.get_uint16(); + // cd._i = scan.get_uint16(); + // cd._net_value = scan.get_float64(); + // } + //} + + PStatGraph::read_datagram(scan); + + normal_guide_bars(); + update(); +} + /** * Adds the data from additional into the data from fdata, after applying the * scale weight. @@ -784,7 +847,6 @@ void PStatStripChart:: idle() { } - // STL function object for sorting labels in order by the collector's sort // index, used in update_labels(), below. class SortCollectorLabels2 { diff --git a/pandatool/src/pstatserver/pStatStripChart.h b/pandatool/src/pstatserver/pStatStripChart.h index 5802d0baa2..25ed47fb39 100644 --- a/pandatool/src/pstatserver/pStatStripChart.h +++ b/pandatool/src/pstatserver/pStatStripChart.h @@ -37,8 +37,9 @@ class PStatView; */ class PStatStripChart : public PStatGraph { public: - PStatStripChart(PStatMonitor *monitor, PStatView &view, - int thread_index, int collector_index, int xsize, int ysize); + PStatStripChart(PStatMonitor *monitor, + int thread_index, int collector_index, bool show_level, + int xsize, int ysize); virtual ~PStatStripChart(); void new_data(int frame_number); @@ -73,6 +74,9 @@ public: std::string get_title_text(); std::string get_label_tooltip(int collector_index) const; + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + protected: class ColorData { public: diff --git a/pandatool/src/pstatserver/pStatThreadData.cxx b/pandatool/src/pstatserver/pStatThreadData.cxx index 06da8a4c3c..a8b1861a88 100644 --- a/pandatool/src/pstatserver/pStatThreadData.cxx +++ b/pandatool/src/pstatserver/pStatThreadData.cxx @@ -202,7 +202,7 @@ get_latest_frame() const { bool PStatThreadData:: get_elapsed_frames(int &then_i, int &now_i) const { if (!_computed_elapsed_frames) { - ((PStatThreadData *)this)->compute_elapsed_frames(); + compute_elapsed_frames(); } now_i = _now_i; @@ -308,17 +308,48 @@ record_new_frame(int frame_number, PStatFrameData *frame_data) { } /** - * Computes the frame numbers returned by get_elapsed_frames(). This is non- - * const, but only updates cached values, so may safely be called from a const - * method. + * Writes the thread data to a datagram. */ void PStatThreadData:: -compute_elapsed_frames() { +write_datagram(Datagram &dg) const { + int frame_number = _first_frame_number; + + for (PStatFrameData *frame_data : _frames) { + if (frame_data != nullptr) { + dg.add_int32(frame_number); + frame_data->write_datagram(dg); + } + ++frame_number; + } + dg.add_int32(-1); +} + +/** + * Restores the thread data from a datagram. + */ +void PStatThreadData:: +read_datagram(DatagramIterator &scan) { + int frame_number; + while ((frame_number = scan.get_int32()) != -1) { + PStatFrameData *frame_data = new PStatFrameData; + frame_data->read_datagram(scan); + + record_new_frame(frame_number, frame_data); + } + + compute_elapsed_frames(); +} + +/** + * Computes the frame numbers returned by get_elapsed_frames(). + */ +void PStatThreadData:: +compute_elapsed_frames() const { if (_frames.empty()) { // No frames in the data at all. _got_elapsed_frames = false; - - } else { + } + else { _now_i = _frames.size() - 1; while (_now_i > 0 && _frames[_now_i] == nullptr) { _now_i--; @@ -326,8 +357,8 @@ compute_elapsed_frames() { if (_now_i < 0) { // No frames have any real data. _got_elapsed_frames = false; - - } else { + } + else { nassertv(_frames[_now_i] != nullptr); double now = _frames[_now_i]->get_end(); diff --git a/pandatool/src/pstatserver/pStatThreadData.h b/pandatool/src/pstatserver/pStatThreadData.h index a22ac9509c..f2dce4338d 100644 --- a/pandatool/src/pstatserver/pStatThreadData.h +++ b/pandatool/src/pstatserver/pStatThreadData.h @@ -15,7 +15,8 @@ #define PSTATTHREADDATA_H #include "pandatoolbase.h" - +#include "datagram.h" +#include "datagramIterator.h" #include "referenceCount.h" #include "pdeque.h" @@ -61,8 +62,12 @@ public: void record_new_frame(int frame_number, PStatFrameData *frame_data); + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan); + private: - void compute_elapsed_frames(); + void compute_elapsed_frames() const; + const PStatClientData *_client_data; typedef pdeque Frames; @@ -70,10 +75,11 @@ private: int _first_frame_number; double _history; - bool _computed_elapsed_frames; - bool _got_elapsed_frames; - int _then_i; - int _now_i; + // Cached values, updated by compute_elapsed_frames(). + mutable bool _computed_elapsed_frames; + mutable bool _got_elapsed_frames; + mutable int _then_i; + mutable int _now_i; static PStatFrameData _null_frame; }; diff --git a/pandatool/src/pstatserver/pStatTimeline.cxx b/pandatool/src/pstatserver/pStatTimeline.cxx index 8c806447c7..a5e3733158 100644 --- a/pandatool/src/pstatserver/pStatTimeline.cxx +++ b/pandatool/src/pstatserver/pStatTimeline.cxx @@ -76,6 +76,8 @@ PStatTimeline(PStatMonitor *monitor, int xsize, int ysize) : _start_time = _lowest_start_time; _target_start_time = _start_time; + + monitor->_timelines.insert(this); } /** @@ -83,6 +85,7 @@ PStatTimeline(PStatMonitor *monitor, int xsize, int ysize) : */ PStatTimeline:: ~PStatTimeline() { + _monitor->_timelines.erase(this); } /** @@ -116,7 +119,7 @@ new_data(int thread_index, int frame_number) { _highest_end_time = frame_end; } - while (thread_index >= _threads.size()) { + while (thread_index >= (int)_threads.size()) { _threads_changed = true; if (_threads.size() == 0) { _threads.resize(1); @@ -261,6 +264,42 @@ get_bar_tooltip(int row, int x) const { return std::string(); } +/** + * Writes the graph state to a datagram. + */ +void PStatTimeline:: +write_datagram(Datagram &dg) const { + dg.add_float64(_time_scale); + dg.add_float64(_start_time); + dg.add_float64(_lowest_start_time); + dg.add_float64(_highest_end_time); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatTimeline:: +read_datagram(DatagramIterator &scan) { + _time_scale = scan.get_float64(); + _start_time = scan.get_float64(); + _lowest_start_time = scan.get_float64(); + _highest_end_time = scan.get_float64(); + + _scroll_speed = 0.0; + _zoom_speed = 0.0; + + _have_start_time = true; + _target_start_time = _start_time; + _target_time_scale = _time_scale; + + PStatGraph::read_datagram(scan); + + normal_guide_bars(); + force_redraw(); +} + /** * To be called by the user class when the widget size has changed. This * updates the chart's internal data and causes it to issue redraw commands to @@ -324,12 +363,12 @@ force_redraw(int row, int from_x, int to_x) { for (size_t ti = 0; ti < _threads.size(); ++ti) { ThreadRow &thread_row = _threads[ti]; - if (thread_row._row_offset > row) { + if ((int)thread_row._row_offset > row) { break; } int row_index = row - (int)thread_row._row_offset; - if (row_index < thread_row._rows.size()) { + if (row_index < (int)thread_row._rows.size()) { draw_row((int)ti, row_index, start_time, end_time); } } @@ -644,12 +683,12 @@ find_bar(int row, int x, ColorBar &bar) const { for (size_t ti = 0; ti < _threads.size(); ++ti) { const ThreadRow &thread_row = _threads[ti]; - if (thread_row._row_offset > row) { + if ((int)thread_row._row_offset > row) { break; } int row_index = row - (int)thread_row._row_offset; - if (row_index < thread_row._rows.size()) { + if (row_index < (int)thread_row._rows.size()) { // Find the first element whose end time is larger than the given time. const Row &bars = thread_row._rows[row_index]; Row::const_iterator it = std::lower_bound(bars.begin(), bars.end(), ColorBar {time, time}); diff --git a/pandatool/src/pstatserver/pStatTimeline.h b/pandatool/src/pstatserver/pStatTimeline.h index 0a9d0f779f..3cb23af559 100644 --- a/pandatool/src/pstatserver/pStatTimeline.h +++ b/pandatool/src/pstatserver/pStatTimeline.h @@ -56,6 +56,9 @@ public: std::string get_bar_tooltip(int row, int x) const; + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + protected: void changed_size(int xsize, int ysize); void force_redraw(); diff --git a/pandatool/src/text-stats/textMonitor.cxx b/pandatool/src/text-stats/textMonitor.cxx index dd437d6106..acaa421d12 100644 --- a/pandatool/src/text-stats/textMonitor.cxx +++ b/pandatool/src/text-stats/textMonitor.cxx @@ -121,8 +121,8 @@ new_data(int thread_index, int frame_number) { } const PStatFrameData &frame_data = thread_data->get_frame(frame_number); - int num_events = frame_data.get_num_events(); - for (int i = 0; i < num_events; ++i) { + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { int collector_index = frame_data.get_time_collector(i); (*_outStream) << "{\"name\":\"" << client_data->get_collector_fullname(collector_index) @@ -141,8 +141,8 @@ new_data(int thread_index, int frame_number) { if (_show_raw_data) { const PStatFrameData &frame_data = thread_data->get_frame(frame_number); (*_outStream) << "raw data:\n"; - int num_events = frame_data.get_num_events(); - for (int i = 0; i < num_events; ++i) { + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { // The iomanipulators are much too clumsy. char formatted[32]; sprintf(formatted, "%15.06lf", frame_data.get_time(i)); diff --git a/pandatool/src/text-stats/textStats.cxx b/pandatool/src/text-stats/textStats.cxx index 0e677172aa..3e5da69c46 100644 --- a/pandatool/src/text-stats/textStats.cxx +++ b/pandatool/src/text-stats/textStats.cxx @@ -69,7 +69,7 @@ TextStats() { * */ PStatMonitor *TextStats:: -make_monitor() { +make_monitor(const NetAddress &address) { return new TextMonitor(this, _outFile, _show_raw_data, _json); } diff --git a/pandatool/src/text-stats/textStats.h b/pandatool/src/text-stats/textStats.h index 99725f0e30..92ce71a789 100644 --- a/pandatool/src/text-stats/textStats.h +++ b/pandatool/src/text-stats/textStats.h @@ -30,7 +30,7 @@ class TextStats : public ProgramBase, public PStatServer { public: TextStats(); - virtual PStatMonitor *make_monitor(); + virtual PStatMonitor *make_monitor(const NetAddress &address); void run(); diff --git a/pandatool/src/win-stats/CMakeLists.txt b/pandatool/src/win-stats/CMakeLists.txt index 99166f6ec9..4e5d607948 100644 --- a/pandatool/src/win-stats/CMakeLists.txt +++ b/pandatool/src/win-stats/CMakeLists.txt @@ -33,7 +33,7 @@ set(WINSTATS_SOURCES composite_sources(win-stats WINSTATS_SOURCES) add_executable(win-stats ${WINSTATS_HEADERS} ${WINSTATS_SOURCES}) -target_link_libraries(win-stats p3progbase p3pstatserver comctl32.lib) +target_link_libraries(win-stats p3progbase p3pstatserver comctl32.lib uxtheme.lib) # This program is NOT actually called win-stats. It's just pstats.exe set_target_properties(win-stats PROPERTIES OUTPUT_NAME "pstats") diff --git a/pandatool/src/win-stats/winStats.cxx b/pandatool/src/win-stats/winStats.cxx index 5dabb7b71a..74add4b083 100644 --- a/pandatool/src/win-stats/winStats.cxx +++ b/pandatool/src/win-stats/winStats.cxx @@ -26,66 +26,6 @@ // Enable common controls version 6, necessary for modern visual styles #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") -static const char *toplevel_class_name = "pstats"; -static WinStatsServer *server = nullptr; - -/** - * - */ -static LONG WINAPI -toplevel_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - switch (msg) { - case WM_TIMER: - server->poll(); - break; - - case WM_DESTROY: - PostQuitMessage(0); - break; - - default: - break; - } - - return DefWindowProc(hwnd, msg, wparam, lparam); -} - - -/** - * Creates the initial, toplevel window for the application. - */ -static HWND -create_toplevel_window(HINSTANCE application) { - WNDCLASS wc; - - ZeroMemory(&wc, sizeof(WNDCLASS)); - wc.lpfnWndProc = (WNDPROC)toplevel_window_proc; - wc.hInstance = application; - wc.lpszClassName = toplevel_class_name; - - if (!RegisterClass(&wc)) { - nout << "Could not register window class!\n"; - exit(1); - } - - DWORD window_style = WS_POPUP | WS_SYSMENU | WS_ICONIC; - - std::ostringstream strm; - strm << "PStats " << pstats_port; - std::string window_name = strm.str(); - - HWND toplevel_window = - CreateWindow(toplevel_class_name, window_name.c_str(), window_style, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, nullptr, application, 0); - if (!toplevel_window) { - nout << "Could not create toplevel window!\n"; - exit(1); - } - - return toplevel_window; -} - int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { // Initialize commctl32.dll. INITCOMMONCONTROLSEX icc; @@ -96,27 +36,9 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { // Signal DPI awareness. SetProcessDPIAware(); - HINSTANCE application = GetModuleHandle(nullptr); - HWND toplevel_window = create_toplevel_window(application); - - ShowWindow(toplevel_window, SW_SHOWMINIMIZED); - - // Create the server object. - server = new WinStatsServer; - if (!server->listen()) { - std::ostringstream stream; - stream - << "Unable to open port " << pstats_port - << ". Try specifying a different\n" - << "port number using pstats-port in your Config file."; - std::string str = stream.str(); - MessageBox(toplevel_window, str.c_str(), "PStats error", - MB_OK | MB_ICONEXCLAMATION); - exit(1); - } - - // Set up a timer to poll the pstats every so often. - SetTimer(toplevel_window, 1, 200, nullptr); + // Create the server window. + WinStatsServer *server = new WinStatsServer; + server->new_session(); // Now get lost in the Windows message loop. MSG msg; diff --git a/pandatool/src/win-stats/winStatsChartMenu.cxx b/pandatool/src/win-stats/winStatsChartMenu.cxx index 71d30282f5..dbcbd22e97 100644 --- a/pandatool/src/win-stats/winStatsChartMenu.cxx +++ b/pandatool/src/win-stats/winStatsChartMenu.cxx @@ -12,6 +12,7 @@ */ #include "winStatsChartMenu.h" +#include "winStatsMenuId.h" #include "winStatsMonitor.h" /** @@ -31,6 +32,7 @@ WinStatsChartMenu(WinStatsMonitor *monitor, int thread_index) : */ WinStatsChartMenu:: ~WinStatsChartMenu() { + DestroyMenu(_menu); } /** @@ -99,14 +101,28 @@ do_update() { if (_thread_index == 0) { // Timeline goes first. - WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_timeline, false); - int menu_id = _monitor->get_menu_id(menu_def); + { + WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_timeline, false); + int menu_id = _monitor->get_menu_id(menu_def); - mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; - mii.fType = MFT_STRING; - mii.wID = menu_id; - mii.dwTypeData = "Timeline"; - InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Timeline"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } + + // And the piano roll (even though it's not very useful nowadays) + { + WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_piano_roll, false); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Piano Roll"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } mii.fMask = MIIM_FTYPE; mii.fType = MFT_SEPARATOR; @@ -141,19 +157,28 @@ do_update() { } } - // Also menu item for piano roll (following a separator). - mii.fMask = MIIM_FTYPE; - mii.fType = MFT_SEPARATOR; - InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); - - { - WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_piano_roll, false); - int menu_id = _monitor->get_menu_id(menu_def); + // For the main thread menu, also some options relating to all graph windows. + if (_thread_index == 0) { + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; mii.fType = MFT_STRING; - mii.wID = menu_id; - mii.dwTypeData = "Piano Roll"; + mii.wID = MI_graphs_close_all; + mii.dwTypeData = "Close All Graphs"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = MI_graphs_reopen_default; + mii.dwTypeData = "Reopen Default Graphs"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = MI_graphs_save_default; + mii.dwTypeData = "Save Current Layout as Default"; InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); } } diff --git a/pandatool/src/win-stats/winStatsFlameGraph.cxx b/pandatool/src/win-stats/winStatsFlameGraph.cxx index 80c3e7a2b8..e67da41eee 100644 --- a/pandatool/src/win-stats/winStatsFlameGraph.cxx +++ b/pandatool/src/win-stats/winStatsFlameGraph.cxx @@ -47,7 +47,7 @@ WinStatsFlameGraph(WinStatsMonitor *monitor, int thread_index, _average_check_box = 0; create_window(); - clear_region(); + force_redraw(); } /** @@ -117,7 +117,7 @@ set_time_units(int unit_mask) { RECT rect; GetClientRect(_window, &rect); - rect.left = _right_margin; + rect.bottom = _top_margin; InvalidateRect(_window, &rect, TRUE); } } @@ -159,6 +159,22 @@ on_leave_label(int collector_index) { } } +/** + * Changes the collector represented by this flame graph. This may force a + * redraw. + */ +void WinStatsFlameGraph:: +set_collector_index(int collector_index) { + PStatFlameGraph::set_collector_index(collector_index); + + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } +} + /** * Calls update_guide_bars with parameters suitable to this kind of graph. */ @@ -176,6 +192,11 @@ normal_guide_bars() { } _guide_bars_changed = true; + + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); } /** @@ -276,6 +297,28 @@ animate(double time, double dt) { return PStatFlameGraph::animate(time, dt); } +/** + * Returns the current window dimensions. + */ +bool WinStatsFlameGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsFlameGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); + + // Set the state of the checkbox. + SendMessage(_average_check_box, BM_SETCHECK, get_average_mode() ? BST_CHECKED : BST_UNCHECKED, 0); +} + /** * */ @@ -316,6 +359,14 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { case 103: WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; } break; @@ -433,6 +484,9 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { } AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); } } diff --git a/pandatool/src/win-stats/winStatsFlameGraph.h b/pandatool/src/win-stats/winStatsFlameGraph.h index 92dd48be36..5a7224bd87 100644 --- a/pandatool/src/win-stats/winStatsFlameGraph.h +++ b/pandatool/src/win-stats/winStatsFlameGraph.h @@ -40,6 +40,8 @@ public: virtual void on_enter_label(int collector_index); virtual void on_leave_label(int collector_index); + void set_collector_index(int collector_index); + protected: virtual void normal_guide_bars(); @@ -51,6 +53,11 @@ protected: virtual bool animate(double time, double dt); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual void additional_window_paint(HDC hdc); diff --git a/pandatool/src/win-stats/winStatsGraph.cxx b/pandatool/src/win-stats/winStatsGraph.cxx index f57d7c1959..24808ba9bc 100644 --- a/pandatool/src/win-stats/winStatsGraph.cxx +++ b/pandatool/src/win-stats/winStatsGraph.cxx @@ -14,6 +14,7 @@ #include "winStatsGraph.h" #include "winStatsMonitor.h" #include "winStatsLabelStack.h" +#include "winStatsServer.h" #include "trueClock.h" #include "convert_srgb.h" @@ -281,6 +282,57 @@ animate(double time, double dt) { return false; } +/** + * Returns the current window dimensions. + */ +void WinStatsGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsServer *server = (WinStatsServer *)_monitor->get_server(); + POINT client_origin = server->get_client_origin(); + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(_window, &wp); + x = wp.rcNormalPosition.left - client_origin.x; + y = wp.rcNormalPosition.top - client_origin.y; + width = wp.rcNormalPosition.right - wp.rcNormalPosition.left; + height = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; + maximized = (wp.showCmd == SW_SHOWMAXIMIZED || (wp.flags & WPF_RESTORETOMAXIMIZED) != 0); + minimized = (wp.showCmd == SW_SHOWMINIMIZED); +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsServer *server = (WinStatsServer *)_monitor->get_server(); + POINT client_origin = server->get_client_origin(); + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + wp.flags = maximized ? WPF_RESTORETOMAXIMIZED : 0; + wp.showCmd = minimized ? SW_SHOWMINIMIZED : (maximized ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL); + wp.ptMinPosition.x = -1; + wp.ptMinPosition.y = -1; + wp.ptMaxPosition.x = -1; + wp.ptMaxPosition.y = -1; + wp.rcNormalPosition.left = client_origin.x + x; + wp.rcNormalPosition.top = client_origin.y + y; + wp.rcNormalPosition.right = wp.rcNormalPosition.left + width; + wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + height; + + if (minimized) { + int x, y; + _monitor->calc_iconic_graph_window_pos(this, x, y); + wp.ptMinPosition.x = x; + wp.ptMinPosition.y = y; + wp.flags |= WPF_SETMINPOSITION; + } + + SetWindowPlacement(_window, &wp); +} + /** * Returns a brush suitable for drawing in the indicated collector's color. */ @@ -331,6 +383,17 @@ get_collector_text_color(int collector_index, bool highlight) { return highlight ? hcolor : color; } +/** + * Called when the given collector has changed colors. + */ +void WinStatsGraph:: +reset_collector_color(int collector_index) { + _brushes.erase(collector_index); + _text_colors.erase(collector_index); + force_redraw(); + _label_stack.update_label_color(collector_index); +} + /** * This window_proc should be called up to by the derived classes for any * messages that are not specifically handled by the derived class. @@ -356,6 +419,31 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { return 0; } + case WM_WINDOWPOSCHANGING: + { + WINDOWPOS &pos = *(WINDOWPOS *)lparam; + if ((pos.flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0 && IsIconic(hwnd)) { + _monitor->calc_iconic_graph_window_pos(this, pos.x, pos.y); + } + } + break; + + case WM_SYSCOMMAND: + if (wparam == SC_MINIMIZE) { + WINDOWPLACEMENT wp; + if (GetWindowPlacement(hwnd, &wp)) { + int x, y; + _monitor->calc_iconic_graph_window_pos(this, x, y); + wp.showCmd = SW_SHOWMINIMIZED; + wp.flags |= WPF_SETMINPOSITION; + wp.ptMinPosition.x = x; + wp.ptMinPosition.y = y; + SetWindowPlacement(hwnd, &wp); + } + return 0; + } + break; + case WM_SIZE: move_label_stack(); InvalidateRect(hwnd, nullptr, TRUE); diff --git a/pandatool/src/win-stats/winStatsGraph.h b/pandatool/src/win-stats/winStatsGraph.h index 1cfc1bcee0..7c1b286cb1 100644 --- a/pandatool/src/win-stats/winStatsGraph.h +++ b/pandatool/src/win-stats/winStatsGraph.h @@ -23,6 +23,7 @@ #endif #include +class PStatGraph; class WinStatsMonitor; /** @@ -67,6 +68,8 @@ public: HWND get_window(); + void reset_collector_color(int collector_index); + protected: void close(); @@ -76,6 +79,11 @@ protected: void start_animation(); virtual bool animate(double time, double dt); + void get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + HBRUSH get_collector_brush(int collector_index, bool highlight = false); COLORREF get_collector_text_color(int collector_index, bool highlight = false); diff --git a/pandatool/src/win-stats/winStatsLabel.cxx b/pandatool/src/win-stats/winStatsLabel.cxx index 595d581a80..93d81f9531 100644 --- a/pandatool/src/win-stats/winStatsLabel.cxx +++ b/pandatool/src/win-stats/winStatsLabel.cxx @@ -42,35 +42,7 @@ WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph, _tooltip_window(0) { update_text(use_fullname); - - LRGBColor rgb = _monitor->get_collector_color(_collector_index); - int r = (int)encode_sRGB_uchar((float)rgb[0]); - int g = (int)encode_sRGB_uchar((float)rgb[1]); - int b = (int)encode_sRGB_uchar((float)rgb[2]); - _bg_brush = CreateSolidBrush(RGB(r, g, b)); - - // Calculate the color when it is highlighted. - int hr = (int)encode_sRGB_uchar((float)rgb[0] * 0.75f); - int hg = (int)encode_sRGB_uchar((float)rgb[1] * 0.75f); - int hb = (int)encode_sRGB_uchar((float)rgb[2] * 0.75f); - _highlight_bg_brush = CreateSolidBrush(RGB(hr, hg, hb)); - - // Should our foreground be black or white? - double bright = - rgb[0] * 0.2126 + - rgb[1] * 0.7152 + - rgb[2] * 0.0722; - - if (bright >= 0.5) { - _fg_color = RGB(0, 0, 0); - } else { - _fg_color = RGB(255, 255, 255); - } - if (bright * 0.75 >= 0.5) { - _highlight_fg_color = RGB(0, 0, 0); - } else { - _highlight_fg_color = RGB(255, 255, 255); - } + update_color(); _x = 0; _y = 0; @@ -95,6 +67,7 @@ WinStatsLabel:: _window = 0; } DeleteObject(_bg_brush); + DeleteObject(_highlight_bg_brush); } /** @@ -147,6 +120,52 @@ set_highlight(bool highlight) { } } +/** + * Updates the colors. + */ +void WinStatsLabel:: +update_color() { + if (_bg_brush != 0) { + DeleteObject(_bg_brush); + } + if (_highlight_bg_brush != 0) { + DeleteObject(_highlight_bg_brush); + } + + LRGBColor rgb = _monitor->get_collector_color(_collector_index); + int r = (int)encode_sRGB_uchar((float)rgb[0]); + int g = (int)encode_sRGB_uchar((float)rgb[1]); + int b = (int)encode_sRGB_uchar((float)rgb[2]); + _bg_brush = CreateSolidBrush(RGB(r, g, b)); + + // Calculate the color when it is highlighted. + int hr = (int)encode_sRGB_uchar((float)rgb[0] * 0.75f); + int hg = (int)encode_sRGB_uchar((float)rgb[1] * 0.75f); + int hb = (int)encode_sRGB_uchar((float)rgb[2] * 0.75f); + _highlight_bg_brush = CreateSolidBrush(RGB(hr, hg, hb)); + + // Should our foreground be black or white? + double bright = + rgb[0] * 0.2126 + + rgb[1] * 0.7152 + + rgb[2] * 0.0722; + + if (bright >= 0.5) { + _fg_color = RGB(0, 0, 0); + } else { + _fg_color = RGB(255, 255, 255); + } + if (bright * 0.75 >= 0.5) { + _highlight_fg_color = RGB(0, 0, 0); + } else { + _highlight_fg_color = RGB(255, 255, 255); + } + + if (_window) { + InvalidateRect(_window, nullptr, TRUE); + } +} + /** * Set to true if the full name of the collector should be shown. */ diff --git a/pandatool/src/win-stats/winStatsLabel.h b/pandatool/src/win-stats/winStatsLabel.h index a970d3e13e..c4478dc61d 100644 --- a/pandatool/src/win-stats/winStatsLabel.h +++ b/pandatool/src/win-stats/winStatsLabel.h @@ -50,6 +50,7 @@ public: void set_highlight(bool highlight); INLINE bool get_highlight() const; + void update_color(); void update_text(bool use_fullname); private: @@ -71,8 +72,8 @@ private: HWND _tooltip_window; COLORREF _fg_color; COLORREF _highlight_fg_color; - HBRUSH _bg_brush; - HBRUSH _highlight_bg_brush; + HBRUSH _bg_brush = 0; + HBRUSH _highlight_bg_brush = 0; int _x; int _y; diff --git a/pandatool/src/win-stats/winStatsLabelStack.cxx b/pandatool/src/win-stats/winStatsLabelStack.cxx index e6633f025b..8303cb0ec6 100644 --- a/pandatool/src/win-stats/winStatsLabelStack.cxx +++ b/pandatool/src/win-stats/winStatsLabelStack.cxx @@ -57,9 +57,7 @@ setup(HWND parent_window) { create_window(parent_window); _ideal_width = 0; - Labels::iterator li; - for (li = _labels.begin(); li != _labels.end(); ++li) { - WinStatsLabel *label = (*li); + for (WinStatsLabel *label : _labels) { label->setup(_window); _ideal_width = std::max(_ideal_width, label->get_ideal_width()); } @@ -85,10 +83,8 @@ set_pos(int x, int y, int width, int height) { SetWindowPos(_window, 0, x, y, _width, _height, SWP_NOZORDER | SWP_SHOWWINDOW); - Labels::iterator li; int yp = height; - for (li = _labels.begin(); li != _labels.end(); ++li) { - WinStatsLabel *label = (*li); + for (WinStatsLabel *label : _labels) { label->set_pos(0, yp, _width); yp -= label->get_height(); } @@ -167,9 +163,8 @@ get_label_collector_index(int label_index) const { */ void WinStatsLabelStack:: clear_labels() { - Labels::iterator li; - for (li = _labels.begin(); li != _labels.end(); ++li) { - delete (*li); + for (WinStatsLabel *label : _labels) { + delete label; } _labels.clear(); _ideal_width = 0; @@ -301,14 +296,25 @@ void WinStatsLabelStack:: highlight_label(int collector_index) { if (_highlight_label != collector_index) { _highlight_label = collector_index; - Labels::iterator li; - for (li = _labels.begin(); li != _labels.end(); ++li) { - WinStatsLabel *label = (*li); + + for (WinStatsLabel *label : _labels) { label->set_highlight(label->get_collector_index() == _highlight_label); } } } +/** + * Refreshes the color of the label with the given index. + */ +void WinStatsLabelStack:: +update_label_color(int collector_index) { + for (WinStatsLabel *label : _labels) { + if (label->get_collector_index() == collector_index) { + label->update_color(); + } + } +} + /** * Creates the window for this stack. */ diff --git a/pandatool/src/win-stats/winStatsLabelStack.h b/pandatool/src/win-stats/winStatsLabelStack.h index 48b9424754..748522e401 100644 --- a/pandatool/src/win-stats/winStatsLabelStack.h +++ b/pandatool/src/win-stats/winStatsLabelStack.h @@ -58,6 +58,7 @@ public: int get_num_labels() const; void highlight_label(int collector_index); + void update_label_color(int collector_index); private: void create_window(HWND parent_window); diff --git a/pandatool/src/win-stats/winStatsMenuId.h b/pandatool/src/win-stats/winStatsMenuId.h index c06169a0fc..9f73b50e0a 100644 --- a/pandatool/src/win-stats/winStatsMenuId.h +++ b/pandatool/src/win-stats/winStatsMenuId.h @@ -22,6 +22,15 @@ */ enum WinStatsMenuId { MI_none, + + MI_session_new, + MI_session_open, + MI_session_open_last, + MI_session_save, + MI_session_close, + MI_session_export_json, + MI_exit, + MI_time_ms, MI_time_hz, MI_frame_rate_label, @@ -32,6 +41,10 @@ enum WinStatsMenuId { MI_speed_12, MI_pause, + MI_graphs_close_all, + MI_graphs_reopen_default, + MI_graphs_save_default, + // This one is last and represents the beginning of the range for the // various "new chart" menu options. MI_new_chart diff --git a/pandatool/src/win-stats/winStatsMonitor.cxx b/pandatool/src/win-stats/winStatsMonitor.cxx index b8fade17f0..331959c5dd 100644 --- a/pandatool/src/win-stats/winStatsMonitor.cxx +++ b/pandatool/src/win-stats/winStatsMonitor.cxx @@ -23,45 +23,29 @@ #include "pStatGraph.h" #include "pStatCollectorDef.h" +#include "convert_srgb.h" + #include #include - -bool WinStatsMonitor::_window_class_registered = false; -const char * const WinStatsMonitor::_window_class_name = "monitor"; +#include +#include /** * */ WinStatsMonitor:: WinStatsMonitor(WinStatsServer *server) : PStatMonitor(server) { - _window = 0; - _menu_bar = 0; - _options_menu = 0; + _window = server->get_window(); + _menu_bar = server->get_menu_bar(); + _status_bar = server->get_status_bar(); // These will be filled in later when the menu is created. - _time_units = 0; _scroll_speed = 0.0; _pause = false; - // Create the fonts used for rendering the UI. - NONCLIENTMETRICS metrics = {0}; - metrics.cbSize = sizeof(NONCLIENTMETRICS); - if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0)) { - _font = CreateFontIndirect(&metrics.lfMenuFont); - } else { - _font = (HFONT)GetStockObject(ANSI_VAR_FONT); - } - - HDC dc = GetDC(nullptr); - _pixel_scale = 0; - if (dc) { - _pixel_scale = GetDeviceCaps(dc, LOGPIXELSX) / (96 / 4); - } - if (_pixel_scale <= 0) { - _pixel_scale = 4; - } - ReleaseDC(nullptr, dc); + setup_speed_menu(); + setup_frame_rate_label(); } /** @@ -69,27 +53,28 @@ WinStatsMonitor(WinStatsServer *server) : PStatMonitor(server) { */ WinStatsMonitor:: ~WinStatsMonitor() { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - delete (*gi); - } - _graphs.clear(); + close(); +} - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - delete (*mi); +/** + * Closes the client connection if it is active. + */ +void WinStatsMonitor:: +close() { + PStatMonitor::close(); + + remove_all_graphs(); + + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + + for (WinStatsChartMenu *chart_menu : _chart_menus) { + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + delete chart_menu; } _chart_menus.clear(); - if (_window) { - DestroyWindow(_window); - _window = 0; - } - -#ifdef DEVELOP_WINSTATS - // For Winstats developers, exit when the first monitor closes. - exit(0); -#endif + DrawMenuBar(_window); } /** @@ -118,8 +103,6 @@ initialized() { */ void WinStatsMonitor:: got_hello() { - create_window(); - open_strip_chart(0, 0, false); } /** @@ -160,16 +143,13 @@ got_bad_version(int client_major, int client_minor, */ void WinStatsMonitor:: new_collector(int collector_index) { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - WinStatsGraph *graph = (*gi); + for (WinStatsGraph *graph : _graphs) { graph->new_collector(collector_index); } // We might need to update our menus. - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - (*mi)->do_update(); + for (WinStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->do_update(); } } @@ -186,6 +166,10 @@ new_thread(int thread_index) { chart_menu->add_to_menu_bar(_menu_bar, MI_frame_rate_label); _chart_menus.push_back(chart_menu); DrawMenuBar(_window); + + if (thread_index == 0) { + update_status_bar(); + } } /** @@ -203,6 +187,11 @@ new_data(int thread_index, int frame_number) { if (thread_index == 0) { update_status_bar(); } + + if (!_have_data) { + open_default_graphs(); + _have_data = true; + } } /** @@ -213,11 +202,6 @@ new_data(int thread_index, int frame_number) { void WinStatsMonitor:: lost_connection() { nout << "Lost connection to " << get_client_hostname() << "\n"; - - if (_window) { - DestroyWindow(_window); - _window = 0; - } } /** @@ -227,9 +211,8 @@ lost_connection() { void WinStatsMonitor:: idle() { // Check if any of our chart menus need updating. - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - (*mi)->check_update(); + for (WinStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->check_update(); } // Update the frame rate label from the main thread (thread 0). @@ -268,9 +251,7 @@ has_idle() { */ void WinStatsMonitor:: user_guide_bars_changed() { - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - WinStatsGraph *graph = (*gi); + for (WinStatsGraph *graph : _graphs) { graph->user_guide_bars_changed(); } } @@ -288,7 +269,7 @@ get_window() const { */ HFONT WinStatsMonitor:: get_font() const { - return _font; + return ((WinStatsServer *)_server)->get_font(); } /** @@ -296,7 +277,7 @@ get_font() const { */ int WinStatsMonitor:: get_pixel_scale() const { - return _pixel_scale; + return ((WinStatsServer *)_server)->get_pixel_scale(); } /** @@ -304,64 +285,100 @@ get_pixel_scale() const { */ POINT WinStatsMonitor:: get_new_window_pos() { - int offset = _graphs.size() * 10 * _pixel_scale; + WinStatsServer *server = (WinStatsServer *)_server; + int offset = _graphs.size() * 10 * server->get_pixel_scale(); + POINT client_origin = server->get_client_origin(); POINT pt; - pt.x = offset + _client_origin.x; - pt.y = offset + _client_origin.y; + pt.x = offset + client_origin.x; + pt.y = offset + client_origin.y; return pt; } -/** - * Opens a new strip chart showing the indicated data. - */ -void WinStatsMonitor:: -open_strip_chart(int thread_index, int collector_index, bool show_level) { - WinStatsStripChart *graph = - new WinStatsStripChart(this, thread_index, collector_index, show_level); - add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); -} - -/** - * Opens a new piano roll showing the indicated data. - */ -void WinStatsMonitor:: -open_piano_roll(int thread_index) { - WinStatsPianoRoll *graph = new WinStatsPianoRoll(this, thread_index); - add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); -} - -/** - * Opens a new flame graph showing the indicated data. - */ -void WinStatsMonitor:: -open_flame_graph(int thread_index, int collector_index) { - WinStatsFlameGraph *graph = new WinStatsFlameGraph(this, thread_index, collector_index); - add_graph(graph); - - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); -} - /** * Opens a new timeline. */ -void WinStatsMonitor:: +PStatGraph *WinStatsMonitor:: open_timeline() { WinStatsTimeline *graph = new WinStatsTimeline(this); add_graph(graph); + return graph; +} - graph->set_time_units(_time_units); - graph->set_scroll_speed(_scroll_speed); - graph->set_pause(_pause); +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + WinStatsStripChart *graph = + new WinStatsStripChart(this, thread_index, collector_index, show_level); + add_graph(graph); + return graph; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_flame_graph(int thread_index, int collector_index) { + WinStatsFlameGraph *graph = new WinStatsFlameGraph(this, thread_index, collector_index); + add_graph(graph); + return graph; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_piano_roll(int thread_index) { + WinStatsPianoRoll *graph = new WinStatsPianoRoll(this, thread_index); + add_graph(graph); + return graph; +} + +/** + * Opens a dialog to change the given collector color. + */ +void WinStatsMonitor:: +choose_collector_color(int collector_index) { + const LRGBColor ¤t = get_collector_color(collector_index); + static COLORREF custom_colors[16] = {0}; + + CHOOSECOLORA cc = { + sizeof(CHOOSECOLORA), + _window, + 0, + RGB(encode_sRGB_uchar(current[0]), encode_sRGB_uchar(current[1]), encode_sRGB_uchar(current[2])), + (LPDWORD)custom_colors, + CC_FULLOPEN | CC_RGBINIT, + 0, + nullptr, + nullptr, + }; + + if (ChooseColorA(&cc)) { + LRGBColor result( + decode_sRGB_float(GetRValue(cc.rgbResult)), + decode_sRGB_float(GetGValue(cc.rgbResult)), + decode_sRGB_float(GetBValue(cc.rgbResult))); + + set_collector_color(collector_index, result); + + for (WinStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } +} + +/** + * Resets the color of the given collector to the default. + */ +void WinStatsMonitor:: +reset_collector_color(int collector_index) { + clear_collector_color(collector_index); + + for (WinStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } } /** @@ -404,28 +421,9 @@ get_menu_id(const MenuDef &menu_def) { */ void WinStatsMonitor:: set_time_units(int unit_mask) { - _time_units = unit_mask; - - // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - WinStatsGraph *graph = (*gi); - graph->set_time_units(_time_units); + for (WinStatsGraph *graph : _graphs) { + graph->set_time_units(unit_mask); } - - // Now change the checkmark on the pulldown menu. - MENUITEMINFO mii; - memset(&mii, 0, sizeof(mii)); - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STATE; - - mii.fState = ((_time_units & PStatGraph::GBU_ms) != 0) ? - MFS_CHECKED : MFS_UNCHECKED; - SetMenuItemInfo(_options_menu, MI_time_ms, FALSE, &mii); - - mii.fState = ((_time_units & PStatGraph::GBU_hz) != 0) ? - MFS_CHECKED : MFS_UNCHECKED; - SetMenuItemInfo(_options_menu, MI_time_hz, FALSE, &mii); } /** @@ -437,9 +435,7 @@ set_scroll_speed(double scroll_speed) { _scroll_speed = scroll_speed; // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - WinStatsGraph *graph = (*gi); + for (WinStatsGraph *graph : _graphs) { graph->set_scroll_speed(_scroll_speed); } @@ -478,9 +474,7 @@ set_pause(bool pause) { _pause = pause; // First, change all of the open graphs appropriately. - Graphs::iterator gi; - for (gi = _graphs.begin(); gi != _graphs.end(); ++gi) { - WinStatsGraph *graph = (*gi); + for (WinStatsGraph *graph : _graphs) { graph->set_pause(_pause); } @@ -500,6 +494,10 @@ set_pause(bool pause) { void WinStatsMonitor:: add_graph(WinStatsGraph *graph) { _graphs.insert(graph); + + graph->set_time_units(((WinStatsServer *)_server)->get_time_units()); + graph->set_scroll_speed(_scroll_speed); + graph->set_pause(_pause); } /** @@ -515,91 +513,14 @@ remove_graph(WinStatsGraph *graph) { } /** - * Creates the window for this monitor. + * Deletes all open graphs. */ void WinStatsMonitor:: -create_window() { - if (_window) { - return; +remove_all_graphs() { + for (WinStatsGraph *graph : _graphs) { + delete graph; } - - HINSTANCE application = GetModuleHandle(nullptr); - register_window_class(application); - - _menu_bar = CreateMenu(); - - setup_options_menu(); - setup_speed_menu(); - setup_frame_rate_label(); - - ChartMenus::iterator mi; - for (mi = _chart_menus.begin(); mi != _chart_menus.end(); ++mi) { - (*mi)->add_to_menu_bar(_menu_bar, MI_frame_rate_label); - } - - _window_title = get_client_progname() + " on " + get_client_hostname(); - DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | - WS_CLIPSIBLINGS | WS_VISIBLE; - - _window = - CreateWindow(_window_class_name, _window_title.c_str(), window_style, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, _menu_bar, application, 0); - if (!_window) { - nout << "Could not create monitor window!\n"; - exit(1); - } - - SetWindowLongPtr(_window, 0, (LONG_PTR)this); - - create_status_bar(application); - - // For some reason, SW_SHOWNORMAL doesn't always work, but SW_RESTORE seems - // to. - ShowWindow(_window, SW_RESTORE); - SetForegroundWindow(_window); - - _client_origin.x = 0; - _client_origin.y = 0; - ClientToScreen(_window, &_client_origin); -} - -/** - * Creates the "Options" pulldown menu. - */ -void WinStatsMonitor:: -setup_options_menu() { - _options_menu = CreatePopupMenu(); - - MENUITEMINFO mii; - memset(&mii, 0, sizeof(mii)); - mii.cbSize = sizeof(mii); - - mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; - mii.fType = MFT_STRING; - mii.hSubMenu = _options_menu; - - // One day, when there is more than one option here, we will actually - // present this to the user as the "Options" menu. For now, the only option - // we have is time units. mii.dwTypeData = "Options"; - mii.dwTypeData = "Units"; - InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); - - - mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE; - mii.fType = MFT_STRING | MFT_RADIOCHECK; - mii.hbmpChecked = nullptr; - mii.hbmpUnchecked = nullptr; - mii.fState = MFS_UNCHECKED; - mii.wID = MI_time_ms; - mii.dwTypeData = "ms"; - InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); - - mii.wID = MI_time_hz; - mii.dwTypeData = "Hz"; - InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); - - set_time_units(PStatGraph::GBU_ms); + _graphs.clear(); } /** @@ -678,26 +599,6 @@ setup_frame_rate_label() { InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); } -/** - * Sets up a status bar at the bottom of the screen showing assorted level - * values. - */ -void WinStatsMonitor:: -create_status_bar(HINSTANCE application) { - _status_bar = CreateWindow(STATUSCLASSNAME, nullptr, - SBARS_SIZEGRIP | WS_CHILD | WS_VISIBLE, - 0, 0, 0, 0, - _window, (HMENU)0, application, nullptr); - - update_status_bar(); - - - ShowWindow(_status_bar, SW_SHOW); - UpdateWindow(_status_bar); - - InvalidateRect(_status_bar, NULL, TRUE); -} - /** * Updates the status bar. */ @@ -758,8 +659,10 @@ update_status_bar() { HLOCAL hloc = LocalAlloc(LHND, sizeof(int) * (parts.size() + 1)); PINT sizes = (PINT)LocalLock(hloc); + int pixel_scale = get_pixel_scale(); + // Allocate the left-most slot for the framerate indicator. - double offset = 28.0 * _pixel_scale; + double offset = 28.0 * pixel_scale; sizes[0] = (int)(offset + 0.5); if (!parts.empty()) { @@ -769,11 +672,11 @@ update_status_bar() { double width_per_char = 0; GetClientRect(_status_bar, &rect); // Leave room for the grip. - rect.right -= _pixel_scale * 4; + rect.right -= pixel_scale * 4; width_per_char = (rect.right - rect.left - offset) / (double)total_chars; // If we get below a minimum width, start chopping parts off. - while (!parts.empty() && width_per_char < _pixel_scale * 1.5) { + while (!parts.empty() && width_per_char < pixel_scale * 1.5) { total_chars -= parts.back().size(); parts.pop_back(); width_per_char = (rect.right - rect.left - offset) / (double)total_chars; @@ -846,152 +749,89 @@ show_popup_menu(int collector) { } /** - * Registers the window class for the monitor window, if it has not already - * been registered. + * Called when a graph window is iconified or moved in the iconified state. */ void WinStatsMonitor:: -register_window_class(HINSTANCE application) { - if (_window_class_registered) { +calc_iconic_graph_window_pos(WinStatsGraph *moved_graph, int &x, int &y) { + MINIMIZEDMETRICS metrics; + metrics.cbSize = sizeof(metrics); + SystemParametersInfoA(SPI_GETMINIMIZEDMETRICS, 0, &metrics, 0); + + int height = GetThemeSysSize(nullptr, SM_CYSIZE) + GetThemeSysSize(nullptr, SM_CXPADDEDBORDER) * 2; + + RECT client_rect; + GetClientRect(_window, &client_rect); + MapWindowPoints(_window, nullptr, (POINT *)&client_rect, 2); + + // Remove the status bar from the client rectangle. + RECT status_bar_rect; + GetWindowRect(_status_bar, &status_bar_rect); + client_rect.bottom -= (status_bar_rect.bottom - status_bar_rect.top); + + int iconic_offset = 0; + + for (WinStatsGraph *graph : _graphs) { + RECT child_rect; + HWND window = graph->get_window(); + if (graph == moved_graph) { + x = client_rect.left + iconic_offset; + y = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + } + else if (IsIconic(window) && GetWindowRect(window, &child_rect)) { + // Keep it glued to the bottom-left corner of the parent window. + child_rect.left = client_rect.left + iconic_offset; + child_rect.top = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + + SetWindowPos(window, 0, child_rect.left, child_rect.top, 0, 0, + SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + } + } +} + +/** + * Called when the server window is moved. + */ +void WinStatsMonitor:: +handle_window_moved(const RECT &client_rect, int delta_x, int delta_y) { + if (_graphs.empty()) { return; } - WNDCLASS wc; + MINIMIZEDMETRICS metrics; + metrics.cbSize = sizeof(metrics); + SystemParametersInfoA(SPI_GETMINIMIZEDMETRICS, 0, &metrics, 0); - ZeroMemory(&wc, sizeof(WNDCLASS)); - wc.style = 0; - wc.lpfnWndProc = (WNDPROC)static_window_proc; - wc.hInstance = application; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.hbrBackground = (HBRUSH)COLOR_WINDOW; - wc.lpszMenuName = nullptr; - wc.lpszClassName = _window_class_name; + int height = GetThemeSysSize(nullptr, SM_CYSIZE) + GetThemeSysSize(nullptr, SM_CXPADDEDBORDER) * 2; - // Reserve space to associate the this pointer with the window. - wc.cbWndExtra = sizeof(WinStatsMonitor *); + int iconic_offset = 0; - if (!RegisterClass(&wc)) { - nout << "Could not register monitor window class!\n"; - exit(1); - } + for (WinStatsGraph *graph : _graphs) { + WINDOWPLACEMENT wp; + HWND window = graph->get_window(); + if (GetWindowPlacement(window, &wp)) { + if (wp.showCmd == SW_SHOWMINIMIZED) { + // Keep the minimized window title bar glued to the bottom-left + // corner of the parent window. + wp.flags |= WPF_SETMINPOSITION; + wp.ptMinPosition.x = client_rect.left + iconic_offset; + wp.ptMinPosition.y = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + } - _window_class_registered = true; -} - -/** - * - */ -LONG WINAPI WinStatsMonitor:: -static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - WinStatsMonitor *self = (WinStatsMonitor *)GetWindowLongPtr(hwnd, 0); - if (self != nullptr && self->_window == hwnd) { - return self->window_proc(hwnd, msg, wparam, lparam); - } else { - return DefWindowProc(hwnd, msg, wparam, lparam); + // Move the "restored" window (even when it's currently min/maximized). + wp.rcNormalPosition.left += delta_x; + wp.rcNormalPosition.top += delta_y; + wp.rcNormalPosition.right += delta_x; + wp.rcNormalPosition.bottom += delta_y; + SetWindowPlacement(window, &wp); + } } } /** - * - */ -LONG WinStatsMonitor:: -window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - switch (msg) { - case WM_DESTROY: - close(); - break; - - case WM_WINDOWPOSCHANGED: - if (!_graphs.empty()) { - RECT status_bar_rect; - GetWindowRect(_status_bar, &status_bar_rect); - - RECT client_rect; - GetClientRect(_window, &client_rect); - MapWindowPoints(_window, nullptr, (POINT *)&client_rect, 2); - - int delta_x = client_rect.left - _client_origin.x; - int delta_y = client_rect.top - _client_origin.y; - _client_origin.x = client_rect.left; - _client_origin.y = client_rect.top; - - int iconic_offset = 0; - - for (WinStatsGraph *graph : _graphs) { - RECT child_rect; - HWND window = graph->get_window(); - if (GetWindowRect(window, &child_rect)) { - if (IsIconic(window)) { - // Keep it glued to the bottom-left corner of the parent window. - child_rect.left = client_rect.left + iconic_offset; - child_rect.top = client_rect.bottom - (child_rect.bottom - child_rect.top) - (status_bar_rect.bottom - status_bar_rect.top); - iconic_offset += (child_rect.right - child_rect.left); - } else { - child_rect.left += delta_x; - child_rect.top += delta_y; - } - SetWindowPos(window, 0, child_rect.left, child_rect.top, 0, 0, - SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOACTIVATE); - } - } - } - break; - - case WM_SIZE: - if (_status_bar) { - SendMessage(_status_bar, WM_SIZE, 0, 0); - update_status_bar(); - } - break; - - case WM_NOTIFY: - if (((LPNMHDR)lparam)->code == NM_DBLCLK) { - NMMOUSE &mouse = *(NMMOUSE *)lparam; - if (mouse.dwItemSpec == 0) { - open_strip_chart(0, 0, false); - } - else if (mouse.dwItemSpec >= 1 && mouse.dwItemSpec <= _status_bar_collectors.size()) { - int collector = _status_bar_collectors[mouse.dwItemSpec - 1]; - open_strip_chart(0, collector, true); - - // Also open a strip chart for other threads with data for this - // collector. - const PStatClientData *client_data = get_client_data(); - for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { - PStatView &view = get_level_view(collector, thread_index); - if (view.get_net_value() > 0.0) { - open_strip_chart(thread_index, collector, true); - } - } - } - return TRUE; - } - else if (((LPNMHDR)lparam)->code == NM_RCLICK) { - NMMOUSE &mouse = *(NMMOUSE *)lparam; - if (mouse.dwItemSpec >= 1 && - mouse.dwItemSpec <= _status_bar_collectors.size()) { - int collector = _status_bar_collectors[mouse.dwItemSpec - 1]; - show_popup_menu(collector); - } - } - break; - - case WM_COMMAND: - if (HIWORD(wparam) <= 1) { - int menu_id = LOWORD(wparam); - handle_menu_command(menu_id); - return 0; - } - break; - - default: - break; - } - - return DefWindowProc(hwnd, msg, wparam, lparam); -} - -/** - * + * Called when a menu item is clicked. */ void WinStatsMonitor:: handle_menu_command(int menu_id) { @@ -999,14 +839,6 @@ handle_menu_command(int menu_id) { case MI_none: break; - case MI_time_ms: - set_time_units(PStatGraph::GBU_ms); - break; - - case MI_time_hz: - set_time_units(PStatGraph::GBU_hz); - break; - case MI_speed_1: set_scroll_speed(1); break; @@ -1031,6 +863,19 @@ handle_menu_command(int menu_id) { set_pause(!_pause); break; + case MI_graphs_close_all: + remove_all_graphs(); + break; + + case MI_graphs_reopen_default: + remove_all_graphs(); + open_default_graphs(); + break; + + case MI_graphs_save_default: + save_default_graphs(); + break; + default: if (menu_id >= MI_new_chart) { const MenuDef &menu_def = lookup_menu(menu_id); @@ -1055,3 +900,38 @@ handle_menu_command(int menu_id) { } } } + +/** + * Called when a status bar item is double-clicked. + */ +void WinStatsMonitor:: +handle_status_bar_click(int item) { + if (item == 0) { + open_strip_chart(0, 0, false); + } + else if (item >= 1 && item <= _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item - 1]; + open_strip_chart(0, collector, true); + + // Also open a strip chart for other threads with data for this + // collector. + const PStatClientData *client_data = get_client_data(); + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + if (view.get_net_value() > 0.0) { + open_strip_chart(thread_index, collector, true); + } + } + } +} + +/** + * Called when a status bar item is right-clicked. + */ +void WinStatsMonitor:: +handle_status_bar_popup(int item) { + if (item >= 1 && item <= _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item - 1]; + show_popup_menu(collector); + } +} diff --git a/pandatool/src/win-stats/winStatsMonitor.h b/pandatool/src/win-stats/winStatsMonitor.h index 9fa4ccf57f..7d57e890c4 100644 --- a/pandatool/src/win-stats/winStatsMonitor.h +++ b/pandatool/src/win-stats/winStatsMonitor.h @@ -59,6 +59,8 @@ public: WinStatsMonitor(WinStatsServer *server); virtual ~WinStatsMonitor(); + void close(); + virtual std::string get_monitor_name(); virtual void initialized(); @@ -79,10 +81,13 @@ public: int get_pixel_scale() const; POINT get_new_window_pos(); - void open_strip_chart(int thread_index, int collector_index, bool show_level); - void open_piano_roll(int thread_index); - void open_flame_graph(int thread_index, int collector_index = -1); - void open_timeline(); + PStatGraph *open_timeline(); + PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + PStatGraph *open_flame_graph(int thread_index, int collector_index = -1); + PStatGraph *open_piano_roll(int thread_index); + + void choose_collector_color(int collector_index); + void reset_collector_color(int collector_index); const MenuDef &lookup_menu(int menu_id) const; int get_menu_id(const MenuDef &menu_def); @@ -91,22 +96,21 @@ public: void set_scroll_speed(double scroll_speed); void set_pause(bool pause); -private: void add_graph(WinStatsGraph *graph); void remove_graph(WinStatsGraph *graph); + void remove_all_graphs(); - void create_window(); - void setup_options_menu(); +private: void setup_speed_menu(); void setup_frame_rate_label(); - void create_status_bar(HINSTANCE application); void update_status_bar(); void show_popup_menu(int collector); - static void register_window_class(HINSTANCE application); - static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); - LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + void calc_iconic_graph_window_pos(WinStatsGraph *graph, int &x, int &y); + void handle_window_moved(const RECT &client_rect, int delta_x, int delta_y); void handle_menu_command(int menu_id); + void handle_status_bar_click(int item); + void handle_status_bar_popup(int item); typedef pset Graphs; Graphs _graphs; @@ -121,23 +125,16 @@ private: HWND _window; HMENU _menu_bar; - HMENU _options_menu; HMENU _speed_menu; HWND _status_bar; - POINT _client_origin; pvector _status_bar_collectors; std::string _window_title; - int _time_units; double _scroll_speed; bool _pause; - int _pixel_scale; - - HFONT _font; - - static bool _window_class_registered; - static const char * const _window_class_name; + bool _have_data = false; friend class WinStatsGraph; + friend class WinStatsServer; }; #include "winStatsMonitor.I" diff --git a/pandatool/src/win-stats/winStatsPianoRoll.cxx b/pandatool/src/win-stats/winStatsPianoRoll.cxx index fc70f6f203..bb118f3039 100644 --- a/pandatool/src/win-stats/winStatsPianoRoll.cxx +++ b/pandatool/src/win-stats/winStatsPianoRoll.cxx @@ -40,7 +40,8 @@ WinStatsPianoRoll(WinStatsMonitor *monitor, int thread_index) : set_guide_bar_units(get_guide_bar_units() | GBU_show_units); create_window(); - clear_region(); + force_redraw(); + idle(); } /** @@ -126,6 +127,9 @@ on_popup_label(int collector_index) { } AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); } } @@ -232,6 +236,25 @@ idle() { } } +/** + * Returns the current window dimensions. + */ +bool WinStatsPianoRoll:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsPianoRoll:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * */ @@ -255,6 +278,14 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { case 103: WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; } break; diff --git a/pandatool/src/win-stats/winStatsPianoRoll.h b/pandatool/src/win-stats/winStatsPianoRoll.h index 1fc3ac2443..ebf6cb4efb 100644 --- a/pandatool/src/win-stats/winStatsPianoRoll.h +++ b/pandatool/src/win-stats/winStatsPianoRoll.h @@ -55,6 +55,11 @@ protected: virtual void end_draw(); virtual void idle(); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual void additional_window_paint(HDC hdc); diff --git a/pandatool/src/win-stats/winStatsServer.cxx b/pandatool/src/win-stats/winStatsServer.cxx index 91ee84134a..4664d22425 100644 --- a/pandatool/src/win-stats/winStatsServer.cxx +++ b/pandatool/src/win-stats/winStatsServer.cxx @@ -12,12 +12,863 @@ */ #include "winStatsServer.h" +#include "winStatsMenuId.h" #include "winStatsMonitor.h" +#include "pandaVersion.h" +#include "pStatGraph.h" +#include "config_pstatclient.h" + +#include +#include + +bool WinStatsServer::_window_class_registered = false; +const char *const WinStatsServer::_window_class_name = "server"; + +/** + * + */ +WinStatsServer:: +WinStatsServer() { + _last_session = Filename::expand_from( + "$USER_APPDATA/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats"); + _last_session.set_binary(); + + _window = 0; + _menu_bar = 0; + _options_menu = 0; + + _time_units = 0; + + // Create the fonts used for rendering the UI. + NONCLIENTMETRICS metrics = {0}; + metrics.cbSize = sizeof(NONCLIENTMETRICS); + if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0)) { + _font = CreateFontIndirect(&metrics.lfMenuFont); + } else { + _font = (HFONT)GetStockObject(ANSI_VAR_FONT); + } + + create_window(); +} /** * */ PStatMonitor *WinStatsServer:: -make_monitor() { - return new WinStatsMonitor(this); +make_monitor(const NetAddress &address) { + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + std::ostringstream strm; + strm << "PStats Server (connected to " << address << ")"; + std::string title = strm.str(); + SetWindowTextA(_window, title.c_str()); + + _monitor = new WinStatsMonitor(this); + return _monitor; +} + +/** + * Called when connection has been lost. + */ +void WinStatsServer:: +lost_connection(PStatMonitor *monitor) { + // Store a backup now, in case PStats crashes or something. + _last_session.make_dir(); + if (monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + } else { + nout << "Failed to write to " << _last_session << "\n"; + } + + stop_listening(); + + SetWindowTextA(_window, "PStats Server (disconnected)"); +} + +/** + * Starts a new session. + */ +bool WinStatsServer:: +new_session() { + if (!close_session()) { + return false; + } + + if (listen()) { + { + std::ostringstream strm; + strm << "PStats Server (listening on port " << pstats_port << ")"; + std::string title = strm.str(); + SetWindowTextA(_window, title.c_str()); + } + { + std::ostringstream strm; + strm << "Waiting for client to connect on port " << pstats_port << "..."; + std::string title = strm.str(); + int part = -1; + SendMessage(_status_bar, SB_SETPARTS, 1, (LPARAM)&part); + SendMessage(_status_bar, WM_SETTEXT, 0, (LPARAM)title.c_str()); + } + + // Disable the "New Session" menu item. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + + // Disable the "Save Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + + // Enable the "Close Session" menu item. + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + + // Disable the "Export Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; + } + + SetWindowTextA(_window, "PStats Server"); + + std::ostringstream stream; + stream + << "Unable to open port " << pstats_port + << ". Try specifying a different\n" + << "port number using pstats-port in your Config file."; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; +} + +/** + * Offers to open an existing session. + */ +bool WinStatsServer:: +open_session() { + if (!close_session()) { + return false; + } + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + nullptr, + "PStats Session Files (*.pstats)\0*.pstats\0All Files (*.*)\0*.*\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Open Session", + OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, + 0, + }; + + if (GetOpenFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + + WinStatsMonitor *monitor = new WinStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + std::ostringstream stream; + stream << "Failed to load session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; + } + + return false; +} + +/** + * Opens the last session, if any. + */ +bool WinStatsServer:: +open_last_session() { + if (!close_session()) { + return false; + } + + Filename fn = _last_session; + WinStatsMonitor *monitor = new WinStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + std::ostringstream stream; + stream << "Failed to load session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + // If the file contained no graphs, open the default graphs. + if (monitor->_graphs.empty()) { + monitor->open_default_graphs(); + } + + return true; +} + +/** + * Offers to save the current session. + */ +bool WinStatsServer:: +save_session() { + nassertr_always(_monitor != nullptr, true); + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + 0, + "PStats Session Files\0*.pstats\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Save Session", + OFN_OVERWRITEPROMPT, + 0, + 0, + "pstats", + 0, + }; + + if (GetSaveFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + + if (!_monitor->write(fn)) { + std::ostringstream stream; + stream << "Failed to save session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor->get_client_data()->clear_dirty(); + return true; + } + + return false; +} + +/** + * Offers to export the current session as a JSON file. + */ +bool WinStatsServer:: +export_session() { + nassertr_always(_monitor != nullptr, true); + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + 0, + "JSON files\0*.json\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Export Session", + OFN_OVERWRITEPROMPT, + 0, + 0, + "json", + 0, + }; + + if (GetSaveFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_text(); + + std::ofstream stream; + if (!fn.open_write(stream)) { + std::ostringstream stream; + stream << "Failed to open file for export: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + + int pid = _monitor->get_client_pid(); + _monitor->get_client_data()->write_json(stream, std::max(0, pid)); + stream.close(); + return true; + } + + return false; +} + +/** + * Closes the current session. + */ +bool WinStatsServer:: +close_session() { + bool wrote_last_session = false; + + if (_monitor != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->is_dirty()) { + if (!_monitor->has_read_filename()) { + _last_session.make_dir(); + if (_monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + wrote_last_session = true; + } + else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + int result = MessageBox(_window, + "Would you like to save the currently open session?", + "Unsaved Data", MB_YESNOCANCEL | MB_ICONQUESTION); + if (result == IDCANCEL || (result == IDYES && !save_session())) { + return false; + } + } + + _monitor->close(); + _monitor = nullptr; + } + + stop_listening(); + + SetWindowTextA(_window, "PStats Server"); + + int part = -1; + SendMessage(_status_bar, SB_SETPARTS, 1, (LPARAM)&part); + SendMessage(_status_bar, WM_SETTEXT, 0, (LPARAM)""); + + // Enable the "New Session" menu item. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + + if (wrote_last_session) { + // And the "Open Last Session" menu item. + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_open_last, FALSE, &mii); + } + + // Disable the "Save Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + + // Disable the "Close Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + + // Disable the "Export Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; +} + +/** + * Returns the window handle to the server's window. + */ +HWND WinStatsServer:: +get_window() const { + return _window; +} + +/** + * Returns the menu handle to the server's menu bar. + */ +HMENU WinStatsServer:: +get_menu_bar() const { + return _menu_bar; +} + +/** + * Returns the window handle to the server's status bar. + */ +HWND WinStatsServer:: +get_status_bar() const { + return _status_bar; +} + +/** + * Returns the font that should be used for rendering text. + */ +HFONT WinStatsServer:: +get_font() const { + return _font; +} + +/** + * Returns the system DPI scaling as a fraction where 4 = no scaling. + */ +int WinStatsServer:: +get_pixel_scale() const { + return _pixel_scale; +} + +/** + * Returns the origin of the window's client area. + */ +POINT WinStatsServer:: +get_client_origin() const { + return _client_origin; +} + +/** + * + */ +int WinStatsServer:: +get_time_units() const { + return _time_units; +} + + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void WinStatsServer:: +set_time_units(int unit_mask) { + _time_units = unit_mask; + + if (_monitor != nullptr) { + _monitor->set_time_units(unit_mask); + } + + // Now change the checkmark on the pulldown menu. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + + mii.fState = ((_time_units & PStatGraph::GBU_ms) != 0) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_options_menu, MI_time_ms, FALSE, &mii); + + mii.fState = ((_time_units & PStatGraph::GBU_hz) != 0) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_options_menu, MI_time_hz, FALSE, &mii); +} + +/** + * Creates the window for this server. + */ +void WinStatsServer:: +create_window() { + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + _menu_bar = CreateMenu(); + + setup_session_menu(); + setup_options_menu(); + + DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | + WS_CLIPSIBLINGS | WS_VISIBLE; + + _window = + CreateWindow(_window_class_name, "PStats Server", window_style, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, _menu_bar, application, 0); + if (!_window) { + nout << "Could not create server window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + + create_status_bar(application); + + // For some reason, SW_SHOWNORMAL doesn't always work, but SW_RESTORE seems + // to. + ShowWindow(_window, SW_RESTORE); + SetForegroundWindow(_window); + + HDC dc = GetDC(_window); + _pixel_scale = 0; + if (dc) { + _pixel_scale = GetDeviceCaps(dc, LOGPIXELSX) / (96 / 4); + } + if (_pixel_scale <= 0) { + _pixel_scale = 4; + } + ReleaseDC(_window, dc); + + _client_origin.x = 0; + _client_origin.y = 0; + ClientToScreen(_window, &_client_origin); + + // Set up a timer to poll the pstats every so often. + SetTimer(_window, 1, 200, nullptr); +} + +/** + * Creates the "Session" pulldown menu. + */ +void WinStatsServer:: +setup_session_menu() { + _session_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _session_menu; + + mii.dwTypeData = "&Session"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + AppendMenu(_session_menu, MF_STRING, MI_session_new, "&New Session\tCtrl+N"); + AppendMenu(_session_menu, MF_STRING, MI_session_open, "&Open Session...\tCtrl+O"); + + if (_last_session.exists()) { + AppendMenu(_session_menu, MF_STRING, MI_session_open_last, "Open &Last Session"); + } else { + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_open_last, "Open &Last Session"); + } + + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_save, "&Save Session...\tCtrl+S"); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_close, "&Close Session\tCtrl+W"); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_export_json, "&Export as JSON..."); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING, MI_exit, "E&xit"); +} + +/** + * Creates the "Options" pulldown menu. + */ +void WinStatsServer:: +setup_options_menu() { + _options_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _options_menu; + + // One day, when there is more than one option here, we will actually + // present this to the user as the "Options" menu. For now, the only option + // we have is time units. mii.dwTypeData = "Options"; + mii.dwTypeData = "Units"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE; + mii.fType = MFT_STRING | MFT_RADIOCHECK; + mii.hbmpChecked = nullptr; + mii.hbmpUnchecked = nullptr; + mii.fState = MFS_UNCHECKED; + mii.wID = MI_time_ms; + mii.dwTypeData = "ms"; + InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); + + mii.wID = MI_time_hz; + mii.dwTypeData = "Hz"; + InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); + + set_time_units(PStatGraph::GBU_ms); +} + +/** + * Sets up a status bar at the bottom of the screen showing assorted level + * values. + */ +void WinStatsServer:: +create_status_bar(HINSTANCE application) { + _status_bar = CreateWindow(STATUSCLASSNAME, nullptr, + SBARS_SIZEGRIP | WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, + _window, (HMENU)0, application, nullptr); + + ShowWindow(_status_bar, SW_SHOW); + UpdateWindow(_status_bar); + + InvalidateRect(_status_bar, NULL, TRUE); +} + +/** + * Registers the window class for the server window, if it has not already + * been registered. + */ +void WinStatsServer:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsServer *); + + if (!RegisterClass(&wc)) { + nout << "Could not register server window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsServer:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsServer *self = (WinStatsServer *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +/** + * + */ +LONG WinStatsServer:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_TIMER: + poll(); + break; + + case WM_CLOSE: + if (!close_session()) { + return 0; + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_WINDOWPOSCHANGED: + { + RECT client_rect; + GetClientRect(_window, &client_rect); + MapWindowPoints(_window, nullptr, (POINT *)&client_rect, 2); + + if (_monitor != nullptr) { + // Remove the status bar from the client rectangle. + RECT status_bar_rect; + GetWindowRect(_status_bar, &status_bar_rect); + client_rect.bottom -= (status_bar_rect.bottom - status_bar_rect.top); + + int delta_x = client_rect.left - _client_origin.x; + int delta_y = client_rect.top - _client_origin.y; + _client_origin.x = client_rect.left; + _client_origin.y = client_rect.top; + + _monitor->handle_window_moved(client_rect, delta_x, delta_y); + } + else { + _client_origin.x = client_rect.left; + _client_origin.y = client_rect.top; + } + } + break; + + case WM_SIZE: + if (_status_bar) { + SendMessage(_status_bar, WM_SIZE, 0, 0); + if (_monitor != nullptr) { + _monitor->update_status_bar(); + } + } + break; + + case WM_NOTIFY: + if (_monitor != nullptr) { + if (((LPNMHDR)lparam)->code == NM_DBLCLK) { + NMMOUSE &mouse = *(NMMOUSE *)lparam; + _monitor->handle_status_bar_click(mouse.dwItemSpec); + return TRUE; + } + else if (((LPNMHDR)lparam)->code == NM_RCLICK) { + NMMOUSE &mouse = *(NMMOUSE *)lparam; + _monitor->handle_status_bar_popup(mouse.dwItemSpec); + return TRUE; + } + } + break; + + case WM_KEYDOWN: + if ((lparam & 0x40000000) == 0 && GetKeyState(VK_CONTROL) < 0) { + switch (wparam) { + case 'N': + new_session(); + return 0; + + case 'O': + open_session(); + return 0; + + case 'S': + save_session(); + return 0; + + case 'W': + close_session(); + return 0; + + default: + break; + } + } + break; + + case WM_COMMAND: + if (HIWORD(wparam) <= 1) { + int menu_id = LOWORD(wparam); + handle_menu_command(menu_id); + return 0; + } + break; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +void WinStatsServer:: +handle_menu_command(int menu_id) { + switch (menu_id) { + case MI_none: + break; + + case MI_session_new: + new_session(); + break; + + case MI_session_open: + open_session(); + break; + + case MI_session_open_last: + open_last_session(); + break; + + case MI_session_save: + save_session(); + break; + + case MI_session_close: + close_session(); + break; + + case MI_session_export_json: + export_session(); + break; + + case MI_exit: + if (close_session()) { + exit(0); + } + break; + + case MI_time_ms: + set_time_units(PStatGraph::GBU_ms); + break; + + case MI_time_hz: + set_time_units(PStatGraph::GBU_hz); + break; + + default: + if (_monitor != nullptr) { + _monitor->handle_menu_command(menu_id); + } + break; + } } diff --git a/pandatool/src/win-stats/winStatsServer.h b/pandatool/src/win-stats/winStatsServer.h index e1da88f5e2..cb069f46e2 100644 --- a/pandatool/src/win-stats/winStatsServer.h +++ b/pandatool/src/win-stats/winStatsServer.h @@ -16,13 +16,63 @@ #include "pandatoolbase.h" #include "pStatServer.h" +#include "winStatsMonitor.h" /** * The class that owns the main loop, waiting for client connections. */ class WinStatsServer : public PStatServer { public: - virtual PStatMonitor *make_monitor(); + WinStatsServer(); + + virtual PStatMonitor *make_monitor(const NetAddress &address); + virtual void lost_connection(PStatMonitor *monitor); + + bool new_session(); + bool open_session(); + bool open_last_session(); + bool save_session(); + bool export_session(); + bool close_session(); + + HWND get_window() const; + HMENU get_menu_bar() const; + HWND get_status_bar() const; + HFONT get_font() const; + int get_pixel_scale() const; + POINT get_client_origin() const; + + int get_time_units() const; + void set_time_units(int unit_mask); + +private: + void create_window(); + void setup_session_menu(); + void setup_options_menu(); + void create_status_bar(HINSTANCE application); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + void handle_menu_command(int menu_id); + + PT(WinStatsMonitor) _monitor; + + Filename _last_session; + + HWND _window = 0; + HMENU _menu_bar = 0; + HMENU _session_menu = 0; + HMENU _options_menu = 0; + HWND _status_bar; + POINT _client_origin; + int _time_units; + int _pixel_scale; + + HFONT _font; + + static bool _window_class_registered; + static const char * const _window_class_name; }; #endif diff --git a/pandatool/src/win-stats/winStatsStripChart.cxx b/pandatool/src/win-stats/winStatsStripChart.cxx index ccf0707ff4..de977da484 100644 --- a/pandatool/src/win-stats/winStatsStripChart.cxx +++ b/pandatool/src/win-stats/winStatsStripChart.cxx @@ -31,9 +31,7 @@ WinStatsStripChart:: WinStatsStripChart(WinStatsMonitor *monitor, int thread_index, int collector_index, bool show_level) : PStatStripChart(monitor, - show_level ? monitor->get_level_view(0, thread_index) : monitor->get_view(thread_index), - thread_index, - collector_index, + thread_index, collector_index, show_level, monitor->get_pixel_scale() * default_strip_chart_width / 4, monitor->get_pixel_scale() * default_strip_chart_height / 4), WinStatsGraph(monitor) @@ -62,6 +60,8 @@ WinStatsStripChart(WinStatsMonitor *monitor, int thread_index, create_window(); clear_region(); + + update(); } /** @@ -217,6 +217,9 @@ on_popup_label(int collector_index) { } else { AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); } + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); } } @@ -239,6 +242,13 @@ set_collector_index(int collector_index) { if (get_collector_index() != collector_index) { PStatStripChart::set_collector_index(collector_index); + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } + // Redraw the scale labels. RECT rect; GetClientRect(_window, &rect); @@ -377,6 +387,28 @@ end_draw(int from_x, int to_x) { InvalidateRect(_graph_window, &rect, FALSE); } +/** + * Returns the current window dimensions. + */ +bool WinStatsStripChart:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsStripChart:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); + + // Set the state of the checkbox. + SendMessage(_smooth_check_box, BM_SETCHECK, get_average_mode() ? BST_CHECKED : BST_UNCHECKED, 0); +} + /** * */ @@ -413,6 +445,14 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { case 103: WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; } break; diff --git a/pandatool/src/win-stats/winStatsStripChart.h b/pandatool/src/win-stats/winStatsStripChart.h index 3874bfe41f..ac8728a156 100644 --- a/pandatool/src/win-stats/winStatsStripChart.h +++ b/pandatool/src/win-stats/winStatsStripChart.h @@ -61,6 +61,11 @@ protected: virtual void draw_cursor(int x); virtual void end_draw(int from_x, int to_x); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual void additional_window_paint(HDC hdc); diff --git a/pandatool/src/win-stats/winStatsTimeline.cxx b/pandatool/src/win-stats/winStatsTimeline.cxx index 225def4f89..246d6d0f9f 100644 --- a/pandatool/src/win-stats/winStatsTimeline.cxx +++ b/pandatool/src/win-stats/winStatsTimeline.cxx @@ -247,6 +247,25 @@ animate(double time, double dt) { return PStatTimeline::animate(time, dt); } +/** + * Returns the current window dimensions. + */ +bool WinStatsTimeline:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsTimeline:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + /** * */ @@ -417,6 +436,9 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); AppendMenu(popup, MF_STRING, 104, "Open Piano Roll"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 105, "Change Color..."); + AppendMenu(popup, MF_STRING, 106, "Reset Color"); TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _graph_window, nullptr); } } @@ -447,6 +469,14 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { case 104: WinStatsGraph::_monitor->open_piano_roll(_popup_bar._thread_index); return 0; + + case 105: + WinStatsGraph::_monitor->choose_collector_color(_popup_bar._collector_index); + return 0; + + case 106: + WinStatsGraph::_monitor->reset_collector_color(_popup_bar._collector_index); + return 0; } break; diff --git a/pandatool/src/win-stats/winStatsTimeline.h b/pandatool/src/win-stats/winStatsTimeline.h index 61dc07b943..3152a7118b 100644 --- a/pandatool/src/win-stats/winStatsTimeline.h +++ b/pandatool/src/win-stats/winStatsTimeline.h @@ -52,6 +52,11 @@ protected: virtual bool animate(double time, double dt); + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual void additional_window_paint(HDC hdc);