Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

gRPC integration #12

Merged
merged 30 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4cecef5
Add initial stats API.
acetechnologist Apr 28, 2017
45542ec
fixed readme.md file
acetechnologist Aug 25, 2017
88f0d49
fixed readme.md file
acetechnologist Aug 25, 2017
ce9353e
fixing yaml file
acetechnologist Aug 25, 2017
9714c63
fixing yaml file
acetechnologist Aug 25, 2017
0ee8a94
remove go 1.7 and 1.6 from yaml file as we use 'sort.Slice' that is o…
acetechnologist Aug 25, 2017
f1d95ba
Added to README.md a section about modifying the reporting interval.
acetechnologist Aug 26, 2017
de85e90
Using inheritence instead of type switch for creating aggregationValu…
acetechnologist Aug 28, 2017
62d36a5
Deleted not used directories
acetechnologist Aug 28, 2017
412e8ae
implemented automatic registration for measures on creation
acetechnologist Aug 29, 2017
8496b0c
implemented codec for tags to be used in GRPC
acetechnologist Sep 1, 2017
8c1b0f5
implemented codec for tags to be used in GRPC
acetechnologist Sep 1, 2017
e2b9e39
implemented codec for tags to be used in GRPC
acetechnologist Sep 1, 2017
a086caa
Fixed readme.md typos. Changed API to create a tagSet builder
acetechnologist Sep 5, 2017
2447f0b
added convenience methods to various structs: String(), Equal(), Cont…
acetechnologist Sep 6, 2017
dacbcce
added getter methods to interfaces and made some fields public to eve…
acetechnologist Sep 12, 2017
9a7ba3f
addressed commnets for the pull request. Fixed both method names and …
acetechnologist Sep 13, 2017
48af633
added installation and prereq comments to readme.md
acetechnologist Sep 13, 2017
da0f143
Merge branch 'master' of https://github.com/census-instrumentation/in…
acetechnologist Sep 14, 2017
0a2a0d2
Fixed aggregationDistirbution to include oldest bucket in interval stats
acetechnologist Sep 14, 2017
2f7f729
Added plugin interceptor for grpc stats
acetechnologist Sep 15, 2017
c86ab26
Added all defaults measures and views to be collected for grpc stats
acetechnologist Sep 18, 2017
85a5395
Addressed PR comments. Modified readme.md to mention the requirement …
acetechnologist Sep 19, 2017
174ba23
Made serverHandler and clientHandler private and added exported const…
acetechnologist Sep 20, 2017
695335d
Requires Go 1.9 to take advantage of context type aliasing.
acetechnologist Sep 20, 2017
b5d7b91
Requiring Go 1.9 seems quite limiting. So I went ahead and replaced c…
acetechnologist Sep 20, 2017
fb0ec85
Reworded comments for NewTestingAggregationDistributionValue
acetechnologist Sep 20, 2017
47e4a1b
Renamed NewTestingAggregationDistributionValue to NewDoNotUseTestingA…
acetechnologist Sep 20, 2017
827f7c2
Fixed naming NewTestingAggregationDistributionValue to NewDoNotUseTes…
acetechnologist Sep 20, 2017
58b58e5
Added gofmt to .travis script. Fixed formatting issues
acetechnologist Sep 22, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions plugins/grpc/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2017, OpenCensus Authors

Choose a reason for hiding this comment

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

Some files are Copyright 2017 Google Inc....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Thank you. Fixed now.

//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package stats

import (
"context"

Choose a reason for hiding this comment

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

gRPC-go still imports "golang.org/x/net/context" (grpc/grpc-go#711).
It will be a massive incompatible API change for all users using <=go1.8 if we change that.

go treats func("context".Context) and func("golang.org/x/net/context".Context) as two different types, which means the stats handler here won't work (it doesn't compile, with <=go1.8).

go1.9 introduced a new feature called type alias, which makes "golang.org/x/net/context" and "context" the same type. So, this actually works fine with >=go1.9.

The solution I can think of right now is to import "golang.org/x/net/context" in the stats package.

Or, if we decide that the stats package only supports users with >=go1.9, the current code will be fine.

Copy link
Contributor Author

@acetechnologist acetechnologist Sep 19, 2017

Choose a reason for hiding this comment

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

Since this is our first release, I think we should avoid creating unnecessary confusion and say that we support >= go1.9 if using stats with grpc.
Wondering if @rakyll have a different opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking about it more. I think it is better to use "golang.org/x/net/context" so we can get more adoption and since once everybody moves beyond 1.8 it will be easy to move to "context".Context


"google.golang.org/grpc/stats"
)

