blob: 4d75fb5500e905d5f2ebfd266b0050622ea8bc6b [file] [log] [blame]
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include "trace-cmd.h"
#include "trace-local.h"
#include "trace-view-store.h"
#include "trace-view.h"
#include "cpu.h"
#define DIALOG_WIDTH 400
#define DIALOG_HEIGHT 600
int str_cmp(const void *a, const void *b)
{
char * const * sa = a;
char * const * sb = b;
return strcmp(*sa, *sb);
}
int id_cmp(const void *a, const void *b)
{
const gint *ia = a;
const gint *ib = b;
if (*ia > *ib)
return 1;
if (*ia < *ib)
return -1;
return 0;
}
struct dialog_helper {
GtkWidget *dialog;
gpointer data;
};
enum {
COL_EVENT,
COL_ACTIVE,
COL_ACTIVE_START,
COL_EVENT_ID,
NUM_EVENT_COLS,
};
struct event_filter_helper {
trace_filter_event_cb_func func;
GtkTreeView *view;
gpointer data;
};
gboolean system_is_enabled(gchar **systems, gint systems_size, const gchar *system)
{
const gchar **sys = &system;
if (!systems)
return FALSE;
sys = bsearch(sys, systems, systems_size, sizeof(system), str_cmp);
return sys != NULL;
}
gboolean event_is_enabled(gint *events, gint events_size, gint event)
{
gint *ret;
if (!events)
return FALSE;
ret = bsearch(&event, events, events_size, sizeof(gint), id_cmp);
return ret != NULL;
}
static GtkTreeModel *
create_tree_event_model(struct tracecmd_input *handle,
gboolean all_events, gchar **systems_set,
gint *event_ids_set)
{
GtkTreeStore *treestore;
GtkTreeIter iter_all, iter_sys, iter_events;
struct pevent *pevent;
struct event_format **events;
struct event_format *event;
char *last_system = NULL;
gboolean sysactive;
gboolean active;
gchar **systems = NULL;
gint *event_ids = NULL;
gint systems_size;
gint event_ids_size;
gint i;
pevent = tracecmd_get_pevent(handle);
treestore = gtk_tree_store_new(NUM_EVENT_COLS, G_TYPE_STRING,
G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
G_TYPE_INT);
gtk_tree_store_append(treestore, &iter_all, NULL);
gtk_tree_store_set(treestore, &iter_all,
COL_EVENT, "All",
COL_ACTIVE, all_events,
COL_ACTIVE_START, FALSE,
-1);
events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
if (!events)
return GTK_TREE_MODEL(treestore);
if (systems_set) {
for (systems_size = 0; systems_set[systems_size]; systems_size++)
;
systems = g_new(typeof(*systems), systems_size + 1);
memcpy(systems, systems_set, sizeof(*systems) * (systems_size + 1));
qsort(systems, systems_size, sizeof(gchar *), str_cmp);
}
if (event_ids_set) {
for (event_ids_size = 0; event_ids_set[event_ids_size] != -1; event_ids_size++)
;
event_ids = g_new(typeof(*event_ids), event_ids_size + 1);
memcpy(event_ids, event_ids_set, sizeof(*event_ids) * (event_ids_size + 1));
qsort(event_ids, event_ids_size, sizeof(gint), id_cmp);
}
for (i = 0; events[i]; i++) {
event = events[i];
if (!last_system || strcmp(last_system, event->system) != 0) {
gtk_tree_store_append(treestore, &iter_sys, &iter_all);
sysactive = all_events ||
system_is_enabled(systems, systems_size, event->system);
gtk_tree_store_set(treestore, &iter_sys,
COL_EVENT, event->system,
COL_ACTIVE, sysactive,
-1);
last_system = event->system;
}
active = all_events || sysactive ||
event_is_enabled(event_ids, event_ids_size, event->id);
gtk_tree_store_append(treestore, &iter_events, &iter_sys);
gtk_tree_store_set(treestore, &iter_events,
COL_EVENT, event->name,
COL_ACTIVE, active,
COL_EVENT_ID, event->id,
-1);
}
g_free(systems);
g_free(event_ids);
return GTK_TREE_MODEL(treestore);
}
static void update_active_events(GtkTreeModel *model, GtkTreeIter *parent,
gboolean active)
{
GtkTreeIter event;
if (!gtk_tree_model_iter_children(model, &event, parent))
return;
for (;;) {
gtk_tree_store_set(GTK_TREE_STORE(model), &event,
COL_ACTIVE, active,
-1);
if (!gtk_tree_model_iter_next(model, &event))
break;
}
}
static void update_active_systems(GtkTreeModel *model, GtkTreeIter *parent,
gboolean active)
{
GtkTreeIter sys;
if (!gtk_tree_model_iter_children(model, &sys, parent))
return;
for (;;) {
gtk_tree_store_set(GTK_TREE_STORE(model), &sys,
COL_ACTIVE, active,
-1);
update_active_events(model, &sys, active);
if (!gtk_tree_model_iter_next(model, &sys))
break;
}
}
static void event_cursor_changed(GtkTreeView *treeview, gpointer data)
{
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter, parent, grandparent;
gboolean active, start;
gint depth;
model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
if (!model)
return;
gtk_tree_view_get_cursor(treeview, &path, NULL);
if (!path)
return;
if (!gtk_tree_model_get_iter(model, &iter, path))
goto free;
depth = gtk_tree_path_get_depth(path);
if (depth == 1) {
/*
* The first time we start up, the cursor will
* select the "All Events" row, and call
* this routine. But we don't want to do anything.
* Check and activate.
*/
gtk_tree_model_get(model, &iter,
COL_ACTIVE_START, &start,
-1);
if (!start) {
gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
COL_ACTIVE_START, TRUE,
-1);
goto free;
}
}
gtk_tree_model_get(model, &iter,
COL_ACTIVE, &active,
-1);
active = active ? FALSE : TRUE;
gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
COL_ACTIVE, active,
-1);
if (depth == 1) {
/* Set all rows */
update_active_systems(model, &iter, active);
} else if (depth == 2) {
/* set this system */
update_active_events(model, &iter, active);
if (!active) {
/* disable the all events toggle */
gtk_tree_model_iter_parent(model, &parent, &iter);
gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
COL_ACTIVE, FALSE,
-1);
}
} else {
if (!active) {
/* disable system and all events toggles */
gtk_tree_model_iter_parent(model, &parent, &iter);
gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
COL_ACTIVE, FALSE,
-1);
gtk_tree_model_iter_parent(model, &grandparent, &parent);
gtk_tree_store_set(GTK_TREE_STORE(model), &grandparent,
COL_ACTIVE, FALSE,
-1);
}
}
free:
gtk_tree_path_free(path);
}
static gboolean child_set(GtkTreeModel *model, GtkTreeIter *parent)
{
GtkTreeIter iter;
gboolean active;
if (!gtk_tree_model_iter_children(model, &iter, parent))
return FALSE;
for (;;) {
gtk_tree_model_get(model, &iter,
COL_ACTIVE, &active,
-1);
if (active)
return TRUE;
if (!gtk_tree_model_iter_next(model, &iter))
break;
}
return FALSE;
}
static void expand_rows(GtkTreeView *tree, GtkTreeModel *model,
gboolean all_events,
gchar **systems, gint *events)
{
GtkTreePath *path;
GtkTreeIter all;
GtkTreeIter sys;
gboolean active;
/* Expand the "All Events" row */
path = gtk_tree_path_new_from_string("0");
gtk_tree_view_expand_row(tree, path, FALSE);
gtk_tree_path_free(path);
if (all_events)
return;
/* Expand the system rows that are not full or empty */
if (!gtk_tree_model_get_iter_first(model, &all))
return;
if (!gtk_tree_model_iter_children(model, &sys, &all))
return;
for (;;) {
gtk_tree_model_get(model, &sys,
COL_ACTIVE, &active,
-1);
if (!active && child_set(model, &sys)) {
path = gtk_tree_model_get_path(model, &sys);
gtk_tree_view_expand_row(tree, path, FALSE);
gtk_tree_path_free(path);
}
if (!gtk_tree_model_iter_next(model, &sys))
break;
}
}
static GtkWidget *
create_event_list_view(struct tracecmd_input *handle,
gboolean all_events, gchar **systems,
gint *events)
{
GtkTreeViewColumn *col;
GtkCellRenderer *renderer;
GtkCellRenderer *togrend;
GtkWidget *view;
GtkTreeModel *model;
view = gtk_tree_view_new();
/* --- events column --- */
col = gtk_tree_view_column_new();
gtk_tree_view_column_set_title(col, "Events");
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
renderer = gtk_cell_renderer_text_new();
togrend = gtk_cell_renderer_toggle_new();
gtk_tree_view_column_pack_start(col, togrend, FALSE);
gtk_tree_view_column_pack_start(col, renderer, FALSE);
gtk_tree_view_column_add_attribute(col, togrend, "active", COL_ACTIVE);
gtk_tree_view_column_add_attribute(col, renderer, "text", COL_EVENT);
model = create_tree_event_model(handle, all_events, systems, events);
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
g_object_unref(model);
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
GTK_SELECTION_NONE);
expand_rows(GTK_TREE_VIEW(view), model, all_events, systems, events);
g_signal_connect_swapped (view, "cursor-changed",
G_CALLBACK (event_cursor_changed),
(gpointer) view);
return view;
}
static gchar **add_system(gchar **systems, gint size, gchar *system)
{
if (!systems) {
systems = g_new0(gchar *, 2);
size = 0;
} else {
systems = g_realloc(systems,
sizeof(*systems) * (size + 2));
}
systems[size] = g_strdup(system);
systems[size+1] = NULL;
return systems;
}
static gint *add_event(gint *events, gint size, gint event)
{
if (!events) {
events = g_new0(gint, 2);
size = 0;
} else {
events = g_realloc(events,
sizeof(*events) * (size + 2));
}
events[size] = event;
events[size+1] = -1;
return events;
}
static gint update_events(GtkTreeModel *model,
GtkTreeIter *parent,
gint **events, gint size)
{
GtkTreeIter event;
gboolean active;
gint id;
if (!gtk_tree_model_iter_children(model, &event, parent))
return size;
for (;;) {
gtk_tree_model_get(model, &event,
COL_ACTIVE, &active,
COL_EVENT_ID, &id,
-1);
if (active)
*events = add_event(*events, size++, id);
if (!gtk_tree_model_iter_next(model, &event))
break;
}
return size;
}
static gint update_system_events(GtkTreeModel *model,
GtkTreeIter *parent,
gchar ***systems,
gint size,
gint **events,
gint *events_size)
{
GtkTreeIter sys;
gboolean active;
gchar *system;
if (!gtk_tree_model_iter_children(model, &sys, parent))
return size;
for (;;) {
gtk_tree_model_get(model, &sys,
COL_ACTIVE, &active,
COL_EVENT, &system,
-1);
if (active)
*systems = add_system(*systems, size++, system);
else
*events_size = update_events(model, &sys, events, *events_size);
g_free(system);
if (!gtk_tree_model_iter_next(model, &sys))
break;
}
return size;
}
static void accept_events(struct event_filter_helper *event_helper)
{
GtkTreeView *view = event_helper->view;
GtkTreeModel *model;
GtkTreeIter iter;
gboolean active;
gchar **systems = NULL;
gint *events = NULL;
gint events_size = 0;
gint systems_size = 0;
gint i;
model = gtk_tree_view_get_model(view);
if (!model)
return;
if (!gtk_tree_model_get_iter_first(model, &iter))
return;
gtk_tree_model_get(model, &iter,
COL_ACTIVE, &active,
-1);
if (!active)
update_system_events(model, &iter,
&systems, systems_size,
&events, &events_size);
event_helper->func(TRUE, active, systems, events,
event_helper->data);
if (systems) {
for (i = 0; systems[i]; i++)
g_free(systems[i]);
g_free(systems);
}
g_free(events);
}
/* Callback for the clicked signal of the Events filter button */
static void
event_dialog_response (gpointer data, gint response_id)
{
struct dialog_helper *helper = data;
struct event_filter_helper *event_helper = helper->data;
switch (response_id) {
case GTK_RESPONSE_ACCEPT:
printf("accept!\n");
accept_events(event_helper);
break;
case GTK_RESPONSE_REJECT:
printf("reject!\n");
event_helper->func(FALSE, FALSE, NULL, NULL,
event_helper->data);
break;
default:
break;
};
gtk_widget_destroy(GTK_WIDGET(helper->dialog));
g_free(event_helper);
g_free(helper);
}
/**
* trace_filter_event_dialog - make dialog with event listing
* @handle: the handle to the tracecmd data file
* @all_events: if TRUE then select all events.
* @systems: NULL or a string array of systems terminated with NULL
* @events: NULL or a int array of event ids terminated with -1
* @func: The function to call when accept or cancel is pressed
* @data: data to pass to the function @func
*
* If @all_events is set, then @systems and @events are ignored.
*/
void trace_filter_event_dialog(struct tracecmd_input *handle,
gboolean all_events,
gchar **systems,
gint *events,
trace_filter_event_cb_func func,
gpointer data)
{
struct dialog_helper *helper;
struct event_filter_helper *event_helper;
GtkWidget *dialog;
GtkWidget *scrollwin;
GtkWidget *view;
helper = g_malloc(sizeof(*helper));
/* --- Make dialog window --- */
dialog = gtk_dialog_new_with_buttons("Filter Events",
NULL,
GTK_DIALOG_MODAL,
"Apply",
GTK_RESPONSE_ACCEPT,
GTK_STOCK_CANCEL,
GTK_RESPONSE_REJECT,
NULL);
event_helper = g_new0(typeof(*event_helper), 1);
g_assert(event_helper);
helper->dialog = dialog;
helper->data = event_helper;
event_helper->func = func;
event_helper->data = data;
/* We can attach the Quit menu item to our exit function */
g_signal_connect_swapped (dialog, "response",
G_CALLBACK (event_dialog_response),
(gpointer) helper);
scrollwin = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
view = create_event_list_view(handle, all_events, systems, events);
event_helper->view = GTK_TREE_VIEW(view);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(scrollwin), view);
gtk_widget_set_size_request(GTK_WIDGET(dialog),
DIALOG_WIDTH, DIALOG_HEIGHT);
gtk_widget_show_all(dialog);
}
struct cpu_filter_helper {
gboolean allcpus;
guint64 *cpu_mask;
GtkWidget **buttons;
int cpus;
trace_filter_cpu_cb_func func;
gpointer data;
};
static void destroy_cpu_helper(struct cpu_filter_helper *cpu_helper)
{
g_free(cpu_helper->cpu_mask);
g_free(cpu_helper->buttons);
g_free(cpu_helper);
}
/* Callback for the clicked signal of the CPUS filter button */
static void
cpu_dialog_response (gpointer data, gint response_id)
{
struct dialog_helper *helper = data;
struct cpu_filter_helper *cpu_helper = helper->data;
guint64 *cpu_mask = NULL;
switch (response_id) {
case GTK_RESPONSE_ACCEPT:
if (!cpu_helper->allcpus) {
cpu_mask = cpu_helper->cpu_mask;
cpu_helper->cpu_mask = NULL;
}
cpu_helper->func(TRUE, cpu_helper->allcpus, cpu_mask, cpu_helper->data);
break;
case GTK_RESPONSE_REJECT:
cpu_helper->func(FALSE, FALSE, NULL, cpu_helper->data);
break;
default:
break;
};
g_free(cpu_mask);
gtk_widget_destroy(GTK_WIDGET(helper->dialog));
destroy_cpu_helper(helper->data);
g_free(helper);
}
#define CPU_ALL_CPUS_STR "All CPUs"
void cpu_toggle(gpointer data, GtkWidget *widget)
{
struct cpu_filter_helper *cpu_helper = data;
const gchar *label;
gboolean active;
gint cpu;
label = gtk_button_get_label(GTK_BUTTON(widget));
active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
if (strcmp(label, CPU_ALL_CPUS_STR) == 0) {
cpu_helper->allcpus = active;
if (active) {
/* enable all toggles */
for (cpu = 0; cpu < cpu_helper->cpus; cpu++)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu]),
TRUE);
}
return;
}
/* Get the CPU # from the label. Pass "CPU " */
cpu = atoi(label + 4);
if (active) {
cpu_set(cpu_helper->cpu_mask, cpu);
return;
}
cpu_clear(cpu_helper->cpu_mask, cpu);
if (!cpu_helper->allcpus)
return;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu_helper->cpus]),
FALSE);
}
/**
* trace_filter_cpu_dialog - make dialog with cpu listing
* @all_cpus: if TRUE then select all cpus.
* @cpus_selected: NULL or a CPU mask with the CPUs to be select set.
* @func: The function to call when accept or cancel is pressed
* @data: data to pass to the function @func
*
* If @all_cpus is set, then @cpus_selected is ignored.
*/
void trace_filter_cpu_dialog(gboolean all_cpus, guint64 *cpus_selected, gint cpus,
trace_filter_cpu_cb_func func, gpointer data)
{
struct dialog_helper *helper;
struct cpu_filter_helper *cpu_helper;
GtkWidget *dialog;
GtkWidget *scrollwin;
GtkWidget *viewport;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *check;
GtkRequisition req;
gchar counter[100];
gint width, height;
gint allset;
gint cpu;
helper = g_malloc(sizeof(*helper));
g_assert(helper != NULL);
cpu_helper = g_new0(typeof(*cpu_helper), 1);
g_assert(cpu_helper != NULL);
helper->data = cpu_helper;
/* --- Make dialog window --- */
dialog = gtk_dialog_new_with_buttons("Filter CPUS",
NULL,
GTK_DIALOG_MODAL,
"Apply",
GTK_RESPONSE_ACCEPT,
GTK_STOCK_CANCEL,
GTK_RESPONSE_REJECT,
NULL);
helper->dialog = dialog;
cpu_helper->cpus = cpus;
cpu_helper->buttons = g_new0(GtkWidget *, cpus + 1);
g_assert(cpu_helper->buttons);
cpu_helper->func = func;
cpu_helper->data = data;
g_signal_connect_swapped (dialog, "response",
G_CALLBACK (cpu_dialog_response),
(gpointer) helper);
scrollwin = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
viewport = gtk_viewport_new(NULL, NULL);
gtk_widget_show(viewport);
gtk_container_add(GTK_CONTAINER(scrollwin), viewport);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);
/* Add hbox to center buttons. Is there a better way? */
hbox = gtk_hbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(viewport), hbox);
gtk_widget_show(hbox);
vbox = gtk_vbox_new(TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, FALSE, 0);
gtk_widget_show(vbox);
check = gtk_check_button_new_with_label(CPU_ALL_CPUS_STR);
gtk_box_pack_start(GTK_BOX(vbox), check, TRUE, TRUE, 0);
/* The last button will be the all CPUs button */
cpu_helper->buttons[cpus] = check;
allset = cpus_selected ? 0 : 1;
if (!allset) {
/* check if the list is all set */
for (cpu = 0; cpu < cpus; cpu++)
if (!cpu_isset(cpus_selected, cpu))
break;
if (cpu == cpus)
allset = 1;
}
if (allset)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
g_signal_connect_swapped (check, "toggled",
G_CALLBACK (cpu_toggle),
(gpointer) cpu_helper);
cpu_helper->allcpus = allset;
cpu_helper->cpu_mask = g_new0(guint64, (cpus >> 6) + 1);
g_assert(cpu_helper->cpu_mask != NULL);
gtk_widget_show(check);
for (cpu = 0; cpu < cpus; cpu++) {
g_snprintf(counter, 100, "CPU %d", cpu);
check = gtk_check_button_new_with_label(counter);
cpu_helper->buttons[cpu] = check;
gtk_box_pack_start(GTK_BOX(vbox), check, TRUE, FALSE, 0);
if (cpus_selected && cpu_isset(cpus_selected, cpu)) {
cpu_set(cpu_helper->cpu_mask, cpu);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
} else
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), FALSE);
g_signal_connect_swapped (check, "toggled",
G_CALLBACK (cpu_toggle),
(gpointer) cpu_helper);
gtk_widget_show(check);
}
/* Figure out a good size to show */
gtk_widget_size_request(hbox, &req);
height = req.height;
gtk_widget_size_request(scrollwin, &req);
height += req.height;
gtk_widget_size_request(dialog, &req);
width = req.width;
height += req.height;
if (width > DIALOG_WIDTH)
width = DIALOG_WIDTH;
if (height > DIALOG_HEIGHT)
height = DIALOG_HEIGHT;
gtk_widget_set_size_request(GTK_WIDGET(dialog),
width, height);
gtk_widget_show_all(dialog);
}