[gnome-system-monitor] Added CPU Affinity feature
- From: Robert Roth <robertroth src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-system-monitor] Added CPU Affinity feature
- Date: Tue, 27 Oct 2020 06:42:02 +0000 (UTC)
commit 8835b97031842fcfe76d8fd346932c2b8f392e9c
Author: Jacob Barkdull <jacobbarkdull gmail com>
Date: Sun Jan 5 16:36:54 2020 -0800
Added CPU Affinity feature
data/menus.ui | 5 +
scripts/meson.build | 1 +
src/application.cpp | 1 +
src/interface.cpp | 11 ++
src/meson.build | 2 +
src/procdialogs.cpp | 18 +++
src/procdialogs.h | 1 +
src/setaffinity.cpp | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/setaffinity.h | 21 +++
9 files changed, 484 insertions(+)
---
diff --git a/data/menus.ui b/data/menus.ui
index 94a331bd..21d2ef7c 100644
--- a/data/menus.ui
+++ b/data/menus.ui
@@ -128,6 +128,11 @@
</item>
</section>
</submenu>
+ <item>
+ <attribute name="label" translatable="yes">Set _Affinity</attribute>
+ <attribute name="action">win.set-affinity</attribute>
+ <attribute name="accel"><Alt>s</attribute>
+ </item>
</section>
<section>
<item>
diff --git a/scripts/meson.build b/scripts/meson.build
index 99e80942..cb2213cf 100644
--- a/scripts/meson.build
+++ b/scripts/meson.build
@@ -1,6 +1,7 @@
commands = [
'renice',
'kill',
+ 'taskset',
]
foreach command : commands
diff --git a/src/application.cpp b/src/application.cpp
index 878f5e8d..1f3d7507 100644
--- a/src/application.cpp
+++ b/src/application.cpp
@@ -399,6 +399,7 @@ void GsmApplication::on_startup()
add_accelerator("<Primary>c", "win.send-signal-cont", g_variant_new_int32 (SIGCONT));
add_accelerator("<Primary>e", "win.send-signal-end", g_variant_new_int32 (SIGTERM));
add_accelerator("<Primary>k", "win.send-signal-kill", g_variant_new_int32 (SIGKILL));
+ add_accelerator("<Alt>s", "win.set-affinity", NULL);
add_accelerator("<Primary>m", "win.memory-maps", NULL);
add_accelerator("<Primary>o", "win.open-files", NULL);
add_accelerator("<Alt>Return", "win.process-properties", NULL);
diff --git a/src/interface.cpp b/src/interface.cpp
index 1b9e0aa1..02f677a6 100644
--- a/src/interface.cpp
+++ b/src/interface.cpp
@@ -34,6 +34,7 @@
#include "proctable.h"
#include "procactions.h"
#include "procdialogs.h"
+#include "setaffinity.h"
#include "memmaps.h"
#include "openfiles.h"
#include "procproperties.h"
@@ -439,6 +440,14 @@ on_activate_send_signal (GSimpleAction *, GVariant *parameter, gpointer data)
}
}
+static void
+on_activate_set_affinity (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ create_set_affinity_dialog (app);
+}
+
static void
on_activate_memory_maps (GSimpleAction *, GVariant *, gpointer data)
{
@@ -725,6 +734,7 @@ create_main_window (GsmApplication *app)
{ "send-signal-end", on_activate_send_signal, "i", NULL, NULL },
{ "send-signal-kill", on_activate_send_signal, "i", NULL, NULL },
{ "priority", on_activate_priority, "i", "@i 0", change_priority_state },
+ { "set-affinity", on_activate_set_affinity, NULL, NULL, NULL },
{ "memory-maps", on_activate_memory_maps, NULL, NULL, NULL },
{ "open-files", on_activate_open_files, NULL, NULL, NULL },
{ "process-properties", on_activate_process_properties, NULL, NULL, NULL },
@@ -809,6 +819,7 @@ update_sensitivity(GsmApplication *app)
"send-signal-end",
"send-signal-kill",
"priority",
+ "set-affinity",
"memory-maps",
"open-files",
"process-properties" };
diff --git a/src/meson.build b/src/meson.build
index 76c5320d..6669158a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,6 +12,7 @@ system_monitor_sources = [
'load-graph.cpp',
'lsof.cpp',
'main.cpp',
+ 'setaffinity.cpp',
'memmaps.cpp',
'openfiles.cpp',
'prefsdialog.cpp',
@@ -38,6 +39,7 @@ system_monitor_headers = [
'lsof.h',
'proctable.h',
'settings-keys.h',
+ 'setaffinity.h',
'memmaps.h',
'procactions.h',
'systemd.h',
diff --git a/src/procdialogs.cpp b/src/procdialogs.cpp
index 886af93b..e148b4f3 100644
--- a/src/procdialogs.cpp
+++ b/src/procdialogs.cpp
@@ -288,6 +288,24 @@ procman_action_to_command(ProcmanActionType type,
}
+gboolean
+multi_root_check (char *command)
+{
+ if (procman_has_pkexec ()) {
+ return gsm_pkexec_create_root_password_dialog (command);
+ } else {
+ if (procman_has_gksu ()) {
+ return gsm_gksu_create_root_password_dialog (command);
+ } else {
+ if (procman_has_gnomesu ()) {
+ return gsm_gnomesu_create_root_password_dialog (command);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
/*
* type determines whether if dialog is for killing process or renice.
* type == PROCMAN_ACTION_KILL, extra_value -> signal to send
diff --git a/src/procdialogs.h b/src/procdialogs.h
index e94a7e5e..93fad173 100644
--- a/src/procdialogs.h
+++ b/src/procdialogs.h
@@ -44,6 +44,7 @@ typedef enum
void procdialog_create_kill_dialog (GsmApplication *app, int signal);
void procdialog_create_renice_dialog (GsmApplication *app);
+gboolean multi_root_check (char *command);
gboolean procdialog_create_root_password_dialog (ProcmanActionType type,
GsmApplication *app,
gint pid, gint extra_value);
diff --git a/src/setaffinity.cpp b/src/setaffinity.cpp
new file mode 100644
index 00000000..f4ca77c5
--- /dev/null
+++ b/src/setaffinity.cpp
@@ -0,0 +1,424 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+**/
+
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sched.h>
+#include <sys/stat.h>
+#include <glib/gi18n.h>
+
+#include "proctable.h"
+#include "procdialogs.h"
+#include "util.h"
+#include "setaffinity.h"
+
+namespace
+{
+ class SetAffinityData
+ {
+ public:
+ GtkWidget *dialog;
+ pid_t pid;
+ GtkWidget **cpus;
+ gdouble cpu_count;
+ gboolean toggle_single_blocked;
+ gboolean toggle_all_blocked;
+ };
+}
+
+static gboolean
+all_toggled (SetAffinityData *affinity)
+{
+ gint 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->cpus[i]))) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+affinity_toggler_single (GtkToggleButton *button,
+ gpointer data)
+{
+ SetAffinityData *affinity = static_cast<SetAffinityData *>(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->cpus[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<SetAffinityData *>(data);
+
+ gint 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->cpus[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 void
+set_affinity (GtkToggleButton *button,
+ gpointer data)
+{
+ SetAffinityData *affinity = static_cast<SetAffinityData *>(data);
+
+ cpu_set_t cpuset;
+ gint i;
+ gint taskset_cpu = 0;
+ gchar **cpu_list;
+ gchar *pc;
+ gchar *command;
+
+ /* Create emtpy struct type */
+ cpu_list = g_new0 (gchar *, affinity->cpu_count);
+
+ /* Check whether we can get process's current affinity */
+ if (sched_getaffinity (affinity->pid, sizeof (cpu_set_t), &cpuset) != -1) {
+ /* If so, set affinity for all toggled CPU check boxes */
+ for (i = 0; i < affinity->cpu_count; i++) {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (affinity->cpus[i + 1]))) {
+ CPU_SET (i, &cpuset);
+
+ /* Add CPU to taskset command in case root is need */
+ cpu_list[taskset_cpu] = g_strdup_printf ("%i", i);
+ taskset_cpu++;
+ } else {
+ CPU_CLR (i, &cpuset);
+ }
+ }
+
+ /* Construct taskset command */
+ pc = g_strjoinv (",", cpu_list);
+ command = g_strdup_printf ("taskset -pc %s %d", pc, affinity->pid);
+
+ /* Set process affinity; Show message dialog upon error */
+ if (sched_setaffinity (affinity->pid, sizeof (cpu_set_t), &cpuset) == -1) {
+ /* Check whether an access error occurred */
+ if (errno == EPERM or errno == EACCES) {
+ /* If so, attempt to run taskset as root */
+ if (!multi_root_check (command)) {
+ /* Error on failure */
+ set_affinity_error ();
+ }
+ } else {
+ /* If not, error normally */
+ set_affinity_error ();
+ }
+ }
+
+ /* Free memory for taskset command */
+ g_free (command);
+ g_free (pc);
+ } 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<GsmApplication *>(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;
+
+ cpu_set_t cpuset;
+ gint i;
+ gint cpu_number;
+ 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->cpus = 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->cpus[0] = gtk_check_button_new_with_label ("Run on all CPUs");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (affinity_data->cpus[0]), TRUE);
+ gtk_widget_set_hexpand (affinity_data->cpus[0], TRUE);
+
+ /* Get process's current affinity */
+ sched_getaffinity (info->pid, sizeof (cpu_set_t), &cpuset);
+
+ /* Check if any CPU's aren't set for this process */
+ for (i = 0; i < app->config.num_cpus; i++) {
+ if (!CPU_ISSET (i, &cpuset)) {
+ /* If so, set the check box inactive */
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (affinity_data->cpus[0]),
+ FALSE
+ );
+
+ break;
+ }
+ }
+
+ /* Add toggle all check box to CPU grid */
+ gtk_grid_attach (cpulist_grid, affinity_data->cpus[0], 0, 0, 1, 1);
+
+ /* Create a check box for each CPU core */
+ for (i = 0, cpu_number = 1; i < app->config.num_cpus; i++, cpu_number++) {
+ /* Set check box label value to CPU [1..2048] */
+ button_text = g_strdup_printf (_("CPU %d"), cpu_number);
+
+ /* Create check box */
+ affinity_data->cpus[cpu_number] = gtk_check_button_new_with_label (button_text);
+ gtk_widget_set_hexpand (affinity_data->cpus[cpu_number], TRUE);
+
+ /* Check if this CPU is set for this process */
+ if (CPU_ISSET (i, &cpuset)) {
+ /* If so, set the check box active */
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (affinity_data->cpus[cpu_number]),
+ TRUE
+ );
+ }
+
+ /* Add check box to CPU grid */
+ gtk_grid_attach (cpulist_grid, affinity_data->cpus[cpu_number], 0, i + 1, 1, 1);
+
+ /* Connect check box to toggler function */
+ g_signal_connect (affinity_data->cpus[cpu_number],
+ "toggled",
+ G_CALLBACK (affinity_toggler_single),
+ affinity_data);
+
+ /* Free check box label value gchar */
+ g_free (button_text);
+ }
+
+ /* 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->cpus[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);
+}
diff --git a/src/setaffinity.h b/src/setaffinity.h
new file mode 100644
index 00000000..6e1c7c96
--- /dev/null
+++ b/src/setaffinity.h
@@ -0,0 +1,21 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/**
+ * Copyright (C) 2020 Jacob Barkdull
+ *
+ * I, Jacob Barkdull, hereby release this work into the public domain.
+ * This applies worldwide. If this is not legally possible, I grant any
+ * entity the right to use this work for any purpose, without any
+ * conditions, unless such conditions are required by law.
+**/
+
+
+#ifndef _GSM_SETAFFINITY_H_
+#define _GSM_SETAFFINITY_H_
+
+#include <glib.h>
+#include "application.h"
+
+void create_set_affinity_dialog (GsmApplication *app);
+
+#endif /* _GSM_SETAFFINITY_H_ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]