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