From fb31765cbe33890f325a87015507364156741321 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:59:44 +0200 Subject: Adding upstream version 42.0. Signed-off-by: Daniel Baumann --- src/setaffinity.cpp | 473 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 src/setaffinity.cpp (limited to 'src/setaffinity.cpp') diff --git a/src/setaffinity.cpp b/src/setaffinity.cpp new file mode 100644 index 0000000..e8c1546 --- /dev/null +++ b/src/setaffinity.cpp @@ -0,0 +1,473 @@ +/* -*- 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); +} -- cgit v1.2.3