github.com/v2fly/tools@v0.100.0/internal/event/export/ocagent/ocagent_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ocagent_test 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "net/http" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/v2fly/tools/internal/event" 19 "github.com/v2fly/tools/internal/event/core" 20 "github.com/v2fly/tools/internal/event/export" 21 "github.com/v2fly/tools/internal/event/export/metric" 22 "github.com/v2fly/tools/internal/event/export/ocagent" 23 "github.com/v2fly/tools/internal/event/keys" 24 "github.com/v2fly/tools/internal/event/label" 25 ) 26 27 const testNodeStr = `{ 28 "node":{ 29 "identifier":{ 30 "host_name":"tester", 31 "pid":1, 32 "start_timestamp":"1970-01-01T00:00:00Z" 33 }, 34 "library_info":{ 35 "language":4, 36 "exporter_version":"0.0.1", 37 "core_library_version":"x/tools" 38 }, 39 "service_info":{ 40 "name":"ocagent-tests" 41 } 42 },` 43 44 var ( 45 keyDB = keys.NewString("db", "the database name") 46 keyMethod = keys.NewString("method", "a metric grouping key") 47 keyRoute = keys.NewString("route", "another metric grouping key") 48 49 key1DB = keys.NewString("1_db", "A test string key") 50 51 key2aAge = keys.NewFloat64("2a_age", "A test float64 key") 52 key2bTTL = keys.NewFloat32("2b_ttl", "A test float32 key") 53 key2cExpiryMS = keys.NewFloat64("2c_expiry_ms", "A test float64 key") 54 55 key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key") 56 key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key") 57 58 key4aMax = keys.NewInt("4a_max", "A test int key") 59 key4bOpcode = keys.NewInt8("4b_opcode", "A test int8 key") 60 key4cBase = keys.NewInt16("4c_base", "A test int16 key") 61 key4eChecksum = keys.NewInt32("4e_checksum", "A test int32 key") 62 key4fMode = keys.NewInt64("4f_mode", "A test int64 key") 63 64 key5aMin = keys.NewUInt("5a_min", "A test uint key") 65 key5bMix = keys.NewUInt8("5b_mix", "A test uint8 key") 66 key5cPort = keys.NewUInt16("5c_port", "A test uint16 key") 67 key5dMinHops = keys.NewUInt32("5d_min_hops", "A test uint32 key") 68 key5eMaxHops = keys.NewUInt64("5e_max_hops", "A test uint64 key") 69 70 recursiveCalls = keys.NewInt64("recursive_calls", "Number of recursive calls") 71 bytesIn = keys.NewInt64("bytes_in", "Number of bytes in") //, unit.Bytes) 72 latencyMs = keys.NewFloat64("latency", "The latency in milliseconds") //, unit.Milliseconds) 73 74 metricLatency = metric.HistogramFloat64{ 75 Name: "latency_ms", 76 Description: "The latency of calls in milliseconds", 77 Keys: []label.Key{keyMethod, keyRoute}, 78 Buckets: []float64{0, 5, 10, 25, 50}, 79 } 80 81 metricBytesIn = metric.HistogramInt64{ 82 Name: "latency_ms", 83 Description: "The latency of calls in milliseconds", 84 Keys: []label.Key{keyMethod, keyRoute}, 85 Buckets: []int64{0, 10, 50, 100, 500, 1000, 2000}, 86 } 87 88 metricRecursiveCalls = metric.Scalar{ 89 Name: "latency_ms", 90 Description: "The latency of calls in milliseconds", 91 Keys: []label.Key{keyMethod, keyRoute}, 92 } 93 ) 94 95 type testExporter struct { 96 ocagent *ocagent.Exporter 97 sent fakeSender 98 } 99 100 func registerExporter() *testExporter { 101 exporter := &testExporter{} 102 cfg := ocagent.Config{ 103 Host: "tester", 104 Process: 1, 105 Service: "ocagent-tests", 106 Client: &http.Client{Transport: &exporter.sent}, 107 } 108 cfg.Start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z") 109 exporter.ocagent = ocagent.Connect(&cfg) 110 111 metrics := metric.Config{} 112 metricLatency.Record(&metrics, latencyMs) 113 metricBytesIn.Record(&metrics, bytesIn) 114 metricRecursiveCalls.SumInt64(&metrics, recursiveCalls) 115 116 e := exporter.ocagent.ProcessEvent 117 e = metrics.Exporter(e) 118 e = spanFixer(e) 119 e = export.Spans(e) 120 e = export.Labels(e) 121 e = timeFixer(e) 122 event.SetExporter(e) 123 return exporter 124 } 125 126 func timeFixer(output event.Exporter) event.Exporter { 127 start, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z") 128 at, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z") 129 end, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z") 130 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { 131 switch { 132 case event.IsStart(ev): 133 ev = core.CloneEvent(ev, start) 134 case event.IsEnd(ev): 135 ev = core.CloneEvent(ev, end) 136 default: 137 ev = core.CloneEvent(ev, at) 138 } 139 return output(ctx, ev, lm) 140 } 141 } 142 143 func spanFixer(output event.Exporter) event.Exporter { 144 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { 145 if event.IsStart(ev) { 146 span := export.GetSpan(ctx) 147 span.ID = export.SpanContext{} 148 } 149 return output(ctx, ev, lm) 150 } 151 } 152 153 func (e *testExporter) Output(route string) []byte { 154 e.ocagent.Flush() 155 return e.sent.get(route) 156 } 157 158 func checkJSON(t *testing.T, got, want []byte) { 159 // compare the compact form, to allow for formatting differences 160 g := &bytes.Buffer{} 161 if err := json.Compact(g, got); err != nil { 162 t.Fatal(err) 163 } 164 w := &bytes.Buffer{} 165 if err := json.Compact(w, want); err != nil { 166 t.Fatal(err) 167 } 168 if g.String() != w.String() { 169 t.Fatalf("Got:\n%s\nWant:\n%s", g, w) 170 } 171 } 172 173 type fakeSender struct { 174 mu sync.Mutex 175 data map[string][]byte 176 } 177 178 func (s *fakeSender) get(route string) []byte { 179 s.mu.Lock() 180 defer s.mu.Unlock() 181 data, found := s.data[route] 182 if found { 183 delete(s.data, route) 184 } 185 return data 186 } 187 188 func (s *fakeSender) RoundTrip(req *http.Request) (*http.Response, error) { 189 s.mu.Lock() 190 defer s.mu.Unlock() 191 if s.data == nil { 192 s.data = make(map[string][]byte) 193 } 194 data, err := ioutil.ReadAll(req.Body) 195 if err != nil { 196 return nil, err 197 } 198 path := req.URL.EscapedPath() 199 if _, found := s.data[path]; found { 200 return nil, fmt.Errorf("duplicate delivery to %v", path) 201 } 202 s.data[path] = data 203 return &http.Response{ 204 Status: "200 OK", 205 StatusCode: 200, 206 Proto: "HTTP/1.0", 207 ProtoMajor: 1, 208 ProtoMinor: 0, 209 }, nil 210 }