Files
sysprof/sysprof.c
Soren Sandmann 0513ed87e8 Valgrind:
2006-11-02  Soren Sandmann <sandmann@daimi.au.dk>

        Valgrind:

        * binparser.c (bin_parser_free): Add this function

        * elfparser.c (elf_parser_free): Call bin_parser_free()

        * sysprof.c (compute_text_width, add_text): Plug leaks

        * collector.c (add_trace_to_stash): Copy n_addresses to a stack
        variable instead of reading it out of the mmap'ed area all the
        time. (That way if there is an overrun, we won't write too much
        into the address array).
2006-11-02 08:33:35 +00:00

1635 lines
39 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 <string.h>
#include <stdlib.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);
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 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, _("Callers"), 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;
}
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;
}
int
main (int argc,
char **argv)
{
Application *app;
const char *filename;
filename = process_options (argc, argv);
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
*/
#endif
#if 0
g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE);
#endif
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;
}