3 #include <sys/sysctl.h>
4 #include <mach-o/dyld.h>
9 #include <darwintest.h>
10 #include <darwintest_utils.h>
13 T_META_NAMESPACE("xnu.vm.perf"),
14 T_META_CHECK_LEAKS(false)
24 #define CREATE_LIST(X) \
26 X(TOO_FEW_ARGUMENTS) \
27 X(SYSCTL_VM_PAGESIZE_FAILED) \
28 X(VM_PAGESIZE_IS_ZERO) \
29 X(UNKNOWN_PAGE_TYPE) \
30 X(DISPATCH_SOURCE_CREATE_FAILED) \
31 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
32 X(SIGNAL_TO_PARENT_FAILED) \
35 #define EXIT_CODES_ENUM(VAR) VAR,
37 CREATE_LIST(EXIT_CODES_ENUM
)
40 #define EXIT_CODES_STRING(VAR) #VAR,
41 static const char *exit_codes_str
[] = {
42 CREATE_LIST(EXIT_CODES_STRING
)
46 static pid_t pid
= -1;
48 static dt_stat_time_t s
;
50 void allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
51 void allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
52 void allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
);
53 void allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
);
54 void run_compressor_test(int size_mb
, int page_type
);
55 void freeze_helper_process(void);
57 void allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
) {
60 for (i
= 0; i
< num_pages
; i
++) {
61 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
62 memset(buf
[i
], 0, vmpgsize
);
66 void allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
) {
69 for (i
= 0; i
< num_pages
; i
++) {
70 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
71 memset(buf
[i
], 0, vmpgsize
);
72 for (j
= 0; j
< 40; j
++) {
73 buf
[i
][j
] = (char)(j
+1);
78 void allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
) {
81 for (i
= 0; i
< num_pages
; i
++) {
82 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
83 arc4random_buf((void*)buf
[i
], (size_t)vmpgsize
);
87 // Gives us the compression ratio we see in the typical case (~2.7)
88 void allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
) {
92 for (j
= 0; j
< num_pages
; j
++) {
93 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
95 for (i
= 0; i
< vmpgsize
; i
+= 16) {
96 memset(&buf
[j
][i
], val
, 16);
97 if (i
< 3400 * (vmpgsize
/ 4096)) {
104 void freeze_helper_process(void) {
106 int64_t compressed_before
, compressed_after
, input_before
, input_after
;
110 * Wait a bit after the pages have been allocated/accessed before trying to freeze.
111 * The sleeps are not needed, they just separate the operations into three logical chunks:
112 * touch a few pages, freeze them, thaw them (and repeat).
115 length
= sizeof(compressed_before
);
116 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_before
, &length
, NULL
, 0),
117 "failed to query vm.compressor_compressed_bytes");
118 length
= sizeof(input_before
);
119 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_before
, &length
, NULL
, 0),
120 "failed to query vm.compressor_input_bytes");
123 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
126 length
= sizeof(compressed_after
);
127 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_after
, &length
, NULL
, 0),
128 "failed to query vm.compressor_compressed_bytes");
129 length
= sizeof(input_after
);
130 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_after
, &length
, NULL
, 0),
131 "failed to query vm.compressor_input_bytes");
133 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed on pid %d", pid
);
135 dt_stat_add(r
, (double)(input_after
- input_before
)/(double)(compressed_after
- compressed_before
));
137 /* Wait a bit after freezing before trying to thaw */
139 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &pid
, sizeof(pid
));
140 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed on pid %d", pid
);
142 /* Wait a bit after thawing before pages can be re-accessed */
144 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGUSR1
), "failed to send SIGUSR1 to child process [%d]", pid
);
147 void run_compressor_test(int size_mb
, int page_type
) {
151 char **launch_tool_args
;
152 char testpath
[PATH_MAX
];
153 uint32_t testpath_buf_size
;
154 dispatch_source_t ds_freeze
, ds_proc
;
156 #ifndef CONFIG_FREEZE
157 T_SKIP("Task freeze not supported.");
160 r
= dt_stat_create("(input bytes / compressed bytes)", "compression_ratio");
161 s
= dt_stat_time_create("compressor_latency");
163 signal(SIGUSR1
, SIG_IGN
);
164 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
165 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
167 dispatch_source_set_event_handler(ds_freeze
, ^{
168 if (!dt_stat_stable(s
)) {
169 freeze_helper_process();
175 dispatch_source_cancel(ds_freeze
);
178 dispatch_activate(ds_freeze
);
180 testpath_buf_size
= sizeof(testpath
);
181 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
182 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
183 T_LOG("Executable path: %s", testpath
);
185 sprintf(sz_str
, "%d", size_mb
);
186 sprintf(pt_str
, "%d", page_type
);
187 launch_tool_args
= (char *[]){
197 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
198 ret
= dt_launch_tool(&pid
, launch_tool_args
, true, NULL
, NULL
);
200 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
202 T_QUIET
; T_ASSERT_POSIX_SUCCESS(pid
, "dt_launch_tool");
204 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
205 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
207 dispatch_source_set_event_handler(ds_proc
, ^{
208 int status
= 0, code
= 0;
209 pid_t rc
= waitpid(pid
, &status
, 0);
210 T_QUIET
; T_ASSERT_EQ(rc
, pid
, "waitpid");
211 code
= WEXITSTATUS(status
);
215 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
216 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
218 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
221 dispatch_activate(ds_proc
);
223 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGCONT
), "failed to send SIGCONT to child process [%d]", pid
);
227 T_HELPER_DECL(allocate_pages
, "allocates pages to compress") {
228 int i
, j
, ret
, size_mb
, page_type
, vmpgsize
;
229 size_t vmpgsize_length
;
230 __block
int num_pages
;
232 dispatch_source_t ds_signal
;
234 vmpgsize_length
= sizeof(vmpgsize
);
235 ret
= sysctlbyname("vm.pagesize", &vmpgsize
, &vmpgsize_length
, NULL
, 0);
237 exit(SYSCTL_VM_PAGESIZE_FAILED
);
240 exit(VM_PAGESIZE_IS_ZERO
);
244 exit(TOO_FEW_ARGUMENTS
);
247 size_mb
= atoi(argv
[0]);
248 page_type
= atoi(argv
[1]);
249 num_pages
= size_mb
* 1024 * 1024 / vmpgsize
;
250 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
252 // Switch on the type of page requested
255 allocate_zero_pages(buf
, num_pages
, vmpgsize
);
258 allocate_mostly_zero_pages(buf
, num_pages
, vmpgsize
);
261 allocate_random_pages(buf
, num_pages
, vmpgsize
);
264 allocate_representative_pages(buf
, num_pages
, vmpgsize
);
267 exit(UNKNOWN_PAGE_TYPE
);
270 for (j
= 0; j
< num_pages
; j
++) {
274 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
275 /* Signal to the parent that we're done allocating and it's ok to freeze us */
276 printf("Sending initial signal to parent to begin freezing\n");
277 if (kill(getppid(), SIGUSR1
) != 0) {
278 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
282 signal(SIGUSR1
, SIG_IGN
);
283 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
284 if (ds_signal
== NULL
) {
285 exit(DISPATCH_SOURCE_CREATE_FAILED
);
288 dispatch_source_set_event_handler(ds_signal
, ^{
291 /* Make sure all the pages are accessed before trying to freeze again */
292 for (int x
= 0; x
< num_pages
; x
++) {
295 if (kill(getppid(), SIGUSR1
) != 0) {
296 exit(SIGNAL_TO_PARENT_FAILED
);
299 dispatch_activate(ds_signal
);
304 // Numbers for 10MB and above are fairly reproducible. Anything smaller shows a lot of variation.
305 T_DECL(compr_10MB_zero
, "Compressor latencies") {
306 run_compressor_test(10, ALL_ZEROS
);
309 T_DECL(compr_10MB_mostly_zero
, "Compressor latencies") {
310 run_compressor_test(10, MOSTLY_ZEROS
);
313 T_DECL(compr_10MB_random
, "Compressor latencies") {
314 run_compressor_test(10, RANDOM
);
317 T_DECL(compr_10MB_typical
, "Compressor latencies") {
318 run_compressor_test(10, TYPICAL
);
321 T_DECL(compr_100MB_zero
, "Compressor latencies") {
322 run_compressor_test(100, ALL_ZEROS
);
325 T_DECL(compr_100MB_mostly_zero
, "Compressor latencies") {
326 run_compressor_test(100, MOSTLY_ZEROS
);
329 T_DECL(compr_100MB_random
, "Compressor latencies") {
330 run_compressor_test(100, RANDOM
);
333 T_DECL(compr_100MB_typical
, "Compressor latencies") {
334 run_compressor_test(100, TYPICAL
);