Vendor dependencies for GCS

This commit is contained in:
Alexander Neumann
2017-08-05 20:17:15 +02:00
parent ba75a3884c
commit 8ca6a9a240
1228 changed files with 1769186 additions and 1 deletions

View File

@@ -0,0 +1,65 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// 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.
// AUTO-GENERATED CODE. DO NOT EDIT.
package trace
import (
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
)
import (
"strconv"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
var _ = iterator.Done
var _ = strconv.FormatUint
var _ = time.Now
func TestTraceServiceSmoke(t *testing.T) {
if testing.Short() {
t.Skip("skipping smoke test in short mode")
}
ctx := context.Background()
ts := testutil.TokenSource(ctx, DefaultAuthScopes()...)
if ts == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}
projectId := testutil.ProjID()
_ = projectId
c, err := NewClient(ctx, option.WithTokenSource(ts))
if err != nil {
t.Fatal(err)
}
var projectId2 string = projectId
var request = &cloudtracepb.ListTracesRequest{
ProjectId: projectId2,
}
iter := c.ListTraces(ctx, request)
if _, err := iter.Next(); err != nil && err != iterator.Done {
t.Error(err)
}
}

48
vendor/cloud.google.com/go/trace/apiv1/doc.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// 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.
// AUTO-GENERATED CODE. DO NOT EDIT.
// Package trace is an experimental, auto-generated package for the
// Stackdriver Trace API.
//
// Send and retrieve trace data from Stackdriver Trace. Data is generated and
// available by default for all App Engine applications. Data from other
// applications can be written to Stackdriver Trace for display, reporting,
// and analysis.
//
// Use the client at cloud.google.com/go/trace in preference to this.
package trace // import "cloud.google.com/go/trace/apiv1"
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
func insertXGoog(ctx context.Context, val []string) context.Context {
md, _ := metadata.FromOutgoingContext(ctx)
md = md.Copy()
md["x-goog-api-client"] = val
return metadata.NewOutgoingContext(ctx, md)
}
// DefaultAuthScopes reports the authentication scopes required
// by this package.
func DefaultAuthScopes() []string {
return []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/trace.append",
"https://www.googleapis.com/auth/trace.readonly",
}
}

321
vendor/cloud.google.com/go/trace/apiv1/mock_test.go generated vendored Normal file
View File

