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
This commit is contained in:
rdb 2022-04-06 15:59:14 +02:00
parent 2a0da59dae
commit bb6976d558
74 changed files with 4097 additions and 1048 deletions

View File

@ -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/

View File

@ -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;

View File

@ -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);
}

View File

@ -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 {

View File

@ -8,7 +8,6 @@ set(GTKSTATS_HEADERS
gtkStatsGraph.h
gtkStatsLabel.h
gtkStatsLabelStack.h
gtkStatsMenuId.h
gtkStatsMonitor.h gtkStatsMonitor.I
gtkStatsPianoRoll.h
gtkStatsServer.h

View File

@ -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();

View File

@ -16,8 +16,4 @@
#include "pStatServer.h"
#include <gtk/gtk.h>
extern GtkWidget *main_window;
#endif

View File

@ -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();
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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.
*/

View File

@ -43,6 +43,7 @@ public:
void set_highlight(bool highlight);
bool get_highlight() const;
void update_color();
void update_text(bool use_fullname);
private:

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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 &current = 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);
}
}

View File

@ -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<GtkStatsGraph *> 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<int> _status_bar_collectors;
pvector<GtkWidget *> _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"

View File

@ -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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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:

View File

@ -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;

View File

@ -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);

View File

@ -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) {
}

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<int, PStatView> Views;
Views _views;
@ -110,6 +128,13 @@ private:
typedef pmap<int, LRGBColor> Colors;
Colors _colors;
public:
typedef pset<PStatGraph *> Graphs;
Graphs _timelines;
Graphs _strip_charts;
Graphs _flame_graphs;
Graphs _piano_rolls;
};
#include "pStatMonitor.I"

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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<PT(Connection), PStatReader *> Readers;
Readers _readers;

View File

@ -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 {

View File

@ -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:

View File

@ -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();

View File

@ -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<PStatFrameData *> 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;
};

View File

@ -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});

View File

@ -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();

View File

@ -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));

View File

@ -69,7 +69,7 @@ TextStats() {
*
*/
PStatMonitor *TextStats::
make_monitor() {
make_monitor(const NetAddress &address) {
return new TextMonitor(this, _outFile, _show_raw_data, _json);
}

View File

@ -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();

View File

@ -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")

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -23,6 +23,7 @@
#endif
#include <windows.h>
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);

View File

@ -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.
*/

View File

@ -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;

View File

@ -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.
*/

View File

@ -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);

View File

@ -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

View File

@ -23,45 +23,29 @@
#include "pStatGraph.h"
#include "pStatCollectorDef.h"
#include "convert_srgb.h"
#include <algorithm>
#include <commctrl.h>
bool WinStatsMonitor::_window_class_registered = false;
const char * const WinStatsMonitor::_window_class_name = "monitor";
#include <commdlg.h>
#include <uxtheme.h>
/**
*
*/
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 &current = 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);
}
}

View File

@ -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<WinStatsGraph *> 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<int> _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"

View File

@ -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;

View File

@ -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);

View File

@ -12,12 +12,863 @@
*/
#include "winStatsServer.h"
#include "winStatsMenuId.h"
#include "winStatsMonitor.h"
#include "pandaVersion.h"
#include "pStatGraph.h"
#include "config_pstatclient.h"
#include <commctrl.h>
#include <commdlg.h>
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;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);