blob: ecd26123f5072eb9d859851c62c8aa11218f613f [file] [log] [blame]
// 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/telemetry/event"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/export/metric"
"golang.org/x/tools/internal/telemetry/export/ocagent"
)
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 = event.NewStringKey("db", "the database name")
keyMethod = event.NewStringKey("method", "a metric grouping key")
keyRoute = event.NewStringKey("route", "another metric grouping key")
key1DB = event.NewStringKey("1_db", "A test string key")
key2aAge = event.NewFloat64Key("2a_age", "A test float64 key")
key2bTTL = event.NewFloat32Key("2b_ttl", "A test float32 key")
key2cExpiryMS = event.NewFloat64Key("2c_expiry_ms", "A test float64 key")
key3aRetry = event.NewBooleanKey("3a_retry", "A test boolean key")
key3bStale = event.NewBooleanKey("3b_stale", "Another test boolean key")
key4aMax = event.NewIntKey("4a_max", "A test int key")
key4bOpcode = event.NewInt8Key("4b_opcode", "A test int8 key")
key4cBase = event.NewInt16Key("4c_base", "A test int16 key")
key4eChecksum = event.NewInt32Key("4e_checksum", "A test int32 key")
key4fMode = event.NewInt64Key("4f_mode", "A test int64 key")
key5aMin = event.NewUIntKey("5a_min", "A test uint key")
key5bMix = event.NewUInt8Key("5b_mix", "A test uint8 key")
key5cPort = event.NewUInt16Key("5c_port", "A test uint16 key")
key5dMinHops = event.NewUInt32Key("5d_min_hops", "A test uint32 key")
key5eMaxHops = event.NewUInt64Key("5e_max_hops", "A test uint64 key")
recursiveCalls = event.NewInt64Key("recursive_calls", "Number of recursive calls")
bytesIn = event.NewInt64Key("bytes_in", "Number of bytes in") //, unit.Bytes)
latencyMs = event.NewFloat64Key("latency", "The latency in milliseconds") //, unit.Milliseconds)
metricLatency = metric.HistogramFloat64{
Name: "latency_ms",
Description: "The latency of calls in milliseconds",
Keys: []event.Key{keyMethod, keyRoute},
Buckets: []float64{0, 5, 10, 25, 50},
}
metricBytesIn = metric.HistogramInt64{
Name: "latency_ms",
Description: "The latency of calls in milliseconds",
Keys: []event.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: []event.Key{keyMethod, keyRoute},
}
)
type testExporter struct {
metrics metric.Exporter
ocagent *ocagent.Exporter
sent fakeSender
start time.Time
at time.Time
end time.Time
}
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)
exporter.start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z")
exporter.at, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z")
exporter.end, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z")
metricLatency.Record(&exporter.metrics, latencyMs)
metricBytesIn.Record(&exporter.metrics, bytesIn)
metricRecursiveCalls.SumInt64(&exporter.metrics, recursiveCalls)
event.SetExporter(exporter.processEvent)
return exporter
}
func (e *testExporter) processEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
switch {
case ev.IsStartSpan():
ev.At = e.start
case ev.IsEndSpan():
ev.At = e.end
default:
ev.At = e.at
}
ctx, ev = export.Tag(ctx, ev)
ctx, ev = export.ContextSpan(ctx, ev)
ctx, ev = e.metrics.ProcessEvent(ctx, ev)
ctx, ev = e.ocagent.ProcessEvent(ctx, ev)
if ev.IsStartSpan() {
span := export.GetSpan(ctx)
span.ID = export.SpanContext{}
}
return ctx, ev
}
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
}