1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <atomic>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <threads.h>
12 #include <unistd.h>
13
14 #include <lib/sync/completion.h>
15 #include <lib/zircon-internal/xorshiftrand.h>
16 #include <perftest/results.h>
17 #include <zircon/device/block.h>
18 #include <zircon/syscalls.h>
19 #include <zircon/time.h>
20 #include <zircon/types.h>
21
number(const char * str)22 static uint64_t number(const char* str) {
23 char* end;
24 uint64_t n = strtoull(str, &end, 10);
25
26 uint64_t m = 1;
27 switch (*end) {
28 case 'G':
29 case 'g':
30 m = 1024*1024*1024;
31 break;
32 case 'M':
33 case 'm':
34 m = 1024*1024;
35 break;
36 case 'K':
37 case 'k':
38 m = 1024;
39 break;
40 }
41 return m * n;
42 }
43
bytes_per_second(uint64_t bytes,uint64_t nanos)44 static void bytes_per_second(uint64_t bytes, uint64_t nanos) {
45 double s = ((double)nanos) / ((double)1000000000);
46 double rate = ((double)bytes) / s;
47
48 const char* unit = "B";
49 if (rate > 1024*1024) {
50 unit = "MB";
51 rate /= 1024*1024;
52 } else if (rate > 1024) {
53 unit = "KB";
54 rate /= 1024;
55 }
56 fprintf(stderr, "%g %s/s\n", rate, unit);
57 }
58
ops_per_second(uint64_t count,uint64_t nanos)59 static void ops_per_second(uint64_t count, uint64_t nanos) {
60 double s = ((double)nanos) / ((double)1000000000);
61 double rate = ((double)count) / s;
62 fprintf(stderr, "%g %s/s\n", rate, "ops");
63 }
64
65 typedef struct {
66 int fd;
67 zx_handle_t vmo;
68 zx_handle_t fifo;
69 reqid_t reqid;
70 vmoid_t vmoid;
71 size_t bufsz;
72 block_info_t info;
73 } blkdev_t;
74
blkdev_close(blkdev_t * blk)75 static void blkdev_close(blkdev_t* blk) {
76 if (blk->fd >= 0) {
77 close(blk->fd);
78 }
79 zx_handle_close(blk->vmo);
80 zx_handle_close(blk->fifo);
81 memset(blk, 0, sizeof(blkdev_t));
82 blk->fd = -1;
83 }
84
blkdev_open(int fd,const char * dev,size_t bufsz,blkdev_t * blk)85 static zx_status_t blkdev_open(int fd, const char* dev, size_t bufsz, blkdev_t* blk) {
86 memset(blk, 0, sizeof(blkdev_t));
87 blk->fd = fd;
88 blk->bufsz = bufsz;
89
90 zx_status_t r;
91 if (ioctl_block_get_info(fd, &blk->info) != sizeof(block_info_t)) {
92 fprintf(stderr, "error: cannot get block device info for '%s'\n", dev);
93 goto fail;
94 }
95 if (ioctl_block_get_fifos(fd, &blk->fifo) != sizeof(zx_handle_t)) {
96 fprintf(stderr, "error: cannot get fifo for '%s'\n", dev);
97 goto fail;
98 }
99 if ((r = zx_vmo_create(bufsz, 0, &blk->vmo)) != ZX_OK) {
100 fprintf(stderr, "error: out of memory %d\n", r);
101 goto fail;
102 }
103
104 zx_handle_t dup;
105 if ((r = zx_handle_duplicate(blk->vmo, ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) {
106 fprintf(stderr, "error: cannot duplicate handle %d\n", r);
107 goto fail;
108 }
109 if (ioctl_block_attach_vmo(fd, &dup, &blk->vmoid) != sizeof(vmoid_t)) {
110 fprintf(stderr, "error: cannot attach vmo for '%s'\n", dev);
111 goto fail;
112 }
113
114 return ZX_OK;
115
116 fail:
117 blkdev_close(blk);
118 return ZX_ERR_INTERNAL;
119 }
120
121 typedef struct {
122 blkdev_t* blk;
123 size_t count;
124 size_t xfer;
125 uint64_t seed;
126 int max_pending;
127 bool write;
128 bool linear;
129
130 std::atomic<int> pending;
131 sync_completion_t signal;
132 } bio_random_args_t;
133
134 std::atomic<reqid_t> next_reqid(0);
135
bio_random_thread(void * arg)136 static int bio_random_thread(void* arg) {
137 auto* a = reinterpret_cast<bio_random_args_t*>(arg);
138
139 size_t off = 0;
140 size_t count = a->count;
141 size_t xfer = a->xfer;
142
143 size_t blksize = a->blk->info.block_size;
144 size_t blkcount = ((count * xfer) / blksize) - (xfer / blksize);
145
146 rand64_t r64 = RAND63SEED(a->seed);
147
148 zx_handle_t fifo = a->blk->fifo;
149 size_t dev_off = 0;
150
151 while (count > 0) {
152 while (a->pending.load() == a->max_pending) {
153 sync_completion_wait(&a->signal, ZX_TIME_INFINITE);
154 sync_completion_reset(&a->signal);
155 }
156
157 block_fifo_request_t req = {};
158 req.reqid = next_reqid.fetch_add(1);
159 req.vmoid = a->blk->vmoid;
160 req.opcode = a->write ? BLOCKIO_WRITE : BLOCKIO_READ;
161 req.length = static_cast<uint32_t>(xfer);
162 req.vmo_offset = off;
163
164 if (a->linear) {
165 req.dev_offset = dev_off;
166 dev_off += xfer;
167 } else {
168 req.dev_offset = (rand64(&r64) % blkcount) * blksize;
169 }
170 off += xfer;
171 if ((off + xfer) > a->blk->bufsz) {
172 off = 0;
173 }
174
175 req.length /= static_cast<uint32_t>(blksize);
176 req.dev_offset /= blksize;
177 req.vmo_offset /= blksize;
178
179 #if 0
180 fprintf(stderr, "IO tid=%u vid=%u op=%x len=%zu vof=%zu dof=%zu\n",
181 req.reqid, req.vmoid, req.opcode, req.length, req.vmo_offset, req.dev_offset);
182 #endif
183 zx_status_t r = zx_fifo_write(fifo, sizeof(req), &req, 1, NULL);
184 if (r == ZX_ERR_SHOULD_WAIT) {
185 r = zx_object_wait_one(fifo, ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED,
186 ZX_TIME_INFINITE, NULL);
187 if (r != ZX_OK) {
188 fprintf(stderr, "failed waiting for fifo\n");
189 zx_handle_close(fifo);
190 return -1;
191 }
192 continue;
193 } else if (r < 0) {
194 fprintf(stderr, "error: failed writing fifo\n");
195 zx_handle_close(fifo);
196 return -1;
197 }
198
199 a->pending.fetch_add(1);
200 count--;
201 }
202 return 0;
203 }
204
205
bio_random(bio_random_args_t * a,uint64_t * _total,zx_duration_t * _res)206 static zx_status_t bio_random(bio_random_args_t* a, uint64_t* _total, zx_duration_t* _res) {
207
208 thrd_t t;
209 int r;
210
211 size_t count = a->count;
212 zx_handle_t fifo = a->blk->fifo;
213
214 zx_time_t t0 = zx_clock_get_monotonic();
215 thrd_create(&t, bio_random_thread, a);
216
217 while (count > 0) {
218 block_fifo_response_t resp;
219 zx_status_t r = zx_fifo_read(fifo, sizeof(resp), &resp, 1, NULL);
220 if (r == ZX_ERR_SHOULD_WAIT) {
221 r = zx_object_wait_one(fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED,
222 ZX_TIME_INFINITE, NULL);
223 if (r != ZX_OK) {
224 fprintf(stderr, "failed waiting for fifo: %d\n", r);
225 goto fail;
226 }
227 continue;
228 } else if (r < 0) {
229 fprintf(stderr, "error: failed reading fifo: %d\n", r);
230 goto fail;
231 }
232 if (resp.status != ZX_OK) {
233 fprintf(stderr, "error: io txn failed %d (%zu remaining)\n",
234 resp.status, count);
235 goto fail;
236 }
237 count--;
238 if (a->pending.fetch_sub(1) == a->max_pending) {
239 sync_completion_signal(&a->signal);
240 }
241 }
242
243 zx_time_t t1;
244 t1 = zx_clock_get_monotonic();
245
246 fprintf(stderr, "waiting for thread to exit...\n");
247 thrd_join(t, &r);
248
249 *_res = zx_time_sub_time(t1, t0);
250 *_total = a->count * a->xfer;
251 return ZX_OK;
252
253 fail:
254 zx_handle_close(a->blk->fifo);
255 thrd_join(t, &r);
256 return ZX_ERR_IO;
257 }
258
usage(void)259 void usage(void) {
260 fprintf(stderr, "usage: biotime <option>* <device>\n"
261 "\n"
262 "args: -bs <num> transfer block size (multiple of 4K)\n"
263 " -tt <num> total bytes to transfer\n"
264 " -mo <num> maximum outstanding ops (1..128)\n"
265 " -read test reading from the block device (default)\n"
266 " -write test writing to the block device\n"
267 " -live-dangerously required if using \"-write\"\n"
268 " -linear transfers in linear order (default)\n"
269 " -random random transfers across total range\n"
270 " -output-file <filename> destination file for "
271 "writing results in JSON format\n"
272 );
273 }
274
275 #define needparam() do { \
276 argc--; argv++; \
277 if (argc == 0) { \
278 fprintf(stderr, "error: option %s needs a parameter\n", argv[-1]); \
279 return -1; \
280 } } while (0)
281 #define nextarg() do { argc--; argv++; } while (0)
282 #define error(x...) do { fprintf(stderr, x); usage(); return -1; } while (0)
283
main(int argc,char ** argv)284 int main(int argc, char** argv) {
285 blkdev_t blk;
286
287 bool live_dangerously = false;
288 bio_random_args_t a = {};
289 a.blk = &blk;
290 a.xfer = 32768;
291 a.seed = 7891263897612ULL;
292 a.max_pending = 128;
293 a.write = false;
294 a.linear = true;
295 const char* output_file = nullptr;
296
297 size_t total = 0;
298
299 nextarg();
300 while (argc > 0) {
301 if (argv[0][0] != '-') {
302 break;
303 }
304 if (!strcmp(argv[0], "-bs")) {
305 needparam();
306 a.xfer = number(argv[0]);
307 if ((a.xfer == 0) || (a.xfer % 4096)) {
308 error("error: block size must be multiple of 4K\n");
309 }
310 } else if (!strcmp(argv[0], "-tt")) {
311 needparam();
312 total = number(argv[0]);
313 } else if (!strcmp(argv[0], "-mo")) {
314 needparam();
315 size_t n = number(argv[0]);
316 if ((n < 1) || (n > 128)) {
317 error("error: max pending must be between 1 and 128\n");
318 }
319 a.max_pending = static_cast<int>(n);
320 } else if (!strcmp(argv[0], "-read")) {
321 a.write = false;
322 } else if (!strcmp(argv[0], "-write")) {
323 a.write = true;
324 } else if (!strcmp(argv[0], "-live-dangerously")) {
325 live_dangerously = true;
326 } else if (!strcmp(argv[0], "-linear")) {
327 a.linear = true;
328 } else if (!strcmp(argv[0], "-random")) {
329 a.linear = false;
330 } else if (!strcmp(argv[0], "-output-file")) {
331 needparam();
332 output_file = argv[0];
333 } else if (!strcmp(argv[0], "-h")) {
334 usage();
335 return 0;
336 } else {
337 error("error: unknown option: %s\n", argv[0]);
338 }
339 nextarg();
340 }
341 if (argc == 0) {
342 error("error: no device specified\n");
343 }
344 if (argc > 1) {
345 error("error: unexpected arguments\n");
346 }
347 if (a.write && !live_dangerously) {
348 error("error: the option \"-live-dangerously\" is required when using"
349 " \"-write\"\n");
350 }
351 const char* device_filename = argv[0];
352
353 int fd;
354 if ((fd = open(device_filename, O_RDONLY)) < 0) {
355 fprintf(stderr, "error: cannot open '%s'\n", device_filename);
356 return -1;
357 }
358 if (blkdev_open(fd, device_filename, 8*1024*1024, &blk) != ZX_OK) {
359 return -1;
360 }
361
362 size_t devtotal = blk.info.block_count * blk.info.block_size;
363
364 // default to entire device
365 if ((total == 0) || (total > devtotal)) {
366 total = devtotal;
367 }
368 a.count = total / a.xfer;
369
370 zx_duration_t res = 0;
371 total = 0;
372 if (bio_random(&a, &total, &res) != ZX_OK) {
373 return -1;
374 }
375
376 fprintf(stderr, "%zu bytes in %zu ns: ", total, res);
377 bytes_per_second(total, res);
378 fprintf(stderr, "%zu ops in %zu ns: ", a.count, res);
379 ops_per_second(a.count, res);
380
381 if (output_file) {
382 perftest::ResultsSet results;
383 auto* test_case = results.AddTestCase(
384 "fuchsia.zircon", "BlockDeviceThroughput", "bytes/second");
385 double time_in_seconds = static_cast<double>(res) / 1e9;
386 test_case->AppendValue(static_cast<double>(total) / time_in_seconds);
387 if (!results.WriteJSONFile(output_file)) {
388 return 1;
389 }
390 }
391
392 return 0;
393 }
394