// Handlers is a composite type implementing the "google.golang.org/grpc/stats.Handler"
// interface to process lifecycle events from a GRPC client or server. Its only
// purpose is to allow for chaining others types implementing the "google.golang.org/grpc/stats.Handler"
// interface. In this package it allows both stats and tracing subHandlers to be chained together.
type Handlers struct {
subHandlers []stats.Handler
}

// TagConn can attach some information to the given context. The returned
// context will be used for stats handling. For conn stats handling, the
// context used in HandleConn for this connection will be derived from the
// context returned.
// For client RPC stats handling, the context used in HandleRPC is not derived
// from the context returned.
// For server RPC stats handling, the context used in HandleRPC for all RPCs on
// this connection will be derived from this returned context.
func (h Handlers) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
for _, sh := range h.subHandlers {
ctx = sh.TagConn(ctx, info)
}
return ctx
}

// HandleConn calls all registered connection subHandlers.
func (h Handlers) HandleConn(ctx context.Context, s stats.ConnStats) {
for _, sh := range h.subHandlers {
sh.HandleConn(ctx, s)
}
}

// TagRPC can attach some information to the given context. The returned
// context is used in the rest lifetime of the RPC.
func (h Handlers) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
for _, sh := range h.subHandlers {
ctx = sh.TagRPC(ctx, info)
}
return ctx
}

// HandleRPC calls all registered RPC subHandlers.
func (h Handlers) HandleRPC(ctx context.Context, s stats.RPCStats) {
for _, sh := range h.subHandlers {
sh.HandleRPC(ctx, s)
}
}

// AddHandler adds a handler to the list of registered subHandlers. This list
// contains all the subhandlers that will be called during HandleConn and
// HandleRPC.
func (h Handlers) AddHandler(sh stats.Handler) {
h.subHandlers = append(h.subHandlers, sh)
}
176 changes: 176 additions & 0 deletions plugins/grpc/stats/client_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package stats

import (
"context"
"strings"
"sync/atomic"
"time"

istats "github.com/census-instrumentation/opencensus-go/stats"
"github.com/census-instrumentation/opencensus-go/tags"
"github.com/golang/glog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/stats"
)

var (
// grpcClientConnKey is the key used to store client instrumentation
// connection related data into the context.
grpcClientConnKey *grpcInstrumentationKey
// grpcClientRPCKey is the key used to store client instrumentation RPC
// related data into the context.
grpcClientRPCKey *grpcInstrumentationKey
)

// ClientHandler is the type implementing the "google.golang.org/grpc/stats.Handler"
// interface to process lifecycle events from the GRPC client.
type ClientHandler struct{}

Choose a reason for hiding this comment

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

Unexport this and add an exported function instead?

Copy link
Contributor Author

@acetechnologist acetechnologist Sep 19, 2017

Choose a reason for hiding this comment

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

Not sure I understand your comment. You mean adding an exported "constructor" for it: eg func NewClientHandler() stats.Handler {...}

Choose a reason for hiding this comment

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

Yes..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


// TagConn adds connection related data to the given context and returns the
// new context.
func (ch ClientHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
// Do nothing. This is here to satisfy the interface "google.golang.org/grpc/stats.Handler"
return ctx
}

// HandleConn processes the connection events.
func (ch ClientHandler) HandleConn(ctx context.Context, s stats.ConnStats) {
// Do nothing. This is here to satisfy the interface "google.golang.org/grpc/stats.Handler"
}

// TagRPC gets the github.com/census-instrumentation/opencensus-go/tags.TagsSet
// populated by the application code, serializes its tags into the GRPC
// metadata in order to be sent to the server.
func (ch ClientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
startTime := time.Now()
if ctx == nil {
if glog.V(2) {
glog.Infoln("ClientHandler.TagRPC called with nil context")
}
return ctx
}

if info == nil {
if glog.V(2) {
glog.Infof("ClientHandler.TagRPC called with nil info.", info.FullMethodName)
}
return ctx
}
names := strings.Split(info.FullMethodName, "/")
if len(names) != 3 {
if glog.V(2) {
glog.Infof("ClientHandler.TagRPC called with info.FullMethodName bad format. got %v, want '/$service/$method/'", info.FullMethodName)
}
return ctx
}
serviceName := names[1]
methodName := names[2]

d := &rpcData{
startTime: startTime,
}

ts := tags.FromContext(ctx)
encoded := tags.EncodeToFullSignature(ts)

// Join old metadata with new one, so the old metadata information is not lost.
md, _ := metadata.FromOutgoingContext(ctx)
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(md, metadata.Pairs(tagsKey, string(encoded))))

tsb := tags.NewTagSetBuilder(ts)
tsb.UpsertString(keyService, serviceName)

Choose a reason for hiding this comment

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

Why does this function return a TagSetBuilder if it's called to update the builder?

Copy link
Contributor Author

@acetechnologist acetechnologist Sep 19, 2017

Choose a reason for hiding this comment

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

To make it easier to use the shortcut version when modifying multiple tags. It returns the same TagSetBuilder. It is just a syntactic sugar to allow for the short form:
tags.NewTagSetBuilder(ts).UpsertString(keyService, serviceName).UpsertString(key2, value2).AddString(key3, value3)...

tsb.UpsertString(keyMethod, methodName)

// TODO(acetechnologist): should we be recording this later? What is the
// point of updating d.reqLen & d.reqCount if we update now?
ctx = tags.NewContext(ctx, tsb.Build())
istats.RecordInt64(ctx, RPCClientStartedCount, 1)

return context.WithValue(ctx, grpcClientRPCKey, d)
}

// HandleRPC processes the RPC events.
func (ch ClientHandler) HandleRPC(ctx context.Context, s stats.RPCStats) {
switch st := s.(type) {
case *stats.Begin, *stats.OutHeader, *stats.InHeader, *stats.InTrailer, *stats.OutTrailer:
// do nothing for client
case *stats.OutPayload:
ch.handleRPCOutPayload(ctx, st)
case *stats.InPayload:
ch.handleRPCInPayload(ctx, st)
case *stats.End:
ch.handleRPCEnd(ctx, st)
default:
glog.Infof("unexpected stats: %T", st)
}
}

func (ch ClientHandler) handleRPCOutPayload(ctx context.Context, s *stats.OutPayload) {
d, ok := ctx.Value(grpcClientRPCKey).(*rpcData)
if !ok {
if glog.V(2) {
glog.Infoln("ClientHandler.handleRPCOutPayload failed to retrieve *rpcData from context")
}
return
}

istats.RecordInt64(ctx, RPCClientRequestBytes, int64(s.Length))
atomic.AddUint64(&d.reqCount, 1)
}

func (ch ClientHandler) handleRPCInPayload(ctx context.Context, s *stats.InPayload) {
d, ok := ctx.Value(grpcClientRPCKey).(*rpcData)
if !ok {
if glog.V(2) {
glog.Infoln("ClientHandler.handleRPCInPayload failed to retrieve *rpcData from context")
}
return
}

istats.RecordInt64(ctx, RPCClientResponseBytes, int64(s.Length))
atomic.AddUint64(&d.respCount, 1)
}

func (ch ClientHandler) handleRPCEnd(ctx context.Context, s *stats.End) {
d, ok := ctx.Value(grpcClientRPCKey).(*rpcData)
if !ok {
if glog.V(2) {
glog.Infoln("ClientHandler.handleRPCEnd failed to retrieve *rpcData from context")
}
return
}
elapsedTime := time.Since(d.startTime)

var measurements []istats.Measurement
measurements = append(measurements, RPCClientRequestCount.Is(int64(d.reqCount)))
measurements = append(measurements, RPCClientResponseCount.Is(int64(d.respCount)))
measurements = append(measurements, RPCClientFinishedCount.Is(1))
measurements = append(measurements, RPCClientRoundTripLatency.Is(float64(elapsedTime)/float64(time.Millisecond)))

if s.Error != nil {
errorCode := s.Error.Error()
ts := tags.FromContext(ctx)
tsb := tags.NewTagSetBuilder(ts)
tsb.UpsertString(keyOpStatus, errorCode)

ctx = tags.NewContext(ctx, tsb.Build())
measurements = append(measurements, RPCClientErrorCount.Is(1))
}

istats.Record(ctx, measurements...)
}
Loading