priod: finish basic operation
This commit is contained in:
parent
7713850987
commit
456c362811
190
priod.c
190
priod.c
|
@ -2,6 +2,7 @@
|
||||||
* priod.c: process reprioritizing daemon
|
* priod.c: process reprioritizing daemon
|
||||||
*
|
*
|
||||||
* Thanks to http://netsplit.com/the-proc-connector-and-socket-filters
|
* Thanks to http://netsplit.com/the-proc-connector-and-socket-filters
|
||||||
|
* for showing the way around the proc connector and BPF.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2017, Přemysl Janouch <p.janouch@gmail.com>
|
* Copyright (c) 2017, Přemysl Janouch <p.janouch@gmail.com>
|
||||||
*
|
*
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
#define PROGRAM_NAME "priod"
|
#define PROGRAM_NAME "priod"
|
||||||
#include "liberty/liberty.c"
|
#include "liberty/liberty.c"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
#include <linux/cn_proc.h>
|
#include <linux/cn_proc.h>
|
||||||
#include <linux/netlink.h>
|
#include <linux/netlink.h>
|
||||||
#include <linux/connector.h>
|
#include <linux/connector.h>
|
||||||
|
@ -37,6 +40,17 @@
|
||||||
|
|
||||||
// --- Main program ------------------------------------------------------------
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
|
#define RULE_UNSET INT_MIN
|
||||||
|
|
||||||
|
struct rule
|
||||||
|
{
|
||||||
|
char *program_name; ///< Program name to match against
|
||||||
|
|
||||||
|
int oom_score_adj; ///< For /proc/%/oom_score_adj
|
||||||
|
int prio; ///< For setpriority()
|
||||||
|
int ioprio; ///< For SYS_ioprio_set
|
||||||
|
};
|
||||||
|
|
||||||
struct app_context
|
struct app_context
|
||||||
{
|
{
|
||||||
struct poller poller; ///< Poller
|
struct poller poller; ///< Poller
|
||||||
|
@ -44,6 +58,9 @@ struct app_context
|
||||||
|
|
||||||
int proc_fd; ///< Proc connector FD
|
int proc_fd; ///< Proc connector FD
|
||||||
struct poller_fd proc_event; ///< Proc connector read event
|
struct poller_fd proc_event; ///< Proc connector read event
|
||||||
|
|
||||||
|
struct rule *rules; ///< Rules
|
||||||
|
size_t rules_len; ///< Number of rules
|
||||||
};
|
};
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -61,6 +78,77 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,
|
||||||
fputs ("\n", stream);
|
fputs ("\n", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Configuration -----------------------------------------------------------
|
||||||
|
|
||||||
|
static bool
|
||||||
|
load_integer (struct str_map *root, const char *key, int min, int max,
|
||||||
|
int *value, struct error **e)
|
||||||
|
{
|
||||||
|
*value = RULE_UNSET;
|
||||||
|
|
||||||
|
struct config_item *item;
|
||||||
|
if (!(item = str_map_find (root, key)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (item->type != CONFIG_ITEM_INTEGER
|
||||||
|
|| item->value.integer < min || item->value.integer > max)
|
||||||
|
return error_set (e, "%s: must be an integer (%d..%d)", key, min, max);
|
||||||
|
|
||||||
|
*value = item->value.integer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
load_rule (const char *name, struct str_map *m, struct rule *r,
|
||||||
|
struct error **e)
|
||||||
|
{
|
||||||
|
r->program_name = xstrdup (name);
|
||||||
|
if (!load_integer (m, "oom_score_adj", -1000, 1000, &r->oom_score_adj, e)
|
||||||
|
|| !load_integer (m, "prio", -20, 19, &r->prio, e)
|
||||||
|
|| !load_integer (m, "ioprio", 0, 7, &r->ioprio, e))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct rule *
|
||||||
|
find_rule (struct app_context *ctx, const char *program_name)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ctx->rules_len; i++)
|
||||||
|
if (!strcmp (ctx->rules[i].program_name, program_name))
|
||||||
|
return ctx->rules + i;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_configuration (struct app_context *ctx, const char *config_path)
|
||||||
|
{
|
||||||
|
struct error *e = NULL;
|
||||||
|
struct config_item *root = config_read_from_file (config_path, &e);
|
||||||
|
|
||||||
|
if (e)
|
||||||
|
{
|
||||||
|
print_error ("error loading configuration: %s", e->message);
|
||||||
|
error_free (e);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str_map_iter iter;
|
||||||
|
str_map_iter_init (&iter, &root->value.object);
|
||||||
|
ctx->rules = xcalloc (iter.map->len, sizeof *ctx->rules);
|
||||||
|
ctx->rules_len = 0;
|
||||||
|
|
||||||
|
struct config_item *subtree;
|
||||||
|
while ((subtree = str_map_iter_next (&iter)))
|
||||||
|
{
|
||||||
|
const char *path = iter.link->key;
|
||||||
|
if (subtree->type != CONFIG_ITEM_OBJECT)
|
||||||
|
exit_fatal ("rule `%s' in configuration is not an object", path);
|
||||||
|
if (!load_rule (path, &subtree->value.object,
|
||||||
|
&ctx->rules[ctx->rules_len++], &e))
|
||||||
|
exit_fatal ("rule `%s': %s", path, e->message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Signals -----------------------------------------------------------------
|
// --- Signals -----------------------------------------------------------------
|
||||||
|
|
||||||
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
|
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
|
||||||
|
@ -125,35 +213,95 @@ enum
|
||||||
#define IOPRIO_CLASS_SHIFT 13
|
#define IOPRIO_CLASS_SHIFT 13
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_exec_name (struct app_context *ctx, int pid, const char *name)
|
adj_oom_score (int pid, const char *program_name, int score)
|
||||||
{
|
{
|
||||||
print_status ("exec %d %s", pid, name);
|
char buf[16]; snprintf (buf, sizeof buf, "%d\n", score);
|
||||||
|
|
||||||
if (true)
|
|
||||||
return;
|
|
||||||
|
|
||||||
setpriority (PRIO_PROCESS, pid, 0 /* TODO -20..20 */);
|
|
||||||
|
|
||||||
// TODO: this is per thread, and there's an inherent race condition;
|
|
||||||
// keep going through /proc/%d/task and reprioritize all threads;
|
|
||||||
// stop trying after N-th try
|
|
||||||
syscall (SYS_ioprio_set, IOPRIO_WHO_PROCESS,
|
|
||||||
IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT | 0 /* TODO 0..7 */);
|
|
||||||
|
|
||||||
char *path = xstrdup_printf ("/proc/%d/oom_score_adj", pid);
|
char *path = xstrdup_printf ("/proc/%d/oom_score_adj", pid);
|
||||||
struct error *e = NULL;
|
struct error *e = NULL;
|
||||||
// TODO: figure out the contents
|
if (!write_file (path, buf, strlen (buf), &e))
|
||||||
if (!write_file (path, "", 0, &e))
|
|
||||||
{
|
{
|
||||||
print_error ("%s", e->message);
|
print_error ("%d (%s): %s", pid, program_name, e->message);
|
||||||
error_free (e);
|
error_free (e);
|
||||||
}
|
}
|
||||||
free (path);
|
free (path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
reprioritize (int pid, const char *program_name, DIR *dir, struct rule *rule,
|
||||||
|
struct str_map *set)
|
||||||
|
{
|
||||||
|
size_t not_previously_visited = 0;
|
||||||
|
struct dirent *iter;
|
||||||
|
while ((errno = 0, iter = readdir (dir)))
|
||||||
|
{
|
||||||
|
int tid = atoi (iter->d_name);
|
||||||
|
if (!tid || str_map_find (set, iter->d_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
print_debug (" - thread %d", tid);
|
||||||
|
str_map_set (set, iter->d_name, (void *) ++not_previously_visited);
|
||||||
|
|
||||||
|
if (RULE_UNSET != rule->prio
|
||||||
|
&& setpriority (PRIO_PROCESS, pid, rule->prio))
|
||||||
|
print_error ("%d (%s): thread %d: setpriority: %s",
|
||||||
|
pid, program_name, tid, strerror (errno));
|
||||||
|
if (RULE_UNSET != rule->ioprio
|
||||||
|
&& syscall (SYS_ioprio_set, IOPRIO_WHO_PROCESS, tid,
|
||||||
|
IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT | rule->ioprio))
|
||||||
|
print_error ("%d (%s): thread %d: ioprio_set: %s",
|
||||||
|
pid, program_name, tid, strerror (errno));
|
||||||
|
}
|
||||||
|
if (errno)
|
||||||
|
{
|
||||||
|
print_error ("%d (%s): readdir: %s",
|
||||||
|
pid, program_name, strerror (errno));
|
||||||
|
}
|
||||||
|
return not_previously_visited == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_exec_name (struct app_context *ctx, int pid, const char *program_name)
|
||||||
|
{
|
||||||
|
// TODO: we might want to at least provide more criteria to match on,
|
||||||
|
// so as to not blindly trust everything, despite these priorities being
|
||||||
|
// relatively harmless if you overlook possible "denial of service"
|
||||||
|
struct rule *rule = find_rule (ctx, program_name);
|
||||||
|
const char *slash = strrchr (program_name, '/');
|
||||||
|
if (!rule && (!slash || !(rule = find_rule (ctx, slash + 1))))
|
||||||
|
return;
|
||||||
|
|
||||||
|
print_debug ("%d (%s) matched", pid, program_name);
|
||||||
|
if (RULE_UNSET != rule->oom_score_adj)
|
||||||
|
adj_oom_score (pid, program_name, rule->oom_score_adj);
|
||||||
|
|
||||||
|
// Priority APIs are strictly per-thread (i.e. Linux "task"), so we must
|
||||||
|
// iterate through all tasks within a thread group
|
||||||
|
char *path = xstrdup_printf ("/proc/%d/task", pid);
|
||||||
|
DIR *dir = opendir (path);
|
||||||
|
free (path);
|
||||||
|
if (!dir)
|
||||||
|
{
|
||||||
|
print_error ("%d (%s): opendir: %s",
|
||||||
|
pid, program_name, strerror (errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str_map set;
|
||||||
|
str_map_init (&set);
|
||||||
|
|
||||||
|
// This has an inherent race condition, but let's give it a try
|
||||||
|
for (size_t retries = 3; retries--; )
|
||||||
|
if (reprioritize (pid, program_name, dir, rule, &set))
|
||||||
|
break;
|
||||||
|
|
||||||
|
str_map_free (&set);
|
||||||
|
closedir (dir);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_exec (struct app_context *ctx, int pid)
|
on_exec (struct app_context *ctx, int pid)
|
||||||
{
|
{
|
||||||
|
// This is inherently racy but there seems to be no better way to do it
|
||||||
char *path = xstrdup_printf ("/proc/%d/cmdline", pid);
|
char *path = xstrdup_printf ("/proc/%d/cmdline", pid);
|
||||||
struct str cmdline;
|
struct str cmdline;
|
||||||
str_init (&cmdline);
|
str_init (&cmdline);
|
||||||
|
@ -240,7 +388,8 @@ parse_program_arguments (int argc, char **argv)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct opt_handler oh;
|
struct opt_handler oh;
|
||||||
opt_handler_init (&oh, argc, argv, opts, "CONFIG", "Fan controller.");
|
opt_handler_init (&oh, argc, argv, opts, "CONFIG",
|
||||||
|
"Process reprioritizing daemon.");
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = opt_handler_get (&oh)) != -1)
|
while ((c = opt_handler_get (&oh)) != -1)
|
||||||
|
@ -334,7 +483,7 @@ main (int argc, char *argv[])
|
||||||
signal_event.user_data = &ctx;
|
signal_event.user_data = &ctx;
|
||||||
poller_fd_set (&signal_event, POLLIN);
|
poller_fd_set (&signal_event, POLLIN);
|
||||||
|
|
||||||
// TODO: load configuration so that we know what to do with the events
|
load_configuration (&ctx, config_path);
|
||||||
|
|
||||||
ctx.proc_fd = socket (PF_NETLINK,
|
ctx.proc_fd = socket (PF_NETLINK,
|
||||||
SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR);
|
SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR);
|
||||||
|
@ -379,5 +528,8 @@ main (int argc, char *argv[])
|
||||||
|
|
||||||
poller_free (&ctx.poller);
|
poller_free (&ctx.poller);
|
||||||
xclose (ctx.proc_fd);
|
xclose (ctx.proc_fd);
|
||||||
|
for (size_t i = 0; i < ctx.rules_len; i++)
|
||||||
|
free (ctx.rules[i].program_name);
|
||||||
|
free (ctx.rules);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue