Merge branch 'master' of ssh://brick.kernel.dk/data/git/fio
[fio.git] / options.c
index f068ebd4fc8594ec4a1cbfbfa9c133d87dc2d62f..ebf5d2e6b240932ae5cbcfa04963e4c16ff26c85 100644 (file)
--- a/options.c
+++ b/options.c
@@ -11,6 +11,7 @@
 #include <sys/stat.h>
 
 #include "fio.h"
+#include "verify.h"
 #include "parse.h"
 #include "lib/fls.h"
 
@@ -40,21 +41,16 @@ static int bs_cmp(const void *p1, const void *p2)
        return bsp1->perc < bsp2->perc;
 }
 
-static int str_bssplit_cb(void *data, const char *input)
+static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
 {
-       struct thread_data *td = data;
-       char *fname, *str, *p;
+       struct bssplit *bssplit;
        unsigned int i, perc, perc_missing;
        unsigned int max_bs, min_bs;
        long long val;
+       char *fname;
 
-       p = str = strdup(input);
-
-       strip_blank_front(&str);
-       strip_blank_end(str);
-
-       td->o.bssplit_nr = 4;
-       td->o.bssplit = malloc(4 * sizeof(struct bssplit));
+       td->o.bssplit_nr[ddir] = 4;
+       bssplit = malloc(4 * sizeof(struct bssplit));
 
        i = 0;
        max_bs = 0;
@@ -68,10 +64,9 @@ static int str_bssplit_cb(void *data, const char *input)
                /*
                 * grow struct buffer, if needed
                 */
-               if (i == td->o.bssplit_nr) {
-                       td->o.bssplit_nr <<= 1;
-                       td->o.bssplit = realloc(td->o.bssplit,
-                                               td->o.bssplit_nr
+               if (i == td->o.bssplit_nr[ddir]) {
+                       td->o.bssplit_nr[ddir] <<= 1;
+                       bssplit = realloc(bssplit, td->o.bssplit_nr[ddir]
                                                  * sizeof(struct bssplit));
                }
 
@@ -87,7 +82,7 @@ static int str_bssplit_cb(void *data, const char *input)
                } else
                        perc = -1;
 
-               if (str_to_decimal(fname, &val, 1)) {
+               if (str_to_decimal(fname, &val, 1, &td)) {
                        log_err("fio: bssplit conversion failed\n");
                        free(td->o.bssplit);
                        return 1;
@@ -98,19 +93,19 @@ static int str_bssplit_cb(void *data, const char *input)
                if (val < min_bs)
                        min_bs = val;
 
-               td->o.bssplit[i].bs = val;
-               td->o.bssplit[i].perc = perc;
+               bssplit[i].bs = val;
+               bssplit[i].perc = perc;
                i++;
        }
 
-       td->o.bssplit_nr = i;
+       td->o.bssplit_nr[ddir] = i;
 
        /*
         * Now check if the percentages add up, and how much is missing
         */
        perc = perc_missing = 0;
-       for (i = 0; i < td->o.bssplit_nr; i++) {
-               struct bssplit *bsp = &td->o.bssplit[i];
+       for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+               struct bssplit *bsp = &bssplit[i];
 
                if (bsp->perc == (unsigned char) -1)
                        perc_missing++;
@@ -120,7 +115,7 @@ static int str_bssplit_cb(void *data, const char *input)
 
        if (perc > 100) {
                log_err("fio: bssplit percentages add to more than 100%%\n");
-               free(td->o.bssplit);
+               free(bssplit);
                return 1;
        }
        /*
@@ -128,24 +123,58 @@ static int str_bssplit_cb(void *data, const char *input)
         * them.
         */
        if (perc_missing) {
-               for (i = 0; i < td->o.bssplit_nr; i++) {
-                       struct bssplit *bsp = &td->o.bssplit[i];
+               for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+                       struct bssplit *bsp = &bssplit[i];
 
                        if (bsp->perc == (unsigned char) -1)
                                bsp->perc = (100 - perc) / perc_missing;
                }
        }
 
-       td->o.min_bs[DDIR_READ] = td->o.min_bs[DDIR_WRITE] = min_bs;
-       td->o.max_bs[DDIR_READ] = td->o.max_bs[DDIR_WRITE] = max_bs;
+       td->o.min_bs[ddir] = min_bs;
+       td->o.max_bs[ddir] = max_bs;
 
        /*
         * now sort based on percentages, for ease of lookup
         */
-       qsort(td->o.bssplit, td->o.bssplit_nr, sizeof(struct bssplit), bs_cmp);
+       qsort(bssplit, td->o.bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
+       td->o.bssplit[ddir] = bssplit;
+       return 0;
+
+}
+
+static int str_bssplit_cb(void *data, const char *input)
+{
+       struct thread_data *td = data;
+       char *str, *p, *odir;
+       int ret = 0;
+
+       p = str = strdup(input);
+
+       strip_blank_front(&str);
+       strip_blank_end(str);
+
+       odir = strchr(str, ',');
+       if (odir) {
+               ret = bssplit_ddir(td, DDIR_WRITE, odir + 1);
+               if (!ret) {
+                       *odir = '\0';
+                       ret = bssplit_ddir(td, DDIR_READ, str);
+               }
+       } else {
+               char *op;
+
+               op = strdup(str);
+
+               ret = bssplit_ddir(td, DDIR_READ, str);
+               if (!ret)
+                       ret = bssplit_ddir(td, DDIR_WRITE, op);
+
+               free(op);
+       }
 
        free(p);
-       return 0;
+       return ret;
 }
 
 static int str_rw_cb(void *data, const char *str)
@@ -154,8 +183,10 @@ static int str_rw_cb(void *data, const char *str)
        char *nr = get_opt_postfix(str);
 
        td->o.ddir_nr = 1;
-       if (nr)
+       if (nr) {
                td->o.ddir_nr = atoi(nr);
+               free(nr);
+       }
 
        return 0;
 }
@@ -244,38 +275,124 @@ static int str_cpumask_cb(void *data, unsigned int *val)
 {
        struct thread_data *td = data;
        unsigned int i;
+       long max_cpu;
+       int ret;
 
-       CPU_ZERO(&td->o.cpumask);
+       ret = fio_cpuset_init(&td->o.cpumask);
+       if (ret < 0) {
+               log_err("fio: cpuset_init failed\n");
+               td_verror(td, ret, "fio_cpuset_init");
+               return 1;
+       }
 
-       for (i = 0; i < sizeof(int) * 8; i++)
-               if ((1 << i) & *val)
-                       CPU_SET(*val, &td->o.cpumask);
+       max_cpu = sysconf(_SC_NPROCESSORS_ONLN);
+
+       for (i = 0; i < sizeof(int) * 8; i++) {
+               if ((1 << i) & *val) {
+                       if (i > max_cpu) {
+                               log_err("fio: CPU %d too large (max=%ld)\n", i,
+                                                               max_cpu);
+                               return 1;
+                       }
+                       dprint(FD_PARSE, "set cpu allowed %d\n", i);
+                       fio_cpu_set(&td->o.cpumask, i);
+               }
+       }
 
        td->o.cpumask_set = 1;
        return 0;
 }
 
-static int str_cpus_allowed_cb(void *data, const char *input)
+static int set_cpus_allowed(struct thread_data *td, os_cpu_mask_t *mask,
+                           const char *input)
 {
-       struct thread_data *td = data;
        char *cpu, *str, *p;
+       long max_cpu;
+       int ret = 0;
 
-       CPU_ZERO(&td->o.cpumask);
+       ret = fio_cpuset_init(mask);
+       if (ret < 0) {
+               log_err("fio: cpuset_init failed\n");
+               td_verror(td, ret, "fio_cpuset_init");
+               return 1;
+       }
 
        p = str = strdup(input);
 
        strip_blank_front(&str);
        strip_blank_end(str);
 
+       max_cpu = sysconf(_SC_NPROCESSORS_ONLN);
+
        while ((cpu = strsep(&str, ",")) != NULL) {
+               char *str2, *cpu2;
+               int icpu, icpu2;
+
                if (!strlen(cpu))
                        break;
-               CPU_SET(atoi(cpu), &td->o.cpumask);
+
+               str2 = cpu;
+               icpu2 = -1;
+               while ((cpu2 = strsep(&str2, "-")) != NULL) {
+                       if (!strlen(cpu2))
+                               break;
+
+                       icpu2 = atoi(cpu2);
+               }
+
+               icpu = atoi(cpu);
+               if (icpu2 == -1)
+                       icpu2 = icpu;
+               while (icpu <= icpu2) {
+                       if (icpu >= FIO_MAX_CPUS) {
+                               log_err("fio: your OS only supports up to"
+                                       " %d CPUs\n", (int) FIO_MAX_CPUS);
+                               ret = 1;
+                               break;
+                       }
+                       if (icpu > max_cpu) {
+                               log_err("fio: CPU %d too large (max=%ld)\n",
+                                                       icpu, max_cpu);
+                               ret = 1;
+                               break;
+                       }
+       
+                       dprint(FD_PARSE, "set cpu allowed %d\n", icpu);
+                       fio_cpu_set(mask, icpu);
+                       icpu++;
+               }
+               if (ret)
+                       break;
        }
 
        free(p);
-       td->o.cpumask_set = 1;
-       return 0;
+       if (!ret)
+               td->o.cpumask_set = 1;
+       return ret;
+}
+
+static int str_cpus_allowed_cb(void *data, const char *input)
+{
+       struct thread_data *td = data;
+       int ret;
+
+       ret = set_cpus_allowed(td, &td->o.cpumask, input);
+       if (!ret)
+               td->o.cpumask_set = 1;
+
+       return ret;
+}
+
+static int str_verify_cpus_allowed_cb(void *data, const char *input)
+{
+       struct thread_data *td = data;
+       int ret;
+
+       ret = set_cpus_allowed(td, &td->o.verify_cpumask, input);
+       if (!ret)
+               td->o.verify_cpumask_set = 1;
+
+       return ret;
 }
 #endif
 
@@ -285,8 +402,10 @@ static int str_fst_cb(void *data, const char *str)
        char *nr = get_opt_postfix(str);
 
        td->file_service_nr = 1;
-       if (nr)
+       if (nr) {
                td->file_service_nr = atoi(nr);
+               free(nr);
+       }
 
        return 0;
 }
@@ -330,6 +449,52 @@ static int check_dir(struct thread_data *td, char *fname)
        return 0;
 }
 
+/*
+ * Return next file in the string. Files are separated with ':'. If the ':'
+ * is escaped with a '\', then that ':' is part of the filename and does not
+ * indicate a new file.
+ */
+static char *get_next_file_name(char **ptr)
+{
+       char *str = *ptr;
+       char *p, *start;
+
+       if (!str || !strlen(str))
+               return NULL;
+
+       start = str;
+       do {
+               /*
+                * No colon, we are done
+                */
+               p = strchr(str, ':');
+               if (!p) {
+                       *ptr = NULL;
+                       break;
+               }
+
+               /*
+                * We got a colon, but it's the first character. Skip and
+                * continue
+                */
+               if (p == start) {
+                       str = ++start;
+                       continue;
+               }
+
+               if (*(p - 1) != '\\') {
+                       *p = '\0';
+                       *ptr = p + 1;
+                       break;
+               }
+
+               memmove(p - 1, p, strlen(p) + 1);
+               str = p;
+       } while (1);
+
+       return start;
+}
+
 static int str_filename_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
@@ -343,7 +508,7 @@ static int str_filename_cb(void *data, const char *input)
        if (!td->files_index)
                td->o.nr_files = 0;
 
-       while ((fname = strsep(&str, ":")) != NULL) {
+       while ((fname = get_next_file_name(&str)) != NULL) {
                if (!strlen(fname))
                        break;
                if (check_dir(td, fname)) {
@@ -426,9 +591,33 @@ static int str_lockfile_cb(void *data, const char *str)
        char *nr = get_opt_postfix(str);
 
        td->o.lockfile_batch = 1;
-       if (nr)
+       if (nr) {
                td->o.lockfile_batch = atoi(nr);
+               free(nr);
+       }
+
+       return 0;
+}
+
+static int str_write_bw_log_cb(void *data, const char *str)
+{
+       struct thread_data *td = data;
+
+       if (str)
+               td->o.bw_log_file = strdup(str);
 
+       td->o.write_bw_log = 1;
+       return 0;
+}
+
+static int str_write_lat_log_cb(void *data, const char *str)
+{
+       struct thread_data *td = data;
+
+       if (str)
+               td->o.lat_log_file = strdup(str);
+
+       td->o.write_lat_log = 1;
        return 0;
 }
 
@@ -446,6 +635,57 @@ static int str_gtod_reduce_cb(void *data, int *il)
        return 0;
 }
 
+static int str_gtod_cpu_cb(void *data, int *il)
+{
+       struct thread_data *td = data;
+       int val = *il;
+
+       td->o.gtod_cpu = val;
+       td->o.gtod_offload = 1;
+       return 0;
+}
+
+static int rw_verify(struct fio_option *o, void *data)
+{
+       struct thread_data *td = data;
+
+       if (read_only && td_write(td)) {
+               log_err("fio: job <%s> has write bit set, but fio is in"
+                       " read-only mode\n", td->o.name);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int gtod_cpu_verify(struct fio_option *o, void *data)
+{
+#ifndef FIO_HAVE_CPU_AFFINITY
+       struct thread_data *td = data;
+
+       if (td->o.gtod_cpu) {
+               log_err("fio: platform must support CPU affinity for"
+                       "gettimeofday() offloading\n");
+               return 1;
+       }
+#endif
+
+       return 0;
+}
+
+static int kb_base_verify(struct fio_option *o, void *data)
+{
+       struct thread_data *td = data;
+
+       if (td->o.kb_base != 1024 && td->o.kb_base != 1000) {
+               log_err("fio: kb_base set to nonsensical value: %u\n",
+                               td->o.kb_base);
+               return 1;
+       }
+
+       return 0;
+}
+
 #define __stringify_1(x)       #x
 #define __stringify(x)         __stringify_1(x)
 
@@ -477,9 +717,18 @@ static struct fio_option options[] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(filename),
                .cb     = str_filename_cb,
-               .prio   = 1, /* must come before "directory" */
+               .prio   = -1, /* must come after "directory" */
                .help   = "File(s) to use for the workload",
        },
+       {
+               .name   = "kb_base",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(kb_base),
+               .verify = kb_base_verify,
+               .prio   = 1,
+               .def    = "1024",
+               .help   = "How many bytes per KB for reporting (1000 or 1024)",
+       },
        {
                .name   = "lockfile",
                .type   = FIO_OPT_STR,
@@ -519,6 +768,7 @@ static struct fio_option options[] = {
                .off1   = td_var_offset(td_ddir),
                .help   = "IO direction",
                .def    = "read",
+               .verify = rw_verify,
                .posval = {
                          { .ival = "read",
                            .oval = TD_DDIR_READ,
@@ -630,7 +880,7 @@ static struct fio_option options[] = {
                .alias  = "iodepth_batch_submit",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth_batch),
-               .help   = "Number of IO to submit in one go",
+               .help   = "Number of IO buffers to submit in one go",
                .parent = "iodepth",
                .minval = 1,
                .def    = "1",
@@ -639,7 +889,7 @@ static struct fio_option options[] = {
                .name   = "iodepth_batch_complete",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth_batch_complete),
-               .help   = "Number of IO to retrieve in one go",
+               .help   = "Number of IO buffers to retrieve in one go",
                .parent = "iodepth",
                .minval = 0,
                .def    = "1",
@@ -684,7 +934,7 @@ static struct fio_option options[] = {
        {
                .name   = "bs",
                .alias  = "blocksize",
-               .type   = FIO_OPT_STR_VAL_INT,
+               .type   = FIO_OPT_INT,
                .off1   = td_var_offset(bs[DDIR_READ]),
                .off2   = td_var_offset(bs[DDIR_WRITE]),
                .minval = 1,
@@ -692,6 +942,16 @@ static struct fio_option options[] = {
                .def    = "4k",
                .parent = "rw",
        },
+       {
+               .name   = "ba",
+               .alias  = "blockalign",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(ba[DDIR_READ]),
+               .off2   = td_var_offset(ba[DDIR_WRITE]),
+               .minval = 1,
+               .help   = "IO block offset alignment",
+               .parent = "rw",
+       },
        {
                .name   = "bsrange",
                .alias  = "blocksize_range",
@@ -771,6 +1031,10 @@ static struct fio_option options[] = {
                            .oval = FIO_FSERVICE_RR,
                            .help = "Round robin select files",
                          },
+                         { .ival = "sequential",
+                           .oval = FIO_FSERVICE_SEQ,
+                           .help = "Finish one file before moving to the next",
+                         },
                },
                .parent = "nrfiles",
        },
@@ -788,6 +1052,13 @@ static struct fio_option options[] = {
                .help   = "Issue fsync for writes every given number of blocks",
                .def    = "0",
        },
+       {
+               .name   = "fdatasync",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(fdatasync_blocks),
+               .help   = "Issue fdatasync for writes every given number of blocks",
+               .def    = "0",
+       },
        {
                .name   = "direct",
                .type   = FIO_OPT_BOOL,
@@ -886,6 +1157,16 @@ static struct fio_option options[] = {
 #endif
                  },
        },
+       {
+               .name   = "iomem_align",
+               .alias  = "mem_align",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(mem_align),
+               .minval = 0,
+               .help   = "IO memory buffer offset alignment",
+               .def    = "0",
+               .parent = "iomem",
+       },
        {
                .name   = "verify",
                .type   = FIO_OPT_STR,
@@ -925,6 +1206,10 @@ static struct fio_option options[] = {
                            .oval = VERIFY_CRC7,
                            .help = "Use crc7 checksums for verification",
                          },
+                         { .ival = "sha1",
+                           .oval = VERIFY_SHA1,
+                           .help = "Use sha1 checksums for verification",
+                         },
                          { .ival = "sha256",
                            .oval = VERIFY_SHA256,
                            .help = "Use sha256 checksums for verification",
@@ -962,7 +1247,7 @@ static struct fio_option options[] = {
        },
        {
                .name   = "verify_interval",
-               .type   = FIO_OPT_STR_VAL_INT,
+               .type   = FIO_OPT_INT,
                .off1   = td_var_offset(verify_interval),
                .minval = 2 * sizeof(struct verify_header),
                .help   = "Store verify buffer header every N bytes",
@@ -970,7 +1255,7 @@ static struct fio_option options[] = {
        },
        {
                .name   = "verify_offset",
-               .type   = FIO_OPT_STR_VAL_INT,
+               .type   = FIO_OPT_INT,
                .help   = "Offset verify header location by N bytes",
                .def    = "0",
                .cb     = str_verify_offset_cb,
@@ -991,6 +1276,23 @@ static struct fio_option options[] = {
                .help   = "Exit on a single verify failure, don't continue",
                .parent = "verify",
        },
+       {
+               .name   = "verify_async",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(verify_async),
+               .def    = "0",
+               .help   = "Number of async verifier threads to use",
+               .parent = "verify",
+       },
+#ifdef FIO_HAVE_CPU_AFFINITY
+       {
+               .name   = "verify_async_cpus",
+               .type   = FIO_OPT_STR,
+               .cb     = str_verify_cpus_allowed_cb,
+               .help   = "Set CPUs allowed for async verify threads",
+               .parent = "verify_async",
+       },
+#endif
        {
                .name   = "write_iolog",
                .type   = FIO_OPT_STR_STORE,
@@ -1117,26 +1419,30 @@ static struct fio_option options[] = {
        {
                .name   = "rate",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate),
+               .off1   = td_var_offset(rate[0]),
+               .off2   = td_var_offset(rate[1]),
                .help   = "Set bandwidth rate",
        },
        {
                .name   = "ratemin",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(ratemin),
+               .off1   = td_var_offset(ratemin[0]),
+               .off2   = td_var_offset(ratemin[1]),
                .help   = "Job must meet this rate or it will be shutdown",
                .parent = "rate",
        },
        {
                .name   = "rate_iops",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate_iops),
+               .off1   = td_var_offset(rate_iops[0]),
+               .off2   = td_var_offset(rate_iops[1]),
                .help   = "Limit IO used to this number of IO operations/sec",
        },
        {
                .name   = "rate_iops_min",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate_iops_min),
+               .off1   = td_var_offset(rate_iops_min[0]),
+               .off2   = td_var_offset(rate_iops_min[1]),
                .help   = "Job must meet this rate or it will be shutdown",
                .parent = "rate_iops",
        },
@@ -1185,6 +1491,20 @@ static struct fio_option options[] = {
                .help   = "Fsync file after creation",
                .def    = "1",
        },
+       {
+               .name   = "create_on_open",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(create_on_open),
+               .help   = "Create files when they are opened for IO",
+               .def    = "0",
+       },
+       {
+               .name   = "pre_read",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(pre_read),
+               .help   = "Preread files before starting official testing",
+               .def    = "0",
+       },
        {
                .name   = "cpuload",
                .type   = FIO_OPT_INT,
@@ -1260,19 +1580,21 @@ static struct fio_option options[] = {
        },
        {
                .name   = "write_bw_log",
-               .type   = FIO_OPT_STR_SET,
+               .type   = FIO_OPT_STR,
                .off1   = td_var_offset(write_bw_log),
+               .cb     = str_write_bw_log_cb,
                .help   = "Write log of bandwidth during run",
        },
        {
                .name   = "write_lat_log",
-               .type   = FIO_OPT_STR_SET,
+               .type   = FIO_OPT_STR,
                .off1   = td_var_offset(write_lat_log),
+               .cb     = str_write_lat_log_cb,
                .help   = "Write log of latency during run",
        },
        {
                .name   = "hugepage-size",
-               .type   = FIO_OPT_STR_VAL_INT,
+               .type   = FIO_OPT_INT,
                .off1   = td_var_offset(hugepage_size),
                .help   = "When using hugepages, specify size of each page",
                .def    = __stringify(FIO_HUGE_PAGE),
@@ -1335,6 +1657,20 @@ static struct fio_option options[] = {
                .parent = "gtod_reduce",
                .def    = "0",
        },
+       {
+               .name   = "gtod_cpu",
+               .type   = FIO_OPT_INT,
+               .cb     = str_gtod_cpu_cb,
+               .help   = "Setup dedicated gettimeofday() thread on this CPU",
+               .verify = gtod_cpu_verify,
+       },
+       {
+               .name   = "continue_on_error",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(continue_on_error),
+               .help   = "Continue on non-fatal errors during I/O",
+               .def    = "0",
+       },
        {
                .name = NULL,
        },
@@ -1366,14 +1702,102 @@ void fio_options_dup_and_init(struct option *long_options)
        }
 }
 
+struct fio_keyword {
+       const char *word;
+       const char *desc;
+       char *replace;
+};
+
+static struct fio_keyword fio_keywords[] = {
+       {
+               .word   = "$pagesize",
+               .desc   = "Page size in the system",
+       },
+       {
+               .word   = "$mb_memory",
+               .desc   = "Megabytes of memory online",
+       },
+       {
+               .word   = "$ncpus",
+               .desc   = "Number of CPUs online in the system",
+       },
+       {
+               .word   = NULL,
+       },
+};
+
+void fio_keywords_init(void)
+{
+       unsigned long mb_memory;
+       char buf[128];
+       long l;
+
+       sprintf(buf, "%lu", page_size);
+       fio_keywords[0].replace = strdup(buf);
+
+       l = sysconf(_SC_PHYS_PAGES);
+       mb_memory = l * (page_size / 1024UL);
+       sprintf(buf, "%lu", mb_memory);
+       fio_keywords[1].replace = strdup(buf);
+
+       l = sysconf(_SC_NPROCESSORS_ONLN);
+       sprintf(buf, "%lu", l);
+       fio_keywords[2].replace = strdup(buf);
+}
+
+/*
+ * Look for reserved variable names and replace them with real values
+ */
+static char *fio_keyword_replace(char *opt)
+{
+       char *s;
+       int i;
+
+       for (i = 0; fio_keywords[i].word != NULL; i++) {
+               struct fio_keyword *kw = &fio_keywords[i];
+
+               while ((s = strstr(opt, kw->word)) != NULL) {
+                       char *new = malloc(strlen(opt) + 1);
+                       char *o_org = opt;
+                       int olen = s - opt;
+                       int len;
+
+                       /*
+                        * Copy part of the string before the keyword and
+                        * sprintf() the replacement after it.
+                        */
+                       memcpy(new, opt, olen);
+                       len = sprintf(new + olen, "%s", kw->replace);
+
+                       /*
+                        * If there's more in the original string, copy that
+                        * in too
+                        */
+                       opt += strlen(kw->word) + olen;
+                       if (strlen(opt))
+                               memcpy(new + olen + len, opt, opt - o_org - 1);
+
+                       /*
+                        * replace opt and free the old opt
+                        */
+                       opt = new;
+                       free(o_org);
+               }
+       }
+
+       return opt;
+}
+
 int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 {
        int i, ret;
 
        sort_options(opts, options, num_opts);
 
-       for (ret = 0, i = 0; i < num_opts; i++)
+       for (ret = 0, i = 0; i < num_opts; i++) {
+               opts[i] = fio_keyword_replace(opts[i]);
                ret |= parse_option(opts[i], options, td);
+       }
 
        return ret;
 }
@@ -1430,3 +1854,16 @@ void options_mem_free(struct thread_data fio_unused *td)
        __options_mem(td, 0);
 #endif
 }
+
+unsigned int fio_get_kb_base(void *data)
+{
+       struct thread_data *td = data;
+       unsigned int kb_base = 0;
+
+       if (td)
+               kb_base = td->o.kb_base;
+       if (!kb_base)
+               kb_base = 1024;
+
+       return kb_base;
+}