pstatserver: Major improvements to PStats server UI, including:

* New "Flame Graph" chart for seeing all collectors in a frame, much easier to read than piano roll
* Update controls, fonts, background color to more modern visual style on Windows
* Proper support for high DPI monitors (with correct scaling)
* Add tooltips for collector labels showing full name and averaged value
* Colors of collectors are now converted to sRGB transfer encoding
* Major performance improvement to piano roll view on Windows
* Movering mouse over labels now highlights the corresponding area in chart
* Label hover effect changed to darkening effect instead of border
* Reimplement graph as static common control on Windows
* Check boxes are now clickable by their label on Windows
* Graph windows have minimum sizes on Windows
This commit is contained in:
rdb 2022-02-02 19:17:02 +01:00
parent f0b81d5bf0
commit 7da70cf939
44 changed files with 2806 additions and 412 deletions

View File

@ -5848,7 +5848,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', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK3'])
TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK3'])
#
# DIRECTORY: pandatool/src/xfileprogs/

View File

@ -4,6 +4,7 @@ endif()
set(GTKSTATS_HEADERS
gtkStatsChartMenu.h
gtkStatsFlameGraph.h
gtkStatsGraph.h
gtkStatsLabel.h
gtkStatsLabelStack.h
@ -17,6 +18,7 @@ set(GTKSTATS_HEADERS
set(GTKSTATS_SOURCES
gtkStats.cxx
gtkStatsChartMenu.cxx
gtkStatsFlameGraph.cxx
gtkStatsGraph.cxx
gtkStatsLabel.cxx
gtkStatsLabelStack.cxx

View File

@ -116,20 +116,34 @@ do_update() {
}
}
// Also a menu item for a piano roll (following a separator).
// Also menu items for flame graph and 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);
GtkStatsMonitor::MenuDef smd(_thread_index, -1, false);
const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd);
{
GtkStatsMonitor::MenuDef smd(_thread_index, -2, 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);
GtkWidget *menu_item = gtk_menu_item_new_with_label("Flame Graph");
gtk_widget_show(menu_item);
gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item);
g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
}
{
GtkStatsMonitor::MenuDef smd(_thread_index, -1, 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_swapped(G_OBJECT(menu_item), "activate",
G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
}
}
/**
@ -189,9 +203,13 @@ handle_menu(gpointer data) {
return;
}
if (menu_def->_collector_index < 0) {
if (menu_def->_collector_index == -2) {
monitor->open_flame_graph(menu_def->_thread_index);
}
else if (menu_def->_collector_index < 0) {
monitor->open_piano_roll(menu_def->_thread_index);
} else {
}
else {
monitor->open_strip_chart(menu_def->_thread_index,
menu_def->_collector_index,
menu_def->_show_level);

View File

@ -0,0 +1,551 @@
/**
* 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 gtkStatsFlameGraph.cxx
* @author rdb
* @date 2022-02-02
*/
#include "gtkStatsFlameGraph.h"
#include "gtkStatsLabel.h"
#include "gtkStatsMonitor.h"
#include "pStatCollectorDef.h"
static const int default_flame_graph_width = 800;
static const int default_flame_graph_height = 150;
/**
*
*/
GtkStatsFlameGraph::
GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index,
int collector_index) :
PStatFlameGraph(monitor, monitor->get_view(thread_index),
thread_index, collector_index,
default_flame_graph_width,
default_flame_graph_height),
GtkStatsGraph(monitor)
{
// Let's show the units on the guide bar labels. There's room.
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
// Put some stuff on top of the graph.
_top_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start(GTK_BOX(_graph_vbox), _top_hbox,
FALSE, FALSE, 0);
_average_check_box = gtk_check_button_new_with_label("Average");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_average_check_box), TRUE);
g_signal_connect(G_OBJECT(_average_check_box), "toggled",
G_CALLBACK(toggled_callback), this);
// Add a DrawingArea widget on top of the graph, to display all of the scale
// units.
_scale_area = gtk_drawing_area_new();
g_signal_connect(G_OBJECT(_scale_area), "draw", G_CALLBACK(draw_callback), this);
_total_label = gtk_label_new("");
gtk_box_pack_start(GTK_BOX(_top_hbox), _average_check_box, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(_top_hbox), _scale_area, TRUE, TRUE, 0);
gtk_box_pack_end(GTK_BOX(_top_hbox), _total_label, FALSE, FALSE, 0);
gtk_widget_set_size_request(_graph_window, default_flame_graph_width,
default_flame_graph_height);
// Add a fixed container to the overlay to allow arbitrary positioning
// of labels therein.
_fixed = gtk_fixed_new();
gtk_overlay_add_overlay(GTK_OVERLAY(_graph_overlay), _fixed);
gtk_widget_show_all(_window);
gtk_widget_show(_window);
// Allow the window to be resized as small as the user likes. We have to do
// this after the window has been shown; otherwise, it will affect the
// window's initial size.
gtk_widget_set_size_request(_window, 0, 0);
clear_region();
}
/**
*
*/
GtkStatsFlameGraph::
~GtkStatsFlameGraph() {
}
/**
* Called whenever a new Collector definition is received from the client.
*/
void GtkStatsFlameGraph::
new_collector(int collector_index) {
GtkStatsGraph::new_collector(collector_index);
}
/**
* Called as each frame's data is made available. There is no guarantee the
* frames will arrive in order, or that all of them will arrive at all. The
* monitor should be prepared to accept frames received out-of-order or
* missing.
*/
void GtkStatsFlameGraph::
new_data(int thread_index, int frame_number) {
if (is_title_unknown()) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str());
}
}
if (!_pause) {
update();
std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
if (_net_value_text != text) {
_net_value_text = text;
gtk_label_set_text(GTK_LABEL(_total_label), _net_value_text.c_str());
}
}
}
/**
* Called when it is necessary to redraw the entire graph.
*/
void GtkStatsFlameGraph::
force_redraw() {
PStatFlameGraph::force_redraw();
}
/**
* Called when the user has resized the window, forcing a resize of the graph.
*/
void GtkStatsFlameGraph::
changed_graph_size(int graph_xsize, int graph_ysize) {
PStatFlameGraph::changed_size(graph_xsize, graph_ysize);
}
/**
* Called when the user selects a new time units from the monitor pulldown
* menu, this should adjust the units for the graph to the indicated mask if
* it is a time-based graph.
*/
void GtkStatsFlameGraph::
set_time_units(int unit_mask) {
int old_unit_mask = get_guide_bar_units();
if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
unit_mask = unit_mask & (GBU_hz | GBU_ms);
unit_mask |= (old_unit_mask & GBU_show_units);
set_guide_bar_units(unit_mask);
gtk_widget_queue_draw(_scale_area);
}
}
/**
* Called when the user single-clicks on a label.
*/
void GtkStatsFlameGraph::
on_click_label(int collector_index) {
int prev_collector_index = get_collector_index();
if (collector_index == prev_collector_index && collector_index != 0) {
// Clicking on the top label means to go up to the parent level.
const PStatClientData *client_data =
GtkStatsGraph::_monitor->get_client_data();
if (client_data->has_collector(collector_index)) {
const PStatCollectorDef &def =
client_data->get_collector_def(collector_index);
collector_index = def._parent_index;
set_collector_index(collector_index);
}
}
else {
// Clicking on any other label means to focus on that.
set_collector_index(collector_index);
}
// Change the root collector to show the full name.
if (prev_collector_index != collector_index) {
auto it = _labels.find(prev_collector_index);
if (it != _labels.end()) {
it->second->update_text(false);
}
it = _labels.find(collector_index);
if (it != _labels.end()) {
it->second->update_text(true);
}
}
}
/**
* Called when the user hovers the mouse over a label.
*/
void GtkStatsFlameGraph::
on_enter_label(int collector_index) {
if (collector_index != _highlighted_index) {
_highlighted_index = collector_index;
}
}
/**
* Called when the user's mouse cursor leaves a label.
*/
void GtkStatsFlameGraph::
on_leave_label(int collector_index) {
if (collector_index == _highlighted_index && collector_index != -1) {
_highlighted_index = -1;
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string GtkStatsFlameGraph::
get_label_tooltip(int collector_index) const {
return PStatFlameGraph::get_label_tooltip(collector_index);
}
/**
* Repositions the labels.
*/
void GtkStatsFlameGraph::
update_labels() {
PStatFlameGraph::update_labels();
}
/**
* Repositions a label. If width is 0, the label should be deleted.
*/
void GtkStatsFlameGraph::
update_label(int collector_index, int row, int x, int width) {
GtkStatsLabel *label;
auto it = _labels.find(collector_index);
if (it != _labels.end()) {
label = it->second;
if (width == 0) {
gtk_container_remove(GTK_CONTAINER(_fixed), label->get_widget());
delete label;
_labels.erase(it);
return;
}
gtk_fixed_move(GTK_FIXED(_fixed), label->get_widget(), x, _ysize - (row + 1) * label->get_height());
}
else {
if (width == 0) {
return;
}
label = new GtkStatsLabel(GtkStatsGraph::_monitor, this, _thread_index, collector_index, false, false);
_labels[collector_index] = label;
gtk_fixed_put(GTK_FIXED(_fixed), label->get_widget(), x, _ysize - (row + 1) * label->get_height());
}
gtk_widget_set_size_request(label->get_widget(), std::min(width, _xsize), label->get_height());
}
/**
* Calls update_guide_bars with parameters suitable to this kind of graph.
*/
void GtkStatsFlameGraph::
normal_guide_bars() {
// We want vaguely 100 pixels between guide bars.
double res = gdk_screen_get_resolution(gdk_screen_get_default());
int num_bars = (int)(get_xsize() / (100.0 * (res > 0 ? res / 96.0 : 1.0)));
_guide_bars.clear();
double dist = get_horizontal_scale() / num_bars;
for (int i = 1; i < num_bars; ++i) {
_guide_bars.push_back(make_guide_bar(i * dist));
}
_guide_bars_changed = true;
}
/**
* Erases the chart area.
*/
void GtkStatsFlameGraph::
clear_region() {
cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0);
cairo_paint(_cr);
}
/**
* Erases the chart area in preparation for drawing a bunch of bars.
*/
void GtkStatsFlameGraph::
begin_draw() {
clear_region();
// Draw in the guide bars.
int num_guide_bars = get_num_guide_bars();
for (int i = 0; i < num_guide_bars; i++) {
draw_guide_bar(_cr, get_guide_bar(i));
}
}
/**
* Called after all the bars have been drawn, this triggers a refresh event to
* draw it to the window.
*/
void GtkStatsFlameGraph::
end_draw() {
gtk_widget_queue_draw(_graph_window);
}
/**
* Called at the end of the draw cycle.
*/
void GtkStatsFlameGraph::
idle() {
}
/**
* 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.
*/
void GtkStatsFlameGraph::
additional_graph_window_paint(cairo_t *cr) {
int num_user_guide_bars = get_num_user_guide_bars();
for (int i = 0; i < num_user_guide_bars; i++) {
draw_guide_bar(cr, get_user_guide_bar(i));
}
}
/**
* Based on the mouse position within the window's client area, look for
* draggable things the mouse might be hovering over and return the
* apprioprate DragMode enum or DM_none if nothing is indicated.
*/
GtkStatsGraph::DragMode GtkStatsFlameGraph::
consider_drag_start(int graph_x, int graph_y) {
if (graph_y >= 0 && graph_y < get_ysize()) {
if (graph_x >= 0 && graph_x < get_xsize()) {
// See if the mouse is over a user-defined guide bar.
int x = graph_x;
double from_height = pixel_to_height(x - 2);
double to_height = pixel_to_height(x + 2);
_drag_guide_bar = find_user_guide_bar(from_height, to_height);
if (_drag_guide_bar >= 0) {
return DM_guide_bar;
}
} else {
// The mouse is left or right of the graph; maybe create a new guide
// bar.
return DM_new_guide_bar;
}
}
return DM_none;
}
/**
* Called when the mouse button is depressed within the graph window.
*/
gboolean GtkStatsFlameGraph::
handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
bool double_click) {
if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) {
if (double_click) {
// Clicking on whitespace in the graph goes to the parent.
on_click_label(get_collector_index());
return TRUE;
}
}
if (_potential_drag_mode == DM_none) {
set_drag_mode(DM_scale);
_drag_scale_start = pixel_to_height(graph_x);
// SetCapture(_graph_window);
return TRUE;
} else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
set_drag_mode(DM_guide_bar);
_drag_start_x = graph_x;
// SetCapture(_graph_window);
return TRUE;
}
return GtkStatsGraph::handle_button_press(widget, graph_x, graph_y,
double_click);
}
/**
* Called when the mouse button is released within the graph window.
*/
gboolean GtkStatsFlameGraph::
handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
if (_drag_mode == DM_scale) {
set_drag_mode(DM_none);
// ReleaseCapture();
return handle_motion(widget, graph_x, graph_y);
} else if (_drag_mode == DM_guide_bar) {
if (graph_x < 0 || graph_x >= get_xsize()) {
remove_user_guide_bar(_drag_guide_bar);
} else {
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
}
set_drag_mode(DM_none);
// ReleaseCapture();
return handle_motion(widget, graph_x, graph_y);
}
return GtkStatsGraph::handle_button_release(widget, graph_x, graph_y);
}
/**
* Called when the mouse is moved within the graph window.
*/
gboolean GtkStatsFlameGraph::
handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
if (_drag_mode == DM_new_guide_bar) {
// We haven't created the new guide bar yet; we won't until the mouse
// comes within the graph's region.
if (graph_x >= 0 && graph_x < get_xsize()) {
set_drag_mode(DM_guide_bar);
_drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x));
return TRUE;
}
}
else if (_drag_mode == DM_guide_bar) {
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
return TRUE;
}
return GtkStatsGraph::handle_motion(widget, graph_x, graph_y);
}
/**
* Draws the line for the indicated guide bar on the graph.
*/
void GtkStatsFlameGraph::
draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar) {
int x = height_to_pixel(bar._height);
if (x > 0 && x < get_xsize() - 1) {
// Only draw it if it's not too close to the top.
switch (bar._style) {
case GBS_target:
cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]);
break;
case GBS_user:
cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]);
break;
case GBS_normal:
cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]);
break;
}
cairo_move_to(cr, x, 0);
cairo_line_to(cr, x, get_ysize());
cairo_stroke(cr);
}
}
/**
* This is called during the servicing of the draw event.
*/
void GtkStatsFlameGraph::
draw_guide_labels(cairo_t *cr) {
int i;
int num_guide_bars = get_num_guide_bars();
for (i = 0; i < num_guide_bars; i++) {
draw_guide_label(cr, get_guide_bar(i));
}
int num_user_guide_bars = get_num_user_guide_bars();
for (i = 0; i < num_user_guide_bars; i++) {
draw_guide_label(cr, get_user_guide_bar(i));
}
}
/**
* Draws the text for the indicated guide bar label at the top of the graph.
*/
void GtkStatsFlameGraph::
draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar) {
switch (bar._style) {
case GBS_target:
cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]);
break;
case GBS_user:
cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]);
break;
case GBS_normal:
cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]);
break;
}
int x = height_to_pixel(bar._height);
const std::string &label = bar._label;
PangoLayout *layout = gtk_widget_create_pango_layout(_window, label.c_str());
int width, height;
pango_layout_get_pixel_size(layout, &width, &height);
if (bar._style != GBS_user) {
double from_height = pixel_to_height(x - width);
double to_height = pixel_to_height(x + width);
if (find_user_guide_bar(from_height, to_height) >= 0) {
// Omit the label: there's a user-defined guide bar in the same space.
g_object_unref(layout);
return;
}
}
if (x >= 0 && x < get_xsize()) {
// Now convert our x to a coordinate within our drawing area.
int junk_y;
// The x coordinate comes from the graph_window.
gtk_widget_translate_coordinates(_graph_window, _scale_area,
x, 0,
&x, &junk_y);
GtkAllocation allocation;
gtk_widget_get_allocation(_scale_area, &allocation);
int this_x = x - width / 2;
if (this_x >= 0 && this_x + width < allocation.width) {
cairo_move_to(cr, this_x, allocation.height - height);
pango_cairo_show_layout(cr, layout);
}
}
g_object_unref(layout);
}
/**
* Called when the average check box is toggled.
*/
void GtkStatsFlameGraph::
toggled_callback(GtkToggleButton *button, gpointer data) {
GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data;
bool active = gtk_toggle_button_get_active(button);
self->set_average_mode(active);
}
/**
* Draws in the scale labels.
*/
gboolean GtkStatsFlameGraph::
draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) {
GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data;
self->draw_guide_labels(cr);
return TRUE;
}

