|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 |
- // Copyright 2016 Google LLC
- //
- // 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) {
- t.Skip("flaky")
- 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)
- }
-
- checkTraces(t, patch, expected)
-
- 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) {
- t.Skip("flaky")
- 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)
- }
-
- checkTraces(t, patch, expected)
-
- 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)
- }
- }
- }
- }
-
- func BenchmarkSpanFromHeader(b *testing.B) {
- const header = `0123456789ABCDEF0123456789ABCDEF/42;o=0`
- const name = "/foo"
-
- rt := newFakeRoundTripper()
- traceClient := newTestClient(rt)
- for n := 0; n < b.N; n++ {
- traceClient.SpanFromHeader(name, header)
- }
- }
-
- func checkTraces(t *testing.T, patch, expected api.Traces) {
- if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
- diff := testutil.Diff(patch.Traces, expected.Traces)
- t.Logf("diff:\n%s", diff)
- got, _ := json.Marshal(patch)
- want, _ := json.Marshal(expected)
- t.Fatalf("PatchTraces request: got %s want %s", got, want)
- }
- }
|