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