@@ -0,0 +1,321 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// 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.
// AUTO-GENERATED CODE. DO NOT EDIT.
package trace
import (
emptypb "github.com/golang/protobuf/ptypes/empty"
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
)
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"golang.org/x/net/context"
"google.golang.org/api/option"
status "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
gstatus "google.golang.org/grpc/status"
)
var _ = io.EOF
var _ = ptypes.MarshalAny
var _ status.Status
type mockTraceServer struct {
// Embed for forward compatibility.
// Tests will keep working if more methods are added
// in the future.
cloudtracepb.TraceServiceServer
reqs []proto.Message
// If set, all calls return this error.
err error
// responses to return if err == nil
resps []proto.Message
}
func (s *mockTraceServer) ListTraces(ctx context.Context, req *cloudtracepb.ListTracesRequest) (*cloudtracepb.ListTracesResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*cloudtracepb.ListTracesResponse), nil
}
func (s *mockTraceServer) GetTrace(ctx context.Context, req *cloudtracepb.GetTraceRequest) (*cloudtracepb.Trace, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*cloudtracepb.Trace), nil
}
func (s *mockTraceServer) PatchTraces(ctx context.Context, req *cloudtracepb.PatchTracesRequest) (*emptypb.Empty, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*emptypb.Empty), nil
}
// clientOpt is the option tests should use to connect to the test server.
// It is initialized by TestMain.
var clientOpt option.ClientOption
var (
mockTrace mockTraceServer
)
func TestMain(m *testing.M) {
flag.Parse()
serv := grpc.NewServer()
cloudtracepb.RegisterTraceServiceServer(serv, &mockTrace)
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
log.Fatal(err)
}
go serv.Serve(lis)
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
clientOpt = option.WithGRPCConn(conn)
os.Exit(m.Run())
}
func TestTraceServicePatchTraces(t *testing.T) {
var expectedResponse *emptypb.Empty = &emptypb.Empty{}
mockTrace.err = nil
mockTrace.reqs = nil
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
var projectId string = "projectId-1969970175"
var traces *cloudtracepb.Traces = &cloudtracepb.Traces{}
var request = &cloudtracepb.PatchTracesRequest{
ProjectId: projectId,
Traces: traces,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.PatchTraces(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
}
func TestTraceServicePatchTracesError(t *testing.T) {
errCode := codes.PermissionDenied
mockTrace.err = gstatus.Error(errCode, "test error")
var projectId string = "projectId-1969970175"
var traces *cloudtracepb.Traces = &cloudtracepb.Traces{}
var request = &cloudtracepb.PatchTracesRequest{
ProjectId: projectId,
Traces: traces,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.PatchTraces(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
}
func TestTraceServiceGetTrace(t *testing.T) {
var projectId2 string = "projectId2939242356"
var traceId2 string = "traceId2987826376"
var expectedResponse = &cloudtracepb.Trace{
ProjectId: projectId2,
TraceId: traceId2,
}
mockTrace.err = nil
mockTrace.reqs = nil
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
var projectId string = "projectId-1969970175"
var traceId string = "traceId1270300245"
var request = &cloudtracepb.GetTraceRequest{
ProjectId: projectId,
TraceId: traceId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetTrace(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestTraceServiceGetTraceError(t *testing.T) {
errCode := codes.PermissionDenied
mockTrace.err = gstatus.Error(errCode, "test error")
var projectId string = "projectId-1969970175"
var traceId string = "traceId1270300245"
var request = &cloudtracepb.GetTraceRequest{
ProjectId: projectId,
TraceId: traceId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetTrace(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestTraceServiceListTraces(t *testing.T) {
var nextPageToken string = ""
var tracesElement *cloudtracepb.Trace = &cloudtracepb.Trace{}
var traces = []*cloudtracepb.Trace{tracesElement}
var expectedResponse = &cloudtracepb.ListTracesResponse{
NextPageToken: nextPageToken,
Traces: traces,
}
mockTrace.err = nil
mockTrace.reqs = nil
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
var projectId string = "projectId-1969970175"
var request = &cloudtracepb.ListTracesRequest{
ProjectId: projectId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListTraces(context.Background(), request).Next()
if err != nil {
t.Fatal(err)
}
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
want := (interface{})(expectedResponse.Traces[0])
got := (interface{})(resp)
var ok bool
switch want := (want).(type) {
case proto.Message:
ok = proto.Equal(want, got.(proto.Message))
default:
ok = want == got
}
if !ok {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestTraceServiceListTracesError(t *testing.T) {
errCode := codes.PermissionDenied
mockTrace.err = gstatus.Error(errCode, "test error")
var projectId string = "projectId-1969970175"
var request = &cloudtracepb.ListTracesRequest{
ProjectId: projectId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListTraces(context.Background(), request).Next()
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}

234
vendor/cloud.google.com/go/trace/apiv1/trace_client.go generated vendored Normal file
View File

@@ -0,0 +1,234 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// 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.
// AUTO-GENERATED CODE. DO NOT EDIT.
package trace
import (
"math"
"time"
"cloud.google.com/go/internal/version"
gax "github.com/googleapis/gax-go"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// CallOptions contains the retry settings for each method of Client.
type CallOptions struct {
PatchTraces []gax.CallOption
GetTrace []gax.CallOption
ListTraces []gax.CallOption
}
func defaultClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("cloudtrace.googleapis.com:443"),
option.WithScopes(DefaultAuthScopes()...),
}
}
func defaultCallOptions() *CallOptions {
retry := map[[2]string][]gax.CallOption{
{"default", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 100 * time.Millisecond,
Max: 1000 * time.Millisecond,
Multiplier: 1.2,
})
}),
},
}
return &CallOptions{
PatchTraces: retry[[2]string{"default", "idempotent"}],
GetTrace: retry[[2]string{"default", "idempotent"}],
ListTraces: retry[[2]string{"default", "idempotent"}],
}
}
// Client is a client for interacting with Stackdriver Trace API.
type Client struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
client cloudtracepb.TraceServiceClient
// The call options for this service.
CallOptions *CallOptions
// The metadata to be sent with each request.
xGoogHeader []string
}
// NewClient creates a new trace service client.
//
// This file describes an API for collecting and viewing traces and spans
// within a trace. A Trace is a collection of spans corresponding to a single
// operation or set of operations for an application. A span is an individual
// timed event which forms a node of the trace tree. Spans for a single trace
// may span multiple services.
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
conn, err := transport.DialGRPC(ctx, append(defaultClientOptions(), opts...)...)
if err != nil {
return nil, err
}
c := &Client{
conn: conn,
CallOptions: defaultCallOptions(),
client: cloudtracepb.NewTraceServiceClient(conn),
}
c.SetGoogleClientInfo()
return c, nil
}
// Connection returns the client's connection to the API service.
func (c *Client) Connection() *grpc.ClientConn {
return c.conn
}
// Close closes the connection to the API service. The user should invoke this when
// the client is no longer required.
func (c *Client) Close() error {
return c.conn.Close()
}
// SetGoogleClientInfo sets the name and version of the application in
// the `x-goog-api-client` header passed on each request. Intended for
// use by Google-written clients.
func (c *Client) SetGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", version.Go()}, keyval...)
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
c.xGoogHeader = []string{gax.XGoogHeader(kv...)}
}
// PatchTraces sends new traces to Stackdriver Trace or updates existing traces. If the ID
// of a trace that you send matches that of an existing trace, any fields
// in the existing trace and its spans are overwritten by the provided values,
// and any new fields provided are merged with the existing trace data. If the
// ID does not match, a new trace is created.
func (c *Client) PatchTraces(ctx context.Context, req *cloudtracepb.PatchTracesRequest, opts ...gax.CallOption) error {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.PatchTraces[0:len(c.CallOptions.PatchTraces):len(c.CallOptions.PatchTraces)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.PatchTraces(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// GetTrace gets a single trace by its ID.
func (c *Client) GetTrace(ctx context.Context, req *cloudtracepb.GetTraceRequest, opts ...gax.CallOption) (*cloudtracepb.Trace, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.GetTrace[0:len(c.CallOptions.GetTrace):len(c.CallOptions.GetTrace)], opts...)
var resp *cloudtracepb.Trace
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetTrace(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListTraces returns of a list of traces that match the specified filter conditions.
func (c *Client) ListTraces(ctx context.Context, req *cloudtracepb.ListTracesRequest, opts ...gax.CallOption) *TraceIterator {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.ListTraces[0:len(c.CallOptions.ListTraces):len(c.CallOptions.ListTraces)], opts...)
it := &TraceIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*cloudtracepb.Trace, string, error) {
var resp *cloudtracepb.ListTracesResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ListTraces(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Traces, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
return it
}
// TraceIterator manages a stream of *cloudtracepb.Trace.
type TraceIterator struct {
items []*cloudtracepb.Trace
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*cloudtracepb.Trace, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *TraceIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *TraceIterator) Next() (*cloudtracepb.Trace, error) {
var item *cloudtracepb.Trace
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *TraceIterator) bufLen() int {
return len(it.items)
}
func (it *TraceIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}

View File

@@ -0,0 +1,92 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// 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.
// AUTO-GENERATED CODE. DO NOT EDIT.
package trace_test
import (
"cloud.google.com/go/trace/apiv1"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
)
func ExampleNewClient() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleClient_PatchTraces() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.PatchTracesRequest{
// TODO: Fill request struct fields.
}
err = c.PatchTraces(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_GetTrace() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.GetTraceRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetTrace(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListTraces() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.ListTracesRequest{
// TODO: Fill request struct fields.
}
it := c.ListTraces(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}

95
vendor/cloud.google.com/go/trace/grpc.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 trace
import (
"encoding/hex"
"fmt"
"cloud.google.com/go/internal/tracecontext"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const grpcMetadataKey = "grpc-trace-bin"
// GRPCClientInterceptor returns a grpc.UnaryClientInterceptor that traces all outgoing requests from a gRPC client.
// The calling context should already have a *trace.Span; a child span will be
// created for the outgoing gRPC call. If the calling context doesn't have a span,
// the call will not be traced.
//
// The functionality in gRPC that this feature relies on is currently experimental.
func (c *Client) GRPCClientInterceptor() grpc.UnaryClientInterceptor {
return grpc.UnaryClientInterceptor(c.grpcUnaryInterceptor)
}
func (c *Client) grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// TODO: also intercept streams.
span := FromContext(ctx).NewChild(method)
if span == nil {
span = c.NewSpan(method)
}
defer span.Finish()
traceContext := make([]byte, tracecontext.Len)
// traceID is a hex-encoded 128-bit value.
// TODO(jbd): Decode trace IDs upon arrival and
// represent trace IDs with 16 bytes internally.
tid, err := hex.DecodeString(span.trace.traceID)
if err != nil {
return invoker(ctx, method, req, reply, cc, opts...)
}
tracecontext.Encode(traceContext, tid, span.span.SpanId, byte(span.trace.globalOptions))
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.Pairs(grpcMetadataKey, string(traceContext))
} else {
md = md.Copy() // metadata is immutable, copy.
md[grpcMetadataKey] = []string{string(traceContext)}
}
ctx = metadata.NewOutgoingContext(ctx, md)
err = invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
// TODO: standardize gRPC label names?
span.SetLabel("error", err.Error())
}
return err
}
// GRPCServerInterceptor returns a grpc.UnaryServerInterceptor that enables the tracing of the incoming
// gRPC calls. Incoming call's context can be used to extract the span on servers that enabled this option:
//
// span := trace.FromContext(ctx)
//
// The functionality in gRPC that this feature relies on is currently experimental.
func (c *Client) GRPCServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
md, _ := metadata.FromIncomingContext(ctx)
var traceHeader string
if header, ok := md[grpcMetadataKey]; ok {
traceID, spanID, opts, ok := tracecontext.Decode([]byte(header[0]))
if ok {
// TODO(jbd): Generate a span directly from string(traceID), spanID and opts.
traceHeader = fmt.Sprintf("%x/%d;o=%d", traceID, spanID, opts)
}
}
span := c.SpanFromHeader(info.FullMethod, traceHeader)
defer span.Finish()
ctx = NewContext(ctx, span)
return handler(ctx, req)
}
}

178
vendor/cloud.google.com/go/trace/grpc_test.go generated vendored Normal file
View File

@@ -0,0 +1,178 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 trace
import (
"io/ioutil"
"log"
"net"
"net/http"
"strings"
"testing"
pb "cloud.google.com/go/trace/testdata/helloworld"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
func TestGRPCInterceptors(t *testing.T) {
tc := newTestClient(&noopTransport{})
// default sampling with global=1.
parent := tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if got, want := in.TraceID(), out.TraceID(); got != want {
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
}
if !in.Traced() {
t.Errorf("incoming span is not traced; want traced")
}
})
// default sampling with global=0.
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=0")
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if got, want := in.TraceID(), out.TraceID(); got != want {
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
}
if in.Traced() {
t.Errorf("incoming span is traced; want not traced")
}
})
// sampling all with global=1.
all, _ := NewLimitedSampler(1.0, 1<<32)
tc.SetSamplingPolicy(all)
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if got, want := in.TraceID(), out.TraceID(); got != want {
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
}
if !in.Traced() {
t.Errorf("incoming span is not traced; want traced")
}
})
// sampling none with global=1.
none, _ := NewLimitedSampler(0, 0)
tc.SetSamplingPolicy(none)
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if got, want := in.TraceID(), out.TraceID(); got != want {
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
}
if in.Traced() {
t.Errorf("incoming span is traced; want not traced")
}
})
// sampling all with no parent span.
tc.SetSamplingPolicy(all)
testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if in.TraceID() == "" {
t.Errorf("incoming call TraceID is empty")
}
if !in.Traced() {
t.Errorf("incoming span is not traced; want traced")
}
})
// sampling none with no parent span.
tc.SetSamplingPolicy(none)
testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) {
if in == nil {
t.Fatalf("missing span in the incoming context")
}
if in.TraceID() == "" {
t.Errorf("incoming call TraceID is empty")
}
if in.Traced() {
t.Errorf("incoming span is traced; want not traced")
}
})
}
func testGRPCInterceptor(t *testing.T, tc *Client, parent *Span, assert func(t *testing.T, out, in *Span)) {
incomingCh := make(chan *Span, 1)
addrCh := make(chan net.Addr, 1)
go func() {
lis, err := net.Listen("tcp", "")
if err != nil {
t.Fatalf("Failed to listen: %v", err)
}
addrCh <- lis.Addr()
s := grpc.NewServer(grpc.UnaryInterceptor(tc.GRPCServerInterceptor()))
pb.RegisterGreeterServer(s, &grpcServer{
fn: func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
incomingCh <- FromContext(ctx)
return &pb.HelloReply{}, nil
},
})
if err := s.Serve(lis); err != nil {
t.Fatalf("Failed to serve: %v", err)
}
}()
addr := <-addrCh
conn, err := grpc.Dial(addr.String(), grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
if err != nil {
t.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
outgoingCtx := NewContext(context.Background(), parent)
_, err = c.SayHello(outgoingCtx, &pb.HelloRequest{})
if err != nil {
log.Fatalf("Could not SayHello: %v", err)
}
assert(t, parent, <-incomingCh)
}
type noopTransport struct{}
func (rt *noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("{}")),
}
return resp, nil
}
type grpcServer struct {
fn func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error)
}
func (s *grpcServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return s.fn(ctx, in)
}

105
vendor/cloud.google.com/go/trace/http.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// +build go1.7
package trace
import (
"net/http"
)
// Transport is an http.RoundTripper that traces the outgoing requests.
//
// Transport is safe for concurrent usage.
type Transport struct {
// Base is the base http.RoundTripper to be used to do the actual request.
//
// Optional. If nil, http.DefaultTransport is used.
Base http.RoundTripper
}
// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.
// The created span can follow a parent span, if a parent is presented in
// the request's context.
func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) {
span := FromContext(req.Context()).NewRemoteChild(req)
resp, err := t.base().RoundTrip(req)
// TODO(jbd): Is it possible to defer the span.Finish?
// In cases where RoundTrip panics, we still can finish the span.
span.Finish(WithResponse(resp))
return resp, err
}
// CancelRequest cancels an in-flight request by closing its connection.
func (t Transport) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
if cr, ok := t.base().(canceler); ok {
cr.CancelRequest(req)
}
}
func (t Transport) base() http.RoundTripper {
if t.Base != nil {
return t.Base
}
return http.DefaultTransport
}
// HTTPHandler returns a http.Handler from the given handler
// that is aware of the incoming request's span.
// The span can be extracted from the incoming request in handler
// functions from incoming request's context:
//
// span := trace.FromContext(r.Context())
//
// The span will be auto finished by the handler.
func (c *Client) HTTPHandler(h http.Handler) http.Handler {
return &handler{traceClient: c, handler: h}
}
type handler struct {
traceClient *Client
handler http.Handler
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
traceID, parentSpanID, options, optionsOk, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
if !ok {
traceID = nextTraceID()
}
t := &trace{
traceID: traceID,
client: h.traceClient,
globalOptions: options,
localOptions: options,
}
span := startNewChildWithRequest(r, t, parentSpanID)
span.span.Kind = spanKindServer
span.rootSpan = true
configureSpanFromPolicy(span, h.traceClient.policy, ok)
defer span.Finish()
r = r.WithContext(NewContext(r.Context(), span))
if ok && !optionsOk {
// Inject the trace context back to the response with the sampling options.
// TODO(jbd): Remove when there is a better way to report the client's sampling.
w.Header().Set(httpHeader, spanHeader(traceID, parentSpanID, span.trace.localOptions))
}
h.handler.ServeHTTP(w, r)
}

151
vendor/cloud.google.com/go/trace/http_test.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// +build go1.7
package trace
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type recorderTransport struct {
ch chan *http.Request
}
func (rt *recorderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
rt.ch <- req
resp := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("{}")),
}
return resp, nil
}
func TestNewHTTPClient(t *testing.T) {
rt := &recorderTransport{
ch: make(chan *http.Request, 1),
}
tc := newTestClient(&noopTransport{})
client := &http.Client{
Transport: &Transport{
Base: rt,
},
}
req, _ := http.NewRequest("GET", "http://example.com", nil)
t.Run("NoTrace", func(t *testing.T) {
_, err := client.Do(req)
if err != nil {
t.Error(err)
}
outgoing := <-rt.ch
if got, want := outgoing.Header.Get(httpHeader), ""; want != got {
t.Errorf("got trace header = %q; want none", got)
}
})
t.Run("Trace", func(t *testing.T) {
span := tc.NewSpan("/foo")
req = req.WithContext(NewContext(req.Context(), span))
_, err := client.Do(req)
if err != nil {
t.Error(err)
}
outgoing := <-rt.ch
s := tc.SpanFromHeader("/foo", outgoing.Header.Get(httpHeader))
if got, want := s.TraceID(), span.TraceID(); got != want {
t.Errorf("trace ID = %q; want %q", got, want)
}
})
}
func TestHTTPHandlerNoTrace(t *testing.T) {
tc := newTestClient(&noopTransport{})
client := &http.Client{
Transport: &Transport{},
}
handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
span := FromContext(r.Context())
if span == nil {
t.Errorf("span is nil; want non-nil span")
}
}))
ts := httptest.NewServer(handler)
defer ts.Close()
req, _ := http.NewRequest("GET", ts.URL, nil)
_, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
}
func TestHTTPHandler_response(t *testing.T) {
tc := newTestClient(&noopTransport{})
p, _ := NewLimitedSampler(1, 1<<32) // all
tc.SetSamplingPolicy(p)
handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(handler)
defer ts.Close()
tests := []struct {
name string
traceHeader string
wantTraceHeader string
}{
{
name: "no global",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123",
wantTraceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
},
{
name: "global=1",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
wantTraceHeader: "",
},
{
name: "global=0",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=0",
wantTraceHeader: "",
},
{
name: "no trace context",
traceHeader: "",
wantTraceHeader: "",
},
}
for _, tt := range tests {
req, _ := http.NewRequest("GET", ts.URL, nil)
req.Header.Set(httpHeader, tt.traceHeader)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("failed to request: %v", err)
}
if got, want := res.Header.Get(httpHeader), tt.wantTraceHeader; got != want {
t.Errorf("%v: response context header = %q; want %q", tt.name, got, want)
}
}
}

57
vendor/cloud.google.com/go/trace/httpexample_test.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// +build go1.7
package trace_test
import (
"log"
"net/http"
"cloud.google.com/go/trace"
)
var traceClient *trace.Client
func ExampleHTTPClient_Do() {
client := http.Client{
Transport: &trace.Transport{},
}
span := traceClient.NewSpan("/foo") // traceClient is a *trace.Client
req, _ := http.NewRequest("GET", "https://metadata/users", nil)
req = req.WithContext(trace.NewContext(req.Context(), span))
if _, err := client.Do(req); err != nil {
log.Fatal(err)
}
}
func ExampleClient_HTTPHandler() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
client := http.Client{
Transport: &trace.Transport{},
}
req, _ := http.NewRequest("GET", "https://metadata/users", nil)
req = req.WithContext(r.Context())
// The outgoing request will be traced with r's trace ID.
if _, err := client.Do(req); err != nil {
log.Fatal(err)
}
})
http.Handle("/foo", traceClient.HTTPHandler(handler)) // traceClient is a *trace.Client
}

117
vendor/cloud.google.com/go/trace/sampling.go generated vendored Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 trace
import (
crand "crypto/rand"
"encoding/binary"
"fmt"
"math/rand"
"sync"
"time"
"golang.org/x/time/rate"
)
type SamplingPolicy interface {
// Sample returns a Decision.
// If Trace is false in the returned Decision, then the Decision should be
// the zero value.
Sample(p Parameters) Decision
}
// Parameters contains the values passed to a SamplingPolicy's Sample method.
type Parameters struct {
HasTraceHeader bool // whether the incoming request has a valid X-Cloud-Trace-Context header.
}
// Decision is the value returned by a call to a SamplingPolicy's Sample method.
type Decision struct {
Trace bool // Whether to trace the request.
Sample bool // Whether the trace is included in the random sample.
Policy string // Name of the sampling policy.
Weight float64 // Sample weight to be used in statistical calculations.
}
type sampler struct {
fraction float64
skipped float64
*rate.Limiter
*rand.Rand
sync.Mutex
}
func (s *sampler) Sample(p Parameters) Decision {
s.Lock()
x := s.Float64()
d := s.sample(p, time.Now(), x)
s.Unlock()
return d
}
// sample contains the a deterministic, time-independent logic of Sample.
func (s *sampler) sample(p Parameters, now time.Time, x float64) (d Decision) {
d.Sample = x < s.fraction
d.Trace = p.HasTraceHeader || d.Sample
if !d.Trace {
// We have no reason to trace this request.
return Decision{}
}
// We test separately that the rate limit is not tiny before calling AllowN,
// because of overflow problems in x/time/rate.
if s.Limit() < 1e-9 || !s.AllowN(now, 1) {
// Rejected by the rate limit.
if d.Sample {
s.skipped++
}
return Decision{}
}
if d.Sample {
d.Policy, d.Weight = "default", (1.0+s.skipped)/s.fraction
s.skipped = 0.0
}
return
}
// NewLimitedSampler returns a sampling policy that randomly samples a given
// fraction of requests. It also enforces a limit on the number of traces per
// second. It tries to trace every request with a trace header, but will not
// exceed the qps limit to do it.
func NewLimitedSampler(fraction, maxqps float64) (SamplingPolicy, error) {
if !(fraction >= 0) {
return nil, fmt.Errorf("invalid fraction %f", fraction)
}
if !(maxqps >= 0) {
return nil, fmt.Errorf("invalid maxqps %f", maxqps)
}
// Set a limit on the number of accumulated "tokens", to limit bursts of
// traced requests. Use one more than a second's worth of tokens, or 100,
// whichever is smaller.
// See https://godoc.org/golang.org/x/time/rate#NewLimiter.
maxTokens := 100
if maxqps < 99.0 {
maxTokens = 1 + int(maxqps)
}
var seed int64
if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil {
seed = time.Now().UnixNano()
}
s := sampler{
fraction: fraction,
Limiter: rate.NewLimiter(rate.Limit(maxqps), maxTokens),
Rand: rand.New(rand.NewSource(seed)),
}
return &s, nil
}

View File

@@ -0,0 +1,161 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 helloworld is a generated protocol buffer package.
It is generated from these files:
helloworld.proto
It has these top-level messages:
HelloRequest
HelloReply
*/
package helloworld
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// The request message containing the user's name.
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage() {}
func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
// The response message containing the greetings
type HelloReply struct {
Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
}
func (m *HelloReply) Reset() { *m = HelloReply{} }
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
func (*HelloReply) ProtoMessage() {}
func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func init() {
proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Greeter service
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
cc *grpc.ClientConn
}
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/helloworld.Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "helloworld.proto",
}
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 174 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7,
0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb,
0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b,
0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,37 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

817
vendor/cloud.google.com/go/trace/trace.go generated vendored Normal file
View File

