cgroup/cpuset: Show invalid partition reason string
authorWaiman Long <longman@redhat.com>
Thu, 1 Sep 2022 20:57:41 +0000 (16:57 -0400)
committerTejun Heo <tj@kernel.org>
Sun, 4 Sep 2022 20:47:27 +0000 (10:47 -1000)
There are a number of different reasons which can cause a partition to
become invalid. A user seeing an invalid partition may not know exactly
why. To help user to get a better understanding of the underlying reason,
The cpuset.cpus.partition control file, when read, will now report the
reason why a partition become invalid. When a partition does become
invalid, reading the control file will show "root invalid (<reason>)"
where <reason> is a string that describes why the partition is invalid.

Signed-off-by: Waiman Long <longman@redhat.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
kernel/cgroup/cpuset.c

index f53ca02..15fcd2f 100644 (file)
@@ -86,6 +86,30 @@ struct fmeter {
        spinlock_t lock;        /* guards read or write of above */
 };
 
+/*
+ * Invalid partition error code
+ */
+enum prs_errcode {
+       PERR_NONE = 0,
+       PERR_INVCPUS,
+       PERR_INVPARENT,
+       PERR_NOTPART,
+       PERR_NOTEXCL,
+       PERR_NOCPUS,
+       PERR_HOTPLUG,
+       PERR_CPUSEMPTY,
+};
+
+static const char * const perr_strings[] = {
+       [PERR_INVCPUS]   = "Invalid cpu list in cpuset.cpus",
+       [PERR_INVPARENT] = "Parent is an invalid partition root",
+       [PERR_NOTPART]   = "Parent is not a partition root",
+       [PERR_NOTEXCL]   = "Cpu list in cpuset.cpus not exclusive",
+       [PERR_NOCPUS]    = "Parent unable to distribute cpu downstream",
+       [PERR_HOTPLUG]   = "No cpu available due to hotplug",
+       [PERR_CPUSEMPTY] = "cpuset.cpus is empty",
+};
+
 struct cpuset {
        struct cgroup_subsys_state css;
 
@@ -169,6 +193,9 @@ struct cpuset {
        int use_parent_ecpus;
        int child_ecpus_count;
 
+       /* Invalid partition error code, not lock protected */
+       enum prs_errcode prs_err;
+
        /* Handle for cpuset.cpus.partition */
        struct cgroup_file partition_file;
 };
@@ -298,6 +325,10 @@ static inline void notify_partition_change(struct cpuset *cs, int old_prs)
        if (old_prs == cs->partition_root_state)
                return;
        cgroup_file_notify(&cs->partition_file);
+
+       /* Reset prs_err if not invalid */
+       if (is_partition_valid(cs))
+               WRITE_ONCE(cs->prs_err, PERR_NONE);
 }
 
 static struct cpuset top_cpuset = {
@@ -1235,7 +1266,7 @@ static int update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
  * @cmd:     Partition root state change command
  * @newmask: Optional new cpumask for partcmd_update
  * @tmp:     Temporary addmask and delmask
- * Return:   0 or -1 (error)
+ * Return:   0 or a partition root state error code
  *
  * For partcmd_enable, the cpuset is being transformed from a non-partition
  * root to a partition root. The cpus_allowed mask of the given cpuset will
@@ -1261,7 +1292,7 @@ static int update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
  *
  * The partcmd_update command is used by update_cpumasks_hier() with newmask
  * NULL and update_cpumask() with newmask set. The callers won't check for
- * error and so partition_root_state will be updated directly.
+ * error and so partition_root_state and prs_error will be updated directly.
  */
 static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                                          struct cpumask *newmask,
@@ -1271,7 +1302,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
        int adding;     /* Moving cpus from effective_cpus to subparts_cpus */
        int deleting;   /* Moving cpus from subparts_cpus to effective_cpus */
        int old_prs, new_prs;
-       bool part_error = false;        /* Partition error? */
+       int part_error = PERR_NONE;     /* Partition error? */
 
        percpu_rwsem_assert_held(&cpuset_rwsem);
 
@@ -1280,10 +1311,13 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
         * The new cpumask, if present, or the current cpus_allowed must
         * not be empty.
         */
-       if (!is_partition_valid(parent) ||
-          (newmask && cpumask_empty(newmask)) ||
+       if (!is_partition_valid(parent)) {
+               return is_partition_invalid(parent)
+                      ? PERR_INVPARENT : PERR_NOTPART;
+       }
+       if ((newmask && cpumask_empty(newmask)) ||
           (!newmask && cpumask_empty(cs->cpus_allowed)))
-               return -1;
+               return PERR_CPUSEMPTY;
 
        /*
         * new_prs will only be changed for the partcmd_update command.
@@ -1296,7 +1330,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                 * doesn't overlap parent's cpus_allowed.
                 */
                if (!cpumask_intersects(cs->cpus_allowed, parent->cpus_allowed))
-                       return -1;
+                       return PERR_INVCPUS;
 
                /*
                 * A parent can be left with no CPU as long as there is no
@@ -1304,7 +1338,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                 */
                if (!cpumask_intersects(cs->cpus_allowed, parent->effective_cpus) &&
                    partition_is_populated(parent, cs))
-                       return -1;
+                       return PERR_NOCPUS;
 
                cpumask_copy(tmp->addmask, cs->cpus_allowed);
                adding = true;
@@ -1341,7 +1375,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                    cpumask_subset(parent->effective_cpus, tmp->addmask) &&
                    !cpumask_intersects(tmp->delmask, cpu_active_mask) &&
                    partition_is_populated(parent, cs)) {
-                       part_error = true;
+                       part_error = PERR_NOCPUS;
                        adding = false;
                        deleting = cpumask_and(tmp->delmask, cs->cpus_allowed,
                                               parent->subparts_cpus);
@@ -1373,7 +1407,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                    (adding &&
                     cpumask_subset(parent->effective_cpus, tmp->addmask) &&
                     partition_is_populated(parent, cs))) {
-                       part_error = true;
+                       part_error = PERR_NOCPUS;
                        adding = false;
                }
 
@@ -1382,6 +1416,8 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
                        deleting = cpumask_and(tmp->delmask, cs->cpus_allowed,
                                               parent->subparts_cpus);
        }
