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:
parent
f0b81d5bf0
commit
7da70cf939
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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::
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "gtkStats.cxx"
|
||||
#include "gtkStatsChartMenu.cxx"
|
||||
#include "gtkStatsFlameGraph.cxx"
|
||||
#include "gtkStatsGraph.cxx"
|
||||
#include "gtkStatsLabel.cxx"
|
||||
#include "gtkStatsLabelStack.cxx"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "pStatClientData.cxx"
|
||||
#include "pStatFlameGraph.cxx"
|
||||
#include "pStatGraph.cxx"
|
||||
#include "pStatListener.cxx"
|
||||
#include "pStatMonitor.cxx"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "winStats.cxx"
|
||||
#include "winStatsChartMenu.cxx"
|
||||
#include "winStatsFlameGraph.cxx"
|
||||
#include "winStatsGraph.cxx"
|
||||
#include "winStatsLabel.cxx"
|
||||
#include "winStatsLabelStack.cxx"
|
||||
|
|
|
|||
Loading…
Reference in New Issue