View File

@ -0,0 +1,81 @@
/**
* 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 gtkStatsFlameGraph.h
* @author rdb
* @date 2022-02-02
*/
#ifndef GTKSTATSFLAMEGRAPH_H
#define GTKSTATSFLAMEGRAPH_H
#include "pandatoolbase.h"
#include "gtkStatsGraph.h"
#include "pStatFlameGraph.h"
class GtkStatsLabel;
/**
* A window that draws a flame chart, which shows the collectors explicitly
* stopping and starting, one frame at a time.
*/
class GtkStatsFlameGraph : public PStatFlameGraph, public GtkStatsGraph {
public:
GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index,
int collector_index=0);
virtual ~GtkStatsFlameGraph();
virtual void new_collector(int collector_index);
virtual void new_data(int thread_index, int frame_number);
virtual void force_redraw();
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
virtual void on_click_label(int collector_index);
virtual void on_enter_label(int collector_index);
virtual void on_leave_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
protected:
virtual void update_labels();
virtual void update_label(int collector_index, int row, int x, int width);
virtual void normal_guide_bars();
void clear_region();
virtual void begin_draw();
virtual void end_draw();
virtual void idle();
virtual void additional_graph_window_paint(cairo_t *cr);
virtual DragMode consider_drag_start(int graph_x, int graph_y);
virtual gboolean handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
bool double_click);
virtual gboolean handle_button_release(GtkWidget *widget, int graph_x, int graph_y);
virtual gboolean handle_motion(GtkWidget *widget, int graph_x, int graph_y);
private:
void draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar);
void draw_guide_labels(cairo_t *cr);
void draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar);
static void toggled_callback(GtkToggleButton *button, gpointer data);
static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data);
private:
std::string _net_value_text;
pmap<int, GtkStatsLabel *> _labels;
GtkWidget *_top_hbox;
GtkWidget *_average_check_box;
GtkWidget *_total_label;
GtkWidget *_fixed;
};
#endif

View File

