diff options
author | Brian Behlendorf <[email protected]> | 2010-08-26 11:58:00 -0700 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2010-08-31 13:42:01 -0700 |
commit | 302ef1517e5769cbe6a12d94c89f44a90721bfd4 (patch) | |
tree | deb845a6a474955769d58062ea39cc19334cbcff /cmd/zpios | |
parent | 9b020fd97a3dc449a94baec028b30b1fe3c2d5bc (diff) |
Add linux zpios support
Linux kernel implementation of PIOS test app.
Signed-off-by: Brian Behlendorf <[email protected]>
Diffstat (limited to 'cmd/zpios')
-rw-r--r-- | cmd/zpios/.gitignore | 1 | ||||
-rw-r--r-- | cmd/zpios/Makefile.am | 12 | ||||
-rw-r--r-- | cmd/zpios/zpios.h | 121 | ||||
-rw-r--r-- | cmd/zpios/zpios_main.c | 629 | ||||
-rw-r--r-- | cmd/zpios/zpios_util.c | 454 |
5 files changed, 1217 insertions, 0 deletions
diff --git a/cmd/zpios/.gitignore b/cmd/zpios/.gitignore new file mode 100644 index 000000000..b83e1d094 --- /dev/null +++ b/cmd/zpios/.gitignore @@ -0,0 +1 @@ +/zpios diff --git a/cmd/zpios/Makefile.am b/cmd/zpios/Makefile.am new file mode 100644 index 000000000..4e13a76c9 --- /dev/null +++ b/cmd/zpios/Makefile.am @@ -0,0 +1,12 @@ +include $(top_srcdir)/config/Rules.am + +DEFAULT_INCLUDES += \ + -I${top_srcdir}/module/zpios/include + +sbin_PROGRAMS = zpios + +zpios_SOURCES = \ + $(top_srcdir)/cmd/zpios/zpios_main.c \ + $(top_srcdir)/cmd/zpios/zpios_util.c \ + $(top_srcdir)/cmd/zpios/zpios.h + diff --git a/cmd/zpios/zpios.h b/cmd/zpios/zpios.h new file mode 100644 index 000000000..ed979459a --- /dev/null +++ b/cmd/zpios/zpios.h @@ -0,0 +1,121 @@ +/*****************************************************************************\ + * ZPIOS is a heavily modified version of the original PIOS test code. + * It is designed to have the test code running in the Linux kernel + * against ZFS while still being flexibly controled from user space. + * + * Copyright (C) 2008-2010 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). + * Written by Brian Behlendorf <[email protected]>. + * LLNL-CODE-403049 + * + * Original PIOS Test Code + * Copyright (C) 2004 Cluster File Systems, Inc. + * Written by Peter Braam <[email protected]> + * Atul Vidwansa <[email protected]> + * Milind Dumbare <[email protected]> + * + * This file is part of ZFS on Linux. + * For details, see <http://github.com/behlendorf/zfs/>. + * + * ZPIOS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * ZPIOS is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with ZPIOS. If not, see <http://www.gnu.org/licenses/>. +\*****************************************************************************/ + +#ifndef _ZPIOS_H +#define _ZPIOS_H + +#include <zpios-ctl.h> + +#define VERSION_SIZE 64 + +/* Regular expressions */ +#define REGEX_NUMBERS "^[0-9]*[0-9]$" +#define REGEX_NUMBERS_COMMA "^([0-9]+,)*[0-9]+$" +#define REGEX_SIZE "^[0-9][0-9]*[kmgt]$" +#define REGEX_SIZE_COMMA "^([0-9][0-9]*[kmgt]+,)*[0-9][0-9]*[kmgt]$" + +/* Flags for low, high, incr */ +#define FLAG_SET 0x01 +#define FLAG_LOW 0x02 +#define FLAG_HIGH 0x04 +#define FLAG_INCR 0x08 + +#define TRUE 1 +#define FALSE 0 + +#define KB (1024) +#define MB (KB * 1024) +#define GB (MB * 1024) +#define TB (GB * 1024) + +#define KMGT_SIZE 16 + +/* All offsets, sizes and counts can be passed to the application in + * multiple ways. + * 1. a value (stored in val[0], val_count will be 1) + * 2. a comma separated list of values (stored in val[], using val_count) + * 3. a range and block sizes, low, high, factor (val_count must be 0) + */ +typedef struct pios_range_repeat { + uint64_t val[32]; /* Comma sep array, or low, high, inc */ + uint64_t val_count; /* Num of values */ + uint64_t val_low; + uint64_t val_high; + uint64_t val_inc_perc; + uint64_t next_val; /* Used for multiple runs in get_next() */ +} range_repeat_t; + +typedef struct cmd_args { + range_repeat_t T; /* Thread count */ + range_repeat_t N; /* Region count */ + range_repeat_t O; /* Offset count */ + range_repeat_t C; /* Chunksize */ + range_repeat_t S; /* Regionsize */ + + const char *pool; /* Pool */ + const char *name; /* Name */ + uint32_t flags; /* Flags */ + uint32_t io_type; /* DMUIO only */ + uint32_t verbose; /* Verbose */ + uint32_t human_readable; /* Human readable output */ + + uint64_t regionnoise; /* Region noise */ + uint64_t chunknoise; /* Chunk noise */ + uint64_t thread_delay; /* Thread delay */ + + char pre[ZPIOS_PATH_SIZE]; /* Pre-exec hook */ + char post[ZPIOS_PATH_SIZE]; /* Post-exec hook */ + char log[ZPIOS_PATH_SIZE]; /* Requested log dir */ + + /* Control */ + int current_id; + uint64_t current_T; + uint64_t current_N; + uint64_t current_C; + uint64_t current_S; + uint64_t current_O; + + uint32_t rc; +} cmd_args_t; + +int set_count(char *pattern1, char *pattern2, range_repeat_t *range, + char *optarg, uint32_t *flags, char *arg); +int set_lhi(char *pattern, range_repeat_t *range, char *optarg, + int flag, uint32_t *flag_thread, char *arg); +int set_noise(uint64_t *noise, char *optarg, char *arg); +int set_load_params(cmd_args_t *args, char *optarg); +int check_mutual_exclusive_command_lines(uint32_t flag, char *arg); +void print_stats_header(cmd_args_t *args); +void print_stats(cmd_args_t *args, zpios_cmd_t *cmd); + +#endif /* _ZPIOS_H */ diff --git a/cmd/zpios/zpios_main.c b/cmd/zpios/zpios_main.c new file mode 100644 index 000000000..14c37e7e3 --- /dev/null +++ b/cmd/zpios/zpios_main.c @@ -0,0 +1,629 @@ +/*****************************************************************************\ + * ZPIOS is a heavily modified version of the original PIOS test code. + * It is designed to have the test code running in the Linux kernel + * against ZFS while still being flexibly controled from user space. + * + * Copyright (C) 2008-2010 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). + * Written by Brian Behlendorf <[email protected]>. + * LLNL-CODE-403049 + * + * Original PIOS Test Code + * Copyright (C) 2004 Cluster File Systems, Inc. + * Written by Peter Braam <[email protected]> + * Atul Vidwansa <[email protected]> + * Milind Dumbare <[email protected]> + * + * This file is part of ZFS on Linux. + * For details, see <http://github.com/behlendorf/zfs/>. + * + * ZPIOS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * ZPIOS is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with ZPIOS. If not, see <http://www.gnu.org/licenses/>. +\*****************************************************************************/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <assert.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include "zpios.h" + +static const char short_opt[] = "t:l:h:e:n:i:j:k:o:m:q:r:c:a:b:g:s:A:B:C:" + "L:p:M:xP:R:G:I:N:T:VzOfHv?"; +static const struct option long_opt[] = { + {"threadcount", required_argument, 0, 't' }, + {"threadcount_low", required_argument, 0, 'l' }, + {"threadcount_high", required_argument, 0, 'h' }, + {"threadcount_incr", required_argument, 0, 'e' }, + {"regioncount", required_argument, 0, 'n' }, + {"regioncount_low", required_argument, 0, 'i' }, + {"regioncount_high", required_argument, 0, 'j' }, + {"regioncount_incr", required_argument, 0, 'k' }, + {"offset", required_argument, 0, 'o' }, + {"offset_low", required_argument, 0, 'm' }, + {"offset_high", required_argument, 0, 'q' }, + {"offset_incr", required_argument, 0, 'r' }, + {"chunksize", required_argument, 0, 'c' }, + {"chunksize_low", required_argument, 0, 'a' }, + {"chunksize_high", required_argument, 0, 'b' }, + {"chunksize_incr", required_argument, 0, 'g' }, + {"regionsize", required_argument, 0, 's' }, + {"regionsize_low", required_argument, 0, 'A' }, + {"regionsize_high", required_argument, 0, 'B' }, + {"regionsize_incr", required_argument, 0, 'C' }, + {"load", required_argument, 0, 'L' }, + {"pool", required_argument, 0, 'p' }, + {"name", required_argument, 0, 'M' }, + {"cleanup", no_argument, 0, 'x' }, + {"prerun", required_argument, 0, 'P' }, + {"postrun", required_argument, 0, 'R' }, + {"log", required_argument, 0, 'G' }, + {"regionnoise", required_argument, 0, 'I' }, + {"chunknoise", required_argument, 0, 'N' }, + {"threaddelay", required_argument, 0, 'T' }, + {"verify", no_argument, 0, 'V' }, + {"zerocopy", no_argument, 0, 'z' }, + {"nowait", no_argument, 0, 'O' }, + {"noprefetch", no_argument, 0, 'f' }, + {"human-readable", no_argument, 0, 'H' }, + {"verbose", no_argument, 0, 'v' }, + {"help", no_argument, 0, '?' }, + { 0, 0, 0, 0 }, +}; + +static int zpiosctl_fd; /* Control file descriptor */ +static char zpios_version[VERSION_SIZE]; /* Kernel version string */ +static char *zpios_buffer = NULL; /* Scratch space area */ +static int zpios_buffer_size = 0; /* Scratch space size */ + +static int +usage(void) +{ + fprintf(stderr, "Usage: zpios\n"); + fprintf(stderr, + " --threadcount -t =values\n" + " --threadcount_low -l =value\n" + " --threadcount_high -h =value\n" + " --threadcount_incr -e =value\n" + " --regioncount -n =values\n" + " --regioncount_low -i =value\n" + " --regioncount_high -j =value\n" + " --regioncount_incr -k =value\n" + " --offset -o =values\n" + " --offset_low -m =value\n" + " --offset_high -q =value\n" + " --offset_incr -r =value\n" + " --chunksize -c =values\n" + " --chunksize_low -a =value\n" + " --chunksize_high -b =value\n" + " --chunksize_incr -g =value\n" + " --regionsize -s =values\n" + " --regionsize_low -A =value\n" + " --regionsize_high -B =value\n" + " --regionsize_incr -C =value\n" + " --load -L =dmuio|ssf|fpp\n" + " --pool -p =pool name\n" + " --name -M =test name\n" + " --cleanup -x\n" + " --prerun -P =pre-command\n" + " --postrun -R =post-command\n" + " --log -G =log directory\n" + " --regionnoise -I =shift\n" + " --chunknoise -N =bytes\n" + " --threaddelay -T =jiffies\n" + " --verify -V\n" + " --zerocopy -z\n" + " --nowait -O\n" + " --noprefetch -f\n" + " --human-readable -H\n" + " --verbose -v =increase verbosity\n" + " --help -? =this help\n\n"); + + return 0; +} + +static void args_fini(cmd_args_t *args) +{ + assert(args != NULL); + free(args); +} + +static cmd_args_t * +args_init(int argc, char **argv) +{ + cmd_args_t *args; + uint32_t fl_th = 0; + uint32_t fl_rc = 0; + uint32_t fl_of = 0; + uint32_t fl_rs = 0; + uint32_t fl_cs = 0; + int c, rc; + + if (argc == 1) { + usage(); + return (cmd_args_t *)NULL; + } + + /* Configure and populate the args structures */ + args = malloc(sizeof(*args)); + if (args == NULL) + return NULL; + + memset(args, 0, sizeof(*args)); + + while ((c=getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { + rc = 0; + + switch (c) { + case 't': /* --thread count */ + rc = set_count(REGEX_NUMBERS, REGEX_NUMBERS_COMMA, + &args->T, optarg, &fl_th, "threadcount"); + break; + case 'l': /* --threadcount_low */ + rc = set_lhi(REGEX_NUMBERS, &args->T, optarg, + FLAG_LOW, &fl_th, "threadcount_low"); + break; + case 'h': /* --threadcount_high */ + rc = set_lhi(REGEX_NUMBERS, &args->T, optarg, + FLAG_HIGH, &fl_th, "threadcount_high"); + break; + case 'e': /* --threadcount_inc */ + rc = set_lhi(REGEX_NUMBERS, &args->T, optarg, + FLAG_INCR, &fl_th, "threadcount_incr"); + break; + case 'n': /* --regioncount */ + rc = set_count(REGEX_NUMBERS, REGEX_NUMBERS_COMMA, + &args->N, optarg, &fl_rc, "regioncount"); + break; + case 'i': /* --regioncount_low */ + rc = set_lhi(REGEX_NUMBERS, &args->N, optarg, + FLAG_LOW, &fl_rc, "regioncount_low"); + break; + case 'j': /* --regioncount_high */ + rc = set_lhi(REGEX_NUMBERS, &args->N, optarg, + FLAG_HIGH, &fl_rc, "regioncount_high"); + break; + case 'k': /* --regioncount_inc */ + rc = set_lhi(REGEX_NUMBERS, &args->N, optarg, + FLAG_INCR, &fl_rc, "regioncount_incr"); + break; + case 'o': /* --offset */ + rc = set_count(REGEX_SIZE, REGEX_SIZE_COMMA, + &args->O, optarg, &fl_of, "offset"); + break; + case 'm': /* --offset_low */ + rc = set_lhi(REGEX_SIZE, &args->O, optarg, + FLAG_LOW, &fl_of, "offset_low"); + break; + case 'q': /* --offset_high */ + rc = set_lhi(REGEX_SIZE, &args->O, optarg, + FLAG_HIGH, &fl_of, "offset_high"); + break; + case 'r': /* --offset_inc */ + rc = set_lhi(REGEX_NUMBERS, &args->O, optarg, + FLAG_INCR, &fl_of, "offset_incr"); + break; + case 'c': /* --chunksize */ + rc = set_count(REGEX_SIZE, REGEX_SIZE_COMMA, + &args->C, optarg, &fl_cs, "chunksize"); + break; + case 'a': /* --chunksize_low */ + rc = set_lhi(REGEX_SIZE, &args->C, optarg, + FLAG_LOW, &fl_cs, "chunksize_low"); + break; + case 'b': /* --chunksize_high */ + rc = set_lhi(REGEX_SIZE, &args->C, optarg, + FLAG_HIGH, &fl_cs, "chunksize_high"); + break; + case 'g': /* --chunksize_inc */ + rc = set_lhi(REGEX_NUMBERS, &args->C, optarg, + FLAG_INCR, &fl_cs, "chunksize_incr"); + break; + case 's': /* --regionsize */ + rc = set_count(REGEX_SIZE, REGEX_SIZE_COMMA, + &args->S, optarg, &fl_rs, "regionsize"); + break; + case 'A': /* --regionsize_low */ + rc = set_lhi(REGEX_SIZE, &args->S, optarg, + FLAG_LOW, &fl_rs, "regionsize_low"); + break; + case 'B': /* --regionsize_high */ + rc = set_lhi(REGEX_SIZE, &args->S, optarg, + FLAG_HIGH, &fl_rs, "regionsize_high"); + break; + case 'C': /* --regionsize_inc */ + rc = set_lhi(REGEX_NUMBERS, &args->S, optarg, + FLAG_INCR, &fl_rs, "regionsize_incr"); + break; + case 'L': /* --load */ + rc = set_load_params(args, optarg); + break; + case 'p': /* --pool */ + args->pool = optarg; + break; + case 'M': + args->name = optarg; + break; + case 'x': /* --cleanup */ + args->flags |= DMU_REMOVE; + break; + case 'P': /* --prerun */ + strncpy(args->pre, optarg, ZPIOS_PATH_SIZE - 1); + break; + case 'R': /* --postrun */ + strncpy(args->post, optarg, ZPIOS_PATH_SIZE - 1); + break; + case 'G': /* --log */ + strncpy(args->log, optarg, ZPIOS_PATH_SIZE - 1); + break; + case 'I': /* --regionnoise */ + rc = set_noise(&args->regionnoise, optarg, "regionnoise"); + break; + case 'N': /* --chunknoise */ + rc = set_noise(&args->chunknoise, optarg, "chunknoise"); + break; + case 'T': /* --threaddelay */ + rc = set_noise(&args->thread_delay, optarg, "threaddelay"); + break; + case 'V': /* --verify */ + args->flags |= DMU_VERIFY; + break; + case 'z': /* --zerocopy */ + args->flags |= (DMU_WRITE_ZC | DMU_READ_ZC); + break; + case 'O': /* --nowait */ + args->flags |= DMU_WRITE_NOWAIT; + break; + case 'f': /* --noprefetch */ + args->flags |= DMU_READ_NOPF; + break; + case 'H': /* --human-readable */ + args->human_readable = 1; + break; + case 'v': /* --verbose */ + args->verbose++; + break; + case '?': + rc = 1; + break; + default: + fprintf(stderr,"Unknown option '%s'\n",argv[optind-1]); + rc = EINVAL; + break; + } + + if (rc) { + usage(); + args_fini(args); + return NULL; + } + } + + check_mutual_exclusive_command_lines(fl_th, "threadcount"); + check_mutual_exclusive_command_lines(fl_rc, "regioncount"); + check_mutual_exclusive_command_lines(fl_of, "offset"); + check_mutual_exclusive_command_lines(fl_rs, "regionsize"); + check_mutual_exclusive_command_lines(fl_cs, "chunksize"); + + if (args->pool == NULL) { + fprintf(stderr, "Error: Pool not specificed\n"); + usage(); + args_fini(args); + return NULL; + } + + if ((args->flags & (DMU_WRITE_ZC | DMU_READ_ZC)) && + (args->flags & DMU_VERIFY)) { + fprintf(stderr, "Error, --zerocopy incompatible --verify, " + "used for performance analysis only\n"); + usage(); + args_fini(args); + return NULL; + } + + return args; +} + +static int +dev_clear(void) +{ + zpios_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = ZPIOS_CFG_MAGIC; + cfg.cfg_cmd = ZPIOS_CFG_BUFFER_CLEAR; + cfg.cfg_arg1 = 0; + + rc = ioctl(zpiosctl_fd, ZPIOS_CFG, &cfg); + if (rc) + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) ZPIOS_CFG, cfg.cfg_cmd, errno); + + lseek(zpiosctl_fd, 0, SEEK_SET); + + return rc; +} + +/* Passing a size of zero simply results in querying the current size */ +static int +dev_size(int size) +{ + zpios_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = ZPIOS_CFG_MAGIC; + cfg.cfg_cmd = ZPIOS_CFG_BUFFER_SIZE; + cfg.cfg_arg1 = size; + + rc = ioctl(zpiosctl_fd, ZPIOS_CFG, &cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) ZPIOS_CFG, cfg.cfg_cmd, errno); + return rc; + } + + return cfg.cfg_rc1; +} + +static void +dev_fini(void) +{ + if (zpios_buffer) + free(zpios_buffer); + + if (zpiosctl_fd != -1) { + if (close(zpiosctl_fd) == -1) { + fprintf(stderr, "Unable to close %s: %d\n", + ZPIOS_DEV, errno); + } + } +} + +static int +dev_init(void) +{ + int rc; + + zpiosctl_fd = open(ZPIOS_DEV, O_RDONLY); + if (zpiosctl_fd == -1) { + fprintf(stderr, "Unable to open %s: %d\n" + "Is the zpios module loaded?\n", ZPIOS_DEV, errno); + rc = errno; + goto error; + } + + if ((rc = dev_clear())) + goto error; + + if ((rc = dev_size(0)) < 0) + goto error; + + zpios_buffer_size = rc; + zpios_buffer = (char *)malloc(zpios_buffer_size); + if (zpios_buffer == NULL) { + rc = ENOMEM; + goto error; + } + + memset(zpios_buffer, 0, zpios_buffer_size); + return 0; +error: + if (zpiosctl_fd != -1) { + if (close(zpiosctl_fd) == -1) { + fprintf(stderr, "Unable to close %s: %d\n", + ZPIOS_DEV, errno); + } + } + + return rc; +} + +static int +get_next(uint64_t *val, range_repeat_t *range) +{ + /* if low, incr, high is given */ + if (range->val_count == 0) { + *val = (range->val_low) + + (range->val_low * range->next_val / 100); + + if (*val > range->val_high) + return 0; /* No more values, limit exceeded */ + + if (!range->next_val) + range->next_val = range->val_inc_perc; + else + range->next_val = range->next_val+range->val_inc_perc; + + return 1; /* more values to come */ + + /* if only one val is given */ + } else if (range->val_count == 1) { + if (range->next_val) + return 0; /* No more values, we only have one */ + + *val = range->val[0]; + range->next_val = 1; + return 1; /* more values to come */ + + /* if comma separated values are given */ + } else if (range->val_count > 1) { + if (range->next_val > range->val_count - 1) + return 0; /* No more values, limit exceeded */ + + *val = range->val[range->next_val]; + range->next_val++; + return 1; /* more values to come */ + } + + return 0; +} + +static int +run_one(cmd_args_t *args, uint32_t id, uint32_t T, uint32_t N, + uint64_t C, uint64_t S, uint64_t O) +{ + zpios_cmd_t *cmd; + int rc, rc2, cmd_size; + + dev_clear(); + + cmd_size = sizeof(zpios_cmd_t) + ((T + N + 1) * sizeof(zpios_stats_t)); + cmd = (zpios_cmd_t *)malloc(cmd_size); + if (cmd == NULL) + return ENOMEM; + + memset(cmd, 0, cmd_size); + cmd->cmd_magic = ZPIOS_CMD_MAGIC; + strncpy(cmd->cmd_pool, args->pool, ZPIOS_NAME_SIZE - 1); + strncpy(cmd->cmd_pre, args->pre, ZPIOS_PATH_SIZE - 1); + strncpy(cmd->cmd_post, args->post, ZPIOS_PATH_SIZE - 1); + strncpy(cmd->cmd_log, args->log, ZPIOS_PATH_SIZE - 1); + cmd->cmd_id = id; + cmd->cmd_chunk_size = C; + cmd->cmd_thread_count = T; + cmd->cmd_region_count = N; + cmd->cmd_region_size = S; + cmd->cmd_offset = O; + cmd->cmd_region_noise = args->regionnoise; + cmd->cmd_chunk_noise = args->chunknoise; + cmd->cmd_thread_delay = args->thread_delay; + cmd->cmd_flags = args->flags; + cmd->cmd_data_size = (T + N + 1) * sizeof(zpios_stats_t); + + rc = ioctl(zpiosctl_fd, ZPIOS_CMD, cmd); + if (rc) + args->rc = errno; + + print_stats(args, cmd); + + if (args->verbose) { + rc2 = read(zpiosctl_fd, zpios_buffer, zpios_buffer_size - 1); + if (rc2 < 0) { + fprintf(stdout, "Error reading results: %d\n", rc2); + } else if ((rc2 > 0) && (strlen(zpios_buffer) > 0)) { + fprintf(stdout, "\n%s\n", zpios_buffer); + fflush(stdout); + } + } + + free(cmd); + + return rc; +} + +static int +run_offsets(cmd_args_t *args) +{ + int rc = 0; + + while (rc == 0 && get_next(&args->current_O, &args->O)) { + rc = run_one(args, args->current_id, + args->current_T, args->current_N, args->current_C, + args->current_S, args->current_O); + args->current_id++; + } + + args->O.next_val = 0; + return rc; +} + +static int +run_region_counts(cmd_args_t *args) +{ + int rc = 0; + + while (rc == 0 && get_next((uint64_t *)&args->current_N, &args->N)) + rc = run_offsets(args); + + args->N.next_val = 0; + return rc; +} + +static int +run_region_sizes(cmd_args_t *args) +{ + int rc = 0; + + while (rc == 0 && get_next(&args->current_S, &args->S)) { + if (args->current_S < args->current_C) { + fprintf(stderr, "Error: in any run chunksize can " + "not be smaller than regionsize.\n"); + return EINVAL; + } + + rc = run_region_counts(args); + } + + args->S.next_val = 0; + return rc; +} + +static int +run_chunk_sizes(cmd_args_t *args) +{ + int rc = 0; + + while (rc == 0 && get_next(&args->current_C, &args->C)) { + rc = run_region_sizes(args); + } + + args->C.next_val = 0; + return rc; +} + +static int +run_thread_counts(cmd_args_t *args) +{ + int rc = 0; + + while (rc == 0 && get_next((uint64_t *)&args->current_T, &args->T)) + rc = run_chunk_sizes(args); + + return rc; +} + +int +main(int argc, char **argv) +{ + cmd_args_t *args; + int rc = 0; + + /* Argument init and parsing */ + if ((args = args_init(argc, argv)) == NULL) { + rc = -1; + goto out; + } + + /* Device specific init */ + if ((rc = dev_init())) + goto out; + + /* Generic kernel version string */ + if (args->verbose) + fprintf(stdout, "%s", zpios_version); + + print_stats_header(args); + rc = run_thread_counts(args); +out: + if (args != NULL) + args_fini(args); + + dev_fini(); + return rc; +} diff --git a/cmd/zpios/zpios_util.c b/cmd/zpios/zpios_util.c new file mode 100644 index 000000000..48a0a469f --- /dev/null +++ b/cmd/zpios/zpios_util.c @@ -0,0 +1,454 @@ +/*****************************************************************************\ + * ZPIOS is a heavily modified version of the original PIOS test code. + * It is designed to have the test code running in the Linux kernel + * against ZFS while still being flexibly controled from user space. + * + * Copyright (C) 2008-2010 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). + * Written by Brian Behlendorf <[email protected]>. + * LLNL-CODE-403049 + * + * Original PIOS Test Code + * Copyright (C) 2004 Cluster File Systems, Inc. + * Written by Peter Braam <[email protected]> + * Atul Vidwansa <[email protected]> + * Milind Dumbare <[email protected]> + * + * This file is part of ZFS on Linux. + * For details, see <http://github.com/behlendorf/zfs/>. + * + * ZPIOS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * ZPIOS is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with ZPIOS. If not, see <http://www.gnu.org/licenses/>. +\*****************************************************************************/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <regex.h> +#include "zpios.h" + +/* extracts an unsigned int (64) and K,M,G,T from the string */ +/* and returns a 64 bit value converted to the proper units */ +static int +kmgt_to_uint64(const char *str, uint64_t *val) +{ + char *endptr; + int rc = 0; + + *val = strtoll(str, &endptr, 0); + if ((str == endptr) && (*val == 0)) + return EINVAL; + + switch (endptr[0]) { + case 'k': case 'K': + *val = (*val) << 10; + break; + case 'm': case 'M': + *val = (*val) << 20; + break; + case 'g': case 'G': + *val = (*val) << 30; + break; + case 't': case 'T': + *val = (*val) << 40; + break; + case '\0': + break; + default: + rc = EINVAL; + } + + return rc; +} + +static char * +uint64_to_kmgt(char *str, uint64_t val) +{ + char postfix[] = "kmgt"; + int i = -1; + + while ((val >= KB) && (i < 4)) { + val = (val >> 10); + i++; + } + + if (i >= 4) + (void)snprintf(str, KMGT_SIZE-1, "inf"); + else + (void)snprintf(str, KMGT_SIZE-1, "%lu%c", (unsigned long)val, + (i == -1) ? '\0' : postfix[i]); + + return str; +} + +static char * +kmgt_per_sec(char *str, uint64_t v, double t) +{ + char postfix[] = "kmgt"; + double val = ((double)v) / t; + int i = -1; + + while ((val >= (double)KB) && (i < 4)) { + val /= (double)KB; + i++; + } + + if (i >= 4) + (void)snprintf(str, KMGT_SIZE-1, "inf"); + else + (void)snprintf(str, KMGT_SIZE-1, "%.2f%c", val, + (i == -1) ? '\0' : postfix[i]); + + return str; +} + +static char * +print_flags(char *str, uint32_t flags) +{ + str[0] = (flags & DMU_WRITE) ? 'w' : '-'; + str[1] = (flags & DMU_READ) ? 'r' : '-'; + str[2] = (flags & DMU_VERIFY) ? 'v' : '-'; + str[3] = (flags & DMU_REMOVE) ? 'c' : '-'; + str[4] = (flags & DMU_FPP) ? 'p' : 's'; + str[5] = (flags & (DMU_WRITE_ZC | DMU_READ_ZC)) ? 'z' : '-'; + str[6] = (flags & DMU_WRITE_NOWAIT) ? 'O' : '-'; + str[7] = '\0'; + + return str; +} + +static int +regex_match(const char *string, char *pattern) +{ + regex_t re = { 0 }; + int rc; + + rc = regcomp(&re, pattern, REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (rc) { + fprintf(stderr, "Error: Couldn't do regcomp, %d\n", rc); + return rc; + } + + rc = regexec(&re, string, (size_t) 0, NULL, 0); + regfree(&re); + + return rc; +} + +/* fills the pios_range_repeat structure of comma separated values */ +static int +split_string(const char *optarg, char *pattern, range_repeat_t *range) +{ + const char comma[] = ","; + char *cp, *token[32]; + int rc, i = 0; + + if ((rc = regex_match(optarg, pattern))) + return rc; + + cp = strdup(optarg); + if (cp == NULL) + return ENOMEM; + + do { + /* STRTOK(3) Each subsequent call, with a null pointer as the + * value of the * first argument, starts searching from the + * saved pointer and behaves as described above. + */ + token[i] = strtok(cp, comma); + cp = NULL; + } while ((token[i++] != NULL) && (i < 32)); + + range->val_count = i - 1; + + for (i = 0; i < range->val_count; i++) + kmgt_to_uint64(token[i], &range->val[i]); + + free(cp); + return 0; +} + +int +set_count(char *pattern1, char *pattern2, range_repeat_t *range, + char *optarg, uint32_t *flags, char *arg) +{ + if (flags) + *flags |= FLAG_SET; + + range->next_val = 0; + + if (regex_match(optarg, pattern1) == 0) { + kmgt_to_uint64(optarg, &range->val[0]); + range->val_count = 1; + } else if (split_string(optarg, pattern2, range) < 0) { + fprintf(stderr, "Error: Incorrect pattern for %s, '%s'\n", + arg, optarg); + return EINVAL; + } + + return 0; +} + +/* validates the value with regular expression and sets low, high, incr + * according to value at which flag will be set. Sets the flag after. */ +int +set_lhi(char *pattern, range_repeat_t *range, char *optarg, + int flag, uint32_t *flag_thread, char *arg) +{ + int rc; + + if ((rc = regex_match(optarg, pattern))) { + fprintf(stderr, "Error: Wrong pattern in %s, '%s'\n", + arg, optarg); + return rc; + } + + switch (flag) { + case FLAG_LOW: + kmgt_to_uint64(optarg, &range->val_low); + break; + case FLAG_HIGH: + kmgt_to_uint64(optarg, &range->val_high); + break; + case FLAG_INCR: + kmgt_to_uint64(optarg, &range->val_inc_perc); + break; + default: + assert(0); + } + + *flag_thread |= flag; + + return 0; +} + +int +set_noise(uint64_t *noise, char *optarg, char *arg) +{ + if (regex_match(optarg, REGEX_NUMBERS) == 0) { + kmgt_to_uint64(optarg, noise); + } else { + fprintf(stderr, "Error: Incorrect pattern for %s\n", arg); + return EINVAL; + } + + return 0; +} + +int +set_load_params(cmd_args_t *args, char *optarg) +{ + char *param, *search, comma[] = ","; + int rc = 0; + + search = strdup(optarg); + if (search == NULL) + return ENOMEM; + + while ((param = strtok(search, comma)) != NULL) { + search = NULL; + + if (strcmp("fpp", param) == 0) { + args->flags |= DMU_FPP; /* File Per Process/Thread */ + } else if (strcmp("ssf", param) == 0) { + args->flags &= ~DMU_FPP; /* Single Shared File */ + } else if (strcmp("dmuio", param) == 0) { + args->io_type |= DMU_IO; + args->flags |= (DMU_WRITE | DMU_READ); + } else { + fprintf(stderr, "Invalid load: %s\n", param); + rc = EINVAL; + } + } + + free(search); + + return rc; +} + + +/* checks the low, high, increment values against the single value for + * mutual exclusion, for e.g threadcount is mutually exclusive to + * threadcount_low, ..._high, ..._incr */ +int +check_mutual_exclusive_command_lines(uint32_t flag, char *arg) +{ + if ((flag & FLAG_SET) && (flag & (FLAG_LOW | FLAG_HIGH | FLAG_INCR))) { + fprintf(stderr, "Error: --%s can not be given with --%s_low, " + "--%s_high or --%s_incr.\n", arg, arg, arg, arg); + return 0; + } + + if ((flag & (FLAG_LOW | FLAG_HIGH | FLAG_INCR)) && !(flag & FLAG_SET)){ + if (flag != (FLAG_LOW | FLAG_HIGH | FLAG_INCR)) { + fprintf(stderr, "Error: One or more values missing " + "from --%s_low, --%s_high, --%s_incr.\n", + arg, arg, arg); + return 0; + } + } + + return 1; +} + +void +print_stats_header(cmd_args_t *args) +{ + if (args->verbose) { + printf("status name id\tth-cnt\trg-cnt\trg-sz\t" + "ch-sz\toffset\trg-no\tch-no\tth-dly\tflags\ttime\t" + "cr-time\trm-time\twr-time\trd-time\twr-data\twr-ch\t" + "wr-bw\trd-data\trd-ch\trd-bw\n"); + printf("------------------------------------------------" + "------------------------------------------------" + "------------------------------------------------" + "----------------------------------------------\n"); + } else { + printf("status name id\t" + "wr-data\twr-ch\twr-bw\t" + "rd-data\trd-ch\trd-bw\n"); + printf("-----------------------------------------" + "--------------------------------------\n"); + } +} + +static void +print_stats_human_readable(cmd_args_t *args, zpios_cmd_t *cmd) +{ + zpios_stats_t *summary_stats; + double t_time, wr_time, rd_time, cr_time, rm_time; + char str[KMGT_SIZE]; + + if (args->rc) + printf("FAIL: %3d ", args->rc); + else + printf("PASS: "); + + printf("%-12s", args->name ? args->name : ZPIOS_NAME); + printf("%2u\t", cmd->cmd_id); + + if (args->verbose) { + printf("%u\t", cmd->cmd_thread_count); + printf("%u\t", cmd->cmd_region_count); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_region_size)); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_chunk_size)); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_offset)); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_region_noise)); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_chunk_noise)); + printf("%s\t", uint64_to_kmgt(str, cmd->cmd_thread_delay)); + printf("%s\t", print_flags(str, cmd->cmd_flags)); + } + + if (args->rc) { + printf("\n"); + return; + } + + summary_stats = (zpios_stats_t *)cmd->cmd_data_str; + t_time = zpios_timespec_to_double(summary_stats->total_time.delta); + wr_time = zpios_timespec_to_double(summary_stats->wr_time.delta); + rd_time = zpios_timespec_to_double(summary_stats->rd_time.delta); + cr_time = zpios_timespec_to_double(summary_stats->cr_time.delta); + rm_time = zpios_timespec_to_double(summary_stats->rm_time.delta); + + if (args->verbose) { + printf("%.2f\t", t_time); + printf("%.3f\t", cr_time); + printf("%.3f\t", rm_time); + printf("%.2f\t", wr_time); + printf("%.2f\t", rd_time); + } + + printf("%s\t", uint64_to_kmgt(str, summary_stats->wr_data)); + printf("%s\t", uint64_to_kmgt(str, summary_stats->wr_chunks)); + printf("%s\t", kmgt_per_sec(str, summary_stats->wr_data, wr_time)); + + printf("%s\t", uint64_to_kmgt(str, summary_stats->rd_data)); + printf("%s\t", uint64_to_kmgt(str, summary_stats->rd_chunks)); + printf("%s\n", kmgt_per_sec(str, summary_stats->rd_data, rd_time)); + fflush(stdout); +} + +static void +print_stats_table(cmd_args_t *args, zpios_cmd_t *cmd) +{ + zpios_stats_t *summary_stats; + double wr_time, rd_time; + + if (args->rc) + printf("FAIL: %3d ", args->rc); + else + printf("PASS: "); + + printf("%-12s", args->name ? args->name : ZPIOS_NAME); + printf("%2u\t", cmd->cmd_id); + + if (args->verbose) { + printf("%u\t", cmd->cmd_thread_count); + printf("%u\t", cmd->cmd_region_count); + printf("%llu\t", (long long unsigned)cmd->cmd_region_size); + printf("%llu\t", (long long unsigned)cmd->cmd_chunk_size); + printf("%llu\t", (long long unsigned)cmd->cmd_offset); + printf("%u\t", cmd->cmd_region_noise); + printf("%u\t", cmd->cmd_chunk_noise); + printf("%u\t", cmd->cmd_thread_delay); + printf("0x%x\t", cmd->cmd_flags); + } + + if (args->rc) { + printf("\n"); + return; + } + + summary_stats = (zpios_stats_t *)cmd->cmd_data_str; + wr_time = zpios_timespec_to_double(summary_stats->wr_time.delta); + rd_time = zpios_timespec_to_double(summary_stats->rd_time.delta); + + if (args->verbose) { + printf("%ld.%02ld\t", + (long)summary_stats->total_time.delta.ts_sec, + (long)summary_stats->total_time.delta.ts_nsec); + printf("%ld.%02ld\t", + (long)summary_stats->cr_time.delta.ts_sec, + (long)summary_stats->cr_time.delta.ts_nsec); + printf("%ld.%02ld\t", + (long)summary_stats->rm_time.delta.ts_sec, + (long)summary_stats->rm_time.delta.ts_nsec); + printf("%ld.%02ld\t", + (long)summary_stats->wr_time.delta.ts_sec, + (long)summary_stats->wr_time.delta.ts_nsec); + printf("%ld.%02ld\t", + (long)summary_stats->rd_time.delta.ts_sec, + (long)summary_stats->rd_time.delta.ts_nsec); + } + + printf("%lld\t", (long long unsigned)summary_stats->wr_data); + printf("%lld\t", (long long unsigned)summary_stats->wr_chunks); + printf("%.4f\t", (double)summary_stats->wr_data / wr_time); + + printf("%lld\t", (long long unsigned)summary_stats->rd_data); + printf("%lld\t", (long long unsigned)summary_stats->rd_chunks); + printf("%.4f\n", (double)summary_stats->rd_data / rd_time); + fflush(stdout); +} + +void +print_stats(cmd_args_t *args, zpios_cmd_t *cmd) +{ + if (args->human_readable) + print_stats_human_readable(args, cmd); + else + print_stats_table(args, cmd); +} |