diff options
| -rw-r--r-- | Documentation/admin-guide/cgroup-v2.rst | 34 | ||||
| -rw-r--r-- | include/linux/cgroup-defs.h | 2 | ||||
| -rw-r--r-- | include/linux/cgroup.h | 2 | ||||
| -rw-r--r-- | kernel/cgroup/cpuset.c | 49 | ||||
| -rw-r--r-- | tools/cgroup/iocost_monitor.py | 10 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/lib/include/cgroup_util.h | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/test_cpu.c | 43 | ||||
| -rwxr-xr-x | tools/testing/selftests/cgroup/test_cpuset_prs.sh | 11 |
8 files changed, 115 insertions, 37 deletions
diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index f0e6bce3f162..b0bd591bc4bf 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1145,7 +1145,7 @@ will be referred to. All time durations are in microseconds. This file exists whether the controller is enabled or not. It always reports the following three stats, which account for all the - processes in the cgroup: + processes in the cgroup (including those in descendant cgroups): - usage_usec - user_usec @@ -1160,6 +1160,27 @@ will be referred to. All time durations are in microseconds. - nr_bursts - burst_usec + Note that the above five CFS bandwidth stats are non-hierarchical; + they only account for throttling caused by this cgroup's own bandwidth + limit, not including throttling inherited from ancestor cgroups. + + cpu.stat.local + A read-only flat-keyed file. + This file exists whether the controller is enabled or not. + + It reports the following stat when the controller is enabled: + + - throttled_usec + + Unlike the ``throttled_usec`` reported by ``cpu.stat`` which + accounts for throttling caused by this cgroup's own CFS + bandwidth limit, ``cpu.stat.local`` reports the actual + throttling time incurred by this cgroup's own runqueues, + which may include throttling inherited from ancestor + cgroup bandwidth limits. + + When the controller is not enabled, this stat is not reported. + cpu.weight A read-write single value file which exists on non-root cgroups. The default is "100". @@ -1570,7 +1591,7 @@ The following nested keys are defined. sock (npn) Amount of memory used in network transmission buffers - vmalloc (npn) + vmalloc Amount of memory used for vmap backed memory. shmem @@ -1735,7 +1756,7 @@ The following nested keys are defined. Number of pages written from zswap to swap. zswap_incomp - Number of incompressible pages currently stored in zswap + Amount of memory used by incompressible pages currently stored in zswap without compression. These pages could not be compressed to a size smaller than PAGE_SIZE, so they are stored as-is. @@ -2527,6 +2548,13 @@ Cpuset Interface Files a need to change "cpuset.mems" with active tasks, it shouldn't be done frequently. + For a multithreaded process, the threadgroup leader is + considered the owner of the group's memory. Memory policy + rebinding and migration will only happen with respect to the + threadgroup leader. To avoid unexpected results, non-leading + threads shouldn't be put into another cgroup whose "cpuset.mems" + doesn't fully overlap that of the threadgroup leader. + cpuset.mems.effective A read-only multiple values file which exists on all cpuset-enabled cgroups. diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h index de2cd6238c2a..7a631a257613 100644 --- a/include/linux/cgroup-defs.h +++ b/include/linux/cgroup-defs.h @@ -896,7 +896,7 @@ static inline void cgroup_threadgroup_change_begin(struct task_struct *tsk) * cgroup_threadgroup_change_end - threadgroup exclusion for cgroups * @tsk: target task * - * Counterpart of cgroup_threadcgroup_change_begin(). + * Counterpart of cgroup_threadgroup_change_begin(). */ static inline void cgroup_threadgroup_change_end(struct task_struct *tsk) { diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index f2aa46a4f871..b905208942bf 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -480,7 +480,7 @@ static inline void cgroup_unlock(void) rcu_read_lock_sched_held() || \ lockdep_is_held(&cgroup_mutex) || \ lockdep_is_held(&css_set_lock) || \ - ((task)->flags & PF_EXITING) || (__c)) + (data_race((task)->flags) & PF_EXITING) || (__c)) #else #define task_css_set_check(task, __c) \ rcu_dereference((task)->cgroups) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 48f5ceeeb8a7..083a3ac988e8 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1089,12 +1089,35 @@ void cpuset_update_tasks_cpumask(struct cpuset *cs, struct cpumask *new_cpus) * @cs: the cpuset the need to recompute the new effective_cpus mask * @parent: the parent cpuset * + * For v2, the parent's effective_cpus is inherited if cpumask is empty. * The result is valid only if the given cpuset isn't a partition root. */ static void compute_effective_cpumask(struct cpumask *new_cpus, struct cpuset *cs, struct cpuset *parent) { - cpumask_and(new_cpus, cs->cpus_allowed, parent->effective_cpus); + bool has_cpus; + + has_cpus = cpumask_and(new_cpus, cs->cpus_allowed, parent->effective_cpus); + if (!has_cpus && is_in_v2_mode()) + cpumask_copy(new_cpus, parent->effective_cpus); +} + +/** + * compute_effective_nodemask - Compute the effective nodemask of the cpuset + * @new_mems: the temp variable for the new effective_mems mask + * @cs: the cpuset the need to recompute the new effective_mems mask + * @parent: the parent cpuset + * + * For v2, the parent's effective_mems is inherited if nodemask is empty. + */ +static void compute_effective_nodemask(nodemask_t *new_mems, + struct cpuset *cs, struct cpuset *parent) +{ + bool has_mems; + + has_mems = nodes_and(*new_mems, cs->mems_allowed, parent->effective_mems); + if (!has_mems && is_in_v2_mode()) + nodes_copy(*new_mems, parent->effective_mems); } /* @@ -2144,15 +2167,6 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp, } /* - * If it becomes empty, inherit the effective mask of the - * parent, which is guaranteed to have some CPUs unless - * it is a partition root that has explicitly distributed - * out all its CPUs. - */ - if (is_in_v2_mode() && !remote && cpumask_empty(tmp->new_cpus)) - cpumask_copy(tmp->new_cpus, parent->effective_cpus); - - /* * Skip the whole subtree if * 1) the cpumask remains the same, * 2) has no partition root state, @@ -2647,6 +2661,10 @@ void cpuset_update_tasks_nodemask(struct cpuset *cs) cpuset_change_task_nodemask(task, &newmems); + /* Rebind and migrate mm only for thread group leader */ + if (!thread_group_leader(task)) + continue; + mm = get_task_mm(task); if (!mm) continue; @@ -2692,14 +2710,7 @@ static void update_nodemasks_hier(struct cpuset *cs, nodemask_t *new_mems) cpuset_for_each_descendant_pre(cp, pos_css, cs) { struct cpuset *parent = parent_cs(cp); - bool has_mems = nodes_and(*new_mems, cp->mems_allowed, parent->effective_mems); - - /* - * If it becomes empty, inherit the effective mask of the - * parent, which is guaranteed to have some MEMs. - */ - if (is_in_v2_mode() && !has_mems) - *new_mems = parent->effective_mems; + compute_effective_nodemask(new_mems, cp, parent); /* Skip the whole subtree if the nodemask remains the same. */ if (nodes_equal(*new_mems, cp->effective_mems)) { @@ -3773,7 +3784,7 @@ retry: parent = parent_cs(cs); compute_effective_cpumask(&new_cpus, cs, parent); - nodes_and(new_mems, cs->mems_allowed, parent->effective_mems); + compute_effective_nodemask(&new_mems, cs, parent); if (!tmp || !cs->partition_root_state) goto update_tasks; diff --git a/tools/cgroup/iocost_monitor.py b/tools/cgroup/iocost_monitor.py index 933c750b319b..bdd78ba271b0 100644 --- a/tools/cgroup/iocost_monitor.py +++ b/tools/cgroup/iocost_monitor.py @@ -15,11 +15,6 @@ import time import json import math -import drgn -from drgn import container_of -from drgn.helpers.linux.list import list_for_each_entry,list_empty -from drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup - import argparse parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) @@ -34,6 +29,11 @@ parser.add_argument('--json', action='store_true', help='Output in json') args = parser.parse_args() +import drgn +from drgn import container_of +from drgn.helpers.linux.list import list_for_each_entry,list_empty +from drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup + def err(s): print(s, file=sys.stderr, flush=True) sys.exit(1) diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h index febc1723d090..8ebb2b4d4ec0 100644 --- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h +++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h @@ -8,6 +8,7 @@ #define MB(x) (x << 20) +#define NSEC_PER_USEC 1000L #define USEC_PER_SEC 1000000L #define NSEC_PER_SEC 1000000000L diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c index 7a40d76b9548..a5eccfcabef5 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -640,21 +640,49 @@ test_cpucg_nested_weight_underprovisioned(const char *root) } /* + * Best effort attempt to get the kernel's HZ value from the config. + * Return the HZ value if found otherwise return 1000 (the default) to + * indicate failure. + */ +static long +get_config_hz(void) +{ + long hz = 1000; + FILE *f; + char cmd[256] = "zcat /proc/config.gz 2>/dev/null | grep '^CONFIG_HZ='"; + + f = popen(cmd, "r"); + + if (!f) + return hz; + + if (fscanf(f, "CONFIG_HZ=%ld", &hz) == EOF) + goto out; + +out: + pclose(f); + return hz; +} + +/* * This test creates a cgroup with some maximum value within a period, and * verifies that a process in the cgroup is not overscheduled. */ static int test_cpucg_max(const char *root) { int ret = KSFT_FAIL; + long hz = get_config_hz(); long quota_usec = 1000; long default_period_usec = 100000; /* cpu.max's default period */ long duration_seconds = 1; - long duration_usec = duration_seconds * USEC_PER_SEC; + long duration_usec; long usage_usec, n_periods, remainder_usec, expected_usage_usec; char *cpucg; char quota_buf[32]; + duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz; + snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec); cpucg = cg_name(root, "cpucg_test"); @@ -670,8 +698,8 @@ static int test_cpucg_max(const char *root) struct cpu_hog_func_param param = { .nprocs = 1, .ts = { - .tv_sec = duration_seconds, - .tv_nsec = 0, + .tv_sec = duration_usec / USEC_PER_SEC, + .tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC, }, .clock_type = CPU_HOG_CLOCK_WALL, }; @@ -710,15 +738,18 @@ cleanup: static int test_cpucg_max_nested(const char *root) { int ret = KSFT_FAIL; + long hz = get_config_hz(); long quota_usec = 1000; long default_period_usec = 100000; /* cpu.max's default period */ long duration_seconds = 1; - long duration_usec = duration_seconds * USEC_PER_SEC; + long duration_usec; long usage_usec, n_periods, remainder_usec, expected_usage_usec; char *parent, *child; char quota_buf[32]; + duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz; + snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec); parent = cg_name(root, "cpucg_parent"); @@ -741,8 +772,8 @@ static int test_cpucg_max_nested(const char *root) struct cpu_hog_func_param param = { .nprocs = 1, .ts = { - .tv_sec = duration_seconds, - .tv_nsec = 0, + .tv_sec = duration_usec / USEC_PER_SEC, + .tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC, }, .clock_type = CPU_HOG_CLOCK_WALL, }; diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 0d41aa0d343d..ca9bc38fdb95 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -495,13 +495,20 @@ REMOTE_TEST_MATRIX=( # Narrowing cpuset.cpus to previously sibling-excluded CPUs should # not return CPUs that were never actually owned. " C1-4:P1 . C1-2:P1 C1-3:P2 . . \ - . . . C3 . . p1:4|c11:1-2|c12:3 \ + . . . C3 . . p1:4|c11:1-2|c12:3 \ p1:P1|c11:P1|c12:P2 3" # Expanding cpuset.cpus to include a previously sibling-excluded CPU # after the sibling has become a member should correctly request it. " C1-4:P1 . C1-2:P1 C1-3:P2 . . \ - . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \ + . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \ p1:P1|c11:P0|c12:P2 2-3" + # Cpusets with empty cpuset.cpus should inherit parent's effective_cpus + " C1-4:P1 C5-6 C1-2 . C5 . \ + . P1 P1 . . . p1:3-4|p2:5-6|c11:1-2|c12:3-4|c21:5|c22:5-6 \ + p1:P1|p2:P1|c11:P1" + " C1-4:P1 C5-6 C1-2 . C5 . \ + . P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \ + p1:P1|p2:P1|c11:P1" ) # |
