/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /** * Copyright (C) 2020 Jacob Barkdull * * This program is part of GNOME System Monitor. * * 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 3 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, see . **/ #include #include #include #include #include #include #include "proctable.h" #include "procdialogs.h" #include "util.h" #include "setaffinity.h" namespace { class SetAffinityData { public: GtkWidget *dialog; pid_t pid; GtkWidget **buttons; guint32 cpu_count; gboolean toggle_single_blocked; gboolean toggle_all_blocked; }; } static gboolean all_toggled (SetAffinityData *affinity) { guint32 i; /* Check if any CPU's aren't set for this process */ for (i = 1; i <= affinity->cpu_count; i++) { /* If so, return FALSE */ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (affinity->buttons[i]))) { return FALSE; } } return TRUE; } static void affinity_toggler_single (GtkToggleButton *button, gpointer data) { SetAffinityData *affinity = static_cast(data); gboolean toggle_all_state = FALSE; /* Return void if toggle single is blocked */ if (affinity->toggle_single_blocked == TRUE) { return; } /* Set toggle all state based on whether all are toggled */ if (gtk_toggle_button_get_active (button)) { toggle_all_state = all_toggled (affinity); } /* Block toggle all signal */ affinity->toggle_all_blocked = TRUE; /* Set toggle all check box state */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (affinity->buttons[0]), toggle_all_state); /* Unblock toggle all signal */ affinity->toggle_all_blocked = FALSE; } static void affinity_toggle_all (GtkToggleButton *toggle_all_button, gpointer data) { SetAffinityData *affinity = static_cast(data); guint32 i; gboolean state; /* Return void if toggle all is blocked */ if (affinity->toggle_all_blocked == TRUE) { return; } /* Set individual CPU toggles based on toggle all state */ state = gtk_toggle_button_get_active (toggle_all_button); /* Block toggle single signal */ affinity->toggle_single_blocked = TRUE; /* Set all CPU check boxes to specified state */ for (i = 1; i <= affinity->cpu_count; i++) { gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON (affinity->buttons[i]), state ); } /* Unblock toggle single signal */ affinity->toggle_single_blocked = FALSE; } static void set_affinity_error (void) { GtkWidget *dialog; /* Create error message dialog */ dialog = gtk_message_dialog_new (GTK_WINDOW (GsmApplication::get()->main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "GNU CPU Affinity error: %s", g_strerror (errno)); /* Set dialog as modal */ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); /* Show the dialog */ gtk_widget_show (dialog); /* Connect response signal to GTK widget destroy function */ g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); } static guint16 * gsm_set_proc_affinity (glibtop_proc_affinity *buf, GArray *cpus, pid_t pid) { #ifdef __linux__ guint i; cpu_set_t set; guint16 cpu; CPU_ZERO (&set); for (i = 0; i < cpus->len; i++) { cpu = g_array_index (cpus, guint16, i); CPU_SET (cpu, &set); } if (sched_setaffinity (pid, sizeof (set), &set) != -1) { return glibtop_get_proc_affinity (buf, pid); } #endif return NULL; } static void execute_taskset_command (gchar **cpu_list, pid_t pid) { #ifdef __linux__ gchar *pc; gchar *command; /* Join CPU number strings by commas for taskset command CPU list */ pc = g_strjoinv (",", cpu_list); /* Construct taskset command */ command = g_strdup_printf ("taskset -pc %s %d", pc, pid); /* Execute taskset command; show error on failure */ if (!multi_root_check (command)) { set_affinity_error (); } /* Free memory for taskset command */ g_free (command); g_free (pc); #endif } static void set_affinity (GtkToggleButton *button, gpointer data) { SetAffinityData *affinity = static_cast(data); glibtop_proc_affinity get_affinity; glibtop_proc_affinity set_affinity; gchar **cpu_list; GArray *cpuset; guint32 i; gint taskset_cpu = 0; /* Create string array for taskset command CPU list */ cpu_list = g_new0 (gchar *, affinity->cpu_count); /* Check whether we can get process's current affinity */ if (glibtop_get_proc_affinity (&get_affinity, affinity->pid) != NULL) { /* If so, create array for CPU numbers */ cpuset = g_array_new (FALSE, FALSE, sizeof (guint16)); /* Run through all CPUs set for this process */ for (i = 0; i < affinity->cpu_count; i++) { /* Check if CPU check box button is active */ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (affinity->buttons[i + 1]))) { /* If so, get its CPU number as 16bit integer */ guint16 n = i; /* Add its CPU for process affinity */ g_array_append_val (cpuset, n); /* Store CPU number as string for taskset command CPU list */ cpu_list[taskset_cpu] = g_strdup_printf ("%i", i); taskset_cpu++; } } /* Set process affinity; Show message dialog upon error */ if (gsm_set_proc_affinity (&set_affinity, cpuset, affinity->pid) == NULL) { /* If so, check whether an access error occurred */ if (errno == EPERM or errno == EACCES) { /* If so, attempt to run taskset as root, show error on failure */ execute_taskset_command (cpu_list, affinity->pid); } else { /* If not, show error immediately */ set_affinity_error (); } } /* Free memory for CPU strings */ for (i = 0; i < affinity->cpu_count; i++) { g_free (cpu_list[i]); } /* Free CPU array memory */ g_array_free (cpuset, TRUE); } else { /* If not, show error message dialog */ set_affinity_error (); } /* Destroy dialog window */ gtk_widget_destroy (affinity->dialog); } static void create_single_set_affinity_dialog (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GsmApplication *app = static_cast(data); ProcInfo *info; SetAffinityData *affinity_data; GtkWidget *cancel_button; GtkWidget *apply_button; GtkWidget *dialog_vbox; GtkWidget *label; GtkWidget *scrolled; GtkStyleContext *scrolled_style; GtkGrid *cpulist_grid; guint16 *affinity_cpus; guint16 affinity_cpu; glibtop_proc_affinity affinity; guint32 affinity_i; gint button_n; gchar *button_text; /* Get selected process information */ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); /* Return void if process information comes back not true */ if (!info) { return; } /* Create affinity data object */ affinity_data = new SetAffinityData (); /* Set initial check box array */ affinity_data->buttons = g_new (GtkWidget *, app->config.num_cpus); /* Create dialog window */ affinity_data->dialog = GTK_WIDGET (g_object_new (GTK_TYPE_DIALOG, "title", _("Set Affinity"), "use-header-bar", TRUE, "destroy-with-parent", TRUE, NULL)); /* Add cancel button to header bar */ cancel_button = gtk_dialog_add_button (GTK_DIALOG (affinity_data->dialog), _("_Cancel"), GTK_RESPONSE_CANCEL); /* Add apply button to header bar */ apply_button = gtk_dialog_add_button (GTK_DIALOG (affinity_data->dialog), _("_Apply"), GTK_RESPONSE_APPLY); /* Set dialog window "transient for" */ gtk_window_set_transient_for (GTK_WINDOW (affinity_data->dialog), GTK_WINDOW (GsmApplication::get()->main_window)); /* Set dialog window to be resizable */ gtk_window_set_resizable (GTK_WINDOW (affinity_data->dialog), TRUE); /* Set default dialog window size */ gtk_widget_set_size_request (affinity_data->dialog, 600, 430); /* Set dialog box padding ("border") */ gtk_container_set_border_width (GTK_CONTAINER (affinity_data->dialog), 5); /* Get dialog content area VBox */ dialog_vbox = gtk_dialog_get_content_area (GTK_DIALOG (affinity_data->dialog)); /* Set dialog VBox padding ("border") */ gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 10); /* Set dialog VBox spacing */ gtk_box_set_spacing (GTK_BOX (dialog_vbox), 10); /* Add selected process pid to affinity data */ affinity_data->pid = info->pid; /* Add CPU count to affinity data */ affinity_data->cpu_count = app->config.num_cpus; /* Set default toggle signal block states */ affinity_data->toggle_single_blocked = FALSE; affinity_data->toggle_all_blocked = FALSE; /* Create a label describing the dialog windows intent */ label = GTK_WIDGET (procman_make_label_for_mmaps_or_ofiles ( _("Select CPUs \"%s\" (PID %u) is allowed to run on:"), info->name.c_str(), info->pid )); /* Add label to dialog VBox */ gtk_box_pack_start (GTK_BOX (dialog_vbox), label, FALSE, TRUE, 0); /* Create scrolled box ("window") */ scrolled = gtk_scrolled_window_new (NULL, NULL); /* Add view class to scrolled box style */ scrolled_style = gtk_widget_get_style_context (scrolled); gtk_style_context_add_class (scrolled_style, "view"); /* Set scrolled box vertical and horizontal policies */ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); /* Set scrolled box shadow */ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN); /* Create grid for CPU list */ cpulist_grid = GTK_GRID (gtk_grid_new ()); /* Set CPU list grid padding ("border") */ gtk_container_set_border_width (GTK_CONTAINER (cpulist_grid), 10); /* Set grid row spacing */ gtk_grid_set_row_spacing (cpulist_grid, 10); /* Create toggle all check box */ affinity_data->buttons[0] = gtk_check_button_new_with_label ("Run on all CPUs"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (affinity_data->buttons[0]), TRUE); gtk_widget_set_hexpand (affinity_data->buttons[0], TRUE); /* Get process's current affinity */ affinity_cpus = glibtop_get_proc_affinity (&affinity, info->pid); /* Set toggle all check box based on whether all CPUs are set for this process */ gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON (affinity_data->buttons[0]), affinity.all ); /* Add toggle all check box to CPU grid */ gtk_grid_attach (cpulist_grid, affinity_data->buttons[0], 0, 0, 1, 1); /* Run through all CPU buttons */ for (button_n = 1; button_n < app->config.num_cpus + 1; button_n++) { /* Set check box label value to CPU [1..2048] */ button_text = g_strdup_printf (_("CPU %d"), button_n); /* Create check box button for current CPU */ affinity_data->buttons[button_n] = gtk_check_button_new_with_label (button_text); gtk_widget_set_hexpand (affinity_data->buttons[button_n], TRUE); /* Add check box to CPU grid */ gtk_grid_attach (cpulist_grid, affinity_data->buttons[button_n], 0, button_n, 1, 1); /* Connect check box to toggler function */ g_signal_connect (affinity_data->buttons[button_n], "toggled", G_CALLBACK (affinity_toggler_single), affinity_data); /* Free check box label value gchar */ g_free (button_text); } /* Run through all CPUs set for this process */ for (affinity_i = 0; affinity_i < affinity.number; affinity_i++) { /* Get CPU button index */ affinity_cpu = affinity_cpus[affinity_i] + 1; /* Set CPU check box active */ gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON (affinity_data->buttons[affinity_cpu]), TRUE ); } /* Add CPU grid to scrolled box */ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (cpulist_grid)); /* Add scrolled box to dialog VBox */ gtk_box_pack_start (GTK_BOX (dialog_vbox), scrolled, TRUE, TRUE, 0); /* Swap click signal on "Cancel" button */ g_signal_connect_swapped (cancel_button, "clicked", G_CALLBACK (gtk_widget_destroy), affinity_data->dialog); /* Connect click signal on "Apply" button */ g_signal_connect (apply_button, "clicked", G_CALLBACK (set_affinity), affinity_data); /* Connect toggle all check box to (de)select all function */ g_signal_connect (affinity_data->buttons[0], "toggled", G_CALLBACK (affinity_toggle_all), affinity_data); /* Show dialog window */ gtk_widget_show_all (affinity_data->dialog); } void create_set_affinity_dialog (GsmApplication *app) { /* Create a dialog window for each selected process */ gtk_tree_selection_selected_foreach (app->selection, create_single_set_affinity_dialog, app); }