Page MenuHomeFreeBSD

D59374.id185771.diff
No OneTemporary

D59374.id185771.diff

diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -8,11 +8,13 @@
ATF_TESTS_C+= pmc_group_gate_test
ATF_TESTS_C+= pmc_fork_test
ATF_TESTS_C+= pmc_exec_test
+ATF_TESTS_C+= pmc_residual_test
PROGS+= pmc_exec_credential_helper
LIBADD+= pmc
LIBADD.pmc_detach_test+= pthread
LIBADD.pmc_fork_test+= pthread
+LIBADD.pmc_residual_test+= pthread
.include <bsd.test.mk>
diff --git a/tests/sys/pmc/pmc_residual_test.c b/tests/sys/pmc/pmc_residual_test.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_residual_test.c
@@ -0,0 +1,1182 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+/*
+ * Sampling residuals must survive eviction.
+ *
+ * A multiplexed group is on hardware for only part of each rotation.
+ * Suppose progress toward the next sample were discarded at eviction.
+ * Then a member with a period longer than one residency window would
+ * restart its climb every window and never overflow. It would
+ * deliver zero samples, no matter how long the run.
+ * With the residual kept, progress adds up across windows.
+ * Delivery then converges on residency times the natural rate.
+ *
+ * The test picks a period larger than what one window can retire.
+ * It oversubscribes the PMU to force rotation, and checks that
+ * samples arrive anyway. A control group with the same period and no
+ * multiplexing shows that the period itself is reachable.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/thr.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <pmc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define TEST_EVENT "instructions"
+#define TEST_SYSCTL_PREFIX "kern.hwpmc.test."
+#define TEST_HOLD_GROUP_RESIDENT \
+ TEST_SYSCTL_PREFIX "hold_group_resident"
+#define TEST_HOLD_GROUP_RESIDENT_ACK \
+ TEST_SYSCTL_PREFIX "hold_group_resident_ack"
+#define TEST_HOLD_GROUP_EVICTED \
+ TEST_SYSCTL_PREFIX "hold_group_evicted"
+#define TEST_HOLD_GROUP_EVICTED_ACK \
+ TEST_SYSCTL_PREFIX "hold_group_evicted_ack"
+#define TEST_LIVE_RESIDUAL_ENTRIES \
+ TEST_SYSCTL_PREFIX "live_residual_entries"
+#define TEST_RESIDUAL_STATE \
+ TEST_SYSCTL_PREFIX "residual_state"
+
+#define RESIDUAL_WORKERS 2
+
+/*
+ * The period must exceed what one residency window can retire.
+ * If not, progress never has to carry across a window, and the test
+ * proves nothing. A 50 ms window retires a few hundred million
+ * events on this class of hardware, so 1e9 spans roughly two windows.
+ * The workload is sized to deliver enough samples for the counts to
+ * mean something.
+ */
+#define LONG_PERIOD (1000ULL * 1000 * 1000)
+#define SPIN_ITERS (4000ULL * 1000 * 1000)
+#define THREAD_A_RESIDUAL (LONG_PERIOD / 4)
+#define THREAD_B_RESIDUAL ((LONG_PERIOD / 4) * 3)
+
+enum pmc_test_residual_operation {
+ PMC_TEST_RESIDUAL_GET_SAVED = 1,
+ PMC_TEST_RESIDUAL_GET_LIVE,
+ PMC_TEST_RESIDUAL_SET_LIVE,
+ PMC_TEST_RESIDUAL_SET_SAVED
+};
+
+enum pmc_test_residual_kind {
+ PMC_TEST_RESIDUAL_UNINITIALIZED,
+ PMC_TEST_RESIDUAL_VALID,
+ PMC_TEST_RESIDUAL_OVERFLOW_PENDING
+};
+
+struct pmc_test_residual_query {
+ uint64_t ptrq_handle;
+ uint64_t ptrq_value;
+ uint32_t ptrq_pid;
+ uint32_t ptrq_tid;
+ uint32_t ptrq_operation;
+ uint32_t ptrq_state;
+ uint32_t ptrq_assigned;
+ uint32_t ptrq_reserved;
+};
+
+struct group {
+ uint32_t g_id;
+ pmc_id_t g_ids[PMC_GROUP_MAX_MEMBERS];
+ u_int g_n;
+ bool g_started;
+};
+
+struct group_build_failure {
+ const char *gbf_operation;
+ u_int gbf_member;
+ int gbf_errno;
+};
+
+struct residual_worker_gate {
+ pthread_mutex_t rwg_lock;
+ pthread_cond_t rwg_cond;
+ bool rwg_release;
+ u_int rwg_ready;
+ int rwg_error;
+ uint32_t rwg_tids[RESIDUAL_WORKERS];
+};
+
+struct residual_worker_arg {
+ struct residual_worker_gate *rwa_gate;
+ u_int rwa_index;
+};
+
+static bool
+is_amd(void)
+{
+ char vendor[64];
+ size_t len;
+
+ len = sizeof(vendor);
+ if (sysctlbyname("kern.hwpmc.cpuid", vendor, &len, NULL, 0) != 0)
+ return (false);
+ return (strstr(vendor, "AuthenticAMD") != NULL ||
+ strstr(vendor, "HygonGenuine") != NULL);
+}
+
+static void
+require_hwpmc(void)
+{
+
+ if (pmc_init() != 0)
+ atf_tc_skip("hwpmc(4) is not available: %s", strerror(errno));
+ if (!is_amd())
+ atf_tc_skip("PMC grouping is supported only on AMD CPUs");
+}
+
+static int
+sysctl_read_u32(const char *name, u_int *value)
+{
+ size_t length;
+
+ length = sizeof(*value);
+ return (sysctlbyname(name, value, &length, NULL, 0));
+}
+
+static int
+sysctl_write_u32(const char *name, u_int value)
+{
+
+ return (sysctlbyname(name, NULL, NULL, &value, sizeof(value)));
+}
+
+static int
+sysctl_read_u64(const char *name, uint64_t *value)
+{
+ size_t length;
+
+ length = sizeof(*value);
+ return (sysctlbyname(name, value, &length, NULL, 0));
+}
+
+static void
+require_hwpmc_test_support(void)
+{
+ uint64_t value;
+ int error;
+
+ errno = 0;
+ error = sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES, &value);
+ if (error == 0)
+ return;
+ if (errno == ENOENT)
+ atf_tc_skip("requires an HWPMCDEBUG module with test support");
+ atf_tc_fail("reading %s failed: errno %d (%s)",
+ TEST_LIVE_RESIDUAL_ENTRIES, errno, strerror(errno));
+}
+
+static void
+require_test_sysctl_write(const char *name, u_int value)
+{
+
+ ATF_REQUIRE_MSG(sysctl_write_u32(name, value) == 0,
+ "writing %s=%u failed: errno %d (%s)", name, value, errno,
+ strerror(errno));
+}
+
+static void
+wait_test_sysctl_ack(const char *name, u_int expected)
+{
+ u_int value;
+ int i;
+
+ for (i = 0; i < 5000; i++) {
+ ATF_REQUIRE_MSG(sysctl_read_u32(name, &value) == 0,
+ "reading %s failed: errno %d (%s)", name, errno,
+ strerror(errno));
+ if (value == expected)
+ return;
+ usleep(1000);
+ }
+ atf_tc_fail("%s did not acknowledge value %u", name, expected);
+}
+
+static void
+reset_residual_test_hooks(void)
+{
+
+ (void)sysctl_write_u32(TEST_HOLD_GROUP_RESIDENT, 0);
+ (void)sysctl_write_u32(TEST_HOLD_GROUP_EVICTED, 0);
+}
+
+static int
+residual_query(pmc_id_t pmcid, uint32_t tid, uint32_t operation,
+ uint32_t state, pmc_value_t value, struct pmc_test_residual_query *result)
+{
+ struct pmc_test_residual_query request;
+ size_t length;
+
+ memset(&request, 0, sizeof(request));
+ request.ptrq_handle = pmcid;
+ request.ptrq_value = value;
+ request.ptrq_pid = getpid();
+ request.ptrq_tid = tid;
+ request.ptrq_operation = operation;
+ request.ptrq_state = state;
+ length = sizeof(*result);
+ memset(result, 0, sizeof(*result));
+ if (sysctlbyname(TEST_RESIDUAL_STATE, result, &length, &request,
+ sizeof(request)) != 0)
+ return (-1);
+ if (length != sizeof(*result)) {
+ errno = EIO;
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+set_live_residual_required(pmc_id_t pmcid, uint32_t tid, pmc_value_t value)
+{
+ struct pmc_test_residual_query result;
+
+ ATF_REQUIRE_MSG(residual_query(pmcid, tid,
+ PMC_TEST_RESIDUAL_SET_LIVE, PMC_TEST_RESIDUAL_VALID, value,
+ &result) == 0,
+ "setting live residual for TID %u failed: errno %d (%s)", tid,
+ errno, strerror(errno));
+ ATF_REQUIRE_MSG(result.ptrq_assigned == 1 &&
+ result.ptrq_state == PMC_TEST_RESIDUAL_VALID &&
+ result.ptrq_value == value,
+ "live residual injection for TID %u returned assigned=%u "
+ "state=%u value=%ju", tid, result.ptrq_assigned,
+ result.ptrq_state, (uintmax_t)result.ptrq_value);
+}
+
+static void
+get_residual_required(pmc_id_t pmcid, uint32_t tid, uint32_t operation,
+ struct pmc_test_residual_query *result)
+{
+
+ ATF_REQUIRE_MSG(residual_query(pmcid, tid, operation,
+ PMC_TEST_RESIDUAL_UNINITIALIZED, 0, result) == 0,
+ "reading residual state for TID %u operation %u failed: "
+ "errno %d (%s)", tid, operation, errno, strerror(errno));
+}
+
+static void *
+residual_worker(void *arg) __no_lock_analysis
+{
+ struct residual_worker_arg *worker;
+ struct residual_worker_gate *gate;
+ long tid;
+ int error, wait_error;
+
+ worker = arg;
+ gate = worker->rwa_gate;
+ errno = 0;
+ error = thr_self(&tid) == 0 ? 0 : errno;
+ if (error == 0 && (tid <= 0 || (uint64_t)tid > UINT32_MAX))
+ error = EOVERFLOW;
+
+ wait_error = pthread_mutex_lock(&gate->rwg_lock);
+ if (wait_error != 0)
+ return ((void *)(uintptr_t)wait_error);
+ if (error == 0)
+ gate->rwg_tids[worker->rwa_index] = (uint32_t)tid;
+ else if (gate->rwg_error == 0)
+ gate->rwg_error = error;
+ gate->rwg_ready++;
+ (void)pthread_cond_broadcast(&gate->rwg_cond);
+ while (!gate->rwg_release && gate->rwg_error == 0) {
+ wait_error = pthread_cond_wait(&gate->rwg_cond, &gate->rwg_lock);
+ if (wait_error != 0) {
+ gate->rwg_error = wait_error;
+ (void)pthread_cond_broadcast(&gate->rwg_cond);
+ break;
+ }
+ }
+ error = gate->rwg_error;
+ (void)pthread_mutex_unlock(&gate->rwg_lock);
+ return ((void *)(uintptr_t)error);
+}
+
+static void
+residual_workers_start(struct residual_worker_gate *gate,
+ pthread_t threads[RESIDUAL_WORKERS],
+ struct residual_worker_arg args[RESIDUAL_WORKERS], u_int nworkers)
+{
+ struct timespec deadline;
+ u_int i;
+ int error;
+
+ ATF_REQUIRE_MSG(nworkers > 0 && nworkers <= RESIDUAL_WORKERS,
+ "invalid residual worker count %u", nworkers);
+ memset(gate, 0, sizeof(*gate));
+ error = pthread_mutex_init(&gate->rwg_lock, NULL);
+ ATF_REQUIRE_MSG(error == 0, "pthread_mutex_init failed: %d (%s)",
+ error, strerror(error));
+ error = pthread_cond_init(&gate->rwg_cond, NULL);
+ ATF_REQUIRE_MSG(error == 0, "pthread_cond_init failed: %d (%s)",
+ error, strerror(error));
+
+ for (i = 0; i < nworkers; i++) {
+ args[i].rwa_gate = gate;
+ args[i].rwa_index = i;
+ error = pthread_create(&threads[i], NULL, residual_worker,
+ &args[i]);
+ ATF_REQUIRE_MSG(error == 0,
+ "pthread_create for residual worker %u failed: %d (%s)",
+ i, error, strerror(error));
+ }
+
+ ATF_REQUIRE_MSG(clock_gettime(CLOCK_REALTIME, &deadline) == 0,
+ "clock_gettime failed: %s", strerror(errno));
+ deadline.tv_sec += 5;
+ error = pthread_mutex_lock(&gate->rwg_lock);
+ ATF_REQUIRE_MSG(error == 0, "pthread_mutex_lock failed: %d (%s)",
+ error, strerror(error));
+ while (gate->rwg_ready != nworkers && gate->rwg_error == 0) {
+ error = pthread_cond_timedwait(&gate->rwg_cond, &gate->rwg_lock,
+ &deadline);
+ if (error != 0)
+ break;
+ }
+ ATF_REQUIRE_MSG(pthread_mutex_unlock(&gate->rwg_lock) == 0,
+ "pthread_mutex_unlock failed");
+ ATF_REQUIRE_MSG(error == 0,
+ "waiting for residual workers failed: %d (%s)", error,
+ strerror(error));
+ ATF_REQUIRE_MSG(gate->rwg_error == 0,
+ "residual worker setup failed: %d (%s)", gate->rwg_error,
+ strerror(gate->rwg_error));
+ ATF_REQUIRE_EQ(gate->rwg_ready, nworkers);
+}
+
+static void
+residual_workers_stop(struct residual_worker_gate *gate,
+ pthread_t threads[RESIDUAL_WORKERS], u_int nworkers)
+{
+ void *result;
+ u_int i;
+ int error;
+
+ ATF_REQUIRE_MSG(nworkers > 0 && nworkers <= RESIDUAL_WORKERS,
+ "invalid residual worker count %u", nworkers);
+ error = pthread_mutex_lock(&gate->rwg_lock);
+ ATF_REQUIRE_MSG(error == 0, "pthread_mutex_lock failed: %d (%s)",
+ error, strerror(error));
+ gate->rwg_release = true;
+ error = pthread_cond_broadcast(&gate->rwg_cond);
+ ATF_REQUIRE_MSG(error == 0, "pthread_cond_broadcast failed: %d (%s)",
+ error, strerror(error));
+ error = pthread_mutex_unlock(&gate->rwg_lock);
+ ATF_REQUIRE_MSG(error == 0, "pthread_mutex_unlock failed: %d (%s)",
+ error, strerror(error));
+
+ for (i = 0; i < nworkers; i++) {
+ error = pthread_join(threads[i], &result);
+ ATF_REQUIRE_MSG(error == 0,
+ "pthread_join for residual worker %u failed: %d (%s)",
+ i, error, strerror(error));
+ ATF_REQUIRE_MSG(result == NULL,
+ "residual worker %u returned error %ju", i,
+ (uintmax_t)(uintptr_t)result);
+ }
+ error = pthread_cond_destroy(&gate->rwg_cond);
+ ATF_REQUIRE_MSG(error == 0, "pthread_cond_destroy failed: %d (%s)",
+ error, strerror(error));
+ error = pthread_mutex_destroy(&gate->rwg_lock);
+ ATF_REQUIRE_MSG(error == 0, "pthread_mutex_destroy failed: %d (%s)",
+ error, strerror(error));
+}
+
+static void
+group_init(struct group *g)
+{
+ u_int i;
+
+ memset(g, 0, sizeof(*g));
+ for (i = 0; i < nitems(g->g_ids); i++)
+ g->g_ids[i] = PMC_ID_INVALID;
+}
+
+static void
+group_teardown(struct group *g)
+{
+ u_int i;
+
+ if (g->g_started)
+ (void)pmc_stop(g->g_ids[0]);
+ for (i = 0; i < g->g_n; i++) {
+ if (g->g_ids[i] != PMC_ID_INVALID)
+ (void)pmc_release(g->g_ids[i]);
+ }
+ group_init(g);
+}
+
+static void
+group_teardown_required(struct group *g, const char *description)
+{
+
+ if (g->g_started) {
+ ATF_REQUIRE_MSG(pmc_stop(g->g_ids[0]) == 0,
+ "%s stop failed: errno %d (%s)", description, errno,
+ strerror(errno));
+ g->g_started = false;
+ }
+ if (g->g_n != 0 && g->g_ids[0] != PMC_ID_INVALID) {
+ ATF_REQUIRE_MSG(pmc_release(g->g_ids[0]) == 0,
+ "%s leader release failed: errno %d (%s)", description,
+ errno, strerror(errno));
+ }
+ group_init(g);
+}
+
+/*
+ * Build and commit an n-member sampling group.
+ * Member 0 is the leader.
+ * With mux set, the group may be time-multiplexed against others.
+ */
+static int
+group_build(struct group *g, u_int n, bool mux,
+ struct group_build_failure *failure)
+{
+ uint32_t flags;
+ u_int i;
+
+ failure->gbf_operation = NULL;
+ failure->gbf_member = UINT_MAX;
+ failure->gbf_errno = 0;
+ group_init(g);
+ g->g_n = n;
+ for (i = 0; i < n; i++) {
+ flags = (i == 0 && mux) ? PMC_F_GROUP_MUX : 0;
+ if (pmc_allocate_group(TEST_EVENT, PMC_MODE_TS, flags,
+ PMC_CPU_ANY, &g->g_ids[i], LONG_PERIOD) != 0) {
+ failure->gbf_operation = "pmc_allocate_group";
+ failure->gbf_member = i;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ }
+ if (pmc_group_create(&g->g_id) != 0) {
+ failure->gbf_operation = "pmc_group_create";
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ for (i = 0; i < n; i++) {
+ if (pmc_group_add(g->g_id, g->g_ids[i], i == 0) != 0) {
+ failure->gbf_operation = "pmc_group_add";
+ failure->gbf_member = i;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ }
+ if (pmc_group_commit(g->g_id) != 0) {
+ failure->gbf_operation = "pmc_group_commit";
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+format_group_build_failure(char *buffer, size_t size, const char *description,
+ const struct group_build_failure *failure)
+{
+ const char *operation;
+
+ operation = failure->gbf_operation != NULL ?
+ failure->gbf_operation : "unknown operation";
+ if (failure->gbf_member == UINT_MAX) {
+ snprintf(buffer, size, "%s failed at %s: errno %d (%s)",
+ description, operation, failure->gbf_errno,
+ strerror(failure->gbf_errno));
+ } else {
+ snprintf(buffer, size,
+ "%s failed at %s for member %u: errno %d (%s)",
+ description, operation, failure->gbf_member,
+ failure->gbf_errno, strerror(failure->gbf_errno));
+ }
+}
+
+static void
+group_build_required(struct group *g, u_int n, bool mux,
+ const char *description)
+{
+ struct group_build_failure failure;
+ char message[256];
+
+ ATF_REQUIRE_MSG(n > 0 && n <= PMC_GROUP_MAX_MEMBERS,
+ "%s requested invalid member count %u", description, n);
+ if (group_build(g, n, mux, &failure) == 0)
+ return;
+ format_group_build_failure(message, sizeof(message), description,
+ &failure);
+ group_teardown(g);
+ atf_tc_fail("%s", message);
+}
+
+/* Return the largest group that commits. This is the usable row capacity. */
+static u_int
+probe_capacity(void)
+{
+ struct group_build_failure failure;
+ struct group probe;
+ char description[128], message[256];
+ u_int n;
+
+ for (n = PMC_GROUP_MAX_MEMBERS; n > 0; n--) {
+ if (group_build(&probe, n, false, &failure) == 0) {
+ group_teardown(&probe);
+ return (n);
+ }
+ group_teardown(&probe);
+ snprintf(description, sizeof(description),
+ "capacity probe for %u members", n);
+ if (failure.gbf_operation == NULL ||
+ strcmp(failure.gbf_operation, "pmc_group_commit") != 0 ||
+ failure.gbf_errno != ENOSPC) {
+ format_group_build_failure(message, sizeof(message),
+ description, &failure);
+ atf_tc_fail("%s; expected pmc_group_commit to fail with "
+ "ENOSPC while reducing the probe size", message);
+ }
+ }
+ return (0);
+}
+
+static void __attribute__((noinline))
+spin(void)
+{
+ volatile uint64_t sink;
+ uint64_t i;
+
+ sink = 0;
+ for (i = 0; i < SPIN_ITERS; i++)
+ sink += i;
+}
+
+/*
+ * Report the leader's delivered sample count, from pm_value, and the
+ * group's residency.
+ * The buffer must hold every member.
+ * A short buffer fails with E2BIG and reads nothing.
+ */
+static pmc_value_t
+leader_samples(struct group *g, double *residency)
+{
+ struct pmc_group_member m[PMC_GROUP_MAX_MEMBERS];
+ struct pmc_group_times t;
+ uint32_t n;
+
+ n = g->g_n;
+ memset(m, 0, sizeof(m));
+ memset(&t, 0, sizeof(t));
+ ATF_REQUIRE_MSG(pmc_group_read(g->g_ids[0], &n, m, &t) == 0,
+ "group read failed: %s", strerror(errno));
+ ATF_REQUIRE_EQ(n, g->g_n);
+ if (residency != NULL) {
+ *residency = t.pgt_enabled != 0 ?
+ (double)t.pgt_running / t.pgt_enabled : 0.0;
+ }
+ return (m[0].pm_value);
+}
+
+static void
+run_group(struct group *g)
+{
+
+ ATF_REQUIRE_MSG(pmc_attach(g->g_ids[0], getpid()) == 0,
+ "attach failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(pmc_start(g->g_ids[0]) == 0, "start failed: %s",
+ strerror(errno));
+ g->g_started = true;
+}
+
+/*
+ * Control case: this period is reachable when the group is never
+ * evicted. If this fails, the multiplexed case below proves nothing.
+ */
+ATF_TC_WITHOUT_HEAD(long_period_unmultiplexed);
+ATF_TC_BODY(long_period_unmultiplexed, tc)
+{
+ struct group g;
+ FILE *log;
+ pmc_value_t samples;
+
+ require_hwpmc();
+ log = tmpfile();
+ ATF_REQUIRE(log != NULL);
+ ATF_REQUIRE(pmc_configure_logfile(fileno(log)) == 0);
+
+ group_build_required(&g, 1, false, "unmultiplexed group");
+ run_group(&g);
+ spin();
+ samples = leader_samples(&g, NULL);
+ group_teardown(&g);
+ (void)pmc_configure_logfile(-1);
+ (void)fclose(log);
+
+ printf("unmultiplexed: %ju samples\n", (uintmax_t)samples);
+ ATF_CHECK_MSG(samples > 0,
+ "no samples without multiplexing: the period is unreachable in "
+ "this workload, so the multiplexed case cannot be judged");
+}
+
+/*
+ * The real check.
+ * Two oversubscribed groups force rotation.
+ * Neither group stays resident continuously.
+ * Without residual preservation, the leader climbs from zero every
+ * window and never overflows.
+ */
+ATF_TC_WITHOUT_HEAD(long_period_under_mux);
+ATF_TC_BODY(long_period_under_mux, tc)
+{
+ struct group_build_failure failure;
+ struct group a, b;
+ char message[256];
+ FILE *log;
+ pmc_value_t samples;
+ double residency;
+ u_int cap;
+
+ require_hwpmc();
+ cap = probe_capacity();
+ if (cap < 2)
+ atf_tc_skip("need at least two group rows, have %u", cap);
+
+ log = tmpfile();
+ ATF_REQUIRE(log != NULL);
+ ATF_REQUIRE(pmc_configure_logfile(fileno(log)) == 0);
+
+ group_build_required(&a, cap, true, "first multiplexed group");
+ if (group_build(&b, cap, true, &failure) != 0) {
+ format_group_build_failure(message, sizeof(message),
+ "second multiplexed group", &failure);
+ group_teardown(&b);
+ group_teardown(&a);
+ (void)pmc_configure_logfile(-1);
+ (void)fclose(log);
+ atf_tc_fail("%s", message);
+ }
+ run_group(&a);
+ run_group(&b);
+ spin();
+ samples = leader_samples(&a, &residency);
+ group_teardown(&b);
+ group_teardown(&a);
+ (void)pmc_configure_logfile(-1);
+ (void)fclose(log);
+
+ printf("multiplexed: %ju samples at %.1f%% residency\n",
+ (uintmax_t)samples, residency * 100.0);
+ ATF_CHECK_MSG(residency < 0.95,
+ "the group was resident %.1f%% of the time, so it was never "
+ "really multiplexed and the check below proves nothing",
+ residency * 100.0);
+ ATF_CHECK_MSG(samples > 0,
+ "a multiplexed member whose period exceeds one residency window "
+ "delivered no samples: the residual was discarded at eviction");
+}
+
+/*
+ * Saved residual state must keep its identity, no matter the value.
+ * An overflow-pending event and an uninitialized event both read zero.
+ * They are not the same state.
+ */
+ATF_TC_WITH_CLEANUP(residual_state_identity);
+ATF_TC_HEAD(residual_state_identity, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "15");
+}
+ATF_TC_BODY(residual_state_identity, tc)
+{
+ static const struct {
+ uint32_t state;
+ pmc_value_t value;
+ const char *name;
+ } cases[] = {
+ { PMC_TEST_RESIDUAL_UNINITIALIZED, 0, "uninitialized" },
+ { PMC_TEST_RESIDUAL_VALID, THREAD_A_RESIDUAL, "valid" },
+ { PMC_TEST_RESIDUAL_OVERFLOW_PENDING, 0, "overflow pending" },
+ };
+ struct pmc_test_residual_query observed[nitems(cases)];
+ struct pmc_test_residual_query result;
+ struct group g;
+ uint64_t residual_entries_after, residual_entries_before;
+ char failure[256];
+ long tid;
+ u_int i;
+ int error, query_errno;
+
+ memset(observed, 0, sizeof(observed));
+ failure[0] = '\0';
+ require_hwpmc();
+ require_hwpmc_test_support();
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_before) == 0,
+ "reading initial residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ group_build_required(&g, 1, false, "residual-state group");
+ if (pmc_attach(g.g_ids[0], getpid()) != 0) {
+ query_errno = errno;
+ group_teardown(&g);
+ atf_tc_fail("residual-state group attach failed: errno %d (%s)",
+ query_errno, strerror(query_errno));
+ }
+ tid = 0;
+ errno = 0;
+ error = thr_self(&tid) == 0 ? 0 : errno;
+ if (error != 0 || tid <= 0 || (uint64_t)tid > UINT32_MAX) {
+ group_teardown(&g);
+ atf_tc_fail("thr_self failed or returned invalid TID %ld: "
+ "errno %d (%s)", tid, error, strerror(error));
+ }
+
+ for (i = 0; i < nitems(cases); i++) {
+ errno = 0;
+ if (residual_query(g.g_ids[0], (uint32_t)tid,
+ PMC_TEST_RESIDUAL_SET_SAVED, cases[i].state,
+ cases[i].value, &result) != 0) {
+ query_errno = errno;
+ snprintf(failure, sizeof(failure),
+ "setting %s saved residual failed: errno %d (%s)",
+ cases[i].name, query_errno, strerror(query_errno));
+ break;
+ }
+ errno = 0;
+ if (residual_query(g.g_ids[0], (uint32_t)tid,
+ PMC_TEST_RESIDUAL_GET_SAVED,
+ PMC_TEST_RESIDUAL_UNINITIALIZED, 0,
+ &observed[i]) != 0) {
+ query_errno = errno;
+ snprintf(failure, sizeof(failure),
+ "reading %s saved residual failed: errno %d (%s)",
+ cases[i].name, query_errno, strerror(query_errno));
+ break;
+ }
+ /* ptrq_assigned shows only the row assignment, not if a residual exists. */
+ }
+
+ group_teardown_required(&g, "residual-state group");
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_after) == 0,
+ "reading final residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+ ATF_REQUIRE_MSG(residual_entries_after == residual_entries_before,
+ "residual entries changed from %ju to %ju after cleanup",
+ (uintmax_t)residual_entries_before,
+ (uintmax_t)residual_entries_after);
+ ATF_REQUIRE_MSG(failure[0] == '\0', "%s", failure);
+
+ for (i = 0; i < nitems(cases); i++) {
+ ATF_CHECK_MSG(observed[i].ptrq_state == cases[i].state &&
+ observed[i].ptrq_value == cases[i].value,
+ "%s saved residual expected state=%u value=%ju, "
+ "observed state=%u value=%ju", cases[i].name,
+ cases[i].state, (uintmax_t)cases[i].value,
+ observed[i].ptrq_state,
+ (uintmax_t)observed[i].ptrq_value);
+ }
+}
+ATF_TC_CLEANUP(residual_state_identity, tc)
+{
+
+ reset_residual_test_hooks();
+}
+
+/*
+ * A thread can start while an event is evicted.
+ * This thread has no saved progress.
+ * It must start with a full period.
+ * It must not use an older thread's residual.
+ */
+ATF_TC_WITH_CLEANUP(thread_residual_new_thread_starts_full_period);
+ATF_TC_HEAD(thread_residual_new_thread_starts_full_period, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(thread_residual_new_thread_starts_full_period, tc)
+{
+ struct pmc_test_residual_query new_thread_live, saved;
+ struct residual_worker_arg old_args[RESIDUAL_WORKERS];
+ struct residual_worker_arg new_args[RESIDUAL_WORKERS];
+ struct residual_worker_gate old_gate, new_gate;
+ pthread_t old_threads[RESIDUAL_WORKERS];
+ pthread_t new_threads[RESIDUAL_WORKERS];
+ struct group a, b;
+ uint64_t residual_entries_after, residual_entries_before;
+ FILE *log;
+ u_int cap;
+
+ require_hwpmc();
+ require_hwpmc_test_support();
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_before) == 0,
+ "reading initial residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ /*
+ * The "old" thread starts before eviction and has its own residual.
+ * Park it in pthread_cond_wait.
+ * This stops the system from overwriting the injected value before
+ * eviction takes its snapshot.
+ */
+ residual_workers_start(&old_gate, old_threads, old_args, 1);
+
+ log = tmpfile();
+ ATF_REQUIRE_MSG(log != NULL, "tmpfile failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pmc_configure_logfile(fileno(log)) == 0,
+ "logfile configuration failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ cap = probe_capacity();
+ ATF_REQUIRE_MSG(cap > 0, "no usable %s group rows were found",
+ TEST_EVENT);
+ group_build_required(&a, cap, true, "new-thread residual group");
+ group_build_required(&b, cap, true, "new-thread competitor group");
+ ATF_REQUIRE_MSG(pmc_attach(a.g_ids[0], getpid()) == 0,
+ "new-thread residual-group attach failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pmc_attach(b.g_ids[0], getpid()) == 0,
+ "new-thread competitor-group attach failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, a.g_id);
+ ATF_REQUIRE_MSG(pmc_start(a.g_ids[0]) == 0,
+ "new-thread residual-group start failed: errno %d (%s)", errno,
+ strerror(errno));
+ a.g_started = true;
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_RESIDENT_ACK, a.g_id);
+ ATF_REQUIRE_MSG(pmc_start(b.g_ids[0]) == 0,
+ "new-thread competitor-group start failed: errno %d (%s)", errno,
+ strerror(errno));
+ b.g_started = true;
+
+ set_live_residual_required(a.g_ids[0], old_gate.rwg_tids[0],
+ THREAD_A_RESIDUAL);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, a.g_id);
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_EVICTED_ACK, a.g_id);
+ get_residual_required(a.g_ids[0], old_gate.rwg_tids[0],
+ PMC_TEST_RESIDUAL_GET_SAVED, &saved);
+
+ /*
+ * The "new" thread starts while the event is evicted.
+ * It must start from a full period.
+ * It must not inherit the old thread's residual.
+ */
+ residual_workers_start(&new_gate, new_threads, new_args, 1);
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, a.g_id);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_RESIDENT_ACK, a.g_id);
+ get_residual_required(a.g_ids[0], new_gate.rwg_tids[0],
+ PMC_TEST_RESIDUAL_GET_LIVE, &new_thread_live);
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ group_teardown_required(&b, "new-thread competitor group");
+ group_teardown_required(&a, "new-thread residual group");
+ ATF_REQUIRE_MSG(pmc_configure_logfile(-1) == 0,
+ "logfile close failed: errno %d (%s)", errno, strerror(errno));
+ ATF_REQUIRE_MSG(fclose(log) == 0,
+ "temporary logfile close failed: errno %d (%s)", errno,
+ strerror(errno));
+ residual_workers_stop(&new_gate, new_threads, 1);
+ residual_workers_stop(&old_gate, old_threads, 1);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_after) == 0,
+ "reading final residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ ATF_CHECK_MSG(residual_entries_after == residual_entries_before,
+ "residual entries changed from %ju to %ju after cleanup",
+ (uintmax_t)residual_entries_before,
+ (uintmax_t)residual_entries_after);
+ ATF_CHECK_MSG(saved.ptrq_assigned == 0 &&
+ saved.ptrq_state == PMC_TEST_RESIDUAL_VALID &&
+ saved.ptrq_value == THREAD_A_RESIDUAL,
+ "existing thread expected evicted saved VALID/%ju, observed "
+ "assigned=%u state=%u value=%ju", (uintmax_t)THREAD_A_RESIDUAL,
+ saved.ptrq_assigned, saved.ptrq_state,
+ (uintmax_t)saved.ptrq_value);
+ ATF_CHECK_MSG(new_thread_live.ptrq_assigned == 1 &&
+ new_thread_live.ptrq_state == PMC_TEST_RESIDUAL_VALID &&
+ new_thread_live.ptrq_value == LONG_PERIOD,
+ "new thread expected full-period VALID/%ju, observed assigned=%u "
+ "state=%u value=%ju", (uintmax_t)LONG_PERIOD,
+ new_thread_live.ptrq_assigned, new_thread_live.ptrq_state,
+ (uintmax_t)new_thread_live.ptrq_value);
+}
+ATF_TC_CLEANUP(thread_residual_new_thread_starts_full_period, tc)
+{
+
+ reset_residual_test_hooks();
+}
+
+/*
+ * A saved residual belongs to one event/TID pair.
+ * When that thread exits, the system must reclaim its entry.
+ * This is true even while the group stays attached.
+ */
+ATF_TC_WITH_CLEANUP(thread_residual_thread_exit_reclaims_state);
+ATF_TC_HEAD(thread_residual_thread_exit_reclaims_state, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "15");
+}
+ATF_TC_BODY(thread_residual_thread_exit_reclaims_state, tc)
+{
+ struct pmc_test_residual_query saved;
+ struct residual_worker_arg args[RESIDUAL_WORKERS];
+ struct residual_worker_gate gate;
+ pthread_t threads[RESIDUAL_WORKERS];
+ struct group g;
+ uint64_t entries_after_cleanup, entries_after_exit;
+ uint64_t entries_before, entries_while_saved;
+
+ require_hwpmc();
+ require_hwpmc_test_support();
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &entries_before) == 0,
+ "reading initial residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+ ATF_REQUIRE_MSG(entries_before != UINT64_MAX,
+ "initial residual-entry count cannot be incremented");
+
+ residual_workers_start(&gate, threads, args, 1);
+ group_build_required(&g, 1, false, "thread-exit residual group");
+ ATF_REQUIRE_MSG(pmc_attach(g.g_ids[0], getpid()) == 0,
+ "thread-exit residual-group attach failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ ATF_REQUIRE_MSG(residual_query(g.g_ids[0], gate.rwg_tids[0],
+ PMC_TEST_RESIDUAL_SET_SAVED, PMC_TEST_RESIDUAL_VALID,
+ THREAD_A_RESIDUAL, &saved) == 0,
+ "setting saved residual for exiting TID %u failed: errno %d (%s)",
+ gate.rwg_tids[0], errno, strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &entries_while_saved) == 0,
+ "reading live residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ residual_workers_stop(&gate, threads, 1);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &entries_after_exit) == 0,
+ "reading post-exit residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ group_teardown_required(&g, "thread-exit residual group");
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &entries_after_cleanup) == 0,
+ "reading final residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ ATF_CHECK_MSG(saved.ptrq_state == PMC_TEST_RESIDUAL_VALID &&
+ saved.ptrq_value == THREAD_A_RESIDUAL,
+ "exiting thread saved residual expected VALID/%ju, observed "
+ "group_assigned=%u state=%u value=%ju",
+ (uintmax_t)THREAD_A_RESIDUAL,
+ saved.ptrq_assigned, saved.ptrq_state,
+ (uintmax_t)saved.ptrq_value);
+ ATF_CHECK_MSG(entries_while_saved == entries_before + 1,
+ "saved residual entry count was %ju, expected initial %ju + 1",
+ (uintmax_t)entries_while_saved, (uintmax_t)entries_before);
+ ATF_CHECK_MSG(entries_after_exit == entries_before,
+ "residual entries after thread exit were %ju, expected %ju",
+ (uintmax_t)entries_after_exit, (uintmax_t)entries_before);
+ ATF_CHECK_MSG(entries_after_cleanup == entries_before,
+ "residual entries after cleanup were %ju, expected %ju",
+ (uintmax_t)entries_after_cleanup, (uintmax_t)entries_before);
+}
+ATF_TC_CLEANUP(thread_residual_thread_exit_reclaims_state, tc)
+{
+
+ reset_residual_test_hooks();
+}
+
+/*
+ * Each target thread has independent progress on a sampling event.
+ * Inject two live residuals. Force eviction, then force re-entry.
+ * Each TID must keep its own value.
+ * The current event-wide minimum model does not do this.
+ * It collapses to one shared value instead.
+ * So an exact assertion must fail here.
+ */
+ATF_TC_WITH_CLEANUP(per_thread_residuals_survive_eviction);
+ATF_TC_HEAD(per_thread_residuals_survive_eviction, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(per_thread_residuals_survive_eviction, tc)
+{
+ struct pmc_test_residual_query live[RESIDUAL_WORKERS];
+ struct pmc_test_residual_query saved[RESIDUAL_WORKERS];
+ struct residual_worker_arg args[RESIDUAL_WORKERS];
+ struct residual_worker_gate gate;
+ pthread_t threads[RESIDUAL_WORKERS];
+ const pmc_value_t expected[RESIDUAL_WORKERS] = {
+ THREAD_A_RESIDUAL,
+ THREAD_B_RESIDUAL,
+ };
+ struct group a, b;
+ uint64_t residual_entries_after, residual_entries_before;
+ FILE *log;
+ u_int cap, i;
+
+ require_hwpmc();
+ require_hwpmc_test_support();
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_before) == 0,
+ "reading initial residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ residual_workers_start(&gate, threads, args, RESIDUAL_WORKERS);
+
+ log = tmpfile();
+ ATF_REQUIRE_MSG(log != NULL, "tmpfile failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pmc_configure_logfile(fileno(log)) == 0,
+ "logfile configuration failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ cap = probe_capacity();
+ ATF_REQUIRE_MSG(cap > 0, "no usable %s group rows were found",
+ TEST_EVENT);
+ group_build_required(&a, cap, true, "residual group");
+ group_build_required(&b, cap, true, "competitor group");
+
+ ATF_REQUIRE_MSG(pmc_attach(a.g_ids[0], getpid()) == 0,
+ "residual-group attach failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pmc_attach(b.g_ids[0], getpid()) == 0,
+ "competitor-group attach failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, a.g_id);
+ ATF_REQUIRE_MSG(pmc_start(a.g_ids[0]) == 0,
+ "residual-group start failed: errno %d (%s)", errno,
+ strerror(errno));
+ a.g_started = true;
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_RESIDENT_ACK, a.g_id);
+
+ ATF_REQUIRE_MSG(pmc_start(b.g_ids[0]) == 0,
+ "competitor-group start failed: errno %d (%s)", errno,
+ strerror(errno));
+ b.g_started = true;
+
+ for (i = 0; i < RESIDUAL_WORKERS; i++)
+ set_live_residual_required(a.g_ids[0], gate.rwg_tids[i],
+ expected[i]);
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, a.g_id);
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_EVICTED_ACK, a.g_id);
+
+ for (i = 0; i < RESIDUAL_WORKERS; i++) {
+ get_residual_required(a.g_ids[0], gate.rwg_tids[i],
+ PMC_TEST_RESIDUAL_GET_SAVED, &saved[i]);
+ ATF_REQUIRE_MSG(saved[i].ptrq_assigned == 0,
+ "saved residual query for TID %u observed assigned=%u",
+ gate.rwg_tids[i], saved[i].ptrq_assigned);
+ ATF_REQUIRE_MSG(
+ saved[i].ptrq_state == PMC_TEST_RESIDUAL_VALID,
+ "saved residual query for TID %u returned state=%u value=%ju",
+ gate.rwg_tids[i], saved[i].ptrq_state,
+ (uintmax_t)saved[i].ptrq_value);
+ }
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, a.g_id);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ wait_test_sysctl_ack(TEST_HOLD_GROUP_RESIDENT_ACK, a.g_id);
+
+ for (i = 0; i < RESIDUAL_WORKERS; i++) {
+ get_residual_required(a.g_ids[0], gate.rwg_tids[i],
+ PMC_TEST_RESIDUAL_GET_LIVE, &live[i]);
+ ATF_REQUIRE_MSG(live[i].ptrq_assigned == 1,
+ "live residual query for TID %u observed assigned=%u",
+ gate.rwg_tids[i], live[i].ptrq_assigned);
+ ATF_REQUIRE_MSG(live[i].ptrq_state == PMC_TEST_RESIDUAL_VALID,
+ "live residual query for TID %u returned state=%u value=%ju",
+ gate.rwg_tids[i], live[i].ptrq_state,
+ (uintmax_t)live[i].ptrq_value);
+ }
+
+ require_test_sysctl_write(TEST_HOLD_GROUP_RESIDENT, 0);
+ require_test_sysctl_write(TEST_HOLD_GROUP_EVICTED, 0);
+ group_teardown_required(&b, "competitor group");
+ group_teardown_required(&a, "residual group");
+ ATF_REQUIRE_MSG(pmc_configure_logfile(-1) == 0,
+ "logfile close failed: errno %d (%s)", errno, strerror(errno));
+ ATF_REQUIRE_MSG(fclose(log) == 0,
+ "temporary logfile close failed: errno %d (%s)", errno,
+ strerror(errno));
+ residual_workers_stop(&gate, threads, RESIDUAL_WORKERS);
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &residual_entries_after) == 0,
+ "reading final residual-entry count failed: errno %d (%s)",
+ errno, strerror(errno));
+
+ ATF_CHECK_MSG(residual_entries_after == residual_entries_before,
+ "residual entries changed from %ju to %ju after cleanup",
+ (uintmax_t)residual_entries_before,
+ (uintmax_t)residual_entries_after);
+ for (i = 0; i < RESIDUAL_WORKERS; i++) {
+ printf("TID %u expected=%ju saved=%ju live=%ju\n",
+ gate.rwg_tids[i], (uintmax_t)expected[i],
+ (uintmax_t)saved[i].ptrq_value,
+ (uintmax_t)live[i].ptrq_value);
+ ATF_CHECK_MSG(saved[i].ptrq_value == expected[i],
+ "thread %c expected saved VALID/%ju, observed VALID/%ju",
+ 'A' + i, (uintmax_t)expected[i],
+ (uintmax_t)saved[i].ptrq_value);
+ ATF_CHECK_MSG(live[i].ptrq_value == expected[i],
+ "thread %c expected restored live VALID/%ju, "
+ "observed VALID/%ju", 'A' + i, (uintmax_t)expected[i],
+ (uintmax_t)live[i].ptrq_value);
+ }
+}
+ATF_TC_CLEANUP(per_thread_residuals_survive_eviction, tc)
+{
+
+ reset_residual_test_hooks();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, long_period_unmultiplexed);
+ ATF_TP_ADD_TC(tp, long_period_under_mux);
+ ATF_TP_ADD_TC(tp, residual_state_identity);
+ ATF_TP_ADD_TC(tp, thread_residual_new_thread_starts_full_period);
+ ATF_TP_ADD_TC(tp, thread_residual_thread_exit_reclaims_state);
+ ATF_TP_ADD_TC(tp, per_thread_residuals_survive_eviction);
+
+ return (atf_no_error());
+}

File Metadata

Mime Type
text/plain
Expires
Thu, Sep 10, 7:23 AM (16 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38538093
Default Alt Text
D59374.id185771.diff (38 KB)

Event Timeline