]>
Commit | Line | Data |
---|---|---|
1a7e3f61 A |
1 | #include <stdio.h> |
2 | #include <stdlib.h> | |
3 | #include <fcntl.h> | |
4 | #include <unistd.h> | |
5 | #include <getopt.h> | |
6 | #include <string.h> | |
7 | #include <sys/resource.h> | |
8 | #include <pthread.h> | |
9 | #include <sys/time.h> | |
10 | #include <sys/stat.h> | |
11 | #include <sys/types.h> | |
12 | #include <math.h> | |
13 | #include <signal.h> | |
14 | #include <libkern/OSAtomic.h> | |
15 | #include <limits.h> | |
16 | #include <errno.h> | |
d52496fd A |
17 | #include <CoreFoundation/CoreFoundation.h> |
18 | #include "panic.h" | |
19 | #include <IOKit/IOKitLib.h> | |
570be395 | 20 | #include <spawn.h> |
1a7e3f61 | 21 | |
cf37c299 A |
22 | #define IO_MODE_SEQ 0 |
23 | #define IO_MODE_RANDOM 1 | |
24 | ||
25 | #define WORKLOAD_TYPE_RO 0 | |
26 | #define WORKLOAD_TYPE_WO 1 | |
27 | #define WORKLOAD_TYPE_RW 2 | |
28 | ||
29 | #define MAX_THREADS 1000 | |
30 | #define MAX_FILENAME 64 | |
31 | #define MAX_ITERATIONS 10000 | |
d52496fd A |
32 | #define LATENCY_BIN_SIZE 1000 |
33 | #define LATENCY_BINS 31 | |
cf37c299 | 34 | #define LOW_LATENCY_BIN_SIZE 50 |
d52496fd | 35 | #define LOW_LATENCY_BINS 21 |
1a7e3f61 | 36 | #define THROUGHPUT_INTERVAL 5000 |
cf37c299 A |
37 | #define DEFAULT_FILE_SIZE (262144) |
38 | #define BLOCKSIZE 1024 | |
39 | #define MAX_CMD_SIZE 256 | |
40 | #define PG_MASK ~(0xFFF) | |
c0bbac3a A |
41 | #define kIONVMeANS2ControllerString "AppleANS2Controller" |
42 | #define kIONVMeANS2EmbeddedControllerString "AppleANS2NVMeController" | |
43 | #define kIONVMeControllerString "AppleNVMeController" | |
d52496fd A |
44 | |
45 | typedef enum { | |
46 | kDefaultDevice = 0, | |
47 | kNVMeDevice = 1, | |
48 | kNVMeDeviceANS2 = 2, | |
49 | } qos_device_type_t; | |
cf37c299 A |
50 | |
51 | int burst_count = 10; /* Unit: Number ; Desc.: I/O Burst Count */ | |
52 | int inter_burst_duration = 0; /* Unit: msecs ; Desc.: I/O Inter-Burst Duration (-1: Random value [0,100]) */ | |
53 | int inter_io_delay_ms = 0; /* Unit: msecs ; Desc.: Inter I/O Delay */ | |
54 | int thread_count = 1; /* Unit: Number ; Desc.: Thread Count */ | |
1a7e3f61 | 55 | int workload_type = WORKLOAD_TYPE_RO; /* Unit: 0/1/2 ; Desc.: Workload Type */ |
cf37c299 A |
56 | int io_size = 4096; /* Unit: Bytes ; Desc.: I/O Unit Size */ |
57 | int sync_frequency_ms = 0; /* Unit: msecs ; Desc.: Sync thread frequency (0: Indicates no sync) */ | |
58 | int io_mode = 0; /* Unit: 0/1 ; Desc.: I/O Mode (Seq./Rand.) */ | |
1a7e3f61 | 59 | int test_duration = 0; /* Unit: secs ; Desc.: Total Test Duration (0 indicates wait for Ctrl+C signal) */ |
cf37c299 A |
60 | int io_tier = 0; /* Unit: 0/1/2/3; Desc.: I/O Tier */ |
61 | int file_size = DEFAULT_FILE_SIZE; /* Unit: pages ; Desc.: File Size in 4096 byte blocks */ | |
62 | int cached_io_flag = 0; /* Unit: 0/1 ; Desc.: I/O Caching behavior (no-cached/cached) */ | |
d52496fd | 63 | int io_qos_timeout_ms = 0; /* Unit: msecs ; Desc.: I/O QOS timeout */ |
cf37c299 | 64 | char *user_fname; |
1a7e3f61 | 65 | int user_specified_file = 0; |
d52496fd | 66 | qos_device_type_t qos_device = 0; |
1a7e3f61 | 67 | |
d52496fd A |
68 | int64_t total_io_count = 0; |
69 | int64_t total_io_size = 0; | |
70 | int64_t total_io_time = 0; | |
71 | int64_t max_io_time = 0; | |
72 | int64_t total_burst_count = 0; | |
1a7e3f61 A |
73 | int64_t latency_histogram[LATENCY_BINS]; |
74 | int64_t burst_latency_histogram[LATENCY_BINS]; | |
75 | int64_t low_latency_histogram[LOW_LATENCY_BINS]; | |
76 | int64_t throughput_histogram[MAX_ITERATIONS]; | |
77 | int64_t throughput_index; | |
d52496fd | 78 | CFRunLoopTimerRef runLoopTimer = NULL; |
1a7e3f61 A |
79 | |
80 | void print_usage(void); | |
d52496fd | 81 | void print_data_percentage(double percent); |
1a7e3f61 A |
82 | void print_stats(void); |
83 | unsigned int find_io_bin(int64_t latency, int latency_bin_size, int latency_bins); | |
84 | void signalHandler(int sig); | |
d52496fd A |
85 | void assertASP(CFRunLoopTimerRef timer, void *info ); |
86 | void start_qos_timer(void); | |
87 | void stop_qos_timer(void); | |
1a7e3f61 A |
88 | void perform_io(int fd, char *buf, int size, int type); |
89 | void *sync_routine(void *arg); | |
90 | void *calculate_throughput(void *arg); | |
91 | void *io_routine(void *arg); | |
92 | void validate_option(int value, int min, int max, char *option, char *units); | |
93 | void print_test_setup(int value, char *option, char *units, char *comment); | |
94 | void setup_process_io_policy(int io_tier); | |
d52496fd A |
95 | void setup_qos_device(void); |
96 | void print_latency_histogram(int64_t *data, int latency_bins, int latency_bin_size, double io_count); | |
570be395 | 97 | int system_cmd(char *command); |
1a7e3f61 | 98 | |
cf37c299 A |
99 | void |
100 | print_usage(void) | |
1a7e3f61 A |
101 | { |
102 | printf("Usage: ./iosim [options]\n"); | |
103 | printf("Options:\n"); | |
104 | printf("-c: (number) Burst Count. No. of I/Os performed in an I/O burst\n"); | |
105 | printf("-i: (msecs) Inter Burst Duration. Amount of time the thread sleeps between bursts (-1 indicates random durations between 0-100 msecs)\n"); | |
106 | printf("-d: (msecs) Inter I/O delay. Amount of time between issuing I/Os\n"); | |
107 | printf("-t: (number) Thread count\n"); | |
108 | printf("-f: (0/1/2 : Read-Only/Write-Only/Mixed RW) Workload Type\n"); | |
109 | printf("-m: (0/1 : Sequential/Random) I/O pattern\n"); | |
110 | printf("-j: (number) Size of I/O in bytes\n"); | |
111 | printf("-s: (msecs) Frequency of sync() calls\n"); | |
112 | printf("-x: (secs) Test duration (0 indicates that the tool would wait for a Ctrl-C)\n"); | |
113 | printf("-l: (0/1/2/3) I/O Tier\n"); | |
114 | printf("-z: (number) File Size in pages (1 page = 4096 bytes) \n"); | |
115 | printf("-n: (string) File name used for tests (the tool would create files if this option is not specified)\n"); | |
116 | printf("-a: (0/1 : Non-cached/Cached) I/O Caching behavior\n"); | |
d52496fd | 117 | printf("-q: (msecs) I/O QoS timeout. Time of I/O before drive assert and system panic\n"); |
1a7e3f61 A |
118 | } |
119 | ||
d52496fd | 120 | void print_data_percentage(double percent) |
1a7e3f61 A |
121 | { |
122 | int count = (int)(round(percent / 5.0)); | |
123 | int spaces = 20 - count; | |
124 | printf("| "); | |
125 | for(; count > 0; count--) | |
126 | printf("*"); | |
127 | for(; spaces > 0; spaces--) | |
128 | printf(" "); | |
129 | printf("|"); | |
130 | } | |
131 | ||
d52496fd | 132 | void print_latency_histogram(int64_t *data, int latency_bins, int latency_bin_size, double io_count) |
1a7e3f61 A |
133 | { |
134 | double percentage; | |
135 | char label[MAX_FILENAME]; | |
136 | int i; | |
137 | ||
138 | for (i = 0; i < latency_bins; i++) { | |
139 | if (i == (latency_bins - 1)) | |
140 | snprintf(label, MAX_FILENAME, "> %d usecs", i * latency_bin_size); | |
141 | else | |
142 | snprintf(label, MAX_FILENAME, "%d - %d usecs", i * latency_bin_size, (i+1) * latency_bin_size); | |
143 | printf("%25s ", label); | |
d52496fd A |
144 | percentage = ((double)data[i] * 100.000000) / io_count; |
145 | print_data_percentage(percentage); | |
146 | printf(" %.6lf%%\n", percentage); | |
1a7e3f61 A |
147 | } |
148 | printf("\n"); | |
149 | } | |
150 | ||
151 | void print_stats() | |
152 | { | |
153 | int i; | |
154 | double percentage; | |
155 | char label[MAX_FILENAME]; | |
156 | ||
157 | printf("I/O Statistics:\n"); | |
158 | ||
159 | printf("Total I/Os : %lld\n", total_io_count); | |
cf37c299 | 160 | printf("Avg. Latency : %.2lf usecs\n", ((double)total_io_time) / ((double)total_io_count)); |
d52496fd | 161 | printf("Max. Latency : %.2lf usecs\n", ((double)max_io_time)); |
1a7e3f61 A |
162 | |
163 | printf("Low Latency Histogram: \n"); | |
d52496fd | 164 | print_latency_histogram(low_latency_histogram, LOW_LATENCY_BINS, LOW_LATENCY_BIN_SIZE, (double)total_io_count); |
1a7e3f61 | 165 | printf("Latency Histogram: \n"); |
d52496fd | 166 | print_latency_histogram(latency_histogram, LATENCY_BINS, LATENCY_BIN_SIZE, (double)total_io_count); |
1a7e3f61 | 167 | printf("Burst Avg. Latency Histogram: \n"); |
d52496fd | 168 | print_latency_histogram(burst_latency_histogram, LATENCY_BINS, LATENCY_BIN_SIZE, (double)total_burst_count); |
cf37c299 | 169 | |
1a7e3f61 A |
170 | printf("Throughput Timeline: \n"); |
171 | ||
172 | int64_t max_throughput = 0; | |
173 | for (i = 0; i < throughput_index; i++) { | |
174 | if (max_throughput < throughput_histogram[i]) | |
175 | max_throughput = throughput_histogram[i]; | |
176 | } | |
177 | ||
178 | for (i = 0; i < throughput_index; i++) { | |
179 | snprintf(label, MAX_FILENAME, "T=%d msecs", (i+1) * THROUGHPUT_INTERVAL); | |
180 | printf("%25s ", label); | |
181 | percentage = ((double)throughput_histogram[i] * 100) / (double)max_throughput; | |
182 | print_data_percentage((int)percentage); | |
183 | printf("%.2lf MBps\n", ((double)throughput_histogram[i] / 1048576.0) / ((double)THROUGHPUT_INTERVAL / 1000.0)); | |
184 | } | |
185 | printf("\n"); | |
1a7e3f61 A |
186 | } |
187 | ||
188 | unsigned int find_io_bin(int64_t latency, int latency_bin_size, int latency_bins) | |
189 | { | |
190 | int bin = (int) (latency / latency_bin_size); | |
191 | if (bin >= latency_bins) | |
192 | bin = latency_bins - 1; | |
193 | return bin; | |
194 | } | |
195 | ||
196 | void signalHandler(int sig) | |
197 | { | |
198 | printf("\n"); | |
199 | print_stats(); | |
200 | exit(0); | |
201 | } | |
202 | ||
d52496fd A |
203 | void setup_qos_device(void) |
204 | { | |
205 | kern_return_t status = kIOReturnError; | |
206 | io_iterator_t iterator = IO_OBJECT_NULL; | |
207 | ||
208 | if(io_qos_timeout_ms <= 0) | |
209 | return; | |
210 | ||
211 | printf ( "*** setup_qos_device *** \n" ); | |
212 | ||
213 | status = IOServiceGetMatchingServices ( kIOMasterPortDefault, IOServiceMatching ( kIONVMeANS2ControllerString ), &iterator ); | |
214 | ||
215 | if ( status != kIOReturnSuccess ) | |
216 | return; | |
217 | ||
218 | if ( iterator != IO_OBJECT_NULL ) { | |
219 | printf ( "Found NVMe ANS2 Device \n" ); | |
220 | qos_device = kNVMeDeviceANS2; | |
c0bbac3a A |
221 | return; |
222 | } | |
223 | ||
224 | status = IOServiceGetMatchingServices ( kIOMasterPortDefault, IOServiceMatching ( kIONVMeANS2EmbeddedControllerString ), &iterator ); | |
225 | ||
226 | if ( status != kIOReturnSuccess ) | |
227 | return; | |
228 | ||
229 | if ( iterator != IO_OBJECT_NULL ) { | |
230 | printf ( "Found NVMe ANS2 Embedded Device \n" ); | |
231 | qos_device = kNVMeDeviceANS2; | |
232 | return; | |
233 | } | |
d52496fd | 234 | |
c0bbac3a | 235 | status= IOServiceGetMatchingServices ( kIOMasterPortDefault, IOServiceMatching ( kIONVMeControllerString ), &iterator ); |
d52496fd | 236 | |
c0bbac3a A |
237 | if ( status != kIOReturnSuccess ) |
238 | return; | |
d52496fd | 239 | |
c0bbac3a A |
240 | if ( iterator != IO_OBJECT_NULL ) { |
241 | printf ( "Found NVMe Device \n" ); | |
242 | qos_device = kNVMeDevice; | |
243 | return; | |
d52496fd | 244 | } |
c0bbac3a A |
245 | |
246 | printf ( "NVMe Device not found, not setting qos timeout\n" ); | |
247 | qos_device = kDefaultDevice; | |
248 | return; | |
d52496fd A |
249 | } |
250 | ||
251 | void assertASP(CFRunLoopTimerRef timer, void *info ) | |
252 | { | |
253 | char command [ 1024 ]; | |
254 | ||
255 | if(qos_device == kDefaultDevice) | |
256 | return; | |
257 | ||
258 | printf("assertASP. Timeout of IO exceeds = %d msec\n", io_qos_timeout_ms); | |
259 | ||
260 | // kNVMe_ANS2_Force_Assert_offset = 0x13EC, // GP59 | |
261 | // kNVMe_Force_Assert_Offset = 0x550, | |
262 | ||
263 | if(qos_device == kNVMeDeviceANS2) | |
264 | snprintf ( command, sizeof ( command ), "/usr/local/bin/nvmectl-tool.py -a WriteRegister32 $((0x13EC)) 0xFFFF" ); | |
265 | else if(qos_device == kNVMeDevice) | |
266 | snprintf ( command, sizeof ( command ), "/usr/local/bin/nvmectl-tool.py -a WriteRegister32 $((0x550)) 0xFFFF" ); | |
267 | else | |
268 | return; | |
269 | ||
270 | // Assert ASP | |
271 | printf("Command : %s\n", command); | |
570be395 | 272 | system_cmd(command); |
d52496fd A |
273 | |
274 | // Panic the system as well | |
275 | panic("IO time > QoS timeout"); | |
276 | ||
277 | return; | |
278 | } | |
279 | ||
280 | void start_qos_timer(void) | |
281 | { | |
282 | float timeout_sec; | |
283 | ||
284 | if(io_qos_timeout_ms <= 0) | |
285 | return; | |
286 | ||
287 | timeout_sec = (float)io_qos_timeout_ms/1000; | |
288 | ||
289 | // Schedule a "timeout" delayed task that checks IO's which take > timeout sec to complete | |
290 | runLoopTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent()+timeout_sec, 0, 0, 0, assertASP, NULL); | |
291 | CFRunLoopAddTimer(CFRunLoopGetMain(), runLoopTimer, kCFRunLoopDefaultMode); | |
292 | } | |
293 | ||
294 | void stop_qos_timer(void) | |
295 | { | |
296 | if(runLoopTimer == NULL) | |
297 | return; | |
298 | ||
299 | CFRunLoopTimerInvalidate(runLoopTimer); | |
300 | CFRunLoopRemoveTimer(CFRunLoopGetMain(), runLoopTimer, kCFRunLoopDefaultMode); | |
301 | CFRelease(runLoopTimer); | |
302 | } | |
1a7e3f61 A |
303 | |
304 | void perform_io(int fd, char *buf, int size, int type) | |
305 | { | |
306 | long ret; | |
307 | ||
308 | if (type == WORKLOAD_TYPE_RW) | |
309 | type = (rand() % 2) ? WORKLOAD_TYPE_WO : WORKLOAD_TYPE_RO; | |
310 | ||
311 | while(size > 0) { | |
312 | ||
313 | if (type == WORKLOAD_TYPE_RO) | |
314 | ret = read(fd, buf, size); | |
315 | else | |
316 | ret = write(fd, buf, size); | |
cf37c299 | 317 | |
1a7e3f61 A |
318 | if (ret == 0) { |
319 | if (lseek(fd, 0, SEEK_SET) < 0) { | |
320 | perror("lseek() to reset file offset to zero failed!\n"); | |
321 | goto error; | |
322 | } | |
323 | } | |
cf37c299 | 324 | |
1a7e3f61 A |
325 | if (ret < 0) { |
326 | perror("read/write syscall failed!\n"); | |
327 | goto error; | |
328 | } | |
329 | size -= ret; | |
330 | } | |
331 | ||
332 | return; | |
333 | ||
334 | error: | |
335 | print_stats(); | |
336 | exit(1); | |
337 | } | |
338 | ||
339 | void *sync_routine(void *arg) | |
340 | { | |
cf37c299 | 341 | while(1) { |
1a7e3f61 A |
342 | usleep(sync_frequency_ms * 1000); |
343 | sync(); | |
344 | } | |
345 | pthread_exit(NULL); | |
346 | } | |
347 | ||
348 | void *calculate_throughput(void *arg) | |
349 | { | |
350 | int64_t prev_total_io_size = 0; | |
351 | int64_t size; | |
352 | ||
353 | while(1) { | |
354 | usleep(THROUGHPUT_INTERVAL * 1000); | |
355 | size = total_io_size - prev_total_io_size; | |
356 | throughput_histogram[throughput_index] = size; | |
357 | prev_total_io_size = total_io_size; | |
cf37c299 | 358 | throughput_index++; |
1a7e3f61 A |
359 | } |
360 | pthread_exit(NULL); | |
cf37c299 | 361 | } |
1a7e3f61 A |
362 | |
363 | void *io_routine(void *arg) | |
364 | { | |
365 | struct timeval start_tv; | |
366 | struct timeval end_tv; | |
367 | int64_t elapsed; | |
368 | int64_t burst_elapsed; | |
369 | char *data; | |
370 | char test_filename[MAX_FILENAME]; | |
371 | struct stat filestat; | |
372 | int i, fd, io_thread_id; | |
373 | ||
374 | io_thread_id = (int)arg; | |
375 | if (user_specified_file) | |
887d5eed | 376 | strlcpy(test_filename, user_fname, MAX_FILENAME); |
1a7e3f61 A |
377 | else |
378 | snprintf(test_filename, MAX_FILENAME, "iosim-%d-%d", (int)getpid(), io_thread_id); | |
379 | ||
380 | if (0 > (fd = open(test_filename, O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { | |
381 | printf("Error opening file %s!\n", test_filename); | |
382 | exit(1); | |
383 | } | |
384 | ||
385 | if (fstat(fd, &filestat) < 0) { | |
386 | printf("Error stat()ing file %s!\n", test_filename); | |
387 | exit(1); | |
cf37c299 | 388 | } |
1a7e3f61 A |
389 | |
390 | if (filestat.st_size < io_size) { | |
391 | printf("%s: File size (%lld) smaller than I/O size (%d)!\n", test_filename, filestat.st_size, io_size); | |
392 | exit(1); | |
393 | } | |
394 | ||
395 | if (!cached_io_flag) | |
396 | fcntl(fd, F_NOCACHE, 1); | |
397 | ||
398 | fcntl(fd, F_RDAHEAD, 0); | |
cf37c299 | 399 | |
1a7e3f61 A |
400 | if(!(data = (char *)calloc(io_size, 1))) { |
401 | perror("Error allocating buffers for I/O!\n"); | |
402 | exit(1); | |
403 | } | |
404 | memset(data, '\0', io_size); | |
405 | ||
406 | while(1) { | |
1a7e3f61 A |
407 | burst_elapsed = 0; |
408 | ||
409 | for(i = 0; i < burst_count; i++) { | |
410 | if (io_mode == IO_MODE_RANDOM) { | |
411 | if (lseek(fd, (rand() % (filestat.st_size - io_size)) & PG_MASK, SEEK_SET) < 0) { | |
412 | perror("Error lseek()ing to random location in file!\n"); | |
413 | exit(1); | |
414 | } | |
415 | } | |
1a7e3f61 | 416 | |
d52496fd A |
417 | start_qos_timer(); |
418 | ||
1a7e3f61 A |
419 | gettimeofday(&start_tv, NULL); |
420 | perform_io(fd, data, io_size, workload_type); | |
421 | gettimeofday(&end_tv, NULL); | |
422 | ||
d52496fd A |
423 | stop_qos_timer(); |
424 | ||
1a7e3f61 A |
425 | OSAtomicIncrement64(&total_io_count); |
426 | OSAtomicAdd64(io_size, &total_io_size); | |
427 | elapsed = ((end_tv.tv_sec - start_tv.tv_sec) * 1000000) + (end_tv.tv_usec - start_tv.tv_usec); | |
d52496fd A |
428 | |
429 | if (elapsed > max_io_time) { | |
430 | max_io_time = elapsed; | |
431 | } | |
432 | ||
1a7e3f61 A |
433 | OSAtomicAdd64(elapsed, &total_io_time); |
434 | OSAtomicIncrement64(&(latency_histogram[find_io_bin(elapsed, LATENCY_BIN_SIZE, LATENCY_BINS)])); | |
435 | OSAtomicIncrement64(&(low_latency_histogram[find_io_bin(elapsed, LOW_LATENCY_BIN_SIZE, LOW_LATENCY_BINS)])); | |
436 | burst_elapsed += elapsed; | |
cf37c299 | 437 | |
1a7e3f61 A |
438 | if (inter_io_delay_ms) |
439 | usleep(inter_io_delay_ms * 1000); | |
440 | } | |
441 | ||
442 | burst_elapsed /= burst_count; | |
443 | OSAtomicIncrement64(&(burst_latency_histogram[find_io_bin(burst_elapsed, LATENCY_BIN_SIZE, LATENCY_BINS)])); | |
444 | OSAtomicIncrement64(&total_burst_count); | |
445 | ||
446 | if(inter_burst_duration == -1) | |
447 | usleep((rand() % 100) * 1000); | |
448 | else | |
449 | usleep(inter_burst_duration * 1000); | |
450 | } | |
451 | ||
452 | free(data); | |
453 | close(fd); | |
454 | pthread_exit(NULL); | |
455 | } | |
456 | ||
457 | void validate_option(int value, int min, int max, char *option, char *units) | |
458 | { | |
459 | if (value < min || value > max) { | |
460 | printf("Illegal option value %d for %s (Min value: %d %s, Max value: %d %s).\n", value, option, min, units, max, units); | |
461 | exit(1); | |
462 | } | |
463 | } | |
464 | ||
465 | void print_test_setup(int value, char *option, char *units, char *comment) | |
466 | { | |
467 | if (comment == NULL) | |
468 | printf("%32s: %16d %-16s\n", option, value, units); | |
469 | else | |
470 | printf("%32s: %16d %-16s (%s)\n", option, value, units, comment); | |
471 | } | |
472 | ||
473 | void setup_process_io_policy(int io_tier) | |
474 | { | |
475 | switch(io_tier) | |
476 | { | |
477 | case 0: | |
478 | if (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_IMPORTANT)) | |
479 | goto iopol_error; | |
480 | break; | |
481 | case 1: | |
482 | if (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_STANDARD)) | |
483 | goto iopol_error; | |
484 | break; | |
485 | case 2: | |
486 | if (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_UTILITY)) | |
487 | goto iopol_error; | |
488 | break; | |
489 | case 3: | |
490 | if (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE)) | |
491 | goto iopol_error; | |
492 | break; | |
493 | } | |
494 | return; | |
495 | ||
496 | iopol_error: | |
497 | printf("Error setting process-wide I/O policy to %d\n", io_tier); | |
498 | exit(1); | |
499 | } | |
500 | ||
501 | int main(int argc, char *argv[]) | |
502 | { | |
503 | int i, option = 0; | |
504 | pthread_t thread_list[MAX_THREADS]; | |
505 | pthread_t sync_thread; | |
506 | pthread_t throughput_thread; | |
507 | char fname[MAX_FILENAME]; | |
508 | ||
d52496fd | 509 | while((option = getopt(argc, argv,"hc:i:d:t:f:m:j:s:x:l:z:n:a:q:")) != -1) { |
1a7e3f61 A |
510 | switch(option) { |
511 | case 'c': | |
512 | burst_count = atoi(optarg); | |
513 | validate_option(burst_count, 0, INT_MAX, "Burst Count", "I/Os"); | |
514 | break; | |
515 | case 'i': | |
516 | inter_burst_duration = atoi(optarg); | |
517 | validate_option(inter_burst_duration, -1, INT_MAX, "Inter Burst duration", "msecs"); | |
518 | break; | |
519 | case 'd': | |
520 | inter_io_delay_ms = atoi(optarg); | |
521 | validate_option(inter_io_delay_ms, 0, INT_MAX, "Inter I/O Delay", "msecs"); | |
522 | break; | |
523 | case 't': | |
524 | thread_count = atoi(optarg); | |
525 | validate_option(thread_count, 0, MAX_THREADS, "Thread Count", "Threads"); | |
526 | break; | |
527 | case 'f': | |
528 | workload_type = atoi(optarg); | |
529 | validate_option(workload_type, 0, 2, "Workload Type", ""); | |
530 | break; | |
531 | case 'm': | |
532 | io_mode = atoi(optarg); | |
533 | validate_option(io_mode, 0, 1, "I/O Mode", ""); | |
534 | break; | |
535 | case 'j': | |
536 | io_size = atoi(optarg); | |
537 | validate_option(io_size, 0, INT_MAX, "I/O Size", "Bytes"); | |
538 | break; | |
539 | case 'h': | |
540 | print_usage(); | |
541 | exit(1); | |
542 | case 's': | |
543 | sync_frequency_ms = atoi(optarg); | |
544 | validate_option(sync_frequency_ms, 0, INT_MAX, "Sync. Frequency", "msecs"); | |
545 | break; | |
546 | case 'x': | |
547 | test_duration = atoi(optarg); | |
548 | validate_option(test_duration, 0, INT_MAX, "Test duration", "secs"); | |
549 | break; | |
550 | case 'l': | |
551 | io_tier = atoi(optarg); | |
552 | validate_option(io_tier, 0, 3, "I/O Tier", ""); | |
553 | break; | |
554 | case 'z': | |
555 | file_size = atoi(optarg); | |
556 | validate_option(file_size, 0, INT_MAX, "File Size", "bytes"); | |
cf37c299 | 557 | break; |
1a7e3f61 A |
558 | case 'n': |
559 | user_fname = optarg; | |
560 | user_specified_file = 1; | |
561 | break; | |
562 | case 'a': | |
563 | cached_io_flag = atoi(optarg); | |
564 | validate_option(cached_io_flag, 0, 1, "I/Os cached/no-cached", ""); | |
565 | break; | |
d52496fd A |
566 | case 'q': |
567 | io_qos_timeout_ms = atoi(optarg); | |
568 | validate_option(io_qos_timeout_ms, 0, INT_MAX, "I/O QoS timeout", "msecs"); | |
569 | break; | |
1a7e3f61 A |
570 | default: |
571 | printf("Unknown option %c\n", option); | |
572 | print_usage(); | |
573 | exit(1); | |
574 | } | |
575 | } | |
576 | ||
577 | printf("***********************TEST SETUP*************************\n"); | |
578 | ||
579 | print_test_setup(burst_count, "Burst Count", "I/Os", 0); | |
580 | print_test_setup(inter_burst_duration, "Inter Burst duration", "msecs", "-1 indicates random burst duration"); | |
581 | print_test_setup(inter_io_delay_ms, "Inter I/O Delay", "msecs", 0); | |
582 | print_test_setup(thread_count, "Thread Count", "Threads", 0); | |
583 | print_test_setup(workload_type, "Workload Type", "", "0:R 1:W 2:RW"); | |
584 | print_test_setup(io_mode, "I/O Mode", "", "0:Seq. 1:Rnd"); | |
585 | print_test_setup(io_size, "I/O Size", "Bytes", 0); | |
586 | print_test_setup(sync_frequency_ms, "Sync. Frequency", "msecs", "0 indicates no sync. thread"); | |
587 | print_test_setup(test_duration, "Test duration", "secs", "0 indicates tool waits for Ctrl+C"); | |
588 | print_test_setup(io_tier, "I/O Tier", "", 0); | |
589 | print_test_setup(cached_io_flag, "I/O Caching", "", "0 indicates non-cached I/Os"); | |
d52496fd | 590 | print_test_setup(io_qos_timeout_ms, "I/O QoS Threshold Timeout", "msecs", 0); |
1a7e3f61 | 591 | print_test_setup(0, "File read-aheads", "", "0 indicates read-aheads disabled"); |
cf37c299 | 592 | |
1a7e3f61 A |
593 | printf("**********************************************************\n"); |
594 | ||
595 | if (user_specified_file == 0) { | |
596 | char dd_command[MAX_CMD_SIZE]; | |
597 | for (i=0; i < thread_count; i++) { | |
598 | snprintf(fname, MAX_FILENAME, "iosim-%d-%d", (int)getpid(), i); | |
cf37c299 | 599 | snprintf(dd_command, MAX_CMD_SIZE, "dd if=/dev/urandom of=%s bs=4096 count=%d", fname, file_size); |
1a7e3f61 | 600 | printf("Creating file %s of size %lld...\n", fname, ((int64_t)file_size * 4096)); |
570be395 | 601 | system_cmd(dd_command); |
1a7e3f61 A |
602 | } |
603 | } else { | |
604 | printf("Using user specified file %s for all threads...\n", user_fname); | |
605 | } | |
570be395 | 606 | system_cmd("purge"); |
1a7e3f61 A |
607 | setup_process_io_policy(io_tier); |
608 | ||
d52496fd A |
609 | setup_qos_device(); |
610 | ||
cf37c299 A |
611 | printf("**********************************************************\n"); |
612 | printf("Creating threads and generating workload...\n"); | |
1a7e3f61 A |
613 | |
614 | signal(SIGINT, signalHandler); | |
615 | signal(SIGALRM, signalHandler); | |
616 | ||
617 | for(i=0; i < thread_count; i++) { | |
618 | if (pthread_create(&thread_list[i], NULL, io_routine, i) < 0) { | |
619 | perror("Could not create I/O thread!\n"); | |
620 | exit(1); | |
621 | } | |
622 | } | |
623 | ||
624 | if (sync_frequency_ms) { | |
625 | if (pthread_create(&sync_thread, NULL, sync_routine, NULL) < 0) { | |
626 | perror("Could not create sync thread!\n"); | |
627 | exit(1); | |
628 | } | |
629 | } | |
630 | ||
631 | if (pthread_create(&throughput_thread, NULL, calculate_throughput, NULL) < 0) { | |
632 | perror("Could not throughput calculation thread!\n"); | |
633 | exit(1); | |
634 | } | |
635 | ||
d52496fd A |
636 | if(io_qos_timeout_ms > 0) { |
637 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, (CFTimeInterval)test_duration, false); | |
638 | alarm(1); | |
639 | } else { | |
640 | /* All threads are now initialized */ | |
641 | if (test_duration) | |
642 | alarm(test_duration); | |
643 | } | |
1a7e3f61 A |
644 | |
645 | for(i=0; i < thread_count; i++) | |
646 | pthread_join(thread_list[i], NULL); | |
cf37c299 | 647 | |
1a7e3f61 A |
648 | if (sync_frequency_ms) |
649 | pthread_join(sync_thread, NULL); | |
650 | ||
651 | pthread_join(throughput_thread, NULL); | |
652 | ||
653 | pthread_exit(0); | |
1a7e3f61 | 654 | } |
570be395 A |
655 | |
656 | extern char **environ; | |
657 | ||
658 | int system_cmd(char *command) | |
659 | { | |
660 | // workaround for rdar://problem/53281655 | |
661 | pid_t pid; | |
662 | char *argv[] = {"sh", "-c", command, NULL}; | |
663 | int status; | |
664 | status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ); | |
665 | if (status == 0) { | |
666 | if (waitpid(pid, &status, 0) != -1) { | |
667 | return status; | |
668 | } else { | |
669 | perror("waitpid"); | |
670 | } | |
671 | } | |
672 | return -1; | |
673 | } |