Repository: bojand/ghz
Pull request #192 (2020-06-01 00:56:21)

Improve config file support and add tests


Patch:
diff --git a/cmd/ghz/main.go b/cmd/ghz/main.go
index 20c2d97..d162c43 100644
--- a/cmd/ghz/main.go
+++ b/cmd/ghz/main.go
@@ -372,7 +372,7 @@ func createConfigFromArgs(cfg *runner.Config) error {
 	cfg.DataPath = *dataPath
 	cfg.BinData = binaryData
 	cfg.BinDataPath = *binPath
-	cfg.Metadata = &metadata
+	cfg.Metadata = metadata
 	cfg.MetadataPath = *mdPath
 	cfg.SI = runner.Duration(*si)
 	cfg.Output = *output
@@ -383,8 +383,8 @@ func createConfigFromArgs(cfg *runner.Config) error {
 	cfg.KeepaliveTime = runner.Duration(*kt)
 	cfg.CPUs = *cpus
 	cfg.Name = *name
-	cfg.Tags = &tagsMap
-	cfg.ReflectMetadata = &rmdMap
+	cfg.Tags = tagsMap
+	cfg.ReflectMetadata = rmdMap
 	cfg.Debug = *debug
 	cfg.EnableCompression = *enableCompression
 
diff --git a/go.mod b/go.mod
index 11d9159..4660808 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/bojand/ghz
 go 1.13
 
 require (
+	cloud.google.com/go v0.46.3 // indirect
 	github.com/alecthomas/kingpin v1.3.8-0.20191105203113-8c96d1c22481
 	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
 	github.com/bojand/hri v1.1.0
@@ -25,6 +26,9 @@ require (
 	go.uber.org/multierr v1.3.0
 	go.uber.org/zap v1.13.0
 	golang.org/x/net v0.0.0-20191021144547-ec77196f6094
+	golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc // indirect
+	google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
 	google.golang.org/grpc v1.24.0
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
+	gopkg.in/yaml.v2 v2.2.4 // indirect
 )
diff --git a/go.sum b/go.sum
index 6409a9d..fb6de0c 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
 cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/alecthomas/kingpin v1.3.8-0.20191105203113-8c96d1c22481 h1:NXM4vkjHeFp3bbp0z/u0AdQRLg6b5LrPeFwgjVHUW58=
@@ -30,6 +40,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -50,23 +61,30 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jhump/protoreflect v1.5.0 h1:NgpVT+dX71c8hZnxHof2M7QDK7QtohIJ7DYycjnkyfc=
 github.com/jhump/protoreflect v1.5.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
@@ -139,6 +157,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
 github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
@@ -153,14 +173,26 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0F
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -171,11 +203,15 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
 golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -188,7 +224,12 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -197,31 +238,58 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
 google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
@@ -237,9 +305,13 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/runner/config.go b/runner/config.go
index 3c3ba1d..3bdcf6c 100644
--- a/runner/config.go
+++ b/runner/config.go
@@ -1,10 +1,12 @@
 package runner
 
 import (
-	"encoding/json"
 	"errors"
+	"path"
 	"strings"
 	"time"
+
+	"github.com/jinzhu/configor"
 )
 
 // Duration is our duration with TOML support
@@ -30,131 +32,67 @@ func (d Duration) String() string {
 	return time.Duration(d).String()
 }
 
-// Config for the run.
-type Config struct {
-	Proto             string             `json:"proto" toml:"proto" yaml:"proto"`
-	Protoset          string             `json:"protoset" toml:"protoset" yaml:"protoset"`
-	Call              string             `json:"call" toml:"call" yaml:"call"`
-	RootCert          string             `json:"cacert" toml:"cacert" yaml:"cacert"`
-	Cert              string             `json:"cert" toml:"cert" yaml:"cert"`
-	Key               string             `json:"key" toml:"key" yaml:"key"`
-	SkipTLSVerify     bool               `json:"skipTLS" toml:"skipTLS" yaml:"skipTLS"`
-	CName             string             `json:"cname" toml:"cname" yaml:"cname"`
-	Authority         string             `json:"authority" toml:"authority" yaml:"authority"`
-	Insecure          bool               `json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty"`
-	N                 uint               `json:"total" toml:"total" yaml:"total" default:"200"`
-	C                 uint               `json:"concurrency" toml:"concurrency" yaml:"concurrency" default:"50"`
-	Connections       uint               `json:"connections" toml:"connections" yaml:"connections" default:"1"`
-	QPS               uint               `json:"qps" toml:"qps" yaml:"qps"`
-	Z                 Duration           `json:"duration" toml:"duration" yaml:"duration"`
-	ZStop             string             `json:"duration-stop" toml:"duration-stop" yaml:"duration-stop" default:"close"`
-	X                 Duration           `json:"max-duration" toml:"max-duration" yaml:"max-duration"`
-	Timeout           Duration           `json:"timeout" toml:"timeout" yaml:"timeout" default:"20s"`
-	Data              interface{}        `json:"data,omitempty" toml:"data,omitempty" yaml:"data,omitempty"`
-	DataPath          string             `json:"data-file" toml:"data-file" yaml:"data-file"`
-	BinData           []byte             `json:"-" toml:"-" yaml:"-"`
-	BinDataPath       string             `json:"binary-file" toml:"binary-file" yaml:"binary-file"`
-	Metadata          *map[string]string `json:"metadata,omitempty" toml:"metadata,omitempty" yaml:"metadata,omitempty"`
-	MetadataPath      string             `json:"metadata-file" toml:"metadata-file" yaml:"metadata-file"`
-	SI                Duration           `json:"stream-interval" toml:"stream-interval" yaml:"stream-interval"`
-	Output            string             `json:"output" toml:"output" yaml:"output"`
-	Format            string             `json:"format" toml:"format" yaml:"format" default:"summary"`
-	DialTimeout       Duration           `json:"connect-timeout" toml:"connect-timeout" yaml:"connect-timeout" default:"10s"`
-	KeepaliveTime     Duration           `json:"keepalive" toml:"keepalive" yaml:"keepalive"`
-	CPUs              uint               `json:"cpus" toml:"cpus" yaml:"cpus"`
-	ImportPaths       []string           `json:"import-paths,omitempty" toml:"import-paths,omitempty" yaml:"import-paths,omitempty"`
-	Name              string             `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty"`
-	Tags              *map[string]string `json:"tags,omitempty" toml:"tags,omitempty" yaml:"tags,omitempty"`
-	ReflectMetadata   *map[string]string `json:"reflect-metadata,omitempty" toml:"reflect-metadata,omitempty" yaml:"reflect-metadata,omitempty"`
-	Debug             string             `json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty"`
-	Host              string             `json:"host" toml:"host" yaml:"host"`
-	EnableCompression bool               `json:"enable-compression,omitempty" toml:"enable-compression,omitempty" yaml:"enable-compression,omitempty"`
-}
-
-// UnmarshalJSON is our custom implementation to handle the Duration fields
-// and validate data
-func (c *Config) UnmarshalJSON(data []byte) error {
-	type Alias Config
-	aux := &struct {
-		Z       string `json:"duration"`
-		X       string `json:"max-duration"`
-		SI      string `json:"stream-interval"`
-		Timeout string `json:"timeout"`
-		*Alias
-	}{
-		Alias: (*Alias)(c),
+// UnmarshalJSON is our custom unmarshaller with JSON support
+func (d *Duration) UnmarshalJSON(text []byte) error {
+	// strValue := string(text)
+	first := text[0]
+	last := text[len(text)-1]
+	if first == '"' && last == '"' {
+		text = text[1 : len(text)-1]
 	}
-	if err := json.Unmarshal(data, &aux); err != nil {
+	dur, err := time.ParseDuration(string(text))
+	if err != nil {
 		return err
 	}
 
-	if aux.Data != nil {
-		err := checkData(aux.Data)
-		if err != nil {
-			return err
-		}
-	}
-
-	if aux.Z != "" {
-		zd, err := time.ParseDuration(aux.Z)
-		if err != nil {
-			return nil
-		}
-
-		c.Z = Duration(zd)
-	}
-
-	aux.ZStop = strings.ToLower(aux.ZStop)
-	if aux.ZStop != "close" && aux.ZStop != "ignore" && aux.ZStop != "wait" {
-		aux.ZStop = "close"
-	}
-
-	if aux.X != "" {
-		xd, err := time.ParseDuration(aux.X)
-		if err != nil {
-			return nil
-		}
-
-		c.X = Duration(xd)
-	}
-
-	if aux.SI != "" {
-		sid, err := time.ParseDuration(aux.SI)
-		if err != nil {
-			return nil
-		}
-
-		c.SI = Duration(sid)
-	}
-
-	if aux.Timeout != "" {
-		td, err := time.ParseDuration(aux.Timeout)
-		if err != nil {
-			return nil
-		}
-
-		c.Timeout = Duration(td)
-	}
-
+	*d = Duration(dur)
 	return nil
 }
 
-// MarshalJSON is our custom implementation to handle the Duration fields
-func (c Config) MarshalJSON() ([]byte, error) {
-	type Alias Config
-	return json.Marshal(&struct {
-		*Alias
-		Z       string `json:"duration"`
-		X       string `json:"max-duration"`
-		SI      string `json:"stream-interval"`
-		Timeout string `json:"timeout"`
-	}{
-		Alias:   (*Alias)(&c),
-		Z:       c.Z.String(),
-		X:       c.X.String(),
-		SI:      c.SI.String(),
-		Timeout: c.Timeout.String(),
-	})
+// MarshalJSON implements encoding JSONMarshaler
+func (d Duration) MarshalJSON() ([]byte, error) {
+	return []byte(time.Duration(d).String()), nil
+}
+
+// Config for the run.
+type Config struct {
+	Proto             string            `json:"proto" toml:"proto" yaml:"proto"`
+	Protoset          string            `json:"protoset" toml:"protoset" yaml:"protoset"`
+	Call              string            `json:"call" toml:"call" yaml:"call"`
+	RootCert          string            `json:"cacert" toml:"cacert" yaml:"cacert"`
+	Cert              string            `json:"cert" toml:"cert" yaml:"cert"`
+	Key               string            `json:"key" toml:"key" yaml:"key"`
+	SkipTLSVerify     bool              `json:"skipTLS" toml:"skipTLS" yaml:"skipTLS"`
+	CName             string            `json:"cname" toml:"cname" yaml:"cname"`
+	Authority         string            `json:"authority" toml:"authority" yaml:"authority"`
+	Insecure          bool              `json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty"`
+	N                 uint              `json:"total" toml:"total" yaml:"total" default:"200"`
+	C                 uint              `json:"concurrency" toml:"concurrency" yaml:"concurrency" default:"50"`
+	Connections       uint              `json:"connections" toml:"connections" yaml:"connections" default:"1"`
+	QPS               uint              `json:"qps" toml:"qps" yaml:"qps"`
+	Z                 Duration          `json:"duration" toml:"duration" yaml:"duration"`
+	ZStop             string            `json:"duration-stop" toml:"duration-stop" yaml:"duration-stop" default:"close"`
+	X                 Duration          `json:"max-duration" toml:"max-duration" yaml:"max-duration"`
+	Timeout           Duration          `json:"timeout" toml:"timeout" yaml:"timeout" default:"20s"`
+	Data              interface{}       `json:"data,omitempty" toml:"data,omitempty" yaml:"data,omitempty"`
+	DataPath          string            `json:"data-file" toml:"data-file" yaml:"data-file"`
+	BinData           []byte            `json:"-" toml:"-" yaml:"-"`
+	BinDataPath       string            `json:"binary-file" toml:"binary-file" yaml:"binary-file"`
+	Metadata          map[string]string `json:"metadata,omitempty" toml:"metadata,omitempty" yaml:"metadata,omitempty"`
+	MetadataPath      string            `json:"metadata-file" toml:"metadata-file" yaml:"metadata-file"`
+	SI                Duration          `json:"stream-interval" toml:"stream-interval" yaml:"stream-interval"`
+	Output            string            `json:"output" toml:"output" yaml:"output"`
+	Format            string            `json:"format" toml:"format" yaml:"format" default:"summary"`
+	DialTimeout       Duration          `json:"connect-timeout" toml:"connect-timeout" yaml:"connect-timeout" default:"10s"`
+	KeepaliveTime     Duration          `json:"keepalive" toml:"keepalive" yaml:"keepalive"`
+	CPUs              uint              `json:"cpus" toml:"cpus" yaml:"cpus"`
+	ImportPaths       []string          `json:"import-paths,omitempty" toml:"import-paths,omitempty" yaml:"import-paths,omitempty"`
+	Name              string            `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty"`
+	Tags              map[string]string `json:"tags,omitempty" toml:"tags,omitempty" yaml:"tags,omitempty"`
+	ReflectMetadata   map[string]string `json:"reflect-metadata,omitempty" toml:"reflect-metadata,omitempty" yaml:"reflect-metadata,omitempty"`
+	Debug             string            `json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty"`
+	Host              string            `json:"host" toml:"host" yaml:"host"`
+	EnableCompression bool              `json:"enable-compression,omitempty" toml:"enable-compression,omitempty" yaml:"enable-compression,omitempty"`
 }
 
 func checkData(data interface{}) error {
@@ -178,3 +116,43 @@ func checkData(data interface{}) error {
 
 	return nil
 }
+
+func loadConfig(p string, c *Config) error {
+	err := configor.Load(c, p)
+	if err != nil {
+		return err
+	}
+
+	if c.Data != nil {
+		ext := path.Ext(p)
+		if strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") {
+			objData, isObjData2 := c.Data.(map[interface{}]interface{})
+			if isObjData2 {
+				nd := make(map[string]interface{})
+				for k, v := range objData {
+					sk, isString := k.(string)
+					if !isString {
+						return errors.New("Data key must string")
+					}
+					if len(sk) > 0 {
+						nd[sk] = v
+					}
+				}
+
+				c.Data = nd
+			}
+		}
+
+		err := checkData(c.Data)
+		if err != nil {
+			return err
+		}
+	}
+
+	c.ZStop = strings.ToLower(c.ZStop)
+	if c.ZStop != "close" && c.ZStop != "ignore" && c.ZStop != "wait" {
+		c.ZStop = "close"
+	}
+
+	return nil
+}
diff --git a/runner/options.go b/runner/options.go
index 42d688d..d4b7465 100644
--- a/runner/options.go
+++ b/runner/options.go
@@ -59,7 +59,7 @@ type RunConfig struct {
 	data     []byte
 	binary   bool
 	metadata []byte
-	rmd      *map[string]string
+	rmd      map[string]string
 
 	// debug
 	hasLog bool
@@ -379,7 +379,7 @@ func WithMetadataFromJSON(md string) Option {
 // 	md["token"] = "foobar"
 // 	md["request-id"] = "123"
 // 	WithMetadata(&md)
-func WithMetadata(md *map[string]string) Option {
+func WithMetadata(md map[string]string) Option {
 	return func(o *RunConfig) error {
 		mdJSON, err := json.Marshal(md)
 		if err != nil {
@@ -425,7 +425,7 @@ func WithName(name string) Option {
 // 	tags["env"] = "staging"
 // 	tags["created by"] = "joe developer"
 // 	WithTags(&tags)
-func WithTags(tags *map[string]string) Option {
+func WithTags(tags map[string]string) Option {
 	return func(o *RunConfig) error {
 		tagsJSON, err := json.Marshal(tags)
 		if err != nil {
@@ -504,7 +504,7 @@ func WithStreamInterval(d time.Duration) Option {
 // 	md["token"] = "foobar"
 // 	md["request-id"] = "123"
 // 	WithReflectionMetadata(&md)
-func WithReflectionMetadata(md *map[string]string) Option {
+func WithReflectionMetadata(md map[string]string) Option {
 	return func(o *RunConfig) error {
 		o.rmd = md
 
diff --git a/runner/requester.go b/runner/requester.go
index 922f00c..9b023f9 100644
--- a/runner/requester.go
+++ b/runner/requester.go
@@ -104,8 +104,8 @@ func newRequester(c *RunConfig) (*Requester, error) {
 		ctx, _ := context.WithTimeout(context.Background(), c.dialTimeout)
 
 		md := make(metadata.MD)
-		if c.rmd != nil && len(*c.rmd) > 0 {
-			md = metadata.New(*c.rmd)
+		if c.rmd != nil && len(c.rmd) > 0 {
+			md = metadata.New(c.rmd)
 		}
 
 		refCtx := metadata.NewOutgoingContext(ctx, md)


Test patch:
diff --git a/protodesc/protodesc_test.go b/protodesc/protodesc_test.go
index 1f6f416..923b394 100644
--- a/protodesc/protodesc_test.go
+++ b/protodesc/protodesc_test.go
@@ -2,7 +2,6 @@ package protodesc
 
 import (
 	"context"
-	"fmt"
 	"testing"
 
 	"github.com/bojand/ghz/internal"
@@ -145,7 +144,6 @@ func TestProtodesc_GetMethodDescFromReflect(t *testing.T) {
 		refClient := grpcreflect.NewClient(refCtx, reflectpb.NewServerReflectionClient(conn))
 
 		mtd, err := GetMethodDescFromReflect("helloworld.Greeter.SayHello", refClient)
-		fmt.Println(mtd)
 		assert.NoError(t, err)
 		assert.NotNil(t, mtd)
 		assert.Equal(t, "SayHello", mtd.GetName())
diff --git a/runner/config_test.go b/runner/config_test.go
new file mode 100644
index 0000000..8c8a1b1
--- /dev/null
+++ b/runner/config_test.go
@@ -0,0 +1,105 @@
+package runner
+
+import (
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConfig_Load(t *testing.T) {
+	var tests = []struct {
+		name     string
+		expected *Config
+		ok       bool
+	}{
+		{
+			"invalid data",
+			&Config{},
+			false,
+		},
+		{
+			"invalid duration",
+			&Config{},
+			false,
+		},
+		{
+			"invalid max-duration",
+			&Config{},
+			false,
+		},
+		{
+			"invalid stream-interval",
+			&Config{},
+			false,
+		},
+		{
+			"invalid timeout",
+			&Config{},
+			false,
+		},
+		{
+			"valid",
+			&Config{
+				Insecure:    true,
+				ImportPaths: []string{"/home/user/pb/grpcbin"},
+				Proto:       "grpcbin.proto",
+				Call:        "grpcbin.GRPCBin.DummyUnary",
+				Host:        "127.0.0.1:9000",
+				Z:           Duration(20 * time.Second),
+				X:           Duration(60 * time.Second),
+				SI:          Duration(25 * time.Second),
+				Timeout:     Duration(30 * time.Second),
+				N:           200,
+				C:           50,
+				Connections: 1,
+				ZStop:       "close",
+				Data: map[string]interface{}{
+					"f_strings": []interface{}{"123", "456"},
+				},
+				Format:      "summary",
+				DialTimeout: Duration(10 * time.Second),
+			},
+			true,
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run("toml "+tt.name, func(t *testing.T) {
+			var actual Config
+			cfgPath := "../testdata/config/config" + strconv.Itoa(i) + ".toml"
+			err := loadConfig(cfgPath, &actual)
+			if tt.ok {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expected, &actual)
+			} else {
+				assert.Error(t, err)
+			}
+		})
+
+		t.Run("json "+tt.name, func(t *testing.T) {
+			var actual Config
+			cfgPath := "../testdata/config/config" + strconv.Itoa(i) + ".toml"
+			err := loadConfig(cfgPath, &actual)
+			if tt.ok {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expected, &actual)
+			} else {
+				assert.Error(t, err)
+			}
+		})
+
+		t.Run("yaml "+tt.name, func(t *testing.T) {
+			var actual Config
+			cfgPath := "../testdata/config/config" + strconv.Itoa(i) + ".yaml"
+			err := loadConfig(cfgPath, &actual)
+			if tt.ok {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expected, &actual)
+			} else {
+				assert.Error(t, err)
+			}
+		})
+	}
+}
diff --git a/runner/data_test.go b/runner/data_test.go
index 33592e2..7f9e31d 100644
--- a/runner/data_test.go
+++ b/runner/data_test.go
@@ -2,7 +2,6 @@ package runner
 
 import (
 	"encoding/json"
-	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -218,8 +217,6 @@ func TestData_createPayloads(t *testing.T) {
 
 		inputs, err := createPayloadsFromJSON(jsonData, mtdWrapped)
 		assert.NoError(t, err)
-		fmt.Println(err)
-		fmt.Println(inputs)
 		assert.NotNil(t, inputs)
 		assert.Len(t, inputs, 1)
 		assert.EqualValues(t, "foo", inputs[0].GetFieldByName("value"))
diff --git a/runner/options_test.go b/runner/options_test.go
index 633fb90..5ecd602 100644
--- a/runner/options_test.go
+++ b/runner/options_test.go
@@ -190,9 +190,9 @@ func TestRunConfig_newRunConfig(t *testing.T) {
 			WithName("asdf"),
 			WithCPUs(4),
 			WithData(d),
-			WithMetadata(&md),
-			WithTags(&tags),
-			WithReflectionMetadata(&rmd),
+			WithMetadata(md),
+			WithTags(tags),
+			WithReflectionMetadata(rmd),
 		)
 
 		assert.NoError(t, err)
@@ -219,7 +219,7 @@ func TestRunConfig_newRunConfig(t *testing.T) {
 		assert.Equal(t, "", string(c.protoset))
 		assert.Equal(t, []string{"testdata", "."}, c.importPaths)
 		assert.NotNil(t, c.creds)
-		assert.Equal(t, map[string]string{"auth": "bizbaz"}, *c.rmd)
+		assert.Equal(t, map[string]string{"auth": "bizbaz"}, c.rmd)
 		assert.Equal(t, c.enableCompression, false)
 	})
 
diff --git a/testdata/config/config0.json b/testdata/config/config0.json
new file mode 100644
index 0000000..2bace5e
--- /dev/null
+++ b/testdata/config/config0.json
@@ -0,0 +1,15 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "connections": 3,
+    "keepalive": 0,
+    "total": 1000,
+    "concurrency": 1,
+    "qps": 0,
+    "host": "127.0.0.1:9000",
+    "data": "foo"
+}
\ No newline at end of file
diff --git a/testdata/config/config0.toml b/testdata/config/config0.toml
new file mode 100644
index 0000000..98707df
--- /dev/null
+++ b/testdata/config/config0.toml
@@ -0,0 +1,23 @@
+insecure = true
+import-paths = ["/home/user/pb/grpcbin"]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+# Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1.
+connections = 3
+# Keepalive time duration. Only used if present and above 0.
+keepalive = 0
+# Number of requests to run. Default is 200.
+total = 1000
+# Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50.
+concurrency = 1
+# Rate limit, in queries per second (QPS). Default is no rate limit.
+qps = 0
+host = "127.0.0.1:9000"
+data="""
+{
+  "f_strings": [
+    "1234567890",
+    "1234567890",
+  ]
+}
+"""
\ No newline at end of file
diff --git a/testdata/config/config0.yaml b/testdata/config/config0.yaml
new file mode 100644
index 0000000..d1993c3
--- /dev/null
+++ b/testdata/config/config0.yaml
@@ -0,0 +1,12 @@
+insecure: true
+import-paths:
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+connections: 3
+keepalive: 0
+total: 1000
+concurrency: 1
+qps: 0
+host: 127.0.0.1:9000
+data: foo
diff --git a/testdata/config/config1.json b/testdata/config/config1.json
new file mode 100644
index 0000000..129ca1e
--- /dev/null
+++ b/testdata/config/config1.json
@@ -0,0 +1,16 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "host": "127.0.0.1:9000",
+    "duration":"asdf",
+    "data": {
+        "f_strings": [
+            "1234567890",
+            "1234567890"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/testdata/config/config1.toml b/testdata/config/config1.toml
new file mode 100644
index 0000000..da6fa31
--- /dev/null
+++ b/testdata/config/config1.toml
@@ -0,0 +1,14 @@
+insecure = true
+import-paths = [
+  "/home/user/pb/grpcbin"
+]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+host = "127.0.0.1:9000"
+duration = "asdf"
+
+[data]
+f_strings = [
+  "1234567890",
+  "1234567890"
+]
\ No newline at end of file
diff --git a/testdata/config/config1.yaml b/testdata/config/config1.yaml
new file mode 100644
index 0000000..34c7baa
--- /dev/null
+++ b/testdata/config/config1.yaml
@@ -0,0 +1,11 @@
+insecure: true
+"import-paths":
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+host: 127.0.0.1:9000
+duration: asdf
+data:
+  f_strings:
+  - '1234567890'
+  - '1234567890'
diff --git a/testdata/config/config2.json b/testdata/config/config2.json
new file mode 100644
index 0000000..9d88282
--- /dev/null
+++ b/testdata/config/config2.json
@@ -0,0 +1,16 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "host": "127.0.0.1:9000",
+    "max-duration":"asdf",
+    "data": {
+        "f_strings": [
+            "1234567890",
+            "1234567890"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/testdata/config/config2.toml b/testdata/config/config2.toml
new file mode 100644
index 0000000..ba76ae2
--- /dev/null
+++ b/testdata/config/config2.toml
@@ -0,0 +1,14 @@
+insecure = true
+import-paths = [
+  "/home/user/pb/grpcbin"
+]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+host = "127.0.0.1:9000"
+max-duration = "asdf"
+
+[data]
+f_strings = [
+  "1234567890",
+  "1234567890"
+]
\ No newline at end of file
diff --git a/testdata/config/config2.yaml b/testdata/config/config2.yaml
new file mode 100644
index 0000000..d2a81c1
--- /dev/null
+++ b/testdata/config/config2.yaml
@@ -0,0 +1,12 @@
+---
+insecure: true
+import-paths:
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+host: 127.0.0.1:9000
+max-duration: asdf
+data:
+  f_strings:
+  - '1234567890'
+  - '1234567890'
diff --git a/testdata/config/config3.json b/testdata/config/config3.json
new file mode 100644
index 0000000..53ef2b8
--- /dev/null
+++ b/testdata/config/config3.json
@@ -0,0 +1,16 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "host": "127.0.0.1:9000",
+    "stream-interval":"foo",
+    "data": {
+        "f_strings": [
+            "1234567890",
+            "1234567890"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/testdata/config/config3.toml b/testdata/config/config3.toml
new file mode 100644
index 0000000..9bdc4e1
--- /dev/null
+++ b/testdata/config/config3.toml
@@ -0,0 +1,14 @@
+insecure = true
+import-paths = [
+  "/home/user/pb/grpcbin"
+]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+host = "127.0.0.1:9000"
+stream-interval = "asdf"
+
+[data]
+f_strings = [
+  "1234567890",
+  "1234567890"
+]
\ No newline at end of file
diff --git a/testdata/config/config3.yaml b/testdata/config/config3.yaml
new file mode 100644
index 0000000..6a16984
--- /dev/null
+++ b/testdata/config/config3.yaml
@@ -0,0 +1,12 @@
+---
+insecure: true
+import-paths:
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+host: 127.0.0.1:9000
+stream-interval: foo
+data:
+  f_strings:
+  - '1234567890'
+  - '1234567890'
diff --git a/testdata/config/config4.json b/testdata/config/config4.json
new file mode 100644
index 0000000..37ce8f9
--- /dev/null
+++ b/testdata/config/config4.json
@@ -0,0 +1,16 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "host": "127.0.0.1:9000",
+    "timeout":"foo",
+    "data": {
+        "f_strings": [
+            "1234567890",
+            "1234567890"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/testdata/config/config4.toml b/testdata/config/config4.toml
new file mode 100644
index 0000000..0bd0b67
--- /dev/null
+++ b/testdata/config/config4.toml
@@ -0,0 +1,14 @@
+insecure = true
+import-paths = [
+  "/home/user/pb/grpcbin"
+]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+host = "127.0.0.1:9000"
+timeout = "asdf"
+
+[data]
+f_strings = [
+  "1234567890",
+  "1234567890"
+]
\ No newline at end of file
diff --git a/testdata/config/config4.yaml b/testdata/config/config4.yaml
new file mode 100644
index 0000000..0937c0d
--- /dev/null
+++ b/testdata/config/config4.yaml
@@ -0,0 +1,12 @@
+---
+insecure: true
+import-paths:
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+host: 127.0.0.1:9000
+timeout: foo
+data:
+  f_strings:
+  - '1234567890'
+  - '1234567890'
diff --git a/testdata/config/config5.json b/testdata/config/config5.json
new file mode 100644
index 0000000..960c6c0
--- /dev/null
+++ b/testdata/config/config5.json
@@ -0,0 +1,19 @@
+{
+    "insecure": true,
+    "import-paths": [
+        "/home/user/pb/grpcbin"
+    ],
+    "proto": "grpcbin.proto",
+    "call": "grpcbin.GRPCBin.DummyUnary",
+    "host": "127.0.0.1:9000",
+    "duration":"20s",
+    "max-duration":"60s",
+    "stream-interval":"25s",
+    "timeout":"30s",
+    "data": {
+        "f_strings": [
+            "123",
+            "456"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/testdata/config/config5.toml b/testdata/config/config5.toml
new file mode 100644
index 0000000..8d5c474
--- /dev/null
+++ b/testdata/config/config5.toml
@@ -0,0 +1,17 @@
+insecure = true
+import-paths = [
+  "/home/user/pb/grpcbin"
+]
+proto = "grpcbin.proto"
+call = "grpcbin.GRPCBin.DummyUnary"
+host = "127.0.0.1:9000"
+duration = "20s"
+max-duration = "60s"
+stream-interval = "25s"
+timeout = "30s"
+
+[data]
+f_strings = [
+  "123",
+  "456"
+]
\ No newline at end of file
diff --git a/testdata/config/config5.yaml b/testdata/config/config5.yaml
new file mode 100644
index 0000000..96a2c5c
--- /dev/null
+++ b/testdata/config/config5.yaml
@@ -0,0 +1,15 @@
+---
+insecure: true
+import-paths:
+- "/home/user/pb/grpcbin"
+proto: grpcbin.proto
+call: grpcbin.GRPCBin.DummyUnary
+host: 127.0.0.1:9000
+duration: 20s
+max-duration: 60s
+stream-interval: 25s
+timeout: 30s
+data:
+  f_strings:
+  - '123'
+  - '456'