taskstats: add a option to send all tasks data to user

Adds a new taskstats command to share task memory statistics
to userspace. The command works in two modes. It can share
information per pid, and also send the statistics for all
tasks within a given oom_score_adj range.

Change-Id: Iae742ea4f96754022bc634d285c5ce140b32749d
Signed-off-by: Vinayak Menon <vinmenon@codeaurora.org>
[guptap@codeaurora.org: enforce policy later using pre_doit]
Signed-off-by: Prakash Gupta <guptap@codeaurora.org>
This commit is contained in:
Vinayak Menon 2019-02-12 17:03:57 +05:30 • committed by Prakash Gupta
commit acffd6f45a
2 changed files with 204 additions and 1 deletions

View file

@ -35,6 +35,7 @@
#define TASKSTATS_VERSION 9
#define TASKSTATS2_VERSION 1
#define TS_COMM_LEN 32 /* should be >= TASK_COMM_LEN
* in linux/sched.h */
@ -170,6 +171,19 @@ struct taskstats {
__u64 thrashing_delay_total;
};
struct taskstats2 {
__u16 version;
__s16 oom_score;
__u32 pid;
__u64 anon_rss; /* KB */
__u64 file_rss; /* KB */
__u64 swap_rss; /* KB */
__u64 shmem_rss; /* KB */
#ifdef CONFIG_MM_STAT_UNRECLAIMABLE_PAGES
__u64 unreclaimable; /* KB */
#endif
/* version 1 ends here */
};
/*
* Commands sent from userspace
@ -181,6 +195,7 @@ enum {
TASKSTATS_CMD_UNSPEC = 0, /* Reserved */
TASKSTATS_CMD_GET, /* user->kernel request/get-response */
TASKSTATS_CMD_NEW, /* kernel->user event */
TASKSTATS2_CMD_GET, /* user->kernel request/get-response */
__TASKSTATS_CMD_MAX,
};
@ -194,6 +209,7 @@ enum {
TASKSTATS_TYPE_AGGR_PID, /* contains pid + stats */
TASKSTATS_TYPE_AGGR_TGID, /* contains tgid + stats */
TASKSTATS_TYPE_NULL, /* contains nothing */
TASKSTATS_TYPE_FOREACH, /* contains stats */
__TASKSTATS_TYPE_MAX,
};
@ -205,6 +221,7 @@ enum {
TASKSTATS_CMD_ATTR_TGID,
TASKSTATS_CMD_ATTR_REGISTER_CPUMASK,
TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK,
TASKSTATS_CMD_ATTR_FOREACH,
__TASKSTATS_CMD_ATTR_MAX,
};

View file