@@ -0,0 +1,817 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 trace is a Google Stackdriver Trace library.
//
// This package is still experimental and subject to change.
//
// See https://cloud.google.com/trace/api/#data_model for a discussion of traces
// and spans.
//
// To initialize a client that connects to the Stackdriver Trace server, use the
// NewClient function. Generally you will want to do this on program
// initialization.
//
// import "cloud.google.com/go/trace"
// ...
// traceClient, err = trace.NewClient(ctx, projectID)
//
// Calling SpanFromRequest will create a new trace span for an incoming HTTP
// request. If the request contains a trace context header, it is used to
// determine the trace ID. Otherwise, a new trace ID is created.
//
// func handler(w http.ResponseWriter, r *http.Request) {
// span := traceClient.SpanFromRequest(r)
// defer span.Finish()
// ...
// }
//
// SpanFromRequest and NewSpan returns nil if the *Client is nil, so you can disable
// tracing by not initializing your *Client variable. All of the exported
// functions on *Span do nothing when the *Span is nil.
//
// If you need to start traces that don't correspond to an incoming HTTP request,
// you can use NewSpan to create a root-level span.
//
// span := traceClient.NewSpan("span name")
// defer span.Finish()
//
// Although a trace span object is created for every request, only a subset of
// traces are uploaded to the server, for efficiency. By default, the requests
// that are traced are those with the tracing bit set in the options field of
// the trace context header. Ideally, you should override this behaviour by
// calling SetSamplingPolicy. NewLimitedSampler returns an implementation of
// SamplingPolicy which traces requests that have the tracing bit set, and also
// randomly traces a specified fraction of requests. Additionally, it sets a
// limit on the number of requests traced per second. The following example
// traces one in every thousand requests, up to a limit of 5 per second.
//
// p, err := trace.NewLimitedSampler(0.001, 5)
// traceClient.SetSamplingPolicy(p)
//
// You can create a new span as a child of an existing span with NewChild.
//
// childSpan := span.NewChild(name)
// ...
// childSpan.Finish()
//
// When sending an HTTP request to another server, NewRemoteChild will create
// a span to represent the time the current program waits for the request to
// complete, and attach a header to the outgoing request so that the trace will
// be propagated to the destination server.
//
// childSpan := span.NewRemoteChild(&httpRequest)
// ...
// childSpan.Finish()
//
// Alternatively, if you have access to the X-Cloud-Trace-Context header value
// but not the underlying HTTP request (this can happen if you are using a
// different transport or messaging protocol, such as gRPC), you can use
// SpanFromHeader instead of SpanFromRequest. In that case, you will need to
// specify the span name explicility, since it cannot be constructed from the
// HTTP request's URL and method.
//
// func handler(r *somepkg.Request) {
// span := traceClient.SpanFromHeader("span name", r.TraceContext())
// defer span.Finish()
// ...
// }
//
// Spans can contain a map from keys to values that have useful information
// about the span. The elements of this map are called labels. Some labels,
// whose keys all begin with the string "trace.cloud.google.com/", are set
// automatically in the following ways:
//
// - SpanFromRequest sets some labels to data about the incoming request.
//
// - NewRemoteChild sets some labels to data about the outgoing request.
//
// - Finish sets a label to a stack trace, if the stack trace option is enabled
// in the incoming trace header.
//
// - The WithResponse option sets some labels to data about a response.
// You can also set labels using SetLabel. If a label is given a value
// automatically and by SetLabel, the automatically-set value is used.
//
// span.SetLabel(key, value)
//
// The WithResponse option can be used when Finish is called.
//
// childSpan := span.NewRemoteChild(outgoingReq)
// resp, err := http.DefaultClient.Do(outgoingReq)
// ...
// childSpan.Finish(trace.WithResponse(resp))
//
// When a span created by SpanFromRequest or SpamFromHeader is finished, the
// finished spans in the corresponding trace -- the span itself and its
// descendants -- are uploaded to the Stackdriver Trace server using the
// *Client that created the span. Finish returns immediately, and uploading
// occurs asynchronously. You can use the FinishWait function instead to wait
// until uploading has finished.
//
// err := span.FinishWait()
//
// Using contexts to pass *trace.Span objects through your program will often
// be a better approach than passing them around explicitly. This allows trace
// spans, and other request-scoped or part-of-request-scoped values, to be
// easily passed through API boundaries. Various Google Cloud libraries will
// retrieve trace spans from contexts and automatically create child spans for
// API requests.
// See https://blog.golang.org/context for more discussion of contexts.
// A derived context containing a trace span can be created using NewContext.
//
// span := traceClient.SpanFromRequest(r)
// ctx = trace.NewContext(ctx, span)
//
// The span can be retrieved from a context elsewhere in the program using
// FromContext.
//
// func foo(ctx context.Context) {
// span := trace.FromContext(ctx).NewChild("in foo")
// defer span.Finish()
// ...
// }
//
package trace // import "cloud.google.com/go/trace"
import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"log"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/context"
api "google.golang.org/api/cloudtrace/v1"
"google.golang.org/api/gensupport"
"google.golang.org/api/option"
"google.golang.org/api/support/bundler"
htransport "google.golang.org/api/transport/http"
)
const (
httpHeader = `X-Cloud-Trace-Context`
userAgent = `gcloud-golang-trace/20160501`
cloudPlatformScope = `https://www.googleapis.com/auth/cloud-platform`
spanKindClient = `RPC_CLIENT`
spanKindServer = `RPC_SERVER`
spanKindUnspecified = `SPAN_KIND_UNSPECIFIED`
maxStackFrames = 20
labelHost = `trace.cloud.google.com/http/host`
labelMethod = `trace.cloud.google.com/http/method`
labelStackTrace = `trace.cloud.google.com/stacktrace`
labelStatusCode = `trace.cloud.google.com/http/status_code`
labelURL = `trace.cloud.google.com/http/url`
labelSamplingPolicy = `trace.cloud.google.com/sampling_policy`
labelSamplingWeight = `trace.cloud.google.com/sampling_weight`
)
const (
// ScopeTraceAppend grants permissions to write trace data for a project.
ScopeTraceAppend = "https://www.googleapis.com/auth/trace.append"
// ScopeCloudPlatform grants permissions to view and manage your data
// across Google Cloud Platform services.
ScopeCloudPlatform = "https://www.googleapis.com/auth/cloud-platform"
)
type contextKey struct{}
type stackLabelValue struct {
Frames []stackFrame `json:"stack_frame"`
}
type stackFrame struct {
Class string `json:"class_name,omitempty"`
Method string `json:"method_name"`
Filename string `json:"file_name"`
Line int64 `json:"line_number"`
}
var (
spanIDCounter uint64
spanIDIncrement uint64
)
func init() {
// Set spanIDCounter and spanIDIncrement to random values. nextSpanID will
// return an arithmetic progression using these values, skipping zero. We set
// the LSB of spanIDIncrement to 1, so that the cycle length is 2^64.
binary.Read(rand.Reader, binary.LittleEndian, &spanIDCounter)
binary.Read(rand.Reader, binary.LittleEndian, &spanIDIncrement)
spanIDIncrement |= 1
// Attach hook for autogenerated Google API calls. This will automatically
// create trace spans for API calls if there is a trace in the context.
gensupport.RegisterHook(requestHook)
}
func requestHook(ctx context.Context, req *http.Request) func(resp *http.Response) {
span := FromContext(ctx)
if span == nil || req == nil {
return nil
}
span = span.NewRemoteChild(req)
return func(resp *http.Response) {
if resp != nil {
span.Finish(WithResponse(resp))
} else {
span.Finish()
}
}
}
// nextSpanID returns a new span ID. It will never return zero.
func nextSpanID() uint64 {
var id uint64
for id == 0 {
id = atomic.AddUint64(&spanIDCounter, spanIDIncrement)
}
return id
}
// nextTraceID returns a new trace ID.
func nextTraceID() string {
id1 := nextSpanID()
id2 := nextSpanID()
return fmt.Sprintf("%016x%016x", id1, id2)
}
// Client is a client for uploading traces to the Google Stackdriver Trace server.
type Client struct {
service *api.Service
projectID string
policy SamplingPolicy
bundler *bundler.Bundler
}
// NewClient creates a new Google Stackdriver Trace client.
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
o := []option.ClientOption{
option.WithScopes(cloudPlatformScope),
option.WithUserAgent(userAgent),
}
o = append(o, opts...)
hc, basePath, err := htransport.NewClient(ctx, o...)
if err != nil {
return nil, fmt.Errorf("creating HTTP client for Google Stackdriver Trace API: %v", err)
}
apiService, err := api.New(hc)
if err != nil {
return nil, fmt.Errorf("creating Google Stackdriver Trace API client: %v", err)
}
if basePath != "" {
// An option set a basepath, so override api.New's default.
apiService.BasePath = basePath
}
c := &Client{
service: apiService,
projectID: projectID,
}
bundler := bundler.NewBundler((*api.Trace)(nil), func(bundle interface{}) {
traces := bundle.([]*api.Trace)
err := c.upload(traces)
if err != nil {
log.Printf("failed to upload %d traces to the Cloud Trace server: %v", len(traces), err)
}
})
bundler.DelayThreshold = 2 * time.Second
bundler.BundleCountThreshold = 100
// We're not measuring bytes here, we're counting traces and spans as one "byte" each.
bundler.BundleByteThreshold = 1000
bundler.BundleByteLimit = 1000
bundler.BufferedByteLimit = 10000
c.bundler = bundler
return c, nil
}
// SetSamplingPolicy sets the SamplingPolicy that determines how often traces
// are initiated by this client.
func (c *Client) SetSamplingPolicy(p SamplingPolicy) {
if c != nil {
c.policy = p
}
}
// SpanFromHeader returns a new trace span, based on a provided request header
// value. See https://cloud.google.com/trace/docs/faq.
//
// It returns nil iff the client is nil.
//
// The trace information and identifiers will be read from the header value.
// Otherwise, a new trace ID is made and the parent span ID is zero.
//
// The name of the new span is provided as an argument.
//
// If a non-nil sampling policy has been set in the client, it can override
// the options set in the header and choose whether to trace the request.
//
// If the header doesn't have existing tracing information, then a *Span is
// returned anyway, but it will not be uploaded to the server, just as when
// calling SpanFromRequest on an untraced request.
//
// Most users using HTTP should use SpanFromRequest, rather than
// SpanFromHeader, since it provides additional functionality for HTTP
// requests. In particular, it will set various pieces of request information
// as labels on the *Span, which is not available from the header alone.
func (c *Client) SpanFromHeader(name string, header string) *Span {
if c == nil {
return nil
}
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(header)
if !ok {
traceID = nextTraceID()
}
t := &trace{
traceID: traceID,
client: c,
globalOptions: options,
localOptions: options,
}
span := startNewChild(name, t, parentSpanID)
span.span.Kind = spanKindServer
span.rootSpan = true
configureSpanFromPolicy(span, c.policy, ok)
return span
}
// SpanFromRequest returns a new trace span for an HTTP request.
//
// It returns nil iff the client is nil.
//
// If the incoming HTTP request contains a trace context header, the trace ID,
// parent span ID, and tracing options will be read from that header.
// Otherwise, a new trace ID is made and the parent span ID is zero.
//
// If a non-nil sampling policy has been set in the client, it can override the
// options set in the header and choose whether to trace the request.
//
// If the request is not being traced, then a *Span is returned anyway, but it
// will not be uploaded to the server -- it is only useful for propagating
// trace context to child requests and for getting the TraceID. All its
// methods can still be called -- the Finish, FinishWait, and SetLabel methods
// do nothing. NewChild does nothing, and returns the same *Span. TraceID
// works as usual.
func (c *Client) SpanFromRequest(r *http.Request) *Span {
if c == nil {
return nil
}
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
if !ok {
traceID = nextTraceID()
}
t := &trace{
traceID: traceID,
client: c,
globalOptions: options,
localOptions: options,
}
span := startNewChildWithRequest(r, t, parentSpanID)
span.span.Kind = spanKindServer
span.rootSpan = true
configureSpanFromPolicy(span, c.policy, ok)
return span
}
// NewSpan returns a new trace span with the given name.
//
// A new trace and span ID is generated to trace the span.
// Returned span need to be finished by calling Finish or FinishWait.
func (c *Client) NewSpan(name string) *Span {
if c == nil {
return nil
}
t := &trace{
traceID: nextTraceID(),
client: c,
localOptions: optionTrace,
globalOptions: optionTrace,
}
span := startNewChild(name, t, 0)
span.span.Kind = spanKindUnspecified
span.rootSpan = true
configureSpanFromPolicy(span, c.policy, false)
return span
}
func configureSpanFromPolicy(s *Span, p SamplingPolicy, ok bool) {
if p == nil {
return
}
d := p.Sample(Parameters{HasTraceHeader: ok})
if d.Trace {
// Turn on tracing locally, and in child requests.
s.trace.localOptions |= optionTrace
s.trace.globalOptions |= optionTrace
} else {
// Turn off tracing locally.
s.trace.localOptions = 0
return
}
if d.Sample {
// This trace is in the random sample, so set the labels.
s.SetLabel(labelSamplingPolicy, d.Policy)
s.SetLabel(labelSamplingWeight, fmt.Sprint(d.Weight))
}
}
// NewContext returns a derived context containing the span.
func NewContext(ctx context.Context, s *Span) context.Context {
if s == nil {
return ctx
}
return context.WithValue(ctx, contextKey{}, s)
}
// FromContext returns the span contained in the context, or nil.
func FromContext(ctx context.Context) *Span {
s, _ := ctx.Value(contextKey{}).(*Span)
return s
}
func traceInfoFromHeader(h string) (traceID string, spanID uint64, options optionFlags, optionsOk bool, ok bool) {
// See https://cloud.google.com/trace/docs/faq for the header format.
// Return if the header is empty or missing, or if the header is unreasonably
// large, to avoid making unnecessary copies of a large string.
if h == "" || len(h) > 200 {
return "", 0, 0, false, false
}
// Parse the trace id field.
slash := strings.Index(h, `/`)
if slash == -1 {
return "", 0, 0, false, false
}
traceID, h = h[:slash], h[slash+1:]
// Parse the span id field.
spanstr := h
semicolon := strings.Index(h, `;`)
if semicolon != -1 {
spanstr, h = h[:semicolon], h[semicolon+1:]
}
spanID, err := strconv.ParseUint(spanstr, 10, 64)
if err != nil {
return "", 0, 0, false, false
}
// Parse the options field, options field is optional.
if !strings.HasPrefix(h, "o=") {
return traceID, spanID, 0, false, true
}
o, err := strconv.ParseUint(h[2:], 10, 64)
if err != nil {
return "", 0, 0, false, false
}
options = optionFlags(o)
return traceID, spanID, options, true, true
}
type optionFlags uint32
const (
optionTrace optionFlags = 1 << iota
optionStack
)
type trace struct {
mu sync.Mutex
client *Client
traceID string
globalOptions optionFlags // options that will be passed to any child requests
localOptions optionFlags // options applied in this server
spans []*Span // finished spans for this trace.
}
// finish appends s to t.spans. If s is the root span, uploads the trace to the
// server.
func (t *trace) finish(s *Span, wait bool, opts ...FinishOption) error {
for _, o := range opts {
o.modifySpan(s)
}
s.end = time.Now()
t.mu.Lock()
t.spans = append(t.spans, s)
spans := t.spans
t.mu.Unlock()
if s.rootSpan {
if wait {
return t.client.upload([]*api.Trace{t.constructTrace(spans)})
}
go func() {
tr := t.constructTrace(spans)
err := t.client.bundler.Add(tr, 1+len(spans))
if err == bundler.ErrOversizedItem {
err = t.client.upload([]*api.Trace{tr})
}
if err != nil {
log.Println("error uploading trace:", err)
}
}()
}
return nil
}
func (t *trace) constructTrace(spans []*Span) *api.Trace {
apiSpans := make([]*api.TraceSpan, len(spans))
for i, sp := range spans {
sp.span.StartTime = sp.start.In(time.UTC).Format(time.RFC3339Nano)
sp.span.EndTime = sp.end.In(time.UTC).Format(time.RFC3339Nano)
if t.localOptions&optionStack != 0 {
sp.setStackLabel()
}
sp.SetLabel(labelHost, sp.host)
sp.SetLabel(labelURL, sp.url)
sp.SetLabel(labelMethod, sp.method)
if sp.statusCode != 0 {
sp.SetLabel(labelStatusCode, strconv.Itoa(sp.statusCode))
}
apiSpans[i] = &sp.span
}
return &api.Trace{
ProjectId: t.client.projectID,
TraceId: t.traceID,
Spans: apiSpans,
}
}
func (c *Client) upload(traces []*api.Trace) error {
_, err := c.service.Projects.PatchTraces(c.projectID, &api.Traces{Traces: traces}).Do()
return err
}
// Span contains information about one span of a trace.
type Span struct {
trace *trace
spanMu sync.Mutex // guards span.Labels
span api.TraceSpan
start time.Time
end time.Time
rootSpan bool
stack [maxStackFrames]uintptr
host string
method string
url string
statusCode int
}
// Traced reports whether the current span is sampled to be traced.
func (s *Span) Traced() bool {
if s == nil {
return false
}
return s.trace.localOptions&optionTrace != 0
}
// NewChild creates a new span with the given name as a child of s.
// If s is nil, does nothing and returns nil.
func (s *Span) NewChild(name string) *Span {
if s == nil {
return nil
}
if !s.Traced() {
// TODO(jbd): Document this behavior in godoc here and elsewhere.
return s
}
return startNewChild(name, s.trace, s.span.SpanId)
}
// NewRemoteChild creates a new span as a child of s.
//
// Some labels in the span are set from the outgoing *http.Request r.
//
// A header is set in r so that the trace context is propagated to the
// destination. The parent span ID in that header is set as follows:
// - If the request is being traced, then the ID of s is used.
// - If the request is not being traced, but there was a trace context header
// in the incoming request for this trace (the request passed to
// SpanFromRequest), the parent span ID in that header is used.
// - Otherwise, the parent span ID is zero.
// The tracing bit in the options is set if tracing is enabled, or if it was
// set in the incoming request.
//
// If s is nil, does nothing and returns nil.
func (s *Span) NewRemoteChild(r *http.Request) *Span {
if s == nil {
return nil
}
if !s.Traced() {
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, s.span.ParentSpanId, s.trace.globalOptions)}
return s
}
newSpan := startNewChildWithRequest(r, s.trace, s.span.SpanId)
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, newSpan.span.SpanId, s.trace.globalOptions)}
return newSpan
}
// Header returns the value of the X-Cloud-Trace-Context header that
// should be used to propagate the span. This is the inverse of
// SpanFromHeader.
//
// Most users should use NewRemoteChild unless they have specific
// propagation needs or want to control the naming of their span.
// Header() does not create a new span.
func (s *Span) Header() string {
if s == nil {
return ""
}
return spanHeader(s.trace.traceID, s.span.SpanId, s.trace.globalOptions)
}
func startNewChildWithRequest(r *http.Request, trace *trace, parentSpanID uint64) *Span {
name := r.URL.Host + r.URL.Path // drop scheme and query params
newSpan := startNewChild(name, trace, parentSpanID)
if r.Host == "" {
newSpan.host = r.URL.Host
} else {
newSpan.host = r.Host
}
newSpan.method = r.Method
newSpan.url = r.URL.String()
return newSpan
}
func startNewChild(name string, trace *trace, parentSpanID uint64) *Span {
spanID := nextSpanID()
for spanID == parentSpanID {
spanID = nextSpanID()
}
newSpan := &Span{
trace: trace,
span: api.TraceSpan{
Kind: spanKindClient,
Name: name,
ParentSpanId: parentSpanID,
SpanId: spanID,
},
start: time.Now(),
}
if trace.localOptions&optionStack != 0 {
_ = runtime.Callers(1, newSpan.stack[:])
}
return newSpan
}
// TraceID returns the ID of the trace to which s belongs.
func (s *Span) TraceID() string {
if s == nil {
return ""
}
return s.trace.traceID
}
// SetLabel sets the label for the given key to the given value.
// If the value is empty, the label for that key is deleted.
// If a label is given a value automatically and by SetLabel, the
// automatically-set value is used.
// If s is nil, does nothing.
//
// SetLabel shouldn't be called after Finish or FinishWait.
func (s *Span) SetLabel(key, value string) {
if s == nil {
return
}
if !s.Traced() {
return
}
s.spanMu.Lock()
defer s.spanMu.Unlock()
if value == "" {
if s.span.Labels != nil {
delete(s.span.Labels, key)
}
return
}
if s.span.Labels == nil {
s.span.Labels = make(map[string]string)
}
s.span.Labels[key] = value
}
type FinishOption interface {
modifySpan(s *Span)
}
type withResponse struct {
*http.Response
}
// WithResponse returns an option that can be passed to Finish that indicates
// that some labels for the span should be set using the given *http.Response.
func WithResponse(resp *http.Response) FinishOption {
return withResponse{resp}
}
func (u withResponse) modifySpan(s *Span) {
if u.Response != nil {
s.statusCode = u.StatusCode
}
}
// Finish declares that the span has finished.
//
// If s is nil, Finish does nothing and returns nil.
//
// If the option trace.WithResponse(resp) is passed, then some labels are set
// for s using information in the given *http.Response. This is useful when the
// span is for an outgoing http request; s will typically have been created by
// NewRemoteChild in this case.
//
// If s is a root span (one created by SpanFromRequest) then s, and all its
// descendant spans that have finished, are uploaded to the Google Stackdriver
// Trace server asynchronously.
func (s *Span) Finish(opts ...FinishOption) {
if s == nil {
return
}
if !s.Traced() {
return
}
s.trace.finish(s, false, opts...)
}
// FinishWait is like Finish, but if s is a root span, it waits until uploading
// is finished, then returns an error if one occurred.
func (s *Span) FinishWait(opts ...FinishOption) error {
if s == nil {
return nil
}
if !s.Traced() {
return nil
}
return s.trace.finish(s, true, opts...)
}
func spanHeader(traceID string, spanID uint64, options optionFlags) string {
// See https://cloud.google.com/trace/docs/faq for the header format.
return fmt.Sprintf("%s/%d;o=%d", traceID, spanID, options)
}
func (s *Span) setStackLabel() {
var stack stackLabelValue
lastSigPanic, inTraceLibrary := false, true
for _, pc := range s.stack {
if pc == 0 {
break
}
if !lastSigPanic {
pc--
}
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
// Name has one of the following forms:
// path/to/package.Foo
// path/to/package.(Type).Foo
// For the first form, we store the whole name in the Method field of the
// stack frame. For the second form, we set the Method field to "Foo" and
// the Class field to "path/to/package.(Type)".
name := fn.Name()
if inTraceLibrary && !strings.HasPrefix(name, "cloud.google.com/go/trace.") {
inTraceLibrary = false
}
var class string
if i := strings.Index(name, ")."); i != -1 {
class, name = name[:i+1], name[i+2:]
}
frame := stackFrame{
Class: class,
Method: name,
Filename: file,
Line: int64(line),
}
if inTraceLibrary && len(stack.Frames) == 1 {
stack.Frames[0] = frame
} else {
stack.Frames = append(stack.Frames, frame)
}
lastSigPanic = fn.Name() == "runtime.sigpanic"
}
if label, err := json.Marshal(stack); err == nil {
s.SetLabel(labelStackTrace, string(label))
}
}

