You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

970 lines
28 KiB

  1. // Copyright 2016 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package trace
  15. import (
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io/ioutil"
  20. "math/rand"
  21. "net/http"
  22. "regexp"
  23. "strings"
  24. "sync"
  25. "testing"
  26. "time"
  27. "cloud.google.com/go/datastore"
  28. "cloud.google.com/go/internal/testutil"
  29. "cloud.google.com/go/storage"
  30. "golang.org/x/net/context"
  31. api "google.golang.org/api/cloudtrace/v1"
  32. compute "google.golang.org/api/compute/v1"
  33. "google.golang.org/api/iterator"
  34. "google.golang.org/api/option"
  35. dspb "google.golang.org/genproto/googleapis/datastore/v1"
  36. "google.golang.org/grpc"
  37. )
  38. const testProjectID = "testproject"
  39. type fakeRoundTripper struct {
  40. reqc chan *http.Request
  41. }
  42. func newFakeRoundTripper() *fakeRoundTripper {
  43. return &fakeRoundTripper{reqc: make(chan *http.Request)}
  44. }
  45. func (rt *fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
  46. rt.reqc <- r
  47. resp := &http.Response{
  48. Status: "200 OK",
  49. StatusCode: 200,
  50. Body: ioutil.NopCloser(strings.NewReader("{}")),
  51. }
  52. return resp, nil
  53. }
  54. func newTestClient(rt http.RoundTripper) *Client {
  55. t, err := NewClient(context.Background(), testProjectID, option.WithHTTPClient(&http.Client{Transport: rt}))
  56. if err != nil {
  57. panic(err)
  58. }
  59. return t
  60. }
  61. type fakeDatastoreServer struct {
  62. dspb.DatastoreServer
  63. fail bool
  64. }
  65. func (f *fakeDatastoreServer) Lookup(ctx context.Context, req *dspb.LookupRequest) (*dspb.LookupResponse, error) {
  66. if f.fail {
  67. return nil, errors.New("lookup failed")
  68. }
  69. return &dspb.LookupResponse{}, nil
  70. }
  71. // makeRequests makes some requests.
  72. // span is the root span. rt is the trace client's http client's transport.
  73. // This is used to retrieve the trace uploaded by the client, if any. If
  74. // expectTrace is true, we expect a trace will be uploaded. If synchronous is
  75. // true, the call to Finish is expected not to return before the client has
  76. // uploaded any traces.
  77. func makeRequests(t *testing.T, span *Span, rt *fakeRoundTripper, synchronous bool, expectTrace bool) *http.Request {
  78. ctx := NewContext(context.Background(), span)
  79. tc := newTestClient(&noopTransport{})
  80. // An HTTP request.
  81. {
  82. req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
  83. if err != nil {
  84. t.Fatal(err)
  85. }
  86. resp := &http.Response{StatusCode: 200}
  87. s := span.NewRemoteChild(req2)
  88. s.Finish(WithResponse(resp))
  89. }
  90. // An autogenerated API call.
  91. {
  92. rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
  93. hc := &http.Client{Transport: rt}
  94. computeClient, err := compute.New(hc)
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. _, err = computeClient.Zones.List(testProjectID).Context(ctx).Do()
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. }
  103. // A cloud library call that uses the autogenerated API.
  104. {
  105. rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)}
  106. hc := &http.Client{Transport: rt}
  107. storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(hc))
  108. if err != nil {
  109. t.Fatal(err)
  110. }
  111. var objAttrsList []*storage.ObjectAttrs
  112. it := storageClient.Bucket("testbucket").Objects(ctx, nil)
  113. for {
  114. objAttrs, err := it.Next()
  115. if err != nil && err != iterator.Done {
  116. t.Fatal(err)
  117. }
  118. if err == iterator.Done {
  119. break
  120. }
  121. objAttrsList = append(objAttrsList, objAttrs)
  122. }
  123. }
  124. // A cloud library call that uses grpc internally.
  125. for _, fail := range []bool{false, true} {
  126. srv, err := testutil.NewServer()
  127. if err != nil {
  128. t.Fatalf("creating test datastore server: %v", err)
  129. }
  130. dspb.RegisterDatastoreServer(srv.Gsrv, &fakeDatastoreServer{fail: fail})
  131. srv.Start()
  132. conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
  133. if err != nil {
  134. t.Fatalf("connecting to test datastore server: %v", err)
  135. }
  136. datastoreClient, err := datastore.NewClient(ctx, testProjectID, option.WithGRPCConn(conn))
  137. if err != nil {
  138. t.Fatalf("creating datastore client: %v", err)
  139. }
  140. k := datastore.NameKey("Entity", "stringID", nil)
  141. e := new(datastore.Entity)
  142. datastoreClient.Get(ctx, k, e)
  143. }
  144. done := make(chan struct{})
  145. go func() {
  146. if synchronous {
  147. err := span.FinishWait()
  148. if err != nil {
  149. t.Errorf("Unexpected error from span.FinishWait: %v", err)
  150. }
  151. } else {
  152. span.Finish()
  153. }
  154. done <- struct{}{}
  155. }()
  156. if !expectTrace {
  157. <-done
  158. select {
  159. case <-rt.reqc:
  160. t.Errorf("Got a trace, expected none.")
  161. case <-time.After(5 * time.Millisecond):
  162. }
  163. return nil
  164. } else if !synchronous {
  165. <-done
  166. return <-rt.reqc
  167. } else {
  168. select {
  169. case <-done:
  170. t.Errorf("Synchronous Finish didn't wait for trace upload.")
  171. return <-rt.reqc
  172. case <-time.After(5 * time.Millisecond):
  173. r := <-rt.reqc
  174. <-done
  175. return r
  176. }
  177. }
  178. }
  179. func TestHeader(t *testing.T) {
  180. tests := []struct {
  181. header string
  182. wantTraceID string
  183. wantSpanID uint64
  184. wantOpts optionFlags
  185. wantOK bool
  186. }{
  187. {
  188. header: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
  189. wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
  190. wantSpanID: 1,
  191. wantOpts: 1,
  192. wantOK: true,
  193. },
  194. {
  195. header: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
  196. wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
  197. wantSpanID: 1,
  198. wantOpts: 0,
  199. wantOK: true,
  200. },
  201. {
  202. header: "0123456789ABCDEF0123456789ABCDEF/1",
  203. wantTraceID: "0123456789ABCDEF0123456789ABCDEF",
  204. wantSpanID: 1,
  205. wantOpts: 0,
  206. wantOK: true,
  207. },
  208. {
  209. header: "",
  210. wantTraceID: "",
  211. wantSpanID: 0,
  212. wantOpts: 0,
  213. wantOK: false,
  214. },
  215. }
  216. for _, tt := range tests {
  217. traceID, parentSpanID, opts, _, ok := traceInfoFromHeader(tt.header)
  218. if got, want := traceID, tt.wantTraceID; got != want {
  219. t.Errorf("TraceID(%v) = %q; want %q", tt.header, got, want)
  220. }
  221. if got, want := parentSpanID, tt.wantSpanID; got != want {
  222. t.Errorf("SpanID(%v) = %v; want %v", tt.header, got, want)
  223. }
  224. if got, want := opts, tt.wantOpts; got != want {
  225. t.Errorf("Options(%v) = %v; want %v", tt.header, got, want)
  226. }
  227. if got, want := ok, tt.wantOK; got != want {
  228. t.Errorf("Header exists (%v) = %v; want %v", tt.header, got, want)
  229. }
  230. }
  231. }
  232. func TestOutgoingReqHeader(t *testing.T) {
  233. all, _ := NewLimitedSampler(1, 1<<16) // trace every request
  234. tests := []struct {
  235. desc string
  236. traceHeader string
  237. samplingPolicy SamplingPolicy
  238. wantHeaderRe *regexp.Regexp
  239. }{
  240. {
  241. desc: "Parent span without sampling options, client samples all",
  242. traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
  243. samplingPolicy: all,
  244. wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
  245. },
  246. {
  247. desc: "Parent span without sampling options, without client sampling",
  248. traceHeader: "0123456789ABCDEF0123456789ABCDEF/1",
  249. samplingPolicy: nil,
  250. wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
  251. },
  252. {
  253. desc: "Parent span with o=1, client samples none",
  254. traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=1",
  255. samplingPolicy: nil,
  256. wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"),
  257. },
  258. {
  259. desc: "Parent span with o=0, without client sampling",
  260. traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=0",
  261. samplingPolicy: nil,
  262. wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"),
  263. },
  264. }
  265. tc := newTestClient(nil)
  266. for _, tt := range tests {
  267. tc.SetSamplingPolicy(tt.samplingPolicy)
  268. span := tc.SpanFromHeader("/foo", tt.traceHeader)
  269. req, _ := http.NewRequest("GET", "http://localhost", nil)
  270. span.NewRemoteChild(req)
  271. if got, re := req.Header.Get(httpHeader), tt.wantHeaderRe; !re.MatchString(got) {
  272. t.Errorf("%v (parent=%q): got header %q; want in format %q", tt.desc, tt.traceHeader, got, re)
  273. }
  274. }
  275. }
  276. func TestTrace(t *testing.T) {
  277. t.Parallel()
  278. testTrace(t, false, true)
  279. }
  280. func TestTraceWithWait(t *testing.T) {
  281. testTrace(t, true, true)
  282. }
  283. func TestTraceFromHeader(t *testing.T) {
  284. t.Parallel()
  285. testTrace(t, false, false)
  286. }
  287. func TestTraceFromHeaderWithWait(t *testing.T) {
  288. testTrace(t, false, true)
  289. }
  290. func TestNewSpan(t *testing.T) {
  291. t.Skip("flaky")
  292. const traceID = "0123456789ABCDEF0123456789ABCDEF"
  293. rt := newFakeRoundTripper()
  294. traceClient := newTestClient(rt)
  295. span := traceClient.NewSpan("/foo")
  296. span.trace.traceID = traceID
  297. uploaded := makeRequests(t, span, rt, true, true)
  298. if uploaded == nil {
  299. t.Fatalf("No trace uploaded, expected one.")
  300. }
  301. expected := api.Traces{
  302. Traces: []*api.Trace{
  303. {
  304. ProjectId: testProjectID,
  305. Spans: []*api.TraceSpan{
  306. {
  307. Kind: "RPC_CLIENT",
  308. Labels: map[string]string{
  309. "trace.cloud.google.com/http/host": "example.com",
  310. "trace.cloud.google.com/http/method": "GET",
  311. "trace.cloud.google.com/http/status_code": "200",
  312. "trace.cloud.google.com/http/url": "http://example.com/bar",
  313. },
  314. Name: "example.com/bar",
  315. },
  316. {
  317. Kind: "RPC_CLIENT",
  318. Labels: map[string]string{
  319. "trace.cloud.google.com/http/host": "www.googleapis.com",
  320. "trace.cloud.google.com/http/method": "GET",
  321. "trace.cloud.google.com/http/status_code": "200",
  322. "trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
  323. },
  324. Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
  325. },
  326. {
  327. Kind: "RPC_CLIENT",
  328. Labels: map[string]string{
  329. "trace.cloud.google.com/http/host": "www.googleapis.com",
  330. "trace.cloud.google.com/http/method": "GET",
  331. "trace.cloud.google.com/http/status_code": "200",
  332. "trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
  333. },
  334. Name: "www.googleapis.com/storage/v1/b/testbucket/o",
  335. },
  336. &api.TraceSpan{
  337. Kind: "RPC_CLIENT",
  338. Labels: nil,
  339. Name: "/google.datastore.v1.Datastore/Lookup",
  340. },
  341. &api.TraceSpan{
  342. Kind: "RPC_CLIENT",
  343. Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
  344. Name: "/google.datastore.v1.Datastore/Lookup",
  345. },
  346. {
  347. Kind: "SPAN_KIND_UNSPECIFIED",
  348. Labels: map[string]string{},
  349. Name: "/foo",
  350. },
  351. },
  352. TraceId: traceID,
  353. },
  354. },
  355. }
  356. body, err := ioutil.ReadAll(uploaded.Body)
  357. if err != nil {
  358. t.Fatal(err)
  359. }
  360. var patch api.Traces
  361. err = json.Unmarshal(body, &patch)
  362. if err != nil {
  363. t.Fatal(err)
  364. }
  365. checkTraces(t, patch, expected)
  366. n := len(patch.Traces[0].Spans)
  367. rootSpan := patch.Traces[0].Spans[n-1]
  368. for i, s := range patch.Traces[0].Spans {
  369. if a, b := s.StartTime, s.EndTime; a > b {
  370. t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
  371. }
  372. if a, b := rootSpan.StartTime, s.StartTime; a > b {
  373. t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
  374. }
  375. if a, b := s.EndTime, rootSpan.EndTime; a > b {
  376. t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
  377. }
  378. if i > 1 && i < n-1 {
  379. if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
  380. t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
  381. }
  382. }
  383. }
  384. if x := rootSpan.ParentSpanId; x != 0 {
  385. t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 0)
  386. }
  387. for i, s := range patch.Traces[0].Spans {
  388. if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
  389. t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
  390. }
  391. }
  392. for i, s := range patch.Traces[0].Spans {
  393. s.EndTime = ""
  394. labels := &expected.Traces[0].Spans[i].Labels
  395. for key, value := range *labels {
  396. if v, ok := s.Labels[key]; !ok {
  397. t.Errorf("Span %d is missing Label %q:%q", i, key, value)
  398. } else if key == "trace.cloud.google.com/http/url" {
  399. if !strings.HasPrefix(v, value) {
  400. t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
  401. }
  402. } else if v != value {
  403. t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
  404. }
  405. }
  406. for key := range s.Labels {
  407. if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
  408. t.Errorf("Span %d: unexpected label %q", i, key)
  409. }
  410. }
  411. *labels = nil
  412. s.Labels = nil
  413. s.ParentSpanId = 0
  414. if s.SpanId == 0 {
  415. t.Errorf("Incorrect SpanId: got 0 want nonzero")
  416. }
  417. s.SpanId = 0
  418. s.StartTime = ""
  419. }
  420. if !testutil.Equal(patch, expected) {
  421. got, _ := json.Marshal(patch)
  422. want, _ := json.Marshal(expected)
  423. t.Errorf("PatchTraces request: got %s want %s", got, want)
  424. }
  425. }
  426. func testTrace(t *testing.T, synchronous bool, fromRequest bool) {
  427. t.Skip("flaky")
  428. const header = `0123456789ABCDEF0123456789ABCDEF/42;o=3`
  429. rt := newFakeRoundTripper()
  430. traceClient := newTestClient(rt)
  431. span := traceClient.SpanFromHeader("/foo", header)
  432. headerOrReqLabels := map[string]string{}
  433. headerOrReqName := "/foo"
  434. if fromRequest {
  435. req, err := http.NewRequest("GET", "http://example.com/foo", nil)
  436. if err != nil {
  437. t.Fatal(err)
  438. }
  439. req.Header.Set("X-Cloud-Trace-Context", header)
  440. span = traceClient.SpanFromRequest(req)
  441. headerOrReqLabels = map[string]string{
  442. "trace.cloud.google.com/http/host": "example.com",
  443. "trace.cloud.google.com/http/method": "GET",
  444. "trace.cloud.google.com/http/url": "http://example.com/foo",
  445. }
  446. headerOrReqName = "example.com/foo"
  447. }
  448. uploaded := makeRequests(t, span, rt, synchronous, true)
  449. if uploaded == nil {
  450. t.Fatalf("No trace uploaded, expected one.")
  451. }
  452. expected := api.Traces{
  453. Traces: []*api.Trace{
  454. {
  455. ProjectId: testProjectID,
  456. Spans: []*api.TraceSpan{
  457. {
  458. Kind: "RPC_CLIENT",
  459. Labels: map[string]string{
  460. "trace.cloud.google.com/http/host": "example.com",
  461. "trace.cloud.google.com/http/method": "GET",
  462. "trace.cloud.google.com/http/status_code": "200",
  463. "trace.cloud.google.com/http/url": "http://example.com/bar",
  464. },
  465. Name: "example.com/bar",
  466. },
  467. {
  468. Kind: "RPC_CLIENT",
  469. Labels: map[string]string{
  470. "trace.cloud.google.com/http/host": "www.googleapis.com",
  471. "trace.cloud.google.com/http/method": "GET",
  472. "trace.cloud.google.com/http/status_code": "200",
  473. "trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones",
  474. },
  475. Name: "www.googleapis.com/compute/v1/projects/testproject/zones",
  476. },
  477. {
  478. Kind: "RPC_CLIENT",
  479. Labels: map[string]string{
  480. "trace.cloud.google.com/http/host": "www.googleapis.com",
  481. "trace.cloud.google.com/http/method": "GET",
  482. "trace.cloud.google.com/http/status_code": "200",
  483. "trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o",
  484. },
  485. Name: "www.googleapis.com/storage/v1/b/testbucket/o",
  486. },
  487. &api.TraceSpan{
  488. Kind: "RPC_CLIENT",
  489. Labels: nil,
  490. Name: "/google.datastore.v1.Datastore/Lookup",
  491. },
  492. &api.TraceSpan{
  493. Kind: "RPC_CLIENT",
  494. Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"},
  495. Name: "/google.datastore.v1.Datastore/Lookup",
  496. },
  497. {
  498. Kind: "RPC_SERVER",
  499. Labels: headerOrReqLabels,
  500. Name: headerOrReqName,
  501. },
  502. },
  503. TraceId: "0123456789ABCDEF0123456789ABCDEF",
  504. },
  505. },
  506. }
  507. body, err := ioutil.ReadAll(uploaded.Body)
  508. if err != nil {
  509. t.Fatal(err)
  510. }
  511. var patch api.Traces
  512. err = json.Unmarshal(body, &patch)
  513. if err != nil {
  514. t.Fatal(err)
  515. }
  516. checkTraces(t, patch, expected)
  517. n := len(patch.Traces[0].Spans)
  518. rootSpan := patch.Traces[0].Spans[n-1]
  519. for i, s := range patch.Traces[0].Spans {
  520. if a, b := s.StartTime, s.EndTime; a > b {
  521. t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b)
  522. }
  523. if a, b := rootSpan.StartTime, s.StartTime; a > b {
  524. t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b)
  525. }
  526. if a, b := s.EndTime, rootSpan.EndTime; a > b {
  527. t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b)
  528. }
  529. if i > 1 && i < n-1 {
  530. if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b {
  531. t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b)
  532. }
  533. }
  534. }
  535. if x := rootSpan.ParentSpanId; x != 42 {
  536. t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 42)
  537. }
  538. for i, s := range patch.Traces[0].Spans {
  539. if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y {
  540. t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x)
  541. }
  542. }
  543. for i, s := range patch.Traces[0].Spans {
  544. s.EndTime = ""
  545. labels := &expected.Traces[0].Spans[i].Labels
  546. for key, value := range *labels {
  547. if v, ok := s.Labels[key]; !ok {
  548. t.Errorf("Span %d is missing Label %q:%q", i, key, value)
  549. } else if key == "trace.cloud.google.com/http/url" {
  550. if !strings.HasPrefix(v, value) {
  551. t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value)
  552. }
  553. } else if v != value {
  554. t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value)
  555. }
  556. }
  557. for key := range s.Labels {
  558. if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok {
  559. t.Errorf("Span %d: unexpected label %q", i, key)
  560. }
  561. }
  562. *labels = nil
  563. s.Labels = nil
  564. s.ParentSpanId = 0
  565. if s.SpanId == 0 {
  566. t.Errorf("Incorrect SpanId: got 0 want nonzero")
  567. }
  568. s.SpanId = 0
  569. s.StartTime = ""
  570. }
  571. if !testutil.Equal(patch, expected) {
  572. got, _ := json.Marshal(patch)
  573. want, _ := json.Marshal(expected)
  574. t.Errorf("PatchTraces request: got %s \n\n want %s", got, want)
  575. }
  576. }
  577. func TestNoTrace(t *testing.T) {
  578. testNoTrace(t, false, true)
  579. }
  580. func TestNoTraceWithWait(t *testing.T) {
  581. testNoTrace(t, true, true)
  582. }
  583. func TestNoTraceFromHeader(t *testing.T) {
  584. testNoTrace(t, false, false)
  585. }
  586. func TestNoTraceFromHeaderWithWait(t *testing.T) {
  587. testNoTrace(t, true, false)
  588. }
  589. func testNoTrace(t *testing.T, synchronous bool, fromRequest bool) {
  590. for _, header := range []string{
  591. `0123456789ABCDEF0123456789ABCDEF/42;o=2`,
  592. `0123456789ABCDEF0123456789ABCDEF/42;o=0`,
  593. `0123456789ABCDEF0123456789ABCDEF/42`,
  594. `0123456789ABCDEF0123456789ABCDEF`,
  595. ``,
  596. } {
  597. rt := newFakeRoundTripper()
  598. traceClient := newTestClient(rt)
  599. var span *Span
  600. if fromRequest {
  601. req, err := http.NewRequest("GET", "http://example.com/foo", nil)
  602. if header != "" {
  603. req.Header.Set("X-Cloud-Trace-Context", header)
  604. }
  605. if err != nil {
  606. t.Fatal(err)
  607. }
  608. span = traceClient.SpanFromRequest(req)
  609. } else {
  610. span = traceClient.SpanFromHeader("/foo", header)
  611. }
  612. uploaded := makeRequests(t, span, rt, synchronous, false)
  613. if uploaded != nil {
  614. t.Errorf("Got a trace, expected none.")
  615. }
  616. }
  617. }
  618. func TestSample(t *testing.T) {
  619. // A deterministic test of the sampler logic.
  620. type testCase struct {
  621. rate float64
  622. maxqps float64
  623. want int
  624. }
  625. const delta = 25 * time.Millisecond
  626. for _, test := range []testCase{
  627. // qps won't matter, so we will sample half of the 79 calls
  628. {0.50, 100, 40},
  629. // with 1 qps and a burst of 2, we will sample twice in second #1, once in the partial second #2
  630. {0.50, 1, 3},
  631. } {
  632. sp, err := NewLimitedSampler(test.rate, test.maxqps)
  633. if err != nil {
  634. t.Fatal(err)
  635. }
  636. s := sp.(*sampler)
  637. sampled := 0
  638. tm := time.Now()
  639. for i := 0; i < 80; i++ {
  640. if s.sample(Parameters{}, tm, float64(i%2)).Sample {
  641. sampled++
  642. }
  643. tm = tm.Add(delta)
  644. }
  645. if sampled != test.want {
  646. t.Errorf("rate=%f, maxqps=%f: got %d samples, want %d", test.rate, test.maxqps, sampled, test.want)
  647. }
  648. }
  649. }
  650. func TestSampling(t *testing.T) {
  651. t.Parallel()
  652. // This scope tests sampling in a larger context, with real time and randomness.
  653. wg := sync.WaitGroup{}
  654. type testCase struct {
  655. rate float64
  656. maxqps float64
  657. expectedRange [2]int
  658. }
  659. for _, test := range []testCase{
  660. {0, 5, [2]int{0, 0}},
  661. {5, 0, [2]int{0, 0}},
  662. {0.50, 100, [2]int{20, 60}},
  663. {0.50, 1, [2]int{3, 4}}, // Windows, with its less precise clock, sometimes gives 4.
  664. } {
  665. wg.Add(1)
  666. go func(test testCase) {
  667. rt := newFakeRoundTripper()
  668. traceClient := newTestClient(rt)
  669. traceClient.bundler.BundleByteLimit = 1
  670. p, err := NewLimitedSampler(test.rate, test.maxqps)
  671. if err != nil {
  672. t.Fatalf("NewLimitedSampler: %v", err)
  673. }
  674. traceClient.SetSamplingPolicy(p)
  675. ticker := time.NewTicker(25 * time.Millisecond)
  676. sampled := 0
  677. for i := 0; i < 79; i++ {
  678. req, err := http.NewRequest("GET", "http://example.com/foo", nil)
  679. if err != nil {
  680. t.Fatal(err)
  681. }
  682. span := traceClient.SpanFromRequest(req)
  683. span.Finish()
  684. select {
  685. case <-rt.reqc:
  686. <-ticker.C
  687. sampled++
  688. case <-ticker.C:
  689. }
  690. }
  691. ticker.Stop()
  692. if test.expectedRange[0] > sampled || sampled > test.expectedRange[1] {
  693. t.Errorf("rate=%f, maxqps=%f: got %d samples want ∈ %v", test.rate, test.maxqps, sampled, test.expectedRange)
  694. }
  695. wg.Done()
  696. }(test)
  697. }
  698. wg.Wait()
  699. }
  700. func TestBundling(t *testing.T) {
  701. t.Parallel()
  702. rt := newFakeRoundTripper()
  703. traceClient := newTestClient(rt)
  704. traceClient.bundler.DelayThreshold = time.Second / 2
  705. traceClient.bundler.BundleCountThreshold = 10
  706. p, err := NewLimitedSampler(1, 99) // sample every request.
  707. if err != nil {
  708. t.Fatalf("NewLimitedSampler: %v", err)
  709. }
  710. traceClient.SetSamplingPolicy(p)
  711. for i := 0; i < 35; i++ {
  712. go func() {
  713. req, err := http.NewRequest("GET", "http://example.com/foo", nil)
  714. if err != nil {
  715. t.Fatal(err)
  716. }
  717. span := traceClient.SpanFromRequest(req)
  718. span.Finish()
  719. }()
  720. }
  721. // Read the first three bundles.
  722. <-rt.reqc
  723. <-rt.reqc
  724. <-rt.reqc
  725. // Test that the fourth bundle isn't sent early.
  726. select {
  727. case <-rt.reqc:
  728. t.Errorf("bundle sent too early")
  729. case <-time.After(time.Second / 4):
  730. <-rt.reqc
  731. }
  732. // Test that there aren't extra bundles.
  733. select {
  734. case <-rt.reqc:
  735. t.Errorf("too many bundles sent")
  736. case <-time.After(time.Second):
  737. }
  738. }
  739. func TestWeights(t *testing.T) {
  740. const (
  741. expectedNumTraced = 10100
  742. numTracedEpsilon = 100
  743. expectedTotalWeight = 50000
  744. totalWeightEpsilon = 5000
  745. )
  746. rng := rand.New(rand.NewSource(1))
  747. const delta = 2 * time.Millisecond
  748. for _, headerRate := range []float64{0.0, 0.5, 1.0} {
  749. // Simulate 10 seconds of requests arriving at 500qps.
  750. //
  751. // The sampling policy tries to sample 25% of them, but has a qps limit of
  752. // 100, so it will not be able to. The returned weight should be higher
  753. // for some sampled requests to compensate.
  754. //
  755. // headerRate is the fraction of incoming requests that have a trace header
  756. // set. The qps limit should not be exceeded, even if headerRate is high.
  757. sp, err := NewLimitedSampler(0.25, 100)
  758. if err != nil {
  759. t.Fatal(err)
  760. }
  761. s := sp.(*sampler)
  762. tm := time.Now()
  763. totalWeight := 0.0
  764. numTraced := 0
  765. seenLargeWeight := false
  766. for i := 0; i < 50000; i++ {
  767. d := s.sample(Parameters{HasTraceHeader: rng.Float64() < headerRate}, tm, rng.Float64())
  768. if d.Trace {
  769. numTraced++
  770. }
  771. if d.Sample {
  772. totalWeight += d.Weight
  773. if x := int(d.Weight) / 4; x <= 0 || x >= 100 || d.Weight != float64(x)*4.0 {
  774. t.Errorf("weight: got %f, want a small positive multiple of 4", d.Weight)
  775. }
  776. if d.Weight > 4 {
  777. seenLargeWeight = true
  778. }
  779. }
  780. tm = tm.Add(delta)
  781. }
  782. if !seenLargeWeight {
  783. t.Errorf("headerRate %f: never saw sample weight higher than 4.", headerRate)
  784. }
  785. if numTraced < expectedNumTraced-numTracedEpsilon || expectedNumTraced+numTracedEpsilon < numTraced {
  786. t.Errorf("headerRate %f: got %d traced requests, want ∈ [%d, %d]", headerRate, numTraced, expectedNumTraced-numTracedEpsilon, expectedNumTraced+numTracedEpsilon)
  787. }
  788. if totalWeight < expectedTotalWeight-totalWeightEpsilon || expectedTotalWeight+totalWeightEpsilon < totalWeight {
  789. t.Errorf("headerRate %f: got total weight %f want ∈ [%d, %d]", headerRate, totalWeight, expectedTotalWeight-totalWeightEpsilon, expectedTotalWeight+totalWeightEpsilon)
  790. }
  791. }
  792. }
  793. type alwaysTrace struct{}
  794. func (a alwaysTrace) Sample(p Parameters) Decision {
  795. return Decision{Trace: true}
  796. }
  797. type neverTrace struct{}
  798. func (a neverTrace) Sample(p Parameters) Decision {
  799. return Decision{Trace: false}
  800. }
  801. func TestPropagation(t *testing.T) {
  802. rt := newFakeRoundTripper()
  803. traceClient := newTestClient(rt)
  804. for _, header := range []string{
  805. `0123456789ABCDEF0123456789ABCDEF/42;o=0`,
  806. `0123456789ABCDEF0123456789ABCDEF/42;o=1`,
  807. `0123456789ABCDEF0123456789ABCDEF/42;o=2`,
  808. `0123456789ABCDEF0123456789ABCDEF/42;o=3`,
  809. `0123456789ABCDEF0123456789ABCDEF/0;o=0`,
  810. `0123456789ABCDEF0123456789ABCDEF/0;o=1`,
  811. `0123456789ABCDEF0123456789ABCDEF/0;o=2`,
  812. `0123456789ABCDEF0123456789ABCDEF/0;o=3`,
  813. ``,
  814. } {
  815. for _, policy := range []SamplingPolicy{
  816. nil,
  817. alwaysTrace{},
  818. neverTrace{},
  819. } {
  820. traceClient.SetSamplingPolicy(policy)
  821. req, err := http.NewRequest("GET", "http://example.com/foo", nil)
  822. if err != nil {
  823. t.Fatal(err)
  824. }
  825. if header != "" {
  826. req.Header.Set("X-Cloud-Trace-Context", header)
  827. }
  828. span := traceClient.SpanFromRequest(req)
  829. req2, err := http.NewRequest("GET", "http://example.com/bar", nil)
  830. if err != nil {
  831. t.Fatal(err)
  832. }
  833. req3, err := http.NewRequest("GET", "http://example.com/baz", nil)
  834. if err != nil {
  835. t.Fatal(err)
  836. }
  837. span.NewRemoteChild(req2)
  838. span.NewRemoteChild(req3)
  839. var (
  840. t1, t2, t3 string
  841. s1, s2, s3 uint64
  842. o1, o2, o3 uint64
  843. )
  844. fmt.Sscanf(header, "%32s/%d;o=%d", &t1, &s1, &o1)
  845. fmt.Sscanf(req2.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t2, &s2, &o2)
  846. fmt.Sscanf(req3.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t3, &s3, &o3)
  847. if header == "" {
  848. if t2 != t3 {
  849. t.Errorf("expected the same trace ID in child requests, got %q %q", t2, t3)
  850. }
  851. } else {
  852. if t2 != t1 || t3 != t1 {
  853. t.Errorf("trace IDs should be passed to child requests")
  854. }
  855. }
  856. trace := policy == alwaysTrace{} || policy == nil && (o1&1) != 0
  857. if header == "" {
  858. if trace && (s2 == 0 || s3 == 0) {
  859. t.Errorf("got span IDs %d %d in child requests, want nonzero", s2, s3)
  860. }
  861. if trace && s2 == s3 {
  862. t.Errorf("got span IDs %d %d in child requests, should be different", s2, s3)
  863. }
  864. if !trace && (s2 != 0 || s3 != 0) {
  865. t.Errorf("got span IDs %d %d in child requests, want zero", s2, s3)
  866. }
  867. } else {
  868. if trace && (s2 == s1 || s3 == s1 || s2 == s3) {
  869. t.Errorf("parent span IDs in input and outputs should be all different, got %d %d %d", s1, s2, s3)
  870. }
  871. if !trace && (s2 != s1 || s3 != s1) {
  872. t.Errorf("parent span ID in input, %d, should have been equal to parent span IDs in output: %d %d", s1, s2, s3)
  873. }
  874. }
  875. expectTraceOption := policy == alwaysTrace{} || (o1&1) != 0
  876. if expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) {
  877. t.Errorf("tracing flag in child requests should be %t, got options %d %d", expectTraceOption, o2, o3)
  878. }
  879. }
  880. }
  881. }
  882. func BenchmarkSpanFromHeader(b *testing.B) {
  883. const header = `0123456789ABCDEF0123456789ABCDEF/42;o=0`
  884. const name = "/foo"
  885. rt := newFakeRoundTripper()
  886. traceClient := newTestClient(rt)
  887. for n := 0; n < b.N; n++ {
  888. traceClient.SpanFromHeader(name, header)
  889. }
  890. }
  891. func checkTraces(t *testing.T, patch, expected api.Traces) {
  892. if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) {
  893. diff := testutil.Diff(patch.Traces, expected.Traces)
  894. t.Logf("diff:\n%s", diff)
  895. got, _ := json.Marshal(patch)
  896. want, _ := json.Marshal(expected)
  897. t.Fatalf("PatchTraces request: got %s want %s", got, want)
  898. }
  899. }