]>
Commit | Line | Data |
---|---|---|
0a7de745 A |
1 | /* Copyright (c) 2018 Apple Inc. All rights reserved. */ |
2 | ||
3 | #include <CoreFoundation/CoreFoundation.h> | |
d9a64523 A |
4 | #include <darwintest.h> |
5 | #include <dispatch/dispatch.h> | |
6 | #include <ktrace/ktrace.h> | |
7 | #include <kern/debug.h> | |
0a7de745 | 8 | #include <notify.h> |
d9a64523 | 9 | #include <sys/kdebug.h> |
cb323159 | 10 | #include <sys/sysctl.h> |
d9a64523 A |
11 | #include <TargetConditionals.h> |
12 | ||
13 | enum telemetry_pmi { | |
14 | TELEMETRY_PMI_NONE, | |
15 | TELEMETRY_PMI_INSTRS, | |
16 | TELEMETRY_PMI_CYCLES, | |
17 | }; | |
18 | #define TELEMETRY_CMD_PMI_SETUP 3 | |
19 | ||
20 | T_GLOBAL_META(T_META_NAMESPACE("xnu.debugging.telemetry"), | |
0a7de745 A |
21 | T_META_CHECK_LEAKS(false), |
22 | T_META_ASROOT(true)); | |
d9a64523 A |
23 | |
24 | extern int __telemetry(uint64_t cmd, uint64_t deadline, uint64_t interval, | |
0a7de745 A |
25 | uint64_t leeway, uint64_t arg4, uint64_t arg5); |
26 | ||
cb323159 A |
27 | /* |
28 | * Microstackshots based on PMI are only supported on devices with monotonic | |
29 | * support. | |
30 | */ | |
31 | ||
32 | static void | |
33 | skip_if_pmi_unsupported(void) | |
34 | { | |
35 | int supported = 0; | |
36 | int ret = sysctlbyname("kern.monotonic.supported", &supported, | |
37 | &(size_t){ sizeof(supported), }, NULL, 0); | |
38 | if (ret < 0) { | |
39 | T_SKIP("monotonic sysctl generated an error: %d (%s)", errno, | |
40 | strerror(errno)); | |
41 | } | |
42 | if (!supported) { | |
43 | T_SKIP("monotonic must be supported for microstackshots"); | |
44 | } | |
45 | } | |
46 | ||
0a7de745 A |
47 | /* |
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. | |
50 | */ | |
51 | ||
52 | static void | |
53 | set_da_microstackshot_period(CFNumberRef num) | |
54 | { | |
55 | CFPreferencesSetValue(CFSTR("microstackshotPMICycleInterval"), num, | |
56 | CFSTR("com.apple.da"), | |
57 | #if TARGET_OS_IPHONE | |
58 | CFSTR("mobile"), | |
59 | #else // TARGET_OS_IPHONE | |
60 | CFSTR("root"), | |
61 | #endif // !TARGET_OS_IPHONE | |
62 | kCFPreferencesCurrentHost); | |
63 | ||
64 | notify_post("com.apple.da.tasking_changed"); | |
65 | } | |
66 | ||
67 | static void | |
68 | disable_da_microstackshots(void) | |
69 | { | |
70 | int64_t zero = 0; | |
71 | CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt64Type, &zero); | |
72 | set_da_microstackshot_period(num); | |
73 | T_LOG("notified da of tasking change, sleeping"); | |
cb323159 A |
74 | #if TARGET_OS_WATCH |
75 | sleep(8); | |
76 | #else /* TARGET_OS_WATCH */ | |
0a7de745 | 77 | sleep(3); |
cb323159 | 78 | #endif /* !TARGET_OS_WATCH */ |
0a7de745 A |
79 | } |
80 | ||
81 | /* | |
82 | * Unset the preference to allow da to reset its configuration. | |
83 | */ | |
84 | static void | |
85 | reenable_da_microstackshots(void) | |
86 | { | |
87 | set_da_microstackshot_period(NULL); | |
88 | } | |
d9a64523 | 89 | |
0a7de745 A |
90 | /* |
91 | * Clean up the test's configuration and allow da to activate again. | |
92 | */ | |
d9a64523 A |
93 | static void |
94 | telemetry_cleanup(void) | |
95 | { | |
cb323159 | 96 | (void)__telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_NONE, 0, 0, 0, 0); |
0a7de745 A |
97 | reenable_da_microstackshots(); |
98 | } | |
99 | ||
100 | /* | |
101 | * Make sure da hasn't configured the microstackshots -- otherwise the PMI | |
102 | * setup command will return EBUSY. | |
103 | */ | |
104 | static void | |
105 | telemetry_init(void) | |
106 | { | |
107 | disable_da_microstackshots(); | |
108 | T_LOG("installing cleanup handler"); | |
109 | T_ATEND(telemetry_cleanup); | |
d9a64523 A |
110 | } |
111 | ||
112 | volatile static bool spinning = true; | |
0a7de745 | 113 | |
d9a64523 A |
114 | static void * |
115 | thread_spin(__unused void *arg) | |
116 | { | |
117 | while (spinning) { | |
118 | } | |
119 | return NULL; | |
120 | } | |
121 | ||
122 | #define MT_MICROSTACKSHOT KDBG_EVENTID(DBG_MONOTONIC, 2, 1) | |
123 | #define MS_RECORD MACHDBG_CODE(DBG_MACH_STACKSHOT, \ | |
0a7de745 | 124 | MICROSTACKSHOT_RECORD) |
d9a64523 A |
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 | |
131 | ||
132 | T_DECL(microstackshot_pmi, "attempt to configure microstackshots on PMI") | |
133 | { | |
cb323159 | 134 | skip_if_pmi_unsupported(); |
d9a64523 A |
135 | |
136 | T_SETUPBEGIN; | |
137 | ktrace_session_t s = ktrace_session_create(); | |
138 | T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(s, "session create"); | |
139 | ||
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; | |
cb323159 | 147 | __block int empty_records = 0; |
d9a64523 A |
148 | |
149 | ktrace_events_single(s, MT_MICROSTACKSHOT, ^(__unused struct trace_point *tp) { | |
150 | pmi_events++; | |
151 | }); | |
152 | ktrace_events_single_paired(s, MS_RECORD, | |
0a7de745 | 153 | ^(struct trace_point *start, __unused struct trace_point *end) { |
d9a64523 | 154 | if (start->arg1 & kPMIRecord) { |
0a7de745 | 155 | pmi_records++; |
d9a64523 A |
156 | } |
157 | if (start->arg1 & kIORecord) { | |
0a7de745 | 158 | io_records++; |
d9a64523 A |
159 | } |
160 | if (start->arg1 & kInterruptRecord) { | |
0a7de745 | 161 | interrupt_records++; |
d9a64523 A |
162 | } |
163 | if (start->arg1 & kTimerArmingRecord) { | |
0a7de745 | 164 | timer_arm_records++; |
d9a64523 A |
165 | } |
166 | ||
cb323159 A |
167 | if (start->arg2 == end->arg2) { |
168 | /* | |
169 | * The buffer didn't grow for this record -- there was | |
170 | * an error. | |
171 | */ | |
172 | empty_records++; | |
173 | } | |
174 | ||
d9a64523 | 175 | const uint8_t any_record = kPMIRecord | kIORecord | kInterruptRecord | |
0a7de745 | 176 | kTimerArmingRecord; |
d9a64523 | 177 | if ((start->arg1 & any_record) == 0) { |
0a7de745 | 178 | unknown_records++; |
d9a64523 A |
179 | } |
180 | ||
181 | microstackshot_record_events++; | |
182 | }); | |
183 | ||
184 | ktrace_set_completion_handler(s, ^{ | |
185 | ktrace_session_destroy(s); | |
186 | T_EXPECT_GT(pmi_events, 0, | |
0a7de745 | 187 | "saw non-zero PMIs (%g/sec)", pmi_events / (double)SLEEP_SECS); |
d9a64523 | 188 | T_EXPECT_GT(pmi_records, 0, "saw non-zero PMI record events (%g/sec)", |
0a7de745 | 189 | pmi_records / (double)SLEEP_SECS); |
d9a64523 | 190 | T_EXPECT_EQ(unknown_records, 0, "saw zero unknown record events"); |
d9a64523 | 191 | T_EXPECT_GT(microstackshot_record_events, 0, |
cb323159 A |
192 | "saw non-zero microstackshot record events (%d -- %g/sec)", |
193 | microstackshot_record_events, | |
0a7de745 | 194 | microstackshot_record_events / (double)SLEEP_SECS); |
cb323159 A |
195 | T_EXPECT_NE(empty_records, microstackshot_record_events, |
196 | "saw non-empty records (%d empty)", empty_records); | |
d9a64523 A |
197 | |
198 | if (interrupt_records > 0) { | |
0a7de745 A |
199 | T_LOG("saw %g interrupt records per second", |
200 | interrupt_records / (double)SLEEP_SECS); | |
d9a64523 | 201 | } else { |
0a7de745 | 202 | T_LOG("saw no interrupt records"); |
d9a64523 A |
203 | } |
204 | if (io_records > 0) { | |
0a7de745 A |
205 | T_LOG("saw %g I/O records per second", |
206 | io_records / (double)SLEEP_SECS); | |
d9a64523 | 207 | } else { |
0a7de745 | 208 | T_LOG("saw no I/O records"); |
d9a64523 A |
209 | } |
210 | if (timer_arm_records > 0) { | |
0a7de745 A |
211 | T_LOG("saw %g timer arming records per second", |
212 | timer_arm_records / (double)SLEEP_SECS); | |
d9a64523 | 213 | } else { |
0a7de745 | 214 | T_LOG("saw no timer arming records"); |
d9a64523 A |
215 | } |
216 | ||
217 | T_END; | |
218 | }); | |
219 | ||
220 | T_SETUPEND; | |
221 | ||
0a7de745 A |
222 | telemetry_init(); |
223 | ||
d9a64523 A |
224 | /* |
225 | * Start sampling via telemetry on the instructions PMI. | |
226 | */ | |
227 | int ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS, | |
0a7de745 A |
228 | INSTRS_PERIOD, 0, 0, 0); |
229 | T_ASSERT_POSIX_SUCCESS(ret, | |
230 | "telemetry syscall succeeded, started microstackshots"); | |
d9a64523 A |
231 | |
232 | pthread_t thread; | |
233 | int error = pthread_create(&thread, NULL, thread_spin, NULL); | |
234 | T_ASSERT_POSIX_ZERO(error, "started thread to spin"); | |
235 | ||
236 | error = ktrace_start(s, dispatch_get_main_queue()); | |
237 | T_ASSERT_POSIX_ZERO(error, "started tracing"); | |
238 | ||
239 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, SLEEP_SECS * NSEC_PER_SEC), | |
0a7de745 | 240 | dispatch_get_main_queue(), ^{ |
d9a64523 A |
241 | spinning = false; |
242 | ktrace_end(s, 0); | |
243 | (void)pthread_join(thread, NULL); | |
244 | T_LOG("ending trace session after %d seconds", SLEEP_SECS); | |
245 | }); | |
246 | ||
247 | dispatch_main(); | |
248 | } | |
249 | ||
250 | T_DECL(error_handling, | |
0a7de745 | 251 | "ensure that error conditions for the telemetry syscall are observed") |
d9a64523 | 252 | { |
cb323159 A |
253 | skip_if_pmi_unsupported(); |
254 | ||
0a7de745 A |
255 | telemetry_init(); |
256 | ||
d9a64523 | 257 | int ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS, |
0a7de745 | 258 | 1, 0, 0, 0); |
d9a64523 A |
259 | T_EXPECT_EQ(ret, -1, "telemetry shouldn't allow PMI every instruction"); |
260 | ||
261 | ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS, | |
0a7de745 | 262 | 1000 * 1000, 0, 0, 0); |
d9a64523 | 263 | T_EXPECT_EQ(ret, -1, |
0a7de745 | 264 | "telemetry shouldn't allow PMI every million instructions"); |
d9a64523 A |
265 | |
266 | ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_CYCLES, | |
0a7de745 | 267 | 1, 0, 0, 0); |
d9a64523 A |
268 | T_EXPECT_EQ(ret, -1, "telemetry shouldn't allow PMI every cycle"); |
269 | ||
270 | ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_CYCLES, | |
0a7de745 | 271 | 1000 * 1000, 0, 0, 0); |
d9a64523 | 272 | T_EXPECT_EQ(ret, -1, |
0a7de745 A |
273 | "telemetry shouldn't allow PMI every million cycles"); |
274 | ||
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"); | |
278 | ||
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"); | |
d9a64523 | 282 | } |