954
vendor/cloud.google.com/go/trace/trace_test.go generated vendored Normal file
View File

@@ -0,0 +1,954 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 trace
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"regexp"
"strings"
"sync"
"testing"
"time"
"cloud.google.com/go/datastore"
"cloud.google.com/go/internal/testutil"
"cloud.google.com/go/storage"
"golang.org/x/net/context"
api "google.golang.org/api/cloudtrace/v1"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
dspb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
)
const testProjectID = "testproject"
type fakeRoundTripper struct {
reqc chan *http.Request
}
func newFakeRoundTripper() *fakeRoundTripper {
return &fakeRoundTripper{reqc: make(chan *http.Request)}
}
func (rt *fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
rt.reqc <- r
resp := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("{}")),
}
return resp, nil
}
func newTestClient(rt http.RoundTripper) *Client {
t, err := NewClient(context.Background(), testProjectID, option.WithHTTPClient(&http.Client{Transport: rt}))
if err != nil {
panic(err)
}
return t
}
type fakeDatastoreServer struct {
dspb.DatastoreServer
fail bool
}
func (f *fakeDatastoreServer) Lookup(ctx context.Context, req *dspb.LookupRequest) (*dspb.LookupResponse, error) {
if f.fail {
return nil, errors.New("lookup failed")
}
return &dspb.LookupResponse{}, nil
}
// makeRequests makes some requests.
// span is the root span. rt is the trace client's http client's transport.
// This is used to retrieve the trace uploaded by the client, if any. If
// expectTrace is true, we expect a trace will be uploaded. If synchronous is
// true, the call to Finish is expected not to return before the client has
// uploaded any traces.
func makeRequests(t *testing.T, span *Span, rt *fakeRoundTripper, synchronous bool, expectTrace bool) *http.Request {
ctx := NewContext(context.Background(), span)
tc := newTestClient(&noopTransport{})
// An HTTP request.
{
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
if err != nil {
t.Fatal(err)
}
resp := &http.Response{StatusCode: 200}
s := span.NewRemoteChild(req2)
s.Finish(WithResponse(resp))
}
// An autogenerated API call.
{
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
hc := &http.Client{Transport: rt}
computeClient, err := compute.New(hc)
if err != nil {
t.Fatal(err)
}
_, err = computeClient.Zones.List(testProjectID).Context(ctx).Do()
if err != nil {
t.Fatal(err)
}
}
// A cloud library call that uses the autogenerated API.
{
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
hc := &http.Client{Transport: rt}
storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(hc))
if err != nil {
t.Fatal(err)
}
var objAttrsList []*storage.ObjectAttrs
it := storageClient.Bucket("testbucket").Objects(ctx, nil)
for {
objAttrs, err := it.Next()
if err != nil && err != iterator.Done {
t.Fatal(err)
}
if err == iterator.Done {
break
}
objAttrsList = append(objAttrsList, objAttrs)
}
}
// A cloud library call that uses grpc internally.
for _, fail := range []bool{false, true} {
srv, err := testutil.NewServer()
if err != nil {
t.Fatalf("creating test datastore server: %v", err)
}
dspb.RegisterDatastoreServer(srv.Gsrv, &fakeDatastoreServer{fail: fail})
srv.Start()
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
if err != nil {
t.Fatalf("connecting to test datastore server: %v", err)
}
datastoreClient, err := datastore.NewClient(ctx, testProjectID, option.WithGRPCConn(conn))
if err != nil {
t.Fatalf("creating datastore client: %v", err)
}
k := datastore.NameKey("Entity", "stringID", nil)
e := new(datastore.Entity)
datastoreClient.Get(ctx, k, e)
}
done := make(chan struct{})
go func() {
if synchronous {
err := span.FinishWait()
if err != nil {
t.Errorf("Unexpected error from span.FinishWait: %v", err)
}
} else {
span.Finish()
}
done <- struct{}{}
}()
if !expectTrace {
<-done
select {
case <-rt.reqc:
t.Errorf("Got a trace, expected none.")
case <-time.After(5 * time.Millisecond):
}
return nil
} else if !synchronous {
<-done
return <-rt.reqc
} else {
select {
case <-done:
t.Errorf("Synchronous Finish didn't wait for trace upload.")
return <-rt.reqc
case <-time.After(5 * time.Millisecond):
r := <-rt.reqc
<-done
return r
}
}
}
func TestHeader(t *testing.T) {
tests := []struct {
header string
wantTraceID string
wantSpanID uint64
wantOpts optionFlags
wantOK bool
}{
{
header: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
wantSpanID: 1,
wantOpts: 1,
wantOK: true,
},
{
header: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
wantSpanID: 1,
wantOpts: 0,
wantOK: true,
},
{
header: "0123456789ABCDEF0123456789ABCDEF/1",
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
wantSpanID: 1,
wantOpts: 0,
wantOK: true,
},
{
header: "",
wantTraceID: "",
wantSpanID: 0,
wantOpts: 0,
wantOK: false,
},
}
for _, tt := range tests {
traceID, parentSpanID, opts, _, ok := traceInfoFromHeader(tt.header)
if got, want := traceID, tt.wantTraceID; got != want {
t.Errorf("TraceID(%v) = %q; want %q", tt.header, got, want)
}
if got, want := parentSpanID, tt.wantSpanID; got != want {
t.Errorf("SpanID(%v) = %v; want %v", tt.header, got, want)
}
if got, want := opts, tt.wantOpts; got != want {
t.Errorf("Options(%v) = %v; want %v", tt.header, got, want)
}
if got, want := ok, tt.wantOK; got != want {
t.Errorf("Header exists (%v) = %v; want %v", tt.header, got, want)
}
}
}
func TestOutgoingReqHeader(t *testing.T) {
all, _ := NewLimitedSampler(1, 1<<16) // trace every request
tests := []struct {
desc string
traceHeader string
samplingPolicy SamplingPolicy
wantHeaderRe *regexp.Regexp
}{
{
desc: "Parent span without sampling options, client samples all",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
samplingPolicy: all,
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
},
{
desc: "Parent span without sampling options, without client sampling",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
samplingPolicy: nil,
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
},
{
desc: "Parent span with o=1, client samples none",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
samplingPolicy: nil,
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
},
{
desc: "Parent span with o=0, without client sampling",
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
samplingPolicy: nil,
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
},
}
tc := newTestClient(nil)
for _, tt := range tests {
tc.SetSamplingPolicy(tt.samplingPolicy)
span := tc.SpanFromHeader("/foo", tt.traceHeader)
req, _ := http.NewRequest("GET", "http://localhost", nil)
span.NewRemoteChild(req)
if got, re := req.Header.Get(httpHeader), tt.wantHeaderRe; !re.MatchString(got) {
t.Errorf("%v (parent=%q): got header %q; want in format %q", tt.desc, tt.traceHeader, got, re)
}
}
}
func TestTrace(t *testing.T) {
t.Parallel()
testTrace(t, false, true)
}
func TestTraceWithWait(t *testing.T) {
testTrace(t, true, true)
}
func TestTraceFromHeader(t *testing.T) {
t.Parallel()
testTrace(t, false, false)
}
func TestTraceFromHeaderWithWait(t *testing.T) {
testTrace(t, false, true)
}
func TestNewSpan(t *testing.T) {
const traceID = "0123456789ABCDEF0123456789ABCDEF"
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
span := traceClient.NewSpan("/foo")
span.trace.traceID = traceID
uploaded := makeRequests(t, span, rt, true, true)
if uploaded == nil {
t.Fatalf("No trace uploaded, expected one.")
}
expected := api.Traces{
Traces: []*api.Trace{
{
ProjectId: testProjectID,
Spans: []*api.TraceSpan{
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "example.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "http://example.com/bar",
},
Name: "example.com/bar",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
},
Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
},
Name: "www.googleapis.com/storage/v1/b/testbucket/o",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: nil,
Name: "/google.datastore.v1.Datastore/Lookup",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
Name: "/google.datastore.v1.Datastore/Lookup",
},
{
Kind: "SPAN_KIND_UNSPECIFIED",
Labels: map[string]string{},
Name: "/foo",
},
},
TraceId: traceID,
},
},
}
body, err := ioutil.ReadAll(uploaded.Body)
if err != nil {
t.Fatal(err)
}
var patch api.Traces
err = json.Unmarshal(body, &patch)
if err != nil {
t.Fatal(err)
}
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Fatalf("PatchTraces request: got %s want %s", got, want)
}
n := len(patch.Traces[0].Spans)
rootSpan := patch.Traces[0].Spans[n-1]
for i, s := range patch.Traces[0].Spans {
if a, b := s.StartTime, s.EndTime; a > b {
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
}
if a, b := rootSpan.StartTime, s.StartTime; a > b {
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
}
if a, b := s.EndTime, rootSpan.EndTime; a > b {
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
}
if i > 1 && i < n-1 {
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
}
}
}
if x := rootSpan.ParentSpanId; x != 0 {
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 0)
}
for i, s := range patch.Traces[0].Spans {
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
}
}
for i, s := range patch.Traces[0].Spans {
s.EndTime = ""
labels := &expected.Traces[0].Spans[i].Labels
for key, value := range *labels {
if v, ok := s.Labels[key]; !ok {
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
} else if key == "trace.cloud.google.com/http/url" {
if !strings.HasPrefix(v, value) {
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
}
} else if v != value {
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
}
}
for key := range s.Labels {
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
t.Errorf("Span %d: unexpected label %q", i, key)
}
}
*labels = nil
s.Labels = nil
s.ParentSpanId = 0
if s.SpanId == 0 {
t.Errorf("Incorrect SpanId: got 0 want nonzero")
}
s.SpanId = 0
s.StartTime = ""
}
if !testutil.Equal(patch, expected) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Errorf("PatchTraces request: got %s want %s", got, want)
}
}
func testTrace(t *testing.T, synchronous bool, fromRequest bool) {
const header = `0123456789ABCDEF0123456789ABCDEF/42;o=3`
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
span := traceClient.SpanFromHeader("/foo", header)
headerOrReqLabels := map[string]string{}
headerOrReqName := "/foo"
if fromRequest {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("X-Cloud-Trace-Context", header)
span = traceClient.SpanFromRequest(req)
headerOrReqLabels = map[string]string{
"trace.cloud.google.com/http/host": "example.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/url": "http://example.com/foo",
}
headerOrReqName = "example.com/foo"
}
uploaded := makeRequests(t, span, rt, synchronous, true)
if uploaded == nil {
t.Fatalf("No trace uploaded, expected one.")
}
expected := api.Traces{
Traces: []*api.Trace{
{
ProjectId: testProjectID,
Spans: []*api.TraceSpan{
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "example.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "http://example.com/bar",
},
Name: "example.com/bar",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
},
Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
},
Name: "www.googleapis.com/storage/v1/b/testbucket/o",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: nil,
Name: "/google.datastore.v1.Datastore/Lookup",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
Name: "/google.datastore.v1.Datastore/Lookup",
},
{
Kind: "RPC_SERVER",
Labels: headerOrReqLabels,
Name: headerOrReqName,
},
},
TraceId: "0123456789ABCDEF0123456789ABCDEF",
},
},
}
body, err := ioutil.ReadAll(uploaded.Body)
if err != nil {
t.Fatal(err)
}
var patch api.Traces
err = json.Unmarshal(body, &patch)
if err != nil {
t.Fatal(err)
}
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Fatalf("PatchTraces request: got %s want %s", got, want)
}
n := len(patch.Traces[0].Spans)
rootSpan := patch.Traces[0].Spans[n-1]
for i, s := range patch.Traces[0].Spans {
if a, b := s.StartTime, s.EndTime; a > b {
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
}
if a, b := rootSpan.StartTime, s.StartTime; a > b {
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
}
if a, b := s.EndTime, rootSpan.EndTime; a > b {
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
}
if i > 1 && i < n-1 {
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
}
}
}
if x := rootSpan.ParentSpanId; x != 42 {
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 42)
}
for i, s := range patch.Traces[0].Spans {
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
}
}
for i, s := range patch.Traces[0].Spans {
s.EndTime = ""
labels := &expected.Traces[0].Spans[i].Labels
for key, value := range *labels {
if v, ok := s.Labels[key]; !ok {
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
} else if key == "trace.cloud.google.com/http/url" {
if !strings.HasPrefix(v, value) {
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
}
} else if v != value {
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
}
}
for key := range s.Labels {
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
t.Errorf("Span %d: unexpected label %q", i, key)
}
}
*labels = nil
s.Labels = nil
s.ParentSpanId = 0
if s.SpanId == 0 {
t.Errorf("Incorrect SpanId: got 0 want nonzero")
}
s.SpanId = 0
s.StartTime = ""
}
if !testutil.Equal(patch, expected) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Errorf("PatchTraces request: got %s \n\n want %s", got, want)
}
}
func TestNoTrace(t *testing.T) {
testNoTrace(t, false, true)
}
func TestNoTraceWithWait(t *testing.T) {
testNoTrace(t, true, true)
}
func TestNoTraceFromHeader(t *testing.T) {
testNoTrace(t, false, false)
}
func TestNoTraceFromHeaderWithWait(t *testing.T) {
testNoTrace(t, true, false)
}
func testNoTrace(t *testing.T, synchronous bool, fromRequest bool) {
for _, header := range []string{
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
`0123456789ABCDEF0123456789ABCDEF/42`,
`0123456789ABCDEF0123456789ABCDEF`,
``,
} {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
var span *Span
if fromRequest {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if header != "" {
req.Header.Set("X-Cloud-Trace-Context", header)
}
if err != nil {
t.Fatal(err)
}
span = traceClient.SpanFromRequest(req)
} else {
span = traceClient.SpanFromHeader("/foo", header)
}
uploaded := makeRequests(t, span, rt, synchronous, false)
if uploaded != nil {
t.Errorf("Got a trace, expected none.")
}
}
}
func TestSample(t *testing.T) {
// A deterministic test of the sampler logic.
type testCase struct {
rate float64
maxqps float64
want int
}
const delta = 25 * time.Millisecond
for _, test := range []testCase{
// qps won't matter, so we will sample half of the 79 calls
{0.50, 100, 40},
// with 1 qps and a burst of 2, we will sample twice in second #1, once in the partial second #2
{0.50, 1, 3},
} {
sp, err := NewLimitedSampler(test.rate, test.maxqps)
if err != nil {
t.Fatal(err)
}
s := sp.(*sampler)
sampled := 0
tm := time.Now()
for i := 0; i < 80; i++ {
if s.sample(Parameters{}, tm, float64(i%2)).Sample {
sampled++
}
tm = tm.Add(delta)
}
if sampled != test.want {
t.Errorf("rate=%f, maxqps=%f: got %d samples, want %d", test.rate, test.maxqps, sampled, test.want)
}
}
}
func TestSampling(t *testing.T) {
t.Parallel()
// This scope tests sampling in a larger context, with real time and randomness.
wg := sync.WaitGroup{}
type testCase struct {
rate float64
maxqps float64
expectedRange [2]int
}
for _, test := range []testCase{
{0, 5, [2]int{0, 0}},
{5, 0, [2]int{0, 0}},
{0.50, 100, [2]int{20, 60}},
{0.50, 1, [2]int{3, 4}}, // Windows, with its less precise clock, sometimes gives 4.
} {
wg.Add(1)
go func(test testCase) {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
traceClient.bundler.BundleByteLimit = 1
p, err := NewLimitedSampler(test.rate, test.maxqps)
if err != nil {
t.Fatalf("NewLimitedSampler: %v", err)
}
traceClient.SetSamplingPolicy(p)
ticker := time.NewTicker(25 * time.Millisecond)
sampled := 0
for i := 0; i < 79; i++ {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
span := traceClient.SpanFromRequest(req)
span.Finish()
select {
case <-rt.reqc:
<-ticker.C
sampled++
case <-ticker.C:
}
}
ticker.Stop()
if test.expectedRange[0] > sampled || sampled > test.expectedRange[1] {
t.Errorf("rate=%f, maxqps=%f: got %d samples want ∈ %v", test.rate, test.maxqps, sampled, test.expectedRange)
}
wg.Done()
}(test)
}
wg.Wait()
}
func TestBundling(t *testing.T) {
t.Parallel()
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
traceClient.bundler.DelayThreshold = time.Second / 2
traceClient.bundler.BundleCountThreshold = 10
p, err := NewLimitedSampler(1, 99) // sample every request.
if err != nil {
t.Fatalf("NewLimitedSampler: %v", err)
}
traceClient.SetSamplingPolicy(p)
for i := 0; i < 35; i++ {
go func() {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
span := traceClient.SpanFromRequest(req)
span.Finish()
}()
}
// Read the first three bundles.
<-rt.reqc
<-rt.reqc
<-rt.reqc
// Test that the fourth bundle isn't sent early.
select {
case <-rt.reqc:
t.Errorf("bundle sent too early")
case <-time.After(time.Second / 4):
<-rt.reqc
}
// Test that there aren't extra bundles.
select {
case <-rt.reqc:
t.Errorf("too many bundles sent")
case <-time.After(time.Second):
}
}
func TestWeights(t *testing.T) {
const (
expectedNumTraced = 10100
numTracedEpsilon = 100
expectedTotalWeight = 50000
totalWeightEpsilon = 5000
)
rng := rand.New(rand.NewSource(1))
const delta = 2 * time.Millisecond
for _, headerRate := range []float64{0.0, 0.5, 1.0} {
// Simulate 10 seconds of requests arriving at 500qps.
//
// The sampling policy tries to sample 25% of them, but has a qps limit of
// 100, so it will not be able to. The returned weight should be higher
// for some sampled requests to compensate.
//
// headerRate is the fraction of incoming requests that have a trace header
// set. The qps limit should not be exceeded, even if headerRate is high.
sp, err := NewLimitedSampler(0.25, 100)
if err != nil {
t.Fatal(err)
}
s := sp.(*sampler)
tm := time.Now()
totalWeight := 0.0
numTraced := 0
seenLargeWeight := false
for i := 0; i < 50000; i++ {
d := s.sample(Parameters{HasTraceHeader: rng.Float64() < headerRate}, tm, rng.Float64())
if d.Trace {
numTraced++
}
if d.Sample {
totalWeight += d.Weight
if x := int(d.Weight) / 4; x <= 0 || x >= 100 || d.Weight != float64(x)*4.0 {
t.Errorf("weight: got %f, want a small positive multiple of 4", d.Weight)
}
if d.Weight > 4 {
seenLargeWeight = true
}
}
tm = tm.Add(delta)
}
if !seenLargeWeight {
t.Errorf("headerRate %f: never saw sample weight higher than 4.", headerRate)
}
if numTraced < expectedNumTraced-numTracedEpsilon || expectedNumTraced+numTracedEpsilon < numTraced {
t.Errorf("headerRate %f: got %d traced requests, want ∈ [%d, %d]", headerRate, numTraced, expectedNumTraced-numTracedEpsilon, expectedNumTraced+numTracedEpsilon)
}
if totalWeight < expectedTotalWeight-totalWeightEpsilon || expectedTotalWeight+totalWeightEpsilon < totalWeight {
t.Errorf("headerRate %f: got total weight %f want ∈ [%d, %d]", headerRate, totalWeight, expectedTotalWeight-totalWeightEpsilon, expectedTotalWeight+totalWeightEpsilon)
}
}
}
type alwaysTrace struct{}
func (a alwaysTrace) Sample(p Parameters) Decision {
return Decision{Trace: true}
}
type neverTrace struct{}
func (a neverTrace) Sample(p Parameters) Decision {
return Decision{Trace: false}
}
func TestPropagation(t *testing.T) {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
for _, header := range []string{
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
`0123456789ABCDEF0123456789ABCDEF/42;o=1`,
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
`0123456789ABCDEF0123456789ABCDEF/42;o=3`,
`0123456789ABCDEF0123456789ABCDEF/0;o=0`,
`0123456789ABCDEF0123456789ABCDEF/0;o=1`,
`0123456789ABCDEF0123456789ABCDEF/0;o=2`,
`0123456789ABCDEF0123456789ABCDEF/0;o=3`,
``,
} {
for _, policy := range []SamplingPolicy{
nil,
alwaysTrace{},
neverTrace{},
} {
traceClient.SetSamplingPolicy(policy)
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
if header != "" {
req.Header.Set("X-Cloud-Trace-Context", header)
}
span := traceClient.SpanFromRequest(req)
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
if err != nil {
t.Fatal(err)
}
req3, err := http.NewRequest("GET", "http://example.com/baz", nil)
if err != nil {
t.Fatal(err)
}
span.NewRemoteChild(req2)
span.NewRemoteChild(req3)
var (
t1, t2, t3 string
s1, s2, s3 uint64
o1, o2, o3 uint64
)
fmt.Sscanf(header, "%32s/%d;o=%d", &t1, &s1, &o1)
fmt.Sscanf(req2.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t2, &s2, &o2)
fmt.Sscanf(req3.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t3, &s3, &o3)
if header == "" {
if t2 != t3 {
t.Errorf("expected the same trace ID in child requests, got %q %q", t2, t3)
}
} else {
if t2 != t1 || t3 != t1 {
t.Errorf("trace IDs should be passed to child requests")
}
}
trace := policy == alwaysTrace{} || policy == nil && (o1&1) != 0
if header == "" {
if trace && (s2 == 0 || s3 == 0) {
t.Errorf("got span IDs %d %d in child requests, want nonzero", s2, s3)
}
if trace && s2 == s3 {
t.Errorf("got span IDs %d %d in child requests, should be different", s2, s3)
}
if !trace && (s2 != 0 || s3 != 0) {
t.Errorf("got span IDs %d %d in child requests, want zero", s2, s3)
}
} else {
if trace && (s2 == s1 || s3 == s1 || s2 == s3) {
t.Errorf("parent span IDs in input and outputs should be all different, got %d %d %d", s1, s2, s3)
}
if !trace && (s2 != s1 || s3 != s1) {
t.Errorf("parent span ID in input, %d, should have been equal to parent span IDs in output: %d %d", s1, s2, s3)
}
}
expectTraceOption := policy == alwaysTrace{} || (o1&1) != 0
if expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) {
t.Errorf("tracing flag in child requests should be %t, got options %d %d", expectTraceOption, o2, o3)
}
}
}
}