From: Punit Agrawal <punit1.agrawal@xxxxxxxxxxxxx> For real time systems, the standard deviation is an important metric to help qualify the suitability for low-latency workloads. Downstream tools such as rteval calculate the value of the standard deviation based on the latency histogram but this approach doesn't work in situations when the max latency exceeds the range of the histogram. The common formula to calculating standard deviation requires tracking all samples which is not feasible. But an alternative approach is to track the streaming standard deviation of the latency samples. With this it is possible to update the standard deviation using - d = d' + (x - avg)(x - avg') stddev = sqrt (d / (n - 1)) where 'd' is similar to the variance, 'x' is the current latency sample, 'n' is the number of samples and prime terms (') refer to the previous values. Implement the above algorithm to calculate the standard deviation and update the reporting to also include standard deviation. Signed-off-by: Punit Agrawal <punit1.agrawal@xxxxxxxxxxxxx> --- Makefile | 2 +- src/cyclictest/cyclictest.c | 43 +++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index ec5d693436d0..3f08788c0208 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ sources = cyclictest.c \ oslat.c TARGETS = $(sources:.c=) -LIBS = -lrt -lpthread +LIBS = -lrt -lpthread -lm RTTESTLIB = -lrttest -L$(OBJDIR) EXTRA_LIBS ?= -ldl # for get_cpu RTTESTNUMA = -lrttestnuma -lnuma diff --git a/src/cyclictest/cyclictest.c b/src/cyclictest/cyclictest.c index dde8b1625c62..2c18013a7643 100644 --- a/src/cyclictest/cyclictest.c +++ b/src/cyclictest/cyclictest.c @@ -23,6 +23,7 @@ #include <errno.h> #include <limits.h> #include <linux/unistd.h> +#include <math.h> #include <sys/prctl.h> #include <sys/stat.h> @@ -134,6 +135,7 @@ struct thread_stat { long max; long act; double avg; + double var; long *values; long *smis; long *hist_array; @@ -358,6 +360,16 @@ try_again: return err; } +static double calc_stddev(struct thread_stat *stat) +{ + /* standard deviation of single samples is 0 */ + if (stat->cycles <= 1) + return 0; + + return sqrt(stat->var / (stat->cycles - 1)); +} + + #ifdef ARCH_HAS_SMI_COUNTER static int open_msr_file(int cpu) { @@ -750,6 +762,17 @@ static void *timerthread(void *param) prev_avg = stat->avg; stat->avg = prev_avg + ((diff - prev_avg) / stat->cycles); + /* + * Calculate a slight variation on variance - the + * variance multiplied by (n - 1). This allows + * updating the quantity as data becomes available + * without keeping track of all past samples or averages. + * + * (n - 1) * var = var' + (x - avg) * (x - avg') + * + */ + stat->var += (diff - stat->avg) * (diff - prev_avg); + next.tv_sec += interval.tv_sec; next.tv_nsec += interval.tv_nsec; if (par->mode == MODE_CYCLIC) { @@ -1379,6 +1402,10 @@ static void print_hist(struct thread_param *par[], int nthreads) fprintf(fd, " %05lu", par[j]->stats->cycles ? (long)(par[j]->stats->avg/par[j]->stats->cycles) : 0); fprintf(fd, "\n"); + fprintf(fd, "# StdDev Latencies:"); + for (j = 0; j < nthreads; j++) + fprintf(fd, " %05lu", (long)calc_stddev(par[j]->stats)); + fprintf(fd, "\n"); fprintf(fd, "# Max Latencies:"); maxmax = 0; for (j = 0; j < nthreads; j++) { @@ -1430,14 +1457,15 @@ static void print_stat(FILE *fp, struct thread_param *par, int index, int verbos char *fmt; if (use_nsecs) fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu " - "Min:%7ld Act:%8ld Avg:%8ld Max:%8ld"; + "Min:%7ld Act:%8ld Avg:%8ld StdDev:%8ld Max:%8ld"; else fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu " - "Min:%7ld Act:%5ld Avg:%5ld Max:%8ld"; + "Min:%7ld Act:%5ld Avg:%5ld StdDev:%5ld Max:%8ld"; fprintf(fp, fmt, index, stat->tid, par->prio, par->interval, stat->cycles, stat->min, - stat->act, (long)stat->avg, stat->max); + stat->act, (long)stat->avg, (long)calc_stddev(stat), + stat->max); if (smi) fprintf(fp, " SMI:%8ld", stat->smi_count); @@ -1485,14 +1513,15 @@ static void rstat_print_stat(struct thread_param *par, int index, int verbose, i char *fmt; if (use_nsecs) fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu " - "Min:%7ld Act:%8ld Avg:%8ld Max:%8ld"; + "Min:%7ld Act:%8ld Avg:%8ld StdDev:%8ld Max:%8ld"; else fmt = "T:%2d (%5d) P:%2d I:%ld C:%7lu " - "Min:%7ld Act:%5ld Avg:%5ld Max:%8ld"; + "Min:%7ld Act:%5ld Avg:%5ld StdDev:%5ld Max:%8ld"; dprintf(fd, fmt, index, stat->tid, par->prio, par->interval, stat->cycles, stat->min, - stat->act, (long)stat->avg, stat->max); + stat->act, (long)stat->avg, (long)calc_stddev(stat), + stat->max); if (smi) dprintf(fd, " SMI:%8ld", stat->smi_count); @@ -1781,6 +1810,7 @@ static void write_stats(FILE *f, void *data) fprintf(f, " \"min\": %ld,\n", s->min); fprintf(f, " \"max\": %ld,\n", s->max); fprintf(f, " \"avg\": %.2f,\n", s->avg); + fprintf(f, " \"stddev\": %.2f,\n", calc_stddev(s)); fprintf(f, " \"cpu\": %d,\n", par[i]->cpu); fprintf(f, " \"node\": %d\n", par[i]->node); fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); @@ -2091,6 +2121,7 @@ int main(int argc, char **argv) stat->min = 1000000; stat->max = 0; stat->avg = 0.0; + stat->var = 0; stat->threadstarted = 1; stat->smi_count = 0; status = pthread_create(&stat->thread, &attr, timerthread, par); -- 2.32.0