]> git.saurik.com Git - apple/xnu.git/blob - tests/perf_compressor.c
xnu-4903.270.47.tar.gz
[apple/xnu.git] / tests / perf_compressor.c
1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/sysctl.h>
4 #include <mach-o/dyld.h>
5 #include <perfcheck_keys.h>
6
7 #ifdef T_NAMESPACE
8 #undef T_NAMESPACE
9 #endif
10 #include <darwintest.h>
11 #include <darwintest_utils.h>
12
13 T_GLOBAL_META(
14 T_META_NAMESPACE("xnu.vm.perf"),
15 T_META_CHECK_LEAKS(false),
16 T_META_TAG_PERF
17 );
18
19 enum {
20 ALL_ZEROS,
21 MOSTLY_ZEROS,
22 RANDOM,
23 TYPICAL
24 };
25
26 #define CREATE_LIST(X) \
27 X(SUCCESS) \
28 X(TOO_FEW_ARGUMENTS) \
29 X(SYSCTL_VM_PAGESIZE_FAILED) \
30 X(VM_PAGESIZE_IS_ZERO) \
31 X(UNKNOWN_PAGE_TYPE) \
32 X(DISPATCH_SOURCE_CREATE_FAILED) \
33 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
34 X(SIGNAL_TO_PARENT_FAILED) \
35 X(EXIT_CODE_MAX)
36
37 #define EXIT_CODES_ENUM(VAR) VAR,
38 enum exit_codes_num {
39 CREATE_LIST(EXIT_CODES_ENUM)
40 };
41
42 #define EXIT_CODES_STRING(VAR) #VAR,
43 static const char *exit_codes_str[] = {
44 CREATE_LIST(EXIT_CODES_STRING)
45 };
46
47 #define SYSCTL_FREEZE_TO_MEMORY "kern.memorystatus_freeze_to_memory=1"
48
49 static pid_t pid = -1;
50 static dt_stat_t r;
51 static dt_stat_time_t s;
52
53 void allocate_zero_pages(char **buf, int num_pages, int vmpgsize);
54 void allocate_mostly_zero_pages(char **buf, int num_pages, int vmpgsize);
55 void allocate_random_pages(char **buf, int num_pages, int vmpgsize);
56 void allocate_representative_pages(char **buf, int num_pages, int vmpgsize);
57 void run_compressor_test(int size_mb, int page_type);
58 void freeze_helper_process(void);
59 void cleanup(void);
60
61 void
62 allocate_zero_pages(char **buf, int num_pages, int vmpgsize)
63 {
64 int i;
65
66 for (i = 0; i < num_pages; i++) {
67 buf[i] = (char*)malloc((size_t)vmpgsize * sizeof(char));
68 memset(buf[i], 0, vmpgsize);
69 }
70 }
71
72 void
73 allocate_mostly_zero_pages(char **buf, int num_pages, int vmpgsize)
74 {
75 int i, j;
76
77 for (i = 0; i < num_pages; i++) {
78 buf[i] = (char*)malloc((size_t)vmpgsize * sizeof(char));
79 memset(buf[i], 0, vmpgsize);
80 for (j = 0; j < 40; j++) {
81 buf[i][j] = (char)(j + 1);
82 }
83 }
84 }
85
86 void
87 allocate_random_pages(char **buf, int num_pages, int vmpgsize)
88 {
89 int i;
90
91 for (i = 0; i < num_pages; i++) {
92 buf[i] = (char*)malloc((size_t)vmpgsize * sizeof(char));
93 arc4random_buf((void*)buf[i], (size_t)vmpgsize);
94 }
95 }
96
97 // Gives us the compression ratio we see in the typical case (~2.7)
98 void
99 allocate_representative_pages(char **buf, int num_pages, int vmpgsize)
100 {
101 int i, j;
102 char val;
103
104 for (j = 0; j < num_pages; j++) {
105 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
106 val = 0;
107 for (i = 0; i < vmpgsize; i += 16) {
108 memset(&buf[j][i], val, 16);
109 if (i < 3400 * (vmpgsize / 4096)) {
110 val++;
111 }
112 }
113 }
114 }
115
116 void
117 freeze_helper_process(void)
118 {
119 int ret, freeze_enabled;
120 int64_t compressed_before, compressed_after, input_before, input_after;
121 size_t length;
122 int errno_sysctl_freeze;
123
124 length = sizeof(compressed_before);
125 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_before, &length, NULL, 0),
126 "failed to query vm.compressor_compressed_bytes");
127 length = sizeof(input_before);
128 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_before, &length, NULL, 0),
129 "failed to query vm.compressor_input_bytes");
130
131 T_STAT_MEASURE(s) {
132 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
133 errno_sysctl_freeze = errno;
134 };
135
136 length = sizeof(compressed_after);
137 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_after, &length, NULL, 0),
138 "failed to query vm.compressor_compressed_bytes");
139 length = sizeof(input_after);
140 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_after, &length, NULL, 0),
141 "failed to query vm.compressor_input_bytes");
142
143 length = sizeof(freeze_enabled);
144 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
145 "failed to query vm.freeze_enabled");
146 if (freeze_enabled) {
147 errno = errno_sysctl_freeze;
148 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
149 } else {
150 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
151 T_LOG("Freeze has been disabled. Terminating early.");
152 T_END;
153 }
154
155 dt_stat_add(r, (double)(input_after - input_before) / (double)(compressed_after - compressed_before));
156
157 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &pid, sizeof(pid));
158 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
159
160 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGUSR1), "failed to send SIGUSR1 to child process");
161 }
162
163 void
164 cleanup(void)
165 {
166 int status = 0;
167
168 /* No helper process. */
169 if (pid == -1) {
170 return;
171 }
172 /* Kill the helper process. */
173 kill(pid, SIGKILL);
174 }
175
176 void
177 run_compressor_test(int size_mb, int page_type)
178 {
179 int ret;
180 char sz_str[50];
181 char pt_str[50];
182 char **launch_tool_args;
183 char testpath[PATH_MAX];
184 uint32_t testpath_buf_size;
185 dispatch_source_t ds_freeze, ds_proc;
186 int freeze_enabled;
187 size_t length;
188
189 length = sizeof(freeze_enabled);
190 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
191 "failed to query vm.freeze_enabled");
192 if (!freeze_enabled) {
193 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
194 T_SKIP("Freeze has been disabled. Skipping test.");
195 }
196
197 T_ATEND(cleanup);
198
199 r = dt_stat_create("(input bytes / compressed bytes)", "compression_ratio");
200 s = dt_stat_time_create("compressor_latency");
201 // This sets the A/B failure threshold at 50% of baseline for compressor_latency
202 dt_stat_set_variable(s, kPCFailureThresholdPctVar, 50.0);
203
204 signal(SIGUSR1, SIG_IGN);
205 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
206 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
207
208 dispatch_source_set_event_handler(ds_freeze, ^{
209 if (!dt_stat_stable(s)) {
210 freeze_helper_process();
211 } else {
212 dt_stat_finalize(s);
213 dt_stat_finalize(r);
214
215 kill(pid, SIGKILL);
216 dispatch_source_cancel(ds_freeze);
217 }
218 });
219 dispatch_activate(ds_freeze);
220
221 testpath_buf_size = sizeof(testpath);
222 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
223 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
224 T_LOG("Executable path: %s", testpath);
225
226 sprintf(sz_str, "%d", size_mb);
227 sprintf(pt_str, "%d", page_type);
228 launch_tool_args = (char *[]){
229 testpath,
230 "-n",
231 "allocate_pages",
232 "--",
233 sz_str,
234 pt_str,
235 NULL
236 };
237
238 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
239 ret = dt_launch_tool(&pid, launch_tool_args, true, NULL, NULL);
240 if (ret != 0) {
241 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
242 }
243 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool");
244
245 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
246 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
247
248 dispatch_source_set_event_handler(ds_proc, ^{
249 int status = 0, code = 0;
250 pid_t rc = waitpid(pid, &status, 0);
251 T_QUIET; T_ASSERT_EQ(rc, pid, "waitpid");
252 code = WEXITSTATUS(status);
253
254 if (code == 0) {
255 T_END;
256 } else if (code > 0 && code < EXIT_CODE_MAX) {
257 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
258 } else {
259 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
260 }
261 });
262 dispatch_activate(ds_proc);
263
264 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGCONT), "failed to send SIGCONT to child process");
265 dispatch_main();
266 }
267
268 T_HELPER_DECL(allocate_pages, "allocates pages to compress") {
269 int i, j, ret, size_mb, page_type, vmpgsize;
270 size_t vmpgsize_length;
271 __block int num_pages;
272 __block char **buf;
273 dispatch_source_t ds_signal;
274
275 vmpgsize_length = sizeof(vmpgsize);
276 ret = sysctlbyname("vm.pagesize", &vmpgsize, &vmpgsize_length, NULL, 0);
277 if (ret != 0) {
278 exit(SYSCTL_VM_PAGESIZE_FAILED);
279 }
280 if (vmpgsize == 0) {
281 exit(VM_PAGESIZE_IS_ZERO);
282 }
283
284 if (argc < 2) {
285 exit(TOO_FEW_ARGUMENTS);
286 }
287
288 size_mb = atoi(argv[0]);
289 page_type = atoi(argv[1]);
290 num_pages = size_mb * 1024 * 1024 / vmpgsize;
291 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
292
293 // Switch on the type of page requested
294 switch (page_type) {
295 case ALL_ZEROS:
296 allocate_zero_pages(buf, num_pages, vmpgsize);
297 break;
298 case MOSTLY_ZEROS:
299 allocate_mostly_zero_pages(buf, num_pages, vmpgsize);
300 break;
301 case RANDOM:
302 allocate_random_pages(buf, num_pages, vmpgsize);
303 break;
304 case TYPICAL:
305 allocate_representative_pages(buf, num_pages, vmpgsize);
306 break;
307 default:
308 exit(UNKNOWN_PAGE_TYPE);
309 }
310
311 for (j = 0; j < num_pages; j++) {
312 i = buf[j][0];
313 }
314
315 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
316 /* Signal to the parent that we're done allocating and it's ok to freeze us */
317 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
318 if (kill(getppid(), SIGUSR1) != 0) {
319 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
320 }
321 });
322
323 signal(SIGUSR1, SIG_IGN);
324 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
325 if (ds_signal == NULL) {
326 exit(DISPATCH_SOURCE_CREATE_FAILED);
327 }
328
329 dispatch_source_set_event_handler(ds_signal, ^{
330 volatile int tmp;
331
332 /* Make sure all the pages are accessed before trying to freeze again */
333 for (int x = 0; x < num_pages; x++) {
334 tmp = buf[x][0];
335 }
336 if (kill(getppid(), SIGUSR1) != 0) {
337 exit(SIGNAL_TO_PARENT_FAILED);
338 }
339 });
340 dispatch_activate(ds_signal);
341
342 dispatch_main();
343 }
344
345 // Numbers for 10MB and above are fairly reproducible. Anything smaller shows a lot of variation.
346
347 // Keeping just the 100MB version for iOSMark
348 #ifndef DT_IOSMARK
349 T_DECL(compr_10MB_zero,
350 "Compression latency for 10MB - zero pages",
351 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
352 run_compressor_test(10, ALL_ZEROS);
353 }
354
355 T_DECL(compr_10MB_mostly_zero,
356 "Compression latency for 10MB - mostly zero pages",
357 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
358 run_compressor_test(10, MOSTLY_ZEROS);
359 }
360
361 T_DECL(compr_10MB_random,
362 "Compression latency for 10MB - random pages",
363 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
364 run_compressor_test(10, RANDOM);
365 }
366
367 T_DECL(compr_10MB_typical,
368 "Compression latency for 10MB - typical pages",
369 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
370 run_compressor_test(10, TYPICAL);
371 }
372
373 T_DECL(compr_100MB_zero,
374 "Compression latency for 100MB - zero pages",
375 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
376 run_compressor_test(100, ALL_ZEROS);
377 }
378
379 T_DECL(compr_100MB_mostly_zero,
380 "Compression latency for 100MB - mostly zero pages",
381 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
382 run_compressor_test(100, MOSTLY_ZEROS);
383 }
384
385 T_DECL(compr_100MB_random,
386 "Compression latency for 100MB - random pages",
387 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
388 run_compressor_test(100, RANDOM);
389 }
390 #endif
391
392 T_DECL(compr_100MB_typical,
393 "Compression latency for 100MB - typical pages",
394 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY)) {
395 run_compressor_test(100, TYPICAL);
396 }