| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ocagent_test |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "sync" |
| "testing" |
| "time" |
| |
| "golang.org/x/tools/internal/event" |
| "golang.org/x/tools/internal/event/core" |
| "golang.org/x/tools/internal/event/export" |
| "golang.org/x/tools/internal/event/export/metric" |
| "golang.org/x/tools/internal/event/export/ocagent" |
| "golang.org/x/tools/internal/event/keys" |
| "golang.org/x/tools/internal/event/label" |
| ) |
| |
| const testNodeStr = `{ |
| "node":{ |
| "identifier":{ |
| "host_name":"tester", |
| "pid":1, |
| "start_timestamp":"1970-01-01T00:00:00Z" |
| }, |
| "library_info":{ |
| "language":4, |
| "exporter_version":"0.0.1", |
| "core_library_version":"x/tools" |
| }, |
| "service_info":{ |
| "name":"ocagent-tests" |
| } |
| },` |
| |
| var ( |
| keyDB = keys.NewString("db", "the database name") |
| keyMethod = keys.NewString("method", "a metric grouping key") |
| keyRoute = keys.NewString("route", "another metric grouping key") |
| |
| key1DB = keys.NewString("1_db", "A test string key") |
| |
| key2aAge = keys.NewFloat64("2a_age", "A test float64 key") |
| key2bTTL = keys.NewFloat32("2b_ttl", "A test float32 key") |
| key2cExpiryMS = keys.NewFloat64("2c_expiry_ms", "A test float64 key") |
| |
| key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key") |
| key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key") |
| |
| key4aMax = keys.NewInt("4a_max", "A test int key") |
| key4bOpcode = keys.NewInt8("4b_opcode", "A test int8 key") |
| key4cBase = keys.NewInt16("4c_base", "A test int16 key") |
| key4eChecksum = keys.NewInt32("4e_checksum", "A test int32 key") |
| key4fMode = keys.NewInt64("4f_mode", "A test int64 key") |
| |
| key5aMin = keys.NewUInt("5a_min", "A test uint key") |
| key5bMix = keys.NewUInt8("5b_mix", "A test uint8 key") |
| key5cPort = keys.NewUInt16("5c_port", "A test uint16 key") |
| key5dMinHops = keys.NewUInt32("5d_min_hops", "A test uint32 key") |
| key5eMaxHops = keys.NewUInt64("5e_max_hops", "A test uint64 key") |
| |
| recursiveCalls = keys.NewInt64("recursive_calls", "Number of recursive calls") |
| bytesIn = keys.NewInt64("bytes_in", "Number of bytes in") //, unit.Bytes) |
| latencyMs = keys.NewFloat64("latency", "The latency in milliseconds") //, unit.Milliseconds) |
| |
| metricLatency = metric.HistogramFloat64{ |
| Name: "latency_ms", |
| Description: "The latency of calls in milliseconds", |
| Keys: []label.Key{keyMethod, keyRoute}, |
| Buckets: []float64{0, 5, 10, 25, 50}, |
| } |
| |
| metricBytesIn = metric.HistogramInt64{ |
| Name: "latency_ms", |
| Description: "The latency of calls in milliseconds", |
| Keys: []label.Key{keyMethod, keyRoute}, |
| Buckets: []int64{0, 10, 50, 100, 500, 1000, 2000}, |
| } |
| |
| metricRecursiveCalls = metric.Scalar{ |
| Name: "latency_ms", |
| Description: "The latency of calls in milliseconds", |
| Keys: []label.Key{keyMethod, keyRoute}, |
| } |
| ) |
| |
| type testExporter struct { |
| ocagent *ocagent.Exporter |
| sent fakeSender |
| } |
| |
| func registerExporter() *testExporter { |
| exporter := &testExporter{} |
| cfg := ocagent.Config{ |
| Host: "tester", |
| Process: 1, |
| Service: "ocagent-tests", |
| Client: &http.Client{Transport: &exporter.sent}, |
| } |
| cfg.Start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z") |
| exporter.ocagent = ocagent.Connect(&cfg) |
| |
| metrics := metric.Config{} |
| metricLatency.Record(&metrics, latencyMs) |
| metricBytesIn.Record(&metrics, bytesIn) |
| metricRecursiveCalls.SumInt64(&metrics, recursiveCalls) |
| |
| e := exporter.ocagent.ProcessEvent |
| e = metrics.Exporter(e) |
| e = spanFixer(e) |
| e = export.Spans(e) |
| e = export.Labels(e) |
| e = timeFixer(e) |
| event.SetExporter(e) |
| return exporter |
| } |
| |
| func timeFixer(output event.Exporter) event.Exporter { |
| start, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z") |
| at, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z") |
| end, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z") |
| return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { |
| switch { |
| case event.IsStart(ev): |
| ev = core.CloneEvent(ev, start) |
| case event.IsEnd(ev): |
| ev = core.CloneEvent(ev, end) |
| default: |
| ev = core.CloneEvent(ev, at) |
| } |
| return output(ctx, ev, lm) |
| } |
| } |
| |
| func spanFixer(output event.Exporter) event.Exporter { |
| return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { |
| if event.IsStart(ev) { |
| span := export.GetSpan(ctx) |
| span.ID = export.SpanContext{} |
| } |
| return output(ctx, ev, lm) |
| } |
| } |
| |
| func (e *testExporter) Output(route string) []byte { |
| e.ocagent.Flush() |
| return e.sent.get(route) |
| } |
| |
| func checkJSON(t *testing.T, got, want []byte) { |
| // compare the compact form, to allow for formatting differences |
| g := &bytes.Buffer{} |
| if err := json.Compact(g, got); err != nil { |
| t.Fatal(err) |
| } |
| w := &bytes.Buffer{} |
| if err := json.Compact(w, want); err != nil { |
| t.Fatal(err) |
| } |
| if g.String() != w.String() { |
| t.Fatalf("Got:\n%s\nWant:\n%s", g, w) |
| } |
| } |
| |
| type fakeSender struct { |
| mu sync.Mutex |
| data map[string][]byte |
| } |
| |
| func (s *fakeSender) get(route string) []byte { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| data, found := s.data[route] |
| if found { |
| delete(s.data, route) |
| } |
| return data |
| } |
| |
| func (s *fakeSender) RoundTrip(req *http.Request) (*http.Response, error) { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| if s.data == nil { |
| s.data = make(map[string][]byte) |
| } |
| data, err := ioutil.ReadAll(req.Body) |
| if err != nil { |
| return nil, err |
| } |
| path := req.URL.EscapedPath() |
| if _, found := s.data[path]; found { |
| return nil, fmt.Errorf("duplicate delivery to %v", path) |
| } |
| s.data[path] = data |
| return &http.Response{ |
| Status: "200 OK", |
| StatusCode: 200, |
| Proto: "HTTP/1.0", |
| ProtoMajor: 1, |
| ProtoMinor: 0, |
| }, nil |
| } |