Repository: istio/fortio
Pull request #152 (2018-02-22 06:00:11)

grpc support for UI
- adds option to run grpc load test from the UI
- #TODO add more options for configuring the grpc run
- this PR may need some refactoring, but sending this out for feedback and course-correction

* * *

@ldemailly can you review? w.r.t #146 
thanks a lot for creating this tool!
it has proven quite useful in quick evaluation of grpc services.



googlebot: Thanks for your pull request.  It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed, please reply here (e.g. `I signed it!`) and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address on your commit.  Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot. The email used to register you as an authorized contributor must be the email used for the Git commit.
- In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again. If the bot doesn't comment, it means it doesn't think anything has changed.

<!-- need_sender_cla -->
sayedasadali: @ldemailly thank you for the pointers. I refactored the shared code and handled the default case.

google cla: I signed it!
ldemailly: can you put just
I signed it!
nothing else in 1 comment (the cla bot is picky)
codecov[bot]: # [Codecov](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=h1) Report
> Merging [#152](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=desc) into [master](https://codecov.io/gh/istio/fortio/commit/eb049a0ca0119eca8ce0a39f482e675f10c3518f?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/istio/fortio/pull/152/graphs/tree.svg?src=pr&width=650&token=6HLno2jpvU&height=150)](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #152   +/-   ##
=====================================
  Coverage      88%    88%           
=====================================
  Files           7      7           
  Lines        1500   1500           
=====================================
  Hits         1320   1320           
  Misses        120    120           
  Partials       60     60
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=footer). Last update [eb049a0...1b50718](https://codecov.io/gh/istio/fortio/pull/152?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

sayedasadali: I signed it!
sayedasadali: I am trying to work out why the bot doesn't seem to pick up the corporate CLA. will ping once I have addressed that
sayedasadali: I signed it
googlebot: CLAs look good, thanks!

<!-- ok -->
danehans: I am trying to test the PR by doing a grpc run through the UI. From the UI, I change URL to `http://localhost:8079` and select the grpc radio button. I get the following Error:
```
20:17:29 I uihandler.go:437> UI: GET /fortio/?labels=Fortio&url=http%3A%2F%2Flocalhost%3A8079&qps=1000&t=3s&n=&c=8&p=50%2C+75%2C+90%2C+99%2C+99.9&r=0.0001&H=User-Agent%3A+istio%2Ffortio-0.7.2-pre&H=&H=&runner=grpc&save=on&load=Start HTTP/1.1 172.17.0.1:53864 ()
20:17:29 V uihandler.go:441> Header Connection: keep-alive
20:17:29 V uihandler.go:441> Header Accept-Encoding: gzip, deflate, br
20:17:29 V uihandler.go:441> Header Accept-Language: en-US,en;q=0.9
20:17:29 V uihandler.go:441> Header Upgrade-Insecure-Requests: 1
20:17:29 V uihandler.go:441> Header User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
20:17:29 V uihandler.go:441> Header Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
20:17:29 V uihandler.go:441> Header Referer: http://localhost:8080/fortio/
20:17:29 I uihandler.go:152> Starting grpc load request from 172.17.0.1:53864 for http://localhost:8079
20:17:29 V stats.go:491> Will use [50 75 90 99 99.9] for percentiles
20:17:29 I uihandler.go:207> New run id 2
20:17:29 V http.go:95> URLSchemeCheck &{URL:http://localhost:8079 NumConnections:1 Compression:false DisableFastClient:false HTTP10:false DisableKeepAlive:false AllowHalfClose:false Insecure:false initDone:true https:false extraHeaders:map[User-Agent:[istio/fortio-0.7.2-pre]] hostOverride: HTTPReqTimeOut:15s}
20:17:29 V uihandler.go:278> adding header User-Agent: istio/fortio-0.7.2-pre
20:17:29 V http.go:175> Setting regular extra header User-Agent: istio/fortio-0.7.2-pre
20:17:29 D http.go:177> headers now map[User-Agent:[istio/fortio-0.7.2-pre]]
20:17:29 V http.go:175> Setting regular extra header X-On-Behalf-Of: 172.17.0.1:53864
20:17:29 D http.go:177> headers now map[User-Agent:[istio/fortio-0.7.2-pre] X-On-Behalf-Of:[172.17.0.1:53864]]
20:17:29 I grpcrunner.go:89> Starting grpc test for http://localhost:8079 with 8 threads at 1000.0 qps
20:17:29 V periodic.go:210> WATCHER 1 First outstanding run starting, catching signal
20:17:29 V periodic.go:216> WATCHER 1 starting new watcher for signal! chan  g 0xc42033c960 r 0xc4202d8ba0 (14)
20:17:29 E grpcrunner.go:112> Error in first grpc health check call for http://localhost:8079 rpc error: code = Unavailable desc = all SubConns are in TransientFailure
20:17:29 V periodic.go:253> Abort called for 0xc4202d2880 &{Runners:[0xc420304580 <nil> <nil> <nil> <nil> <nil> <nil> <nil>] QPS:1000 Duration:3s NumThreads:8 Percentiles:[50 75 90 99 99.9] Resolution:0.0001 Out:0xc4202d17c0 Labels:Fortio Stop:0xc4202d7b30 Exactly:0}
20:17:29 V periodic.go:81> Closing 0xc4202d8ba0
20:17:29 E uihandler.go:310> Init error {Runners:[0xc420304580 <nil> <nil> <nil> <nil> <nil> <nil> <nil>] QPS:1000 Duration:3s NumThreads:8 Percentiles:[50 75 90 99 99.9] Resolution:0.0001 Out:0xc4202d17c0 Labels:Fortio Stop:0xc4202d7b30 Exactly:0} : rpc error: code = Unavailable desc = all SubConns are in TransientFailure
20:17:29 V periodic.go:231> WATCHER 1 r.Stop readable
20:17:29 V periodic.go:234> WATCHER 1 End of go routine
20:17:29 V periodic.go:238> WATCHER 1 Last watcher: resetting signal handler
```

However, I am able to `grpcping` the same Fortio instance:
```
$ docker run --name client --rm istio/fortio:webtest grpcping 172.17.0.2:8079
20:18:56 I pingsrv.go:119> Ping RTT 1152190 (avg of 1427595, 969342, 1059635 ns) clock skew 2248
Clock skew histogram usec : count 1 avg 2.248 +/- 0 min 2.248 max 2.248 sum 2.248
# range, mid point, percentile, count
>= 2.248 <= 2.248 , 2.248 , 100.00, 1
# target 50% 2.248
RTT histogram usec : count 3 avg 1152.1907 +/- 198.2 min 969.342 max 1427.595 sum 3456.572
# range, mid point, percentile, count
>= 969.342 <= 1000 , 984.671 , 33.33, 1
> 1000 <= 1200 , 1100 , 66.67, 1
> 1400 <= 1427.6 , 1413.8 , 100.00, 1
# target 50% 1100
```
sayedasadali: @danehans can you try without the http prefix?
eg localhost:8079

i am not entirely sure if the http prefix should be honored for grpc calls?
ldemailly: we could be nice and strip the http/https prefix for grpc
and I suppose even nicer would be to have https prefix imply --grpc-secure

speaking of which can you add a checkbox for the Secure option of grpc ?

ldemailly: @danehans the error is definitely obscure, so good find!
sayedasadali: added a grpc secure option

not totally convinced about the automatic translation of http/https to equivalent grpc secure/insecure mode. 
should we fail fast or build tolerations into the system?
danehans: I removed `http://` from the url and it worked. __Note:__ After the run completes, the diagram adds the `http://` in front of `localhost:8079`.
danehans: Here is a screenshot of the diagram showing http://localhost:8079"

![screen shot 2018-02-23 at 3 47 44 pm](https://user-images.githubusercontent.com/1878195/36622224-efdf8fde-18b0-11e8-97a5-b96c5790f292.png)

sayedasadali: tl:dr; fixing the title will require a larger refactoring of RunnerResults and its derived structs

the value for the diagram's title is determined [here](https://github.com/istio/fortio/blob/master/ui/static/js/fortio_chart.js#L90)

the GRPCRunnerResults do not contain a URL json field, instead they contain Destination. However, Destination isn't currently written out to the result file for a grpc result save so the value for the title should show up as undefined. I am not sure how the `http://localhost:8079` value got populated.
ldemailly: fail fast is ok I guess - the message is obscure and delayed, I would expect the Dial to fail because http://x isn't a valid hostname
sayedasadali: understood. do you want the validation to be added as part of this PR?
ldemailly: the title logic is here: https://github.com/istio/fortio/blob/master/ui/static/js/fortio_chart.js#L90
ldemailly: yeah let's just add something in fgrpc/ Dial() to bail early with a nice message (though maybe just dropping http:// and https:// again would be nicer

it's like when a compiler tells you "you forgot a ; on line 23" it would be nice to just assume it's there and continue with a warning instead (for compilers you can make the case it's ok to abort early to avoid doing completely wrong guesses but in this case it's pretty clear what to guess)

ldemailly: pro tip:

please never force push a pr once reviewing has started

do a pull instead of a merge/rebase to be in sync

sayedasadali: sorry about the inconvenience with the force pushes. i guess i went overboard with trying to keep commit history clean
ldemailly: it gets squashed at the end on master so the PR can have 30 commits it's no problems


Patch:
diff --git a/fgrpc/grpcrunner.go b/fgrpc/grpcrunner.go
index 76c6193..bb151f1 100644
--- a/fgrpc/grpcrunner.go
+++ b/fgrpc/grpcrunner.go
@@ -26,6 +26,8 @@ import (
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/health/grpc_health_v1"
 
+	"strings"
+
 	"istio.io/fortio/fnet"
 	"istio.io/fortio/log"
 	"istio.io/fortio/periodic"
@@ -34,15 +36,18 @@ import (
 const (
 	// DefaultGRPCPort is the Fortio gRPC server default port number.
 	DefaultGRPCPort = "8079"
+	prefixHTTP      = "http://"
+	prefixHTTPS     = "https://"
 )
 
 // Dial dials grpc either using insecure or using default tls setup.
 // TODO: option to specify certs.
 func Dial(serverAddr string, tls bool) (conn *grpc.ClientConn, err error) {
 	opts := grpc.WithInsecure()
-	if tls {
+	if tls || (strings.HasPrefix(serverAddr, "https://")) {
 		opts = grpc.WithTransportCredentials(credentials.NewTLS(nil))
 	}
+	serverAddr = grpcDestination(serverAddr)
 	conn, err = grpc.Dial(serverAddr, opts)
 	if err != nil {
 		log.Errf("failed to conect to %s with tls %v: %v", serverAddr, tls, err)
@@ -94,6 +99,7 @@ func RunGRPCTest(o *GRPCRunnerOptions) (*GRPCRunnerResults, error) {
 		RetCodes: make(map[grpc_health_v1.HealthCheckResponse_ServingStatus]int64),
 	}
 	grpcstate := make([]GRPCRunnerResults, numThreads)
+
 	for i := 0; i < numThreads; i++ {
 		r.Options().Runners[i] = &grpcstate[i]
 		conn, err := Dial(o.Destination, o.Secure)
@@ -156,9 +162,18 @@ func RunGRPCTest(o *GRPCRunnerOptions) (*GRPCRunnerResults, error) {
 	return &total, nil
 }
 
-// GRPCDestination parses dest and returns dest:port based on dest type
+// grpcDestination parses dest and returns dest:port based on dest type
 // being a hostname, IP address or hostname/ip:port pair.
-func GRPCDestination(dest string) string {
+func grpcDestination(dest string) string {
+	// strip any unintentional http/https scheme prefixes from destination
+	if strings.HasPrefix(dest, prefixHTTP) {
+		dest = strings.Replace(dest, prefixHTTP, "", 1)
+		log.Infof("stripping http scheme. grpc destination: %v", dest)
+	} else if strings.HasPrefix(dest, prefixHTTPS) {
+		dest = strings.Replace(dest, prefixHTTPS, "", 1)
+		log.Infof("stripping https scheme. grpc destination: %v", dest)
+	}
+
 	if _, _, err := net.SplitHostPort(dest); err == nil {
 		return dest
 	}
diff --git a/fortio_main.go b/fortio_main.go
index 6360b5b..3afdb03 100644
--- a/fortio_main.go
+++ b/fortio_main.go
@@ -260,6 +260,7 @@ func fortioLoad(justCurl bool, percList []float64) {
 			RunnerOptions: ro,
 			Destination:   url,
 			Secure:        *grpcSecureFlag,
+			Service:       *healthSvcFlag,
 		}
 		res, err = fgrpc.RunGRPCTest(&o)
 	} else {
diff --git a/pingsrv.go b/pingsrv.go
index 9cf2236..91490d1 100644
--- a/pingsrv.go
+++ b/pingsrv.go
@@ -153,15 +153,14 @@ func grpcClient() {
 		usage("Error: fortio grpcping needs host argument in the form of host, host:port or ip:port")
 	}
 	host := flag.Arg(0)
-	dest := fgrpc.GRPCDestination(host)
 	count := int(*exactlyFlag)
 	if count <= 0 {
 		count = 1
 	}
 	tls := *grpcSecureFlag
 	if *doHealthFlag {
-		grpcHealthCheck(dest, tls, *healthSvcFlag, count)
+		grpcHealthCheck(host, tls, *healthSvcFlag, count)
 	} else {
-		pingClientCall(dest, tls, count, *payloadFlag)
+		pingClientCall(host, tls, count, *payloadFlag)
 	}
 }
diff --git a/ui/templates/main.html b/ui/templates/main.html
index 9d9ce2d..161f32a 100644
--- a/ui/templates/main.html
+++ b/ui/templates/main.html
@@ -52,6 +52,9 @@ Headers: <br />
 {{end}}{{end}} <!-- 2 extra header lines, TODO: add a JS 'more headers' button -->
 <input type="text" name="H" size=40 value="" /> <br />
 <input type="text" name="H" size=40 value="" /> <br />
+http: <input type="radio" name="runner" value="http" checked/>,
+grpc: <input type="radio" name="runner" value="grpc"/><br/>
+Use secure transport (tls) for GRPC <input type="checkbox" name="grpc-secure"/><br/>
 JSON output: <input type="checkbox" name="json" />,
 Save output: <input type="checkbox" name="save" checked /> <br />
 <input type="submit" name="load" value="Start"/>
diff --git a/ui/uihandler.go b/ui/uihandler.go
index 89a26a9..3342697 100644
--- a/ui/uihandler.go
+++ b/ui/uihandler.go
@@ -37,6 +37,7 @@ import (
 	"sync"
 	"time"
 
+	"istio.io/fortio/fgrpc"
 	"istio.io/fortio/fhttp"
 	"istio.io/fortio/fnet"
 	"istio.io/fortio/log"
@@ -80,6 +81,7 @@ var (
 const (
 	fetchURI    = "fetch/"
 	faviconPath = "/favicon.ico"
+	modegrpc    = "grpc"
 )
 
 // Gets the resources directory from one of 3 sources:
@@ -140,13 +142,14 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 	DoSave := (r.FormValue("save") == "on")
 	url := r.FormValue("url")
 	runid := int64(0)
+	runner := r.FormValue("runner")
 	if r.FormValue("load") == "Start" {
 		mode = run
 		if r.FormValue("json") == "on" {
 			JSONOnly = true
-			log.Infof("Starting JSON only load request from %v for %s", r.RemoteAddr, url)
+			log.Infof("Starting JSON only %s load request from %v for %s", runner, r.RemoteAddr, url)
 		} else {
-			log.Infof("Starting load request from %v for %s", r.RemoteAddr, url)
+			log.Infof("Starting %s load request from %v for %s", runner, r.RemoteAddr, url)
 		}
 	} else {
 		if r.FormValue("stop") == "Stop" {
@@ -161,6 +164,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 	percList, _ := stats.ParsePercentiles(r.FormValue("p"))   // nolint: gas
 	qps, _ := strconv.ParseFloat(r.FormValue("qps"), 64)      // nolint: gas
 	durStr := r.FormValue("t")
+	grpcSecure := (r.FormValue("grpc-secure") == "on")
 	var dur time.Duration
 	if durStr == "on" || ((len(r.Form["t"]) > 1) && r.Form["t"][1] == "on") {
 		dur = -1
@@ -184,7 +188,6 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 	if strings.TrimSpace(url) == "" {
 		url = "http://url.needed" // just because url validation doesn't like empty urls
 	}
-	opts := fhttp.NewHTTPOptions(url)
 	ro := periodic.RunnerOptions{
 		QPS:         qps,
 		Duration:    dur,
@@ -204,6 +207,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 		uiRunMapMutex.Unlock()
 		log.Infof("New run id %d", runid)
 	}
+	httpopts := fhttp.NewHTTPOptions(url)
 	if !JSONOnly {
 		// Normal html mode
 		if mainTemplate == nil {
@@ -237,7 +241,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 			URLHostPort                 string
 			DoStop                      bool
 			DoLoad                      bool
-		}{r, opts.GetHeaders(), version.Short(), logoPath, debugPath, chartJSPath,
+		}{r, httpopts.GetHeaders(), version.Short(), logoPath, debugPath, chartJSPath,
 			startTime.Format(time.ANSIC), url, labels, runid,
 			fhttp.RoundDuration(time.Since(startTime)), durSeconds, urlHostPort, mode == stop, mode == run})
 		if err != nil {
@@ -275,29 +279,37 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 			log.LogVf("adding header %v", header)
 			if firstHeader {
 				// If there is at least 1 non empty H passed, reset the header list
-				opts.ResetHeaders()
+				httpopts.ResetHeaders()
 				firstHeader = false
 			}
-			err := opts.AddAndValidateExtraHeader(header)
+			err := httpopts.AddAndValidateExtraHeader(header)
 			if err != nil {
 				log.Errf("Error adding custom headers: %v", err)
 			}
 		}
-		onBehalfOf(opts, r)
-		o := fhttp.HTTPRunnerOptions{
-			RunnerOptions:      ro,
-			HTTPOptions:        *opts,
-			AllowInitialErrors: true,
-		}
+		onBehalfOf(httpopts, r)
 		if !JSONOnly {
 			flusher.Flush()
 		}
-		res, err := fhttp.RunHTTPTest(&o)
-		uiRunMapMutex.Lock()
-		delete(runs, runid)
-		uiRunMapMutex.Unlock()
+		var res periodic.HasRunnerResult
+		var err error
+		if runner == modegrpc {
+			o := fgrpc.GRPCRunnerOptions{
+				RunnerOptions: ro,
+				Destination:   url,
+				Secure:        grpcSecure,
+			}
+			res, err = fgrpc.RunGRPCTest(&o)
+		} else {
+			o := fhttp.HTTPRunnerOptions{
+				HTTPOptions:        *httpopts,
+				RunnerOptions:      ro,
+				AllowInitialErrors: true,
+			}
+			res, err = fhttp.RunHTTPTest(&o)
+		}
 		if err != nil {
-			log.Errf("Init error %+v : %v", o, err)
+			log.Errf("Init error for %s mode with url %s and options %+v : %v", runner, url, ro, err)
 			// nolint: errcheck,gas
 			w.Write([]byte(fmt.Sprintf(
 				"Aborting because %s\n</pre><script>document.getElementById('running').style.display = 'none';</script></body></html>\n",
@@ -310,7 +322,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 		}
 		savedAs := ""
 		if DoSave {
-			savedAs = SaveJSON(res.ID(), json)
+			savedAs = SaveJSON(res.Result().ID(), json)
 		}
 		if JSONOnly {
 			w.Header().Set("Content-Type", "application/json")
@@ -326,9 +338,9 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 		}
 		// nolint: errcheck, gas
 		w.Write([]byte(fmt.Sprintf("All done %d calls %.3f ms avg, %.1f qps\n</pre>\n<script>\n",
-			res.DurationHistogram.Count,
-			1000.*res.DurationHistogram.Avg,
-			res.ActualQPS)))
+			res.Result().DurationHistogram.Count,
+			1000.*res.Result().DurationHistogram.Avg,
+			res.Result().ActualQPS)))
 		ResultToJsData(w, json)
 		w.Write([]byte("</script></body></html>\n")) // nolint: gas
 	}


Test patch:
diff --git a/Webtest.sh b/Webtest.sh
index 797c0da..46d5d9e 100755
--- a/Webtest.sh
+++ b/Webtest.sh
@@ -21,8 +21,10 @@ CURL="docker exec fortio_server /usr/local/bin/fortio load -curl -loglevel $LOGL
 $CURL https://istio.io/robots.txt
 # Check that browse doesn't 404s
 $CURL ${BASE_FORTIO}browse
-# Check we can connect, and run a QPS test against ourselves through fetch
+# Check we can connect, and run a http QPS test against ourselves through fetch
 $CURL "${BASE_FORTIO}fetch/localhost:8080$FORTIO_UI_PREFIX?url=http://localhost:8080/debug&load=Start&qps=-1&json=on" | grep ActualQPS
+# Check we can connect, and run a grpc QPS test against ourselves through fetch
+$CURL "${BASE_FORTIO}fetch/localhost:8080$FORTIO_UI_PREFIX?url=localhost:8079&load=Start&qps=-1&json=on&n=100&runner=grpc" | grep '"1": 100'
 # Check we get the logo (need to remove the CR from raw headers)
 VERSION=$(docker exec fortio_server /usr/local/bin/fortio version -s)
 LOGO_TYPE=$($CURL "${BASE_FORTIO}${VERSION}/static/img/logo.svg" | grep -i Content-Type: | tr -d '\r'| awk '{print $2}')
@@ -38,10 +40,12 @@ if [ "$SIZE" -lt 50000 ]; then
 fi
 # Check the main page
 $CURL $BASE_FORTIO
-# Do a small load using std client
+# Do a small http load using std client
 docker exec fortio_server /usr/local/bin/fortio load -stdclient -qps 1 -t 2s -c 1 https://www.google.com/
 # and with normal and with custom headers
 docker exec fortio_server /usr/local/bin/fortio load -H Foo:Bar -H Blah:Blah -qps 1 -t 2s -c 2 http://www.google.com/
 # Do a grpcping
 docker exec fortio_server /usr/local/bin/fortio grpcping localhost
+# Do a grpcping to a scheme-prefixed destination
+docker exec fortio_server /usr/local/bin/fortio grpcping https://fortio.istio.io:443
 # TODO: check report mode and pprof
diff --git a/fgrpc/grpcrunner_test.go b/fgrpc/grpcrunner_test.go
index d8b1472..d6c16f7 100644
--- a/fgrpc/grpcrunner_test.go
+++ b/fgrpc/grpcrunner_test.go
@@ -95,6 +95,16 @@ func TestGRPCDestination(t *testing.T) {
 			"localhost:1234",
 			"localhost:1234",
 		},
+		{
+			"http hostname and port",
+			"http://localhost:1234",
+			"localhost:1234",
+		},
+		{
+			"https hostname and port",
+			"https://localhost:1234",
+			"localhost:1234",
+		},
 		{
 			"IPv4 address",
 			"1.2.3.4",
@@ -115,6 +125,16 @@ func TestGRPCDestination(t *testing.T) {
 			"[2001:dba::1]:1234",
 			"[2001:dba::1]:1234",
 		},
+		{
+			"http hostname and port",
+			"http://foo.bar:2567",
+			"foo.bar:2567",
+		},
+		{
+			"https hostname and port",
+			"https://foo.bar:2567",
+			"foo.bar:2567",
+		},
 		{
 			"hostname and no port",
 			"foo.bar.com",
@@ -128,7 +148,7 @@ func TestGRPCDestination(t *testing.T) {
 	}
 
 	for _, tc := range tests {
-		dest := GRPCDestination(tc.dest)
+		dest := grpcDestination(tc.dest)
 		if dest != tc.output {
 			t.Errorf("Test case %s failed to set gRPC destination\n\texpected: %s\n\t  actual: %s",
 				tc.name,