Page MenuHomeFreeBSD

D61371.1794399275.diff
No OneTemporary

Authored By
Unknown
Size
21 KB
Referenced Files
None
Subscribers
None

D61371.1794399275.diff

diff --git a/share/man/man4/hwpmc.4 b/share/man/man4/hwpmc.4
--- a/share/man/man4/hwpmc.4
+++ b/share/man/man4/hwpmc.4
@@ -428,7 +428,7 @@
.Bl -tag -width indent
.It Va kern.hwpmc.callchaindepth Pq integer, read-only
The maximum number of call chain records to capture per sample.
-The default is 8.
+The default is 128.
.It Va kern.hwpmc.debugflags Pq string, read-write
(Only available if the
.Nm
@@ -458,6 +458,18 @@
logging function.
The default buffer size is 256KB.
The maximum value is 16MB.
+.It Va kern.hwpmc.max_sample_rate Pq integer, read-write
+The maximum number of samples each second for one sampling PMC on one CPU.
+The driver divides this value by
+.Va hz
+to get a limit for each clock tick.
+When a PMC interrupts more times than this limit in one clock tick, the
+driver drops the sample, stops the PMC, and counts the drop.
+The driver starts the PMC again at the next clock tick.
+The value 0 disables the limit.
+Without the limit, a short sample period can use all the CPU time in the
+interrupt handler.
+The default is 100000.
.It Va kern.hwpmc.mincount Pq integer, read-write
The minimum sampling rate for sampling mode PMCs.
The default count is 1000 events.
@@ -476,7 +488,7 @@
must not exceed 32MB per CPU.
.It Va kern.hwpmc.nsamples Pq integer, read-only
The number of entries in the per-CPU ring buffer used during sampling.
-The default is 512.
+The default is 256.
.It Va security.bsd.unprivileged_syspmcs Pq boolean, read-write
If set to non-zero, allow unprivileged processes to allocate system-wide
PMCs.
@@ -536,6 +548,10 @@
The module loading process failed because the currently executing kernel
was not configured with the required configuration option
.Dv HWPMC_HOOKS .
+.It "hwpmc: sample rate limit disabled, sampling can stall the system"
+An administrator set
+.Va kern.hwpmc.max_sample_rate
+to 0.
.It "hwpmc: tunable hashsize=%d must be greater than zero."
A negative value was supplied for tunable
.Va kern.hwpmc.hashsize .
diff --git a/sys/dev/hwpmc/hwpmc_logging.c b/sys/dev/hwpmc/hwpmc_logging.c
--- a/sys/dev/hwpmc/hwpmc_logging.c
+++ b/sys/dev/hwpmc/hwpmc_logging.c
@@ -896,9 +896,9 @@
spinlock_enter();
cpu = curcpu;
- /* tell hardclock not to run again */
+ /* Non-NULL means flush only. */
if (PMC_CPU_HAS_SAMPLES(cpu))
- PMC_CALL_HOOK_UNLOCKED(curthread, PMC_FN_DO_SAMPLES, NULL);
+ PMC_CALL_HOOK_UNLOCKED(curthread, PMC_FN_DO_SAMPLES, po);
plb = po->po_curbuf[cpu];
if (plb && plb->plb_ptr != plb->plb_base)
diff --git a/sys/dev/hwpmc/hwpmc_mod.c b/sys/dev/hwpmc/hwpmc_mod.c
--- a/sys/dev/hwpmc/hwpmc_mod.c
+++ b/sys/dev/hwpmc/hwpmc_mod.c
@@ -200,6 +200,7 @@
static int pmc_debugflags_sysctl_handler(SYSCTL_HANDLER_ARGS);
static int pmc_debugflags_parse(char *newstr, char *fence);
#endif
+static int pmc_max_sample_rate_sysctl_handler(SYSCTL_HANDLER_ARGS);
static void pmc_multipart_add(struct pmc_sample *ps, int type,
int length);
@@ -265,6 +266,7 @@
static void pmc_release_pmc_descriptor(struct pmc *pmc);
static void pmc_remove_owner(struct pmc_owner *po);
static void pmc_remove_process_descriptor(struct pmc_process *pp);
+static void pmc_restart_stalled(int cpu);
static int pmc_start(struct pmc *pm);
static int pmc_stop(struct pmc *pm);
static int pmc_syscall_handler(struct thread *td, void *syscall_args);
@@ -273,6 +275,7 @@
static void pmc_thread_descriptor_pool_free(struct pmc_thread *pt);
static void pmc_unlink_target_process(struct pmc *pmc,
struct pmc_process *pp);
+static void pmc_update_sample_limit(void);
static int generic_switch_in(struct pmc_cpu *pc, struct pmc_process *pp);
static int generic_switch_out(struct pmc_cpu *pc, struct pmc_process *pp);
@@ -318,6 +321,19 @@
SYSCTL_COUNTER_U64(_kern_hwpmc_stats, OID_AUTO, overwrites, CTLFLAG_RW,
&pmc_stats.pm_overwrites,
"# of times a sample was overwritten before being logged");
+SYSCTL_COUNTER_U64(_kern_hwpmc_stats, OID_AUTO, intr_throttled, CTLFLAG_RW,
+ &pmc_stats.pm_intr_throttled,
+ "# of times a PMC stopped at the sample rate limit");
+/* Per-PMC, per-CPU rate limit. Zero disables it. */
+static u_int pmc_max_sample_rate = PMC_MAX_SAMPLE_RATE;
+static u_int pmc_max_samples_per_tick;
+SYSCTL_PROC(_kern_hwpmc, OID_AUTO, max_sample_rate,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, 0,
+ pmc_max_sample_rate_sysctl_handler, "IU",
+ "maximum samples per second for one PMC on one CPU (0: no limit)");
+
+DPCPU_DEFINE_STATIC(int, pmc_tick_seq); /* Tick number. */
+DPCPU_DEFINE_STATIC(uint8_t, pmc_throttled); /* Restart next tick. */
static int pmc_callchaindepth = PMC_CALLCHAIN_DEPTH;
SYSCTL_INT(_kern_hwpmc, OID_AUTO, callchaindepth, CTLFLAG_RDTUN,
@@ -612,6 +628,36 @@
}
#endif
+/* Rate limit. */
+static void
+pmc_update_sample_limit(void)
+{
+ u_int rate;
+
+ rate = atomic_load_int(&pmc_max_sample_rate);
+ /* Avoid 32-bit overflow. */
+ atomic_store_int(&pmc_max_samples_per_tick,
+ rate == 0 ? 0 : howmany((uint64_t)rate, hz));
+}
+
+static int
+pmc_max_sample_rate_sysctl_handler(SYSCTL_HANDLER_ARGS)
+{
+ u_int rate;
+ int error;
+
+ rate = pmc_max_sample_rate;
+ error = sysctl_handle_int(oidp, &rate, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+ if (rate == 0 && pmc_max_sample_rate != 0)
+ log(LOG_WARNING,
+ "hwpmc: sample rate limit disabled, sampling can stall the system\n");
+ atomic_store_int(&pmc_max_sample_rate, rate);
+ pmc_update_sample_limit();
+ return (0);
+}
+
/*
* Map a row index to a classdep structure and return the adjusted row
* index for the PMC class index.
@@ -2268,15 +2314,7 @@
pmc_process_csw_out(td);
break;
- /*
- * Process accumulated PC samples.
- *
- * This function is expected to be called by hardclock() for
- * each CPU that has accumulated PC samples.
- *
- * This function is to be executed on the CPU whose samples
- * are being processed.
- */
+ /* Process CPU samples. A non-NULL arg means flush only. */
case PMC_FN_DO_SAMPLES:
/*
* Clear the cpu specific bit in the CPU mask before
@@ -2291,9 +2329,24 @@
*/
DPCPU_SET(pmc_sampled, 0);
cpu = PCPU_GET(cpuid);
+ if (arg != NULL) {
+ pmc_process_samples(cpu, PMC_HR);
+ pmc_process_samples(cpu, PMC_SR);
+ pmc_process_samples(cpu, PMC_UR);
+ /* Restart next tick. */
+ if (DPCPU_GET(pmc_throttled) != 0)
+ DPCPU_SET(pmc_sampled, 1);
+ break;
+ }
+ DPCPU_SET(pmc_tick_seq, DPCPU_GET(pmc_tick_seq) + 1);
pmc_process_samples(cpu, PMC_HR);
pmc_process_samples(cpu, PMC_SR);
pmc_process_samples(cpu, PMC_UR);
+ /* Restart even if the ring is empty. */
+ if (DPCPU_GET(pmc_throttled) != 0) {
+ DPCPU_SET(pmc_throttled, 0);
+ pmc_restart_stalled(cpu);
+ }
break;
case PMC_FN_MMAP:
@@ -2920,9 +2973,7 @@
PMCDBG2(PMC,REL,2, "decfg cpu=%d ri=%d", cpu, ri);
- critical_enter();
(void)pcd->pcd_config_pmc(cpu, adjri, NULL);
- critical_exit();
/* adjust the global and process count of SS mode PMCs */
if (mode == PMC_MODE_SS && pm->pm_state == PMC_STATE_RUNNING) {
@@ -4941,7 +4992,7 @@
ps->ps_flags = inuserspace ? PMC_CC_F_USERSPACE : 0;
ps->ps_nsamples_actual = 0;
- callchaindepth = (pm->pm_flags & PMC_F_CALLCHAIN) ?
+ callchaindepth = (pm->pm_flags & PMC_F_CALLCHAIN) != 0 ?
pmc_callchaindepth : 1;
MPASS(ps->ps_pc != NULL);
@@ -5003,7 +5054,28 @@
pmc_process_interrupt_mp(int ring, struct pmc *pm, struct trapframe *tf,
struct pmc_multipart *mp)
{
+ struct pmc_pcpu_state *pps;
struct thread *td;
+ u_int limit;
+ int seq;
+
+ /* Stop and drop above the tick limit. */
+ limit = atomic_load_int(&pmc_max_samples_per_tick);
+ if (limit != 0) {
+ pps = &pm->pm_pcpu_state[curcpu];
+ seq = DPCPU_GET(pmc_tick_seq);
+ if (pps->pps_intr_tick != seq) {
+ pps->pps_intr_tick = seq;
+ pps->pps_intr_count = 0;
+ }
+ if (++pps->pps_intr_count > limit) {
+ DPCPU_SET(pmc_throttled, 1);
+ pps->pps_stalled = 1;
+ counter_u64_add(pmc_stats.pm_intr_throttled, 1);
+ DPCPU_SET(pmc_sampled, 1);
+ return (EBUSY);
+ }
+ }
td = curthread;
if ((pm->pm_flags & PMC_F_USERCALLCHAIN) &&
@@ -5143,10 +5215,9 @@
struct thread *td;
struct pmc_owner *po;
struct pmc_sample *ps;
- struct pmc_classdep *pcd;
struct pmc_samplebuffer *psb;
uint64_t delta __diagused;
- int adjri, n;
+ int n;
KASSERT(PCPU_GET(cpuid) == cpu,
("[pmc,%d] not on the correct CPU pcpu=%d cpu=%d", __LINE__,
@@ -5236,13 +5307,32 @@
if (n == 0)
return;
- /*
- * Restart any stalled sampling PMCs on this CPU.
- *
- * If the NMI handler sets the pm_stalled field of a PMC after
- * the check below, we'll end up processing the stalled PMC at
- * the next hardclock tick.
- */
+ pmc_restart_stalled(cpu);
+}
+
+
+/* Check the tick limit. */
+static bool
+pmc_over_limit(struct pmc *pm, int cpu)
+{
+ struct pmc_pcpu_state *pps;
+ u_int limit;
+
+ limit = atomic_load_int(&pmc_max_samples_per_tick);
+ pps = &pm->pm_pcpu_state[cpu];
+ return (limit != 0 &&
+ pps->pps_intr_tick == DPCPU_ID_GET(cpu, pmc_tick_seq) &&
+ pps->pps_intr_count > limit);
+}
+
+/* Restart stalled PMCs only below the tick limit. */
+static void
+pmc_restart_stalled(int cpu)
+{
+ struct pmc *pm;
+ struct pmc_classdep *pcd;
+ int adjri, n;
+
for (n = 0; n < md->pmd_npmc; n++) {
pcd = pmc_ri_to_classdep(md, n, &adjri);
KASSERT(pcd != NULL,
@@ -5251,11 +5341,19 @@
if (pm == NULL || /* !cfg'ed */
pm->pm_state != PMC_STATE_RUNNING || /* !active */
- !PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pm)) || /* !sampling */
- !pm->pm_pcpu_state[cpu].pps_cpustate || /* !desired */
+ !PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pm))) /* !sampling */
+ continue;
+
+ if (!pm->pm_pcpu_state[cpu].pps_cpustate || /* !desired */
!pm->pm_pcpu_state[cpu].pps_stalled) /* !stalled */
continue;
+ if (pmc_over_limit(pm, cpu)) {
+ DPCPU_ID_SET(cpu, pmc_throttled, 1);
+ DPCPU_ID_SET(cpu, pmc_sampled, 1);
+ continue;
+ }
+
pm->pm_pcpu_state[cpu].pps_stalled = 0;
(void)(*pcd->pcd_start_pmc)(cpu, adjri, pm);
}
@@ -5751,6 +5849,7 @@
pmc_stats.pm_log_sweeps = counter_u64_alloc(M_WAITOK);
pmc_stats.pm_merges = counter_u64_alloc(M_WAITOK);
pmc_stats.pm_overwrites = counter_u64_alloc(M_WAITOK);
+ pmc_stats.pm_intr_throttled = counter_u64_alloc(M_WAITOK);
#ifdef HWPMC_DEBUG
/* parse debug flags first */
@@ -5791,6 +5890,8 @@
}
pmc_sample_mask = pmc_nsamples - 1;
+ pmc_update_sample_limit();
+
if (pmc_callchaindepth <= 0 ||
pmc_callchaindepth > PMC_CALLCHAIN_DEPTH_MAX) {
printf("hwpmc: tunable \"callchaindepth\"=%d out of "
@@ -6163,6 +6264,7 @@
counter_u64_free(pmc_stats.pm_log_sweeps);
counter_u64_free(pmc_stats.pm_merges);
counter_u64_free(pmc_stats.pm_overwrites);
+ counter_u64_free(pmc_stats.pm_intr_throttled);
sx_xunlock(&pmc_sx); /* we are done */
}
diff --git a/sys/sys/pmc.h b/sys/sys/pmc.h
--- a/sys/sys/pmc.h
+++ b/sys/sys/pmc.h
@@ -578,6 +578,7 @@
passes */
counter_u64_t pm_merges; /* merged k+u */
counter_u64_t pm_overwrites; /* UR overwrites */
+ counter_u64_t pm_intr_throttled; /* Rate stops. */
};
#endif
@@ -669,6 +670,7 @@
#define PMC_NLOGBUFFERS_PCPU 32
#define PMC_NLOGBUFFERS_PCPU_MEM_MAX (32 * 1024)
#define PMC_NSAMPLES 256
+#define PMC_MAX_SAMPLE_RATE 100000
#define PMC_CALLCHAIN_DEPTH 128
#define PMC_THREADLIST_MAX 128
@@ -747,6 +749,8 @@
*/
struct pmc_pcpu_state {
uint32_t pps_overflowcnt; /* count overflow interrupts */
+ int pps_intr_tick; /* Tick number. */
+ u_int pps_intr_count; /* Interrupts per tick. */
uint8_t pps_stalled;
uint8_t pps_cpustate;
} __aligned(CACHE_LINE_SIZE);
diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -10,12 +10,15 @@
.endif
ATF_TESTS_C+= pmc_lifecycle_test
ATF_TESTS_C+= pmc_log_test
+ATF_TESTS_C+= pmc_throttle_test
ATF_TESTS_C+= pmc_wrap_test
TEST_METADATA.pmc_exterr_test+= required_user="root"
+TEST_METADATA.pmc_throttle_test+= is_exclusive="true"
LIBADD+= pmc
LIBADD.pmc_detach_test+= pthread
+LIBADD.pmc_throttle_test+= pthread
CFLAGS.pmc_exterr_test.c+= -I${SRCTOP}/tests
diff --git a/tests/sys/pmc/pmc_throttle_test.c b/tests/sys/pmc/pmc_throttle_test.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_throttle_test.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* PMC throttle tests. */
+
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define TEST_CPU 1
+#define TEST_PERIOD 2000
+#define TEST_SECONDS 2
+
+static const char *cycle_events[] = {
+ "ls_not_halted_cyc", /* AMD Zen */
+ "unhalted-cycles", /* Generic alias */
+ "cpu_clk_unhalted.thread_p", /* Intel */
+};
+
+static atomic_bool spin_stop;
+static atomic_bool flush_stop;
+static atomic_ulong nflush;
+static bool use_flush;
+static int test_period = TEST_PERIOD;
+static const char *test_event; /* NULL: cycle event. */
+static uint32_t test_flags;
+static double test_secs;
+
+static void
+require_hwpmc(void)
+{
+
+ if (pmc_init() != 0)
+ atf_tc_skip("hwpmc(4) is not available");
+ if (sysconf(_SC_NPROCESSORS_ONLN) <= TEST_CPU)
+ atf_tc_skip("needs at least %d CPUs", TEST_CPU + 1);
+}
+
+static void
+pin(int cpu)
+{
+ cpuset_t set;
+
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ ATF_REQUIRE(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(set), &set) == 0);
+}
+
+static void *
+spin_main(void *arg __unused)
+{
+ volatile uint64_t x;
+
+ pin(TEST_CPU);
+ x = 1;
+ while (!atomic_load(&spin_stop))
+ x = x * 6364136223846793005ULL + 1;
+ return (NULL);
+}
+
+static void *
+flush_main(void *arg __unused)
+{
+
+ while (!atomic_load(&flush_stop)) {
+ if (pmc_flush_logfile() == 0)
+ atomic_fetch_add(&nflush, 1);
+ }
+ return (NULL);
+}
+
+static uint64_t
+stat_get(const char *name)
+{
+ char full[128];
+ uint64_t v;
+ size_t len;
+
+ snprintf(full, sizeof(full), "kern.hwpmc.stats.%s", name);
+ len = sizeof(v);
+ ATF_REQUIRE_MSG(sysctlbyname(full, &v, &len, NULL, 0) == 0,
+ "sysctl %s: %s", full, strerror(errno));
+ return (v);
+}
+
+/* Read a PMC sysctl or skip. */
+static u_int
+hwpmc_get(const char *name)
+{
+ char full[64];
+ size_t len;
+ u_int v;
+
+ snprintf(full, sizeof(full), "kern.hwpmc.%s", name);
+ len = sizeof(v);
+ if (sysctlbyname(full, &v, &len, NULL, 0) != 0)
+ atf_tc_skip("%s is not available", full);
+ return (v);
+}
+
+static int
+hwpmc_write(const char *name, u_int v)
+{
+ char full[64];
+
+ snprintf(full, sizeof(full), "kern.hwpmc.%s", name);
+ return (sysctlbyname(full, NULL, NULL, &v, sizeof(v)));
+}
+
+static void
+hwpmc_set(const char *name, u_int v)
+{
+
+ ATF_REQUIRE_MSG(hwpmc_write(name, v) == 0, "set kern.hwpmc.%s: %s",
+ name, strerror(errno));
+}
+
+static int
+hz_get(void)
+{
+ struct clockinfo ci;
+ size_t len;
+
+ len = sizeof(ci);
+ ATF_REQUIRE(sysctlbyname("kern.clockrate", &ci, &len, NULL, 0) == 0);
+ return (ci.hz);
+}
+
+/* Save sysctls for the ATF cleanup process. */
+#define SAVE_FILE "sysctl.saved"
+
+struct saved_sysctls {
+ u_int rate;
+ u_int mincount;
+};
+
+static void
+sysctls_save(void)
+{
+ struct saved_sysctls sv;
+ int fd;
+
+ sv.mincount = hwpmc_get("mincount");
+ sv.rate = hwpmc_get("max_sample_rate");
+ fd = open(SAVE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ ATF_REQUIRE(fd >= 0);
+ ATF_REQUIRE(write(fd, &sv, sizeof(sv)) == sizeof(sv));
+ close(fd);
+}
+
+static void
+sysctls_restore(void)
+{
+ struct saved_sysctls sv;
+ int fd;
+
+ fd = open(SAVE_FILE, O_RDONLY);
+ if (fd < 0)
+ return;
+ if (read(fd, &sv, sizeof(sv)) == sizeof(sv)) {
+ (void)hwpmc_write("max_sample_rate", sv.rate);
+ (void)hwpmc_write("mincount", sv.mincount);
+ }
+ close(fd);
+ (void)unlink(SAVE_FILE);
+}
+
+/* Set mincount and the tick limit. */
+static int
+throttle_setup(u_int rate_per_tick)
+{
+ int hz;
+
+ require_hwpmc();
+ sysctls_save();
+ hz = hz_get();
+ hwpmc_set("mincount", 1);
+ hwpmc_set("max_sample_rate", rate_per_tick * hz);
+ return (hz);
+}
+
+/* Measure the rate and throttle count on TEST_CPU. */
+static double
+measure_log(uint64_t *throttled, const char *logpath, pmc_id_t *idp)
+{
+ struct timeval t0, t1;
+ pthread_t fthr, thr;
+ pmc_id_t id;
+ uint64_t p0, p1, th0, th1;
+ double secs;
+ u_int i;
+ int fd;
+ bool ok;
+
+ ok = false;
+ if (test_event != NULL) {
+ ok = pmc_allocate(test_event, PMC_MODE_SS, test_flags,
+ TEST_CPU, &id, test_period) == 0;
+ if (!ok)
+ atf_tc_skip("no %s event", test_event);
+ }
+ for (i = 0; !ok && i < nitems(cycle_events); i++) {
+ if (pmc_allocate(cycle_events[i], PMC_MODE_SS, test_flags,
+ TEST_CPU, &id, test_period) == 0)
+ ok = true;
+ }
+ if (!ok)
+ atf_tc_skip("no cycle event for a system sampling PMC");
+
+ if (logpath != NULL)
+ fd = open(logpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ else
+ fd = open("/dev/null", O_WRONLY);
+ ATF_REQUIRE(fd >= 0);
+ ATF_REQUIRE(pmc_configure_logfile(fd) == 0);
+ if (idp != NULL)
+ *idp = id;
+
+ atomic_store(&spin_stop, false);
+ ATF_REQUIRE(pthread_create(&thr, NULL, spin_main, NULL) == 0);
+ usleep(100000);
+ atomic_store(&flush_stop, false);
+ atomic_store(&nflush, 0);
+ if (use_flush)
+ ATF_REQUIRE(pthread_create(&fthr, NULL, flush_main, NULL) == 0);
+
+ p0 = stat_get("intr_processed");
+ th0 = stat_get("intr_throttled");
+ gettimeofday(&t0, NULL);
+ ATF_REQUIRE(pmc_start(id) == 0);
+ sleep(TEST_SECONDS);
+ ATF_REQUIRE(pmc_stop(id) == 0);
+ gettimeofday(&t1, NULL);
+ p1 = stat_get("intr_processed");
+ th1 = stat_get("intr_throttled");
+
+ atomic_store(&spin_stop, true);
+ pthread_join(thr, NULL);
+ if (use_flush) {
+ atomic_store(&flush_stop, true);
+ pthread_join(fthr, NULL);
+ }
+ ATF_REQUIRE(pmc_release(id) == 0);
+ if (logpath != NULL)
+ ATF_REQUIRE(pmc_close_logfile() == 0);
+ (void)pmc_configure_logfile(-1);
+ close(fd);
+
+ secs = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) / 1e6;
+ test_secs = secs;
+ *throttled = th1 - th0;
+ return ((p1 - p0) / secs);
+}
+
+static double
+measure(uint64_t *throttled)
+{
+
+ return (measure_log(throttled, NULL, NULL));
+}
+
+/* Allow stop interrupts and other PMCs. */
+ATF_TC_WITH_CLEANUP(rate_limited);
+ATF_TC_HEAD(rate_limited, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(rate_limited, tc)
+{
+ uint64_t throttled;
+ double rate, bound;
+ int hz;
+
+ hz = throttle_setup(10);
+
+ rate = measure(&throttled);
+
+ sysctls_restore();
+
+ bound = 1.5 * 11 * hz;
+ printf("hz %d, rate %.0f/s, bound %.0f/s, throttled %ju\n", hz, rate,
+ bound, (uintmax_t)throttled);
+ ATF_CHECK_MSG(throttled > 0, "the PMC never reached the limit");
+ ATF_CHECK_MSG(rate <= bound, "rate %.0f/s is above %.0f/s", rate,
+ bound);
+}
+
+ATF_TC_CLEANUP(rate_limited, tc)
+{
+
+ sysctls_restore();
+}
+
+/* No limit must give a higher rate. */
+ATF_TC_WITH_CLEANUP(rate_unlimited);
+ATF_TC_HEAD(rate_unlimited, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(rate_unlimited, tc)
+{
+ uint64_t throttled;
+ double rate, bound;
+ int hz;
+
+ hz = throttle_setup(0);
+
+ rate = measure(&throttled);
+
+ sysctls_restore();
+
+ bound = 1.5 * 11 * hz;
+ printf("hz %d, rate %.0f/s, throttled %ju\n", hz, rate,
+ (uintmax_t)throttled);
+ ATF_CHECK_EQ_MSG(throttled, 0, "throttled %ju times",
+ (uintmax_t)throttled);
+ ATF_CHECK_MSG(rate > bound, "rate %.0f/s is not above %.0f/s", rate,
+ bound);
+}
+
+ATF_TC_CLEANUP(rate_unlimited, tc)
+{
+
+ sysctls_restore();
+}
+
+/* One throttle per tick. */
+ATF_TC_WITH_CLEANUP(short_period);
+ATF_TC_HEAD(short_period, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(short_period, tc)
+{
+ uint64_t throttled;
+ double per_tick, rate;
+ int hz;
+
+ hz = throttle_setup(100);
+ test_period = 100;
+
+ rate = measure(&throttled);
+
+ sysctls_restore();
+
+ per_tick = throttled / test_secs / hz;
+ printf("hz %d, rate %.0f/s, throttled %ju, %.2f each tick\n", hz,
+ rate, (uintmax_t)throttled, per_tick);
+ ATF_CHECK_MSG(throttled > 0, "the PMC never reached the limit");
+ ATF_CHECK_MSG(per_tick < 1.5, "%.2f throttles in each tick",
+ per_tick);
+}
+
+ATF_TC_CLEANUP(short_period, tc)
+{
+
+ sysctls_restore();
+}
+
+/* Flush must not reset the tick. */
+ATF_TC_WITH_CLEANUP(flush_loop);
+ATF_TC_HEAD(flush_loop, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(flush_loop, tc)
+{
+ uint64_t throttled;
+ double rate, bound;
+ int hz;
+
+ hz = throttle_setup(10);
+ use_flush = true;
+
+ rate = measure(&throttled);
+
+ sysctls_restore();
+
+ bound = 1.5 * 11 * hz;
+ printf("hz %d, rate %.0f/s, bound %.0f/s, flushes %lu, "
+ "throttled %ju\n", hz, rate, bound, atomic_load(&nflush),
+ (uintmax_t)throttled);
+ ATF_CHECK_MSG(atomic_load(&nflush) > 0, "no flush succeeded");
+ ATF_CHECK_MSG(rate <= bound, "rate %.0f/s is above %.0f/s", rate,
+ bound);
+}
+
+ATF_TC_CLEANUP(flush_loop, tc)
+{
+
+ sysctls_restore();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, rate_limited);
+ ATF_TP_ADD_TC(tp, rate_unlimited);
+ ATF_TP_ADD_TC(tp, short_period);
+ ATF_TP_ADD_TC(tp, flush_loop);
+ return (atf_no_error());
+}

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 11, 12:14 PM (8 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41652417
Default Alt Text
D61371.1794399275.diff (21 KB)

Event Timeline