]> granicus.if.org Git - sysstat/commitdiff
sar: Compute global CPU stats as the sum of individual ones
authorSebastien GODARD <sysstat@users.noreply.github.com>
Fri, 16 Mar 2018 09:58:37 +0000 (10:58 +0100)
committerSebastien GODARD <sysstat@users.noreply.github.com>
Fri, 16 Mar 2018 09:58:37 +0000 (10:58 +0100)
sar used to get statistics for CPU "all" from the first line of the
/proc/stat file giving global CPU utilization.

There are several problems with this:

1) With recent kernels (problem detected on a 4.4.14 kernel), the number
of jiffies spent in idle and iowait modes given by this file for global
CPU utilization goes crazy when a CPU is set offline or comes back
online. These counters may even not be monotonic, resulting in wrong
results being displayed by sar.
E.g.:

cat /proc/stat |grep "cpu "
cpu  8123 235 3359 1099139 15985 0 14 0 0 0

(Set a CPU offline)

cat /proc/stat |grep "cpu "
cpu  8146 235 3374 1168377 18919 0 14 0 0 0

(Set the CPU back online)

cat /proc/stat |grep "cpu "
cpu  8169 236 3391 1033989 15978 0 14 0 0 0

2) The updating of the /proc/stat global and individual values is not
done atomically. As a result there can be skew between the global and
individual values reported by sar.
E.g.:

01:46:12   CPU   %user   %nice  %system  %iowait  %steal  %idle
01:46:13   all    0.25    0.00    10.89     0.00    0.00  88.86
01:46:13     0    0.00    0.00    84.80     0.00    0.00  15.20
01:46:13     1    0.00    0.00    89.65     0.00    0.00  10.35
01:46:13     2    0.17    0.00    83.33     0.00    0.00  16.50
01:46:13     3    0.00    0.00    83.64     0.00    0.00  16.36

In the above case, the %system and %idle for "all" is wrongly
reported as 10.89%.

This patch fixes those problems by calculating the statistics for CPU
"all" (global CPU utilization) as the sum of each individual CPU.

Signed-off-by: Sebastien GODARD <sysstat@users.noreply.github.com>
pr_stats.c

index 9ba63a512da5e5030171f40f4e93572094a9e4f9..8b08e3bdcd4ef8b121b80e054c643b45b126c837 100644 (file)
@@ -127,8 +127,11 @@ __print_funct_t print_cpu_stats(struct activity *a, int prev, int curr,
 {
        int i;
        unsigned long long tot_jiffies_c, tot_jiffies_p;
-       unsigned long long deltot_jiffies;
+       unsigned long long deltot_jiffies = 0;
        struct stats_cpu *scc, *scp;
+       struct stats_cpu *scc_all = (struct stats_cpu *) ((char *) a->buf[curr]);
+       struct stats_cpu *scp_all = (struct stats_cpu *) ((char *) a->buf[prev]);
+       unsigned char offline_cpu_bitmap[BITMAP_SIZE(NR_CPUS)] = {0};
 
        if (dis) {
                print_hdr_line(timestamp[!curr], a, FIRST + DISPLAY_CPU_ALL(a->opt_flags), 7, 9);
@@ -147,7 +150,19 @@ __print_funct_t print_cpu_stats(struct activity *a, int prev, int curr,
                a->nr_ini = a->nr[curr];
        }
 
-       for (i = 0; (i < a->nr_ini) && (i < a->bitmap->b_size + 1); i++) {
+       /*
+        * Initial processing.
+        * Compute CPU "all" as sum of all individual CPU. Done only on SMP machines (a->nr_ini > 1).
+        * For UP machines we keep the values read from global CPU line in /proc/stat.
+        * Also look for offline CPU: They won't be displayed, and some of their values may
+        * have to be modified.
+        */
+       if (a->nr_ini > 1) {
+               memset(scc_all, 0, sizeof(struct stats_cpu));
+               memset(scp_all, 0, sizeof(struct stats_cpu));
+       }
+
+       for (i = 1; (i < a->nr_ini) && (i < a->bitmap->b_size + 1); i++) {
 
                /*
                 * The size of a->buf[...] CPU structure may be different from the default
@@ -159,20 +174,7 @@ __print_funct_t print_cpu_stats(struct activity *a, int prev, int curr,
                scp = (struct stats_cpu *) ((char *) a->buf[prev] + i * a->msize);
 
                /*
-                * Note: @nr[curr] is in [1, NR_CPUS + 1].
-                * Bitmap size is provided for (NR_CPUS + 1) CPUs.
-                * Anyway, NR_CPUS may vary between the version of sysstat
-                * used by sadc to create a file, and the version of sysstat
-                * used by sar to read it...
-                */
-
-               /* Should current CPU (including CPU "all") be displayed? */
-               if (!(a->bitmap->b_array[i >> 3] & (1 << (i & 0x07))))
-                       /* No */
-                       continue;
-
-               /*
-                * Yes: Compute the total number of jiffies spent by current processor.
+                * Compute the total number of jiffies spent by current processor.
                 * NB: Don't add cpu_guest/cpu_guest_nice because cpu_user/cpu_nice
                 * already include them.
                 */
@@ -185,9 +187,6 @@ __print_funct_t print_cpu_stats(struct activity *a, int prev, int curr,
                                scp->cpu_iowait + scp->cpu_hardirq +
                                scp->cpu_steal + scp->cpu_softirq;
 
-               /* Total number of jiffies spent on the interval */
-               deltot_jiffies = get_interval(tot_jiffies_p, tot_jiffies_c);
-
                /*
                 * If the CPU is offline then it is omited from /proc/stat:
                 * All the fields couldn't have been read and the sum of them is zero.
@@ -198,32 +197,107 @@ __print_funct_t print_cpu_stats(struct activity *a, int prev, int curr,
                         * Set current struct fields (which have been set to zero)
                         * to values from previous iteration. Hence their values won't
                         * jump from zero when the CPU comes back online.
-                        * Note that this workaround is no longer enough with recent kernels,
+                        * Note that this workaround no longer fully applies with recent kernels,
                         * as I have noticed that when a CPU comes back online, some fields
                         * restart from their previous value (e.g. user, nice, system)
-                        * whereas others restart from zero (idle, iowait)!
+                        * whereas others restart from zero (idle, iowait)! To deal with this,
+                        * the get_per_cpu_interval() function will set these previous values
+                        * to zero if necessary.
                         */
                        *scc = *scp;
 
-                       /* An offline CPU is not displayed */
-                       continue;
+                       /*
+                        * Mark CPU as offline to not display it
+                        * (and thus it will not be confused with a tickless CPU).
+                        */
+                       offline_cpu_bitmap[i >> 3] |= 1 << (i & 0x07);
                }
-               if ((tot_jiffies_p == 0) && !WANT_SINCE_BOOT(flags))
+
+               if ((tot_jiffies_p == 0) && !WANT_SINCE_BOOT(flags)) {
                        /*
                         * CPU has just come back online.
                         * Unfortunately, no reference values are available
                         * from a previous iteration, probably because it was
                         * already offline when the first sample has been taken.
                         * So don't display that CPU to prevent "jump-from-zero"
-                        * output syndrome.
+                        * output syndrome, and don't take it into account for CPU "all".
                         */
+                       offline_cpu_bitmap[i >> 3] |= 1 << (i & 0x07);
                        continue;
+               }
+
+               /*
+                * Get interval for current CPU and add it to global CPU.
+                * Note: Previous idle and iowait values (saved in scp) may be modified here.
+                */
+               deltot_jiffies += get_per_cpu_interval(scc, scp);
+
+               scc_all->cpu_user += scc->cpu_user;
+               scp_all->cpu_user += scp->cpu_user;
+
+               scc_all->cpu_nice += scc->cpu_nice;
+               scp_all->cpu_nice += scp->cpu_nice;
+
+               scc_all->cpu_sys += scc->cpu_sys;
+               scp_all->cpu_sys += scp->cpu_sys;
+
+               scc_all->cpu_idle += scc->cpu_idle;
+               scp_all->cpu_idle += scp->cpu_idle;
+
+               scc_all->cpu_iowait += scc->cpu_iowait;
+               scp_all->cpu_iowait += scp->cpu_iowait;
+
+               scc_all->cpu_hardirq += scc->cpu_hardirq;
+               scp_all->cpu_hardirq += scp->cpu_hardirq;
+
+               scc_all->cpu_steal += scc->cpu_steal;
+               scp_all->cpu_steal += scp->cpu_steal;
+
+               scc_all->cpu_softirq += scc->cpu_softirq;
+               scp_all->cpu_softirq += scp->cpu_softirq;
+
+               scc_all->cpu_guest += scc->cpu_guest;
+               scp_all->cpu_guest += scp->cpu_guest;
+
+               scc_all->cpu_guest_nice += scc->cpu_guest_nice;
+               scp_all->cpu_guest_nice += scp->cpu_guest_nice;
+       }
+
+       /*
+        * Now display CPU statistics (including CPU "all"),
+        * except for offline CPU or CPU that the user doesn't want to see.
+        */
+       for (i = 0; (i < a->nr_ini) && (i < a->bitmap->b_size + 1); i++) {
+
+               /*
+                * Should current CPU (including CPU "all") be displayed?
+                * Note: @nr[curr] is in [1, NR_CPUS + 1].
+                * Bitmap size is provided for (NR_CPUS + 1) CPUs.
+                * Anyway, NR_CPUS may vary between the version of sysstat
+                * used by sadc to create a file, and the version of sysstat
+                * used by sar to read it...
+                */
+               if (!(a->bitmap->b_array[i >> 3] & (1 << (i & 0x07))) ||
+                   offline_cpu_bitmap[i >> 3] & (1 << (i & 0x07)))
+                       /* Don't display CPU */
+                       continue;
+
+               scc = (struct stats_cpu *) ((char *) a->buf[curr] + i * a->msize);
+               scp = (struct stats_cpu *) ((char *) a->buf[prev] + i * a->msize);
 
                printf("%-11s", timestamp[curr]);
 
-               if (!i) {
+               if (i == 0) {
                        /* This is CPU "all" */
                        cprintf_in(IS_STR, " %s", "    all", 0);
+
+                       if (a->nr_ini == 1) {
+                               /*
+                                * This is a UP machine. In this case
+                                * interval has still not been calculated.
+                                */
+                               deltot_jiffies = get_per_cpu_interval(scc, scp);
+                       }
                }
                else {
                        cprintf_in(IS_INT, " %7d", "", i - 1);