Add tests for interference
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 26 Sep 2017 19:41:44 +0000 (15:41 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 26 Sep 2017 19:46:22 +0000 (15:46 -0400)
The kernel test runs via kfunc.  The user test runs from userspace.
Both require running 'perf stat -e cycles sleep 99999999' or something
similar to turn on the pmc.

When running either test, you can kfunc ipi_spam to blast the core with
IPIs (spaced by 1 ms).  For userspace, you can notify PID 9 in a bash
for loop.

The userspace test can also be built for Linux, though you won't get the
histogram or other stats for values above the threshold.  If someone
wants to link in benchutil, then that should work.  I tried briefly, but
ld didn't like it.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/arch/x86/rdtsc_test.c
tests/interference.c [new file with mode: 0644]

index c591d35..f2f0308 100644 (file)
 #include <stdio.h>
 #include <kmalloc.h>
 #include <time.h>
+#include <smp.h>
 #include <ros/procinfo.h>
 
 #define STAT_SIZE_DEF 10000
@@ -761,3 +762,81 @@ void test_tsc_cycles(void)
 
        enable_irqsave(&irq_state);
 }
+
+static inline __attribute__((always_inline))
+uint64_t pmc_cycles(void)
+{
+       unsigned int a = 0, d = 0;
+       int ecx = (1 << 30) + 1;
+
+       asm volatile("lfence; rdpmc" : "=a"(a), "=d"(d) : "c"(ecx));
+       return ((uint64_t)a) | (((uint64_t)d) << 32);
+}
+
+/* Does a basic test for interference.  You should kfunc this, often after
+ * starting the monitor on another core.  You can spam it with ipi_spam().
+ * You'll also need the PMCs to run.  Easiest way is with:
+ * $ perf stat -e cycles sleep 9999999. */
+void interference_test(void)
+{
+       #define THRESHOLD 200
+       uint64_t low_samples[THRESHOLD] = {0};
+       uint64_t deadline = sec2tsc(5); /* assumes TSC and cycles are close */
+       uint64_t start, diff;
+       size_t nr_below_thresh = 0;
+       size_t nr_over_thresh = 0;
+       size_t total = 0;
+       size_t max = 0;
+
+    deadline += pmc_cycles();
+       enable_irq();
+       do {
+               total++;
+               start = pmc_cycles();
+               diff = pmc_cycles() - start;
+               if (diff < COUNT_OF(low_samples))
+                       low_samples[diff]++;
+               max = diff > max ? diff : max;
+               if (diff < THRESHOLD)
+                       nr_below_thresh++;
+               else
+                       nr_over_thresh++;
+               if (!start) {
+                       warn("rdpmc got 0, is perf stat -e cycles running? (aborting)");
+                       break;
+               }
+       } while (start < deadline);
+       disable_irq();
+
+       printk("\nCore %d\n--------\n", core_id());
+       for (int i = 0; i < COUNT_OF(low_samples); i++) {
+               if (low_samples[i])
+                       printk("\t[ %2d ] : %lu\n", i, low_samples[i]);
+       }
+       printk("Total loops %lu, threshold %u\n", total, THRESHOLD);
+       printk("Nr over thresh %lu\n", nr_over_thresh);
+       printk("Nr below thresh %lu\n", nr_below_thresh);
+       printk("Max %lu\n", max);
+}
+
+/* Kfunc this to spam a core with IPIs */
+void ipi_spam(int coreid)
+{
+       for (int i = 0; i < 1000; i++) {
+               send_ipi(coreid, I_POKE_CORE);
+               udelay(1000);
+       }
+}
+
+/* Kfunc this to halt with IRQs off.  Note this doesn't fully work as
+ * advertised.  Keyboard and NIC IRQs still wake it up, but LAPIC timers don't
+ * seem to. */
+void superhalt(void)
+{
+       unsigned int x86_cstate = X86_MWAIT_C2;
+
+       disable_irq();
+       asm volatile("monitor" : : "a"(KERNBASE), "c"(0), "d"(0));
+       asm volatile("mwait" : : "c"(0x0), "a"(x86_cstate) : "memory");
+       printk("Core %d woke from superhalt!\n", core_id());
+}
diff --git a/tests/interference.c b/tests/interference.c
new file mode 100644 (file)
index 0000000..093ad68
--- /dev/null
@@ -0,0 +1,138 @@
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef __akaros__
+
+#include <parlib/stdio.h>
+#include <parlib/uthread.h>
+#include <parlib/timing.h>
+#include <parlib/event.h>
+#include <benchutil/measure.h>
+
+static void ev_handle_null(struct event_msg *ev_msg, unsigned int ev_type,
+                           void *data)
+{
+}
+
+static void os_init(void)
+{
+       uthread_mcp_init();
+       register_ev_handler(EV_FREE_APPLE_PIE, ev_handle_null, NULL);
+}
+
+#else
+
+/* Build on linux from $AKAROS_ROOT:
+ * gcc -static --std=gnu99 tests/interference.c -o lin-interference
+ */
+
+#include <stdio.h>
+#include "../user/parlib/include/parlib/tsc-compat.h"
+#include "misc-compat.h"
+
+struct sample_stats {
+       int (*get_sample)(void **data, int i, int j, uint64_t *sample);
+       uint64_t                                        avg_time;
+       uint64_t                                        var_time;
+       uint64_t                                        max_time;
+       uint64_t                                        min_time;
+       unsigned int                            lat_50;
+       unsigned int                            lat_75;
+       unsigned int                            lat_90;
+       unsigned int                            lat_99;
+       uint64_t                                        total_samples;
+};
+
+void compute_stats(void **data, int nr_i, int nr_j, struct sample_stats *stats)
+{
+       /* TODO: could try to link against benchutil. */
+}
+
+static void os_init(void)
+{
+       printf("Linux: If you get a segfault, make sure rdpmc is allowed.\n"
+              "Linux: Set /sys/bus/event_source/devices/cpu/rdpmc = 2 on recent kernels (4.0), or 1 for older kernels.\n");
+}
+
+#endif
+
+static uint64_t *delays;
+
+static int get_delay(void **data, int i, int j, uint64_t *sample)
+{
+       uint64_t **delay_array = (uint64_t**)data;
+
+       *sample = delay_array[i][j];
+       return 0;
+}
+
+static inline __attribute__((always_inline))
+uint64_t pmc_cycles(void)
+{
+       unsigned int a = 0, d = 0;
+       int ecx = (1 << 30) + 1;
+
+       asm volatile("lfence; rdpmc" : "=a"(a), "=d"(d) : "c"(ecx));
+       return ((uint64_t)a) | (((uint64_t)d) << 32);
+}
+
+int main(int argc, char **argv)
+{
+       #define THRESHOLD 200
+       uint64_t start, diff;
+       struct sample_stats stats[1];
+       size_t idx = 0;
+       size_t nr_below_thresh = 0;
+       size_t nr_over_thresh = 0;
+       size_t total = 0;
+       int pcoreid;
+       uint64_t low_samples[THRESHOLD] = {0};
+       int nr_samples = 1000;
+       uint64_t deadline = sec2tsc(5); /* assumes TSC and cycles are close */
+
+       /* Normally we'd use a 2D array, but since we're just one thread, we just
+        * need our first thread's array. */
+       delays = malloc(sizeof(uint64_t) * nr_samples);
+       os_init();
+       pcoreid = get_pcoreid();
+       udelay(1000);
+       deadline += pmc_cycles();
+
+       do {
+               if (idx >= nr_samples)
+                       break;
+               total++;
+               start = pmc_cycles();
+               diff = pmc_cycles() - start;
+               if (diff < COUNT_OF(low_samples))
+                       low_samples[diff]++;
+               if (diff < THRESHOLD) {
+                       nr_below_thresh++;
+               } else {
+                       nr_over_thresh++;
+                       delays[idx++] = diff;
+               }
+               if (!start) {
+                       printf("rdpmc got 0, is perf stat -e cycles running? (aborting)\n");
+                       break;
+               }
+       } while (start < deadline);
+
+       printf("\n\nStats for interference\n");
+       stats->get_sample = get_delay;
+       compute_stats((void**)&delays, 1, idx, stats);
+
+       printf("\n\nStats for low rdtsc times (tsc ticks for two rdtscs)\n");
+       for (int i = 0; i < COUNT_OF(low_samples); i++) {
+               if (low_samples[i])
+                       printf("\t[ %2d ] : %lu\n", i, low_samples[i]);
+       }
+
+       printf("Pcoreid was %d (and is now %d)\n\n", pcoreid, get_pcoreid());
+       printf("Total loops %lu, threshold %u\n", total, THRESHOLD);
+       printf("Nr over thresh %lu (%f%% total)\n", nr_over_thresh,
+              nr_over_thresh * 100.0 / total);
+       printf("Nr below thresh %lu (%f%% total)\n", nr_below_thresh,
+              nr_below_thresh * 100.0 / total);
+       return 0;
+}