Files
sysprof/sysprof.c
Soren Sandmann f06e272ea7 Make the load_file() idle low priority to avoid weird toolbar flash.
2006-08-08  Soren Sandmann  <ssp@localhost.localdomain>

	* sysprof.c (main): Make the load_file() idle low priority to
	avoid weird toolbar flash.

	* TODO: updates.
2006-08-09 03:05:34 +00:00

1593 lines
38 KiB
C

/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, 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 "treeviewutils.h"
#include "profile.h"
#include "collector.h"
#include "module/sysprof-module.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;
GtkWidget * main_window;
GdkPixbuf * icon;
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 * samples_label;
gboolean screenshot_window_visible;
GtkWidget * screenshot_textview;
GtkWidget * screenshot_close_button;
GtkWidget * screenshot_window;
Profile * profile;
ProfileDescendant * descendants;
ProfileCaller * callers;
int timeout_id;
char * loaded_profile;
gboolean profile_from_file; /* FIXME - not10: This is a kludge. Figure out how
* to maintain the application model properly
*
* The fundamental issue is that the state of
* widgets is controlled by two different
* entities:
*
* The user clicks on them, changing their
* state.
*
* The application model changes, changing their
* state.
*
* Model/View/Controller is a possibility.
*/
};
static void update_screenshot_window (Application *app);
static gboolean
show_samples_timeout (gpointer data)
{
Application *app = data;
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 ("Samples: %d", n_samples);
gtk_label_set_label (GTK_LABEL (app->samples_label), label);
g_free (label);
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_label;
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_label = 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_label = 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_label = 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_label), sensitive_samples_label);
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);
queue_show_samples (app);
}
static void
set_busy (GtkWidget *widget,
gboolean busy)
{
GdkCursor *cursor;
if (busy)
cursor = gdk_cursor_new (GDK_WATCH);
else
cursor = NULL;
gdk_window_set_cursor (widget->window, cursor);
if (cursor)
gdk_cursor_unref (cursor);
gdk_display_flush (gdk_display_get_default ());
}
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);
queue_show_samples (app);
app->profile_from_file = FALSE;
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, 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_NON_RECURSE,
DESCENDANTS_OBJECT
};
static char *
get_current_object (Application *app)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter selected;
char *object;
selection = gtk_tree_view_get_selection (app->object_view);
if (gtk_tree_selection_get_selected (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)
{
gpointer sort_state;
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,
OBJECT_OBJECT, object->name,
-1);
}
g_list_foreach (objects, (GFunc)g_free, NULL);
g_list_free (objects);
sort_state = save_sort_state (app->object_view);
gtk_tree_view_set_model (app->object_view, GTK_TREE_MODEL (list_store));
if (sort_state)
{
restore_sort_state (app->object_view, sort_state);
}
else
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (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 (GtkTreeStore *store,
int size,
const GtkTreeIter *parent,
ProfileDescendant *node)
{
GtkTreeIter iter;
if (!node)
return;
gtk_tree_store_insert (store, &iter, (GtkTreeIter *)parent, 0);
gtk_tree_store_set (store, &iter,
DESCENDANTS_NAME, node->name,
DESCENDANTS_SELF, 100 * (node->self)/(double)size,
DESCENDANTS_NON_RECURSE, 100 * (node->non_recursion)/(double)size,
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)
{
GtkTreeStore *tree_store;
gpointer sort_state;
sort_state = save_sort_state (app->descendants_view);
if (app->descendants)
{
profile_descendant_free (app->descendants);
app->descendants = NULL;
}
tree_store =
gtk_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);
}
}
gtk_tree_view_set_model (
app->descendants_view, GTK_TREE_MODEL (tree_store));
g_object_unref (G_OBJECT (tree_store));
if (!sort_state)
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree_store),
DESCENDANTS_NON_RECURSE,
GTK_SORT_DESCENDING);
}
else
{
restore_sort_state (app->descendants_view, sort_state);
}
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,
CALLERS_OBJECT, callers->name,
-1);
callers = callers->next;
}
}
static void
fill_callers_list (Application *app)
{
GtkListStore *list_store;
gpointer sort_state;
sort_state = save_sort_state (app->descendants_view);
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);
}
}
gtk_tree_view_set_model (
app->callers_view, GTK_TREE_MODEL (list_store));
g_object_unref (G_OBJECT (list_store));
if (!sort_state)
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
CALLERS_TOTAL,
GTK_SORT_DESCENDING);
}
else
{
restore_sort_state (app->callers_view, sort_state);
}
gtk_tree_view_columns_autosize (app->callers_view);
}
static void
fill_lists (Application *app)
{
fill_main_list (app);
fill_callers_list (app);
fill_descendants_tree (app);
}
static void
ensure_profile (Application *app)
{
if (app->profile)
return;
app->profile = collector_create_profile (app->collector);
collector_stop (app->collector);
fill_lists (app);
app->state = DISPLAYING;
update_sensitivity (app);
}
static void
on_about_activated (GtkWidget *widget, gpointer data)
{
#define OSLASH "\303\270"
Application *app = data;
gtk_show_about_dialog (GTK_WINDOW (app->main_window),
"logo", app->icon,
"name", APPLICATION_NAME,
"copyright", "Copyright 2004-2006, 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);
if (app->profile && !app->profile_from_file)
{
profile_free (app->profile);
app->profile = NULL;
}
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);
set_busy (app->main_window, TRUE);
delete_data (app);
app->state = DISPLAYING;
app->profile = profile;
app->profile_from_file = TRUE;
fill_lists (app);
set_application_title (app, name);
update_sensitivity (app);
collector_stop (app->collector);
set_busy (app->main_window, FALSE);
}
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_NON_RECURSE, 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);
}
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);
}
static void
update_screenshot_window (Application *app)
{
GtkTextBuffer *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);
}
}
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);
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);
update_screenshot_window (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_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);
}
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"
);
}
#define GLADE_FILE DATADIR "/sysprof.glade"
#define ICON_FILE PIXMAPDIR "/sysprof-icon.png"
static gboolean
build_gui (Application *app)
{
GladeXML *xml;
GtkTreeSelection *selection;
GtkTreeViewColumn *col;
set_shadows ();
if (!g_file_test (GLADE_FILE, G_FILE_TEST_EXISTS) ||
!g_file_test (ICON_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;
}
xml = glade_xml_new (GLADE_FILE, NULL, NULL);
/* Main Window */
app->main_window = glade_xml_get_widget (xml, "main_window");
app->icon = gdk_pixbuf_new_from_file (ICON_FILE, NULL);
gtk_window_set_icon (GTK_WINDOW (app->main_window), app->icon);
g_signal_connect (G_OBJECT (app->main_window), "delete_event",
G_CALLBACK (on_delete), NULL);
gtk_widget_realize (GTK_WIDGET (app->main_window));
/* Tool items */
app->start_button = glade_xml_get_widget (xml, "start_button");
app->profile_button = glade_xml_get_widget (xml, "profile_button");
app->reset_button = glade_xml_get_widget (xml, "reset_button");
app->save_as_button = glade_xml_get_widget (xml, "save_as_button");
app->dummy_button = glade_xml_get_widget (xml, "dummy_button");
gtk_toggle_tool_button_set_active (
GTK_TOGGLE_TOOL_BUTTON (app->profile_button), FALSE);
g_signal_connect (G_OBJECT (app->start_button), "toggled",
G_CALLBACK (on_start_toggled), app);
g_signal_connect (G_OBJECT (app->profile_button), "toggled",
G_CALLBACK (on_profile_toggled), app);
g_signal_connect (G_OBJECT (app->reset_button), "clicked",
G_CALLBACK (on_reset_clicked), app);
g_signal_connect (G_OBJECT (app->save_as_button), "clicked",
G_CALLBACK (on_save_as_clicked), app);
app->samples_label = glade_xml_get_widget (xml, "samples_label");
/* Menu items */
app->start_item = glade_xml_get_widget (xml, "start_item");
app->profile_item = glade_xml_get_widget (xml, "profile_item");
app->reset_item = glade_xml_get_widget (xml, "reset_item");
app->open_item = glade_xml_get_widget (xml, "open_item");
app->save_as_item = glade_xml_get_widget (xml, "save_as_item");
app->screenshot_item = glade_xml_get_widget (xml, "screenshot_item");
g_assert (app->start_item);
g_assert (app->profile_item);
g_assert (app->save_as_item);
g_assert (app->open_item);
g_signal_connect (G_OBJECT (app->start_item), "activate",
G_CALLBACK (on_menu_item_activated), app->start_button);
g_signal_connect (G_OBJECT (app->profile_item), "activate",
G_CALLBACK (on_menu_item_activated), app->profile_button);
g_signal_connect (G_OBJECT (app->reset_item), "activate",
G_CALLBACK (on_reset_clicked), app);
g_signal_connect (G_OBJECT (app->open_item), "activate",
G_CALLBACK (on_open_clicked), app);
g_signal_connect (G_OBJECT (app->save_as_item), "activate",
G_CALLBACK (on_save_as_clicked), app);
g_signal_connect (G_OBJECT (app->screenshot_item), "activate",
G_CALLBACK (on_screenshot_activated), app);
g_signal_connect (G_OBJECT (glade_xml_get_widget (xml, "quit")), "activate",
G_CALLBACK (on_delete), NULL);
g_signal_connect (G_OBJECT (glade_xml_get_widget (xml, "about")), "activate",
G_CALLBACK (on_about_activated), app);
/* TreeViews */
/* object view */
app->object_view =
(GtkTreeView *)glade_xml_get_widget (xml, "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, "%.2f ");
add_double_format_column (app->object_view, _("Total"), OBJECT_TOTAL, "%.2f ");
selection = gtk_tree_view_get_selection (app->object_view);
g_signal_connect (selection, "changed", G_CALLBACK (on_object_selection_changed), app);
gtk_tree_view_column_set_expand (col, TRUE);
/* callers view */
app->callers_view = (GtkTreeView *)glade_xml_get_widget (xml, "callers_view");
gtk_tree_view_set_enable_search (app->callers_view, FALSE);
col = add_plain_text_column (app->callers_view, _("Ancestors"), CALLERS_NAME);
add_double_format_column (app->callers_view, _("Self"), CALLERS_SELF, "%.2f ");
add_double_format_column (app->callers_view, _("Total"), CALLERS_TOTAL, "%.2f ");
g_signal_connect (app->callers_view, "row-activated",
G_CALLBACK (on_callers_row_activated), app);
gtk_tree_view_column_set_expand (col, TRUE);
/* descendants view */
app->descendants_view = (GtkTreeView *)glade_xml_get_widget (xml, "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, "%.2f ");
add_double_format_column (app->descendants_view, _("Cumulative"), DESCENDANTS_NON_RECURSE, "%.2f ");
g_signal_connect (app->descendants_view, "row-activated",
G_CALLBACK (on_descendants_row_activated), app);
g_signal_connect (app->descendants_view, "row_expanded",
G_CALLBACK (on_descendants_row_expanded_or_collapsed), app);
g_signal_connect (app->descendants_view, "row_collapsed",
G_CALLBACK (on_descendants_row_expanded_or_collapsed), app);
gtk_tree_view_column_set_expand (col, TRUE);
/* screenshot window */
app->screenshot_window = glade_xml_get_widget (xml, "screenshot_window");
app->screenshot_textview = glade_xml_get_widget (xml, "screenshot_textview");
app->screenshot_close_button = glade_xml_get_widget (xml, "screenshot_close_button");
g_signal_connect (app->screenshot_window, "delete_event",
G_CALLBACK (on_screenshot_window_delete), app);
g_signal_connect (app->screenshot_close_button, "clicked",
G_CALLBACK (on_screenshot_close_button_clicked), app);
/* set sizes */
set_sizes (GTK_WINDOW (app->main_window),
GTK_WINDOW (app->screenshot_window),
glade_xml_get_widget (xml, "hpaned"),
glade_xml_get_widget (xml, "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);
return TRUE;
}
static void
on_new_sample (gpointer data)
{
Application *app = data;
if (app->state == PROFILING)
update_sensitivity (app);
}
static Application *
application_new (void)
{
Application *app = g_new0 (Application, 1);
app->collector = collector_new (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);
set_busy (app->main_window, FALSE);
if (profile)
{
set_loaded_profile (app, filename, profile);
}
else
{
show_could_not_open (app, filename, err);
g_error_free (err);
}
g_free (file_open_data);
return FALSE;
}
int
main (int argc,
char **argv)
{
Application *app;
gtk_init (&argc, &argv);
#if 0
/* FIXME: enable this when compiled against the relevant glib
* version. The reason we want to disable it is that gslice
* - confuses valgrind
* - caches too much memory
* - is not actually faster
*/
g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE);
#endif
app = application_new ();
if (!build_gui (app))
return -1;
update_sensitivity (app);
if (argc > 1)
{
FileOpenData *file_open_data = g_new0 (FileOpenData, 1);
file_open_data->filename = argv[1];
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;
}