Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176480253
D59368.id189832.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D59368.id189832.diff
View Options
diff --git a/tools/regression/pmc/Makefile b/tools/regression/pmc/Makefile
--- a/tools/regression/pmc/Makefile
+++ b/tools/regression/pmc/Makefile
@@ -39,7 +39,8 @@
pmc_mux_rotation_test pmc_mux_drain_test pmc_group_fuzz
# Userland-only tests (no PMU access).
-UTESTS= test_libpmcstat_group
+UTESTS= test_libpmcstat_group \
+ pmcstat_mux_warning_shim.so
PMCSTAT?= /usr/sbin/pmcstat
@@ -76,9 +77,16 @@
pmc_group_fuzz: ${.CURDIR}/pmc_group_fuzz.c
${CC} ${CFLAGS} -o ${.TARGET} ${.ALLSRC} -lpmc
+pmcstat_mux_warning_shim.so: ${.CURDIR}/pmcstat_mux_warning_shim.c
+ ${CC} ${CFLAGS} -fPIC -shared ${LIBPMCSTAT_INC} \
+ -I${SRCROOT}/sys -o ${.TARGET} ${.ALLSRC}
+
# These are userland-only checks: parsers and pmcstat warning policy.
check: ${UTESTS}
./test_libpmcstat_group
+ PMCSTAT=${PMCSTAT} \
+ PMCSTAT_WARNING_SHIM=${.CURDIR}/pmcstat_mux_warning_shim.so \
+ sh ${.CURDIR}/pmcstat_mux_warning_test.sh
# This in-kernel check needs `kldload hwpmc` and root.
check-root: ${KTESTS}
diff --git a/tools/regression/pmc/pmcstat_mux_warning_shim.c b/tools/regression/pmc/pmcstat_mux_warning_shim.c
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmcstat_mux_warning_shim.c
@@ -0,0 +1,311 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Deterministic libpmc/sysctl boundary for pmcstat multiplex-warning tests.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <pmc.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static pmc_id_t next_pmcid = 1;
+
+static int
+copy_sysctl_value(void *oldp, size_t *oldlenp, const void *value,
+ size_t value_size)
+{
+
+ if (oldp == NULL || oldlenp == NULL || *oldlenp < value_size) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ memcpy(oldp, value, value_size);
+ *oldlenp = value_size;
+ return (0);
+}
+
+static int
+read_time(const char *name, uint64_t *value)
+{
+ const char *text;
+ char *end;
+ unsigned long long parsed;
+
+ text = getenv(name);
+ if (text == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ errno = 0;
+ parsed = strtoull(text, &end, 0);
+ if (errno != 0 || *text == '\0' || *end != '\0') {
+ errno = EINVAL;
+ return (-1);
+ }
+ *value = parsed;
+ return (0);
+}
+
+int
+sysctlbyname(const char *name, void *oldp, size_t *oldlenp,
+ const void *newp, size_t newlen)
+{
+ uint64_t freq;
+ int value;
+
+ if (newp != NULL || newlen != 0) {
+ errno = EPERM;
+ return (-1);
+ }
+ if (strcmp(name, "vm.ndomains") == 0) {
+ value = 1;
+ return (copy_sysctl_value(oldp, oldlenp, &value,
+ sizeof(value)));
+ }
+ if (strcmp(name, "machdep.tsc_freq") == 0) {
+ freq = 1000;
+ return (copy_sysctl_value(oldp, oldlenp, &freq, sizeof(freq)));
+ }
+ if (strcmp(name, "kern.hwpmc.mux_period_ms") == 0) {
+ value = 1;
+ return (copy_sysctl_value(oldp, oldlenp, &value,
+ sizeof(value)));
+ }
+ errno = ENOENT;
+ return (-1);
+}
+
+int
+pmc_init(void)
+{
+
+ return (0);
+}
+
+int
+pmc_npmc(int cpu)
+{
+
+ (void)cpu;
+ return (6);
+}
+
+int
+pmc_allocate(const char *spec, enum pmc_mode mode, uint32_t flags, int cpu,
+ pmc_id_t *pmcid, uint64_t count)
+{
+
+ (void)spec;
+ (void)mode;
+ (void)flags;
+ (void)cpu;
+ (void)count;
+ *pmcid = next_pmcid++;
+ return (0);
+}
+
+int
+pmc_allocate_group(const char *spec, enum pmc_mode mode, uint32_t flags,
+ int cpu, pmc_id_t *pmcid, uint64_t count)
+{
+
+ return (pmc_allocate(spec, mode, flags, cpu, pmcid, count));
+}
+
+int
+pmc_capabilities(pmc_id_t pmcid, uint32_t *caps)
+{
+
+ (void)pmcid;
+ *caps = 0;
+ return (0);
+}
+
+int
+pmc_group_create(uint32_t *groupid)
+{
+
+ *groupid = 1;
+ return (0);
+}
+
+int
+pmc_group_add(uint32_t groupid, pmc_id_t pmcid, int leader)
+{
+
+ (void)groupid;
+ (void)pmcid;
+ (void)leader;
+ return (0);
+}
+
+int
+pmc_group_commit(uint32_t groupid)
+{
+
+ (void)groupid;
+ return (0);
+}
+
+int
+pmc_group_read(pmc_id_t leader, uint32_t *nmembers,
+ struct pmc_group_member *members, struct pmc_group_times *times)
+{
+ uint64_t enabled, running;
+ uint32_t capacity;
+
+ (void)leader;
+ if (nmembers == NULL || read_time("PMCSTAT_TEST_ENABLED", &enabled) != 0 ||
+ read_time("PMCSTAT_TEST_RUNNING", &running) != 0)
+ return (-1);
+ capacity = *nmembers;
+ *nmembers = 2;
+ if (capacity != 0 && capacity < 2) {
+ errno = E2BIG;
+ return (-1);
+ }
+ if (members != NULL) {
+ memset(members, 0, 2 * sizeof(*members));
+ members[0].pm_pmcid = 1;
+ members[1].pm_pmcid = 2;
+ }
+ if (times != NULL) {
+ memset(times, 0, sizeof(*times));
+ times->pgt_enabled = enabled;
+ times->pgt_running = running;
+ times->pgt_flags = PMC_GROUP_F_TIME_WALL_NS;
+ }
+ return (0);
+}
+
+int
+pmc_configure_logfile(int fd)
+{
+
+ (void)fd;
+ return (0);
+}
+
+int
+pmc_close_logfile(void)
+{
+
+ return (0);
+}
+
+int
+pmc_flush_logfile(void)
+{
+
+ return (0);
+}
+
+int
+pmc_get_driver_stats(struct pmc_driverstats *stats)
+{
+
+ memset(stats, 0, sizeof(*stats));
+ return (0);
+}
+
+int
+pmc_set(pmc_id_t pmcid, pmc_value_t value)
+{
+
+ (void)pmcid;
+ (void)value;
+ return (0);
+}
+
+int
+pmc_start(pmc_id_t pmcid)
+{
+
+ (void)pmcid;
+ return (0);
+}
+
+int
+pmc_stop(pmc_id_t pmcid)
+{
+
+ (void)pmcid;
+ return (0);
+}
+
+int
+pmc_release(pmc_id_t pmcid)
+{
+
+ (void)pmcid;
+ return (0);
+}
+
+int
+pmc_width(pmc_id_t pmcid, uint32_t *width)
+{
+
+ (void)pmcid;
+ *width = 48;
+ return (0);
+}
+
+int
+pmc_read(pmc_id_t pmcid, pmc_value_t *value)
+{
+
+ (void)pmcid;
+ *value = 0;
+ return (0);
+}
+
+int
+pmc_attach(pmc_id_t pmcid, pid_t pid)
+{
+
+ (void)pmcid;
+ (void)pid;
+ return (0);
+}
+
+int
+pmc_pmu_enabled(void)
+{
+
+ return (1);
+}
+
+void
+pmc_pmu_print_counters(const char *filter)
+{
+
+ (void)filter;
+}
+
+void
+pmc_pmu_print_counter_desc(const char *event)
+{
+
+ (void)event;
+}
+
+uint64_t
+pmc_pmu_sample_rate_get(const char *event)
+{
+
+ (void)event;
+ return (100);
+}
+
+const char *
+pmc_name_of_cputype(enum pmc_cputype cputype)
+{
+
+ (void)cputype;
+ return ("test");
+}
diff --git a/tools/regression/pmc/pmcstat_mux_warning_test.sh b/tools/regression/pmc/pmcstat_mux_warning_test.sh
new file mode 100644
--- /dev/null
+++ b/tools/regression/pmc/pmcstat_mux_warning_test.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+# This test checks that pmcstat sends the long-sampling-period warning
+# only after multiplexing starts.
+#
+
+set -u
+
+: "${PMCSTAT:?set PMCSTAT to the pmcstat binary under test}"
+: "${PMCSTAT_WARNING_SHIM:?set PMCSTAT_WARNING_SHIM to the preload shim}"
+
+if [ ! -x "${PMCSTAT}" ]; then
+ echo "ERROR: pmcstat binary is not executable: ${PMCSTAT}" >&2
+ exit 2
+fi
+if [ ! -r "${PMCSTAT_WARNING_SHIM}" ]; then
+ echo "ERROR: preload shim is not readable: ${PMCSTAT_WARNING_SHIM}" >&2
+ exit 2
+fi
+
+tmpdir=$(mktemp -d -t pmcstat_mux_warning) || exit 2
+trap 'rm -rf "${tmpdir}"' EXIT
+
+pass=0
+fail=0
+
+run_case()
+{
+ name=$1
+ enabled=$2
+ running=$3
+ expected=$4
+ out="${tmpdir}/${name}.out"
+
+ PMCSTAT_TEST_ENABLED=${enabled} \
+ PMCSTAT_TEST_RUNNING=${running} \
+ LD_PRELOAD=${PMCSTAT_WARNING_SHIM} \
+ "${PMCSTAT}" -b -n 100 \
+ -S '{instructions,unhalted-cycles}' \
+ -l 0.01 -O /dev/null >"${out}" 2>&1
+ rc=$?
+ if [ "${rc}" -ne 0 ]; then
+ echo "FAIL: ${name}: pmcstat exit=${rc}; output follows"
+ sed 's/^/ /' "${out}"
+ fail=$((fail + 1))
+ return
+ fi
+
+ count=$(grep -c \
+ 'kern.hwpmc.mux_period_ms rotation window can retire' \
+ "${out}" || true)
+ if [ "${count}" -ne "${expected}" ]; then
+ echo "FAIL: ${name}: warning count=${count}, expected ${expected}"
+ sed 's/^/ /' "${out}"
+ fail=$((fail + 1))
+ return
+ fi
+
+ echo "PASS: ${name}: warning count=${count}"
+ pass=$((pass + 1))
+}
+
+# This case catches a bug: braces alone must not trigger the warning by setting PMC_F_GROUP_MUX.
+run_case resident 100 100 0
+
+# This case catches a bug: code must add a condition to the warning, not delete the warning.
+run_case multiplexed 100 50 2
+
+echo "Results: ${pass} pass, ${fail} fail"
+[ "${fail}" -eq 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,265 @@
+#!/bin/sh
+#
+# These are pmcstat(8) regression tests for the -b / {a,b,c} group syntax.
+#
+# Run this script as root, with hwpmc(4) loaded, on an AMD CPU. Each
+# test needs a workload program. This script pipes /usr/bin/yes to
+# /dev/null, so each test gets a few seconds of busy CPU time to count.
+#
+# Each test prints a "PASS:" or a "FAIL:" line. The script prints an
+# exit-status row at the end. The script returns 0 only if every test 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 CPU-bound workload to count events.
+busy_workload() {
+ # `yes` is CPU-bound. Redirect its stdout and 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: Check a group of three events in process counting mode.
+t_basic_group() {
+ out="${LOGDIR}/basic.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles,ls_dispatch.ld_st_dispatch}' \
+ -- 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: Check two 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.ld_st_dispatch}' \
+ -- 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: Check that an oversubscribed single group fails.
+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.ld_st_dispatch,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: Check a system-wide counting group on a 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: Check a process sampling group.
+t_sampling_group() {
+ log="${LOGDIR}/samples.log"
+ out="${LOGDIR}/sampling.out"
+ ${PMCSTAT} -b \
+ -P '{instructions,unhalted-cycles,ls_dispatch.ld_st_dispatch}' \
+ -O "${log}" \
+ -- sh -c 'i=0; while [ $i -lt 200000 ]; do i=$((i+1)); done' \
+ >"${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: Check that brace syntax without -b fails cleanly.
+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 not valid
+ # event-spec syntax. pmc_allocate must fail. pmcstat must report this
+ # error with err(EX_OSERR, ...). The exit code 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: Check that non-grouped events work as normal.
+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: Check that a single-element group works as one event.
+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: Check that a missing closing brace fails cleanly.
+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: Check grouped and ungrouped events in the same command.
+t_mixed() {
+ out="${LOGDIR}/mixed.out"
+ ${PMCSTAT} -b \
+ -p '{instructions,unhalted-cycles}' \
+ -p ls_dispatch.ld_st_dispatch \
+ -- 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
Tue, Oct 20, 5:07 AM (6 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41049979
Default Alt Text
D59368.id189832.diff (14 KB)
Attached To
Mode
D59368: tools/regression/pmc: test pmcstat grouping end to end
Attached
Detach File
Event Timeline
Log In to Comment