@ -24,6 +24,7 @@
#include <net/genetlink.h>
#include <linux/atomic.h>
#include <linux/sched/cputime.h>
#include <linux/oom.h>
/*
* Maximum length of a cpumask that can be specified in
@ -41,7 +42,8 @@ static const struct nla_policy taskstats_cmd_get_policy[TASKSTATS_CMD_ATTR_MAX+1
[TASKSTATS_CMD_ATTR_PID] = { .type = NLA_U32 },
[TASKSTATS_CMD_ATTR_TGID] = { .type = NLA_U32 },
[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK] = { .type = NLA_STRING },
[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK] = { .type = NLA_STRING },};
[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK] = { .type = NLA_STRING },
[TASKSTATS_CMD_ATTR_FOREACH] = { .type = NLA_U32 },};
/*
* We have to use TASKSTATS_CMD_ATTR_MAX here, it is the maxattr in the family.
@ -68,6 +70,11 @@ struct listener_list {
};
static DEFINE_PER_CPU(struct listener_list, listener_array);
struct tgid_iter {
unsigned int tgid;
struct task_struct *task;
};
enum actions {
REGISTER,
DEREGISTER,
@ -625,6 +632,68 @@ static size_t taskstats_packet_size(void)
return size;
}
static int taskstats2_cmd_attr_pid(struct genl_info *info)
{
struct taskstats2 *stats;
struct sk_buff *rep_skb;
struct nlattr *ret;
struct task_struct *tsk;
struct task_struct *p;
size_t size;
u32 pid;
int rc;
size = nla_total_size_64bit(sizeof(struct taskstats2));
rc = prepare_reply(info, TASKSTATS_CMD_NEW, &rep_skb, size);
if (rc < 0)
return rc;
rc = -EINVAL;
pid = nla_get_u32(info->attrs[TASKSTATS_CMD_ATTR_PID]);
ret = nla_reserve_64bit(rep_skb, TASKSTATS_TYPE_STATS,
sizeof(struct taskstats2), TASKSTATS_TYPE_NULL);
if (!ret)
goto err;
stats = nla_data(ret);
rcu_read_lock();
tsk = find_task_by_vpid(pid);
if (tsk)
get_task_struct(tsk);
rcu_read_unlock();
if (!tsk) {
rc = -ESRCH;
goto err;
}
memset(stats, 0, sizeof(*stats));
stats->version = TASKSTATS2_VERSION;
stats->pid = task_pid_nr_ns(tsk, task_active_pid_ns(current));
p = find_lock_task_mm(tsk);
if (p) {
__acquire(p->alloc_lock); /* Fake it out ;) */
#define K(x) ((x) << (PAGE_SHIFT - 10))
stats->anon_rss = K(get_mm_counter(p->mm, MM_ANONPAGES));
stats->file_rss = K(get_mm_counter(p->mm, MM_FILEPAGES));
stats->shmem_rss = K(get_mm_counter(p->mm, MM_SHMEMPAGES));
stats->swap_rss = K(get_mm_counter(p->mm, MM_SWAPENTS));
#ifdef CONFIG_MM_STAT_UNRECLAIMABLE_PAGES
stats->unreclaimable =
K(get_mm_counter(p->mm, MM_UNRECLAIMABLE));
#endif
#undef K
task_unlock(p);
}
put_task_struct(tsk);
return send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
return rc;
}
static int cmd_attr_pid(struct genl_info *info)
{
struct taskstats *stats;
@ -683,6 +752,115 @@ err:
return rc;
}
static struct tgid_iter next_tgid(struct pid_namespace *ns,
struct tgid_iter iter)
{
struct pid *pid;
if (iter.task)
put_task_struct(iter.task);
rcu_read_lock();
retry:
iter.task = NULL;
pid = find_ge_pid(iter.tgid, ns);
if (pid) {
iter.tgid = pid_nr_ns(pid, ns);
iter.task = pid_task(pid, PIDTYPE_PID);
if (!iter.task || !has_group_leader_pid(iter.task)) {
iter.tgid += 1;
goto retry;
}
get_task_struct(iter.task);
}
rcu_read_unlock();
return iter;
}
static int taskstats2_foreach(struct sk_buff *skb, struct netlink_callback *cb)
{
struct pid_namespace *ns = task_active_pid_ns(current);
struct tgid_iter iter;
void *reply;
struct nlattr *attr;
struct nlattr *nla;
struct taskstats2 *stats;
struct task_struct *p;
short oom_score;
short oom_score_min;
short oom_score_max;
u32 buf;
nla = nla_find(nlmsg_attrdata(cb->nlh, GENL_HDRLEN),
nlmsg_attrlen(cb->nlh, GENL_HDRLEN),
TASKSTATS_TYPE_FOREACH);
buf = nla_get_u32(nla);
oom_score_min = (short) (buf & 0xFFFF);
oom_score_max = (short) ((buf >> 16) & 0xFFFF);
iter.tgid = cb->args[0];
iter.task = NULL;
for (iter = next_tgid(ns, iter); iter.task;
iter.tgid += 1, iter = next_tgid(ns, iter)) {
if (iter.task->flags & PF_KTHREAD)
continue;
oom_score = iter.task->signal->oom_score_adj;
if ((oom_score < oom_score_min)
|| (oom_score > oom_score_max))
continue;
reply = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, &family, 0, TASKSTATS2_CMD_GET);
if (reply == NULL) {
put_task_struct(iter.task);
break;
}
attr = nla_reserve(skb, TASKSTATS_TYPE_FOREACH,
sizeof(struct taskstats2));
if (!attr) {
put_task_struct(iter.task);
genlmsg_cancel(skb, reply);
break;
}
stats = nla_data(attr);
memset(stats, 0, sizeof(struct taskstats2));
stats->version = TASKSTATS2_VERSION;
rcu_read_lock();
stats->pid = task_pid_nr_ns(iter.task,
task_active_pid_ns(current));
stats->oom_score = iter.task->signal->oom_score_adj;
rcu_read_unlock();
p = find_lock_task_mm(iter.task);
if (p) {
#define K(x) ((x) << (PAGE_SHIFT - 10))
__acquire(p->alloc_lock); /* Fake it out ;) */
stats->anon_rss =
K(get_mm_counter(p->mm, MM_ANONPAGES));
stats->file_rss =
K(get_mm_counter(p->mm, MM_FILEPAGES));
stats->shmem_rss =
K(get_mm_counter(p->mm, MM_SHMEMPAGES));
stats->swap_rss =
K(get_mm_counter(p->mm, MM_SWAPENTS));
task_unlock(p);
#undef K
}
genlmsg_end(skb, reply);
}
cb->args[0] = iter.tgid;
return skb->len;
}
static int taskstats2_user_cmd(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TASKSTATS_CMD_ATTR_PID])
return taskstats2_cmd_attr_pid(info);
else
return -EINVAL;
}
static int taskstats_user_cmd(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK])
@ -798,6 +976,13 @@ static const struct genl_ops taskstats_ops[] = {
/* policy enforced later */
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_HASPOL,
},
{
.cmd = TASKSTATS2_CMD_GET,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = taskstats2_user_cmd,
.dumpit = taskstats2_foreach,
/* policy enforced later */
},
{
.cmd = CGROUPSTATS_CMD_GET,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
@ -820,6 +1005,7 @@ static int taskstats_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
switch (ops->cmd) {
case TASKSTATS_CMD_GET:
case TASKSTATS2_CMD_GET:
policy = taskstats_cmd_get_policy;
break;
case CGROUPSTATS_CMD_GET: