Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions rd_stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -492,12 +492,20 @@ void compute_ext_disk_stats(struct stats_disk *sdc, struct stats_disk *sdp,
* sar, sadf, mpstat
***************************************************************************
*/
static unsigned long long get_cpu_interval_delta(unsigned long long prev,
unsigned long long curr)
{
return (curr > prev) ? curr - prev : 0;
}

unsigned long long get_per_cpu_interval(struct stats_cpu *scc,
struct stats_cpu *scp)
{
unsigned long long ishift = 0LL;
unsigned long long interval = 0LL;

if ((scc->cpu_user - scc->cpu_guest) < (scp->cpu_user - scp->cpu_guest)) {
if ((scc->cpu_user >= scp->cpu_user) &&
((scc->cpu_user - scc->cpu_guest) < (scp->cpu_user - scp->cpu_guest))) {
/*
* Sometimes the nr of jiffies spent in guest mode given by the guest
* counter in /proc/stat is slightly higher than that included in
Expand All @@ -506,7 +514,8 @@ unsigned long long get_per_cpu_interval(struct stats_cpu *scc,
ishift += (scp->cpu_user - scp->cpu_guest) -
(scc->cpu_user - scc->cpu_guest);
}
if ((scc->cpu_nice - scc->cpu_guest_nice) < (scp->cpu_nice - scp->cpu_guest_nice)) {
if ((scc->cpu_nice >= scp->cpu_nice) &&
((scc->cpu_nice - scc->cpu_guest_nice) < (scp->cpu_nice - scp->cpu_guest_nice))) {
/*
* Idem for nr of jiffies spent in guest_nice mode.
*/
Expand Down Expand Up @@ -549,16 +558,22 @@ unsigned long long get_per_cpu_interval(struct stats_cpu *scc,
/*
* Don't take cpu_guest and cpu_guest_nice into account
* because cpu_user and cpu_nice already include them.
*
* Some kernels may transiently report per-CPU counters moving
* backwards. Avoid unsigned underflow here: a single regressing
* field must not turn the interval into a huge value, otherwise
* all percentages for the CPU are rounded down to 0.00.
*/
return ((scc->cpu_user + scc->cpu_nice +
scc->cpu_sys + scc->cpu_iowait +
scc->cpu_idle + scc->cpu_steal +
scc->cpu_hardirq + scc->cpu_softirq) -
(scp->cpu_user + scp->cpu_nice +
scp->cpu_sys + scp->cpu_iowait +
scp->cpu_idle + scp->cpu_steal +
scp->cpu_hardirq + scp->cpu_softirq) +
ishift);
interval += get_cpu_interval_delta(scp->cpu_user, scc->cpu_user);
interval += get_cpu_interval_delta(scp->cpu_nice, scc->cpu_nice);
interval += get_cpu_interval_delta(scp->cpu_sys, scc->cpu_sys);
interval += get_cpu_interval_delta(scp->cpu_iowait, scc->cpu_iowait);
interval += get_cpu_interval_delta(scp->cpu_idle, scc->cpu_idle);
interval += get_cpu_interval_delta(scp->cpu_steal, scc->cpu_steal);
interval += get_cpu_interval_delta(scp->cpu_hardirq, scc->cpu_hardirq);
interval += get_cpu_interval_delta(scp->cpu_softirq, scc->cpu_softirq);

return interval + ishift;
}

#ifdef SOURCE_SADC
Expand Down
127 changes: 127 additions & 0 deletions tests/01655
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
set -eu

tmpdir="tests/sar-cpu-zero.$$"
shim_src="$tmpdir/fake-proc-stat.c"
shim_so="$tmpdir/fake-proc-stat.so"
data_file="$tmpdir/sa.data"
sar_out="$tmpdir/sar.out"

cleanup()
{
rm -rf "$tmpdir"
}
trap cleanup EXIT HUP INT TERM

mkdir -p "$tmpdir"

cat > "$shim_src" <<'EOF'
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

static FILE *(*real_fopen_fn)(const char *, const char *);
static FILE *(*real_fopen64_fn)(const char *, const char *);
static unsigned int proc_stat_open_count;

static const char proc_stat_prev[] =
"cpu 2000 0 1000 10000 0 0 0 0 0 0\n"
"cpu0 1000 0 500 5000 0 0 0 0 0 0\n"
"cpu1 1000 0 500 5000 0 0 0 0 0 0\n"
"intr 1000\n"
"ctxt 1000\n"
"btime 1000\n"
"processes 100\n"
"procs_running 1\n"
"procs_blocked 0\n";

static const char proc_stat_curr[] =
"cpu 1910 0 910 10100 0 0 0 0 0 0\n"
"cpu0 900 0 400 5000 0 0 0 0 0 0\n"
"cpu1 1010 0 510 5100 0 0 0 0 0 0\n"
"intr 1010\n"
"ctxt 1010\n"
"btime 1000\n"
"processes 101\n"
"procs_running 1\n"
"procs_blocked 0\n";

static int is_read_mode(const char *mode)
{
return mode && mode[0] == 'r';
}

static FILE *fake_proc_stat(void)
{
const char *sample;

proc_stat_open_count++;
sample = proc_stat_open_count <= 2 ? proc_stat_prev : proc_stat_curr;

return fmemopen((void *) sample, strlen(sample), "r");
}

FILE *fopen(const char *path, const char *mode)
{
if (!real_fopen_fn)
real_fopen_fn = dlsym(RTLD_NEXT, "fopen");

if (path && !strcmp(path, "/proc/stat") && is_read_mode(mode))
return fake_proc_stat();

return real_fopen_fn(path, mode);
}

FILE *fopen64(const char *path, const char *mode)
{
if (!real_fopen64_fn)
real_fopen64_fn = dlsym(RTLD_NEXT, "fopen64");
if (!real_fopen_fn)
real_fopen_fn = dlsym(RTLD_NEXT, "fopen");

if (path && !strcmp(path, "/proc/stat") && is_read_mode(mode))
return fake_proc_stat();

return real_fopen64_fn ? real_fopen64_fn(path, mode) : real_fopen_fn(path, mode);
}
EOF

${CC:-cc} -shared -fPIC -o "$shim_so" "$shim_src" -ldl

LC_ALL=C S_TIME_FORMAT=ISO LD_PRELOAD="$shim_so" ./sadc 1 2 "$data_file" >/dev/null
LC_ALL=C S_TIME_FORMAT=ISO ./sar -P ALL -u ALL -f "$data_file" > "$sar_out"

awk '
function is_cpu_name(value) {
return value == "all" || value ~ /^[0-9]+$/
}

function is_numeric(value) {
return value ~ /^-?[0-9]+([.][0-9]+)?$/
}

/^Linux/ || /^$/ { next }
$1 == "Average:" { next }
{
cpu_col = 2
if ($2 == "AM" || $2 == "PM")
cpu_col = 3
if (!is_cpu_name($cpu_col))
next

zero = 1
numeric = 0
for (i = cpu_col + 1; i <= NF; i++) {
if (!is_numeric($i))
continue
numeric++
if ($i != "0" && $i != "0.00")
zero = 0
}
if (numeric >= 6 && zero) {
print "unexpected all-zero CPU utilization row: " $0 > "/dev/stderr"
bad = 1
}
}
END { exit bad ? 1 : 0 }
' "$sar_out"
1 change: 1 addition & 0 deletions tests/TLIST
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ NOTES:
01630 LC_ALL=C TZ=GMT ./sadf -p tests/data-wghfreq.tmp -- -m FREQ -P ALL > tests/out.data-wghfreq-sadf-p.tmp
01640 LC_ALL=C TZ=GMT ./sadf -r tests/data-wghfreq.tmp -- -m FREQ -P ALL > tests/out.data-wghfreq-sadf-r.tmp
01650 LC_ALL=C TZ=GMT ./sadf -j tests/data-wghfreq.tmp -- -m FREQ -P ALL > tests/out.data-wghfreq-sadf-j.tmp
01655 LD_PRELOAD fake /proc/stat; ./sadc 1 2 tests/sa.data; ./sar -P ALL -u ALL -f tests/sa.data
01657 cat tests/out.data-wghfreq-sadf-j.tmp | $VER_JSON >/dev/null;
01660 LC_ALL=C TZ=GMT ./sadf -x tests/data-wghfreq.tmp -- -m FREQ -P ALL > tests/out.data-wghfreq-sadf-x.tmp
01667 cat tests/out.data-wghfreq-sadf-x.tmp | $VER_XML --schema xml/sysstat.xsd - >/dev/null;
Expand Down