Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177432897
D61372.1794373344.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
11 KB
Referenced Files
None
Subscribers
None
D61372.1794373344.diff
View Options
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
@@ -429,6 +429,19 @@
.It Va kern.hwpmc.callchaindepth Pq integer, read-only
The maximum number of call chain records to capture per sample.
The default is 128.
+.It Va kern.hwpmc.cpu_time_max_percent Pq integer, read-write
+The maximum percentage of CPU time that the PMC interrupt handler can use.
+The driver measures the time of each interrupt.
+When the average time is above this percentage of the time between two
+samples at
+.Va kern.hwpmc.max_sample_rate ,
+the driver decreases
+.Va kern.hwpmc.max_sample_rate
+and logs a message.
+The driver does not increase the rate again.
+The values 0 and 100 disable this limit.
+This limit is available only on x86.
+The default is 25.
.It Va kern.hwpmc.debugflags Pq string, read-write
(Only available if the
.Nm
@@ -466,7 +479,8 @@
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.
+The value 0 disables the limit and the limit of
+.Va kern.hwpmc.cpu_time_max_percent .
Without the limit, a short sample period can use all the CPU time in the
interrupt handler.
The default is 100000.
@@ -552,6 +566,15 @@
An administrator set
.Va kern.hwpmc.max_sample_rate
to 0.
+.It "hwpmc: interrupt time limit disabled, sampling can stall the system"
+An administrator set
+.Va kern.hwpmc.cpu_time_max_percent
+to 0 or 100.
+.It "hwpmc: interrupt took too long (%u ns > %u ns), lowering kern.hwpmc.max_sample_rate to %u"
+The average time of the PMC interrupt handler was above the limit of
+.Va kern.hwpmc.cpu_time_max_percent .
+The driver decreased
+.Va kern.hwpmc.max_sample_rate .
.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_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_cpu_time_max_percent_sysctl_handler(SYSCTL_HANDLER_ARGS);
static int pmc_max_sample_rate_sysctl_handler(SYSCTL_HANDLER_ARGS);
static void pmc_multipart_add(struct pmc_sample *ps, int type,
@@ -324,6 +325,10 @@
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");
+SYSCTL_COUNTER_U64(_kern_hwpmc_stats, OID_AUTO, rate_lowered, CTLFLAG_RW,
+ &pmc_stats.pm_rate_lowered,
+ "# of times the interrupt time limit lowered max_sample_rate");
+
/* 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;
@@ -332,6 +337,20 @@
pmc_max_sample_rate_sysctl_handler, "IU",
"maximum samples per second for one PMC on one CPU (0: no limit)");
+/* Interrupt CPU budget. Zero and 100 disable it. */
+static u_int pmc_cpu_time_max_percent = PMC_CPU_TIME_MAX_PERCENT;
+static u_int pmc_intr_allowed_ns; /* 0: limit disabled */
+static u_int pmc_rate_lowered; /* Report pending. */
+static u_int pmc_report_avg_ns, pmc_report_allowed_ns;
+SYSCTL_PROC(_kern_hwpmc, OID_AUTO, cpu_time_max_percent,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, 0,
+ pmc_cpu_time_max_percent_sysctl_handler, "IU",
+ "maximum percent of CPU time for the PMC interrupt (0, 100: no limit)");
+
+/* Per-CPU interrupt time sum, in ns. */
+#define PMC_INTR_NSAMPLES 128
+DPCPU_DEFINE_STATIC(uint64_t, pmc_intr_running_ns);
+
DPCPU_DEFINE_STATIC(int, pmc_tick_seq); /* Tick number. */
DPCPU_DEFINE_STATIC(uint8_t, pmc_throttled); /* Restart next tick. */
@@ -628,16 +647,27 @@
}
#endif
-/* Rate limit. */
+/* Rate and time limits. */
static void
pmc_update_sample_limit(void)
{
- u_int rate;
+ uint64_t allowed;
+ u_int pct, rate;
rate = atomic_load_int(&pmc_max_sample_rate);
+ pct = atomic_load_int(&pmc_cpu_time_max_percent);
/* Avoid 32-bit overflow. */
atomic_store_int(&pmc_max_samples_per_tick,
rate == 0 ? 0 : howmany((uint64_t)rate, hz));
+
+ /* CPU time budget per sample. */
+ allowed = 0;
+ if (rate != 0 && pct != 0 && pct < 100) {
+ allowed = 1000000000ULL / rate * pct / 100;
+ if (allowed == 0)
+ allowed = 1;
+ }
+ atomic_store_int(&pmc_intr_allowed_ns, MIN(allowed, UINT_MAX));
}
static int
@@ -658,6 +688,67 @@
return (0);
}
+static int
+pmc_cpu_time_max_percent_sysctl_handler(SYSCTL_HANDLER_ARGS)
+{
+ u_int pct;
+ int error;
+
+ pct = pmc_cpu_time_max_percent;
+ error = sysctl_handle_int(oidp, &pct, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+ if (pct > 100)
+ return (EINVAL);
+ if ((pct == 0 || pct == 100) && pmc_cpu_time_max_percent != 0 &&
+ pmc_cpu_time_max_percent != 100)
+ log(LOG_WARNING,
+ "hwpmc: interrupt time limit disabled, sampling can stall the system\n");
+ atomic_store_int(&pmc_cpu_time_max_percent, pct);
+ pmc_update_sample_limit();
+ return (0);
+}
+
+/* NMI-safe rate cut. No locks. */
+void
+pmc_intr_took(uint64_t ns)
+{
+ uint64_t avg, budget, running;
+ u_int allowed, old, per_tick, pct;
+
+ allowed = atomic_load_int(&pmc_intr_allowed_ns);
+ if (allowed == 0)
+ return;
+
+ running = DPCPU_GET(pmc_intr_running_ns);
+ running -= running / PMC_INTR_NSAMPLES;
+ running += ns;
+ DPCPU_SET(pmc_intr_running_ns, running);
+
+ avg = running / PMC_INTR_NSAMPLES;
+ if (avg <= allowed)
+ return;
+
+ pct = atomic_load_int(&pmc_cpu_time_max_percent);
+ avg += avg / 4;
+ budget = (1000000000 / hz / 100) * pct;
+ per_tick = avg < budget ? budget / avg : 1;
+ old = atomic_load_int(&pmc_max_samples_per_tick);
+ atomic_store_int(&pmc_intr_allowed_ns, MIN(avg, UINT_MAX));
+ if (old != 0 && per_tick >= old)
+ return;
+
+ atomic_store_int(&pmc_max_samples_per_tick, per_tick);
+ atomic_store_int(&pmc_max_sample_rate, per_tick * hz);
+ counter_u64_add(pmc_stats.pm_rate_lowered, 1);
+
+ /* Log NMI time at hardclock. */
+ pmc_report_avg_ns = MIN(running / PMC_INTR_NSAMPLES, UINT_MAX);
+ pmc_report_allowed_ns = allowed;
+ atomic_store_int(&pmc_rate_lowered, 1);
+ DPCPU_SET(pmc_sampled, 1);
+}
+
/*
* Map a row index to a classdep structure and return the adjusted row
* index for the PMC class index.
@@ -2347,6 +2438,12 @@
DPCPU_SET(pmc_throttled, 0);
pmc_restart_stalled(cpu);
}
+ if (atomic_load_int(&pmc_rate_lowered) != 0 &&
+ atomic_cmpset_int(&pmc_rate_lowered, 1, 0))
+ log(LOG_INFO,
+ "hwpmc: interrupt took too long (%u ns > %u ns), lowering kern.hwpmc.max_sample_rate to %u\n",
+ pmc_report_avg_ns, pmc_report_allowed_ns,
+ atomic_load_int(&pmc_max_sample_rate));
break;
case PMC_FN_MMAP:
@@ -5850,6 +5947,7 @@
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);
+ pmc_stats.pm_rate_lowered = counter_u64_alloc(M_WAITOK);
#ifdef HWPMC_DEBUG
/* parse debug flags first */
@@ -6265,6 +6363,7 @@
counter_u64_free(pmc_stats.pm_merges);
counter_u64_free(pmc_stats.pm_overwrites);
counter_u64_free(pmc_stats.pm_intr_throttled);
+ counter_u64_free(pmc_stats.pm_rate_lowered);
sx_xunlock(&pmc_sx); /* we are done */
}
diff --git a/sys/dev/hwpmc/hwpmc_x86.c b/sys/dev/hwpmc/hwpmc_x86.c
--- a/sys/dev/hwpmc/hwpmc_x86.c
+++ b/sys/dev/hwpmc/hwpmc_x86.c
@@ -38,6 +38,7 @@
#include <vm/vm.h>
#include <vm/pmap.h>
+#include <machine/clock.h>
#include <machine/cpu.h>
#include <machine/cputypes.h>
#include <machine/intr_machdep.h> /* For x86/apicvar.h */
@@ -224,6 +225,23 @@
return (n);
}
+/* TSC scale: 32.32 ns per cycle. */
+static int (*pmc_x86_md_intr)(struct trapframe *);
+static uint64_t pmc_x86_tsc_scale;
+
+static int
+pmc_x86_intr(struct trapframe *tf)
+{
+ uint64_t t0;
+ int rv;
+
+ t0 = rdtsc();
+ rv = pmc_x86_md_intr(tf);
+ if (rv != 0 && pmc_x86_tsc_scale != 0)
+ pmc_intr_took(((rdtsc() - t0) * pmc_x86_tsc_scale) >> 32);
+ return (rv);
+}
+
/*
* Machine dependent initialization for x86 class platforms.
*/
@@ -243,7 +261,10 @@
if (md == NULL)
return (NULL);
- nmi_register_handler(md->pmd_intr);
+ pmc_x86_md_intr = md->pmd_intr;
+ pmc_x86_tsc_scale = tsc_freq != 0 ?
+ ((uint64_t)1000000000 << 32) / tsc_freq : 0;
+ nmi_register_handler(pmc_x86_intr);
/* disallow sampling if we do not have an LAPIC */
if (!lapic_enable_pcint())
for (i = 0; i < md->pmd_nclass; i++) {
@@ -260,7 +281,7 @@
{
if (md != NULL) {
lapic_disable_pcint();
- nmi_remove_handler(md->pmd_intr);
+ nmi_remove_handler(pmc_x86_intr);
}
if (cpu_vendor_id == CPU_VENDOR_AMD ||
cpu_vendor_id == CPU_VENDOR_HYGON)
diff --git a/sys/sys/pmc.h b/sys/sys/pmc.h
--- a/sys/sys/pmc.h
+++ b/sys/sys/pmc.h
@@ -579,6 +579,7 @@
counter_u64_t pm_merges; /* merged k+u */
counter_u64_t pm_overwrites; /* UR overwrites */
counter_u64_t pm_intr_throttled; /* Rate stops. */
+ counter_u64_t pm_rate_lowered; /* Rate cuts. */
};
#endif
@@ -671,6 +672,7 @@
#define PMC_NLOGBUFFERS_PCPU_MEM_MAX (32 * 1024)
#define PMC_NSAMPLES 256
#define PMC_MAX_SAMPLE_RATE 100000
+#define PMC_CPU_TIME_MAX_PERCENT 25
#define PMC_CALLCHAIN_DEPTH 128
#define PMC_THREADLIST_MAX 128
@@ -1270,6 +1272,7 @@
struct trapframe *_tf, struct pmc_multipart *mp);
int pmc_process_interrupt(int _ring, struct pmc *_pm,
struct trapframe *_tf);
+void pmc_intr_took(uint64_t _ns);
int pmc_save_kernel_callchain(uintptr_t *_cc, int _maxsamples,
struct trapframe *_tf);
int pmc_save_user_callchain(uintptr_t *_cc, int _maxsamples,
diff --git a/tests/sys/pmc/pmc_throttle_test.c b/tests/sys/pmc/pmc_throttle_test.c
--- a/tests/sys/pmc/pmc_throttle_test.c
+++ b/tests/sys/pmc/pmc_throttle_test.c
@@ -152,6 +152,7 @@
struct saved_sysctls {
u_int rate;
+ u_int pct;
u_int mincount;
};
@@ -163,6 +164,7 @@
sv.mincount = hwpmc_get("mincount");
sv.rate = hwpmc_get("max_sample_rate");
+ sv.pct = hwpmc_get("cpu_time_max_percent");
fd = open(SAVE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600);
ATF_REQUIRE(fd >= 0);
ATF_REQUIRE(write(fd, &sv, sizeof(sv)) == sizeof(sv));
@@ -180,6 +182,7 @@
return;
if (read(fd, &sv, sizeof(sv)) == sizeof(sv)) {
(void)hwpmc_write("max_sample_rate", sv.rate);
+ (void)hwpmc_write("cpu_time_max_percent", sv.pct);
(void)hwpmc_write("mincount", sv.mincount);
}
close(fd);
@@ -347,6 +350,46 @@
sysctls_restore();
}
+/* A 1% CPU budget must cut the rate. */
+ATF_TC_WITH_CLEANUP(time_limited);
+ATF_TC_HEAD(time_limited, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(time_limited, tc)
+{
+ uint64_t lowered0, lowered1, throttled;
+ double rate, bound;
+ u_int newrate;
+ int hz;
+
+ hz = throttle_setup(100);
+ hwpmc_set("cpu_time_max_percent", 1);
+
+ lowered0 = stat_get("rate_lowered");
+ rate = measure(&throttled);
+ lowered1 = stat_get("rate_lowered");
+ newrate = hwpmc_get("max_sample_rate");
+
+ sysctls_restore();
+
+ bound = 1.5 * (newrate + hz);
+ printf("hz %d, rate %.0f/s, new limit %u/s, lowered %ju\n", hz,
+ rate, newrate, (uintmax_t)(lowered1 - lowered0));
+ ATF_CHECK_MSG(lowered1 > lowered0, "the rate was not lowered");
+ ATF_CHECK_MSG(newrate < 100 * (u_int)hz,
+ "max_sample_rate %u was not lowered", newrate);
+ ATF_CHECK_MSG(rate <= bound, "rate %.0f/s is above %.0f/s", rate,
+ bound);
+}
+
+ATF_TC_CLEANUP(time_limited, tc)
+{
+
+ sysctls_restore();
+}
+
/* One throttle per tick. */
ATF_TC_WITH_CLEANUP(short_period);
ATF_TC_HEAD(short_period, tc)
@@ -420,6 +463,7 @@
{
ATF_TP_ADD_TC(tp, rate_limited);
ATF_TP_ADD_TC(tp, rate_unlimited);
+ ATF_TP_ADD_TC(tp, time_limited);
ATF_TP_ADD_TC(tp, short_period);
ATF_TP_ADD_TC(tp, flush_loop);
return (atf_no_error());
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Nov 11, 5:02 AM (6 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41653871
Default Alt Text
D61372.1794373344.diff (11 KB)
Attached To
Mode
D61372: hwpmc: limit PMC interrupt time
Attached
Detach File
Event Timeline