Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F160803481
D57638.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
D57638.diff
View Options
diff --git a/tools/regression/pmc/Makefile b/tools/regression/pmc/Makefile
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/Makefile
@@ -0,0 +1,75 @@
+# Build the hwpmc grouping/multiplex regression tests.
+#
+# Usage from this directory:
+# make # builds all three test binaries
+# make check # userland-only test (no root needed)
+# make check-root # in-kernel tests (need hwpmc(4) + root)
+# make clean
+#
+# Plain Makefile (no <bsd.prog.mk>) so it works the same in-tree and
+# out-of-tree. libpmcstat is INTERNALLIB on FreeBSD - it has no
+# installed .so/.a - so test_libpmcstat_group compiles libpmcstat_group.c
+# directly rather than -lpmcstat.
+
+# Disable bmake's auto-objdir; build in-place next to the sources so
+# the recipes can find pmc_group_test.c et al with relative paths and
+# the resulting binaries land where `make check` expects them.
+.OBJDIR: ${.CURDIR}
+
+CC?= cc
+
+# Auto-detects when this directory still lives at tools/regression/pmc
+# in a FreeBSD source tree; override on the command line if copied.
+SRCROOT?= ${.CURDIR}/../../..
+
+LIBPMCSTAT_SRC= ${SRCROOT}/lib/libpmcstat
+LIBPMCSTAT_INC= -I${LIBPMCSTAT_SRC}
+
+CFLAGS+= -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
+
+# Userland-only test (parser unit test, no PMU access).
+UTESTS= test_libpmcstat_group
+
+ALL= ${KTESTS} ${UTESTS}
+
+.PHONY: all check check-root clean
+
+all: ${ALL}
+
+pmc_group_test: ${.CURDIR}/pmc_group_test.c
+ ${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc
+
+pmc_mux_test: ${.CURDIR}/pmc_mux_test.c
+ ${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc
+
+pmc_mux_works_test: ${.CURDIR}/pmc_mux_works_test.c
+ ${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc
+
+# libpmcstat is INTERNALLIB (no .so installed), so build the parser TU
+# directly into the test binary.
+test_libpmcstat_group: ${.CURDIR}/test_libpmcstat_group.c \
+ ${LIBPMCSTAT_SRC}/libpmcstat_group.c
+ ${CC} ${CFLAGS} ${LIBPMCSTAT_INC} -o ${.TARGET} ${.ALLSRC} -lpmc
+
+# Userland-only check: brace-list parser, no PMU.
+check: test_libpmcstat_group
+ ./test_libpmcstat_group
+
+# In-kernel check: requires `kldload hwpmc` and root.
+check-root: ${KTESTS}
+ @echo "==> pmc_group_test"
+ ./pmc_group_test
+ @echo "==> pmc_mux_test"
+ ./pmc_mux_test
+ @echo "==> pmc_mux_works_test"
+ ./pmc_mux_works_test
+ @if [ -r ${.CURDIR}/pmcstat_test.sh ]; then \
+ echo "==> pmcstat(8) shell tests"; \
+ sh ${.CURDIR}/pmcstat_test.sh; \
+ fi
+
+clean:
+ rm -f ${ALL} *.o *.core
diff --git a/tools/regression/pmc/pmc_group_test.c b/tools/regression/pmc/pmc_group_test.c
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmc_group_test.c
@@ -0,0 +1,251 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Regression test for hwpmc grouping (phase 1).
+ *
+ * Build: cc -o pmc_group_test pmc_group_test.c -lpmc
+ * Run: sudo ./pmc_group_test (requires hwpmc loaded, AMD CPU)
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pmc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+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 how many process-mode core PMCs we can simultaneously allocate
+ * with the canonical "instructions" event. Returns the count without
+ * leaving any allocations behind. Used to size the rest of the test
+ * dynamically per-CPU instead of relying on pmc_npmc(0), which sums
+ * SOFT/TSC/K8/IBS classes and is therefore meaningless for sizing a
+ * single core-class group.
+ */
+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 int
+test_basic_group(void)
+{
+ uint32_t gid;
+ pmc_id_t pmc0, pmc1, pmc2;
+ pmc_value_t v0, v1, v2;
+ const char *ev0 = "instructions";
+ const char *ev1 = "unhalted-cycles";
+ const char *ev2 = "branches";
+ volatile uint64_t spin;
+
+ if (pmc_group_create(&gid) < 0) {
+ warn("pmc_group_create");
+ return (1);
+ }
+ if (pmc_allocate_group(ev0, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &pmc0, 0) < 0) {
+ warn("allocate %s", ev0);
+ return (1);
+ }
+ if (pmc_allocate_group(ev1, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &pmc1, 0) < 0) {
+ warn("allocate %s", ev1);
+ return (1);
+ }
+ if (pmc_allocate_group(ev2, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &pmc2, 0) < 0) {
+ warn("allocate %s", ev2);
+ return (1);
+ }
+ if (pmc_group_add(gid, pmc0, 1) < 0) {
+ warn("group_add leader");
+ return (1);
+ }
+ if (pmc_group_add(gid, pmc1, 0) < 0) {
+ warn("group_add sibling 1");
+ return (1);
+ }
+ if (pmc_group_add(gid, pmc2, 0) < 0) {
+ warn("group_add sibling 2");
+ return (1);
+ }
+ if (pmc_group_commit(gid) < 0) {
+ warn("group_commit");
+ return (1);
+ }
+
+ /*
+ * Process-mode PMCs require an explicit attach before start.
+ * pmu_group_target_proc() now insists on an attached target proc
+ * (the owner is intentionally NOT used as a fallback because that
+ * confused csw_in/out under the multiplex rework), so calling
+ * pmc_start without first pmc_attach'ing to ourselves now returns
+ * EINVAL. Attach every sibling so the whole group can bind.
+ */
+ if (pmc_attach(pmc0, getpid()) < 0) {
+ warn("pmc_attach pmc0");
+ return (1);
+ }
+ if (pmc_attach(pmc1, getpid()) < 0) {
+ warn("pmc_attach pmc1");
+ return (1);
+ }
+ if (pmc_attach(pmc2, getpid()) < 0) {
+ warn("pmc_attach pmc2");
+ return (1);
+ }
+
+ if (pmc_start(pmc0) < 0) {
+ warn("pmc_start leader");
+ return (1);
+ }
+ if (pmc_start(pmc1) < 0) {
+ warn("pmc_start sibling 1");
+ return (1);
+ }
+ if (pmc_start(pmc2) < 0) {
+ warn("pmc_start sibling 2");
+ return (1);
+ }
+
+ for (spin = 0; spin < 100000000ULL; spin++)
+ ;
+
+ if (pmc_read(pmc0, &v0) < 0 || pmc_read(pmc1, &v1) < 0 ||
+ pmc_read(pmc2, &v2) < 0) {
+ warn("pmc_read");
+ return (1);
+ }
+ (void)pmc_stop(pmc0);
+ (void)pmc_stop(pmc1);
+ (void)pmc_stop(pmc2);
+ printf("3-event group: pmc0=%ju pmc1=%ju pmc2=%ju\n",
+ (uintmax_t)v0, (uintmax_t)v1, (uintmax_t)v2);
+ if (v0 == 0 || v1 == 0 || v2 == 0) {
+ fprintf(stderr, "FAIL: at least one sibling never counted\n");
+ (void)pmc_release(pmc0);
+ (void)pmc_release(pmc1);
+ (void)pmc_release(pmc2);
+ return (1);
+ }
+ (void)pmc_release(pmc0);
+ (void)pmc_release(pmc1);
+ (void)pmc_release(pmc2);
+ return (0);
+}
+
+/*
+ * The atomic-group scheduler must reject a single group whose event
+ * count exceeds the core HW counter pool. Within-group placement is
+ * all-or-none, so a group that cannot fit at commit MUST fail rather
+ * than silently get split across rotation windows.
+ *
+ * Sizing: query the actual per-class core count via probe_core_pmcs()
+ * (Zen5 = 6, Zen3/4 = 6, Zen6 = up to 12, EPYC = vendor-dependent),
+ * then attempt a group of (core + 2) events. Hard-coding 16 missed
+ * Zen6 and any future generation that exceeds it.
+ */
+static int
+test_oversubscription_rejected(void)
+{
+ uint32_t gid;
+ pmc_id_t *ids;
+ int core, target, i, allocated, err;
+
+ core = probe_core_pmcs();
+ if (core <= 0) {
+ printf("SKIP: probe_core_pmcs returned %d\n", core);
+ return (0);
+ }
+ target = core + 2;
+ ids = calloc(target, sizeof(*ids));
+ if (ids == NULL)
+ return (1);
+
+ if (pmc_group_create(&gid) < 0) {
+ free(ids);
+ return (1);
+ }
+ allocated = 0;
+ for (i = 0; i < target; i++) {
+ if (pmc_allocate_group("instructions", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &ids[i], 0) < 0)
+ break;
+ if (pmc_group_add(gid, ids[i], i == 0) < 0)
+ break;
+ allocated++;
+ }
+ if (allocated <= core) {
+ fprintf(stderr,
+ "SKIP: only allocated %d events for a %d-counter pool; "
+ "cannot oversubscribe\n", allocated, core);
+ for (i = 0; i < allocated; i++)
+ (void)pmc_release(ids[i]);
+ free(ids);
+ return (0);
+ }
+ err = pmc_group_commit(gid);
+ for (i = 0; i < allocated; i++)
+ (void)pmc_release(ids[i]);
+ free(ids);
+ if (err == 0) {
+ fprintf(stderr,
+ "FAIL: oversubscribed commit succeeded (allocated=%d "
+ "core_pool=%d)\n", allocated, core);
+ return (1);
+ }
+ if (errno != ENOSPC && errno != EOPNOTSUPP) {
+ fprintf(stderr,
+ "FAIL: expected ENOSPC/EOPNOTSUPP got errno=%d\n", errno);
+ return (1);
+ }
+ printf("oversubscription rejected (allocated=%d core_pool=%d "
+ "errno=%d)\n", allocated, core, errno);
+ return (0);
+}
+
+int
+main(void)
+{
+ if (pmc_init() < 0)
+ err(1, "pmc_init");
+ if (!is_amd()) {
+ printf("SKIP: non-AMD CPU\n");
+ return (77);
+ }
+ if (test_basic_group() != 0)
+ return (1);
+ if (test_oversubscription_rejected() != 0)
+ return (1);
+ printf("pmc_group_test: OK\n");
+ return (0);
+}
diff --git a/tools/regression/pmc/pmcstat_test.sh b/tools/regression/pmc/pmcstat_test.sh
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmcstat_test.sh
@@ -0,0 +1,271 @@
+#!/bin/sh
+#
+# pmcstat(8) regression tests for -b / {a,b,c} group syntax.
+#
+# Must run as root with hwpmc(4) loaded on an AMD CPU. A workload
+# program is needed; we use /usr/bin/yes piped to /dev/null so each
+# test has a few seconds of busy CPU to collect counts on.
+#
+# Each test prints a "PASS:" or "FAIL:" line and an exit-status row
+# at the end. The script returns 0 only if every case passes.
+#
+# Usage:
+# sudo sh tools/regression/pmc/pmcstat_test.sh
+#
+
+set -u
+
+PMCSTAT=${PMCSTAT:-/usr/sbin/pmcstat}
+WORKLOAD_SECS=${WORKLOAD_SECS:-3}
+LOGDIR=$(mktemp -d -t pmcstat_test) || exit 1
+SUMMARY="${LOGDIR}/summary.log"
+PASS=0
+FAIL=0
+
+trap "rm -rf ${LOGDIR}" EXIT
+
+note() { printf '%s\n' "$*" | tee -a "${SUMMARY}"; }
+fail() { note "FAIL: $*"; FAIL=$((FAIL + 1)); }
+pass() { note "PASS: $*"; PASS=$((PASS + 1)); }
+
+# Run a workload that produces a few hundred million instructions so
+# every counter in the group has something to read.
+busy_workload() {
+ # `yes` is CPU-bound; redirect its stdout/stderr.
+ yes >/dev/null 2>&1 &
+ pid=$!
+ sleep "${WORKLOAD_SECS}"
+ kill -9 "${pid}" 2>/dev/null
+ wait "${pid}" 2>/dev/null
+}
+
+require_root() {
+ if [ "$(id -u)" -ne 0 ]; then
+ echo "ERROR: must run as root" >&2
+ exit 2
+ fi
+}
+
+require_hwpmc() {
+ if ! kldstat -q -m hwpmc; then
+ if ! kldload hwpmc 2>/dev/null; then
+ echo "ERROR: hwpmc(4) is not loaded and could not be loaded" >&2
+ exit 2
+ fi
+ fi
+}
+
+require_amd() {
+ cpuid=$(sysctl -n kern.hwpmc.cpuid 2>/dev/null || echo unknown)
+ case "${cpuid}" in
+ *AuthenticAMD*|*HygonGenuine*) ;;
+ *) echo "SKIP: AMD-only test, cpuid=${cpuid}"; exit 77 ;;
+ esac
+}
+
+# t_basic_group: three events, one group, process-counting mode.
+# Verifies -b accepts {ev1,ev2,ev3} and pmcstat exits 0 with non-empty output.
+t_basic_group() {
+ out="${LOGDIR}/basic.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles,ls_dispatch.all}' \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "basic_group: pmcstat exit=${rc}; see ${out}"
+ return
+ fi
+ if ! grep -q '[0-9]' "${out}"; then
+ fail "basic_group: no numeric counter output in ${out}"
+ return
+ fi
+ pass "basic_group"
+}
+
+# t_two_groups: two independent process-counting groups.
+t_two_groups() {
+ out="${LOGDIR}/two.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles}' \
+ -p '{ls_alloc_mab_count, ls_not_halted_cyc, ls_dispatch.all}' \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "two_groups: pmcstat exit=${rc}; see ${out}"
+ return
+ fi
+ pass "two_groups"
+}
+
+# t_oversubscribe: 7 events on a typical 6-counter Zen forces failure.
+# Requires PMC_F_GROUP_MUX support in the kernel (commit fails otherwise).
+t_oversubscribe() {
+ out="${LOGDIR}/mux.out"
+ ${PMCSTAT} -b -O /dev/null \
+ -p '{instructions,unhalted-cycles,ls_alloc_mab_count, ls_not_halted_cyc, ls_dispatch.all,ls_stlf, ls_int_taken}' \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -eq 0 ]; then
+ fail "oversubscribe: should have rejected"
+ return
+ fi
+ if [ "${rc}" -ge 128 ]; then
+ fail "oversubscribe: signal exit=${rc}"
+ return
+ fi
+ pass "oversubscribe"
+}
+
+# t_system_mode: system-wide counting, single CPU.
+t_system_mode() {
+ out="${LOGDIR}/sys.out"
+ ${PMCSTAT} -b -c 0 \
+ -s '{instructions,unhalted-cycles}' \
+ -- sleep "${WORKLOAD_SECS}" \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "system_mode: pmcstat exit=${rc}; see ${out}"
+ return
+ fi
+ pass "system_mode"
+}
+
+# t_sampling_group: process sampling with a group leader.
+t_sampling_group() {
+ log="${LOGDIR}/samples.log"
+ out="${LOGDIR}/sampling.out"
+ ${PMCSTAT} -b \
+ -P '{instructions,unhalted-cycles,ls_dispatch.all}' \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "sampling_group: pmcstat exit=${rc}; see ${out}"
+ return
+ fi
+ if [ ! -s "${log}" ]; then
+ fail "sampling_group: empty log ${log}"
+ return
+ fi
+ pass "sampling_group"
+}
+
+# t_compat_no_b: without -b, brace text is treated as a literal event
+# spec by pmc_allocate which should reject it; we expect non-zero
+# exit and a useful message, never a kernel panic / segfault.
+t_compat_no_b() {
+ out="${LOGDIR}/compat_no_b.out"
+ ${PMCSTAT} \
+ -p '{instructions,unhalted-cycles}' \
+ -- /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ # Without -b the literal "{instructions,...}" string is invalid
+ # event-spec syntax, so pmc_allocate must fail; pmcstat reports it
+ # via err(EX_OSERR, ...). Exit must be non-zero, NOT a SIGSEGV.
+ if [ "${rc}" -eq 0 ]; then
+ fail "compat_no_b: should have rejected brace without -b"
+ return
+ fi
+ if [ "${rc}" -ge 128 ]; then
+ fail "compat_no_b: pmcstat killed by signal (exit=${rc})"
+ return
+ fi
+ pass "compat_no_b"
+}
+
+# t_compat_legacy: classic non-grouped pmcstat invocation must still work.
+t_compat_legacy() {
+ out="${LOGDIR}/legacy.out"
+ ${PMCSTAT} \
+ -p instructions -p unhalted-cycles \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "compat_legacy: exit=${rc}; see ${out}"
+ return
+ fi
+ pass "compat_legacy"
+}
+
+# t_single_brace: {ev} (one element) must fall through to single-event
+# behaviour without erroring.
+t_single_brace() {
+ out="${LOGDIR}/single.out"
+ ${PMCSTAT} -b \
+ -p '{instructions}' \
+ -- /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "single_brace: exit=${rc}; see ${out}"
+ return
+ fi
+ pass "single_brace"
+}
+
+# t_malformed_brace: missing closing '}' must be rejected with EX_USAGE
+# (64), never a crash.
+t_malformed_brace() {
+ out="${LOGDIR}/malformed.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles' \
+ -- /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -eq 0 ]; then
+ fail "malformed_brace: should have rejected"
+ return
+ fi
+ if [ "${rc}" -ge 128 ]; then
+ fail "malformed_brace: signal exit=${rc}"
+ return
+ fi
+ pass "malformed_brace"
+}
+
+# t_mixed: one grouped -p and one ungrouped -p in the same invocation.
+t_mixed() {
+ out="${LOGDIR}/mixed.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles}' \
+ -p ls_dispatch.all \
+ -- sh -c /usr/bin/true \
+ >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ fail "mixed: exit=${rc}; see ${out}"
+ return
+ fi
+ pass "mixed"
+}
+
+main() {
+ require_root
+ require_hwpmc
+ require_amd
+
+ note "pmcstat(8) regression tests; logdir=${LOGDIR}"
+
+ t_compat_legacy
+ t_basic_group
+ t_two_groups
+ t_system_mode
+ t_sampling_group
+ t_single_brace
+ t_mixed
+ t_compat_no_b
+ t_malformed_brace
+ t_oversubscribe
+
+ note "------------------------------------"
+ note "PASS: ${PASS} FAIL: ${FAIL}"
+ [ "${FAIL}" -eq 0 ]
+}
+
+main "$@"
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jun 29, 1:25 AM (11 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34448025
Default Alt Text
D57638.diff (15 KB)
Attached To
Mode
D57638: tools/regression/pmc: hwpmc grouping regression test
Attached
Detach File
Event Timeline
Log In to Comment