mirror of
https://github.com/restic/restic.git
synced 2025-08-23 16:17:53 +00:00
Vendor dependencies for GCS
This commit is contained in:
65
vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go
generated
vendored
Normal file
65
vendor/cloud.google.com/go/trace/apiv1/ListTraces_smoke_test.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
|
||||
)
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var _ = iterator.Done
|
||||
var _ = strconv.FormatUint
|
||||
var _ = time.Now
|
||||
|
||||
func TestTraceServiceSmoke(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping smoke test in short mode")
|
||||
}
|
||||
ctx := context.Background()
|
||||
ts := testutil.TokenSource(ctx, DefaultAuthScopes()...)
|
||||
if ts == nil {
|
||||
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
|
||||
}
|
||||
|
||||
projectId := testutil.ProjID()
|
||||
_ = projectId
|
||||
|
||||
c, err := NewClient(ctx, option.WithTokenSource(ts))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var projectId2 string = projectId
|
||||
var request = &cloudtracepb.ListTracesRequest{
|
||||
ProjectId: projectId2,
|
||||
}
|
||||
|
||||
iter := c.ListTraces(ctx, request)
|
||||
if _, err := iter.Next(); err != nil && err != iterator.Done {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
48
vendor/cloud.google.com/go/trace/apiv1/doc.go
generated
vendored
Normal file
48
vendor/cloud.google.com/go/trace/apiv1/doc.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
// Package trace is an experimental, auto-generated package for the
|
||||
// Stackdriver Trace API.
|
||||
//
|
||||
// Send and retrieve trace data from Stackdriver Trace. Data is generated and
|
||||
// available by default for all App Engine applications. Data from other
|
||||
// applications can be written to Stackdriver Trace for display, reporting,
|
||||
// and analysis.
|
||||
//
|
||||
// Use the client at cloud.google.com/go/trace in preference to this.
|
||||
package trace // import "cloud.google.com/go/trace/apiv1"
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func insertXGoog(ctx context.Context, val []string) context.Context {
|
||||
md, _ := metadata.FromOutgoingContext(ctx)
|
||||
md = md.Copy()
|
||||
md["x-goog-api-client"] = val
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the authentication scopes required
|
||||
// by this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/trace.append",
|
||||
"https://www.googleapis.com/auth/trace.readonly",
|
||||
}
|
||||
}
|
321
vendor/cloud.google.com/go/trace/apiv1/mock_test.go
generated
vendored
Normal file
321
vendor/cloud.google.com/go/trace/apiv1/mock_test.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
emptypb "github.com/golang/protobuf/ptypes/empty"
|
||||
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
|
||||
)
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
status "google.golang.org/genproto/googleapis/rpc/status"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
gstatus "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var _ = io.EOF
|
||||
var _ = ptypes.MarshalAny
|
||||
var _ status.Status
|
||||
|
||||
type mockTraceServer struct {
|
||||
// Embed for forward compatibility.
|
||||
// Tests will keep working if more methods are added
|
||||
// in the future.
|
||||
cloudtracepb.TraceServiceServer
|
||||
|
||||
reqs []proto.Message
|
||||
|
||||
// If set, all calls return this error.
|
||||
err error
|
||||
|
||||
// responses to return if err == nil
|
||||
resps []proto.Message
|
||||
}
|
||||
|
||||
func (s *mockTraceServer) ListTraces(ctx context.Context, req *cloudtracepb.ListTracesRequest) (*cloudtracepb.ListTracesResponse, error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
|
||||
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
|
||||
}
|
||||
s.reqs = append(s.reqs, req)
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return s.resps[0].(*cloudtracepb.ListTracesResponse), nil
|
||||
}
|
||||
|
||||
func (s *mockTraceServer) GetTrace(ctx context.Context, req *cloudtracepb.GetTraceRequest) (*cloudtracepb.Trace, error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
|
||||
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
|
||||
}
|
||||
s.reqs = append(s.reqs, req)
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return s.resps[0].(*cloudtracepb.Trace), nil
|
||||
}
|
||||
|
||||
func (s *mockTraceServer) PatchTraces(ctx context.Context, req *cloudtracepb.PatchTracesRequest) (*emptypb.Empty, error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
|
||||
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
|
||||
}
|
||||
s.reqs = append(s.reqs, req)
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return s.resps[0].(*emptypb.Empty), nil
|
||||
}
|
||||
|
||||
// clientOpt is the option tests should use to connect to the test server.
|
||||
// It is initialized by TestMain.
|
||||
var clientOpt option.ClientOption
|
||||
|
||||
var (
|
||||
mockTrace mockTraceServer
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
serv := grpc.NewServer()
|
||||
cloudtracepb.RegisterTraceServiceServer(serv, &mockTrace)
|
||||
|
||||
lis, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go serv.Serve(lis)
|
||||
|
||||
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
clientOpt = option.WithGRPCConn(conn)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestTraceServicePatchTraces(t *testing.T) {
|
||||
var expectedResponse *emptypb.Empty = &emptypb.Empty{}
|
||||
|
||||
mockTrace.err = nil
|
||||
mockTrace.reqs = nil
|
||||
|
||||
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var traces *cloudtracepb.Traces = &cloudtracepb.Traces{}
|
||||
var request = &cloudtracepb.PatchTracesRequest{
|
||||
ProjectId: projectId,
|
||||
Traces: traces,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.PatchTraces(context.Background(), request)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
|
||||
t.Errorf("wrong request %q, want %q", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTraceServicePatchTracesError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockTrace.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var traces *cloudtracepb.Traces = &cloudtracepb.Traces{}
|
||||
var request = &cloudtracepb.PatchTracesRequest{
|
||||
ProjectId: projectId,
|
||||
Traces: traces,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.PatchTraces(context.Background(), request)
|
||||
|
||||
if st, ok := gstatus.FromError(err); !ok {
|
||||
t.Errorf("got error %v, expected grpc error", err)
|
||||
} else if c := st.Code(); c != errCode {
|
||||
t.Errorf("got error code %q, want %q", c, errCode)
|
||||
}
|
||||
}
|
||||
func TestTraceServiceGetTrace(t *testing.T) {
|
||||
var projectId2 string = "projectId2939242356"
|
||||
var traceId2 string = "traceId2987826376"
|
||||
var expectedResponse = &cloudtracepb.Trace{
|
||||
ProjectId: projectId2,
|
||||
TraceId: traceId2,
|
||||
}
|
||||
|
||||
mockTrace.err = nil
|
||||
mockTrace.reqs = nil
|
||||
|
||||
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var traceId string = "traceId1270300245"
|
||||
var request = &cloudtracepb.GetTraceRequest{
|
||||
ProjectId: projectId,
|
||||
TraceId: traceId,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.GetTrace(context.Background(), request)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
|
||||
t.Errorf("wrong request %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
|
||||
t.Errorf("wrong response %q, want %q)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceServiceGetTraceError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockTrace.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var traceId string = "traceId1270300245"
|
||||
var request = &cloudtracepb.GetTraceRequest{
|
||||
ProjectId: projectId,
|
||||
TraceId: traceId,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.GetTrace(context.Background(), request)
|
||||
|
||||
if st, ok := gstatus.FromError(err); !ok {
|
||||
t.Errorf("got error %v, expected grpc error", err)
|
||||
} else if c := st.Code(); c != errCode {
|
||||
t.Errorf("got error code %q, want %q", c, errCode)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
func TestTraceServiceListTraces(t *testing.T) {
|
||||
var nextPageToken string = ""
|
||||
var tracesElement *cloudtracepb.Trace = &cloudtracepb.Trace{}
|
||||
var traces = []*cloudtracepb.Trace{tracesElement}
|
||||
var expectedResponse = &cloudtracepb.ListTracesResponse{
|
||||
NextPageToken: nextPageToken,
|
||||
Traces: traces,
|
||||
}
|
||||
|
||||
mockTrace.err = nil
|
||||
mockTrace.reqs = nil
|
||||
|
||||
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var request = &cloudtracepb.ListTracesRequest{
|
||||
ProjectId: projectId,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.ListTraces(context.Background(), request).Next()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
|
||||
t.Errorf("wrong request %q, want %q", got, want)
|
||||
}
|
||||
|
||||
want := (interface{})(expectedResponse.Traces[0])
|
||||
got := (interface{})(resp)
|
||||
var ok bool
|
||||
|
||||
switch want := (want).(type) {
|
||||
case proto.Message:
|
||||
ok = proto.Equal(want, got.(proto.Message))
|
||||
default:
|
||||
ok = want == got
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("wrong response %q, want %q)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceServiceListTracesError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockTrace.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var projectId string = "projectId-1969970175"
|
||||
var request = &cloudtracepb.ListTracesRequest{
|
||||
ProjectId: projectId,
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), clientOpt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.ListTraces(context.Background(), request).Next()
|
||||
|
||||
if st, ok := gstatus.FromError(err); !ok {
|
||||
t.Errorf("got error %v, expected grpc error", err)
|
||||
} else if c := st.Code(); c != errCode {
|
||||
t.Errorf("got error code %q, want %q", c, errCode)
|
||||
}
|
||||
_ = resp
|
||||
}
|
234
vendor/cloud.google.com/go/trace/apiv1/trace_client.go
generated
vendored
Normal file
234
vendor/cloud.google.com/go/trace/apiv1/trace_client.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/version"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// CallOptions contains the retry settings for each method of Client.
|
||||
type CallOptions struct {
|
||||
PatchTraces []gax.CallOption
|
||||
GetTrace []gax.CallOption
|
||||
ListTraces []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("cloudtrace.googleapis.com:443"),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCallOptions() *CallOptions {
|
||||
retry := map[[2]string][]gax.CallOption{
|
||||
{"default", "idempotent"}: {
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 1000 * time.Millisecond,
|
||||
Multiplier: 1.2,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
return &CallOptions{
|
||||
PatchTraces: retry[[2]string{"default", "idempotent"}],
|
||||
GetTrace: retry[[2]string{"default", "idempotent"}],
|
||||
ListTraces: retry[[2]string{"default", "idempotent"}],
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a client for interacting with Stackdriver Trace API.
|
||||
type Client struct {
|
||||
// The connection to the service.
|
||||
conn *grpc.ClientConn
|
||||
|
||||
// The gRPC API client.
|
||||
client cloudtracepb.TraceServiceClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *CallOptions
|
||||
|
||||
// The metadata to be sent with each request.
|
||||
xGoogHeader []string
|
||||
}
|
||||
|
||||
// NewClient creates a new trace service client.
|
||||
//
|
||||
// This file describes an API for collecting and viewing traces and spans
|
||||
// within a trace. A Trace is a collection of spans corresponding to a single
|
||||
// operation or set of operations for an application. A span is an individual
|
||||
// timed event which forms a node of the trace tree. Spans for a single trace
|
||||
// may span multiple services.
|
||||
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
|
||||
conn, err := transport.DialGRPC(ctx, append(defaultClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
conn: conn,
|
||||
CallOptions: defaultCallOptions(),
|
||||
|
||||
client: cloudtracepb.NewTraceServiceClient(conn),
|
||||
}
|
||||
c.SetGoogleClientInfo()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns the client's connection to the API service.
|
||||
func (c *Client) Connection() *grpc.ClientConn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *Client) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *Client) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogHeader = []string{gax.XGoogHeader(kv...)}
|
||||
}
|
||||
|
||||
// PatchTraces sends new traces to Stackdriver Trace or updates existing traces. If the ID
|
||||
// of a trace that you send matches that of an existing trace, any fields
|
||||
// in the existing trace and its spans are overwritten by the provided values,
|
||||
// and any new fields provided are merged with the existing trace data. If the
|
||||
// ID does not match, a new trace is created.
|
||||
func (c *Client) PatchTraces(ctx context.Context, req *cloudtracepb.PatchTracesRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
opts = append(c.CallOptions.PatchTraces[0:len(c.CallOptions.PatchTraces):len(c.CallOptions.PatchTraces)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.client.PatchTraces(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTrace gets a single trace by its ID.
|
||||
func (c *Client) GetTrace(ctx context.Context, req *cloudtracepb.GetTraceRequest, opts ...gax.CallOption) (*cloudtracepb.Trace, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
opts = append(c.CallOptions.GetTrace[0:len(c.CallOptions.GetTrace):len(c.CallOptions.GetTrace)], opts...)
|
||||
var resp *cloudtracepb.Trace
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.GetTrace(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListTraces returns of a list of traces that match the specified filter conditions.
|
||||
func (c *Client) ListTraces(ctx context.Context, req *cloudtracepb.ListTracesRequest, opts ...gax.CallOption) *TraceIterator {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
opts = append(c.CallOptions.ListTraces[0:len(c.CallOptions.ListTraces):len(c.CallOptions.ListTraces)], opts...)
|
||||
it := &TraceIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*cloudtracepb.Trace, string, error) {
|
||||
var resp *cloudtracepb.ListTracesResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListTraces(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.Traces, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// TraceIterator manages a stream of *cloudtracepb.Trace.
|
||||
type TraceIterator struct {
|
||||
items []*cloudtracepb.Trace
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*cloudtracepb.Trace, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *TraceIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *TraceIterator) Next() (*cloudtracepb.Trace, error) {
|
||||
var item *cloudtracepb.Trace
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *TraceIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *TraceIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
92
vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go
generated
vendored
Normal file
92
vendor/cloud.google.com/go/trace/apiv1/trace_client_example_test.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package trace_test
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/trace/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
|
||||
)
|
||||
|
||||
func ExampleNewClient() {
|
||||
ctx := context.Background()
|
||||
c, err := trace.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use client.
|
||||
_ = c
|
||||
}
|
||||
|
||||
func ExampleClient_PatchTraces() {
|
||||
ctx := context.Background()
|
||||
c, err := trace.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &cloudtracepb.PatchTracesRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.PatchTraces(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_GetTrace() {
|
||||
ctx := context.Background()
|
||||
c, err := trace.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &cloudtracepb.GetTraceRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetTrace(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_ListTraces() {
|
||||
ctx := context.Background()
|
||||
c, err := trace.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &cloudtracepb.ListTracesRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTraces(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
95
vendor/cloud.google.com/go/trace/grpc.go
generated
vendored
Normal file
95
vendor/cloud.google.com/go/trace/grpc.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/internal/tracecontext"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const grpcMetadataKey = "grpc-trace-bin"
|
||||
|
||||
// GRPCClientInterceptor returns a grpc.UnaryClientInterceptor that traces all outgoing requests from a gRPC client.
|
||||
// The calling context should already have a *trace.Span; a child span will be
|
||||
// created for the outgoing gRPC call. If the calling context doesn't have a span,
|
||||
// the call will not be traced.
|
||||
//
|
||||
// The functionality in gRPC that this feature relies on is currently experimental.
|
||||
func (c *Client) GRPCClientInterceptor() grpc.UnaryClientInterceptor {
|
||||
return grpc.UnaryClientInterceptor(c.grpcUnaryInterceptor)
|
||||
}
|
||||
|
||||
func (c *Client) grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
// TODO: also intercept streams.
|
||||
span := FromContext(ctx).NewChild(method)
|
||||
if span == nil {
|
||||
span = c.NewSpan(method)
|
||||
}
|
||||
defer span.Finish()
|
||||
|
||||
traceContext := make([]byte, tracecontext.Len)
|
||||
// traceID is a hex-encoded 128-bit value.
|
||||
// TODO(jbd): Decode trace IDs upon arrival and
|
||||
// represent trace IDs with 16 bytes internally.
|
||||
tid, err := hex.DecodeString(span.trace.traceID)
|
||||
if err != nil {
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
}
|
||||
tracecontext.Encode(traceContext, tid, span.span.SpanId, byte(span.trace.globalOptions))
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.Pairs(grpcMetadataKey, string(traceContext))
|
||||
} else {
|
||||
md = md.Copy() // metadata is immutable, copy.
|
||||
md[grpcMetadataKey] = []string{string(traceContext)}
|
||||
}
|
||||
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||
|
||||
err = invoker(ctx, method, req, reply, cc, opts...)
|
||||
if err != nil {
|
||||
// TODO: standardize gRPC label names?
|
||||
span.SetLabel("error", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GRPCServerInterceptor returns a grpc.UnaryServerInterceptor that enables the tracing of the incoming
|
||||
// gRPC calls. Incoming call's context can be used to extract the span on servers that enabled this option:
|
||||
//
|
||||
// span := trace.FromContext(ctx)
|
||||
//
|
||||
// The functionality in gRPC that this feature relies on is currently experimental.
|
||||
func (c *Client) GRPCServerInterceptor() grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
var traceHeader string
|
||||
if header, ok := md[grpcMetadataKey]; ok {
|
||||
traceID, spanID, opts, ok := tracecontext.Decode([]byte(header[0]))
|
||||
if ok {
|
||||
// TODO(jbd): Generate a span directly from string(traceID), spanID and opts.
|
||||
traceHeader = fmt.Sprintf("%x/%d;o=%d", traceID, spanID, opts)
|
||||
}
|
||||
}
|
||||
span := c.SpanFromHeader(info.FullMethod, traceHeader)
|
||||
defer span.Finish()
|
||||
ctx = NewContext(ctx, span)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
178
vendor/cloud.google.com/go/trace/grpc_test.go
generated
vendored
Normal file
178
vendor/cloud.google.com/go/trace/grpc_test.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pb "cloud.google.com/go/trace/testdata/helloworld"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestGRPCInterceptors(t *testing.T) {
|
||||
tc := newTestClient(&noopTransport{})
|
||||
|
||||
// default sampling with global=1.
|
||||
parent := tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
|
||||
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if got, want := in.TraceID(), out.TraceID(); got != want {
|
||||
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
|
||||
}
|
||||
if !in.Traced() {
|
||||
t.Errorf("incoming span is not traced; want traced")
|
||||
}
|
||||
})
|
||||
|
||||
// default sampling with global=0.
|
||||
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=0")
|
||||
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if got, want := in.TraceID(), out.TraceID(); got != want {
|
||||
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
|
||||
}
|
||||
if in.Traced() {
|
||||
t.Errorf("incoming span is traced; want not traced")
|
||||
}
|
||||
})
|
||||
|
||||
// sampling all with global=1.
|
||||
all, _ := NewLimitedSampler(1.0, 1<<32)
|
||||
tc.SetSamplingPolicy(all)
|
||||
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
|
||||
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if got, want := in.TraceID(), out.TraceID(); got != want {
|
||||
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
|
||||
}
|
||||
if !in.Traced() {
|
||||
t.Errorf("incoming span is not traced; want traced")
|
||||
}
|
||||
})
|
||||
|
||||
// sampling none with global=1.
|
||||
none, _ := NewLimitedSampler(0, 0)
|
||||
tc.SetSamplingPolicy(none)
|
||||
parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1")
|
||||
testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if got, want := in.TraceID(), out.TraceID(); got != want {
|
||||
t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want)
|
||||
}
|
||||
if in.Traced() {
|
||||
t.Errorf("incoming span is traced; want not traced")
|
||||
}
|
||||
})
|
||||
|
||||
// sampling all with no parent span.
|
||||
tc.SetSamplingPolicy(all)
|
||||
testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if in.TraceID() == "" {
|
||||
t.Errorf("incoming call TraceID is empty")
|
||||
}
|
||||
if !in.Traced() {
|
||||
t.Errorf("incoming span is not traced; want traced")
|
||||
}
|
||||
})
|
||||
|
||||
// sampling none with no parent span.
|
||||
tc.SetSamplingPolicy(none)
|
||||
testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) {
|
||||
if in == nil {
|
||||
t.Fatalf("missing span in the incoming context")
|
||||
}
|
||||
if in.TraceID() == "" {
|
||||
t.Errorf("incoming call TraceID is empty")
|
||||
}
|
||||
if in.Traced() {
|
||||
t.Errorf("incoming span is traced; want not traced")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testGRPCInterceptor(t *testing.T, tc *Client, parent *Span, assert func(t *testing.T, out, in *Span)) {
|
||||
incomingCh := make(chan *Span, 1)
|
||||
addrCh := make(chan net.Addr, 1)
|
||||
go func() {
|
||||
lis, err := net.Listen("tcp", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
addrCh <- lis.Addr()
|
||||
|
||||
s := grpc.NewServer(grpc.UnaryInterceptor(tc.GRPCServerInterceptor()))
|
||||
pb.RegisterGreeterServer(s, &grpcServer{
|
||||
fn: func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
incomingCh <- FromContext(ctx)
|
||||
return &pb.HelloReply{}, nil
|
||||
},
|
||||
})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
t.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
addr := <-addrCh
|
||||
conn, err := grpc.Dial(addr.String(), grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
|
||||
if err != nil {
|
||||
t.Fatalf("Did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewGreeterClient(conn)
|
||||
|
||||
outgoingCtx := NewContext(context.Background(), parent)
|
||||
_, err = c.SayHello(outgoingCtx, &pb.HelloRequest{})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not SayHello: %v", err)
|
||||
}
|
||||
|
||||
assert(t, parent, <-incomingCh)
|
||||
}
|
||||
|
||||
type noopTransport struct{}
|
||||
|
||||
func (rt *noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(strings.NewReader("{}")),
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type grpcServer struct {
|
||||
fn func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error)
|
||||
}
|
||||
|
||||
func (s *grpcServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
return s.fn(ctx, in)
|
||||
}
|
105
vendor/cloud.google.com/go/trace/http.go
generated
vendored
Normal file
105
vendor/cloud.google.com/go/trace/http.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Transport is an http.RoundTripper that traces the outgoing requests.
|
||||
//
|
||||
// Transport is safe for concurrent usage.
|
||||
type Transport struct {
|
||||
// Base is the base http.RoundTripper to be used to do the actual request.
|
||||
//
|
||||
// Optional. If nil, http.DefaultTransport is used.
|
||||
Base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.
|
||||
// The created span can follow a parent span, if a parent is presented in
|
||||
// the request's context.
|
||||
func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
span := FromContext(req.Context()).NewRemoteChild(req)
|
||||
resp, err := t.base().RoundTrip(req)
|
||||
|
||||
// TODO(jbd): Is it possible to defer the span.Finish?
|
||||
// In cases where RoundTrip panics, we still can finish the span.
|
||||
span.Finish(WithResponse(resp))
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t Transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (t Transport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// HTTPHandler returns a http.Handler from the given handler
|
||||
// that is aware of the incoming request's span.
|
||||
// The span can be extracted from the incoming request in handler
|
||||
// functions from incoming request's context:
|
||||
//
|
||||
// span := trace.FromContext(r.Context())
|
||||
//
|
||||
// The span will be auto finished by the handler.
|
||||
func (c *Client) HTTPHandler(h http.Handler) http.Handler {
|
||||
return &handler{traceClient: c, handler: h}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
traceClient *Client
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
traceID, parentSpanID, options, optionsOk, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
|
||||
if !ok {
|
||||
traceID = nextTraceID()
|
||||
}
|
||||
t := &trace{
|
||||
traceID: traceID,
|
||||
client: h.traceClient,
|
||||
globalOptions: options,
|
||||
localOptions: options,
|
||||
}
|
||||
span := startNewChildWithRequest(r, t, parentSpanID)
|
||||
span.span.Kind = spanKindServer
|
||||
span.rootSpan = true
|
||||
configureSpanFromPolicy(span, h.traceClient.policy, ok)
|
||||
defer span.Finish()
|
||||
|
||||
r = r.WithContext(NewContext(r.Context(), span))
|
||||
if ok && !optionsOk {
|
||||
// Inject the trace context back to the response with the sampling options.
|
||||
// TODO(jbd): Remove when there is a better way to report the client's sampling.
|
||||
w.Header().Set(httpHeader, spanHeader(traceID, parentSpanID, span.trace.localOptions))
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
|
||||
}
|
151
vendor/cloud.google.com/go/trace/http_test.go
generated
vendored
Normal file
151
vendor/cloud.google.com/go/trace/http_test.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type recorderTransport struct {
|
||||
ch chan *http.Request
|
||||
}
|
||||
|
||||
func (rt *recorderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt.ch <- req
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(strings.NewReader("{}")),
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func TestNewHTTPClient(t *testing.T) {
|
||||
rt := &recorderTransport{
|
||||
ch: make(chan *http.Request, 1),
|
||||
}
|
||||
|
||||
tc := newTestClient(&noopTransport{})
|
||||
client := &http.Client{
|
||||
Transport: &Transport{
|
||||
Base: rt,
|
||||
},
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
|
||||
t.Run("NoTrace", func(t *testing.T) {
|
||||
_, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
outgoing := <-rt.ch
|
||||
if got, want := outgoing.Header.Get(httpHeader), ""; want != got {
|
||||
t.Errorf("got trace header = %q; want none", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Trace", func(t *testing.T) {
|
||||
span := tc.NewSpan("/foo")
|
||||
|
||||
req = req.WithContext(NewContext(req.Context(), span))
|
||||
_, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
outgoing := <-rt.ch
|
||||
|
||||
s := tc.SpanFromHeader("/foo", outgoing.Header.Get(httpHeader))
|
||||
if got, want := s.TraceID(), span.TraceID(); got != want {
|
||||
t.Errorf("trace ID = %q; want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTPHandlerNoTrace(t *testing.T) {
|
||||
tc := newTestClient(&noopTransport{})
|
||||
client := &http.Client{
|
||||
Transport: &Transport{},
|
||||
}
|
||||
handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := FromContext(r.Context())
|
||||
if span == nil {
|
||||
t.Errorf("span is nil; want non-nil span")
|
||||
}
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
_, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPHandler_response(t *testing.T) {
|
||||
tc := newTestClient(&noopTransport{})
|
||||
p, _ := NewLimitedSampler(1, 1<<32) // all
|
||||
tc.SetSamplingPolicy(p)
|
||||
handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
traceHeader string
|
||||
wantTraceHeader string
|
||||
}{
|
||||
{
|
||||
name: "no global",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123",
|
||||
wantTraceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
|
||||
},
|
||||
{
|
||||
name: "global=1",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
|
||||
wantTraceHeader: "",
|
||||
},
|
||||
{
|
||||
name: "global=0",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=0",
|
||||
wantTraceHeader: "",
|
||||
},
|
||||
{
|
||||
name: "no trace context",
|
||||
traceHeader: "",
|
||||
wantTraceHeader: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
req.Header.Set(httpHeader, tt.traceHeader)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("failed to request: %v", err)
|
||||
}
|
||||
if got, want := res.Header.Get(httpHeader), tt.wantTraceHeader; got != want {
|
||||
t.Errorf("%v: response context header = %q; want %q", tt.name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
57
vendor/cloud.google.com/go/trace/httpexample_test.go
generated
vendored
Normal file
57
vendor/cloud.google.com/go/trace/httpexample_test.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package trace_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/trace"
|
||||
)
|
||||
|
||||
var traceClient *trace.Client
|
||||
|
||||
func ExampleHTTPClient_Do() {
|
||||
client := http.Client{
|
||||
Transport: &trace.Transport{},
|
||||
}
|
||||
span := traceClient.NewSpan("/foo") // traceClient is a *trace.Client
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://metadata/users", nil)
|
||||
req = req.WithContext(trace.NewContext(req.Context(), span))
|
||||
|
||||
if _, err := client.Do(req); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_HTTPHandler() {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
client := http.Client{
|
||||
Transport: &trace.Transport{},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://metadata/users", nil)
|
||||
req = req.WithContext(r.Context())
|
||||
|
||||
// The outgoing request will be traced with r's trace ID.
|
||||
if _, err := client.Do(req); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
http.Handle("/foo", traceClient.HTTPHandler(handler)) // traceClient is a *trace.Client
|
||||
}
|
117
vendor/cloud.google.com/go/trace/sampling.go
generated
vendored
Normal file
117
vendor/cloud.google.com/go/trace/sampling.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type SamplingPolicy interface {
|
||||
// Sample returns a Decision.
|
||||
// If Trace is false in the returned Decision, then the Decision should be
|
||||
// the zero value.
|
||||
Sample(p Parameters) Decision
|
||||
}
|
||||
|
||||
// Parameters contains the values passed to a SamplingPolicy's Sample method.
|
||||
type Parameters struct {
|
||||
HasTraceHeader bool // whether the incoming request has a valid X-Cloud-Trace-Context header.
|
||||
}
|
||||
|
||||
// Decision is the value returned by a call to a SamplingPolicy's Sample method.
|
||||
type Decision struct {
|
||||
Trace bool // Whether to trace the request.
|
||||
Sample bool // Whether the trace is included in the random sample.
|
||||
Policy string // Name of the sampling policy.
|
||||
Weight float64 // Sample weight to be used in statistical calculations.
|
||||
}
|
||||
|
||||
type sampler struct {
|
||||
fraction float64
|
||||
skipped float64
|
||||
*rate.Limiter
|
||||
*rand.Rand
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *sampler) Sample(p Parameters) Decision {
|
||||
s.Lock()
|
||||
x := s.Float64()
|
||||
d := s.sample(p, time.Now(), x)
|
||||
s.Unlock()
|
||||
return d
|
||||
}
|
||||
|
||||
// sample contains the a deterministic, time-independent logic of Sample.
|
||||
func (s *sampler) sample(p Parameters, now time.Time, x float64) (d Decision) {
|
||||
d.Sample = x < s.fraction
|
||||
d.Trace = p.HasTraceHeader || d.Sample
|
||||
if !d.Trace {
|
||||
// We have no reason to trace this request.
|
||||
return Decision{}
|
||||
}
|
||||
// We test separately that the rate limit is not tiny before calling AllowN,
|
||||
// because of overflow problems in x/time/rate.
|
||||
if s.Limit() < 1e-9 || !s.AllowN(now, 1) {
|
||||
// Rejected by the rate limit.
|
||||
if d.Sample {
|
||||
s.skipped++
|
||||
}
|
||||
return Decision{}
|
||||
}
|
||||
if d.Sample {
|
||||
d.Policy, d.Weight = "default", (1.0+s.skipped)/s.fraction
|
||||
s.skipped = 0.0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewLimitedSampler returns a sampling policy that randomly samples a given
|
||||
// fraction of requests. It also enforces a limit on the number of traces per
|
||||
// second. It tries to trace every request with a trace header, but will not
|
||||
// exceed the qps limit to do it.
|
||||
func NewLimitedSampler(fraction, maxqps float64) (SamplingPolicy, error) {
|
||||
if !(fraction >= 0) {
|
||||
return nil, fmt.Errorf("invalid fraction %f", fraction)
|
||||
}
|
||||
if !(maxqps >= 0) {
|
||||
return nil, fmt.Errorf("invalid maxqps %f", maxqps)
|
||||
}
|
||||
// Set a limit on the number of accumulated "tokens", to limit bursts of
|
||||
// traced requests. Use one more than a second's worth of tokens, or 100,
|
||||
// whichever is smaller.
|
||||
// See https://godoc.org/golang.org/x/time/rate#NewLimiter.
|
||||
maxTokens := 100
|
||||
if maxqps < 99.0 {
|
||||
maxTokens = 1 + int(maxqps)
|
||||
}
|
||||
var seed int64
|
||||
if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil {
|
||||
seed = time.Now().UnixNano()
|
||||
}
|
||||
s := sampler{
|
||||
fraction: fraction,
|
||||
Limiter: rate.NewLimiter(rate.Limit(maxqps), maxTokens),
|
||||
Rand: rand.New(rand.NewSource(seed)),
|
||||
}
|
||||
return &s, nil
|
||||
}
|
161
vendor/cloud.google.com/go/trace/testdata/helloworld/helloworld.pb.go
generated
vendored
Normal file
161
vendor/cloud.google.com/go/trace/testdata/helloworld/helloworld.pb.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package helloworld is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
helloworld.proto
|
||||
|
||||
It has these top-level messages:
|
||||
HelloRequest
|
||||
HelloReply
|
||||
*/
|
||||
package helloworld
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// The request message containing the user's name.
|
||||
type HelloRequest struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
|
||||
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloRequest) ProtoMessage() {}
|
||||
func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
// The response message containing the greetings
|
||||
type HelloReply struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (m *HelloReply) Reset() { *m = HelloReply{} }
|
||||
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloReply) ProtoMessage() {}
|
||||
func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
|
||||
proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Greeter service
|
||||
|
||||
type GreeterClient interface {
|
||||
// Sends a greeting
|
||||
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
|
||||
}
|
||||
|
||||
type greeterClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
|
||||
return &greeterClient{cc}
|
||||
}
|
||||
|
||||
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
|
||||
out := new(HelloReply)
|
||||
err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Greeter service
|
||||
|
||||
type GreeterServer interface {
|
||||
// Sends a greeting
|
||||
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
|
||||
}
|
||||
|
||||
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
|
||||
s.RegisterService(&_Greeter_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(HelloRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GreeterServer).SayHello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/helloworld.Greeter/SayHello",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Greeter_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "helloworld.Greeter",
|
||||
HandlerType: (*GreeterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SayHello",
|
||||
Handler: _Greeter_SayHello_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "helloworld.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 174 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
|
||||
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
|
||||
0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
|
||||
0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
|
||||
0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
|
||||
0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
|
||||
0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
|
||||
0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7,
|
||||
0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb,
|
||||
0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b,
|
||||
0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
|
||||
}
|
37
vendor/cloud.google.com/go/trace/testdata/helloworld/helloworld.proto
generated
vendored
Normal file
37
vendor/cloud.google.com/go/trace/testdata/helloworld/helloworld.proto
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.grpc.examples.helloworld";
|
||||
option java_outer_classname = "HelloWorldProto";
|
||||
|
||||
package helloworld;
|
||||
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
817
vendor/cloud.google.com/go/trace/trace.go
generated
vendored
Normal file
817
vendor/cloud.google.com/go/trace/trace.go
generated
vendored
Normal file
@@ -0,0 +1,817 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package trace is a Google Stackdriver Trace library.
|
||||
//
|
||||
// This package is still experimental and subject to change.
|
||||
//
|
||||
// See https://cloud.google.com/trace/api/#data_model for a discussion of traces
|
||||
// and spans.
|
||||
//
|
||||
// To initialize a client that connects to the Stackdriver Trace server, use the
|
||||
// NewClient function. Generally you will want to do this on program
|
||||
// initialization.
|
||||
//
|
||||
// import "cloud.google.com/go/trace"
|
||||
// ...
|
||||
// traceClient, err = trace.NewClient(ctx, projectID)
|
||||
//
|
||||
// Calling SpanFromRequest will create a new trace span for an incoming HTTP
|
||||
// request. If the request contains a trace context header, it is used to
|
||||
// determine the trace ID. Otherwise, a new trace ID is created.
|
||||
//
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// span := traceClient.SpanFromRequest(r)
|
||||
// defer span.Finish()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// SpanFromRequest and NewSpan returns nil if the *Client is nil, so you can disable
|
||||
// tracing by not initializing your *Client variable. All of the exported
|
||||
// functions on *Span do nothing when the *Span is nil.
|
||||
//
|
||||
// If you need to start traces that don't correspond to an incoming HTTP request,
|
||||
// you can use NewSpan to create a root-level span.
|
||||
//
|
||||
// span := traceClient.NewSpan("span name")
|
||||
// defer span.Finish()
|
||||
//
|
||||
// Although a trace span object is created for every request, only a subset of
|
||||
// traces are uploaded to the server, for efficiency. By default, the requests
|
||||
// that are traced are those with the tracing bit set in the options field of
|
||||
// the trace context header. Ideally, you should override this behaviour by
|
||||
// calling SetSamplingPolicy. NewLimitedSampler returns an implementation of
|
||||
// SamplingPolicy which traces requests that have the tracing bit set, and also
|
||||
// randomly traces a specified fraction of requests. Additionally, it sets a
|
||||
// limit on the number of requests traced per second. The following example
|
||||
// traces one in every thousand requests, up to a limit of 5 per second.
|
||||
//
|
||||
// p, err := trace.NewLimitedSampler(0.001, 5)
|
||||
// traceClient.SetSamplingPolicy(p)
|
||||
//
|
||||
// You can create a new span as a child of an existing span with NewChild.
|
||||
//
|
||||
// childSpan := span.NewChild(name)
|
||||
// ...
|
||||
// childSpan.Finish()
|
||||
//
|
||||
// When sending an HTTP request to another server, NewRemoteChild will create
|
||||
// a span to represent the time the current program waits for the request to
|
||||
// complete, and attach a header to the outgoing request so that the trace will
|
||||
// be propagated to the destination server.
|
||||
//
|
||||
// childSpan := span.NewRemoteChild(&httpRequest)
|
||||
// ...
|
||||
// childSpan.Finish()
|
||||
//
|
||||
// Alternatively, if you have access to the X-Cloud-Trace-Context header value
|
||||
// but not the underlying HTTP request (this can happen if you are using a
|
||||
// different transport or messaging protocol, such as gRPC), you can use
|
||||
// SpanFromHeader instead of SpanFromRequest. In that case, you will need to
|
||||
// specify the span name explicility, since it cannot be constructed from the
|
||||
// HTTP request's URL and method.
|
||||
//
|
||||
// func handler(r *somepkg.Request) {
|
||||
// span := traceClient.SpanFromHeader("span name", r.TraceContext())
|
||||
// defer span.Finish()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Spans can contain a map from keys to values that have useful information
|
||||
// about the span. The elements of this map are called labels. Some labels,
|
||||
// whose keys all begin with the string "trace.cloud.google.com/", are set
|
||||
// automatically in the following ways:
|
||||
//
|
||||
// - SpanFromRequest sets some labels to data about the incoming request.
|
||||
//
|
||||
// - NewRemoteChild sets some labels to data about the outgoing request.
|
||||
//
|
||||
// - Finish sets a label to a stack trace, if the stack trace option is enabled
|
||||
// in the incoming trace header.
|
||||
//
|
||||
// - The WithResponse option sets some labels to data about a response.
|
||||
// You can also set labels using SetLabel. If a label is given a value
|
||||
// automatically and by SetLabel, the automatically-set value is used.
|
||||
//
|
||||
// span.SetLabel(key, value)
|
||||
//
|
||||
// The WithResponse option can be used when Finish is called.
|
||||
//
|
||||
// childSpan := span.NewRemoteChild(outgoingReq)
|
||||
// resp, err := http.DefaultClient.Do(outgoingReq)
|
||||
// ...
|
||||
// childSpan.Finish(trace.WithResponse(resp))
|
||||
//
|
||||
// When a span created by SpanFromRequest or SpamFromHeader is finished, the
|
||||
// finished spans in the corresponding trace -- the span itself and its
|
||||
// descendants -- are uploaded to the Stackdriver Trace server using the
|
||||
// *Client that created the span. Finish returns immediately, and uploading
|
||||
// occurs asynchronously. You can use the FinishWait function instead to wait
|
||||
// until uploading has finished.
|
||||
//
|
||||
// err := span.FinishWait()
|
||||
//
|
||||
// Using contexts to pass *trace.Span objects through your program will often
|
||||
// be a better approach than passing them around explicitly. This allows trace
|
||||
// spans, and other request-scoped or part-of-request-scoped values, to be
|
||||
// easily passed through API boundaries. Various Google Cloud libraries will
|
||||
// retrieve trace spans from contexts and automatically create child spans for
|
||||
// API requests.
|
||||
// See https://blog.golang.org/context for more discussion of contexts.
|
||||
// A derived context containing a trace span can be created using NewContext.
|
||||
//
|
||||
// span := traceClient.SpanFromRequest(r)
|
||||
// ctx = trace.NewContext(ctx, span)
|
||||
//
|
||||
// The span can be retrieved from a context elsewhere in the program using
|
||||
// FromContext.
|
||||
//
|
||||
// func foo(ctx context.Context) {
|
||||
// span := trace.FromContext(ctx).NewChild("in foo")
|
||||
// defer span.Finish()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
package trace // import "cloud.google.com/go/trace"
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
api "google.golang.org/api/cloudtrace/v1"
|
||||
"google.golang.org/api/gensupport"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/support/bundler"
|
||||
htransport "google.golang.org/api/transport/http"
|
||||
)
|
||||
|
||||
const (
|
||||
httpHeader = `X-Cloud-Trace-Context`
|
||||
userAgent = `gcloud-golang-trace/20160501`
|
||||
cloudPlatformScope = `https://www.googleapis.com/auth/cloud-platform`
|
||||
spanKindClient = `RPC_CLIENT`
|
||||
spanKindServer = `RPC_SERVER`
|
||||
spanKindUnspecified = `SPAN_KIND_UNSPECIFIED`
|
||||
maxStackFrames = 20
|
||||
labelHost = `trace.cloud.google.com/http/host`
|
||||
labelMethod = `trace.cloud.google.com/http/method`
|
||||
labelStackTrace = `trace.cloud.google.com/stacktrace`
|
||||
labelStatusCode = `trace.cloud.google.com/http/status_code`
|
||||
labelURL = `trace.cloud.google.com/http/url`
|
||||
labelSamplingPolicy = `trace.cloud.google.com/sampling_policy`
|
||||
labelSamplingWeight = `trace.cloud.google.com/sampling_weight`
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeTraceAppend grants permissions to write trace data for a project.
|
||||
ScopeTraceAppend = "https://www.googleapis.com/auth/trace.append"
|
||||
|
||||
// ScopeCloudPlatform grants permissions to view and manage your data
|
||||
// across Google Cloud Platform services.
|
||||
ScopeCloudPlatform = "https://www.googleapis.com/auth/cloud-platform"
|
||||
)
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
type stackLabelValue struct {
|
||||
Frames []stackFrame `json:"stack_frame"`
|
||||
}
|
||||
|
||||
type stackFrame struct {
|
||||
Class string `json:"class_name,omitempty"`
|
||||
Method string `json:"method_name"`
|
||||
Filename string `json:"file_name"`
|
||||
Line int64 `json:"line_number"`
|
||||
}
|
||||
|
||||
var (
|
||||
spanIDCounter uint64
|
||||
spanIDIncrement uint64
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Set spanIDCounter and spanIDIncrement to random values. nextSpanID will
|
||||
// return an arithmetic progression using these values, skipping zero. We set
|
||||
// the LSB of spanIDIncrement to 1, so that the cycle length is 2^64.
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &spanIDCounter)
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &spanIDIncrement)
|
||||
spanIDIncrement |= 1
|
||||
// Attach hook for autogenerated Google API calls. This will automatically
|
||||
// create trace spans for API calls if there is a trace in the context.
|
||||
gensupport.RegisterHook(requestHook)
|
||||
}
|
||||
|
||||
func requestHook(ctx context.Context, req *http.Request) func(resp *http.Response) {
|
||||
span := FromContext(ctx)
|
||||
if span == nil || req == nil {
|
||||
return nil
|
||||
}
|
||||
span = span.NewRemoteChild(req)
|
||||
return func(resp *http.Response) {
|
||||
if resp != nil {
|
||||
span.Finish(WithResponse(resp))
|
||||
} else {
|
||||
span.Finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nextSpanID returns a new span ID. It will never return zero.
|
||||
func nextSpanID() uint64 {
|
||||
var id uint64
|
||||
for id == 0 {
|
||||
id = atomic.AddUint64(&spanIDCounter, spanIDIncrement)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// nextTraceID returns a new trace ID.
|
||||
func nextTraceID() string {
|
||||
id1 := nextSpanID()
|
||||
id2 := nextSpanID()
|
||||
return fmt.Sprintf("%016x%016x", id1, id2)
|
||||
}
|
||||
|
||||
// Client is a client for uploading traces to the Google Stackdriver Trace server.
|
||||
type Client struct {
|
||||
service *api.Service
|
||||
projectID string
|
||||
policy SamplingPolicy
|
||||
bundler *bundler.Bundler
|
||||
}
|
||||
|
||||
// NewClient creates a new Google Stackdriver Trace client.
|
||||
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
|
||||
o := []option.ClientOption{
|
||||
option.WithScopes(cloudPlatformScope),
|
||||
option.WithUserAgent(userAgent),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
hc, basePath, err := htransport.NewClient(ctx, o...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating HTTP client for Google Stackdriver Trace API: %v", err)
|
||||
}
|
||||
apiService, err := api.New(hc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating Google Stackdriver Trace API client: %v", err)
|
||||
}
|
||||
if basePath != "" {
|
||||
// An option set a basepath, so override api.New's default.
|
||||
apiService.BasePath = basePath
|
||||
}
|
||||
c := &Client{
|
||||
service: apiService,
|
||||
projectID: projectID,
|
||||
}
|
||||
bundler := bundler.NewBundler((*api.Trace)(nil), func(bundle interface{}) {
|
||||
traces := bundle.([]*api.Trace)
|
||||
err := c.upload(traces)
|
||||
if err != nil {
|
||||
log.Printf("failed to upload %d traces to the Cloud Trace server: %v", len(traces), err)
|
||||
}
|
||||
})
|
||||
bundler.DelayThreshold = 2 * time.Second
|
||||
bundler.BundleCountThreshold = 100
|
||||
// We're not measuring bytes here, we're counting traces and spans as one "byte" each.
|
||||
bundler.BundleByteThreshold = 1000
|
||||
bundler.BundleByteLimit = 1000
|
||||
bundler.BufferedByteLimit = 10000
|
||||
c.bundler = bundler
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// SetSamplingPolicy sets the SamplingPolicy that determines how often traces
|
||||
// are initiated by this client.
|
||||
func (c *Client) SetSamplingPolicy(p SamplingPolicy) {
|
||||
if c != nil {
|
||||
c.policy = p
|
||||
}
|
||||
}
|
||||
|
||||
// SpanFromHeader returns a new trace span, based on a provided request header
|
||||
// value. See https://cloud.google.com/trace/docs/faq.
|
||||
//
|
||||
// It returns nil iff the client is nil.
|
||||
//
|
||||
// The trace information and identifiers will be read from the header value.
|
||||
// Otherwise, a new trace ID is made and the parent span ID is zero.
|
||||
//
|
||||
// The name of the new span is provided as an argument.
|
||||
//
|
||||
// If a non-nil sampling policy has been set in the client, it can override
|
||||
// the options set in the header and choose whether to trace the request.
|
||||
//
|
||||
// If the header doesn't have existing tracing information, then a *Span is
|
||||
// returned anyway, but it will not be uploaded to the server, just as when
|
||||
// calling SpanFromRequest on an untraced request.
|
||||
//
|
||||
// Most users using HTTP should use SpanFromRequest, rather than
|
||||
// SpanFromHeader, since it provides additional functionality for HTTP
|
||||
// requests. In particular, it will set various pieces of request information
|
||||
// as labels on the *Span, which is not available from the header alone.
|
||||
func (c *Client) SpanFromHeader(name string, header string) *Span {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(header)
|
||||
if !ok {
|
||||
traceID = nextTraceID()
|
||||
}
|
||||
t := &trace{
|
||||
traceID: traceID,
|
||||
client: c,
|
||||
globalOptions: options,
|
||||
localOptions: options,
|
||||
}
|
||||
span := startNewChild(name, t, parentSpanID)
|
||||
span.span.Kind = spanKindServer
|
||||
span.rootSpan = true
|
||||
configureSpanFromPolicy(span, c.policy, ok)
|
||||
return span
|
||||
}
|
||||
|
||||
// SpanFromRequest returns a new trace span for an HTTP request.
|
||||
//
|
||||
// It returns nil iff the client is nil.
|
||||
//
|
||||
// If the incoming HTTP request contains a trace context header, the trace ID,
|
||||
// parent span ID, and tracing options will be read from that header.
|
||||
// Otherwise, a new trace ID is made and the parent span ID is zero.
|
||||
//
|
||||
// If a non-nil sampling policy has been set in the client, it can override the
|
||||
// options set in the header and choose whether to trace the request.
|
||||
//
|
||||
// If the request is not being traced, then a *Span is returned anyway, but it
|
||||
// will not be uploaded to the server -- it is only useful for propagating
|
||||
// trace context to child requests and for getting the TraceID. All its
|
||||
// methods can still be called -- the Finish, FinishWait, and SetLabel methods
|
||||
// do nothing. NewChild does nothing, and returns the same *Span. TraceID
|
||||
// works as usual.
|
||||
func (c *Client) SpanFromRequest(r *http.Request) *Span {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
|
||||
if !ok {
|
||||
traceID = nextTraceID()
|
||||
}
|
||||
t := &trace{
|
||||
traceID: traceID,
|
||||
client: c,
|
||||
globalOptions: options,
|
||||
localOptions: options,
|
||||
}
|
||||
span := startNewChildWithRequest(r, t, parentSpanID)
|
||||
span.span.Kind = spanKindServer
|
||||
span.rootSpan = true
|
||||
configureSpanFromPolicy(span, c.policy, ok)
|
||||
return span
|
||||
}
|
||||
|
||||
// NewSpan returns a new trace span with the given name.
|
||||
//
|
||||
// A new trace and span ID is generated to trace the span.
|
||||
// Returned span need to be finished by calling Finish or FinishWait.
|
||||
func (c *Client) NewSpan(name string) *Span {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
t := &trace{
|
||||
traceID: nextTraceID(),
|
||||
client: c,
|
||||
localOptions: optionTrace,
|
||||
globalOptions: optionTrace,
|
||||
}
|
||||
span := startNewChild(name, t, 0)
|
||||
span.span.Kind = spanKindUnspecified
|
||||
span.rootSpan = true
|
||||
configureSpanFromPolicy(span, c.policy, false)
|
||||
return span
|
||||
}
|
||||
|
||||
func configureSpanFromPolicy(s *Span, p SamplingPolicy, ok bool) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
d := p.Sample(Parameters{HasTraceHeader: ok})
|
||||
if d.Trace {
|
||||
// Turn on tracing locally, and in child requests.
|
||||
s.trace.localOptions |= optionTrace
|
||||
s.trace.globalOptions |= optionTrace
|
||||
} else {
|
||||
// Turn off tracing locally.
|
||||
s.trace.localOptions = 0
|
||||
return
|
||||
}
|
||||
if d.Sample {
|
||||
// This trace is in the random sample, so set the labels.
|
||||
s.SetLabel(labelSamplingPolicy, d.Policy)
|
||||
s.SetLabel(labelSamplingWeight, fmt.Sprint(d.Weight))
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext returns a derived context containing the span.
|
||||
func NewContext(ctx context.Context, s *Span) context.Context {
|
||||
if s == nil {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, contextKey{}, s)
|
||||
}
|
||||
|
||||
// FromContext returns the span contained in the context, or nil.
|
||||
func FromContext(ctx context.Context) *Span {
|
||||
s, _ := ctx.Value(contextKey{}).(*Span)
|
||||
return s
|
||||
}
|
||||
|
||||
func traceInfoFromHeader(h string) (traceID string, spanID uint64, options optionFlags, optionsOk bool, ok bool) {
|
||||
// See https://cloud.google.com/trace/docs/faq for the header format.
|
||||
// Return if the header is empty or missing, or if the header is unreasonably
|
||||
// large, to avoid making unnecessary copies of a large string.
|
||||
if h == "" || len(h) > 200 {
|
||||
return "", 0, 0, false, false
|
||||
|
||||
}
|
||||
|
||||
// Parse the trace id field.
|
||||
slash := strings.Index(h, `/`)
|
||||
if slash == -1 {
|
||||
return "", 0, 0, false, false
|
||||
|
||||
}
|
||||
traceID, h = h[:slash], h[slash+1:]
|
||||
|
||||
// Parse the span id field.
|
||||
spanstr := h
|
||||
semicolon := strings.Index(h, `;`)
|
||||
if semicolon != -1 {
|
||||
spanstr, h = h[:semicolon], h[semicolon+1:]
|
||||
}
|
||||
spanID, err := strconv.ParseUint(spanstr, 10, 64)
|
||||
if err != nil {
|
||||
return "", 0, 0, false, false
|
||||
|
||||
}
|
||||
|
||||
// Parse the options field, options field is optional.
|
||||
if !strings.HasPrefix(h, "o=") {
|
||||
return traceID, spanID, 0, false, true
|
||||
|
||||
}
|
||||
o, err := strconv.ParseUint(h[2:], 10, 64)
|
||||
if err != nil {
|
||||
return "", 0, 0, false, false
|
||||
|
||||
}
|
||||
options = optionFlags(o)
|
||||
return traceID, spanID, options, true, true
|
||||
}
|
||||
|
||||
type optionFlags uint32
|
||||
|
||||
const (
|
||||
optionTrace optionFlags = 1 << iota
|
||||
optionStack
|
||||
)
|
||||
|
||||
type trace struct {
|
||||
mu sync.Mutex
|
||||
client *Client
|
||||
traceID string
|
||||
globalOptions optionFlags // options that will be passed to any child requests
|
||||
localOptions optionFlags // options applied in this server
|
||||
spans []*Span // finished spans for this trace.
|
||||
}
|
||||
|
||||
// finish appends s to t.spans. If s is the root span, uploads the trace to the
|
||||
// server.
|
||||
func (t *trace) finish(s *Span, wait bool, opts ...FinishOption) error {
|
||||
for _, o := range opts {
|
||||
o.modifySpan(s)
|
||||
}
|
||||
s.end = time.Now()
|
||||
t.mu.Lock()
|
||||
t.spans = append(t.spans, s)
|
||||
spans := t.spans
|
||||
t.mu.Unlock()
|
||||
if s.rootSpan {
|
||||
if wait {
|
||||
return t.client.upload([]*api.Trace{t.constructTrace(spans)})
|
||||
}
|
||||
go func() {
|
||||
tr := t.constructTrace(spans)
|
||||
err := t.client.bundler.Add(tr, 1+len(spans))
|
||||
if err == bundler.ErrOversizedItem {
|
||||
err = t.client.upload([]*api.Trace{tr})
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("error uploading trace:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trace) constructTrace(spans []*Span) *api.Trace {
|
||||
apiSpans := make([]*api.TraceSpan, len(spans))
|
||||
for i, sp := range spans {
|
||||
sp.span.StartTime = sp.start.In(time.UTC).Format(time.RFC3339Nano)
|
||||
sp.span.EndTime = sp.end.In(time.UTC).Format(time.RFC3339Nano)
|
||||
if t.localOptions&optionStack != 0 {
|
||||
sp.setStackLabel()
|
||||
}
|
||||
sp.SetLabel(labelHost, sp.host)
|
||||
sp.SetLabel(labelURL, sp.url)
|
||||
sp.SetLabel(labelMethod, sp.method)
|
||||
if sp.statusCode != 0 {
|
||||
sp.SetLabel(labelStatusCode, strconv.Itoa(sp.statusCode))
|
||||
}
|
||||
apiSpans[i] = &sp.span
|
||||
}
|
||||
|
||||
return &api.Trace{
|
||||
ProjectId: t.client.projectID,
|
||||
TraceId: t.traceID,
|
||||
Spans: apiSpans,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) upload(traces []*api.Trace) error {
|
||||
_, err := c.service.Projects.PatchTraces(c.projectID, &api.Traces{Traces: traces}).Do()
|
||||
return err
|
||||
}
|
||||
|
||||
// Span contains information about one span of a trace.
|
||||
type Span struct {
|
||||
trace *trace
|
||||
|
||||
spanMu sync.Mutex // guards span.Labels
|
||||
span api.TraceSpan
|
||||
|
||||
start time.Time
|
||||
end time.Time
|
||||
rootSpan bool
|
||||
stack [maxStackFrames]uintptr
|
||||
host string
|
||||
method string
|
||||
url string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// Traced reports whether the current span is sampled to be traced.
|
||||
func (s *Span) Traced() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
return s.trace.localOptions&optionTrace != 0
|
||||
}
|
||||
|
||||
// NewChild creates a new span with the given name as a child of s.
|
||||
// If s is nil, does nothing and returns nil.
|
||||
func (s *Span) NewChild(name string) *Span {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if !s.Traced() {
|
||||
// TODO(jbd): Document this behavior in godoc here and elsewhere.
|
||||
return s
|
||||
}
|
||||
return startNewChild(name, s.trace, s.span.SpanId)
|
||||
}
|
||||
|
||||
// NewRemoteChild creates a new span as a child of s.
|
||||
//
|
||||
// Some labels in the span are set from the outgoing *http.Request r.
|
||||
//
|
||||
// A header is set in r so that the trace context is propagated to the
|
||||
// destination. The parent span ID in that header is set as follows:
|
||||
// - If the request is being traced, then the ID of s is used.
|
||||
// - If the request is not being traced, but there was a trace context header
|
||||
// in the incoming request for this trace (the request passed to
|
||||
// SpanFromRequest), the parent span ID in that header is used.
|
||||
// - Otherwise, the parent span ID is zero.
|
||||
// The tracing bit in the options is set if tracing is enabled, or if it was
|
||||
// set in the incoming request.
|
||||
//
|
||||
// If s is nil, does nothing and returns nil.
|
||||
func (s *Span) NewRemoteChild(r *http.Request) *Span {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if !s.Traced() {
|
||||
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, s.span.ParentSpanId, s.trace.globalOptions)}
|
||||
return s
|
||||
}
|
||||
newSpan := startNewChildWithRequest(r, s.trace, s.span.SpanId)
|
||||
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, newSpan.span.SpanId, s.trace.globalOptions)}
|
||||
return newSpan
|
||||
}
|
||||
|
||||
// Header returns the value of the X-Cloud-Trace-Context header that
|
||||
// should be used to propagate the span. This is the inverse of
|
||||
// SpanFromHeader.
|
||||
//
|
||||
// Most users should use NewRemoteChild unless they have specific
|
||||
// propagation needs or want to control the naming of their span.
|
||||
// Header() does not create a new span.
|
||||
func (s *Span) Header() string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return spanHeader(s.trace.traceID, s.span.SpanId, s.trace.globalOptions)
|
||||
}
|
||||
|
||||
func startNewChildWithRequest(r *http.Request, trace *trace, parentSpanID uint64) *Span {
|
||||
name := r.URL.Host + r.URL.Path // drop scheme and query params
|
||||
newSpan := startNewChild(name, trace, parentSpanID)
|
||||
if r.Host == "" {
|
||||
newSpan.host = r.URL.Host
|
||||
} else {
|
||||
newSpan.host = r.Host
|
||||
}
|
||||
newSpan.method = r.Method
|
||||
newSpan.url = r.URL.String()
|
||||
return newSpan
|
||||
}
|
||||
|
||||
func startNewChild(name string, trace *trace, parentSpanID uint64) *Span {
|
||||
spanID := nextSpanID()
|
||||
for spanID == parentSpanID {
|
||||
spanID = nextSpanID()
|
||||
}
|
||||
newSpan := &Span{
|
||||
trace: trace,
|
||||
span: api.TraceSpan{
|
||||
Kind: spanKindClient,
|
||||
Name: name,
|
||||
ParentSpanId: parentSpanID,
|
||||
SpanId: spanID,
|
||||
},
|
||||
start: time.Now(),
|
||||
}
|
||||
if trace.localOptions&optionStack != 0 {
|
||||
_ = runtime.Callers(1, newSpan.stack[:])
|
||||
}
|
||||
return newSpan
|
||||
}
|
||||
|
||||
// TraceID returns the ID of the trace to which s belongs.
|
||||
func (s *Span) TraceID() string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return s.trace.traceID
|
||||
}
|
||||
|
||||
// SetLabel sets the label for the given key to the given value.
|
||||
// If the value is empty, the label for that key is deleted.
|
||||
// If a label is given a value automatically and by SetLabel, the
|
||||
// automatically-set value is used.
|
||||
// If s is nil, does nothing.
|
||||
//
|
||||
// SetLabel shouldn't be called after Finish or FinishWait.
|
||||
func (s *Span) SetLabel(key, value string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if !s.Traced() {
|
||||
return
|
||||
}
|
||||
s.spanMu.Lock()
|
||||
defer s.spanMu.Unlock()
|
||||
|
||||
if value == "" {
|
||||
if s.span.Labels != nil {
|
||||
delete(s.span.Labels, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
if s.span.Labels == nil {
|
||||
s.span.Labels = make(map[string]string)
|
||||
}
|
||||
s.span.Labels[key] = value
|
||||
}
|
||||
|
||||
type FinishOption interface {
|
||||
modifySpan(s *Span)
|
||||
}
|
||||
|
||||
type withResponse struct {
|
||||
*http.Response
|
||||
}
|
||||
|
||||
// WithResponse returns an option that can be passed to Finish that indicates
|
||||
// that some labels for the span should be set using the given *http.Response.
|
||||
func WithResponse(resp *http.Response) FinishOption {
|
||||
return withResponse{resp}
|
||||
}
|
||||
func (u withResponse) modifySpan(s *Span) {
|
||||
if u.Response != nil {
|
||||
s.statusCode = u.StatusCode
|
||||
}
|
||||
}
|
||||
|
||||
// Finish declares that the span has finished.
|
||||
//
|
||||
// If s is nil, Finish does nothing and returns nil.
|
||||
//
|
||||
// If the option trace.WithResponse(resp) is passed, then some labels are set
|
||||
// for s using information in the given *http.Response. This is useful when the
|
||||
// span is for an outgoing http request; s will typically have been created by
|
||||
// NewRemoteChild in this case.
|
||||
//
|
||||
// If s is a root span (one created by SpanFromRequest) then s, and all its
|
||||
// descendant spans that have finished, are uploaded to the Google Stackdriver
|
||||
// Trace server asynchronously.
|
||||
func (s *Span) Finish(opts ...FinishOption) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if !s.Traced() {
|
||||
return
|
||||
}
|
||||
s.trace.finish(s, false, opts...)
|
||||
}
|
||||
|
||||
// FinishWait is like Finish, but if s is a root span, it waits until uploading
|
||||
// is finished, then returns an error if one occurred.
|
||||
func (s *Span) FinishWait(opts ...FinishOption) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if !s.Traced() {
|
||||
return nil
|
||||
}
|
||||
return s.trace.finish(s, true, opts...)
|
||||
}
|
||||
|
||||
func spanHeader(traceID string, spanID uint64, options optionFlags) string {
|
||||
// See https://cloud.google.com/trace/docs/faq for the header format.
|
||||
return fmt.Sprintf("%s/%d;o=%d", traceID, spanID, options)
|
||||
}
|
||||
|
||||
func (s *Span) setStackLabel() {
|
||||
var stack stackLabelValue
|
||||
lastSigPanic, inTraceLibrary := false, true
|
||||
for _, pc := range s.stack {
|
||||
if pc == 0 {
|
||||
break
|
||||
}
|
||||
if !lastSigPanic {
|
||||
pc--
|
||||
}
|
||||
fn := runtime.FuncForPC(pc)
|
||||
file, line := fn.FileLine(pc)
|
||||
// Name has one of the following forms:
|
||||
// path/to/package.Foo
|
||||
// path/to/package.(Type).Foo
|
||||
// For the first form, we store the whole name in the Method field of the
|
||||
// stack frame. For the second form, we set the Method field to "Foo" and
|
||||
// the Class field to "path/to/package.(Type)".
|
||||
name := fn.Name()
|
||||
if inTraceLibrary && !strings.HasPrefix(name, "cloud.google.com/go/trace.") {
|
||||
inTraceLibrary = false
|
||||
}
|
||||
var class string
|
||||
if i := strings.Index(name, ")."); i != -1 {
|
||||
class, name = name[:i+1], name[i+2:]
|
||||
}
|
||||
frame := stackFrame{
|
||||
Class: class,
|
||||
Method: name,
|
||||
Filename: file,
|
||||
Line: int64(line),
|
||||
}
|
||||
if inTraceLibrary && len(stack.Frames) == 1 {
|
||||
stack.Frames[0] = frame
|
||||
} else {
|
||||
stack.Frames = append(stack.Frames, frame)
|
||||
}
|
||||
lastSigPanic = fn.Name() == "runtime.sigpanic"
|
||||
}
|
||||
if label, err := json.Marshal(stack); err == nil {
|
||||
s.SetLabel(labelStackTrace, string(label))
|
||||
}
|
||||
}
|
954
vendor/cloud.google.com/go/trace/trace_test.go
generated
vendored
Normal file
954
vendor/cloud.google.com/go/trace/trace_test.go
generated
vendored
Normal file
@@ -0,0 +1,954 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/datastore"
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"cloud.google.com/go/storage"
|
||||
"golang.org/x/net/context"
|
||||
api "google.golang.org/api/cloudtrace/v1"
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
dspb "google.golang.org/genproto/googleapis/datastore/v1"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const testProjectID = "testproject"
|
||||
|
||||
type fakeRoundTripper struct {
|
||||
reqc chan *http.Request
|
||||
}
|
||||
|
||||
func newFakeRoundTripper() *fakeRoundTripper {
|
||||
return &fakeRoundTripper{reqc: make(chan *http.Request)}
|
||||
}
|
||||
|
||||
func (rt *fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
rt.reqc <- r
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(strings.NewReader("{}")),
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func newTestClient(rt http.RoundTripper) *Client {
|
||||
t, err := NewClient(context.Background(), testProjectID, option.WithHTTPClient(&http.Client{Transport: rt}))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type fakeDatastoreServer struct {
|
||||
dspb.DatastoreServer
|
||||
fail bool
|
||||
}
|
||||
|
||||
func (f *fakeDatastoreServer) Lookup(ctx context.Context, req *dspb.LookupRequest) (*dspb.LookupResponse, error) {
|
||||
if f.fail {
|
||||
return nil, errors.New("lookup failed")
|
||||
}
|
||||
return &dspb.LookupResponse{}, nil
|
||||
}
|
||||
|
||||
// makeRequests makes some requests.
|
||||
// span is the root span. rt is the trace client's http client's transport.
|
||||
// This is used to retrieve the trace uploaded by the client, if any. If
|
||||
// expectTrace is true, we expect a trace will be uploaded. If synchronous is
|
||||
// true, the call to Finish is expected not to return before the client has
|
||||
// uploaded any traces.
|
||||
func makeRequests(t *testing.T, span *Span, rt *fakeRoundTripper, synchronous bool, expectTrace bool) *http.Request {
|
||||
ctx := NewContext(context.Background(), span)
|
||||
tc := newTestClient(&noopTransport{})
|
||||
|
||||
// An HTTP request.
|
||||
{
|
||||
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp := &http.Response{StatusCode: 200}
|
||||
s := span.NewRemoteChild(req2)
|
||||
s.Finish(WithResponse(resp))
|
||||
}
|
||||
|
||||
// An autogenerated API call.
|
||||
{
|
||||
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
|
||||
hc := &http.Client{Transport: rt}
|
||||
computeClient, err := compute.New(hc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = computeClient.Zones.List(testProjectID).Context(ctx).Do()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// A cloud library call that uses the autogenerated API.
|
||||
{
|
||||
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
|
||||
hc := &http.Client{Transport: rt}
|
||||
storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(hc))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var objAttrsList []*storage.ObjectAttrs
|
||||
it := storageClient.Bucket("testbucket").Objects(ctx, nil)
|
||||
for {
|
||||
objAttrs, err := it.Next()
|
||||
if err != nil && err != iterator.Done {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
objAttrsList = append(objAttrsList, objAttrs)
|
||||
}
|
||||
}
|
||||
|
||||
// A cloud library call that uses grpc internally.
|
||||
for _, fail := range []bool{false, true} {
|
||||
srv, err := testutil.NewServer()
|
||||
if err != nil {
|
||||
t.Fatalf("creating test datastore server: %v", err)
|
||||
}
|
||||
dspb.RegisterDatastoreServer(srv.Gsrv, &fakeDatastoreServer{fail: fail})
|
||||
srv.Start()
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
|
||||
if err != nil {
|
||||
t.Fatalf("connecting to test datastore server: %v", err)
|
||||
}
|
||||
datastoreClient, err := datastore.NewClient(ctx, testProjectID, option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
t.Fatalf("creating datastore client: %v", err)
|
||||
}
|
||||
k := datastore.NameKey("Entity", "stringID", nil)
|
||||
e := new(datastore.Entity)
|
||||
datastoreClient.Get(ctx, k, e)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
if synchronous {
|
||||
err := span.FinishWait()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error from span.FinishWait: %v", err)
|
||||
}
|
||||
} else {
|
||||
span.Finish()
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
if !expectTrace {
|
||||
<-done
|
||||
select {
|
||||
case <-rt.reqc:
|
||||
t.Errorf("Got a trace, expected none.")
|
||||
case <-time.After(5 * time.Millisecond):
|
||||
}
|
||||
return nil
|
||||
} else if !synchronous {
|
||||
<-done
|
||||
return <-rt.reqc
|
||||
} else {
|
||||
select {
|
||||
case <-done:
|
||||
t.Errorf("Synchronous Finish didn't wait for trace upload.")
|
||||
return <-rt.reqc
|
||||
case <-time.After(5 * time.Millisecond):
|
||||
r := <-rt.reqc
|
||||
<-done
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
header string
|
||||
wantTraceID string
|
||||
wantSpanID uint64
|
||||
wantOpts optionFlags
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
header: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
|
||||
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
|
||||
wantSpanID: 1,
|
||||
wantOpts: 1,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
header: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
|
||||
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
|
||||
wantSpanID: 1,
|
||||
wantOpts: 0,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
header: "0123456789ABCDEF0123456789ABCDEF/1",
|
||||
wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
|
||||
wantSpanID: 1,
|
||||
wantOpts: 0,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
header: "",
|
||||
wantTraceID: "",
|
||||
wantSpanID: 0,
|
||||
wantOpts: 0,
|
||||
wantOK: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
traceID, parentSpanID, opts, _, ok := traceInfoFromHeader(tt.header)
|
||||
if got, want := traceID, tt.wantTraceID; got != want {
|
||||
t.Errorf("TraceID(%v) = %q; want %q", tt.header, got, want)
|
||||
}
|
||||
if got, want := parentSpanID, tt.wantSpanID; got != want {
|
||||
t.Errorf("SpanID(%v) = %v; want %v", tt.header, got, want)
|
||||
}
|
||||
if got, want := opts, tt.wantOpts; got != want {
|
||||
t.Errorf("Options(%v) = %v; want %v", tt.header, got, want)
|
||||
}
|
||||
if got, want := ok, tt.wantOK; got != want {
|
||||
t.Errorf("Header exists (%v) = %v; want %v", tt.header, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutgoingReqHeader(t *testing.T) {
|
||||
all, _ := NewLimitedSampler(1, 1<<16) // trace every request
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
traceHeader string
|
||||
samplingPolicy SamplingPolicy
|
||||
|
||||
wantHeaderRe *regexp.Regexp
|
||||
}{
|
||||
{
|
||||
desc: "Parent span without sampling options, client samples all",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
|
||||
samplingPolicy: all,
|
||||
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
|
||||
},
|
||||
{
|
||||
desc: "Parent span without sampling options, without client sampling",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
|
||||
samplingPolicy: nil,
|
||||
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
|
||||
},
|
||||
{
|
||||
desc: "Parent span with o=1, client samples none",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
|
||||
samplingPolicy: nil,
|
||||
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
|
||||
},
|
||||
{
|
||||
desc: "Parent span with o=0, without client sampling",
|
||||
traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
|
||||
samplingPolicy: nil,
|
||||
wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
|
||||
},
|
||||
}
|
||||
|
||||
tc := newTestClient(nil)
|
||||
for _, tt := range tests {
|
||||
tc.SetSamplingPolicy(tt.samplingPolicy)
|
||||
span := tc.SpanFromHeader("/foo", tt.traceHeader)
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost", nil)
|
||||
span.NewRemoteChild(req)
|
||||
|
||||
if got, re := req.Header.Get(httpHeader), tt.wantHeaderRe; !re.MatchString(got) {
|
||||
t.Errorf("%v (parent=%q): got header %q; want in format %q", tt.desc, tt.traceHeader, got, re)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrace(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTrace(t, false, true)
|
||||
}
|
||||
|
||||
func TestTraceWithWait(t *testing.T) {
|
||||
testTrace(t, true, true)
|
||||
}
|
||||
|
||||
func TestTraceFromHeader(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTrace(t, false, false)
|
||||
}
|
||||
|
||||
func TestTraceFromHeaderWithWait(t *testing.T) {
|
||||
testTrace(t, false, true)
|
||||
}
|
||||
|
||||
func TestNewSpan(t *testing.T) {
|
||||
const traceID = "0123456789ABCDEF0123456789ABCDEF"
|
||||
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
span := traceClient.NewSpan("/foo")
|
||||
span.trace.traceID = traceID
|
||||
|
||||
uploaded := makeRequests(t, span, rt, true, true)
|
||||
|
||||
if uploaded == nil {
|
||||
t.Fatalf("No trace uploaded, expected one.")
|
||||
}
|
||||
|
||||
expected := api.Traces{
|
||||
Traces: []*api.Trace{
|
||||
{
|
||||
ProjectId: testProjectID,
|
||||
Spans: []*api.TraceSpan{
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "example.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "http://example.com/bar",
|
||||
},
|
||||
Name: "example.com/bar",
|
||||
},
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "www.googleapis.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
|
||||
},
|
||||
Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
|
||||
},
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "www.googleapis.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
|
||||
},
|
||||
Name: "www.googleapis.com/storage/v1/b/testbucket/o",
|
||||
},
|
||||
&api.TraceSpan{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: nil,
|
||||
Name: "/google.datastore.v1.Datastore/Lookup",
|
||||
},
|
||||
&api.TraceSpan{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
|
||||
Name: "/google.datastore.v1.Datastore/Lookup",
|
||||
},
|
||||
{
|
||||
Kind: "SPAN_KIND_UNSPECIFIED",
|
||||
Labels: map[string]string{},
|
||||
Name: "/foo",
|
||||
},
|
||||
},
|
||||
TraceId: traceID,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(uploaded.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var patch api.Traces
|
||||
err = json.Unmarshal(body, &patch)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
|
||||
got, _ := json.Marshal(patch)
|
||||
want, _ := json.Marshal(expected)
|
||||
t.Fatalf("PatchTraces request: got %s want %s", got, want)
|
||||
}
|
||||
|
||||
n := len(patch.Traces[0].Spans)
|
||||
rootSpan := patch.Traces[0].Spans[n-1]
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
if a, b := s.StartTime, s.EndTime; a > b {
|
||||
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
|
||||
}
|
||||
if a, b := rootSpan.StartTime, s.StartTime; a > b {
|
||||
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
|
||||
}
|
||||
if a, b := s.EndTime, rootSpan.EndTime; a > b {
|
||||
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
|
||||
}
|
||||
if i > 1 && i < n-1 {
|
||||
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
|
||||
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if x := rootSpan.ParentSpanId; x != 0 {
|
||||
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 0)
|
||||
}
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
|
||||
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
|
||||
}
|
||||
}
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
s.EndTime = ""
|
||||
labels := &expected.Traces[0].Spans[i].Labels
|
||||
for key, value := range *labels {
|
||||
if v, ok := s.Labels[key]; !ok {
|
||||
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
|
||||
} else if key == "trace.cloud.google.com/http/url" {
|
||||
if !strings.HasPrefix(v, value) {
|
||||
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
|
||||
}
|
||||
} else if v != value {
|
||||
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
|
||||
}
|
||||
}
|
||||
for key := range s.Labels {
|
||||
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
|
||||
t.Errorf("Span %d: unexpected label %q", i, key)
|
||||
}
|
||||
}
|
||||
*labels = nil
|
||||
s.Labels = nil
|
||||
s.ParentSpanId = 0
|
||||
if s.SpanId == 0 {
|
||||
t.Errorf("Incorrect SpanId: got 0 want nonzero")
|
||||
}
|
||||
s.SpanId = 0
|
||||
s.StartTime = ""
|
||||
}
|
||||
if !testutil.Equal(patch, expected) {
|
||||
got, _ := json.Marshal(patch)
|
||||
want, _ := json.Marshal(expected)
|
||||
t.Errorf("PatchTraces request: got %s want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testTrace(t *testing.T, synchronous bool, fromRequest bool) {
|
||||
const header = `0123456789ABCDEF0123456789ABCDEF/42;o=3`
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
|
||||
span := traceClient.SpanFromHeader("/foo", header)
|
||||
headerOrReqLabels := map[string]string{}
|
||||
headerOrReqName := "/foo"
|
||||
|
||||
if fromRequest {
|
||||
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("X-Cloud-Trace-Context", header)
|
||||
span = traceClient.SpanFromRequest(req)
|
||||
headerOrReqLabels = map[string]string{
|
||||
"trace.cloud.google.com/http/host": "example.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/url": "http://example.com/foo",
|
||||
}
|
||||
headerOrReqName = "example.com/foo"
|
||||
}
|
||||
|
||||
uploaded := makeRequests(t, span, rt, synchronous, true)
|
||||
if uploaded == nil {
|
||||
t.Fatalf("No trace uploaded, expected one.")
|
||||
}
|
||||
|
||||
expected := api.Traces{
|
||||
Traces: []*api.Trace{
|
||||
{
|
||||
ProjectId: testProjectID,
|
||||
Spans: []*api.TraceSpan{
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "example.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "http://example.com/bar",
|
||||
},
|
||||
Name: "example.com/bar",
|
||||
},
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "www.googleapis.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
|
||||
},
|
||||
Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
|
||||
},
|
||||
{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{
|
||||
"trace.cloud.google.com/http/host": "www.googleapis.com",
|
||||
"trace.cloud.google.com/http/method": "GET",
|
||||
"trace.cloud.google.com/http/status_code": "200",
|
||||
"trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
|
||||
},
|
||||
Name: "www.googleapis.com/storage/v1/b/testbucket/o",
|
||||
},
|
||||
&api.TraceSpan{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: nil,
|
||||
Name: "/google.datastore.v1.Datastore/Lookup",
|
||||
},
|
||||
&api.TraceSpan{
|
||||
Kind: "RPC_CLIENT",
|
||||
Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
|
||||
Name: "/google.datastore.v1.Datastore/Lookup",
|
||||
},
|
||||
{
|
||||
Kind: "RPC_SERVER",
|
||||
Labels: headerOrReqLabels,
|
||||
Name: headerOrReqName,
|
||||
},
|
||||
},
|
||||
TraceId: "0123456789ABCDEF0123456789ABCDEF",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(uploaded.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var patch api.Traces
|
||||
err = json.Unmarshal(body, &patch)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
|
||||
got, _ := json.Marshal(patch)
|
||||
want, _ := json.Marshal(expected)
|
||||
t.Fatalf("PatchTraces request: got %s want %s", got, want)
|
||||
}
|
||||
|
||||
n := len(patch.Traces[0].Spans)
|
||||
rootSpan := patch.Traces[0].Spans[n-1]
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
if a, b := s.StartTime, s.EndTime; a > b {
|
||||
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
|
||||
}
|
||||
if a, b := rootSpan.StartTime, s.StartTime; a > b {
|
||||
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
|
||||
}
|
||||
if a, b := s.EndTime, rootSpan.EndTime; a > b {
|
||||
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
|
||||
}
|
||||
if i > 1 && i < n-1 {
|
||||
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
|
||||
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if x := rootSpan.ParentSpanId; x != 42 {
|
||||
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 42)
|
||||
}
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
|
||||
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
|
||||
}
|
||||
}
|
||||
for i, s := range patch.Traces[0].Spans {
|
||||
s.EndTime = ""
|
||||
labels := &expected.Traces[0].Spans[i].Labels
|
||||
for key, value := range *labels {
|
||||
if v, ok := s.Labels[key]; !ok {
|
||||
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
|
||||
} else if key == "trace.cloud.google.com/http/url" {
|
||||
if !strings.HasPrefix(v, value) {
|
||||
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
|
||||
}
|
||||
} else if v != value {
|
||||
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
|
||||
}
|
||||
}
|
||||
for key := range s.Labels {
|
||||
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
|
||||
t.Errorf("Span %d: unexpected label %q", i, key)
|
||||
}
|
||||
}
|
||||
*labels = nil
|
||||
s.Labels = nil
|
||||
s.ParentSpanId = 0
|
||||
if s.SpanId == 0 {
|
||||
t.Errorf("Incorrect SpanId: got 0 want nonzero")
|
||||
}
|
||||
s.SpanId = 0
|
||||
s.StartTime = ""
|
||||
}
|
||||
if !testutil.Equal(patch, expected) {
|
||||
got, _ := json.Marshal(patch)
|
||||
want, _ := json.Marshal(expected)
|
||||
t.Errorf("PatchTraces request: got %s \n\n want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoTrace(t *testing.T) {
|
||||
testNoTrace(t, false, true)
|
||||
}
|
||||
|
||||
func TestNoTraceWithWait(t *testing.T) {
|
||||
testNoTrace(t, true, true)
|
||||
}
|
||||
|
||||
func TestNoTraceFromHeader(t *testing.T) {
|
||||
testNoTrace(t, false, false)
|
||||
}
|
||||
|
||||
func TestNoTraceFromHeaderWithWait(t *testing.T) {
|
||||
testNoTrace(t, true, false)
|
||||
}
|
||||
|
||||
func testNoTrace(t *testing.T, synchronous bool, fromRequest bool) {
|
||||
for _, header := range []string{
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/42`,
|
||||
`0123456789ABCDEF0123456789ABCDEF`,
|
||||
``,
|
||||
} {
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
var span *Span
|
||||
if fromRequest {
|
||||
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
if header != "" {
|
||||
req.Header.Set("X-Cloud-Trace-Context", header)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
span = traceClient.SpanFromRequest(req)
|
||||
} else {
|
||||
span = traceClient.SpanFromHeader("/foo", header)
|
||||
}
|
||||
uploaded := makeRequests(t, span, rt, synchronous, false)
|
||||
if uploaded != nil {
|
||||
t.Errorf("Got a trace, expected none.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSample(t *testing.T) {
|
||||
// A deterministic test of the sampler logic.
|
||||
type testCase struct {
|
||||
rate float64
|
||||
maxqps float64
|
||||
want int
|
||||
}
|
||||
const delta = 25 * time.Millisecond
|
||||
for _, test := range []testCase{
|
||||
// qps won't matter, so we will sample half of the 79 calls
|
||||
{0.50, 100, 40},
|
||||
// with 1 qps and a burst of 2, we will sample twice in second #1, once in the partial second #2
|
||||
{0.50, 1, 3},
|
||||
} {
|
||||
sp, err := NewLimitedSampler(test.rate, test.maxqps)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := sp.(*sampler)
|
||||
sampled := 0
|
||||
tm := time.Now()
|
||||
for i := 0; i < 80; i++ {
|
||||
if s.sample(Parameters{}, tm, float64(i%2)).Sample {
|
||||
sampled++
|
||||
}
|
||||
tm = tm.Add(delta)
|
||||
}
|
||||
if sampled != test.want {
|
||||
t.Errorf("rate=%f, maxqps=%f: got %d samples, want %d", test.rate, test.maxqps, sampled, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSampling(t *testing.T) {
|
||||
t.Parallel()
|
||||
// This scope tests sampling in a larger context, with real time and randomness.
|
||||
wg := sync.WaitGroup{}
|
||||
type testCase struct {
|
||||
rate float64
|
||||
maxqps float64
|
||||
expectedRange [2]int
|
||||
}
|
||||
for _, test := range []testCase{
|
||||
{0, 5, [2]int{0, 0}},
|
||||
{5, 0, [2]int{0, 0}},
|
||||
{0.50, 100, [2]int{20, 60}},
|
||||
{0.50, 1, [2]int{3, 4}}, // Windows, with its less precise clock, sometimes gives 4.
|
||||
} {
|
||||
wg.Add(1)
|
||||
go func(test testCase) {
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
traceClient.bundler.BundleByteLimit = 1
|
||||
p, err := NewLimitedSampler(test.rate, test.maxqps)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLimitedSampler: %v", err)
|
||||
}
|
||||
traceClient.SetSamplingPolicy(p)
|
||||
ticker := time.NewTicker(25 * time.Millisecond)
|
||||
sampled := 0
|
||||
for i := 0; i < 79; i++ {
|
||||
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
span := traceClient.SpanFromRequest(req)
|
||||
span.Finish()
|
||||
select {
|
||||
case <-rt.reqc:
|
||||
<-ticker.C
|
||||
sampled++
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
ticker.Stop()
|
||||
if test.expectedRange[0] > sampled || sampled > test.expectedRange[1] {
|
||||
t.Errorf("rate=%f, maxqps=%f: got %d samples want ∈ %v", test.rate, test.maxqps, sampled, test.expectedRange)
|
||||
}
|
||||
wg.Done()
|
||||
}(test)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestBundling(t *testing.T) {
|
||||
t.Parallel()
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
traceClient.bundler.DelayThreshold = time.Second / 2
|
||||
traceClient.bundler.BundleCountThreshold = 10
|
||||
p, err := NewLimitedSampler(1, 99) // sample every request.
|
||||
if err != nil {
|
||||
t.Fatalf("NewLimitedSampler: %v", err)
|
||||
}
|
||||
traceClient.SetSamplingPolicy(p)
|
||||
|
||||
for i := 0; i < 35; i++ {
|
||||
go func() {
|
||||
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
span := traceClient.SpanFromRequest(req)
|
||||
span.Finish()
|
||||
}()
|
||||
}
|
||||
|
||||
// Read the first three bundles.
|
||||
<-rt.reqc
|
||||
<-rt.reqc
|
||||
<-rt.reqc
|
||||
|
||||
// Test that the fourth bundle isn't sent early.
|
||||
select {
|
||||
case <-rt.reqc:
|
||||
t.Errorf("bundle sent too early")
|
||||
case <-time.After(time.Second / 4):
|
||||
<-rt.reqc
|
||||
}
|
||||
|
||||
// Test that there aren't extra bundles.
|
||||
select {
|
||||
case <-rt.reqc:
|
||||
t.Errorf("too many bundles sent")
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeights(t *testing.T) {
|
||||
const (
|
||||
expectedNumTraced = 10100
|
||||
numTracedEpsilon = 100
|
||||
expectedTotalWeight = 50000
|
||||
totalWeightEpsilon = 5000
|
||||
)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
const delta = 2 * time.Millisecond
|
||||
for _, headerRate := range []float64{0.0, 0.5, 1.0} {
|
||||
// Simulate 10 seconds of requests arriving at 500qps.
|
||||
//
|
||||
// The sampling policy tries to sample 25% of them, but has a qps limit of
|
||||
// 100, so it will not be able to. The returned weight should be higher
|
||||
// for some sampled requests to compensate.
|
||||
//
|
||||
// headerRate is the fraction of incoming requests that have a trace header
|
||||
// set. The qps limit should not be exceeded, even if headerRate is high.
|
||||
sp, err := NewLimitedSampler(0.25, 100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := sp.(*sampler)
|
||||
tm := time.Now()
|
||||
totalWeight := 0.0
|
||||
numTraced := 0
|
||||
seenLargeWeight := false
|
||||
for i := 0; i < 50000; i++ {
|
||||
d := s.sample(Parameters{HasTraceHeader: rng.Float64() < headerRate}, tm, rng.Float64())
|
||||
if d.Trace {
|
||||
numTraced++
|
||||
}
|
||||
if d.Sample {
|
||||
totalWeight += d.Weight
|
||||
if x := int(d.Weight) / 4; x <= 0 || x >= 100 || d.Weight != float64(x)*4.0 {
|
||||
t.Errorf("weight: got %f, want a small positive multiple of 4", d.Weight)
|
||||
}
|
||||
if d.Weight > 4 {
|
||||
seenLargeWeight = true
|
||||
}
|
||||
}
|
||||
tm = tm.Add(delta)
|
||||
}
|
||||
if !seenLargeWeight {
|
||||
t.Errorf("headerRate %f: never saw sample weight higher than 4.", headerRate)
|
||||
}
|
||||
if numTraced < expectedNumTraced-numTracedEpsilon || expectedNumTraced+numTracedEpsilon < numTraced {
|
||||
t.Errorf("headerRate %f: got %d traced requests, want ∈ [%d, %d]", headerRate, numTraced, expectedNumTraced-numTracedEpsilon, expectedNumTraced+numTracedEpsilon)
|
||||
}
|
||||
if totalWeight < expectedTotalWeight-totalWeightEpsilon || expectedTotalWeight+totalWeightEpsilon < totalWeight {
|
||||
t.Errorf("headerRate %f: got total weight %f want ∈ [%d, %d]", headerRate, totalWeight, expectedTotalWeight-totalWeightEpsilon, expectedTotalWeight+totalWeightEpsilon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type alwaysTrace struct{}
|
||||
|
||||
func (a alwaysTrace) Sample(p Parameters) Decision {
|
||||
return Decision{Trace: true}
|
||||
}
|
||||
|
||||
type neverTrace struct{}
|
||||
|
||||
func (a neverTrace) Sample(p Parameters) Decision {
|
||||
return Decision{Trace: false}
|
||||
}
|
||||
|
||||
func TestPropagation(t *testing.T) {
|
||||
rt := newFakeRoundTripper()
|
||||
traceClient := newTestClient(rt)
|
||||
for _, header := range []string{
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=1`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/42;o=3`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/0;o=0`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/0;o=1`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/0;o=2`,
|
||||
`0123456789ABCDEF0123456789ABCDEF/0;o=3`,
|
||||
``,
|
||||
} {
|
||||
for _, policy := range []SamplingPolicy{
|
||||
nil,
|
||||
alwaysTrace{},
|
||||
neverTrace{},
|
||||
} {
|
||||
traceClient.SetSamplingPolicy(policy)
|
||||
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if header != "" {
|
||||
req.Header.Set("X-Cloud-Trace-Context", header)
|
||||
}
|
||||
|
||||
span := traceClient.SpanFromRequest(req)
|
||||
|
||||
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req3, err := http.NewRequest("GET", "http://example.com/baz", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
span.NewRemoteChild(req2)
|
||||
span.NewRemoteChild(req3)
|
||||
|
||||
var (
|
||||
t1, t2, t3 string
|
||||
s1, s2, s3 uint64
|
||||
o1, o2, o3 uint64
|
||||
)
|
||||
fmt.Sscanf(header, "%32s/%d;o=%d", &t1, &s1, &o1)
|
||||
fmt.Sscanf(req2.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t2, &s2, &o2)
|
||||
fmt.Sscanf(req3.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t3, &s3, &o3)
|
||||
|
||||
if header == "" {
|
||||
if t2 != t3 {
|
||||
t.Errorf("expected the same trace ID in child requests, got %q %q", t2, t3)
|
||||
}
|
||||
} else {
|
||||
if t2 != t1 || t3 != t1 {
|
||||
t.Errorf("trace IDs should be passed to child requests")
|
||||
}
|
||||
}
|
||||
trace := policy == alwaysTrace{} || policy == nil && (o1&1) != 0
|
||||
if header == "" {
|
||||
if trace && (s2 == 0 || s3 == 0) {
|
||||
t.Errorf("got span IDs %d %d in child requests, want nonzero", s2, s3)
|
||||
}
|
||||
if trace && s2 == s3 {
|
||||
t.Errorf("got span IDs %d %d in child requests, should be different", s2, s3)
|
||||
}
|
||||
if !trace && (s2 != 0 || s3 != 0) {
|
||||
t.Errorf("got span IDs %d %d in child requests, want zero", s2, s3)
|
||||
}
|
||||
} else {
|
||||
if trace && (s2 == s1 || s3 == s1 || s2 == s3) {
|
||||
t.Errorf("parent span IDs in input and outputs should be all different, got %d %d %d", s1, s2, s3)
|
||||
}
|
||||
if !trace && (s2 != s1 || s3 != s1) {
|
||||
t.Errorf("parent span ID in input, %d, should have been equal to parent span IDs in output: %d %d", s1, s2, s3)
|
||||
}
|
||||
}
|
||||
expectTraceOption := policy == alwaysTrace{} || (o1&1) != 0
|
||||
if expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) {
|
||||
t.Errorf("tracing flag in child requests should be %t, got options %d %d", expectTraceOption, o2, o3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user