aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/cloud.google.com/go/trace
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-13 19:31:19 +0200
committerGitHub <noreply@github.com>2017-06-13 19:31:19 +0200
commit5b060131006494cbc077f08b9b2fbf172f3eb239 (patch)
tree04f8586899db96f7fd8e7bc6a010fc10f1e2bb3b /vendor/cloud.google.com/go/trace
parentcd8e13f826ff24f5f8e0b8de1b9d3373aaf93d2f (diff)
parent612b82714b3e6660bf702f801ab96aacb3432e1f (diff)
Merge pull request #226 from google/dvyukov-vendor
vendor: vendor dependencies
Diffstat (limited to 'vendor/cloud.google.com/go/trace')
-rw-r--r--vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go65
-rw-r--r--vendor/cloud.google.com/go/trace/apiv1/doc.go46
-rw-r--r--vendor/cloud.google.com/go/trace/apiv1/mock_test.go314
-rw-r--r--vendor/cloud.google.com/go/trace/apiv1/trace_client.go234
-rw-r--r--vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go89
-rw-r--r--vendor/cloud.google.com/go/trace/grpc.go86
-rw-r--r--vendor/cloud.google.com/go/trace/http.go116
-rw-r--r--vendor/cloud.google.com/go/trace/http_test.go158
-rw-r--r--vendor/cloud.google.com/go/trace/httpexample_test.go50
-rw-r--r--vendor/cloud.google.com/go/trace/sampling.go117
-rw-r--r--vendor/cloud.google.com/go/trace/trace.go812
-rw-r--r--vendor/cloud.google.com/go/trace/trace_test.go954
12 files changed, 3041 insertions, 0 deletions
diff --git a/vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go b/vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go
new file mode 100644
index 000000000..b1e50022a
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go
@@ -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)
+ }
+}
diff --git a/vendor/cloud.google.com/go/trace/apiv1/doc.go b/vendor/cloud.google.com/go/trace/apiv1/doc.go
new file mode 100644
index 000000000..0b0437610
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/apiv1/doc.go
@@ -0,0 +1,46 @@
+// 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
+// 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)
+}
+
+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",
+ }
+}
diff --git a/vendor/cloud.google.com/go/trace/apiv1/mock_test.go b/vendor/cloud.google.com/go/trace/apiv1/mock_test.go
new file mode 100644
index 000000000..b8f338a5b
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/apiv1/mock_test.go
@@ -0,0 +1,314 @@
+// 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"
+)
+
+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 = grpc.Errorf(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 c := grpc.Code(err); 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 = grpc.Errorf(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 c := grpc.Code(err); 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 = grpc.Errorf(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 c := grpc.Code(err); c != errCode {
+ t.Errorf("got error code %q, want %q", c, errCode)
+ }
+ _ = resp
+}
diff --git a/vendor/cloud.google.com/go/trace/apiv1/trace_client.go b/vendor/cloud.google.com/go/trace/apiv1/trace_client.go
new file mode 100644
index 000000000..7560c1643
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/apiv1/trace_client.go
@@ -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
+}
diff --git a/vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go b/vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go
new file mode 100644
index 000000000..733c5918c
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go
@@ -0,0 +1,89 @@
+// 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"
+ 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 != nil {
+ // TODO: Handle error.
+ break
+ }
+ // TODO: Use resp.
+ _ = resp
+ }
+}
diff --git a/vendor/cloud.google.com/go/trace/grpc.go b/vendor/cloud.google.com/go/trace/grpc.go
new file mode 100644
index 000000000..16ecf5f96
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/grpc.go
@@ -0,0 +1,86 @@
+// 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 (
+ "strings"
+
+ "golang.org/x/net/context"
+ "google.golang.org/api/option"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+)
+
+const grpcMetadataKey = "x-cloud-trace-context"
+
+// 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 GRPCClientInterceptor() grpc.UnaryClientInterceptor {
+ return grpc.UnaryClientInterceptor(grpcUnaryInterceptor)
+}
+
+func 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)
+ defer span.Finish()
+
+ if span != nil {
+ header := spanHeader(span.trace.traceID, span.span.ParentSpanId, span.trace.globalOptions)
+ md, ok := metadata.FromOutgoingContext(ctx)
+ if !ok {
+ md = metadata.Pairs(grpcMetadataKey, header)
+ } else {
+ md = md.Copy() // metadata is immutable, copy.
+ md[grpcMetadataKey] = []string{header}
+ }
+ 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 GRPCServerInterceptor(tc *Client) grpc.UnaryServerInterceptor {
+ return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
+ md, _ := metadata.FromIncomingContext(ctx)
+ if header, ok := md[grpcMetadataKey]; ok {
+ span := tc.SpanFromHeader("", strings.Join(header, ""))
+ defer span.Finish()
+ ctx = NewContext(ctx, span)
+ }
+ return handler(ctx, req)
+ }
+}
+
+// EnableGRPCTracing automatically traces all outgoing gRPC calls from cloud.google.com/go clients.
+//
+// The functionality in gRPC that this relies on is currently experimental.
+//
+// Deprecated: Use option.WithGRPCDialOption(grpc.WithUnaryInterceptor(GRPCClientInterceptor())) instead.
+var EnableGRPCTracing option.ClientOption = option.WithGRPCDialOption(grpc.WithUnaryInterceptor(GRPCClientInterceptor()))
diff --git a/vendor/cloud.google.com/go/trace/http.go b/vendor/cloud.google.com/go/trace/http.go
new file mode 100644
index 000000000..072f51e8f
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/http.go
@@ -0,0 +1,116 @@
+// 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"
+
+type tracerTransport struct {
+ base http.RoundTripper
+}
+
+func (tt *tracerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ span := FromContext(req.Context()).NewRemoteChild(req)
+ resp, err := tt.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
+}
+
+// HTTPClient is an HTTP client that enhances http.Client
+// with automatic tracing support.
+type HTTPClient struct {
+ http.Client
+ traceClient *Client
+}
+
+// Do behaves like (*http.Client).Do but automatically traces
+// outgoing requests if tracing is enabled for the current request.
+//
+// If req.Context() contains a traced *Span, the outgoing request
+// is traced with the existing span. If not, the request is not traced.
+func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
+ return c.Client.Do(req)
+}
+
+// NewHTTPClient creates a new HTTPClient that will trace the outgoing
+// requests using tc. The attributes of this client are inherited from the
+// given http.Client. If orig is nil, http.DefaultClient is used.
+func (c *Client) NewHTTPClient(orig *http.Client) *HTTPClient {
+ if orig == nil {
+ orig = http.DefaultClient
+ }
+ rt := orig.Transport
+ if rt == nil {
+ rt = http.DefaultTransport
+ }
+ client := http.Client{
+ Transport: &tracerTransport{base: rt},
+ CheckRedirect: orig.CheckRedirect,
+ Jar: orig.Jar,
+ Timeout: orig.Timeout,
+ }
+ return &HTTPClient{
+ Client: client,
+ traceClient: c,
+ }
+}
+
+// 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)
+
+}
diff --git a/vendor/cloud.google.com/go/trace/http_test.go b/vendor/cloud.google.com/go/trace/http_test.go
new file mode 100644
index 000000000..431159deb
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/http_test.go
@@ -0,0 +1,158 @@
+// 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 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 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 := tc.NewHTTPClient(&http.Client{
+ Transport: 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 := tc.NewHTTPClient(&http.Client{})
+ 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)
+ }
+ }
+}
diff --git a/vendor/cloud.google.com/go/trace/httpexample_test.go b/vendor/cloud.google.com/go/trace/httpexample_test.go
new file mode 100644
index 000000000..e82482d0b
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/httpexample_test.go
@@ -0,0 +1,50 @@
+// 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 := traceClient.NewHTTPClient(nil) // traceClient is a *Client
+
+ req, _ := http.NewRequest("GET", "https://metadata/users", nil)
+ if _, err := client.Do(req); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleClient_HTTPHandler() {
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ client := traceClient.NewHTTPClient(nil)
+
+ 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 *Client
+}
diff --git a/vendor/cloud.google.com/go/trace/sampling.go b/vendor/cloud.google.com/go/trace/sampling.go
new file mode 100644
index 000000000..d609290b9
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/sampling.go
@@ -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
+}
diff --git a/vendor/cloud.google.com/go/trace/trace.go b/vendor/cloud.google.com/go/trace/trace.go
new file mode 100644
index 000000000..28c1e28a9
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/trace.go
@@ -0,0 +1,812 @@
+// 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"
+ "google.golang.org/api/transport"
+)
+
+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 := transport.NewHTTPClient(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
+}
+
+func (s *Span) tracing() bool {
+ 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.tracing() {
+ 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.tracing() {
+ 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.tracing() {
+ 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.tracing() {
+ 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.tracing() {
+ 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))
+ }
+}
diff --git a/vendor/cloud.google.com/go/trace/trace_test.go b/vendor/cloud.google.com/go/trace/trace_test.go
new file mode 100644
index 000000000..995e99eb2
--- /dev/null
+++ b/vendor/cloud.google.com/go/trace/trace_test.go
@@ -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"
+ "reflect"
+ "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)
+
+ // 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(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 !reflect.DeepEqual(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 !reflect.DeepEqual(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)
+ }
+ }
+ }
+}