Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F162920865
D57640.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D57640.diff
View Options
diff --git a/tools/regression/pmc/pmc_mux_works_test.c b/tools/regression/pmc/pmc_mux_works_test.c
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmc_mux_works_test.c
@@ -0,0 +1,467 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Positive regression test for hwpmc PMU multiplexing under the new
+ * strictly-atomic group scheduler.
+ *
+ * Two invariants must hold:
+ *
+ * 1. Within-group all-or-none. Every event in a group is either
+ * simultaneously bound to a HW counter or simultaneously deferred.
+ * Siblings of one group never see a partial schedule.
+ *
+ * 2. Inter-group rotation. When the union of two committed groups
+ * exceeds the HW counter pool, the per-pp rotation kthread must
+ * cycle whole groups in/out so both eventually progress.
+ *
+ * Strategy:
+ * - Allocate two groups whose combined event count > npmc but each
+ * group individually <= npmc, with PMC_F_GROUP_MUX on each leader.
+ * - Tighten kern.hwpmc.mux_period_ms so rotation is fast.
+ * - Start both groups, run a busy workload, snapshot.
+ * - Demand:
+ * (a) every event in every group progressed (rotation is fair);
+ * (b) within each group, all siblings progressed at comparable
+ * rates (all-or-none atomicity, not partial placement).
+ *
+ * Build: cc -o pmc_mux_works_test pmc_mux_works_test.c -lpmc
+ * Run: sudo ./pmc_mux_works_test (requires hwpmc loaded, AMD CPU)
+ *
+ * Exit codes: 0 = pass, 1 = fail, 77 = skip.
+ */
+
+#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_GROUPS 2
+#define MAX_PER_GROUP 8
+#define MAX_EVENTS (MAX_GROUPS * MAX_PER_GROUP)
+
+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]))
+
+#define MUX_PERIOD_MS 10
+#define WARMUP_NS (200ULL * 1000 * 1000)
+#define WINDOW_NS (1200ULL * 1000 * 1000)
+
+struct pmu_grp {
+ uint32_t gid;
+ int nevents;
+ pmc_id_t ids[MAX_PER_GROUP];
+ const char *names[MAX_PER_GROUP];
+ pmc_value_t v1[MAX_PER_GROUP];
+ pmc_value_t v2[MAX_PER_GROUP];
+ pmc_value_t vfin[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);
+}
+
+/*
+ * Probe the actual per-class core PMC capacity. pmc_npmc(0) sums all
+ * classes (SOFT/TSC/K8/IBS), which on Zen5 is 47 -- meaningless for
+ * sizing a core-class group. The real constraint is the number of
+ * core counters (Zen2/3/4/5: 6, Zen6: up to 12).
+ */
+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));
+}
+
+static int
+build_group(struct pmu_grp *g, int n_target, int pool_start, int pool_end)
+{
+ int i;
+
+ if (pmc_group_create(&g->gid) < 0) {
+ warn("pmc_group_create");
+ return (-1);
+ }
+ g->nevents = 0;
+ for (i = pool_start; i < pool_end && 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) {
+ /* Event unsupported on this CPU model -- skip. */
+ 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;
+
+ for (i = 0; i < g->nevents; i++)
+ (void)pmc_release(g->ids[i]);
+ g->nevents = 0;
+}
+
+int
+main(void)
+{
+ struct pmu_grp grps[MAX_GROUPS];
+ int core, saved_period;
+ int per_group, total, i, j, rc;
+
+ memset(grps, 0, sizeof(grps));
+
+ 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 < 2) {
+ printf("SKIP: only %d core PMCs available\n", core);
+ return (77);
+ }
+
+ /*
+ * Pick per_group so that each group fits the core pool but two
+ * groups together oversubscribe. Concretely:
+ * core=6 (Zen3/4/5) -> per_group=4, total=8 > 6 (mux)
+ * core=8 -> per_group=5, total=10 > 8 (mux)
+ * core=12 (Zen6 perf core) -> per_group=8, total=16 > 12 (mux)
+ * core=4 -> per_group=2, total=4 = 4 (skip)
+ * The per_group computation is (core*2)/3 (integer-truncated)
+ * which keeps single-group commits safe but guarantees inter-
+ * group oversubscription when core >= 5.
+ */
+ 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);
+ }
+
+ /* Build the groups out of disjoint slices of event_pool[]. */
+ int pool_used = 0;
+ for (i = 0; i < MAX_GROUPS; i++) {
+ int got = build_group(&grps[i], per_group, pool_used,
+ POOL_SIZE);
+ if (got < per_group) {
+ fprintf(stderr,
+ "SKIP: only %d events available for group %d "
+ "(needed %d)\n", got, i, per_group);
+ for (j = 0; j <= i; j++)
+ release_group(&grps[j]);
+ return (77);
+ }
+ /*
+ * Each event in event_pool[] only allocates once on a
+ * given CPU; advance the cursor by the actual number of
+ * pool entries we consumed (some may have failed
+ * allocate). Conservatively advance by per_group * 2 so
+ * the next group does not race for the same slots.
+ */
+ pool_used += per_group;
+ if (pool_used >= POOL_SIZE) {
+ fprintf(stderr, "SKIP: ran out of distinct events\n");
+ for (j = 0; j <= i; j++)
+ release_group(&grps[j]);
+ return (77);
+ }
+ }
+
+ printf("inter-group multiplex test: %d groups x %d events on "
+ "%d HW core counters\n", MAX_GROUPS, per_group, core);
+
+ 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);
+ }
+ }
+
+ /*
+ * Process-mode PMCs require an explicit attach before start;
+ * the kernel's pmu_group_target_proc() resolves the target proc
+ * exclusively from pm_targets and pg_attach_proc, never the
+ * owner. Attach every sibling so the whole group binds when
+ * we start the leader.
+ */
+ for (i = 0; i < MAX_GROUPS; i++) {
+ for (j = 0; j < grps[i].nevents; j++) {
+ if (pmc_attach(grps[i].ids[j], getpid()) < 0) {
+ warn("pmc_attach group %d sibling %d", i, j);
+ for (int k = 0; k < MAX_GROUPS; k++)
+ release_group(&grps[k]);
+ return (1);
+ }
+ }
+ }
+
+ 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");
+
+ for (i = 0; i < MAX_GROUPS; i++) {
+ if (pmc_start(grps[i].ids[0]) < 0) {
+ warn("pmc_start group %d leader", i);
+ restore_mux_period(saved_period);
+ for (j = 0; j < MAX_GROUPS; j++)
+ release_group(&grps[j]);
+ return (1);
+ }
+ }
+
+ busy_for(WARMUP_NS);
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ for (j = 0; j < grps[i].nevents; j++) {
+ grps[i].v1[j] = 0;
+ (void)pmc_read(grps[i].ids[j], &grps[i].v1[j]);
+ }
+
+ busy_for(WINDOW_NS);
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ for (j = 0; j < grps[i].nevents; j++) {
+ grps[i].v2[j] = 0;
+ (void)pmc_read(grps[i].ids[j], &grps[i].v2[j]);
+ }
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ (void)pmc_stop(grps[i].ids[0]);
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ for (j = 0; j < grps[i].nevents; j++) {
+ grps[i].vfin[j] = 0;
+ (void)pmc_read(grps[i].ids[j], &grps[i].vfin[j]);
+ }
+
+ /*
+ * Validation.
+ * (a) Every sibling in every group must have advanced strictly
+ * between snapshot 1 and snapshot 2. If rotation is broken
+ * one entire group will sit at zero deltas.
+ * (b) Within each group, the spread of deltas across siblings
+ * must be modest. All-or-none atomic placement means every
+ * sibling sees the same set of rotation windows, so their
+ * deltas should differ only by their natural per-event
+ * count rates. We bound the ratio min/max >= 1/256, which
+ * is loose enough to pass even very different events
+ * (cache-misses vs instructions) but still catches the
+ * failure mode where some siblings of a group are stuck
+ * deferred while their peers are running.
+ */
+ rc = 0;
+ pmc_value_t group_cycles[MAX_GROUPS] = { 0 };
+ for (i = 0; i < MAX_GROUPS; i++) {
+ printf("\nGroup %u (gid=%u):\n", i, grps[i].gid);
+ printf(" %-26s %16s %16s %16s %16s\n",
+ "event", "snapshot1", "snapshot2", "final", "delta");
+ int progressed = 0, nonzero = 0;
+ for (j = 0; j < grps[i].nevents; j++) {
+ pmc_value_t d = grps[i].v2[j] > grps[i].v1[j] ?
+ grps[i].v2[j] - grps[i].v1[j] : 0;
+ printf(" %-26s %16ju %16ju %16ju %16ju\n",
+ grps[i].names[j],
+ (uintmax_t)grps[i].v1[j],
+ (uintmax_t)grps[i].v2[j],
+ (uintmax_t)grps[i].vfin[j],
+ (uintmax_t)d);
+ if (d > 0)
+ progressed++;
+ if (grps[i].vfin[j] > 0)
+ nonzero++;
+ /*
+ * Track the highest-rate event per group as a
+ * proxy for "how much HW time did this group
+ * actually get". cycles or instructions in a
+ * busy-spin loop dominate; the ratio across
+ * groups gives us a fairness proxy without
+ * having to name a specific event.
+ */
+ if (d > group_cycles[i])
+ group_cycles[i] = d;
+ }
+
+ /*
+ * Within-group all-or-none atomicity = every sibling
+ * progressed AND every sibling finished with a non-zero
+ * count. We deliberately do NOT enforce a ratio across
+ * siblings here: a group can legitimately mix events
+ * with vastly different natural rates (e.g. cycles vs
+ * a rare ls_smi_rx subset), and a tight ratio test would
+ * false-positive on rate variance instead of catching
+ * real "sibling stuck deferred" bugs. The progressed
+ * + nonzero pair already catches the real failure mode.
+ */
+ if (nonzero != grps[i].nevents) {
+ fprintf(stderr,
+ "FAIL: group %d: %d/%d siblings finished with "
+ "zero count -- within-group atomicity broken\n",
+ i, grps[i].nevents - nonzero, grps[i].nevents);
+ rc = 1;
+ }
+ if (progressed != grps[i].nevents) {
+ fprintf(stderr,
+ "FAIL: group %d: %d/%d siblings did not advance "
+ "between snapshots -- inter-group rotation is "
+ "not reaching them\n", i,
+ grps[i].nevents - progressed, grps[i].nevents);
+ rc = 1;
+ }
+ }
+
+ /*
+ * Inter-group fairness check. Rotation is round-robin with
+ * equal-length windows, so over a 1.2-second window with
+ * 10-ms ticks each group should get ~50% of HW time. If
+ * one group's max delta is more than 64x the other's, the
+ * rotation kthread is starving one group. Threshold 64
+ * leaves headroom for first-window startup transients while
+ * still catching the "Group 0 got 2 us / Group 1 got 1 s"
+ * failure mode.
+ */
+ if (MAX_GROUPS == 2 && group_cycles[0] > 0 && group_cycles[1] > 0) {
+ pmc_value_t hi = group_cycles[0] > group_cycles[1] ?
+ group_cycles[0] : group_cycles[1];
+ pmc_value_t lo = group_cycles[0] < group_cycles[1] ?
+ group_cycles[0] : group_cycles[1];
+ printf("\nfairness: max-rate per group: g0=%ju g1=%ju "
+ "(ratio hi/lo = %ju)\n",
+ (uintmax_t)group_cycles[0],
+ (uintmax_t)group_cycles[1],
+ (uintmax_t)(hi / lo));
+ if (hi / lo > 64) {
+ fprintf(stderr,
+ "FAIL: inter-group rotation is unfair: "
+ "one group got %jux more HW time than the "
+ "other\n", (uintmax_t)(hi / lo));
+ rc = 1;
+ }
+ }
+
+ for (i = 0; i < MAX_GROUPS; i++)
+ release_group(&grps[i]);
+ restore_mux_period(saved_period);
+
+ if (rc == 0)
+ printf("\npmc_mux_works_test: OK (%d groups x %d events on "
+ "%d HW core counters, %d-ms rotation)\n",
+ MAX_GROUPS, per_group, core, MUX_PERIOD_MS);
+ return (rc);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jul 19, 7:52 AM (16 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34068309
Default Alt Text
D57640.diff (13 KB)
Attached To
Mode
D57640: tools/regression/pmc: hwpmc multiplex positive-path test
Attached
Detach File
Event Timeline
Log In to Comment