aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2020-07-28 11:57:51 +0200
committerlaforge <laforge@osmocom.org>2020-08-10 14:11:18 +0000
commiteb6882fa5249c1f4fd764f4339ff7d749e1cfb99 (patch)
treeebde0bec33713ff53472316c2f88dac308bf9b7a /src
parent7dbae9ea40e2855729f9efa7264b5af78af779db (diff)
vty: Introduce support to set cpu-affinity and scheduler policy
Process willing to support this kind of configuration through VTY simply need to call "osmo_sched_vty_init(tall_ctx);" during startup to register the commands. For multithreaded processes, new threads willing to get their cpu-affinity mask according to VTY config should call osmo_sched_vty_apply_localthread() (potentially after setting the thread name through pthread_setname_np()). Related: SYS#4986 Change-Id: If76a4bd2cc7b3c7adf5d84790a944d78be70e10a
Diffstat (limited to 'src')
-rw-r--r--src/vty/Makefile.am2
-rw-r--r--src/vty/cpu_sched_vty.c674
2 files changed, 675 insertions, 1 deletions
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index 35350cc3..81ff1045 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -12,7 +12,7 @@ lib_LTLIBRARIES = libosmovty.la
libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
telnet_interface.c logging_vty.c stats_vty.c \
fsm_vty.c talloc_ctx_vty.c \
- tdef_vty.c
+ cpu_sched_vty.c tdef_vty.c
libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
endif
diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c
new file mode 100644
index 00000000..3b6067a3
--- /dev/null
+++ b/src/vty/cpu_sched_vty.c
@@ -0,0 +1,674 @@
+/*! \file cpu_sched_vty.c
+ * Implementation to CPU / Threading / Scheduler properties from VTY configuration.
+ */
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPLv2+
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sched.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+
+/*! \addtogroup Tdef_VTY
+ *
+ * CPU Scheduling related VTY API.
+ *
+ * @{
+ * \file cpu_sched_vty.c
+ */
+
+enum sched_vty_thread_id {
+ SCHED_VTY_THREAD_SELF,
+ SCHED_VTY_THREAD_ALL,
+ SCHED_VTY_THREAD_ID,
+ SCHED_VTY_THREAD_NAME,
+ SCHED_VTY_THREAD_UNKNOWN,
+};
+
+struct cpu_affinity_it {
+ struct llist_head entry;
+ enum sched_vty_thread_id tid_type;
+ char bufname[64];
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+ bool delay;
+};
+
+struct sched_vty_opts {
+ void *tall_ctx;
+ int sched_rr_prio;
+ struct llist_head cpu_affinity_li;
+ pthread_mutex_t cpu_affinity_li_mutex;
+};
+
+static struct sched_vty_opts *sched_vty_opts;
+
+static struct cmd_node sched_node = {
+ L_CPU_SCHED_NODE,
+ "%s(config-sched)# ",
+ 1,
+};
+
+/* returns number of configured CPUs in the system, or negative otherwise */
+static int get_num_cpus() {
+ static unsigned int num_cpus = 0;
+ long ln;
+
+ if (num_cpus)
+ return num_cpus;
+
+ /* This is expensive (goes across /sys, so let's do it only once. It is
+ * guaranteed it won't change during process span anyway). */
+ ln = sysconf(_SC_NPROCESSORS_CONF);
+ if (ln < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "sysconf(_SC_NPROCESSORS_CONF) failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ num_cpus = (unsigned int) ln;
+ return num_cpus;
+}
+
+/* Parses string with CPU hex Affinity Mask, with right-most bit being CPU0, and
+ * fills a cpuset of size cpuset_size.
+ */
+static int parse_cpu_hex_mask(const char *str, cpu_set_t *cpuset, size_t cpuset_size)
+{
+ int len = strlen(str);
+ const char *ptr = str + len - 1;
+ int cpu = 0;
+
+ /* skip optional '0x' prefix format */
+ if (len >= 2 && str[0] == '0' && str[1] == 'x')
+ str += 2;
+ CPU_ZERO_S(cpuset_size, cpuset);
+
+ while (ptr >= str) {
+ char c = *ptr;
+ uint8_t val;
+
+ if (c >= '0' && c <= '9') {
+ val = c - '0';
+ } else {
+ c = (char)tolower((int)c);
+ if (c >= 'a' && c <= 'f')
+ val = c + (10 - 'a');
+ else
+ return -1;
+ }
+ if (val & 0x01)
+ CPU_SET_S(cpu, cpuset_size, cpuset);
+ if (val & 0x02)
+ CPU_SET_S(cpu + 1, cpuset_size, cpuset);
+ if (val & 0x04)
+ CPU_SET_S(cpu + 2, cpuset_size, cpuset);
+ if (val & 0x08)
+ CPU_SET_S(cpu + 3, cpuset_size, cpuset);
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+/* Generates a hexstring in str from cpuset of size cpuset_size */
+static int generate_cpu_hex_mask(char *str, size_t str_buf_size,
+ cpu_set_t *cpuset, size_t cpuset_size)
+{
+ char *ptr = str;
+ int cpu;
+ bool first_nonzero_found = false;
+
+ /* 2 char per byte, + '0x' prefix + '\0' */
+ if (cpuset_size * 2 + 2 + 1 > str_buf_size)
+ return -1;
+
+ *ptr++ = '0';
+ *ptr++ = 'x';
+
+ for (cpu = cpuset_size*8 - 4; cpu >= 0; cpu -= 4) {
+ uint8_t val = 0;
+
+ if (CPU_ISSET_S(cpu, cpuset_size, cpuset))
+ val |= 0x01;
+ if (CPU_ISSET_S(cpu + 1, cpuset_size, cpuset))
+ val |= 0x02;
+ if (CPU_ISSET_S(cpu + 2, cpuset_size, cpuset))
+ val |= 0x04;
+ if (CPU_ISSET_S(cpu + 3, cpuset_size, cpuset))
+ val |= 0x08;
+
+ if (val >= 0 && val < 10)
+ *ptr = '0' + val;
+ else
+ *ptr = ('a' - 10) + val;
+ if (val)
+ first_nonzero_found = true;
+ if (first_nonzero_found)
+ ptr++;
+
+ }
+ if (!first_nonzero_found)
+ *ptr++ = '0';
+ *ptr = '\0';
+ return 0;
+}
+
+/* Checks whther a thread identified by tid exists and belongs to the running process */
+static bool proc_tid_exists(pid_t tid)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char dirname[100];
+ int tid_it;
+ bool found = false;
+
+ snprintf(dirname, sizeof(dirname), "/proc/%ld/task", (long int)getpid());
+ proc_dir = opendir(dirname);
+ if (!proc_dir)
+ return false; /*FIXME; print error */
+
+ while ((entry = readdir(proc_dir))) {
+ if (entry->d_name[0] == '.')
+ continue;
+ tid_it = atoi(entry->d_name);
+ if (tid_it == tid) {
+ found = true;
+ break;
+ }
+ }
+
+ closedir(proc_dir);
+ return found;
+}
+
+/* Checks whther a thread identified by name exists and belongs to the running
+ * process, and returns its disocevered TID in res_pid.
+ */
+static bool proc_name_exists(const char *name, pid_t *res_pid)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char path[100];
+ char buf[17]; /* 15 + \n + \0 */
+ int tid_it;
+ int fd;
+ pid_t mypid = getpid();
+ bool found = false;
+ int rc;
+
+ *res_pid = 0;
+
+ snprintf(path, sizeof(path), "/proc/%ld/task", (long int)mypid);
+ proc_dir = opendir(path);
+ if (!proc_dir)
+ return false;
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+
+ tid_it = atoi(entry->d_name);
+ snprintf(path, sizeof(path), "/proc/%ld/task/%ld/comm", (long int)mypid, (long int) tid_it);
+ if ((fd = open(path, O_RDONLY)) == -1)
+ continue;
+ rc = read(fd, buf, sizeof(buf) - 1);
+ if (rc >= 0) {
+ /* Last may char contain a '\n', get rid of it */
+ if (rc > 0 && buf[rc - 1] == '\n')
+ buf[rc - 1] = '\0';
+ else
+ buf[rc] = '\0';
+ if (strcmp(name, buf) == 0) {
+ *res_pid = tid_it;
+ found = true;
+ }
+ }
+ close(fd);
+
+ if (found)
+ break;
+ }
+
+ closedir(proc_dir);
+ return found;
+}
+
+/* Parse VTY THREADNAME variable, return its type and fill discovered res_pid if required */
+static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bool applynow)
+{
+ size_t i, len;
+ char *end;
+ bool is_pid = true;
+
+ if (strcmp(str, "all") == 0) {
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_ALL;
+ }
+
+ if (strcmp(str, "self") == 0) {
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_SELF;
+ }
+
+ len = strlen(str);
+ for (i = 0; i < len; i++) {
+ if (!isdigit(str[i])) {
+ is_pid = false;
+ break;
+ }
+ }
+ if (is_pid) {
+ errno = 0;
+ *res_pid = strtoul(str, &end, 0);
+ if ((errno == ERANGE && *res_pid == ULONG_MAX) || (errno && !*res_pid) ||
+ str == end) {
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+ if (!applynow || proc_tid_exists(*res_pid))
+ return SCHED_VTY_THREAD_ID;
+ else
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+
+ if (len > 15) {
+ /* Thread names only allow up to 15+1 null chars, see man pthread_setname_np */
+ return SCHED_VTY_THREAD_UNKNOWN;
+ }
+
+ if (applynow) {
+ if (proc_name_exists(str, res_pid))
+ return SCHED_VTY_THREAD_NAME;
+ else
+ return SCHED_VTY_THREAD_UNKNOWN;
+ } else {
+ /* assume a thread will be named after it */
+ *res_pid = 0;
+ return SCHED_VTY_THREAD_NAME;
+ }
+}
+
+/* Wrapper for sched_setaffinity applying to single thread or all threads in process based on tid_type. */
+static int my_sched_setaffinity(enum sched_vty_thread_id tid_type, pid_t pid,
+ cpu_set_t *cpuset, size_t cpuset_size)
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char dirname[100];
+ char str_mask[1024];
+ int tid_it;
+ int rc = 0;
+
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), cpuset, cpuset_size) < 0)
+ str_mask[0] = '\0';
+
+ if (tid_type != SCHED_VTY_THREAD_ALL) {
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting CPU affinity mask for tid %lu to: %s\n",
+ (unsigned long) pid, str_mask);
+
+ rc = sched_setaffinity(pid, sizeof(cpu_set_t), cpuset);
+ return rc;
+ }
+
+ snprintf(dirname, sizeof(dirname), "/proc/%ld/task", (long int)getpid());
+ proc_dir = opendir(dirname);
+ if (!proc_dir)
+ return -EINVAL;
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+ tid_it = atoi(entry->d_name);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting CPU affinity mask for tid %lu to: %s\n",
+ (unsigned long) tid_it, str_mask);
+
+ rc = sched_setaffinity(tid_it, sizeof(cpu_set_t), cpuset);
+ if (rc == -1)
+ break;
+ }
+
+ closedir(proc_dir);
+ return rc;
+
+}
+
+DEFUN(cfg_sched_cpu_affinity, cfg_sched_cpu_affinity_cmd,
+ "cpu-affinity (self|all|<0-4294967295>|THREADNAME) CPUHEXMASK [delay]",
+ "Set CPU affinity mask on a (group of) thread(s)\n"
+ "Set CPU affinity mask on thread running the VTY\n"
+ "Set CPU affinity mask on all process' threads\n"
+ "Set CPU affinity mask on a thread with specified PID\n"
+ "Set CPU affinity mask on a thread with specified thread name\n"
+ "CPU affinity mask\n"
+ "If set, delay applying the affinity mask now and let the app handle it at a later point\n")
+{
+ const char* str_who = argv[0];
+ const char *str_mask = argv[1];
+ bool applynow = (argc != 3);
+ int rc;
+ pid_t pid;
+ enum sched_vty_thread_id tid_type;
+ struct cpu_affinity_it *it, *it_next;
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+
+ tid_type = procname2pid(&pid, str_who, applynow);
+ if (tid_type == SCHED_VTY_THREAD_UNKNOWN) {
+ vty_out(vty, "%% Failed parsing target thread %s%s",
+ str_who, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (tid_type == SCHED_VTY_THREAD_ID && !applynow) {
+ vty_out(vty, "%% It makes no sense to delay applying cpu-affinity on tid %lu%s",
+ (unsigned long)pid, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (tid_type == SCHED_VTY_THREAD_ALL && !applynow) {
+ vty_out(vty, "%% It makes no sense to delay applying cpu-affinity on all threads%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ cpuset = CPU_ALLOC(get_num_cpus());
+ cpuset_size = CPU_ALLOC_SIZE(get_num_cpus());
+ if (parse_cpu_hex_mask(str_mask, cpuset, cpuset_size) < 0) {
+ vty_out(vty, "%% Failed parsing CPU Affinity Mask %s%s",
+ str_mask, VTY_NEWLINE);
+ CPU_FREE(cpuset);
+ return CMD_WARNING;
+ }
+
+ if (applynow) {
+ rc = my_sched_setaffinity(tid_type, pid, cpuset, cpuset_size);
+ if (rc == -1) {
+ vty_out(vty, "%% Failed setting sched CPU Affinity Mask %s: %s%s",
+ str_mask, strerror(errno), VTY_NEWLINE);
+ CPU_FREE(cpuset);
+ return CMD_WARNING;
+ }
+ }
+
+ /* Keep history of cmds applied to be able to rewrite config. If PID was passed
+ directly it makes no sense to store it since PIDs are temporary */
+ if (tid_type == SCHED_VTY_THREAD_SELF ||
+ tid_type == SCHED_VTY_THREAD_ALL ||
+ tid_type == SCHED_VTY_THREAD_NAME) {
+ pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
+
+ /* Drop previous entries matching, since they will be overwritten */
+ llist_for_each_entry_safe(it, it_next, &sched_vty_opts->cpu_affinity_li, entry) {
+ if (strcmp(it->bufname, str_who) == 0) {
+ llist_del(&it->entry);
+ CPU_FREE(it->cpuset);
+ talloc_free(it);
+ break;
+ }
+ }
+ it = talloc_zero(sched_vty_opts->tall_ctx, struct cpu_affinity_it);
+ OSMO_STRLCPY_ARRAY(it->bufname, str_who);
+ it->tid_type = tid_type;
+ it->cpuset = cpuset;
+ it->cpuset_size = cpuset_size;
+ it->delay = !applynow;
+ llist_add_tail(&it->entry, &sched_vty_opts->cpu_affinity_li);
+
+ pthread_mutex_unlock(&sched_vty_opts->cpu_affinity_li_mutex);
+ } else {
+ /* We don't need cpuset for later, free it: */
+ CPU_FREE(cpuset);
+ }
+ return CMD_SUCCESS;
+}
+
+static int set_sched_rr(unsigned int prio)
+{
+ struct sched_param param;
+ int rc;
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = prio;
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Setting SCHED_RR priority %d\n", param.sched_priority);
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc == -1) {
+ LOGP(DLGLOBAL, LOGL_FATAL, "Setting SCHED_RR priority %d failed: %s\n",
+ param.sched_priority, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+DEFUN(cfg_sched_policy, cfg_sched_policy_cmd,
+ "policy rr <1-32>",
+ "Set the scheduling policy to use for the process\n"
+ "Use the SCHED_RR real-time scheduling algorithm\n"
+ "Set the SCHED_RR real-time priority\n")
+{
+ sched_vty_opts->sched_rr_prio = atoi(argv[0]);
+
+ if (set_sched_rr(sched_vty_opts->sched_rr_prio) < 0) {
+ vty_out(vty, "%% Failed setting SCHED_RR priority %d%s",
+ sched_vty_opts->sched_rr_prio, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sched,
+ cfg_sched_cmd,
+ "cpu-sched", "Configure CPU Scheduler related settings")
+{
+ vty->index = NULL;
+ vty->node = L_CPU_SCHED_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sched_threads, show_sched_threads_cmd,
+ "show cpu-sched threads",
+ SHOW_STR
+ "Show Sched section information\n"
+ "Show information about running threads)\n")
+{
+ DIR *proc_dir;
+ struct dirent *entry;
+ char path[100];
+ char name[17];
+ char str_mask[1024];
+ int tid_it;
+ int fd;
+ pid_t mypid = getpid();
+ int rc;
+ cpu_set_t *cpuset;
+ size_t cpuset_size;
+
+ vty_out(vty, "Thread list for PID %lu:%s", (unsigned long) mypid, VTY_NEWLINE);
+
+ snprintf(path, sizeof(path), "/proc/%ld/task", (long int)mypid);
+ proc_dir = opendir(path);
+ if (!proc_dir) {
+ vty_out(vty, "%% Failed opening dir%s%s", path, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ while ((entry = readdir(proc_dir)))
+ {
+ if (entry->d_name[0] == '.')
+ continue;
+
+ tid_it = atoi(entry->d_name);
+ snprintf(path, sizeof(path), "/proc/%ld/task/%ld/comm", (long int)mypid, (long int)tid_it);
+ if ((fd = open(path, O_RDONLY)) != -1) {
+ rc = read(fd, name, sizeof(name) - 1);
+ if (rc >= 0) {
+ /* Last may char contain a '\n', get rid of it */
+ if (rc > 0 && name[rc - 1] == '\n')
+ name[rc - 1] = '\0';
+ else
+ name[rc] = '\0';
+ }
+ close(fd);
+ } else {
+ name[0] = '\0';
+ }
+
+ str_mask[0] = '\0';
+ cpuset = CPU_ALLOC(get_num_cpus());
+ cpuset_size = CPU_ALLOC_SIZE(get_num_cpus());
+ CPU_ZERO_S(cpuset_size, cpuset);
+ if (sched_getaffinity(tid_it, cpuset_size, cpuset) == 0) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), cpuset, cpuset_size) < 0)
+ str_mask[0] = '\0';
+ }
+ CPU_FREE(cpuset);
+
+ vty_out(vty, " TID: %lu, NAME: '%s', cpu-affinity: %s%s",
+ (unsigned long) tid_it, name, str_mask, VTY_NEWLINE);
+ }
+
+ closedir(proc_dir);
+ return CMD_SUCCESS;
+}
+
+static int config_write_sched(struct vty *vty)
+{
+ struct cpu_affinity_it *it;
+ char str_mask[1024];
+
+ /* Only add the node if there's something to write under it */
+ if (sched_vty_opts->sched_rr_prio || !llist_empty(&sched_vty_opts->cpu_affinity_li))
+ vty_out(vty, "cpu-sched%s", VTY_NEWLINE);
+
+ if (sched_vty_opts->sched_rr_prio)
+ vty_out(vty, " policy rr %d%s", sched_vty_opts->sched_rr_prio, VTY_NEWLINE);
+
+ llist_for_each_entry(it, &sched_vty_opts->cpu_affinity_li, entry) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask), it->cpuset, it->cpuset_size) < 0)
+ OSMO_STRLCPY_ARRAY(str_mask, "ERROR");
+ vty_out(vty, " cpu-affinity %s %s%s%s", it->bufname, str_mask,
+ it->delay ? " delay" : "", VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+/*! Initialize sched VTY nodes
+ * \param[in] tall_ctx Talloc context to use internally by vty_sched subsystem.
+ * \return 0 on success, non-zero on error.
+ */
+int osmo_cpu_sched_vty_init(void *tall_ctx)
+{
+ OSMO_ASSERT(!sched_vty_opts); /* assert only called once */
+
+ sched_vty_opts = talloc_zero(tall_ctx, struct sched_vty_opts);
+ sched_vty_opts->tall_ctx = tall_ctx;
+ INIT_LLIST_HEAD(&sched_vty_opts->cpu_affinity_li);
+ pthread_mutex_init(&sched_vty_opts->cpu_affinity_li_mutex, NULL);
+
+ install_element(CONFIG_NODE, &cfg_sched_cmd);
+ install_node(&sched_node, config_write_sched);
+
+ install_element(L_CPU_SCHED_NODE, &cfg_sched_policy_cmd);
+ install_element(L_CPU_SCHED_NODE, &cfg_sched_cpu_affinity_cmd);
+
+ install_element_ve(&show_sched_threads_cmd);
+
+ /* Initialize amount of cpus now */
+ if (get_num_cpus() < 0)
+ return -1;
+
+ return 0;
+}
+
+/*! Apply cpu-affinity on calling thread based on VTY configuration
+ * \return 0 on success, non-zero on error.
+ */
+int osmo_cpu_sched_vty_apply_localthread(void)
+{
+ struct cpu_affinity_it *it, *it_match = NULL;
+ char name[16]; /* 15 + \0 */
+ char str_mask[1024];
+ bool has_name = false;
+ int rc = 0;
+
+ /* Assert subsystem was inited and structs are preset */
+ OSMO_ASSERT(sched_vty_opts);
+
+ if (pthread_getname_np(pthread_self(), name, sizeof(name)) == 0)
+ has_name = true;
+
+ /* Get latest matching mask for the thread */
+ pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
+ llist_for_each_entry(it, &sched_vty_opts->cpu_affinity_li, entry) {
+ switch (it->tid_type) {
+ case SCHED_VTY_THREAD_SELF:
+ continue; /* self to the VTY thread, not us */
+ case SCHED_VTY_THREAD_ALL:
+ it_match = it;
+ break;
+ case SCHED_VTY_THREAD_NAME:
+ if (!has_name)
+ continue;
+ if (strcmp(name, it->bufname) != 0)
+ continue;
+ it_match = it;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ }
+
+ if (it_match) {
+ rc = my_sched_setaffinity(SCHED_VTY_THREAD_SELF, 0, it_match->cpuset, it_match->cpuset_size);
+ if (rc == -1) {
+ if (generate_cpu_hex_mask(str_mask, sizeof(str_mask),
+ it_match->cpuset, it_match->cpuset_size) < 0)
+ str_mask[0] = '\0';
+ LOGP(DLGLOBAL, LOGL_FATAL, "Setting cpu-affinity mask %s failed: %s\n",
+ str_mask, strerror(errno));
+ }
+ }
+ pthread_mutex_unlock(&sched_vty_opts->cpu_affinity_li_mutex);
+ return rc;
+}
+
+/*! @} */