libpmem: code cleanups
[fio.git] / engines / libpmem.c
1 /*
2  * libpmem: IO engine that uses NVML libpmem to read and write data
3  *
4  * Copyright (C) 2017 Nippon Telegraph and Telephone Corporation.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License,
8  * version 2 as published by the Free Software Foundation..
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  */
16
17 /*
18  * libpmem engine
19  *
20  * IO engine that uses libpmem to read and write data
21  *
22  * To use:
23  *   ioengine=libpmem
24  *
25  * Other relevant settings:
26  *   iodepth=1
27  *   direct=1
28  *   directory=/mnt/pmem0/
29  *   bs=4k
30  *
31  *   direct=1 means that pmem_drain() is executed for each write operation.
32  *   In contrast, direct=0 means that pmem_drain() is not executed.
33  *
34  *   The pmem device must have a DAX-capable filesystem and be mounted
35  *   with DAX enabled. directory must point to a mount point of DAX FS.
36  *
37  *   Example:
38  *     mkfs.xfs /dev/pmem0
39  *     mkdir /mnt/pmem0
40  *     mount -o dax /dev/pmem0 /mnt/pmem0
41  *
42  *
43  * See examples/libpmem.fio for more.
44  *
45  *
46  * libpmem.so
47  *   By default, the libpmem engine will let the system find the libpmem.so
48  *   that it uses. You can use an alternative libpmem by setting the
49  *   FIO_PMEM_LIB environment variable to the full path to the desired
50  *   libpmem.so.
51  */
52
53 #include <stdio.h>
54 #include <limits.h>
55 #include <stdlib.h>
56 #include <unistd.h>
57 #include <errno.h>
58 #include <sys/mman.h>
59 #include <sys/stat.h>
60 #include <sys/sysmacros.h>
61 #include <libgen.h>
62 #include <libpmem.h>
63
64 #include "../fio.h"
65 #include "../verify.h"
66
67 /*
68  * Limits us to 1GiB of mapped files in total to model after
69  * libpmem engine behavior
70  */
71 #define MMAP_TOTAL_SZ   (1 * 1024 * 1024 * 1024UL)
72
73 struct fio_libpmem_data {
74         void *libpmem_ptr;
75         size_t libpmem_sz;
76         off_t libpmem_off;
77 };
78
79 #define MEGABYTE ((uintptr_t)1 << 20)
80 #define GIGABYTE ((uintptr_t)1 << 30)
81 #define PROCMAXLEN 2048 /* maximum expected line length in /proc files */
82 #define roundup(x, y)   ((((x) + ((y) - 1)) / (y)) * (y))
83
84 static int Mmap_no_random;
85 static void *Mmap_hint;
86 static unsigned long long Mmap_align;
87 static unsigned long long Pagesize = 0;
88
89 /*
90  * util_map_hint_align -- choose the desired mapping alignment
91  *
92  * Use 2MB/1GB page alignment only if the mapping length is at least
93  * twice as big as the page size.
94  */
95 static inline size_t util_map_hint_align(size_t len, size_t req_align)
96 {
97         size_t align = 0;
98
99         dprint(FD_IO, "DEBUG util_map_hint_align\n" );
100 #ifndef WIN32
101         Mmap_align = Pagesize;
102 #else
103         if (Mmap_align == 0) {
104                 SYSTEM_INFO si;
105                 GetSystemInfo(&si);
106                 Mmap_align = si.dwAllocationGranularity;
107         }
108 #endif
109
110         align = Mmap_align;
111
112         if (req_align)
113                 align = req_align;
114         else if (len >= 2 * GIGABYTE)
115                 align = GIGABYTE;
116         else if (len >= 4 * MEGABYTE)
117                 align = 2 * MEGABYTE;
118
119         dprint(FD_IO, "align=%d\n", (int)align);
120         return align;
121 }
122
123 #ifdef __FreeBSD__
124 static const char *sscanf_os = "%p %p";
125 #define MAP_NORESERVE 0
126 #define OS_MAPFILE "/proc/curproc/map"
127 #else
128 static const char *sscanf_os = "%p-%p";
129 #define OS_MAPFILE "/proc/self/maps"
130 #endif
131
132 /*
133  * util_map_hint_unused -- use /proc to determine a hint address for mmap()
134  *
135  * This is a helper function for util_map_hint().
136  * It opens up /proc/self/maps and looks for the first unused address
137  * in the process address space that is:
138  * - greater or equal 'minaddr' argument,
139  * - large enough to hold range of given length,
140  * - aligned to the specified unit.
141  *
142  * Asking for aligned address like this will allow the DAX code to use large
143  * mappings.  It is not an error if mmap() ignores the hint and chooses
144  * different address.
145  */
146 static char *util_map_hint_unused(void *minaddr, size_t len, size_t align)
147 {
148         char *lo = NULL;        /* beginning of current range in maps file */
149         char *hi = NULL;        /* end of current range in maps file */
150         char *raddr = minaddr;  /* ignore regions below 'minaddr' */
151
152 #ifdef WIN32
153         MEMORY_BASIC_INFORMATION mi;
154 #else
155         FILE *fp;
156         char line[PROCMAXLEN];  /* for fgets() */
157 #endif
158
159         dprint(FD_IO, "DEBUG util_map_hint_unused\n");
160         assert(align > 0);
161
162         /* XXX - replace sysconf() with util_get_sys_xxx() */
163         Pagesize = (unsigned long) sysconf(_SC_PAGESIZE);
164
165         if (raddr == NULL)
166                 raddr += Pagesize;
167
168         raddr = (char *)roundup((uintptr_t)raddr, align);
169
170 #ifdef WIN32
171         while ((uintptr_t)raddr < UINTPTR_MAX - len) {
172                 size_t ret = VirtualQuery(raddr, &mi, sizeof(mi));
173                 if (ret == 0) {
174                         ERR("VirtualQuery %p", raddr);
175                         return MAP_FAILED;
176                 }
177                 dprint(FD_IO, "addr %p len %zu state %d",
178                                 mi.BaseAddress, mi.RegionSize, mi.State);
179
180                 if ((mi.State != MEM_FREE) || (mi.RegionSize < len)) {
181                         raddr = (char *)mi.BaseAddress + mi.RegionSize;
182                         raddr = (char *)roundup((uintptr_t)raddr, align);
183                         dprint(FD_IO, "nearest aligned addr %p", raddr);
184                 } else {
185                         dprint(FD_IO, "unused region of size %zu found at %p",
186                                         mi.RegionSize, mi.BaseAddress);
187                         return mi.BaseAddress;
188                 }
189         }
190
191         dprint(FD_IO, "end of address space reached");
192         return MAP_FAILED;
193 #else
194         fp = fopen(OS_MAPFILE, "r");
195         if (!fp) {
196                 log_err("!%s\n", OS_MAPFILE);
197                 return MAP_FAILED;
198         }
199
200         while (fgets(line, PROCMAXLEN, fp) != NULL) {
201                 /* check for range line */
202                 if (sscanf(line, sscanf_os, &lo, &hi) == 2) {
203                         dprint(FD_IO, "%p-%p\n", lo, hi);
204                         if (lo > raddr) {
205                                 if ((uintptr_t)(lo - raddr) >= len) {
206                                         dprint(FD_IO, "unused region of size "
207                                                         "%zu found at %p\n",
208                                                         lo - raddr, raddr);
209                                         break;
210                                 } else {
211                                         dprint(FD_IO, "region is too small: "
212                                                         "%zu < %zu\n",
213                                                         lo - raddr, len);
214                                 }
215                         }
216
217                         if (hi > raddr) {
218                                 raddr = (char *)roundup((uintptr_t)hi, align);
219                                 dprint(FD_IO, "nearest aligned addr %p\n",
220                                                 raddr);
221                         }
222
223                         if (raddr == 0) {
224                                 dprint(FD_IO, "end of address space reached\n");
225                                 break;
226                         }
227                 }
228         }
229
230         /*
231          * Check for a case when this is the last unused range in the address
232          * space, but is not large enough. (very unlikely)
233          */
234         if ((raddr != NULL) && (UINTPTR_MAX - (uintptr_t)raddr < len)) {
235                 dprint(FD_IO, "end of address space reached");
236                 raddr = MAP_FAILED;
237         }
238
239         fclose(fp);
240
241         dprint(FD_IO, "returning %p", raddr);
242         return raddr;
243 #endif
244 }
245
246 /*
247  * util_map_hint -- determine hint address for mmap()
248  *
249  * If PMEM_MMAP_HINT environment variable is not set, we let the system to pick
250  * the randomized mapping address.  Otherwise, a user-defined hint address
251  * is used.
252  *
253  * Windows Environment:
254  *   XXX - Windows doesn't support large DAX pages yet, so there is
255  *   no point in aligning for the same.
256  *
257  * Except for Windows Environment:
258  *   ALSR in 64-bit Linux kernel uses 28-bit of randomness for mmap
259  *   (bit positions 12-39), which means the base mapping address is randomized
260  *   within [0..1024GB] range, with 4KB granularity.  Assuming additional
261  *   1GB alignment, it results in 1024 possible locations.
262  *
263  *   Configuring the hint address via PMEM_MMAP_HINT environment variable
264  *   disables address randomization.  In such case, the function will search for
265  *   the first unused, properly aligned region of given size, above the
266  *   specified address.
267  */
268 static char *util_map_hint(size_t len, size_t req_align)
269 {
270         char *addr;
271         size_t align = 0;
272         char *e = NULL;
273
274         dprint(FD_IO, "DEBUG util_map_hint\n");
275         dprint(FD_IO, "len %zu req_align %zu\n", len, req_align);
276
277         /* choose the desired alignment based on the requested length */
278         align = util_map_hint_align(len, req_align);
279
280         e = getenv("PMEM_MMAP_HINT");
281         if (e) {
282                 char *endp;
283                 unsigned long long val = 0;
284
285                 errno = 0;
286
287                 val = strtoull(e, &endp, 16);
288                 if (errno || endp == e) {
289                         dprint(FD_IO, "Invalid PMEM_MMAP_HINT\n");
290                 } else {
291                         Mmap_hint = (void *)val;
292                         Mmap_no_random = 1;
293                         dprint(FD_IO, "PMEM_MMAP_HINT set to %p\n", Mmap_hint);
294                 }
295         }
296
297         if (Mmap_no_random) {
298                 dprint(FD_IO, "user-defined hint %p\n", (void *)Mmap_hint);
299                 addr = util_map_hint_unused((void *)Mmap_hint, len, align);
300         } else {
301                 /*
302                  * Create dummy mapping to find an unused region of given size.
303                  * * Request for increased size for later address alignment.
304                  *
305                  * Windows Environment: 
306                  *   Use MAP_NORESERVE flag to only reserve the range of pages
307                  *   rather than commit.  We don't want the pages to be actually
308                  *   backed by the operating system paging file, as the swap
309                  *   file is usually too small to handle terabyte pools.
310                  *
311                  * Except for Windows Environment:
312                  *   Use MAP_PRIVATE with read-only access to simulate
313                  *   zero cost for overcommit accounting.  Note: MAP_NORESERVE
314                  *   flag is ignored if overcommit is disabled (mode 2).
315                  */
316 #ifndef WIN32
317                 addr = mmap(NULL, len + align, PROT_READ,
318                                 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
319 #else
320                 addr = mmap(NULL, len + align, PROT_READ,
321                                 MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0);
322 #endif
323                 if (addr != MAP_FAILED) {
324                         dprint(FD_IO, "system choice %p\n", addr);
325                         munmap(addr, len + align);
326                         addr = (char *)roundup((uintptr_t)addr, align);
327                 }
328         }
329
330         dprint(FD_IO, "hint %p\n", addr);
331
332         return addr;
333 }
334
335 /*
336  * This is the mmap execution function
337  */
338 static int fio_libpmem_file(struct thread_data *td, struct fio_file *f,
339                             size_t length, off_t off)
340 {
341         struct fio_libpmem_data *fdd = FILE_ENG_DATA(f);
342         int flags = 0;
343         void *addr = NULL;
344
345         dprint(FD_IO, "DEBUG fio_libpmem_file\n");
346
347         if (td_rw(td))
348                 flags = PROT_READ | PROT_WRITE;
349         else if (td_write(td)) {
350                 flags = PROT_WRITE;
351
352                 if (td->o.verify != VERIFY_NONE)
353                         flags |= PROT_READ;
354         } else
355                 flags = PROT_READ;
356
357         dprint(FD_IO, "f->file_name = %s  td->o.verify = %d \n", f->file_name,
358                         td->o.verify);
359         dprint(FD_IO, "length = %ld  flags = %d  f->fd = %d off = %ld \n",
360                         length, flags, f->fd,off);
361
362         addr = util_map_hint(length, 0);
363
364         fdd->libpmem_ptr = mmap(addr, length, flags, MAP_SHARED, f->fd, off);
365         if (fdd->libpmem_ptr == MAP_FAILED) {
366                 fdd->libpmem_ptr = NULL;
367                 td_verror(td, errno, "mmap");
368         }
369
370         if (td->error && fdd->libpmem_ptr)
371                 munmap(fdd->libpmem_ptr, length);
372
373         return td->error;
374 }
375
376 /*
377  * XXX Just mmap an appropriate portion, we cannot mmap the full extent
378  */
379 static int fio_libpmem_prep_limited(struct thread_data *td, struct io_u *io_u)
380 {
381         struct fio_file *f = io_u->file;
382         struct fio_libpmem_data *fdd = FILE_ENG_DATA(f);
383
384         dprint(FD_IO, "DEBUG fio_libpmem_prep_limited\n" );
385
386         if (io_u->buflen > f->real_file_size) {
387                 log_err("libpmem: bs too big for libpmem engine\n");
388                 return EIO;
389         }
390
391         fdd->libpmem_sz = min(MMAP_TOTAL_SZ, f->real_file_size);
392         if (fdd->libpmem_sz > f->io_size)
393                 fdd->libpmem_sz = f->io_size;
394
395         fdd->libpmem_off = io_u->offset;
396
397         return fio_libpmem_file(td, f, fdd->libpmem_sz, fdd->libpmem_off);
398 }
399
400 /*
401  * Attempt to mmap the entire file
402  */
403 static int fio_libpmem_prep_full(struct thread_data *td, struct io_u *io_u)
404 {
405         struct fio_file *f = io_u->file;
406         struct fio_libpmem_data *fdd = FILE_ENG_DATA(f);
407         int ret;
408
409         dprint(FD_IO, "DEBUG fio_libpmem_prep_full\n" );
410
411         if (fio_file_partial_mmap(f))
412                 return EINVAL;
413
414         dprint(FD_IO," f->io_size %ld : io_u->offset %lld \n",
415                         f->io_size, io_u->offset);
416
417         if (io_u->offset != (size_t) io_u->offset ||
418             f->io_size != (size_t) f->io_size) {
419                 fio_file_set_partial_mmap(f);
420                 return EINVAL;
421         }
422         fdd->libpmem_sz = f->io_size;
423         fdd->libpmem_off = 0;
424
425         ret = fio_libpmem_file(td, f, fdd->libpmem_sz, fdd->libpmem_off);
426         if (ret)
427                 fio_file_set_partial_mmap(f);
428
429         return ret;
430 }
431
432 static int fio_libpmem_prep(struct thread_data *td, struct io_u *io_u)
433 {
434         struct fio_file *f = io_u->file;
435         struct fio_libpmem_data *fdd = FILE_ENG_DATA(f);
436         int ret;
437
438         dprint(FD_IO, "DEBUG fio_libpmem_prep\n" );
439         /*
440          * It fits within existing mapping, use it
441          */
442         dprint(FD_IO," io_u->offset %lld : fdd->libpmem_off %ld : "
443                         "io_u->buflen %ld : fdd->libpmem_sz %ld\n",
444                         io_u->offset, fdd->libpmem_off,
445                         io_u->buflen, fdd->libpmem_sz);
446
447         if (io_u->offset >= fdd->libpmem_off &&
448             (io_u->offset + io_u->buflen <
449              fdd->libpmem_off + fdd->libpmem_sz))
450                 goto done;
451
452         /*
453          * unmap any existing mapping
454          */
455         if (fdd->libpmem_ptr) {
456                 dprint(FD_IO,"munmap \n");
457                 if (munmap(fdd->libpmem_ptr, fdd->libpmem_sz) < 0)
458                         return errno;
459                 fdd->libpmem_ptr = NULL;
460         }
461
462         if (fio_libpmem_prep_full(td, io_u)) {
463                 td_clear_error(td);
464                 ret = fio_libpmem_prep_limited(td, io_u);
465                 if (ret)
466                         return ret;
467         }
468
469 done:
470         io_u->mmap_data = fdd->libpmem_ptr + io_u->offset - fdd->libpmem_off
471                                 - f->file_offset;
472         return 0;
473 }
474
475 static int fio_libpmem_queue(struct thread_data *td, struct io_u *io_u)
476 {
477         fio_ro_check(td, io_u);
478         io_u->error = 0;
479
480         dprint(FD_IO, "DEBUG fio_libpmem_queue\n");
481
482         switch (io_u->ddir) {
483         case DDIR_READ:
484                 memcpy(io_u->xfer_buf, io_u->mmap_data, io_u->xfer_buflen);
485                 break;
486         case DDIR_WRITE:
487                 dprint(FD_IO, "DEBUG mmap_data=%p, xfer_buf=%p\n",
488                                 io_u->mmap_data, io_u->xfer_buf );
489                 dprint(FD_IO,"td->o.odirect %d \n",td->o.odirect);
490                 if (td->o.odirect) {
491                         pmem_memcpy_persist(io_u->mmap_data,
492                                                 io_u->xfer_buf,
493                                                 io_u->xfer_buflen);
494                 } else {
495                         pmem_memcpy_nodrain(io_u->mmap_data,
496                                                 io_u->xfer_buf,
497                                                 io_u->xfer_buflen);
498                 }
499                 break;
500         case DDIR_SYNC:
501         case DDIR_DATASYNC:
502         case DDIR_SYNC_FILE_RANGE:
503                 break;
504         default:
505                 io_u->error = EINVAL;
506                 break;
507         }
508
509         return FIO_Q_COMPLETED;
510 }
511
512 static int fio_libpmem_init(struct thread_data *td)
513 {
514         struct thread_options *o = &td->o;
515
516         dprint(FD_IO,"o->rw_min_bs %d \n o->fsync_blocks %d \n o->fdatasync_blocks %d \n",
517                         o->rw_min_bs,o->fsync_blocks,o->fdatasync_blocks);
518         dprint(FD_IO, "DEBUG fio_libpmem_init\n");
519
520         if ((o->rw_min_bs & page_mask) &&
521             (o->fsync_blocks || o->fdatasync_blocks)) {
522                 log_err("libpmem: mmap options dictate a minimum block size of "
523                                 "%llu bytes\n", (unsigned long long) page_size);
524                 return 1;
525         }
526         return 0;
527 }
528
529 static int fio_libpmem_open_file(struct thread_data *td, struct fio_file *f)
530 {
531         struct fio_libpmem_data *fdd;
532         int ret;
533
534         dprint(FD_IO,"DEBUG fio_libpmem_open_file\n");
535         dprint(FD_IO,"f->io_size=%ld \n",f->io_size);
536         dprint(FD_IO,"td->o.size=%lld \n",td->o.size);
537         dprint(FD_IO,"td->o.iodepth=%d\n",td->o.iodepth);
538         dprint(FD_IO,"td->o.iodepth_batch=%d \n",td->o.iodepth_batch);
539
540         ret = generic_open_file(td, f);
541         if (ret)
542                 return ret;
543
544         fdd = calloc(1, sizeof(*fdd));
545         if (!fdd) {
546                 int fio_unused __ret;
547                 __ret = generic_close_file(td, f);
548                 return 1;
549         }
550
551         FILE_SET_ENG_DATA(f, fdd);
552
553         return 0;
554 }
555
556 static int fio_libpmem_close_file(struct thread_data *td, struct fio_file *f)
557 {
558         struct fio_libpmem_data *fdd = FILE_ENG_DATA(f);
559
560         dprint(FD_IO,"DEBUG fio_libpmem_close_file\n");
561         dprint(FD_IO,"td->o.odirect %d \n",td->o.odirect);
562
563         if (!td->o.odirect) {
564                 dprint(FD_IO,"pmem_drain\n");
565                 pmem_drain();
566         }
567
568         FILE_SET_ENG_DATA(f, NULL);
569         free(fdd);
570         fio_file_clear_partial_mmap(f);
571
572         return generic_close_file(td, f);
573 }
574
575 static struct ioengine_ops ioengine = {
576         .name           = "libpmem",
577         .version        = FIO_IOOPS_VERSION,
578         .init           = fio_libpmem_init,
579         .prep           = fio_libpmem_prep,
580         .queue          = fio_libpmem_queue,
581         .open_file      = fio_libpmem_open_file,
582         .close_file     = fio_libpmem_close_file,
583         .get_file_size  = generic_get_file_size,
584         .flags          = FIO_SYNCIO |FIO_NOEXTEND,
585 };
586
587 static void fio_init fio_libpmem_register(void)
588 {
589         register_ioengine(&ioengine);
590 }
591
592 static void fio_exit fio_libpmem_unregister(void)
593 {
594         unregister_ioengine(&ioengine);
595 }