mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
perf lets you decide to only get events that concerns a single process and thus make sysprof profile this process instead of the whole system (it can happen that you don't really care about other processes that are just then noise). As a side effect, this allows sysprof to not run as root if you have the rights on the process you want to profile.
1735 lines
39 KiB
C
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, -1, &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;
|
|
}
|