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  }