1 /* Copyright (c) 2018 Apple Inc. All rights reserved. */
3 #include <CoreFoundation/CoreFoundation.h>
4 #include <darwintest.h>
5 #include <dispatch/dispatch.h>
6 #include <ktrace/ktrace.h>
7 #include <kern/debug.h>
9 #include <sys/kdebug.h>
10 #include <sys/sysctl.h>
11 #include <TargetConditionals.h>
18 #define TELEMETRY_CMD_PMI_SETUP 3
20 T_GLOBAL_META(T_META_NAMESPACE("xnu.debugging.telemetry"),
21 T_META_CHECK_LEAKS(false),
24 extern int __telemetry(uint64_t cmd
, uint64_t deadline
, uint64_t interval
,
25 uint64_t leeway
, uint64_t arg4
, uint64_t arg5
);
28 * Microstackshots based on PMI are only supported on devices with monotonic
33 skip_if_pmi_unsupported(void)
36 int ret
= sysctlbyname("kern.monotonic.supported", &supported
,
37 &(size_t){ sizeof(supported
), }, NULL
, 0);
39 T_SKIP("monotonic sysctl generated an error: %d (%s)", errno
,
43 T_SKIP("monotonic must be supported for microstackshots");
48 * Data Analytics (da) also has a microstackshot configuration -- set a PMI
49 * cycle interval of 0 to force it to disable microstackshot on PMI.
53 set_da_microstackshot_period(CFNumberRef num
)
55 CFPreferencesSetValue(CFSTR("microstackshotPMICycleInterval"), num
,
56 CFSTR("com.apple.da"),
59 #else // TARGET_OS_IPHONE
61 #endif // !TARGET_OS_IPHONE
62 kCFPreferencesCurrentHost
);
64 notify_post("com.apple.da.tasking_changed");
68 disable_da_microstackshots(void)
71 CFNumberRef num
= CFNumberCreate(NULL
, kCFNumberSInt64Type
, &zero
);
72 set_da_microstackshot_period(num
);
73 T_LOG("notified da of tasking change, sleeping");
76 #else /* TARGET_OS_WATCH */
78 #endif /* !TARGET_OS_WATCH */
82 * Unset the preference to allow da to reset its configuration.
85 reenable_da_microstackshots(void)
87 set_da_microstackshot_period(NULL
);
91 * Clean up the test's configuration and allow da to activate again.
94 telemetry_cleanup(void)
96 (void)__telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_NONE
, 0, 0, 0, 0);
97 reenable_da_microstackshots();
101 * Make sure da hasn't configured the microstackshots -- otherwise the PMI
102 * setup command will return EBUSY.
107 disable_da_microstackshots();
108 T_LOG("installing cleanup handler");
109 T_ATEND(telemetry_cleanup
);
112 volatile static bool spinning
= true;
115 thread_spin(__unused
void *arg
)
122 #define MT_MICROSTACKSHOT KDBG_EVENTID(DBG_MONOTONIC, 2, 1)
123 #define MS_RECORD MACHDBG_CODE(DBG_MACH_STACKSHOT, \
124 MICROSTACKSHOT_RECORD)
125 #if defined(__arm64__) || defined(__arm__)
126 #define INSTRS_PERIOD (100ULL * 1000 * 1000)
127 #else /* defined(__arm64__) || defined(__arm__) */
128 #define INSTRS_PERIOD (1ULL * 1000 * 1000 * 1000)
129 #endif /* !defined(__arm64__) && !defined(__arm__) */
130 #define SLEEP_SECS 10
132 T_DECL(microstackshot_pmi
, "attempt to configure microstackshots on PMI")
134 skip_if_pmi_unsupported();
137 ktrace_session_t s
= ktrace_session_create();
138 T_QUIET
; T_WITH_ERRNO
; T_ASSERT_NOTNULL(s
, "session create");
140 __block
int pmi_events
= 0;
141 __block
int microstackshot_record_events
= 0;
142 __block
int pmi_records
= 0;
143 __block
int io_records
= 0;
144 __block
int interrupt_records
= 0;
145 __block
int timer_arm_records
= 0;
146 __block
int unknown_records
= 0;
147 __block
int empty_records
= 0;
149 ktrace_events_single(s
, MT_MICROSTACKSHOT
, ^(__unused
struct trace_point
*tp
) {
152 ktrace_events_single_paired(s
, MS_RECORD
,
153 ^(struct trace_point
*start
, __unused
struct trace_point
*end
) {
154 if (start
->arg1
& kPMIRecord
) {
157 if (start
->arg1
& kIORecord
) {
160 if (start
->arg1
& kInterruptRecord
) {
163 if (start
->arg1
& kTimerArmingRecord
) {
167 if (start
->arg2
== end
->arg2
) {
169 * The buffer didn't grow for this record -- there was
175 const uint8_t any_record
= kPMIRecord
| kIORecord
| kInterruptRecord
|
177 if ((start
->arg1
& any_record
) == 0) {
181 microstackshot_record_events
++;
184 ktrace_set_completion_handler(s
, ^{
185 ktrace_session_destroy(s
);
186 T_EXPECT_GT(pmi_events
, 0,
187 "saw non-zero PMIs (%g/sec)", pmi_events
/ (double)SLEEP_SECS
);
188 T_EXPECT_GT(pmi_records
, 0, "saw non-zero PMI record events (%g/sec)",
189 pmi_records
/ (double)SLEEP_SECS
);
190 T_EXPECT_EQ(unknown_records
, 0, "saw zero unknown record events");
191 T_EXPECT_GT(microstackshot_record_events
, 0,
192 "saw non-zero microstackshot record events (%d -- %g/sec)",
193 microstackshot_record_events
,
194 microstackshot_record_events
/ (double)SLEEP_SECS
);
195 T_EXPECT_NE(empty_records
, microstackshot_record_events
,
196 "saw non-empty records (%d empty)", empty_records
);
198 if (interrupt_records
> 0) {
199 T_LOG("saw %g interrupt records per second",
200 interrupt_records
/ (double)SLEEP_SECS
);
202 T_LOG("saw no interrupt records");
204 if (io_records
> 0) {
205 T_LOG("saw %g I/O records per second",
206 io_records
/ (double)SLEEP_SECS
);
208 T_LOG("saw no I/O records");
210 if (timer_arm_records
> 0) {
211 T_LOG("saw %g timer arming records per second",
212 timer_arm_records
/ (double)SLEEP_SECS
);
214 T_LOG("saw no timer arming records");
225 * Start sampling via telemetry on the instructions PMI.
227 int ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_INSTRS
,
228 INSTRS_PERIOD
, 0, 0, 0);
229 T_ASSERT_POSIX_SUCCESS(ret
,
230 "telemetry syscall succeeded, started microstackshots");
233 int error
= pthread_create(&thread
, NULL
, thread_spin
, NULL
);
234 T_ASSERT_POSIX_ZERO(error
, "started thread to spin");
236 error
= ktrace_start(s
, dispatch_get_main_queue());
237 T_ASSERT_POSIX_ZERO(error
, "started tracing");
239 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, SLEEP_SECS
* NSEC_PER_SEC
),
240 dispatch_get_main_queue(), ^{
243 (void)pthread_join(thread
, NULL
);
244 T_LOG("ending trace session after %d seconds", SLEEP_SECS
);
250 T_DECL(error_handling
,
251 "ensure that error conditions for the telemetry syscall are observed")
253 skip_if_pmi_unsupported();
257 int ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_INSTRS
,
259 T_EXPECT_EQ(ret
, -1, "telemetry shouldn't allow PMI every instruction");
261 ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_INSTRS
,
262 1000 * 1000, 0, 0, 0);
264 "telemetry shouldn't allow PMI every million instructions");
266 ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_CYCLES
,
268 T_EXPECT_EQ(ret
, -1, "telemetry shouldn't allow PMI every cycle");
270 ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_CYCLES
,
271 1000 * 1000, 0, 0, 0);
273 "telemetry shouldn't allow PMI every million cycles");
275 ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_CYCLES
,
276 UINT64_MAX
, 0, 0, 0);
277 T_EXPECT_EQ(ret
, -1, "telemetry shouldn't allow PMI every UINT64_MAX cycles");
279 ret
= __telemetry(TELEMETRY_CMD_PMI_SETUP
, TELEMETRY_PMI_CYCLES
,
280 (1ULL << 55), 0, 0, 0);
281 T_EXPECT_EQ(ret
, -1, "telemetry shouldn't allow PMI with extremely long periods");