Switch to using the dep tool and update all the dependencies

This commit is contained in:
Nick Craig-Wood
2017-05-11 15:39:54 +01:00
parent 5135ff73cb
commit 98c2d2c41b
5321 changed files with 4483197 additions and 5918 deletions
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// AUTO-GENERATED CODE. DO NOT EDIT.
// Package trace is an experimental, auto-generated package for the
// trace API.
//
// Send and retrieve trace data from Stackdriver Trace. Data is generated and
// available by default for all App Engine applications. Data from other
// applications can be written to Stackdriver Trace for display, reporting,
// and analysis.
//
// Use the client at cloud.google.com/go/trace in preference to this.
package trace // import "cloud.google.com/go/trace/apiv1"
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
func insertXGoog(ctx context.Context, val string) context.Context {
md, _ := metadata.FromContext(ctx)
md = md.Copy()
md["x-goog-api-client"] = []string{val}
return metadata.NewContext(ctx, md)
}
+299
View File
@@ -0,0 +1,299 @@
// 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 (
google_protobuf "github.com/golang/protobuf/ptypes/empty"
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
)
import (
"flag"
"io"
"log"
"net"
"os"
"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"
)
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(_ context.Context, req *cloudtracepb.ListTracesRequest) (*cloudtracepb.ListTracesResponse, error) {
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(_ context.Context, req *cloudtracepb.GetTraceRequest) (*cloudtracepb.Trace, error) {
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(_ context.Context, req *cloudtracepb.PatchTracesRequest) (*google_protobuf.Empty, error) {
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*google_protobuf.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 *google_protobuf.Empty = &google_protobuf.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.Internal
mockTrace.err = grpc.Errorf(errCode, "test error")
var projectId string = "projectId-1969970175"
var traces *cloudtracepb.Traces = &cloudtracepb.Traces{}
var request = &cloudtracepb.PatchTracesRequest{
ProjectId: projectId,
Traces: traces,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.PatchTraces(context.Background(), request)
if c := grpc.Code(err); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
}
func TestTraceServiceGetTrace(t *testing.T) {
var projectId2 string = "projectId2939242356"
var traceId2 string = "traceId2987826376"
var expectedResponse = &cloudtracepb.Trace{
ProjectId: projectId2,
TraceId: traceId2,
}
mockTrace.err = nil
mockTrace.reqs = nil
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
var projectId string = "projectId-1969970175"
var traceId string = "traceId1270300245"
var request = &cloudtracepb.GetTraceRequest{
ProjectId: projectId,
TraceId: traceId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetTrace(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestTraceServiceGetTraceError(t *testing.T) {
errCode := codes.Internal
mockTrace.err = grpc.Errorf(errCode, "test error")
var projectId string = "projectId-1969970175"
var traceId string = "traceId1270300245"
var request = &cloudtracepb.GetTraceRequest{
ProjectId: projectId,
TraceId: traceId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetTrace(context.Background(), request)
if c := grpc.Code(err); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestTraceServiceListTraces(t *testing.T) {
var nextPageToken string = ""
var tracesElement *cloudtracepb.Trace = &cloudtracepb.Trace{}
var traces = []*cloudtracepb.Trace{tracesElement}
var expectedResponse = &cloudtracepb.ListTracesResponse{
NextPageToken: nextPageToken,
Traces: traces,
}
mockTrace.err = nil
mockTrace.reqs = nil
mockTrace.resps = append(mockTrace.resps[:0], expectedResponse)
var projectId string = "projectId-1969970175"
var request = &cloudtracepb.ListTracesRequest{
ProjectId: projectId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListTraces(context.Background(), request).Next()
if err != nil {
t.Fatal(err)
}
if want, got := request, mockTrace.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
want := (interface{})(expectedResponse.Traces[0])
got := (interface{})(resp)
var ok bool
switch want := (want).(type) {
case proto.Message:
ok = proto.Equal(want, got.(proto.Message))
default:
ok = want == got
}
if !ok {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestTraceServiceListTracesError(t *testing.T) {
errCode := codes.Internal
mockTrace.err = grpc.Errorf(errCode, "test error")
var projectId string = "projectId-1969970175"
var request = &cloudtracepb.ListTracesRequest{
ProjectId: projectId,
}
c, err := NewClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListTraces(context.Background(), request).Next()
if c := grpc.Code(err); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
+235
View File
@@ -0,0 +1,235 @@
// 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(
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/trace.append",
"https://www.googleapis.com/auth/trace.readonly",
),
}
}
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", "")
c.xGoogHeader = 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) error {
ctx = insertXGoog(ctx, c.xGoogHeader)
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
_, err = c.client.PatchTraces(ctx, req)
return err
}, c.CallOptions.PatchTraces...)
return err
}
// GetTrace gets a single trace by its ID.
func (c *Client) GetTrace(ctx context.Context, req *cloudtracepb.GetTraceRequest) (*cloudtracepb.Trace, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
var resp *cloudtracepb.Trace
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
resp, err = c.client.GetTrace(ctx, req)
return err
}, c.CallOptions.GetTrace...)
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) *TraceIterator {
ctx = insertXGoog(ctx, c.xGoogHeader)
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) error {
var err error
resp, err = c.client.ListTraces(ctx, req)
return err
}, c.CallOptions.ListTraces...)
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
}
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// AUTO-GENERATED CODE. DO NOT EDIT.
package trace_test
import (
"cloud.google.com/go/trace/apiv1"
"golang.org/x/net/context"
cloudtracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
)
func ExampleNewClient() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleClient_PatchTraces() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.PatchTracesRequest{
// TODO: Fill request struct fields.
}
err = c.PatchTraces(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_GetTrace() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.GetTraceRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetTrace(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListTraces() {
ctx := context.Background()
c, err := trace.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &cloudtracepb.ListTracesRequest{
// TODO: Fill request struct fields.
}
it := c.ListTraces(ctx, req)
for {
resp, err := it.Next()
if err != nil {
// TODO: Handle error.
break
}
// TODO: Use resp.
_ = resp
}
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trace
import (
crand "crypto/rand"
"encoding/binary"
"fmt"
"math/rand"
"sync"
"time"
"golang.org/x/time/rate"
)
type SamplingPolicy interface {
// Sample returns a Decision.
// If Trace is false in the returned Decision, then the Decision should be
// the zero value.
Sample(p Parameters) Decision
}
// Parameters contains the values passed to a SamplingPolicy's Sample method.
type Parameters struct {
HasTraceHeader bool // whether the incoming request has a valid X-Cloud-Trace-Context header.
}
// Decision is the value returned by a call to a SamplingPolicy's Sample method.
type Decision struct {
Trace bool // Whether to trace the request.
Sample bool // Whether the trace is included in the random sample.
Policy string // Name of the sampling policy.
Weight float64 // Sample weight to be used in statistical calculations.
}
type sampler struct {
fraction float64
skipped float64
*rate.Limiter
*rand.Rand
sync.Mutex
}
func (s *sampler) Sample(p Parameters) Decision {
s.Lock()
x := s.Float64()
d := s.sample(p, time.Now(), x)
s.Unlock()
return d
}
// sample contains the a deterministic, time-independent logic of Sample.
func (s *sampler) sample(p Parameters, now time.Time, x float64) (d Decision) {
d.Sample = x < s.fraction
d.Trace = p.HasTraceHeader || d.Sample
if !d.Trace {
// We have no reason to trace this request.
return Decision{}
}
// We test separately that the rate limit is not tiny before calling AllowN,
// because of overflow problems in x/time/rate.
if s.Limit() < 1e-9 || !s.AllowN(now, 1) {
// Rejected by the rate limit.
if d.Sample {
s.skipped++
}
return Decision{}
}
if d.Sample {
d.Policy, d.Weight = "default", (1.0+s.skipped)/s.fraction
s.skipped = 0.0
}
return
}
// NewLimitedSampler returns a sampling policy that randomly samples a given
// fraction of requests. It also enforces a limit on the number of traces per
// second. It tries to trace every request with a trace header, but will not
// exceed the qps limit to do it.
func NewLimitedSampler(fraction, maxqps float64) (SamplingPolicy, error) {
if !(fraction >= 0) {
return nil, fmt.Errorf("invalid fraction %f", fraction)
}
if !(maxqps >= 0) {
return nil, fmt.Errorf("invalid maxqps %f", maxqps)
}
// Set a limit on the number of accumulated "tokens", to limit bursts of
// traced requests. Use one more than a second's worth of tokens, or 100,
// whichever is smaller.
// See https://godoc.org/golang.org/x/time/rate#NewLimiter.
maxTokens := 100
if maxqps < 99.0 {
maxTokens = 1 + int(maxqps)
}
var seed int64
if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil {
seed = time.Now().UnixNano()
}
s := sampler{
fraction: fraction,
Limiter: rate.NewLimiter(rate.Limit(maxqps), maxTokens),
Rand: rand.New(rand.NewSource(seed)),
}
return &s, nil
}
+811
View File
@@ -0,0 +1,811 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package trace is a Google Stackdriver Trace library.
//
// This package is still experimental and subject to change.
//
// See https://cloud.google.com/trace/api/#data_model for a discussion of traces
// and spans.
//
// To initialize a client that connects to the Stackdriver Trace server, use the
// NewClient function. Generally you will want to do this on program
// initialization.
//
// import "cloud.google.com/go/trace"
// ...
// traceClient, err = trace.NewClient(ctx, projectID)
//
// Calling SpanFromRequest will create a new trace span for an incoming HTTP
// request. If the request contains a trace context header, it is used to
// determine the trace ID. Otherwise, a new trace ID is created.
//
// func handler(w http.ResponseWriter, r *http.Request) {
// span := traceClient.SpanFromRequest(r)
// defer span.Finish()
// ...
// }
//
// SpanFromRequest and NewSpan returns nil if the *Client is nil, so you can disable
// tracing by not initializing your *Client variable. All of the exported
// functions on *Span do nothing when the *Span is nil.
//
// If you need to start traces that don't correspond to an incoming HTTP request,
// you can use NewSpan to create a root-level span.
//
// span := traceClient.NewSpan("span name")
// defer span.Finish()
//
// Although a trace span object is created for every request, only a subset of
// traces are uploaded to the server, for efficiency. By default, the requests
// that are traced are those with the tracing bit set in the options field of
// the trace context header. Ideally, you should override this behaviour by
// calling SetSamplingPolicy. NewLimitedSampler returns an implementation of
// SamplingPolicy which traces requests that have the tracing bit set, and also
// randomly traces a specified fraction of requests. Additionally, it sets a
// limit on the number of requests traced per second. The following example
// traces one in every thousand requests, up to a limit of 5 per second.
//
// p, err := trace.NewLimitedSampler(0.001, 5)
// traceClient.SetSamplingPolicy(p)
//
// You can create a new span as a child of an existing span with NewChild.
//
// childSpan := span.NewChild(name)
// ...
// childSpan.Finish()
//
// When sending an HTTP request to another server, NewRemoteChild will create
// a span to represent the time the current program waits for the request to
// complete, and attach a header to the outgoing request so that the trace will
// be propagated to the destination server.
//
// childSpan := span.NewRemoteChild(&httpRequest)
// ...
// childSpan.Finish()
//
// Alternatively, if you have access to the X-Cloud-Trace-Context header value
// but not the underlying HTTP request (this can happen if you are using a
// different transport or messaging protocol, such as gRPC), you can use
// SpanFromHeader instead of SpanFromRequest. In that case, you will need to
// specify the span name explicility, since it cannot be constructed from the
// HTTP request's URL and method.
//
// func handler(r *somepkg.Request) {
// span := traceClient.SpanFromHeader("span name", r.TraceContext())
// defer span.Finish()
// ...
// }
//
// Spans can contain a map from keys to values that have useful information
// about the span. The elements of this map are called labels. Some labels,
// whose keys all begin with the string "trace.cloud.google.com/", are set
// automatically in the following ways:
//
// - SpanFromRequest sets some labels to data about the incoming request.
//
// - NewRemoteChild sets some labels to data about the outgoing request.
//
// - Finish sets a label to a stack trace, if the stack trace option is enabled
// in the incoming trace header.
//
// - The WithResponse option sets some labels to data about a response.
// You can also set labels using SetLabel. If a label is given a value
// automatically and by SetLabel, the automatically-set value is used.
//
// span.SetLabel(key, value)
//
// The WithResponse option can be used when Finish is called.
//
// childSpan := span.NewRemoteChild(outgoingReq)
// resp, err := http.DefaultClient.Do(outgoingReq)
// ...
// childSpan.Finish(trace.WithResponse(resp))
//
// When a span created by SpanFromRequest or SpamFromHeader is finished, the
// finished spans in the corresponding trace -- the span itself and its
// descendants -- are uploaded to the Stackdriver Trace server using the
// *Client that created the span. Finish returns immediately, and uploading
// occurs asynchronously. You can use the FinishWait function instead to wait
// until uploading has finished.
//
// err := span.FinishWait()
//
// Using contexts to pass *trace.Span objects through your program will often
// be a better approach than passing them around explicitly. This allows trace
// spans, and other request-scoped or part-of-request-scoped values, to be
// easily passed through API boundaries. Various Google Cloud libraries will
// retrieve trace spans from contexts and automatically create child spans for
// API requests.
// See https://blog.golang.org/context for more discussion of contexts.
// A derived context containing a trace span can be created using NewContext.
//
// span := traceClient.SpanFromRequest(r)
// ctx = trace.NewContext(ctx, span)
//
// The span can be retrieved from a context elsewhere in the program using
// FromContext.
//
// func foo(ctx context.Context) {
// span := trace.FromContext(ctx).NewChild("in foo")
// defer span.Finish()
// ...
// }
//
package trace // import "cloud.google.com/go/trace"
import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"log"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/context"
api "google.golang.org/api/cloudtrace/v1"
"google.golang.org/api/gensupport"
"google.golang.org/api/option"
"google.golang.org/api/support/bundler"
"google.golang.org/api/transport"
"google.golang.org/grpc"
)
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()
}
}
}
// EnableGRPCTracingDialOption 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 relies on is currently experimental.
var EnableGRPCTracingDialOption grpc.DialOption = grpc.WithUnaryInterceptor(grpc.UnaryClientInterceptor(grpcUnaryInterceptor))
// EnableGRPCTracing automatically traces all gRPC calls from cloud.google.com/go clients.
//
// The functionality in gRPC that this relies on is currently experimental.
var EnableGRPCTracing option.ClientOption = option.WithGRPCDialOption(EnableGRPCTracingDialOption)
func grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// TODO: also intercept streams.
span := FromContext(ctx).NewChild(method)
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
// TODO: standardize gRPC label names?
span.SetLabel("error", err.Error())
}
span.Finish()
return err
}
// nextSpanID returns a new span ID. It will never return zero.
func nextSpanID() uint64 {
var id uint64
for id == 0 {
id = atomic.AddUint64(&spanIDCounter, spanIDIncrement)
}
return id
}
// nextTraceID returns a new trace ID.
func nextTraceID() string {
id1 := nextSpanID()
id2 := nextSpanID()
return fmt.Sprintf("%016x%016x", id1, id2)
}
// Client is a client for uploading traces to the Google Stackdriver Trace server.
type Client struct {
service *api.Service
projectID string
policy SamplingPolicy
bundler *bundler.Bundler
}
// NewClient creates a new Google Stackdriver Trace client.
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
o := []option.ClientOption{
option.WithScopes(cloudPlatformScope),
option.WithUserAgent(userAgent),
}
o = append(o, opts...)
hc, basePath, err := transport.NewHTTPClient(ctx, o...)
if err != nil {
return nil, fmt.Errorf("creating HTTP client for Google Stackdriver Trace API: %v", err)
}
apiService, err := api.New(hc)
if err != nil {
return nil, fmt.Errorf("creating Google Stackdriver Trace API client: %v", err)
}
if basePath != "" {
// An option set a basepath, so override api.New's default.
apiService.BasePath = basePath
}
c := &Client{
service: apiService,
projectID: projectID,
}
bundler := bundler.NewBundler((*api.Trace)(nil), func(bundle interface{}) {
traces := bundle.([]*api.Trace)
err := c.upload(traces)
if err != nil {
log.Printf("failed to upload %d traces to the Cloud Trace server.", len(traces))
}
})
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 = spanKindServer
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) (string, uint64, optionFlags, 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
}
// Parse the trace id field.
slash := strings.Index(h, `/`)
if slash == -1 {
return "", 0, 0, false
}
traceID, h := h[:slash], h[slash+1:]
// Parse the span id field.
semicolon := strings.Index(h, `;`)
if semicolon == -1 {
return "", 0, 0, false
}
spanstr, h := h[:semicolon], h[semicolon+1:]
spanID, err := strconv.ParseUint(spanstr, 10, 64)
if err != nil {
return "", 0, 0, false
}
// Parse the options field.
if !strings.HasPrefix(h, "o=") {
return "", 0, 0, false
}
o, err := strconv.ParseUint(h[2:], 10, 64)
if err != nil {
return "", 0, 0, false
}
options := optionFlags(o)
return traceID, spanID, options, 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
span api.TraceSpan
start time.Time
end time.Time
rootSpan bool
stack [maxStackFrames]uintptr
host string
method string
url string
statusCode int
}
func (s *Span) tracing() bool {
return s.trace.localOptions&optionTrace != 0
}
// NewChild creates a new span with the given name as a child of s.
// If s is nil, does nothing and returns nil.
func (s *Span) NewChild(name string) *Span {
if s == nil {
return nil
}
if !s.tracing() {
return s
}
return startNewChild(name, s.trace, s.span.SpanId)
}
// NewRemoteChild creates a new span as a child of s.
//
// Some labels in the span are set from the outgoing *http.Request r.
//
// A header is set in r so that the trace context is propagated to the
// destination. The parent span ID in that header is set as follows:
// - If the request is being traced, then the ID of s is used.
// - If the request is not being traced, but there was a trace context header
// in the incoming request for this trace (the request passed to
// SpanFromRequest), the parent span ID in that header is used.
// - Otherwise, the parent span ID is zero.
// The tracing bit in the options is set if tracing is enabled, or if it was
// set in the incoming request.
//
// If s is nil, does nothing and returns nil.
func (s *Span) NewRemoteChild(r *http.Request) *Span {
if s == nil {
return nil
}
if !s.tracing() {
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, s.span.ParentSpanId, s.trace.globalOptions)}
return s
}
newSpan := startNewChildWithRequest(r, s.trace, s.span.SpanId)
r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, newSpan.span.SpanId, s.trace.globalOptions)}
return newSpan
}
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.
func (s *Span) SetLabel(key, value string) {
if s == nil {
return
}
if !s.tracing() {
return
}
if value == "" {
if s.span.Labels != nil {
delete(s.span.Labels, key)
}
return
}
if s.span.Labels == nil {
s.span.Labels = make(map[string]string)
}
s.span.Labels[key] = value
}
type FinishOption interface {
modifySpan(s *Span)
}
type withResponse struct {
*http.Response
}
// WithResponse returns an option that can be passed to Finish that indicates
// that some labels for the span should be set using the given *http.Response.
func WithResponse(resp *http.Response) FinishOption {
return withResponse{resp}
}
func (u withResponse) modifySpan(s *Span) {
if u.Response != nil {
s.statusCode = u.StatusCode
}
}
// Finish declares that the span has finished.
//
// If s is nil, Finish does nothing and returns nil.
//
// If the option trace.WithResponse(resp) is passed, then some labels are set
// for s using information in the given *http.Response. This is useful when the
// span is for an outgoing http request; s will typically have been created by
// NewRemoteChild in this case.
//
// If s is a root span (one created by SpanFromRequest) then s, and all its
// descendant spans that have finished, are uploaded to the Google Stackdriver
// Trace server asynchronously.
func (s *Span) Finish(opts ...FinishOption) {
if s == nil {
return
}
if !s.tracing() {
return
}
s.trace.finish(s, false, opts...)
}
// FinishWait is like Finish, but if s is a root span, it waits until uploading
// is finished, then returns an error if one occurred.
func (s *Span) FinishWait(opts ...FinishOption) error {
if s == nil {
return nil
}
if !s.tracing() {
return nil
}
return s.trace.finish(s, true, opts...)
}
func spanHeader(traceID string, spanID uint64, options optionFlags) string {
// See https://cloud.google.com/trace/docs/faq for the header format.
return fmt.Sprintf("%s/%d;o=%d", traceID, spanID, options)
}
func (s *Span) setStackLabel() {
var stack stackLabelValue
lastSigPanic, inTraceLibrary := false, true
for _, pc := range s.stack {
if pc == 0 {
break
}
if !lastSigPanic {
pc--
}
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
// Name has one of the following forms:
// path/to/package.Foo
// path/to/package.(Type).Foo
// For the first form, we store the whole name in the Method field of the
// stack frame. For the second form, we set the Method field to "Foo" and
// the Class field to "path/to/package.(Type)".
name := fn.Name()
if inTraceLibrary && !strings.HasPrefix(name, "cloud.google.com/go/trace.") {
inTraceLibrary = false
}
var class string
if i := strings.Index(name, ")."); i != -1 {
class, name = name[:i+1], name[i+2:]
}
frame := stackFrame{
Class: class,
Method: name,
Filename: file,
Line: int64(line),
}
if inTraceLibrary && len(stack.Frames) == 1 {
stack.Frames[0] = frame
} else {
stack.Frames = append(stack.Frames, frame)
}
lastSigPanic = fn.Name() == "runtime.sigpanic"
}
if label, err := json.Marshal(stack); err == nil {
s.SetLabel(labelStackTrace, string(label))
}
}
+849
View File
@@ -0,0 +1,849 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trace
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"reflect"
"strings"
"sync"
"testing"
"time"
"cloud.google.com/go/datastore"
"cloud.google.com/go/internal/testutil"
"cloud.google.com/go/storage"
"golang.org/x/net/context"
api "google.golang.org/api/cloudtrace/v1"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
dspb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
)
const testProjectID = "testproject"
type fakeRoundTripper struct {
reqc chan *http.Request
}
func newFakeRoundTripper() *fakeRoundTripper {
return &fakeRoundTripper{reqc: make(chan *http.Request)}
}
func (rt *fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
rt.reqc <- r
resp := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("{}")),
}
return resp, nil
}
func newTestClient(rt http.RoundTripper) *Client {
t, err := NewClient(context.Background(), testProjectID, option.WithHTTPClient(&http.Client{Transport: rt}))
if err != nil {
panic(err)
}
return t
}
type fakeDatastoreServer struct {
dspb.DatastoreServer
fail bool
}
func (f *fakeDatastoreServer) Lookup(ctx context.Context, req *dspb.LookupRequest) (*dspb.LookupResponse, error) {
if f.fail {
return nil, errors.New("lookup failed")
}
return &dspb.LookupResponse{}, nil
}
// makeRequests makes some requests.
// span is the root span. rt is the trace client's http client's transport.
// This is used to retrieve the trace uploaded by the client, if any. If
// expectTrace is true, we expect a trace will be uploaded. If synchronous is
// true, the call to Finish is expected not to return before the client has
// uploaded any traces.
func makeRequests(t *testing.T, span *Span, rt *fakeRoundTripper, synchronous bool, expectTrace bool) *http.Request {
ctx := NewContext(context.Background(), span)
// An HTTP request.
{
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
if err != nil {
t.Fatal(err)
}
resp := &http.Response{StatusCode: 200}
s := span.NewRemoteChild(req2)
s.Finish(WithResponse(resp))
}
// An autogenerated API call.
{
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
hc := &http.Client{Transport: rt}
computeClient, err := compute.New(hc)
if err != nil {
t.Fatal(err)
}
_, err = computeClient.Zones.List(testProjectID).Context(ctx).Do()
if err != nil {
t.Fatal(err)
}
}
// A cloud library call that uses the autogenerated API.
{
rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
hc := &http.Client{Transport: rt}
storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(hc))
if err != nil {
t.Fatal(err)
}
var objAttrsList []*storage.ObjectAttrs
it := storageClient.Bucket("testbucket").Objects(ctx, nil)
for {
objAttrs, err := it.Next()
if err != nil && err != iterator.Done {
t.Fatal(err)
}
if err == iterator.Done {
break
}
objAttrsList = append(objAttrsList, objAttrs)
}
}
// A cloud library call that uses grpc internally.
for _, fail := range []bool{false, true} {
srv, err := testutil.NewServer()
if err != nil {
t.Fatalf("creating test datastore server: %v", err)
}
dspb.RegisterDatastoreServer(srv.Gsrv, &fakeDatastoreServer{fail: fail})
srv.Start()
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), EnableGRPCTracingDialOption)
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 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: "RPC_SERVER",
Labels: map[string]string{},
Name: "/foo",
},
},
TraceId: traceID,
},
},
}
body, err := ioutil.ReadAll(uploaded.Body)
if err != nil {
t.Fatal(err)
}
var patch api.Traces
err = json.Unmarshal(body, &patch)
if err != nil {
t.Fatal(err)
}
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Fatalf("PatchTraces request: got %s want %s", got, want)
}
n := len(patch.Traces[0].Spans)
rootSpan := patch.Traces[0].Spans[n-1]
for i, s := range patch.Traces[0].Spans {
if a, b := s.StartTime, s.EndTime; a > b {
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
}
if a, b := rootSpan.StartTime, s.StartTime; a > b {
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
}
if a, b := s.EndTime, rootSpan.EndTime; a > b {
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
}
if i > 1 && i < n-1 {
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
}
}
}
if x := rootSpan.ParentSpanId; x != 0 {
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 0)
}
for i, s := range patch.Traces[0].Spans {
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
}
}
for i, s := range patch.Traces[0].Spans {
s.EndTime = ""
labels := &expected.Traces[0].Spans[i].Labels
for key, value := range *labels {
if v, ok := s.Labels[key]; !ok {
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
} else if key == "trace.cloud.google.com/http/url" {
if !strings.HasPrefix(v, value) {
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
}
} else if v != value {
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
}
}
for key := range s.Labels {
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
t.Errorf("Span %d: unexpected label %q", i, key)
}
}
*labels = nil
s.Labels = nil
s.ParentSpanId = 0
if s.SpanId == 0 {
t.Errorf("Incorrect SpanId: got 0 want nonzero")
}
s.SpanId = 0
s.StartTime = ""
}
if !reflect.DeepEqual(patch, expected) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Errorf("PatchTraces request: got %s want %s", got, want)
}
}
func testTrace(t *testing.T, synchronous bool, fromRequest bool) {
const header = `0123456789ABCDEF0123456789ABCDEF/42;o=3`
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
span := traceClient.SpanFromHeader("/foo", header)
headerOrReqLabels := map[string]string{}
headerOrReqName := "/foo"
if fromRequest {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("X-Cloud-Trace-Context", header)
span = traceClient.SpanFromRequest(req)
headerOrReqLabels = map[string]string{
"trace.cloud.google.com/http/host": "example.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/url": "http://example.com/foo",
}
headerOrReqName = "example.com/foo"
}
uploaded := makeRequests(t, span, rt, synchronous, true)
if uploaded == nil {
t.Fatalf("No trace uploaded, expected one.")
}
expected := api.Traces{
Traces: []*api.Trace{
{
ProjectId: testProjectID,
Spans: []*api.TraceSpan{
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "example.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "http://example.com/bar",
},
Name: "example.com/bar",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
},
Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
},
{
Kind: "RPC_CLIENT",
Labels: map[string]string{
"trace.cloud.google.com/http/host": "www.googleapis.com",
"trace.cloud.google.com/http/method": "GET",
"trace.cloud.google.com/http/status_code": "200",
"trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
},
Name: "www.googleapis.com/storage/v1/b/testbucket/o",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: nil,
Name: "/google.datastore.v1.Datastore/Lookup",
},
&api.TraceSpan{
Kind: "RPC_CLIENT",
Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
Name: "/google.datastore.v1.Datastore/Lookup",
},
{
Kind: "RPC_SERVER",
Labels: headerOrReqLabels,
Name: headerOrReqName,
},
},
TraceId: "0123456789ABCDEF0123456789ABCDEF",
},
},
}
body, err := ioutil.ReadAll(uploaded.Body)
if err != nil {
t.Fatal(err)
}
var patch api.Traces
err = json.Unmarshal(body, &patch)
if err != nil {
t.Fatal(err)
}
if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Fatalf("PatchTraces request: got %s want %s", got, want)
}
n := len(patch.Traces[0].Spans)
rootSpan := patch.Traces[0].Spans[n-1]
for i, s := range patch.Traces[0].Spans {
if a, b := s.StartTime, s.EndTime; a > b {
t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
}
if a, b := rootSpan.StartTime, s.StartTime; a > b {
t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
}
if a, b := s.EndTime, rootSpan.EndTime; a > b {
t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
}
if i > 1 && i < n-1 {
if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
}
}
}
if x := rootSpan.ParentSpanId; x != 42 {
t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 42)
}
for i, s := range patch.Traces[0].Spans {
if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
}
}
for i, s := range patch.Traces[0].Spans {
s.EndTime = ""
labels := &expected.Traces[0].Spans[i].Labels
for key, value := range *labels {
if v, ok := s.Labels[key]; !ok {
t.Errorf("Span %d is missing Label %q:%q", i, key, value)
} else if key == "trace.cloud.google.com/http/url" {
if !strings.HasPrefix(v, value) {
t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
}
} else if v != value {
t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
}
}
for key := range s.Labels {
if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
t.Errorf("Span %d: unexpected label %q", i, key)
}
}
*labels = nil
s.Labels = nil
s.ParentSpanId = 0
if s.SpanId == 0 {
t.Errorf("Incorrect SpanId: got 0 want nonzero")
}
s.SpanId = 0
s.StartTime = ""
}
if !reflect.DeepEqual(patch, expected) {
got, _ := json.Marshal(patch)
want, _ := json.Marshal(expected)
t.Errorf("PatchTraces request: got %s \n\n want %s", got, want)
}
}
func TestNoTrace(t *testing.T) {
testNoTrace(t, false, true)
}
func TestNoTraceWithWait(t *testing.T) {
testNoTrace(t, true, true)
}
func TestNoTraceFromHeader(t *testing.T) {
testNoTrace(t, false, false)
}
func TestNoTraceFromHeaderWithWait(t *testing.T) {
testNoTrace(t, true, false)
}
func testNoTrace(t *testing.T, synchronous bool, fromRequest bool) {
for _, header := range []string{
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
`0123456789ABCDEF0123456789ABCDEF/42`,
`0123456789ABCDEF0123456789ABCDEF`,
``,
} {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
var span *Span
if fromRequest {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if header != "" {
req.Header.Set("X-Cloud-Trace-Context", header)
}
if err != nil {
t.Fatal(err)
}
span = traceClient.SpanFromRequest(req)
} else {
span = traceClient.SpanFromHeader("/foo", header)
}
uploaded := makeRequests(t, span, rt, synchronous, false)
if uploaded != nil {
t.Errorf("Got a trace, expected none.")
}
}
}
func TestSample(t *testing.T) {
// A deterministic test of the sampler logic.
type testCase struct {
rate float64
maxqps float64
want int
}
const delta = 25 * time.Millisecond
for _, test := range []testCase{
// qps won't matter, so we will sample half of the 79 calls
{0.50, 100, 40},
// with 1 qps and a burst of 2, we will sample twice in second #1, once in the partial second #2
{0.50, 1, 3},
} {
sp, err := NewLimitedSampler(test.rate, test.maxqps)
if err != nil {
t.Fatal(err)
}
s := sp.(*sampler)
sampled := 0
tm := time.Now()
for i := 0; i < 80; i++ {
if s.sample(Parameters{}, tm, float64(i%2)).Sample {
sampled++
}
tm = tm.Add(delta)
}
if sampled != test.want {
t.Errorf("rate=%f, maxqps=%f: got %d samples, want %d", test.rate, test.maxqps, sampled, test.want)
}
}
}
func TestSampling(t *testing.T) {
t.Parallel()
// This scope tests sampling in a larger context, with real time and randomness.
wg := sync.WaitGroup{}
type testCase struct {
rate float64
maxqps float64
expectedRange [2]int
}
for _, test := range []testCase{
{0, 5, [2]int{0, 0}},
{5, 0, [2]int{0, 0}},
{0.50, 100, [2]int{20, 60}},
{0.50, 1, [2]int{3, 4}}, // Windows, with its less precise clock, sometimes gives 4.
} {
wg.Add(1)
go func(test testCase) {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
traceClient.bundler.BundleByteLimit = 1
p, err := NewLimitedSampler(test.rate, test.maxqps)
if err != nil {
t.Fatalf("NewLimitedSampler: %v", err)
}
traceClient.SetSamplingPolicy(p)
ticker := time.NewTicker(25 * time.Millisecond)
sampled := 0
for i := 0; i < 79; i++ {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
span := traceClient.SpanFromRequest(req)
span.Finish()
select {
case <-rt.reqc:
<-ticker.C
sampled++
case <-ticker.C:
}
}
ticker.Stop()
if test.expectedRange[0] > sampled || sampled > test.expectedRange[1] {
t.Errorf("rate=%f, maxqps=%f: got %d samples want ∈ %v", test.rate, test.maxqps, sampled, test.expectedRange)
}
wg.Done()
}(test)
}
wg.Wait()
}
func TestBundling(t *testing.T) {
t.Parallel()
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
traceClient.bundler.DelayThreshold = time.Second / 2
traceClient.bundler.BundleCountThreshold = 10
p, err := NewLimitedSampler(1, 99) // sample every request.
if err != nil {
t.Fatalf("NewLimitedSampler: %v", err)
}
traceClient.SetSamplingPolicy(p)
for i := 0; i < 35; i++ {
go func() {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
span := traceClient.SpanFromRequest(req)
span.Finish()
}()
}
// Read the first three bundles.
<-rt.reqc
<-rt.reqc
<-rt.reqc
// Test that the fourth bundle isn't sent early.
select {
case <-rt.reqc:
t.Errorf("bundle sent too early")
case <-time.After(time.Second / 4):
<-rt.reqc
}
// Test that there aren't extra bundles.
select {
case <-rt.reqc:
t.Errorf("too many bundles sent")
case <-time.After(time.Second):
}
}
func TestWeights(t *testing.T) {
const (
expectedNumTraced = 10100
numTracedEpsilon = 100
expectedTotalWeight = 50000
totalWeightEpsilon = 5000
)
rng := rand.New(rand.NewSource(1))
const delta = 2 * time.Millisecond
for _, headerRate := range []float64{0.0, 0.5, 1.0} {
// Simulate 10 seconds of requests arriving at 500qps.
//
// The sampling policy tries to sample 25% of them, but has a qps limit of
// 100, so it will not be able to. The returned weight should be higher
// for some sampled requests to compensate.
//
// headerRate is the fraction of incoming requests that have a trace header
// set. The qps limit should not be exceeded, even if headerRate is high.
sp, err := NewLimitedSampler(0.25, 100)
if err != nil {
t.Fatal(err)
}
s := sp.(*sampler)
tm := time.Now()
totalWeight := 0.0
numTraced := 0
seenLargeWeight := false
for i := 0; i < 50000; i++ {
d := s.sample(Parameters{HasTraceHeader: rng.Float64() < headerRate}, tm, rng.Float64())
if d.Trace {
numTraced++
}
if d.Sample {
totalWeight += d.Weight
if x := int(d.Weight) / 4; x <= 0 || x >= 100 || d.Weight != float64(x)*4.0 {
t.Errorf("weight: got %f, want a small positive multiple of 4", d.Weight)
}
if d.Weight > 4 {
seenLargeWeight = true
}
}
tm = tm.Add(delta)
}
if !seenLargeWeight {
t.Errorf("headerRate %f: never saw sample weight higher than 4.", headerRate)
}
if numTraced < expectedNumTraced-numTracedEpsilon || expectedNumTraced+numTracedEpsilon < numTraced {
t.Errorf("headerRate %f: got %d traced requests, want ∈ [%d, %d]", headerRate, numTraced, expectedNumTraced-numTracedEpsilon, expectedNumTraced+numTracedEpsilon)
}
if totalWeight < expectedTotalWeight-totalWeightEpsilon || expectedTotalWeight+totalWeightEpsilon < totalWeight {
t.Errorf("headerRate %f: got total weight %f want ∈ [%d, %d]", headerRate, totalWeight, expectedTotalWeight-totalWeightEpsilon, expectedTotalWeight+totalWeightEpsilon)
}
}
}
type alwaysTrace struct{}
func (a alwaysTrace) Sample(p Parameters) Decision {
return Decision{Trace: true}
}
type neverTrace struct{}
func (a neverTrace) Sample(p Parameters) Decision {
return Decision{Trace: false}
}
func TestPropagation(t *testing.T) {
rt := newFakeRoundTripper()
traceClient := newTestClient(rt)
for _, header := range []string{
`0123456789ABCDEF0123456789ABCDEF/42;o=0`,
`0123456789ABCDEF0123456789ABCDEF/42;o=1`,
`0123456789ABCDEF0123456789ABCDEF/42;o=2`,
`0123456789ABCDEF0123456789ABCDEF/42;o=3`,
`0123456789ABCDEF0123456789ABCDEF/0;o=0`,
`0123456789ABCDEF0123456789ABCDEF/0;o=1`,
`0123456789ABCDEF0123456789ABCDEF/0;o=2`,
`0123456789ABCDEF0123456789ABCDEF/0;o=3`,
``,
} {
for _, policy := range []SamplingPolicy{
nil,
alwaysTrace{},
neverTrace{},
} {
traceClient.SetSamplingPolicy(policy)
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}
if header != "" {
req.Header.Set("X-Cloud-Trace-Context", header)
}
span := traceClient.SpanFromRequest(req)
req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
if err != nil {
t.Fatal(err)
}
req3, err := http.NewRequest("GET", "http://example.com/baz", nil)
if err != nil {
t.Fatal(err)
}
span.NewRemoteChild(req2)
span.NewRemoteChild(req3)
var (
t1, t2, t3 string
s1, s2, s3 uint64
o1, o2, o3 uint64
)
fmt.Sscanf(header, "%32s/%d;o=%d", &t1, &s1, &o1)
fmt.Sscanf(req2.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t2, &s2, &o2)
fmt.Sscanf(req3.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t3, &s3, &o3)
if header == "" {
if t2 != t3 {
t.Errorf("expected the same trace ID in child requests, got %q %q", t2, t3)
}
} else {
if t2 != t1 || t3 != t1 {
t.Errorf("trace IDs should be passed to child requests")
}
}
trace := policy == alwaysTrace{} || policy == nil && (o1&1) != 0
if header == "" {
if trace && (s2 == 0 || s3 == 0) {
t.Errorf("got span IDs %d %d in child requests, want nonzero", s2, s3)
}
if trace && s2 == s3 {
t.Errorf("got span IDs %d %d in child requests, should be different", s2, s3)
}
if !trace && (s2 != 0 || s3 != 0) {
t.Errorf("got span IDs %d %d in child requests, want zero", s2, s3)
}
} else {
if trace && (s2 == s1 || s3 == s1 || s2 == s3) {
t.Errorf("parent span IDs in input and outputs should be all different, got %d %d %d", s1, s2, s3)
}
if !trace && (s2 != s1 || s3 != s1) {
t.Errorf("parent span ID in input, %d, should have been equal to parent span IDs in output: %d %d", s1, s2, s3)
}
}
expectTraceOption := policy == alwaysTrace{} || (o1&1) != 0
if expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) {
t.Errorf("tracing flag in child requests should be %t, got options %d %d", expectTraceOption, o2, o3)
}
}
}
}