Files
sysprof/sysprof.c
Søren Sandmann Pedersen 737409baf1 Call gdk_flush() in set_busy().
Otherwise, the cursor won't show up. (GTK+ 2.22).
2011-01-27 17:00:02 -05:00

1735 lines
39 KiB
C

/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, 2007, 2008, Soeren Sandmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <errno.h>
#include <glib/gprintf.h>
#include <string.h>
#include <stdlib.h>
#include "footreestore.h"
#include "treeviewutils.h"
#include "profile.h"
#include "collector.h"
/* FIXME - not10 */
#define _(a) a
#define APPLICATION_NAME "System Profiler"
typedef struct Application Application;
typedef enum
{
INITIAL,
DISPLAYING,
PROFILING
} State;
struct Application
{
Collector * collector;
State state;
GdkPixbuf * icon;
GtkWidget * main_window;
GtkTreeView * object_view;
GtkTreeView * callers_view;
GtkTreeView * descendants_view;
GtkWidget * start_button;
GtkWidget * profile_button;
GtkWidget * reset_button;
GtkWidget * save_as_button;
GtkWidget * dummy_button;
GtkWidget * start_item;
GtkWidget * profile_item;
GtkWidget * reset_item;
GtkWidget * save_as_item;
GtkWidget * open_item;
GtkWidget * screenshot_item;
GtkWidget * about_item;
GtkWidget * quit_item;
GtkWidget * hpaned;
GtkWidget * vpaned;
GtkTreeSelection * object_selection;
GtkWidget * samples_label;
GtkWidget * samples_hbox;
gboolean screenshot_window_visible;
GtkWidget * screenshot_textview;
GtkWidget * screenshot_close_button;
GtkWidget * screenshot_window;
Profile * profile;
ProfileDescendant * descendants;
ProfileCaller * callers;
int timeout_id;
int update_screenshot_id;
char * loaded_profile;
gboolean inhibit_forced_redraw;
};
static void update_screenshot_window (Application *app);
static void
show_samples (Application *app)
{
char *label;
int n_samples;
switch (app->state)
{
case INITIAL:
n_samples = 0;
break;
case PROFILING:
n_samples = collector_get_n_samples (app->collector);
break;
case DISPLAYING:
n_samples = profile_get_size (app->profile);
break;
default:
g_assert_not_reached();
break;
}
label = g_strdup_printf ("%d", n_samples);
gtk_label_set_label (GTK_LABEL (app->samples_label), label);
g_free (label);
}
static gboolean
show_samples_timeout (gpointer data)
{
Application *app = data;
show_samples (app);
app->timeout_id = 0;
return FALSE;
}
static void
queue_show_samples (Application *app)
{
if (!app->timeout_id)
app->timeout_id = g_timeout_add (225, show_samples_timeout, app);
}
static void
update_sensitivity (Application *app)
{
gboolean sensitive_profile_button;
gboolean sensitive_save_as_button;
gboolean sensitive_start_button;
gboolean sensitive_tree_views;
gboolean sensitive_samples_hbox;
gboolean sensitive_reset_button;
GtkWidget *active_radio_button;
gboolean has_samples;
switch (app->state)
{
case INITIAL:
sensitive_profile_button = FALSE;
sensitive_save_as_button = FALSE;
sensitive_start_button = TRUE;
sensitive_reset_button = FALSE;
sensitive_tree_views = FALSE;
sensitive_samples_hbox = FALSE;
active_radio_button = app->dummy_button;
break;
case PROFILING:
has_samples = (collector_get_n_samples (app->collector) > 0);
sensitive_profile_button = has_samples;
sensitive_save_as_button = has_samples;
sensitive_reset_button = has_samples;
sensitive_start_button = TRUE;
sensitive_tree_views = FALSE;
sensitive_samples_hbox = TRUE;
active_radio_button = app->start_button;
break;
case DISPLAYING:
sensitive_profile_button = TRUE;
sensitive_save_as_button = TRUE;
sensitive_start_button = TRUE;
sensitive_tree_views = TRUE;
sensitive_reset_button = TRUE;
sensitive_samples_hbox = FALSE;
active_radio_button = app->profile_button;
break;
default:
g_assert_not_reached();
break;
}
gtk_toggle_tool_button_set_active (
GTK_TOGGLE_TOOL_BUTTON (active_radio_button), TRUE);
/* "profile" widgets */
gtk_widget_set_sensitive (GTK_WIDGET (app->profile_button),
sensitive_profile_button);
gtk_widget_set_sensitive (GTK_WIDGET (app->profile_item),
sensitive_profile_button);
/* "save as" widgets */
gtk_widget_set_sensitive (GTK_WIDGET (app->save_as_button),
sensitive_save_as_button);
gtk_widget_set_sensitive (app->save_as_item,
sensitive_save_as_button);
/* "start" widgets */
gtk_widget_set_sensitive (GTK_WIDGET (app->start_button),
sensitive_start_button);
gtk_widget_set_sensitive (GTK_WIDGET (app->start_item),
sensitive_start_button);
#if 0
/* FIXME - not10: gtk+ doesn't handle changes in sensitivity in response
* to a click on the same button very well
*/
gtk_widget_set_sensitive (GTK_WIDGET (app->reset_button),
sensitive_reset_button);
gtk_widget_set_sensitive (GTK_WIDGET (app->reset_item),
sensitive_reset_button);
#endif
gtk_widget_set_sensitive (GTK_WIDGET (app->object_view), sensitive_tree_views);
gtk_widget_set_sensitive (GTK_WIDGET (app->callers_view), sensitive_tree_views);
gtk_widget_set_sensitive (GTK_WIDGET (app->descendants_view), sensitive_tree_views);
gtk_widget_set_sensitive (GTK_WIDGET (app->samples_hbox), sensitive_samples_hbox);
if (app->screenshot_window_visible)
gtk_widget_show (app->screenshot_window);
else
gtk_widget_hide (app->screenshot_window);
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (app->screenshot_item),
app->screenshot_window_visible);
show_samples (app);
}
static void
set_busy (GtkWidget *widget,
gboolean busy)
{
GdkCursor *cursor;
GdkWindow *window;
if (busy)
cursor = gdk_cursor_new (GDK_WATCH);
else
cursor = NULL;
if (GTK_IS_TEXT_VIEW (widget))
window = gtk_text_view_get_window (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_TEXT);
else
window = widget->window;
gdk_window_set_cursor (window, cursor);
if (cursor)
gdk_cursor_unref (cursor);
gdk_flush();
}
static void
set_application_title (Application *app,
const char * name)
{
char *new_name;
if (name)
new_name = g_path_get_basename (name);
else
new_name = NULL;
if (app->loaded_profile)
g_free (app->loaded_profile);
app->loaded_profile = new_name;
if (app->loaded_profile)
{
gtk_window_set_title (GTK_WINDOW (app->main_window),
app->loaded_profile);
}
else
{
gtk_window_set_title (GTK_WINDOW (app->main_window),
APPLICATION_NAME);
}
}
static void
delete_data (Application *app)
{
if (app->profile)
{
profile_free (app->profile);
app->profile = NULL;
gtk_tree_view_set_model (GTK_TREE_VIEW (app->object_view), NULL);
gtk_tree_view_set_model (GTK_TREE_VIEW (app->callers_view), NULL);
gtk_tree_view_set_model (GTK_TREE_VIEW (app->descendants_view), NULL);
}
collector_reset (app->collector);
set_application_title (app, NULL);
}
static void
sorry (GtkWidget *parent_window,
const gchar *format,
...)
{
va_list args;
char *message;
GtkWidget *dialog;
va_start (args, format);
g_vasprintf (&message, format, args);
va_end (args);
dialog = gtk_message_dialog_new (parent_window ? GTK_WINDOW (parent_window) : NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_OK, "%s", message);
g_free (message);
gtk_window_set_title (GTK_WINDOW (dialog), APPLICATION_NAME " Warning");
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
}
static void
on_menu_item_activated (GtkWidget *menu_item, GtkWidget *tool_button)
{
GtkToggleToolButton *button = GTK_TOGGLE_TOOL_BUTTON (tool_button);
if (!gtk_toggle_tool_button_get_active (button))
gtk_toggle_tool_button_set_active (button, TRUE);
}
static void
on_start_toggled (GtkWidget *widget, gpointer data)
{
Application *app = data;
GError *err = NULL;
if (!gtk_toggle_tool_button_get_active (
GTK_TOGGLE_TOOL_BUTTON (app->start_button)))
{
return;
}
if (collector_start (app->collector, &err))
{
delete_data (app);
app->state = PROFILING;
}
else
{
sorry (app->main_window, err->message);
g_error_free (err);
}
update_screenshot_window (app);
update_sensitivity (app);
}
enum
{
OBJECT_NAME,
OBJECT_SELF,
OBJECT_TOTAL,
OBJECT_OBJECT
};
enum
{
CALLERS_NAME,
CALLERS_SELF,
CALLERS_TOTAL,
CALLERS_OBJECT
};
enum
{
DESCENDANTS_NAME,
DESCENDANTS_SELF,
DESCENDANTS_CUMULATIVE,
DESCENDANTS_OBJECT
};
static char *
get_current_object (Application *app)
{
GtkTreeModel *model;
GtkTreeIter selected;
char *object;
if (gtk_tree_selection_get_selected (app->object_selection, &model, &selected))
{
gtk_tree_model_get (model, &selected,
OBJECT_OBJECT, &object,
-1);
return object;
}
else
{
return NULL;
}
}
static void
fill_main_list (Application *app)
{
GList *list;
GtkListStore *list_store;
Profile *profile = app->profile;
GList *objects;
if (profile)
{
list_store = gtk_list_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
objects = profile_get_objects (profile);
for (list = objects; list != NULL; list = list->next)
{
ProfileObject *object = list->data;
GtkTreeIter iter;
double profile_size = profile_get_size (profile);
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
OBJECT_NAME, object->name,
OBJECT_SELF, 100.0 * object->self / profile_size,
OBJECT_TOTAL, 100.0 * object->total / profile_size,
#if 0
OBJECT_SELF, (double)object->self,
OBJECT_TOTAL, (double)object->total,
#endif
OBJECT_OBJECT, object->name,
-1);
}
g_list_foreach (objects, (GFunc)g_free, NULL);
g_list_free (objects);
tree_view_set_model_with_default_sort (app->object_view, GTK_TREE_MODEL (list_store),
OBJECT_TOTAL, GTK_SORT_DESCENDING);
g_object_unref (G_OBJECT (list_store));
}
gtk_tree_view_columns_autosize (app->object_view);
}
static void
add_node (FooTreeStore *store,
int size,
const GtkTreeIter *parent,
ProfileDescendant *node)
{
GtkTreeIter iter;
if (!node)
return;
foo_tree_store_insert (store, &iter, (GtkTreeIter *)parent, 0);
foo_tree_store_set (store, &iter,
DESCENDANTS_NAME, node->name,
DESCENDANTS_SELF, 100 * (node->self)/(double)size,
DESCENDANTS_CUMULATIVE, 100 * (node->cumulative)/(double)size,
#if 0
DESCENDANTS_SELF, (double)node->self,
DESCENDANTS_CUMULATIVE, (double)node->non_recursion,
#endif
DESCENDANTS_OBJECT, node->name,
-1);
add_node (store, size, parent, node->siblings);
add_node (store, size, &iter, node->children);
}
static void
fill_descendants_tree (Application *app)
{
FooTreeStore *tree_store;
if (app->descendants)
{
profile_descendant_free (app->descendants);
app->descendants = NULL;
}
tree_store =
foo_tree_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
if (app->profile)
{
char *object = get_current_object (app);
if (object)
{
app->descendants =
profile_create_descendants (app->profile, object);
add_node (tree_store,
profile_get_size (app->profile), NULL, app->descendants);
}
}
tree_view_set_model_with_default_sort (app->descendants_view, GTK_TREE_MODEL (tree_store),
DESCENDANTS_CUMULATIVE, GTK_SORT_DESCENDING);
g_object_unref (G_OBJECT (tree_store));
gtk_tree_view_columns_autosize (app->descendants_view);
}
static void
add_callers (GtkListStore *list_store,
Profile *profile,
ProfileCaller *callers)
{
while (callers)
{
gchar *name;
GtkTreeIter iter;
double profile_size = profile_get_size (profile);
if (callers->name)
name = callers->name;
else
name = "<spontaneous>";
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (
list_store, &iter,
CALLERS_NAME, name,
CALLERS_SELF, 100.0 * callers->self / profile_size,
CALLERS_TOTAL, 100.0 * callers->total / profile_size,
#if 0
CALLERS_SELF, (double)callers->self,
CALLERS_TOTAL, (double)callers->total,
#endif
CALLERS_OBJECT, callers->name,
-1);
callers = callers->next;
}
}
static void
fill_callers_list (Application *app)
{
GtkListStore *list_store;
if (app->callers)
{
profile_caller_free (app->callers);
app->callers = NULL;
}
list_store =
gtk_list_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
if (app->profile)
{
char *object = get_current_object (app);
if (object)
{
app->callers = profile_list_callers (app->profile, object);
add_callers (list_store, app->profile, app->callers);
}
}
tree_view_set_model_with_default_sort (app->callers_view, GTK_TREE_MODEL (list_store),
CALLERS_TOTAL, GTK_SORT_DESCENDING);
g_object_unref (G_OBJECT (list_store));
gtk_tree_view_columns_autosize (app->callers_view);
}
static void
enter_display_mode (Application *app)
{
app->state = DISPLAYING;
update_sensitivity (app);
app->inhibit_forced_redraw = TRUE;
fill_main_list (app);
/* This has the side effect of selecting the first row, which in turn causes
* the other lists to be filled out
*/
gtk_widget_grab_focus (GTK_WIDGET (app->object_view));
app->inhibit_forced_redraw = FALSE;
}
static void
ensure_profile (Application *app)
{
if (app->profile)
return;
collector_stop (app->collector);
app->profile = collector_create_profile (app->collector);
collector_reset (app->collector);
enter_display_mode (app);
}
static void
on_about_activated (GtkWidget *widget, gpointer data)
{
#define OSLASH "\303\270"
Application *app = data;
char *name_property;
if (gtk_minor_version >= 12)
name_property = "program-name";
else
name_property = "name";
gtk_show_about_dialog (GTK_WINDOW (app->main_window),
"logo", app->icon,
name_property, APPLICATION_NAME,
"copyright", "Copyright 2004-2009, S"OSLASH"ren Sandmann",
"version", PACKAGE_VERSION,
NULL);
}
static void
on_profile_toggled (GtkWidget *widget, gpointer data)
{
Application *app = data;
if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (app->profile_button)))
{
set_busy (app->main_window, TRUE);
ensure_profile (app);
set_busy (app->main_window, FALSE);
}
}
static void
on_reset_clicked (gpointer widget, gpointer data)
{
Application *app = data;
set_busy (app->main_window, TRUE);
delete_data (app);
if (app->state == DISPLAYING)
{
app->state = INITIAL;
collector_stop (app->collector);
}
update_sensitivity (app);
set_busy (app->main_window, FALSE);
}
static gboolean
overwrite_file (GtkWindow *window,
const char *filename)
{
GtkWidget *msgbox;
gchar *utf8_file_name;
AtkObject *obj;
gint ret;
utf8_file_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
msgbox = gtk_message_dialog_new (window,
(GtkDialogFlags)GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
_("A file named \"%s\" already exists."),
utf8_file_name);
g_free (utf8_file_name);
gtk_message_dialog_format_secondary_text (
GTK_MESSAGE_DIALOG (msgbox),
_("Do you want to replace it with the one you are saving?"));
gtk_dialog_add_button (GTK_DIALOG (msgbox),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
gtk_dialog_add_button (GTK_DIALOG (msgbox),
_("_Replace"), GTK_RESPONSE_YES);
gtk_dialog_set_default_response (GTK_DIALOG (msgbox),
GTK_RESPONSE_CANCEL);
obj = gtk_widget_get_accessible (msgbox);
if (GTK_IS_ACCESSIBLE (obj))
atk_object_set_name (obj, _("Question"));
ret = gtk_dialog_run (GTK_DIALOG (msgbox));
gtk_widget_destroy (msgbox);
return (ret == GTK_RESPONSE_YES);
}
static void
on_save_as_clicked (gpointer widget,
gpointer data)
{
Application *app = data;
GtkWidget *dialog;
ensure_profile (app);
set_busy (app->main_window, TRUE);
dialog = gtk_file_chooser_dialog_new ("Save As",
GTK_WINDOW (app->main_window),
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
set_busy (app->main_window, FALSE);
retry:
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
GError *err = NULL;
gchar *filename;
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
if (g_file_test (filename, G_FILE_TEST_EXISTS) &&
!overwrite_file (GTK_WINDOW (app->main_window), filename))
{
g_free (filename);
goto retry;
}
set_busy (dialog, TRUE);
if (!profile_save (app->profile, filename, &err))
{
sorry (app->main_window, "Could not save %s: %s",
filename, err->message);
set_busy (dialog, FALSE);
g_free (filename);
goto retry;
}
set_application_title (app, filename);
set_busy (dialog, FALSE);
g_free (filename);
}
gtk_widget_destroy (dialog);
}
static void
set_loaded_profile (Application *app,
const char *name,
Profile *profile)
{
g_return_if_fail (name != NULL);
g_return_if_fail (profile != NULL);
collector_stop (app->collector);
delete_data (app);
app->profile = profile;
set_application_title (app, name);
enter_display_mode (app);
}
static void
show_could_not_open (Application *app,
const char *filename,
GError *err)
{
sorry (app->main_window,
"Could not open %s: %s",
filename,
err->message);
}
static void
on_open_clicked (gpointer widget,
gpointer data)
{
Application *app = data;
gchar *filename = NULL;
Profile *profile = NULL;
GtkWidget *dialog;
set_busy (app->main_window, TRUE);
dialog = gtk_file_chooser_dialog_new ("Open",
GTK_WINDOW (app->main_window),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
NULL);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
set_busy (app->main_window, FALSE);
retry:
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
GError *err = NULL;
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
set_busy (dialog, TRUE);
profile = profile_load (filename, &err);
if (!profile)
{
set_busy (dialog, FALSE);
show_could_not_open (app, filename, err);
g_error_free (err);
g_free (filename);
filename = NULL;
goto retry;
}
set_busy (dialog, FALSE);
}
gtk_widget_destroy (dialog);
if (profile)
{
g_assert (filename);
set_loaded_profile (app, filename, profile);
g_free (filename);
}
}
static void
on_delete (GtkWidget *window,
Application *app)
{
/* Workaround for http://bugzilla.gnome.org/show_bug.cgi?id=317775
*
* Without it, the read callbacks can fire _after_ gtk_main_quit()
* has been called and cause stuff to be called on destroyed widgets.
*/
while (gtk_main_iteration ())
;
gtk_main_quit ();
}
static void
expand_descendants_tree (Application *app)
{
GtkTreeModel *model = gtk_tree_view_get_model (app->descendants_view);
GtkTreeIter iter;
GList *all_paths = NULL;
int n_rows;
int max_rows = 40; /* FIXME */
double top_value = 0.0;
GtkTreePath *first_path;
GList *list;
first_path = gtk_tree_path_new_first();
all_paths = g_list_prepend (all_paths, first_path);
n_rows = 1;
gtk_tree_model_get_iter (model, &iter, first_path);
gtk_tree_model_get (model, &iter,
OBJECT_TOTAL, &top_value,
-1);
while (all_paths && n_rows < max_rows)
{
GtkTreeIter best_iter;
GtkTreePath *best_path;
double best_value;
int n_children;
int i;
best_value = 0.0;
best_path = NULL;
for (list = all_paths; list != NULL; list = list->next)
{
GtkTreePath *path = list->data;
GtkTreeIter iter;
g_assert (path != NULL);
if (gtk_tree_model_get_iter (model, &iter, path))
{
double value;
gtk_tree_model_get (model, &iter,
OBJECT_TOTAL, &value,
-1);
if (value >= best_value)
{
best_value = value;
best_path = path;
best_iter = iter;
}
}
}
n_children = gtk_tree_model_iter_n_children (model, &best_iter);
if (n_children && (best_value / top_value) > 0.04 &&
(n_children + gtk_tree_path_get_depth (best_path)) /
(double)max_rows < (best_value / top_value) )
{
gtk_tree_view_expand_row (
GTK_TREE_VIEW (app->descendants_view), best_path, FALSE);
n_rows += n_children;
if (gtk_tree_path_get_depth (best_path) < 4)
{
GtkTreePath *path = gtk_tree_path_copy (best_path);
gtk_tree_path_down (path);
for (i = 0; i < n_children; ++i)
{
all_paths = g_list_prepend (all_paths, path);
path = gtk_tree_path_copy (path);
gtk_tree_path_next (path);
}
gtk_tree_path_free (path);
}
}
all_paths = g_list_remove (all_paths, best_path);
if (!all_paths && n_rows == 1)
{
/* Always expand at least once */
gtk_tree_view_expand_row (GTK_TREE_VIEW (app->descendants_view),
best_path, FALSE);
}
gtk_tree_path_free (best_path);
}
for (list = all_paths; list != NULL; list = list->next)
gtk_tree_path_free (list->data);
g_list_free (all_paths);
}
static void
get_data (GtkTreeView *view,
GtkTreeIter *iter,
gchar **name,
double *self,
double *cumulative)
{
char *dummy1;
double dummy2;
double dummy3;
GtkTreeModel *model = gtk_tree_view_get_model (view);
gtk_tree_model_get (
model, iter,
DESCENDANTS_NAME, name? name : &dummy1,
DESCENDANTS_SELF, self? self : &dummy2,
DESCENDANTS_CUMULATIVE, cumulative? cumulative : &dummy3,
-1);
}
static int
get_indent (GtkTreePath *path)
{
return 2 * (gtk_tree_path_get_depth (path) - 1);
}
static void
compute_text_width (GtkTreeView *view,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer data)
{
int *width = data;
char *name;
get_data (view, iter, &name, NULL, NULL);
*width = MAX (g_utf8_strlen (name, -1) + get_indent (path), *width);
g_free (name);
}
typedef struct
{
int max_width;
GString *text;
} AddTextInfo;
static void
set_monospace (GtkWidget *widget)
{
PangoFontDescription *desc =
pango_font_description_from_string ("monospace");
gtk_widget_modify_font (widget, desc);
pango_font_description_free (desc);
}
static void
add_text (GtkTreeView *view,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer data)
{
AddTextInfo *info = data;
char *name;
double self;
double cumulative;
int indent;
int i;
get_data (view, iter, &name, &self, &cumulative);
indent = get_indent (path);
for (i = 0; i < indent; ++i)
g_string_append_c (info->text, ' ');
g_string_append_printf (info->text, "%-*s %6.2f%% %6.2f%%\n",
info->max_width - indent, name, self, cumulative);
g_free (name);
}
static gboolean
update_screenshot_window_idle (gpointer data)
{
Application *app = data;
GtkTextBuffer *text_buffer;
if (!app->screenshot_window_visible)
return FALSE;
text_buffer =
gtk_text_view_get_buffer (GTK_TEXT_VIEW (app->screenshot_textview));
gtk_text_buffer_set_text (text_buffer, "", -1);
if (app->descendants)
{
AddTextInfo info;
info.max_width = 0;
info.text = g_string_new ("");
tree_view_foreach_visible (app->descendants_view,
compute_text_width,
&info.max_width);
tree_view_foreach_visible (app->descendants_view,
add_text,
&info);
gtk_text_buffer_set_text (text_buffer, info.text->str, -1);
set_monospace (app->screenshot_textview);
g_string_free (info.text, TRUE);
}
app->update_screenshot_id = 0;
if (app->screenshot_window_visible)
{
set_busy (app->screenshot_window, FALSE);
set_busy (app->screenshot_textview, FALSE);
}
return FALSE;
}
static void
update_screenshot_window (Application *app)
{
/* We do this in an idle handler to deal with the case where
* someone presses Shift-RightArrow on the root of a huge
* profile. This causes a ton of 'expanded' notifications,
* each of which would cause us to traverse the tree and
* update the screenshot window.
*/
if (app->update_screenshot_id)
g_source_remove (app->update_screenshot_id);
if (app->screenshot_window_visible)
{
/* don't swamp the X server with cursor change requests */
if (!app->update_screenshot_id)
{
set_busy (app->screenshot_window, TRUE);
set_busy (app->screenshot_textview, TRUE);
}
}
app->update_screenshot_id = g_idle_add (
update_screenshot_window_idle, app);
}
static void
on_descendants_row_expanded_or_collapsed (GtkTreeView *tree,
GtkTreeIter *iter,
GtkTreePath *path,
Application *app)
{
update_screenshot_window (app);
}
static void
on_object_selection_changed (GtkTreeSelection *selection,
gpointer data)
{
Application *app = data;
set_busy (app->main_window, TRUE);
update_screenshot_window (app);
if (!app->inhibit_forced_redraw)
gdk_window_process_all_updates (); /* Display updated selection */
fill_descendants_tree (app);
fill_callers_list (app);
if (get_current_object (app))
expand_descendants_tree (app);
set_busy (app->main_window, FALSE);
}
static void
really_goto_object (Application *app,
char *object)
{
GtkTreeModel *profile_objects;
GtkTreeIter iter;
gboolean found = FALSE;
profile_objects = gtk_tree_view_get_model (app->object_view);
if (gtk_tree_model_get_iter_first (profile_objects, &iter))
{
do
{
char *list_object;
gtk_tree_model_get (profile_objects, &iter,
OBJECT_OBJECT, &list_object,
-1);
if (list_object == object)
{
found = TRUE;
break;
}
}
while (gtk_tree_model_iter_next (profile_objects, &iter));
}
if (found)
{
GtkTreePath *path =
gtk_tree_model_get_path (profile_objects, &iter);
gtk_tree_view_set_cursor (app->object_view, path, 0, FALSE);
}
}
static void
goto_object (Application *app,
GtkTreeView *tree_view,
GtkTreePath *path,
gint column)
{
GtkTreeIter iter;
GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
char *object;
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (model, &iter, column, &object, -1);
if (!object)
return;
really_goto_object (app, object);
}
static void
on_descendants_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer data)
{
Application *app = data;
goto_object (app, tree_view, path, DESCENDANTS_OBJECT);
gtk_widget_grab_focus (GTK_WIDGET (app->descendants_view));
}
static void
on_callers_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer data)
{
Application *app = data;
goto_object (app, tree_view, path, CALLERS_OBJECT);
gtk_widget_grab_focus (GTK_WIDGET (app->callers_view));
}
static void
on_screenshot_activated (GtkCheckMenuItem *menu_item,
Application *app)
{
app->screenshot_window_visible = gtk_check_menu_item_get_active (menu_item);
update_screenshot_window (app);
update_sensitivity (app);
}
static void
on_screenshot_window_delete (GtkWidget *window,
GdkEvent *event,
Application *app)
{
app->screenshot_window_visible = FALSE;
update_sensitivity (app);
}
static void
on_screenshot_close_button_clicked (GtkWidget *widget,
Application *app)
{
app->screenshot_window_visible = FALSE;
update_sensitivity (app);
}
static void
set_sizes (GtkWindow *window,
GtkWindow *screenshot_window,
GtkWidget *hpaned,
GtkWidget *vpaned)
{
GdkScreen *screen;
int monitor_num;
GdkRectangle monitor;
int width, height;
GtkWidget *widget = GTK_WIDGET (window);
screen = gtk_widget_get_screen (widget);
monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
width = monitor.width * 3 / 4;
height = monitor.height * 3 / 4;
gtk_window_resize (window, width, height);
gtk_paned_set_position (GTK_PANED (vpaned), height / 2);
gtk_paned_set_position (GTK_PANED (hpaned), width * 3 / 8);
width = monitor.width * 5 / 8;
height = monitor.height * 5 / 8;
gtk_window_resize (screenshot_window, width, height);
}
#define GLADE_FILE DATADIR "/sysprof.glade"
static void
gather_widgets (Application *app)
{
typedef struct
{
void *location;
const char *name;
} WidgetInfo;
const WidgetInfo widgets[] =
{
{ &app->main_window, "main_window" },
{ &app->start_button, "start_button" },
{ &app->profile_button, "profile_button" },
{ &app->reset_button, "reset_button" },
{ &app->save_as_button, "save_as_button" },
{ &app->dummy_button, "dummy_button" },
{ &app->samples_label, "samples_label" },
{ &app->samples_hbox, "samples_hbox" },
{ &app->start_item, "start_item" },
{ &app->profile_item, "profile_item" },
{ &app->reset_item, "reset_item" },
{ &app->open_item, "open_item" },
{ &app->save_as_item, "save_as_item" },
{ &app->screenshot_item, "screenshot_item" },
{ &app->quit_item, "quit" },
{ &app->about_item, "about" },
{ &app->object_view, "object_view" },
{ &app->callers_view, "callers_view" },
{ &app->descendants_view, "descendants_view" },
{ &app->screenshot_window, "screenshot_window" },
{ &app->screenshot_textview, "screenshot_textview" },
{ &app->screenshot_close_button, "screenshot_close_button" },
{ &app->vpaned, "vpaned" },
{ &app->hpaned, "hpaned" },
};
GladeXML *xml = glade_xml_new (GLADE_FILE, NULL, NULL);
int i;
for (i = 0; i < G_N_ELEMENTS (widgets); ++i)
{
const WidgetInfo *info = &(widgets[i]);
*(GtkWidget **)(info->location) = glade_xml_get_widget (xml, info->name);
g_assert (GTK_IS_WIDGET (*(GtkWidget **)info->location));
}
g_object_unref (xml);
}
static void
connect_signals (Application *app)
{
typedef struct
{
gpointer object;
const char *signal;
gpointer callback;
gpointer data;
} SignalInfo;
const SignalInfo signals[] =
{
{ app->main_window, "delete_event", on_delete, NULL },
{ app->start_button, "toggled", on_start_toggled, app },
{ app->profile_button, "toggled", on_profile_toggled, app },
{ app->reset_button, "clicked", on_reset_clicked, app },
{ app->save_as_button, "clicked", on_save_as_clicked, app },
{ app->start_item, "activate", on_menu_item_activated, app->start_button },
{ app->profile_item, "activate", on_menu_item_activated, app->profile_button },
{ app->reset_item, "activate", on_reset_clicked, app },
{ app->open_item, "activate", on_open_clicked, app },
{ app->save_as_item, "activate", on_save_as_clicked, app },
{ app->screenshot_item, "activate", on_screenshot_activated, app },
{ app->quit_item, "activate", on_delete, NULL },
{ app->about_item, "activate", on_about_activated, app },
{ app->object_selection, "changed", on_object_selection_changed, app },
{ app->callers_view, "row-activated", on_callers_row_activated, app },
{ app->descendants_view, "row-activated", on_descendants_row_activated, app },
{ app->descendants_view, "row-expanded", on_descendants_row_expanded_or_collapsed, app },
{ app->descendants_view, "row-collapsed", on_descendants_row_expanded_or_collapsed, app },
{ app->screenshot_window, "delete_event", on_screenshot_window_delete, app },
{ app->screenshot_close_button, "clicked", on_screenshot_close_button_clicked, app },
};
int i;
for (i = 0; i < G_N_ELEMENTS (signals); ++i)
{
const SignalInfo *info = &(signals[i]);
g_signal_connect (info->object, info->signal, info->callback, info->data);
}
}
static void
set_shadows (void)
{
/* Get rid of motif out-bevels */
gtk_rc_parse_string (
"style \"blah\" "
"{ "
" GtkToolbar::shadow_type = none "
" GtkMenuBar::shadow_type = none "
" GtkMenuBar::internal_padding = 2 "
"} "
"widget \"*toolbar\" style : rc \"blah\"\n"
"widget \"*menubar\" style : rc \"blah\"\n"
);
}
static void
set_icons (Application *app)
{
const char *icon_files [] = {
PIXMAPDIR "/sysprof-icon-16.png",
PIXMAPDIR "/sysprof-icon-24.png",
PIXMAPDIR "/sysprof-icon-32.png",
PIXMAPDIR "/sysprof-icon-48.png",
NULL
};
GList *pixbufs = NULL;
int i;
for (i = 0; icon_files[i] != NULL; ++i)
{
const char *file = icon_files[i];
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (file, NULL);
if (pixbuf)
{
pixbufs = g_list_prepend (pixbufs, pixbuf);
if (i == 3) /* 48 x 48 */
app->icon = g_object_ref (pixbuf);
}
else
{
g_warning ("Could not open %s\n", file);
}
}
gtk_window_set_icon_list (GTK_WINDOW (app->main_window), pixbufs);
g_list_foreach (pixbufs, (GFunc)g_object_unref, NULL);
g_list_free (pixbufs);
}
#define PCT_FORMAT "%.2f<span size='smaller'><span size='smaller'> </span>%%</span>"
static gboolean
build_gui (Application *app)
{
GtkTreeViewColumn *col;
set_shadows ();
if (!g_file_test (GLADE_FILE, G_FILE_TEST_EXISTS))
{
sorry (NULL,
"Sysprof was not compiled or installed correctly.\n"
"\n"
"Running \"make install\" may solve this problem.\n");
return FALSE;
}
gather_widgets (app);
g_assert (app->main_window);
/* Main Window */
set_icons (app);
gtk_widget_realize (GTK_WIDGET (app->main_window));
/* Tool items */
gtk_toggle_tool_button_set_active (
GTK_TOGGLE_TOOL_BUTTON (app->profile_button), FALSE);
/* TreeViews */
/* object view */
gtk_tree_view_set_enable_search (app->object_view, FALSE);
col = add_plain_text_column (app->object_view, _("Functions"),
OBJECT_NAME);
add_double_format_column (app->object_view, _("Self"),
OBJECT_SELF, PCT_FORMAT);
add_double_format_column (app->object_view, _("Total"),
OBJECT_TOTAL, PCT_FORMAT);
app->object_selection = gtk_tree_view_get_selection (app->object_view);
gtk_tree_view_column_set_expand (col, TRUE);
/* callers view */
gtk_tree_view_set_enable_search (app->callers_view, FALSE);
col = add_plain_text_column (app->callers_view, _("Callers"),
CALLERS_NAME);
add_double_format_column (app->callers_view, _("Self"),
CALLERS_SELF, PCT_FORMAT);
add_double_format_column (app->callers_view, _("Total"),
CALLERS_TOTAL, PCT_FORMAT);
gtk_tree_view_column_set_expand (col, TRUE);
/* descendants view */
gtk_tree_view_set_enable_search (app->descendants_view, FALSE);
col = add_plain_text_column (app->descendants_view, _("Descendants"),
DESCENDANTS_NAME);
add_double_format_column (app->descendants_view, _("Self"),
DESCENDANTS_SELF, PCT_FORMAT);
add_double_format_column (app->descendants_view, _("Cumulative"),
DESCENDANTS_CUMULATIVE, PCT_FORMAT);
gtk_tree_view_column_set_expand (col, TRUE);
/* screenshot window */
/* set sizes */
set_sizes (GTK_WINDOW (app->main_window),
GTK_WINDOW (app->screenshot_window),
app->hpaned, app->vpaned);
/* hide/show widgets */
gtk_widget_show_all (app->main_window);
gtk_widget_hide (app->dummy_button);
gtk_widget_hide (app->screenshot_window);
gtk_widget_grab_focus (GTK_WIDGET (app->object_view));
queue_show_samples (app);
connect_signals (app);
return TRUE;
}
static void
on_new_sample (gboolean first_sample,
gpointer data)
{
Application *app = data;
if (app->state == PROFILING && first_sample)
update_sensitivity (app);
else
queue_show_samples (app);
}
static Application *
application_new (void)
{
Application *app = g_new0 (Application, 1);
app->collector = collector_new (FALSE, on_new_sample, app);
app->state = INITIAL;
return app;
}
typedef struct
{
const char *filename;
Application *app;
} FileOpenData;
static gboolean
load_file (gpointer data)
{
FileOpenData *file_open_data = data;
const char *filename = file_open_data->filename;
Application *app = file_open_data->app;
GError *err = NULL;
Profile *profile;
set_busy (app->main_window, TRUE);
profile = profile_load (filename, &err);
if (profile)
{
set_loaded_profile (app, filename, profile);
gdk_window_process_all_updates ();
set_busy (app->main_window, FALSE);
}
else
{
set_busy (app->main_window, FALSE);
show_could_not_open (app, filename, err);
g_error_free (err);
}
g_free (file_open_data);
return FALSE;
}
static const char *
process_options (int argc,
char **argv)
{
int i;
gboolean show_version = FALSE;
const char *filename = NULL;
for (i = 1; i < argc; ++i)
{
char *option = argv[i];
if (strcmp (option, "--version") == 0)
{
show_version = TRUE;
}
else if (!filename)
{
filename = argv[i];
}
}
if (show_version)
{
g_print ("%s %s\n", APPLICATION_NAME, PACKAGE_VERSION);
exit (1);
}
return filename;
}
static void
apply_workarounds (void)
{
/* Disable gslice, since it
*
* - confuses valgrind
* - caches too much memory
* - hides memory access bugs
* - is not faster than malloc
*
* Note that g_slice_set_config() is broken in some versions of
* GLib (and 'declared internal' according to Tim), so we use the
* environment variable instead.
*/
if (!getenv ("G_SLICE"))
putenv ("G_SLICE=always_malloc");
/* Accessibility prevents sysprof from working reliably, so
* disable it. Specifically, it
*
* - causes large amounts of time to be spent in sysprof itself
* whenever the label is updated.
* - sometimes hangs at shutdown
* - does long-running roundtrip requests that prevents
* reading the event buffers, resulting in lost events.
*/
putenv ("NO_GAIL=1");
putenv ("NO_AT_BRIDGE=1");
}
int
main (int argc,
char **argv)
{
Application *app;
const char *filename;
apply_workarounds();
filename = process_options (argc, argv);
gtk_init (&argc, &argv);
app = application_new ();
if (!build_gui (app))
return -1;
update_sensitivity (app);
if (filename)
{
FileOpenData *file_open_data = g_new0 (FileOpenData, 1);
file_open_data->filename = filename;
file_open_data->app = app;
/* This has to run at G_PRIORITY_LOW because of bug 350517
*/
g_idle_add_full (G_PRIORITY_LOW, load_file, file_open_data, NULL);
}
gtk_main ();
return 0;
}