Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grpc support for UI #152

Merged
merged 3 commits into from
Feb 27, 2018
Merged

grpc support for UI #152

merged 3 commits into from
Feb 27, 2018

Conversation

asadali
Copy link
Collaborator

@asadali asadali commented Feb 22, 2018

  • 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
Copy link

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).

📝 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 and verify that your email is set on your git commits.
  • 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.

Copy link
Member

@ldemailly ldemailly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to leave it backward compatible URL wise;
that's why the test is failing because with your PR one must set runner=http - make sure it's optional

also see below

and thanks for your contribution!
please sign the cla though

ui/uihandler.go Outdated
@@ -265,8 +275,53 @@ func Handler(w http.ResponseWriter, r *http.Request) {
}
uiRunMapMutex.Unlock()
}
case run:
// mode == run case:
case rungrpc:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more code should be shareable between the 2 cases

see how fortio_main.go does grpc/http with sharing the interface between the 2

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

@ldemailly thank you for the pointers. I refactored the shared code and handled the default case.

google cla: I signed it!

ui/uihandler.go Outdated
if err != nil {
log.Errf("Init error %+v : %v", o, err)
log.Errf("Init error %+v : %v", ro, err)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do lose some visibility here. but I did not think this merited creation of a HasRunnerOptions interface

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's ok, though maybe add the input url and runner var

@ldemailly
Copy link
Member

can you put just
I signed it!
nothing else in 1 comment (the cla bot is picky)

@codecov
Copy link

codecov bot commented Feb 23, 2018

Codecov Report

Merging #152 into master will decrease coverage by 0.4%.
The diff coverage is 88.9%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master   #152     +/-   ##
=======================================
- Coverage    88.4%    88%   -0.4%     
=======================================
  Files           7      7             
  Lines        1488   1518     +30     
=======================================
+ Hits         1315   1336     +21     
- Misses        115    121      +6     
- Partials       58     61      +3
Impacted Files Coverage Δ
fgrpc/grpcrunner.go 75% <88.9%> (-2.8%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5ee4cef...b3b31a0. Read the comment docs.

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

I signed it!

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

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

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

I signed it

@googlebot
Copy link

CLAs look good, thanks!

@danehans
Copy link
Collaborator

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

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

@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?

Webtest.sh Outdated
$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&runner=grpc" | grep ActualQPS
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danehans you raised a good point about the intended usage of grpc mode. I added an acceptance test here. let me know if this helps

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe grep for something GRPC specific like Health SERVING

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the grep is on the result json object which is currently shared between the http/grpc modes. the result object does not have anything grpc-specific for now.
Health SERVING is printed to stdout. not to the json file. correct me if I understood this wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k can you file a follow up that the GRPC json is missing the equivalent of the Http code map that http has
(don't need to fix everything in this 1 pr if you don't have time)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure that I understand this. the grpc object does have a retcode map object eg from a sample run of 3000 queries on localhost:8079

  "RetCodes": {
    "1": 3000
  }

however, the json doesn't contain a grpc-specific message eg Health SERVING, this is logged to stdout, hence can't be grep-ed for in the json result.

are you suggesting that the result.json should contain http/grpc specific messages?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the code printing stdout is
fmt.Printf("Health %s : %d\n", k.String(), total.RetCodes[k])

for some reason the Json has the numerical value "1" instead of "SERVING" - I wonder if that's fixable easily with some annotation on the struct

but we can postpone - I guess just grep for "1": 100 and use exactly 100 (-n 100)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, now i get it! will give it a shot

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure how the automation works on this repo? will referencing the issue auto-close it if PR is merged?

@ldemailly
Copy link
Member

ldemailly commented Feb 23, 2018

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
but that can be in follow up pr

speaking of which can you add a checkbox for the Secure option of grpc ?

@ldemailly
Copy link
Member

@danehans the error is definitely obscure, so good find!

@asadali
Copy link
Collaborator Author

asadali commented Feb 23, 2018

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
Copy link
Collaborator

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
Copy link
Collaborator

Here is a screenshot of the diagram showing http://localhost:8079"

screen shot 2018-02-23 at 3 47 44 pm

@asadali
Copy link
Collaborator Author

asadali commented Feb 24, 2018

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

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
Copy link
Member

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

@asadali
Copy link
Collaborator Author

asadali commented Feb 24, 2018

understood. do you want the validation to be added as part of this PR?

uiRunMapMutex.Unlock()
var res periodic.HasRunnerResult
var err error
if runner == modegrpc {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Webtest.sh Outdated
$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&runner=grpc" | grep ActualQPS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe grep for something GRPC specific like Health SERVING

@@ -52,6 +52,9 @@ <h1>Φορτίο (fortio) v{{.Version}}{{if not .DoLoad}} control UI{{end}}</h1>
{{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/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for having used the same url param name as the flag, as is attempted for most of the other options

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that was my intention. however, i did deviate a bit for the radio buttons. :|

@ldemailly
Copy link
Member

ldemailly commented Feb 24, 2018

the title logic is here: https://github.com/istio/fortio/blob/master/ui/static/js/fortio_chart.js#L90

edit: you already found this :-)

@ldemailly
Copy link
Member

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)

@@ -29,6 +29,8 @@ import (
"istio.io/fortio/fnet"
"istio.io/fortio/log"
"istio.io/fortio/periodic"
"strings"
"regexp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let’s not drag in regexp for a prefix match?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack. it felt like a hammer. thanks for calling it out. removed.

{
"http hostname and port",
"http://localhost:1234",
"localhost:1234",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

{
"https hostname and port",
"https://localhost:1234",
"localhost:1234",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we check grpc secure state ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is not being set in the parseGRPCDestination function. It's tied in with the fgrpc.Dial function.

if tls || (strings.HasPrefix(serverAddr, "https://")) {
		opts = grpc.WithTransportCredentials(credentials.NewTLS(nil))
	}

not sure how to best test it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can test it (at least manually, and probably also in Webtest.sh, with
fortio grpcping https://fortio.istio.io/
(it will need to set the port to 443 too ideally if not supported)

maybe we should just bail if it has http[s]:// prefix

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, was unclear earlier. i had tested it manually and the https state led to secureTLS internal state. In any case, I added an additional grpcping in webtest.sh

@@ -156,9 +160,16 @@ func RunGRPCTest(o *GRPCRunnerOptions) (*GRPCRunnerResults, error) {
return &total, nil
}

// GRPCDestination parses dest and returns dest:port based on dest type
// parseGRPCDestination parses dest and returns dest:port based on dest type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the name/visibility change ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since its not being used outside the package anymore and its being embedded into the Dial method

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go style would probably be to call this grpcDestination() then but it's ok

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to grpcDestination

@asadali
Copy link
Collaborator Author

asadali commented Feb 26, 2018

@ldemailly let me know if this needs any more work. rebased with current master

Copy link
Member

@ldemailly ldemailly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice, thanks for all of this, see below for minor things

Webtest.sh Outdated
$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&runner=grpc" | grep ActualQPS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the code printing stdout is
fmt.Printf("Health %s : %d\n", k.String(), total.RetCodes[k])

for some reason the Json has the numerical value "1" instead of "SERVING" - I wonder if that's fixable easily with some annotation on the struct

but we can postpone - I guess just grep for "1": 100 and use exactly 100 (-n 100)

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 can we make it work without :443 :-) ? [it's ok as, we can do that later]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean, be intelligent and assume 443 on a https prefix?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes if no :port is specified but http:// prefix is there, use 80, if https:// is there, use 443
but we can do that in followup

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened 162 to track that

// strip any unintentional http/https scheme prefixes from destination
if strings.HasPrefix(dest, "http://") || strings.HasPrefix(dest, "https://") {
dest = strings.Replace(strings.Replace(dest, "https://", "", 1), "http://", "", 1)
log.Warnf("stripping http/https scheme. grpc destination: %v", dest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a feature so probably Infof or LogVf

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to infof

func grpcDestination(dest string) string {
// strip any unintentional http/https scheme prefixes from destination
if strings.HasPrefix(dest, "http://") || strings.HasPrefix(dest, "https://") {
dest = strings.Replace(strings.Replace(dest, "https://", "", 1), "http://", "", 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit obscure, split the if ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ui/uihandler.go Outdated
if err != nil {
log.Errf("Init error %+v : %v", o, err)
log.Errf("Init error %+v : %v", ro, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's ok, though maybe add the input url and runner var

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)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enhanced the error log message

@ldemailly
Copy link
Member

pro tip:

please never force push a pr once reviewing has started

do a pull instead of a merge/rebase to be in sync

"https hostname and port",
"https://localhost:1234",
"localhost:1234",
},
{
"IPv4 address",
"1.2.3.4",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add tests for http:// and https://foo.bar:2567 -> foo.bar:2567 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@ldemailly ldemailly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just add 2 tests in existing grpcDestination and we'll merge this ! thanks for your patience!

@asadali
Copy link
Collaborator Author

asadali commented Feb 27, 2018

sorry about the inconvenience with the force pushes. i guess i went overboard with trying to keep commit history clean

@ldemailly
Copy link
Member

it gets squashed at the end on master so the PR can have 30 commits it's no problems

@ldemailly ldemailly merged commit 646e917 into fortio:master Feb 27, 2018
@asadali asadali deleted the grpc branch February 27, 2018 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants