cgroup, sched: cgroup2 basic resource accounting
Subsystems (3)
Tejun Heo
maintainerPATCH 1/1 cgroup, sched: cgroup2 basic resource accounting
Hello,
This patchset implement cgroup2 basic resource accounting and track CPU usages on all cgroups by default.
In cgroup1, collecting non-trivial statistics by default wasn't viable because there can be any number of hierarchies. While cgroup2's single hierarchy removes that part of the problem, propagating up the hierarchy on each accounting event is still problematic.
This patchset implements cgroup2 basic resource accounting mechanism which keeps all the counters per-cpu and propagates lazily to allow the accounting side to only perform single per-cpu accounting in most cases and the reader side's complexity to be O(nr updated descendants) instead of O(nr total descendants).
This will be used by cgroup2 CPU interface later. The patchset contains the following three patches.
0001-sched-cputime-Expose-cputime_adjust.patch 0002-cpuacct-Introduce-cgroup_account_cputime-_field.patch 0003-cgroup-Implement-cgroup2-basic-CPU-usage-accounting.patch
The patchset is on top of cgroup/for-4.14 and available in the following git branch.
git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git review-cgroup2-basic-acct
diffstat follows. Thanks.
Documentation/cgroup-v2.txt | 9 + include/linux/cgroup-defs.h | 47 +++++ include/linux/cgroup.h | 60 +++++++ include/linux/sched/cputime.h | 3 kernel/cgroup/Makefile | 2 kernel/cgroup/cgroup-internal.h | 8 kernel/cgroup/cgroup.c | 24 ++ kernel/cgroup/stat.c | 333 ++++++++++++++++++++++++++++++++++++++++ kernel/sched/cpuacct.h | 17 -- kernel/sched/cputime.c | 7 kernel/sched/deadline.c | 2 kernel/sched/fair.c | 2 kernel/sched/rt.c | 2 kernel/sched/sched.h | 1 kernel/sched/stop_task.c | 2 15 files changed, 489 insertions(+), 30 deletions(-)
--
tejun
Tejun Heo
maintainerPATCH 1/3 sched/cputime: Expose cputime_adjust()
Will be used by basic cgroup resource stat reporting later.
--- include/linux/sched/cputime.h | 3 ++- kernel/sched/cputime.c | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-)
include/linux/sched/cputime.h +2 -1
@@ -53,7 +53,8 @@ static inline void task_cputime_scaled(struct task_struct *t,extern void task_cputime_adjusted (struct task_struct *p,u64 *ut,u64 *st);extern void thread_group_cputime_adjusted (struct task_struct *p,u64 *ut,u64 *st); - +extern void cputime_adjust (struct task_cputime *curr,struct prev_cputime *prev, +u64 *ut,u64 *st);/* * Thread group CPU time accounting.
kernel/sched/cputime.c +2 -3
@@ -585,9 +585,8 @@ static u64 scale_stime(u64 stime, u64 rtime, u64 total)* * Assuming that rtime_i+1 >= rtime_i. */ -static void cputime_adjust (struct task_cputime *curr, -struct prev_cputime *prev, -u64 *ut,u64 *st) +void cputime_adjust (struct task_cputime *curr,struct prev_cputime *prev, +u64 *ut,u64 *st) {u64 rtime, stime, utime;unsigned long flags;
Signature · 1 line
2.9.3
Tejun Heo
maintainerPATCH 2/3 cpuacct: Introduce cgroup_account_cputime[_field]()
In reply to Tejun HeoIntroduce cgroup_account_cputime[_field]() which wrap cpuacct_charge() and cgroup_account_field(). This doesn't introduce any functional changes and will be used to add cgroup basic resource accounting.
--- include/linux/cgroup.h | 38 ++++++++++++++++++++++++++++++++++++++ kernel/sched/cpuacct.h | 17 ----------------- kernel/sched/cputime.c | 2 +- kernel/sched/deadline.c | 2 +- kernel/sched/fair.c | 2 +- kernel/sched/rt.c | 2 +- kernel/sched/sched.h | 1 - kernel/sched/stop_task.c | 2 +- 8 files changed, 43 insertions(+), 23 deletions(-) delete mode 100644 kernel/sched/cpuacct.h
include/linux/cgroup.h +38 -0
@@ -22,6 +22,7 @@#include <linux/nsproxy.h> #include <linux/user_namespace.h> #include <linux/refcount.h> +#include <linux/kernel_stat.h> #include <linux/cgroup-defs.h>
@@ -675,6 +676,43 @@ static inline bool task_under_cgroup_hierarchy(struct task_struct *task,#endif /* !CONFIG_CGROUPS */ /* +* Basic resource stats. +*/ +#ifdef CONFIG_CGROUPS + +#ifdef CONFIG_CGROUP_CPUACCT +void cpuacct_charge (struct task_struct *tsk,u64 cputime); +void cpuacct_account_field (struct task_struct *tsk,int index,u64 val); +#else +static inline void cpuacct_charge (struct task_struct *tsk,u64 cputime) {} +static inline void cpuacct_account_field (struct task_struct *tsk,int index, +u64 val) {} +#endif + +static inline void cgroup_account_cputime (struct task_struct *task, +u64 delta_exec) +{ +cpuacct_charge (task, delta_exec); +} + +static inline void cgroup_account_cputime_field (struct task_struct *task, +enum cpu_usage_stat index, +u64 delta_exec) +{ +cpuacct_account_field (task, index, delta_exec); +} + +#else /* CONFIG_CGROUPS */ + +static inline void cgroup_account_cputime (struct task_struct *task, +u64 delta_exec) {} +static inline void cgroup_account_cputime_field (struct task_struct *task, +enum cpu_usage_stat index, +u64 delta_exec) {} + +#endif /* CONFIG_CGROUPS */ + +/* * sock->sk_cgrp_data handling. For more info, see sock_cgroup_data * definition in cgroup-defs.h. */
kernel/sched/cpuacct.h deleted +0 -17
@@ -1,17 +0,0 @@ -#ifdef CONFIG_CGROUP_CPUACCT - -extern void cpuacct_charge (struct task_struct *tsk,u64 cputime); -extern void cpuacct_account_field (struct task_struct *tsk,int index,u64 val); - -#else - -static inline void cpuacct_charge (struct task_struct *tsk,u64 cputime) -{ -} - -static inline void -cpuacct_account_field (struct task_struct *tsk,int index,u64 val) -{ -} - -#endif
kernel/sched/cputime.c +1 -1
@@ -109,7 +109,7 @@ static inline void task_group_account_field(struct task_struct *p, int index,*/ __this_cpu_add (kernel_cpustat.cpustat[index], tmp); -cpuacct_account_field (p, index, tmp); +cgroup_account_cputime_field (p, index, tmp); }/*
kernel/sched/deadline.c +1 -1
@@ -1145,7 +1145,7 @@ static void update_curr_dl(struct rq *rq)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec);sched_rt_avg_update (rq, delta_exec);
kernel/sched/fair.c +1 -1
@@ -864,7 +864,7 @@ static void update_curr(struct cfs_rq *cfs_rq)struct task_struct *curtask =task_of (curr);trace_sched_stat_runtime (curtask, delta_exec, curr->vruntime); -cpuacct_charge (curtask, delta_exec); +cgroup_account_cputime (curtask, delta_exec);account_group_exec_runtime (curtask, delta_exec); }
kernel/sched/rt.c +1 -1
@@ -979,7 +979,7 @@ static void update_curr_rt(struct rq *rq)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec);sched_rt_avg_update (rq, delta_exec);
kernel/sched/sched.h +0 -1
@@ -36,7 +36,6 @@#include "cpupri.h" #include "cpudeadline.h" -#include "cpuacct.h" #ifdef CONFIG_SCHED_DEBUG # define SCHED_WARN_ON (x) WARN_ONCE(x, #x)
kernel/sched/stop_task.c +1 -1
@@ -71,7 +71,7 @@ static void put_prev_task_stop(struct rq *rq, struct task_struct *prev)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec); }static void task_tick_stop (struct rq *rq,struct task_struct *curr,int queued)
Signature · 1 line
2.9.3
Tejun Heo
maintainerPATCH v2 2/3 cpuacct: Introduce cgroup_account_cputime[_field]()
From 1f88d33de13b02e2d6235c0d9451cd10c6e8210f Mon Sep 17 00:00:00 2001
From: Tejun Heo <[email protected]>
Date: Fri, 11 Aug 2017 10:24:12 -0700
Introduce cgroup_account_cputime[_field]() which wrap cpuacct_charge() and cgroup_account_field(). This doesn't introduce any functional changes and will be used to add cgroup basic resource accounting.
---
Fix build failure from missing include.
Thanks.
include/linux/cgroup.h | 38 ++++++++++++++++++++++++++++++++++++++ kernel/sched/cpuacct.h | 17 ----------------- kernel/sched/cputime.c | 2 +- kernel/sched/deadline.c | 2 +- kernel/sched/fair.c | 2 +- kernel/sched/rt.c | 2 +- kernel/sched/sched.h | 2 +- kernel/sched/stop_task.c | 2 +- 8 files changed, 44 insertions(+), 23 deletions(-) delete mode 100644 kernel/sched/cpuacct.h
include/linux/cgroup.h +38 -0
@@ -22,6 +22,7 @@#include <linux/nsproxy.h> #include <linux/user_namespace.h> #include <linux/refcount.h> +#include <linux/kernel_stat.h> #include <linux/cgroup-defs.h>
@@ -675,6 +676,43 @@ static inline bool task_under_cgroup_hierarchy(struct task_struct *task,#endif /* !CONFIG_CGROUPS */ /* +* Basic resource stats. +*/ +#ifdef CONFIG_CGROUPS + +#ifdef CONFIG_CGROUP_CPUACCT +void cpuacct_charge (struct task_struct *tsk,u64 cputime); +void cpuacct_account_field (struct task_struct *tsk,int index,u64 val); +#else +static inline void cpuacct_charge (struct task_struct *tsk,u64 cputime) {} +static inline void cpuacct_account_field (struct task_struct *tsk,int index, +u64 val) {} +#endif + +static inline void cgroup_account_cputime (struct task_struct *task, +u64 delta_exec) +{ +cpuacct_charge (task, delta_exec); +} + +static inline void cgroup_account_cputime_field (struct task_struct *task, +enum cpu_usage_stat index, +u64 delta_exec) +{ +cpuacct_account_field (task, index, delta_exec); +} + +#else /* CONFIG_CGROUPS */ + +static inline void cgroup_account_cputime (struct task_struct *task, +u64 delta_exec) {} +static inline void cgroup_account_cputime_field (struct task_struct *task, +enum cpu_usage_stat index, +u64 delta_exec) {} + +#endif /* CONFIG_CGROUPS */ + +/* * sock->sk_cgrp_data handling. For more info, see sock_cgroup_data * definition in cgroup-defs.h. */
kernel/sched/cpuacct.h deleted +0 -17
@@ -1,17 +0,0 @@ -#ifdef CONFIG_CGROUP_CPUACCT - -extern void cpuacct_charge (struct task_struct *tsk,u64 cputime); -extern void cpuacct_account_field (struct task_struct *tsk,int index,u64 val); - -#else - -static inline void cpuacct_charge (struct task_struct *tsk,u64 cputime) -{ -} - -static inline void -cpuacct_account_field (struct task_struct *tsk,int index,u64 val) -{ -} - -#endif
kernel/sched/cputime.c +1 -1
@@ -109,7 +109,7 @@ static inline void task_group_account_field(struct task_struct *p, int index,*/ __this_cpu_add (kernel_cpustat.cpustat[index], tmp); -cpuacct_account_field (p, index, tmp); +cgroup_account_cputime_field (p, index, tmp); }/*
kernel/sched/deadline.c +1 -1
@@ -1145,7 +1145,7 @@ static void update_curr_dl(struct rq *rq)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec);sched_rt_avg_update (rq, delta_exec);
kernel/sched/fair.c +1 -1
@@ -864,7 +864,7 @@ static void update_curr(struct cfs_rq *cfs_rq)struct task_struct *curtask =task_of (curr);trace_sched_stat_runtime (curtask, delta_exec, curr->vruntime); -cpuacct_charge (curtask, delta_exec); +cgroup_account_cputime (curtask, delta_exec);account_group_exec_runtime (curtask, delta_exec); }
kernel/sched/rt.c +1 -1
@@ -979,7 +979,7 @@ static void update_curr_rt(struct rq *rq)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec);sched_rt_avg_update (rq, delta_exec);
kernel/sched/sched.h +1 -1
@@ -29,6 +29,7 @@#include <linux/irq_work.h> #include <linux/tick.h> #include <linux/slab.h> +#include <linux/cgroup.h> #ifdef CONFIG_PARAVIRT #include <asm/paravirt.h>
@@ -36,7 +37,6 @@#include "cpupri.h" #include "cpudeadline.h" -#include "cpuacct.h" #ifdef CONFIG_SCHED_DEBUG # define SCHED_WARN_ON (x) WARN_ONCE(x, #x)
kernel/sched/stop_task.c +1 -1
@@ -71,7 +71,7 @@ static void put_prev_task_stop(struct rq *rq, struct task_struct *prev)account_group_exec_runtime (curr, delta_exec); curr->se.exec_start =rq_clock_task (rq); -cpuacct_charge (curr, delta_exec); +cgroup_account_cputime (curr, delta_exec); }static void task_tick_stop (struct rq *rq,struct task_struct *curr,int queued)
Signature · 1 line
2.9.3
Tejun Heo
maintainerPATCH 3/3 cgroup: Implement cgroup2 basic CPU usage accounting
In reply to Tejun HeoIn cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combinaton of two factors -
1. enabling cpu controller has significant side effects, and 2. we have to pick one of the hierarchies to account CPU usages on. cpuacct controller is effectively used to designate a hierarchy to track CPU usages on.
cgroup2's unified hierarchy removes the second reason and we can account basic CPU usages by default. While we can use cpuacct for this purpose, both its interface and implementation leave a lot to be desired - it collects and exposes two sources of truth which don't agree with each other and some of the exposed statistics don't make much sense. Also, it propagates all the way up the hierarchy on each accounting event which is unnecessary.
This patch adds basic resource accounting mechanism to cgroup2's unified hierarchy and accounts CPU usages using it.
* All accountings are done per-cpu and don't propagate immediately. It just bumps the per-cgroup per-cpu counters and links to the parent's updated list if not already on it.
* On a read, the per-cpu counters are collected into the global ones and then propagated upwards. Only the per-cpu counters which have changed since the last read are propagated.
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using cputime_adjuts().
This keeps the accounting side hot path O(1) and per-cpu and the read side O(nr_updated_since_last_read).
--- Documentation/cgroup-v2.txt | 9 ++ include/linux/cgroup-defs.h | 47 ++++++ include/linux/cgroup.h | 22 +++ kernel/cgroup/Makefile | 2 +- kernel/cgroup/cgroup-internal.h | 8 + kernel/cgroup/cgroup.c | 24 ++- kernel/cgroup/stat.c | 333 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 kernel/cgroup/stat.c
Documentation/cgroup-v2.txt +9 -0
@@ -886,6 +886,15 @@ All cgroup core files are prefixed with "cgroup." A dying cgroup can consume system resources not exceeding limits, which were active at the moment of cgroup deletion. + cpu.usage_usec + CPU time consumed in the subtree. + + cpu.user_usec + User CPU time consumed in the subtree. + + cpu.system_usec + System CPU time consumed in the subtree. + Controllers ===========
include/linux/cgroup-defs.h +47 -0
65 lines not on this page · raw message
include/linux/cgroup.h +22 -0
@@ -689,17 +689,39 @@ static inline void cpuacct_account_field(struct task_struct *tsk, int index, u64 val) {}#endif +void cgroup_stat_show_cputime (struct seq_file *seq,const char *prefix); + +void __cgroup_account_cputime (struct cgroup *cgrp,u64 delta_exec); +void __cgroup_account_cputime_field (struct cgroup *cgrp, +enum cpu_usage_stat index,u64 delta_exec); +static inline void cgroup_account_cputime (struct task_struct *task,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_charge (task, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime (cgrp, delta_exec); +rcu_read_unlock (); }static inline void cgroup_account_cputime_field (struct task_struct *task,enum cpu_usage_stat index,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_account_field (task, index, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime_field (cgrp, index, delta_exec); +rcu_read_unlock (); }#else /* CONFIG_CGROUPS */
kernel/cgroup/Makefile +1 -1
@@ -1,4 +1,4 @@ -obj-y := cgroup.o namespace.o cgroup-v1.o +obj-y := cgroup.o stat.o namespace.o cgroup-v1.o obj-$(CONFIG_CGROUP_FREEZER) += freezer.o obj-$(CONFIG_CGROUP_PIDS) += pids.o
kernel/cgroup/cgroup-internal.h +8 -0
@@ -197,6 +197,14 @@ int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,int cgroup_task_count (const struct cgroup *cgrp);/* +* stat.c +*/ +void cgroup_stat_flush (struct cgroup *cgrp); +int cgroup_stat_init (struct cgroup *cgrp); +void cgroup_stat_exit (struct cgroup *cgrp); +void cgroup_stat_boot (void ); + +/* * namespace.c */ extern const struct proc_ns_operations cgroupns_operations;
kernel/cgroup/cgroup.c +22 -2
@@ -142,12 +142,14 @@ static struct static_key_true *cgroup_subsys_on_dfl_key[] = { };#undef SUBSYS +static DEFINE_PER_CPU(struct cgroup_cpu_stat, cgrp_dfl_root_cpu_stat); +/* * The default hierarchy, reserved for the subsystems that are otherwise * unattached - it never has more than a single cgroup, and all tasks are * part of that cgroup. */ -struct cgroup_root cgrp_dfl_root; +struct cgroup_root cgrp_dfl_root = { .cgrp.cpu_stat = &cgrp_dfl_root_cpu_stat };EXPORT_SYMBOL_GPL (cgrp_dfl_root);/*
@@ -3296,6 +3298,8 @@ static int cgroup_stat_show(struct seq_file *seq, void *v)seq_printf (seq,"nr_dying_descendants %d\n" , cgroup->nr_dying_descendants); +cgroup_stat_show_cputime (seq,"cpu." ); +return 0 ; }
@@ -4466,6 +4470,8 @@ static void css_free_work_fn(struct work_struct *work)*/ cgroup_put (cgroup_parent (cgrp));kernfs_put (cgrp->kn); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_exit (cgrp);kfree (cgrp); }else {/*
@@ -4510,6 +4516,9 @@ static void css_release_work_fn(struct work_struct *work)/* cgroup release path */ trace_cgroup_release (cgrp); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_flush (cgrp); +for (tcgrp =cgroup_parent (cgrp); tcgrp; tcgrp =cgroup_parent (tcgrp)) tcgrp->nr_dying_descendants--;
@@ -4696,6 +4705,12 @@ static struct cgroup *cgroup_create(struct cgroup *parent)if (ret) goto out_free_cgrp; +if (cgroup_on_dfl (parent)) { + ret =cgroup_stat_init (cgrp); +if (ret) + goto out_cancel_ref; + } +/* * Temporarily set the pointer to NULL, so idr_find() won't return * a half-baked cgroup.
@@ -4703,7 +4718,7 @@ static struct cgroup *cgroup_create(struct cgroup *parent) cgrp->id =cgroup_idr_alloc (&root->cgroup_idr,NULL ,2 ,0 ,GFP_KERNEL );if (cgrp->id <0 ) { ret = -ENOMEM ; - goto out_cancel_ref; + goto out_stat_exit; }init_cgroup_housekeeping (cgrp);
@@ -4752,6 +4767,9 @@ static struct cgroup *cgroup_create(struct cgroup *parent)return cgrp; +out_stat_exit: +if (cgroup_on_dfl (parent)) +cgroup_stat_exit (cgrp); out_cancel_ref:percpu_ref_exit (&cgrp->self.refcnt); out_free_cgrp:
@@ -5146,6 +5164,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup1_base_files)); +cgroup_stat_boot (); +/* * The latency of the synchronize_sched() is too high for cgroups, * avoid it at the cost of forcing all readers into the slow path.
kernel/cgroup/stat.c new file +333 -0
333 lines not on this page · raw message
Signature · 1 line
2.9.3
Re: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Quoted 22 lines · Tejun Heo
In cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combinaton of two factors -
1. enabling cpu controller has significant side effects, and 2. we have to pick one of the hierarchies to account CPU usages on. cpuacct controller is effectively used to designate a hierarchy to track CPU usages on.cgroup2's unified hierarchy removes the second reason and we can account basic CPU usages by default. While we can use cpuacct for this purpose, both its interface and implementation leave a lot to be desired - it collects and exposes two sources of truth which don't agree with each other and some of the exposed statistics don't make much sense. Also, it propagates all the way up the hierarchy on each accounting event which is unnecessary.
This patch adds basic resource accounting mechanism to cgroup2's unified hierarchy and accounts CPU usages using it.
* All accountings are done per-cpu and don't propagate immediately. It just bumps the per-cgroup per-cpu counters and links to the parent's updated list if not already on it.
Quoted 8 lines
* On a read, the per-cpu counters are collected into the global ones and then propagated upwards. Only the per-cpu counters which have changed since the last read are propagated.
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using
cputime_adjuts().
Typo: cputime_adjust()
Quoted 279 lines
This keeps the accounting side hot path O(1) and per-cpu and the read side
O(nr_updated_since_last_read).Signed-off-by Tejun Heo <[email protected]>Cc Ingo Molnar <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Li Zefan <[email protected]>Cc Johannes Weiner <[email protected]>--- Documentation/cgroup-v2.txt | 9 ++ include/linux/cgroup-defs.h | 47 ++++++ include/linux/cgroup.h | 22 +++ kernel/cgroup/Makefile | 2 +- kernel/cgroup/cgroup-internal.h | 8 + kernel/cgroup/cgroup.c | 24 ++- kernel/cgroup/stat.c | 333 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 kernel/cgroup/stat.cDocumentation/cgroup-v2.txt +9
-0@@ -886,6 +886,15 @@ All cgroup core files are prefixed with "cgroup." A dying cgroup can consume system resources not exceeding limits, which were active at the moment of cgroup deletion. + cpu.usage_usec + CPU time consumed in the subtree. + + cpu.user_usec + User CPU time consumed in the subtree. + + cpu.system_usec + System CPU time consumed in the subtree. + Controllers ===========include/linux/cgroup-defs.h +47
-0@@ -16,6 +16,7 @@#include <linux/refcount.h> #include <linux/percpu-refcount.h> #include <linux/percpu-rwsem.h> +#include <linux/u64_stats_sync.h> #include <linux/workqueue.h> #include <linux/bpf-cgroup.h> @@ -249,6 +250,47 @@ struct css_set {struct rcu_head rcu_head; }; +/* +* cgroup basic resource usage statistics. Accounting is done per-cpu in +* cgroup_cpu_stat which is then lazily propagated up the hierarchy on +* reads. The propagation is selective - only the cgroup_cpu_stats which +* have been updated since the last propagation are propagated. +*/ +struct cgroup_cpu_stat { +/* +* ->sync protects all the current counters. These are the only +* fields which get updated in the hot path. +*/ +struct u64_stats_sync sync; +struct task_cputime cputime; + +/* +* Snapshots at the last reading. These are used to calculate the +* deltas to propagate to the global counters. +*/ +struct task_cputime last_cputime; + +/* +* Child cgroups with stat updates on this cpu since the last read +* are linked on the parent's ->updated_children through +* ->updated_next. +* +* In addition to being more compact, singly-linked list pointing +* to the cgroup makes it unnecessary for each per-cpu struct to +* point back to the associated cgroup. +* +* Protected by per-cpu cgroup_cpu_stat_lock. +*/ +struct cgroup *updated_children;/* terminated by self cgroup */ +struct cgroup *updated_next;/* NULL iff not on the list */ +}; + +struct cgroup_stat { +/* per-cpu statistics are collected into the folowing global counters */ +struct task_cputime cputime; +struct prev_cputime prev_cputime; +}; +struct cgroup {/* self css with NULL ->ss, points back to this cgroup */ struct cgroup_subsys_state self;@@ -348,6 +390,11 @@ struct cgroup {*/ struct cgroup *dom_cgrp; +/* cgroup basic resource statistics */ +struct cgroup_cpu_stat __percpu *cpu_stat; +struct cgroup_stat pending_stat;/* pending from children */ +struct cgroup_stat stat; +/* * list of pidlists, up to two for each namespace (one for procs, one * for tasks); created on demand. include/linux/cgroup.h +22
-0@@ -689,17 +689,39 @@ static inline void cpuacct_account_field(struct task_struct *tsk, int index, u64 val) {}#endif +void cgroup_stat_show_cputime (struct seq_file *seq,const char *prefix); + +void __cgroup_account_cputime (struct cgroup *cgrp,u64 delta_exec); +void __cgroup_account_cputime_field (struct cgroup *cgrp, +enum cpu_usage_stat index,u64 delta_exec); +static inline void cgroup_account_cputime (struct task_struct *task,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_charge (task, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime (cgrp, delta_exec); +rcu_read_unlock (); }static inline void cgroup_account_cputime_field (struct task_struct *task,enum cpu_usage_stat index,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_account_field (task, index, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime_field (cgrp, index, delta_exec); +rcu_read_unlock (); }#else /* CONFIG_CGROUPS */ kernel/cgroup/Makefile +1
-1@@ -1,4 +1,4 @@ -obj-y := cgroup.o namespace.o cgroup-v1.o +obj-y := cgroup.o stat.o namespace.o cgroup-v1.o obj-$(CONFIG_CGROUP_FREEZER) += freezer.o obj-$(CONFIG_CGROUP_PIDS) += pids.okernel/cgroup/cgroup-internal.h +8
-0@@ -197,6 +197,14 @@ int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,int cgroup_task_count (const struct cgroup *cgrp);/* +* stat.c +*/ +void cgroup_stat_flush (struct cgroup *cgrp); +int cgroup_stat_init (struct cgroup *cgrp); +void cgroup_stat_exit (struct cgroup *cgrp); +void cgroup_stat_boot (void ); + +/* * namespace.c */ extern const struct proc_ns_operations cgroupns_operations;kernel/cgroup/cgroup.c +22
-2@@ -142,12 +142,14 @@ static struct static_key_true *cgroup_subsys_on_dfl_key[] = { };#undef SUBSYS +static DEFINE_PER_CPU(struct cgroup_cpu_stat, cgrp_dfl_root_cpu_stat); +/* * The default hierarchy, reserved for the subsystems that are otherwise * unattached - it never has more than a single cgroup, and all tasks are * part of that cgroup. */ -struct cgroup_root cgrp_dfl_root; +struct cgroup_root cgrp_dfl_root = { .cgrp.cpu_stat = &cgrp_dfl_root_cpu_stat };EXPORT_SYMBOL_GPL (cgrp_dfl_root);/* @@ -3296,6 +3298,8 @@ static int cgroup_stat_show(struct seq_file *seq, void *v)seq_printf (seq,"nr_dying_descendants %d\n" , cgroup->nr_dying_descendants); +cgroup_stat_show_cputime (seq,"cpu." ); +return 0 ; }@@ -4466,6 +4470,8 @@ static void css_free_work_fn(struct work_struct *work)*/ cgroup_put (cgroup_parent (cgrp));kernfs_put (cgrp->kn); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_exit (cgrp);kfree (cgrp); }else {/* @@ -4510,6 +4516,9 @@ static void css_release_work_fn(struct work_struct *work)/* cgroup release path */ trace_cgroup_release (cgrp); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_flush (cgrp); +for (tcgrp =cgroup_parent (cgrp); tcgrp; tcgrp =cgroup_parent (tcgrp)) tcgrp->nr_dying_descendants--;@@ -4696,6 +4705,12 @@ static struct cgroup *cgroup_create(struct cgroup *parent)if (ret) goto out_free_cgrp; +if (cgroup_on_dfl (parent)) { + ret =cgroup_stat_init (cgrp); +if (ret) + goto out_cancel_ref; + } +/* * Temporarily set the pointer to NULL, so idr_find() won't return * a half-baked cgroup. @@ -4703,7 +4718,7 @@ static struct cgroup *cgroup_create(struct cgroup *parent) cgrp->id =cgroup_idr_alloc (&root->cgroup_idr,NULL ,2 ,0 ,GFP_KERNEL );if (cgrp->id <0 ) { ret = -ENOMEM ; - goto out_cancel_ref; + goto out_stat_exit; }init_cgroup_housekeeping (cgrp);@@ -4752,6 +4767,9 @@ static struct cgroup *cgroup_create(struct cgroup *parent)return cgrp; +out_stat_exit: +if (cgroup_on_dfl (parent)) +cgroup_stat_exit (cgrp); out_cancel_ref:percpu_ref_exit (&cgrp->self.refcnt); out_free_cgrp:@@ -5146,6 +5164,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup1_base_files)); +cgroup_stat_boot (); +/* * The latency of the synchronize_sched() is too high for cgroups, * avoid it at the cost of forcing all readers into the slow path. kernel/cgroup/stat.c new file +0
-0
kernel/cgroup/stat.c line 6
Quoted 6 lines
kernel/cgroup/stat.c new file +6
-0@@ -0,0 +1,333 @@ +#include "cgroup-internal.h" + +#include <linux/sched/cputime.h> + +static DEFINE_MUTEX (cgroup_stat_mutex); +static DEFINE_PER_CPU(raw_spinlock_t, cgroup_cpu_stat_lock);
If the hierarchy is large enough and the stat data hasn't been read recently, it may take a while to accumulate all the stat data even for one cpu in cgroup_stat_flush_locked(). So I think it will make more sense to use regular spinlock_t instead of raw_spinlock_t.
Quoted 60 lines
kernel/cgroup/stat.c new file +60
-0+ +static struct cgroup_cpu_stat *cgroup_cpu_stat (struct cgroup *cgrp,int cpu) +{ +return per_cpu_ptr (cgrp->cpu_stat, cpu); +} + +/** +* cgroup_cpu_stat_updated - keep track of updated cpu_stat +* @cgrp: target cgroup +* @cpu: cpu on which cpu_stat was updated +* +* @cgrp's cpu_stat on @cpu was updated. Put it on the parent's matching +* cpu_stat->updated_children list. +*/ +static void cgroup_cpu_stat_updated (struct cgroup *cgrp,int cpu) +{ +raw_spinlock_t *cpu_lock =per_cpu_ptr (&cgroup_cpu_stat_lock, cpu); +struct cgroup *parent; +unsigned long flags; + +/* +* Speculative already-on-list test. This may race leading to +* temporary inaccuracies, which is fine. +* +* Because @parent's updated_children is terminated with @parent +* instead of NULL, we can tell whether @cgrp is on the list by +* testing the next pointer for NULL. +*/ +if (cgroup_cpu_stat (cgrp, cpu)->updated_next) +return ; + +raw_spin_lock_irqsave (cpu_lock, flags); + +/* put @cgrp and all ancestors on the corresponding updated lists */ +for (parent =cgroup_parent (cgrp); parent; + cgrp = parent, parent =cgroup_parent (cgrp)) { +struct cgroup_cpu_stat *cstat =cgroup_cpu_stat (cgrp, cpu); +struct cgroup_cpu_stat *pcstat =cgroup_cpu_stat (parent, cpu); + +/* +* Both additions and removals are bottom-up. If a cgroup +* is already in the tree, all ancestors are. +*/ +if (cstat->updated_next) +break ; + + cstat->updated_next = pcstat->updated_children; + pcstat->updated_children = cgrp; + } + +raw_spin_unlock_irqrestore (cpu_lock, flags); +} + +/** +* cgroup_cpu_stat_next_updated - iterate cpu_stat updated tree +* @pos: current position +* @root: root of the tree to traversal +* @cpu: target cpu +* +* Walks the udpated cpu_stat tree on @cpu from @root. %NULL @pos starts
kernel/cgroup/stat.c line 76
Quoted 10 lines
kernel/cgroup/stat.c new file +10
-0+* the traversal and %NULL return indicates the end. During traversal, +* each returned cgroup is unlinked from the tree. Must be called with the +* matching cgroup_cpu_stat_lock held. +* +* The only ordering guarantee is that, for a parent and a child pair +* covered by a given traversal, if a child is visited, its parent is +* guaranteed to be visited afterwards. +*/ +static struct cgroup *cgroup_cpu_stat_next_updated (struct cgroup *pos, +struct cgroup *root,int cpu)
This function is trying to unwind one cgrp from the updated_children and updated_next linkage. It is somewhat like the opposite of cgroup_cpu_stat_updated(). I just feel like its name isn't intuitive enough to convey what it is doing. Maybe use name like cgroup_cpu_stat_unlink_one() to match cgroup_cpu_stat_flush_one().
Cheers,
Longman
Tejun Heo
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Hello, Waiman.
Quoted 6 lines · Waiman Long
Quoted 4 lines
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using
cputime_adjuts().Typo:
cputime_adjust()
Oops, will fix.
Quoted 7 lines
kernel/cgroup/stat.c line 6
Quoted 2 lines
kernel/cgroup/stat.c new file +2
-0+static DEFINE_MUTEX (cgroup_stat_mutex); +static DEFINE_PER_CPU(raw_spinlock_t, cgroup_cpu_stat_lock);If the hierarchy is large enough and the stat data hasn't been read recently, it may take a while to accumulate all the stat data even for one cpu in
cgroup_stat_flush_locked(). So I think it will make more sense to use regularspinlock_tinstead ofraw_spinlock_t.
They need to be raw spinlocks because the accounting side may grab them while scheduling. If the accumulating latency becomes problematic, we can test for need_resched and spin_needbreak and break out as necessary. The iteration logic is built to allow that and an earlier revision actually did that but I wasn't sure whether it's actually necessary and removed it for simplicity.
If the latency ever becomes a problem, resurrecting that isn't difficult at all.
Quoted 8 lines
kernel/cgroup/stat.c line 76
Quoted 2 lines
kernel/cgroup/stat.c new file +2
-0+static struct cgroup *cgroup_cpu_stat_next_updated (struct cgroup *pos, +struct cgroup *root,int cpu)This function is trying to unwind one cgrp from the
updated_childrenandupdated_nextlinkage. It is somewhat like the opposite ofcgroup_cpu_stat_updated(). I just feel like its name isn't intuitive enough to convey what it is doing. Maybe use name likecgroup_cpu_stat_unlink_one()to matchcgroup_cpu_stat_flush_one().
Hmm... the name comes from it being an iterator - most interators are named _next. But, yeah, the name doesn't signifiy that it unlinks as it goes along. I'll rename it to cgroup_cpu_stat_pop_updated().
Thanks.
Signature · 1 line
tejun
Re: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Quoted 23 lines · Tejun Heo
Hello, Waiman.
Quoted 5 lines · Waiman Long
Quoted 4 lines
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using
cputime_adjuts().Typo:
cputime_adjust()Oops, will fix.
Quoted 6 lines
kernel/cgroup/stat.c line 6
Quoted 2 lines
kernel/cgroup/stat.c new file +2
-0+static DEFINE_MUTEX (cgroup_stat_mutex); +static DEFINE_PER_CPU(raw_spinlock_t, cgroup_cpu_stat_lock);If the hierarchy is large enough and the stat data hasn't been read recently, it may take a while to accumulate all the stat data even for one cpu in
cgroup_stat_flush_locked(). So I think it will make more sense to use regularspinlock_tinstead ofraw_spinlock_t.They need to be raw spinlocks because the accounting side may grab them while scheduling. If the accumulating latency becomes problematic, we can test for
need_reschedandspin_needbreakand break out as necessary. The iteration logic is built to allow that and an earlier revision actually did that but I wasn't sure whether it's actually necessary and removed it for simplicity.
Quoted 2 lines
If the latency ever becomes a problem, resurrecting that isn't difficult at all.
Right, I forgot they will be used by the scheduler.
I think it is prudent to limit the latency, but it can be another patch when the need arises.
Quoted 10 lines
Quoted 7 lines
kernel/cgroup/stat.c line 76
Quoted 2 lines
kernel/cgroup/stat.c new file +2
-0+static struct cgroup *cgroup_cpu_stat_next_updated (struct cgroup *pos, +struct cgroup *root,int cpu)This function is trying to unwind one cgrp from the
updated_childrenandupdated_nextlinkage. It is somewhat like the opposite ofcgroup_cpu_stat_updated(). I just feel like its name isn't intuitive enough to convey what it is doing. Maybe use name likecgroup_cpu_stat_unlink_one()to matchcgroup_cpu_stat_flush_one().Hmm... the name comes from it being an iterator - most interators are named
_next. But, yeah, the name doesn't signifiy that it unlinks as it goes along. I'll rename it tocgroup_cpu_stat_pop_updated().
I am fine with that.
Thanks,
Longman
Re: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
In reply to Tejun HeoHi Tejun!
Quoted 2 lines · Tejun Heo
In cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combinaton of two factors -
s/combinaton/combination
kernel/cgroup/cgroup.c static void css_free_work_fn(struct work_struct *work) · line 4474
Quoted 6 lines
kernel/cgroup/cgroup.c +2
-0@@ -4466,6 +4470,8 @@ static void css_free_work_fn(struct work_struct *work)*/ cgroup_put (cgroup_parent (cgrp));kernfs_put (cgrp->kn); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_exit (cgrp);
It looks like this "if (cgroup_on_dfl(cgrp))" works here and further similar to "#ifdef CGROUP_V2". I wonder, if it's better to move this check into the calling function: cgroup_stat_exit() in this case.
Review static void css_free_work_fn(struct work_struct *work) · line 4476
Quoted 2 lines
(quoted diff) +2
-0+void cgroup_stat_show_cputime(struct seq_file *seq, const char *prefix) +{
What are any other possible prefix values except "cpu."?
kernel/cgroup/stat.c line 332
Quoted 8 lines
kernel/cgroup/stat.c new file +8
-0+void __init cgroup_stat_boot (void ) +{ +int cpu; + +for_each_possible_cpu (cpu) +raw_spin_lock_init (per_cpu_ptr (&cgroup_cpu_stat_lock, cpu)); + +WARN_ON (cgroup_stat_init (&cgrp_dfl_root.cgrp));
I'm not sure WARN_ON() is enough here: if cgroup_stat_init() returned -ENOMEM, the following OOPS is not avoidable, as you don't check cpu_stat pointer. But it's very unlikely, of course.
Overall looks very good!
Thanks!
Tejun Heo
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Hello,
Quoted 5 lines · Roman Gushchin
Quoted 2 lines · Tejun Heo
In cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combinaton of two factors -
s/combinaton/combination
Fixed.
Quoted 10 lines
kernel/cgroup/cgroup.c static void css_free_work_fn(struct work_struct *work) · line 4474
Quoted 6 lines
kernel/cgroup/cgroup.c +2
-0@@ -4466,6 +4470,8 @@ static void css_free_work_fn(struct work_struct *work)*/ cgroup_put (cgroup_parent (cgrp));kernfs_put (cgrp->kn); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_exit (cgrp);It looks like this "if (
cgroup_on_dfl(cgrp))" works here and further similar to "#ifdefCGROUP_V2". I wonder, if it's better to move this check into the calling function:cgroup_stat_exit()in this case.
I have a slight preference to keeping these topology-aware tests on the core / management part of code because that makes it obvious that these stats aren't available for all cgroups. Also, during cgroup creation, because @cgrp isn't linked to its parent yet, we'd have to pass @parent to cgroup_stat_init/exit() too.
Quoted 4 lines
Review static void css_free_work_fn(struct work_struct *work) · line 4476
Quoted 2 lines
(quoted diff) +2
-0+void cgroup_stat_show_cputime(struct seq_file *seq, const char *prefix) +{What are any other possible prefix values except "cpu."?
Empty string when the stats are exposed through cpu.stat.
Quoted 4 lines
Quoted 4 lines
kernel/cgroup/stat.c new file +4
-0+void __init cgroup_stat_boot (void ) +{ +int cpu; +
Quoted 8 lines
kernel/cgroup/stat.c line 332
Quoted 4 lines
kernel/cgroup/stat.c new file +4
-0+for_each_possible_cpu (cpu) +raw_spin_lock_init (per_cpu_ptr (&cgroup_cpu_stat_lock, cpu)); + +WARN_ON (cgroup_stat_init (&cgrp_dfl_root.cgrp));I'm not sure
WARN_ON()is enough here: ifcgroup_stat_init()returned-ENOMEM, the following OOPS is not avoidable, as you don't checkcpu_statpointer. But it's very unlikely, of course.
Sure, will switch to BUG_ON().
Thanks.
Signature · 1 line
tejun
Peter Zijlstra
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
In reply to Tejun HeoSo I mostly like. On accounting it only adds to the immediate cgroup (if it has a parent, aka !root).
On update it does a DFS of all sub-groups and propagates the deltas up to the requested group.
Quoted 62 lines · Tejun Heo
kernel/cgroup/stat.c new file +61
-0+/** +* cgroup_cpu_stat_updated - keep track of updated cpu_stat +* @cgrp: target cgroup +* @cpu: cpu on which cpu_stat was updated +* +* @cgrp's cpu_stat on @cpu was updated. Put it on the parent's matching +* cpu_stat->updated_children list. +*/ +static void cgroup_cpu_stat_updated (struct cgroup *cgrp,int cpu) +{ +raw_spinlock_t *cpu_lock =per_cpu_ptr (&cgroup_cpu_stat_lock, cpu); +struct cgroup *parent; +unsigned long flags; + +/* +* Speculative already-on-list test. This may race leading to +* temporary inaccuracies, which is fine. +* +* Because @parent's updated_children is terminated with @parent +* instead of NULL, we can tell whether @cgrp is on the list by +* testing the next pointer for NULL. +*/ +if (cgroup_cpu_stat (cgrp, cpu)->updated_next) +return ; + +raw_spin_lock_irqsave (cpu_lock, flags); + +/* put @cgrp and all ancestors on the corresponding updated lists */ +for (parent =cgroup_parent (cgrp); parent; + cgrp = parent, parent =cgroup_parent (cgrp)) { +struct cgroup_cpu_stat *cstat =cgroup_cpu_stat (cgrp, cpu); +struct cgroup_cpu_stat *pcstat =cgroup_cpu_stat (parent, cpu); + +/* +* Both additions and removals are bottom-up. If a cgroup +* is already in the tree, all ancestors are. +*/ +if (cstat->updated_next) +break ; + + cstat->updated_next = pcstat->updated_children; + pcstat->updated_children = cgrp; + } + +raw_spin_unlock_irqrestore (cpu_lock, flags); +} +static struct cgroup_cpu_stat *cgroup_cpu_stat_account_begin (struct cgroup *cgrp) +{ +struct cgroup_cpu_stat *cstat; + + cstat =get_cpu_ptr (cgrp->cpu_stat); +u64_stats_update_begin (&cstat->sync); +return cstat; +} + +static void cgroup_cpu_stat_account_end (struct cgroup *cgrp, +struct cgroup_cpu_stat *cstat) +{ +u64_stats_update_end (&cstat->sync); +cgroup_cpu_stat_updated (cgrp,smp_processor_id ()); +put_cpu_ptr (cstat);
kernel/cgroup/stat.c line 170
Quoted 10 lines
kernel/cgroup/stat.c new file +10
-0+} + +void __cgroup_account_cputime (struct cgroup *cgrp,u64 delta_exec) +{ +struct cgroup_cpu_stat *cstat; + + cstat =cgroup_cpu_stat_account_begin (cgrp); + cstat->cputime.sum_exec_runtime += delta_exec; +cgroup_cpu_stat_account_end (cgrp, cstat); +}
What I don't get is why you need cgroup_cpu_stat_updated(). That is, I see you use it to keep the keep the DFS 'stack' up-to-date, but what I don't see is why you'd need that.
Have a look at walk_tg_tree_from(), I think we can do something like that on struct cgroup_subsys_state, it has that children list and the parent pointer.
And yes, walk_tg_tree_from() is tricky, it always takes a fair while to remember how it works.
Tejun Heo
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Hello, Peter.
Quoted 5 lines · Peter Zijlstra
So I mostly like. On accounting it only adds to the immediate cgroup (if it has a parent, aka !root).
On update it does a DFS of all sub-groups and propagates the deltas up to the requested group.
...
Quoted 3 lines
What I don't get is why you need
cgroup_cpu_stat_updated(). That is, I see you use it to keep the keep the DFS 'stack' up-to-date, but what I don't see is why you'd need that.
That is to make reading stats O(number of descendants which have been active since last read) instad of O(number of all descendants) as there can be a lot of not-too-active cgroups in a system. Stat reading can be frequent, so the combination can get really bad. By keeping the updated list separate, increasing read frequency decreases the cost of each read.
Also, please note that a system may end up with a lot of cgroups without the user intending to. memcg drains removed cgroups lazily and the number of draining cgroups can reach very high numbers if the system isn't under memory pressure. The plan is to add basic stats for other resources too and keeping it scalable w.r.t. idle cgroups allows using the same mechanism for all resources.
Quoted 6 lines
Have a look at
walk_tg_tree_from(), I think we can do something like that on structcgroup_subsys_state, it has that children list and the parent pointer.And yes,
walk_tg_tree_from()is tricky, it always takes a fair while to remember how it works.
We can propagate "updated" flag up the tree (we need to, otherwise we can't tell which subtree to descend into) and prune the iteration on subtrees which haven't been updated; however, this can still become very costly depending on the topology as it can't jump over the siblings which haven't been updated.
Thanks.
Signature · 1 line
tejun
Re: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Quoted 19 lines · Tejun Heo
Hello, Peter.
Quoted 5 lines · Peter Zijlstra
So I mostly like. On accounting it only adds to the immediate cgroup (if it has a parent, aka !root).
On update it does a DFS of all sub-groups and propagates the deltas up to the requested group.
...
Quoted 3 lines
What I don't get is why you need
cgroup_cpu_stat_updated(). That is, I see you use it to keep the keep the DFS 'stack' up-to-date, but what I don't see is why you'd need that.That is to make reading stats O(number of descendants which have been active since last read) instad of O(number of all descendants) as there can be a lot of not-too-active cgroups in a system. Stat reading can be frequent, so the combination can get really bad. By keeping the updated list separate, increasing read frequency decreases the cost of each read.
Quoted 6 lines
Also, please note that a system may end up with a lot of cgroups without the user intending to. memcg drains removed cgroups lazily and the number of draining cgroups can reach very high numbers if the system isn't under memory pressure. The plan is to add basic stats for other resources too and keeping it scalable w.r.t. idle cgroups allows using the same mechanism for all resources.
I think it will be good to put the rationale behind this function as a comment in the code.
Cheers,
Longman
Tejun Heo
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Hello, Waiman.
Quoted 9 lines · Waiman Long
Quoted 6 lines
Also, please note that a system may end up with a lot of cgroups without the user intending to. memcg drains removed cgroups lazily and the number of draining cgroups can reach very high numbers if the system isn't under memory pressure. The plan is to add basic stats for other resources too and keeping it scalable w.r.t. idle cgroups allows using the same mechanism for all resources.
I think it will be good to put the rationale behind this function as a comment in the code.
Ah, good point. I'll beef up the overall comment in the header file and point to that from the function comment.
Thanks.
Signature · 1 line
tejun
Peter Zijlstra
maintainerRe: [PATCH 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
In reply to Tejun HeoQuoted 13 lines · Tejun Heo
Hello, Peter.
Quoted 5 lines · Peter Zijlstra
So I mostly like. On accounting it only adds to the immediate cgroup (if it has a parent, aka !root).
On update it does a DFS of all sub-groups and propagates the deltas up to the requested group.
...
Quoted 3 lines
What I don't get is why you need
cgroup_cpu_stat_updated(). That is, I see you use it to keep the keep the DFS 'stack' up-to-date, but what I don't see is why you'd need that.
Quoted 6 lines
That is to make reading stats O(number of descendants which have been active since last read) instad of O(number of all descendants) as there can be a lot of not-too-active cgroups in a system. Stat reading can be frequent, so the combination can get really bad. By keeping the updated list separate, increasing read frequency decreases the cost of each read.
Hmm, and when its never read, we only do the O(depth) thing _once_, right?
Tejun Heo
maintainerPATCH v2 3/3 cgroup: Implement cgroup2 basic CPU usage accounting
In reply to Tejun HeoIn cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combination of two factors -
1. enabling cpu controller has significant side effects, and 2. we have to pick one of the hierarchies to account CPU usages on. cpuacct controller is effectively used to designate a hierarchy to track CPU usages on.
cgroup2's unified hierarchy removes the second reason and we can account basic CPU usages by default. While we can use cpuacct for this purpose, both its interface and implementation leave a lot to be desired - it collects and exposes two sources of truth which don't agree with each other and some of the exposed statistics don't make much sense. Also, it propagates all the way up the hierarchy on each accounting event which is unnecessary.
This patch adds basic resource accounting mechanism to cgroup2's unified hierarchy and accounts CPU usages using it.
* All accountings are done per-cpu and don't propagate immediately. It just bumps the per-cgroup per-cpu counters and links to the parent's updated list if not already on it.
* On a read, the per-cpu counters are collected into the global ones and then propagated upwards. Only the per-cpu counters which have changed since the last read are propagated.
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using cputime_adjust().
This keeps the accounting side hot path O(1) and per-cpu and the read side O(nr_updated_since_last_read).
v2: Minor changes and documentation updates as suggested by Waiman and
Roman.
--- Documentation/cgroup-v2.txt | 9 + include/linux/cgroup-defs.h | 57 ++++++ include/linux/cgroup.h | 22 ++ kernel/cgroup/Makefile | 2 kernel/cgroup/cgroup-internal.h | 8 kernel/cgroup/cgroup.c | 24 ++ kernel/cgroup/stat.c | 334 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 453 insertions(+), 3 deletions(-)
Documentation/cgroup-v2.txt +9 -0
@@ -886,6 +886,15 @@ All cgroup core files are prefixed with A dying cgroup can consume system resources not exceeding limits, which were active at the moment of cgroup deletion. + cpu.usage_usec + CPU time consumed in the subtree. + + cpu.user_usec + User CPU time consumed in the subtree. + + cpu.system_usec + System CPU time consumed in the subtree. + Controllers ===========
include/linux/cgroup-defs.h +57 -0
75 lines not on this page · raw message
include/linux/cgroup.h +22 -0
@@ -689,17 +689,39 @@ static inline void cpuacct_account_field u64 val) {}#endif +void cgroup_stat_show_cputime (struct seq_file *seq,const char *prefix); + +void __cgroup_account_cputime (struct cgroup *cgrp,u64 delta_exec); +void __cgroup_account_cputime_field (struct cgroup *cgrp, +enum cpu_usage_stat index,u64 delta_exec); +static inline void cgroup_account_cputime (struct task_struct *task,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_charge (task, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime (cgrp, delta_exec); +rcu_read_unlock (); }static inline void cgroup_account_cputime_field (struct task_struct *task,enum cpu_usage_stat index,u64 delta_exec) { +struct cgroup *cgrp; +cpuacct_account_field (task, index, delta_exec); + +rcu_read_lock (); + cgrp =task_dfl_cgroup (task); +if (cgroup_parent (cgrp)) +__cgroup_account_cputime_field (cgrp, index, delta_exec); +rcu_read_unlock (); }#else /* CONFIG_CGROUPS */
kernel/cgroup/Makefile +1 -1
@@ -1,4 +1,4 @@ -obj-y := cgroup.o namespace.o cgroup-v1.o +obj-y := cgroup.o stat.o namespace.o cgroup-v1.o obj-$(CONFIG_CGROUP_FREEZER) += freezer.o obj-$(CONFIG_CGROUP_PIDS) += pids.o
kernel/cgroup/cgroup-internal.h +8 -0
@@ -197,6 +197,14 @@ int cgroup_show_path(struct seq_file *sfint cgroup_task_count (const struct cgroup *cgrp);/* +* stat.c +*/ +void cgroup_stat_flush (struct cgroup *cgrp); +int cgroup_stat_init (struct cgroup *cgrp); +void cgroup_stat_exit (struct cgroup *cgrp); +void cgroup_stat_boot (void ); + +/* * namespace.c */ extern const struct proc_ns_operations cgroupns_operations;
kernel/cgroup/cgroup.c +22 -2
@@ -142,12 +142,14 @@ static struct static_key_true *cgroup_su };#undef SUBSYS +static DEFINE_PER_CPU(struct cgroup_cpu_stat, cgrp_dfl_root_cpu_stat); +/* * The default hierarchy, reserved for the subsystems that are otherwise * unattached - it never has more than a single cgroup, and all tasks are * part of that cgroup. */ -struct cgroup_root cgrp_dfl_root; +struct cgroup_root cgrp_dfl_root = { .cgrp.cpu_stat = &cgrp_dfl_root_cpu_stat };EXPORT_SYMBOL_GPL (cgrp_dfl_root);/*
@@ -3296,6 +3298,8 @@ static int cgroup_stat_show(struct seq_fseq_printf (seq,"nr_dying_descendants %d\n" , cgroup->nr_dying_descendants); +cgroup_stat_show_cputime (seq,"cpu." ); +return 0 ; }
@@ -4466,6 +4470,8 @@ static void css_free_work_fn(struct work*/ cgroup_put (cgroup_parent (cgrp));kernfs_put (cgrp->kn); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_exit (cgrp);kfree (cgrp); }else {/*
@@ -4510,6 +4516,9 @@ static void css_release_work_fn(struct w/* cgroup release path */ trace_cgroup_release (cgrp); +if (cgroup_on_dfl (cgrp)) +cgroup_stat_flush (cgrp); +for (tcgrp =cgroup_parent (cgrp); tcgrp; tcgrp =cgroup_parent (tcgrp)) tcgrp->nr_dying_descendants--;
@@ -4696,6 +4705,12 @@ static struct cgroup *cgroup_create(struif (ret) goto out_free_cgrp; +if (cgroup_on_dfl (parent)) { + ret =cgroup_stat_init (cgrp); +if (ret) + goto out_cancel_ref; + } +/* * Temporarily set the pointer to NULL, so idr_find() won't return * a half-baked cgroup.
@@ -4703,7 +4718,7 @@ static struct cgroup *cgroup_create(stru cgrp->id =cgroup_idr_alloc (&root->cgroup_idr,NULL ,2 ,0 ,GFP_KERNEL );if (cgrp->id <0 ) { ret = -ENOMEM ; - goto out_cancel_ref; + goto out_stat_exit; }init_cgroup_housekeeping (cgrp);
@@ -4752,6 +4767,9 @@ static struct cgroup *cgroup_create(strureturn cgrp; +out_stat_exit: +if (cgroup_on_dfl (parent)) +cgroup_stat_exit (cgrp); out_cancel_ref:percpu_ref_exit (&cgrp->self.refcnt); out_free_cgrp:
@@ -5146,6 +5164,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup1_base_files)); +cgroup_stat_boot (); +/* * The latency of the synchronize_sched() is too high for cgroups, * avoid it at the cost of forcing all readers into the slow path.
kernel/cgroup/stat.c new file +334 -0
334 lines not on this page · raw message
Re: [PATCH v2 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Quoted 37 lines · Tejun Heo
In cgroup1, while cpuacct isn't actually controlling any resources, it is a separate controller due to combination of two factors -
1. enabling cpu controller has significant side effects, and 2. we have to pick one of the hierarchies to account CPU usages on. cpuacct controller is effectively used to designate a hierarchy to track CPU usages on.cgroup2's unified hierarchy removes the second reason and we can account basic CPU usages by default. While we can use cpuacct for this purpose, both its interface and implementation leave a lot to be desired - it collects and exposes two sources of truth which don't agree with each other and some of the exposed statistics don't make much sense. Also, it propagates all the way up the hierarchy on each accounting event which is unnecessary.
This patch adds basic resource accounting mechanism to cgroup2's unified hierarchy and accounts CPU usages using it.
* All accountings are done per-cpu and don't propagate immediately. It just bumps the per-cgroup per-cpu counters and links to the parent's updated list if not already on it.
* On a read, the per-cpu counters are collected into the global ones and then propagated upwards. Only the per-cpu counters which have changed since the last read are propagated.
* CPU usage stats are collected and shown in "cgroup.stat" with "cpu." prefix. Total usage is collected from scheduling events. User/sys breakdown is sourced from tick sampling and adjusted to the usage using
cputime_adjust().This keeps the accounting side hot path O(1) and per-cpu and the read side
O(nr_updated_since_last_read).v2: Minor changes and documentation updates as suggested by Waiman and
Roman.
Quoted 6 lines
Signed-off-by Tejun Heo <[email protected]>Cc Ingo Molnar <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Li Zefan <[email protected]>Cc Johannes Weiner <[email protected]>Cc Waiman Long <[email protected]>
I just noticed that you have used my obsoleted email address in the cc line. Would you mind changing it to the one that I am currently using?
Thanks,
Longman
Tejun Heo
maintainerRe: [PATCH v2 3/3] cgroup: Implement cgroup2 basic CPU usage accounting
Quoted 4 lines · Waiman Long
Quoted 1 line
Cc Waiman Long <[email protected]>I just noticed that you have used my obsoleted email address in the cc line. Would you mind changing it to the one that I am currently using?
Ah, my apologies. Will update.
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerHello,
Quoted 2 lines · Tejun Heo
This patchset implement cgroup2 basic resource accounting and track CPU usages on all cgroups by default.
Ping. I've renamed as Waiman suggested. If there's no further objection, how should we route these patches and the following cpu cgroup2 interface patch?
Thanks.
Signature · 1 line
tejun
Quoted 9 lines · Tejun Heo
Hello,
Quoted 2 lines · Tejun Heo
This patchset implement cgroup2 basic resource accounting and track CPU usages on all cgroups by default.
Ping. I've renamed as Waiman suggested. If there's no further objection, how should we route these patches and the following cpu cgroup2 interface patch?
Please hold off on applying it before PeterZ gets back from vacation and has had a chance to review it.
Thanks,
Ingo
Tejun Heo
maintainerHello, Ingo.
Quoted 2 lines · Ingo Molnar
Please hold off on applying it before PeterZ gets back from vacation and has had a chance to review it.
Of course. These aren't going anywhere without Peter's explicit acks.
Thanks.
Signature · 1 line
tejun
Quoted 7 lines · Tejun Heo
Hello, Ingo.
Quoted 2 lines · Ingo Molnar
Please hold off on applying it before PeterZ gets back from vacation and has had a chance to review it.
Of course. These aren't going anywhere without Peter's explicit acks.
Thank you!
Ingo
Tejun Heo
maintainerA friendly ping.
Thanks!
Signature · 1 line
tejun
Tejun Heo
maintainerPeter, ping?
Thanks.
Signature · 1 line
tejun
Peter Zijlstra
maintainerQuoted 1 line · Tejun Heo
Peter, ping?
Humm,. So I think I was good, but I was under the impression you'd send a new version better explaining the need/design of that iteration stuff. Could be I lost the plot somehow.
Tejun Heo
maintainerHello, Peter.
Quoted 6 lines · Peter Zijlstra
Quoted 1 line · Tejun Heo
Peter, ping?
Humm,. So I think I was good, but I was under the impression you'd send a new version better explaining the need/design of that iteration stuff. Could be I lost the plot somehow.
The updated version was posted quite a while ago.
http://lkml.kernel.org/r/20170829174325.GS491396-4dN5La/[email protected]
Thanks.
Signature · 1 line
tejun
Quoted 6 lines · Tejun Heo
Hello, Peter.
Quoted 3 lines · Peter Zijlstra
Quoted 1 line · Tejun Heo
Peter, ping?
Quoted 7 lines
Quoted 3 lines
Humm,. So I think I was good, but I was under the impression you'd send a new version better explaining the need/design of that iteration stuff. Could be I lost the plot somehow.
The updated version was posted quite a while ago.
http://lkml.kernel.org/r/20170829174325.GS491396-4dN5La/[email protected]
Oh that's in the same thread, wasn't expecting that. Look ok I suppose.
Tejun Heo
maintainerHello,
Quoted 11 lines · Peter Zijlstra
Quoted 10 lines · Tejun Heo
Hello, Peter.
Quoted 6 lines · Peter Zijlstra
Quoted 1 line · Tejun Heo
Peter, ping?
Humm,. So I think I was good, but I was under the impression you'd send a new version better explaining the need/design of that iteration stuff. Could be I lost the plot somehow.
Quoted 8 lines
Quoted 3 lines
The updated version was posted quite a while ago.
http://lkml.kernel.org/r/20170829174325.GS491396-4dN5La/[email protected]
Oh that's in the same thread, wasn't expecting that. Look ok I suppose.
Applying to cgroup/for-4.15.
Thanks.
Signature · 1 line
tejun
PATCH 1/1 cgroup: statically initialize init_css_set->dfl_cgrp
In reply to Tejun HeoHello,
A follow-up patch to fix sporadic oops during boot which is caused by trying dereference the default cgroup from accounting code before cgroup is initialized.
Applied to cgroup/for-4.15.
Thanks.
------ 8< ------
From 38683148828165ea0b66ace93a9fedc2d3281e27 Mon Sep 17 00:00:00 2001
From: Tejun Heo <[email protected]>
Date: Mon, 25 Sep 2017 13:50:20 -0700
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Like other csets, init_css_set's dfl_cgrp is initialized when the cset gets linked. init_css_set gets linked in cgroup_init(). This has been fine till now but the recently added basic CPU usage accounting may end up accessing dfl_cgrp of init before cgroup_init() leading to the following oops.
SELinux: Initializing.
BUG: unable to handle kernel NULL pointer dereference at 00000000000000b0 IP: account_system_index_time+0x60/0x90 PGD 0 P4D 0 Oops: 0000 [#1] SMP Modules linked in: CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.14.0-rc2-00003-g041cd64 #10 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS +1.9.3-20161025_171302-gandalf 04/01/2014 task: ffffffff81e10480 task.stack: ffffffff81e00000 RIP: 0010:account_system_index_time+0x60/0x90 RSP: 0000:ffff880011e03cb8 EFLAGS: 00010002 RAX: ffffffff81ef8800 RBX: ffffffff81e10480 RCX: 0000000000000003 RDX: 0000000000000000 RSI: 00000000000f4240 RDI: 0000000000000000 RBP: ffff880011e03cc0 R08: 0000000000010000 R09: 0000000000000000 R10: 0000000000000020 R11: 0000003b9aca0000 R12: 000000000001c100 R13: 0000000000000000 R14: ffffffff81e10480 R15: ffffffff81e03cd8 FS: 0000000000000000(0000) GS:ffff880011e00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000000000b0 CR3: 0000000001e09000 CR4: 00000000000006b0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: <IRQ> account_system_time+0x45/0x60 account_process_tick+0x5a/0x140 update_process_times+0x22/0x60 tick_periodic+0x2b/0x90 tick_handle_periodic+0x25/0x70 timer_interrupt+0x15/0x20 __handle_irq_event_percpu+0x7e/0x1b0 handle_irq_event_percpu+0x23/0x60 handle_irq_event+0x42/0x70 handle_level_irq+0x83/0x100 handle_irq+0x6f/0x110 do_IRQ+0x46/0xd0 common_interrupt+0x9d/0x9d
Fix it by statically initializing init_css_set.dfl_cgrp so that init's default cgroup is accessible from the get-go.
--- kernel/cgroup/cgroup.c | 8 ++++++++ 1 file changed, 8 insertions(+)
kernel/cgroup/cgroup.c +8 -0
@@ -649,6 +649,14 @@ struct css_set init_css_set = { .cgrp_links = LIST_HEAD_INIT(init_css_set.cgrp_links), .mg_preload_node = LIST_HEAD_INIT(init_css_set.mg_preload_node), .mg_node = LIST_HEAD_INIT(init_css_set.mg_node), + +/* +* The following field is re-initialized when this cset gets linked +* in cgroup_init(). However, let's initialize the field +* statically too so that the default cgroup can be accessed safely +* early during boot. +*/ + .dfl_cgrp = &cgrp_dfl_root.cgrp, };static int css_set_count =1 ;/* 1 for init_css_set */
Signature · 1 line
2.9.5
Tejun Heo
maintainerPATCH 1/1 sched/cputime: Add dummy cputime_adjust() implementation for CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
In reply to Tejun HeoHello,
Another follow-up fix. I missed a dummy implementation leading to bulid failure on s390. Because the bug and fix are trivial, I directly applied the patch to cgroup/for-4.15. Please let me know if it should be routed differently.
Thanks.
------ 8< ------
From 8157a7faf94135386bf04b1cf94e6efd3fb94702 Mon Sep 17 00:00:00 2001
From: Tejun Heo <[email protected]>
Date: Mon, 25 Sep 2017 14:27:54 -0700
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
cfb766da54d9 ("sched/cputime: Expose cputime_adjust()") made cputime_adjust() public for cgroup basic cpu stat support; however, the commit forgot to add a dummy implementaiton for CONFIG_VIRT_CPU_ACCOUNTING_NATIVE leading to compiler errors on some s390 configurations.
Fix it by adding the missing dummy implementation.
--- kernel/sched/cputime.c | 7 +++++++ 1 file changed, 7 insertions(+)
kernel/sched/cputime.c +7 -0
@@ -447,6 +447,13 @@ void vtime_account_irq_enter(struct task_struct *tsk)EXPORT_SYMBOL_GPL (vtime_account_irq_enter);#endif /* __ARCH_HAS_VTIME_ACCOUNT */ +void cputime_adjust (struct task_cputime *curr,struct prev_cputime *prev, +u64 *ut,u64 *st) +{ + *ut = curr->utime; + *st = curr->stime; +} +void task_cputime_adjusted (struct task_struct *p,u64 *ut,u64 *st) { *ut = p->utime;
Signature · 1 line
2.9.5
Re: [PATCH cgroup/for-4.15] sched/cputime: Add dummy cputime_adjust() implementation for CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
Quoted 22 lines · Tejun Heo
Hello,
Another follow-up fix. I missed a dummy implementation leading to bulid failure on s390. Because the bug and fix are trivial, I directly applied the patch to cgroup/for-4.15. Please let me know if it should be routed differently.
Thanks.
------ 8< ------
From8157a7faf94135386bf04b1cf94e6efd3fb94702Mon Sep 17 00:00:00 2001
From: Tejun Heo <[email protected]>
Date: Mon, 25 Sep 2017 14:27:54 -0700
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
cfb766da54d9("sched/cputime: Exposecputime_adjust()") madecputime_adjust()public for cgroup basic cpu stat support; however, the commit forgot to add a dummy implementaiton forCONFIG_VIRT_CPU_ACCOUNTING_NATIVEleading to compiler errors on some s390 configurations.
Quoted 5 lines
Fix it by adding the missing dummy implementation.
Reported-by “[email protected]” <[email protected]>Fixes cfb766da54d9 ("sched/cputime: Expose cputime_adjust()")Signed-off-by Tejun Heo <[email protected]>
Seems fine,