func WithReflectionMetadata(md map[string]string) Option {
	return func(o *RunConfig) error {
		o.rmd = md
		return nil
	}
}
WithReflectionMetadata specifies the metadata to be used as a map
md := make(map[string]string)
md["token"] = "foobar"
md["request-id"] = "123"
WithReflectionMetadata(&md)
package runner
import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"
	"github.com/bojand/ghz/load"
	"github.com/jhump/protoreflect/desc"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	humanize "github.com/dustin/go-humanize"
)
type BinaryDataFunc func(mtd *desc.MethodDescriptor, callData *CallData) []byte
const ScheduleConst = "const"
const ScheduleStep = "step"
const ScheduleLine = "line"
type RunConfig struct {
	call               string
	host               string
	proto              string
	importPaths        []string
	protoset           string
	protosetBinary     []byte
	enableCompression  bool
	defaultCallOptions []grpc.CallOption
	creds      credentials.TransportCredentials
	cacert     string
	cert       string
	key        string
	cname      string
	skipVerify bool
	insecure   bool
	authority  string
	rps              int
	loadStart        uint
	loadEnd          uint
	loadStep         int
	loadSchedule     string
	loadDuration     time.Duration
	loadStepDuration time.Duration
	pacer load.Pacer
	c             int
	cStart        uint
	cEnd          uint
	cStep         int
	cSchedule     string
	cMaxDuration  time.Duration
	cStepDuration time.Duration
	workerTicker load.WorkerTicker
	n     int
	async bool
	nConns int
	z             time.Duration
	timeout       time.Duration
	dialTimeout   time.Duration
	keepaliveTime time.Duration
	zstop string
	streamInterval        time.Duration
	streamCallDuration    time.Duration
	streamCallCount       uint
	streamDynamicMessages bool
	lbStrategy string
	data     []byte
	metadata []byte
	binary   bool
	dataFunc         BinaryDataFunc
	dataProviderFunc DataProviderFunc
	dataStreamFunc   StreamMessageProviderFunc
	mdProviderFunc   MetadataProviderFunc
	funcs template.FuncMap
	rmd map[string]string
	hasLog bool
	log    Logger
	name        string
	cpus        int
	tags        []byte
	skipFirst   int
	countErrors bool
	recvMsgFunc StreamRecvMsgInterceptFunc
}
type Option func(*RunConfig) error
func NewConfig(call, host string, options ...Option) (*RunConfig, error) {
	c := &RunConfig{
		n:            200,
		c:            50,
		nConns:       1,
		timeout:      time.Duration(20 * time.Second),
		dialTimeout:  time.Duration(10 * time.Second),
		cpus:         runtime.GOMAXPROCS(-1),
		zstop:        "close",
		loadSchedule: ScheduleConst,
	}
	for _, option := range options {
		err := option(c)
		if err != nil {
			return nil, err
		}
	}
	if c.host == "" {
		c.host = strings.TrimSpace(host)
	}
	if c.call == "" {
		c.call = strings.TrimSpace(call)
	}
	if c.z > 0 {
		c.n = math.MaxInt32
	}
	if c.nConns > c.c {
		return nil, errors.New("number of connections cannot be greater than concurrency")
	}
	if c.call == "" {
		return nil, errors.New("call required")
	}
	if c.host == "" {
		return nil, errors.New("host required")
	}
	if c.binary && c.streamDynamicMessages {
		return nil, errors.New("cannot use dynamic messages with binary data")
	}
	if c.loadSchedule != ScheduleConst &&
		c.loadSchedule != ScheduleStep &&
		c.loadSchedule != ScheduleLine {
		return nil, fmt.Errorf(`schedule much be "%s", "%s", or "%s"`,
			ScheduleConst, ScheduleStep, ScheduleLine)
	}
	if c.loadSchedule == ScheduleStep || c.loadSchedule == ScheduleLine {
		if c.loadStart == c.loadEnd {
			return nil, errors.New("load start cannot equal load end")
		}
		if c.loadStep == 0 {
			return nil, errors.New("invalid load step")
		}
		if c.loadSchedule == ScheduleStep && c.loadStepDuration == 0 {
			return nil, errors.New("invalid load step duration")
		}
	}
	if c.cSchedule == ScheduleStep || c.cSchedule == ScheduleLine {
		if c.cStart == c.cEnd {
			return nil, errors.New("concurrency start start cannot equal concurrency end")
		}
		if c.cStep == 0 {
			return nil, errors.New("invalid concurrency step")
		}
		if c.cSchedule == ScheduleStep && c.cStepDuration == 0 {
			return nil, errors.New("invalid concurrency step duration")
		}
	}
	if c.loadSchedule == ScheduleLine {
		c.loadStepDuration = time.Second
	}
	if c.cSchedule == ScheduleLine {
		c.cStepDuration = time.Second
	}
	if c.skipFirst > 0 && int(c.skipFirst) > c.n {
		return nil, errors.New("you cannot skip more requests than those run")
	}
	creds, err := createClientTransportCredentials(
		c.skipVerify,
		c.cacert,
		c.cert,
		c.key,
		c.cname,
	)
	if err != nil {
		return nil, err
	}
	c.creds = creds
	return c, nil
}
func WithConfigFromFile(file string) Option {
	return func(o *RunConfig) error {
		var cfg Config
		err := LoadConfig(file, &cfg)
		if err != nil {
			return err
		}
		for _, option := range fromConfig(&cfg) {
			if err := option(o); err != nil {
				return err
			}
		}
		return nil
	}
}
func WithConfigFromReader(reader io.Reader) Option {
	return func(o *RunConfig) error {
		var cfg Config
		if err := json.NewDecoder(reader).Decode(&cfg); err != nil {
			return fmt.Errorf("unmarshal config: %w", err)
		}
		for _, option := range fromConfig(&cfg) {
			if err := option(o); err != nil {
				return err
			}
		}
		return nil
	}
}
func WithConfig(cfg *Config) Option {
	return func(o *RunConfig) error {
		if cfg.X > 0 {
			cfg.Z = cfg.X
		} else if cfg.Z > 0 {
			cfg.N = math.MaxInt32
		}
		for _, option := range fromConfig(cfg) {
			if err := option(o); err != nil {
				return err
			}
		}
		return nil
	}
}
func WithCertificate(cert, key string) Option {
	return func(o *RunConfig) error {
		cert = strings.TrimSpace(cert)
		key = strings.TrimSpace(key)
		o.cert = cert
		o.key = key
		return nil
	}
}
func WithServerNameOverride(cname string) Option {
	return func(o *RunConfig) error {
		o.cname = cname
		return nil
	}
}
func WithAuthority(authority string) Option {
	return func(o *RunConfig) error {
		o.authority = authority
		return nil
	}
}
func WithRootCertificate(cert string) Option {
	return func(o *RunConfig) error {
		cert = strings.TrimSpace(cert)
		o.cacert = cert
		return nil
	}
}
func WithInsecure(insec bool) Option {
	return func(o *RunConfig) error {
		o.insecure = insec
		return nil
	}
}
func WithSkipTLSVerify(skip bool) Option {
	return func(o *RunConfig) error {
		o.skipVerify = skip
		return nil
	}
}
func WithTotalRequests(n uint) Option {
	return func(o *RunConfig) error {
		o.n = int(n)
		return nil
	}
}
func WithConcurrency(c uint) Option {
	return func(o *RunConfig) error {
		o.c = int(c)
		return nil
	}
}
func WithRPS(v uint) Option {
	return func(o *RunConfig) error {
		o.rps = int(v)
		return nil
	}
}
func WithRunDuration(z time.Duration) Option {
	return func(o *RunConfig) error {
		o.z = z
		return nil
	}
}
func WithDurationStopAction(action string) Option {
	return func(o *RunConfig) error {
		action = strings.ToLower(action)
		if action == "close" || action == "wait" || action == "ignore" {
			o.zstop = action
		}
		return nil
	}
}
func WithTimeout(timeout time.Duration) Option {
	return func(o *RunConfig) error {
		o.timeout = timeout
		return nil
	}
}
func WithDialTimeout(dt time.Duration) Option {
	return func(o *RunConfig) error {
		o.dialTimeout = dt
		return nil
	}
}
func WithKeepalive(k time.Duration) Option {
	return func(o *RunConfig) error {
		o.keepaliveTime = k
		return nil
	}
}
func WithBinaryData(data []byte) Option {
	return func(o *RunConfig) error {
		o.data = data
		o.binary = true
		return nil
	}
}
func WithClientLoadBalancing(strategy string) Option {
	return func(o *RunConfig) error {
		o.lbStrategy = strategy
		return nil
	}
}
func WithBinaryDataFunc(data func(mtd *desc.MethodDescriptor, callData *CallData) []byte) Option {
	return func(o *RunConfig) error {
		o.dataFunc = data
		o.binary = true
		return nil
	}
}
func WithBinaryDataFromFile(path string) Option {
	return func(o *RunConfig) error {
		data, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		o.data = data
		o.binary = true
		return nil
	}
}
func WithDataFromJSON(data string) Option {
	return func(o *RunConfig) error {
		o.data = []byte(data)
		o.binary = false
		return nil
	}
}
func WithData(data interface{}) Option {
	return func(o *RunConfig) error {
		dataJSON, err := json.Marshal(data)
		if err != nil {
			return err
		}
		o.data = dataJSON
		o.binary = false
		return nil
	}
}
func WithDataFromReader(r io.Reader) Option {
	return func(o *RunConfig) error {
		data, err := ioutil.ReadAll(r)
		if err != nil {
			return err
		}
		o.data = data
		o.binary = false
		return nil
	}
}
func WithDataFromFile(path string) Option {
	return func(o *RunConfig) error {
		data, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		o.data = data
		o.binary = false
		return nil
	}
}
func WithMetadataFromJSON(md string) Option {
	return func(o *RunConfig) error {
		o.metadata = []byte(md)
		return nil
	}
}
func WithMetadata(md map[string]string) Option {
	return func(o *RunConfig) error {
		mdJSON, err := json.Marshal(md)
		if err != nil {
			return err
		}
		o.metadata = mdJSON
		return nil
	}
}
func WithMetadataFromFile(path string) Option {
	return func(o *RunConfig) error {
		mdJSON, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		o.metadata = mdJSON
		return nil
	}
}
func WithName(name string) Option {
	return func(o *RunConfig) error {
		name = strings.TrimSpace(name)
		if name != "" {
			o.name = name
		}
		return nil
	}
}
func WithTags(tags map[string]string) Option {
	return func(o *RunConfig) error {
		tagsJSON, err := json.Marshal(tags)
		if err != nil {
			return err
		}
		o.tags = tagsJSON
		return nil
	}
}
func WithCPUs(c uint) Option {
	return func(o *RunConfig) error {
		if c > 0 {
			o.cpus = int(c)
		}
		return nil
	}
}
func WithSkipFirst(c uint) Option {
	return func(o *RunConfig) error {
		if c > 0 {
			o.skipFirst = int(c)
		}
		return nil
	}
}
func WithCountErrors(v bool) Option {
	return func(o *RunConfig) error {
		o.countErrors = v
		return nil
	}
}
func WithProtoFile(proto string, importPaths []string) Option {
	return func(o *RunConfig) error {
		proto = strings.TrimSpace(proto)
		if proto != "" {
			if filepath.Ext(proto) != ".proto" {
				return errors.New("proto: must have .proto extension")
			}
			o.proto = proto
			dir := filepath.Dir(proto)
			if dir != "." {
				o.importPaths = append(o.importPaths, dir)
			}
			o.importPaths = append(o.importPaths, ".")
			if len(importPaths) > 0 {
				o.importPaths = append(o.importPaths, importPaths...)
			}
		}
		return nil
	}
}
func WithProtoset(protoset string) Option {
	return func(o *RunConfig) error {
		protoset = strings.TrimSpace(protoset)
		o.protoset = protoset
		return nil
	}
}
func WithProtosetBinary(b []byte) Option {
	return func(o *RunConfig) error {
		o.protosetBinary = b
		return nil
	}
}
func WithStreamInterval(d time.Duration) Option {
	return func(o *RunConfig) error {
		o.streamInterval = d
		return nil
	}
}
func WithStreamCallDuration(d time.Duration) Option {
	return func(o *RunConfig) error {
		o.streamCallDuration = d
		return nil
	}
}
func WithStreamCallCount(c uint) Option {
	return func(o *RunConfig) error {
		o.streamCallCount = c
		return nil
	}
}
func WithStreamDynamicMessages(v bool) Option {
	return func(o *RunConfig) error {
		o.streamDynamicMessages = v
		return nil
	}
}