Commit | Line | Data |
---|---|---|
1f961edd JA |
1 | /* |
2 | * direct disk operation exerciser | |
3 | * | |
4 | * (C) 2005 Jens Axboe <axboe@suse.de> | |
5 | * | |
6 | */ | |
7 | #include <stdio.h> | |
8 | #include <stdlib.h> | |
9 | #include <unistd.h> | |
10 | #include <fcntl.h> | |
11 | #include <errno.h> | |
12 | #include <sys/time.h> | |
13 | #include <sys/types.h> | |
14 | #include <sys/stat.h> | |
15 | #include <sys/ioctl.h> | |
16 | #include <libaio.h> | |
17 | #include <getopt.h> | |
18 | #include <locale.h> | |
19 | ||
20 | #include <linux/fs.h> | |
21 | ||
22 | #define MAX_DEPTH 128 | |
23 | ||
24 | static int io_size = 512; /* bytes */ | |
25 | static int sequential = 1; /* 0 for random, 1 for sequential */ | |
26 | static unsigned long long range = 2; /* megabytes */ | |
27 | static char filename[256] = "/dev/sdb"; | |
28 | static int depth = 32; | |
29 | static int total_run = 15000; | |
30 | static int display_stats = 0; | |
31 | static int writing = 0; | |
32 | static int force = 0; | |
33 | ||
34 | struct iocb *iocbs[MAX_DEPTH]; | |
35 | struct io_event *events; | |
36 | ||
37 | struct drand48_data random_state; | |
38 | static unsigned long long next_block; | |
39 | static unsigned long long blocks_done; | |
40 | static unsigned long long nr_blocks; | |
41 | ||
42 | #define ALIGN(buf) (((unsigned long) (buf) + (io_size-1)) & ~((io_size-1))) | |
43 | ||
44 | unsigned long long get_next_offset(void) | |
45 | { | |
46 | unsigned long long block; | |
47 | long r; | |
48 | ||
49 | if (sequential) { | |
50 | if (next_block >= nr_blocks) | |
51 | next_block = 0; | |
52 | ||
53 | block = next_block; | |
54 | next_block += (256 * 1024 / io_size); | |
55 | return block; | |
56 | } | |
57 | ||
58 | lrand48_r(&random_state, &r); | |
59 | block = (1 + (double) (nr_blocks) * (unsigned long long) r / (RAND_MAX+1.0)); | |
60 | return block - 1; | |
61 | } | |
62 | ||
63 | void init_randomizer(void) | |
64 | { | |
65 | unsigned long seed = 0; | |
66 | ||
67 | if (sequential) | |
68 | return; | |
69 | ||
70 | srand48_r(seed, &random_state); | |
71 | } | |
72 | ||
73 | unsigned long time_elapsed(struct timeval *start, struct timeval *end) | |
74 | { | |
75 | int seconds = end->tv_sec - start->tv_sec; | |
76 | int useconds = end->tv_usec - start->tv_usec; | |
77 | ||
78 | return (1000 * seconds) + (useconds / 1000); | |
79 | } | |
80 | ||
81 | void do_io(io_context_t io_ctx, int fd, char *buffer) | |
82 | { | |
83 | struct timeval real_start, start, end; | |
84 | int i, ret, cur_depth; | |
85 | unsigned long ops, best_ops; | |
86 | unsigned long bytes, best_bytes; | |
87 | ||
88 | for (i = 0; i < depth; i++) { | |
89 | struct iocb *io = iocbs[i]; | |
90 | char *b = buffer + i * io_size; | |
91 | unsigned long long off = get_next_offset(); | |
92 | unsigned long long offset = off * io_size; | |
93 | ||
94 | if (writing) | |
95 | io_prep_pwrite(io, fd, b, io_size, offset); | |
96 | else | |
97 | io_prep_pread(io, fd, b, io_size, offset); | |
98 | } | |
99 | ||
100 | ret = io_submit(io_ctx, depth, iocbs); | |
101 | if (ret < 0) { | |
102 | fprintf(stderr, "io_submit: %d\n", ret); | |
103 | return; | |
104 | } | |
105 | ||
106 | cur_depth = ret; | |
107 | gettimeofday(&start, NULL); | |
108 | gettimeofday(&real_start, NULL); | |
109 | ||
110 | best_bytes = best_ops = bytes = ops = 0; | |
111 | do { | |
112 | struct timespec ts = { .tv_sec = 30, .tv_nsec = 0 }; | |
113 | unsigned long elapsed; | |
114 | ||
115 | ret = io_getevents(io_ctx, 1, cur_depth, events, &ts); | |
116 | if (ret < 0) { | |
117 | if (ret == EINTR) | |
118 | continue; | |
119 | ||
120 | fprintf(stderr, "io_getevents: %d\n", ret); | |
121 | break; | |
122 | } else if (!ret) { | |
123 | fprintf(stderr, "No events completed, depth %d\n", cur_depth); | |
124 | cur_depth = 0; | |
125 | break; | |
126 | } | |
127 | ||
128 | cur_depth -= ret; | |
129 | ops += (ret * (io_size >> 9)); | |
130 | bytes += (ret * io_size); | |
131 | blocks_done += ret; | |
132 | ||
133 | gettimeofday(&end, NULL); | |
134 | ||
135 | elapsed = time_elapsed(&start, &end); | |
136 | if (elapsed >= 1000) { | |
137 | unsigned long kb = bytes >> 10; | |
138 | unsigned long o, b; | |
139 | ||
140 | o = ops * 1000 / elapsed; | |
141 | b = kb * 1000 / elapsed; | |
142 | ||
143 | if (display_stats) | |
144 | printf("stat: ops/sec = %8lu, KiB/sec = %8lu (depth=%2d)\n", o, b, cur_depth); | |
145 | if (o > best_ops) | |
146 | best_ops = o; | |
147 | if (b > best_bytes) | |
148 | best_bytes = b; | |
149 | ||
150 | bytes = ops = 0; | |
151 | gettimeofday(&start, NULL); | |
152 | } | |
153 | ||
154 | if (total_run) { | |
155 | elapsed = time_elapsed(&real_start, &end); | |
156 | if (total_run && (elapsed >= total_run)) { | |
157 | printf("OPS_SEC=%'lu RATE=%'lu\n",best_ops, best_bytes); | |
158 | break; | |
159 | } | |
160 | } | |
161 | ||
162 | for (i = 0; i < ret; i++) { | |
163 | struct io_event *ev = events + i; | |
164 | struct iocb *io = ev->obj; | |
165 | unsigned long long offset = get_next_offset(); | |
166 | int err; | |
167 | ||
168 | offset *= io_size; | |
169 | ||
170 | if (writing) { | |
171 | io_prep_pwrite(io, fd, io->u.c.buf, io_size, offset); | |
172 | } else | |
173 | io_prep_pread(io, fd, io->u.c.buf, io_size, offset); | |
174 | ||
175 | err = io_submit(io_ctx, 1, &io); | |
176 | if (err != 1) { | |
177 | fprintf(stderr, "io_submit: %d\n", err); | |
178 | goto error; | |
179 | } | |
180 | ||
181 | cur_depth++; | |
182 | } | |
183 | ||
184 | } while (1); | |
185 | ||
186 | error: | |
187 | if (cur_depth) | |
188 | io_getevents(io_ctx, cur_depth, cur_depth, events, NULL); | |
189 | } | |
190 | ||
191 | int parse_options(int argc, char **argv) | |
192 | { | |
193 | static struct option longoptions[] = { | |
194 | { "device", 1, NULL, 'd' }, | |
195 | { "area", 1, NULL, 'a' }, | |
196 | { "random", 0, NULL, 'r' }, | |
197 | { "blocks", 1, NULL, 'b' }, | |
198 | { "queue depth",1, NULL, 'q' }, | |
199 | { "time", 1, NULL, 't' }, | |
200 | { "stats", 1, NULL, 's' }, | |
201 | { "write", 0, NULL, 'w' }, | |
202 | { "force", 0, NULL, 'w' }, | |
203 | }; | |
204 | int c, res; | |
205 | ||
206 | while (1) { | |
207 | c = getopt_long(argc, argv, "d:a:rb:q:t:swf", longoptions,&res); | |
208 | if (c == -1) | |
209 | break; | |
210 | ||
211 | switch (c) { | |
212 | case 'd': | |
213 | strncpy(filename, optarg, sizeof(filename)-1); | |
214 | break; | |
215 | case 'a': | |
216 | range = atoi(optarg); | |
217 | break; | |
218 | case 'r': | |
219 | sequential = 0; | |
220 | break; | |
221 | case 'b': | |
222 | io_size = atoi(optarg) * 512; | |
223 | break; | |
224 | case 'q': | |
225 | depth = atoi(optarg); | |
226 | if (depth <= 0 || depth > MAX_DEPTH) { | |
227 | fprintf(stderr, "queue depth out-of-range %d\n", depth); | |
228 | return 1; | |
229 | } | |
230 | break; | |
231 | case 't': | |
232 | total_run = atoi(optarg) * 1000; | |
233 | if (total_run < 0) { | |
234 | fprintf(stderr, "bad runtime\n"); | |
235 | return 1; | |
236 | } | |
237 | break; | |
238 | case 's': | |
239 | display_stats = 1; | |
240 | break; | |
241 | case 'w': | |
242 | writing = 1; | |
243 | break; | |
244 | case 'f': | |
245 | force = 1; | |
246 | break; | |
247 | } | |
248 | } | |
249 | ||
250 | printf("Dev=%s, %s, a=%LuMiB, %s, b=%u, q=%u, t=%u\n", filename, writing ? "write" : "read", range, sequential ? "sequential" : "random", io_size / 512, depth, total_run / 1000); | |
251 | return 0; | |
252 | } | |
253 | ||
254 | unsigned long get_size(int fd) | |
255 | { | |
256 | unsigned long long blocks; | |
257 | struct stat sb; | |
258 | ||
259 | if (fstat(fd, &sb) == -1) { | |
260 | perror("fstat"); | |
261 | return -1; | |
262 | } | |
263 | ||
264 | if (S_ISBLK(sb.st_mode)) { | |
265 | /* | |
266 | * be a little cautious about writing to a device. if | |
267 | * the user didn't use -f for force, ask for confirmation | |
268 | */ | |
269 | if (writing && !force) { | |
270 | printf("Writing directly to block device, are you sure? CTRL-C to cancel now\n"); | |
271 | (void) getc(stdin); | |
272 | } | |
273 | if (ioctl(fd, BLKGETSIZE64, &blocks) == -1) { | |
274 | perror("BLKGETSIZE64"); | |
275 | return -1; | |
276 | } | |
277 | return blocks >> 20ULL; | |
278 | } else if (S_ISREG(sb.st_mode)) | |
279 | return sb.st_size >> 20ULL; | |
280 | ||
281 | fprintf(stderr, "Bad file type (must be file or block device)\n"); | |
282 | return -1; | |
283 | } | |
284 | ||
285 | int main(int argc, char *argv[]) | |
286 | { | |
287 | unsigned long mb; | |
288 | io_context_t *io_ctx; | |
289 | char *orig, *buffer; | |
290 | int fd, i; | |
291 | ||
292 | if (parse_options(argc, argv)) | |
293 | return 1; | |
294 | ||
295 | if (writing) | |
296 | fd = open(filename, O_WRONLY | O_DIRECT); | |
297 | else | |
298 | fd = open(filename, O_RDONLY | O_DIRECT); | |
299 | ||
300 | if (fd == -1) { | |
301 | perror("open"); | |
302 | return 1; | |
303 | } | |
304 | ||
305 | mb = get_size(fd); | |
306 | if (nr_blocks == -1) | |
307 | return 1; | |
308 | ||
309 | if (!range || (range > mb)) | |
310 | range = mb; | |
311 | ||
312 | nr_blocks = (range << 20ULL) / (unsigned long long) io_size; | |
313 | ||
314 | printf("nr_blocks=%Lu (size=%u)\n", nr_blocks, io_size); | |
315 | ||
316 | io_ctx = malloc(sizeof(*io_ctx)); | |
317 | ||
318 | i = io_queue_init(depth, io_ctx); | |
319 | if (i) { | |
320 | fprintf(stderr, "io_queue_init: %d\n", i); | |
321 | return 1; | |
322 | } | |
323 | ||
324 | init_randomizer(); | |
325 | ||
326 | orig = malloc((depth + 1) * io_size); | |
327 | buffer = (char *) ALIGN(orig); | |
328 | ||
329 | for (i = 0; i < depth; i++) | |
330 | iocbs[i] = malloc(sizeof(struct iocb)); | |
331 | ||
332 | setlocale(LC_NUMERIC, "en_US"); | |
333 | ||
334 | events = malloc(depth * sizeof(struct io_event)); | |
335 | ||
336 | do_io(*io_ctx, fd, buffer); | |
337 | ||
338 | io_destroy(*io_ctx); | |
339 | ||
340 | for (i = 0; i < depth; i++) | |
341 | free(iocbs[i]); | |
342 | ||
343 | free(io_ctx); | |
344 | free(events); | |
345 | free(orig); | |
346 | close(fd); | |
347 | return 0; | |
348 | } |