@ -14,6 +14,7 @@
#include "gtkStatsGraph.h"
#include "gtkStatsMonitor.h"
#include "gtkStatsLabelStack.h"
#include "convert_srgb.h"
const double GtkStatsGraph::rgb_light_gray[3] = {
0x9a / (double)0xff, 0x9a / (double)0xff, 0x9a / (double)0xff,
@ -84,15 +85,20 @@ GtkStatsGraph(GtkStatsMonitor *monitor) :
g_signal_connect(G_OBJECT(_graph_window), "motion_notify_event",
G_CALLBACK(motion_notify_event_callback), this);
// An overlay inside the frame, for charts that want to display widgets on
// top of the graph.
_graph_overlay = gtk_overlay_new();
gtk_container_add(GTK_CONTAINER(_graph_overlay), _graph_window);
// A Frame to hold the graph.
GtkWidget *graph_frame = gtk_frame_new(nullptr);
gtk_frame_set_shadow_type(GTK_FRAME(graph_frame), GTK_SHADOW_IN);
gtk_container_add(GTK_CONTAINER(graph_frame), _graph_window);
_graph_frame = gtk_frame_new(nullptr);
gtk_frame_set_shadow_type(GTK_FRAME(_graph_frame), GTK_SHADOW_IN);
gtk_container_add(GTK_CONTAINER(_graph_frame), _graph_overlay);
// A VBox to hold the graph's frame, and any numbers (scale legend? total?)
// above it.
_graph_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_end(GTK_BOX(_graph_vbox), graph_frame,
gtk_box_pack_end(GTK_BOX(_graph_vbox), _graph_frame,
TRUE, TRUE, 0);
// An HBox to hold the graph's frame, and the scale legend to the right of
@ -103,10 +109,11 @@ GtkStatsGraph(GtkStatsMonitor *monitor) :
// An HPaned to hold the label stack and the graph hbox.
_hpaned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
gtk_paned_set_wide_handle(GTK_PANED(_hpaned), TRUE);
gtk_container_add(GTK_CONTAINER(_window), _hpaned);
gtk_container_set_border_width(GTK_CONTAINER(_window), 8);
gtk_paned_pack1(GTK_PANED(_hpaned), _label_stack.get_widget(), TRUE, TRUE);
gtk_paned_pack1(GTK_PANED(_hpaned), _label_stack.get_widget(), FALSE, FALSE);
gtk_paned_pack2(GTK_PANED(_hpaned), _graph_hbox, TRUE, TRUE);
_drag_mode = DM_none;
@ -124,10 +131,11 @@ GtkStatsGraph::
_monitor = nullptr;
release_surface();
Brushes::iterator bi;
for (bi = _brushes.begin(); bi != _brushes.end(); ++bi) {
cairo_pattern_destroy((*bi).second);
for (auto &item : _brushes) {
cairo_pattern_destroy(item.second.first);
cairo_pattern_destroy(item.second.second);
}
_brushes.clear();
_label_stack.clear_labels();
@ -152,13 +160,6 @@ void GtkStatsGraph::
new_data(int thread_index, int frame_number) {
}
/**
* Called when it is necessary to redraw the entire graph.
*/
void GtkStatsGraph::
force_redraw() {
}
/**
* Called when the user has resized the window, forcing a resize of the graph.
*/
@ -207,7 +208,38 @@ user_guide_bars_changed() {
* Called when the user single-clicks on a label.
*/
void GtkStatsGraph::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
}
/**
* Called when the user hovers the mouse over a label.
*/
void GtkStatsGraph::
on_enter_label(int collector_index) {
if (collector_index != _highlighted_index) {
_highlighted_index = collector_index;
force_redraw();
}
}
/**
* Called when the user's mouse cursor leaves a label.
*/
void GtkStatsGraph::
on_leave_label(int collector_index) {
if (collector_index == _highlighted_index && collector_index != -1) {
_highlighted_index = -1;
force_redraw();
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string GtkStatsGraph::
get_label_tooltip(int collector_index) const {
return std::string();
}
/**
@ -232,23 +264,30 @@ close() {
* Returns a pattern suitable for drawing in the indicated collector's color.
*/
cairo_pattern_t *GtkStatsGraph::
get_collector_pattern(int collector_index) {
get_collector_pattern(int collector_index, bool highlight) {
Brushes::iterator bi;
bi = _brushes.find(collector_index);
if (bi != _brushes.end()) {
return (*bi).second;
return highlight ? (*bi).second.second : (*bi).second.first;
}
// Ask the monitor what color this guy should be.
LRGBColor rgb = _monitor->get_collector_color(collector_index);
cairo_pattern_t *pattern = cairo_pattern_create_rgb(rgb[0], rgb[1], rgb[2]);
cairo_pattern_t *pattern = cairo_pattern_create_rgb(
encode_sRGB_float(rgb[0]),
encode_sRGB_float(rgb[1]),
encode_sRGB_float(rgb[2]));
cairo_pattern_t *hpattern = cairo_pattern_create_rgb(
encode_sRGB_float(rgb[0] * 0.75f),
encode_sRGB_float(rgb[1] * 0.75f),
encode_sRGB_float(rgb[2] * 0.75f));
_brushes[collector_index] = pattern;
return pattern;
_brushes[collector_index] = std::make_pair(pattern, hpattern);
return highlight ? hpattern : pattern;
}
/**
* This is called during the servicing of expose_event; it gives a derived
* 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.
*/
void GtkStatsGraph::

View File

@ -44,7 +44,7 @@ public:
virtual void new_collector(int collector_index);
virtual void new_data(int thread_index, int frame_number);
virtual void force_redraw();
virtual void force_redraw()=0;
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
@ -52,11 +52,14 @@ public:
void set_pause(bool pause);
void user_guide_bars_changed();
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
virtual void on_enter_label(int collector_index);
virtual void on_leave_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
protected:
void close();
cairo_pattern_t *get_collector_pattern(int collector_index);
cairo_pattern_t *get_collector_pattern(int collector_index, bool highlight = false);
virtual void additional_graph_window_paint(cairo_t *cr);
virtual DragMode consider_drag_start(int graph_x, int graph_y);
@ -69,12 +72,14 @@ protected:
protected:
// Table of patterns for our various collectors.
typedef pmap<int, cairo_pattern_t *> Brushes;
typedef pmap<int, std::pair<cairo_pattern_t *, cairo_pattern_t *> > Brushes;
Brushes _brushes;
GtkStatsMonitor *_monitor;
GtkWidget *_parent_window;
GtkWidget *_window;
GtkWidget *_graph_frame;
GtkWidget *_graph_overlay;
GtkWidget *_graph_window;
GtkWidget *_graph_hbox;
GtkWidget *_graph_vbox;
@ -88,21 +93,14 @@ protected:
cairo_t *_cr;
int _surface_xsize, _surface_ysize;
/*
COLORREF _dark_color;
COLORREF _light_color;
COLORREF _user_guide_bar_color;
HPEN _dark_pen;
HPEN _light_pen;
HPEN _user_guide_bar_pen;
*/
DragMode _drag_mode;
DragMode _potential_drag_mode;
int _drag_start_x, _drag_start_y;
double _drag_scale_start;
int _drag_guide_bar;
int _highlighted_index = -1;
bool _pause;
static const double rgb_white[3];

View File

@ -14,6 +14,7 @@
#include "gtkStatsLabel.h"
#include "gtkStatsMonitor.h"
#include "gtkStatsGraph.h"
#include "convert_srgb.h"
int GtkStatsLabel::_left_margin = 2;
int GtkStatsLabel::_right_margin = 2;
@ -25,19 +26,14 @@ int GtkStatsLabel::_bottom_margin = 2;
*/
GtkStatsLabel::
GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph,
int thread_index, int collector_index, bool use_fullname) :
int thread_index, int collector_index, bool use_fullname,
bool align_right) :
_monitor(monitor),
_graph(graph),
_thread_index(thread_index),
_collector_index(collector_index)
_collector_index(collector_index),
_align_right(align_right)
{
_widget = nullptr;
if (use_fullname) {
_text = _monitor->get_client_data()->get_collector_fullname(_collector_index);
} else {
_text = _monitor->get_client_data()->get_collector_name(_collector_index);
}
_widget = gtk_drawing_area_new();
gtk_widget_add_events(_widget,
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
@ -50,32 +46,41 @@ GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph,
G_CALLBACK(leave_notify_event_callback), this);
g_signal_connect(G_OBJECT(_widget), "button_press_event",
G_CALLBACK(button_press_event_callback), this);
g_signal_connect(G_OBJECT(_widget), "query-tooltip",
G_CALLBACK(query_tooltip_callback), this);
gtk_widget_set_has_tooltip(_widget, TRUE);
gtk_widget_show(_widget);
// Make up a PangoLayout to represent the text.
_layout = gtk_widget_create_pango_layout(_widget, _text.c_str());
// Set the fg and bg colors on the label.
_bg_color = _monitor->get_collector_color(_collector_index);
LRGBColor rgb = _monitor->get_collector_color(_collector_index);
_bg_color = LRGBColor(
encode_sRGB_float(rgb[0]),
encode_sRGB_float(rgb[1]),
encode_sRGB_float(rgb[2]));
_highlight_bg_color = LRGBColor(
encode_sRGB_float(rgb[0] * 0.75f),
encode_sRGB_float(rgb[1] * 0.75f),
encode_sRGB_float(rgb[2] * 0.75f));
// Should our foreground be black or white?
PN_stdfloat bright = _bg_color.dot(LRGBColor(0.299, 0.587, 0.114));
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);
}
// What are the extents of the text? This determines the minimum size of
// our widget.
int width, height;
pango_layout_get_pixel_size(_layout, &width, &height);
gtk_widget_set_size_request(_widget, width + 8, height);
if (bright >= 0.5 * 0.75) {
_highlight_fg_color = LRGBColor(0);
} else {
_highlight_fg_color = LRGBColor(1);
}
_highlight = false;
_mouse_within = false;
_height = height;
update_text(use_fullname);
}
/**
@ -83,7 +88,10 @@ GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph,
*/
GtkStatsLabel::
~GtkStatsLabel() {
// DeleteObject(_bg_brush);
if (_layout) {
g_object_unref(_layout);
_layout = nullptr;
}
}
/**
@ -137,6 +145,33 @@ get_highlight() const {
return _highlight;
}
/**
* Set to true if the full name of the collector should be shown.
*/
void GtkStatsLabel::
update_text(bool use_fullname) {
const PStatClientData *client_data = _monitor->get_client_data();
if (use_fullname) {
_text = client_data->get_collector_fullname(_collector_index);
} else {
_text = client_data->get_collector_name(_collector_index);
}
// Make up a PangoLayout to represent the text.
if (_layout) {
g_object_unref(_layout);
}
_layout = gtk_widget_create_pango_layout(_widget, _text.c_str());
// What are the extents of the text? This determines the minimum size of
// our widget.
int width, height;
pango_layout_get_pixel_size(_layout, &width, &height);
gtk_widget_set_size_request(_widget, width + 8, height);
_ideal_width = width;
_height = height;
}
/**
* Used internally to indicate whether the mouse is within the label's widget.
*/
@ -155,7 +190,16 @@ gboolean GtkStatsLabel::
draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) {
GtkStatsLabel *self = (GtkStatsLabel *)data;
cairo_set_source_rgb(cr, self->_bg_color[0], self->_bg_color[1], self->_bg_color[2]);
LRGBColor bg, fg;
if (self->_highlight || self->_mouse_within) {
bg = self->_highlight_bg_color;
fg = self->_highlight_fg_color;
} else {
bg = self->_bg_color;
fg = self->_fg_color;
}
cairo_set_source_rgb(cr, bg[0], bg[1], bg[2]);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
@ -163,19 +207,16 @@ draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) {
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_fill(cr);
// Center the text within the rectangle.
int width, height;
pango_layout_get_pixel_size(self->_layout, &width, &height);
cairo_set_source_rgb(cr, self->_fg_color[0], self->_fg_color[1], self->_fg_color[2]);
cairo_move_to(cr, (allocation.width - width) / 2, 0);
pango_cairo_show_layout(cr, self->_layout);
// Now draw the highlight rectangle, if any.
if (self->_highlight || self->_mouse_within) {
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_stroke(cr);
cairo_set_source_rgb(cr, fg[0], fg[1], fg[2]);
if (self->_align_right) {
cairo_move_to(cr, allocation.width - width, 0);
} else {
cairo_move_to(cr, 0, 0);
}
pango_cairo_show_layout(cr, self->_layout);
return TRUE;
}
@ -211,7 +252,21 @@ button_press_event_callback(GtkWidget *widget, GdkEventButton *event,
GtkStatsLabel *self = (GtkStatsLabel *)data;
bool double_click = (event->type == GDK_2BUTTON_PRESS);
if (double_click) {
self->_graph->clicked_label(self->_collector_index);
self->_graph->on_click_label(self->_collector_index);
}
return TRUE;
}
/**
* Called when a tooltip should be displayed.
*/
gboolean GtkStatsLabel::
query_tooltip_callback(GtkWidget *widget, gint x, gint y,
gboolean keyboard_tip, GtkTooltip *tooltip,
gpointer data) {
GtkStatsLabel *self = (GtkStatsLabel *)data;
std::string text = self->_graph->get_label_tooltip(self->_collector_index);
gtk_tooltip_set_text(tooltip, text.c_str());
return !text.empty();
}

View File

@ -30,7 +30,8 @@ class GtkStatsGraph;
class GtkStatsLabel {
public:
GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph,
int thread_index, int collector_index, bool use_fullname);
int thread_index, int collector_index, bool use_fullname,
bool align_right = true);
~GtkStatsLabel();
GtkWidget *get_widget() const;
@ -42,6 +43,8 @@ public:
void set_highlight(bool highlight);
bool get_highlight() const;
void update_text(bool use_fullname);
private:
void set_mouse_within(bool mouse_within);
static gboolean draw_callback(GtkWidget *widget,
@ -55,6 +58,9 @@ private:
static gboolean button_press_event_callback(GtkWidget *widget,
GdkEventButton *event,
gpointer data);
static gboolean query_tooltip_callback(GtkWidget *widget, gint x, gint y,
gboolean keyboard_tip, GtkTooltip *tooltip,
gpointer data);
GtkStatsMonitor *_monitor;
GtkStatsGraph *_graph;
@ -63,19 +69,16 @@ private:
std::string _text;
GtkWidget *_widget;
LRGBColor _fg_color;
LRGBColor _highlight_fg_color;
LRGBColor _bg_color;
PangoLayout *_layout;
/*
COLORREF _bg_color;
COLORREF _fg_color;
HBRUSH _bg_brush;
HBRUSH _highlight_brush;
*/
LRGBColor _highlight_bg_color;
PangoLayout *_layout = nullptr;
int _height;
int _ideal_width;
bool _highlight;
bool _mouse_within;
int _height;
bool _align_right;
static int _left_margin, _right_margin;
static int _top_margin, _bottom_margin;

View File

@ -84,9 +84,7 @@ get_label_collector_index(int label_index) const {
*/
void GtkStatsLabelStack::
clear_labels(bool delete_widgets) {
Labels::iterator li;
for (li = _labels.begin(); li != _labels.end(); ++li) {
GtkStatsLabel *label = (*li);
for (GtkStatsLabel *label : _labels) {
if (delete_widgets) {
gtk_container_remove(GTK_CONTAINER(_widget), label->get_widget());
}

View File

@ -17,6 +17,7 @@
#include "gtkStatsStripChart.h"
#include "gtkStatsChartMenu.h"
#include "gtkStatsPianoRoll.h"
#include "gtkStatsFlameGraph.h"
#include "gtkStatsMenuId.h"
#include "pStatGraph.h"
#include "pStatCollectorDef.h"
@ -251,6 +252,19 @@ open_piano_roll(int thread_index) {
graph->set_pause(_pause);
}
/**
* Opens a new flame graph showing the indicated data.
*/
void GtkStatsMonitor::
open_flame_graph(int thread_index) {
GtkStatsFlameGraph *graph = new GtkStatsFlameGraph(this, thread_index);
add_graph(graph);
graph->set_time_units(_time_units);
graph->set_scroll_speed(_scroll_speed);
graph->set_pause(_pause);
}
/**
* Adds a new MenuDef to the monitor, or returns an existing one if there is
* already one just like it.

View File

@ -66,6 +66,7 @@ public:
GtkWidget *get_window() 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);
const MenuDef *add_menu(const MenuDef &menu_def);

View File

@ -16,7 +16,7 @@
#include "numeric_types.h"
#include "gtkStatsLabelStack.h"
static const int default_piano_roll_width = 400;
static const int default_piano_roll_width = 600;
static const int default_piano_roll_height = 200;
/**
@ -126,7 +126,7 @@ set_time_units(int unit_mask) {
* Called when the user single-clicks on a label.
*/
void GtkStatsPianoRoll::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
if (collector_index >= 0) {
GtkStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
}
@ -167,6 +167,18 @@ begin_draw() {
}
}
/**
* Should be overridden by the user class. This hook will be called before
* drawing any one row of bars. These bars correspond to the collector whose
* index is get_row_collector(row), and in the color get_row_color(row).
*/
void GtkStatsPianoRoll::
begin_row(int row) {
int collector_index = get_label_collector(row);
cairo_set_source(_cr, get_collector_pattern(collector_index,
_highlighted_index == collector_index));
}
/**
* Draws a single bar on the chart.
*/
@ -176,8 +188,6 @@ draw_bar(int row, int from_x, int to_x) {
int y = _label_stack.get_label_y(row, _graph_window);
int height = _label_stack.get_label_height(row);
int collector_index = get_label_collector(row);
cairo_set_source(_cr, get_collector_pattern(collector_index));
cairo_rectangle(_cr, from_x, y - height + 2, to_x - from_x, height - 4);
cairo_fill(_cr);
}
@ -203,7 +213,7 @@ idle() {
}
/**
* This is called during the servicing of expose_event; it gives a derived
* 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.
*/
void GtkStatsPianoRoll::
@ -251,7 +261,7 @@ handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
if (double_click) {
// Double-clicking on a color bar in the graph is the same as double-
// clicking on the corresponding label.
clicked_label(get_collector_under_pixel(graph_x, graph_y));
on_click_label(get_collector_under_pixel(graph_x, graph_y));
return TRUE;
}
@ -303,7 +313,9 @@ gboolean GtkStatsPianoRoll::
handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
// When the mouse is over a color bar, highlight it.
_label_stack.highlight_label(get_collector_under_pixel(graph_x, graph_y));
int collector_index = get_collector_under_pixel(graph_x, graph_y);
_label_stack.highlight_label(collector_index);
on_enter_label(collector_index);
/*
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
@ -320,6 +332,7 @@ handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
} else {
// If the mouse is in some drag mode, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
}
if (_drag_mode == DM_scale) {
@ -409,7 +422,7 @@ draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar) {
}
/**
* This is called during the servicing of expose_event.
* This is called during the servicing of the draw event.
*/
void GtkStatsPianoRoll::
draw_guide_labels(cairo_t *cr) {

View File

@ -38,12 +38,13 @@ public:
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
void set_horizontal_scale(double time_width);
protected:
void clear_region();
virtual void begin_draw();
virtual void begin_row(int row);
virtual void draw_bar(int row, int from_x, int to_x);
virtual void end_draw();
virtual void idle();

View File

@ -68,8 +68,15 @@ GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index,
G_CALLBACK(draw_callback), this);
gtk_box_pack_start(GTK_BOX(_graph_hbox), _scale_area,
FALSE, FALSE, 0);
gtk_widget_set_size_request(_scale_area, 40, 0);
// Make it wide enough to display a typical label.
{
PangoLayout *layout = gtk_widget_create_pango_layout(_window, "99 ms");
int width, height;
pango_layout_get_pixel_size(layout, &width, &height);
gtk_widget_set_size_request(_scale_area, width, 0);
g_object_unref(layout);
}
gtk_widget_set_size_request(_graph_window, default_strip_chart_width,
default_strip_chart_height);
@ -175,7 +182,7 @@ set_scroll_speed(double scroll_speed) {
* Called when the user single-clicks on a label.
*/
void GtkStatsStripChart::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
if (collector_index < 0) {
// Clicking on whitespace in the graph is the same as clicking on the top
// label.
@ -202,6 +209,15 @@ clicked_label(int collector_index) {
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string GtkStatsStripChart::
get_label_tooltip(int collector_index) const {
return PStatStripChart::get_label_tooltip(collector_index);
}
/**
* Changes the value the height of the vertical axis represents. This may
* force a redraw.
@ -287,7 +303,8 @@ draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) {
for (fi = fdata.begin(); fi != fdata.end(); ++fi) {
const ColorData &cd = (*fi);
overall_time += cd._net_value;
cairo_set_source(_cr, get_collector_pattern(cd._collector_index));
cairo_set_source(_cr, get_collector_pattern(cd._collector_index,
_highlighted_index == cd._collector_index));
if (overall_time > get_vertical_scale()) {
// Off the top. Go ahead and clamp it by hand, in case it's so far off
@ -346,7 +363,7 @@ end_draw(int from_x, int to_x) {
}
/**
* This is called during the servicing of expose_event; it gives a derived
* 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.
*/
void GtkStatsStripChart::
@ -418,7 +435,7 @@ handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
if (double_click) {
// Double-clicking on a color bar in the graph is the same as double-
// clicking on the corresponding label.
clicked_label(get_collector_under_pixel(graph_x, graph_y));
on_click_label(get_collector_under_pixel(graph_x, graph_y));
return TRUE;
}
@ -473,23 +490,14 @@ handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
if (_drag_mode == DM_none && _potential_drag_mode == DM_none &&
graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) {
// When the mouse is over a color bar, highlight it.
_label_stack.highlight_label(get_collector_under_pixel(graph_x, graph_y));
/*
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
// window.
TRACKMOUSEEVENT tme = {
sizeof(TRACKMOUSEEVENT),
TME_LEAVE,
_graph_window,
0
};
TrackMouseEvent(&tme);
*/
} else {
int collector_index = get_collector_under_pixel(graph_x, graph_y);
_label_stack.highlight_label(collector_index);
on_enter_label(collector_index);
}
else {
// If the mouse is in some drag mode, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
}
if (_drag_mode == DM_scale) {
@ -546,7 +554,7 @@ draw_guide_bar(cairo_t *cr, int from_x, int to_x,
}
/**
* This is called during the servicing of expose_event.
* This is called during the servicing of the draw event.
*/
void GtkStatsStripChart::
draw_guide_labels(cairo_t *cr) {

View File

@ -40,7 +40,8 @@ public:
virtual void set_time_units(int unit_mask);
virtual void set_scroll_speed(double scroll_speed);
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
void set_vertical_scale(double value_height);
protected:

View File

@ -1,5 +1,6 @@
#include "gtkStats.cxx"
#include "gtkStatsChartMenu.cxx"
#include "gtkStatsFlameGraph.cxx"
#include "gtkStatsGraph.cxx"
#include "gtkStatsLabel.cxx"
#include "gtkStatsLabelStack.cxx"

View File

@ -4,6 +4,7 @@ endif()
set(P3PSTATSERVER_HEADERS
pStatClientData.h
pStatFlameGraph.h pStatFlameGraph.I
pStatGraph.h pStatGraph.I
pStatListener.h
pStatMonitor.h pStatMonitor.I
@ -17,7 +18,9 @@ set(P3PSTATSERVER_HEADERS
)
set(P3PSTATSERVER_SOURCES
pStatClientData.cxx pStatGraph.cxx
pStatClientData.cxx
pStatFlameGraph.cxx
pStatGraph.cxx
pStatListener.cxx
pStatMonitor.cxx pStatPianoRoll.cxx
pStatReader.cxx pStatServer.cxx

View File

@ -1,4 +1,5 @@
#include "pStatClientData.cxx"
#include "pStatFlameGraph.cxx"
#include "pStatGraph.cxx"
#include "pStatListener.cxx"
#include "pStatMonitor.cxx"

View File

@ -0,0 +1,89 @@
/**
* 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 pStatFlameGraph.I
* @author rdb
* @date 2022-01-28
*/
/**
* Returns the View this chart represents.
*/
INLINE PStatView &PStatFlameGraph::
get_view() const {
return _view;
}
/**
* Returns the particular collector whose data this strip chart reflects.
*/
INLINE int PStatFlameGraph::
get_collector_index() const {
return _collector_index;
}
/**
* Returns the amount of total time the width of the horizontal axis
* represents.
*/
INLINE double PStatFlameGraph::
get_horizontal_scale() const {
return _time_width;
}
/**
* Changes the average_mode flag. When true, the strip chart will average out
* the color values over pstats_average_time seconds, which hides spikes and
* makes the overall trends easier to read. When false, the strip chart shows
* the actual data as it is happening.
*/
INLINE void PStatFlameGraph::
set_average_mode(bool average_mode) {
if (_average_mode != average_mode) {
_average_mode = average_mode;
force_redraw();
}
}
/**
* Returns the current state of the average_mode flag. When true, the strip
* chart will average out the color values over pstats_average_time seconds,
* which hides spikes and makes the overall trends easier to read. When
* false, the strip chart shows the actual data as it is happening.
*/
INLINE bool PStatFlameGraph::
get_average_mode() const {
return _average_mode;
}
/**
* Converts a value (i.e. a "height" in the strip chart) to a horizontal
* pixel offset.
*/
INLINE int PStatFlameGraph::
height_to_pixel(double value) const {
return (int)((double)_xsize * value / _time_width);
}
/**
* Converts a horizontal pixel offset to a value (a "height" in the strip
* chart).
*/
INLINE double PStatFlameGraph::
pixel_to_height(int x) const {
return _time_width * (double)x / (double)_xsize;
}
/**
* Returns true if get_title_text() has never yet returned an answer, false if
* it has.
*/
INLINE bool PStatFlameGraph::
is_title_unknown() const {
return _title_unknown;
}

View File

@ -0,0 +1,297 @@
/**
* 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 pStatFlameGraph.cxx
* @author rdb
* @date 2022-01-28
*/
#include "pStatFlameGraph.h"
#include "pStatFrameData.h"
#include "pStatCollectorDef.h"
#include "string_utils.h"
#include "config_pstatclient.h"
#include <algorithm>
#include <sstream>
/**
*
*/
PStatFlameGraph::
PStatFlameGraph(PStatMonitor *monitor, PStatView &view,
int thread_index, int collector_index, int xsize, int ysize) :
PStatGraph(monitor, xsize, ysize),
_thread_index(thread_index),
_view(view),
_collector_index(collector_index)
{
_average_mode = true;
_average_cursor = 0;
_time_width = 1.0 / pstats_target_frame_rate;
_current_frame = -1;
_title_unknown = true;
_guide_bar_units = GBU_ms | GBU_hz | GBU_show_units;
normal_guide_bars();
}
/**
*
*/
PStatFlameGraph::
~PStatFlameGraph() {
}
/**
* Updates the chart with the latest data.
*/
void PStatFlameGraph::
update() {
const PStatClientData *client_data = _monitor->get_client_data();
// Don't bother to update the thread data until we know at least something
// about the collectors and threads.
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();
if (frame_number != _current_frame) {
_current_frame = frame_number;
update_data();
force_redraw();
update_labels();
}
}
}
idle();
}
/**
* Changes the collector represented by this flame graph. This may force a
* redraw.
*/
void PStatFlameGraph::
set_collector_index(int collector_index) {
if (_collector_index != collector_index) {
_collector_index = collector_index;
_title_unknown = true;
update_data();
force_redraw();
update_labels();
}
}
/**
* Returns the text suitable for the title label on the top line.
*/
std::string PStatFlameGraph::
get_title_text() {
std::string text;
_title_unknown = false;
const PStatClientData *client_data = _monitor->get_client_data();
if (client_data->has_collector(_collector_index)) {
text = client_data->get_collector_fullname(_collector_index);
text += " flame graph";
} else {
_title_unknown = true;
}
if (_thread_index != 0) {
if (client_data->has_thread(_thread_index)) {
text += " (" + client_data->get_thread_name(_thread_index) + " thread)";
} else {
_title_unknown = true;
}
}
return text;
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string PStatFlameGraph::
get_label_tooltip(int collector_index) const {
const PStatClientData *client_data = _monitor->get_client_data();
if (!client_data->has_collector(collector_index)) {
return std::string();
}
std::ostringstream text;
text << client_data->get_collector_fullname(collector_index);
Data::const_iterator it = _data.find(collector_index);
if (it != _data.end()) {
const CollectorData &cd = it->second;
text << " (" << format_number(cd._net_value, get_guide_bar_units(), get_guide_bar_unit_name()) << ")";
}
return text.str();
}
/**
*
*/
void PStatFlameGraph::
update_data() {
// First clear the net values, so we'll know which labels should be deleted.
for (auto it = _data.begin(); it != _data.end(); ++it) {
it->second._net_value = 0;
}
_view.set_to_frame(_current_frame);
const PStatViewLevel *level = _view.get_level(_collector_index);
double offset = 0;
update_data(level, 0, offset);
_time_width = (offset != 0) ? offset : 1.0 / pstats_target_frame_rate;
normal_guide_bars();
// Cycle through the ring buffers.
_average_cursor = (_average_cursor + 1) % _num_average_frames;
}
/**
* Recursive helper for get_frame_data.
*/
void PStatFlameGraph::
update_data(const PStatViewLevel *level, int depth, double &offset) {
double net_value = level->get_net_value();
Data::iterator it;
bool inserted;
std::tie(it, inserted) = _data.insert(std::make_pair(level->get_collector(), CollectorData()));
CollectorData &cd = it->second;
cd._offset = offset;
cd._depth = depth;
if (inserted || !_average_mode) {
// Initialize the values array.
for (double &v : cd._values) {
v = net_value;
}
cd._net_value = net_value;
} else {
cd._values[_average_cursor] = net_value;
// Calculate the average.
cd._net_value = 0;
for (double value : cd._values) {
cd._net_value += value;
}
cd._net_value /= _num_average_frames;
}
if (cd._net_value != 0.0) {
cd._net_value = std::max(cd._net_value, 0.0);
double child_offset = offset;
offset += cd._net_value;
int num_children = level->get_num_children();
for (int i = 0; i < num_children; i++) {
const PStatViewLevel *child = level->get_child(i);
update_data(child, depth + 1, child_offset);
}
}
}
/**
* 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
* reflect the new size.
*/
void PStatFlameGraph::
changed_size(int xsize, int ysize) {
if (xsize != _xsize || ysize != _ysize) {
_xsize = xsize;
_ysize = ysize;
normal_guide_bars();
force_redraw();
update_labels();
}
}
/**
* To be called by the user class when the whole thing needs to be redrawn for
* some reason.
*/
void PStatFlameGraph::
force_redraw() {
begin_draw();
end_draw();
}
/**
* Resets the list of labels.
*/
void PStatFlameGraph::
update_labels() {
for (auto it = _data.begin(); it != _data.end(); ++it) {
int collector_index = it->first;
const CollectorData &cd = it->second;
update_label(collector_index, cd._depth, height_to_pixel(cd._offset), height_to_pixel(cd._net_value));
}
}
/**
* Calls update_guide_bars with parameters suitable to this kind of graph.
*/
void PStatFlameGraph::
normal_guide_bars() {
// We want vaguely 100 pixels between guide bars.
int num_bars = get_xsize() / 100;
_guide_bars.clear();
double dist = _time_width / num_bars;
for (int i = 1; i < num_bars; ++i) {
_guide_bars.push_back(make_guide_bar(i * dist));
}
_guide_bars_changed = true;
}
/**
* Should be overridden by the user class. This hook will be called before
* drawing any bars in the chart.
*/
void PStatFlameGraph::
begin_draw() {
}
/**
* Should be overridden by the user class. This hook will be called after
* drawing a series of color bars in the chart.
*/
void PStatFlameGraph::
end_draw() {
}
/**
* Should be overridden by the user class to perform any other updates might
* be necessary after the bars have been redrawn.
*/
void PStatFlameGraph::
idle() {
}

View File

@ -0,0 +1,107 @@
/**
* 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 pStatFlameGraph.h
* @author rdb
* @date 2022-01-28
*/
#ifndef PSTATFLAMEGRAPH_H
#define PSTATFLAMEGRAPH_H
#include "pandatoolbase.h"
#include "pStatGraph.h"
#include "pStatMonitor.h"
#include "pStatClientData.h"
#include "pmap.h"
#include "pdeque.h"
class PStatFrameData;
/**
* This is an abstract class that presents the interface for drawing a flame
* chart: it shows the time spent in each of a number of collectors
* as a horizontal bar of color, with time as the horizontal axis.
*
* This class just manages all the flame chart logic; the actual nuts and bolts
* of drawing pixels is left to a user-derived class.
*/
class PStatFlameGraph : public PStatGraph {
public:
PStatFlameGraph(PStatMonitor *monitor, PStatView &view,
int thread_index, int collector_index,
int xsize, int ysize);
virtual ~PStatFlameGraph();
void update();
INLINE PStatView &get_view() const;
INLINE int get_collector_index() const;
void set_collector_index(int collector_index);
INLINE double get_horizontal_scale() const;
INLINE void set_average_mode(bool average_mode);
INLINE bool get_average_mode() const;
INLINE int height_to_pixel(double value) const;
INLINE double pixel_to_height(int y) const;
INLINE bool is_title_unknown() const;
std::string get_title_text();
std::string get_label_tooltip(int collector_index) const;
protected:
static const size_t _num_average_frames = 200;
struct CollectorData {
double _offset;
double _net_value;
int _depth;
// This is updated like a ring buffer, initialized with all the same value
// at first, then always at _average_cursor.
double _values[_num_average_frames];
};
typedef pmap<int, CollectorData> Data;
void update_data();
void update_data(const PStatViewLevel *level, int depth, double &offset);
void changed_size(int xsize, int ysize);
void force_redraw();
virtual void update_labels();
virtual void update_label(int collector_index, int row, int x, int width)=0;
virtual void normal_guide_bars();
virtual void begin_draw();
virtual void end_draw();
virtual void idle();
private:
void compute_page(const PStatFrameData &frame_data);
protected:
int _thread_index;
private:
PStatView &_view;
int _collector_index;
bool _average_mode;
size_t _average_cursor;
Data _data;
double _time_width;
int _current_frame;
bool _title_unknown;
};
#include "pStatFlameGraph.I"
#endif

View File

@ -156,6 +156,15 @@ pixel_to_height(int x) const {
return _value_height * (double)(get_ysize() - x) / (double)get_ysize();
}
/**
* Returns true if get_title_text() has never yet returned an answer, false if
* it has.
*/
INLINE bool PStatStripChart::
is_title_unknown() const {
return _title_unknown;
}
/**
* Returns true if the indicated collector appears anywhere on the chart at
* the current time, false otherwise.

View File

@ -284,12 +284,71 @@ get_title_text() {
}
/**
* Returns true if get_title_text() has never yet returned an answer, false if
* it has.
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
bool PStatStripChart::
is_title_unknown() const {
return _title_unknown;
std::string PStatStripChart::
get_label_tooltip(int collector_index) const {
const PStatClientData *client_data = _monitor->get_client_data();
if (!client_data->has_collector(collector_index)) {
return std::string();
}
std::ostringstream text;
text << client_data->get_collector_fullname(collector_index);
double value;
if (collector_index == _collector_index) {
value = get_average_net_value();
}
else {
const PStatThreadData *thread_data = _view.get_thread_data();
int now_i, then_i;
if (!thread_data->get_elapsed_frames(then_i, now_i)) {
return text.str();
}
double now = _time_width + _start_time;
double then = now - pstats_average_time;
double net_value = 0.0f;
double net_time = 0.0f;
// We start with just the portion of frame then_i that actually does fall
// within our "then to now" window (usually some portion of it will).
const PStatFrameData &frame_data = thread_data->get_frame(then_i);
if (frame_data.get_end() > then) {
double this_time = (frame_data.get_end() - then);
_view.set_to_frame(frame_data);
const PStatViewLevel *level = _view.get_level(collector_index);
if (level != nullptr) {
net_value += level->get_net_value() * this_time;
net_time += this_time;
}
}
// Then we get all of each of the remaining frames.
for (int frame_number = then_i + 1;
frame_number <= now_i;
frame_number++) {
const PStatFrameData &frame_data = thread_data->get_frame(frame_number);
double this_time = frame_data.get_net_time();
_view.set_to_frame(frame_data);
const PStatViewLevel *level = _view.get_level(collector_index);
if (level != nullptr) {
net_value += level->get_net_value() * this_time;
net_time += this_time;
}
}
if (net_time == 0) {
return text.str();
}
value = net_value / net_time;
}
text << " (" << format_number(value, get_guide_bar_units(), get_guide_bar_unit_name()) << ")";
return text.str();
}
/**

View File

@ -68,8 +68,9 @@ public:
INLINE int height_to_pixel(double value) const;
INLINE double pixel_to_height(int y) const;
std::string get_title_text();
bool is_title_unknown() const;
std::string get_title_text();
std::string get_label_tooltip(int collector_index) const;
protected:
class ColorData {
@ -89,6 +90,7 @@ protected:
void compute_average_pixel_data(PStatStripChart::FrameData &result,
int &then_i, int &now_i, double now);
double get_net_value(int frame_number) const;
double get_net_value(int frame_number, int collector_index) const;
double get_average_net_value() const;
void changed_size(int xsize, int ysize);

View File

@ -4,9 +4,10 @@ endif()
set(WINSTATS_HEADERS
winStatsChartMenu.h
winStatsFlameGraph.h
winStatsGraph.h
winStats.h
winStatsLabel.h
winStatsLabel.h winStatsLabel.I
winStatsLabelStack.h
winStatsMenuId.h
winStatsMonitor.h winStatsMonitor.I
@ -18,6 +19,7 @@ set(WINSTATS_HEADERS
set(WINSTATS_SOURCES
winStatsChartMenu.cxx
winStats.cxx
winStatsFlameGraph.cxx
winStatsGraph.cxx
winStatsLabel.cxx
winStatsLabelStack.cxx
@ -29,7 +31,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)
target_link_libraries(win-stats p3progbase p3pstatserver comctl32.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

@ -20,6 +20,11 @@
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#include <commctrl.h>
#include <shellscalingapi.h>
// 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;
@ -82,6 +87,15 @@ create_toplevel_window(HINSTANCE application) {
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
// Initialize commctl32.dll.
INITCOMMONCONTROLSEX icc;
icc.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCommonControlsEx(&icc);
// Signal DPI awareness.
SetProcessDPIAware();
HINSTANCE application = GetModuleHandle(nullptr);
HWND toplevel_window = create_toplevel_window(application);

View File

@ -125,19 +125,32 @@ do_update() {
}
}
// Also a menu item for a piano roll (following a separator).
// Also menu items for flame graph and 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, false);
int menu_id = _monitor->get_menu_id(menu_def);
{
WinStatsMonitor::MenuDef menu_def(_thread_index, -2, 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_STRING | MIIM_FTYPE | MIIM_ID;
mii.fType = MFT_STRING;
mii.wID = menu_id;
mii.dwTypeData = "Flame Graph";
InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii);
}
{
WinStatsMonitor::MenuDef menu_def(_thread_index, -1, 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);
}
}
/**

View File

@ -0,0 +1,639 @@
/**
* 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 winStatsFlameGraph.cxx
* @author rdb
* @date 2022-01-28
*/
#include "winStatsFlameGraph.h"
#include "winStatsLabel.h"
#include "winStatsMonitor.h"
#include "pStatCollectorDef.h"
#include <commctrl.h>
static const int default_flame_graph_width = 800;
static const int default_flame_graph_height = 150;
bool WinStatsFlameGraph::_window_class_registered = false;
const char * const WinStatsFlameGraph::_window_class_name = "flame";
/**
*
*/
WinStatsFlameGraph::
WinStatsFlameGraph(WinStatsMonitor *monitor, int thread_index,
int collector_index) :
PStatFlameGraph(monitor, monitor->get_view(thread_index),
thread_index, collector_index,
monitor->get_pixel_scale() * default_flame_graph_width / 4,
monitor->get_pixel_scale() * default_flame_graph_height / 4),
WinStatsGraph(monitor)
{
_left_margin = _pixel_scale * 2;
_right_margin = _pixel_scale * 2;
_top_margin = _pixel_scale * 6;
_bottom_margin = _pixel_scale * 2;
// Let's show the units on the guide bar labels. There's room.
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
_average_check_box = 0;
create_window();
clear_region();
}
/**
*
*/
WinStatsFlameGraph::
~WinStatsFlameGraph() {
}
/**
* Called as each frame's data is made available. There is no guarantee the
* frames will arrive in order, or that all of them will arrive at all. The
* monitor should be prepared to accept frames received out-of-order or
* missing.
*/
void WinStatsFlameGraph::
new_data(int thread_index, int frame_number) {
if (is_title_unknown()) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
SetWindowText(_window, window_title.c_str());
}
}
if (!_pause) {
update();
std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
if (_net_value_text != text) {
_net_value_text = text;
RECT rect;
GetClientRect(_window, &rect);
rect.bottom = _top_margin;
InvalidateRect(_window, &rect, TRUE);
}
}
}
/**
* Called when it is necessary to redraw the entire graph.
*/
void WinStatsFlameGraph::
force_redraw() {
PStatFlameGraph::force_redraw();
}
/**
* Called when the user has resized the window, forcing a resize of the graph.
*/
void WinStatsFlameGraph::
changed_graph_size(int graph_xsize, int graph_ysize) {
PStatFlameGraph::changed_size(graph_xsize, graph_ysize);
}
/**
* Called when the user selects a new time units from the monitor pulldown
* menu, this should adjust the units for the graph to the indicated mask if
* it is a time-based graph.
*/
void WinStatsFlameGraph::
set_time_units(int unit_mask) {
int old_unit_mask = get_guide_bar_units();
if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
unit_mask = unit_mask & (GBU_hz | GBU_ms);
unit_mask |= (old_unit_mask & GBU_show_units);
set_guide_bar_units(unit_mask);
RECT rect;
GetClientRect(_window, &rect);
rect.left = _right_margin;
InvalidateRect(_window, &rect, TRUE);
}
}
/**
* Called when the user single-clicks on a label.
*/
void WinStatsFlameGraph::
on_click_label(int collector_index) {
int prev_collector_index = get_collector_index();
if (collector_index == prev_collector_index && collector_index != 0) {
// Clicking on the top label means to go up to the parent level.
const PStatClientData *client_data =
WinStatsGraph::_monitor->get_client_data();
if (client_data->has_collector(collector_index)) {
const PStatCollectorDef &def =
client_data->get_collector_def(collector_index);
collector_index = def._parent_index;
set_collector_index(collector_index);
}
}
else {
// Clicking on any other label means to focus on that.
set_collector_index(collector_index);
}
// Change the root collector to show the full name.
if (prev_collector_index != collector_index) {
auto it = _labels.find(prev_collector_index);
if (it != _labels.end()) {
it->second->update_text(false);
}
it = _labels.find(collector_index);
if (it != _labels.end()) {
it->second->update_text(true);
}
}
}
/**
* Called when the user hovers the mouse over a label.
*/
void WinStatsFlameGraph::
on_enter_label(int collector_index) {
if (collector_index != _highlighted_index) {
_highlighted_index = collector_index;
}
}
/**
* Called when the user's mouse cursor leaves a label.
*/
void WinStatsFlameGraph::
on_leave_label(int collector_index) {
if (collector_index == _highlighted_index && collector_index != -1) {
_highlighted_index = -1;
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string WinStatsFlameGraph::
get_label_tooltip(int collector_index) const {
return PStatFlameGraph::get_label_tooltip(collector_index);
}
/**
* Repositions the labels.
*/
void WinStatsFlameGraph::
update_labels() {
if (_graph_window) {
PStatFlameGraph::update_labels();
}
}
/**
* Repositions a label. If width is 0, the label should be deleted.
*/
void WinStatsFlameGraph::
update_label(int collector_index, int row, int x, int width) {
WinStatsLabel *label;
auto it = _labels.find(collector_index);
if (it != _labels.end()) {
if (width == 0) {
delete it->second;
_labels.erase(it);
return;
}
label = it->second;
} else {
if (width == 0) {
return;
}
label = new WinStatsLabel(WinStatsGraph::_monitor, this, _thread_index, collector_index, false, false);
_labels[collector_index] = label;
label->setup(_graph_window);
}
label->set_pos(x, _ysize - 2 - row * label->get_height(), std::min(width, _xsize - 2));
}
/**
* Calls update_guide_bars with parameters suitable to this kind of graph.
*/
void WinStatsFlameGraph::
normal_guide_bars() {
// We want vaguely 100 pixels between guide bars.
int num_bars = get_xsize() / (_pixel_scale * 25);
_guide_bars.clear();
double dist = get_horizontal_scale() / num_bars;
for (int i = 1; i < num_bars; ++i) {
_guide_bars.push_back(make_guide_bar(i * dist));
}
_guide_bars_changed = true;
}
/**
* Erases the chart area.
*/
void WinStatsFlameGraph::
clear_region() {
RECT rect = { 0, 0, get_xsize(), get_ysize() };
FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
}
/**
* Erases the chart area in preparation for drawing a bunch of bars.
*/
void WinStatsFlameGraph::
begin_draw() {
clear_region();
// Draw in the guide bars.
int num_guide_bars = get_num_guide_bars();
for (int i = 0; i < num_guide_bars; i++) {
draw_guide_bar(_bitmap_dc, get_guide_bar(i));
}
}
/**
* Called after all the bars have been drawn, this triggers a refresh event to
* draw it to the window.
*/
void WinStatsFlameGraph::
end_draw() {
InvalidateRect(_graph_window, nullptr, FALSE);
}
/**
* Called at the end of the draw cycle.
*/
void WinStatsFlameGraph::
idle() {
}
/**
*
*/
LONG WinStatsFlameGraph::
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_LBUTTONDOWN:
if (_potential_drag_mode == DM_new_guide_bar) {
set_drag_mode(DM_new_guide_bar);
SetCapture(_graph_window);
return 0;
}
break;
case WM_COMMAND:
switch (LOWORD(wparam)) {
case BN_CLICKED:
if ((HWND)lparam == _average_check_box) {
int result = SendMessage(_average_check_box, BM_GETCHECK, 0, 0);
set_average_mode(result == BST_CHECKED);
return 0;
}
break;
}
break;
default:
break;
}
return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam);
}
/**
*
*/
LONG WinStatsFlameGraph::
graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_LBUTTONDOWN:
if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
set_drag_mode(DM_guide_bar);
int16_t x = LOWORD(lparam);
_drag_start_x = x;
SetCapture(_graph_window);
return 0;
}
break;
case WM_MOUSEMOVE:
if (_drag_mode == DM_new_guide_bar) {
// We haven't created the new guide bar yet; we won't until the mouse
// comes within the graph's region.
int16_t x = LOWORD(lparam);
if (x >= 0 && x < get_xsize()) {
set_drag_mode(DM_guide_bar);
_drag_guide_bar = add_user_guide_bar(pixel_to_height(x));
return 0;
}
}
else if (_drag_mode == DM_guide_bar) {
int16_t x = LOWORD(lparam);
move_user_guide_bar(_drag_guide_bar, pixel_to_height(x));
return 0;
}
break;
case WM_LBUTTONUP:
if (_drag_mode == DM_guide_bar) {
int16_t x = LOWORD(lparam);
if (x < 0 || x >= get_xsize()) {
remove_user_guide_bar(_drag_guide_bar);
} else {
move_user_guide_bar(_drag_guide_bar, pixel_to_height(x));
}
set_drag_mode(DM_none);
ReleaseCapture();
return 0;
}
break;
case WM_LBUTTONDBLCLK:
{
// Clicking on whitespace in the graph goes to the parent.
on_click_label(get_collector_index());
return 0;
}
break;
default:
break;
}
return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam);
}
/**
* This is called during the servicing of WM_PAINT; it gives a derived class
* opportunity to do some further painting into the window (the outer window,
* not the graph window).
*/
void WinStatsFlameGraph::
additional_window_paint(HDC hdc) {
// Draw in the labels for the guide bars.
SelectObject(hdc, WinStatsGraph::_monitor->get_font());
SetTextAlign(hdc, TA_LEFT | TA_BOTTOM);
SetBkMode(hdc, TRANSPARENT);
int y = _top_margin - _pixel_scale / 2;
int i;
int num_guide_bars = get_num_guide_bars();
for (i = 0; i < num_guide_bars; i++) {
draw_guide_label(hdc, y, get_guide_bar(i));
}
int num_user_guide_bars = get_num_user_guide_bars();
for (i = 0; i < num_user_guide_bars; i++) {
draw_guide_label(hdc, y, get_user_guide_bar(i));
}
RECT rect;
GetClientRect(_window, &rect);
// Now draw the "net value" label at the top.
SetTextAlign(hdc, TA_RIGHT | TA_BOTTOM);
SetTextColor(hdc, RGB(0, 0, 0));
TextOut(hdc, rect.right - _right_margin, y,
_net_value_text.data(), _net_value_text.length());
}
/**
* This is called during the servicing of WM_PAINT; it gives a derived class
* opportunity to do some further painting into the window (the outer window,
* not the graph window).
*/
void WinStatsFlameGraph::
additional_graph_window_paint(HDC hdc) {
int num_user_guide_bars = get_num_user_guide_bars();
for (int i = 0; i < num_user_guide_bars; i++) {
draw_guide_bar(hdc, get_user_guide_bar(i));
}
}
/**
* Based on the mouse position within the window's client area, look for
* draggable things the mouse might be hovering over and return the
* apprioprate DragMode enum or DM_none if nothing is indicated.
*/
WinStatsGraph::DragMode WinStatsFlameGraph::
consider_drag_start(int mouse_x, int mouse_y, int width, int height) {
if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) {
if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) {
// See if the mouse is over a user-defined guide bar.
int x = mouse_x - _graph_left;
double from_height = pixel_to_height(x - 2);
double to_height = pixel_to_height(x + 2);
_drag_guide_bar = find_user_guide_bar(from_height, to_height);
if (_drag_guide_bar >= 0) {
return DM_guide_bar;
}
} else if (mouse_x < _left_margin - 2 ||
mouse_x > width - _right_margin + 2) {
// The mouse is left or right of the graph; maybe create a new guide
// bar.
return DM_new_guide_bar;
}
}
// Don't upcall; there's no point resizing the margins.
return DM_none;
}
/**
* Repositions the graph child window within the parent window according to
* the _margin variables.
*/
void WinStatsFlameGraph::
move_graph_window(int graph_left, int graph_top, int graph_xsize, int graph_ysize) {
WinStatsGraph::move_graph_window(graph_left, graph_top, graph_xsize, graph_ysize);
if (_average_check_box != 0) {
SIZE size;
SendMessage(_average_check_box, BCM_GETIDEALSIZE, 0, (LPARAM)&size);
SetWindowPos(_average_check_box, 0,
_left_margin, _top_margin - size.cy - _pixel_scale / 2,
size.cx, size.cy,
SWP_NOZORDER | SWP_SHOWWINDOW);
InvalidateRect(_average_check_box, nullptr, TRUE);
}
}
/**
* Draws the line for the indicated guide bar on the graph.
*/
void WinStatsFlameGraph::
draw_guide_bar(HDC hdc, const PStatGraph::GuideBar &bar) {
int x = height_to_pixel(bar._height);
if (x > 0 && x < get_xsize() - 1) {
// Only draw it if it's not too close to either edge.
switch (bar._style) {
case GBS_target:
SelectObject(hdc, _light_pen);
break;
case GBS_user:
SelectObject(hdc, _user_guide_bar_pen);
break;
case GBS_normal:
SelectObject(hdc, _dark_pen);
break;
}
MoveToEx(hdc, x, 0, nullptr);
LineTo(hdc, x, get_ysize());
}
}
/**
* Draws the text for the indicated guide bar label at the top of the graph.
*/
void WinStatsFlameGraph::
draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar) {
switch (bar._style) {
case GBS_target:
SetTextColor(hdc, _light_color);
break;
case GBS_user:
SetTextColor(hdc, _user_guide_bar_color);
break;
case GBS_normal:
SetTextColor(hdc, _dark_color);
break;
}
int x = height_to_pixel(bar._height);
const std::string &label = bar._label;
SIZE size;
GetTextExtentPoint32(hdc, label.data(), label.length(), &size);
if (bar._style != GBS_user) {
double from_height = pixel_to_height(x - size.cx);
double to_height = pixel_to_height(x + size.cx);
if (find_user_guide_bar(from_height, to_height) >= 0) {
// Omit the label: there's a user-defined guide bar in the same space.
return;
}
}
int this_x = _graph_left + x - size.cx / 2;
if (x >= 0 && x < get_xsize()) {
TextOut(hdc, this_x, y,
label.data(), label.length());
}
}
/**
* Creates the window for this strip chart.
*/
void WinStatsFlameGraph::
create_window() {
if (_window) {
return;
}
HINSTANCE application = GetModuleHandle(nullptr);
register_window_class(application);
std::string window_title = get_title_text();
RECT win_rect = {
0, 0,
_left_margin + get_xsize() + _right_margin,
_top_margin + get_ysize() + _bottom_margin
};
// compute window size based on desired client area size
AdjustWindowRect(&win_rect, graph_window_style, FALSE);
_window =
CreateWindow(_window_class_name, window_title.c_str(), graph_window_style,
CW_USEDEFAULT, CW_USEDEFAULT,
win_rect.right - win_rect.left,
win_rect.bottom - win_rect.top,
WinStatsGraph::_monitor->get_window(), nullptr, application, 0);
if (!_window) {
nout << "Could not create FlameGraph window!\n";
exit(1);
}
SetWindowLongPtr(_window, 0, (LONG_PTR)this);
_average_check_box =
CreateWindow(WC_BUTTON, "Average", WS_CHILD | BS_AUTOCHECKBOX,
0, 0, 0, 0,
_window, nullptr, application, 0);
SendMessage(_average_check_box, WM_SETFONT,
(WPARAM)WinStatsGraph::_monitor->get_font(), TRUE);
if (get_average_mode()) {
SendMessage(_average_check_box, BM_SETCHECK, BST_CHECKED, 0);
}
// Ensure that the window is on top of the stack.
SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}
/**
* Registers the window class for the FlameGraph window, if it has not already
* been registered.
*/
void WinStatsFlameGraph::
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(WinStatsFlameGraph *);
if (!RegisterClass(&wc)) {
nout << "Could not register FlameGraph window class!\n";
exit(1);
}
_window_class_registered = true;
}
/**
*
*/
LONG WINAPI WinStatsFlameGraph::
static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
WinStatsFlameGraph *self = (WinStatsFlameGraph *)GetWindowLongPtr(hwnd, 0);
if (self != nullptr && self->_window == hwnd) {
return self->window_proc(hwnd, msg, wparam, lparam);
} else {
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}

