diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/Makefile.am | 2 | ||||
-rw-r--r-- | cmd/zpios/Makefile.am | 8 | ||||
-rw-r--r-- | cmd/zpios/zpios.h | 117 | ||||
-rw-r--r-- | cmd/zpios/zpios_main.c | 619 | ||||
-rw-r--r-- | cmd/zpios/zpios_util.c | 440 |
5 files changed, 1185 insertions, 1 deletions
diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 437374f0c..8259b7bde 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -1 +1 @@ -SUBDIRS = zfs zpool zdb zdump zinject ztest +SUBDIRS = zfs zpool zdb zdump zinject ztest zpios diff --git a/cmd/zpios/Makefile.am b/cmd/zpios/Makefile.am new file mode 100644 index 000000000..91029dbe7 --- /dev/null +++ b/cmd/zpios/Makefile.am @@ -0,0 +1,8 @@ +include $(top_srcdir)/config/Rules.am + +sbin_PROGRAMS = zpios + +zpios_SOURCES = \ + $(top_srcdir)/cmd/zpios/zpios.h \ + $(top_srcdir)/cmd/zpios/zpios_main.c \ + $(top_srcdir)/cmd/zpios/zpios_util.c diff --git a/cmd/zpios/zpios.h b/cmd/zpios/zpios.h new file mode 100644 index 000000000..01c2a9b6c --- /dev/null +++ b/cmd/zpios/zpios.h @@ -0,0 +1,117 @@ +/* + * This file is part of the ZFS Linux port. + * + * Copyright (c) 2008 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory + * Written by: + * Brian Behlendorf <[email protected]>, + * Herb Wartens <[email protected]>, + * Jim Garlick <[email protected]> + * LLNL-CODE-403049 + * + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +#ifndef _ZPIOS_H +#define _ZPIOS_H + +#include <kpios-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) + +/* 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 */ + 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[KPIOS_PATH_SIZE]; /* Pre-exec hook */ + char post[KPIOS_PATH_SIZE]; /* Post-exec hook */ + char log[KPIOS_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(void); +void print_stats(cmd_args_t *args, kpios_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..e1e988680 --- /dev/null +++ b/cmd/zpios/zpios_main.c @@ -0,0 +1,619 @@ +/* + * This file is part of the ZFS Linux port. + * + * Copyright (c) 2008 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory + * Written by: + * Brian Behlendorf <[email protected]>, + * Herb Wartens <[email protected]>, + * Jim Garlick <[email protected]> + * LLNL-CODE-403049 + * + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * Kernel PIOS DMU implemenation originally derived from PIOS test code. + * Character control interface derived from SPL code. + */ + +#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:c:u:a:b:g:L:P:R:I:" + "G:T:Vzs:A:B:C:o:m:q:r:fwxdp:v?"; +static const struct option long_opt[] = { + {"chunksize", required_argument, 0, 'c' }, + {"chunksize_low", required_argument, 0, 'a' }, + {"chunksize_high", required_argument, 0, 'b' }, + {"chunksize_incr", required_argument, 0, 'g' }, + {"offset", required_argument, 0, 'o' }, + {"offset_low", required_argument, 0, 'm' }, + {"offset_high", required_argument, 0, 'q' }, + {"offset_incr", required_argument, 0, 'r' }, + {"regioncount", required_argument, 0, 'n' }, + {"regioncount_low", required_argument, 0, 'i' }, + {"regioncount_high", required_argument, 0, 'j' }, + {"regioncount_incr", required_argument, 0, 'k' }, + {"threadcount", required_argument, 0, 't' }, + {"threadcount_low", required_argument, 0, 'l' }, + {"threadcount_high", required_argument, 0, 'h' }, + {"threadcount_incr", required_argument, 0, 'e' }, + {"regionsize", required_argument, 0, 's' }, + {"regionsize_low", required_argument, 0, 'A' }, + {"regionsize_high", required_argument, 0, 'B' }, + {"regionsize_incr", required_argument, 0, 'C' }, + {"cleanup", no_argument, 0, 'x' }, + {"verify", no_argument, 0, 'V' }, + {"zerocopy", no_argument, 0, 'z' }, + {"threaddelay", required_argument, 0, 'T' }, + {"regionnoise", required_argument, 0, 'I' }, + {"chunknoise", required_argument, 0, 'N' }, + {"prerun", required_argument, 0, 'P' }, + {"postrun", required_argument, 0, 'R' }, + {"log", required_argument, 0, 'G' }, + {"path", required_argument, 0, 'p' }, + {"pool", required_argument, 0, 'p' }, + {"load", required_argument, 0, 'L' }, + {"human-readable", no_argument, 0, 'H' }, + {"help", no_argument, 0, '?' }, + {"verbose", no_argument, 0, 'v' }, + { 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, + " --chunksize -c =values\n" + " --chunksize_low -a =value\n" + " --chunksize_high -b =value\n" + " --chunksize_incr -g =value\n" + " --offset -o =values\n" + " --offset_low -m =value\n" + " --offset_high -q =value\n" + " --offset_incr -r =value\n" + " --regioncount -n =values\n" + " --regioncount_low -i =value\n" + " --regioncount_high -j =value\n" + " --regioncount_incr -k =value\n" + " --threadcount -t =values\n" + " --threadcount_low -l =value\n" + " --threadcount_high -h =value\n" + " --threadcount_incr -e =value\n" + " --regionsize -s =values\n" + " --regionsize_low -A =value\n" + " --regionsize_high -B =value\n" + " --regionsize_incr -C =value\n" + " --cleanup -x\n" + " --verify -V\n" + " --zerocopy -z\n" + " --threaddelay -T =jiffies\n" + " --regionnoise -I =shift\n" + " --chunknoise -N =bytes\n" + " --prerun -P =pre-command\n" + " --postrun -R =post-command\n" + " --log -G =log directory\n" + " --pool | --path -p =pool name\n" + " --load -L =dmuio\n" + " --human-readable -H\n" + " --help -? =this help\n" + " --verbose -v =increase verbosity\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 'v': /* --verbose */ + args->verbose++; + break; + 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 'x': /* --cleanup */ + args->flags |= DMU_REMOVE; + break; + case 'P': /* --prerun */ + strncpy(args->pre, optarg, KPIOS_PATH_SIZE - 1); + break; + case 'R': /* --postrun */ + strncpy(args->post, optarg, KPIOS_PATH_SIZE - 1); + break; + case 'G': /* --log */ + strncpy(args->log, optarg, KPIOS_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': /* --verify */ + args->flags |= (DMU_WRITE_ZC | DMU_READ_ZC); + break; + case 'H': + args->human_readable = 1; + 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) +{ + kpios_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = KPIOS_CFG_MAGIC; + cfg.cfg_cmd = KPIOS_CFG_BUFFER_CLEAR; + cfg.cfg_arg1 = 0; + + rc = ioctl(zpiosctl_fd, KPIOS_CFG, &cfg); + if (rc) + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KPIOS_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) +{ + kpios_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = KPIOS_CFG_MAGIC; + cfg.cfg_cmd = KPIOS_CFG_BUFFER_SIZE; + cfg.cfg_arg1 = size; + + rc = ioctl(zpiosctl_fd, KPIOS_CFG, &cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KPIOS_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", + KPIOS_DEV, errno); + } + } +} + +static int +dev_init(void) +{ + int rc; + + zpiosctl_fd = open(KPIOS_DEV, O_RDONLY); + if (zpiosctl_fd == -1) { + fprintf(stderr, "Unable to open %s: %d\n" + "Is the zpios module loaded?\n", KPIOS_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", + KPIOS_DEV, errno); + } + } + + return rc; +} + +static int +get_next(uint64_t *val, range_repeat_t *range) +{ + int i; + + /* 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) +{ + kpios_cmd_t *cmd; + int rc, rc2, cmd_size; + + dev_clear(); + + cmd_size = sizeof(kpios_cmd_t) + ((T + N + 1) * sizeof(kpios_stats_t)); + cmd = (kpios_cmd_t *)malloc(cmd_size); + if (cmd == NULL) + return ENOMEM; + + memset(cmd, 0, cmd_size); + cmd->cmd_magic = KPIOS_CMD_MAGIC; + strncpy(cmd->cmd_pool, args->pool, KPIOS_NAME_SIZE - 1); + strncpy(cmd->cmd_pre, args->pre, KPIOS_PATH_SIZE - 1); + strncpy(cmd->cmd_post, args->post, KPIOS_PATH_SIZE - 1); + strncpy(cmd->cmd_log, args->log, KPIOS_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(kpios_stats_t); + + rc = ioctl(zpiosctl_fd, KPIOS_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(); + 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..4d7e52d64 --- /dev/null +++ b/cmd/zpios/zpios_util.c @@ -0,0 +1,440 @@ +/* + * This file is part of the ZFS Linux port. + * + * Copyright (c) 2008 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory + * Written by: + * Brian Behlendorf <[email protected]>, + * Herb Wartens <[email protected]>, + * Jim Garlick <[email protected]> + * LLNL-CODE-403049 + * + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * Kernel PIOS DMU implemenation originally derived from PIOS test code. + * Character control interface derived from SPL code. + */ + +#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) + sprintf(str, "inf"); + else + sprintf(str, "%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) + sprintf(str, "inf"); + else + sprintf(str, "%.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] = '\0'; + + return str; +} + +static double +timespec_to_double(struct timespec t) +{ + return ((double)(t.tv_sec) + + ((double)(t.tv_nsec) / (double)(1000*1000*1000))); +} + +static int +regex_match(const char *string, char *pattern) +{ + regex_t re; + 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("sff", param) == 0) { + args->flags &= ~DMU_FPP; /* Shared 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(void) +{ + printf("ret-code id\tth-cnt\trg-cnt\trg-sz\tch-sz\toffset\trg-no\tch-no\t" + "th-dly\tflags\ttime\tcr-time\trm-time\twr-time\t" + "rd-time\twr-data\twr-ch\twr-bw\trd-data\trd-ch\trd-bw\n"); + printf("------------------------------------------------------------" + "------------------------------------------------------------" + "-----------------------------------------------------------\n"); +} + +static void +print_stats_human_readable(cmd_args_t *args, kpios_cmd_t *cmd) +{ + kpios_stats_t *summary_stats; + double t_time, wr_time, rd_time, cr_time, rm_time; + char str[16]; + + if (args->rc) + printf("FAILED: %3d ", args->rc); + else + printf("PASSED: %3d ", args->rc); + + printf("%u\t", cmd->cmd_id); + 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 = (kpios_stats_t *)cmd->cmd_data_str; + t_time = timespec_to_double(summary_stats->total_time.delta); + wr_time = timespec_to_double(summary_stats->wr_time.delta); + rd_time = timespec_to_double(summary_stats->rd_time.delta); + cr_time = timespec_to_double(summary_stats->cr_time.delta); + rm_time = timespec_to_double(summary_stats->rm_time.delta); + + 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, kpios_cmd_t *cmd) +{ + kpios_stats_t *summary_stats; + double wr_time, rd_time; + + if (args->rc) + printf("FAILED: %3d ", args->rc); + else + printf("PASSED: %3d ", args->rc); + + printf("%u\t", cmd->cmd_id); + 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 = (kpios_stats_t *)cmd->cmd_data_str; + wr_time = timespec_to_double(summary_stats->wr_time.delta); + rd_time = timespec_to_double(summary_stats->rd_time.delta); + + printf("%ld.%02ld\t", + summary_stats->total_time.delta.tv_sec, + summary_stats->total_time.delta.tv_nsec); + printf("%ld.%02ld\t", + summary_stats->cr_time.delta.tv_sec, + summary_stats->cr_time.delta.tv_nsec); + printf("%ld.%02ld\t", + summary_stats->rm_time.delta.tv_sec, + summary_stats->rm_time.delta.tv_nsec); + printf("%ld.%02ld\t", + summary_stats->wr_time.delta.tv_sec, + summary_stats->wr_time.delta.tv_nsec); + printf("%ld.%02ld\t", + summary_stats->rd_time.delta.tv_sec, + summary_stats->rd_time.delta.tv_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, kpios_cmd_t *cmd) +{ + if (args->human_readable) + print_stats_human_readable(args, cmd); + else + print_stats_table(args, cmd); +} |