+       if (part_error)
+               WRITE_ONCE(cs->prs_err, part_error);
 
        if (cmd == partcmd_update) {
                /*
@@ -1412,7 +1448,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd,
        if (old_prs != new_prs) {
                if (is_prs_invalid(old_prs) && !is_cpu_exclusive(cs) &&
                    (update_flag(CS_CPU_EXCLUSIVE, cs, 1) < 0))
-                       return -1;
+                       return PERR_NOTEXCL;
                if (is_prs_invalid(new_prs) && is_cpu_exclusive(cs))
                        update_flag(CS_CPU_EXCLUSIVE, cs, 0);
        }
@@ -1547,6 +1583,9 @@ update_parent_subparts:
                                 */
                                if (is_partition_valid(cp))
                                        new_prs = -cp->partition_root_state;
+                               WRITE_ONCE(cp->prs_err,
+                                          is_partition_invalid(parent)
+                                          ? PERR_INVPARENT : PERR_NOTPART);
                                break;
                        }
                }
@@ -2121,13 +2160,13 @@ out:
  * update_prstate - update partition_root_state
  * @cs: the cpuset to update
  * @new_prs: new partition root state
- * Return: 0 if successful, < 0 if error
+ * Return: 0 if successful, != 0 if error
  *
  * Call with cpuset_rwsem held.
  */
 static int update_prstate(struct cpuset *cs, int new_prs)
 {
-       int err = 0, old_prs = cs->partition_root_state;
+       int err = PERR_NONE, old_prs = cs->partition_root_state;
        bool sched_domain_rebuilt = false;
        struct cpuset *parent = parent_cs(cs);
        struct tmpmasks tmpmask;
@@ -2154,13 +2193,15 @@ static int update_prstate(struct cpuset *cs, int new_prs)
                 * cannot be empty.
                 */
                if (cpumask_empty(cs->cpus_allowed)) {
-                       err = 1;
+                       err = PERR_CPUSEMPTY;
                        goto out;
                }
 
                err = update_flag(CS_CPU_EXCLUSIVE, cs, 1);
-               if (err)
+               if (err) {
+                       err = PERR_NOTEXCL;
                        goto out;
+               }
 
                err = update_parent_subparts_cpumask(cs, partcmd_enable,
                                                     NULL, &tmpmask);
@@ -2730,6 +2771,7 @@ static s64 cpuset_read_s64(struct cgroup_subsys_state *css, struct cftype *cft)
 static int sched_partition_show(struct seq_file *seq, void *v)
 {
        struct cpuset *cs = css_cs(seq_css(seq));
+       const char *err, *type = NULL;
 
        switch (cs->partition_root_state) {
        case PRS_ROOT:
@@ -2742,9 +2784,17 @@ static int sched_partition_show(struct seq_file *seq, void *v)
                seq_puts(seq, "member\n");
                break;
        case PRS_INVALID_ROOT:
-               seq_puts(seq, "root invalid\n");
-               break;
+               type = "root";
+               fallthrough;
        case PRS_INVALID_ISOLATED:
+               if (!type)
+                       type = "isolated";
+               err = perr_strings[READ_ONCE(cs->prs_err)];
+               if (err)
+                       seq_printf(seq, "%s invalid (%s)\n", type, err);
+               else
+                       seq_printf(seq, "%s invalid\n", type);
+               break;
                seq_puts(seq, "isolated invalid\n");
                break;
        }
@@ -3335,7 +3385,7 @@ retry:
         */
        if (is_partition_valid(cs) && (!parent->nr_subparts_cpus ||
           (cpumask_empty(&new_cpus) && partition_is_populated(cs, NULL)))) {
-               int old_prs;
+               int old_prs, parent_prs;
 
                update_parent_subparts_cpumask(cs, partcmd_disable, NULL, tmp);
                if (cs->nr_subparts_cpus) {
@@ -3347,10 +3397,17 @@ retry:
                }
 
                old_prs = cs->partition_root_state;
+               parent_prs = parent->partition_root_state;
                if (is_partition_valid(cs)) {
                        spin_lock_irq(&callback_lock);
                        make_partition_invalid(cs);
                        spin_unlock_irq(&callback_lock);
+                       if (is_prs_invalid(parent_prs))
+                               WRITE_ONCE(cs->prs_err, PERR_INVPARENT);
+                       else if (!parent_prs)
+                               WRITE_ONCE(cs->prs_err, PERR_NOTPART);
+                       else
+                               WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
                        notify_partition_change(cs, old_prs);
                }
                cpuset_force_rebuild();