View File

@ -0,0 +1,80 @@
/**
* 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 winStatsFlameGraph.h
* @author rdb
* @date 2022-01-28
*/
#ifndef WINSTATSFLAMEGRAPH_H
#define WINSTATSFLAMEGRAPH_H
#include "pandatoolbase.h"
#include "winStatsGraph.h"
#include "pStatFlameGraph.h"
class WinStatsLabel;
/**
* A window that draws a flame chart, which shows the collectors explicitly
* stopping and starting, one frame at a time.
*/
class WinStatsFlameGraph : public PStatFlameGraph, public WinStatsGraph {
public:
WinStatsFlameGraph(WinStatsMonitor *monitor, int thread_index,
int collector_index=0);
virtual ~WinStatsFlameGraph();
virtual void new_data(int thread_index, int frame_number);
virtual void force_redraw();
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
virtual void on_click_label(int collector_index);
virtual void on_enter_label(int collector_index);
virtual void on_leave_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
protected:
virtual void update_labels();
virtual void update_label(int collector_index, int row, int x, int width);
virtual void normal_guide_bars();
void clear_region();
virtual void begin_draw();
virtual void end_draw();
virtual void idle();
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);
virtual void additional_graph_window_paint(HDC hdc);
virtual DragMode consider_drag_start(int mouse_x, int mouse_y,
int width, int height);
virtual void move_graph_window(int graph_left, int graph_top,
int graph_xsize, int graph_ysize);
private:
void draw_guide_bar(HDC hdc, const GuideBar &bar);
void draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar);
void create_window();
static void register_window_class(HINSTANCE application);
static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
std::string _net_value_text;
pmap<int, WinStatsLabel *> _labels;
HWND _average_check_box;
static bool _window_class_registered;
static const char * const _window_class_name;
};
#endif

