Page MenuHomeFreeBSD

D59366.diff
No OneTemporary

D59366.diff

diff --git a/tools/regression/pmc/Makefile b/tools/regression/pmc/Makefile
--- a/tools/regression/pmc/Makefile
+++ b/tools/regression/pmc/Makefile
@@ -35,7 +35,8 @@
CXXFLAGS+= -O -g -Wall
# Kernel ABI tests (need hwpmc(4) loaded and root to run).
-KTESTS= pmc_group_test pmc_mux_test pmc_mux_works_test
+KTESTS= pmc_group_test pmc_mux_test pmc_mux_works_test \
+ pmc_mux_rotation_test pmc_mux_drain_test
# Userland-only tests (no PMU access).
UTESTS= test_libpmcstat_group
@@ -63,6 +64,15 @@
${LIBPMCSTAT_SRC}/libpmcstat_group.c
${CC} ${CFLAGS} ${LIBPMCSTAT_INC} -o ${.TARGET} ${.ALLSRC} -lpmc
+pmc_mux_rotation_test: ${.CURDIR}/pmc_mux_rotation_test.c
+ ${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc
+
+# This test reproduces the multiplex switch-off drain wedge. It needs
+# -lpthread for the pinned real-time worker threads that starve the
+# passive drain.
+pmc_mux_drain_test: ${.CURDIR}/pmc_mux_drain_test.c
+ ${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc -lpthread
+
# These are userland-only checks: parsers and pmcstat warning policy.
check: ${UTESTS}
./test_libpmcstat_group
@@ -75,6 +85,10 @@
./pmc_mux_test
@echo "==> pmc_mux_works_test"
./pmc_mux_works_test
+ @echo "==> pmc_mux_rotation_test"
+ ./pmc_mux_rotation_test
+ @echo "==> pmc_mux_drain_test"
+ ./pmc_mux_drain_test
clean:
rm -f ${ALL} *.o *.core
diff --git a/tools/regression/pmc/pmc_mux_drain_test.c b/tools/regression/pmc/pmc_mux_drain_test.c
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmc_mux_drain_test.c
@@ -0,0 +1,333 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Test multiplex rotation drain with pinned real-time worker threads.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#include <sys/rtprio.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pmc.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAX_GROUPS 2
+#define MAX_PER_GROUP 8
+#define MAX_WORKERS 8
+
+/* This is the maximum run time in seconds before the watchdog fires. */
+#define WATCHDOG_SECS 60
+#define STRESS_SECS 3
+
+static const char *event_pool[] = {
+ "instructions",
+ "unhalted-cycles",
+ "branches",
+ "branch-misses",
+ "cache-references",
+ "cache-misses",
+ "l1d-loads",
+ "l1d-load-misses",
+ "l2-cache-references",
+ "l2-cache-misses",
+ "dispatch-stalls",
+ "fp-retired",
+ "branches-retired",
+ "de-no-dispatch-per-slot",
+ "ls_alloc_mab_count",
+ "ls_not_halted_cyc",
+ "ls_dispatch.all",
+ "ls_mab_alloc.ls",
+ "ls_mab_alloc.hwpf",
+ "ls_mab_alloc.all",
+ "ls_int_taken",
+ "ls_stlf",
+};
+#define POOL_SIZE (int)(sizeof(event_pool) / sizeof(event_pool[0]))
+
+struct pmu_grp {
+ uint32_t gid;
+ int nevents;
+ int committed;
+ pmc_id_t ids[MAX_PER_GROUP];
+ const char *names[MAX_PER_GROUP];
+};
+
+static volatile int g_stop; /* Tell workers to stop. */
+static volatile int g_running; /* This is the active worker count. */
+
+static int
+is_amd(void)
+{
+ char buf[64];
+ size_t s = sizeof(buf);
+
+ if (sysctlbyname("kern.hwpmc.cpuid", buf, &s, NULL, 0) != 0)
+ return (0);
+ return (strstr(buf, "AuthenticAMD") != NULL ||
+ strstr(buf, "HygonGenuine") != NULL);
+}
+
+static int
+probe_core_pmcs(void)
+{
+ pmc_id_t ids[64];
+ int n = 0;
+
+ while (n < (int)(sizeof(ids) / sizeof(ids[0]))) {
+ if (pmc_allocate("instructions", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &ids[n], 0) < 0)
+ break;
+ n++;
+ }
+ for (int i = 0; i < n; i++)
+ (void)pmc_release(ids[i]);
+ return (n);
+}
+
+/* This pinned real-time thread runs a CPU loop. */
+static void *
+worker(void *arg)
+{
+ cpuset_t mask;
+ struct rtprio rtp;
+ int cpu = (int)(intptr_t)arg;
+ volatile uint64_t spin = 0;
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpu, &mask);
+ if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(mask), &mask) != 0)
+ warn("cpuset_setaffinity cpu=%d", cpu);
+
+ rtp.type = RTP_PRIO_REALTIME;
+ rtp.prio = 30; /* This is the real-time priority. */
+ if (rtprio_thread(RTP_SET, 0, &rtp) != 0)
+ warn("rtprio_thread cpu=%d (continuing at normal prio)", cpu);
+
+ __atomic_add_fetch(&g_running, 1, __ATOMIC_SEQ_CST);
+
+ while (g_stop == 0) {
+ for (int k = 0; k < 100000; k++)
+ spin++;
+ }
+ return (NULL);
+}
+
+static int
+build_group(struct pmu_grp *g, int n_target, int pool_start)
+{
+ int i;
+
+ if (pmc_group_create(&g->gid) < 0) {
+ warn("pmc_group_create");
+ return (-1);
+ }
+ g->nevents = 0;
+ for (i = pool_start; i < POOL_SIZE && g->nevents < n_target; i++) {
+ uint32_t flags = 0;
+ pmc_id_t id;
+
+ if (g->nevents == 0)
+ flags |= PMC_F_GROUP_MUX;
+ if (pmc_allocate_group(event_pool[i], PMC_MODE_TC, flags,
+ PMC_CPU_ANY, &id, 0) < 0)
+ continue;
+ if (pmc_group_add(g->gid, id, g->nevents == 0) < 0) {
+ (void)pmc_release(id);
+ continue;
+ }
+ g->ids[g->nevents] = id;
+ g->names[g->nevents] = event_pool[i];
+ g->nevents++;
+ }
+ return (g->nevents);
+}
+
+static void
+release_group(struct pmu_grp *g)
+{
+ int i;
+
+ if (g->committed)
+ (void)pmc_release(g->ids[0]);
+ else {
+ for (i = 0; i < g->nevents; i++)
+ (void)pmc_release(g->ids[i]);
+ }
+ g->committed = 0;
+ g->nevents = 0;
+}
+
+static void
+on_watchdog(int sig __unused)
+{
+ static const char msg[] =
+ "\nFAIL: watchdog fired -- a mux drain is wedged waiting on "
+ "pm_runcount (a pinned RT target never context-switched out). "
+ "This is the passive-drain bug.\n";
+
+ (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
+ _exit(1);
+}
+
+int
+main(void)
+{
+ struct pmu_grp grps[MAX_GROUPS];
+ pthread_t tid[MAX_WORKERS];
+ int core, ncpu, nworkers, saved_period, new_period;
+ int per_group, total, i, j;
+ size_t s;
+
+ memset(grps, 0, sizeof(grps));
+
+ if (pmc_init() < 0)
+ err(1, "pmc_init");
+ if (!is_amd()) {
+ printf("SKIP: non-AMD CPU\n");
+ return (77);
+ }
+
+ s = sizeof(ncpu);
+ if (sysctlbyname("hw.ncpu", &ncpu, &s, NULL, 0) != 0 || ncpu < 2) {
+ printf("SKIP: need >= 2 CPUs to pin a lone RT target\n");
+ return (77);
+ }
+
+ core = probe_core_pmcs();
+ if (core < 2) {
+ printf("SKIP: only %d core PMCs available\n", core);
+ return (77);
+ }
+
+ per_group = (core * 2) / 3;
+ if (per_group < 2)
+ per_group = 2;
+ if (per_group > MAX_PER_GROUP)
+ per_group = MAX_PER_GROUP;
+ total = per_group * MAX_GROUPS;
+ if (total <= core) {
+ printf("SKIP: %d-counter CPU; cannot oversubscribe with "
+ "%d groups of %d\n", core, MAX_GROUPS, per_group);
+ return (77);
+ }
+
+ /* Pin workers to CPUs 1 through nworkers. */
+ nworkers = ncpu - 1;
+ if (nworkers > MAX_WORKERS)
+ nworkers = MAX_WORKERS;
+
+ printf("mux drain stress: %d groups x %d events on %d HW core "
+ "counters, %d pinned RT workers (cpus 1..%d)\n",
+ MAX_GROUPS, per_group, core, nworkers, nworkers);
+
+ /* Create groups with events that do not overlap. */
+ for (i = 0; i < MAX_GROUPS; i++) {
+ int got = build_group(&grps[i], per_group, i * per_group);
+
+ if (got < per_group) {
+ fprintf(stderr, "SKIP: only %d events for group %d "
+ "(needed %d)\n", got, i, per_group);
+ for (j = 0; j <= i; j++)
+ release_group(&grps[j]);
+ return (77);
+ }
+ }
+
+ for (i = 0; i < MAX_GROUPS; i++) {
+ if (pmc_group_commit(grps[i].gid) < 0) {
+ warn("pmc_group_commit g%d", i);
+ for (j = 0; j < MAX_GROUPS; j++)
+ release_group(&grps[j]);
+ return (1);
+ }
+ grps[i].committed = 1;
+ }
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ if (pmc_attach(grps[i].ids[0], getpid()) < 0) {
+ warn("pmc_attach g%d leader", i);
+ for (int k = 0; k < MAX_GROUPS; k++)
+ release_group(&grps[k]);
+ return (1);
+ }
+
+ /* Set the rotation period to the minimum value. */
+ new_period = 1;
+ s = sizeof(saved_period);
+ saved_period = -1;
+ if (sysctlbyname("kern.hwpmc.mux_period_ms", &saved_period, &s,
+ &new_period, sizeof(new_period)) == 0)
+ printf("set kern.hwpmc.mux_period_ms = %d (was %d)\n",
+ new_period, saved_period);
+ else
+ saved_period = -1;
+
+ /* Set the watchdog timer. */
+ signal(SIGALRM, on_watchdog);
+ alarm(WATCHDOG_SECS);
+
+ /* Start the worker threads. */
+ g_stop = 0;
+ g_running = 0;
+ for (i = 0; i < nworkers; i++)
+ if (pthread_create(&tid[i], NULL, worker,
+ (void *)(intptr_t)(i + 1)) != 0)
+ err(1, "pthread_create %d", i);
+ while (__atomic_load_n(&g_running, __ATOMIC_SEQ_CST) < nworkers)
+ usleep(1000);
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ if (pmc_start(grps[i].ids[0]) < 0) {
+ warn("pmc_start g%d leader", i);
+ g_stop = 1;
+ for (j = 0; j < nworkers; j++)
+ pthread_join(tid[j], NULL);
+ goto restore;
+ }
+
+ /* Phase A: Test the rotation drain. */
+ printf("stressing rotation drain for %d s ...\n", STRESS_SECS);
+ sleep(STRESS_SECS);
+
+ /* Phase B: Test the teardown drain while workers still run. */
+ printf("tearing down while workers still spinning ...\n");
+ for (i = 0; i < MAX_GROUPS; i++)
+ (void)pmc_stop(grps[i].ids[0]);
+ for (i = 0; i < MAX_GROUPS; i++)
+ release_group(&grps[i]);
+
+ /* Stop the worker threads. */
+ g_stop = 1;
+ for (i = 0; i < nworkers; i++)
+ pthread_join(tid[i], NULL);
+
+ alarm(0);
+ if (saved_period >= 0)
+ (void)sysctlbyname("kern.hwpmc.mux_period_ms", NULL, NULL,
+ &saved_period, sizeof(saved_period));
+
+ printf("pmc_mux_drain_test: OK (rotation + teardown drains completed "
+ "against %d pinned RT targets)\n", nworkers);
+ return (0);
+
+restore:
+ alarm(0);
+ for (i = 0; i < MAX_GROUPS; i++)
+ release_group(&grps[i]);
+ if (saved_period >= 0)
+ (void)sysctlbyname("kern.hwpmc.mux_period_ms", NULL, NULL,
+ &saved_period, sizeof(saved_period));
+ return (1);
+}
diff --git a/tools/regression/pmc/pmc_mux_rotation_test.c b/tools/regression/pmc/pmc_mux_rotation_test.c
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmc_mux_rotation_test.c
@@ -0,0 +1,612 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Test PMU multiplex rotation policy.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pmc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAX_PER_GROUP 8
+
+static const char *event_pool[] = {
+ "instructions",
+ "unhalted-cycles",
+ "branches",
+ "branch-misses",
+ "cache-references",
+ "cache-misses",
+ "l1d-loads",
+ "l1d-load-misses",
+ "l2-cache-references",
+ "l2-cache-misses",
+ "dispatch-stalls",
+ "fp-retired",
+ "branches-retired",
+ "de-no-dispatch-per-slot",
+ "ls_alloc_mab_count",
+ "ls_not_halted_cyc",
+ "ls_dispatch.all",
+ "ls_mab_alloc.ls",
+ "ls_mab_alloc.hwpf",
+ "ls_mab_alloc.all",
+ "ls_int_taken",
+ "ls_stlf",
+ /* These are native Zen event names. */
+ "ex_ret_brn",
+ "ex_ret_brn_misp",
+ "ex_ret_brn_tkn",
+ "ex_ret_near_ret",
+ "ex_ret_cond",
+ "ex_ret_ind_brch_instr",
+ "bp_l2_btb_correct",
+ "bp_dyn_ind_pred",
+ "bp_de_redirect",
+ "ls_mab_alloc.load_store_allocations",
+ "ls_dmnd_fills_from_sys.local_l2",
+ "ls_any_fills_from_sys.local_l2",
+};
+#define POOL_SIZE (int)(sizeof(event_pool) / sizeof(event_pool[0]))
+
+#define MUX_PERIOD_MS 10
+
+struct grp {
+ uint32_t gid;
+ int nevents;
+ int committed;
+ pmc_id_t ids[MAX_PER_GROUP];
+};
+
+static int
+is_amd(void)
+{
+ char buf[64];
+ size_t s = sizeof(buf);
+
+ if (sysctlbyname("kern.hwpmc.cpuid", buf, &s, NULL, 0) != 0)
+ return (0);
+ return (strstr(buf, "AuthenticAMD") != NULL ||
+ strstr(buf, "HygonGenuine") != NULL);
+}
+
+/* Return the number of available core hardware counters. */
+static int
+probe_core_pmcs(void)
+{
+ pmc_id_t ids[64];
+ int n = 0;
+
+ while (n < (int)(sizeof(ids) / sizeof(ids[0]))) {
+ if (pmc_allocate("instructions", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &ids[n], 0) < 0)
+ break;
+ n++;
+ }
+ for (int i = 0; i < n; i++)
+ (void)pmc_release(ids[i]);
+ return (n);
+}
+
+static void
+busy_for(uint64_t ns)
+{
+ struct timespec t0, now;
+ volatile uint64_t spin = 0;
+
+ clock_gettime(CLOCK_MONOTONIC, &t0);
+ for (;;) {
+ for (int k = 0; k < 200000; k++)
+ spin++;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ uint64_t elapsed = (uint64_t)(now.tv_sec - t0.tv_sec) *
+ 1000000000ULL + (uint64_t)(now.tv_nsec - t0.tv_nsec);
+ if (elapsed >= ns)
+ return;
+ }
+}
+
+static int
+set_mux_period(int new_ms, int *saved)
+{
+ size_t s = sizeof(*saved);
+
+ *saved = -1;
+ if (sysctlbyname("kern.hwpmc.mux_period_ms", saved, &s,
+ &new_ms, sizeof(new_ms)) != 0) {
+ *saved = -1;
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+restore_mux_period(int saved)
+{
+ if (saved < 0)
+ return;
+ (void)sysctlbyname("kern.hwpmc.mux_period_ms", NULL, NULL,
+ &saved, sizeof(saved));
+}
+
+/* Build a group of n distinct events from the pool. */
+static int
+build_group(struct grp *g, int n, int *cursor, int mux)
+{
+ memset(g, 0, sizeof(*g));
+ if (pmc_group_create(&g->gid) < 0) {
+ warn("pmc_group_create");
+ return (-1);
+ }
+ while (g->nevents < n && *cursor < POOL_SIZE) {
+ uint32_t flags = 0;
+ pmc_id_t id;
+ const char *ev = event_pool[(*cursor)++];
+
+ if (g->nevents == 0 && mux)
+ flags |= PMC_F_GROUP_MUX;
+ if (pmc_allocate_group(ev, PMC_MODE_TC, flags,
+ PMC_CPU_ANY, &id, 0) < 0)
+ continue; /* This model does not support the event. */
+ if (pmc_group_add(g->gid, id, g->nevents == 0) < 0) {
+ (void)pmc_release(id);
+ continue;
+ }
+ g->ids[g->nevents++] = id;
+ }
+ return (g->nevents == n ? 0 : -1);
+}
+
+static void
+destroy_group(struct grp *g)
+{
+ if (g->nevents == 0)
+ return;
+ if (g->committed)
+ (void)pmc_release(g->ids[0]);
+ else {
+ for (int i = 0; i < g->nevents; i++)
+ (void)pmc_release(g->ids[i]);
+ }
+ g->nevents = 0;
+ g->committed = 0;
+}
+
+/* Commit, attach, and start the group. */
+static int
+launch_group(struct grp *g, const char *tag)
+{
+ if (pmc_group_commit(g->gid) < 0) {
+ warn("pmc_group_commit %s", tag);
+ return (-1);
+ }
+ g->committed = 1;
+ if (pmc_attach(g->ids[0], getpid()) < 0) {
+ warn("pmc_attach %s", tag);
+ return (-1);
+ }
+ if (pmc_start(g->ids[0]) < 0) {
+ warn("pmc_start %s", tag);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+grp_times(struct grp *g, uint64_t *enabled, uint64_t *running)
+{
+ struct pmc_group_times t;
+ uint32_t n = 0;
+
+ memset(&t, 0, sizeof(t));
+ if (pmc_group_read(g->ids[0], &n, NULL, &t) < 0)
+ return (-1);
+ *enabled = t.pgt_enabled;
+ *running = t.pgt_running;
+ return (0);
+}
+
+/* Return 1 if the rotation thread exists, 0 if not, -1 on error. */
+static int
+rot_thread_visible(void)
+{
+ FILE *fp;
+ char line[512];
+ int found = 0, sawany = 0;
+
+ fp = popen("ps -axH 2>/dev/null", "r");
+ if (fp == NULL)
+ return (-1);
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ sawany = 1;
+ if (strstr(line, "pmu_rot") != NULL)
+ found = 1;
+ }
+ (void)pclose(fp);
+ return (sawany ? found : -1);
+}
+
+/* Calculate the group size for three oversubscribing groups. */
+static int
+mux_group_size(int core)
+{
+ int s = core / 2;
+
+ if (s < 1 || s > MAX_PER_GROUP || 3 * s <= core)
+ return (-1);
+ return (s);
+}
+
+/* Test 1: Check equal execution ratios across three rotating groups. */
+static int
+test_fairness(int core)
+{
+ struct grp g[3];
+ uint64_t en1[3], ru1[3], en2[3], ru2[3];
+ double ratio[3], lo, hi;
+ int cursor = 0, i, rc = 1, s;
+
+ s = mux_group_size(core);
+ if (s < 0) {
+ printf("SKIP fairness: cannot oversubscribe on %d counters\n",
+ core);
+ return (77);
+ }
+
+ memset(g, 0, sizeof(g));
+ for (i = 0; i < 3; i++) {
+ if (build_group(&g[i], s, &cursor, 1) < 0 ||
+ launch_group(&g[i], "fairness") < 0) {
+ printf("SKIP fairness: could not build/launch "
+ "group %d\n", i);
+ rc = 77;
+ goto out;
+ }
+ }
+
+ busy_for(500ULL * 1000 * 1000);
+ for (i = 0; i < 3; i++)
+ if (grp_times(&g[i], &en1[i], &ru1[i]) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+ busy_for(3000ULL * 1000 * 1000);
+ for (i = 0; i < 3; i++)
+ if (grp_times(&g[i], &en2[i], &ru2[i]) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+
+ lo = 2.0;
+ hi = 0.0;
+ for (i = 0; i < 3; i++) {
+ uint64_t den = en2[i] - en1[i], drn = ru2[i] - ru1[i];
+
+ if (den == 0) {
+ fprintf(stderr, "FAIL fairness: group %d accrued no "
+ "enabled time\n", i);
+ goto out;
+ }
+ ratio[i] = (double)drn / (double)den;
+ printf(" fairness: group %d running/enabled = %.4f\n",
+ i, ratio[i]);
+ if (ratio[i] <= 0.0) {
+ fprintf(stderr, "FAIL fairness: group %d got no "
+ "hardware time\n", i);
+ goto out;
+ }
+ if (ratio[i] < lo)
+ lo = ratio[i];
+ if (ratio[i] > hi)
+ hi = ratio[i];
+ }
+ printf(" fairness: max/min ratio = %.4f (limit 1.25)\n", hi / lo);
+ if (hi / lo > 1.25) {
+ fprintf(stderr, "FAIL fairness: ratio spread %.4f > 1.25\n",
+ hi / lo);
+ goto out;
+ }
+ rc = 0;
+out:
+ for (i = 0; i < 3; i++)
+ destroy_group(&g[i]);
+ return (rc);
+}
+
+/* Test 2: Check that non-multiplex groups are never evicted. */
+static int
+test_pinned(int core)
+{
+ struct grp p, m1, m2;
+ uint64_t pen1, pru1, pen2, pru2, en, ru1a, ru2a, ru1b, ru2b;
+ double pinned_ratio;
+ int cursor = 0, rc = 1, s;
+
+ s = mux_group_size(core);
+ if (s < 0) {
+ printf("SKIP pinned: cannot oversubscribe on %d counters\n",
+ core);
+ return (77);
+ }
+
+ memset(&p, 0, sizeof(p));
+ memset(&m1, 0, sizeof(m1));
+ memset(&m2, 0, sizeof(m2));
+ if (build_group(&p, s, &cursor, 0) < 0 ||
+ launch_group(&p, "pinned") < 0 ||
+ build_group(&m1, s, &cursor, 1) < 0 ||
+ launch_group(&m1, "mux1") < 0 ||
+ build_group(&m2, s, &cursor, 1) < 0 ||
+ launch_group(&m2, "mux2") < 0) {
+ printf("SKIP pinned: could not build/launch groups\n");
+ rc = 77;
+ goto out;
+ }
+
+ busy_for(500ULL * 1000 * 1000);
+ if (grp_times(&p, &pen1, &pru1) < 0 ||
+ grp_times(&m1, &en, &ru1a) < 0 ||
+ grp_times(&m2, &en, &ru1b) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+ busy_for(3000ULL * 1000 * 1000);
+ if (grp_times(&p, &pen2, &pru2) < 0 ||
+ grp_times(&m1, &en, &ru2a) < 0 ||
+ grp_times(&m2, &en, &ru2b) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+
+ if (pen2 <= pen1) {
+ fprintf(stderr, "FAIL pinned: no enabled time accrued\n");
+ goto out;
+ }
+ pinned_ratio = (double)(pru2 - pru1) / (double)(pen2 - pen1);
+ printf(" pinned: non-MUX running/enabled = %.4f, "
+ "MUX deltas = %ju / %ju\n", pinned_ratio,
+ (uintmax_t)(ru2a - ru1a), (uintmax_t)(ru2b - ru1b));
+ if (pinned_ratio < 0.99) {
+ fprintf(stderr, "FAIL pinned: non-MUX group lost hardware "
+ "time (ratio %.4f < 0.99) -- it was evicted\n",
+ pinned_ratio);
+ goto out;
+ }
+ if (ru2a <= ru1a || ru2b <= ru1b) {
+ fprintf(stderr, "FAIL pinned: a MUX group made no progress "
+ "alongside the pinned group\n");
+ goto out;
+ }
+ rc = 0;
+out:
+ destroy_group(&m2);
+ destroy_group(&m1);
+ destroy_group(&p);
+ return (rc);
+}
+
+/* Test 3: Check multi-victim escalation when large groups wait. */
+static int
+test_escalation(int core)
+{
+ struct grp a, b, c;
+ uint64_t en, ru;
+ int cursor = 0, rc = 1, i, sa, sc;
+
+ sa = core / 2;
+ sc = core - sa + 1;
+ if (sa < 1 || sc > MAX_PER_GROUP || sc > core || sa + sa > core) {
+ printf("SKIP escalation: no valid sizes on %d counters\n",
+ core);
+ return (77);
+ }
+
+ memset(&a, 0, sizeof(a));
+ memset(&b, 0, sizeof(b));
+ memset(&c, 0, sizeof(c));
+ if (build_group(&a, sa, &cursor, 1) < 0 ||
+ launch_group(&a, "escA") < 0 ||
+ build_group(&b, sa, &cursor, 1) < 0 ||
+ launch_group(&b, "escB") < 0 ||
+ build_group(&c, sc, &cursor, 1) < 0 ||
+ launch_group(&c, "escC") < 0) {
+ printf("SKIP escalation: could not build/launch groups\n");
+ rc = 77;
+ goto out;
+ }
+
+ printf(" escalation: A=%d B=%d C=%d on %d counters\n",
+ sa, sa, sc, core);
+ for (i = 0; i < 50; i++) {
+ busy_for(200ULL * 1000 * 1000);
+ if (grp_times(&c, &en, &ru) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+ if (ru > 0)
+ break;
+ }
+ printf(" escalation: C running=%ju enabled=%ju after %d ms\n",
+ (uintmax_t)ru, (uintmax_t)en, (i < 50 ? i + 1 : 50) * 200);
+ if (ru == 0) {
+ fprintf(stderr, "FAIL escalation: C never got hardware time "
+ "within 10 s -- multi-victim escalation broken\n");
+ goto out;
+ }
+ rc = 0;
+out:
+ destroy_group(&c);
+ destroy_group(&b);
+ destroy_group(&a);
+ return (rc);
+}
+
+/* Test 4: Check that the rotation thread stops when idle and restarts on new demand. */
+static int
+test_selfstop_rekick(int core)
+{
+ struct grp m1, m2, m3;
+ uint64_t en, ru_a, ru_b, ru;
+ int cursor = 0, rc = 1, vis, i, s;
+
+ s = core / 2 + 1;
+ if (s > MAX_PER_GROUP || 2 * s <= core || s > core) {
+ printf("SKIP self-stop: no valid sizes on %d counters\n",
+ core);
+ return (77);
+ }
+
+ memset(&m1, 0, sizeof(m1));
+ memset(&m2, 0, sizeof(m2));
+ memset(&m3, 0, sizeof(m3));
+ if (build_group(&m1, s, &cursor, 1) < 0 ||
+ launch_group(&m1, "ss1") < 0 ||
+ build_group(&m2, s, &cursor, 1) < 0 ||
+ launch_group(&m2, "ss2") < 0) {
+ printf("SKIP self-stop: could not build/launch groups\n");
+ rc = 77;
+ goto out;
+ }
+
+ busy_for(500ULL * 1000 * 1000);
+ if (grp_times(&m1, &en, &ru_a) < 0 ||
+ grp_times(&m2, &en, &ru_b) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+ if (ru_a == 0 || ru_b == 0) {
+ fprintf(stderr, "FAIL self-stop: rotation between two "
+ "mutually exclusive MUX groups is not happening\n");
+ goto out;
+ }
+ vis = rot_thread_visible();
+ if (vis < 0) {
+ printf("SKIP self-stop: ps -axH unavailable, cannot "
+ "observe kthreads\n");
+ rc = 77;
+ goto out;
+ }
+ if (vis == 0) {
+ fprintf(stderr, "FAIL self-stop: groups rotate but no "
+ "pmu_rot thread is visible in ps -axH\n");
+ goto out;
+ }
+
+ /* The thread must stop when the remaining groups fit. */
+ destroy_group(&m1);
+ usleep(600 * 1000);
+ vis = rot_thread_visible();
+ if (vis != 0) {
+ fprintf(stderr, "FAIL self-stop: pmu_rot thread still "
+ "alive 600 ms after every group fit\n");
+ goto out;
+ }
+ printf(" self-stop: kthread exited after the survivors fit\n");
+
+ /* The thread must restart when a new group is deferred. */
+ if (build_group(&m3, s, &cursor, 1) < 0 ||
+ launch_group(&m3, "ss3") < 0) {
+ printf("SKIP self-stop: could not build/launch third group\n");
+ rc = 77;
+ goto out;
+ }
+ vis = 0;
+ for (i = 0; i < 3 && vis <= 0; i++) {
+ vis = rot_thread_visible();
+ if (vis <= 0)
+ usleep(MUX_PERIOD_MS * 1000);
+ }
+ if (vis <= 0) {
+ fprintf(stderr, "FAIL re-kick: no pmu_rot thread within two "
+ "periods of starting a deferred group\n");
+ goto out;
+ }
+ busy_for(2000ULL * 1000 * 1000);
+ if (grp_times(&m3, &en, &ru) < 0) {
+ warn("pmc_group_read");
+ goto out;
+ }
+ printf(" re-kick: kthread respawned, new group running=%ju\n",
+ (uintmax_t)ru);
+ if (ru == 0) {
+ fprintf(stderr, "FAIL re-kick: respawned rotation never "
+ "placed the new group\n");
+ goto out;
+ }
+ rc = 0;
+out:
+ destroy_group(&m3);
+ destroy_group(&m2);
+ destroy_group(&m1);
+ return (rc);
+}
+
+int
+main(void)
+{
+ int core, saved_period;
+ int rc, failed = 0, passed = 0, skipped = 0;
+
+ if (pmc_init() < 0)
+ err(1, "pmc_init");
+ if (!is_amd()) {
+ printf("SKIP: non-AMD CPU\n");
+ return (77);
+ }
+ core = probe_core_pmcs();
+ if (core < 4) {
+ printf("SKIP: only %d core PMCs available\n", core);
+ return (77);
+ }
+
+ if (set_mux_period(MUX_PERIOD_MS, &saved_period) == 0)
+ printf("set kern.hwpmc.mux_period_ms = %d (was %d)\n",
+ MUX_PERIOD_MS, saved_period);
+ else
+ printf("note: could not write kern.hwpmc.mux_period_ms; "
+ "using kernel default\n");
+
+ static const struct {
+ const char *name;
+ int (*fn)(int);
+ } tests[] = {
+ { "fairness", test_fairness },
+ { "pinned", test_pinned },
+ { "escalation", test_escalation },
+ { "self-stop/re-kick", test_selfstop_rekick },
+ };
+ for (size_t t = 0; t < sizeof(tests) / sizeof(tests[0]); t++) {
+ printf("==> %s\n", tests[t].name);
+ rc = tests[t].fn(core);
+ if (rc == 0) {
+ printf("PASS: %s\n", tests[t].name);
+ passed++;
+ } else if (rc == 77) {
+ skipped++;
+ } else {
+ printf("FAIL: %s\n", tests[t].name);
+ failed++;
+ }
+ }
+
+ restore_mux_period(saved_period);
+
+ printf("\npmc_mux_rotation_test: %d passed, %d failed, %d skipped "
+ "(%d HW core counters, %d-ms rotation floor)\n",
+ passed, failed, skipped, core, MUX_PERIOD_MS);
+ if (failed > 0)
+ return (1);
+ if (passed == 0)
+ return (77);
+ return (0);
+}

File Metadata

Mime Type
text/plain
Expires
Wed, Sep 16, 4:45 PM (2 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39004123
Default Alt Text
D59366.diff (24 KB)

Event Timeline