Page MenuHomeFreeBSD

D59368.id189832.diff
No OneTemporary

D59368.id189832.diff

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

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)

Event Timeline