irqbalance/irqbalance.c

739 lines
18 KiB
C

/*
* Copyright (C) 2006, Intel Corporation
* Copyright (C) 2012, Neil Horman <nhorman@tuxdriver.com>
*
* This file is part of irqbalance
*
* This program file 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; version 2 of the License.
*
* 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 in a file named COPYING; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/time.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#endif
#ifdef HAVE_LIBCAP_NG
#include <cap-ng.h>
#endif
#ifdef HAVE_IRQBALANCEUI
#include <sys/un.h>
#include <sys/socket.h>
#endif
#include "irqbalance.h"
#include "thermal.h"
volatile int keep_going = 1;
int one_shot_mode;
int debug_mode;
int foreground_mode;
int numa_avail;
int journal_logging = 0;
int need_rescan;
int need_rebuild;
unsigned int log_mask = TO_ALL;
const char *log_indent;
unsigned long power_thresh = ULONG_MAX;
unsigned long deepest_cache = 2;
unsigned long long cycle_count = 0;
char *pidfile = NULL;
char *polscript = NULL;
long HZ;
int sleep_interval = SLEEP_INTERVAL;
int last_interval;
GMainLoop *main_loop;
char *cpu_ban_string = NULL;
unsigned long migrate_ratio = 0;
#ifdef HAVE_IRQBALANCEUI
int socket_fd;
char socket_name[64];
char *banned_cpumask_from_ui = NULL;
#endif
static void sleep_approx(int seconds)
{
struct timespec ts;
struct timeval tv;
gettimeofday(&tv, NULL);
ts.tv_sec = seconds;
ts.tv_nsec = -tv.tv_usec*1000;
while (ts.tv_nsec < 0) {
ts.tv_sec--;
ts.tv_nsec += 1000000000;
}
nanosleep(&ts, NULL);
}
#ifdef HAVE_GETOPT_LONG
struct option lopts[] = {
{"oneshot", 0, NULL, 'o'},
{"debug", 0, NULL, 'd'},
{"foreground", 0, NULL, 'f'},
{"powerthresh", 1, NULL, 'p'},
{"banirq", 1 , NULL, 'i'},
{"deepestcache", 1, NULL, 'c'},
{"policyscript", 1, NULL, 'l'},
{"pid", 1, NULL, 's'},
{"journal", 0, NULL, 'j'},
{"banmod", 1 , NULL, 'm'},
{"interval", 1 , NULL, 't'},
{"version", 0, NULL, 'V'},
{"migrateval", 1, NULL, 'e'},
{0, 0, 0, 0}
};
static void usage(void)
{
log(TO_CONSOLE, LOG_INFO, "irqbalance [--oneshot | -o] [--debug | -d] [--foreground | -f] [--journal | -j]\n");
log(TO_CONSOLE, LOG_INFO, " [--powerthresh= | -p <off> | <n>] [--banirq= | -i <n>] [--banmod= | -m <module>] [--policyscript= | -l <script>]\n");
log(TO_CONSOLE, LOG_INFO, " [--pid= | -s <file>] [--deepestcache= | -c <n>] [--interval= | -t <n>] [--migrateval= | -e <n>]\n");
}
static void version(void)
{
log(TO_CONSOLE, LOG_INFO, "irqbalance version " VERSION "\n");
}
static void parse_command_line(int argc, char **argv)
{
int opt;
int longind;
unsigned long val;
char *endptr;
while ((opt = getopt_long(argc, argv,
"odfjVi:p:s:c:l:m:t:e:",
lopts, &longind)) != -1) {
switch(opt) {
case '?':
usage();
exit(1);
break;
case 'V':
version();
exit(1);
break;
case 'c':
deepest_cache = strtoul(optarg, &endptr, 10);
if (optarg == endptr || deepest_cache == ULONG_MAX || deepest_cache < 1) {
usage();
exit(1);
}
break;
case 'd':
debug_mode=1;
foreground_mode=1;
break;
case 'f':
foreground_mode=1;
break;
case 'i':
val = strtoull(optarg, &endptr, 10);
if (optarg == endptr || val == ULONG_MAX) {
usage();
exit(1);
}
add_cl_banned_irq((int)val);
break;
case 'l':
free(polscript);
polscript = strdup(optarg);
break;
case 'm':
add_cl_banned_module(optarg);
break;
case 'p':
if (!strncmp(optarg, "off", strlen(optarg)))
power_thresh = ULONG_MAX;
else {
power_thresh = strtoull(optarg, &endptr, 10);
if (optarg == endptr || power_thresh == ULONG_MAX) {
usage();
exit(1);
}
}
break;
case 'o':
one_shot_mode=1;
break;
case 's':
pidfile = optarg;
break;
case 'j':
journal_logging=1;
foreground_mode=1;
break;
case 't':
sleep_interval = strtol(optarg, &endptr, 10);
if (optarg == endptr || sleep_interval < 1) {
usage();
exit(1);
}
break;
case 'e':
migrate_ratio = strtoul(optarg, &endptr, 10);
if (optarg == endptr) {
usage();
exit(1);
}
break;
}
}
}
#else /* ! HAVE_GETOPT_LONG */
static void parse_command_line(int argc, char **argv)
{
if (argc>1 && strstr(argv[1],"--debug")) {
debug_mode=1;
foreground_mode=1;
}
if (argc>1 && strstr(argv[1],"--foreground"))
foreground_mode=1;
if (argc>1 && strstr(argv[1],"--oneshot"))
one_shot_mode=1;
if (argc>1 && strstr(argv[1],"--journal")) {
journal_logging=1;
foreground_mode=1;
}
}
#endif /* HAVE_GETOPT_LONG */
/*
* This builds our object tree. The Hierarchy is typically pretty
* straightforward.
* At the top are numa_nodes
* CPU packages belong to a single numa_node, unless the cache domains are in
* separate nodes. In that case, the cache domain's parent is the package, but
* the numa nodes point to the cache domains instead of the package as their
* children. This allows us to maintain the CPU hierarchy while adjusting for
* alternate memory topologies that are present on recent processor.
* All Cache domains belong to a CPU package
* All CPU cores belong to a cache domain
*
* Objects are built in that order (top down)
*
* Object workload is the aggregate sum of the
* workload of the objects below it
*/
static void build_object_tree(void)
{
build_numa_node_list();
parse_cpu_tree();
rebuild_irq_db();
}
static void free_object_tree(void)
{
free_numa_node_list();
clear_cpu_tree();
free_irq_db();
}
static void dump_object_tree(void)
{
for_each_object(numa_nodes, dump_numa_node_info, NULL);
}
void force_rebalance_irq(struct irq_info *info, void *data __attribute__((unused)))
{
if (info->level == BALANCE_NONE)
return;
if (info->assigned_obj == NULL)
rebalance_irq_list = g_list_append(rebalance_irq_list, info);
else
migrate_irq(&info->assigned_obj->interrupts, &rebalance_irq_list, info);
info->assigned_obj = NULL;
}
gboolean handler(gpointer data __attribute__((unused)))
{
keep_going = 0;
g_main_loop_quit(main_loop);
return TRUE;
}
gboolean force_rescan(gpointer data __attribute__((unused)))
{
if (cycle_count)
need_rescan = 1;
return TRUE;
}
gboolean scan(gpointer data __attribute__((unused)))
{
log(TO_CONSOLE, LOG_INFO, "\n\n\n-----------------------------------------------------------------------------\n");
clear_work_stats();
parse_proc_interrupts();
/* cope with cpu hotplug -- detected during /proc/interrupts parsing */
while (keep_going && (need_rescan || need_rebuild)) {
int try_times = 0;
need_rescan = 0;
cycle_count = 0;
log(TO_CONSOLE, LOG_INFO, "Rescanning cpu topology \n");
clear_work_stats();
do {
free_object_tree();
if (++try_times > 3) {
log(TO_CONSOLE, LOG_WARNING, "Rescanning cpu topology: fail\n");
goto out;
}
need_rebuild = 0;
build_object_tree();
} while (need_rebuild);
for_each_irq(NULL, force_rebalance_irq, NULL);
parse_proc_interrupts();
parse_proc_stat();
sleep_approx(sleep_interval);
clear_work_stats();
parse_proc_interrupts();
}
parse_proc_stat();
if (cycle_count)
update_migration_status();
calculate_placement();
activate_mappings();
out:
if (debug_mode)
dump_tree();
if (one_shot_mode)
keep_going = 0;
cycle_count++;
/* sleep_interval may be changed by socket */
if (last_interval != sleep_interval) {
last_interval = sleep_interval;
g_timeout_add_seconds(sleep_interval, scan, NULL);
return FALSE;
}
if (keep_going) {
return TRUE;
} else {
g_main_loop_quit(main_loop);
return FALSE;
}
}
void get_irq_data(struct irq_info *irq, void *data)
{
char **irqdata = (char **)data;
char *newptr = NULL;
if (!*irqdata)
newptr = calloc(24 + 1 + 11 + 20 + 20 + 11, 1);
else
newptr = realloc(*irqdata, strlen(*irqdata) + 24 + 1 + 11 + 20 + 20 + 11);
if (!newptr)
return;
*irqdata = newptr;
sprintf(*irqdata + strlen(*irqdata),
"IRQ %d LOAD %" PRIu64 " DIFF %" PRIu64 " CLASS %d ", irq->irq, irq->load,
(irq->irq_count - irq->last_irq_count), irq->class);
}
void get_object_stat(struct topo_obj *object, void *data)
{
char **stats = (char **)data;
char *irq_data = NULL;
char *newptr = NULL;
size_t irqdlen;
if (g_list_length(object->interrupts) > 0) {
for_each_irq(object->interrupts, get_irq_data, &irq_data);
}
irqdlen = irq_data ? strlen(irq_data) : 0;
/*
* Note, the size in both conditional branches below is made up as follows:
* strlen(irq_data) - self explanitory
* 31 - The size of "TYPE NUMBER LOAD SAVE_MODE "
* 11 - The maximal size of a %d printout
* 20 - The maximal size of a %lu printout
* 1 - The trailing string terminator
* This should be adjusted if the string in the sprintf is changed
*/
if (!*stats) {
newptr = calloc(irqdlen + 31 + 11 + 20 + 11 + 1, 1);
} else {
newptr = realloc(*stats, strlen(*stats) + irqdlen + 31 + 11 + 20 + 11 + 1);
}
if (!newptr) {
free(irq_data);
return;
}
*stats = newptr;
sprintf(*stats + strlen(*stats), "TYPE %d NUMBER %d LOAD %" PRIu64 " SAVE_MODE %d %s",
object->obj_type, object->number, object->load,
object->powersave_mode, irq_data ? irq_data : "");
free(irq_data);
if (object->obj_type != OBJ_TYPE_CPU) {
for_each_object(object->children, get_object_stat, data);
}
}
#ifdef HAVE_IRQBALANCEUI
gboolean sock_handle(gint fd, GIOCondition condition, gpointer user_data __attribute__((unused)))
{
char buff[500];
int sock;
int recv_size = 0;
int valid_user = 0;
struct iovec iov = { buff, 500 };
struct msghdr msg = { 0 };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = malloc(CMSG_SPACE(sizeof(struct ucred)));
msg.msg_controllen = CMSG_SPACE(sizeof(struct ucred));
struct cmsghdr *cmsg;
if (condition == G_IO_IN) {
sock = accept(fd, NULL, NULL);
if (sock < 0) {
log(TO_ALL, LOG_WARNING, "Connection couldn't be accepted.\n");
goto out;
}
if ((recv_size = recvmsg(sock, &msg, 0)) < 0) {
log(TO_ALL, LOG_WARNING, "Error while receiving data.\n");
goto out_close;
}
cmsg = CMSG_FIRSTHDR(&msg);
if (!cmsg) {
log(TO_ALL, LOG_WARNING, "Connection no memory.\n");
goto out_close;
}
if ((cmsg->cmsg_level == SOL_SOCKET) &&
(cmsg->cmsg_type == SCM_CREDENTIALS)) {
struct ucred *credentials = (struct ucred *) CMSG_DATA(cmsg);
if (!credentials->uid) {
valid_user = 1;
}
}
if (!valid_user) {
log(TO_ALL, LOG_INFO, "Permission denied for user to connect to socket.\n");
goto out_close;
}
if (!strncmp(buff, "stats", strlen("stats"))) {
char *stats = NULL;
for_each_object(numa_nodes, get_object_stat, &stats);
send(sock, stats, strlen(stats), 0);
free(stats);
}
if (!strncmp(buff, "settings ", strlen("settings "))) {
if (!(strncmp(buff + strlen("settings "), "sleep ",
strlen("sleep ")))) {
char *sleep_string = malloc(
sizeof(char) * (recv_size - strlen("settings sleep ") + 1));
if (!sleep_string)
goto out_close;
strncpy(sleep_string, buff + strlen("settings sleep "),
recv_size - strlen("settings sleep "));
sleep_string[recv_size - strlen("settings sleep ")] = '\0';
int new_iterval = strtoul(sleep_string, NULL, 10);
if (new_iterval >= 1) {
sleep_interval = new_iterval;
}
free(sleep_string);
} else if (!(strncmp(buff + strlen("settings "), "ban irqs ",
strlen("ban irqs ")))) {
char *end;
char *irq_string = malloc(
sizeof(char) * (recv_size - strlen("settings ban irqs ") + 1));
if (!irq_string)
goto out_close;
strncpy(irq_string, buff + strlen("settings ban irqs "),
recv_size - strlen("settings ban irqs "));
irq_string[recv_size - strlen("settings ban irqs ")] = '\0';
g_list_free_full(cl_banned_irqs, free);
cl_banned_irqs = NULL;
need_rescan = 1;
if (!strncmp(irq_string, "NONE", strlen("NONE"))) {
free(irq_string);
goto out_close;
}
int irq = strtoul(irq_string, &end, 10);
do {
add_cl_banned_irq(irq);
} while((irq = strtoul(end, &end, 10)));
free(irq_string);
} else if (!(strncmp(buff + strlen("settings "), "cpus ",
strlen("cpus")))) {
banned_cpumask_from_ui = NULL;
free(cpu_ban_string);
cpu_ban_string = NULL;
cpu_ban_string = malloc(
sizeof(char) * (recv_size - strlen("settings cpus ") + 1));
if (!cpu_ban_string)
goto out_close;
strncpy(cpu_ban_string, buff + strlen("settings cpus "),
recv_size - strlen("settings cpus "));
cpu_ban_string[recv_size - strlen("settings cpus ")] = '\0';
banned_cpumask_from_ui = strtok(cpu_ban_string, " ");
if (!strncmp(banned_cpumask_from_ui, "NULL", strlen("NULL"))) {
banned_cpumask_from_ui = NULL;
free(cpu_ban_string);
cpu_ban_string = NULL;
}
need_rescan = 1;
}
}
if (!strncmp(buff, "setup", strlen("setup"))) {
char banned[512];
char *setup = calloc(strlen("SLEEP ") + 11 + 1, 1);
char *newptr = NULL;
if (!setup)
goto out_close;
snprintf(setup, strlen("SLEEP ") + 11 + 1, "SLEEP %d ", sleep_interval);
if(g_list_length(cl_banned_irqs) > 0) {
for_each_irq(cl_banned_irqs, get_irq_data, &setup);
}
cpumask_scnprintf(banned, 512, banned_cpus);
newptr = realloc(setup, strlen(setup) + strlen(banned) + 7 + 1);
if (!newptr)
goto out_free_setup;
setup = newptr;
snprintf(setup + strlen(setup), strlen(banned) + 7 + 1,
"BANNED %s", banned);
send(sock, setup, strlen(setup), 0);
out_free_setup:
free(setup);
}
out_close:
close(sock);
}
out:
free(msg.msg_control);
return TRUE;
}
int init_socket()
{
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (socket_fd < 0) {
log(TO_ALL, LOG_WARNING, "Socket couldn't be created.\n");
return 1;
}
/*
* First try to create a file-based socket in tmpfs. If that doesn't
* succeed, fall back to an abstract socket (non file-based).
*/
addr.sun_family = AF_UNIX;
snprintf(socket_name, 64, "%s/%s%d.sock", SOCKET_TMPFS, SOCKET_PATH, getpid());
strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path));
if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
log(TO_ALL, LOG_WARNING, "Daemon couldn't be bound to the file-based socket.\n");
/* Try binding to abstract */
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
log(TO_ALL, LOG_WARNING, "Daemon couldn't be bound to the abstract socket, bailing out.\n");
return 1;
}
}
int optval = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) < 0) {
log(TO_ALL, LOG_WARNING, "Unable to set socket options.\n");
return 1;
}
listen(socket_fd, 1);
g_unix_fd_add(socket_fd, G_IO_IN, sock_handle, NULL);
return 0;
}
#endif
int main(int argc, char** argv)
{
sigset_t sigset, old_sigset;
int ret = EXIT_SUCCESS;
sigemptyset(&sigset);
sigaddset(&sigset,SIGINT);
sigaddset(&sigset,SIGHUP);
sigaddset(&sigset,SIGTERM);
sigaddset(&sigset,SIGUSR1);
sigaddset(&sigset,SIGUSR2);
sigprocmask(SIG_BLOCK, &sigset, &old_sigset);
parse_command_line(argc, argv);
/*
* Open the syslog connection
*/
openlog(argv[0], 0, LOG_DAEMON);
if (getenv("IRQBALANCE_ONESHOT"))
one_shot_mode=1;
if (getenv("IRQBALANCE_DEBUG")) {
debug_mode=1;
foreground_mode=1;
}
/*
* If we are't in debug mode, don't dump anything to the console
* note that everything goes to the console before we check this
*/
if (journal_logging)
log_indent = "....";
else
log_indent = " ";
if (!debug_mode)
log_mask &= ~TO_CONSOLE;
if (numa_available() > -1) {
numa_avail = 1;
} else
log(TO_CONSOLE, LOG_INFO, "This machine seems not NUMA capable.\n");
if (geteuid() != 0)
log(TO_ALL, LOG_WARNING, "Irqbalance hasn't been executed under root privileges, thus it won't in fact balance interrupts.\n");
HZ = sysconf(_SC_CLK_TCK);
if (HZ == -1) {
log(TO_ALL, LOG_WARNING, "Unable to determine HZ defaulting to 100\n");
HZ = 100;
}
if (!foreground_mode) {
int pidfd = -1;
if (daemon(0,0))
exit(EXIT_FAILURE);
/* Write pidfile which can be used to avoid starting multiple instances */
if (pidfile && (pidfd = open(pidfile,
O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) {
char str[16];
snprintf(str, sizeof(str), "%u\n", getpid());
write(pidfd, str, strlen(str));
close(pidfd);
}
}
build_object_tree();
if (debug_mode)
dump_object_tree();
/* On single core UP systems irqbalance obviously has no work to do */
if (num_online_cpus() <= 1) {
char *msg = "Balancing is ineffective on systems with a "
"single cpu. Shutting down\n";
log(TO_ALL, LOG_WARNING, "%s", msg);
goto out;
}
g_unix_signal_add(SIGINT, handler, NULL);
g_unix_signal_add(SIGTERM, handler, NULL);
g_unix_signal_add(SIGUSR1, handler, NULL);
g_unix_signal_add(SIGUSR2, handler, NULL);
g_unix_signal_add(SIGHUP, force_rescan, NULL);
sigprocmask(SIG_SETMASK, &old_sigset, NULL);
#ifdef HAVE_LIBCAP_NG
// Drop capabilities
capng_clear(CAPNG_SELECT_BOTH);
capng_lock();
capng_apply(CAPNG_SELECT_BOTH);
#endif
for_each_irq(NULL, force_rebalance_irq, NULL);
parse_proc_interrupts();
parse_proc_stat();
#ifdef HAVE_IRQBALANCEUI
if (init_socket()) {
ret = EXIT_FAILURE;
goto out;
}
#endif
if (init_thermal())
log(TO_ALL, LOG_WARNING, "Failed to initialize thermal events.\n");
main_loop = g_main_loop_new(NULL, FALSE);
last_interval = sleep_interval;
g_timeout_add_seconds(sleep_interval, scan, NULL);
g_main_loop_run(main_loop);
g_main_loop_quit(main_loop);
out:
deinit_thermal();
free_object_tree();
free_cl_opts();
free(polscript);
/* Remove pidfile */
if (!foreground_mode && pidfile)
unlink(pidfile);
#ifdef HAVE_IRQBALANCEUI
/* Remove socket */
if (socket_fd > 0)
close(socket_fd);
if (socket_name[0])
unlink(socket_name);
#endif
return ret;
}