View File

@ -14,9 +14,11 @@
#include "winStatsGraph.h"
#include "winStatsMonitor.h"
#include "winStatsLabelStack.h"
#include "convert_srgb.h"
bool WinStatsGraph::_graph_window_class_registered = false;
const char * const WinStatsGraph::_graph_window_class_name = "graph";
#include <commctrl.h>
#define IDC_GRAPH 100
DWORD WinStatsGraph::graph_window_style =
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW | WS_VISIBLE;
@ -40,6 +42,8 @@ WinStatsGraph(WinStatsMonitor *monitor) :
_bitmap_xsize = 0;
_bitmap_ysize = 0;
_pixel_scale = monitor->get_pixel_scale();
_dark_color = RGB(51, 51, 51);
_light_color = RGB(154, 154, 154);
_user_guide_bar_color = RGB(130, 150, 255);
@ -66,11 +70,11 @@ WinStatsGraph::
DeleteObject(_light_pen);
DeleteObject(_user_guide_bar_pen);
Brushes::iterator bi;
for (bi = _brushes.begin(); bi != _brushes.end(); ++bi) {
HBRUSH brush = (*bi).second;
DeleteObject(brush);
for (auto &item : _brushes) {
DeleteObject(item.second.first);
DeleteObject(item.second.second);
}
_brushes.clear();
if (_graph_window) {
DestroyWindow(_graph_window);
@ -97,13 +101,6 @@ void WinStatsGraph::
new_data(int thread_index, int frame_number) {
}
/**
* Called when it is necessary to redraw the entire graph.
*/
void WinStatsGraph::
force_redraw() {
}
/**
* Called when the user has resized the window, forcing a resize of the graph.
*/
@ -150,7 +147,46 @@ user_guide_bars_changed() {
* Called when the user single-clicks on a label.
*/
void WinStatsGraph::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
}
/**
* Called when the user hovers the mouse over a label.
*/
void WinStatsGraph::
on_enter_label(int collector_index) {
if (collector_index != _highlighted_index) {
_highlighted_index = collector_index;
force_redraw();
}
}
/**
* Called when the user's mouse cursor leaves a label.
*/
void WinStatsGraph::
on_leave_label(int collector_index) {
if (collector_index == _highlighted_index && collector_index != -1) {
_highlighted_index = -1;
force_redraw();
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string WinStatsGraph::
get_label_tooltip(int collector_index) const {
return std::string();
}
/**
* Returns the window handle of the surrounding window.
*/
HWND WinStatsGraph::
get_window() {
return _window;
}
/**
@ -184,8 +220,8 @@ move_label_stack() {
RECT rect;
GetClientRect(_window, &rect);
rect.left += 8;
rect.right = _left_margin - 8;
rect.left += _pixel_scale * 2;
rect.right = _left_margin - _pixel_scale * 2;
rect.bottom -= _bottom_margin;
_label_stack.set_pos(rect.left, rect.top,
@ -197,22 +233,27 @@ move_label_stack() {
* Returns a brush suitable for drawing in the indicated collector's color.
*/
HBRUSH WinStatsGraph::
get_collector_brush(int collector_index) {
get_collector_brush(int collector_index, bool highlight) {
Brushes::iterator bi;
bi = _brushes.find(collector_index);
if (bi != _brushes.end()) {
return (*bi).second;
return highlight ? (*bi).second.second : (*bi).second.first;
}
// Ask the monitor what color this guy should be.
LRGBColor rgb = _monitor->get_collector_color(collector_index);
int r = (int)(rgb[0] * 255.0f);
int g = (int)(rgb[1] * 255.0f);
int b = (int)(rgb[2] * 255.0f);
int r = (int)encode_sRGB_uchar(rgb[0]);
int g = (int)encode_sRGB_uchar(rgb[1]);
int b = (int)encode_sRGB_uchar(rgb[2]);
HBRUSH brush = CreateSolidBrush(RGB(r, g, b));
_brushes[collector_index] = brush;
return brush;
int hr = (int)encode_sRGB_uchar(rgb[0] * 0.75f);
int hg = (int)encode_sRGB_uchar(rgb[1] * 0.75f);
int hb = (int)encode_sRGB_uchar(rgb[2] * 0.75f);
HBRUSH hbrush = CreateSolidBrush(RGB(hr, hg, hb));
_brushes[collector_index] = std::make_pair(brush, hbrush);
return highlight ? hbrush : brush;
}
/**
@ -226,6 +267,20 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
close();
break;
case WM_GETMINMAXINFO:
{
WINDOWINFO winfo;
GetWindowInfo(hwnd, &winfo);
MINMAXINFO &minmax = *(MINMAXINFO *)lparam;
minmax.ptMinTrackSize.x = (winfo.rcClient.left - winfo.rcWindow.left)
+ (winfo.rcWindow.right - winfo.rcClient.right)
+ (_right_margin + _left_margin);
minmax.ptMinTrackSize.y = (winfo.rcClient.top - winfo.rcWindow.top)
+ (winfo.rcWindow.bottom - winfo.rcClient.bottom)
+ (_bottom_margin + _top_margin);
return 0;
}
case WM_SIZE:
move_label_stack();
InvalidateRect(hwnd, nullptr, TRUE);
@ -319,8 +374,6 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
rect.bottom -= _bottom_margin;
if (rect.right > rect.left && rect.bottom > rect.top) {
DrawEdge(hdc, &rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
int graph_xsize = rect.right - rect.left;
int graph_ysize = rect.bottom - rect.top;
if (_bitmap_dc == 0 ||
@ -339,6 +392,21 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
return 0;
}
case WM_DRAWITEM:
if (wparam == IDC_GRAPH) {
const DRAWITEMSTRUCT &dis = *(DRAWITEMSTRUCT *)lparam;
// Repaint the graph by copying the backing pixmap in.
BitBlt(dis.hDC, 0, 0,
_bitmap_xsize, _bitmap_ysize,
_bitmap_dc, 0, 0,
SRCCOPY);
additional_graph_window_paint(dis.hDC);
return 0;
}
break;
default:
break;
}
@ -357,6 +425,10 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
force_redraw();
break;
case WM_NCHITTEST:
// Necessary for mouse events to work; default returns HTTRANSPARENT
return HTCLIENT;
case WM_LBUTTONDOWN:
// Vector any uncaught WM_LBUTTONDOWN into the main window, so we can drag
// margins, etc.
@ -372,28 +444,11 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
ReleaseCapture();
break;
case WM_PAINT:
{
// Repaint the graph by copying the backing pixmap in.
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
BitBlt(hdc, 0, 0,
_bitmap_xsize, _bitmap_ysize,
_bitmap_dc, 0, 0,
SRCCOPY);
additional_graph_window_paint(hdc);
EndPaint(hwnd, &ps);
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
return DefSubclassProc(hwnd, msg, wparam, lparam);
}
/**
@ -506,61 +561,29 @@ create_graph_window() {
}
HINSTANCE application = GetModuleHandle(nullptr);
register_graph_window_class(application);
std::string window_title = "graph";
DWORD window_style = WS_CHILD | WS_CLIPSIBLINGS;
DWORD window_style = WS_CHILD | WS_CLIPSIBLINGS |
SS_SUNKEN | SS_OWNERDRAW;
_graph_window =
CreateWindow(_graph_window_class_name, window_title.c_str(), window_style,
0, 0, 0, 0,
_window, nullptr, application, 0);
CreateWindow(WC_STATIC, "", window_style, 0, 0, 0, 0,
_window, (HMENU)IDC_GRAPH, application, 0);
if (!_graph_window) {
nout << "Could not create graph window!\n";
exit(1);
}
SetWindowLongPtr(_graph_window, 0, (LONG_PTR)this);
}
EnableWindow(_graph_window, TRUE);
/**
* Registers the window class for the stripChart window, if it has not already
* been registered.
*/
void WinStatsGraph::
register_graph_window_class(HINSTANCE application) {
if (_graph_window_class_registered) {
return;
}
WNDCLASS wc;
ZeroMemory(&wc, sizeof(WNDCLASS));
wc.style = CS_DBLCLKS;
wc.lpfnWndProc = (WNDPROC)static_graph_window_proc;
wc.hInstance = application;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = _graph_window_class_name;
// Reserve space to associate the this pointer with the window.
wc.cbWndExtra = sizeof(WinStatsGraph *);
if (!RegisterClass(&wc)) {
nout << "Could not register graph window class!\n";
exit(1);
}
_graph_window_class_registered = true;
SetWindowSubclass(_graph_window, &static_graph_subclass_proc, 1234, (DWORD_PTR)this);
}
/**
*
*/
LONG WINAPI WinStatsGraph::
static_graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
WinStatsGraph *self = (WinStatsGraph *)GetWindowLongPtr(hwnd, 0);
LRESULT WINAPI WinStatsGraph::
static_graph_subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR subclass, DWORD_PTR ref_data) {
WinStatsGraph *self = (WinStatsGraph *)ref_data;
if (self != nullptr && self->_graph_window == hwnd) {
return self->graph_window_proc(hwnd, msg, wparam, lparam);
} else {

View File

@ -48,7 +48,7 @@ public:
virtual void new_collector(int collector_index);
virtual void new_data(int thread_index, int frame_number);
virtual void force_redraw();
virtual void force_redraw()=0;
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
@ -56,7 +56,12 @@ public:
void set_pause(bool pause);
void user_guide_bars_changed();
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
virtual void on_enter_label(int collector_index);
virtual void on_leave_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
HWND get_window();
protected:
void close();
@ -64,7 +69,7 @@ protected:
void setup_label_stack();
void move_label_stack();
HBRUSH get_collector_brush(int collector_index);
HBRUSH get_collector_brush(int collector_index, bool highlight = false);
LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
@ -80,7 +85,7 @@ protected:
protected:
// Table of brushes for our various collectors.
typedef pmap<int, HBRUSH> Brushes;
typedef pmap<int, std::pair<HBRUSH, HBRUSH> > Brushes;
Brushes _brushes;
WinStatsMonitor *_monitor;
@ -98,6 +103,7 @@ protected:
int _bitmap_xsize, _bitmap_ysize;
int _left_margin, _right_margin;
int _top_margin, _bottom_margin;
int _pixel_scale;
COLORREF _dark_color;
COLORREF _light_color;
@ -112,18 +118,16 @@ protected:
double _drag_scale_start;
int _drag_guide_bar;
int _highlighted_index = -1;
bool _pause;
private:
void setup_bitmap(int xsize, int ysize);
void release_bitmap();
void create_graph_window();
static void register_graph_window_class(HINSTANCE application);
static LONG WINAPI static_graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static bool _graph_window_class_registered;
static const char * const _graph_window_class_name;
static LRESULT WINAPI static_graph_subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR subclass, DWORD_PTR ref_data);
protected:
static DWORD graph_window_style;

View File

@ -0,0 +1,68 @@
/**
* 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 winStatsLabel.I
* @author rdb
* @date 2022-01-29
*/
/**
* Returns the x position of the label on its parent.
*/
INLINE int WinStatsLabel::
get_x() const {
return _x;
}
/**
* Returns the y position of the label on its parent.
*/
INLINE int WinStatsLabel::
get_y() const {
return _y;
}
/**
* Returns the width of the label as we requested it.
*/
INLINE int WinStatsLabel::
get_width() const {
return _width;
}
/**
* Returns the height of the label as we requested it.
*/
INLINE int WinStatsLabel::
get_height() const {
return _height;
}
/**
* Returns the width the label would really prefer to be.
*/
INLINE int WinStatsLabel::
get_ideal_width() const {
return _ideal_width;
}
/**
* Returns the collector this label represents.
*/
INLINE int WinStatsLabel::
get_collector_index() const {
return _collector_index;
}
/**
* Returns true if the visual highlight for this label is enabled.
*/
INLINE bool WinStatsLabel::
get_highlight() const {
return _highlight;
}

View File

@ -14,6 +14,9 @@
#include "winStatsLabel.h"
#include "winStatsMonitor.h"
#include "winStatsGraph.h"
#include "convert_srgb.h"
#include <commctrl.h>
int WinStatsLabel::_left_margin = 2;
int WinStatsLabel::_right_margin = 2;
@ -28,38 +31,45 @@ const char * const WinStatsLabel::_window_class_name = "label";
*/
WinStatsLabel::
WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph,
int thread_index, int collector_index, bool use_fullname) :
int thread_index, int collector_index, bool use_fullname,
bool align_right) :
_monitor(monitor),
_graph(graph),
_thread_index(thread_index),
_collector_index(collector_index)
_collector_index(collector_index),
_align_right(align_right),
_window(0),
_tooltip_window(0)
{
_window = 0;
if (use_fullname) {
_text = _monitor->get_client_data()->get_collector_fullname(_collector_index);
} else {
_text = _monitor->get_client_data()->get_collector_name(_collector_index);
}
update_text(use_fullname);
LRGBColor rgb = _monitor->get_collector_color(_collector_index);
int r = (int)(rgb[0] * 255.0f);
int g = (int)(rgb[1] * 255.0f);
int b = (int)(rgb[2] * 255.0f);
_bg_color = RGB(r, g, b);
int r = (int)encode_sRGB_uchar(rgb[0]);
int g = (int)encode_sRGB_uchar(rgb[1]);
int b = (int)encode_sRGB_uchar(rgb[2]);
_bg_brush = CreateSolidBrush(RGB(r, g, b));
// Calculate the color when it is highlighted.
int hr = (int)encode_sRGB_uchar(rgb[0] * 0.75f);
int hg = (int)encode_sRGB_uchar(rgb[1] * 0.75f);
int hb = (int)encode_sRGB_uchar(rgb[2] * 0.75f);
_highlight_bg_brush = CreateSolidBrush(RGB(hr, hg, hb));
// Should our foreground be black or white?
double bright =
rgb[0] * 0.299 +
rgb[1] * 0.587 +
rgb[2] * 0.114;
rgb[0] * 0.2126 +
rgb[1] * 0.7152 +
rgb[2] * 0.0722;
if (bright >= 0.5) {
_fg_color = RGB(0, 0, 0);
_highlight_brush = (HBRUSH)GetStockObject(BLACK_BRUSH);
} else {
_fg_color = RGB(255, 255, 255);
_highlight_brush = (HBRUSH)GetStockObject(WHITE_BRUSH);
}
if (bright >= 0.5 * 0.75) {
_highlight_fg_color = RGB(0, 0, 0);
} else {
_highlight_fg_color = RGB(255, 255, 255);
}
_x = 0;
@ -77,6 +87,10 @@ WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph,
WinStatsLabel::
~WinStatsLabel() {
if (_window) {
if (_tooltip_window) {
DestroyWindow(_tooltip_window);
_tooltip_window = 0;
}
DestroyWindow(_window);
_window = 0;
}
@ -96,7 +110,7 @@ setup(HWND parent_window) {
create_window(parent_window);
HDC hdc = GetDC(_window);
HFONT hfnt = (HFONT)GetStockObject(ANSI_VAR_FONT);
HFONT hfnt = _monitor->get_font();
SelectObject(hdc, hfnt);
SIZE size;
@ -113,59 +127,13 @@ setup(HWND parent_window) {
*/
void WinStatsLabel::
set_pos(int x, int y, int width) {
_x = x;
_y = y;
_width = width;
SetWindowPos(_window, 0, x, y - _height, _width, _height,
SWP_NOZORDER | SWP_SHOWWINDOW);
}
/**
* Returns the x position of the label on its parent.
*/
int WinStatsLabel::
get_x() const {
return _x;
}
/**
* Returns the y position of the label on its parent.
*/
int WinStatsLabel::
get_y() const {
return _y;
}
/**
* Returns the width of the label as we requested it.
*/
int WinStatsLabel::
get_width() const {
return _width;
}
/**
* Returns the height of the label as we requested it.
*/
int WinStatsLabel::
get_height() const {
return _height;
}
/**
* Returns the width the label would really prefer to be.
*/
int WinStatsLabel::
get_ideal_width() const {
return _ideal_width;
}
/**
* Returns the collector this label represents.
*/
int WinStatsLabel::
get_collector_index() const {
return _collector_index;
if (x != _x || y != _y || width != _width) {
_x = x;
_y = y;
_width = width;
SetWindowPos(_window, 0, x, y - _height, _width, _height,
SWP_NOZORDER | SWP_SHOWWINDOW);
}
}
/**
@ -180,11 +148,29 @@ set_highlight(bool highlight) {
}
/**
* Returns true if the visual highlight for this label is enabled.
* Set to true if the full name of the collector should be shown.
*/
bool WinStatsLabel::
get_highlight() const {
return _highlight;
void WinStatsLabel::
update_text(bool use_fullname) {
const PStatClientData *client_data = _monitor->get_client_data();
_tooltip_text = client_data->get_collector_fullname(_collector_index);
if (use_fullname) {
_text = _tooltip_text;
} else {
_text = client_data->get_collector_name(_collector_index);
}
// Recalculate the dimensions.
if (_window) {
HDC hdc = GetDC(_window);
HFONT hfnt = _monitor->get_font();
SelectObject(hdc, hfnt);
SIZE size;
GetTextExtentPoint32(hdc, _text.data(), _text.length(), &size);
_height = size.cy + _top_margin + _bottom_margin;
_ideal_width = size.cx + _left_margin + _right_margin;
}
}
/**
@ -220,6 +206,25 @@ create_window(HWND parent_window) {
}
SetWindowLongPtr(_window, 0, (LONG_PTR)this);
// Create the tooltip window. This will cause a TTN_GETDISPINFO message to
// be sent to the window to acquire the tooltip text.
_tooltip_window = CreateWindow(TOOLTIPS_CLASS, nullptr,
WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
_window, nullptr,
application, nullptr);
if (_tooltip_window != 0) {
TOOLINFO info = { 0 };
info.cbSize = sizeof(info);
info.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
info.hwnd = _window;
info.uId = (UINT_PTR)_window;
info.lpszText = LPSTR_TEXTCALLBACK;
SendMessage(_tooltip_window, TTM_ADDTOOL, 0, (LPARAM)&info);
}
}
/**
@ -274,13 +279,16 @@ LONG WinStatsLabel::
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_LBUTTONDBLCLK:
_graph->clicked_label(_collector_index);
_graph->on_click_label(_collector_index);
return 0;
case WM_MOUSEMOVE:
{
// When the mouse enters the label area, highlight the label.
set_mouse_within(true);
if (!_mouse_within) {
set_mouse_within(true);
_graph->on_enter_label(_collector_index);
}
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the label.
TRACKMOUSEEVENT tme = {
@ -294,7 +302,10 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
break;
case WM_MOUSELEAVE:
set_mouse_within(false);
if (_mouse_within) {
set_mouse_within(false);
_graph->on_leave_label(_collector_index);
}
break;
case WM_PAINT:
@ -303,26 +314,33 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect = { 0, 0, _width, _height };
FillRect(hdc, &rect, _bg_brush);
FillRect(hdc, &rect, (_highlight || _mouse_within) ? _highlight_bg_brush : _bg_brush);
if (_highlight || _mouse_within) {
FrameRect(hdc, &rect, _highlight_brush);
}
HFONT hfnt = (HFONT)GetStockObject(ANSI_VAR_FONT);
HFONT hfnt = _monitor->get_font();
SelectObject(hdc, hfnt);
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
SetTextAlign(hdc, (_align_right ? TA_RIGHT : TA_LEFT) | TA_TOP);
SetBkColor(hdc, _bg_color);
SetBkMode(hdc, OPAQUE);
SetTextColor(hdc, _fg_color);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, (_highlight || _mouse_within) ? _highlight_fg_color : _fg_color);
TextOut(hdc, _width - _right_margin, _top_margin,
_text.data(), _text.length());
TextOut(hdc, _align_right ? (_width - _right_margin) : _left_margin,
_top_margin, _text.data(), _text.length());
EndPaint(hwnd, &ps);
return 0;
}
case WM_NOTIFY:
switch (((LPNMHDR)lparam)->code) {
case TTN_GETDISPINFO:
{
NMTTDISPINFO &info = *(NMTTDISPINFO *)lparam;
_tooltip_text = _graph->get_label_tooltip(_collector_index);
info.lpszText = (char *)_tooltip_text.c_str();
}
return 0;
}
break;
default:
break;
}

View File

@ -32,22 +32,25 @@ class WinStatsGraph;
class WinStatsLabel {
public:
WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph,
int thread_index, int collector_index, bool use_fullname);
int thread_index, int collector_index, bool use_fullname,
bool align_right = true);
~WinStatsLabel();
void setup(HWND parent_window);
void set_pos(int x, int y, int width);
int get_x() const;
int get_y() const;
int get_width() const;
int get_height() const;
int get_ideal_width() const;
INLINE int get_x() const;
INLINE int get_y() const;
INLINE int get_width() const;
INLINE int get_height() const;
INLINE int get_ideal_width() const;
int get_collector_index() const;
INLINE int get_collector_index() const;
void set_highlight(bool highlight);
bool get_highlight() const;
INLINE bool get_highlight() const;
void update_text(bool use_fullname);
private:
void set_mouse_within(bool mouse_within);
@ -63,11 +66,13 @@ private:
int _thread_index;
int _collector_index;
std::string _text;
std::string _tooltip_text;
HWND _window;
COLORREF _bg_color;
HWND _tooltip_window;
COLORREF _fg_color;
COLORREF _highlight_fg_color;
HBRUSH _bg_brush;
HBRUSH _highlight_brush;
HBRUSH _highlight_bg_brush;
int _x;
int _y;
@ -76,6 +81,7 @@ private:
int _ideal_width;
bool _highlight;
bool _mouse_within;
bool _align_right;
static int _left_margin, _right_margin;
static int _top_margin, _bottom_margin;
@ -84,4 +90,6 @@ private:
static const char * const _window_class_name;
};
#include "winStatsLabel.I"
#endif

View File

@ -200,6 +200,90 @@ add_label(WinStatsMonitor *monitor, WinStatsGraph *graph,
return label_index;
}
/**
* Replaces the labels with the given collector indices.
*/
void WinStatsLabelStack::
replace_labels(WinStatsMonitor *monitor, WinStatsGraph *graph,
int thread_index, const vector_int &collector_indices,
bool use_fullname) {
_ideal_width = 0;
// First skip the part of the stack that hasn't changed.
size_t li = 0;
size_t ci = 0;
while (ci < collector_indices.size() && li < _labels.size()) {
WinStatsLabel *label = _labels[li];
if (collector_indices[ci] != label->get_collector_index()) {
// Mismatch.
break;
}
_ideal_width = std::max(_ideal_width, label->get_ideal_width());
++ci;
++li;
}
if (ci == collector_indices.size()) {
if (ci == _labels.size()) {
// Perfect, nothing changed.
return;
}
// Simple case, just delete the rest.
while (li < _labels.size()) {
delete _labels[li++];
}
_labels.resize(ci);
return;
}
int yp = _height;
if (li > 0) {
WinStatsLabel *label = _labels[li - 1];
yp = label->get_y() - label->get_height();
}
// Make a map of remaining labels.
std::map<int, WinStatsLabel *> label_map;
for (size_t li2 = li; li2 < _labels.size(); ++li2) {
WinStatsLabel *label = _labels[li2];
label_map[label->get_collector_index()] = label;
}
_labels.resize(collector_indices.size());
while (ci < collector_indices.size()) {
int collector_index = collector_indices[ci++];
WinStatsLabel *label;
auto it = label_map.find(collector_index);
if (it == label_map.end()) {
// It's not in the map. Create a new label.
label = new WinStatsLabel(monitor, graph, thread_index, collector_index, use_fullname);
if (_window) {
label->setup(_window);
}
} else {
// Erase it from the map, so that it's not deleted.
label = it->second;
label_map.erase(it);
}
if (_window) {
label->set_pos(0, yp, _width);
}
_ideal_width = std::max(_ideal_width, label->get_ideal_width());
yp -= label->get_height();
_labels[li++] = label;
}
// Anything that's remaining in the label map should be deleted.
for (auto it = label_map.begin(); it != label_map.end(); ++it) {
delete it->second;
}
}
/**
* Returns the number of labels in the stack.
*/
@ -225,7 +309,6 @@ highlight_label(int collector_index) {
}
}
/**
* Creates the window for this stack.
*/
@ -306,7 +389,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect = { 0, 0, _width, _height };
FillRect(hdc, &rect, (HBRUSH)COLOR_BACKGROUND);
FillRect(hdc, &rect, (HBRUSH)COLOR_WINDOW);
EndPaint(hwnd, &ps);
return 0;
}

View File

@ -16,6 +16,7 @@
#include "pandatoolbase.h"
#include "pvector.h"
#include "vector_int.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
@ -51,6 +52,9 @@ public:
void clear_labels();
int add_label(WinStatsMonitor *monitor, WinStatsGraph *graph,
int thread_index, int collector_index, bool use_fullname);
void replace_labels(WinStatsMonitor *monitor, WinStatsGraph *graph,
int thread_index, const vector_int &collector_indices,
bool use_fullname);
int get_num_labels() const;
void highlight_label(int collector_index);

View File

@ -15,6 +15,7 @@
#include "winStatsServer.h"
#include "winStatsStripChart.h"
#include "winStatsPianoRoll.h"
#include "winStatsFlameGraph.h"
#include "winStatsChartMenu.h"
#include "winStatsMenuId.h"
#include "pStatGraph.h"
@ -37,6 +38,25 @@ WinStatsMonitor(WinStatsServer *server) : PStatMonitor(server) {
_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);
}
/**
@ -252,6 +272,22 @@ get_window() const {
return _window;
}
/**
* Returns the font that should be used for rendering text.
*/
HFONT WinStatsMonitor::
get_font() const {
return _font;
}
/**
* Returns the system DPI scaling as a fraction where 4 = no scaling.
*/
int WinStatsMonitor::
get_pixel_scale() const {
return _pixel_scale;
}
/**
* Opens a new strip chart showing the indicated data.
*/
@ -279,6 +315,19 @@ open_piano_roll(int thread_index) {
graph->set_pause(_pause);
}
/**
* Opens a new flame graph showing the indicated data.
*/
void WinStatsMonitor::
open_flame_graph(int thread_index) {
WinStatsFlameGraph *graph = new WinStatsFlameGraph(this, thread_index);
add_graph(graph);
graph->set_time_units(_time_units);
graph->set_scroll_speed(_scroll_speed);
graph->set_pause(_pause);
}
/**
* Returns the MenuDef properties associated with the indicated menu ID. This
* specifies what we expect to do when the given menu has been selected.
@ -604,7 +653,7 @@ register_window_class(HINSTANCE application) {
wc.lpfnWndProc = (WNDPROC)static_window_proc;
wc.hInstance = application;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = _window_class_name;
@ -701,9 +750,13 @@ handle_menu_command(int menu_id) {
default:
if (menu_id >= MI_new_chart) {
const MenuDef &menu_def = lookup_menu(menu_id);
if (menu_def._collector_index < 0) {
if (menu_def._collector_index == -2) {
open_flame_graph(menu_def._thread_index);
}
else if (menu_def._collector_index < 0) {
open_piano_roll(menu_def._thread_index);
} else {
}
else {
open_strip_chart(menu_def._thread_index, menu_def._collector_index,
menu_def._show_level);
}

View File

@ -66,8 +66,12 @@ public:
virtual void user_guide_bars_changed();
HWND get_window() const;
HFONT get_font() const;
int get_pixel_scale() 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);
const MenuDef &lookup_menu(int menu_id) const;
int get_menu_id(const MenuDef &menu_def);
@ -109,6 +113,9 @@ private:
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;

View File

@ -15,7 +15,7 @@
#include "winStatsMonitor.h"
#include "numeric_types.h"
static const int default_piano_roll_width = 400;
static const int default_piano_roll_width = 600;
static const int default_piano_roll_height = 200;
bool WinStatsPianoRoll::_window_class_registered = false;
@ -27,14 +27,14 @@ const char * const WinStatsPianoRoll::_window_class_name = "piano";
WinStatsPianoRoll::
WinStatsPianoRoll(WinStatsMonitor *monitor, int thread_index) :
PStatPianoRoll(monitor, thread_index,
default_piano_roll_width,
default_piano_roll_height),
monitor->get_pixel_scale() * default_piano_roll_width / 4,
monitor->get_pixel_scale() * default_piano_roll_height / 4),
WinStatsGraph(monitor)
{
_left_margin = 128;
_right_margin = 8;
_top_margin = 16;
_bottom_margin = 8;
_left_margin = _pixel_scale * 32;
_right_margin = _pixel_scale * 2;
_top_margin = _pixel_scale * 5;
_bottom_margin = _pixel_scale * 2;
// Let's show the units on the guide bar labels. There's room.
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
@ -103,7 +103,7 @@ set_time_units(int unit_mask) {
* Called when the user single-clicks on a label.
*/
void WinStatsPianoRoll::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
if (collector_index >= 0) {
WinStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
}
@ -123,6 +123,15 @@ set_horizontal_scale(double time_width) {
InvalidateRect(_window, &rect, TRUE);
}
/**
* Calls update_guide_bars with parameters suitable to this kind of graph.
*/
void WinStatsPianoRoll::
normal_guide_bars() {
// We want vaguely 100 pixels between guide bars.
update_guide_bars(get_xsize() / (_pixel_scale * 25), get_horizontal_scale());
}
/**
* Erases the chart area.
*/
@ -144,6 +153,21 @@ begin_draw() {
for (int i = 0; i < num_guide_bars; i++) {
draw_guide_bar(_bitmap_dc, get_guide_bar(i));
}
SelectObject(_bitmap_dc, GetStockObject(NULL_PEN));
}
/**
* Should be overridden by the user class. This hook will be called before
* drawing any one row of bars. These bars correspond to the collector whose
* index is get_row_collector(row), and in the color get_row_color(row).
*/
void WinStatsPianoRoll::
begin_row(int row) {
int collector_index = get_label_collector(row);
HBRUSH brush = get_collector_brush(collector_index, _highlighted_index == collector_index);
SelectObject(_bitmap_dc, brush);
SelectObject(_bitmap_dc, GetStockObject(NULL_PEN));
}
/**
@ -155,13 +179,7 @@ draw_bar(int row, int from_x, int to_x) {
int y = _label_stack.get_label_y(row) - _graph_top;
int height = _label_stack.get_label_height(row);
RECT rect = {
from_x, y - height + 2,
to_x, y - 2,
};
int collector_index = get_label_collector(row);
HBRUSH brush = get_collector_brush(collector_index);
FillRect(_bitmap_dc, &rect, brush);
RoundRect(_bitmap_dc, from_x, y - height + 2, to_x, y - 2, _pixel_scale, _pixel_scale);
}
}
@ -233,7 +251,10 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
// When the mouse is over a color bar, highlight it.
int16_t x = LOWORD(lparam);
int16_t y = HIWORD(lparam);
_label_stack.highlight_label(get_collector_under_pixel(x, y));
int collector_index = get_collector_under_pixel(x, y);
_label_stack.highlight_label(collector_index);
on_enter_label(collector_index);
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
// window.
@ -244,10 +265,11 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
0
};
TrackMouseEvent(&tme);
} else {
}
else {
// If the mouse is in some drag mode, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
}
if (_drag_mode == DM_scale) {
@ -278,6 +300,7 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
case WM_MOUSELEAVE:
// When the mouse leaves the graph, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
break;
case WM_LBUTTONUP:
@ -305,7 +328,7 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
// clicking on the corresponding label.
int16_t x = LOWORD(lparam);
int16_t y = HIWORD(lparam);
clicked_label(get_collector_under_pixel(x, y));
on_click_label(get_collector_under_pixel(x, y));
return 0;
}
break;
@ -325,12 +348,11 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
void WinStatsPianoRoll::
additional_window_paint(HDC hdc) {
// Draw in the labels for the guide bars.
HFONT hfnt = (HFONT)GetStockObject(ANSI_VAR_FONT);
SelectObject(hdc, hfnt);
SelectObject(hdc, WinStatsGraph::_monitor->get_font());
SetTextAlign(hdc, TA_LEFT | TA_BOTTOM);
SetBkMode(hdc, TRANSPARENT);
int y = _top_margin;
int y = _top_margin - 2;
int i;
int num_guide_bars = get_num_guide_bars();
@ -411,13 +433,8 @@ get_collector_under_pixel(int xpoint, int ypoint) {
*/
void WinStatsPianoRoll::
update_labels() {
_label_stack.clear_labels();
for (int i = 0; i < get_num_labels(); i++) {
int label_index =
_label_stack.add_label(WinStatsGraph::_monitor, this,
_thread_index,
get_label_collector(i), true);
}
_label_stack.replace_labels(WinStatsGraph::_monitor, this,
_thread_index, _labels, true);
_labels_changed = false;
}
@ -551,7 +568,7 @@ register_window_class(HINSTANCE application) {
wc.lpfnWndProc = (WNDPROC)static_window_proc;
wc.hInstance = application;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = _window_class_name;

View File

@ -41,12 +41,14 @@ public:
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
virtual void set_time_units(int unit_mask);
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
void set_horizontal_scale(double time_width);
protected:
virtual void normal_guide_bars();
void clear_region();
virtual void begin_draw();
virtual void begin_row(int row);
virtual void draw_bar(int row, int from_x, int to_x);
virtual void end_draw();
virtual void idle();

View File

@ -16,17 +16,13 @@
#include "pStatCollectorDef.h"
#include "numeric_types.h"
#include <commctrl.h>
using std::string;
static const int default_strip_chart_width = 400;
static const int default_strip_chart_height = 100;
// Surely we aren't expected to hardcode the size of a normal checkbox. But
// Windows seems to require this data to be passed to CreateWindow(), so what
// else can I do?
size_t WinStatsStripChart::_check_box_height = 13;
size_t WinStatsStripChart::_check_box_width = 13;
bool WinStatsStripChart::_window_class_registered = false;
const char * const WinStatsStripChart::_window_class_name = "strip";
@ -40,16 +36,16 @@ WinStatsStripChart(WinStatsMonitor *monitor, int thread_index,
show_level ? monitor->get_level_view(collector_index, thread_index) : monitor->get_view(thread_index),
thread_index,
collector_index,
default_strip_chart_width,
default_strip_chart_height),
monitor->get_pixel_scale() * default_strip_chart_width / 4,
monitor->get_pixel_scale() * default_strip_chart_height / 4),
WinStatsGraph(monitor)
{
_brush_origin = 0;
_left_margin = 96;
_right_margin = 32;
_top_margin = 16;
_bottom_margin = 8;
_left_margin = _pixel_scale * 24;
_right_margin = _pixel_scale * 12;
_top_margin = _pixel_scale * 6;
_bottom_margin = _pixel_scale * 2;
if (show_level) {
// If it's a level-type graph, show the appropriate units.
@ -170,7 +166,7 @@ set_scroll_speed(double scroll_speed) {
* Called when the user single-clicks on a label.
*/
void WinStatsStripChart::
clicked_label(int collector_index) {
on_click_label(int collector_index) {
if (collector_index < 0) {
// Clicking on whitespace in the graph is the same as clicking on the top
// label.
@ -197,6 +193,15 @@ clicked_label(int collector_index) {
}
}
/**
* Called when the mouse hovers over a label, and should return the text that
* should appear on the tooltip.
*/
std::string WinStatsStripChart::
get_label_tooltip(int collector_index) const {
return PStatStripChart::get_label_tooltip(collector_index);
}
/**
* Changes the value the height of the vertical axis represents. This may
* force a redraw.
@ -218,11 +223,8 @@ void WinStatsStripChart::
update_labels() {
PStatStripChart::update_labels();
_label_stack.clear_labels();
for (int i = 0; i < get_num_labels(); i++) {
_label_stack.add_label(WinStatsGraph::_monitor, this, _thread_index,
get_label_collector(i), false);
}
_label_stack.replace_labels(WinStatsGraph::_monitor, this,
_thread_index, _labels, false);
_labels_changed = false;
}
@ -273,7 +275,7 @@ draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) {
for (fi = fdata.begin(); fi != fdata.end(); ++fi) {
const ColorData &cd = (*fi);
overall_time += cd._net_value;
HBRUSH brush = get_collector_brush(cd._collector_index);
HBRUSH brush = get_collector_brush(cd._collector_index, cd._collector_index == _highlighted_index);
if (overall_time > get_vertical_scale()) {
// Off the top. Go ahead and clamp it by hand, in case it's so far off
@ -391,7 +393,10 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
// When the mouse is over a color bar, highlight it.
int16_t x = LOWORD(lparam);
int16_t y = HIWORD(lparam);
_label_stack.highlight_label(get_collector_under_pixel(x, y));
int collector_index = get_collector_under_pixel(x, y);
_label_stack.highlight_label(collector_index);
on_enter_label(collector_index);
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
// window.
@ -402,10 +407,11 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
0
};
TrackMouseEvent(&tme);
} else {
}
else {
// If the mouse is in some drag mode, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
}
if (_drag_mode == DM_scale) {
@ -436,6 +442,7 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
case WM_MOUSELEAVE:
// When the mouse leaves the graph, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
break;
case WM_LBUTTONUP:
@ -463,7 +470,7 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
// clicking on the corresponding label.
int16_t x = LOWORD(lparam);
int16_t y = HIWORD(lparam);
clicked_label(get_collector_under_pixel(x, y));
on_click_label(get_collector_under_pixel(x, y));
return 0;
}
break;
@ -483,14 +490,13 @@ graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
void WinStatsStripChart::
additional_window_paint(HDC hdc) {
// Draw in the labels for the guide bars.
HFONT hfnt = (HFONT)GetStockObject(ANSI_VAR_FONT);
SelectObject(hdc, hfnt);
SelectObject(hdc, WinStatsGraph::_monitor->get_font());
SetTextAlign(hdc, TA_LEFT | TA_TOP);
SetBkMode(hdc, TRANSPARENT);
RECT rect;
GetClientRect(_window, &rect);
int x = rect.right - _right_margin + 2;
int x = rect.right - _right_margin + _pixel_scale;
int last_y = -100;
int i;
@ -511,15 +517,8 @@ additional_window_paint(HDC hdc) {
// Now draw the "net value" label at the top.
SetTextAlign(hdc, TA_RIGHT | TA_BOTTOM);
SetTextColor(hdc, RGB(0, 0, 0));
TextOut(hdc, rect.right - _right_margin, _top_margin,
TextOut(hdc, rect.right - _right_margin - _pixel_scale, _top_margin - _pixel_scale / 2,
_net_value_text.data(), _net_value_text.length());
// Also draw the "Smooth" label on the check box. This isn't part of the
// check box itself, because doing that doesn't use the right font! Surely
// this isn't the correct Windows(tm) way to do this sort of thing, but I
// don't know any better for now.
SetTextAlign(hdc, TA_LEFT | TA_BOTTOM);
TextOut(hdc, _left_margin + _check_box_width + 2, _top_margin, "Smooth", 6);
}
/**
@ -594,10 +593,13 @@ void WinStatsStripChart::
move_graph_window(int graph_left, int graph_top, int graph_xsize, int graph_ysize) {
WinStatsGraph::move_graph_window(graph_left, graph_top, graph_xsize, graph_ysize);
if (_smooth_check_box != 0) {
SIZE size;
SendMessage(_smooth_check_box, BCM_GETIDEALSIZE, 0, (LPARAM)&size);
SetWindowPos(_smooth_check_box, 0,
_left_margin, _top_margin - _check_box_height - 1,
0, 0,
SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
_left_margin, _top_margin - size.cy - _pixel_scale / 2,
size.cx, size.cy,
SWP_NOZORDER | SWP_SHOWWINDOW);
InvalidateRect(_smooth_check_box, nullptr, TRUE);
}
}
@ -715,10 +717,15 @@ create_window() {
setup_label_stack();
_smooth_check_box =
CreateWindow("BUTTON", "",
WS_CHILD | BS_AUTOCHECKBOX,
0, 0, _check_box_width, _check_box_height,
CreateWindow(WC_BUTTON, "Smooth", WS_CHILD | BS_AUTOCHECKBOX,
0, 0, 0, 0,
_window, nullptr, application, 0);
SendMessage(_smooth_check_box, WM_SETFONT,
(WPARAM)WinStatsGraph::_monitor->get_font(), TRUE);
if (get_average_mode()) {
SendMessage(_smooth_check_box, BM_SETCHECK, BST_CHECKED, 0);
}
// Ensure that the window is on top of the stack.
SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0,
@ -742,7 +749,7 @@ register_window_class(HINSTANCE application) {
wc.lpfnWndProc = (WNDPROC)static_window_proc;
wc.hInstance = application;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = _window_class_name;

View File

@ -43,7 +43,8 @@ public:
virtual void set_time_units(int unit_mask);
virtual void set_scroll_speed(double scroll_speed);
virtual void clicked_label(int collector_index);
virtual void on_click_label(int collector_index);
virtual std::string get_label_tooltip(int collector_index) const;
void set_vertical_scale(double value_height);
protected:
@ -79,7 +80,6 @@ private:
std::string _net_value_text;
HWND _smooth_check_box;
static size_t _check_box_height, _check_box_width;
static bool _window_class_registered;
static const char * const _window_class_name;

View File

@ -1,5 +1,6 @@
#include "winStats.cxx"
#include "winStatsChartMenu.cxx"
#include "winStatsFlameGraph.cxx"
#include "winStatsGraph.cxx"
#include "winStatsLabel.cxx"
#include "winStatsLabelStack.cxx"